Skip to content

fix(ios): fix lock screen controls not working on first play#2583

Open
anojht wants to merge 1 commit intodoublesymmetry:mainfrom
anojht:fix/ios-lock-screen-controls-on-first-play
Open

fix(ios): fix lock screen controls not working on first play#2583
anojht wants to merge 1 commit intodoublesymmetry:mainfrom
anojht:fix/ios-lock-screen-controls-on-first-play

Conversation

@anojht
Copy link

@anojht anojht commented Feb 18, 2026

Summary

Lock screen media controls (play/pause, skip forward/backward, seek) do not appear when the user locks their iOS device immediately after pressing play for the first time in a session. If the user backgrounds the app first and then locks the screen, controls appear correctly. This PR fixes two root causes in TrackPlayer.swift:

Bug 1: Audio session category set after activation

In configureAudioSession(), audioSessionController.activateSession() was called before AVAudioSession.sharedInstance().setCategory(.playback, ...). This caused the audio session to activate with iOS's default .soloAmbient category, which does not support MPRemoteCommandCenter or lock screen media controls. Only .playback and .playAndRecord categories support remote commands.

Fix: Swap the order so setCategory() runs before activateSession(). The session now activates with the correct .playback category from the start.

// Before (broken):
try? audioSessionController.activateSession()  // activates with .soloAmbient
try? AVAudioSession.sharedInstance().setCategory(...)  // too late

// After (fixed):
try? AVAudioSession.sharedInstance().setCategory(...)  // set .playback first
try? audioSessionController.activateSession()  // activates with .playback

Bug 2: Now playing info race condition on lock screen

NowPlayingInfoController in SwiftAudioEx publishes metadata asynchronously via a concurrent dispatch queue (infoQueue.async(flags: .barrier)). When the user locks the screen immediately after pressing play, MPNowPlayingInfoCenter.default().nowPlayingInfo is still nil when iOS queries it to render the lock screen media widget — the async update hasn't completed yet.

This explains why backgrounding the app first makes it work: the async dispatch has time to complete before the lock screen appears.

Fix: Synchronously write the current track's core metadata (title, artist, album, playback rate, duration) directly to MPNowPlayingInfoCenter in the play() method, immediately after player.play(). This ensures iOS always has metadata available when rendering the lock screen. The async NowPlayingInfoController updates will overwrite this with the full metadata shortly after.

Reproduction steps

  1. Fresh app launch (no audio session active yet)
  2. Start playing a track by pressing play
  3. Immediately lock the device (press the power button) without backgrounding the app first
  4. Expected: Lock screen shows media controls with track title, artist, and playback controls
  5. Actual (before fix): Lock screen shows no media controls or shows controls with no metadata

Workaround (before fix): Background the app first (swipe to home screen), then lock the device — controls appear because both the audio session and the async metadata dispatch have time to complete.

Changes

  • ios/TrackPlayer.swift: Reorder setCategory() before activateSession() in configureAudioSession()
  • ios/TrackPlayer.swift: Add synchronous MPNowPlayingInfoCenter metadata write in play()

Test plan

  • Start playback and immediately lock the device → verify lock screen shows media controls with correct metadata
  • Start playback, background the app, then lock → verify controls still work (regression check)
  • Play multiple tracks in sequence → verify metadata updates correctly on lock screen
  • Verify pause/resume from lock screen works on first play
  • Verify skip forward/backward from lock screen works on first play

🤖 Generated with Claude Code

Two issues caused lock screen media controls to not appear when the user
locked the screen immediately after pressing play for the first time:

1. Audio session category set after activation: `configureAudioSession()`
   called `activateSession()` before `setCategory(.playback)`, causing the
   session to activate with the default `.soloAmbient` category. This
   category does not support `MPRemoteCommandCenter` / lock screen controls.
   Fix: swap the order so `setCategory()` runs before `activateSession()`.

2. Now playing info race condition: `NowPlayingInfoController` in
   SwiftAudioEx publishes metadata asynchronously via a concurrent dispatch
   queue (`infoQueue.async(flags: .barrier)`). When the user locks the
   screen immediately after pressing play, `MPNowPlayingInfoCenter` is still
   nil when iOS queries it. Fix: synchronously write core metadata fields
   directly to `MPNowPlayingInfoCenter` in the `play()` method.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@anojht anojht requested a review from dcvz as a code owner February 18, 2026 06:56
@oojr
Copy link

oojr commented Feb 19, 2026

This pull request doesn't work @dcvz, please test your slop @anojht

@anojht
Copy link
Author

anojht commented Feb 19, 2026

This pull request doesn't work @dcvz, please test your slop @anojht

I opened this PR as this patch is in my production react native app and it fixed the exact issues described.

Can you walk me through how you tested this?

You'll want to ensure you build cleanly, by running pod install in a clean iOS project directory before building.

@alhansrisuk
Copy link

@anojht are you using Expo by any chance?

@anojht
Copy link
Author

anojht commented Feb 20, 2026

@anojht are you using Expo by any chance?

Yes our app is a Expo managed React Native application.

@alhansrisuk
Copy link

@anojht are you using Expo by any chance?

Yes our app is a Expo managed React Native application.

what version of expo? we're using 54 and are still encountering this issue with your patch applied

@anojht
Copy link
Author

anojht commented Feb 20, 2026

@anojht are you using Expo by any chance?

Yes our app is a Expo managed React Native application.

what version of expo? we're using 54 and are still encountering this issue with your patch applied

We're using Expo SDK 54 and the next version of the library (v5.0.0-alpha0).
The only other patch we have is the second PR I opened and that is for AirPods gestures.

Copy link

@alhansrisuk alhansrisuk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tested on expo 54 and seems to be working

@oojr
Copy link

oojr commented Feb 25, 2026

doesn't work, I am using React Native bare 0.84.0 but it doesn't break anything either, I did clean and did a pod install @anojht @alhansrisuk

@bitcrumb bitcrumb mentioned this pull request Feb 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants