@@ -48,60 +48,51 @@ AampUnderflowMonitor::~AampUnderflowMonitor() {
4848 Stop ();
4949}
5050
51- void AampUnderflowMonitor::Start () {
52- // Use unique_lock to allow unlock around join operations
53- std::unique_lock <std::mutex> lock (mMutex );
51+ void AampUnderflowMonitor::Start ()
52+ {
53+ std::lock_guard <std::mutex> lock (mStartStopMutex );
5454
55- // If a previous thread exists but is not running, join it before starting a new
56- if (mThread .joinable ())
55+ if (mUnderflowMonitorThread .joinable ())
5756 {
5857 if (mRunning .load ())
5958 {
6059 AAMPLOG_INFO (" AampUnderflowMonitor already running; skipping start" );
6160 return ;
6261 }
63- lock.unlock ();
64- mThread .join ();
62+ mUnderflowMonitorThread .join ();
6563 AAMPLOG_INFO (" AampUnderflowMonitor previous thread joined before restart" );
66- lock.lock ();
6764 }
6865
6966 try
7067 {
68+ {
69+ std::lock_guard<std::mutex> sleepLock (mSleepMutex );
70+ mWakeupSignalled = false ;
71+ }
7172 mRunning .store (true );
72- mThread = std::thread (&AampUnderflowMonitor::Run, this );
73- AAMPLOG_INFO (" AampUnderflowMonitor thread created [%zx]" , GetPrintableThreadID (mThread ));
73+ mUnderflowMonitorThread = std::thread (&AampUnderflowMonitor::Run, this );
74+ AAMPLOG_INFO (" AampUnderflowMonitor thread created [%zx]" , GetPrintableThreadID (mUnderflowMonitorThread ));
7475 }
7576 catch (const std::exception& e)
7677 {
7778 mRunning .store (false );
7879 AAMPLOG_WARN (" Failed to create AampUnderflowMonitor thread : %s" , e.what ());
79- return ;
80- }
81-
82- // If the thread exited immediately (e.g., due to player state), ensure we join it to avoid a dangling joinable thread
83- if (!mRunning .load () && mThread .joinable ())
84- {
85- lock.unlock ();
86- mThread .join ();
87- AAMPLOG_WARN (" AampUnderflowMonitor thread exited immediately after start; joined" );
8880 }
8981}
9082
9183void AampUnderflowMonitor::Stop ()
9284{
93- // Use unique_lock so we can release the mutex before joining
94- std::unique_lock<std::mutex> lock (mMutex );
95- // Signal thread to stop
85+ std::lock_guard<std::mutex> lock (mStartStopMutex );
9686 mRunning .store (false );
97-
98- // If a thread is joinable, release the lock before joining to avoid deadlock
99- if (mThread .joinable ())
10087 {
101- lock.unlock ();
102- mThread .join ();
88+ std::lock_guard<std::mutex> sleepLock (mSleepMutex );
89+ mWakeupSignalled = true ;
90+ mSleepCv .notify_all ();
91+ }
92+ if (mUnderflowMonitorThread .joinable ())
93+ {
94+ mUnderflowMonitorThread .join ();
10395 AAMPLOG_INFO (" AampUnderflowMonitor thread joined" );
104- lock.lock ();
10596 }
10697}
10798
@@ -116,136 +107,76 @@ void AampUnderflowMonitor::Run()
116107 const int kMediumBufferPollMs = mAamp ->mConfig ->GetConfigValue (eAAMPConfig_UnderflowMediumBufferPollMs);
117108 const int kHighBufferPollMs = mAamp ->mConfig ->GetConfigValue (eAAMPConfig_UnderflowHighBufferPollMs);
118109
119- // Wait until playback enters PLAYING state or underflow becomes active; exit if playback stops
120- while (mRunning .load ()) {
121- AAMPPlayerState state;
122- bool shouldBreak = false ;
123- bool underflowStatus = false ;
124-
125- {
126- std::lock_guard<std::mutex> lock (mMutex );
127- state = mAamp ->GetState ();
128- if (state == eSTATE_STOPPED || state == eSTATE_RELEASED || state == eSTATE_ERROR) {
129- mRunning .store (false );
130- return ;
131- }
132- underflowStatus = mAamp ->GetBufUnderFlowStatus ();
133- shouldBreak = (state == eSTATE_PLAYING || underflowStatus);
134- }
135-
136- if (shouldBreak) {
137- break ;
138- }
139- mAamp ->interruptibleMsSleep (100 );
140- }
110+ while (mRunning .load ())
111+ {
112+ AAMPPlayerState playerState = mAamp ->GetState ();
113+ bool underflowActive = mAamp ->GetBufUnderFlowStatus ();
114+ double bufferedTimeSec = 0.0 ;
141115
142- while (mRunning .load ()) {
143- // Check player state and underflow status under mutex
144- bool underflowActive;
145- AAMPPlayerState playerState;
146- float currentRate;
147- double bufferedTimeSec;
148-
116+ const bool isPlayingOrUnderflow = (playerState == eSTATE_PLAYING) || underflowActive;
117+ if (isPlayingOrUnderflow)
149118 {
150- std::lock_guard<std::mutex> lock (mMutex );
151-
152- underflowActive = mAamp ->GetBufUnderFlowStatus ();
153- playerState = mAamp ->GetState ();
154- // Exit when playback transitions to stopped/released/error/idle
155- if (playerState == eSTATE_STOPPED || playerState == eSTATE_RELEASED || playerState == eSTATE_ERROR || playerState == eSTATE_IDLE) {
156- break ;
157- }
158-
159- // Skip buffer-based underflow checks during trickplay or seeking
160- currentRate = mAamp ->rate ;
161-
162- // Query buffered duration once and reuse for detection and sleep cadence
163119 bufferedTimeSec = mStream ->GetBufferedVideoDurationSec ();
164120 if (bufferedTimeSec < 0.0 ) bufferedTimeSec = 0.0 ;
165- }
166-
167- const bool inPlayOrUnderflow = (playerState == eSTATE_PLAYING) || underflowActive;
168- const bool isTrickplay = (currentRate != AAMP_NORMAL_PLAY_RATE && currentRate != AAMP_SLOWMOTION_RATE && currentRate != AAMP_RATE_PAUSE);
169- const bool isSeekingState = (playerState == eSTATE_SEEKING);
170-
171- if (inPlayOrUnderflow) {
172- // Video underflow detection/resume (query under mutex)
173- bool trackDownloadsEnabled;
174- bool sinkCacheEmpty;
175-
176- {
177- std::lock_guard<std::mutex> lock (mMutex );
178- trackDownloadsEnabled = mAamp ->TrackDownloadsAreEnabled (eMEDIATYPE_VIDEO);
179- sinkCacheEmpty = mAamp ->IsSinkCacheEmpty (eMEDIATYPE_VIDEO);
180- }
181121
182- // Only evaluate buffer threshold when not in trickplay/seeking; still honor sink cache emptiness
122+ const float currentRate = mAamp ->rate ;
123+ const bool isTrickplay = (currentRate != AAMP_NORMAL_PLAY_RATE && currentRate != AAMP_SLOWMOTION_RATE && currentRate != AAMP_RATE_PAUSE);
124+ const bool isSeekingState = (playerState == eSTATE_SEEKING);
125+
126+ const bool sinkCacheEmpty = mAamp ->IsSinkCacheEmpty (eMEDIATYPE_VIDEO);
183127 const bool allowBufferCheck = (!isTrickplay && !isSeekingState);
184- if (((allowBufferCheck && bufferedTimeSec <= kUnderflowDetectThresholdSec && trackDownloadsEnabled)) || sinkCacheEmpty)
128+
129+ // Detection: only when not in trickplay/seeking
130+ if (allowBufferCheck && (bufferedTimeSec <= kUnderflowDetectThresholdSec || sinkCacheEmpty))
185131 {
186132 if (!underflowActive)
187133 {
188- AAMPLOG_INFO (" [video] underflow detected. buffered=%.3f cacheEmpty=%d (rate=%.2f, trickplay=%d, seeking=%d)" , bufferedTimeSec, (int )sinkCacheEmpty, currentRate, (int )isTrickplay, (int )isSeekingState);
189-
190- std::lock_guard<std::mutex> lock (mMutex );
134+ AAMPLOG_INFO (" [video] underflow detected. buffered=%.3f cacheEmpty=%d (rate=%.2f)" , bufferedTimeSec, (int )sinkCacheEmpty, currentRate);
191135 mAamp ->SetBufferingState (true );
192- PlaybackErrorType errorType = eGST_ERROR_UNDERFLOW;
193- mAamp ->SendAnomalyEvent (ANOMALY_WARNING, " %s %s" , GetMediaTypeName (eMEDIATYPE_VIDEO), mAamp ->getStringForPlaybackError (errorType));
194- }
195- else
196- {
197- if (!trackDownloadsEnabled && sinkCacheEmpty)
198- {
199- AAMPLOG_WARN (" [video] downloads blocked with empty cache during underflow; resuming" );
200- std::lock_guard<std::mutex> lock (mMutex );
201- mAamp ->ResumeTrackDownloads (eMEDIATYPE_VIDEO);
202- }
203136 }
204137 }
205- else
138+ else if (!allowBufferCheck)
206139 {
207- if (!allowBufferCheck)
208- {
209- // Informational: buffer-based underflow checks suppressed during trickplay/seeking
210- AAMPLOG_TRACE (" [video] skipping buffer-based underflow check (rate=%.2f, trickplay=%d, seeking=%d). cacheEmpty=%d buffered=%.3f" ,
211- currentRate, (int )isTrickplay, (int )isSeekingState, (int )sinkCacheEmpty, bufferedTimeSec);
212- }
213-
214- bool pipelinePaused = false ;
215- {
216- std::lock_guard<std::mutex> lock (mMutex );
217- if (!mAamp ) return ;
218- pipelinePaused = mAamp ->mSinkPaused .load ();
219- }
220-
221- if (underflowActive && pipelinePaused)
140+ AAMPLOG_TRACE (" [video] skipping underflow detection (rate=%.2f, trickplay=%d, seeking=%d). buffered=%.3f" ,
141+ currentRate, (int )isTrickplay, (int )isSeekingState, bufferedTimeSec);
142+ }
143+
144+ // Resume: always evaluate when underflow is active so it can clear during trickplay/seeking
145+ if (underflowActive)
146+ {
147+ const bool pipelinePaused = mAamp ->mSinkPaused .load ();
148+ if (pipelinePaused)
222149 {
223150 if (bufferedTimeSec >= kUnderflowResumeThresholdSec && !sinkCacheEmpty)
224151 {
225152 AAMPLOG_INFO (" [video] underflow ended. buffered=%.3f cacheEmpty=%d" , bufferedTimeSec, (int )sinkCacheEmpty);
226- std::lock_guard<std::mutex> lock (mMutex );
227153 mAamp ->SetBufferingState (false );
228154 }
229155 else
230156 {
231157 AAMPLOG_INFO (" [video] waiting to end underflow. buffered=%.3f cacheEmpty=%d" , bufferedTimeSec, (int )sinkCacheEmpty);
232158 }
233159 }
234- else if (underflowActive && !trackDownloadsEnabled && sinkCacheEmpty)
235- {
236- AAMPLOG_WARN (" [video] underflow ongoing, downloads blocked and cache empty; resuming track downloads" );
237- mAamp ->ResumeTrackDownloads (eMEDIATYPE_VIDEO);
238- }
239160 }
240161 // Audio underflow is not handled currently as we are aligning with the existing behavior
241162 }
242163
243- // Choose sleep interval based on buffer level (branchless style)
244- const int sleepMs = (bufferedTimeSec < kLowBufferSec ) ? kLowBufferPollMs
245- : (bufferedTimeSec >= kHighBufferSec ) ? kHighBufferPollMs
246- : kMediumBufferPollMs ;
247- mAamp ->interruptibleMsSleep (sleepMs);
164+ const int sleepMs = (bufferedTimeSec < kLowBufferSec ) ? kLowBufferPollMs
165+ : (bufferedTimeSec >= kHighBufferSec ) ? kHighBufferPollMs
166+ : kMediumBufferPollMs ;
167+ WaitMs (sleepMs);
248168 }
249169 mRunning .store (false );
250170}
251171
172+ void AampUnderflowMonitor::WaitMs (int ms)
173+ {
174+ std::unique_lock<std::mutex> lk (mSleepMutex );
175+ mSleepCv .wait_for (lk,
176+ std::chrono::milliseconds (ms),
177+ [this ]() {
178+ return mWakeupSignalled || !mRunning .load ();
179+ });
180+ mWakeupSignalled = false ;
181+ }
182+
0 commit comments