This library simplifies integrating video playback functionalities into OTT applications. It provides a unified interface for interacting with video APIs and managing playback logic.
Key Features:
- Abstraction:Â Hides the complexities of underlying video APIs, allowing you to focus on the core playback experience.
- Flexibility:Â Supports different video providers and allows the creation of custom playback plugins for extended functionalities.
- Error Handling:Â Provides mechanisms to handle potential issues during playback and notify your application.
- Platforms: iOS 14 and later
To initialize the SDK, you will need an API key, which can be obtained by contacting your StreamAMG account manager. Additionally, to use the playback default plugin, your app needs to be whitelisted. Please communicate the bundle ID of your app to your StreamAMG account manager for whitelisting.
Once you have obtained the API key and your app has been whitelisted, you can proceed with the initialisation of the SDK in your project.
- Add the Playback SDK dependency to your project using Swift Package Manager.
dependencies: [
.package(url: "https://github.com/StreamAMG/playback-sdk-ios", .branch("main"))
]- Import theÂ
PlaybackSDKÂ module in your Swift files.
import PlaybackSDKThe PlaybackSDKManager is a singleton object designed to manage the functionalities of the playback SDK. It provides methods for initialization, loading player UI, and loading HLS streams.
To initialize the playback SDK, use the initialize method of the PlaybackSDKManager singleton object. This method requires an API key for authentication. Optionally, you can specify a base URL for the playback API.
Example:
// Initialize SDK with the settings
PlaybackSDKManager.shared.initialize(apiKey: "<API_KEY>", baseURL: "<BASE_URL>") { result ->
// Register default layer plugin
switch result {
case .success(let license):
// Register default player plugin
let customPlugin = BitmovinPlayerPlugin()
VideoPlayerPluginManager.shared.registerPlugin(customPlugin)
case .failure(let error):
// Handle error as SDKError
}
}Error Handling: For information on handling potential errors during playlist loading, see the Error Handling section.
This snippet demonstrates how to configure a video player using the VideoPlayerConfig object in Swift.
The provided example sets the following playback configurations:
autoplayEnabled = true: Enables automatic playback of the video when it is loaded.backgroundPlaybackEnabled = true: Allows the video to continue playing even when the application is in the background.skipBackForwardButton: A boolean property that controls the visibility and functionality of the Skip Forward and Skip Backward buttons on the Player UI. When set totrue, these buttons are displayed on the player interface, allowing users to skip 10 seconds backward or forward in the video. If set tofalse, the buttons are hidden, and the skip functionality is disabled.fullscreenButtonEnabled: A boolean property that controls the visibility and functionality of the Fullscreen button on the Player UI. When set totrue, this button is displayed on the player interface. Once the button has been pressed, a notification on theNotificationCenterwill be fired with the namefullscreenToggleand you can observe that as the example below.
To utilize these configurations, create a VideoPlayerConfig instance and modify the desired properties within the playbackConfig object. Then, apply this configuration to your video player implementation.
// Register default player plugin
let customPlugin = BitmovinPlayerPlugin()
// Setting up player plugin
var config = VideoPlayerConfig()
config.playbackConfig.autoplayEnabled = true // Toggle autoplay
config.playbackConfig.backgroundPlaybackEnabled = true // Toggle background playback
config.playbackConfig.skipBackForwardButton = false // Toggle Skip Forward and Backward buttons
config.playbackConfig.fullscreenButtonEnabled = true // Toggle Fullscreen button
customPlugin.setup(config: config)
VideoPlayerPluginManager.shared.registerPlugin(customPlugin)
NotificationCenter.default.addObserver(forName: NSNotification.Name("fullscreenToggle"), object: nil, queue: .main) { _ in
// Put your rotation logic here
}To load the player UI in your application, use the loadPlayer method of the PlaybackSDKManager singleton object. This method is a SwiftUI view function that you can use to load and render the player UI.
Example:
PlaybackSDKManager.shared.loadPlayer(
entryID: entryId,
authorizationToken: authorizationToken
) { error in
// Handle player UI error as PlaybackAPIError
}Â Error Handling: For information on handling potential errors during playlist loading, see the Error Handling section.
To load a sequential list of videos into the player UI, use the loadPlaylist method of the PlaybackSDKManager singleton object. This method is a SwiftUI view 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:
PlaybackSDKManager.shared.loadPlaylist(
entryIDs: listEntryId,
entryIDToPlay: "0_xxxxxxxx",
authorizationToken: authorizationToken
) { errors in
// Handle player UI playlist errors as [PlaybackAPIError]
}Â Error Handling: For information on handling potential errors during playlist loading, see the Error Handling section.
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:
@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 IdTo 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:
@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)")
}
}
}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:
"\(baseURL)/sso/start?token=\(authorizationToken)"Then the same token should be passed into the loadPlaylist or 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
If the user is authenticated, has enough access level to watch a video, the session was started and the same token was passed to the player but the videos still throw a 401 error, it might be related to these requests having different user-agent headers.
If you want to allow users to access free content or if you're implementing a guest mode, you can pass an empty string or nil value as the authorizationToken parameter when calling the loadPlayer or loadPlaylist function. This will bypass the need for authentication, enabling unrestricted access to the specified content.
Sometimes a custom user-agent header is automatically set for the requests on iOS when creating a token and starting a session. Alamofire and other 3rd party networking frameworks can modify this header to include information about themselves. In such cases they should either be configured to not modify the header, or the custom header should be passed to the player as well.
Example:
PlaybackSDKManager.shared.initialize(
apiKey: apiKey,
baseURL: baseURL,
userAgent: customUserAgent
) { result in
// Handle player UI error as SDKError
}By default the SDK uses system user agent, so if your app uses native URL Session, the userAgent parameter most likely can be omitted.
The PlaybackSDKManager provides error handling through sealed classes SDKError and PlaybackAPIError. These classes represent various errors that can occur during SDK and API operations respectively.
-
SDKErrorincludes subclasses for initialization errors and missing license.initializationError: General SDK initialization failure. Occurs with configuration issues or internal problems.missingLicense: No valid license found. Occurs if no license key is provided or the key is invalid/expired.
-
PlaybackAPIErrorThis enum defines several cases, each representing a specific type of error that can occur during playback:initializationError: An error during the initialization of the Playback API.invalidResponsePlaybackData: The playback data received from the API was invalid.invalidPlaybackDataURL: The URL providing the playback data was invalid.invalidPlayerInformationURL: The URL providing player information was invalid.loadHLSStreamError: An error occurred while loading the HLS stream.networkError(Error): A network error occurred; it wraps another Error object for more detail.apiError(statusCode: Int, message: String, reason: PlaybackErrorReason): Represents API errors with specific details explained belowunknown: A generic error case for situations where the specific error type isn't known.
statusCode: API error code (400, 401, 404, etc.)message: Error description.reason: Specific error classification fromPlaybackErrorReason:headerError: Invalid or missing request headersbadRequestError: Malformed request syntaxsiteNotFound: Requested site resource doesn't existconfigurationError: Invalid backend configurationapiKeyError: Invalid or missing API keympPartnerError: Partner-specific validation failuretokenError: Invalid or expired authentication tokentooManyDevices: Device limit exceeded for accounttooManyRequests: Rate limit exceedednoEntitlement: User lacks content access rightsnoSubscription: No active subscription foundnoActiveSession: Valid viewing session not foundnotAuthenticated: General authentication failurenoEntityExist: Requested resource doesn't existunknownError(String): Unclassified error with original error string
| Code | Message | Description | Reasons |
|---|---|---|---|
| 400 | Bad Request | The request sent to the API was invalid or malformed. | headerError, badRequestError, siteNotFound, apiKeyError, mpPartnerError, configurationError |
| 401 | Unauthorized | The user is not authenticated or authorized to access the requested resource. | tokenError, tooManyDevices, tooManyRequests, noEntitlement, noSubscription, notAuthenticated, mpPartnerError, configurationError, noActiveSession |
| 403 | Forbidden | The user is not allowed to access the requested resource. | |
| 404 | Not Found | The requested resource was not found on the server. | noEntityExist |
| 440 | Login Time-out | Login session expired due to inactivity. | noActiveSession |
| 500 | Internal Server Error | An unexpected error occurred on the server. |
Handle errors based on these classes to provide appropriate feedback to users.
PlaybackSDKManager.shared.loadPlayer(entryID: entryId,
authorizationToken: authorizationToken,
onError: { error in
switch error {
case .apiError(let statusCode, let message, let reason):
switch reason {
case .noEntitlement:
errorMessage = "User lacks content access rights."
case .notAuthenticated:
errorMessage = "User is not authenticated."
default:
errorMessage = ""
}
case .networkError(let error):
errorMessage = "Network issue: \(error.localizedDescription)"
case .initializationError:
errorMessage = "Initialization failed."
default:
errorMessage = "An unknown error occurred."
}
})Additionally, the package includes a singleton object VideoPlayerPluginManager for managing video player plugins. This object allows you to register, remove, and retrieve the currently selected video player plugin.
For further details on how to use the VideoPlayerPluginManager, refer to the inline documentation provided in the code.
The Playback SDK provides methods to access and update the PlayerConfig of the Bitmovin player. This allows you to customize various aspects of the player's behavior and appearance dynamically.
To modify the player's configuration, you can use the following methods:
updatePlayerConfig(_ newConfig: PlayerConfig): Updates the current player configuration with a new PlayerConfig object.getPlayerConfig() -> PlayerConfig: Retrieves the current player configuration. These methods enable you to adjust settings such as playback options, style configurations, and more, ensuring flexibility for your integration.
Example:
import PlaybackSDK
import BitmovinPlayer
let bitmovinPlugin = BitmovinPlayerPlugin()
let myPlayerConfig = PlayerConfig()
// Disable Default Player UI
myPlayerConfig.styleConfig.isUiEnabled = false
bitmovinPlugin.updatePlayerConfig(myPlayerConfig)Currently SDK supports tracking analytics on Bitmovin service. In case you have a logged-in user and want to track Bitmovin analytics for the current session, you need to pass the user's ID in the analyticsViewerId parameter.
Example:
let entryId = "..."
let authorizationToken = "..."
let analyticsViewerId = "user id or empty string"
/// ** Load player with the playback SDK **
PlaybackSDKManager.shared.loadPlayer(entryID: entryId,
authorizationToken: authorizationToken,
analyticsViewerId: analyticsViewerId,
onError: {
error in
// Handle the error as PlaybackAPIError
})If you still need to track analytics with the playlist functionality, you can pass the user's ID in the analyticsViewerId parameter.
private let entryIDs = ["ENTRY_ID1", "ENTRY_ID_2", "ENTRY_ID_3"]
private let entryIDToPlay = "ENTRY_ID_2" // Optional parameter
private let authorizationToken = "JWT_TOKEN"
let analyticsViewerId = "user id or empty string"
var body: some View {
VStack {
// Load playlist with the playback SDK
PlaybackSDKManager.shared.loadPlaylist(entryIDs: entryIDs,
entryIDToPlay: entryIDToPlay,
authorizationToken: authorizationToken,
analyticsViewerId: analyticsViewerId) {
errors in
// Handle Errors as [PlaybackAPIError]
handlePlaybackError(errors)
}
.onDisappear {
// Remove the player here
}
Spacer()
}
.padding()
}- Demo app: GitHub Repository
- Stoplight API documentation: Documentation
- Tutorial (deprecated): Tutorial