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