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

Release/1.3.0 #31

Merged
merged 31 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9d13de1
CORE-4594 Playlist API integration
Sep 27, 2024
5327ff7
CORE-4594 Playlist API integration
Oct 1, 2024
90577b5
CORE-4594 Playlist API integration
Oct 3, 2024
ea0c7d9
CORE-4594 Playlist API integration
Oct 9, 2024
906752f
CORE-4594 Playlist API integration
Oct 23, 2024
0f6505a
CORE-4594 Fixing test
Oct 25, 2024
0365480
Update Sources/PlaybackSDK/PlayerUIView/PlaybackUIView.swift
StefanoStream Oct 28, 2024
6d2286b
Update Sources/PlaybackSDK/Player Plugin/BitMovinPlugin/BitmovinPlaye…
StefanoStream Oct 28, 2024
96f282b
Update Sources/PlaybackSDK/Player Plugin/BitMovinPlugin/BitmovinPlaye…
StefanoStream Oct 28, 2024
c177c24
Playlist unit test cases
Oct 28, 2024
349ce61
CORE-4594 Playlist API integration
Oct 28, 2024
0b6e45b
Merge branch 'feature/playlist_unit-test-cases' into feature/CORE-459…
Oct 28, 2024
be5c99b
CORE-5100 CORE-5085 Playlist documentation
Nov 1, 2024
c576a85
CORE-4594 Playlist API integration
StefanoStream Nov 13, 2024
a30b267
Merge branch 'feature/CORE-4594-iOS-Playlist-API-integration' into fe…
StefanoStream Nov 14, 2024
7280775
CORE-5100 Playlist documentation
StefanoStream Nov 21, 2024
be2019b
CORE-4594 Playlist API integration
StefanoStream Nov 21, 2024
858ba2e
CORE-5100 Playlist documentation
StefanoStream Nov 27, 2024
2ab5b3c
CORE-4594 Playlist API integration
StefanoStream Nov 28, 2024
f224ab9
CORE-5100 Playlist documentation
StefanoStream Nov 28, 2024
ad6d29e
CORE-4594 Playlist API integration
StefanoStream Nov 29, 2024
c7c2b4a
CORE-5100 Playlist documentation
StefanoStream Nov 29, 2024
8170e41
Merge pull request #29 from StreamAMG/feature/CORE-5100-Playlist-docu…
StefanoStream Nov 29, 2024
7a2b39f
CORE-4594 Playlist API integration
StefanoStream Nov 29, 2024
0029ac4
CORE-4594 Playlist API integration
StefanoStream Dec 2, 2024
4760107
CORE-4594 Playback API integration
StefanoStream Dec 2, 2024
7dcebf7
CORE-4594 Playback API integration
StefanoStream Dec 2, 2024
494db35
CORE-4594 Playlist API integration
StefanoStream Dec 2, 2024
2318ff7
CORE-4594 Playlist API integration
StefanoStream Dec 5, 2024
b351d10
CORE-4594 Playlist API integration
StefanoStream Dec 5, 2024
a014b9e
Merge pull request #26 from StreamAMG/feature/CORE-4594-iOS-Playlist-…
StefanoStream Dec 5, 2024
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
93 changes: 89 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,14 @@ Example:

switch result {
case .success(let license):
val customPlugin = BitmovinVideoPlayerPlugin()
let customPlugin = BitmovinPlayerPlugin()

// Setting up player plugin
var config = VideoPlayerConfig()
config.playbackConfig.autoplayEnabled = true // Toggle autoplay
config.playbackConfig.backgroundPlaybackEnabled = true // Toggle background playback
customPlugin.setup(config: config)

VideoPlayerPluginManager.shared.registerPlugin(customPlugin)
case .failure(let error):
// Handle error
Expand All @@ -78,17 +85,95 @@ To load the player UI in your application, use the `loadPlayer` method of the `P
Example:

```swift
PlayBackSDKManager.shared.loadPlayer(entryID: entryId, authorizationToken: authorizationToken) { error in
PlaybackSDKManager.shared.loadPlayer(
entryID: entryId,
authorizationToken: authorizationToken
) { error in
// Handle player UI error 
```

# Loading a Playlist

To load a sequential list of videos into the player UI, use the `loadPlaylist` method of the `PlaybackSDKManager` singleton object. This method is a Composable function that you can use to load and render the player UI.
`entryIDs`: An array of Strings containing the unique identifiers of all the videos in the playlist.
`entryIDToPlay`: (Optional) Specifies the unique video identifier that will be played first in the playlist. If not provided, the first video in the `entryIDs` array will be played.

Example:

```swift
PlaybackSDKManager.shared.loadPlaylist(
entryIDs: listEntryId,
entryIDToPlay: "0_xxxxxxxx",
authorizationToken: authorizationToken
) { errors in
// Handle player UI playlist errors
```

## Controlling Playlist Playback
To control playlist playback, declare a VideoPlayerPluginManager singleton instance as a @StateObject variable. This allows you to access various control functions and retrieve information about the current playback state.

Here are some of the key functions you can utilize:

`playFirst()`: Plays the first video in the playlist.
`playPrevious()`: Plays the previous video in the playlist.
`playNext()`: Plays the next video in the playlist.
`playLast()`: Plays the last video in the playlist.
`seek(entryIdToSeek)`: Seek a specific video Id
`activeEntryId()`: Returns the unique identifier of the currently playing video.

By effectively leveraging these functions, you can create dynamic and interactive video player experiences.

Example:

```swift
@StateObject private var pluginManager = VideoPlayerPluginManager.shared
...
// You can use the following playlist controls
pluginManager.selectedPlugin?.playFirst() // Play the first video
pluginManager.selectedPlugin?.playPrevious() // Play the previous video
pluginManager.selectedPlugin?.playNext() // Play the next video
pluginManager.selectedPlugin?.playLast() // Play the last video
pluginManager.selectedPlugin?.seek(entryIdToSeek) { success in // Seek a specific video
if (!success) {
let errorMessage = "Unable to seek video Id \(entryIdToSeek)"
}
}
pluginManager.selectedPlugin?.activeEntryId() // Get the active video Id
```

## Receiving Playlist Events
To receive playlist events, declare a VideoPlayerPluginManager singleton instance, similar to how you did in the Controlling Playlist Playback section.
Utilize the `onReceive` modifier to listen for player events, such as the `PlaylistTransitionEvent`. This event provides information about the transition from one video to another.

Example:

```swift
@StateObject private var pluginManager = VideoPlayerPluginManager.shared
...
PlaybackSDKManager.shared.loadPlaylist(
entryIDs: entryIDs,
entryIDToPlay: entryIDToPlay,
authorizationToken: authorizationToken
) { errors in
...
}
.onReceive(pluginManager.selectedPlugin!.event) { event in
if let event = event as? PlaylistTransitionEvent { // Playlist Event
if let from = event.from.metadata?["entryId"], let to = event.to.metadata?["entryId"] {
print("Playlist event changed from \(from) to \(to)")
}
}
}
```

# Playing Access-Controlled Content
To play on-demand and live videos that require authorization, at some point before loading the player your app must call CloudPay to start session, passing the authorization token:
```swift
"\(baseURL)/sso/start?token=\(authorizationToken)"
```
Then the same token should be passed into the `loadPlayer(entryID:, authorizationToken:)` method of `PlayBackSDkManager`.
Then the same token should be passed into the `loadPlayer(entryID:, authorizationToken:)` method of `PlaybackSDkManager`.
For the free videos that user should be able to watch without logging in, starting the session is not required and `authorizationToken` can be set to an empty string.

> [!NOTE]
Expand All @@ -100,7 +185,7 @@ Sometimes a custom `user-agent` header is automatically set for the requests on
Example:

```swift
PlayBackSDKManager.shared.initialize(
PlaybackSDKManager.shared.initialize(
apiKey: apiKey,
baseURL: baseURL,
userAgent: customUserAgent
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

PlayBackSDKManager.shared.initialize(apiKey: settingsManager.apiKey, baseURL: settingsManager.baseURL) { result in
PlaybackSDKManager.shared.initialize(apiKey: settingsManager.apiKey, baseURL: settingsManager.baseURL) { result in
switch result {
case .success(let license):
print("SDK initialized with license: \(license)")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

PlayBackSDKManager.shared.loadPlayer(entryID: settingsManager.entryId, authorizationToken: settingsManager.authorizationToken, onError: { error in
PlaybackSDKManager.shared.loadPlayer(entryID: settingsManager.entryId, authorizationToken: settingsManager.authorizationToken, onError: { error in
// Handle the error here
switch error {
case .apiError(let statusCode, _):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import SwiftUI
import PlaybackSDK

@main
struct PlayBackDemoApp: App {
struct PlaybackDemoApp: App {

let sdkManager = PlayBackSDKManager()
let sdkManager = PlaybackSDKManager()
let apiKey = "API_KEY"
var body: some Scene {
WindowGroup {
Expand All @@ -14,14 +14,21 @@ struct PlayBackDemoApp: App {

init() {
// Initialize the Playback SDK with the provided API key and base URL
PlayBackSDKManager.shared.initialize(apiKey: apiKey) { result in
PlaybackSDKManager.shared.initialize(apiKey: apiKey) { result in
switch result {
case .success(let license):
// Obtained license upon successful initialization
print("SDK initialized with license: \(license)")

// Register the video player plugin
let bitmovinPlugin = BitmovinPlayerPlugin()

// Setting up player plugin
var config = VideoPlayerConfig()
config.playbackConfig.autoplayEnabled = true // Toggle autoplay
config.playbackConfig.backgroundPlaybackEnabled = true // Toggle background playback
bitmovinPlugin.setup(config: config)

VideoPlayerPluginManager.shared.registerPlugin(bitmovinPlugin)

case .failure(let error):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import PlaybackSDK
import Alamofire

@main
struct PlayBackDemoApp: App {
struct PlaybackDemoApp: App {

let sdkManager = PlayBackSDKManager()
let sdkManager = PlaybackSDKManager()
let apiKey = "API_KEY"
var body: some Scene {
WindowGroup {
Expand All @@ -18,14 +18,21 @@ struct PlayBackDemoApp: App {
let userAgent = AF.session.configuration.httpAdditionalHeaders?["User-Agent"]

// Initialize the Playback SDK with the provided API key and custom user-agent
PlayBackSDKManager.shared.initialize(apiKey: apiKey, userAgent: userAgent) { result in
PlaybackSDKManager.shared.initialize(apiKey: apiKey, userAgent: userAgent) { result in
switch result {
case .success(let license):
// Obtained license upon successful initialization
print("SDK initialized with license: \(license)")

// Register the video player plugin
let bitmovinPlugin = BitmovinPlayerPlugin()

// Setting up player plugin
var config = VideoPlayerConfig()
config.playbackConfig.autoplayEnabled = true // Toggle autoplay
config.playbackConfig.backgroundPlaybackEnabled = true // Toggle background playback
bitmovinPlugin.setup(config: config)

VideoPlayerPluginManager.shared.registerPlugin(bitmovinPlugin)

case .failure(let error):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import SwiftUI
import PlaybackSDK

struct PlayerTestPlaylistControlsAndEventsView: View {

@StateObject private var pluginManager = VideoPlayerPluginManager.shared
private let entryIDs = ["ENTRY_ID1", "ENTRY_ID_2", "ENTRY_ID_3"]
private let entryIDToPlay = "ENTRY_ID_2" // Optional parameter
private let entryIdToSeek = "ENTRY_ID_TO_SEEK"
private let authorizationToken = "JWT_TOKEN"

var body: some View {
VStack {
// Load playlist with the playback SDK
PlaybackSDKManager.shared.loadPlaylist(entryIDs: entryIDs, entryIDToPlay: entryIDToPlay, authorizationToken: authorizationToken) { errors in
handlePlaybackError(errors)
}
.onReceive(pluginManager.selectedPlugin!.event) { event in
if let event = event as? PlaylistTransitionEvent { // Playlist Event
if let from = event.from.metadata?["entryId"], let to = event.to.metadata?["entryId"] {
print("Playlist event changed from \(from) to \(to)")
}
}
}
.onDisappear {
// Remove the player here
}

Spacer()

Button {
// You can use the following playlist controls
pluginManager.selectedPlugin?.playFirst() // Play the first video
pluginManager.selectedPlugin?.playPrevious() // Play the previous video
pluginManager.selectedPlugin?.playNext() // Play the next video
pluginManager.selectedPlugin?.playLast() // Play the last video
pluginManager.selectedPlugin?.seek(entryIdToSeek) { success in // Seek a specific video
if (!success) {
let errorMessage = "Unable to seek video Id \(entryIdToSeek)"
}
}
pluginManager.selectedPlugin?.activeEntryId() // Get the active video Id
} label: {
Image(systemName: "list.triangle")
}

Spacer()
}
.padding()
}

private func handlePlaybackErrors(_ errors: [PlaybackAPIError]) {

for error in errors {
switch error {
case .apiError(let statusCode, let message, let reason):
let message = "\(message) Status Code \(statusCode), Reason: \(reason)"
print(message)
default:
print("Error code and errorrMessage not found: \(error.localizedDescription)")
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import SwiftUI
import PlaybackSDK

struct PlayerTestPlaylistView: View {

private let entryIDs = ["ENTRY_ID1", "ENTRY_ID_2", "ENTRY_ID_3"]
private let entryIDToPlay = "ENTRY_ID_2" // Optional parameter
private let authorizationToken = "JWT_TOKEN"

var body: some View {
VStack {
// Load playlist with the playback SDK
PlaybackSDKManager.shared.loadPlaylist(entryIDs: entryIDs, entryIDToPlay: entryIDToPlay, authorizationToken: authorizationToken) { errors in
handlePlaybackError(errors)
}
.onDisappear {
// Remove the player here
}
Spacer()
}
.padding()
}

private func handlePlaybackErrors(_ errors: [PlaybackAPIError]) {

for error in errors {
switch error {
case .apiError(let statusCode, let message, let reason):
let message = "\(message) Status Code \(statusCode), Reason: \(reason)"
print(message)
default:
print("Error code and errorrMessage not found: \(error.localizedDescription)")
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ struct PlayerTestView: View {
var body: some View {
VStack {
// Load player with the playback SDK
PlayBackSDKManager.shared.loadPlayer(entryID: entryID, authorizationToken: authorizationToken) { error in
PlaybackSDKManager.shared.loadPlayer(entryID: entryID, authorizationToken: authorizationToken) { error in
handlePlaybackError(error)
}
.onDisappear {
Expand All @@ -20,7 +20,7 @@ struct PlayerTestView: View {
.padding()
}

private func handlePlaybackError(_ error: PlaybackError) {
private func handlePlaybackError(_ error: PlaybackAPIError) {
switch error {
case .apiError(let statusCode, let errorMessage, let reason):
print("\(errorMessage) Status Code \(statusCode)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@
@Steps {

@Step {
Initialize the Playback SDK by providing your API key and register the default player plugin.
Initialize the Playback SDK by providing your API key, setup and register the default player plugin.
**Make sure this step is done when the app starts.**


@Code(name: "PlayBackDemoApp.swift", file: PlayBackDemoApp.swift)
@Code(name: "PlaybackDemoApp.swift", file: PlaybackDemoApp.swift)
}
@Step {
Add custom `user-agent` header.

This step is only required for content that needs a token, when using Alamofire or other 3rd party frameworks that overwrite the standard `user-agent` header with their own.
If the content requires starting a CloudPay session, it's important that the request to start the session has the same `user-agent` header as the video loading requests from the player. This can be achieved either by disabling the overwriting behaviour in the 3rd party networking framework you're using, or by passing a `userAgent` parameter to the `initialize` method, like in this example with Alamofire.
@Code(name: "PlayBackDemoAppWithUserAgent.swift", file: PlayBackDemoAppWithUserAgent.swift, previousFile: PlayBackDemoApp.swift)
@Code(name: "PlaybackDemoAppWithUserAgent.swift", file: PlaybackDemoAppWithUserAgent.swift, previousFile: PlaybackDemoApp.swift)
}
@Step {
Load the player using the Playback SDK and handle any playback errors.
Expand All @@ -40,11 +40,30 @@

@Code(name: "PlayerTestView.swift", file: PlayerTestView.swift)
}
@Step {
Load the player passing a playlist using the Playback SDK and handle any playlist errors.

To load a playlist and handle errors, use the **loadPlaylist** function provided by the Playback SDK to initialize and load the video player. This function takes an array of entry IDs, the starting entry ID, and an authorization token as parameters. Additionally, it includes a closure to handle any potential playlist errors that may occur during the loading process.
The **handlePlaybackErrors** function is called within the closure to handle the playlist errors. It iterates through an array of **PlaybackError** objects and, for each error, switches on the error type to provide appropriate error handling.
The code also includes a placeholder comment to indicate where the removal of the player can be implemented in the **onDisappear** modifier.
If you want to allow users to access free content or implement a guest mode, you can pass an empty string or **nil** value as the **authorizationToken** when calling the **loadPlaylist** function. This will bypass the need for authentication, enabling unrestricted access to the specified content.

@Code(name: "PlayerTestPlaylistView.swift", file: PlayerTestPlaylistView.swift)
}
@Step {
Playlist controls and events

To control playlist playback and events, declare a **VideoPlayerPluginManager** singleton instance as a **@StateObject** variable. This allows you to access playlist controls and listen to player events.
In the **onReceive** modifier, you can listen to player events such as the **PlaylistTransitionEvent**, which provides information about transitions between videos.
Through the **pluginManager.selectedPlugin**, you can interact with playlist controls and retrieve the current video ID using the **activeEntryId** function.

@Code(name: "PlayerTestPlaylistControlsAndEventsView.swift", file: PlayerTestPlaylistControlsAndEventsView.swift, previousFile: PlayerTestPlaylistView.swift)
}
@Step {
Handle the playback errors from Playback SDK.

This step describes enum for error handling. Above is the error enum returned by the SDK, where the apiError also has the reason code and message for the API error. The playback API is returning the reason code in the response. For the list of the error codes and reasons, please refer to [Get Video Playback Data | Playback](https://streamamg.stoplight.io/docs/playback-documentation-portal/ec642e6dcbb13-get-video-playback-data)
@Code(name: "PlayBackAPIError.swift", file: PlayBackAPIError.swift)
@Code(name: "PlaybackAPIError.swift", file: PlaybackAPIError.swift)
}
}
}
Expand Down
Loading
Loading