This article details how to integrate CDN Mesh Delivery into your mobile app using IDVIU on Android.
This is a first version of the integration. We are missing some player optimization that will result on a lower DNA offload for Live streams.
We are actively working with IDVIU development team to fully extend it.
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 Streamroot dependency to the build.gradle file of your app module.
implementation 'io.streamroot.dna:dna-core:3.21.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="streamrootKey"/>
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 instantiation.
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.
private void startDnaClient(Entry entry) {
// Init the player interactor
PlayerInteractor playerInteractor = new IdviuInteractor(this.mPlayerView);
// Build and start the DNAClient
try {
this.dnaClient = DnaClient.newBuilder()
.context(getApplicationContext())
.playerInteractor(playerInteractor)
.streamrootKey(<String>)
.latency(latency)
.start(Uri.parse(entry.getUrl()));
} catch (Exception e){
Log.e("Streamroot", e.getLocalizedMessage());
}
}
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 getManifestUrl method that will do it as expected.
dnaClient.getManifestUrl() : <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. The recommended way is to use theapplicationLooper
function. - 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.labgency.hss.HSSPlayer;
import com.labgency.hss.views.HSSPlayerView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import io.streamroot.dna.core.PlayerInteractor;
import io.streamroot.dna.core.TimeRange;
import static com.labgency.hss.HSSPlayer.PLAYER_PARAM_MAX_BUFFER_LENGTH;
public class IdviuInteractor implements PlayerInteractor {
private HSSPlayerView mPlayerView = null;
public IdviuInteractor(HSSPlayerView mPlayerView) {
this.mPlayerView = mPlayerView;
}
@NotNull
@Override
public List<TimeRange> loadedTimeRanges() {
long currentPosition = this.mPlayerView.getPlayer().getLivePosition();
long bufferedPosition = currentPosition + this.mPlayerView.getPlayer().getBufferLength();
return Collections.singletonList(new TimeRange(currentPosition, bufferedPosition));
}
@Nullable
@Override
public Looper looper() {
return Looper.getMainLooper();
}
@Override
public long playbackTime() {
return this.mPlayerView.getPlayer().getPosition();
}
@Override
public double bufferTarget() {
return this.mPlayerView.getPlayer().getMaxBufferLength();
}
@Override
public void setBufferTarget(double v) {
this.mPlayerView.getPlayer().setParam("max_buffer_length", String.valueOf(v));
}
}
5. Implement the BandwidthMeter
The BandwidthMeter allows making an estimation of the bandwidth needed to download the video chunks. The SDK will need:
- A bandwidth listener
Implementation of Streamroot BandwidthListener
Please refer to the example below.
import android.util.Log;
import com.labgency.hss.HSSAgent;
import com.labgency.hss.HSSDownload;
import com.labgency.hss.views.HSSPlayerView;
import io.streamroot.dna.core.BandwidthListener;
public class IdviuBandwithMeter implements BandwidthListener {
private HSSPlayerView mPlayerView = null;
public IdviuBandwithMeter(HSSPlayerView mPlayerView) {
this.mPlayerView = mPlayerView;
}
@Override
public void onBandwidthChange(long bandwith) {
long dldId = HSSAgent.getDownloadManager().addDownload(mPlayerView.getPlaylist().items().get(0).url(), true);
HSSDownload download = HSSAgent.getDownloadManager().getDownload(dldId);
if (download != null){
download.setMaxVideoBitrate(bandwith);
HSSAgent.getDownloadManager().unpauseDownload(dldId);
}
}
}
It is then required to call the BandwidthListener in the builder.
// Init the player bandwidth listener
BandwidthListener bandwidthListener = new IdviuBandwithMeter(this.mPlayerView);
// Build and start the DNAClient
try {
this.dnaClient = DnaClient.newBuilder()
.context(getApplicationContext())
.playerInteractor(playerInteractor)
...
.bandwidthListener(bandwidthListener)
...
.start(Uri.parse(entry.getUrl()));
} catch (Exception e){
Log.e("Streamroot", e.getLocalizedMessage());
}
}
6. Integrate the QoS module (optional)
This QoS module will link IDVIU Player 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 android.media.MediaPlayer;
import com.labgency.hss.views.HSSPlayerView;
import com.labgency.hss.views.HSSPlayerViewListeners;
import io.streamroot.dna.core.PlaybackState;
import io.streamroot.dna.core.QosModule;
final class IdviuQoSModule extends QosModule implements
MediaPlayer.OnCompletionListener,
MediaPlayer.OnErrorListener,
MediaPlayer.OnInfoListener, HSSPlayerViewListeners.OnTrackSelectionListener, MediaPlayer.OnBufferingUpdateListener {
private HSSPlayerView mPlayerView = null;
IdviuQoSModule(HSSPlayerView mPlayerView) {
super();
this.mPlayerView = mPlayerView;
this.mPlayerView.setOnCompletionListener(this);
this.mPlayerView.setOnErrorListener(this);
this.mPlayerView.setOnInfoListener(this);
this.mPlayerView.setOnTrackSelectionChangeListener(this);
}
void setBufferStateListener() {
if (this.mPlayerView.getPlayer() != null) {
this.mPlayerView.getPlayer().setOnBufferingUpdateListener(this);
}
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
playbackErrorOccurred();
return false;
}
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
if (mp.isPlaying()) {
playbackStateChange(PlaybackState.PLAYING);
} else {
playbackStateChange(PlaybackState.PAUSING);
}
return true;
}
@Override
public void onCompletion(MediaPlayer mp) {
playbackStateChange(PlaybackState.ENDED);
}
@Override
public boolean onAudioTrackSelectorRequested() {
return false;
}
@Override
public boolean onCaptionsTrackSelectorRequested() {
trackSwitchOccurred();
return false;
}
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
playbackStateChange(PlaybackState.BUFFERING);
}
}
It is then required to call the IdviuQoSModule in the builder.
// Init qosModule (optional)
QosModule qosModule = new IdviuQoSModule(this.mPlayerView);
// Build and start the DNAClient
try {
this.dnaClient = DnaClient.newBuilder()
.context(getApplicationContext())
.playerInteractor(playerInteractor)
...
.qosModule(qosModule)
...
.start(Uri.parse(entry.getUrl()));
} catch (Exception e){
Log.e("Streamroot", e.getLocalizedMessage());
}
}
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.
dnaClient.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.