Skip to content

Commit

Permalink
Merge branch 'main' into feature/ZEUS-4454-Fix-tests-for-PlaybackSDK-iOS
Browse files Browse the repository at this point in the history
  • Loading branch information
KharchenkoAlex authored Jul 2, 2024
2 parents cc83fd4 + d54667d commit 78fd2c6
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 13 deletions.
4 changes: 2 additions & 2 deletions Sources/PlaybackSDK/PlayBackSDKManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,8 @@ public class PlayBackSDKManager {
```swift
let playerView = loadPlayer(entryID: "exampleEntryID", authorizationToken: "exampleToken")
*/
public func loadPlayer(entryID: String, authorizationToken: String? = nil, onError: ((PlayBackAPIError) -> Void)?) -> some View {
return PlaybackUIView(entryId: entryID, authorizationToken: authorizationToken, onError: onError)
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)
}

// MARK: Private fuctions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#if !os(macOS)
import SwiftUI
import BitmovinPlayer
import MediaPlayer

public struct BitMovinPlayerView: View {
internal let player: Player
Expand All @@ -17,10 +18,12 @@ public struct BitMovinPlayerView: View {
guard let hlsURL = URL(string: hlsURLString) else {
return nil
}
return SourceConfig(url: hlsURL, type: .hls)
let sConfig = SourceConfig(url: hlsURL, type: .hls)

return sConfig
}

public init(hlsURLString: String, playerConfig: PlayerConfig) {
public init(hlsURLString: String, playerConfig: PlayerConfig, title: String) {

self.hlsURLString = hlsURLString

Expand All @@ -35,6 +38,15 @@ public struct BitMovinPlayerView: View {

// 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()
}


Expand All @@ -59,6 +71,85 @@ public struct BitMovinPlayerView: View {
player.load(sourceConfig: sourceConfig)
}
}
.onDisappear {
removeRemoteTransportControlsAndAudioSession()
}
}

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

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

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

/// Remove RemoteCommandCenter and AudioSession
func removeRemoteTransportControlsAndAudioSession() {
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.isEnabled = false
commandCenter.playCommand.removeTarget(playTarget)
commandCenter.pauseCommand.isEnabled = false
commandCenter.pauseCommand.removeTarget(pauseTarget)

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 }

player.play()
if player.isPlaying {
return .success
}
return .commandFailed
}

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

player.pause()
if player.isPaused {
return .success
}
return .commandFailed
}

func setupNowPlayingMetadata(key: String, value: Any) {
var nowPlayingInfo: [String: Any] = [:]
nowPlayingInfo[key] = value
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
UIApplication.shared.beginReceivingRemoteControlEvents()
}

/* Set AVAudioSessionCategoryPlayback category on the audio session. This category indicates that audio playback
is a central feature of your app. When you specify this category, your app’s audio continues with the Ring/Silent
switch set to silent mode (iOS only). With this category, your app can also play background audio if you're
using the Audio, AirPlay, and Picture in Picture background mode. To enable this mode, under the Capabilities
tab in your XCode project, set the Background Modes switch to ON and select the “Audio, AirPlay, and Picture in
Picture” option under the list of available modes. */
func handleAudioSessionCategorySetting() {
let audioSession = AVAudioSession.sharedInstance()

// When AVAudioSessionCategoryPlayback is already active, we have nothing to do here
guard audioSession.category.rawValue != AVAudioSession.Category.playback.rawValue else { return }

do {
try audioSession.setCategory(AVAudioSession.Category.playback, mode: AVAudioSession.Mode.moviePlayback)
try audioSession.setActive(true)
} catch {
print("Setting category to AVAudioSessionCategoryPlayback failed.")
}
}

}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class BitmovinPlayerPlugin: VideoPlayerPlugin {
let playerConfig = PlayerConfig()

playerConfig.playbackConfig.isAutoplayEnabled = true
playerConfig.playbackConfig.isBackgroundPlaybackEnabled = true

playerConfig.key = PlayBackSDKManager.shared.bitmovinLicense
self.playerConfig = playerConfig
Expand All @@ -33,13 +34,13 @@ public class BitmovinPlayerPlugin: VideoPlayerPlugin {
}

// MARK: VideoPlayerPlugin protocol implementation
public func setup() {
// Additional setup logic if needed, not required in this case
// Might call here the configuration API
public func setup(config: VideoPlayerConfig) {
playerConfig.playbackConfig.isAutoplayEnabled = config.playbackConfig.autoplayEnabled
playerConfig.playbackConfig.isBackgroundPlaybackEnabled = config.playbackConfig.backgroundPlaybackEnabled
}

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

self.player = videoPlayerView

Expand All @@ -55,6 +56,7 @@ public class BitmovinPlayerPlugin: VideoPlayerPlugin {
}

public func removePlayer() {
player?.player.unload()
player?.player.destroy()
player = nil
}
Expand Down
10 changes: 10 additions & 0 deletions Sources/PlaybackSDK/Player Plugin/VideoPlayerConfig.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
public struct VideoPlayerConfig {
public var playbackConfig = PlaybackConfig()

public init() {}
}

public struct PlaybackConfig {
public var autoplayEnabled: Bool = true
public var backgroundPlaybackEnabled: Bool = true
}
4 changes: 2 additions & 2 deletions Sources/PlaybackSDK/Player Plugin/VideoPlayerPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ public protocol VideoPlayerPlugin: AnyObject {
var name: String { get }
var version: String { get }

func setup()
func setup(config: VideoPlayerConfig)

// TODO: add event
/// func handleEvent(event: BitmovinPlayerCore.PlayerEvent)

func playerView(hlsURLString: String) -> AnyView
func playerView(hlsURLString: String, title: String) -> AnyView

func play()

Expand Down
8 changes: 6 additions & 2 deletions Sources/PlaybackSDK/PlayerUIView/PlaybackUIView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ internal struct PlaybackUIView: View {
/// The entry ID of the video to be played.
private var entryId: String

/// The title of the video to be played.
private var mediaTitle: String?

/// Optional authorization token if required to fetch the video details.
private var authorizationToken: String?

Expand All @@ -37,10 +40,11 @@ internal struct PlaybackUIView: View {
- entryId: The entry ID of the video to be played.
- authorizationToken: Optional authorization token if required to fetch the video details.
*/
internal init(entryId: String, authorizationToken: String?, onError: ((PlayBackAPIError) -> Void)?) {
internal init(entryId: String, authorizationToken: String?, mediaTitle: String?, onError: ((PlayBackAPIError) -> Void)?) {
self.entryId = entryId
self.authorizationToken = authorizationToken
self.onError = onError
self.mediaTitle = mediaTitle
}

/// The body of the view.
Expand All @@ -54,7 +58,7 @@ internal struct PlaybackUIView: View {
} else {
if let videoURL = videoURL {
if let plugin = pluginManager.selectedPlugin {
plugin.playerView(hlsURLString: videoURL.absoluteString)
plugin.playerView(hlsURLString: videoURL.absoluteString, title: self.mediaTitle ?? "")
} else {
ErrorUIView(errorMessage: "No plugin selected")
.background(Color.white)
Expand Down

0 comments on commit 78fd2c6

Please sign in to comment.