This article details how to integrate CDN Mesh Delivery into your mobile app using Kaltura PlayKit Player SDK on Android.
Not into tutorials? |
Kaltura PlayKit
The Kaltura Mobile Video SDK for Android is a mobile media player that allows to embed Kaltura Video Player into native environments in your Android application.
To learn more about the Kaltura Player SDK, please refer to their documentation.
Project setup
Dependencies
The easiest way to get the Streamroot SDK is to add it as a Gradle dependency. We assume you are using Android Studio with the latest tools updates as recommended by Google. If not, drop us a line.
repositories {
maven {
url 'https://sdk.streamroot.io/android'
}
}
Next, add the DNA PlayKit and Streamroot dependency to the build.gradle file of your app module.
implementation 'com.kaltura.playkit:dna-playkit:3.9.3'
implementation 'io.streamroot.dna:dna-core:3.23.0'
Java version requirement
The latest versions of our SDK use the lambda feature of Java 8, which is supported by all Android devices (cf. Android documentation).
As mentioned in the official documentation, you have to configure your project with the following snippet.
android {
// Configure only for each module that uses Java 8
// language features (either in its source code or
// through dependencies).
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
Integrate Streamroot SDK
1. Set streamrootKey
Before doing anything, you must set the streamrootKey
as a meta-data in the application manifest.
<meta-data
android:name="io.streamroot.dna.StreamrootKey"
android:value="your_streamroot_key"/>
streamrootKey
: Refers to your Streamroot unique identifier that you will find in the Account section of the Streamroot dashboard. If you don't have a Streamroot Key, you can ask a free trial on our website.
It is also possible to pass your Streamroot Key at Mesh Delivery Client instanciation.
2. Create a new Mesh Delivery Client instance
Now that you have set the streamrootKey, you are able to create the Mesh Delivery Client instances.
DnaClient mDnaClient = DnaClient.newBuilder()
.context(<Context>)
.playerInteractor(PlayKitInteractor(<Player>))
.streamrootKey(<String>)
.latency(<Integer>)
.start(<Uri>);
Mandatory builder parameters:
context
: An instance of Context.playerInteractor
: An object that implementsPlayerInteractor
. See section below for more details.streamrootKey
: A method to pass your Streamroot Key. It will override the one set in the application manifest.latency
: A method that works for HLS and MPEG-DASH on live content. If it's set for a VoD content, this parameter will be ignored. The value must be strictly positive and in seconds. We recommend setting the value to 30s.start
: A method that will create and start a new instance of the SDK. It expects the URL of your stream.
Other DNA options can be called in the builder. Please refer to this article to learn more about them.
3. Give your player the new URL
Once you have a running instance of the SDK, you must retrieve its URL and input it to your player instead of your original one.
The SDK exposes a manifestUrl
method that will do it as expected.
mDnaClient.manifestUrl : <Uri>
4. Implement the PlayerInteractor
The PlayerInteractor is how the SDK gets player specifics. The SDK will need the following information to work optimally:
- Looper
looper must return the looper used to create the player.
Loaded TimeRanges
loadedTimeRanges must return a list of TimeRange (in milliseconds).
Each TimeRange represents a continuous interval of content present in the buffer. It is mandatory to return at least the TimeRange going from the playback time to the end of the buffered interval containing the playback time. - Current playback time
playbackTime must return the current time of the player in milliseconds. - Buffer target
bufferTarget must return the target the player will try to buffer.
setBufferTarget allows us to dynamically change the buffer target of the player to optimize Mesh Delivery performances.
import android.os.Looper
import com.kaltura.playkit.Player
import io.streamroot.dna.core.PlayerInteractor
import io.streamroot.dna.core.TimeRange
import java.util.*
class PlayKitInteractor(
player : Player
) : PlayerInteractor {
private val mPlayer = player
override fun looper(): Looper? = Looper.getMainLooper()
override fun playbackTime() : Long {
return mPlayer.currentPosition
}
override fun loadedTimeRanges(): List<TimeRange> {
val currentPosition = mPlayer.currentPosition
val bufferedPosition = mPlayer.bufferedPosition
return Collections.singletonList(TimeRange(currentPosition, bufferedPosition))
}
override fun bufferTarget(): Double {
return mPlayer.bufferTarget ?: 0.0
}
override fun setBufferTarget(target: Double) {
mPlayer.bufferTarget = target
}
}
5. Implement the BandwidthMeter
The BandwidthMeter allow making an estimation of the bandwidth needed to download the video chunks. The SDK will need:
- A bandwidth listener
Implementation of Streamroot BandwidthListener for Kaltura Playkit.
Please refer to the example below.
import com.kaltura.playlkit.Player
import io.streamroot.dna.core.BandwidthListener
class PlaykitBandwidthListener(
player: Player
): BandwidthListener {
private val mPlayer = player
override fun onBandwidthChange(estimatedBandwidth: Long) {
mPlayer.estimatedBandwidth = estimatedBandwidth
}
}
It is then required to call the BandwidthListener in the builder.
DnaClient dnaClient = DnaClient.newBuilder()
.context(<Context>)
.playerInteractor(PlayKitInteractor(mPlayer))
...
.bandwidthListener(PlaykitBandwidthListener(mPlayer))
...
.start(<String>);
6. Integrate the QoS module (optional)
This QoS module will link Kaltura Playkit events to our SDK in order to get accurate metrics regarding the client session.
If this QoS module isn't implemented, a default module directly embedded in the SDK will be used instead.
Hereunder is an example of the class to add to the app in order to do so.
import com.kaltura.playkit.PKEvent
import com.kaltura.playkit.Player
import com.kaltura.playkit.PlayerEvent
import com.kaltura.playkit.PlayerState
import io.streamroot.dna.core.PlaybackState
import io.streamroot.dna.core.QosModule
class PlayKitQoSModule(
player: Player
) : QosModule() {
private val mPlayer = player
private var mLastVideoBitrate: Long? = null
private var mLastAudioBitrate: Long? = null
private var currentPlayerState: PlayerState? = null
init {
// Register useful player events
mPlayer.addEventListener((PKEvent.Listener<PlayerEvent.PlaybackInfoUpdated> {
val playbackInfo = it.playbackInfo
if (mLastVideoBitrate != playbackInfo.videoBitrate
|| mLastAudioBitrate != playbackInfo.audioBitrate) {
trackSwitchOccurred()
}
mLastVideoBitrate = playbackInfo.videoBitrate
mLastAudioBitrate = playbackInfo.audioBitrate
}), PlayerEvent.Type.PLAYBACK_INFO_UPDATED)
mPlayer.addEventListener((PKEvent.Listener<PlayerEvent> {
playbackStateChange(PlaybackState.ENDED)
}), PlayerEvent.Type.ENDED)
mPlayer.addStateChangeListener {
val eventDetails = it as PlayerEvent.StateChanged
when (eventDetails.newState) {
PlayerState.IDLE -> {
playbackStateChange(PlaybackState.IDLE)
}
PlayerState.BUFFERING -> {
playbackStateChange(PlaybackState.BUFFERING)
}
PlayerState.READY -> {
when (mPlayer.isPlaying) {
true -> playbackStateChange(PlaybackState.PLAYING)
false -> playbackStateChange(PlaybackState.PAUSING)
}
}
}
currentPlayerState = it.newState
}
}
}
It is then required to call the PlaykitQosModule in the builder.
DnaClient dnaClient = DnaClient.newBuilder()
.context(<Context>)
.playerInteractor(PlayKitInteractor(mPlayer))
...
.qosModule(PlayKitQoSModule(mPlayer))
...
.start(<String>);
Stop the Mesh Delivery Client
Once the video is done playing, you have to stop the SDK you created earlier. Calling the following method will finish the ongoing tasks and release the resources.
mDnaClient.close();
Network security configuration
Starting with Android 9 (API level 28), cleartext support is disabled by default.
As Streamroot SDK works as a local HTTP proxy, it is required to do one of the following
- Activate back cleartext to reactivate all HTTP calls in the AndroidManifest.xml (Not recommended)
<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
<application
...
android:usesCleartextTraffic="true">
...
</application>
</manifest>
- Add 'localhost' in the allowed domains in a new network_security_config.xml file
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">localhost</domain>
</domain-config>
</network-security-config>
And add this file in the AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
<application
...
android:networkSecurityConfig="@xml/network_security_config">
...
</application>
</manifest>
More info can be found in the Android documentation.
Integrating with the OvpMediaProvider
If you are using OVP content you will need to get the media using the OvpMediaProvider
to remotely get your media. More information can be found in Kaltura's documentation.
This step must be done before creating the Mesh Delivery Client instance explained in Step 2.
You need to pass the media URL retrieved from the OvpMediaProvider
to the SDK and then retrieve the manifestUrl
to pass it to your player.
Hereunder is an example on how to do so.
private void loadMedia() {
new KalturaOvpMediaProvider(BASE_URL, PARTNER_ID, this.ks)
.setEntryId(entryId)
.load(response -> runOnUiThread(() -> {
if (response.isSuccess()) {
// Find the preferred source format and replace it with DNA URL
PKMediaEntry mediaEntry = response.getResponse();
PKMediaFormat preferredFormat = ((PlayerSettings) player.getSettings()).getPreferredMediaFormat();
for (PKMediaSource source : mediaEntry.getSources()) {
if (source.getMediaFormat().name().equals(preferredFormat.name())) {
source.setUrl(loadStreamroot(source));
break;
}
}
// Update with entryId and KS
player.updatePluginConfig(KavaAnalyticsPlugin.factory.getName(), getKavaConfig());
player.getSettings().setAllowCrossProtocolRedirect(true);
player.prepare(new PKMediaConfig().setMediaEntry(mediaEntry));
player.play(); // Will play when ready
startStatsView();
} else {
Toast.makeText(PlayerActivity.this, "Failed loading media: " + response.getError(), Toast.LENGTH_SHORT).show();
}
}));
}
Then, it is recommended to also pass the content Id in the builder using the mediaId fetched from the PKMediaSource.
DnaClient dnaClient = DnaClient.newBuilder()
.context(<Context>)
.playerInteractor(PlayKitInteractor(mPlayer))
...
.contentId(source.getId())
...
.start(<String>);
A full sample application can be found here.