Skip to content
AF
Audio Coaching

Testing workout audio cues: calls, Siri, Bluetooth and the podcast that never resumes

Last verified October 3, 2026 · 6 min read

Sources checked (5) · October 3, 2026
Audio cue bugs live in transitions a desk test never triggers, so the test plan is a list of documented platform events caused on purpose, each with an expected result from the platform's documentation. On iOS that means a screen lock with music playing, a podcast app playing, an incoming call in both banner and full-screen styles, Siri listening, which Apple documents as setting promptStyle to none, headphones being removed, and Apple's own Reset Media Services item in the Developer menu of Settings. On Android it means a background cue from an app targeting API level 35, speech content playing, a phone call, headphones removed and a missing TTS voice. Keep the cue decision a pure function you unit-test, and test the delivery on devices.

Audio cue bugs do not live in the cue. They live in transitions: the screen locking, a call arriving, Siri listening, a podcast that should pause and resume, an earbud falling out, an Android build targeting a newer API level. A desk test with the screen on and nothing else playing triggers none of them. So the test plan is a list of documented platform events, each caused on purpose, each with an expected result taken from the platform's own documentation.

This page is that list. Each row links to the page that explains the behaviour.

What can be unit-tested, and what cannot#

Split the cue system in two. The decision ("given the workout time, the prompt style, whether focus was granted and whether headphones are connected, should this cue be speech, a tone, a haptic or nothing?") is a pure function. Test it exhaustively in your normal suite, alongside the rest of test. The delivery, meaning the session, focus, route and engine, can only be tested on a device, because every interesting input comes from the operating system.

enum CueOutput { case speech, tone, haptic, skip }

func output(promptStyle: AVAudioSession.PromptStyle,
            headphonesConnected: Bool,
            speakerOptIn: Bool) -> CueOutput {
    switch promptStyle {
    case .none:   return .haptic
    case .short:  return .tone
    default:      return (headphonesConnected || speakerOptIn) ? .speech : .haptic
    }
}

The mapping from promptStyle comes from Apple's descriptions of the three styles, set out on the AVSpeechSynthesizer page. The headphones branch is our judgement from the route-change page.

The iOS device matrix#

Cause thisHowExpected, per AppleAssert in your app
Screen lock with music playingStart music, start a workout, lockplayback audio continues when the screen locks, with audio in UIBackgroundModesEvery cue plays; music ducks and returns (background audio)
A podcast is playingPlay a podcast app, then trigger a cueinterruptSpokenAudioAndMixWithOthers pauses spokenAudio sessions while you are active; the system resumes them after you deactivateThe podcast resumes after each cue (duckOthers)
Incoming call, banner styleCall the deviceWith setPrefersNoInterruptionsFromSystemAlerts(true), the session is interrupted only if the user acceptsCues pause only on accept
Incoming call, full-screen styleSwitch the call display style, call againThe preference "has no effect"; the system interrupts the sessionSpeech suspended; workout clock unaffected (interruptions)
Siri listeningInvoke Siri mid-intervalpromptStyle is .none while another session uses the microphoneNo speech or tone; haptic fires
A voice call in progressJoin a call, then trigger a cuepromptStyle is .short "when a voice call is active"A tone, not words
Wired or Bluetooth headphones removedUnplug or power off mid-cuerouteChangeNotification with oldDeviceUnavailable, posted on a secondary threadSpeech stops; workout continues; no stale cues on reconnect
Media services resetSettings, then Developer, then Reset Media ServicesmediaServicesWereResetNotification; reinitialize audio objects and reset category, options and modeNext cue plays without restarting the app

The last row is a test Apple documents directly. The mediaServicesWereResetNotification page: "You can trigger a media server reset by choosing the "Reset Media Services" selection under the Developer menu in the iOS Settings app. Using this utility helps to ensure that your app responds appropriately if media services were reset." Most audio code has never been through it.

The Android device matrix#

Cause thisHowExpected, per GoogleAssert in your app
Background cue, app targeting API level 35 or higherScreen off, no foreground servicerequestAudioFocus returns AUDIOFOCUS_REQUEST_FAILEDWith the workout's foreground service running, the cue plays (Android 15)
Music playingPlay music, trigger a cueOn Android 8.0 and higher, a MAY_DUCK request ducks the music automatically and restores it on abandonMusic ducks and returns (MAY_DUCK)
Speech content playingPlay a podcast or audiobook appNo automatic ducking of CONTENT_TYPE_SPEECH; that app's listener gets AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCKWhatever the other app does, your cue still plays and focus is abandoned
Phone callCall the device mid-workoutFocus may be locked; requestAudioFocus returns AUDIOFOCUS_REQUEST_FAILEDCue skipped, haptic fires, not replayed after the call
Headphones removedUnplug, or disconnect a Bluetooth headsetACTION_AUDIO_BECOMING_NOISY broadcastSpeech stops; workout continues
TTS voice missingSet a language with no installed voicesetLanguage returns LANG_MISSING_DATA or LANG_NOT_SUPPORTEDUser told before the workout starts (TextToSpeech)
Wear OS watch, no earbudsRun with nothing pairedMedia should play only with a suitable output; short cues may use the speakerCues play on the speaker or as haptics; media offers Bluetooth settings (watch audio)

Two Android rows depend on the OS version, so run them on at least one device below Android 12 and one on Android 12 or later: Google's guide says audio focus is "not managed by the system" before Android 12, and the forced fade-out and call mute arrive with it.

Logging that makes field reports useful#

The matrix catches what you can cause. Field reports catch the rest, but only if the log has the inputs. Our judgement on what to log per cue, on both platforms:

  • scheduled time and actual time played, or why it was skipped
  • the focus request result on Android, or whether activation threw on iOS
  • promptStyle on iOS
  • the current output route
  • app state: foreground or background, and on Android whether the foreground service was running

With those five fields, most "the coach went quiet" reports can be matched to a row in one of the tables above.

Who should run it#

Run the matrix on real devices, with the screen locked, for a full workout, not in short bursts with the screen on. That is our recommendation, not something either platform states. The device-lab mechanics are in device lab and CI; watch-specific setup is in testing watch apps; and accessibility testing of the same flows, including a screen reader talking over your cues, is in testing accessibility.

Monday morning#

Extract the cue decision into a pure function and give it a table-driven test. Print the two device matrices and walk one device through each before the next release, starting with Reset Media Services on iOS and a screen-off cue on an Android build targeting API level 35. Add the five log fields. Then hand the matrix to whoever does release testing, because none of these rows will ever fail on a developer's desk.

Frequently asked questions

How do I test my iOS app's response to a media services reset?
Use the switch Apple documents for it. Apple's mediaServicesWereResetNotification page says you can trigger a media server reset by choosing Reset Media Services under the Developer menu in the iOS Settings app, and that doing so helps ensure your app responds appropriately. Apple says to respond by reinitializing your audio objects and resetting the session's category, options and mode, and notes you don't need to re-register for audio session notifications. Run it mid-workout and confirm the next cue plays without restarting the app.
What should I test before shipping workout audio cues on an app targeting Android 15?
A cue with the screen off. Google documents that an app targeting Android 15 or higher cannot request audio focus unless it's the top app or running a foreground service, and gets AUDIOFOCUS_REQUEST_FAILED otherwise, so a screen-off cue without a running foreground service fails silently. Test it with and without your workout's foreground service, log every focus result with the app state, and also test a phone call, which locks focus and produces the same return value for a different reason.
Which audio events should a workout app test on a real device before release?
Our list, built from what the platforms document: a full workout with the screen locked and music playing; a podcast app playing, which should pause and resume around each cue; an incoming call, in both call display styles on iOS; Siri or another microphone user mid-interval; wired and Bluetooth headphones removed mid-cue; and, on Android, a device below Android 12 and one at Android 12 or later, because Google documents that audio focus is only managed by the system from Android 12.

All 42 questions in Audio Coaching →

Keep reading

Elsewhere on the site

Pages that share this one’s concepts and sources, from other sections.

Next steps

Was this page useful?

Independent comparison, last reviewed October 3, 2026. Pricing, rate limits, and feature availability change often — confirm current details in each provider’s official documentation before you commit. Product and company names are trademarks of their respective owners; AIFitnessAPI is not affiliated with, endorsed by, or sponsored by any product listed here.

← All audio coaching · by AIFitnessAPI