Video Player
This guide will walk you through the steps to start a BlendVision One streaming playback using the iOS player SDK.
Additionally, you can also check out the sample project for an even smoother start: https://github.com/BlendVision/iOS-Player-SDK/tree/main/Samples
Prerequisites
- A valid BlendVision Player License Key
Step 1: Initialize the Player
Install Player SDK
You can install the BlendVision iOS Player SDK token in two ways:
- Using Swift Package Manager
- Adding the Player SDK to the Project directly
Swift Package Manager is a tool for managing the distribution of Swift frameworks. It integrates with the Swift build system to automate the process of downloading, compiling, and linking dependencies.
Using Xcode
To integrate using Xcode 13, open your Project file and specify it in Project > Package
Dependencies using the following URL: https://github.com/BlendVision/bvplayer-ios
Using Package.swift
To integrate using Apple's Swift Package Manager, add the following as a dependency to your Package.swift
and replace Version Number
with the desired version of the SDK.
.package(name: "BVPlayer", url: "https://github.com/BlendVision/bvplayer-ios", .exact("Version Number"))
And then specify the BVPlayer as a dependency of the desired target. Here is an example of a Package.swift file:
let package = Package(
...
dependencies: [
...
.package(name: "BVPlayer", url: "https://github.com/BlendVision/bvplayer-ios", .exact("Version Number"))
],
targets: [
.target(name: "<NAME_OF_YOUR_PACKAGE>", dependencies: ["BVPlayer"])
]
...
)
Executing swift build
from the command line is currently not supported. Open the Package in Xcode if you are developing another Package depending on BVPlayer
.
KKSPlayer.xcframework
is not suppoeted in Simulator arm64 (M1).
Download and unzip the appropriate dynamic SDK for your project from https://github.com/BlendVision/iOS-Player-SDK/releases.
In your Xcode target, go to the
General
page or your app target and add the SDK bundleKKSPlayer.xcframework
under Linked Frameworks and Libraries.
Create a Player
To play the streaming content, create a player instance with the license key and stream information in the initialization configuration:
// Create player configuration
let playerConfig = UniPlayerConfig()
playerConfig.key = "Your player license key"
// Create player based on player config
let player = UniPlayerFactory.create(player: playerConfig)
// Create player view and pass the player instance to it
playerView = UniPlayerView(player: player, frame: .zero)
// Add player view into subview and layout your player view
view.addSubview(playerView)
view.bringSubviewToFront(playerView)
// Handle HLS stream
guard let hlsUrl = URL(string: "Your HLS URL string") else {
debugPrint("The HLS URL not found")
return
}
// Create source config
let sourceConfig = UniSourceConfig(url: hlsUrl, type: .hls)
sourceConfig.title = "your title"
sourceConfig.description = "your description"
// Load source config
player.load(sourceConfig: sourceConfig)
How to create your player view: https://developer.apple.com/documentation/avfoundation/avplayerlayer
Disable the Application Idle Timer
To prevent device sleep and ensure an uninterrupted experience, disable the Application Idle Timer:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIApplication.shared.isIdleTimerDisabled = true
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
UIApplication.shared.isIdleTimerDisabled = false
}
Step 2: Obtain a Playback Token
Make a POST request to the API: POST /bv/cms/v1/tokens
Query Parameters | Required | Description |
---|---|---|
resource_id | Required | The unique ID of your content. You can retrieve this in two ways: 1. Using BlendVision One CMS - VOD > Publishing Tools - Live > Publishing Tools 2. Using BlendVision One API - VOD: GET /bv/cms/v1/vods - Live: GET /bv/cms/v1/lives |
resource_type | Required | The streaming type of your content. |
customer_id | Optional | Your custom user ID to identify who is watching the content. This parameter is required if the content's security privacy is set to SECURITY_PRIVACY_TYPE_TOKEN . |
let API_DOMAIN = "https://api.one.blendvision.com"
struct PlaybackTokenResponse: Codable {
let playbackToken: String
}
protocol ApiService {
func getPlaybackToken(resourceId: String, resourceType: String, customerId: String?, completion: @escaping (Result<PlaybackTokenResponse, Error>) -> Void)
}
extension ApiService {
func getPlaybackToken(resourceId: String, resourceType: String, customerId: String? = nil, completion: @escaping (Result<PlaybackTokenResponse, Error>) -> Void) {
guard let url = URL(string: API_DOMAIN + "/bv/cms/v1/tokens") else {
// Handle invalid URL error
return
}
var parameters: [String: Any]
if let customerId = customerId {
parameters = [
"resource_id": resourceId,
"resource_type": resourceType,
"customer_id": customerId
]
} else {
parameters = [
"resource_id": resourceId,
"resource_type": resourceType
]
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: [])
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
// Handle network error
completion(.failure(error))
return
}
guard let data = data else {
// Handle empty response data
return
}
do {
let response = try JSONDecoder().decode(PlaybackTokenResponse.self, from: data)
completion(.success(response))
} catch {
// Handle decoding error
completion(.failure(error))
}
}.resume()
}
}
Step 3: Start a Playback Session
Start a playback session and obtain the stream data using the APIs:
Replace the sample URLs in Step 1 with the obtained manifest_url
(hlsUrl
) for playback.
let API_DOMAIN = "https://api.one.blendvision.com"
struct StartSessionResponse: Codable {
let endPoint: String
}
struct GetStreamInfoResponse: Codable {
let sources: [Source]
}
struct Source: Codable {
let manifests: [Manifest]
let thumbnailSeekingUrl: String
}
struct Manifest: Codable {
let protocal: String
let url: String
let resolutions: [Resolution]
}
struct Resolution: Codable {
let height: String
let width: String
}
protocol ApiService {
func startPlaybackSession(playbackToken: String, deviceId: String, completion: @escaping (Result<StartSessionResponse, Error>) -> Void)
func getStreamInfo(playbackToken: String, deviceId: String, completion: @escaping (Result<GetStreamInfoResponse, Error>) -> Void)
}
extension ApiService {
func startPlaybackSession(playbackToken: String, deviceId: String, completion: @escaping (Result<StartSessionResponse, Error>) -> Void) {
guard let url = URL(string: "\(API_DOMAIN)/bv/playback/v1/sessions/\(deviceId):start") else {
// Handle invalid URL error
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(playbackToken, forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
// Handle network error
completion(.failure(error))
return
}
guard let data = data else {
// Handle empty response data
return
}
do {
let response = try JSONDecoder().decode(StartSessionResponse.self, from: data)
completion(.success(response))
} catch {
// Handle decoding error
completion(.failure(error))
}
}.resume()
}
func getStreamInfo(playbackToken: String, deviceId: String, completion: @escaping (Result<GetStreamInfoResponse, Error>) -> Void) {
guard let url = URL(string: "\(API_DOMAIN)/bv/playback/v1/sessions/\(deviceId)") else {
// Handle invalid URL error
return
}
var request = URLRequest(url: url)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(playbackToken, forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
// Handle network error
completion(.failure(error))
return
}
guard let data = data else {
// Handle empty response data
return
}
do {
let response = try JSONDecoder().decode(GetStreamInfoResponse.self, from: data)
completion(.success(response))
} catch {
// Handle decoding error
completion(.failure(error))
}
}.resume()
}
}
Step 4: Manage the Lifecycle of a Playback Session
To keep the playback session alive, periodically invoke the heartbeat API every 10 seconds:
POST /bv/playback/v1/sessions/:device_id:heartbeat
func startHeartbeatTimer(deviceId: String, headers: [String: String], player: UniPlayer) {
let heartbeatTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { _ in
guard let heartbeatURL = URL(string: "https://api.one.blendvision.com/bv/playback/v1/sessions/\(deviceId):heartbeat") else {
// Handle invalid URL error
return
}
var heartbeatRequest = URLRequest(url: heartbeatURL)
heartbeatRequest.allHTTPHeaderFields = headers
URLSession.shared.dataTask(with: heartbeatRequest) { data, response, error in
if let error = error {
// Handle network error
return
}
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
// Handle player release
player.destroy()
return
}
}.resume()
}
// Start the timer
heartbeatTimer.fire()
}
func endPlayback(deviceId: String, headers: [String: String]) {
guard let endPlaybackURL = URL(string: "https://api.one.blendvision.com/bv/playback/v1/sessions/\(deviceId):end") else {
// Handle invalid URL error
return
}
var endPlaybackRequest = URLRequest(url: endPlaybackURL)
endPlaybackRequest.allHTTPHeaderFields = headers
URLSession.shared.dataTask(with: endPlaybackRequest) { data, response, error in
if let error = error {
// Handle network error
return
}
// Handle successful end of playback
}.resume()
}