Skip to content

Commit

Permalink
Merge pull request #362 from THEOplayer/feature/lockscreen-interrupti…
Browse files Browse the repository at this point in the history
…on-handling

Feature/lockscreen interruption handling
  • Loading branch information
wvanhaevre authored Aug 9, 2024
2 parents 60353e3 + 0e76821 commit a84d768
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
26 changes: 16 additions & 10 deletions example/src/custom/BackgroundAudioSubMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,19 @@ export interface BackgroundAudioSubMenuProps {
export const BackgroundAudioSubMenu = (props?: BackgroundAudioSubMenuProps) => {
const ctx = useContext(PlayerContext);

return <CustomSubMenu
title={'Background Audio'}
menuStyle={props?.menuStyle}
label={'Bg Audio'}
options={[{ label: 'Disabled', value: false }, { label: 'Enabled', value: true }]}
onOptionSelected={(option: Option<boolean>) => {
ctx.player.backgroundAudioConfiguration = { enabled: option.value };
}}
currentOption={() => ctx.player.backgroundAudioConfiguration.enabled ?? false} />;
}
return (
<CustomSubMenu
title={'Background Audio'}
menuStyle={props?.menuStyle}
label={'Bg Audio'}
options={[
{ label: 'Disabled', value: false },
{ label: 'Enabled', value: true },
]}
onOptionSelected={(option: Option<boolean>) => {
ctx.player.backgroundAudioConfiguration = { ...ctx.player.backgroundAudioConfiguration, enabled: option.value };
}}
currentOption={() => ctx.player.backgroundAudioConfiguration.enabled ?? false}
/>
);
};
3 changes: 3 additions & 0 deletions ios/THEOplayerRCTDebug.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions ios/THEOplayerRCTPlayerAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
6 changes: 5 additions & 1 deletion ios/THEOplayerRCTView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ public class THEOplayerRCTView: UIView {
var adsConfig = AdsConfig()
var castConfig = CastConfig()
var uiConfig = UIConfig()
var backgroundAudioConfig = BackgroundAudioConfig()

var mediaControlConfig = MediaControlConfig() {
didSet {
Expand All @@ -34,6 +33,11 @@ public class THEOplayerRCTView: UIView {
self.pipControlsManager.setPipConfig(pipConfig)
}
}
var backgroundAudioConfig = BackgroundAudioConfig() {
didSet {
self.updateInterruptionNotifications()
}
}

// MARK: Events
var onNativePlayerReady: RCTDirectEventBlock?
Expand Down
51 changes: 51 additions & 0 deletions ios/backgroundAudio/THEOplayerRCTView+BackgroundAudioConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import Foundation
import THEOplayerSDK
import AVFAudio

struct BackgroundAudioConfig {
var enabled: Bool = false
var shouldResumeAfterInterruption: Bool = false
}

extension THEOplayerRCTView: BackgroundPlaybackDelegate {
Expand All @@ -18,6 +20,9 @@ extension THEOplayerRCTView: BackgroundPlaybackDelegate {
return
}
player.backgroundPlaybackDelegate = DefaultBackgroundPlaybackDelegate()
NotificationCenter.default.removeObserver(self,
name: AVAudioSession.interruptionNotification,
object: AVAudioSession.sharedInstance())
}

public func shouldContinueAudioPlaybackInBackground() -> Bool {
Expand All @@ -26,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 {
Expand Down
8 changes: 8 additions & 0 deletions src/api/backgroundAudio/BackgroundAudioConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

0 comments on commit a84d768

Please sign in to comment.