Extensions and Plugins
Extensions and Plugins¶
Recovering From Loss Of Connectivity¶
By default, the PRESTOplay SDK for Android will not check specifically for loss of connectivity and if a device looses its internet connection during playback, fatal player errors will be raises. With version 3.1.2, we introduced a new mechanism to enable the player to recover automatically from connectivity loss without fatal errors being raised. You will need to enable that feature explicitly before you initialize the SDK:
PlayerSDK.ENABLE_CONNECTIVITY_CHECKS = true;
If the feature is enabled, the player will catch download errors
internally and check for network connectivity. If no internet connection
is available, a CastlabsPlayerException
will be
passed to any registered PlayerListener instances. The type of the error
will be TYPE_CONNECTIVITY_LOST_ERROR
,
which indicates that a download error occurred due to lack of
connectivity. The PRESTOplay SDK for Android will then register a broadcast listener and wait
for connectivity changes. During that period, the player might run out of
buffered data and will go into Buffering state. Once device connectivity
changes, the playback will be resumed automatically and
a TYPE_CONNECTIVITY_GAINED_INFO
error will be raised through all registered listeners. This error is not
severe and serves only informational purpose. For more
information about error handling and how to add a listener see
Error Handling.
If you are implementing a user interface component to inform the user of
the loss of connectivity, you might want to consider waiting until the
player goes into buffering state after you observed the
TYPE_CONNECTIVITY_LOST_ERROR
error. If the connectivity loss is short enough and the buffers contain enough
data, the player might actually recover silently from such interruptions.
Handling Connectivity Loss for Live Playback¶
In case connectivity loss detection is enabled and you are playing live
content, you might need to manually re-start playback if the connection
loss was too long and the playback head moved behind the current live
Window. In that case the player will raise a TYPE_BEHIND_LIVE_WINDOW
error, which can be used as a marker for these situations. A possible re-start
might look like this:
@Override
public void onError(@NonNull CastlabsPlayerException error) {
// restart playback for live streams that fell behind the live
// window.
if (error.getType() == CastlabsPlayerException.TYPE_BEHIND_LIVE_WINDOW) {
// Save the current playback state to a bundle
Bundle store = new Bundle();
controller.saveState(store);
// release the player and re-open it with the bundle
controller.release();
try {
controller.open(store);
} catch (Exception e) {
Log.e(TAG, "Error re-opening stream: " + e, e);
}
}
}
Customize Connectivity Check¶
The PRESTOplay SDK for Android uses its default DefaultConnectivityCheck
to check if the
device is currently connected to the internet. The default implementation
does used the Android ConnectivityManager
to check basic connectivity
and then ensures that an internet connection is available by doing a DNS
host name lookup to google.com. You can customize the checker
implementation using the global PlayerSDK#CONNECTIVITY_CHECKER
setting. You can
either set this to a custom implementation of the
ConnectivityCheck
interface, or use
a custom instance of the DefaultConnectivityCheck
with a customized
host lookup name.
Please note that the default implementation does not actually open a connection to google.com (or any other lookup host) but does connect to the DNS service of the device to perform a host lookup for the given name.
Offline Keys¶
The PRESTOplay SDK for Android allows you to store offline keys for some DRM systems. If you
want to leverage this feature, you will need to enable it by setting
a unique identifier as the offline ID in the DrmConfiguration
(see
Offline Key Storage).
Once the license is loaded, the offline ID will be used to store
a reference to the key, the so called “keySetId”, on the device. Note that
this is not the actual decryption key, but an identifier used by the DRM
system to find and load the key later. This “keySetId” needs to be stored
and the SDK does this using an implementation of the KeyStore
interface. The default implementation
is using your application’s shared preferences in private mode to store
a mapping from your offline ID to the “keySetId”.
You can, however use a custom implementation of the KeyStore
. The store
can be configured globally using the
PlayerSDK.DEFAULT_KEY_STORE
field. You can
also set it explicitly for a given PlayerController
.
Key info¶
You can get Key related info through the KeyStore
interface. You can get the currently used KeyStore
instance with the static
PlayerSDK#DEFAULT_KEY_STORE
field.
Providing the offlineId of your content, you can get an instance of DrmKeyStorage
with KeyStore
‘s get
method.
The DrmKeyStorage
object provides DRM-related for a particular key. You can use this for instance
to get the expiration date
.
Interactive Media Ads (IMA)¶
The PRESTOplay SDK for Android provides with IMA service including Dynamic Ads Insertion (DAI) integrated in the form of plugin. Any VAST-compliant ad server is supported.
IMA plugin needs to be registered and enabled by the app. Optionally the plugin
can be initialized with the custom ImaSdkSettings
e.g. in order to change the default ads UI language:
ImaPlugin imaPlugin = new ImaPlugin();
imaPlugin.setEnabled(true);
// (Optional) Set the IMA SDK settings
ImaSdkSettings imaSdkSettings = ImaSdkFactory.getInstance().createImaSdkSettings();
imaSdkSettings.setLanguage("en");
imaPlugin.setImaSdkSettings(imaSdkSettings);
PlayerSDK.register(imaPlugin);
..
// (Optional) Update the IMA settings run-time e.g. on a stream basis
ImaPlugin imaPlugin = PlayerSDK.getPlugin(ImaPlugin.class);
if (imaPlugin != null) {
ImaSdkSettings imaSdkSettings = imaPlugin.getImaSdkSettings();
if (imaSdkSettings != null) {
imaSdkSettings.setLanguage("fr");
imaPlugin.setImaSdkSettings(imaSdkSettings);
}
}
The PlayerController
expects either
ImaAdRequest
or
ImaStreamRequest
when opening content video from Bundle (Starting Playback from an Intent) or
PlayerConfig
(Starting Playback from a PlayerConfig).
// IMA:
intent.putExtra(SdkConsts.INTENT_ADVERTS_DATA, new ImaAdRequest("https://ima.mydomain/request1").toAdRequest());
// IMA DAI:
// note that specifying content type is mandatory
intent.putExtra(SdkConsts.INTENT_ADVERTS_DATA, new ImaStreamRequest("ContentSourceId", "VideoId", "ApiKey").toAdRequest());
intent.putExtra(SdkConsts.INTENT_CONTENT_TYPE, SdkConsts.CONTENT_TYPE_DASH)
As a result, the PRESTOplay SDK for Android requests the ads, starts the ads playback according to the ads schedule and pauses the content video playback. After the ad is completed the content video playback is resumed automatically.
Additionally the application may need to be notified when the ad starts and completes. For instance, it may disable the user content video playback controls or hide them during the ads playback. In this case the application installs the listener as follows:
PlayerView playerView;
...
playerView.getPlayerController().getAdInterface().addAdListener(new AdInterface.Listener() {
@Override
public void onAdStarted(@NonNull Ad ad) {
Log.d(TAG, "Ad started : " + ad.id + ", position = " + ad.position);
// May hide controls and disable touch event triggers
}
@Override
public void onAdCompleted() {
Log.d(TAG, "Ad completed");
// May show the controls again if we keep them on screen
}
@Override
public void onAdPlaybackPositionChanged(long playbackPositionMs) {
}
});
Custom ad UI¶
The PRESTOplay SDK for Android allows you to build your own ad UI. For this, you’ll need to hook up to the relevant
ad events, and hide the default ad provider UI. Additionally, an AdApi
is provided so that ad-related operations can be invoked.
Please note for the IMA Ad Provider the SDK requires the following Tag in the VAST response for Custom UI
to be useable. Once this is available you may check whether the Default AdProvider UI has been disabled
using currentAd.isUiDisabled
.
<Extension type="uiSettings">
<UiHideable>1</UiHideable>
</Extension>
This is how a basic implementation could look:
protected void onCreate() {
// ...
customAdUi = new CustomAdUi(playerView.getAdInterface().getAdApi());
ImaAdRequest adReq = new ImaAdRequest.Builder().tagUrl("<url>")
// This is a NO-OP in case VAST responses do not have the hideable tag set
.disableDefaultAdUi(true).get()
.toAdRequest());
}
protected void onStart() {
// ...
playerView.getAdInterface().addAdListener(customAdUi);
}
protected void onStop() {
// ...
playerView.getAdInterface().removeAdListener(customAdUi)
}
The CustomAdUi
class would implement the AdInterface.Listener
interface:
class CustomAdUi implements AdInterface.Listener {
private AdApi api;
private Button skipButton;
CustomAdUi(AdApi adApi) {
this.api = adApi;
// Create Views
// ...
skipButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
adApi.skipAd();
}
});
}
@Override
public void onAdStarted(@NonNull Ad ad) {
// Show UI
}
@Override
public void onAdCompleted() {
// Hide UI
}
@Override
public void onAdPlaybackPositionChanged(long playbackPositionMs) {
// Update UI
}
}
Manual ad Scheduling¶
The PRESTOplay SDK for Android, when using the IMA Plugin, allows for manual ad scheduling. This enables the user to perform ad requests without the need to inform them on playback start, but rather after playback has already started.
In order to enable this capability, it must be first enabled in the PlayerConfig
or in the Bundle
used to start playback.
The AdSchedule
configuration object should be
built using the manual
value
for the scheduleType
.
Alternatively, you can use the AD_SCHEDULE_MANUAL
constant.
// Example with Bundle
Bundle bundle = new Bundle();
bundle.putString(SdkConsts.INTENT_URL, "http://example.com/manifest.mpd");
bundle.putParcelable(SdkConsts.INTENT_AD_SCHEDULE, SdkConsts.AD_SCHEDULE_MANUAL);
// ...
playerController.open(bundle);
// Example with PlayerConfig
PlayerConfig playerConfig = new PlayerConfig.Builder("http://example.com/manifest.mpd")
.adSchedule(SdkConsts.AD_SCHEDULE_MANUAL)
// ...
.get();
playerController.open(playerConfig);
In order to perform ad requests, you need to get a reference to the AdInterface
.
playerController.getAdInterface().scheduleAd(new ImaAdRequest(adTag).toAdRequest());
By default, ads will be scheduled to be played 1.5 seconds after the response has been received. This is in order to provide a smooth transition into the ad and avoid running in a buffer underrun.
Note
This delay is not intended to schedule ads in advance. The IMA SDK expects the ad to be played shortly after the ad response, and it may completely drop the ad and skip its playback if the delay is too big. For this reason, this value is limited to 5 seconds.
The can be configured on a per-request basis as follows:
playerController.getAdInterface().scheduleAd(new ImaAdRequest.Builder()
.tagUrl(adTag)
.scheduleDelayMs(4000)
.get()
.toAdRequest());
Note that there are some limitations when it comes to manual ad scheduling:
No ads can be scheduled while already playing an ad.
Only VAST ads may be requested. If a VMAP is scheduled, it will be ignored.
Already scheduled ads, for instance, with a VMAP AdRequest informed on playback start, will be dropped if an another request is performed.
Downloader¶
The downloader plugin of the PRESTOplay SDK for Android allows the content to be downloaded and played back offline later on. Currently, the DASH, Smooth Streaming and MP4 content types are supported for the download.
In order to use the Downloader plugin you must show a persistent notification, as it is a foreground
service. This is achieved through the DownloadNotificationProvider
abstract class.
First, the application needs to register the downloader plugin prior to PRESTOplay SDK for Android initialization:
PlayerSDK.register(new DownloaderPlugin(notificationProvider));
PlayerSDK.init(getApplicationContext());
Please note that by default the downloader will permit 10 parallel segment downloads. You can configure this number in the DownloaderPlugin constructor, for example:
PlayerSDK.register(new DownloaderPlugin(notificationProvider, 5));
PlayerSDK.init(getApplicationContext());
The downloader is a foreground service and the applications need to use
DownloadServiceBinder
in order to gain access to the downloader:
DownloadServiceBinder downloadServiceBinder;
ServiceConnection downloadServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
downloadServiceBinder = (DownloadServiceBinder) iBinder;
}
public void onServiceDisconnected(ComponentName componentName) {
downloadServiceBinder = null;
}
}
Note
Do not start the service on your own, always interact with it through the DownloadServiceBinder
.
The service will automatically start itself when there’s at least one download ongoing and will
stop when there are no pending downloads.
The downloader plugin needs a DownloadNotificationProvider
implementation.
This is a sample implementation that will show a progress bar in such notification.
public class NotificationProvider extends DownloadNotificationProvider {
public NP(int notificationId) {
super(notificationId);
}
@NonNull
@Override
public Notification getNotification(@NonNull DownloadServiceBinder downloadServiceBinder, @NonNull Context context) {
Notification.Builder builder;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
String id = "content_download_channel";
createNotificationChannel(context, id);
builder = new Notification.Builder(context, id);
} else {
builder = new Notification.Builder(context);
}
builder.setContentTitle(context.getString(R.string.app_name))
.setAutoCancel(false)
.setSmallIcon(R.drawable.ic_cloud_download_black_24dp);
List<Download> downloads;
try {
downloads = downloadServiceBinder.getDownloads();
} catch (Exception e) {
e.printStackTrace();
return builder.build();
}
long totalSize = 0;
long downloadedSize = 0;
boolean allCompleted = true;
// Compute sum for all downloads
for (Download download : downloads) {
totalSize += download.getEstimatedSize();
downloadedSize += download.getDownloadedSize();
allCompleted = allCompleted && download.getState() == Download.STATE_DONE;
}
if (allCompleted) {
builder.setContentText("Downloads finished")
.setProgress(0, 0, false)
.setOngoing(false);
} else {
double progress = (double) downloadedSize / totalSize;
builder.setContentText("Downloading content")
.setProgress(100, (int) (100.0 * progress), false)
.setOngoing(true);
}
return builder.build();
}
}
The getNotification
method will be called after each downloader event (explained below).
If you want to change this behaviour you can override the onDownloadEvent
method.
To start with the new download the application provides DownloadService
with the Bundle
so that the downloader can fetch and parse the manifest and prepare the
Download
model. The Bundle
should hold the following keys:
INTENT_URL
expects aString
(mandatory),INTENT_DOWNLOAD_ID
expects aString
(mandatory),INTENT_DOWNLOAD_FOLDER
expects aString
(mandatory),INTENT_DRM_CONFIGURATION
expects aDrmConfiguration
(optional),INTENT_CONTENT_TYPE
expects one ofCONTENT_TYPE_UNKNOWN
for auto-detection,CONTENT_TYPE_DASH
,CONTENT_TYPE_SMOOTHSTREAMING
, orCONTENT_TYPE_MP4
INTENT_HD_CONTENT_FILTER
expects an integer bit fieldINTENT_VIDEO_SIZE_FILTER
expectsPoint
defining the video size filter or one ofVIDEO_SIZE_FILTER_NONE
,VIDEO_SIZE_FILTER_AUTO
INTENT_VIDEO_CODEC_FILTER
expects one ofVIDEO_CODEC_FILTER_NONE
,VIDEO_CODEC_FILTER_CAPS
String downloadId = "ID";
File moviesFolder = getExternalFilesDir(Environment.DIRECTORY_MOVIES);
File target = new File(moviesFolder, "Downloads/" + downloadId);
Bundle bundle = new Bundle();
bundle.putString(SdkConsts.INTENT_URL, "MANIFEST_URL");
bundle.putString(SdkConsts.INTENT_DOWNLOAD_ID, downloadId);
bundle.putString(SdkConsts.INTENT_DOWNLOAD_FOLDER, target.getAbsolutePath());
downloadServiceBinder.prepareDownload(context, bundle, new Downloader.ModelReadyCallback() {
public void onError(@NonNull Exception e) {
Log.e(TAG, "Error while preparing download: " + e.getMessage(), e);
}
public void onModelAvailable(@NonNull Download download) {
// initiate selection here of video quality, audio and subtitle tracks
// either automatically or manually
}
});
The client callback ModelReadyCallback
is invoked
when the Download
model is ready. Now the desired video quality,
audio and subtitle tracks have to be selected by the application either automatically or manually:
download.setSelectedVideoTrackQuality(0);
download.setSelectedAudioTracks(new int[]{0});
And finally the download needs to be registered in the downloader and optionally started:
downloadServiceBinder.createDownload(download, true);
The downloader keeps the list of downloads with states and each download can be paused, resumed and deleted independently:
List<Download> downloads = downloadServiceBinder.getDownloads();
downloadServiceBinder.pauseDownload(downloads.get(0).getId());
downloadServiceBinder.resumeDownload(downloads.get(0).getId());
downloadServiceBinder.deleteDownload(downloads.get(0).getId());
Note that createDownload
,
deleteDownload
,
resumeDownload
and
pauseDownload
are asynchronous and the results are broadcasted using LocalBroadcastManager
.
The following messages reflecting the downloader state change are defined:
IntentFilter filter = new IntentFilter();
filter.addCategory(MessageHandler.INTENT_DOWNLOAD_CATEGORY);
filter.addAction(MessageHandler.ACTION_DOWNLOAD_ERROR);
filter.addAction(MessageHandler.ACTION_DOWNLOAD_STOPPED);
filter.addAction(MessageHandler.ACTION_DOWNLOAD_CREATED);
filter.addAction(MessageHandler.ACTION_DOWNLOAD_STARTED);
filter.addAction(MessageHandler.ACTION_DOWNLOAD_DELETED);
filter.addAction(MessageHandler.ACTION_DOWNLOAD_COMPLETED);
filter.addAction(MessageHandler.ACTION_DOWNLOAD_NO_PENDING);
filter.addAction(MessageHandler.ACTION_DOWNLOAD_STORAGE_OK);
filter.addAction(MessageHandler.ACTION_DOWNLOAD_STORAGE_LOW);
filter.addAction(MessageHandler.ACTION_DOWNLOAD_PROGRESS);
filter.addAction(MessageHandler.ACTION_DOWNLOAD_PATH_UPDATE);
LocalBroadcastManager.getInstance(context).registerReceiver(new BroadcastReceiver{
public void onReceive(Context context, Intent intent) {
String downloadId = intent.getStringExtra(MessageHandler.INTENT_DOWNLOAD_ID);
Log.d(TAG, "Message: " + intent.getAction() + ", download Id: " + downloadId);
switch (intent.getAction()) {
case MessageHandler.ACTION_DOWNLOAD_STOPPED:
break;
case MessageHandler.ACTION_DOWNLOAD_CREATED:
break;
case MessageHandler.ACTION_DOWNLOAD_STARTED:
break;
case MessageHandler.ACTION_DOWNLOAD_DELETED:
break;
case MessageHandler.ACTION_DOWNLOAD_COMPLETED:
break;
case MessageHandler.ACTION_DOWNLOAD_NO_PENDING:
break;
case MessageHandler.ACTION_DOWNLOAD_ERROR:
break;
case MessageHandler.ACTION_DOWNLOAD_STORAGE_LOW:
break;
case MessageHandler.ACTION_DOWNLOAD_STORAGE_OK:
break;
case MessageHandler.ACTION_DOWNLOAD_PROGRESS:
break;
case MessageHandler.ACTION_DOWNLOAD_PATH_UPDATE:
break;
default:
break;
}
}
}, filter);
Note that when the system storage becomes low all the downloads will be paused automatically and message
ACTION_DOWNLOAD_STORAGE_LOW
will be sent.
The device storage should then be cleaned up by the application either manually or automatically and the downloads should
be resumed.
The downloader is a sticky service and will continue downloads after the system re-creates it (e.g. when the service process is killed).
To start the playback of the downloaded content the application should follow Starting Playback from an Intent with some additional keys:
Intent intent = new Intent(this, PlayerActivity.class);
intent.putExtra(SdkConsts.INTENT_URL, download.getLocalManifestUrl());
intent.putExtra(SdkConsts.INTENT_DRM_CONFIGURATION, download.getDrmConfiguration());
intent.putExtra(SdkConsts.INTENT_DOWNLOAD_FOLDER, target.getAbsolutePath());
The downloader will always download one asset (movie) at a time. The reasoning for this behaviour is that, assuming constant bandwidth for all the assets, the amount of time for all the downloads to complete would be equal regardless they’re downloaded either sequentially or in parallel. In addition, with sequential downloading, the first asset will be ready for playback sooner and should the connection be lost, only one download would be affected.
Scoped Storage¶
With the introduction of Android 10 (API 29), the concept of “scoped access” has been introduced by Google (link).
On Android 10 and higher, all apps by default get access to this mode without any additional permissions. Scoped storage allows them to access the Media of the device through the media store API, in addition to an app-specific directories.
There’s is the possibility to keep the old permission behaviour on API 29, if the application sets
the requestLegacyExternalStorage
flag to true
in the manifest.
This flag only serves for API 29, and will be ignored if the targetSdk
is set to API 30 or higher.
Your app is affected by change if your downloads are not in one of the directories returned by the following methods:
If you’re using any other path, and you update to targetSdk
30, access to those downloads will
be lost.
You can take care of migrating the existing Downloads to a scoped storage path on your own, and
updating the reference for those in our SDK. You can do this through the
updateDownloadPath
method. Note that this method will not migrate the files themselves.
We are also currently working on bringing to the Downloader additional helper methods to ease migration of data. Those will be published on a later release.
Even if you choose not to do the Downloads migration now, it is highly recommended to start using one of the aforementioned methods to retrieve a scoped storage path in order to save the new Downloads into.
For this reason the SDK will, by default, refuse to download anything into a path which is not part
of the scoped storage locations. This behaviour can be disabled by setting allowNonScopedStorageDownload
to true
if needed, but we encourage you not to do so.
Analytics¶
The PRESTOplay SDK for Android offers plugins to integrate the following third party analytics services:
The integrations use a common interface AnalyticsSession
to expose a running session.
Please note though that for the most common use-cases, you will not need
to interact with the analytics session directly. The plugins are
integrated with the player and trigger the calls against the analytics
session automatically.
By default the analytics session is created and started whenever a stream is opened with
open(Bundle)
.
However, it is also possible to create and start the analytics later, upon play()
.
This option is useful when the playback does not start automatically and the user has to actively trigger playback start:
// Add the analytics session option
bundle.putInt(SdkConsts.INTENT_ANALYTICS_SESSION_TYPE, SdkConsts.ANALYTICS_SESSION_TYPE_PLAY);
// Add the option not to start playback automatically
bundle.putBoolean(SdkConsts.INTENT_START_PLAYING, false);
All the analytics plugins use AnalyticsMetaData
provided to the PlayerController
by the client application either
explicitly through setAnalyticsMetaData
,
or through the Intent
(see Starting Playback from an Intent for an example
on how to use a Intent
to configure the player). The analytics metadata has to be created and passed before starting the playback.
Passing the metadata through the intent:
// Create analytics meta-data
AnalyticsMetaData analyticsMetaData = new AnalyticsMetaData(isLive, assetId);
// Set analytics common values
analyticsMetaData.viewerId = prefs.getUserName();
// ...
// Set analytics custom values
Bundle customValues = new Bundle();
customValues.putString("title", ".....");
customValues.putString("cdn", ".....");
analyticsMetaData.extra.putBundle("media", customValues);
// Set the analytics meta-data
intent.putExtra(SdkConsts.INTENT_ANALYTICS_DATA, analyticsMetaData);
Session detaching¶
By default, AnalyticSession are started and stopped automatically by the PlayerController. Sessions start whenever playback starts, and stop whenever content ends or the PlayerController is disposed.
If you want to avoid this, and persist a session beyond the PlayerController lifecycle, you can do the following.
In your teardown code, get the reference to the currently used AnalyticsSession and detach it from the PlayerController before releasing it.
// Keep analytics session, analyticsSession is a class variable
analyticsSession = playerView.getPlayerController().getAnalyticsSession();
if (analyticsSession != null) {
// Unbind this AnalyticsSession from the PlayerController
analyticsSession.detachFromController();
}
// Release player
playerView.getPlayerController().release();
While in this state. The AnalyticsSession will be still alive and likely, depending on the analytics provider, sending keep-alive traces.
When you want to re-attach the AnalyticsSession to the PlayerController instance, which can be
the same or a completely new instance, you just need to set it before its open()
method.
// Restore ongoing AnalyticsSession
playerView.getPlayerController().setAnalyticsSession(analyticsSession);
try {
playerView.getPlayerController().open(bundle);
} catch (Exception e) {
Log.e(TAG, "Error while opening player: " + e.getMessage(), e);
}
If a non-null AnalyticsSession is set, it will be used instead of creating a new session.
The detachFromController
method returns a boolean indicating if the AnalyticsSession could successfully unbind itself from
the PlayerController. Currently only the Youbora and Conviva implementations support session
detaching.
You should never use the same AnalyticsSession concurrently in more than one instance of PlayerController.
Make sure to always call detachFromController()
before setting the AnalyticsSession to a new
PlayerController.
Youbora¶
The PRESTOplay SDK for Android bundles an integration for Youbora, the Analytics Service from Nice People At Work.
If you are coming from a PRESTOplay SDK for Android version 4.1.9 or earlier and want to update your integration refer to the Update from PRESTOplay SDK for Android version 4.1.9 or earlier section.
The plugin and all its dependencies are part of the bundle repository and can be added as a dependency to your application build:
dependencies {
...
compile 'com.castlabs.player:youbora-plugin:1.0.0'
...
}
You should also add NPAW’s public Maven repository to your gradle file in order to get their binaries:
repositories {
...
maven { url 'http://dl.bintray.com/npaw/youbora' }
...
}
This will add the YouboraPlugin
to your project.
You can register the plugin with the SDK.
By default, the Youbora plugin will report only fatal errors to the backend. If you wish to also report the SDK warnings as Youbora non-fatal errors, you have to enable it before registering the plugin:
YouboraPlugin youboraPlugin = new YouboraPlugin(accountCode);
// Optional: Report SDK warnings as Youbora non-fatal errors (default: false)
// youboraPlugin.reportWarnings(true);
PlayerSDK.register(youboraPlugin);
Note that you have to pass a valid Youbora accountCode to the constructor in order to initialize the plugin and get access to the backend service.
In addition, you can use the Youbora Options object to customize the plugin configuration.
You should create the Options object and fill it with the desired config. This object
will be later passed to the createMetadata
method.
Please refer to more details about possible options to the Youbora documentation.
By default, the plugin overrides the following global options upon initialization:
accountCode
is always set to the systemId you pass in the plugin constructor
username
is set toanalyticsMetadata.viewerId
contentIsLive
is set toanalyticsMetadata.live
Also, the following fields may be filled by the plugin, if they are not informed:
content_id
insidecontentMetadata
will be filled withanalyticsMetadata.assetId
contentDuration
will be filled withanalyticsMetadata.durationSeconds
contentResource
will be filled with PlayerController’sgetPath
return value
The Youbora documentations lists more options you can pass in the global configuration.
Passing Youbora configuration to the player:
// Create Youbora Options object
Options youboraOptions = new Options();
// Set any desired fields
youboraOptions.setContentTitle("Movie");
// Extra properties
Bundle properties = new Bundle();
properties.putString("language", "English");
properties.putString("year", "2018");
properties.putString("price", "Free");
youboraOptions.setContentMetadata(properties);
// Create AnalyticsMetadata
AnalyticsMetaData analyticsMetaData = YouboraPlugin.createMetadata(
false, // Live or not, will set analyticsMetadata.live
prefs.getAssetId(), // Unique asset identifier, will set analyticsMetadata.assetId
youboraOptions); // Youbora Options object
// Set a user ID
analyticsMetaData.viewerId = prefs.getUserName();
// Set the analytics meta-data
intent.putExtra(SdkConsts.INTENT_ANALYTICS_DATA, analyticsMetaData);
In this example, we create the meta-data and then pass it to the Intent Bundle that is used to start playback.
With this meta-data configuration in place and passed to the
PlayerController
either through the
Intent Bundle or explicitly trough setAnalyticsMetaData
,
the session will be automatically started and stopped.
Youbora Sessions¶
Youbora also offers application tracking, called Youbora Sessions. For exact details on how to use this service please refer to NPAW’s documentation. In this section we exclusively present the minimum code required to achieve interoperability, and allow Youbora’s library to match the application Session with the video ones generated by the PRESTOplay SDK for Android.
In the Application’s onCreate
, when initializing the SDK alongside with the YouboraPlugin
,
pass in an instance of the Plugin you will use to perform application tracking.
private static final String YOUBORA_CUSTOMER_KEY = "youboracustomerkey";
@Override
public void onCreate() {
super.onCreate();
// ...
Options options = new Options();
options.setAccountCode(YOUBORA_CUSTOMER_KEY);
// Set other required options
// Plugin instance to use for application tracking
this.youboraSDK = new Plugin(options, this);
YouboraPlugin youboraPlugin = new YouboraPlugin(YOUBORA_CUSTOMER_KEY, this.youboraSDK);
// ...
PlayerSDK.register(youboraPlugin);
}
In case you want to update the Plugin instance once the application has been created, this can be achieved through the component. This should be done before starting a playback session, but it is usually not required if the Plugin instance stays the same throughout the whole application lifecycle.
// (Optional) Set the Youbora plugin to use before opening the PlayerController
// This is not needed if the Youbora's Plugin instance is the same as set on
// the app's onCreate
YouboraAnalyticsSession component = playerController.getComponent(YouboraAnalyticsSession.class);
component.setPlugin(youboraPlugin);
playerController.open(...);
Update from PRESTOplay SDK for Android version 4.1.9 or earlier¶
The PRESTOplay SDK for Android version 4.1.10 updates the Youbora integration to version 6. This introduces some breaking changes that are detailed here.
The Youbora binaries are now distributed in a Maven public repository. So you will need to add NPAW’s repository to your gradle file:
repositories {
...
maven { url 'http://dl.bintray.com/npaw/youbora' }
...
}
The Youbora plugin is now configured through a custom configuration object; Options
,
instead of a Map
.
Refer to the Youbora developers portal for a complete explanation about these Options and how to migrate them to V6; Setting Youbora Options.
You should create the Options
object and fill it with the desired config. This object
will be later passed to the createMetadata
method.
Nielsen¶
The PRESTOplay SDK for Android is integrated with Nielsen service in the form of a plugin. First, the plugin needs to be added as a dependency in the application gradle:
dependencies {
...
implementation 'com.castlabs.player:nielsen-plugin:1.0.0'
...
}
Then the plugin is registered and enabled by the application:
PlayerSDK.register(new NielsenPlugin(appId, appName, appVersion, sfCode));
...
NielsenPlugin nielsen = PlayerSDK.getPlugin(NielsenPlugin.class);
if (nielsen != null) {
nielsen.setEnabled(true);
}
Or the other constructor could be used to make a new instance of NielsenPlugin
:
JSONObject config = new JSONObject();
try {
config.put(NielsenPlugin.CONFIG_KEY_APPID, appId);
config.put(NielsenPlugin.CONFIG_KEY_APPNAME, appName);
config.put(NielsenPlugin.CONFIG_KEY_APPVERSION, appVersion);
config.put(NielsenPlugin.CONFIG_KEY_SFCODE, sfCode);
} catch (JSONException e) {
e.printStackTrace();
}
NielsenPlugin nielsenPlugin = new NielsenPlugin(config);
The Nielsen plugin benefits from the PRESTOplay SDK for Android integrated with IMA service and provides two use cases out of the box: content with and without IMA advertisements. See an example above on how to use the PRESTOplay SDK for Android plugin integrated with IMA.
The plugin sends content and ads metadata to the Nielsen backend. The content metadata is taken by the plugin from the
AnalyticsMetaData
. The advertisements metadata is taken from IMA Ad
object.
The plugin uses the following variables to generate the channelInfo
:
channelName
will be filled withanalyticsMetadata.assetId
videoUrl
will be filled withplayerController.getPlayerConfig().contentUrl
And in order to generate the jsonMetadata
(in loadMetadata(JSONObject jsonMetadata)):
assetId
will be filled withanalyticsMetadata.assetId
length
will be filled withplayerController.getDuration()
And finally to generate the advertisement metadata:
type
is one ofpreroll
,midroll
andpostroll
according toad.position
length
is equal toTimeUtils.ms2s(ad.durationMs)
assetid
is equal toad.id
title
is equal toad.title
However, when needed, the advertisement metadata can be enriched by the client application by implementing and
registering AdClientInterface.Listener
:
PlayerView playerView;
...
AdController adController = playerView.getPlayerController().getAdController();
if (adController != null) {
adController.setClientListener(new AdClientInterface.Listener() {
@Nullable
public Bundle onGetMetadata(@NonNull Ad ad) {
Bundle bundle = new Bundle();
bundle.putString("cdn", ".....");
return bundle;
}
});
}
It is also possible that instead of the integrated IMA service the client uses its own implementation of an ads provider. In this case, the client needs to take care of content and ads playback switches and the plugin only forwards the ads metadata to the Nielsen backend:
PlayerView playerView;
...
AdController adController = playerView.getPlayerController().getAdController();
if (adController != null) {
adController.adStarted(new Ad());
...
adController.adSetPlaybackPosition(1);
adController.adSetPlaybackPosition(2);
...
adController.adCompleted();
}
Note, that in the latter case the ads metadata enrichment is also possible.
Mux¶
The PRESTOplay SDK for Android is integrated with Mux service in the form of a plugin. First, the plugin needs to be added as a dependency in the application gradle:
dependencies {
...
compile 'com.castlabs.player:mux-plugin:1.0.0'
...
}
You also need to add Mux’ Maven repository to your dependencies in gradle:
repositories{
...
maven { url 'https://muxinc.jfrog.io/artifactory/default-maven-release-local/' }
...
}
This will add Mux plugin to the project and the plugin can be registered with the PRESTOplay SDK for Android:
MuxPlugin muxPlugin = new MuxPlugin(MUX_ENVIRONMENT_KEY);
PlayerSDK.register(muxPlugin);
Note that you can also omit the Mux ENVIRONMENT KEY in the constructor. If you chose not to inform it,
it must be set in Mux’s CustomerPlayerData
right before opening the PlayerController
. If the
Environment Key is informed in the CustomerPlayerData
passed to the Player, it will override
the one used to create the MuxPlugin
.
In addition, you can use the Mux CustomerPlayerData
object to add content metadata.
You should create the CustomerPlayerData
object and fill it with the desired fields. This object
will be later passed to the createMetadata
method.
Please refer to more details about this object to the Mux documentation.
The Plugin will automatically map the following values from AnalyticsMetaData
.
videoIsLive
is informed with the value fromanalyticsMetaData.live
videoId
is informed with the value fromanalyticsMetaData.assetId
In addition, the Plugin will try to fill the following fields if they are uninformed:
videoDuration
is taken fromanalyticsMetaData.durationSeconds
if informed, or from the stream itself otherwise (if available)
videoSourceUrl
filled with PlayerController’sgetPath
return value
videoStreamType
possible values are DASH, HLS, MP4, SmoothStreaming and Unknown
Passing Mux metadata to the player:
// Create MUX CustomerData, fill in metadata
CustomerVideoData videoData = new CustomerVideoData();
videoData.setVideoTitle("Inception");
videoData.setVideoLanguageCode("en");
videoData.setVideoProducer(...);
videoData.setVideoEncodingVariant(...);
// Create MUX PlayerData
CustomerPlayerData playerData = new CustomerPlayerData();
playerData.setViewerUserId("userId");
playerData.setExperimentName(...);
// This will override the key set when creating the MuxPlugin
// Not needed in most cases
playerData.setEnvironmentKey("MUX_ENVIRONMENT_KEY");
CustomerViewData viewData = new CustomerViewData();
viewData.setViewSessionId("viewSessionId");
// (Optional) Set custom dimensions data
CustomData customData = new CustomData();
customData.setCustomData1("customDimensionValue1");
// (Optional) Custom Options
CustomOptions customOptions = new CustomOptions();
//customOptions.setBeaconDomain("beacon.example.domain.com");
// Create CustomerData and pass all data objects
CustomerData customerData = new CustomerData(playerData, videoData, viewData);
customerData.setCustomData(customData);
// Create AnalyticsMetadata
AnalyticsMetaData analyticsMetaData = MuxPlugin.createMetadata(
false, // Live or not, will set analyticsMetadata.live
prefs.getAssetId(), // Unique asset identifier, will set analyticsMetadata.assetId
customerData, // CustomerData
customOptions, // CustomOptions, or null
null); // Already existing AnalyticsMetaData object, or null
// Set the analytics meta-data
intent.putExtra(SdkConsts.INTENT_ANALYTICS_DATA, analyticsMetaData);
In this example, we create the meta-data and then pass it to the Intent Bundle that is used to start playback.
With this meta-data configuration in place and passed to the
PlayerController
either through the
Intent Bundle or explicitly trough setAnalyticsMetaData
,
the session will be automatically started and stopped.
Conviva¶
The PRESTOplay SDK for Android is integrated with Conviva analytics service in the form of Conviva plugin. The Conviva plugin is part of the bundle repository and can be added as a dependency in the application gradle:
dependencies {
...
implementation 'com.castlabs.player:conviva-plugin:1.0.0'
...
}
This will add Conviva plugin to the project and the plugin can be registered with the PRESTOplay SDK for Android:
ConvivaPlugin convivaPlugin = new ConvivaPlugin(CONVIVA_CUSTOMER_KEY);
if (BuildConfig.DEBUG) {
HashMap<String, Object> settings = new HashMap<>();
settings.put(ConvivaSdkConstants.GATEWAY_URL, CONVIVA_TOUCHSTONE_GATEWAY);
settings.put(ConvivaSdkConstants.LOG_LEVEL, ConvivaSdkConstants.LogLevel.DEBUG);
convivaPlugin.setSettings(settings);
}
HashMap<String, String> deviceInfo = new HashMap<>();
deviceInfo.put(ConvivaSdkConstants.DEVICEINFO.ANROID_BUILD_MODEL, ".....");
deviceInfo.put(ConvivaSdkConstants.DEVICEINFO.OPERATING_SYSTEM_VERSION, ".....");
deviceInfo.put(ConvivaSdkConstants.DEVICEINFO.DEVICE_BRAND, ".....");
deviceInfo.put(ConvivaSdkConstants.DEVICEINFO.DEVICE_MANUFACTURER, ".....");
deviceInfo.put(ConvivaSdkConstants.DEVICEINFO.DEVICE_MODEL, ".....");
deviceInfo.put(ConvivaSdkConstants.DEVICEINFO.DEVICE_TYPE, ".....");
deviceInfo.put(ConvivaSdkConstants.DEVICEINFO.DEVICE_VERSION, ".....");
convivaPlugin.setDeviceInfo(deviceInfo);
PlayerSDK.register(convivaPlugin);
The Conviva plugin benefits from the PRESTOplay SDK for Android integrated with IMA service and provides with two use cases
out of the box: content with and without IMA advertisements. The content metadata is taken by the plugin from the
AnalyticsMetaData
. The advertisements metadata is taken from
IMA Ad
object.
The Plugin will automatically map the following values:
ConvivaSdkConstants.FRAMEWORK_NAME
is filled with the value fromSdkConsts.PLAYER_NAME
ConvivaSdkConstants.FRAMEWORK_VERSION
is filled with the value fromPlayerSDK.getVersion()
ConvivaSdkConstants.STREAM_URL
is filled with the value fromplayerController.getPath()
ConvivaSdkConstants.ASSET_NAME
is filled with the value fromanalyticsMetaData.assetId
ConvivaSdkConstants.IS_LIVE
is filled with the value fromanalyticsMetaData.live
ConvivaSdkConstants.VIEWER_ID
is filled with the value fromanalyticsMetaData.viewerId
ConvivaSdkConstants.DURATION
is filled with the value fromTimeUtils.us2s(playerController.getDuration())
(If the duration was not valid it will filled withanalyticsMetaData.durationSeconds
)
Also the advertisement metadata will automatically map the following values:
ConvivaSdkConstants.AdType
is determined with the value ofad.streamType
(It will be ConvivaSdkConstants.AdType.SERVER_SIDE or ConvivaSdkConstants.AdType.CLIENT_SIDE)
ConvivaSdkConstants.AdPlayer
is determined with the value ofad.playerType
(It will be ConvivaSdkConstants.AdPlayer.CONTENT or ConvivaSdkConstants.AdPlayer.SEPARATE)
Passing Conviva metadata to the player:
// Create extra tags
Bundle extra = new Bundle();
// Conviva predefined tags
extra.putString(ConvivaAnalyticsSession.META_KEY_APPLICATION_NAME, "My Application Name");
extra.putString(ConvivaAnalyticsSession.META_KEY_DEFAULT_RESOURCE, "...");
// Optional custom tags
Bundle customTags = new Bundle();
customTags.putString("sampleCustomTag", "sampleCustomValue");
// Create analytics meta-data
AnalyticsMetaData analyticsMetaData = ConvivaPlugin.createMetadata(live, assetId, "viewerID", extra, customTags);
// Set the analytics meta-data
intent.putExtra(SdkConsts.INTENT_ANALYTICS_DATA, analyticsMetaData);
The Conviva plugin also exposes the underlying ConvivaVideoAnalytics and ConvivaAdAnalytics instances. This may be useful for an advanced use cases, such as sending custom playback related events.
To send Conviva custom playback related event:
// Get the Analytics session
ConvivaAnalyticsSession convivaAnalyticsSession = playerView.getPlayerController().getComponent(ConvivaAnalyticsSession.class);
if (convivaAnalyticsSession != null) {
// Fill custom map
HashMap<String, Object> customData = new HashMap<>();
customData.put("now", Long.toString(System.currentTimeMillis()));
customData.put("fizz", "buzz");
String eventType = "customEvent";
// Get the ConvivaVideoAnalytics instance
ConvivaVideoAnalytics videoAnalytics = convivaAnalyticsSession.getVideoAnalytics();
// Send event
if (videoAnalytics != null) {
videoAnalytics.reportPlaybackEvent(eventType, customData);
}
// Get the ConvivaAdAnalytics instance
ConvivaAdAnalytics adAnalytics = convivaAnalyticsSession.getAdAnalytics();
// Send event
if (adAnalytics != null) {
String errorMessage = "...";
adAnalytics.reportAdFailed(errorMessage);
}
}
Broadpeak¶
The PRESTOplay SDK for Android is integrated with Broadpeak analytics service in the form of Broadpeak plugin. The Broadpeak plugin is part of the bundle repository and can be added as a dependency in the application gradle:
dependencies {
...
compile 'com.castlabs.player:broadpeak-plugin:1.0.0'
...
}
This will add Broadpeak plugin to the project and the plugin can be registered with the PRESTOplay SDK for Android:
BroadpeakPlugin broadpeakPlugin = new BroadpeakPlugin("analytics_url", "nano_cdn_host", "*")
PlayerSDK.register(broadpeakPlugin);
// ...
PlayerSDK.init(getApplicationContext());
The Broadpeak plugin uses takes care of sending player metrics and managing view lifecycle calls as required by the underlying SmartLib class.
The BroadpeakPlugin
intercepts the first manifest request, and replaces it with the return value
of SmartLib::getURL
. It’s the SmartLib responsibility to perform such Uri redirect. Internally,
the SmartLib uses the domains name list in order to decide whether to actually perform a url redirect
for the current domain or not. This domain list should be set while creating the BroadpeakPlugin
,
through its last argument.
Warning
If the default domain names list is used ("*"
), all manifest requests will try to be redirected
by the SmartLib
. This can introduce a significant delay in start-up time as the SmartLib
tries to resolve the redirect.
Make sure you set the domain names list appropriately, following Broadpeak’s recommendation.
If you want to perform any advanced operations and interact directly with the Broadpeak SDK, you can use the SmartLib class.
In case that the BroadpeakPlugin should be disabled for a playback session, this can be achieved
as follows, before creating the PlayerController
:
BroadpeakPlugin broadpeakPlugin = PlayerSDK.getPlugin(BroadpeakPlugin.class);
broadpeakPlugin.setEnabled(false);
// ...
playerView.getPlayerController().open(...);
This way the BroadpeakPlugin
can be enabled or disabled at run-time without needing to re-init
the SDK.
The Broadpeak session allows to set custom parameters and options. This is how those can be passed to the player:
// Create Analytics Metadata for Broadpeak
Bundle customParameters = new Bundle();
customParameters.putString("name", "value");
customParameters.putString("pre_startup_time", "in_ms");
// Options
SparseArray<Object> options = new SparseArray<Object>();
options.put(StreamingSessionOptions.GDPR_PREFERENCE, StreamingSessionOptions.GDPR_CLEAR);
options.put(StreamingSessionOptions.USERAGENT_AD_EVENT, "useragent");
options.put(StreamingSessionOptions.SESSION_KEEPALIVE_FREQUENCY, 5000);
AnalyticsMetaData analyticsMetaData = BroadpeakPlugin.createMetadata(live, assetId, customParameters, options);
put(SdkConsts.INTENT_ANALYTICS_DATA, metadata);
// Set the analytics meta-data
intent.putExtra(SdkConsts.INTENT_ANALYTICS_DATA, analyticsMetaData);
In this example, we create the meta-data and then pass it to the Intent Bundle that is used to start playback.
Please check the Broadpeak documentation for a complete list of the available parameters and options.
Vimond¶
The PRESTOplay SDK for Android is integrated with Vimond Player Session API in the form of a plugin.
In addition to providing basic analytics capabilties, the Vimond
plugin provides an implementation
of concurrent stream limiting.
First, the plugin needs to be added as a dependency in the application gradle:
dependencies {
...
compile 'com.castlabs.player:vimond-plugin:1.0.0'
...
}
This will add Vimond plugin to the project and the plugin can be registered with the PRESTOplay SDK for Android:
VimondPlugin vimondPlugin = new VimondPlugin();
PlayerSDK.register(vimondPlugin);
At playback time, a JSONObject
must be provided to the plugin, alongside with Vimond’s authentication
token.
This object, alongside with the token, will be later passed to the createMetadata
method. This object, or “template”, must be retrieved through
Vimond’s Play Service API prior to
playback session start. Please refer to more details about this object to the Vimond documentation.
In order to retrieve a response from Vimond’s Play Service API, an authentication token is needed, which will be also needed by the Plugin to perform the Player Session API calls.
From the Play Service API response, the template object must be retrieved. The Plugin will automatically fill in most of the fields present in the Vimond template, but you are still required to provide values for the following fields:
originator
client.envPlatform
client.envVersion
viewingSession
You can get a detailed description of each field on Vimond’s documentation.
Passing Vimond template to the player:
// Extract Vimond template from Vimond's Play Service API response
// ...
vimondObject = new JSONObject(...);
// Fill in required fields
vimondObject.put("originator", ...);
vimondObject.getJSONObject("client").put("envPlatform", ...);
vimondObject.getJSONObject("client").put("envVersion", ...);
vimondObject.put("viewingSession", ...);
// Create AnalyticsMetadata with the template and the Auth token.
AnalyticsMetaData analyticsMetaData = VimondPlugin.createMetadata(vimondObject, "token");
// Set the analytics meta-data
intent.putExtra(SdkConsts.INTENT_ANALYTICS_DATA, analyticsMetaData);
In this example, we create the meta-data and then pass it to the Intent Bundle that is used to start playback.
Alternatively, you can also use a PlayerConfig
object and set its analyticsMetaData
field:
// ...
// Set the analytics meta-data
playerConfigBuilder.analyticsMetaData(analyticsMetaData);
With this meta-data configuration in place and passed to the
PlayerController
either through the
Intent Bundle or explicitly trough setAnalyticsMetaData
,
the session will be automatically started and stopped.
The VimondPlugin
allows for further customisation via the VimondComponent
.
The component allows for registering a callback
to receive all Vimond backend response codes and act accordingly. It also allows for setting a new
authentication token at runtime, and other configuration flags.
The callback can be used for logging, or to perform a custom action if desired.
For instance, if the VimondPlugin
is configured not to stop playback on an invalid response
code, such action could be performed in the callback: playerController.release()
.
In order to register a callback, you must retrieve the instance of the VimondComponent
as follows:
VimondComponent vimond = playerController.getComponent(VimondComponent.class);
vimond.setCallback(new VimondCallback() {
@Override
public void onVimondResponse(int httpStatusCode,
@Nullable Map<String, List<String>> headers,
@Nullable String responseBody) {
Log.d(TAG, "Vimond response (" + httpStatusCode + ") " + responseBody);
}
});
Adobe¶
The PRESTOplay SDK for Android is integrated with Adobe Mobile Services SDK in the form of a plugin.
Adobe’s SDK allows for tracking app navigation events, alongside with video tracking events. This integration takes care of the later of those. The SDK can be used from app code to perform additional operations, such as app navigation tracking.
First, the plugin needs to be added as a dependency in the application gradle:
dependencies {
...
compile 'com.castlabs.player:adobe-plugin:1.0.0'
...
}
This will add the Adobe plugin to the project and the plugin can be registered with the PRESTOplay SDK for Android:
AdobePlugin adobePlugin = new AdobePlugin();
PlayerSDK.register(adobePlugin);
To configure Adobe’s SDK, a JSON file with name ADBMobileConfig.json
must be included
in the application’s assets
directory. The file contains the information about your
Adobe account. At runtime, the Adobe SDK will read such JSON and use its configuration. For
more information on how to obtain the configuration JSON please refer to Adobe’s
documentation.
To enable the Adobe plugin, you must create it like any other plugin, and register it in the PlayerSDK
:
AdobePlugin adobePlugin = new AdobePlugin(/* optional boolean for debug logging */);
PlayerSDK.register(adobePlugin);
// ...
PlayerSDK.init(getApplicationContext());
At playback time, a MediaSettings
instance must be provided to the plugin, in order for it to generate
the AnalyticsMetadata
instance, which will be later passed to the Player through the Intent
or
the PlayerConfig
when calling one of the PlayerController.open(...)
methods.
The plugin will not override any of the fields in the MediaSettings
, except for the
name
in case it is null
.
When starting playback, the MediaSettings
must be provided to the plugin, via
createMetadata
, in order to generate
the AnalyticsMetaData
instance, which is
then passed to the open()
method:
MediaSettings mediaSettings = new MediaSettings();
// Populate MediaSettings as desired
bundle.putParcelable(SdkConsts.INTENT_ANALYTICS_DATA, AdobePlugin.createMetadata(mediaSettings));
// ...
playerController.open(bundle);
Alternatively, you can also use a PlayerConfig
object and set its analyticsMetaData
field:
// ...
// Set the analytics meta-data
playerConfigBuilder.analyticsMetaData(analyticsMetaData);
With this meta-data configuration in place and passed to the
PlayerController
either through the
Intent Bundle or explicitly trough setAnalyticsMetaData
,
the session will be automatically started and stopped.
Crash Logging¶
The Crashlog
class can be used to hook up third party crash
reporting frameworks (such as Crashlytics). It is used throughout the SDK to
report additional data in case a crash occurs. These data are delegated to
registered CrashReporter
instances.
In order to integrate a third party service, you will need to implement
the CrashReporter
interface and register it using the
addReporter
method.
The SDK logs the following meta data through the crash logger class:
Key |
Description |
---|---|
CL-Playback-URL |
The current playback URL |
CL-DRM-URL |
The license server URL |
CL-DRM-Offline-Id |
The identifier used to store the offline key |
CL-DRM-RequestID |
The log request ID generated for calls to DRMToday |
CL-DRM-AssetID |
The last used asset ID |
CL-DRM-VariantID |
The last used variant ID |
CL-DRM-Type |
The last used DRM type (Widevine, Playready, or OMA) |
CL-DRM-Audio-Type |
The last used DRM type for Audio tracks(Widevine, Playready, or OMA) |
CL-DRM-Type |
The last used DRM type (Widevine, Playready, or OMA) |
CL-DRM-Device-Level |
The security level available for the selected DRM type |
CL-Playback-State |
The current playback state |
CL-SDK-Version |
The version of the PRESTOplay SDK for Android |
CL-Playback-Video |
The dimensions of the currently played video representation |
CL-Playback-Video-Bitrate |
The bitrate of the currently played video representation |
CL-Playback-Audio-Bitrate |
The bitrate of the currently played audio representation |
CL-Playback-PositionMS |
The current playback position in milliseconds. Please note that this is updated at most with a resolution of one second. |
In addition to the meta-data, the PRESTOplay SDK for Android will log all Exceptions that
raised during playback as non-fatal errors to the Crashlog
. You
can configure this behavior globally using the global
PlayerSDK.CRASHLOG_AUTO_REPORT
configuration option.
Crashlytics Integration¶
The SDK package bundles a plugin that can be used to setup and integrate
Crashlytics with the SDK’s Crashlog
mechanism. To enable the plugin,
you will need to add it as a dependency in your project setup:
dependencies {
...
compile 'com.castlabs.player:crashlytics-connector:1.0.0'
...
}
With the connector added to your project dependencies, you can load and register the plugin before you initialize the SDK:
Fabric.with(getApplicationContext(), new Crashlytics());
PlayerSDK.register(new CrashlyticsPlugin());
PlayerSDK.init(getApplicationContext());
Please note that the plugin does not initialize Fabric and Crashlytics for you. Before you register an instance of this plugin with the SDK, please make sure that you initialize the Fabric SDK and add at least the Crashlytics Kit.
User Agent¶
The PRESTOplay SDK for Android uses a custom user agent string for all network communications with our DRMtoday service and to the content provider.
You can use this information to monitor which SDK version, device model, or API are in use. As well, castLabs compiles this information to help customers solve issues quickly and to help improve the overall quality of the service.
The parameters found on the customer User Agent are:
SDK Version: The three digit number representing the used PRESTOplay SDK for Android version.
Model: The device model.
API Version: The Android API version of the device.
ExoPlayer Library Version: The ExoPlayer library version used by the SDK.
Customer Id: An identification string for the service provider.
Device Id: The device Id as provided by the operating system.
In addition to these predefined values, you can register additional key value
pairs using the UserAgent.register()
method.
These values will be stored globally and sent with each request.
Streamroot Plugin¶
The SDK package bundles a plugin that can be used to setup and integrate Streamroot DNA multi-source content delivery solutions. To enable the plugin, you will need to add it as a dependency in your project setup:
dependencies {
...
compile 'com.castlabs.player:streamroot-plugin:1.0.0'
...
}
The example above integrates the plugin as a dependency. You can now register it with the SDK:
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
PlayerSDK.register(new StreamrootPlugin(
"backend_url",
this
));
PlayerSDK.init(getApplicationContext());
}
}
The streamrootKey is now set as a meta-data in the application manifest.
<manifest package="com.castlabs.sdk.streamroot_demo" xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<meta-data android:name="io.streamroot.dna.StreamrootKey" android:value="your_streamroot_key"/>
After the plugin is registered, the player will use Streamroot DNA to create manifest and segments proxy.
Streamroot Plugin requires Java 8 to operate. Streamroot SDK is built with a support range going from Android 19 (4.4 KitKat) to Android 27 (8.0 Oreo). (for more details please check Streamroot DNA Support <https://support.streamroot.io/>)
OkHttp Plugin¶
If you want to use the popular OkHttp Library, the SDK bundle contains a plugin that you can use to integrate OkHttp into the player.
To use the plugin, you need to integrate it as a dependency into you Application build and register the plugin with the SDK:
dependencies{
...
compile 'com.castlabs.player:okhttp-plugin:1.0.0'
...
}
The example above integrates the plugin as a dependency. You can now register it with the SDK:
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
PlayerSDK.register(new OkHttpPlugin());
PlayerSDK.init(getApplicationContext());
}
}
After the plugin is registered, the player will use OkHttp to create HTTP requests to manifests and content.
Intercept Traffic¶
OkHttp can be also used for debugging purposes to intercept and show the network traffic. Please note that you should not deploy this in production when you publish your Application. This is a tool used for development and debugging purposes. You can find more information about Stetho, the library used for this, on their Website.
In order to use the network interceptor, you will need to add additional dependencies to the Stetho library and its OkHttp integration:
dependencies{
...
compile 'com.castlabs.player:okhttp-plugin:1.0.0'
compile 'com.facebook.stetho:stetho:1.3.1'
compile 'com.facebook.stetho:stetho-okhttp3:1.3.1'
...
}
With these dependencies in place, you can setup the plugin using the network interceptor:
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
PlayerSDK.register(new OkHttpPlugin(
new OkHttpClient.Builder().addNetworkInterceptor(
new StethoInterceptor())));
PlayerSDK.register(new OkHttpPlugin());
PlayerSDK.init(getApplicationContext());
}
}
The OkHttp plugin allows you to pass a pre-configured builder in its’ constructor. We use that to pass in a builder configured to interecept network traffic using Stetho.
Using this extended setup allows you to use your local chrome development tools to intercept and show network traffic. For that, run the Application on a device connected via USB, start chrome and open a tab on chrome://inspect/#devices. Enable USB discovery and you will see you Application listed. You can now inspect the network traffic of your application.
Idle connection eviction¶
While usually there’s no need to handle connections at this level, it’s possible to evict all idle connections held by OkHttp. This might be of interest to save server resources if connections are kept alive by it for a very long time:
.. code-block:: java
playerController.release();
// Evict idle connections final OkHttpPlugin okHttpPlugin = PlayerSDK.getPlugin(OkHttpPlugin.class); okHttpPlugin.evictIdleConnections();
Thumbnails¶
Since version 4 of the PRESTOplay SDK for Android, the bundle contains the Thumbnail plugin. This plugins adds support to load and display scrubbing thumbnails in various formats.
The plugin is distributed with the SDK and you can add it as a dependency:
dependencies { ... compile 'com.castlabs.player:thumbs-plugin:4.2.85' ... }
Once added you can register it with the SDK:
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
PlayerSDK.register(new ThumbsPlugin(true));
PlayerSDK.init(getApplicationContext());
}
}
Note that we pass true
as the constructor argument here. This will add
the default view that can be used for basic rendering on top of
a PlayerView
. You can find more about the parameter in the API
documentation for ThumbsPlugin
.
The plugin supports the following formats:
BIF Containers
WebVTT index
Single or gridded JPG files with a template URL
DASH in-stream thumbnails
Loading strategy¶
In order to improve the user experience. The Thumbnail plugin also allows for smarter preloading
approaches. This is achieved through the LoadingStrategy
class.
By default, thumbnail load will start whenever the first thumbnail is requested. It is possible
to configure this behaviour so that loading starts immediately, or after a delay following playback
start. This can be configured by providing a delay:
Builder.loadStartDelayMs(long)
.
A loading strategy defines loading “waves”. These waves only load a subset of all the available thumbnails for an asset. The goal being to spread out the thumbnails and to be able to provide a reasonable thumbnail for any media time in a short time.
A more spaced-out Wave will load fewer thumbnails and finish preloading faster, but will provide less accurate thumbnails. A less spaced Wave will provide more accurate thumbnails but takes longer to complete.
The plugin already uses a Wave-based approach for preloading by default. Such default consists of 3 waves which will preload thumbnails in 1-minute intervals, followed by 15-second intervals followed by all thumbnails.
A custom LoadingStrategy
can be defined upon plugin creation. The following example defines four
waves:
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
final ThumbsPlugin plugin = new ThumbsPlugin(true);
plugin.setLoadingStrategy(new LoadingStrategy.Builder()
.loadStartDelayMs(0) // Start loading immediately
.addPercentageWave(0.10f)
.addTimeWave(1, TimeUnit.MINUTES)
.addTimeWave(20, TimeUnit.SECONDS)
.addStepWave(1)
.get());
PlayerSDK.register(plugin);
PlayerSDK.init(getApplicationContext());
}
}
Note that waves are processed sequentially in addition order, so they should be defined in ascending “density” order, ie. waves which will preload more thumbnails should be added last.
This can also be achieved by setting a different loading strategy at runtime, before opening the Player:
final ThumbnailProvider thumbs = playerController.getComponent(ThumbnailProvider.class);
thumbs.setLoadingStrategy(new LoadingStrategy.Builder()
.loadStartDelayMs(0) // Start loading immediately
.addPercentageWave(0.10f)
.addTimeWave(1, TimeUnit.MINUTES)
.addTimeWave(20, TimeUnit.SECONDS)
.addStepWave(1)
.get());
playerController.open(...);
For more details check the documentation of the LoadingStrategy
class.
Network Configuration¶
The NetworkConfiguration
can be used to specify the timeouts for the thumbnail downloads.
The following example shows how you can achieve this:
bundle.put(SdkConsts.INTENT_NETWORK_CONFIGURATION, new NetworkConfiguration.Builder()
// You can specify the timeouts for the thumbnails
.thumbnailConnectionTimeoutMs(500)
.thumbnailReadTimeoutMs(500)
.get());
Embedded¶
For embedded thumbnails, such as in a DASH in-stream thumbnails, there’s no additional action required. The SDK will automatically load the thumbnails defined in AdaptationSets onto the Player Model. You still need to control how those will be rendered, which is detailed in the section Using the built-in thumbnails view.
Side-loaded¶
You can start loading thumbnail data directly if you registered the plugin and you are loading
your streams through an Intent Bundle (see Starting Playback from an Intent).
You need to create an instance of SideloadedTrack
and add it to your intent bundle
using the SdkConsts.INTENT_SIDELOADED_TRACKS_ARRAYLIST
key. For example:
ArrayList<SideloadedTrack> data = new ArrayList<SideloadedTrack>(){{
add(new SideloadedTrack.ThumbnailBuilder()
.url("thumbs/thumbs.vtt")
.thumbnailType(ThumbnailDataTrack.TYPE_WEBVTT_INDEX)
.get());
}}
...
intent.putParcelableArrayList(SdkConsts.INTENT_SIDELOADED_TRACKS_ARRAYLIST, data);
The ThumbnailBuilder
also provides methods for configuring extra fields necessary for other
thumbnail formats, such as gridded JPEG template:
ArrayList<SideloadedTrack> data = new ArrayList<SideloadedTrack>(){{
add(new SideloadedTrack.ThumbnailBuilder()
.url("http://example.com/content/thumbs/$index$.jpg")
.gridHeight(4)
.gridWidth(5)
.thumbnailType(ThumbnailDataTrack.TYPE_JPEG_TEMPLATE)
.intervalMs(10_000)
.get());
}}
...
intent.putParcelableArrayList(ThumbsPlugin.INTENT_SIDELOADED_TRACKS_ARRAYLIST, data);
This will create a data object and add it to the intent. Note that we are specifying the type explicitly here. This is not strictly necessary and the plugin will try to infer the type from the URL extension, i.e. .vtt vs. .bif vs. .jpg.
With the data object added to the intent, an implementation of
ThumbnailProvider
will be created automatically and is exposed as a component of the
PlayerController
. You can access the provider instance using
PlayerController.getComponent()
.
For example:
ThumbnailProvider provider = playerController.getComponent(ThumbnailProvider.class);
The ThumbnailProvider
interface is the main
interaction point to get access to thumbnails. It provides
a getThumbnail
method that asynchronously loads a thumbnail for the
requested position and provides it back the callback implementation that
is passed to the method. It supports performing multiple concurrent getThumbnail
calls.
Thumbnails can also be requested with a time tolerance. Check the
Thumbnail rendering time tolerance section for more details.
At this point a view implementation can use the provider and a callback to get a handle on the Bitmap and show it on screen. The PRESTOplay SDK for Android already bundles a simple view implementation with a relatively flexible way to render and position a thumbnail.
Using the built-in thumbnails view¶
If you registered the thumbnails plugin and enabled the usage of the
internal view component, you do not need to create a view or interact with
the provider explicitly. Instead, you add your ThumbnailData
to the
Intent Bundle and once a PlayerView is initialized, you can access the
thumbnail view component and show or hide thumbnails on to of your player
view.
In order to properly render the thumbnails, you’ll need to register a
SeekBarListener
to the
PlayerControllerView
. This listener will be invoked whenever there’s a scrubbing event on
the Seekbar, so you can implement your logic regarding thumbnail rendering.
You can also specify a third parameter to fine-tune which Thumbnail should be fetched in relation to the requested time. More info about this parameter is available in the Thumbnail index selection section.
Here you can see a sample implementation.
public void onSeekbarScrubbed(long positionUs, double seekBarProgressPercent) {
ThumbsPlugin.ThumbnailViewComponent thumbsView = playerView.getComponent(
ThumbsPlugin.ThumbnailViewComponent.class);
if (thumbsView != null){
thumbsView.show(positionUs, new DefaultThumbnailView.Callback() {
@Override
public void getThumbnailRect(Rect output, DefaultThumbnailView.ThumbnailInfo info, boolean isSmallScreen) {
ViewGroup videoView = playerView.getVideoView();
output.set(
videoView.getLeft(),
videoView.getTop(),
videoView.getRight(),
videoView.getBottom());
}
}, ThumbsPlugin.THUMBNAIL_INDEX_CURRENT);
}
}
@Override
public void onSeekbarReleased() {
final ThumbsPlugin.ThumbnailViewComponent thumbsView = playerView.getComponent(
ThumbsPlugin.ThumbnailViewComponent.class);
if (thumbsView != null){
thumbsView.hide();
}
}
The example above demonstrates the basic interactions with the
ThumbnailViewComponent
. Here we assume we have to methods, one that is
called when a Seekbar is scrubbed and thumbnails should be shown, and the
counterpart that is triggered when scrubbing stops and the thumbnails
should be hidden. Hiding is relatively simple and a matter of calling the
hide()
method. Showing the thumbnails is slightly more complex. We
call the show()
method and pass two parameters: the time in
microseconds for which we would like to receive a thumbnail and
a Callback
implementation. The callback is triggered once the Bitmap
data are loaded. It its called with size information about the thumbnail
as well as an output
rectangle. The output
rectangle needs to be
filled with the coordinates and size of the desired thumbnail. The
coordinates are relative to the thumbnail view container, but the default
view container has the same dimensions as the player view. The example
above uses the exposed video view to place the thumbnail image full-screen
above the video, but treat this as a simple example. You can use the
output
rectangle to customize the size and the position of the
resulting thumbnail.
The returned Thumbnail might not be the corresponding one for the requested
position, as the DefaultThumbnailView
uses the getThumbnail
API
defining some tolerance. It is recommended that this is the behaviour used
as it improves the perceived responsibility. Nevertheless, this is a configurable
setting on the DefaultThumbnailView
and you may change it through
DefaultThumbnailView.setThumbnailTolerance(long)
.
The DefaultThumbnailView
will automatically be created if you use a PlayerView
.
If you don’t want to use a PlayerView
but still decide to use the DefaultThumbnailView
available in the PRESTOplay SDK for Android, you can create it yourself, either programmatically or by defining it in
an xml layout file.
If you use the latest approach, you must pass your layout View to PlayerController.setComponentView(int, android.view.View)
with the corresponding id, defined in PRESTO_DEFAULT_THUMBNAIL_VIEW
,
or R.id.presto_default_thumbnail_view
so the ThumbsPlugin
can properly find the DefaultThumbnailView
.
Thumbnail rendering time tolerance¶
In order to benefit from the several loading Waves approach, the ThumbsPlugin
provides an API
to return a close neighbour of the desired thumbnail if its data is already available.
This tolerance represents the maximum difference of time time between the position of the requested thumbnail and that of a potentially already-loaded thumbnail in the index. If a matching nearby thumbnail with data within the specified tolerance is found, it will be returned immediately. Otherwise, the exact thumbnail will be scheduled for fetching.
By default, the DefaultThumbnailView
performs thumbnail requests with some tolerance, which
can be changed through DefaultThumbnailView.setThumbnailTolerance(long)
.
If you’re performing direct thumbnail requests through a ThumbnailProvider
, you can specify
a tolerance with the ThumbnailProvider.getThumbnail(long, com.castlabs.sdk.thumbs.ThumbnailProvider$Callback, int, long)
method.
Thumbnail index selection¶
When using the ThumbnailViewComponent
, it’s possible to pass a third parameter to the
show()
method
in order to determine which Thumbnail should be returned, relative to the requested media time.
There are three possible values:
ThumbsPlugin.THUMBNAIL_INDEX_CURRENT
: This corresponds to the thumbnail with the highest media time that is lower than the requested position.ThumbsPlugin.THUMBNAIL_INDEX_NEXT
: This corresponds to the thumbnail with the lowest media time that is higher than the requested position.ThumbsPlugin.THUMBNAIL_INDEX_CLOSEST
: This corresponds to the thumbnail with the lowest difference between its media time and the requested position.
The following scheme can better illustrate the behaviour. The example assumes Thumbnails are available
every 10 seconds, starting from the beginning (media time 0). The top marker represents the requested
time, that being the first parameter to the ThumbnailViewComponent.show(...)
call, and the ones
below the timeline, the Thumbnail times for each indexing type:
Requested Thumbnail for position 4
0 v 10 20 30
Return |---------|---------|---------|-----
Current: ^
Closest: ^
Next: ^
Requested Thumbnail for position 6
0 v 10 20 30
Return |---------|---------|---------|-----
Current: ^
Closest: ^
Next: ^
Requested Thumbnail for position 18
0 10 v 20 30
Return |---------|---------|---------|-----
Current: ^
Closest: ^
Next: ^
Downloader integration¶
The thumbnails plugin is integrated with the Downloader Plugin, hence
there is offline support for thumbnails as well. This is currently limited
to BIF and WebVTT thumbnails. Just add the ThumbnailData
to the
Intent that is used to initiate the download and the thumbnails will be
fetched and downloaded as well. When you start the local playback, pass
the same ThumbnailData
to the local playback intent. The player will
translate any remote URLs automatically to the correct local files and
relative URLs will still resolve correctly, now relative to the local
manifest.
Thumbnail loading details¶
The thumbnail plugin can preload thumbnails in the background in order for them to be ready as much a possible whenever a request comes in.
The background loader is multi-threaded, and the number of threads is configurable
through the ThumbsPlugin.setThumbnailLoadThreads(int)
static method.
Whenever a specific thumbnail request (either by asking the DefaultThumbnailView
to show
a thumbnail for a time, or through one of the getThumbnail
methods) is issued, it will skip the
pending queue of background thumbnail loads in order to be as responsive as possible. Successive
specific requests will be queued and executed in a FIFO fashion. To clear this queue of pending
specific thumbnail requests, the following method can be used: ThumbnailProvider.cancelPendingRequests()
.
Standalone Thumbnail providers¶
It is possible to obtain an instance of a ThumbnailProvider
without using a
PlayerController
instance which will allow for fetching thumbnails independently of
any ongoing playback, even for completely different content.
In order to obtain a StandaloneThumbnailProvider
to fetch embedded DASH thumbnails use StandaloneThumbnailFactory.getProvider
:
StandaloneThumbnailProvider provider = StandaloneThumbnailFactory.getProvider(
new PlayerConfig.Builder("https://example.com/manifest.mpd").get());
// Fetch thumbnails
Future<ThumbnailProvider.ThumbnailResult> thumbnailResultFuture = provider.getThumbnail(30_000_000L, ThumbsPlugin.THUMBNAIL_INDEX_CURRENT);
Bitmap thumbnailBitmap = thumbnailResultFuture.get().thumbnail;
// Free up resources
provider.destroy();
In case the provided DASH manifest is a live stream manifest updates will be performed in the background
resulting in the thumbnail index also being updated alongside. To get updates of this process it is
possible to register a IndexRefreshListener
:
provider.addIndexRefreshListener(new ThumbnailProvider.IndexRefreshListener() {
@Override
public void onIndexRefreshed(@NonNull ThumbnailProvider provider) {
// Index has been refreshed. Fetch thumbnail for a given time
Future<ThumbnailProvider.ThumbnailResult> thumbRequest = provider.getThumbnail(position, ThumbsPlugin.THUMBNAIL_INDEX_CURRENT);
}
});
It is also possible to get thumbnails out of a VTT or JPG Template SideloadedTrack
:
StandaloneThumbnailProvider provider = StandaloneThumbnailFactory.getProvider(new SideloadedTrack.ThumbnailBuilder()
.url("https://demo.castlabs.com/media/QA/tos_thumbs/$index$.jpg")
.gridHeight(1)
.gridWidth(1)
.thumbnailType(ThumbnailDataTrack.TYPE_JPEG_TEMPLATE)
.intervalMs(5_000)
.get());
360-Degree Playback Plugin¶
The PRESTOplay SDK for Android can be used with the optional 360 Degree player plugin to enable 360 playback using the PRESTOplay SDK for Android.
The plugin is distributed with the PRESTOplay SDK for Android. You can reference and load the 360 plugin as a separate dependency:
dependencies {
...
compile 'com.castlabs.player:360-plugin:3.2.1'
...
}
Before you can use this plugin, you have to register it before you initialize the SDK:
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
PlayerSDK.register(new ThreesixtyPlugin());
PlayerSDK.init(getApplicationContext());
}
}
Google Cardboard¶
The PlayerView360
is an extension of the
default PlayerView. You can add it as a view component to your own activity. The view exposes the
underlying GvrView which needs to be passed to the Google VR SDK in your activity.
You can use the PlayerView360 in your own Activity (that usually extends GvrActivity). The PlayerView360 will handle all the transformations required to render the view.
An example of this is the CardboardActivity class in the ‘threesixty_demo’ sample app.
Google cast Manager¶
The PRESTOplay SDK for Android contains the GoogleCastManager
class that will ease the process of starting and managing remote cast session.
This class allows to start a cast session, get the current remote player state and select audio, video and subtitle tracks.
AndroidX Media2¶
MediaSessionPlugin
integrates PRESTOplay SDK for Android with AndroidX Media2 Session APIs providing easy to use Media2
functionality. The application does not need to create and maintain MediaSession instance, it is only
responsible for Media Session lifecycle i.e. when Media Session is created and optionally when it is destroyed.
First, MediaSessionPlugin
shall be enabled before PRESTOplay SDK for Android initialization:
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
PlayerSDK.register(new MediaSessionPlugin());
PlayerSDK.init(getApplicationContext());
}
}
Registering the plugin does not create Media Session yet and the application will need to explicitly
request its creation by calling MediaSessionPlugin.enableMediaSession()
. Media Session is linked
to the PlayerController
and therefore the application has to provide
PlayerController
instance
when enabling Media Session. MediaSessionPlugin
supports both PlayerController
and
SingleControllerPlaylist
instances.
Media Session can be enabled any time before or after opening the playback and the only constraint is the presence of a valid
PlayerController
instance.
The code below enables Media Session when PlayerController
is created and before the playback is opened:
public class SimplePlaybackDemo extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// ...
PlayerView playerView = (PlayerView) findViewById(R.id.player_view);
MediaSessionPlugin.enableMediaSession(playerView.getPlayerController());
// ...
PlayerConfig playerConfig;
// playerConfig = ...
playerController.open(playerConfig);
}
@Override
protected void onResume() {
super.onResume();
// ...
PlayerView playerView = (PlayerView) findViewById(R.id.player_view);
MediaSessionPlugin.enableMediaSession(playerView.getPlayerController());
playerView.getLifecycleDelegate().resume();
}
}
Media Session is closed automatically upon PlayerController.destroy()
. However,
it is also possible to close Media Session explicitly:
// ...
PlayerView playerView = (PlayerView) findViewById(R.id.player_view);
MediaSessionPlugin.disableMediaSession(playerView.getPlayerController());
// ...
MediaSession can also be customized according to the application needs e.g. default seek values or callbacks can be installed:
final MediaSessionBuilder mediaSessionBuilder = new MediaSessionBuilder()
.setFastForwardIncrementMs(10000)
.setRewindIncrementMs(10000);
// ...
MediaSessionPlugin.enableMediaSession(playerView.getPlayerController(), mediaSessionBuilder);
// ...
Drm Device Time Checker¶
The PRESTOplay SDK for Android contains the DrmDeviceTimeCheckerPlugin
Plugin which is able to perform periodic checks related to DRM licensing in order to minimize some
Widevine limitations.
The plugin is distributed with the PRESTOplay SDK for Android. You can reference and load the Device Time Checker plugin as a separate dependency:
dependencies {
...
compile 'com.castlabs.player:drm-device-time-checker-plugin:1.0.0'
...
}
Before you can use this plugin, you have to register it before you initialize the SDK:
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
PlayerSDK.register(new DrmDeviceTimeCheckerPlugin());
PlayerSDK.init(getApplicationContext());
}
}
Optionally, you can specify the time period at which he license checks should be performed. Default value is every 5 minutes.
// ...
PlayerSDK.register(new DrmDeviceTimeCheckerPlugin(60)); // Check every minute
// ...
This plugin needs two extra Android permissions. These permissions allow to internally keep track of license times.
These permissions are automatically added to your AndroidManifest file once you include this plugin in your build file.