fix(ios): keep NWC Live Activity on playing track during in-app pick

This commit is contained in:
Evan Kaloudis
2026-06-05 14:33:54 -04:00
parent cda87392c8
commit 6154db80d0
3 changed files with 41 additions and 9 deletions
+16
View File
@@ -10,6 +10,7 @@ enum NWCLiveActivityShared {
private static let keyIsMuted = "nwc.isMuted"
private static let keyRevision = "nwc.contentRevision"
private static let keyActivityId = "nwc.activityId"
private static let keyPreferredTrackIndex = "nwc.preferredTrackIndex"
private static var defaults: UserDefaults? {
UserDefaults(suiteName: appGroupID)
@@ -36,6 +37,19 @@ enum NWCLiveActivityShared {
d?.set(Int(revision), forKey: keyRevision)
}
/// Track the user prefers next start to play. Distinct from the display state
/// (`keyTrackIndex`) so picking in settings during an active session doesn't
/// race the refresh timer into showing a track the audio isn't actually playing.
static func readPreferredTrackIndex() -> Int? {
guard let d = defaults, d.object(forKey: keyPreferredTrackIndex) != nil
else { return nil }
return d.integer(forKey: keyPreferredTrackIndex)
}
static func writePreferredTrackIndex(_ index: Int) {
defaults?.set(index, forKey: keyPreferredTrackIndex)
}
static func setPreferredActivityId(_ id: String?) {
let d = defaults
if let id {
@@ -125,6 +139,7 @@ enum NWCLiveActivityShared {
static func applyNextTrack() async {
var (index, muted, _) = readDisplay()
index = (index + 1) % max(tracks.count, 1)
writePreferredTrackIndex(index)
await pushLiveActivity(trackIndex: index, isMuted: muted)
}
@@ -133,6 +148,7 @@ enum NWCLiveActivityShared {
var (index, muted, _) = readDisplay()
let count = max(tracks.count, 1)
index = (index - 1 + count) % count
writePreferredTrackIndex(index)
await pushLiveActivity(trackIndex: index, isMuted: muted)
}
+11 -9
View File
@@ -118,10 +118,11 @@ RCT_EXPORT_MODULE();
_disconnectCount = 0;
_trackNames = [NWCAmbientTracks trackNames];
// Restore last-selected track from the app group (written by
// switchToTrackAtIndex: via NWCLiveActivityBridge). UserDefaults
// returns 0 if the key was never written, which matches the default.
NSInteger persisted = [NWCLiveActivityBridge appGroupTrackIndex];
// Restore the user's preferred track. Preference key wins; fall back
// to the display-state index (which reflects in-session changes made
// from the Dynamic Island) if the user never picked in settings.
NSInteger fallback = [NWCLiveActivityBridge appGroupTrackIndex];
NSInteger persisted = [NWCLiveActivityBridge preferredTrackIndexWithFallback:fallback];
NSInteger count = (NSInteger)_trackNames.count;
_currentTrackIndex = (count > 0 && persisted >= 0 && persisted < count)
? persisted
@@ -258,8 +259,10 @@ RCT_EXPORT_METHOD(setTrack:(NSInteger)index
// Updates the preferred track without restarting playback. Used when the
// settings UI changes selection during an active NWC session — the user's
// pick should persist (app group, in-memory) so the next start uses it,
// but the currently playing ambient track must not be interrupted.
// pick should persist so the next start uses it, but the currently playing
// ambient track and the Dynamic Island must not be interrupted. Writes only
// the preference key in the app group; the display-state index (which the
// Live Activity refresh timer reads every 15s) is intentionally left alone.
RCT_EXPORT_METHOD(setTrackPreference:(NSInteger)index
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
@@ -268,9 +271,7 @@ RCT_EXPORT_METHOD(setTrackPreference:(NSInteger)index
return;
}
self.currentTrackIndex = index;
[NWCLiveActivityBridge syncAppGroupFromAudioWithTrackIndex:index
isMuted:self.isMuted];
[NWCLiveActivityBridge setPreferredTrackIndex:index];
resolve([self currentStatusDict]);
}
@@ -345,6 +346,7 @@ RCT_EXPORT_METHOD(disarmNWCAudio:(RCTPromiseResolveBlock)resolve
[NWCLiveActivityBridge syncAppGroupFromAudioWithTrackIndex:index
isMuted:self.isMuted];
[NWCLiveActivityBridge setPreferredTrackIndex:index];
if (@available(iOS 16.1, *)) {
NSString *trackName = self.trackNames[index];
+14
View File
@@ -19,4 +19,18 @@ import Foundation
revision: revision
)
}
/// Returns the persisted preferred track index, or `fallback` if unset.
/// Distinct from the display-state track index so the Live Activity
/// refresh timer doesn't surface the preference while a different track
/// is actually playing.
@objc(preferredTrackIndexWithFallback:)
static func preferredTrackIndex(fallback: Int) -> Int {
NWCLiveActivityShared.readPreferredTrackIndex() ?? fallback
}
@objc(setPreferredTrackIndex:)
static func setPreferredTrackIndex(_ index: Int) {
NWCLiveActivityShared.writePreferredTrackIndex(index)
}
}