From da30ff9d37502de11fa96d302f63ab0c316be272 Mon Sep 17 00:00:00 2001 From: William Van Haevre Date: Fri, 9 Aug 2024 11:54:25 +0200 Subject: [PATCH 1/4] Add config entry for interruption handling on ios --- ios/THEOplayerRCTDebug.swift | 3 +++ ios/THEOplayerRCTPlayerAPI.swift | 1 + .../THEOplayerRCTView+BackgroundAudioConfig.swift | 1 + src/api/backgroundAudio/BackgroundAudioConfiguration.ts | 8 ++++++++ 4 files changed, 13 insertions(+) diff --git a/ios/THEOplayerRCTDebug.swift b/ios/THEOplayerRCTDebug.swift index 4339c82ae..9dd3ebadc 100644 --- a/ios/THEOplayerRCTDebug.swift +++ b/ios/THEOplayerRCTDebug.swift @@ -44,3 +44,6 @@ let DEBUG_CACHE_EVENTS = DEBUG && false // Debug flag to monitor cache API usage let DEBUG_CACHE_API = DEBUG && false + +// Debug flag to monitor AudioSession interruptions (e.g. phone cll) +let DEBUG_INTERRUPTIONS = DEBUG && false diff --git a/ios/THEOplayerRCTPlayerAPI.swift b/ios/THEOplayerRCTPlayerAPI.swift index cbbf12450..5342e1f0f 100644 --- a/ios/THEOplayerRCTPlayerAPI.swift +++ b/ios/THEOplayerRCTPlayerAPI.swift @@ -223,6 +223,7 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule { private func parseBackgroundAudioConfig(configDict: NSDictionary) -> BackgroundAudioConfig { var backgroundAudio = BackgroundAudioConfig() backgroundAudio.enabled = configDict["enabled"] as? Bool ?? false + backgroundAudio.shouldResumeAfterInterruption = configDict["shouldResumeAfterInterruption"] as? Bool ?? false return backgroundAudio } diff --git a/ios/backgroundAudio/THEOplayerRCTView+BackgroundAudioConfig.swift b/ios/backgroundAudio/THEOplayerRCTView+BackgroundAudioConfig.swift index a092cc1e1..aefe24de7 100644 --- a/ios/backgroundAudio/THEOplayerRCTView+BackgroundAudioConfig.swift +++ b/ios/backgroundAudio/THEOplayerRCTView+BackgroundAudioConfig.swift @@ -5,6 +5,7 @@ import THEOplayerSDK struct BackgroundAudioConfig { var enabled: Bool = false + var shouldResumeAfterInterruption: Bool = false } extension THEOplayerRCTView: BackgroundPlaybackDelegate { diff --git a/src/api/backgroundAudio/BackgroundAudioConfiguration.ts b/src/api/backgroundAudio/BackgroundAudioConfiguration.ts index df9c8580e..b013a8b05 100644 --- a/src/api/backgroundAudio/BackgroundAudioConfiguration.ts +++ b/src/api/backgroundAudio/BackgroundAudioConfiguration.ts @@ -10,4 +10,12 @@ export interface BackgroundAudioConfiguration { * @defaultValue `false` */ readonly enabled?: boolean; + + /** + * Whether background audio should be resumed after an interruption (e.g. incoming call ended). + * + * @defaultValue `false` + * @remark Applies to iOS only, impacting behaviour for handling interruptions while on the lockscreen. + */ + readonly shouldResumeAfterInterruption?: boolean; } From 88071107d583edcfc42d80d9b48a38ede6c61f3d Mon Sep 17 00:00:00 2001 From: William Van Haevre Date: Fri, 9 Aug 2024 11:54:51 +0200 Subject: [PATCH 2/4] keep setting inBG audio submenu --- example/src/custom/BackgroundAudioSubMenu.tsx | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/example/src/custom/BackgroundAudioSubMenu.tsx b/example/src/custom/BackgroundAudioSubMenu.tsx index bd7de8fa2..b734a803d 100644 --- a/example/src/custom/BackgroundAudioSubMenu.tsx +++ b/example/src/custom/BackgroundAudioSubMenu.tsx @@ -17,13 +17,19 @@ export interface BackgroundAudioSubMenuProps { export const BackgroundAudioSubMenu = (props?: BackgroundAudioSubMenuProps) => { const ctx = useContext(PlayerContext); - return ) => { - ctx.player.backgroundAudioConfiguration = { enabled: option.value }; - }} - currentOption={() => ctx.player.backgroundAudioConfiguration.enabled ?? false} />; -} + return ( + ) => { + ctx.player.backgroundAudioConfiguration = { ...ctx.player.backgroundAudioConfiguration, enabled: option.value }; + }} + currentOption={() => ctx.player.backgroundAudioConfiguration.enabled ?? false} + /> + ); +}; From c14e338ede03a70be156df8eaa4821ec5a7bc883 Mon Sep 17 00:00:00 2001 From: William Van Haevre Date: Fri, 9 Aug 2024 11:55:29 +0200 Subject: [PATCH 3/4] Setup AVAudioSession notification handling to process interruptions --- ios/THEOplayerRCTView.swift | 6 ++- ...OplayerRCTView+BackgroundAudioConfig.swift | 50 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/ios/THEOplayerRCTView.swift b/ios/THEOplayerRCTView.swift index c33dacbe2..cc4f4b14d 100644 --- a/ios/THEOplayerRCTView.swift +++ b/ios/THEOplayerRCTView.swift @@ -22,7 +22,6 @@ public class THEOplayerRCTView: UIView { var adsConfig = AdsConfig() var castConfig = CastConfig() var uiConfig = UIConfig() - var backgroundAudioConfig = BackgroundAudioConfig() var mediaControlConfig = MediaControlConfig() { didSet { @@ -34,6 +33,11 @@ public class THEOplayerRCTView: UIView { self.pipControlsManager.setPipConfig(pipConfig) } } + var backgroundAudioConfig = BackgroundAudioConfig() { + didSet { + self.updateInterruptionNotifications() + } + } // MARK: Events var onNativePlayerReady: RCTDirectEventBlock? diff --git a/ios/backgroundAudio/THEOplayerRCTView+BackgroundAudioConfig.swift b/ios/backgroundAudio/THEOplayerRCTView+BackgroundAudioConfig.swift index aefe24de7..d2d08c20e 100644 --- a/ios/backgroundAudio/THEOplayerRCTView+BackgroundAudioConfig.swift +++ b/ios/backgroundAudio/THEOplayerRCTView+BackgroundAudioConfig.swift @@ -2,6 +2,7 @@ import Foundation import THEOplayerSDK +import AVFAudio struct BackgroundAudioConfig { var enabled: Bool = false @@ -19,6 +20,9 @@ extension THEOplayerRCTView: BackgroundPlaybackDelegate { return } player.backgroundPlaybackDelegate = DefaultBackgroundPlaybackDelegate() + NotificationCenter.default.removeObserver(self, + name: AVAudioSession.interruptionNotification, + object: AVAudioSession.sharedInstance()) } public func shouldContinueAudioPlaybackInBackground() -> Bool { @@ -27,6 +31,52 @@ extension THEOplayerRCTView: BackgroundPlaybackDelegate { return self.backgroundAudioConfig.enabled } + + func updateInterruptionNotifications() { + // Get the default notification center instance. + if self.backgroundAudioConfig.shouldResumeAfterInterruption { + NotificationCenter.default.addObserver(self, + selector: #selector(handleInterruption), + name: AVAudioSession.interruptionNotification, + object: AVAudioSession.sharedInstance()) + } else { + NotificationCenter.default.removeObserver(self, + name: AVAudioSession.interruptionNotification, + object: AVAudioSession.sharedInstance()) + } + } + + + @objc func handleInterruption(notification: Notification) { + guard let userInfo = notification.userInfo, + let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt, + let type = AVAudioSession.InterruptionType(rawValue: typeValue) else { + return + } + + // Switch over the interruption type. + switch type { + case .began: + // An interruption began. Update the UI as necessary. + if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[INTERRUPTION] An interruption began")} + case .ended: + // An interruption ended. Resume playback, if appropriate. + if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[INTERRUPTION] An interruption ended")} + guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return } + let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue) + if options.contains(.shouldResume) { + // An interruption ended. Resume playback. + if let player = self.player { + if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[INTERRUPTION] Ended interruption should resume playback => play()")} + player.play() + } + } else { + // An interruption ended. Don't resume playback. + if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[INTERRUPTION] Ended interruption should not resume playback.")} + } + default: () + } + } } struct DefaultBackgroundPlaybackDelegate: BackgroundPlaybackDelegate { From 0e7682107ac2177ea03115a4f7146e342885ab25 Mon Sep 17 00:00:00 2001 From: William Van Haevre Date: Fri, 9 Aug 2024 11:58:40 +0200 Subject: [PATCH 4/4] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 525400ea4..1619e6995 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added +- Added shouldResumeAfterInterruptions to the BackgroundAudioConfiguration which toggles playback resume after an interruption (e.g. phone call) on the lockscreen. - Added the option to set a nil source on the iOS Bridge, to 'unset' the previous source. - Added the fast-forward & rewind buttons for the Android notification when `mediaControl.mediaSessionEnabled` is set to `true`. - Added a `mediaControl.convertSkipToSeek` player config property to allow seeking with the `NEXT` and `PREVIOUS` media buttons. Its default value is `false`.