콘텐츠로 이동

Widevine Android SDK 가이드

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

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

  • Android 6.0 (API level 23) 이상
  • 이 SDK는 Gradle 8.12.2, Android Studio Chipmunk에서 테스트되었으며 시뮬레이터에서는 작동하지 않습니다.
  • 이 SDK는 Media3 1.8.0을 지원합니다. (다른 버전은 문의해 주세요)
    • Exoplayer 2.11 이하 버전은 DrWVSDK v1.15.0과 함께 사용해야 합니다.
    • Exoplayer 2.16 이하 버전은 DrWVSDK v2.x.x와 함께 사용해야 합니다.
    • ExoPlayer 2.18.1 이상 버전은 DrWVSDK v4.3.2와 함께 사용해야 합니다.
  • SDK를 사용하여 애플리케이션을 개발하려면 도브러너 콘솔 사이트에 가입하여 Site ID와 Site Key를 발급받아야 합니다.

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

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

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

dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenLocal()
google()
mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
}
// GitHub Packages for Widevine SDK
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/doverunner/widevine-android-sdk")
credentials {
username = "Git hub ID"
password = "password"
}
}
}
}
  • build.gradle (app)에 다음 설정을 적용합니다.
plugins {
id 'kotlin-parcelize'
}
android {
defaultConfig {
minSdkVersion 23
targetSdkVersion 36
multiDexEnabled true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.17.0'
implementation 'com.google.android.material:material:1.13.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2'
implementation 'androidx.appcompat:appcompat:1.7.1'
implementation 'androidx.recyclerview:recyclerview:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
implementation 'androidx.navigation:navigation-fragment-ktx:2.9.3'
implementation 'androidx.navigation:navigation-ui-ktx:2.9.3'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
// Exo
implementation "androidx.media3:media3-exoplayer:1.8.0"
implementation "androidx.media3:media3-ui:1.8.0"
implementation "androidx.media3:media3-exoplayer-dash:1.8.0"
implementation "androidx.media3:media3-exoplayer-hls:1.8.0"
implementation "androidx.media3:media3-exoplayer-rtsp:1.8.0"
implementation "androidx.media3:media3-exoplayer-smoothstreaming:1.8.0"
implementation "androidx.media3:media3-datasource-okhttp:1.8.0"
implementation "androidx.media3:media3-cast:1.8.0"
// Gson
implementation 'com.google.code.gson:gson:2.13.1'
// Secure
implementation "androidx.security:security-crypto-ktx:1.1.0"
}
  • MainActivityWvEventListener를 구현합니다. (샘플 프로젝트 참조)
val wvEventListener: WvEventListener = object : WvEventListener {
override fun onCompleted(contentData: ContentData) {
// 다운로드가 완료 되었을때 호출: API Guide 문서를 참고해 주십시오.
}
override fun onProgress(contentData: ContentData, percent: Float, downloadedBytes: Long) {
// 다운로드가 시작하고 끝날때까지 호출: API Guide 문서를 참고해 주십시오.
}
override fun onStopped(contentData: ContentData) {
// 다운로드가 정지 되었을때 호출: API Guide 문서를 참고해 주십시오.
}
override fun onRestarting(contentData: ContentData) {
// 다운로드가 중단된 콘텐츠가 다시 시작 되었을때 호출: API Guide 문서를 참고해 주십시오.
}
override fun onRemoved(contentData: ContentData) {
// 다운로드된 콘텐츠가 제거되었을때 호출: API Guide 문서를 참고해 주십시오.
}
override fun onPaused(contentData: ContentData) {
// 다운로드 중 pause 되었을 때 호출: API Guide 문서를 참고해 주십시오.
}
override fun onFailed(contentData: ContentData, e: WvException?) {
// 콘텐츠 다운로드 중 오류가 발생하거나 라이선스에서 오류가 발생한 경우 호출: API Guide 문서를 참고해 주십시오.
}
override fun onFailed(contentData: ContentData, e: WvLicenseServerException?) {
// 라이선스 획득 시 서버에서 전송된 오류가 발생한 경우 호출: API Guide 문서를 참고해 주십시오.
}
}
  • 다운로드할 콘텐츠 정보를 넣어 DrWvSDK 객체를 생성합니다. 도브러너 콘솔 사이트에서 확인한 Site ID를 설정합니다. (샘플 프로젝트 참조)
// DRM 관련 정보를 입력한다.
val config = DrmConfigration(
"site id",
"site key", // 알지 못하는 경우 빈 문자열로 설정
"content token",
"custom data",
mutableMapOf(), // custom header
"cookie",
"licenseCipherPath", // 도브러너 라이선스 사이퍼 기능을 사용하여 서버와 통신하려면 true로 설정합니다.
"drmLicenseUrl", // 라이선스 서버 URL이 있는 경우 설정
"uuid" // 알지 못하는 경우 빈 문자열로 설정
)
// localFileUrl: SDK 2.x.x에서 얻은 콘텐츠 URL 또는 외부 스토리지에 저장된 URL
// 버전 2.x.x에서 다운로드 중에 사용된 contentName이 TestRunner_User인 경우 다음과 같이 URL을 설정해야 합니다:
// var file = File(context.getExternalFilesDir(null), "TestRunner_User/stream.mpd")
// val localUrl = "file://${file.absolutePath}"
// localFileUrl = localUrl
val data = ContentData(
contentId = "content id",
url = "content URL",
localFileUrl = "SDK 2.x.x에서 얻은 콘텐츠 URL 또는 외부 스토리지에 저장된 URL",
drmConfig = config,
cookie = null,
httpHeaders = null
)
val wvSDK = DrWvSDK.createWvSDK(
Context, // Context
data
)
DrWvSDK.addWvEventListener(wvEventListener)
  • 다운로드할 콘텐츠의 트랙 정보를 가져옵니다. (샘플 프로젝트 참조)
// 단말기가 네트워크에 연결되어 있어야 합니다.
// 트랙정보 획득시 자동으로 라이선스도 다운로드 합니다.
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()

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.doverunner.advencedsample.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>

콘텐츠 다운로드 여부와 관계없이 라이선스를 다운로드하고 삭제할 수 있습니다.

라이선스 다운로드

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)
// format 파라미터는 로컬 파일이 아닌 경우 입력할 필요가 없습니다.
// format 값이 NULL인 경우 REMOTE CONTENT URL을 통해 SDK 내부에서 자동으로 정의됩니다.
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)

기존 사용자를 위한 마이그레이션

Section titled “기존 사용자를 위한 마이그레이션”

widevine sdk 3.0.0부터는 다운로드 방식이 다르기 때문에 기존 widevine sdk 2.x.x 버전을 사용 중인 고객은 다운로드된 콘텐츠를 마이그레이션해야 합니다. needsMigrateDownloadedContent 함수를 사용하여 콘텐츠가 마이그레이션이 필요한지 확인할 수 있습니다. 마이그레이션 함수는 내부에 마이그레이션 콘텐츠가 있을 때만 작동하므로 여러 번 호출해도 문제가 되지 않으며, 함수의 파라미터 값은 기존 2.x.x 버전에서 사용된 값과 동일하게 설정해야 합니다. ContentData 객체를 생성할 때 사용된 localPath는 기존 다운로드된 콘텐츠의 상위 디렉토리로 설정하면 안 됩니다. 따라서 MigrationLocalPathException 예외가 발생하면 ContentData 객체를 생성할 때 사용된 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 DoveRunner 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: WvException.MigrationException) {
print(e)
} catch (e: WvException.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 DoveRunner service registration
)

각 API의 자세한 설명은 SDK zip 파일의 doc/en/api_reference.html 파일을 참고해 주시기 바랍니다.