fix(ios): fix lock screen controls not working on first play#2583
fix(ios): fix lock screen controls not working on first play#2583anojht wants to merge 1 commit intodoublesymmetry:mainfrom
Conversation
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>
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. |
|
@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). |
alhansrisuk
left a comment
There was a problem hiding this comment.
tested on expo 54 and seems to be working
|
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 |
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 beforeAVAudioSession.sharedInstance().setCategory(.playback, ...). This caused the audio session to activate with iOS's default.soloAmbientcategory, which does not supportMPRemoteCommandCenteror lock screen media controls. Only.playbackand.playAndRecordcategories support remote commands.Fix: Swap the order so
setCategory()runs beforeactivateSession(). The session now activates with the correct.playbackcategory from the start.Bug 2: Now playing info race condition on lock screen
NowPlayingInfoControllerin 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().nowPlayingInfois stillnilwhen 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
MPNowPlayingInfoCenterin theplay()method, immediately afterplayer.play(). This ensures iOS always has metadata available when rendering the lock screen. The asyncNowPlayingInfoControllerupdates will overwrite this with the full metadata shortly after.Reproduction steps
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: ReordersetCategory()beforeactivateSession()inconfigureAudioSession()ios/TrackPlayer.swift: Add synchronousMPNowPlayingInfoCentermetadata write inplay()Test plan
🤖 Generated with Claude Code