Skip to main content

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


Step 1: Initialize the Player

Install Player SDK

You can install the BlendVision iOS Player SDK token in two ways:

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"])
]
...
)
Limitation

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.

Limitation

KKSPlayer.xcframework is not suppoeted in Simulator arm64 (M1).


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)

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 ParametersRequiredDescription
resource_idRequiredThe 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_typeRequiredThe streaming type of your content.
customer_idOptionalYour 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()
}


Workflow Sequence Diagram