iOS Player SDK

Quickstart: Playback a BlendVision One Stream

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

 

Before We Start

Before proceeding, ensure that you have prepared your BlendVision One streaming content. You can prepare your content in two ways:

Step 1: Obtain a Playback Token

  • Obtain a valid API token by following the authentication steps
  • Retrieve the resource_id and resource_type of the streaming content you would like to playback. There are two ways to obtain this information:
    • Retrieve using the API
    • Copy from your BlendVision One console
      • VOD > Publishing Tools > Player SDK > VOD ID
      • Live > Publishing Tools > Player SDK > Event ID

  • Obtain the playback token using the API: /bv/cms/v1/tokens, with the resource_id and resource_type as body parameters

    • You can also include customer_id in the body parameters if you want to track user playback with your own defined user ID

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 2: Start a Playback Session

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 3: Initialize the Player

Obtain Player License Key

Obtain a valid player license key from your BlendVision One console

Install Player SDK

  1. Download and unzip the appropriate dynamic SDK for your project from https://github.com/BlendVision/iOS-Player-SDK/releases.

  2. Drag the unzipped .framework or .xcframework into your main project in the Xcode project navigator. Check 'Copy all items if needed', and add to all targets.

  3. In your Xcode target, under the General tab, select Embed and Sign for KKSPlayer.framework or KKSPlayer.xcframework and GPUImage_iOS.framework

Create a Player

To play the streaming content, create a player instance with the license key and stream information in the initialization configuration:

How to create your player view, please refer to the following url
https://developer.apple.com/documentation/avfoundation/avplayerlayer

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

 

Step 4: Manage the Lifecycle of a Playback Session

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

 

What's More

Updated