3535#include < mach/mach_time.h>
3636
3737
38- // How long to wait before pausing/unpausing. This is so short sounds can play without awkwardly causing a short period of silence,
39- // and other audio can have short periods of silence without causing music to play and quickly pause again. Of course, it's a
40- // trade-off against how long the music will overlap the other audio before it gets paused and how long the music will stay paused
41- // after a sound that was only slightly longer than the pause delay.
42- static UInt64 const kPauseDelayNSec = 1500 * NSEC_PER_MSEC;
43- // The delay before unpausing the music player is proportional to how long we paused it for, bounded by these limits. This makes it
44- // a bit less annoying when a sound is just long enough to cause an auto-pause.
45- //
46- // I haven't spent much time experimenting with different values for these constants, so they could probably be improved a fair
47- // bit.
48- //
49- // TODO: Would it be worth listening for kAudioDeviceCustomPropertyDeviceIsRunningSomewhereOtherThanBGMApp so we can unpause
50- // immediately if we haven't been paused for long and the non-music-player client stops IO? That would usually indicate that
51- // it doesn't intend to start playing audio again soon. We'd also have to deal with music players that don't stop IO when
52- // they're paused.
53- static UInt64 const kMaxUnpauseDelayNSec = 3500 * NSEC_PER_MSEC;
54- static UInt64 const kMinUnpauseDelayNSec = kMaxUnpauseDelayNSec / 10 ;
5538// We multiply the time spent paused by this factor to calculate the delay before we consider unpausing.
5639static Float32 const kUnpauseDelayWeightingFactor = 0 .1f ;
5740
@@ -60,6 +43,7 @@ @implementation BGMAutoPauseMusic {
6043
6144 BGMAudioDeviceManager* audioDevices;
6245 BGMMusicPlayers* musicPlayers;
46+ BGMUserDefaults* userDefaults;
6347
6448 dispatch_queue_t listenerQueue;
6549 // Have to keep track of the listener block we add so we can remove it later.
@@ -76,10 +60,11 @@ @implementation BGMAutoPauseMusic {
7660 UInt64 wentAudible;
7761}
7862
79- - (id ) initWithAudioDevices : (BGMAudioDeviceManager*)inAudioDevices musicPlayers : (BGMMusicPlayers*)inMusicPlayers {
63+ - (id ) initWithAudioDevices : (BGMAudioDeviceManager*)inAudioDevices musicPlayers : (BGMMusicPlayers*)inMusicPlayers userDefaults : (BGMUserDefaults*) inUserDefaults {
8064 if ((self = [super init ])) {
8165 audioDevices = inAudioDevices;
8266 musicPlayers = inMusicPlayers;
67+ userDefaults = inUserDefaults;
8368
8469 enabled = NO ;
8570 wePaused = NO ;
@@ -135,6 +120,7 @@ - (void) initListenerBlock {
135120 } else if (audibleState == kBGMDeviceIsSilentExceptMusic ) {
136121 // If we pause the music player and then the user unpauses it before the other audio stops, we need to set
137122 // wePaused to false at some point before the other audio starts again so we know we should pause
123+ DebugMsg (" BGMAutoPauseMusic: Device is silent except music, resetting wePaused flag" );
138124 wePaused = NO ;
139125 }
140126 // TODO: Add a fourth audible state, something like "AudibleAndMusicPlaying", and check it here to
@@ -153,8 +139,23 @@ - (void) queuePauseBlock {
153139 wentAudible = now;
154140 UInt64 startedPauseDelay = now;
155141
142+ UInt64 pauseDelayMS = userDefaults.pauseDelayMS ;
143+
144+ // If pause delay is 0, pause immediately (no delay)
145+ if (pauseDelayMS == 0 ) {
146+ DebugMsg (" BGMAutoPauseMusic::queuePauseBlock: Pause delay is 0, pausing immediately" );
147+
148+ // Pause immediately if device is audible and we haven't already paused
149+ if (!wePaused && ([self deviceAudibleState ] == kBGMDeviceIsAudible )) {
150+ wePaused = ([musicPlayers.selectedMusicPlayer pause ] || wePaused);
151+ }
152+ return ;
153+ }
154+
155+ UInt64 pauseDelayNSec = pauseDelayMS * NSEC_PER_MSEC;
156+
156157 DebugMsg (" BGMAutoPauseMusic::queuePauseBlock: Dispatching pause block at %llu" , now);
157- dispatch_after (dispatch_time (DISPATCH_TIME_NOW, kPauseDelayNSec ),
158+ dispatch_after (dispatch_time (DISPATCH_TIME_NOW, pauseDelayNSec ),
158159 pauseUnpauseMusicQueue,
159160 ^{
160161 BOOL stillAudible = ([self deviceAudibleState ] == kBGMDeviceIsAudible );
@@ -178,6 +179,27 @@ - (void) queueUnpauseBlock {
178179 wentSilent = now;
179180 UInt64 startedUnpauseDelay = now;
180181
182+ // Get user-configurable max delay
183+ UInt64 maxUnpauseDelayMS = userDefaults.maxUnpauseDelayMS ;
184+
185+ // If max unpause delay is 0, unpause immediately (no delay)
186+ if (maxUnpauseDelayMS == 0 ) {
187+ DebugMsg (" BGMAutoPauseMusic::queueUnpauseBlock: Max unpause delay is 0, unpausing immediately" );
188+
189+ // Unpause immediately if we were the one who paused and device is still silent
190+ BGMDeviceAudibleState currentState = [self deviceAudibleState ];
191+ DebugMsg (" BGMAutoPauseMusic::queueUnpauseBlock: Immediate unpause - wePaused=%s, currentState=%s" ,
192+ wePaused ? " YES" : " NO" ,
193+ currentState == kBGMDeviceIsSilent ? " Silent" :
194+ (currentState == kBGMDeviceIsAudible ? " Audible" : " SilentExceptMusic" ));
195+
196+ if (wePaused && (currentState == kBGMDeviceIsSilent )) {
197+ wePaused = NO ;
198+ [musicPlayers.selectedMusicPlayer unpause ];
199+ }
200+ return ;
201+ }
202+
181203 // Unpause sooner if we've only been paused for a short time. This is so a notification sound causing an auto-pause is
182204 // less of an interruption.
183205 //
@@ -191,9 +213,11 @@ - (void) queueUnpauseBlock {
191213 mach_timebase_info (&info);
192214 unpauseDelayNsec = unpauseDelayNsec * info.numer / info.denom ;
193215
194- // Clamp.
195- unpauseDelayNsec = std::min (kMaxUnpauseDelayNSec , unpauseDelayNsec);
196- unpauseDelayNsec = std::max (kMinUnpauseDelayNSec , unpauseDelayNsec);
216+ // Clamp using user-configurable max delay and calculated min delay.
217+ UInt64 maxUnpauseDelayNSec = maxUnpauseDelayMS * NSEC_PER_MSEC;
218+ UInt64 minUnpauseDelayNSec = maxUnpauseDelayNSec / 10 ;
219+ unpauseDelayNsec = std::min (maxUnpauseDelayNSec, unpauseDelayNsec);
220+ unpauseDelayNsec = std::max (minUnpauseDelayNSec, unpauseDelayNsec);
197221
198222 DebugMsg (" BGMAutoPauseMusic::queueUnpauseBlock: Dispatched unpause block at %llu. unpauseDelayNsec=%llu" ,
199223 now,
@@ -202,17 +226,22 @@ - (void) queueUnpauseBlock {
202226 dispatch_after (dispatch_time (DISPATCH_TIME_NOW, unpauseDelayNsec),
203227 pauseUnpauseMusicQueue,
204228 ^{
205- BOOL stillSilent = ([self deviceAudibleState ] == kBGMDeviceIsSilent );
229+ BGMDeviceAudibleState currentState = [self deviceAudibleState ];
230+ BOOL stillSilent = (currentState == kBGMDeviceIsSilent );
231+ BOOL isLatestUnpause = (startedUnpauseDelay == wentSilent);
206232
207- DebugMsg (" BGMAutoPauseMusic::queueUnpauseBlock: Running unpause block dispatched at %llu.%s%s wentSilent=%llu" ,
233+ DebugMsg (" BGMAutoPauseMusic::queueUnpauseBlock: Running unpause block dispatched at %llu. wePaused=%s, isLatest=%s, currentState=%s, wentSilent=%llu" ,
208234 startedUnpauseDelay,
209- wePaused ? " " : " Not unpausing because we weren't the one who paused." ,
210- stillSilent ? " " : " Not unpausing because the device isn't silent." ,
235+ wePaused ? " YES" : " NO" ,
236+ isLatestUnpause ? " YES" : " NO" ,
237+ currentState == kBGMDeviceIsSilent ? " Silent" :
238+ (currentState == kBGMDeviceIsAudible ? " Audible" : " SilentExceptMusic" ),
211239 wentSilent);
212240
213241 // Unpause if we were the one who paused. Also check that this is the most recent unpause block and the
214242 // device is still silent, which means the audible state hasn't changed since this block was queued.
215- if (wePaused && (startedUnpauseDelay == wentSilent) && stillSilent) {
243+ if (wePaused && isLatestUnpause && stillSilent) {
244+ DebugMsg (" BGMAutoPauseMusic::queueUnpauseBlock: Unpausing music player" );
216245 wePaused = NO ;
217246 [musicPlayers.selectedMusicPlayer unpause ];
218247 }
0 commit comments