---
title: "Widevine Android SDK 가이드"
description: "본 문서는 Widevine Android SDK에 포함된 라이브러리와 샘플 프로젝트의 사용법에 대해 설명합니다."
---
> For the complete documentation index, see [llms.txt](/llms.txt).

import YouTubeEmbed from '../../../../../../components/YouTubeEmbed.astro';

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

Widevine 클라이언트와 연동되는 도브러너 멀티 DRM 서비스에 대한 설명은 [라이선스 토큰 가이드](/ko/content-security/multi-drm/license/license-token/)를 참고하시기 바랍니다.
SDK 사용과 관련한 기술 문의는 [헬프데스크](https://support.doverunner.com) 사이트를 이용해 주시기 바랍니다.

:::note 

해당 SDK 제품은 [도브러너 깃허브 저장소](https://github.com/doverunner/widevine-android-sdk)에서 다운로드 받을 수 있습니다. 트라이얼 계정에서는 라이선스 발급 수 제한 내에서 자유롭게 SDK 제품을 테스트할 수 있으나, 상용 서비스에 SDK를 적용하기 위해서는 도브러너 상용 서비스 가입 시 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 콘텐츠를 재생하는 튜토리얼 영상입니다.

<YouTubeEmbed videoId="POAKSZqkpHk" />

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

## 퀵 스타트

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

* SDK는 도브러너 GitHub Packages에서 사용할 수 있습니다.

* GitHub ID와 Git을 사용할 때 비밀번호 대신 사용할 액세스 토큰을 받게 됩니다.
  * https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens

* 샘플의 `settings.gradle` 파일에서 github package 값을 수정합니다.
  * Widevine Android SDK GitHub 저장소를 포함하려면 `settings.gradle` 파일의 `dependencyResolutionManagement` 섹션에 다음 코드를 추가하세요:

```gradle
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)에 다음 설정을 적용합니다.

```gradle
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"
}
```
   
* `MainActivity`에 `WvEventListener`를 구현합니다. (샘플 프로젝트 참조)

```kotlin
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를 설정합니다. (샘플 프로젝트 참조)

```kotlin
// 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)
```

* 다운로드할 콘텐츠의 트랙 정보를 가져옵니다. (샘플 프로젝트 참조)

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

* 트랙 정보에서 다운로드할 트랙을 선택합니다. (샘플 프로젝트 참조)

```kotlin
// 샘플에선 TrackSelectDialog 를 이용하여 선택합니다.
trackInfo.video[0].isDownload = true
trackInfo.audio[0].isDownload = true
```

* 콘텐츠가 이미 다운로드되어 있는지 확인 후 다운로드를 실행합니다. (샘플 프로젝트 참조)

```kotlin
val state = wvSDK.getDownloadState()
if (state != COMPLETED) {
    wvSDK.download(trackInfo)
}
```

* 다운로드된 콘텐츠를 재생하려면 다음 API를 사용하여 `MediaItem` 또는 `MediaSource`를 획득합니다. (샘플 프로젝트 참조)

```kotlin
// use MediaSource or MediaItem
val mediaSource = wvSDK.getMediaSource()
val mediaItem = wvSDK.getMediaItem()
```

* PlayerActivity.java에 다음 개발 가이드를 참고해 플레이어를 구현합니다.
    http://google.github.io/ExoPlayer/guide.html
> Exoplayer에 관한 자세한 정보는 다음 구글 문서를 참고하시기 바랍니다.
> https://developer.android.com/guide/topics/media/exoplayer.html

* DRM 라이선스의 license duration과 playback duration을 확인합니다.

```kotlin
val drmInfo = wvSDK.getDrmInformation()
val licenseDuration = drmInfo.licenseDuration
val playbackDuration = drmInfo.playbackDuration

if (licenseDuration <= 0 || playbackDuration <= 0) {
    // DRM License Expired
}
```

* `ExoPlayer`를 다음과 같이 설정합니다. (샘플 프로젝트 참조)

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

### 다운로드 서비스 등록

고객의 상황에 따라 다운로드 서비스를 등록합니다.

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

```kotlin
// DemoDownloadService 는 advanced 샘플을 확인해 주세요.
wvSDK.setDownloadService(DemoDownloadService::class.java)
```

- `AndroidManifest.xml`에 다운로드 서비스를 등록합니다.

```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>
```

### 라이선스 관리

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

**라이선스 다운로드**

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

**라이선스 삭제**

```kotlin
wvSDK.removeLicense()
```

### 화면 녹화 차단

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

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

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

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

```kotlin
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를 직접 삭제할 수 있습니다.

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

### 도브러너 Widevine SDK API

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