fix: Dynamic Island, Live Activity errors, background audio stop, CFBundleVersion
Some checks failed
CI / Deploy (pull_request) Has been skipped
PR → Build → WiFi/USB Deploy → LGTM / Wait for LGTM comment (pull_request) Failing after 8m7s
CI / Detect Changes (pull_request) Successful in 3s
CI / Admin Web CI (pull_request) Has been skipped
CI / YouTube Worker (pull_request) Has been skipped
PR → Build → WiFi/USB Deploy → LGTM / Build & Deploy to iPhone (WiFi/USB) (pull_request) Successful in 2m58s
Some checks failed
CI / Deploy (pull_request) Has been skipped
PR → Build → WiFi/USB Deploy → LGTM / Wait for LGTM comment (pull_request) Failing after 8m7s
CI / Detect Changes (pull_request) Successful in 3s
CI / Admin Web CI (pull_request) Has been skipped
CI / YouTube Worker (pull_request) Has been skipped
PR → Build → WiFi/USB Deploy → LGTM / Build & Deploy to iPhone (WiFi/USB) (pull_request) Successful in 2m58s
Phase 1 — Audio background resilience: - Remove .mixWithOthers, app takes audio focus for coach cues - Add AVAudioSession.interruptionNotification handler (re-activates after calls/Siri) - Add AVAudioSession.routeChangeNotification handler (re-activates on headphone changes) Phase 2 — Live Activity error surfacing: - PlayerViewModel: @Published liveActivityError captures auth-denied + request errors - PlayerView: translucent error banner with dismiss button (non-blocking) Phase 3 — CFBundleVersion 1→2 in all 4 Info.plist files (match project.yml CURRENT_PROJECT_VERSION: 2)
This commit is contained in:
@@ -28,12 +28,55 @@ final class AudioService {
|
||||
try audioSession.setCategory(
|
||||
.playback,
|
||||
mode: .default,
|
||||
options: [.mixWithOthers, .allowAirPlay, .allowBluetooth]
|
||||
options: [.allowAirPlay, .allowBluetooth]
|
||||
)
|
||||
try audioSession.setActive(true)
|
||||
} catch {
|
||||
print("[Audio] Session configuration failed: \(error)")
|
||||
}
|
||||
|
||||
// ─── Interruption Handler
|
||||
NotificationCenter.default.addObserver(
|
||||
forName: AVAudioSession.interruptionNotification,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] notification in
|
||||
guard let self,
|
||||
let typeValue = notification.userInfo?[AVAudioSessionInterruptionTypeKey] as? UInt,
|
||||
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else { return }
|
||||
|
||||
if type == .ended {
|
||||
guard let optionsValue = notification.userInfo?[AVAudioSessionInterruptionOptionKey] as? UInt,
|
||||
AVAudioSession.InterruptionOptions(rawValue: optionsValue).contains(.shouldResume) else { return }
|
||||
do {
|
||||
try self.audioSession.setActive(true)
|
||||
} catch {
|
||||
print("[Audio] Re-activation failed after interruption: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Route Change Handler
|
||||
NotificationCenter.default.addObserver(
|
||||
forName: AVAudioSession.routeChangeNotification,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] notification in
|
||||
guard let self,
|
||||
let reasonValue = notification.userInfo?[AVAudioSessionRouteChangeReasonKey] as? UInt,
|
||||
let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else { return }
|
||||
|
||||
switch reason {
|
||||
case .newDeviceAvailable, .oldDeviceUnavailable:
|
||||
do {
|
||||
try self.audioSession.setActive(true)
|
||||
} catch {
|
||||
print("[Audio] Route re-activation failed: \(error)")
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Phase Audio Cues ─────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user