CCClient Developers Guide Android

User Manual:

Open the PDF directly: View PDF PDF.
Page Count: 32

DownloadCCClient Developers Guide Android
Open PDF In BrowserView PDF
Penthera Download2Go™
Client Developer Guide (Android)
This document describes integrating and using the ​Penthera Download2Go Android SDK​.
The SDK is the client piece of Penthera’s Download2Go (Download2Go), a software system that
manages download, storage, and playback of videos on mobile devices. We assume that you will
integrate the SDK into your own streaming video app that handles all UI/UX, user authentication,
DRM, and video playout.
This document is a how-to guide. It will teach you to:
1. compile and run a sample Android app using the SDK
2. link the SDK into your Android app
3. perform common functions using the SDK: enqueue, play, expire, configure, etc.
We assume you are an experienced Android developer, and you’re using Android’s latest SDK
and platform tools. Although the SDK does not require it, some examples in this document
assume you are using Android Studio.
The SDK communicates with a server, the ​Download2Go Backplane, ​using an internal,
proprietary web services protocol. This communication occurs via regular client-server syncs
and via server-to-client GCM messages. Penthera hosts a developer server instance, at
demo.penthera.com​, which you may use to build a proof-of-concept app.
Internally, the SDK is codenamed “Virtuoso.” You’ll notice this a lot in the headers.
We’re here to help! Email​ ​support@penthera.com​ if you run into any problems.
NOTE:​ This document contains method signatures and reference source code. We try to keep
this document up-to-date, but you’ll find the ​authoritative​ header files and reference source in
the Android developer package.

v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 1 of 32

Table of Contents

Other Documentation
Let’s Get Started
Unpacking
SdkDemo: A Reference Client
Building Your Own App
Common Functions
Enqueue an Asset
Access Downloaded/Queued Assets
Remove an Asset
Flush Queue
Expire an Asset
Configure Download Rules
Retrieve and Persist Widevine Licenses
Playout with Widevine Persisted License
Set Availability Window for an Asset
Enable Device for Download
Using/Configuring Broadcasts
Configure Logging
Play a Downloaded Asset
Subscriptions
Appendix A: How Downloading Works
Appendix B: Manually Creating an App

v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 2 of 32

Other Documentation
This document is part of a family of documents covering Download2Go:

Let’s Get Started
Unpacking
We’ll provide you details of a public github repository where you can access the Android
developer package. The contents of the deliverable are:
●
●

CnCDemo​: Android Studio project - contains source code for demo app that uses the
SDK
javadocs

Permissions used by the SDK
The SDK uses the following permissions:
●
●
●
●
●

android.permission.RECEIVE_BOOT_COMPLETED:​ ​Required to allow the SDK to
resume downloads after a device reboot without user interaction
android.permission.INTERNET: ​Required to access the network
android.permission.WRITE_EXTERNAL_STORAGE: ​Limited to Android SDK API
version 18 or less. API versions greater than 18 do not require this permission. The
permission is ​required to write downloads to disk
android.permission.ACCESS_NETWORK_STATE: ​Required to determine the state of
the network, so the SDK can respond to network loss and return appropriately.
android.permission.ACCESS_WIFI_STATE: ​Required along with network state to react
to network changes appropriately.

v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 3 of 32

●
●

android.permission.CHANGE_WIFI_STATE: ​Needed to refresh the wifi connection
if/when it gets stale. Known Android issue. Allows for robust downloading.
android.permission.WAKE_LOCK: ​Required to receive and handle push notifications
and ​to allow efficient background downloading after user exits application.

Penthera also recommends using the ​android.permission.QUICKBOOT_POWERON
permission. This permission allows the SDK to restart and​ resume downloads after a quick boot
without user interaction. i.e. This occurs when the device is not fully powered off but all services
and applications are killed before it turns back on..
SdkDemo: A Reference Client
SdkDemo is a reference standalone Android app that uses the SDK. We provide this as a
convenience so you can see how to call the SDK to enqueue, configure, download, and play
video. It uses public-domain videos (HLS, HSS, and mp4), hosted by Penthera on Amazon AWS.
To build and run SdkDemo:
1. In Android Studio Select ​File-New-Import Project
2. In the Select Project window navigate to the ​CnCDemo​ folder of the deliverable and select
the​ build.gradle​ file.
3. Run or Debug the SdkDemo module in an emulator or on a device.
4. You will need a public and private key to build the application. Please contact
support@penthera.com​ to get them.
5. You will need to provide a ​google-services.json​ and an ​api_key.txt​ to use Firebase
Cloud Messaging and/or Amazon Device Messaging. There are placeholder files which
need to be populated or removed. If not using Push Notifications then remove the files
and comment out or remove the relevant sections from the Android Manifest. These
sections are detailed here: ​Setting up Push Notifications
Congratulations! You’ve now got a video-downloading app up and running. You’re ready
to develop your own apps with the SDK.
Note: The SDK Demo uses ExoPlayer it will only play videos on Kindle Devices running Fire OS 5
or greater. If you need to support earlier Fire OS versions then a different player will need to be
used. Penthera will be happy to help you with an implementation if needed.
Building Your Own App
Either create a new project within Android Studio or use an existing one.
In the​ build.gradle​ file make sure you include the following snippet:
allprojects {
repositories {
jcenter()
​maven {
url
'http://clientbuilds.penthera.com:8081/repository/releases/'
​}
}
}
This will allow Android Archives to be read from the Pethera hosted Maven repository
Add the following implementation statement to the dependencies list in your applications
build.gradle​:
v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 4 of 32

implementation (​'com.penthera:cnc-android-sdk:3.14.9@aar'​)
Sync the project and you are now ready to start using the Download2Go SDK.
To use a copy of the library which defaults to debug logging, which is best during development,
use the following implementation statement:
implementation (​'com.penthera:cnc-android-sdk-debug:3.14.9@aar'​)

v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 5 of 32

Overall SDK Architecture

Android SDK architecture overview. For simplicity, not all components/interconnects are shown.

The SDK consists of a few components:
●

●

●

●
●

Virtuoso​: For the most part, this is your app’s main interface with the SDK. Provides
interfaces to register the device, manipulate the download queue, subscribe to feeds and
get status on the download service.
Virtuoso Service​: A background service running in its own process. Downloads queued
assets, deletes expired assets, communicates with the Backplane, and sends
notifications to the enclosing app.
Push Notification Receiver and Services​:
○ ADMReceiver: A Receiver to handle the Amazon Device Messaging (ADM)
Client broadcasts.
○ ADMService: The Service which handles Amazon Device Messaging (ADM)
messages forwarded from the receiver.
○ FcmInstanceIdService: Service which handles Firebase Instance Id token
refreshes.
○ FcmMessagingService: Service which handles the Firebase Cloud Messaging
(FCM) notifications.
Subscriptions Service​: Handles the synchronization of subscribed feeds, manages
when new episodes should be added for download and deletes old episodes.
Virtuoso Content Provider​: Keeps track of events and all information regarding assets

v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 6 of 32

●
●

known to the SDK.
Virtuoso Client HTTP Service​: A proxy for playout of segmented assets, e.g. HLS
videos.
Virtuoso Service Starte​r: A mini-Service that starts the Virtuoso Service on device boot.

As of Android 3.1, apps begin in a stopped state. In a stopped state, an app won’t receive any
broadcast messages. This means that background services can’t be automatically started on
boot, unless the app has first been launched by the user.
Known OS Issue on Android 4.4.X
Android 4.4.1/4.4.2 has a known issue, documented in
https://code.google.com/p/android/issues/detail?id=63618​.
In short, when a user removes an app from the recent tasks list, Android puts the app in a
stopped state and terminates the app’s background services. This means that the background
services won’t restart until a user once again launches the app.
A work-around for this is to use a foreground service. We provide an example of the workaround
in the ​com.penthera.sdkdemo.notification​ package.
App Identification
The SDK needs access to your app's content provider for various purposes, including to send out
broadcasts and perform local database functions.
You provide the SDK this information by including a name/value pair in AndroidManifest.xml:


Instantiation
Virtuoso​ has one constructor which takes the current application context. Virtuoso can be
instantiated within any component where a handle on the context is available. Generally, each
Activity has one instance of Virtuoso, but you can use multiple instances or a singleton if required.
To initialize ​Virtuoso​ in the ​onCreate()​ of an Activity:
private Virtuoso mVirtuoso;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mVirtuoso = new Virtuoso(getApplicationContext());
}

Registering with the Backplane
Before it can perform any meaningful task (download, process events, receive subscription
notifications), your app must register with the Download2Go Backplane. For building a proof of
concept app, you are welcome to use the shared Penthera-hosted Backplane instance; ask
Penthera for the URL and credentials.
Typically you’ll want to carry out this initialization within a splash activity.

v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 7 of 32

To register with the Backplane, an app must call ​startup​, with the following parameters:
●
●
●
●
●
●

aBackplaneUrl ​: URL of the Backplane
aUser : User identifier (which you assign) to be registered on the Backplane
aExternalDeviceId ​: Optional device ID defined by your app
aPublicKey ​: Key used to identify the app on the Backplane (provided to you
by Penthera)
aPrivateKey ​: Key used to sign all communications between the SDK and
Backplane (provided to you by Penthera)
aPushRegistrationObserver ​: Listener to monitor if
the registration for push messages through FCM or ADM messaging was successful.
Refer to the ‘Subscriptions’ section, below.

Success/failure of the registration is reported through an ​IBackplaneObserver​ with a callback
type of ​BackplaneCallbackType.REGISTER​. (I​BackplaneObserver​ is described below)​.
Once an installed app has successfully registered with the Backplane, it doesn’t need to call
startup​ again. If the installed app calls startup with the same user and keys then it is a NOOP. If
the keys change but not the user then the SDK will revalidate with the backplane. You can check
your app’s authentication status via ​getAuthenticationStatus​:
private Virtuoso mVirtuoso;
public void onLoginClick() {
IBackplane backplane = mVirtuoso.getBackplane();
if (backplane.getAuthenticationStatus() ==
Common.AuthenticationStatus.NOT_AUTHENTICATED) {
// handle user login
// would need to listen for the registration success through
// an IBackplaneObserver - see details below
mVirtuoso.startup(“​https://backplane​.server.com”,
“APPLICATION_USER”,
null, // could provide a device identifier here
“MY APPLICATION PUBLIC KEY”,
“MY APPLICATION PRIVATE KEY”,
new IPushRegistrationObserver(){.....}
);
}
else { /* proceed to main activity */ }
}

NOTE:​ If after you are authenticated with the backplane, you call startup with a different user then
the SDK is reset. I.e.: All media for the old user is deleted and all the settings are reset to the
defaults.
Service Observers
The SDK provides several observers for use within your Activity. The observers allow your
application to get updates from the SDK and keep your views refreshed. See the SdkDemo
source code for example source code, and the Javadocs describe their callbacks.
Observer

Description

IBackplaneObserver

Notifies observer when any communication with the Backplane has
completed. Provides a callback which informs the client the type of
communication that occurred and the result.

IEngineObserver

Notifies observer of changes in the download engine status, changes to
the settings used by the service, and when assets are deleted or expired.

v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 8 of 32

IQueueObserver

Notifies observer on all callbacks which affect the queue or assets within
it. Notifies when downloads start and end, a downloaded error occurs, and
any queue changes.

ISubscriptionObserver

Notifies observer when communication with the Backplane Subscription
Service completes.

In addition to the Observer interfaces, the SDK ​provides a base class implementation of the
IEngineObserver, ​IQueueObserver​ and ​ISubscriptionObserver​ interfaces. The base
implementation of each interface method does nothing.
OnResume
You should call the SDK’s ​onResume​ and ​onPause​ methods at the respective places within an
Activity.
Calling ​onResume()​ ensures that any service observers are correctly linked to the relative service
callbacks. It is in the Activity’s ​onResume​ that you’ll register observers with the ​Virtuoso
instance:
@Override
protected void onResume() {
super.onResume();
mVirtuoso.onResume();
mVirtuoso.addObserver(mBackplaneObserver);
mVirtuoso.addObserver(mEngineObserver);
mVirtuoso.addObserver(mQueueObserver);
}

OnPause
Call ​onPause​ to allow the ​Virtuoso​ instance to unregister any observers it has linked to the
background service. Although not necessary, it’s good practice to unregister any of your own
observers before calling ​onPause()​:
@Override
protected void onPause() {
super.onPause();
mVirtuoso.removeObserver(mEngineObserver);
mVirtuoso.removeObserver(mBackplaneObserver);
mVirtuoso.removeObserver(mQueueObserver);
mVirtuoso.onPause();
}

Interacting with the Background Service
Some of the APIs require a bound connection to the background service. These are provided in
the ​IService​ interface. The ​IService​ interface obviates the need to maintain a persistent
connection; it allows you to bind and unbind from the service as and when needed.
The ​IService​ interface allows you to:
● pause/resume downloads
● retrieve the current status
● retrieve network throughput data
private int mServiceStatus;
v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 9 of 32

private Virtuoso mVirtuoso;
private IService mService;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
mVirtuoso = new Virtuoso(getApplicationContext());
mService = mVirtuoso.getService();
}

protected void onResume() {
super.onResume();
if(mService.bind())
mServiceStatus = mService.getStatus();
}
protected void onPause() {
super.onPause();
mService.unbind();
}

Setting up Push Notifications
To receive Push Notifications either through Amazon Device Messaging or Firebase Cloud
Messaging you will need to add the push notification Receivers and Services to your application.
For supporting FCM you will need to provide the ​google-services.json​ file obtained from the
Firebase console. If supporting ADM then you will need to provide an ​api_key.txt​ which
contains the ADM key supplied in the Amazon developer console.
Instructions for setting up FCM can be found here: ​Manually add Firebase
Instructions for setting up ADM can be found here: ​Obtaining ADM Credentials​ and ​Store Your
API Key as an Asset
TIP :​ Not all Android devices have GooglePlay Services on the ROM. You may want to check
within your app and prompt the user to install it. We suggest you do so within the
onServiceAvailabilityResponse​ response of the I
​ PushRegistrationListener​. See the
SDK Demo for details on how to do this, or visit ​Check for Google Play services
For the SDK to function correctly with FCM and ADM you must use the Push Notification services
and receivers provided in the SDK. You will need to declare these in the manifest as shown
below. If you need to have access to the token or will be handling messages other than those
needed by the SDK then you can subclass the SDK classes (remember to always call
super.​ on any methods which are overridden), The SDK Demo application shows how
to subclass the SDK classes and declare them in the manifest..
Use the ​IPushRegistrationObserver ​interface at ​startup​ to monitor for success/failure when
registering with the Push Services..
Modify AndroidManifest.XML
Model your Android manifest after the following:

v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 10 of 32


...
​





...


...

​








​
<
​ receiver
android:name="com.penthera.virtuososdk.client.subscriptions.ADMReceiver"
android:permission="com.amazon.device.messaging.permission.SEND" >
​






v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 11 of 32












Common Functions
Here we list common ways to use the SDK. This is just a sliver of the overall functionality; after
you’re done here, have a look at the javadocs to see what else is available.

Enqueue an Asset
Enqueue a Single File (e.g. an mp4)
Use this method to enqueue a single file for download:
IAssetManager assetManager = mVirtuoso.getAssetManager();
IFile vi = ​assetManager​.createFileAsset (

“http://some.server.com/media.mp4”, // remote URL
“MY_CATALOG_IDENTIFIER”,
// An asset identifier your app can use to
// map this asset to your catalog
“video/mp4”,
// asset mime type, for validation
“c611b4e6014055f5986b188da2c41ee7”, // asset md5 hash, for validation
“{
// Additional metadata that SDK should store with the asset
\”title\”: \”media title\”,
\”desc\”:\”media description\”,
\”img\”:\”​http://myimage.png\​”
}”);

assetManager.getQueue()​.add(vi);
The SDK can validate downloads through any (or none) of the following: expected file size, mime
type, and hash. Supply null for the mime type and md5 hash to suppress these validation
methods. Supply -1 for the file size to suppress file size validation.
Enqueue a segmented video, specifying a target bitrate
Use these methods (provided through the ​IAssetManager​ interface) to enqueue a video that’s split
into multiple segments (e.g. an HLS,HSS or MPEG-DASH video:
● createHLSSegmentedAsset ​or​ createHLSSegmentedAssetAsync
● createHSSSegmentedAsset ​or​ createHSSSegmentedAssetAsync
● createMPDSegmentedAsset ​or​ createMPDSegmentedAssetAsync
You’ll provide the URL of the manifest (the .​m3u8​ file), and a max desired bitrate. The SDK will
parse the manifest, identify the highest-quality profile whose bitrate doesn’t exceed the specified
max bitrate, and then download all the fragments belonging to that profile.

// This observer will receive notification when asset has been created
final ISegmentedAssetFromParserObserver observer =
v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 12 of 32

new ISegmentedAssetFromParserObserver() {
@Override
public void complete(ISegmentedAsset aSegmentedAsset, int aError,
boolean addedToQueue) {
if (addedToQueue) {
// success
} else {
// something went wrong
}
}
};
IAssetManager assetManager = mVirtuoso.getAssetManager();
assetManager​.createHLSSegmentedAssetAsync(
observer,
// just-created observer; see above
“​http://some.server.com/manifest.m3u8​”, // URL of manifest
1927853,
// max desired bitrate to use
“MY_CATALOG_IDENTIFIER”,
// see above
“(metadata key:value bindings go here)”, // see above
true,
// Download encryption keys?
true
// Add to download queue?
);

Supplying Integer.MAX_VALUE for the max bitrate parameter tells the SDK to select the
highest-bitrate profile available. Supplying 1 for the max bitrate parameter tells the SDK to select
the lowest profile available.
Enqueue a Segmented Asset Manually from the Segments
You can “do-it-yourself” and manually enqueue the individual video fragments:
// create list of segment URLs
ArrayList segments = new ArrayList();
segments.add(“http://some.server.com/media/low-profile/frag0.ts”):
segments.add(“http://some.server.com/media/low-profile/frag1.ts”)):
segments.add(“http://some.server.com/media/low-profile/frag2.ts”)):
// instantiate the HLS segmented asset
IAssetManager assetManager = mVirtuoso.getAssetManager();
ISegmentedAsset hls =
assetManager.createSegmentedAsset(“MY_CATALOG_IDENTIFIER”,
“(metadata key:value bindings go here)” );
// add segments
hls.addSegments(segments);
// enqueue for download
assetManager.getQueue().add(hls);

Access Downloaded/Queued Assets
Retrieve a cursor on the assets previously downloaded and now stored on the device:
IAssetProvider downloaded = mAssetManager.getDownloaded()
Cursor c = downloaded.getCursor();

Retrieve a cursor on the assets in the download queue:
IQueue queue = mAssetManager.getQueue()
Cursor c = queue.getCursor();

Remove an Asset
Delete an asset that is either enqueued or already downloaded:
v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 13 of 32

IIdentifier vi = mAssetManager.get(uuidString);
mAssetManager.delete(vi.getId());

Flush Queue
Removes all assets from the download queue:
mAssetManager.getQueue().flush();

Expire an Asset
Manually mark an asset as having expired:
IIdentifier vi = mAssetManager.get(uuidString);
mAssetManager.expire(vi.getId());

Notes:
● The SDK automatically removes expired assets from the download queue.
● The SDK provides access to expired assets through the ​IAssetManager​.
● When an asset expires, the SDK deletes the data associated with the asset (e.g. the mp4
file or the .ts files), to free disk space. However, the SDK retains the asset’s metadata.
This allows your app to access information about the expired asset.
● The SDK will automatically track and mark expiry if appropriate metadata has been
supplied, such as the expiry timestamp, expiry after download, or expiry after play values.

Configure Download Rules
The SDK obeys several behavioral settings. You can access and configure these settings
through the ​ISettings​ interface. ​Notice that the default values are very conservative; for most
apps, you’ll want to tune these to more aggressive values.
●

headroom​:​ Storage capacity that the SDK will leave available on the device. If there’s
less than this amount of free space on the device, the SDK won’t download (default:
100MB)

●

maxStorageAllowed​:​ Maximum disk space the SDK will ever use on the device. If the
SDK is storing this or more downloaded data on the device, it won’t download (default
100MB).

Visualizing maxStorageAllowed and headroom parameters. In this scenario, the device has 32GB of disk
space. The Engine will always preserve 6GB of free space on disk (i.e. “headroom”). Currently, the Engine is
using 7GB, and may never use more than 14GB total (i.e. “maxStorageAlllowed”).

●

batteryThreshold​: fractional battery charge level below which SDK suspends
downloading. A value of 0 (completely discharged) means “no limit.” A value greater than

v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 14 of 32

1 (completely charged) means “only download when charging.” (default: 1)
●

cellularDataQuota​: MB/month the SDK can download over cellular. A value of 0
means “don’t download any bytes over cellular.” A negative value indicates an unlimited
quota. (default: 0). The SDK divides this number into four and enforces the smaller
number on a week-to-week basis.

●

destinationPath​: ​an additional relative path added to the SDK root download location.
The SDK will store all downloaded content here. By default, the SDK stores all
downloads in the the SDK root directory, ​/virtuoso/media/, u
​ nder appropriate
sub-directories.

Retrieve a setting:
​ISettings settings = mVirtuoso.getSettings();
long maxStorage = settings.getMaxStorageAllowed();
Override settings:
settings.setMaxStorageAllowed(1024)
.setHeadroom(200)
.setBatteryThreshold(0.5)
.save();
Reset a setting to the SDK default or to that provided by a Backplane:
settings.resetMaxStorageAllowed().save();

Retrieve and Persist Widevine Licenses
When downloading a widevine protected MPEG-DASH asset the SDK will attempt to download
and persist the license. In order to retrieve the license it needs to request it from the licensing
server, the licensing server url may need to be formatted differently depending on the asset.
To provide the correct Url for license retrieval you need to provide a License Manager that the
SDK can use. The easiest way to do this is to extend the ​LicenseManager​ class in the
com.penthera.virtuososdk.client.drm​ ​package and override the g​ etLicenseAcquistionUrl
method.
E.g.:
public class ​DemoLicenseManager ​extends ​LicenseManager {
@Override
​public ​String getLicenseAcquistionUrl() {
String license_server_url = ​"https://proxy.uat.widevine.com/proxy"​;

/*
Here you can examine the mAsset and mAssetId member variables and modify the
license server url if needed:
Example:
String video_id = mAsset != null ? mAsset.getAssetId() :
mAssetId != null ? mAssetId : null;
if(!TextUtils.isEmpty(video_id){
license_server_url += "?video_id="+video_id + "&provider=widevine_test";
}
*/

}

r
​ eturn ​license_server_url;

}
For the SDK to use your License Manager implementation you need to override a metadata value
in the AndroidManifest.
To override metadata you will need to add the tools namespace:
v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 15 of 32


Now add in the metadata (replace the part in bold with the fully qualified class name of your
License Manager implementation):


Playout with Widevine Persisted License
The SDK provides a Drm Session Manager: ​VirtuosoDrmSessionManager​ ​it can be found in
the ​com.penthera.virtuososdk.client.drm​ ​package. You will need to integrate this with your
player implementation. The SdkDemo project shows how to integrate this with ExoPlayer by
implementing a wrapper that implements the Exoplayer DrmSessionManager interface. You can
find the wrapper in the demo project:
com.google.android.exoplayer2.drm.DrmSessionManagerWrapper
The ​buildDrmSessionManager​ method in
com.penthera.sdkdemo.exoplayer.PlayerActivity ​shows how to integrate the wrapper with
the ExoPlayer.

Set Availability Window for an Asset
The 'Availability Window' governs when the video is actually available for the end user. The SDK
enforces several windowing parameters on each video:
Window Parameter

Description

Start Window
(a.k.a. “Publish Date”)

The SDK downloads the video as soon as possible, but will not make the
video available through any of its APIs until after this date.

End Window
(a.k.a. Expiry Date”)

The SDK automatically deletes the video as soon as possible after this date.

Expiry After Download
(EAD)

The duration a video is accessible after download has completed. As soon
as possible after this time period has elapsed, the SDK automatically deletes
this video.

Expiry After Play (EAP)

The duration a video is accessible after first play. As soon as possible after
this time period has elapsed, the SDK deletes this video. To enforce this
rule, the SDK has to know when the video is played, so be sure to register a
play-start ​event when the video is played.

NOTE: The Backplane stores a global default value for EAP and EAD. You may set these values
from the Backplane web API. The Backplane transmits these default values to all SDK instances.
Typically, it’s a Content Management System (CMS) which stores the windowing information and
communicates it to the enclosing app. The app then feeds this windowing information to the SDK.
The data flows as follows:

v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 16 of 32

To set the availability window for a video:
asset.setEndWindow(new_long_value);
asset.setStartWindow(new_long_value);
asset.setEad(new_long_value);
asset.setEap(new_long_value);
mAssetManager.update(asset);

To get the availability window for a video:
IAsset asset = (IAsset) mAssetManager.getAsset(uuidString);
long expiry_after_download = asset.getEad();
long expiry_after_play
= asset.getEap();
long start_window
= asset.getStartWindow();
long end_window
= asset.getEndWindow()

Enable Device for Download
The Backplane tracks which devices are enabled for download, and enforces the global “max
download-enabled devices per user” parameter, which you may set via the Backplane.
The individual SDK instances “know” their own ​download-enabled​ status, because the
Backplane communicates it to them. An SDK whose download-enabled status is false can
enqueue, but will not download enqueued assets.
You can request to change the ​download-enabled​ status for a device as follows:
// create and register a backplane observer - so we know if the request succeeded
IBackplaneObserver mBackplaneObserver = new IBackplaneObserver () {
​@Override
public void requestComplete(int callbackType, int result) {
// only checking for download-enablement changes
if(callbackType == BackplaneCallbackType.DOWNLOAD_ENABLEMENT_CHANGE) {
switch(result) {
case ​BackplaneResult.SUCCESS​:
// Changed the ‘downloaded-enabled’ flag
break;
case ​BackplaneResult.DOWNLOAD_LIMIT_REACHED​:
// User has already reached quota of devices.
break;

}

// other failure codes you may want to communicate to user
case ​BackplaneResult.DEVICE_NOT_REGISTERED​:/*do something*/ break;
case ​BackplaneResult.INVALID_CREDENTIALS: ​/*do something*/ break;
case ​BackplaneResult.FAILURE​:
/*do something*/ break;

}
}
};
mVirtuoso.addObserver(mBackplaneObserver);
// Relies on having an active network connection and the SDK being authenticated
// with the Backplane.
mVirtuoso.getBackplane().changeDownloadEnablement(true); // true=enable, false=disable

It is also possible to enable / disable download on other devices associated with the user.
Enable / Disable Download on Other Device
v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 17 of 32

Download enablement and disablement can only be done on devices associated with the user’s
account. The backplane manages the listing of download enabled devices and enforces the
global “max download-enabled devices per user” policy.
The example below shows how to retrieve the listing of User devices and change the download
setting on one of them.
// Relies on having an active network connection and the SDK being authenticated
// with the Backplane.
String anExternalIdToMatch = “AN_EXTERNAL_ID”
IBackplane backplane = mVirtuoso.getBackplane();
backplane.getDevices( new IBackplaneDevicesObserver (){
@Override
public void backplaneDevicesComplete(IBackplaneDevice[] aDevices){
//if there is no active connection then an empty array is returned.
for(IBackplaneDevice device : aDevices){
//check the device
if(anExternalIdToMatch.equals(device.externalId()){
backplane.changeDownloadEnablement(true,device)
}
}
}
});

Using/Configuring Broadcasts
Besides issuing callbacks to the client through observers, the SDK also sends system broadcasts
which you can capture with a broadcast receiver. You can use this to update your UI widgets,
perform notifications, and track SDK analytics.
Notification Broadcasts
Broadcast

Description

NOTIFICATION_DOWNLOAD_START

A downloaded has started. The extras in the ​Intent​ will
contain the number of assets in the queue, the asset that
started downloading and the status of the download engine.

NOTIFICATION_DOWNLOAD_STOPPED

A download has stopped. The ​Intent​’s extras will detail
which asset, the number of assets in the queue and the
reason for stopping.

NOTIFICATION_DOWNLOAD_COMPLETE

A download has completed. The extras in the ​Intent ​will
detail which asset.

NOTIFICATION_DOWNLOAD_UPDATE

A progress update for a download. The Intent’s extras will
contain the asset and the number of assets in the queue.

NOTIFICATION_DOWNLOADS_PAUSED

A download was paused. The Intent’s extras will contain the
asset and the number of assets in the queue.

NOTIFICATION_MANIFEST_PARSE_FAILED

Commonly an observer is passed as an argument when a
new asset is queued for download. If the app is
backgrounded and cleaned up during the process so the
observer cannot be notified then this notification can be used
to receive notice that an asset has not been queued. The
Intent’s extras will contain the asset id for the asset which

v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 18 of 32

could not be queued.

The diagram below shows the different download states and the notification broadcasts that are
generated in moving between states.

Event-Related Broadcasts
These additional broadcasts are sent to support custom or third-party analytics and directly map
to log events recorded in the Backplane. The ​IEvent​ being reported is always available in the
Intent extras at the EXTRA_NOTIFICATION_EVENT key. The event object contains additional
details about the event, such as the asset ID of the video it relates to.
Broadcast

Description

EVENT_APP_LAUNCH

A fresh application launch is detected

EVENT_QUEUE_FOR_DOWNLOAD

The user has added a file to the download queue.

EVENT_ASSET_REMOVED_FROM_QUEUE

The SDK has removed a file from the download queue.

EVENT_DOWNLOAD_START

A file began to download.

EVENT_DOWNLOAD_COMPLETE

A file download completed.

EVENT_DOWNLOAD_ERROR

A file has stopped downloading due to too many errors.

EVENT_MAX_ERRORS_RESET

A file previously stopped due to download errors has been
reset and will continue downloading.

EVENT_ASSET_DELETED

A file was deleted.

EVENT_ASSET_EXPIRE

An asset was determined to be expired.

EVENT_SYNC_WITH_SERVER

A sync with the Backplane completed.

EVENT_PLAY_START

A player has begun local playback of the asset.

v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 19 of 32

EVENT_STREAM_PLAY_START

A player has begun streamed playback of the asset.

EVENT_PLAY_STOP

A player has stopped local playback of the asset.

EVENT_STREAM_PLAY_STOP

A player has stopped streamed playback of the asset.

EVENT_SUBSCRIBE

The user has subscribed to receive updates to a feed.

EVENT_UNSUBSCRIBE

The user has unsubscribed to receive updates to a feed.

EVENT_RESET

The SDK has handled a remote kill or detected a reinstall
of the client app..

Receiving Broadcasts
See SdkDemo for an example of using notifications and events. To receive the correct
broadcasts, the receiver must register an intent filter. The action names in the intent filter must be
in the following format:
CLIENT_PACKAGE_IDENTIFIER + “.” + BROADCAST_NAME
where CLIENT_PACKAGE_IDENTIFIER is the same value as that specified for the
com.penthera.virtuososdk.client.pckg ​metadata declared in the AndroidManifest and
BROADCAST_NAME is one of the broadcasts in the above table.
For example:














Configuring Notification Broadcast Frequency
You can configure the frequency at which your app receives notifications regarding downloads
through the ​ISettings​ interface.​ Y
​ ou can specify the update frequency on a time basis or on a
percent complete basis. For Segmented Assets you can also specify updates to occur on a
Segment downloaded basis.
I​Settings settings = mVirtuoso.getSettings();
settings.setProgressUpdateByPercent(1)
v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 20 of 32

.setProgressUpdateByTime(3000)
.setProgressUpdatesPerSegment(20)
.save()

Progress Updates By Percent (Integer):​ min % interval at which a broadcast should be sent out.
Set to 100 to disable updates based on % intervals. (default: 1)

Progress Updates By Time (Long):​ min # of ms that should pass before the SDK sends out a
broadcast regarding updates. Set to ​Long.MAX_VALUE​ to disable updates based on timed
intervals. (default: 5000)
Progress Updates Per Segment (Integer):​ min # of segments that should complete download
before the SDK sends out a broadcast. (default: 10)
If multiple values are used in the configuration, the SDK will send out a Broadcast at the next
interval.
The SDK guarantees that no update is sent out before the configured intervals. Broadcasts rely
on Android delivery, and so you may observe a variation between the times it receives updates
and the configured values.
Retrieve a setting: ​settings.getProgressUpdateByPercent();
Reset a setting: ​settings.resetProgressUpdateByPercent();

Configure Logging
The Backplane can track and report on a variety of SDK-related events. For a complete list of
events, see the SDK documentation. Each SDK build is configured with a certain set of events
enabled by default. Standard release builds are configured with “download queued”, “download
start”, “download pause”, “download complete”, “reset”, “play start” and “play stop” events. To
enable or disable other events, use the Common.Events methods to configure your desired
options as early as possible in the application lifecycle.
You may also configure the SDK to issue notifications to your application for all enabled events
(See section ​Using/Configuring Broadcasts​ for more details).
For example, to enable the “app launch” event:
Common.Events.enableEvent(getApplicationContext(),Common.Events.EVENT_APP_LAUNCH,true);

and to send the “app launch” event to the Backplane:
Common.Events.addAppLaunchEvent(getApplicationContext());

Play a Downloaded Asset
The SDK doesn’t include a video player. It does, however, provide a ​playout proxy,​ a local
HTTP proxy that sits between an media player and the downloaded videos.

v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 21 of 32

The SDK offers two different approaches to video playback, depending on the type of asset. A
Segmented Asset is always played through the ​VirtuosoClientHTTPProxy,​ and its URL must
point to the proxy. A single File can use its path as the URL.
You can retrieve the correct path for a Segmented Asset by using the ​getPlaylist​ method. For
a singleton file, use the ​getFilePath​ method.
Example of playing back assets:
public static void play(Context context, IAsset i) {
Intent openIntent = new Intent(android.content.Intent.ACTION_VIEW);
if (i.type() == ​Common.AssetIdentifierType.​FILE_IDENTIFIER​) {
IFile f = (IFile) i;
File file = new File(f.getFilePath());
String mimeType = f.mimeType();
openIntent.setDataAndType(Uri.fromFile(file), mimeType);
} else if(i.type() == ​Common.AssetIdentifierType.​SEGMENTED_ASSET_IDENTIFIER​) {
ISegmentedAsset f = (ISegmentedAsset) i;
String mimeType = "video/*";
URL pl;
try {
pl = f.playlist();
openIntent.setDataAndType(Uri.parse(pl.toString()), mimeType);
} catch (MalformedURLException e) {
throw new RuntimeException("Not a playable file");
}
} else throw new RuntimeException("Not a playable file");
// Register a ‘play start’ event
Common.Events.addPlayStartEvent(context,i.getAssetId());
// Play the Asset
context.startActivity(openIntent);
}

You can also retrieve the file path and the playlist through the Cursors provided by the Asset
Manager:
// Representation of a playable asset
private class MyAsset {
v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 22 of 32

private
private
private
private

final
final
final
final

int mId;
Uri mUri;
String mAssetId;
String mMime;

MyAsset(int id, Uri uri, String assetId, String mime){
mId = id;
mUri = uri;
mAssetId = assetId;
mMime = mime;
}
void play(Context context){
// Register a ‘play start’ event
Common.Events.addPlayStartEvent(context,mAssetId);
Intent openIntent = new Intent(android.content.Intent.ACTION_VIEW);
openIntent.setDataAndType(Uri.parse(pl.toString()), mimeType);
context.startActivity(openIntent);
}
}
List myAssets = new ArrayList();
Cursor c = null;
try {
c = mAssetManager.getDownloaded()
.getCursor( new String[] {AssetColumns._ID
,AssetColumns.TYPE
,AssetColumns.PLAYLIST
,AssetColumns.ASSET_ID
,AssetColumns.FILE_PATH
,AssetColumns.MIME_TYPE
},null,null);
if( c != null ) {
while (c.moveToNext()) {
int type = c.getInt(1);
Uri uri = null;
String mimeType = null;
if(type == Common.AssetIdentifierType.FILE_IDENTIFIER) {
File file = new File(c.getString(4));
uri = Uri.fromFile(file);
mimeType = c.getString(5);
if(TextUtils.isEmpty(mimeType)) {
String ext =
android.webkit.MimeTypeMap.getFileExtensionFromUrl(uri.toString());
mimeType =
android.webkit.MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext)
}
}
else if (type == Common.AssetIdentifierType.SEGMENTED_ASSET_IDENTIFIER) {
mimeType = "video/*";
URL pl = new URL(playlist);
uri = Uri.parse(pl.toString());
}
if(uri != null) {
myAssets.add(new MyAsset(c.getInt(0),uri,c.getString(3),mimeType));
}
}
}
}
finally { if( c != null && !c.isClosed()) { c.close(); } }

v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 23 of 32

Subscriptions
Above we described how you can enqueue a single video (flat file or segmented) for download. In
addition, the SDK can subscribe to a ​feed​ of episodic videos. We’ll explain that here.
The Backplane keeps track of which SDK instance is subscribed to which feeds. As new
episodes in a feed become known to the Backplane, the Backplane informs each SDK that
subscribes to that feed, either through a GCM push notification (if you’ve set up GCM for your
app), or through the normal SDK-Backplane sync. In response, the SDK automatically adds the
new episode to its download queue:

Let’s go through how this works.

Subscription-Related API Methods
You manage subscriptions via the following methods:
/*
subscribe to the feed with the given feedID. Store no more than maxAssets
episodes on the device at any one time. If you’re storing maxAssets already,
and a new episode is available for download, then ‘canDelete’ determines the
behavior. If canDelete==TRUE, then delete the oldest stored episode to make
room for the new one. maxBitRate is the highest bitrate version (profile)
to download.
*/
void subscribe(final String feedUuid, int maxAssets, boolean canDelete, int maxBitRate)
/*
subscribe to the feed with the given feedID. Rely on default values for the other
parameters.
*/
void subscribe(String feedUuid)
/*
unsubscribe from the given feed
*/
void unsubscribe(final String feedUuid)

To retrieve results from the calls ​unsubscribe()​, ​subscribe()​ or ​subscriptions(),​ you must
implement an ​ISubscriptionObserver​:
final ISubscriptionObserver mSubscriptionsObserver = new ISubscriptionObserver() {
@Override
public void ​onSubscribe​(final int result, final String uuid) {
if (result == BackplaneResult.SUCCESS) {
/* Successfully subscribed to ‘uuid’ */
} else { /* failure; do something here */ }
}
@Override
public void ​onUnsubscribe​(int result, String uuid) {
if (result == BackplaneResult.SUCCESS) {
v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 24 of 32

/* Successfully unsubscribed from ‘uuid’ */
} else { /* failure; do something here */ }
}
@Override
public void ​onSubscriptions​(final int result, final String[] uuids) {
if (result == BackplaneResult.SUCCESS) {
/* ‘uuids’ includes all the feeds the user is subscribed to */
} else { /* failure; do something here */ }
}
};

Getting Metadata for a New Episode
When the SDK receives a GCM push notification to download a new episode, the SDK needs to
get the metadata for that episode from somewhere. The “missing” metadata may include the
remote URL, bitrate/profile to use, asset expiry, title, description, an image, to name a few.
To fill in this missing data, you must implement a class derived from ​SubscriptionsService:
public class HarnessSubscriptionsService extends SubscriptionsService {
@Override
protected void onHandleIntent(Intent intent) {
super.onHandleIntent(intent);
}

@Override
public JSONObject processingFeedWithData(String uuid, JSONObject data,
boolean complete) {
//set values for Max bitrate,Max Episodes, Can Delete rules
return data;
}
@Override
public ​JSONObject​ processingAssetWithData(String uuid, ​JSONObject​ data,
boolean complete) {
// Add Data Here
return data;
}
@Override
public void processedAsset(IIdentifier aIdentifier) {
//update asset data if needed. Has not yet been added to the queue
}
@Override
public IVirtuosoIdentifier willAddAssetToQueue(IIdentifier aIdentifier) {
// Modify asset before it is added to the queue
return content;
}
}

How does the client access this metadata? There’s two options:
Scenario 1: ​All required asset metadata is provided from the Backplane

In this scenario, the SDK receives all the mandatory metadata from the Backplane. The SDK will
call the ​processingAssetWithData​ method with ​complete=true​. In this case, your app doesn’t
v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 25 of 32

need to provide any additional data and can simply return the passed-in JSONObject.
Scenario 2:​ The Backplane doesn’t supply all metadata; you need to fetch it manually.

In this case, you must provide the data to the SDK via the ​processingAssetWithData​ method.
In that method, you need to add any required metadata to the supplied JSONObject. For
instance:
@Override
public ​JSONObject​ processingAssetWithData(String uuid,
​JSONObject​ data, boolean complete) {
...
if (!data.has(SubscriptionKey.DOWNLOAD_URL)) {
data.put(SubscriptionKey.DOWNLOAD_URL, url);
}
...
return data;
}

The ​SubscriptionKey ​class​ ​details the available keys used by the subscription service.
Configuring Subscription Behavior
The SDK provides three ways to configure subscription behavior.
Maximum Bitrate: ​When the SDK encounters a manifest with multiple profiles, which profile
should it download? If this value is Integer.MAX_VALUE, the SDK selects the profile with the
highest bitrate. If the value is not Integer.MAX_VALUE, the SDK selects the profile with the
highest bit-rate not exceeding this value. If no such profile exists (all profiles are of a higher bitrate), the SDK will select the profile with the lowest bit-rate. ​ ​(default: 1)
Maximum Subscribed Assets Per Feed:​ The SDK can limit the number of episodes N saved in
each feed. Once the SDK has downloaded its quota in a feed, it won’t download a new episode
until one of the existing episodes is deleted. (default: I​ nteger.MAX_VALUE​)
Auto Delete Old Assets​: Determines how the SDK behaves when a new episode is available
and the device is already storing its quota for this feed. If YES, the SDK deletes the oldest
downloaded file in this feed. If NO, then the SDK won’t automatically download the new episode.
(default: YES)
You can set these values when subscribing to a feed:
public void subscribe(final String feedUuid, final int maxAssets,
final boolean canDelete, int maxBitRate)

Later, you can update the values with the following APIs:
v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 26 of 32

// Global values
public ISettings
public ISettings
public ISettings

for all feeds applied through ISettings Interface.
setMaxBitrateForSubscriptions(int bitrate)
setMaxAssetsForSubscriptions(int max);
setCanAutoDeleteForSubscriptions(boolean canDelete);

// Values for a specific feed
public void setFeedMaxBitRate(String feedUuid, int bitrate)
public void setFeedMaxAssets(String feeduuid, int max);
public void setFeedCanDelete(String feeduuid, boolean canDelete);

NOTE: Only assets downloaded through a subscription count towards these rules. If you enqueue
an episode of a feed manually, that enqueued asset will not be automatically deleted or cause
new downloads to be deferred.

v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 27 of 32

Appendix A: How Downloading Works
Here we describe what happens after you enqueue an asset for download. This section is for the
curious developer. You don’t need to understand this in order to use the SDK.
Virtuoso follows a “Rule of Threes” in downloading:
1. Proceed through the download queue in order.
2. If Virtuoso encounters an error downloading the file, it will try that file two more times
before increasing its error count and moving on. (This is the “inner” three).
3. When it reaches the end of the download queue, Virtuoso will return to the beginning of
the queue and make another pass, trying to download the errored files as well as new
files that may have been added.
4. Virtuoso will no longer try to download an asset once its error count reaches 3, unless
you reset it (This is the “outer” three). You can reset the error state on an asset by using
the ​resetErrors​ method of the I
​ Queue​ interface.
Here are the error conditions Virtuoso may encounter:
Condition

Description

Result

Server-advertised
file size disagrees
with expected file
size

You provided an expected size for the file
and it does not match the Content-Length
supplied by the HTTP server.

SDK updates Asset’s status to
AssetStatus.DOWNLOAD_FILE_SIZE
_MISMATCH​ and increments its error
count.

Invalid mime type

MIME type advertised by the HTTP
server does not match the expected
MIME type you supplied.

SDK updates Asset’s status to
AssetStatus.DOWNLOAD_FILE_MIME
_MISMATCH​ and increments its error
count

Observed file size
disagrees with
expected file size

After the download completes, the size of
the downloaded file on disk doesn’t
match the expected size specified when
the file was created OR doesn’t match
the Content-Length supplied by HTTP
server.

SDK updates Asset’s status to
AssetStatus.DOWNLOAD_FILE_SIZE
_MISMATCH​ and increments its error
count

Hash mismatch

Observed MD5 of downloaded file
doesn’t match the expected MD5 you
supplied.

SDK updates Asset’s status to
AssetStatus.DOWNLOAD_FILE_CORR
UPT​ and increments its error count

Network error

Some network issue (HTTP 404,416,
etc.) caused the download to fail.

SDK updates Asset’s status to
AssetStatus.DOWNLOAD_NETWORK_E
RROR​ and increments its error count

File system error

The OS couldn't write the file to disk. In
most cases, the root cause is a full disk.

SDK updates Asset’s status to
AssetStatus.DOWNLOAD_FILE_COPY
_ERROR​ and increments its error count

The SDK notifies your app of errors that occur during download through the ​IQueueObserver
interface. The callback method for receiving details of errored assets is
engineEncounteredErrorDownloadingAsset(IIdentifier aAsset)​. You can determine the
type of error by examining the status of the asset (​getDownloadStatus​) sent in the callback.
If you are using a Cursor obtained through the ​IQueue​ interface then the SDK will notify the
IQueue​ Content Uri of the change.
v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 28 of 32

Appendix B: Manually Creating an App
Earlier in this document, we described a shell script (included in the Android developer package)
that automatically generates a skeleton app. We now describe how to do this manually, if you’re
so inclined.
The quickest way to start developing your own app is to create a simple Android “hello world”
project. You can then add in the SDK libraries and modify the manifest accordingly.
Create “hello world” app
1. Create a new Android Project
2. Provide the app’s name and package; click next
3. Leave the default options alone; click next
4. Choose an icon to use in the app or leave the defaults; click next.
5. Choose the kind of activity to create; click next.
6. Set the name of your activity; click finish
Add libraries
Browse to the SDK deliverable directory and locate the version of the ​virtuososdk.jar​ you
want to use. Drag this file into the ​libs​ directory of your project.
Create your ContentProvider
You’ll need to add a content provider to your app. This content provider will provide the Virtuoso
service with access to the database used for your app’s media.
Each app has its own database and is stored in the app’s data directory.
Android doesn’t permit installation of a content provider it there already exists one on the device
with the same name or using the same authority. The SDK provides a framework for content
providers so that the Virtuoso service can use a content provider for each application without
conflicts.
To create the new content provider:
1. Click on your package and open the context menu to add a new class.
2. Provide a name for your class. To ensure its uniqueness, we recommend naming it with
the name of your application followed by the string ​“ContentProvider”.
3. Extend your class from the abstract Virtuoso content provider. Click on the “Browse...”
button of the Superclass field and select the ​VirtuosoSdkContentProvider​ as the
super class.
4. Click Finish
5. Your generated class should look similar to this:
package com.demo.myvirtuosoapp;
import com.penthera.virtuososdk.database.impl.provider.VirtuosoSDKContentProvider;
public class MyVirtuosoAppContentProvider extends VirtuosoSDKContentProvider {
@Override
protected String getAuthority() {
// TODO Auto-generated method stub
return null;
}
}

Implement the ​getAuthority​ method and provide a static implementation that sets the Authority
to be used for this content provider. The authority string should uniquely identify your content
provider on an Android device. We recommend that the authority string consist of:your package
name, your application name and the string ​“virtuoso.content.provider.”​ For example:
v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 29 of 32

public class MyVirtuosoAppContentProvider extends VirtuosoSDKContentProvider {
static{
setAuthority("com.demo.myvirtuosoapp.myvirtuosoapp.virtuoso.content.provider");
}
@Override
protected String getAuthority() {
return "com.demo.myvirtuosoapp.myvirtuosoapp.virtuoso.content.provider";
}
}

Update the AndroidManifest.xml
Add the following permissions to the manifest:








Add large heap support to the Application
Add the the largeHeap flag to the application in the manifest to allow for best performance if you
are downloading large files:


Add the Virtuoso Service
Add the Background service in the application section of the AndroidManifest:






.
.
.


The action for starting the service (​START_VIRTUOSO_SERVICE​) must ​exactly​ match the string
declared in ​com.penthera.virtuososdk.Common.START_VIRTUOSO_SERVICE.
Note that this string is based on the version number of the SDK, so you’ll need to update it when
upgrading the SDK. The value of this string is in the Javadocs.
v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 30 of 32

Add the Client HTTP Service
The HTTP service sits between the device media player and the downloaded files. Its job is to
abstract the location of the downloaded files, and to enforce access policies on the files.
For downloaded HLS assets, the HTTP service generates an HLS manifest (m3u8) for the media
player.






Add the Virtuoso Service starter
The Virtuoso service starter ensures the Virtuoso service starts on device boot and keeps running
in the background. You’ll need to add it to the application section of the AndroidManifest:

















Add the metadata
This metadata identifies your app with the background service. The meta-data is used when a
Virtuoso service needs to access your content provider. The metadata details the authority string
used by your provider. The name of the metadata is
com.penthera.virtuososdk.client.pckg.


Add the content provider
Add this to application element of the AndroidManifest. The content provider enables the SDK to
access the SQLite database within your app directory. The authority string for the content
provider must match that supplied in the metadata element and the authority String being
supplied in the content provider class you created.
v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 31 of 32


In your class derived from ​VirtuosoSDKContentProvider:
public class MyVirtuosoAppContentProvider extends VirtuosoSDKContentProvider {
static {
setAuthority("com.demo.myvirtuosoapp.myvirtuosoapp.virtuoso.content.provider");
}
@Override
protected String getAuthority() {
return "com.demo.myvirtuosoapp.myvirtuosoapp.virtuoso.content.provider";
}
}

You should now be able to build and run your project without errors.

** END OF DOCUMENT **

v3.13 March 10 2017
Questions? Email ​support@penthera.com

page 32 of 32



Source Exif Data:
File Type                       : PDF
File Type Extension             : pdf
MIME Type                       : application/pdf
PDF Version                     : 1.5
Linearized                      : Yes
Producer                        : Skia/PDF m70
Page Count                      : 32
EXIF Metadata provided by EXIF.tools

Navigation menu