@@ -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,75 @@ 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+
183126 const bool allowBufferCheck = (!isTrickplay && !isSeekingState);
184- if ((( allowBufferCheck && bufferedTimeSec <= kUnderflowDetectThresholdSec && trackDownloadsEnabled)) || sinkCacheEmpty )
127+ if (! allowBufferCheck)
185128 {
186- if (!underflowActive)
187- {
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 );
191- 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- }
203- }
129+ AAMPLOG_TRACE (" [video] skipping buffer-based underflow check (rate=%.2f, trickplay=%d, seeking=%d). buffered=%.3f" ,
130+ currentRate, (int )isTrickplay, (int )isSeekingState, bufferedTimeSec);
204131 }
205132 else
206133 {
207- if (!allowBufferCheck)
134+ const bool sinkCacheEmpty = mAamp ->IsSinkCacheEmpty (eMEDIATYPE_VIDEO);
135+ if (bufferedTimeSec <= kUnderflowDetectThresholdSec || sinkCacheEmpty)
208136 {
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)
222- {
223- if (bufferedTimeSec >= kUnderflowResumeThresholdSec && !sinkCacheEmpty)
224- {
225- AAMPLOG_INFO (" [video] underflow ended. buffered=%.3f cacheEmpty=%d" , bufferedTimeSec, (int )sinkCacheEmpty);
226- std::lock_guard<std::mutex> lock (mMutex );
227- mAamp ->SetBufferingState (false );
228- }
229- else
137+ if (!underflowActive)
230138 {
231- AAMPLOG_INFO (" [video] waiting to end underflow. buffered=%.3f cacheEmpty=%d" , bufferedTimeSec, (int )sinkCacheEmpty);
139+ AAMPLOG_INFO (" [video] underflow detected. buffered=%.3f cacheEmpty=%d (rate=%.2f)" , bufferedTimeSec, (int )sinkCacheEmpty, currentRate);
140+ mAamp ->SetBufferingState (true );
232141 }
233142 }
234- else if (underflowActive && !trackDownloadsEnabled && sinkCacheEmpty)
143+ else
235144 {
236- AAMPLOG_WARN (" [video] underflow ongoing, downloads blocked and cache empty; resuming track downloads" );
237- mAamp ->ResumeTrackDownloads (eMEDIATYPE_VIDEO);
145+ const bool pipelinePaused = mAamp ->mSinkPaused .load ();
146+ if (underflowActive && pipelinePaused)
147+ {
148+ if (bufferedTimeSec >= kUnderflowResumeThresholdSec && !sinkCacheEmpty)
149+ {
150+ AAMPLOG_INFO (" [video] underflow ended. buffered=%.3f cacheEmpty=%d" , bufferedTimeSec, (int )sinkCacheEmpty);
151+ mAamp ->SetBufferingState (false );
152+ }
153+ else
154+ {
155+ AAMPLOG_INFO (" [video] waiting to end underflow. buffered=%.3f cacheEmpty=%d" , bufferedTimeSec, (int )sinkCacheEmpty);
156+ }
157+ }
238158 }
159+ // Audio underflow is not handled currently as we are aligning with the existing behavior
239160 }
240- // Audio underflow is not handled currently as we are aligning with the existing behavior
241161 }
242162
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);
163+ const int sleepMs = (bufferedTimeSec < kLowBufferSec ) ? kLowBufferPollMs
164+ : (bufferedTimeSec >= kHighBufferSec ) ? kHighBufferPollMs
165+ : kMediumBufferPollMs ;
166+ WaitMs (sleepMs);
248167 }
249168 mRunning .store (false );
250169}
251170
171+ void AampUnderflowMonitor::WaitMs (int ms)
172+ {
173+ std::unique_lock<std::mutex> lk (mSleepMutex );
174+ mSleepCv .wait_for (lk,
175+ std::chrono::milliseconds (ms),
176+ [this ]() {
177+ return mWakeupSignalled || !mRunning .load ();
178+ });
179+ mWakeupSignalled = false ;
180+ }
181+
0 commit comments