Recover the Silent Tune keep-alive after audio interruptions - #745
Open
bjorkert wants to merge 2 commits into
Open
Recover the Silent Tune keep-alive after audio interruptions#745bjorkert wants to merge 2 commits into
bjorkert wants to merge 2 commits into
Conversation
The silent audio session is the only background execution claim Silent Tune has, so when another app interrupts it the process is suspended within seconds and no app code runs again until iOS resumes it. Reactivation now happens inside a UIApplication background task assertion, which grants runtime independently of the audio claim, so the retry attempts actually run instead of being frozen by suspension. Attempts are spread over about 18 seconds, which fits inside both the assertion and a BGAppRefreshTask window, and playAudio confirms the player is really playing rather than assuming play() worked. The BGAppRefreshTask stays open until the restart resolves, so a failed first attempt is retried within the same window. It also queues its successor before doing any work, asks for the earliest possible window when a restart fails, and cancels itself when the user is no longer on Silent Tune. scheduleRefresh leaves a pending request alone when it would run at least as soon, so repeated background transitions no longer push the check further out. Logging follows the same shape: quiet when the keep-alive is healthy, and detailed once it is struggling. A first attempt success stays behind debug logging, while recovery after a failure reports the attempts and elapsed time, repeated failures with an unchanged error code are suppressed so a long ladder cannot bury the log, and session errors are named instead of being printed as raw four character codes. The task scheduler records one line per lost runtime window with its length and which background alerts fired, and the background and foreground transitions record the refresh mode, Low Power Mode and Background App Refresh status.
Device logs show isPlaying still reporting true at the moment an interruption is delivered, so the recovery entered on interruption began was skipped before it took its background task assertion, leaving the app dependent on iOS delivering interruption ended after all. Recovery no longer consults isPlaying to decide whether to run. Reattempting against a player that is genuinely playing is harmless, because the session is activated before the player is replaced, so a failed attempt leaves a working player untouched. Recovery on interruption began now waits a second before its first attempt. A brief interrupter's interruption ended lands inside that window and supersedes the work, so momentary blips stay as quiet as before, and work that does run means the claim is really gone whatever isPlaying says. The immediate background refresh request moved to that point as well, so it is made once per real interruption instead of once per blip, and the assertion expiring now arms it unconditionally since reaching expiration means the claim was never re-established.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Silent Tune's looping silent audio is the app's only background claim. When a call, Siri or an alarm takes the audio session, iOS suspends the app within seconds and it cannot recover on its own, which is what produces the "App inactive for N minutes" notification. In logs from two users reporting that notification, the app went dark for 6 to 19 minutes at a time. In the worst case a retry was scheduled two seconds out and did not run for 19 minutes, because the BGAppRefreshTask that scheduled it had already called setTaskCompleted and the app was suspended again before the timer could fire.
Reactivation now runs inside a UIApplication background task assertion so the retries actually run, keeps trying for about 18 seconds, and no longer trusts player.isPlaying, which keeps reporting true after the session is taken. The BGAppRefreshTask stays open until the restart resolves, and scheduleRefresh no longer pushes a pending request further out.
This does not make Silent Tune reliable, since an interruption that outlasts the assertion still suspends the app. Logging is quiet while healthy and detailed once struggling, including one line per lost runtime window.
Verified on device: a Teams call reclaimed the audio session two seconds after losing it and the app kept running for the whole call, in a case where iOS never delivered the interruption-ended notification and the app would previously have gone dark until opened.