Skip to content

Commit

Permalink
fix: handle iOS carplay audio interruptions
Browse files Browse the repository at this point in the history
  • Loading branch information
KestasVenslauskas committed Jan 16, 2025
1 parent 7cd78fd commit f58fdc4
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 36 deletions.
8 changes: 4 additions & 4 deletions ios/lrtApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -864,13 +864,13 @@
CODE_SIGN_ENTITLEMENTS = lrtApp/lrtApp.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 205201;
CURRENT_PROJECT_VERSION = 205202;
DEVELOPMENT_TEAM = TGMUP98888;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64";
INFOPLIST_FILE = lrtApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 2.52.1;
MARKETING_VERSION = 2.52.2;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
Expand Down Expand Up @@ -898,11 +898,11 @@
CODE_SIGN_ENTITLEMENTS = lrtApp/lrtAppRelease.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 205201;
CURRENT_PROJECT_VERSION = 205202;
DEVELOPMENT_TEAM = TGMUP98888;
INFOPLIST_FILE = lrtApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 2.52.1;
MARKETING_VERSION = 2.52.2;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
Expand Down
63 changes: 31 additions & 32 deletions ios/lrtApp/PlayerController.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import AVFoundation
import FirebaseAnalytics
import MediaPlayer

class PlayerController {
Expand Down Expand Up @@ -29,10 +30,6 @@ class PlayerController {
return (player?.currentItem?.asset as? AVURLAsset)?.url
}

private init() {
addInterruptionObserver()
}

func setupStream(for item: CarPlayItem) throws {
player?.pause()
removeObservers()
Expand All @@ -46,7 +43,8 @@ class PlayerController {

let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(.playback, mode: .spokenAudio, policy: .longFormAudio)
try audioSession.setCategory(
.playback, mode: .spokenAudio, policy: .default, options: .mixWithOthers)
try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
} catch {
print("Failed to configure audio session: \(error.localizedDescription)")
Expand Down Expand Up @@ -122,36 +120,10 @@ class PlayerController {
[weak self] _ in
self?.updateNowPlayingTime()
}
}

private func onPlayerEnded() {
MPNowPlayingInfoCenter.default().playbackState = .stopped
do {
try playNext()
} catch {}
}

private func removeObservers() {
if let observer = mediaEndObserver {
NotificationCenter.default.removeObserver(observer)
mediaEndObserver = nil
}

if let observer = playerIntervalObserver, let player = player {
player.removeTimeObserver(observer)
playerIntervalObserver = nil
}

if let observer = interruptionObserver {
NotificationCenter.default.removeObserver(observer)
interruptionObserver = nil
}
}

private func addInterruptionObserver() {
interruptionObserver = NotificationCenter.default.addObserver(
forName: AVAudioSession.interruptionNotification,
object: nil,
object: AVAudioSession.sharedInstance(),
queue: .main
) { [weak self] notification in
guard let self = self else { return }
Expand All @@ -165,13 +137,16 @@ class PlayerController {

switch type {
case .began:
Analytics.logEvent("carplay_interruption_began", parameters: nil)
// Pause playback when interruption begins
self.pause()
case .ended:
Analytics.logEvent("carplay_interruption_ended", parameters: nil)
// Resume playback when interruption ends, if appropriate
if let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt {
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
if options.contains(.shouldResume) {
Analytics.logEvent("carplay_interruption_resuming", parameters: nil)
self.play()
}
}
Expand All @@ -181,6 +156,30 @@ class PlayerController {
}
}

private func onPlayerEnded() {
MPNowPlayingInfoCenter.default().playbackState = .stopped
do {
try playNext()
} catch {}
}

private func removeObservers() {
if let observer = mediaEndObserver {
NotificationCenter.default.removeObserver(observer)
mediaEndObserver = nil
}

if let observer = playerIntervalObserver, let player = player {
player.removeTimeObserver(observer)
playerIntervalObserver = nil
}

if let observer = interruptionObserver {
NotificationCenter.default.removeObserver(observer)
interruptionObserver = nil
}
}

private func updateNowPlayingTime() {
guard let player = player else { return }
var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [:]
Expand Down

0 comments on commit f58fdc4

Please sign in to comment.