Android SDK 및 네이티브 Media3
드래그로 정렬하는 재생목록, 저장되는 셔플·반복 설정, 오프라인 이어보기를 제공하는 Android 플레이어를 다운로드하세요. DRM-X 또는 네이티브 Media3로 보호된 재생을 통합하세요.
UI 사용자 지정, 매개변수 기본값 및 수명 주기 예제는 PLAYER-UI-CONFIGURATION.md를 참조하세요. 플레이어의 기본 팝업 패널은 불투명하며 자막과 워터마크의 불투명도는 별도로 설정합니다.
SDK ZIP의 DEMO-QUICKSTART.md에 따라 PHP 데모를 사용해 보세요. 자체 콘텐츠를 사용하려면 먼저 웹사이트와 백엔드를 통합한 다음, MOBILE-AND-TV-APPS.md에 따라 앱을 동일한 로그인 및 시청 권한 규칙에 연결하세요.
Android 플레이어 #를 사용해보십시오.
1.0.0-beta.11 · 12.15 MB · Android 6.0+ (API 23+) · DRM-X 6.0 Player
- Android 전화에서이 페이지를 열고 APK를 다운로드하십시오.
- 다운로드 된 파일을 엽니 다. Android 요청하면 브라우저 또는 파일 관리자에서 설치를 허용 한 다음 Install을 탭합니다.
- Open DRM-X 6.0 Player, 연결 유지 및 비디오의 Play 버튼을 탭합니다. 이 응용 프로그램은 재생을 누르면 스트리밍 라이센스를 요청합니다.
- Settings 을 클릭하여 품질, 오디오, 캡션 또는 재생 속도를 선택할 수 있습니다. 전체 화면에서 계속 전화를 회전하십시오.
- 동영상의 세 줄 메뉴를 열고 Download를 선택하세요. Downloaded videos를 켜면 Offline Mode, 끄면 Online Mode가 됩니다. Offline Mode에서는 해당 동영상 메뉴의 Delete를 선택하고 확인하세요. 라이선스 갱신에는 인터넷 연결과 현재 유효한 권한이 필요합니다. 오프라인 오류가 발생하면 오프라인을 유지하거나 온라인으로 전환할 수 있습니다.
Already queued media downloadsContinue in the background. If Android stop the app while prepare licenses or tracks, reopen it while connected to finish 준비 잔여 항목. 기존의 다운로드가 유지됩니다. 오프라인 재생은 캐시 된 미디어 만 사용합니다. 완전하거나 만료 된 다운로드는 완료 또는 갱신이 필요합니다.
샘플 APK은 공개 및 평가에 서명합니다. AAR 및 전체 SDK은 해당 콘솔 계정에서 사용할 수 있습니다. 자신의 생산 응용 프로그램을 유지 키와 백엔드 로그인이 데모에서 분리.
APK 체크섬 및 USB 설치
SHA-256:
b96ba527ddf9abae5bff2f8b85a39a12b59c934aaacde1961c8096aa32524c54Android 플랫폼 도구 및 허가 된 USB 장치 :
adb install -r drmx-android-player-1.0.0-beta.11.apk이 APK을 제거하지 않고 기존 공식 샘플에 설치하십시오. 동일한 서명 키 및 응용 프로그램 ID는 다운로드, 오프라인 라이센스, 저장된 위치 및 선호도를 보존합니다. 자신의 빌드가 다른 서명 키를 사용하여 별도의 응용 프로그램을 사용하십시오. ID 앱을 유지하고 샘플의 오프라인 라이센스를 보존합니다.
Download controls and language names in preview.40#
Connect to the internet before starting a download. Choose the permitted video quality and audio track in the download dialog; available subtitles are included. Audio and subtitle menus display each language in its own language, such as English and 中文. Customer configuration and device DRM support determine the available quality ceiling.
Offline Mode에서 동영상의 세 줄 메뉴를 열고 Delete를 선택하세요. 확인하면 해당 동영상의 로컬 암호화 미디어와 라이선스 참조만 삭제됩니다. Cancel을 선택하면 유지됩니다. 다시 다운로드하려면 인터넷에 연결하세요.
New downloads require an internet connection. A queued transfer can pause when connectivity disappears and resume after reconnection. Completed downloads play offline while their device-bound licenses remain valid. Playlist posters and seek-preview thumbnails remain cached as described below.
Offline artwork and downloads in preview.39#
Playlist posters and seek-preview thumbnails are saved on the device with their artwork metadata. They remain available after closing and reopening the app without a network connection. After upgrading, open the offline demonstration library once while connected and allow the images to load; existing encrypted videos do not need to be downloaded again.
Wait for each video to finish downloading, then switch off both Wi-Fi and mobile data and choose Play offline. Test the downloaded playlist, selected audio and included captions. Reconnect before using Renew when a stored license needs renewal. Install this update over the existing sample to retain its downloads and settings.
Publisher images must be available while connected before they can appear offline. Images use a bounded 96 MiB app-private store, separate from encrypted media and licenses; older artwork can be evicted when this store fills. Missing images do not bypass DRM or prevent an otherwise valid downloaded video from playing.
Live DRM in preview.13#
2026년 9월 17일 출시. 휴대폰 앱과 고객 SDK에는 4K 라이브 DRM 테스트가 포함됩니다. 실시간 재생은 새로운 매니페스트를 사용하며 다운로드할 수 없습니다. 테스트 이벤트는 활성 방송 중에만 사용할 수 있습니다. AWS 및 OBS 설정 가이드에 따라 나만의 이벤트를 만들어보세요.
Android 플레이어 미리보기.12#
출시 14 9월 2026. 수정은 Widevine 라이센스가 만료되거나 1 분 이하 남아있을 때 예상치 못한 라이센스 요청을 만드는 재생을 수정합니다. 이것은 Media3 오류 6004, 연결되는 동안 HTTP 오류 또는 분리 된 동안 DNS 오류로 나타날 수 있습니다.
더 많은 정보 플레이어 이제 로컬로 다운로드 된 라이센스를 복원합니다. 유효 라이센스는 나머지 재생 시간을 유지; 만료 된 라이센스 쇼 연결 및 탭 Renew. 면허 유효성 및 보호 규칙은 장치에 의해 실행됩니다. 진단은 또한 수치를 포함합니다 HTTP 네트워크 작업이 실패할 때, 요청 헤더, URL 또는 라이센스 응답없이.
- 기존 공식 앱에서 미리보기.12을 설치합니다. 현재 앱 데이터를 유지하고 다운로드하십시오.
- WayMaker 또는 또 다른 다운로드 된 비디오가 갱신되면 인터넷에 연결하여 비디오 및 탭을 선택하십시오. Renew.
- License 갱신을 기다리십시오. 오프라인 재생을 준비하십시오. , 다음을 선택하십시오. 오프라인 재생 . 암호화 된 비디오는 다운로드 남아 있습니다.
This release retains preview.11's controls and playlist features. The public evaluation app and its embedded player identify preview.12; at that release, the separately distributed Console SDK was preview.10 / bundle 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 위치는 스트리밍 또는 다운로드 재생으로 돌아갈 때 복원됩니다. 진행은 현재 시청자에게이 장치에 저장됩니다; 장치간에 동기화되지 않습니다.
재생목록을 정리하고 순서를 저장하며 재생을 이어가세요. 오프라인 시청에는 완료된 다운로드와 사용 가능하고 만료되지 않은 오프라인 라이선스가 필요합니다.
The preview.11 release updated the public evaluation APK. The current Console SDK is 1.0.0-beta.14, bundle 20261028.1; the integration instructions below use that bundle.
1. 통합 경로 선택#
DrmXPlayer, 전화 샘플 및 오프라인 관리자를 사용하십시오. 로그인 및 비즈니스 규칙을 유지하십시오.
PATH B · KEEP YOUR UIMedia3 + DRM-X 도움기존 플레이어을 유지하고 스택을 다운로드하십시오. DrmXOfflineManager는 영구 라이센스를 관리 할 수 있습니다.
PATH C · NATIVE ONLYMedia3 AAR없이동일한 백엔드 계약과 네이티브 라이센스 라이프사이클을 직접 구현합니다. DRM-X 플레이어 의존도가 없습니다.
Developer Tools → Device SDKs에서 다운로드하세요. SDK 사용에는 승인된 Professional 평가 계정을 포함하여 유효한 Professional 또는 Enterprise 권한이 필요합니다. Standard에서도 자체 플레이어용 네이티브 APIs를 사용할 수 있습니다. 세션 생성 및 라이선스 갱신 시 SDK 권한을 확인하며 오프라인 라이선스는 계정 서비스 기간을 초과할 수 없습니다.
Current preview: 1.0.0-beta.14 · customer bundle 20261028.1 · 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. 내용을 준비하고 백엔드#
- 패키지 및 Widevine 호환 암호화 된 DASH DRM-X 환경의 제목을 게시합니다. 정확한 를 복사 Content ID .
- 서버의 Environment ID, Site Key 및 Access Key 구성. 사용 다운로드 PHP 통합 작업 시작 지점에 대 한.
- 사용자 로그인 및 제목 확인을 구현하십시오. 앱은 짧은 라이브 사용자 토큰으로 귀하의 백엔드를 호출합니다. 백엔드는 콘텐츠, 정책 및 신뢰할 수있는 URL을 선택합니다.
- 물리적 Widevine-capable 장치에 온라인 재생을 실행하십시오. 스트리밍 계약이 작동 한 후 오프라인 구매 만 추가하십시오.
암호화된 동영상과 DRM 라이선스는 서로 다른 경로로 전달됩니다
CDN은 암호화된 동영상을 전달합니다. 백엔드가 접근 권한을 확인하고, DRM 라이선스 서비스가 호환 기기에서의 복호화를 승인합니다.
- 스토리지 / CDN
암호화된 미디어를 플레이어에 전달합니다.
- 고객 백엔드
시청 권한을 확인하고 DRM-X에 재생 승인을 요청합니다.
- DRM-X
재생 승인 정보를 검증하고 DRM 라이선스 요청을 처리합니다.
- 플레이어 + 기기
기기의 DRM 시스템으로 라이선스를 받아 암호화된 미디어를 재생합니다.
| 이 값 변경 | 그것은 어디에 속한다 | 이름 * |
|---|---|---|
| sessionEndpoint | Android | 라이선스 서버의 직접 URL이 아닌, 자체 HTTPS 재생 세션 엔드포인트입니다. |
| offlineEndpoint | Android, 오프라인 전용 | 당신의 취득/renew/release 허가 엔드포인트. |
| contentId | Android 요청; 백엔드 검증 | 출판물 보호 Content ID 뷰어는 액세스 할 수 있습니다. |
| 고객 액세스 토큰 | 로그인 후 앱 메모리 | 짧은 라이브 사용자 토큰, Site Key 또는 Access Key이 없습니다. |
| Environment ID, Site Key, Access Key | Server secret 설정만 | DRM-X 계정 인증 정보. APK, HTML 또는 소스 컨트롤에서 절대 발송하지 마십시오. |
| 정책 및 신뢰할 수있는 표시 URL | 고객 백엔드 | 사업 제목 및 패키지 매핑. 앱에서 이러한 것을 허용하지 마십시오. |
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-beta.14")
}
독립형 설치 AAR 대신
앱 모듈의 libs/에서 AAR을 넣으십시오. 플랫 AAR에는 자동 변속성 해상도가 없으며 세 개의 일치 Media3 의존도를 추가합니다. 두 가지 형태를 설치하지 마십시오.
implementation(files("libs/drmx-universal-player-1.0.0-beta.14.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.contentId 및 customer.loginEndpoint에 대한 전화 샘플의 MainActivity.kt을 검색하십시오. 표시된 fallback 설정을 변경하거나 로그인 후 응용 프로그램에서 Intent extras를 전달하십시오. 실행시 customer.accessToken를 통과하십시오. ZIP README는 각 매개 변수를 설명하고 교체 할 수있는 데모 후크가 설명합니다.
AAR#를 사용하여 테스트 APK를 빌드하십시오.
이 예제는 전체 SDK ZIP에서 실행 가능한 전화 프로젝트를 사용하고 명시적으로 독립 AAR을 링크합니다. 동일한 접근법은 기존 Android 앱에서 작동합니다.
- full Android SDK ZIP 다운로드 및 추출. Android Studio에서 루트 폴더를 엽니다.
- Keep
libs/drmx-universal-player-1.0.0-beta.14.aarin the extracted root. Use JDK 17, Android SDK 36 and the included Gradle wrapper. samples/phone/build.gradle.kts에서 DRM-Xdependencies내의 항목에 대한 Maven 의존성을 대체합니다. 그 파일을 구축하는 나머지를 유지하십시오.
implementation(files(rootProject.file("libs/drmx-universal-player-1.0.0-beta.14.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-beta.14")을 제거하기 때문에 같은 SDK는 두 번 포함되지 않습니다. 평평한 AAR는 위의 세 가지 Media3 의존성을 필요로합니다.
- 자신의 테스트 빌드와 함께 설치된 다운로드 된 데모를 유지하려면 전화 모듈의
applicationId을com.example.drmxtest로 변경하십시오. 이 공급 된 샘플에 대한 네임스페이스를 변경하십시오. - 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 에 비디오에서 권한 부여를 시작합니다. 화면 또는 설정 열기는 스트리밍 라이센스를 취득하지 않습니다. 일시 중지 / 이력서, 설정 변경 및 휴대 전화를 회전은 현재 세션을 보존합니다.

| (주) | 어떤 고객이 할 수 있습니다 |
|---|---|
| 품질 관리 | 매니페스트에서 지원되고 라이선스가 허용하는 해상도 또는 Auto를 선택하세요. 사용할 수 없는 옵션에는 라이선스나 기기/DRM 제한 사유가 표시됩니다. Auto에도 동일한 제한이 적용됩니다. |
| 오디오 트랙 | 패키지에 의해 제공된 지원되는 오디오 언어를 선택하십시오. |
| 제목/CC | 캡션 언어 또는 오프를 선택하십시오. CC 버튼은 지원된 캡션이 존재하고 직접 견인할 때 나타납니다. |
| Caption 외관 | 글꼴, 텍스트 색상 및 크기, 배경 / 창 색상 및 불투명을 조정합니다. 설정은 장치에 저장됩니다. |
| 재생 속도 / 수면 타이머 | 선택된 간격 후에 0.25–2× 속도 또는 일시적으로 재생하십시오. |
| Seek / 전체 화면 | 타임 라인, 10 초, 또는 두 배 비누를 찾습니다. 재생 재생을 재시작하지 않고 풍경에 회전합니다. |
각 동영상 메뉴에는 Move up / Move down과 함께 온라인에서는 Download, 오프라인에서는 Delete가 표시됩니다. 다운로드가 완료되면 Download가 비활성화됩니다. Downloaded videos 스위치로 오프라인 재생을 선택하며 캐시된 트랙만 사용할 수 있습니다. 다운로드, 삭제, 전체 재생목록 다운로드를 위한 별도 버튼은 없습니다.
본문 바로가기 텍스트로 정보 발러 사본 최근 오류 외에도 스크린 샷없이 세부 사항을 지원할 수 있습니다. 보고서에는 보고서가 포함되어 있습니다. ID, 가동, SDK/Android/device 버전, 숫자 CDM/Media3 오류, 선택된 스택 프레임, 연결/download/playback 상태 및 반복 카운트. 그들은 제외 인증 정보, 미디어 URL, 키, 키 세트 ID, 라이센스 데이터 및 전체 CDM 덤프.
샘플은 5 시간 동안 최대 32 로컬 보고서 (64 KB)를 유지합니다. 연결 및 서명하면 구성 된 고객 백엔드에 오류를 업로드하고 30 초 당 한 번에 한 번에. 공공 데모 서버는 5 시간 동안 경고 / 오류를 유지하고 반복 된 이벤트를 결합하여 512 KB 및 prunes의 파일을 매 분마다 캡합니다. 루틴 진행 및 성공적인 재생은 기록되지 않습니다. 사용자 정의 통합은 명시적으로 자신의 동일한 시작 오류를 공급해야합니다. drmx.diagnosticsEndpoint는 진단하지 않습니다.
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#을 다운로드하십시오.
오프라인 재생 준비
다운로드가 완료되어야 하며 기기에 연결된 유효한 라이선스도 있어야 합니다.
- 암호화된 미디어 다운로드
온라인 상태에서 동영상과 필요한 오디오 및 자막 트랙을 다운로드하세요.
- 영구 저장 라이선스 취득
온라인 상태에서 기기에 연결된 Widevine 라이선스를 취득하세요. 불투명한 식별자인 Key-set ID는 비공개로 보관하세요.
- 준비 상태 확인
다운로드가 완료되었고 라이선스가 유효한지 확인하세요.
- 오프라인 재생
캐시된 미디어를 읽고 로컬 라이선스를 복원하세요. 갱신과 해제에는 인터넷 연결이 필요합니다.
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). 전화 샘플의 enqueueDownload 및 OfflineTestRuntime 실행 가능한 구현을 보여줍니다.
// 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 전장 서비스 / 알림 요구 사항을 통합하십시오. 데모 큐는 처리됩니다.
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와 동일한 오프라인 흐름을 사용하려면 DrmXPlayer이 mediaSourceFactory을 구성하고 player.prepareOffline(playback)을 호출합니다. 정규식 복원은 새로운 것을 요청하지 않고 로컬 CDM 라이센스를 사용합니다. 누락 된 캐시 입력은 인터넷에 조용히 전환하지 않는 한 오류를 생성해야합니다.
8. 경로 C: 네이티브 Media3, 아니 DRM-X AAR #
DrmXPlayer 또는 DrmXOfflineManager를 사용할 필요는 없습니다. 3단계의 세 가지 Media3 종속성을 추가하고 동일한 고객 백엔드 계약을 구현합니다. session.manifestUrl(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. 장치 및 문제 해결을 테스트#
| 시험/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 콘텐츠는 권리 보유자의 보호 및 장치 요구 사항이 필요하며 성공적인 디코딩이 아닙니다.
Universal Player 재생 목록 및 공유 컨트롤#
Android 및 Apple 플레이어는 Previous/Play-Pause/Next, CC, Settings 및 전체 화면 제어를 제공합니다. Move up / Move down은 현재 동영상을 유지하면서 온라인 및 오프라인 재생에 사용할 목록 순서를 저장합니다. 셔플과 반복 선택은 앱을 다시 열어도 유지되며, 둘 다 끄면 마지막 항목 이후 재생이 멈춥니다.
동일한 고객/뷰어 storageNamespace을 DrmXPlayer 및 playerView.storageNamespace으로 재생 목록을 설정하기 전에 공급하십시오. VOD 진행은 주기적으로 저장되며 일시 중지 / 정지에서 스트리밍 또는 다운로드 된 재생에서 Content ID에 복원됩니다. 라이브 재생은 저장된 VOD 위치를 사용하지 않습니다. 완료된 다운로드는 네트워크 미디어 소스없이 동일한 shuffle/repeat/resume 동작을 유지합니다. 이 장치 로컬 상태는 장치의 진행 상황을 동기화하지 않습니다.
Web과 Android는 작은 파란 컨트롤과 어두운 설정 패널을 공유합니다. 다음·이전, 셔플, 한 곡·전체 반복, 취소 가능한 자동 다음 재생을 지원합니다. 항목마다 새 승인 요청을 사용하며 챕터와 대본 메타데이터는 콘텐츠 카탈로그에서 제공합니다.
속도, 취침 타이머, 자막, 허용 화질, 오디오 언어, 세션 북마크, A–B 반복을 지원합니다. Web에는 키보드 단축키, 선택적 사용자별 로컬 이어보기, 대본 검색, 지원 브라우저의 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 클라이언트 인증 정보를 넣지 마세요.
DRM-X 6.0