DRM-X 6.0 dove 및 올리브 지점 로고DRM-X 6.0문서
◎ 한국어

Android SDK 및 네이티브 Media3

다운로드 Android 플레이어 in-video controls, drawgable playlists, save shuffle/repeat 및 오프라인 이력서. 사용하여 보호 된 재생 통합 DRM-X 또는 네이티브 Media3.

Markdown 보기실행 예제업데이트 2026-09-14
플레이어을 선택하십시오. 1 라이센스 서비스를 유지하십시오.DRM-X 6.0 Android SDK 가이드 통합을 위해 또는 네이티브 Media3 플레이어을 유지하십시오. 둘 다 인증 된 백엔드 및 Widevine에 연결됩니다. Android SDK은 스트리밍 및 지속 가능한 오프라인 VOD을 지원하며, 전화, 태블릿, TV 및 내장 된 Android 시스템을 포함하여 호환되는 API 23 + 장치에서 작동합니다.

Android 플레이어 #를 사용해보십시오.

다운로드, 설치, 프레스 플레이.무료 평가 앱은 공개 데모 영상을 사용하고 자동으로 로그인합니다. 체험하려고 APK를 빌드하거나 DRM-X 인증 정보를 입력할 필요가 없습니다.

다운로드 Android 샘플 APK

1.0.0-preview.12 · 7.02 MB · Android 6.0+ (API 23+) · DRM-X 6.0 Player · 업데이트 2026-09-14

  1. Android 전화에서이 페이지를 열고 APK를 다운로드하십시오.
  2. 다운로드 된 파일을 엽니 다. Android 요청하면 브라우저 또는 파일 관리자에서 설치를 허용 한 다음 Install을 탭합니다.
  3. Open DRM-X 6.0 Player, 연결 유지 및 비디오의 Play 버튼을 탭합니다. 이 응용 프로그램은 재생을 누르면 스트리밍 라이센스를 요청합니다.
  4. Settings 을 클릭하여 품질, 오디오, 캡션 또는 재생 속도를 선택할 수 있습니다. 전체 화면에서 계속 전화를 회전하십시오.
  5. 제품정보 지원하다 현재 영상 또는 다운로드 playlist 모든 것 VOD 품목. 다운로드는 1080p에 가장 높은 지원되는 면허증 전통을 이용합니다 L1, 또는 480p에 L3. 앱 다운로드 다음 / 이전, shuffle, 반복 및 autoplay와 함께 완료 된 로컬 재생 목록을 엽니 다. 사이트 맵 전체 재생 목록을 반환합니다. 이름 * 선택된 라이선스를 갱신합니다.

Already queued media downloadsContinue in the background. If Android stop the app while prepare licenses or tracks, reopen it while connected to finish 준비 잔여 항목. 기존의 다운로드가 유지됩니다. 오프라인 재생은 캐시 된 미디어 만 사용합니다. 완전하거나 만료 된 다운로드는 완료 또는 갱신이 필요합니다.

Widevine 프로비저닝 실패 (Media3 6002) 이제 공식 Google 서비스에 정통 데모 서버 릴레이를 사용합니다. L3 CDM 함수는 SD 키가 수신될 수 있습니다. 작업하지 않은 장치 Widevine 구현은 장치 / firmware 수정이 필요합니다. 라이센스는 누락 된 DRM 지원을 추가 할 수 없습니다.

샘플 APK은 공개 및 평가에 서명합니다. AAR 및 전체 SDK은 해당 콘솔 계정에서 사용할 수 있습니다. 자신의 생산 응용 프로그램을 유지 키와 백엔드 로그인이 데모에서 분리.

APK 체크섬 및 USB 설치

SHA-256:

8fa044b82c0abab7ce2a987f349524fd7ad1e78f3c897771f4764cb2aa521180

Android 플랫폼 도구 및 허가 된 USB 장치 :

adb install -r drmx-android-player-1.0.0-preview.12.apk

이 APK을 제거하지 않고 기존 공식 샘플에 설치하십시오. 동일한 서명 키 및 응용 프로그램 ID는 다운로드, 오프라인 라이센스, 저장된 위치 및 선호도를 보존합니다. 자신의 빌드가 다른 서명 키를 사용하여 별도의 응용 프로그램을 사용하십시오. ID 앱을 유지하고 샘플의 오프라인 라이센스를 보존합니다.

Android 플레이어 미리보기.12#

출시 14 9월 2026. 수정은 Widevine 라이센스가 만료되거나 1 분 이하 남아있을 때 예상치 못한 라이센스 요청을 만드는 재생을 수정합니다. 이것은 Media3 오류 6004, 연결되는 동안 HTTP 오류 또는 분리 된 동안 DNS 오류로 나타날 수 있습니다.

더 많은 정보 플레이어 이제 로컬로 다운로드 된 라이센스를 복원합니다. 유효 라이센스는 나머지 재생 시간을 유지; 만료 된 라이센스 쇼 연결 및 탭 Renew. 면허 유효성 및 보호 규칙은 장치에 의해 실행됩니다. 진단은 또한 수치를 포함합니다 HTTP 네트워크 작업이 실패할 때, 요청 헤더, URL 또는 라이센스 응답없이.

  1. 기존 공식 앱에서 미리보기.12을 설치합니다. 현재 앱 데이터를 유지하고 다운로드하십시오.
  2. WayMaker 또는 또 다른 다운로드 된 비디오가 갱신되면 인터넷에 연결하여 비디오 및 탭을 선택하십시오. Renew.
  3. License 갱신을 기다리십시오. 오프라인 재생을 준비하십시오. , 다음을 선택하십시오. 오프라인 재생 . 암호화 된 비디오는 다운로드 남아 있습니다.

이 릴리스는 미리보기를 유지합니다.11의 제어 및 재생 목록 기능. 공개 평가 앱과 그 임베디드 플레이어 미리보기를 확인합니다.12; 별도의 배포 콘솔 SDK 미리보기 남아.10 / 번들 20260910.8.

Android 플레이어 미리보기.11#

출시 14 9월 2026. 이 평가 응용 프로그램은 최신 플레이어 컨트롤 및 재생 목록 개선을 포함, 우리의 Android 및 애플 플레이어에 걸쳐 일관된 상호 작용 스타일.

  • 비디오 내부 제어: 중앙 Play/Pause, 이전과 다음 멀티 비디오 재생 목록, CC, 설정 및 전체 화면 버튼. 제어는 두 초 후 숨을 수 있습니다. 동영상을 다시 표시하십시오.
  • Captions and playback settings: use CC to show or hide available subtitles, and Settings to choose caption language, audio, playback speed and allowed quality. 오프라인 선택은 다운로드에 포함 된 트랙 만 사용합니다.
  • 주문을 취소하려면 드래그 : 재생 목록 아이템의 핸들을 드래그하여 위치를 변경하십시오. 저장된 주문은 앱 재시작을 생존하고 다운로드 된 재생 목록에 반영됩니다. 주문은 현재 비디오를 재생합니다. 액세스 가능한 이동 / 이동을위한 핸들을 탭하십시오.
  • Sved shuffle and repeat: 선택된 모드는 온라인 및 오프라인 재생에서 강조되고 살아남을 수 있습니다. 셔플과 반복으로, 동영상은 저장된 순서를 따라 최종 항목 후 중지합니다. 재생 목록 메뉴의 "다음"작동이 제거되었습니다.
  • Resume where you stop: 각 비디오의 저장된 VOD 위치는 스트리밍 또는 다운로드 재생으로 돌아갈 때 복원됩니다. 진행은 현재 시청자에게이 장치에 저장됩니다; 장치간에 동기화되지 않습니다.

플레이어 변경은 재생 중에 재생 목록 드래그를 포함하여 삼성 S24 울트라에 확인되었습니다, 다시 시작 후 저장 주문, 재생 이력서 및 다운로드 재생 Wi-Fi 및 모바일 데이터가 비활성화. 오프라인보기는 여전히 완료된 다운로드 및 사용 가능한, 비유효한 오프라인 라이센스가 필요합니다.

이 릴리스는 공개 평가 APK을 업데이트합니다. 별도의 배포 콘솔 SDK은 1.0.0-preview.10, 번들 20260910.8을 유지; 그 아래에 기존의 통합 지침은 번들 참조.

1. 통합 경로 선택#

Console → Integrations에서 Android SDK ZIP 또는 AAR을 다운로드하세요. 활성 Professional($499/월) 및 Enterprise 조직에서 다운로드할 수 있습니다. Trial 및 Standard 계정은 SDK를 다운로드할 수 없습니다. 네이티브 Media3 예제를 포함한 이 문서는 공개되어 있지만, 재생에는 시청 권한이 있는 계정, 게시된 콘텐츠 및 호환 기기가 필요합니다.

Current preview: 1.0.0-preview.10 · customer bundle 20260910.8 · Media3 1.11.0. The ZIP contains a local Maven repository, Gradle wrapper, annotated README, phone/TV/native samples, an installable phone sample APK in apks/, and offline integration notes. It contains no customer credentials or protected movies.

2. 내용을 준비하고 백엔드#

  1. 패키지 및 Widevine 호환 암호화 된 DASH DRM-X 환경의 제목을 게시합니다. 정확한 를 복사 Content ID .
  2. 서버의 Environment ID, Site Key 및 Access Key 구성. 사용 다운로드 PHP 통합 작업 시작 지점에 대 한.
  3. 사용자 로그인 및 제목 확인을 구현하십시오. 앱은 짧은 라이브 사용자 토큰으로 귀하의 백엔드를 호출합니다. 백엔드는 콘텐츠, 정책 및 신뢰할 수있는 URL을 선택합니다.
  4. 물리적 Widevine-capable 장치에 온라인 재생을 실행하십시오. 스트리밍 계약이 작동 한 후 오프라인 구매 만 추가하십시오.
Android 앱은 서버 전용 인증 정보을 사용하여 DRM-X 세션을 요청하는 고객 백엔드에 인증합니다. 플레이어는 별도로 암호화 된 미디어를 다운로드하고 라이센스 서비스에 대한 Widevine 도전을 보냅니다.
미디어 배송 및 라이센스 배송은 별도입니다. 고객 백엔드가 DRM-X 서버 인증 정보를 보유하고 있습니다.
이 값 변경그것은 어디에 속한다이름 *
세션EndpointAndroid라이선스 서버의 직접 URL이 아닌, 자체 HTTPS 재생 세션 엔드포인트입니다.
오프라인 종료점Android, 오프라인 전용당신의 취득/renew/release 허가 엔드포인트.
내용IdAndroid 요청; 백엔드 검증출판물 보호 Content ID 뷰어는 액세스 할 수 있습니다.
고객 액세스 토큰로그인 후 앱 메모리짧은 라이브 사용자 토큰, Site Key 또는 Access Key이 없습니다.
Environment ID, Site Key, Access KeyServer secret 설정만DRM-X 계정 인증 정보. APK, HTML 또는 소스 컨트롤에서 절대 발송하지 마십시오.
정책 및 신뢰할 수있는 표시 URL당신의 backend사업 제목 및 패키지 매핑. 앱에서 이러한 것을 허용하지 마십시오.

3. 설치 및 실행 SDK#

ZIP의 루트 폴더를 Android Studio에서 엽니다. JDK 17, Android SDK 36 및 Android Gradle Plugin 9.2.0을 사용하십시오. 포함 된 래퍼는 Gradle 9.4.1을 사용합니다. 최소 장치 API은 23입니다. 의존성을 구축하면 인터넷 액세스가 필요합니다. samples.phone을 선택하고 실행하십시오. 초기 값은 공개 데모 백엔드 및 타이틀을 사용합니다. 로그인 및 스트리밍 컨트롤을 따라 자신의 백엔드로 전환하기 전에.

# Windows
gradlew.bat :samples:phone:assembleDebug
# macOS / Linux
sh gradlew :samples:phone:assembleDebug

기존 앱의 경우, ZIP의 maven/ 폴더를 프로젝트 루트로 복사합니다. settings.gradle.kts:

dependencyResolutionManagement {
    repositories {
        maven { url = uri(rootDir.resolve("maven")) }
        google()
        mavenCentral()
    }
}

앱 모듈의 build.gradle.kts에서:

dependencies {
    implementation("com.haihaisoft.drmx:drmx-universal-player:1.0.0-preview.10")
}
독립형 설치 AAR 대신

앱 모듈의 libs/에서 AAR을 넣으십시오. 플랫 AAR에는 자동 변속성 해상도가 없으며 세 개의 일치 Media3 의존도를 추가합니다. 두 가지 형태를 설치하지 마십시오.

implementation(files("libs/drmx-universal-player-1.0.0-preview.10.aar"))
implementation("androidx.media3:media3-exoplayer:1.11.0")
implementation("androidx.media3:media3-exoplayer-dash:1.11.0")
implementation("androidx.media3:media3-ui:1.11.0")

AndroidManifest.xml에서 인터넷 액세스를 허용하고이 최소 샘플에 대한 백업을 비활성화 할 수 있습니다. 백업을 필요로하는 생산 응용 프로그램에서, 특히 대신 장치 반행 오프라인 라이센스 메타 데이터를 제외합니다.

<uses-permission android:name="android.permission.INTERNET" />
<application android:allowBackup="false" android:usesCleartextTraffic="false" ... >

drmx.sessionEndpoint, drmx.offlineEndpoint, drmx.contentIdcustomer.loginEndpoint에 대한 전화 샘플의 MainActivity.kt을 검색하십시오. 표시된 fallback 설정을 변경하거나 로그인 후 응용 프로그램에서 Intent extras를 전달하십시오. 실행시 customer.accessToken를 통과하십시오. ZIP README는 각 매개 변수를 설명하고 교체 할 수있는 데모 후크가 설명합니다.

AAR#를 사용하여 테스트 APK를 빌드하십시오.

이 예제는 전체 SDK ZIP에서 실행 가능한 전화 프로젝트를 사용하고 명시적으로 독립 AAR을 링크합니다. 동일한 접근법은 기존 Android 앱에서 작동합니다.

  1. full Android SDK ZIP 다운로드 및 추출. Android Studio에서 루트 폴더를 엽니다.
  2. Keep libs/drmx-universal-player-1.0.0-preview.10.aar in the extracted root. Use JDK 17, Android SDK 36 and the included Gradle wrapper.
  3. samples/phone/build.gradle.kts에서 DRM-X dependencies 내의 항목에 대한 Maven 의존성을 대체합니다. 그 파일을 구축하는 나머지를 유지하십시오.
implementation(files(rootProject.file("libs/drmx-universal-player-1.0.0-preview.10.aar")))
implementation("androidx.media3:media3-exoplayer:1.11.0")
implementation("androidx.media3:media3-exoplayer-dash:1.11.0")
implementation("androidx.media3:media3-ui:1.11.0")

implementation("com.haihaisoft.drmx:drmx-universal-player:1.0.0-preview.10")을 제거하기 때문에 같은 SDK는 두 번 포함되지 않습니다. 평평한 AAR는 위의 세 가지 Media3 의존성을 필요로합니다.

  1. 자신의 테스트 빌드와 함께 설치된 다운로드 된 데모를 유지하려면 전화 모듈의 applicationIdcom.example.drmxtest로 변경하십시오. 이 공급 된 샘플에 대한 네임스페이스를 변경하십시오.
  2. APK 을 작성한 다음 USB-connected Android 디바이스에 USB 을 디버깅할 수 있습니다.
# Windows, from the extracted SDK root
gradlew.bat :samples:phone:assembleDebug
adb install -r samples/phone/build/outputs/apk/debug/phone-debug.apk

# macOS / Linux
sh gradlew :samples:phone:assembleDebug
adb install -r samples/phone/build/outputs/apk/debug/phone-debug.apk

테스트 앱을 열고 재생을 탭합니다. 공급된 전화 소스는 동시에 동일한 공개 데모를 사용합니다. 자신의 제목을 테스트하려면 표시된 세션/오프라인/로그인 엔드포인트를 교체하고 게시하십시오. Content ID 내 계정 MainActivity.kt, 런타임에 자신의 사용자의 부족한 인증을 공급합니다. 서버가 유지 Site Key 이름 * Access Key.

컴팩트 컨트롤은 AAR에서 DrmXPlayerView에서 제공됩니다. 공급된 활동은 Play-to-authorize, 응답 회전, 다운로드 소유권, 오프라인 재생 및 갱신을 보여줍니다. 제어 통합 예제을 참조하십시오.

4. 서버 요청 및 기본 정책#

Android 앱은 재생 세션을 위해 백엔드를 요청합니다. 백엔드가 사용자를 인증하고 자막, 맵 Content ID을 정의하여 미디어를 게시하고 DRM-X을 호출합니다. Playback Session API을 참조하십시오.

{
  "contentId": "YOUR-PUBLISHED-CONTENT-ID",
  "contentType": "vod",
  "drmSystem": "widevine",
  "playbackMode": "streaming",
  "platformCapabilities": {
    "clientPlatform": "android",
    "drmSystems": ["widevine"], "manifestTypes": ["dash"],
    "maximumHeight": 1080, "secureDecoder": false, "persistentState": false
  }
}

위의 보수적 인 기능은 assert Widevine L1을 주장하지 않습니다. SDK의 기능 도우미 또는 실제 기기에 대한 자신의 장치 평가를 사용하십시오. 클라이언트 기능 지원 호환성 선택; 그들은 결코 품질 제목을 부여하지 않습니다.

PHP 표본은 온라인 재생을 위한 multi-tier-standard에 과태합니다. 패키지가 하나의 키로 사용할 때 문서화 된 단일 키 정책을 선택합니다. 귀하의 정책은 패키지 키 모델과 뷰어의 권리를 일치해야합니다. 서버 구성 또는 DRMX_LICENSE_POLICY_TEMPLATE 환경 설정 변경, 플레이어의 쿼리 매개 변수가 아닙니다. license 정책을 참조하십시오.

오프라인은 자체 지속적 정책을 필요로 합니다.설치하기 playback_policy.persistent 서버 조직 정책에서 true로하고 라이센스, 대여, 재생 및 갱신 기간을 선택하십시오. 참고는 7 일 / 3 일 / 48 시간 / 7 일 각각을 사용합니다. 예를 들어 비즈니스 규칙, 스튜디오 승인 또는 플랫폼 전체 기본이 아닙니다. 일치하는 메타 데이터를 반환합니다. Widevine 라이센스는 권한이 유지됩니다.

5. 경로 A: DrmXPlayer#를 통해 플레이

활동 내에서 PlayerView을 만들고 로그인 후 안전한 창을 사용하여 prepare을 호출합니다. 래퍼는 세션을 교환하고 Widevine을 구성하며, 리턴된 최대 높이를 적용하고 중지될 때 스트리밍 예약을 해제합니다. 가져 오기 com.haihaisoft.drmx.player 및 AndroidX Media3에서 가져옵니다.

val view = PlayerView(this)
setContentView(view)
DrmXPlayer.protectWindow(window)
val player = DrmXPlayer(this, view)
val capabilities = WidevineCapabilities.playbackCapabilities(
    clientPlatform = "android",
    displayMetrics = resources.displayMetrics,
    applicationVersion = "1.0",
)
player.prepare(
    endpoint = "https://customer.example/api/drmx/playback-session",
    contentId = "YOUR-PUBLISHED-CONTENT-ID",
    contentType = "vod", // use live only for a published live title
    capabilities = capabilities,
    customerAuthenticationHeaders = mapOf("Authorization" to "Bearer $userToken"),
)
// Keep player as an Activity/Fragment-owned field in your actual application.
// Stop when your playback lifecycle ends; close when the owner is destroyed.
// player.stop()
// player.close()

DrmXPlayerListener 등록, 준비, 재생 및 오류 UI. 저장 라이센스 토큰 또는 응답 기관보다 안전한 오류 코드를 표시하십시오. 전체 수명주기, 풀 스크린 컨트롤 및 다운로드 큐에 대한 포함 된 전화 샘플을 사용하십시오.

플레이어 제어 및 무정전 회전 #

Play 에 비디오에서 권한 부여를 시작합니다. 화면 또는 설정 열기는 스트리밍 라이센스를 취득하지 않습니다. 일시 중지 / 이력서, 설정 변경 및 휴대 전화를 회전은 현재 세션을 보존합니다.

DRM-X Android 플레이어 삼성 S24 울트라 설정, 품질, 오디오 트랙, 자막 / CC, 재생 속도 및 수면 타이머를 보여주는.
Samsung S24 Ultra의 실제 설정. 사용할 수있는 옵션을 볼 수있는 행을 탭; 오디오 submenu는 전체 언어 이름을 보여줍니다. 사용 가능한 품질과 언어는 내용, 라이센스 및 장치에 따라 다릅니다.
(주)어떤 고객이 할 수 있습니다
품질 관리매니페스트에서 지원되고 라이선스가 허용하는 해상도 또는 Auto를 선택하세요. 사용할 수 없는 옵션에는 라이선스나 기기/DRM 제한 사유가 표시됩니다. Auto에도 동일한 제한이 적용됩니다.
오디오 트랙패키지에 의해 제공된 지원되는 오디오 언어를 선택하십시오.
제목/CC캡션 언어 또는 오프를 선택하십시오. CC 버튼은 지원된 캡션이 존재하고 직접 견인할 때 나타납니다.
Caption 외관글꼴, 텍스트 색상 및 크기, 배경 / 창 색상 및 불투명을 조정합니다. 설정은 장치에 저장됩니다.
재생 속도 / 수면 타이머선택된 간격 후에 0.25–2× 속도 또는 일시적으로 재생하십시오.
Seek / 전체 화면타임 라인, 10 초, 또는 두 배 비누를 찾습니다. 재생 재생을 재시작하지 않고 풍경에 회전합니다.

아래 세 가지 컴팩트 한 작업은 Download ( 오프라인 사용), 오프라인 Renew ( 오프라인 라이센스)입니다. 샘플은 허용된 비디오 번역 및 지원되는 오디오 / 자막 트랙을 다운로드합니다. 오프라인 메뉴는 다운로드 된 미디어 만 노출됩니다. 갱신은 연결이 필요합니다.

본문 바로가기 텍스트로 정보 발러 사본 최근 오류 외에도 스크린 샷없이 세부 사항을 지원할 수 있습니다. 보고서에는 보고서가 포함되어 있습니다. ID, 가동, SDK/Android/device 버전, 숫자 CDM/Media3 오류, 선택된 스택 프레임, 연결/download/playback 상태 및 반복 카운트. 그들은 제외 인증 정보, 미디어 URL, 키, 키 세트 ID, 라이센스 데이터 및 전체 CDM 덤프.

샘플은 5 시간 동안 최대 32 로컬 보고서 (64 KB)를 유지합니다. 연결 및 서명하면 구성 된 고객 백엔드에 오류를 업로드하고 30 초 당 한 번에 한 번에. 공공 데모 서버는 5 시간 동안 경고 / 오류를 유지하고 반복 된 이벤트를 결합하여 512 KB 및 prunes의 파일을 매 분마다 캡합니다. 루틴 진행 및 성공적인 재생은 기록되지 않습니다. 사용자 정의 통합은 명시적으로 자신의 동일한 시작 오류를 공급해야합니다. drmx.diagnosticsEndpoint는 진단하지 않습니다.

Offline 라이센스는 이미 사용 중입니까?Preview.5는 S24의 경우를 수정합니다. CDM 368 와 "다른 세션에서로드 된 라이센스". 재생을 오프라인으로 다시 다시 다시 시작 기존 플레이어. 갱신 첫 번째 릴리스는 DRM 세션을 릴리스 한 다음 저장 된 라이센스를 갱신합니다. 다운로드 및 영구 라이센스는 보존되지 않습니다. 앱 데이터 재설정이 필요하지 않습니다.
val player = DrmXPlayer(this)
val controls = DrmXPlayerView(this)
controls.bind(player) // Does not acquire a session or license.
controls.onPlayRequested = {
    player.prepare(endpoint = sessionEndpoint, contentId = contentId,
        capabilities = capabilities, customerAuthenticationHeaders = userHeaders)
}
controls.onFullScreenRequested = { /* resize the existing player view */ }
player.listener = object : DrmXPlayerListener {
    override fun onAuthorizing(contentId: String) { controls.setPreparing(true) }
    override fun onReady(info: DrmXPlaybackInfo) { controls.setPreparing(false) }
    override fun onError(error: PlaybackSessionException) { controls.setPreparing(false) }
}
// Keep protectWindow(window) and the secure video surface.
// At final owner destruction: controls.unbind(); player.close()

공급된 전화는 오리엔테이션과 스크린 크기 변화를 취급합니다; 그것의 onConfigurationChanged 동일한 활동의 크기를 조정하고 플레이어. 그것을 적응할 때 두 부품을 지키십시오. 활동을 재구성하는 주인은 그것의 재생 소유자를 유지하고 전망을 다시 놓아야 합니다. 과정 죽음은 교체에서 다르고 정상적인 국가 회복을 필요로 합니다. 4K/8K 선택권은 일치 내용, 면허 entitlement 및 장치/를 요구합니다DRM 지원; CDM 권한이 있습니다.

맞춤 플레이어를 위해, 중지 및 같은 지속 가능한 라이센스를 검사, 갱신 또는 해제하기 전에 활성 오프라인 DRM 세션을 해제합니다. SDK의 player.stopForOfflineLicenseOperation { /* call manager here */ }은 Media3 playback queue를 기다립니다. 보통 stop()는 비동기적으로 작동한다. 동일한 라이센스가 재생되는 동안 별도의 checkLicense()를 수행하지 마십시오. 재생 자체는 CDM 상태를 검증합니다. 공급된 표본은 갱신 도중 완전한 handoff와 disables overlapping 활동을 보여줍니다.

6. 라이센스를 취득하고 media#을 다운로드하십시오.

연결된 동안, persistent Widevine 라이센스를 취득하고 선택한 암호화 된 트랙을 다운로드하십시오. 유효한 라이센스 플러스 전체 미디어는 오프라인 읽기 재생을 생산합니다. 분리 된 동안 개인 캐시를 읽고 로컬 CDM 라이센스를 복원하십시오.
Ready 오프라인 = 완전한 암호화 된 미디어 + 유효한 장치-바운드 라이센스. Neither 하나 다른 대체.
val offlineManager = DrmXOfflineManager(applicationContext)
val capabilities = WidevineCapabilities.offlinePlaybackCapabilities(
    clientPlatform = "android", // android-tv or android-automotive where applicable
    displayMetrics = resources.displayMetrics,
    applicationVersion = "1.0",
)
offlineManager.acquireLicense(
    endpoint = offlineEndpoint,
    contentId = contentId,
    capabilities = capabilities,
    customerAuthenticationHeaders = customerHeaders,
) { result ->
    result.onSuccess { stored ->
        // Next: select tracks <= stored.maximumHeight and queue their download.
        // Acquisition alone does not download the video.
    }.onFailure { /* show a safe retry/error state */ }
}

SDK는 opaque Widevine key-set ID 및 non-secret metadata를 저장합니다. CDM는 라이센스를 보유합니다. 그것은 persist DRM License Tokens, raw keys, challenge or license response 바이트가 없습니다. 백업에서 com.haihaisoft.drmx.offline.licenses.v1.xml를 제외하십시오.

하나의 응용 프로그램 소유 Media3 SimpleCache, 다운로드 관리자 및 데이터베이스. 사용 DownloadHelper 라이센스 MediaItem 및 선택 비디오에서 maximumHeight. 에서 Media3 1.11 준비된 콜백은 onPrepared(helper, tracksInfoUpdated). 전화 샘플의 enqueueDownloadOfflineTestRuntime 실행 가능한 구현을 보여줍니다.

// Inside DownloadHelper's prepared callback, after selecting licensed video:
helper.addAudioLanguagesToSelection("en", "zh-Hans", "zh")
helper.addTextLanguagesToSelection(true, "en", "zh-Hans", "zh")
val selected = helper.getDownloadRequest(offlineLicenseId, byteArrayOf())
val request = offlineManager.createDownloadRequest(contentId, selected.streamKeys)
downloadManager.addDownload(request)
helper.release()

오프라인 노출을 원하는 모든 언어를 다운로드하십시오. 스트림 키가 모든 트랙을 다운로드하지 않고 createDownloadRequest을 호출하면 일반적으로 적합한 비디오를 낭비합니다. 다운로드 진행 상황을 표시하고 Download.STATE_COMPLETED을 기다립니다. 생산 배경 전송을 위해 Media3 DownloadService, 저장 제한 및 Android 전장 서비스 / 알림 요구 사항을 통합하십시오. 데모 큐는 처리됩니다.

Multi-key 오프라인 콘텐츠:Media3은 재생 항목에 대한 하나의 키 세트 ID을 복원합니다. 그것의 영구 라이센스는 선택한 비디오, 오디오 및 기타 암호화 된 트랙에 필요한 모든 키를 포함해야합니다. 실제 패키지 및 서버 정책으로 이것을 테스트하십시오. 여러 세션을 사용하면 독립적 인 오프라인 라이센스를 결합하지 않습니다.

7. Path B: 기존 Media3 플레이어#으로 복원

완성 된 다운로드를 읽고 정확한 스트림 키를 사용합니다. 다운로드 및 재생 스택은 동일한 캐시 위치와 키 매핑을 공유해야합니다. 이 예는 엄격한 오프라인 재생을위한 상류 액세스를 비활성화합니다.

val cacheOnly = CacheDataSource.Factory()
    .setCache(downloadCache)
    .setUpstreamDataSourceFactory(null)
val mediaSourceFactory = DefaultMediaSourceFactory(this)
    .setDataSourceFactory(cacheOnly)
val nativePlayer = ExoPlayer.Builder(this)
    .setMediaSourceFactory(mediaSourceFactory)
    .build()
val completed = downloadIndex.getDownload(offlineLicenseId)
require(completed?.state == Download.STATE_COMPLETED)
val playback = offlineManager.createPlayback(contentId, streamKeys = completed!!.request.streamKeys)
nativePlayer.setMediaItem(playback.mediaItem)
nativePlayer.prepare()
nativePlayer.play()
// Release nativePlayer when the owner is destroyed.

Path A와 동일한 오프라인 흐름을 사용하려면 DrmXPlayermediaSourceFactory을 구성하고 player.prepareOffline(playback)을 호출합니다. 정규식 복원은 새로운 것을 요청하지 않고 로컬 CDM 라이센스를 사용합니다. 누락 된 캐시 입력은 인터넷에 조용히 전환하지 않는 한 오류를 생성해야합니다.

8. 경로 C: 네이티브 Media3, 아니 DRM-X AAR #

DrmXPlayer 또는 DrmXOfflineManager를 사용할 필요가 없습니다. 단계 3에서 세 개의 Media3 의존성을 추가하고 동일한 고객 백엔드 계약을 실행하십시오. session.manifestUrl (not session.manifest.url)을 사용하여 반품 된 라이센스 URL 및 바인딩 된 권한 헤더. 스톱, 교체, 종료, 실패 및 파괴에 대한 수신 된 최대 높이 및 처리 스트리밍 세션 릴리스를 적용하십시오.

아래 전체 네이티브 활동은 samples:native이 SDK 릴리스와 함께 컴파일됩니다. 그것은 HTTPS을 처리, 경계 응답 읽기, 배경 권한 부여, stale 응답 및 예약 릴리스를 읽습니다. 표시된 엔드 포인트 / Content ID을 대체하고 자신의 로그인 토큰을 공급합니다. 예는 시작점입니다; 생산 새로 고침, 계정 스위치 및 앱 탐색 수명주기를 추가하십시오.

기본 스트리밍 활동 완료 (Kotlin, no DRM-X 의존성)
package com.haihaisoft.drmx.samples.nativeplayer

import android.app.Activity
import android.os.Bundle
import android.view.WindowManager
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
import org.json.JSONObject
import java.net.HttpURLConnection
import java.net.URI
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicBoolean

/** Complete native streaming example. No DRM-X AAR or server credentials. */
@UnstableApi
class MainActivity : Activity() {
    private lateinit var player: ExoPlayer
    private lateinit var status: TextView
    private val worker = Executors.newSingleThreadExecutor()
    private val closed = AtomicBoolean(false)
    private var activeSession: JSONObject? = null
    private var requesting = false
    private var generation = 0
    // CUSTOMIZE: use YOUR authenticated customer backend and Published Content ID.
    // The sample can receive these after your application's login via Intent extras.
    // Never replace the app login token with a DRM-X Site Key or Access Key.
    private val endpoint get() = intent.getStringExtra("drmx.sessionEndpoint")
        ?: "https://customer.example/api/drmx/playback-session"
    private val contentId get() = intent.getStringExtra("drmx.contentId") ?: "YOUR-PUBLISHED-CONTENT-ID"
    private val customerToken get() = intent.getStringExtra("customer.accessToken").orEmpty()

    override fun onCreate(state: Bundle?) {
        super.onCreate(state)
        window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
        val root = LinearLayout(this).apply { orientation = LinearLayout.VERTICAL; fitsSystemWindows = true; setPadding(24, 24, 24, 24) }
        status = TextView(this).apply { text = "Native Media3 · connect your backend, then Play"; textSize = 16f }
        val view = PlayerView(this)
        player = ExoPlayer.Builder(this).build(); view.player = player
        root.addView(status)
        root.addView(view, LinearLayout.LayoutParams(-1, 0, 1f))
        root.addView(Button(this).apply { text = "Play protected video"; setOnClickListener { startPlayback() } })
        root.addView(Button(this).apply { text = "Stop & release session"; setOnClickListener { stopPlayback() } })
        setContentView(root)
        player.addListener(object : Player.Listener {
            override fun onPlayerError(error: PlaybackException) { status.text = "Playback error: ${error.errorCodeName}"; releaseSession() }
            override fun onPlaybackStateChanged(state: Int) { if (state == Player.STATE_ENDED) releaseSession() }
        })
    }

    private fun startPlayback() {
        if (requesting) return
        stopPlayback(); requesting = true; status.text = "Authorizing…"
        val attempt = generation
        val request = JSONObject().put("contentId", contentId).put("contentType", "vod")
            .put("drmSystem", "widevine").put("playbackMode", "streaming")
            .put("platformCapabilities", JSONObject().put("clientPlatform", "android")
                .put("drmSystems", org.json.JSONArray(listOf("widevine")))
                .put("manifestTypes", org.json.JSONArray(listOf("dash")))
                .put("maximumHeight", 1080).put("secureDecoder", false).put("persistentState", false))
        val headers = if (customerToken.isBlank()) emptyMap() else mapOf("Authorization" to "Bearer $customerToken")
        worker.execute {
            val result = runCatching { JSONObject(post(endpoint, request.toString(), headers)) }
            runOnUiThread {
                requesting = false
                result.onSuccess { session ->
                    if (closed.get() || attempt != generation) { workerRelease(session); return@onSuccess }
                    // Keep release information even if contract validation fails.
                    activeSession = session
                    runCatching {
                        require(session.getInt("contractVersion") == 1 && session.getString("contentId") == contentId)
                        require(session.getString("drmSystem") == "widevine" && session.getString("playbackMode") == "streaming")
                        val drm = session.getJSONObject("drm")
                        require(drm.getString("system") == "widevine")
                        val maximumHeight = session.getJSONObject("policy").getInt("maximumHeight")
                        require(maximumHeight in 1..4320)
                        val mediaItem = MediaItem.Builder().setUri(https(session.getString("manifestUrl")))
                            .setMimeType("application/dash+xml")
                            .setDrmConfiguration(MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
                                .setLicenseUri(https(drm.getString("licenseServerUrl")))
                                .setForceDefaultLicenseUri(true).setLicenseRequestHeaders(authorization(drm))
                                .setMultiSession(false).build()).build()
                        player.trackSelectionParameters = player.trackSelectionParameters.buildUpon()
                            .setMaxVideoSize(Int.MAX_VALUE, maximumHeight).setPreferredAudioLanguages("en")
                            .setTrackTypeDisabled(C.TRACK_TYPE_TEXT, true).build()
                        player.setMediaItem(mediaItem); player.prepare(); player.play()
                        status.text = "Native Media3 · protected playback"
                    }.onFailure { status.text = "Invalid session contract. Check your backend."; releaseSession() }
                }.onFailure { if (!closed.get()) status.text = "Authorization failed. Check login, Content ID, policy, and network." }
            }
        }
    }

    private fun releaseSession() { activeSession?.let(::workerRelease); activeSession = null }
    private fun workerRelease(session: JSONObject) {
        // A bounded release may finish after Activity destruction, including a
        // session response that arrived after the user left the screen.
        Thread({ runCatching {
            val release = session.getJSONObject("release")
            val headers = if (release.has("authorization")) authorization(release)
                else authorization(session.getJSONObject("drm"))
            post(release.getString("url"), "", headers)
        } }, "drmx-native-release").start()
    }
    private fun stopPlayback() { generation++; player.stop(); player.clearMediaItems(); releaseSession() }
    override fun onStop() { stopPlayback(); super.onStop() }
    override fun onDestroy() { closed.set(true); stopPlayback(); player.release(); worker.shutdown(); super.onDestroy() }

    companion object {
        fun https(url: String): String {
            val uri = URI(url); require(uri.scheme == "https" && uri.host != null && uri.userInfo == null)
            return url
        }
        fun authorization(drm: JSONObject): Map<String, String> {
            val auth = drm.optJSONObject("authorization")
            val name = auth?.getString("headerName") ?: "Authorization"
            val scheme = auth?.getString("scheme") ?: drm.optString("tokenType", "Bearer")
            val value = auth?.getString("value") ?: drm.getString("drmLicenseToken")
            require(name.matches(Regex("^[A-Za-z0-9-]{1,64}$")) && scheme.matches(Regex("^[A-Za-z][A-Za-z0-9._-]{0,31}$")))
            require(value.isNotBlank() && !value.contains('\r') && !value.contains('\n'))
            return mapOf(name to "$scheme $value")
        }
        fun post(url: String, body: String, headers: Map<String, String>): String {
            val connection = URI(https(url)).toURL().openConnection() as HttpURLConnection
            try {
                connection.requestMethod = "POST"; connection.instanceFollowRedirects = false
                connection.connectTimeout = 5000; connection.readTimeout = 15000; connection.doOutput = true
                connection.setRequestProperty("Content-Type", "application/json")
                headers.forEach { (name, value) -> connection.setRequestProperty(name, value) }
                connection.outputStream.use { it.write(body.toByteArray(Charsets.UTF_8)) }
                require(connection.responseCode in 200..299) { "Customer request rejected" }
                return connection.inputStream.use { stream ->
                    val output = java.io.ByteArrayOutputStream(); val buffer = ByteArray(8192)
                    while (true) {
                        val count = stream.read(buffer); if (count < 0) break
                        require(output.size() + count <= 1_048_576); output.write(buffer, 0, count)
                    }
                    output.toString("UTF-8")
                }
            } finally { connection.disconnect() }
        }
    }
}

네이티브 오프라인 라이선스에는 OfflineLicenseHelper.downloadLicense을 사용하고, 불투명한 key-set ID를 비공개 저장소에 보관한 뒤 MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID).setKeySetId(id)로 복원하세요. 아래의 컴파일된 도우미는 라이선스 획득, 기간 확인, 갱신, 해제 및 다운로드/재생 항목을 처리합니다. 이 도우미는 완전한 다운로드 애플리케이션이 아닙니다. 캐시, 다운로드 서비스, 진행 상태, 메타데이터, 사용자/기기 연결 및 신뢰할 수 있는 작업 권한 부여는 직접 구현해야 합니다.

Native persistent-license helper (Kotlin, DRM-X 의존성 없음)
package com.haihaisoft.drmx.samples.nativeplayer

import android.content.Context
import android.util.Base64
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.StreamKey
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DefaultHttpDataSource
import androidx.media3.exoplayer.dash.DashUtil
import androidx.media3.exoplayer.drm.DrmSessionEventListener
import androidx.media3.exoplayer.drm.OfflineLicenseHelper
import androidx.media3.exoplayer.offline.DownloadRequest
import org.json.JSONObject

/** Native Media3 alternative: no DRM-X AAR. Call blocking operations on a worker.
 * Application owns downloads/cache, user/device entitlement, progress and lifecycle.
 * Never persist the session, request headers, tokens, challenges or license bytes.
 */
@UnstableApi
class NativeOfflineLicense(context: Context) {
    private val storage = context.getSharedPreferences("native.offline.keysets", Context.MODE_PRIVATE)
    private val upstream = DefaultHttpDataSource.Factory().setConnectTimeoutMs(10000).setReadTimeoutMs(20000)

    // session is the validated response from YOUR offline-license-session endpoint.
    fun acquire(session: JSONObject): ByteArray {
        validate(session)
        val contentId = session.getString("contentId")
        require(!storage.contains(contentId)) { "Renew or release the stored license before acquiring again" }
        val source = upstream.createDataSource()
        val manifest = DashUtil.loadManifest(source, android.net.Uri.parse(MainActivity.https(session.getString("manifestUrl"))))
        val format = DashUtil.loadFormatWithDrmInitData(upstream.createDataSource(), manifest.getPeriod(0))
            ?: error("Missing Widevine initialization data")
        val helper = helper(session)
        try {
            val id = helper.downloadLicense(format)
            save(contentId, id)
            return id
        } finally { helper.release() }
    }

    fun renew(contentId: String, freshSession: JSONObject) {
        validate(freshSession); require(freshSession.getString("contentId") == contentId)
        val helper = helper(freshSession)
        try { save(contentId, helper.renewLicense(keySet(contentId))) } finally { helper.release() }
    }

    // Local CDM check only. No token or network license URI is used.
    fun remainingSeconds(contentId: String): Pair<Long, Long> {
        val config = MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID).build()
        val helper = OfflineLicenseHelper.newWidevineInstance(config, upstream, DrmSessionEventListener.EventDispatcher())
        try {
            val remaining = helper.getLicenseDurationRemainingSec(keySet(contentId))
            return Pair(remaining.first, remaining.second)
        } finally { helper.release() }
    }

    fun release(contentId: String, freshSession: JSONObject) {
        validate(freshSession); require(freshSession.getString("contentId") == contentId)
        val helper = helper(freshSession)
        try {
            helper.releaseLicense(keySet(contentId))
            check(storage.edit().remove(contentId).commit())
        } finally { helper.release() }
    }

    fun downloadRequest(contentId: String, downloadId: String, manifestUrl: String, streamKeys: List<StreamKey>): DownloadRequest {
        require(streamKeys.isNotEmpty()) { "Select policy-compliant video, audio and subtitle tracks first" }
        return DownloadRequest.Builder(downloadId, android.net.Uri.parse(MainActivity.https(manifestUrl)))
            .setMimeType("application/dash+xml").setStreamKeys(streamKeys).setKeySetId(keySet(contentId)).build()
    }

    fun mediaItem(contentId: String, manifestUrl: String, streamKeys: List<StreamKey>): MediaItem = MediaItem.Builder()
        .setUri(MainActivity.https(manifestUrl)).setMimeType("application/dash+xml").setStreamKeys(streamKeys)
        .setDrmConfiguration(MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID).setKeySetId(keySet(contentId)).build()).build()

    private fun helper(session: JSONObject): OfflineLicenseHelper {
        val drm = session.getJSONObject("drm")
        // Use HTTP license headers, NOT Widevine optional key-request parameters.
        val config = MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
            .setLicenseUri(MainActivity.https(drm.getString("licenseServerUrl")))
            .setForceDefaultLicenseUri(true).setLicenseRequestHeaders(MainActivity.authorization(drm)).build()
        return OfflineLicenseHelper.newWidevineInstance(config, upstream, DrmSessionEventListener.EventDispatcher())
    }
    private fun validate(session: JSONObject) {
        require(session.getInt("contractVersion") == 1 && session.getString("playbackMode") == "offline")
        require(session.getString("drmSystem") == "widevine" && session.getString("contentType") == "vod")
        require(session.getJSONObject("policy").getBoolean("persistent"))
    }
    private fun keySet(contentId: String): ByteArray = Base64.decode(storage.getString(contentId, null)
        ?: error("No device-bound license for this content"), Base64.NO_WRAP)
    private fun save(contentId: String, id: ByteArray) {
        check(storage.edit().putString(contentId, Base64.encodeToString(id, Base64.NO_WRAP)).commit())
    }
}

이 저수준 도우미를 호출하기 전에 신선한 백엔드 응답은 요청한 Content ID, 가동, 서명하에서 user/device 및 허가 유효 기간 일치한다는 것을 검증합니다. Persist a stable offlineLicenseId 과 신뢰할 수 있는 appear/track metadata alongside the key-set ID, 결코 토큰. worker thread에 helper 호출을 차단하십시오. 라이센스 HTTP 헤더는 DRM 구성에 setLicenseRequestHeaders을 사용; Widevine 옵션 키 요청 매개 변수는 다른지도입니다.

새로운 통합은 AndroidX Media3 DRM support을 사용해야 합니다. 레거시 com.google.android.exoplayer2 프로젝트는 official 마이그레이션 가이드를 따르거나 레거시 및 AndroidX 클래스를 혼합하지 않거나, 샘플은 이전 버전에 대해 변경되지 않습니다.

9. 확인, 갱신, 릴리스 및 전환 언어#

이들은 별도의 행동입니다. 활성 오프라인 DRM 세션이 출시 된 후 한 번에 실행하십시오. DrmXPlayer와 player.stopForOfflineLicenseOperation { /* one operation below */ }을 사용하거나 기존의 Media3 플레이어를 먼저 출시하십시오. 게임을하고 있는 동안 라이선스를 확인하지 마십시오. 체크는 지속 시간 표시에 대한 옵션입니다; 만료 된 라이센스의 갱신을 방지하지 않아야합니다.

offlineManager.checkLicense(contentId) { result ->
    result.onSuccess { license ->
        if (license.state == DrmXOfflineLicenseState.EXPIRED) {
            // Require renewal online before offering offline playback.
        }
    }
}
offlineManager.renewLicense(offlineEndpoint, contentId, capabilities, customerHeaders) {
    /* update duration and UI after success; show retry on failure */
}
// Only when the customer requests deletion/release:
offlineManager.releaseLicense(offlineEndpoint, contentId, capabilities, customerHeaders) {
    /* after success, remove the corresponding encrypted download */
}

getStoredLicense는 메타데이터를 읽습니다. checkLicense 쿼리 권한 로컬 CDM 지속 시간. 갱신 / 릴리스는 연결성이 필요하며 백엔드에서 신선한 운영 별 허가가 필요합니다. 미디어가 라이센스를 해제하지 않습니다. 차단되면 서버가 확인되었기 때문에 다음 연결에 대한 큐 릴리스가 표시됩니다. 소유자가 파괴 될 때 관리자를 닫으십시오.

// Use player.media3Player for the DRM-X wrapper; nativePlayer for plain Media3.
nativePlayer.trackSelectionParameters = nativePlayer.trackSelectionParameters
    .buildUpon()
    .setPreferredAudioLanguages("en") // Chinese: "zh-Hans", "zh"
    .setPreferredTextLanguages("en")  // choose a packaged/downloaded language
    .setTrackTypeDisabled(C.TRACK_TYPE_TEXT, false) // true = subtitles Off
    .build()

실제 사용 가능한 트랙과 사용 가능한 선택의 선택을 구축하십시오. 선호도는 패키지에서 영어 트랙 absent를 만들 수 없습니다. 오프라인 사용자는 다운로드 된 언어를 선택할 수 있습니다. 화면 회전을 가로지르는 라이센스 / 캐시 상태를 보존하십시오. 전체 화면은 다른 라이센스를 취득하는 이유가 아닙니다.

10. 오프라인 승인 endpoint#을 구현하십시오.

응용 프로그램 게시물 contentId, contentType: "vod", drmSystem: "widevine", playbackMode: "offline", operation: "acquire" | "renew" | "release", offlineLicenseId (초기 취득을위한 null) 및 진실한 platformCapabilities. 갱신 / 릴리스 ID는 인증 된 사용자, 장치 및 콘텐츠에 속합니다. 정책 서버 측을 선택하고, 신선한 DRM License Token을 발행하고 일치하는 계약을 반환하십시오.

{
  "contractVersion": 1, "playbackMode": "offline",
  "offlineLicenseId": "YOUR-STABLE-USER-DEVICE-CONTENT-RECORD",
  "contentId": "YOUR-PUBLISHED-CONTENT-ID", "contentType": "vod",
  "drmSystem": "widevine",
  "manifestUrl": "https://media.customer.example/title/manifest.mpd",
  "authorizationExpiresAt": "YOUR-FRESH-UTC-EXPIRY",
  "drm": {
    "system": "widevine",
    "licenseServerUrl": "https://license6.drm-x.com/v1/licenses/widevine",
    "integrationMode": "direct-token",
    "drmLicenseToken": "SHORT-LIVED-OPERATION-AUTHORIZATION",
    "tokenType": "Bearer"
  },
  "policy": {
    "persistent": true, "renewAllowed": true, "maximumHeight": 1080,
    "allowedTrackTypes": ["AUDIO", "SD", "HD"],
    "licenseDurationSeconds": 604800, "rentalDurationSeconds": 259200,
    "playbackDurationSeconds": 172800, "renewalDurationSeconds": 604800
  }
}

이것은 재사용 가능한 라이센스 응답이 아닌 explanatory placeholders입니다. 실제 작업에서 신선한 값을 반환합니다. SDK은 바인딩 된 drm.authorization 디코더를 사용하여 integrationMode: "token-proxy"을 지원합니다. Cache-Control: no-store을 반환하십시오. 성공적인 승인 기관을 로그하지 마십시오. 다운로드 가능한 SDK의 OFFLINE_INTEGRATION.md 및 PHP 샘플의 콘크리트 요청 처리를위한 오프라인 엔드 포인트를 참조하십시오.

11. 장치 및 문제 해결을 테스트#

Widevine 프로비저닝 및 중국 네트워크 테스트

Provisioning은 콘텐츠 라이센스 취득 전에 장치 인증서를 취득합니다. 미리보기.9에서 전화 데모는 인증 된 고객 HTTPS을 통해 CDM의 서명 요청을 보냅니다. 서명 된 응답은 변경되지 않습니다. 이것은 전화가 Google의 프로비저닝 서비스에 직접 도달할 수 없을 때 도움이됩니다. 고객 릴레이는 여전히 도달해야합니다.

Provisioning 2.0는 공장 keybox를 사용합니다. Provisioning 4.0는 다른 장치 credential 모형을 이용합니다. 이 버전은 분리됩니다 L1/L3 보안 수준 및 플레이어 그들을 전환하지 않습니다. 이미 제공 L3 전화는 다른 전화가 장치 프로비저닝을 기다립니다 동안 재생할 수 있습니다. 여기에서 검토 된 공공 공식 소스는 중국 만 2.0을 지원하지 않습니다. 참조 NXP's Device 통합 가이드 이름 * Androidο 회원 관리.

Preview.10 오류 보고는 직접 또는 릴레이 수송, timeout/를 구별합니다DNS/TLS/HTTP 실패, HTTP 상태와 탈출 시간. provisioningModel: BootCertificateChain 이름 * AOSP 4.0 모형; unknown 또는 other 2.0을 설치하지 않습니다. PHP 로그는 상류 구글 실패에서 거부 된 입력을 구별합니다. 서명 된 페이로드, URL을 제외한 보고서, 인증 정보 그리고 유일한 장치 식별자.

업데이트 APK 위치, 재시동 및 영향을받는 네트워크에서 하나의 다운로드 한 다음 복사 로그와 대략적인 시간을 유지합니다. 기존 다운로드 및 라이센스가 유지됩니다. 릴레이는 사용할 수 없습니다. Widevine 또는 인증 정보 device/Google에 의해 거부됩니다. 영향을받는 전화에 대한 실제 수용이 필요합니다.

미리보기.8에서, 기본 L2/L3 또는 알 수없는 수준 Widevine 재생은 제한됩니다 SD (최대 576p) 디코딩 하기 전에. 원래 앞으로 Android 귀하의 백엔드를 통해 기능: 세션 API 회사 소개 Widevine 다중 키 라이센스 오디오 /SD 키. PHP 샘플은이 필드를 보존하고 오프라인 요청을 캡합니다. SD rendition 및 정책 허가 소프트웨어 DRM 필수; 하드웨어 전용 및 균일 한 단일 키 보안 규칙은 변경되지 않습니다. 설정은 비디오를 흐리게하지 않고 반투명 패널을 사용합니다. 숫자 cryptoErrorCode 안전한 오류 보고서에 포함되어 있습니다.

시험/symptom예상된 행동 / 다음 체크
SDK 다운로드는 403을 반환Active Professional 또는 Enterprise 구독을 사용하여 조직을 선택하십시오. 재판 후 계획 된 업그레이드는 SDK을 잠금 해제하지 않습니다.
세션 401 / 403로그인, 시청 권한, Environment, 게시된 Content ID 및 정책을 확인하세요. 인증을 절대 우회하지 마세요.
오프라인은 실패하지만 온라인 작업CDM 기간, 완료된 다운로드, 캐시 위치, 스트림 키 및 모든 필요한 라이센스 키를 확인합니다.
한국어 / 중국어 누락패키지 언어 Metadata, 실제 트랙 및 오프라인 언어 선택 확인.
L1, HDCP 또는 디코더 실패물리적 하드웨어, 프로비저닝, 코덱 및 출력 요구 사항을 확인하십시오. 기능 문자열은 인증되지 않습니다.
재부팅, 회전, 업그레이드Persistent 라이센스 및 캐시는 중복 취득없이 지원되는 수명주기 변경을 생존합니다. 백업은 새로운 장치에 라이센스를 마이그레이션하지 않아야합니다.
비행기 모드Wi-Fi 및 모바일 데이터를 너무 비활성화; 앱을 다시 시작, 로컬로 재생하고 모든 약속 된 언어를 전환.
만기, 저장 압력, 계정 스위치clear unavailable/renew state를 보여주고, 불완전한 매체를 재기하고, 다른 사용자의 오프라인 카탈로그를 새기 없이 user/device 접근을 강제하십시오.

각 지원 전화, 태블릿, Android TV/Google TV, Fire TV 또는 Widevine, 보안 디코딩, 코덱/리솔루션, HDCP, 외부 디스플레이, 시계 변경 및 긴 단자에 대한 Android 모델을 내장. 모든 Android 장치에는 동일한 DRM 기능이 없습니다. Hollywood 콘텐츠는 권리 보유자의 보호 및 장치 요구 사항이 필요하며 성공적인 디코딩이 아닙니다.

연결하기 전에 취득 할 기회가 없습니까?그것은 다른 배포입니다: 자격이 된 내장된 라이센스 서비스 문제 라이센스 지역 네트워크에 대한 라이센스. 그것은 단지이 SDK을 사용 하 여 활성화 되지 않습니다. DRM-X 사용자 지정 배포 제공 업체 권한, 제공, 키 보호 및 완전히 분리된 수용 테스트에 당신과 함께 작동할 수 있습니다. 참조 on-board DRM 항공기, 버스, 기차 및 선박.

Universal Player 재생 목록 및 공유 컨트롤 #

더 많은 정보 Android preview.11 이상 평가 앱 및 현재 Android/Apple 개발 소스 공유 in-video 이전/Play-Pause/다음, CC, 설정 및 풀 스크린은 재생 중에 두 번째로 숨깁니다. Android's playlist menu no longer include “Play next”. 선택된 shuffle/repeat 설정은 다시 시작에 걸쳐 강조되고 persist 남아. 모두, 재생은 저장된 재생 목록을 주문하고 최종 비디오에서 중지합니다. 드래그는 현재 비디오를 다시 시작하지 않고 주문 항목을 처리; 저장된 순서는 또한 다운로드 된 재생 목록에 적용됩니다. 이 최신 소스 APIs는 콘솔의 미리보기에 포함되지 않습니다.10 SDK 뭉치.

동일한 고객/뷰어 storageNamespaceDrmXPlayerplayerView.storageNamespace으로 재생 목록을 설정하기 전에 공급하십시오. VOD 진행은 주기적으로 저장되며 일시 중지 / 정지에서 스트리밍 또는 다운로드 된 재생에서 Content ID에 복원됩니다. 라이브 재생은 저장된 VOD 위치를 사용하지 않습니다. 완료된 다운로드는 네트워크 미디어 소스없이 동일한 shuffle/repeat/resume 동작을 유지합니다. 이 장치 로컬 상태는 장치의 진행 상황을 동기화하지 않습니다.

웹 및 Android 판은 컴팩트한 블루 컨트롤이있는 일관된 어두운 설정 패널을 사용합니다. Playlists는 다음 / 이전, shuffle, 반복 한 / 모든 및 cancellable 자동 사전을 지원합니다. 모든 항목은 새로운 공인 재생 요청을 사용합니다. 장 및 성적 메타 데이터는 콘텐츠 카탈로그에서 제공됩니다.

재생 속도, 수면 타이머, 캡션, 허용된 품질, 오디오 언어, 세션 북마크 및 A-B 루프를 사용할 수 있습니다. 웹은 키보드 단축키, 옵션 뷰어-스코프 로컬 이력서, 성적 검색 및 지원 브라우저 PiP를 추가합니다. Android는 기본 Widevine 오프라인 다운로드 및 갱신을 유지합니다. 주조 및 크로스 디바이스 진행은 별도의 응용 통합이 필요합니다.

전문 및 기업 로고 설정

브랜딩은 비디오 표면을 차단합니다. 인증 된 로고, 제품 정보 및 플레이어 버전의 Open Settings → About 플레이어. 웹은 또한 오른쪽 클릭에이 정보를 제공합니다. 공중 전화 데모에는 WayMaker, Goodness of God, Fresh Oil 및 4K Test Video가 포함됩니다. 모든 선택은 자체 승인을받습니다.

playerBranding을 신뢰받는 백엔드에서 {"mode":"hidden"} 또는 {"mode":"custom","logoUrl":"https://your-site.example/logo.png","label":"Your brand"}로 설정하십시오. 플랫폼은 활성 Professional 또는 Enterprise 구독을 확인합니다. 다른 플랜은 {"mode":"default"}을 사용; 권한없는 사용자 정의 / 숨겨진 요청은 403을 반환합니다. 이것은 보안 워터 마크로 분리됩니다.

PHP 다운로드에는 편집 가능한 재생 목록과 브랜딩 구성이 포함되어 있습니다. Android 고객 ZIP에는 재생 목록 콜백 예제가 포함되어 있습니다. 백엔드에서 재생 목록 작성자를 유지하십시오. API 클라이언트 인증 정보을 플레이어 중 하나에 포함하지 마십시오.