Widevine Android SDK 가이드
개요
PallyCon Widevine Android SDK는 안드로이드 OS용 미디어 서비스 앱을 개발할 때 구글의 Widevine Modular DRM을 쉽게 적용할 수 있게 해주는 제품입니다. 본 문서는 SDK에 포함된 라이브러리와 샘플 프로젝트의 사용법에 대해 설명합니다.
Widevine 클라이언트와 연동되는 PallyCon 멀티 DRM 서비스에 대한 설명은 라이선스 토큰 가이드를 참고하시기 바랍니다. SDK 사용과 관련한 기술 문의는 헬프데스크 사이트를 이용해 주시기 바랍니다.
지원 환경
- 안드로이드 5.0 버전(롤리팝) 이상, AndroidX 라이브러리 사용
- PallyCon Widevine Android SDK는 내부적으로 구글 Media3 프레임워크를 사용합니다.
동영상 튜토리얼
SDK에 포함된 샘플 프로젝트를 사용하여 DRM 콘텐츠를 재생하는 튜토리얼 영상입니다.
최적의 재생을 위해 화면 품질을 ‘1080p’로 선택하고 자막(한글 또는 영문)을 선택하여 재생하시기 바랍니다.
퀵 스타트
다음과 같은 과정으로 PallyCon Widevine 안드로이드 SDK를 개발 프로젝트에 추가할 수 있습니다.
-
다운로드 받은 SDK 파일을 압축해제합니다.
-
libs 폴더의
PallyconWVMSDK.aar
파일을 작업 중인 프로젝트의project/module/libs/
에 복사합니다. -
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() } }
-
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" }
-
MainActivity
에PallyConEventListener
를 구현합니다. (샘플 소스 참조)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 문서를 참고해 주십시오. } }
-
다운로드할 콘텐츠 정보를 넣어
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 )
-
다운로드할 콘텐츠의 트랙 정보를 가져옵니다.
// 단말기가 네트워크에 연결되어 있어야 합니다. // 트랙정보 획득시 자동으로 라이선스도 다운로드 합니다. val trackInfo = wvSDK.getContentTrackInfo()
-
트랙 정보에서 다운로드할 트랙을 선택합니다.
// 샘플에선 TrackSelectDialog 를 이용하여 선택합니다. trackInfo.video[0].isDownload = true trackInfo.audio[0].isDownload = true
-
콘텐츠가 이미 다운로드되어 있는지 확인 후 다운로드를 실행합니다.
val state = wvSDK.getDownloadState() if (state != COMPLETED) { wvSDK.download(trackInfo) }
-
다운로드된 콘텐츠를 재생하려면 다음 API를 사용하여
MediaItem
또는MediaSource
를 획득합니다.// use MediaSource or MediaItem val mediaSource = wvSDK.getMediaSource() val mediaItem = wvSDK.getMediaItem()
-
PlayerActivity.kt에 다음 개발 가이드를 참고해 플레이어를 구현합니다. http://google.github.io/ExoPlayer/guide.html
Exoplayer에 관한 자세한 정보는 다음 구글 문서를 참고하시기 바랍니다. https://developer.android.com/guide/topics/media/exoplayer.html
-
DRM 라이선스의
license duration
과playback duration
을 확인합니다.val drmInfo = wvSDK.getDrmInformation() val licenseDuration = drmInfo.licenseDuration val playbackDuration = drmInfo.playbackDuration if (licenseDuration <= 0 || playbackDuration <= 0) { // DRM License Expired }
-
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 파일을 참고하여 주시기 바랍니다.