Usage

Prerequisites

  • Android Studio
  • Gradle (5.6.4 or later)
  • Target API level 24 or later
  • Kotlin version 1.4.30
  • Bitmovin Key to add in AndroidManifest.xml

Quick Start Guide

Adding MKPlayer Dependencies

    $ROOT/build.gradle:

        allprojects {
            repositories {
                maven {
                    url 'https://bitmovin.jfrog.io/artifactory/public-releases'
                }
                maven {
                    url 'https://mkplayer.jfrog.io/artifactory/mkplayer/'
                }
            }
        }

    $ROOT/app/build.gradle:
       // Please always use updated released `version`
        dependencies {
            implementation(group: 'com.mediakind', name: 'mkplayer', version: '1.0.7', ext: 'aar') {
               transitive =true
            }
        }

Add mandatory meta-data in AndroidManifest.xml

Add your Bitmovin Player license key inside the application tag in the manifest file as shown below.

REPLACE_ME has to be replaced by your own license key.

    <meta-data
                android:name="BITMOVIN_PLAYER_LICENSE_KEY"
                android:value="REPLACE_ME" />

To enable Bitmovin analytics, REPLACE_ME has to be replaced by your own analytics key. Adding the valid license and by passing MKPAnalyticsConfiguration refer 'Enabling Bitmovin analytics' section to publish the data to Bitmovin dashboard.Enabling analytics is optional.

   <meta-data
               android:name="BITMOVIN_ANALYTICS_LICENSE_KEY"
               android:value="REPLACE_ME" />

Player Initialization

Before creating player instance it is expected to initialize several configurations such as

  1. MKPlayerConfiguration
  2. MKPBackendConfiguration
Initialization of Player configuration
    private var playerConfiguration: MKPlayerConfiguration? = null // Global variable
    this.playerConfiguration = MKPlayerConfiguration()
    this.playerConfiguration!!.isUiEnabled = false // Pass as true if Bitmovin UI is required

To play MediaKind assets MKPBackendConfiguration is required and has to be used with MKPlayerConfiguration.

Usage example:

Note that API MKPBackendConfiguration("https://ottapp-appgw-amp-a.cim.mr.tv3cloud.com" /* MK server End Point */, sourceConfig.stsToken!!/* auth token received from MK backend*/) is deprecated. Refer updated MKPBackendConfiguration.

    private var backendConfiguration: MKPBackendConfiguration? = null
    
    /* accountId is mandatory param  primarily required for registration of device.
    This is same as login id used to generate the AuthToken. */
    val accountId: String = "name@company.com"
    
    /* auth token received from MK backend*/
    val authToken = "Auth****"   // sourceConfig.stsToken!!

    backendConfiguration = MKPBackendConfiguration("https://ottapp-appgw-amp-a.cim.mr.tv3cloud.com" /* MK server End Point */, authToken , accountId, "azuki" /* Default argument for ownerUid */)
    this.playerConfiguration?.backendConfiguration = backendConfiguration //set on the player configuration

MKAdaptationConfiguration can also be set on player configuration as shown below - (change values based on requirement). If not set then default values of adaptation configuration is set on the player configuration.

    private var adaptationConfig: MKAdaptationConfiguration? = null
    adaptationConfig = MKAdaptationConfiguration()
    adaptationConfig!!.isAllowRebuffering = true
    adaptationConfig!!.maxSelectableVideoBitrate = 6_000_000
    adaptationConfig!!.startupBitrate = 1_200_000

    // set AdaptationConfiguration on PlayerConfiguration
    playerConfiguration!!.setAdaptationConfiguration(adaptationConfig!!)
Creating MKPlayer instance with player configuration
    private lateinit var mkplayer: MKPlayer
    mkplayer = MKPlayer(applicationContext, rootView /* RelativeLayout */, playerConfiguration /* MKPlayerConfiguration */)

Calling initPlayer() is deprecated from version 0.9.2. Instead initialization of player is moved to MKPlayer() constructor. // mkplayer.initPlayer()

Event Registration

Register your event listeners prior to loading a source to make sure you have visibility into the video playback session:

    mkplayer.addEventListener(onMKAdEventListener)
    mkplayer.addEventListener(onMKErrorListener)
    mkplayer.addEventListener(onMKMutedListener)
    mkplayer.addEventListener(onMKPausedListener)
    mkplayer.addEventListener(onMKPlayListener)
    mkplayer.addEventListener(onMKPlaybackFinishedListener)
    mkplayer.addEventListener(onMKPlayingListener)
    mkplayer.addEventListener(onMKReadyListener)
    mkplayer.addEventListener(onMKSeekListener)
    mkplayer.addEventListener(onMKSeekedListener)
    mkplayer.addEventListener(onMKSourceLoadListener)
    mkplayer.addEventListener(onMKSourceLoadedListener)
    mkplayer.addEventListener(onMKSourceUnloadedListener)
    mkplayer.addEventListener(onMKUnmutedListener)
    mkplayer.addEventListener(onMKTimeChangedListener)

    private val onMKErrorListener = object : OnMKErrorListener {
        override fun onError(event: MKPErrorEvent) {
            Log.e(TAG,"An error occurred (" + event.getCode().toString() + "): " + event.getMessage())
        }
    }

    private val onMKPlayListener = object : OnMKPlayListener {
        override fun onPlay(eventMKP: MKPPlayEvent) {
            Log.d(TAG, "Received onMKPlay Callback " + eventMKP.getTime())
        }
    }

    private val onMKPausedListener = object : OnMKPausedListener {
        override fun onPaused(event: MKPPausedEvent) {
            Log.d(TAG, "Received onPaused Callback " + event.getTime())
        }
    }

    private val onMKMutedListener = object : OnMKMutedListener {
        override fun onMuted(event: MKPMutedEvent) {
        }
    }

    private val onMKPlaybackFinishedListener = object : OnMKPlaybackFinishedListener {
        override fun onPlaybackFinished(finishedEvent: MKPlaybackFinishedEvent) {
            Log.d(TAG, "Received onMKPlaybackFinished Callback")
        }
    }

    private val onMKPlayingListener = object : OnMKPlayingListener {
        override fun onPlaying(event: MKPPlayingEvent) {
        }
    }

    private val onMKReadyListener = object : OnMKReadyListener {
        override fun onReady(event: MKPReadyEvent) {
            Log.d(TAG, "Received onMKReady Callback")
        }
    }

    private val onMKSeekListener = object : OnMKSeekListener {
        override fun onSeek(event: MKPSeekEvent) {
            Log.d(TAG, "Received onMKSeek Callback positionMK" +
                        " ${event.getPosition()} " +
                        "target positionMK  ${event.getSeekTarget()}"
            )
        }
    }

    private val onMKSeekedListener = object : OnMKSeekedListener {
        override fun onSeeked(event: MKPSeekedEvent) {
            Log.d(TAG, "Received onMKSeeked Callback")
        }
    }

    private val onMKSourceLoadListener = object : OnMKSourceLoadListener {
        override fun onSourceLoad(event: MKPSourceLoadEvent) {
            Log.d(TAG, "Received onMKSourceLoad Callback")
        }
    }

    private val onMKSourceLoadedListener = object : OnMKSourceLoadedListener {
        override fun onSourceLoaded(event: MKPSourceLoadedEvent) {
            Log.d(TAG, "Received onMKSourceLoaded Callback")
        }
    }

    private val onMKSourceUnloadedListener = object : OnMKSourceUnloadedListener {
        override fun onSourceUnloaded(event: MKPSourceUnloadEvent) {
            Log.d(TAG, "Received onMKSourceUnLoaded Callback")
        }
    }

    private val onMKUnmutedListener = object : OnMKUnmutedListener {
        override fun onUnmute(event: MKPUnmutedEvent) {
        }
    }

    private val onMKTimeChangedListener = object : OnMKTimeChangeListener {
        override fun onTimeChanged(var1: Double) {
        }
    }

For receiving PBR restriction list of a program, register for OnMKPProgramRestrictionListener as shown below, MKPProgramRestrictionsEvent would be passed in callback method onProgramRestrictions.

mkplayer.addEventListener(programEntitlementListener)

private var programEntitlementListener =
    object : OnMKPProgramRestrictionListener {
        override fun onProgramRestrictions(event: MKPProgramRestrictionsEvent) {
            for (restriction in event.restrictionList) {
                Log.d(
                    TAG,
                    "onProgramRestrictions received code:${restriction.code} and message is ${restriction.message} "
                )
            }
        }
    }

Unregister programEntitlementListener after unload callback.

mkplayer.removeEventListener(programEntitlementListener)

StreamLoading

Below are the examples to load and play streams:
Play clear content using Source URL
    mkplayer.load("https://bitdash-a.akamaihd.net/content/sintel/sintel.mpd")

Play encrypted content using a Source Configuration (Please refer source_list.json[MK Ref app] ). Based on the stream type use appropriate MKPSourceConfiguration constructor methods

  1. To play external source url.

Usage example:

    var title: String = "External URL"
    var externalSourceUrl: String = "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd" /*Source URL*/
    var externalSourceLicenseUrl: String = "https://proxy.uat.widevine.com/proxy?provider=widevine_test" /*Source License URL*/
    sourceConfiguration = MKPSourceConfiguration(title, externalSourceUrl, externalSourceLicenseUrl)
    mkplayer.load(sourceConfiguration)
  1. To play MediaKind content.

Note that if MKPBackendConfiguration is not passed in PlayerConfiguration then SDK throws below one of the NullPointerException :

1. If MKPBackendConfiguration instance is null in PlayerConfiguration then
NullPointerException("Missing mandatory backend configuration") thrown from load method.

2. If STSToken is null or empty in MKPBackendConfiguration instance then
NullPointerException("Access token cannot be null") thrown from load method.

3. If 'MK server End Point' is null or empty in MKPBackendConfiguration instance then
NullPointerException("Server Url expected") thrown from load method.

4. If 'accountId' is null or empty in MKPBackendConfiguration instance then
NullPointerException("Account Id is expected") thrown from load method.

PS: MKPBackendConfiguration are mandatory for playing MK assets.

Usage example:

Note that API MKPSourceConfiguration(title, mediaUid, applicationName, playbackMode) is deprecated. Refer updated MKPSourceConfiguration.

    /* title of the video asset */
    var title: String = "Game of Thrones"

    /* mediaUid is mandatory param which is unique media identifier for a Mediakind registered content. */
    var mediaUid: String = "kpixsd"

    /* applicationToken value is mandatory and
    this corresponds to the Service Collection ID for a the selected channel entry in the channel map.
    This is mandatory for Live and Dvr and optional for VoD.*/
    var applicationToken: String = "9357"

    /*isLive is mandatory flag to indicate whether the given source is Live or VoD*/
    var isLive: Boolean = true //Value for LIVE -> true and for VOD -> false

    sourceConfiguration = MKPSourceConfiguration(title, mediaUid,
                                                          applicationToken, isLive)
    mkplayer.load(sourceConfiguration)

Enabling Bitmovin analytics

Create a MKPAnalyticsConfiguration instance as below

    val analyticsConfig = MKPAnalyticsConfiguration()
    analyticsConfig.title = sourceConfig.title
    analyticsConfig.videoId = sourceConfig.mediaUid
    analyticsConfig.userId =  sourceConfig.primaryAccount // SDK user value
    analyticsConfig.experimentName = "MKPlayer-Android"
    analyticsConfig.customData1 = "customer data1"
    analyticsConfig.customData2 = "customer data2"
    analyticsConfig.customData3 = "customer data3"
    analyticsConfig.customData4 = "customer data4"
    analyticsConfig.customData5 = "customer data5"
     // CDN will be passed internally by SDK for MKP Streams
     analyticsConfig.cdnProvider = "CDN"

Pass analyticsConfig in the constructor of MKPSourceConfiguration. If analyticsConfig is not passed in the constructor then analytics will not be enabled.

    /* title of the video asset */
    var title: String = "Game of Thrones"

    /* mediaUid is mandatory param which is unique media identifier for a Mediakind registered content. */
    var mediaUid: String = "kpixsd"

    /* applicationToken value is mandatory and
    this corresponds to the Service Collection ID for a the selected channel entry in the channel map.
    This is mandatory for Live and Dvr and optional for VoD.*/
    var applicationToken: String = "9357"

    /*isLive is mandatory flag to indicate whether the given source is Live or VoD*/
    var isLive: Boolean = true //Value for LIVE -> true and for VOD -> false

    sourceConfig = MKSourceConfig(title, mediaUid, applicationToken, isLive)
    sourceConfiguration = MKPSourceConfiguration(sourceConfig, analyticsConfig)
    mkplayer.load(sourceConfiguration)

or

    /* title of the video asset */
    var title: String = "Game of Thrones"

    /* mediaUid is mandatory param which is unique media identifier for a Mediakind registered content. */
    var mediaUid: String = "kpixsd"

    /* applicationToken value is mandatory and
    this corresponds to the Service Collection ID for a the selected channel entry in the channel map.
    This is mandatory for Live and Dvr and optional for VoD.*/
    var applicationToken: String = "9357"
    
    /*isLive is mandatory flag to indicate whether the given source is Live or VoD*/
    var isLive: Boolean = true //Value for LIVE -> true and for VOD -> false

    this.sourceConfiguration = MKPSourceConfiguration(title, mediaUid,
                                                          applicationToken, isLive, analyticsConfig)
    mkplayer.load(sourceConfiguration)

Enabling CDN Configuration

Create MKPCdnOptions instance for example as below:

    val cdnTokens = mapOf("cdntoken" to "239d67703a9b8fd18199d539a13c1ca2", "token" to "339d67703a9b8fd18199d539a13c1ca3")
    // OR
    val cdnTokens = HashMap<String, String>()
    cdnTokens.put("cdntoken", "239d67703a9b8fd18199d539a13c1ca2")
    cdnTokens.put("token", "339d67703a9b8fd18199d539a13c1ca3")

    //Pass cdn key value pair params to MKPCdnOptions
    val cdnOptions = MKPCdnOptions(cdnTokens)
    // OR passing CDN failover percent. Please refer MKPCdnOptions. Default 0.
    val cdnOptions = MKPCdnOptions(cdnTokens, 10)

Pass cdnOptions instance in the constructor of MKPSourceConfiguration.

If not passed in the constructor then cdn token query params will not be part of manifest url.

    sourceConfiguration = MKPSourceConfiguration(sourceConfig, cdnOptions = cdnOptions)
    mkplayer.load(sourceConfiguration)

Stream Playback

Start playback on receiving onReady or onSourceLoaded callbacks using play() method.

    mkplayer.play()

Application Lifecycle Events

These methods have to be called when there is a change in the activity life cycle

    mkplayer.onActivityPaused()
    mkplayer.onActivityResumed()
    mkplayer.onActivityStopped()
    mkplayer.onActivityDestroyed()

Load new stream

1. Unload current stream.
     mkplayer.unload()
  1. On receiving onSourceUnloaded callback, follow Stream Loading and Stream Playback sections to load a new stream

Picture-in-picture

To use MKPlayer api's to enter Picture-in-picture mode please see below code snippet

    val configuration = MKPlayerConfiguration()

     // Pass as true if Bitmovin UI is required
    this.configuration!!.isUiEnabled = false

    // Enable Pip
    this.configuration?.isPictureInPictureEnabled = true

    val mkplayer = MKPlayer(
    applicationContext,
    rootView,
    configuration!!
    )

    // Pass activity to create Pip Handler
    mkplayer.setPictureInPictureHandler(this)

    // Call enterPictureInPicture on user action of minimise or
    // while leaving activity in onUserLeavingHint().
    mkplayer.enterPictureInPicture()

For more details of Picture-in-picture please refer MKPictureInPictureApi

Player Cleanup

Call removeEventListener which are registered with addEventListener

    // For example
    mkplayer.removeEventListener(onMKErrorListener)

    // To unload current URL
    mkplayer.unload()

    // After calling destroy it is expected that no api should be called on mkplayer object.
    mkplayer.destroy()

Support

Check out our full API documentation here