Widevine Android SDK 가이드

개요

PallyCon Widevine Android SDK는 안드로이드 OS용 미디어 서비스 앱을 개발할 때 구글의 Widevine Modular DRM을 쉽게 적용할 수 있게 해주는 제품입니다. 본 문서는 SDK에 포함된 라이브러리와 샘플 프로젝트의 사용법에 대해 설명합니다.

Widevine 클라이언트와 연동되는 PallyCon 멀티 DRM 서비스에 대한 설명은 라이선스 토큰 가이드를 참고하시기 바랍니다. SDK 사용과 관련한 기술 문의는 헬프데스크 사이트를 이용해 주시기 바랍니다.

해당 SDK 제품은 PallyCon 깃허브 저장소에서 다운로드 받을 수 있습니다. 트라이얼 계정에서는 라이선스 발급 수 제한 내에서 자유롭게 SDK 제품을 테스트할 수 있으나, 상용 서비스에 SDK를 적용하기 위해서는 PallyCon 상용 서비스 가입 시 SDK 사용 권한이 포함된 요금제로 신청해야 합니다.

지원 환경

  • 안드로이드 5.0 버전(롤리팝) 이상, AndroidX 라이브러리 사용
  • PallyCon Widevine Android SDK는 내부적으로 구글 Media3 프레임워크를 사용합니다.

동영상 튜토리얼

SDK에 포함된 샘플 프로젝트를 사용하여 DRM 콘텐츠를 재생하는 튜토리얼 영상입니다.

최적의 재생을 위해 화면 품질을 ‘1080p’로 선택하고 자막(한글 또는 영문)을 선택하여 재생하시기 바랍니다.

퀵 스타트

다음과 같은 과정으로 PallyCon Widevine 안드로이드 SDK를 개발 프로젝트에 추가할 수 있습니다.

  1. 다운로드 받은 SDK 파일을 압축해제합니다.

  2. libs 폴더의 PallyconWVMSDK.aar 파일을 작업 중인 프로젝트의 project/module/libs/에 복사합니다.

  3. 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. build.gradle (module)에 다음 사항을 반영합니다.

    android {
        defaultConfig {
            minSdkVersion 21
            targetSdkVersion 33
            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.4.2'
        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. MainActivityPallyConEventListener를 구현합니다. (샘플 소스 참조)

    val pallyConEventListener: PallyConEventListener = object : PallyConEventListener {
        override fun onCompleted(currentUrl: String?) {
            // 다운로드가 완료 되었을때 호출: API Guide 문서를 참고해 주십시오.
        }
    
        override fun onProgress(currentUrl: String?, percent: Float, downloadedBytes: Long) {
            // 다운로드가 시작하고 끝날때까지 호출: API Guide 문서를 참고해 주십시오.
        }
    
        override fun onStopped(currentUrl: String?) {
            // 다운로드가 정지 되었을때 호출: API Guide 문서를 참고해 주십시오.
        }
    
        override fun onRestarting(currentUrl: String?) {
            // 다운로드가 중단된 콘텐츠가 다시 시작 되었을때 호출: API Guide 문서를 참고해 주십시오.
        }
    
        override fun onRemoved(currentUrl: String?) {
            // 다운로드된 콘텐츠가 제거되었을때 호출: API Guide 문서를 참고해 주십시오.
        }
    
        override fun onPaused(currentUrl: String?) {
            // 다운로드 중 pause 되었을 때 호출: API Guide 문서를 참고해 주십시오.
        }
    
        override fun onFailed(currentUrl: String?, e: PallyConException?) {
            // 다운로드 및 라이선스 오류: API Guide 문서를 참고해 주십시오.
        }
    
        override fun onFailed(currentUrl: String?, e: PallyConLicenseServerException?) {
            // 라이선스 오류: API Guide 문서를 참고해 주십시오.
        }
    }
    
  6. 다운로드할 콘텐츠 정보를 넣어 PallyConWvSDK 객체를 생성합니다. PallyCon 콘솔 사이트에서 확인한 Site ID를 설정합니다.

    
    // DRM 관련 정보를 입력한다.
    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 = ContentData(
        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
    )
    
  7. 다운로드할 콘텐츠의 트랙 정보를 가져옵니다.

    // 단말기가 네트워크에 연결되어 있어야 합니다.
    // 트랙정보 획득시 자동으로 라이선스도 다운로드 합니다.
    val trackInfo = wvSDK.getContentTrackInfo()
    
  8. 트랙 정보에서 다운로드할 트랙을 선택합니다.

    // 샘플에선 TrackSelectDialog 를 이용하여 선택합니다.
    trackInfo.video[0].isDownload = true
    trackInfo.audio[0].isDownload = true
    
  9. 콘텐츠가 이미 다운로드되어 있는지 확인 후 다운로드를 실행합니다.

    val state = wvSDK.getDownloadState()
    if (state != COMPLETED) {
        wvSDK.download(trackInfo)
    }
    
  10. 다운로드된 콘텐츠를 재생하려면 다음 API를 사용하여 MediaItem 또는 MediaSource 를 획득합니다.

    // use MediaSource or MediaItem
    val mediaSource = wvSDK.getMediaSource()
    val mediaItem = wvSDK.getMediaItem()
    
  11. PlayerActivity.kt에 다음 개발 가이드를 참고해 플레이어를 구현합니다. http://google.github.io/ExoPlayer/guide.html

    Exoplayer에 관한 자세한 정보는 다음 구글 문서를 참고하시기 바랍니다. https://developer.android.com/guide/topics/media/exoplayer.html

  12. DRM 라이선스의 license durationplayback duration을 확인합니다.

    val drmInfo = wvSDK.getDrmInformation()
    val licenseDuration = drmInfo.licenseDuration
    val playbackDuration = drmInfo.playbackDuration
    
    if (licenseDuration <= 0 || playbackDuration <= 0) {
        // DRM License Expired
    }
    
  13. ExoPlayer를 다음과 같이 설정합니다.

    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)
                }
            })
        }
    

다운로드 서비스 등록

  • 다운로드에 대한 백그라운드 알림을 지원하려면 아래와 같이 다운로드 서비스를 등록합니다.

     // DemoDownloadService 는 advanced 샘플을 확인해 주세요.
     wvSDK.setDownloadService(DemoDownloadService::class.java)
    
  • 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>
    

라이선스 관리

다음과 같이 DRM 라이선스를 다운로드 및 삭제할 수 있습니다.

라이선스 다운로드

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)
})

라이선스 삭제

wvSDK.removeLicense()

기타 가이드

화면 녹화 차단

화면 녹화 앱을 이용한 콘텐츠 유출을 방지하려면, 애플리케이션에 다음 코드를 추가해 캡쳐 기능을 차단해야 합니다.

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

콘텐츠 마이그레이션

widevine sdk 3.0.0 부터는 다운로드 방식이 기존과 달라지기 때문에 기존 widevine sdk 2.x.x 버전대 사용중인 고객은 다운로드 받아져있던 콘텐츠의 마이그레이션이 반드시 필요합니다.

needsMigrateDownloadedContent 함수를 이용하여 해당 콘텐츠가 마이그레이션이 필요한지 여부를 확인할 수 있습니다. 마이그레이션 함수는 내부에서 마이그레이션 콘텐츠가 있을 경우에만 동작하기 때문에 여러번 호출하여도 문제가 되지 않으며, 함수의 파라미터값들은 기존 2.x.x 버전에서 사용된 값으로 동일하게 설정해야 합니다.

PallyConData 객체 생성시 사용된 localPath 는 기존의 다운로드 받아진 콘텐츠들의 상위 디렉토리로 설정되면 안됩니다. 따라서 MigrationLocalPathException 예외가 발생될 경우 PallyConData 객체 생성시 사용된 localPath 값을 수정해야 정상 동작 합니다.

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)
}

만약 마이그레이션이 성공할 경우 2.x.x 버전대 db 는 아래코드처럼 사용자가 직접 삭제 할 수 있습니다.

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 레퍼런스

API별 상세 설명은 SDK zip 파일 내의 doc/index.html 파일을 참고하여 주시기 바랍니다.

이전
다음