Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ZEUS-4591] Fix video playing in background when switching #23

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions Sources/PlaybackSDK/PlayBackSDKManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,20 @@ public class PlayBackSDKManager {
```swift
let playerView = loadPlayer(entryID: "exampleEntryID", authorizationToken: "exampleToken")
*/
public func loadPlayer(entryID: String, authorizationToken: String? = nil, mediaTitle: String? = nil, onError: ((PlayBackAPIError) -> Void)?) -> some View {
return PlaybackUIView(entryId: entryID, authorizationToken: authorizationToken, mediaTitle: mediaTitle, onError: onError)
public func loadPlayer(
entryID: String,
authorizationToken: String? = nil,
mediaTitle: String? = nil,
onError: ((PlayBackAPIError) -> Void)?
) -> some View {

PlaybackUIView(
entryId: entryID,
authorizationToken: authorizationToken,
mediaTitle: mediaTitle,
onError: onError
)
.id(entryID)
}

// MARK: Private fuctions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@ import BitmovinPlayer
import MediaPlayer

public struct BitMovinPlayerView: View {
internal let player: Player
private let playerViewConfig: PlayerViewConfig
// These targets are used by the MPRemoteCommandCenter,
// to remove the command event handlers from memory.
@State private var playEventTarget: Any?
@State private var pauseEventTarget: Any?

private let player: Player
private let playerViewConfig = PlayerViewConfig()
private let hlsURLString: String

private var sourceConfig: SourceConfig? {
guard let hlsURL = URL(string: hlsURLString) else {
return nil
Expand All @@ -22,7 +27,34 @@ public struct BitMovinPlayerView: View {

return sConfig
}


/// Initializes the view with the player passed from outside.
///
/// This version of the initializer does not modify the `player`'s configuration, so any additional configuration steps
/// like setting `userInterfaceConfig` should be performed externally.
///
/// - parameter hlsURLString: Full URL of the HLS video stream that will be loaded by the player as the video source
/// - parameter player: Instance of the player that was created and configured outside of this view.
/// - parameter title: Video source title that will be set in playback metadata for the "now playing" source
public init(hlsURLString: String, player: Player, title: String) {
artem-y-pamediagroup marked this conversation as resolved.
Show resolved Hide resolved

self.hlsURLString = hlsURLString

self.player = player

setup(title: title)
}

/// Initializes the view with an instance of player created inside of it, upon initialization.
///
/// In this version of the initializer, a `userInterfaceConfig` is being added to the `playerConfig`'s style configuration.
///
/// - Note: If the player config had `userInterfaceConfig` already modified before passing into this `init`,
/// those changes will take no effect sicne they will get overwritten here.
///
/// - parameter hlsURLString: Full URL of the HLS video stream that will be loaded by the player as the video source
/// - parameter playerConfig: Configuration that will be passed into the player upon creation, with an additional update in this initializer.
/// - parameter title: Video source title that will be set in playback metadata for the "now playing" source
public init(hlsURLString: String, playerConfig: PlayerConfig, title: String) {

self.hlsURLString = hlsURLString
Expand All @@ -35,21 +67,10 @@ public struct BitMovinPlayerView: View {
self.player = PlayerFactory.createPlayer(
playerConfig: playerConfig
)

// Create player view configuration
self.playerViewConfig = PlayerViewConfig()

// Setup remote control commands to be able to control playback from Control Center
setupRemoteTransportControls()

// Set playback metadata. Updates to the other metadata values are done in the specific listeners
setupNowPlayingMetadata(key: MPMediaItemPropertyTitle, value: title)

// Make sure that the correct audio session category is set to allow for background playback.
handleAudioSessionCategorySetting()

setup(title: title)
}



public var body: some View {
ZStack {
Color.black
Expand All @@ -65,7 +86,6 @@ public struct BitMovinPlayerView: View {
dump(event, name: "[Source Event]", maxDepth: 1)
}
}
// .padding()
.onAppear {
if let sourceConfig = self.sourceConfig {
player.load(sourceConfig: sourceConfig)
Expand All @@ -75,53 +95,47 @@ public struct BitMovinPlayerView: View {
removeRemoteTransportControlsAndAudioSession()
}
}

func setupRemoteTransportControls() {
// Get the shared MPRemoteCommandCenter
let commandCenter = MPRemoteCommandCenter.shared()

// Add handler for Play Command
commandCenter.playCommand.addTarget(handler: playTarget)
playEventTarget = commandCenter.playCommand.addTarget(handler: handleRemotePlayEvent)
commandCenter.playCommand.isEnabled = true

// Add handler for Pause Command
commandCenter.pauseCommand.addTarget(handler: pauseTarget)
pauseEventTarget = commandCenter.pauseCommand.addTarget(handler: handleRemotePauseEvent)
commandCenter.pauseCommand.isEnabled = true
}

/// Remove RemoteCommandCenter and AudioSession
func removeRemoteTransportControlsAndAudioSession() {
let commandCenter = MPRemoteCommandCenter.shared()

commandCenter.playCommand.isEnabled = false
commandCenter.playCommand.removeTarget(playTarget)
commandCenter.playCommand.removeTarget(playEventTarget)
playEventTarget = nil

commandCenter.pauseCommand.isEnabled = false
commandCenter.pauseCommand.removeTarget(pauseTarget)

commandCenter.pauseCommand.removeTarget(pauseEventTarget)
pauseEventTarget = nil

let sessionAV = AVAudioSession.sharedInstance()
try? sessionAV.setActive(false, options: AVAudioSession.SetActiveOptions.notifyOthersOnDeactivation)
MPNowPlayingInfoCenter.default().nowPlayingInfo = [:]
UIApplication.shared.endReceivingRemoteControlEvents()
}
/// Play Target for RemoteCommandCenter
func playTarget(_: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
guard let player = self.player as? Player else { return .commandFailed }

/// Play event handler for RemoteCommandCenter
func handleRemotePlayEvent(_: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
player.play()
if player.isPlaying {
return .success
}
return .commandFailed
return player.isPlaying ? .success : .commandFailed
}

/// Pause Target for RemoteCommandCenter
func pauseTarget(_: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
guard let player = self.player as? Player else { return .commandFailed }

/// Pause event handler for RemoteCommandCenter
func handleRemotePauseEvent(_: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
player.pause()
if player.isPaused {
return .success
}
return .commandFailed
return player.isPaused ? .success : .commandFailed
}

func setupNowPlayingMetadata(key: String, value: Any) {
Expand Down Expand Up @@ -150,6 +164,17 @@ public struct BitMovinPlayerView: View {
print("Setting category to AVAudioSessionCategoryPlayback failed.")
}
}


private func setup(title: String) {

// Setup remote control commands to be able to control playback from Control Center
setupRemoteTransportControls()

// Set playback metadata. Updates to the other metadata values are done in the specific listeners
setupNowPlayingMetadata(key: MPMediaItemPropertyTitle, value: title)

// Make sure that the correct audio session category is set to allow for background playback.
handleAudioSessionCategorySetting()
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import SwiftUI
public class BitmovinPlayerPlugin: VideoPlayerPlugin {

private let playerConfig: PlayerConfig
private var player: BitMovinPlayerView?
private weak var player: Player?

// Required properties
public let name: String
public let version: String
Expand All @@ -29,36 +29,49 @@ public class BitmovinPlayerPlugin: VideoPlayerPlugin {
self.version = "1.0.1"
}

func getPlayer() -> Player? {
return player?.player
}

// MARK: VideoPlayerPlugin protocol implementation
public func setup(config: VideoPlayerConfig) {
playerConfig.playbackConfig.isAutoplayEnabled = config.playbackConfig.autoplayEnabled
playerConfig.playbackConfig.isBackgroundPlaybackEnabled = config.playbackConfig.backgroundPlaybackEnabled

let uiConfig = BitmovinUserInterfaceConfig()
uiConfig.hideFirstFrame = true
playerConfig.styleConfig.userInterfaceConfig = uiConfig
}

public func playerView(hlsURLString: String, title: String = "") -> AnyView {
let videoPlayerView = BitMovinPlayerView(hlsURLString: hlsURLString, playerConfig: playerConfig, title: title)

self.player = videoPlayerView

return AnyView(videoPlayerView)

// Create player based on player and analytics configurations
let player = PlayerFactory.createPlayer(
playerConfig: playerConfig
)

self.player = player

return AnyView(
BitMovinPlayerView(
hlsURLString: hlsURLString,
player: player,
title: title
)
)
}

public func play() {
getPlayer()?.play()
player?.play()
}

public func pause() {
getPlayer()?.pause()
player?.pause()
}


public func unload() {
player?.unload()
}

public func removePlayer() {
player?.player.unload()
player?.player.destroy()
player = nil
player?.unload()
player?.destroy()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ public class VideoPlayerPluginManager: ObservableObject {
}

public func removePlugin() {
DispatchQueue.main.async {
self.selectedPlugin = nil
}
}
DispatchQueue.main.async {
self.selectedPlugin = nil
}
}
}

#endif
Expand Down
6 changes: 4 additions & 2 deletions Sources/PlaybackSDK/PlayerUIView/PlaybackUIView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,10 @@ internal struct PlaybackUIView: View {
switch result {
case .success(let hlsURL):
print("HLS URL: \(hlsURL)")
self.videoURL = hlsURL
self.hasFetchedVideoDetails = true
DispatchQueue.main.async {
self.videoURL = hlsURL
self.hasFetchedVideoDetails = true
}
case .failure(let error):
// Trow error to the app
onError?(error)
Expand Down