SDK Integration guide
Version: 18.4.2

Contents

1. Introduction

The Predictive Content Delivery (PCD) SDK allows users to download videos intelligently to mobile devices. Users initiate downloads by browsing your video catalog and selecting the content to watch.

A sophisticated Download Manager checks the battery, device storage, network, and other variables to make sure that the video transfer will succeed. Errors are thrown if the video transfer fails.

Downloads continue whether the app is active in the foreground or running in the background. In addition, the SDK acts as a data cache, providing instant access to content along with its current download state.

1.1 Audience

This document is for developers implementing the PCD SDK; deep knowledge of Android and/or iOS is assumed.

There are separate instructions for Android and iOS. Configuring your project to support these platforms is covered in the following chapters.

2. Getting Started with Android

The fastest way to get started with SDK is to try the sample app. Once you've downloaded the Zip file and changed the necessary requirements and dependencies, you're ready to go.

After you're done playing with the sample app, the next steps are SDK initialization and registration. These steps are covered in detail in Integrating With Your Android Application.

2.1. Requirements and Dependencies

Required gradle dependencies:

2.2. SDK Contents

When you download the PCD SDK zip, the contents include:

2.3. Trying the Sample App

The easiest way to get started is to try the sample app.

  1. Open Android Studio and navigate to where the SDK was unzipped.
  2. Import d2go.
  3. Substitute your SDK license key for the existing key.
  4. Build/run the d2go sample app.

3. Integrating With Your Android Application

To start using the PCD SDK with your own app, you'll need to include the SDK in your app. This is followed by initialization which also handles registration.

3.1. Installing the SDK - Use jcenter

To install and include the SDK using jcenter, add the dependency as below.

implementation 'com.akamai.android:pcd-sdk:{version}@aar'

3.2. Including the SDK

  1. Copy pcd-sdk-version.aar from the Zip file into the /app/libs directory in your project’s root directory.
  2. Now update the client build.gradle by adding the following gradle dependencies into your application build.gradle
implementation 'com.google.android.gms:play-services-gcm:10.2.1'
implementation (name:'pcd-sdk-release-{version}', ext:'aar')
implementation 'com.android.support:support-v4:26.1.0'

useLibrary 'org.apache.http.legacy'

3.2.1. Sample build.gradle

android {
     compileSdkVersion 26
     buildToolsVersion '26.0.2'
     defaultConfig {
         minSdkVersion 15
         targetSdkVersion 25
         versionCode 1
         versionName "1.0"
     }
     buildTypes {
         release {
             runProguard false
             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
         }
     }
     useLibrary 'org.apache.http.legacy'
 }

 repositories {
     mavenCentral()
     flatDir {
         dirs 'libs'
     }
 }

 dependencies {
    // Required gradle dependencies
    implementation 'com.google.android.gms:play-services-gcm:10.2.1'
    implementation (name:'pcd-sdk-release-{version}', ext:'aar')
    implementation 'com.android.support:support-v4:26.1.0'
 }

Note you will need to add google maven repo in the root build.gradle for support-v4:26.1.0

maven {
    url "https://maven.google.com"
}

3.3. Updating the Client Manifest

You also need to update AndroidManifest.xml to complete the SDK integration.

<manifest . . . >
     <application . . . >
          . . .
          **<!-- {ApplicationId} should be replaced with the client app package name , example - com.domain.applicationname -->**
          **<!-- A receiver to listen to Voc Status messages -->**
          <receiver
               android:name="{ApplicationId}.VocBroadcastReceiver">
                    <intent-filter>
                         <action android:name="com.akamai.android.sdk.ACTION_VOC_STATUS" />
                         <category android:name="{ApplicationId}" />
                    </intent-filter>
          </receiver>

          **<!-- A provider to access cached content -->**
          <provider
               android:name="com.akamai.android.sdk.db.AnaContentProvider"
               android:authorities="{ApplicationId}.AnaContentProvider" >
          </provider>
          **<!-- SDK init file -->**
          <meta-data
           android:name="com.akamai.android.sdk"
           android:resource="@xml/akamai_sdk_init" />
          . . .
     </application>
</manifest>

Note: the SDK requires these permissions for full functionality:

<manifest . . . >
     . . .
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     . . .
</manifest>

Note: The PCD SDK relies on GCM Notifications that are delivered to a GCM broadcast receiver in the SDK; if the client app has its own GCM receivers then they could also be triggered due to PCD GCM notifications.

3.4. Adding License key

During initialization, the SDK will attempt to register using the license key. This key is linked to a content catalog, generation of usage statistics, and other SDK capabilities. In this guide, we are discussing the most common way of providing license key in your app.

The SDK uses license key to initialize. This information is stored in a file android_sdk_init.xml in the client app’s resources folder. The sample file is shown below.

In …./main/res/xml (create if it doesn’t exist) folder, add a new file android_sdk_init.xml.

<?xml version="1.0" encoding="utf-8"?>
<com_akamai_sdk_init>
   <!-- SDK license key provided by Akamai-->
   <com_akamai_sdk_license_key>
      < !-- Your Akamai License Key -->
   </com_akamai_sdk_license_key>  
</com_akamai_sdk_init>

3.5. Initialization

After the app starts, the first step is to create an instance of VocService using VocService.createVocService(Context applicationContext), then set up the configuration using VocConfigBuilder. This should be done on main application create or onCreate of the main activity.

VocService vocService = VocService.createVocService(getApplicationContext());

For Download to Go, disable auto prefetch:

VocConfigBuilder builder = new VocConfigBuilder(context.getApplicationContext());
        builder.disableAutoPrefetch();

3.6. SDK Events

The initialization of the SDK triggers registration in the background. If registration succeeds, cache initialization is triggered in the background. The client listens to the cache status by registering a BroadCastReceiver with filter set to "com.akamai.android.sdk.ACTION_VOC_STATUS".

Example in Client Manifest 

<receiver android:name={ApplicationId}.MyVocBroadcastReceiver">
     <intent-filter>
          <action android:name="com.akamai.android.sdk.ACTION_VOC_STATUS" />
          <category android:name="{ApplicationId}" />
     </intent-filter>
</receiver>

The client app can then extend the VocStatusReceiver API and override methods to listen to different status updates like - cache sync start, cache sync done, download policy status failures, new content available etc.

For example:

public class MyVocBroadcastReceiver extends VocStatusReceiver {

    private static final String LOG_TAG = MyVocBroadcastReceiver.class.getSimpleName();

    /**
     * Triggered when the policy status update fails
     *
     * @param context    The application context
     * @param policyCode The policy code such as POLICY_NO_WIFI, POLICY_NO_NETWORK, POLICY_NOT_CHARGING
     */
    protected void onPolicyStatusFailure(Context context, int policyCode) {
        Log.d(LOG_TAG, " onPolicyStatusFailure " + policyCode);
    }

    /**
     * Triggered when cache sync is initiated
     *
     * @param context The application context
     */
    protected void onCacheSynchStart(Context context) {
        Log.d(LOG_TAG, " onCacheSynchStart");
    }

    /**
     * Triggered when cache sync is complete
     *
     * @param context The application context
     */
    protected void onCacheSynchDone(Context context) {
        Log.d(LOG_TAG, " onCacheSynchDone");
    }

    /**
     * Triggered when new content is available
     *
     * @param context     The application context
     * @param numNewFeeds Number of new feeds available
     */
    protected void onNewContentAvailable(Context context, int numNewFeeds) {
        Log.d(LOG_TAG, " onNewContentAvailable " + numNewFeeds);
    }

    /**
     * Triggered when content is purged from server
     *
     * @param context   The application context
     * @param purgeType Type of purge - global ,provider or id based
     * @param purgeIds  Ids of purged content
     */
    protected void onPurgeStatus(Context context, String purgeType, String[] purgeIds) {
        Log.d(LOG_TAG, " onPurgeStatus " + purgeType + " " + purgeIds);
    }

    /**
     * Called when status update to server fails
     *
     * @param context The application context
     */
    protected void onSendServerStatusFailure(Context context) {
        Log.d(LOG_TAG, " onSendServerStatusFailure");
    }

    /**
     * Triggered when a manifest is downloaded from the server.
     * Manifest contains the feed catalogue that can be cached by the client
     *
     * @param context: The application context
     */
    protected void onManifestDownloaded(Context context) {
        Log.d(LOG_TAG, " onManifestDownloaded");
    }
}

4. Download to Go

The PCD SDK has a number of ways users can get content, but it's suggested that you start by using Download to Go (D2G). D2G allows users to browse your video catalog and download one or more selections to their mobile device.

4.1. Setting up D2G

Before you can use D2G, you need to ingest your video catalog to the PCD Server. Ingesting content isn't a self-service operation at this time, so talk to your Akamai representative about ingesting content. Each piece of content (usually a video) ingested is represented by a unique content Id.

4.2. Configure SDK for D2G

VocConfigBuilder enables the client to set up the required preferences for voc content like network preference (wifi or wifi + cellular), media path to cached content, other properties that control downloads like cache size, individual file limit, minimum battery level for prefetch, etc. Clients can use this builder to change any values based on its requirement. Default values for some of the configurations are set at the global level (set to server values) and you could override those at the client level. You should discuss with your Akamai representative about the default value preferences for the configurations such as permitted network types for download (wifi, cellular, or both), content items per category, daily download size limit, maximum size allowed per file download, permitted time of day for downloading, etc. Some of the mandatory configurations for D2G that needs to be set at the client level are the following. The client should setup these configurations right after the SDK initialization/registration.

4.2.1. Item Download Behavior

Most common D2G download behavior is to download no content unless requested explicitly by the user. The default behavior of the SDK is to preposition all the content from the content catalog.

For D2G behavior, disable the auto prefetch downloads as below.

VocConfigBuilder builder = new VocConfigBuilder(context.getApplicationContext());
      builder.disableAutoPrefetch();

4.2.2. Auto Purge

The deletion of content in case of D2G is handled by the user. The automatic purging of the content is disabled in D2G case.

4.2.3. Maximum File Size

The SDK limits the size of an individual content that can be downloaded, default is 100MB. VocConfigBuilder.individualFileLimit can be used to change that limit.

VocConfigBuilder builder = new VocConfigBuilder(this)
      .individualFileLimit(1000); // Increase the file size to 1GB

4.3. Initiating a Download

To initiate a download, you need the contentId and provider of the content that was ingested.

  1. The client triggers a download using VocService::downloadFeeds(ArrayList<AnaDownloadPrefs>), providing a list of contentIds and providers. Optionally you can provide resolution (defaults to medium).

  2. The client can access the metadata of the content being downloaded (represented by AnaFeedItem) using VocUtils.getAnaFeedItemFromContentId.

  3. The client can listen to the changes when data related to a contentId (metadata or state) is updated or added, this can be achieved using the standard Android-based cursor notifications, or by extending VocDownloadStatusReceiver which is described in the Managing Downloads section.

Here is an example to initiate the download using ContentId and provider:

/**
 * Downloads by content Id and Provider.
 *
 * @param contentId Previously ingested contentId.
 * @param provider The provider of the content.
 */
public void download(String contentId, String provider) {
    ArrayList<AnaDownloadPrefs> list = new ArrayList<>();
    AnaDownloadPrefs prefs = new AnaDownloadPrefs();
    prefs.setContentId(contentId);    
    prefs.setProvider(provider);
    prefs.setVideoQualityPreference(VocVideoQualityPreference.LOWRES);
    // Optionally provide any initial metadata(thumbnail, title etc) using prefs.setFeedItem.
    list.add(prefs);
    VocService.createVocService(getApplicationContext()).downloadFeeds(list);
}

4.4. Pause and Resume Downloading a Specific Item

The client can pause a specific content item download by call the API method VocService.pauseFeedDownload to pause the ongoing download of a video by passing the feedId as the argument.

/**
 * Pause download for a content Id.
 *
 * @param contentId Previously ingested contentId.
 */
public void pauseDownload(String contentId) {
    // getFeedIdFromContentId gets the unique feedId.
    final String feedId = VocUtils.getFeedIdFromContentId(getApplicationContext(), contentId);
    final VocService vocService = VocService.createVocService(getApplicationContext());
    vocService.pauseFeedDownloads(Arrays.asList(feedId));
}

Resume the download of a paused item (or items) by using VocService.resumeFeedDownloads.

/**
 * Resume download for a content Id.
 *
 * @param contentId Previously ingested contentId.
 */
public void resumeDownload(String contentId) {
    // getFeedIdFromContentId gets the unique feedId.
    final String feedId = VocUtils.getFeedIdFromContentId(getApplicationContext(), contentId);
    final VocService vocService = VocService.createVocService(getApplicationContext());
    vocService.resumeFeedDownloads(Arrays.asList(feedId));
}

5. Accessing Content

Content begins loading onto the device once the client has initiated a download. Downloading also happens automatically based on background GCM notifications the SDK receives.

5.1. Accessing Content Items

The client app can use AnaContentProvider to manage  its lists of cached content based on categories and providers. AnaContentProvider can be accessed using the public contract provided by AnaProviderContract, which supports a cursor/adapter framework for CRUD operations and notifications.

An additional Model API - AnaFeedItem, AnaFeedCategory, AnaContentSource are provided to be used as data models. See javadocs provided for the API reference

Data Object Description
AnaFeedItem A single video, song, image, etc.
AnaContentSource Content providers configured at the Web portal. Every content item belongs to a provider.
AnaFeedCategory A named group of related content items (e.g., "Sports").

For example:

// Get a list of all content metadata - client would use
Cursor cursor = getApplicationContext().getContentResolver().query(
        AnaProviderContract.CONTENT_URI_FEEDS,
        null, null, null, null);
ArrayList<AnaFeedItem> items = new ArrayList<AnaFeedItem>()
// See AnaFeedItem for the properties exposed
if (cursor != null) {
    cursor.moveToFirst();
    while (!cursor.isAfterLast()) {
        AnaFeedItem feedItem = new AnaFeedItem(cursor);
        items.add(feedItem);
        cursor.moveToNext();
    }
    cursor.close();
}

To show content in a my downloads list activity:

public class MyDownloadsListActivity extends Activity implements LoaderManager.LoaderCallbacks<Cursor>{

...

@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {

   CursorLoader loader = new CursorLoader(getApplicationContext(),
   	AnaProviderContract.CONTENT_URI_FEED_DOWNLOAD_STATE,
     	null, null, null, AnaProviderContract.FeedItem.MARK_FOR_DOWNLOAD_TIMESTAMP + " DESC");
    return loader;
}

...
}

public class MyDownloadsFeedAdapter extends RecyclerView.Adapter<MyFeedItemViewHolder>{
...

@Override
public void onBindViewHolder(MyFeedItemViewHolder holder, int position) {
    if (mCursor.moveToPosition(position)) {
        final AnaFeedItem feedItem = new AnaFeedItem(mCursor);
        // Use AnaFeedItem properties using getters to paint the list
        if (!feedItem.isResourceReady()) {
            //content is not available locally
            playOverlay.setBackgroundColor(Color.GRAY);
        } else {
            //Enable play button
        }
        ...
    }
}

5.2. Listening for Data Set Changes

Any changes in the feeditem state will be notified using cursor based notifications to the respective URI in above case AnaProviderContract.CONTENT_URI_FEED_DOWNLOAD_STATE

To get the latest download states:

// Get the states defined in VocDownloadStatusReceiver
AnaFeedItem.getDownloadState();

// Get the number of bytes downloaded to show progress
AnaFeedItem.getBytesDownloaded();

// If Download fails, get the error code from
AnaFeedItem.getDownloadFailureErrorCode

5.3. Accessing Cached Content

5.3.1. Playing HLS Video Content

HTTP Live Streaming (HLS) video can be played from fully- or partially-prepositioned content.

Playback requires starting the HLS server using VocService.startMediaServer(). This starts media server on a random port to serve cached HLS segments. Once the playback is finished, stop the media server by calling VocService.stopMediaServer(). The client controls the starting and stopping of the media server.

VocUtils is a utility class that helps client to get media/data/resource path. Once the media server is started, client must call VocUtils.getResourcePath() to get appropriate HTTP URL to play locally cached HLS content. Note – The path returned by VocUtils.getResourcePath becomes invalid once the media server is stopped.

// To play locally cached HLS content using default android VideoView:
AnaFeedItem feedItem = VocUtils.getAnaFeedItemFromContentId(getApplicationContext(), "<Your content Id>");
if (feedItem != null && feedItem.getType().equals(AnaFeedItem.CONTENT_TYPE_HLS)) {
    // Start media server to serve hls content over http.
    VocServiceResult result = VocService.createVocService(getApplicationContext()).startMediaServer();
    if (result.isSuccess()) {
        String cachedFeedUrl = VocUtils.getResourcePath(feedItem,getApplicationContext());
        MediaController mediaController = new MediaController(this);
        // Previously initialized mVideoView
        mediaController.setAnchorView(mVideoView);
        mVideoView.setMediaController(mediaController);
        mVideoView.setVideoURI(Uri.parse(cachedFeedUrl));
        mVideoView.start();
    }
}

// Stop the media server once cached HLS playback is finished.
vocService.stopMediaServer();

// And report consumption stats using
vocService.updateFeedConsumptionStats(feedItem.getId(),  startTime,  stopPosition);

5.3.2. Playing Cached Non-HLS Video

To play downloaded content (non-HLS content) using default android VideoView:

AnaFeedItem feedItem = VocUtils.getAnaFeedItemFromContentId(getApplicationContext(), "<Your content Id>");
if (feedItem != null) {
    // Start media server to serve hls content over http.
    String cachedFeedUrl = VocUtils.getResourcePath(feedItem,getApplicationContext());
    MediaController mediaController = new MediaController(this);
    // Previously initialized mVideoView
    mediaController.setAnchorView(mVideoView);
    mVideoView.setMediaController(mediaController);
    mVideoView.setVideoURI(Uri.parse(cachedFeedUrl));
    mVideoView.start();
}

// On video play completion client can update consumption stats to the server using
vocService.updateFeedConsumptionStats(feedItem.getId(),  startTime,  stopPosition);

5.3.3. Record Video Consumption by User

When the user watches a video partially or fully, the client reports the consumption to the PCD SDK.

/**
* Update the consumption statistics
*
* @param item      item representing the video that was played
* @param startTime time when the video playing started
*/
private void updateConsumptionStatistics(AnaFeedItem item, long startTime) {
   VideoPlayerController videoController = getActivity().getVideoPlayerController();
   int stopPosition = videoController.getContentProgress();
   VocService.createVocService(getActivity()).updateFeedConsumptionStats(item.getId(), startTime, stopPosition);
}

5.4. Content Item States

Each item progresses through different states before getting cached. VocDownloadStatusReceiver has constants which can take the following values:

DownloadState Description
TYPE_DOWNLOAD_STATE_QUEUED Download state indicating download is queued; it is currently not downloading. It will start downloading.
TYPE_DOWNLOAD_STATE_PROGRESS Download state indicating download is in progress.
TYPE_DOWNLOAD_STATE_STARTED Download state indicating download is started.
TYPE_DOWNLOAD_STATE_COMPLETED Download state indicating download is successfully completed. Cached content is ready for use.
TYPE_DOWNLOAD_STATE_FAILED Download state indicating download failed either because user paused it or because of some failure condition indicated by error codes.

5.5. Download Error Codes

When the download fails, the following error codes can be used to verify the reason for failure or download suspension.

Error code Description
DOWNLOAD_ERROR_CODE_INTERRUPTED Error code indicating that download was interrupted by user. This is same as user pausing the download.
DOWNLOAD_ERROR_CODE_NOT_DOWNLOADABLE Error code indicating that this feed doesn't allow download e.g. if the response header for the content url specifies "no-store" directive.
DOWNLOAD_ERROR_CODE_NETWORK_UNAVAILABLE Error code indicating that download failed because of network connection loss.
DOWNLOAD_ERROR_CODE_UNKNOWN Error code indicating some unknown error occurred.
DOWNLOAD_ERROR_OUT_OF_POLICY Error code indicating PCD policy failure.
DOWNLOAD_ERROR_CACHE_FULL Error code indicating no space left on device to download content.
DOWNLOAD_ERROR_HTTP_ERROR Error code indicating Http error.
DOWNLOAD_ERROR_DISK_SPACE_NA Error code indicating that the disk space for download is no longer available. Typically happens when storage is filled by some other app during download.

5.6. Content Sources

A content source identifies the provider (e.g., a channel, company, or aggregator) of content items. Sources are defined on the SDK Web portal. The SDK allows sources to be toggled on or off.

// AnaProviderContract.CONTENT_URI_SOURCES - Provides list of content sources/provider present
Cursor cursor = context.getContentResolver().query(
        AnaProviderContract.CONTENT_URI_SOURCES,
        null, null, null, null);
ArrayList<AnaContentSource> sources = new ArrayList<AnaContentSource>();
if (cursor != null) {
    cursor.moveToFirst();
    while (!cursor.isAfterLast()) {
        AnaContentSource feedSource = new AnaContentSource(cursor);
        sources.add(feedSource);
        cursor.moveToNext();
    }
    cursor.close();
}

5.7. Content Categories

Categories are named groups of related content, such as “family,” “trending,” or “news.” Working with a category makes it easy to display dedicated views for broad content types, or to toggle the display of an entire group.

// AnaProviderContract.CONTENT_URI_FEEDCATEGORY - provides Categories of content  
// (Entertainment , sports etc)
Cursor cursor = context.getContentResolver().query(
        AnaProviderContract.CONTENT_URI_FEEDCATEGORY,
        null, null, null, null);
ArrayList<AnaFeedCategory> retVal = new ArrayList<AnaFeedCategory>();
if (cursor != null) {
    cursor.moveToFirst();
    while (!cursor.isAfterLast()) {
        AnaFeedCategory feedCategory = new AnaFeedCategory(cursor);
        retVal.add(feedCategory);
        cursor.moveToNext();
    }
    cursor.close();
}

5.8. Source and Category Filters

Additionally you can filter content by using standard content provider queries and selection mechanism based on AnaProviderContract.

// Filter items based on Category
Uri.Builder builder;
String category = "News";
builder = AnaProviderContract.CONTENT_URI_FEEDS_FILTER.buildUpon();
builder.appendQueryParameter(AnaProviderContract.FeedItem.CATEGORIES, category);
Cursor cursor = getApplicationContext().getContentResolver().query(builder.build(),
	null, null,
	null, null);


// Filter items that are pre-positioned
Uri.Builder builder = AnaProviderContract.CONTENT_URI_FEEDS_FILTER.buildUpon();
builder.appendQueryParameter(AnaProviderContract.FeedItem.RESOURCEREADY, "true");
Cursor cursor = getApplicationContext().getContentResolver().query(builder.build(),
	null, null,
	null, null);

// Filter items based on video type example - m3u8
String selection = AnaProviderContract.FeedItem.FEEDTYPE + "=?";
String[] selectionArgs = new String[]{AnaFeedItem.CONTENT_TYPE_HLS};
Cursor cursor = getApplicationContext().getContentResolver().query(
	AnaProviderContract.CONTENT_URI_FEEDS,
	null, selection, selectionArgs, null);

// Filter items based on type example - images
String selection = AnaProviderContract.FeedItem.FEEDTYPE + "=?";
String[] selectionArgs = new String[]{"image/jpeg"};
Cursor cursor = getApplicationContext().getContentResolver().query(
	AnaProviderContract.CONTENT_URI_FEEDS,
	null, selection, selectionArgs, null);

5.9. Accessing Subtitles and Thumbnails

At this point in your app lifecycle, we assume you have Download to Go working, and a sizeable and growing video catalog. As your video catalog grows, your home page may start to slow down as it tries to load perhaps hundreds of thumbnails images. The solution to this is to pre-position images on the user's mobile device, before they are requested.

Later you may want to implement pre-positioning video content as well, which is covered in the next chapter. For the time being, let's dip a toe into this technology and simply pre-position the video thumbnails. Here is how to access the thumbfiles.

AnaFeedItem feedItem = VocUtils.getAnaFeedItemFromContentId(context, "<Your content Id>");
File thumbFile = new File(VocUtils.getMediaPath(context, feedItem) + feedItem.getThumbFileName());

6. Managing Downloads

6.1. Track Progress of a Downloading Video

Apart from the cursor based notifications in list views, a client app can also dynamically register BroadcastReceiver to listen to the download status, such as bytes downloaded, bytes remaining, and ETA for download. The client app can subclass VocDownloadStatusReceiver class and implement the callback method onFeedDownloadStatus and use the parameters of the method to show download progress in the status bar.

public class ClientBroadcastReceiver extends VocDownloadStatusReceiver {

    private static final String LOG_TAG = ClientBroadcastReceiver.class.getSimpleName();

    /**
     * Called whenever the download progress status changes
     *
     * @param context         The application context
     * @param feedId          Feed id of the feed item getting downloaded
     * @param bytesDownloaded Bytes downloaded
     * @param bytesRemaining  Bytes remaining for download
     * @param eta             ETA for the download
     */
    protected void onFeedDownloadStatus(Context context, String feedId, long bytesDownloaded, long bytesRemaining, long eta) {
        Log.d(LOG_TAG, "onFeedDownloadStatus" + " feedId " + feedId);
    }

    /**
     * Called when a feed is queued for download. It means that it will start downloading
     * once other downloads in the download queue are finished
     *
     * @param context The application context
     * @param feedId  Feed id of the feed item to download
     */
    protected void onFeedDownloadQueued(Context context, String feedId) {
        Log.d(LOG_TAG, "onFeedDownloadQueued" + " feedId " + feedId);
    }

    /**
     * Called when a feed download starts
     *
     * @param context The application context
     * @param feedId  Feed id of the feed item to download
     */
    protected void onFeedDownloadStarted(Context context, String feedId) {
        Log.d(LOG_TAG, "onFeedDownloadStarted" + " feedId " + feedId);
    }

    /**
     * Called when a feed download is completed successfully
     *
     * @param context The application context
     * @param feedId  Feed id of the feed item downloaded
     */
    protected void onFeedDownloadCompleted(Context context, String feedId) {
        Log.d(LOG_TAG, "onFeedDownloadCompleted" + " feedId " + feedId);
    }

    /**
     * Called when a feed download is failed
     *
     * @param context   The application context
     * @param feedId    Feed id of the feed item downloaded
     * @param errorCode Error code which hints why the download failed
     */
    protected void onFeedDownloadFailed(Context context, String feedId, int errorCode) {
        Log.d(LOG_TAG, "onFeedDownloadFailed" + " feedId " + feedId + " errorcode " + errorCode);
    }

    /**
     * Callback to let the application know of the content id(s) that were not found
     *
     * @param context    The application context
     * @param contentIds The content ids that were not found
     * @param errorMsg   The error message with more details
     */
    protected void onContentNotFound(Context context, ArrayList<String> contentIds, String errorMsg) {
        Log.d(LOG_TAG, "onContentNotFound" + " contentIds " + contentIds + " errorMsg " + errorMsg);
    }
}

The receiver can be registered dynamically or the client manifest can be updated with the new receiver.

private BroadcastReceiver receiver;

@Override
public void onCreate(Bundle savedInstanceState) {
   ...
   IntentFilter filter = new IntentFilter();
   filter.addAction("com.akamai.android.sdk.ACTION_VOC_DOWNLOAD_STATUS");
   filter.addCategory(getPackageName());
   receiver = new ClientBroadcastReceiver();
   this.registerReceiver(receiver, filter);
   ...
}

@Override
public void onDestroy() {
   this.unregisterReceiver(receiver);
   super.onDestroy();   
}

Example change in Client Manifest

<manifest . . . >
     <application . . . >
          . . .
          **<!-- {ApplicationId} should be replaced with the client app package name , example - com.domain.applicationname -->**
          **<!-- A receiver to listen to Voc Download Status messages -->**
          <receiver
               android:name="{ApplicationId}.ClientBroadcastReceiver">
                    <intent-filter>
                         <action android:name="com.akamai.android.sdk.ACTION_VOC_DOWNLOAD_STATUS" />
                         <category android:name="{ApplicationId}" />
                    </intent-filter>
          </receiver>
          . . .
     </application>
</manifest>

Alternatively instead of listening, the client app can use the API method VocService.getDownloadStatus by passing the feedId to get the download information of that particular feed item whenever they need.

@Override
public void onCreate(Bundle savedInstanceState) {
   AnaFeedDownloadStatus downloadStatus = vocService.getDownloadStatus(feedId);
   long bytesDownloaded = downloadStatus.getBytesDownloaded();
   long bytesRemaining = downloadStatus.getBytesRemaining();
}

On app restart, any queued/in progress downloads can be restarted using VocService.performCacheFill(). Typical use case would be to call this on application's main activity onCreate.

6.2. Delete/Cancel Content

Call the API method VocService.deleteFeed to delete all the files and metadata related to the video from the device by passing the feedId in the argument. Optionally, the app can retain the thumbnail and metadata using VocService.deleteFiles.

// getFeedIdFromContentId gets the feedId from contentId.
final String feedId = VocUtils.getFeedIdFromContentId(getApplicationContext(), <contentId>);
final VocService vocService = VocService.createVocService(getApplicationContext());
VocServiceResult result = vocService.deleteFeed(feedId);

6.3. Download Policy

See section 3.6 for sdk events to handle using VocStatusReceiver. Alternatively, client can also check the current download policy using VocService.getPolicyStatus(). It returns the current policy state defined in VocPolicyStatus.

6.4. Token Authentication

TOKEN Authentication

Some of the customer's content is protected by a combination of time constrained tokens. There are long lived and short lived tokens. Some are part of the query string, some are cookies.

6.4.1. Assumptions

6.4.2. Workflow

  1. Customer implements getTokenizedUrl callback provided by PCD SDK.

  2. PCD SDK calls getTokenizedUrl call back with the item about to start downloading, the url and the headers.

  3. Customer modifies url and headers, inserting the correct tokens where they need to be.

  4. Download proceeds with the modified url and headers.

6.4.3. Sample

  1. Customers should Implement the interface VocUrlTokenAuthInterface and override the method getTokenizedUrl(AnaFeedItem feedItem, HashMap<String,String> requestHeaders).

FeedItem has properties like contentId that can be used to map the Url. The requestHeaders map can be used to store any request headers that are associated with the Url.

This part is new

Tokenized Url = https:///i/videos/movies/movie1//master.m3u8?hdnea=st=1479797033~exp=1480 157033~acl=/*~hmac=25edcab2ac5f0f3656faaf49a0c6ac972cf763076392890171731fe099368aba

Customers should return the encoded Url to the SDK for downloads as shown below.

An example of Token Auth Implementation where the dynamic query parameters (time bound) of the base url Is updated for downloading, this will change based on tokenization being used by client app.

public class VocUrlTokenAuthTest implements VocUrlTokenAuthInterface {
   @Override
   public String getTokenizedUrl(AnaFeedItem feedItem, HashMap<String, String> requestHeaders) {
      Uri.Builder uriBuilder = new Uri.Builder().encodedPath(feedItem.getUrl());
      uriBuilder.appendQueryParameter(key, value); //append any tokens required
      return uriBuilder.toString();
   }
}
  1. Next step is to pass the instance of VocUrlTokenAuthTest class to the SDK on initialization using
VocService.setUrlTokenizer(VocUrlTokenAuthInterface vocUrlTokenizer)

@Override
protected void onCreate(Bundle savedInstanceState) {

   VocService vocService = VocService.createVocService(getApplicationContext());
   vocService.setUrlTokenizer(new VocUrlTokenAuthTest());
}

Also set it on onCacheSynchStart event in case of downloads triggered based on background GCM notifications

public class MyVocBroadcastReceiver extends VocStatusReceiver {
  /**
   * Triggered when cache sync is initiated
   *
   * @param context The application context
   */
  protected void onCacheSynchStart(Context context) {
      VocService vocService = VocService.createVocService(getApplicationContext());
      vocService.setUrlTokenizer(new VocUrlTokenAuthTest());
  }
}

7. SDK Settings

VocConfigBuilder enables the client to set up the required preferences for voc content like network preference (wifi or wifi + cellular), media path to cached content, other properties that control downloads like cache size, individual file limit, minimum battery level for prefetch, etc. Clients can use this builder to change any values based on its requirement.

VocConfigBuilder can also be used to disable background downloads. When disabled, only the manifest from voc server is downloaded, but the actual content is not; the client would have to use its own download manager to download content. By default background downloads are enabled.

Please see VocConfigBuilder in the API reference for all the available SDK settings.

7.1 Maximum File Size

The D2G use cases generally have content that has large file size. VocConfig has a preference, individualFileLimit, which provides the client an option to increase the max file size. The default file size limit is 100MB.

VocConfigBuilder builder = new VocConfigBuilder(this)
      .individualFileLimit(1000); // Increase the file size to 1GB

7.2. Network Preference

The API provides control over whether downloads are permitted on wifi, cellular, or both. Setting the VocConfigBuilder networkPreference preference affects downloads that have not already started.

// Setup SDK Config Network preferences
VocConfigBuilder builder = new VocConfigBuilder(this)
      .networkPreference(VocNetworkPreference.WIFICELLULAR);

7.3. Maximum Number of Concurrent Downloads

The default behavior of the SDK is to perform any number of concurrent file downloads. VocConfigBuilder has a preference, ‘maxThreadsForSync,’ which provides the client an option to restrict the number of simultaneous downloads. This applies to both foreground sync and background sync. Use with caution, changing this value will have affect on performance and memory utilization.

// Setup SDK Config concurrent download preferences
VocConfigBuilder builder = new VocConfigBuilder(this)
      .maxThreadsForSync(2); // two simultaneous downloads

8. Appendix

8.1. Content Migration

The SDK supports migrating existing content to PCD system. Once migrated the content will be managed using PCD sdk.

8.1.1. Assumptions

8.1.2. Workflow

    VocService.addDownloadedItem(AnaFeedItem feeditem);

8.1.3. Example

AnaFeedItem feedItem = new AnaFeedItem();
feedItem.setContentId("452");
feedItem.setProvider("NewUser");
feedItem.setVideoFileName("1000159691-local_v1.m3u8");
feedItem.setSourcePath("/storage/emulated/0/Android/data/example.com.d2go/userdownload/files/1000159691/");
feedItem.setUrl("https://sampleurl.com");
feedItem.setType(AnaConstants.CONTENT_TYPE_HLS);
feedItem.setDuration(1000);
feedItem.setTitle("Test HLS");
feedItem.setSummary("Test Summary");
feedItem.setThumbFile("http://samplethumbfile.jpg");
feedItem.setSize(1000);
VocServiceResult result = vocService.addDownloadedItem(feedItem);
if (result.isSuccess()) {
    // feed migrated successfully.
} else {
    // migration failed, check the error message, rectify the error and retry.
    result.getErrorCode();
    result.getErrorMessage();
}