Widevine Android SDK Guide

Overview

PallyCon Widevine Android SDK makes it easy to apply Google’s Widevine Modular DRM when developing media service apps for Android. This document describes how to use the libraries and sample project included in the SDK.

Details of PallyCon Multi DRM service integration are provided in License Token Guide. For technical questions about using the SDK, please visit our Helpdesk site.

This SDK product can be downloaded from PallyCon Github repository. In the trial account, you can freely test SDK products within the trial limit on the number of licenses issued. However, in order to apply the SDK to commercial services, you must apply for a plan that includes SDK usage rights when subscribing to PallyCon commercial plans.

Requirements

  • Android version 5.0 (Lollipop) or later. AndroidX library is used.
  • PallyCon Widevine Android SDK uses Google Media3 framework internally.

Tutorial Video

This video is a tutorial for playing DRM content using the sample project included in the SDK.

For optimal playback, select ‘1080p’ as the video quality and enable subtitle (Korean or English) before starting playback.

Quick Start

You can apply PallyCon Widevine Android SDK to your development project by following these steps:

  1. Extract SDK zip file.

  2. Copy PallyconWVMSDK.aar file to project/module/libs/ folder in your project.

  3. Apply the below configuration in build.gradle (project).

    buildscript {
        repositories {
            jcenter()
            google()
        }
        dependencies {
            classpath "com.android.tools.build:gradle:7.2.2"
            classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10"
        }
    }
    
    allprojects {
        repositories {
            jcenter()
            google()
        }
    }
    
  4. Apply the below configuration in build.gradle (app).

    android {
        defaultConfig {
            minSdkVersion 21
            targetSdkVersion 32
            multiDexEnabled true
        }
    
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation fileTree(dir: 'libs', include: ['*.aar'])
        implementation 'androidx.appcompat:appcompat:1.5.0'
        implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
        implementation "androidx.core:core-ktx:1.8.0"
        implementation "com.google.android.material:material:1.6.1"
        implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
    
        // Exo
        implementation 'com.google.android.exoplayer:exoplayer:2.18.1'
        implementation "com.google.android.exoplayer:exoplayer-core:2.18.1"
        implementation "com.google.android.exoplayer:exoplayer-dash:2.18.1"
        implementation "com.google.android.exoplayer:extension-okhttp:2.18.1"
    
        // Gson
        implementation "com.google.code.gson:gson:2.9.1"
    
        // Secure
        implementation "androidx.security:security-crypto-ktx:1.1.0-alpha03"
    }
    
  5. Implement PallyConEventListener in MainActivity. (Please refer to sample project)

    val pallyConEventListener: PallyConEventListener = object : PallyConEventListener {
        override fun onCompleted(currentUrl: String?) {
            // Called when download is complete: Please refer to the API Guide.
        }
    
        override fun onProgress(currentUrl: String?, percent: Float, downloadedBytes: Long) {
            // Call from start to end of download: Please refer to the API Guide.
        }
    
        override fun onStopped(currentUrl: String?) {
            // Called when download is stopped: Refer to the API Guide.
        }
    
        override fun onRestarting(currentUrl: String?) {
            // Called when download is restarting: Refer to the API Guide.
        }
    
        override fun onRemoved(currentUrl: String?) {
            // Called when downloaded content is removed: Refer to the API Guide.
        }
    
        override fun onPaused(currentUrl: String?) {
            // Called when download is pause: Refer to the API Guide.
        }
    
        override fun onFailed(currentUrl: String?, e: PallyConException?) {
            // Called when an error occurs while downloading content or an error occurs in the license: Refer to the API Guide.
        }
    
        override fun onFailed(currentUrl: String?, e: PallyConLicenseServerException?) {
            // Called when error sent from server when acquiring license: Refer to the API Guide.
        }
    }
    
  6. Create a PallyConWvSDK object with content information to download. Set your Site ID shown on PallyCon Console. (Please refer to sample project)

    
    // Enter DRM related information.
    val config = PallyConDrmConfigration(
        "site id",
         "site key", // Set to an empty string if you don't know 
        "content token",
         "custom data",
         mutableMapOf(), // custom header
         "cookie"
    )
    
    val data = PallyConData(
        contentId = "content id",
        url = "content URL",
        localPath = "Download location where content will be stored",
        drmConfig = config,
        cookie = null
    )
    
    val wvSDK = PallyConWvSDK.createPallyConWvSDK(
        Context, // Context
        data
    )
    
    wvSDK.setPallyConEventListener(pallyConEventListener)
    
  7. Get the track information of the content to be downloaded.

    // The device must be connected to a network.
    // When track information is acquired, the license is also automatically downloaded.
    val trackInfo = wvSDK.getContentTrackInfo()
    
  8. Select the track you want to download from the track information.

    // In our sample, we use TrackSelectDialog to select.
    trackInfo.video[0].isDownload = true
    trackInfo.audio[0].isDownload = true
    
  9. Execute the download after checking if the content has already been downloaded.

    val state = wvSDK.getDownloadState()
    if (state != COMPLETED) {
        wvSDK.download(trackInfo)
    }
    
  10. To play downloaded content, obtain MediaItem or MediaSource using the following API.

    // use MediaSource or MediaItem
    val mediaSource = wvSDK.getMediaSource()
    val mediaItem = wvSDK.getMediaItem()
    
  11. Implement player in PlayerActivity.java using the below development guide. http://google.github.io/ExoPlayer/guide.html

    Please refer to the below guide from Google for more information about Exoplayer. https://developer.android.com/guide/topics/media/exoplayer.html

  12. Check license duration and playback duration of DRM license.

    val drmInfo = wvSDK.getDrmInformation()
    val licenseDuration = drmInfo.licenseDuration
    val playbackDuration = drmInfo.playbackDuration
    
    if (licenseDuration <= 0 || playbackDuration <= 0) {
        // DRM License Expired
    }
    
  13. Set up ExoPlayer as follows.

    ExoPlayer.Builder(this).build()
        .also { player ->
            exoPlayer = player
            binding.exoplayerView.player = player
            exoPlayer?.setMediaSource(mediaSource) //use mediaSource.
            exoPlayer?.addListener(object : Player.Listener {
                override fun onPlayerError(error: PlaybackException) {
                    super.onPlayerError(error)
                }
    
                override fun onIsPlayingChanged(isPlaying: Boolean) {
                    super.onIsPlayingChanged(isPlaying)
                }
            })
        }
    

Registering download services

  • To support background notifications for downloads, register your download service as shown below.

     wvSDK.setDownloadService(DemoDownloadService::class.java)
    
  • Register the download service in the AndroidManifest.xml.

    <service
        android:name="com.pallycon.pallyconsample.DemoDownloadService"
        android:exported="false">
        <intent-filter>
            <action android:name="com.google.android.exoplayer.downloadService.action.RESTART" />
    
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </service>
    

Managing licenses

You can download and delete DRM licenses as below.

Downloading license

val uri = Uri.parse("content url")
// val dataSource = FileDataSource.Factory() // local file
val okHttpClient = OkHttpClient.Builder().build()
val dataSource = OkHttpDataSource.Factory(okHttpClient) // remote content
val dashManifest =
    DashUtil.loadManifest(dataSource.createDataSource(), uri)
val format = DashUtil.loadFormatWithDrmInitData(
    dataSource.createDataSource(),
    dashManifest.getPeriod(0)

// The format parameter does not need to be entered unless it is a local file.
// If the format value is NULL, it is automatically defined inside the SDK via the REMOTE CONTENT URL.
wvSDK.downloadLicense(format = format, { 
    Toast.makeText(this@MainActivity, "success download license", Toast.LENGTH_SHORT).show()
}, { e ->
    Toast.makeText(this@MainActivity, "${e.message()}", Toast.LENGTH_SHORT).show()
    print(e.msg)
})

Removing license

wvSDK.removeLicense()

Additional Guides

Blocking screen recording

To prevent content leaks with screen recording apps, you should block the capture function by adding the following code to your application:

val view = binding.exoplayerView.videoSurfaceView as SurfaceView
view.setSecure(true)

Migration from previous SDK version

Since the download method is different from widevine sdk 3.0.0, customers who are using the existing widevine sdk 2.x.x version must migrate the downloaded content.

You can use the needsMigrateDownloadedContent function to determine if the content needs to be migrated. Since the migration function operates only when there is migration content inside, it does not matter if it is called multiple times, and the parameter values of the function should be set identically to the values used in the existing 2.x.x version.

The localPath used when creating the PallyConData object should not be set as the parent directory of the existing downloaded contents. Therefore, if a MigrationLocalPathException exception occurs, the localPath value used when creating the PallyConData object must be modified for normal operation.

try {
    if (wvSDK.needsMigrateDownloadedContent(
            url = contents[index].content.url!!,
            contentId = contents[index].cid,
            siteId = contents[index].content.drmConfig!!.siteId!!)
    ) {
        val isSuccess = wvSDK.migrateDownloadedContent(
            url = "", // remote content URL
            contentId = "", // ID of content
            siteId = "", // inputs Site ID which is issued on PallyCon service registration
            contentName = "", // content's name which will be used for the name of downloaded content's folder
            downloadedFolderName = null // content download folder name
        )
    }
} catch (e: PallyConException.MigrationException) {
    print(e)
} catch (e: PallyConException.MigrationLocalPathException) {
    // you have to change localPath
    // ex) val localPath = File(fi, "downloads_v2").toString()
    print(e)
}

If the migration is successful, you can delete the 2.x.x version db by yourself like the code below.

val isSuccess = wvSDK.removeOldDownloadedContentDB(
    url = "", // remote content URL
    contentId = "", // ID of content
    siteId = "", // inputs Site ID which is issued on PallyCon service registration
)

SDK API Reference

Please refer to the doc/en/api_reference.html file of the SDK zip file for detailed explanation of each API.

Previous
Next