Android Player SDK

Quickstart: Android TV Player

This guide will walk you through the steps to start a BlendVision One streaming playback on Android TV using the Android player SDK.

 

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

const val API_DOMAIN = "https://api.one.blendvision.com"

interface ApiService {
    @POST("bv/cms/v1/tokens")
    suspend fun getPlaybackToken(
        @Header("Authorization") cmsToken: String, // 'Bearer ${CMS token}'
        @Body request: PlaybackTokenRequest()
    }: PlaybackTokenResponse
}

data class PlaybackTokenRequest(
    @SerializedName("resource_id")
    val resourceId: String,
    @SerializedName("resource_type")
    val resourceType: String,
    @SerializedName("customer_id")
    val customerId: String
)

data class PlaybackTokenResponse(
    @SerializedName("token")
    val playbackToken: String
)

 

Step 2: Start a Playback Session

const val API_DOMAIN = "https://api.one.blendvision.com"

interface ApiService {
    @POST("bv/playback/v1/sessions/{deviceId}:start")
    suspend fun startPlaybackSession(
        @Header("Authorization") playbackToken: String,
        @Path("deviceId") deviceId: String
    ): StartSessionResponse
    
    @POST("bv/playback/v1/sessions/${deviceId}")
    suspend fun getStreamInfo(
        @Header("Authorization") playbackToken: String,
        @Path("deviceId") deviceId: String
    ): GetStreamInfoResponse
}

// data for start a session
data class StartSessionResponse(
    @SerializedName("drm_server_endpoint")
    val endPoint: String
)

// data for get stream information
data class GetStreamInfoResponse(
    @SerializedName("sources")
    val sources: List
)

data class Source(
    @SerializedName("manifests")
    val manifests: List,
    @SerializedName("thumbnail_seeking_url")
    val thumbnailSeekingUrl: String
)

data class Manifest(
    @SerializedName("protocol")
    val protocal: String,
    @SerializedName("url")
    val url: String,
    @SerializedName("resolutions")
    val resolutions: List
)

data class Resolution(
    @SerializedName("height")
    val height: String,
    @SerializedName("width")
    val width: String
)

 

Step 3: Initialize the Player

Obtain Player License Key

Obtain a valid player license key from your BlendVision One console

Install Player SDK

  • Download from https://github.com/BlendVision/Android-Player-SDK/releases
  • Create /libs folder in the project root path

  • Add below description in the gradle

    implementation fileTree(dir: 'libs', include: ['*.aar'])
    
  • Add following .aar files to libs/.

    // UniPlayer
    kks-playcraft-daas.aar
    kks-playcraft-paas.aar
    
    // KKSPlayer
    kksplayer.aar
    kksplayer-kkdrm.aar
    kksplayer-library-core-release.aar
    kksplayer-library-common-release.aar
    kksplayer-library-extractor-release.aar
    kksplayer-library-dash-release.aar
    kksplayer-library-ui-release.aar
    
    // KKS-network
    kks-network.aar
    
    • others (can be imported from public maven)
    plugins {
        ...
        id 'kotlin-kapt'
    }
    
    api ('com.google.guava:guava:' + guava_version) {
        // Exclude dependencies that are only used by Guava at compile time
        // (but declared as runtime deps) [internal b/168188131].
        exclude group: 'com.google.code.findbugs', module: 'jsr305'
        exclude group: 'org.checkerframework', module: 'checker-compat-qual'
        exclude group: 'com.google.errorprone', module: 'error_prone_annotations'
        exclude group: 'com.google.j2objc', module: 'j2objc-annotations'
        exclude group: 'org.codehaus.mojo', module: 'animal-sniffer-annotations'
    }
    
    //Google IMA SDK
    implementation "com.google.ads.interactivemedia.v3:interactivemedia:$google_ima_version"
    
    //Chromecast
    implementation "com.google.android.gms:play-services-cast-framework:$cast_version"
    
    //Coroutines
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
    
    //Okhttp
    implementation 'com.squareup.okhttp3:okhttp:' + okhttp_version
    implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
    
    // gson
    implementation 'com.google.code.gson:gson:' + gsonVersion
    
    // thumbnail
    implementation "com.github.bumptech.glide:glide:$glideVersion"
    kapt "com.github.bumptech.glide:compiler:$glideVersion"
    
  • Set gradle.properties

    android.enableDexingArtifactTransform.desugaring=false
    android.enableJetifier=true

Create a Player

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

// create the player instance
private var player: UniPlayer? = null
player = UniPlayer.Builder(
        requireContext(),
        PlayerConfig(
            license = "YOUR_LICENSE_KEY"
        )
    ).build()
    
val uniTvFragment: UniTvFragment  =
        supportFragmentManager.findFragmentById(R.id.uniTvFragment) as UniTvFragment
        
player?.let {
    uniTvFragment.setup(it)
}

// To pass events sent by the TV remote control, 
// bypass the onKeyUp/onKeyDown events through the following interface.

override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
    return if (isFragmentHandleOnKeyUpEvent(keyCode, event)) {
        // If isFragmentHandleOnKeyUpEvent return true, it means we handle the onKeyUp event.
        // So we return true to tell parent we has handled it.
        true
    } else {
        super.onKeyUp(keyCode, event)
    }
}

override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
    return if (isFragmentHandleOnKeyDownEvent(keyCode, event)) {
        // If isFragmentHandleOnKeyUpEvent return true, it means we handle the onKeyUp event.
        // So we return true to tell parent we has handled it.
        true
    } else {
        super.onKeyDown(keyCode, event)
    }
}

private fun isFragmentHandleOnKeyUpEvent(keyCode: Int, event: KeyEvent?): Boolean {
    return (uniTvFragment as? CustomUniTVFragmentInterface)?.onKeyUp(keyCode, event, null)
        ?: false
}

private fun isFragmentHandleOnKeyDownEvent(keyCode: Int, event: KeyEvent?): Boolean {
    return (uniTvFragment as? CustomUniTVFragmentInterface)?.onKeyDown(keyCode, event, null)
        ?: false
}

// load stream information
player?.load(
    MediaConfig(
        source = listOf(
            MediaConfig.Source(
                url = streamInfoResponse.sources[0].manifests.filter { it.protocol == MediaConfig.Protocol.DASH }[0].url,
                protocal = MediaConfig.Protocol.DASH,
                drm = MediaConfig.DrmInfo.Widevine(
                    licenseUrl = startSessionResponse.endPoint,
                    headers = mapOf("" to "")
                )
            )
        ),
        title = "",
        imageUrl = "",
        thumbnailSeekingUrl = ""
    )
)

// start playback
player?.start()

// pause/play during playback
player?.pause()
player?.play()

// release the player
player?.release()

 

Step 4: Manage the Lifecycle of a Playback Session

const val API_DOMAIN = "https://api.one.blendvision.com"

interface ApiService {

    // post this API every 10 seconds
    @POST("bv/playback/v1/sessions/${deviceId}:heartbeat")
    suspend fun Heartbeat(
        @Header("Authorization") playbackToken: String,
        @Path("deviceId") deviceId: String
    )
    
    @POST("bv/playback/v1/sessions/${deviceId}:end")
    suspend fun EndPlaybackSession(
        @Header("Authorization") playbackToken: String,
        @Path("deviceId") deviceId: String
    )
}

 

What's More

Updated