# Android SDK dan Media3 native

Unduh Android pemutar dengan kontrol video in- video, playlist yang dapat diseret, shuffle / resume / luring dan pemutaran offline. Integrasikan yang dilindungi menggunakan DRM-X atau native Media3.

**Pilih pemutar Anda, simpan satu layanan lisensi.**Gunakan DRM-X 6.0 Android SDK untuk integrasi, atau tetap asli Anda Media3 pemutarKeduanya terhubung ke backend terotentikasi Anda dan Widevine. Android SDK mendukung streaming dan persisten luring VOD di kompatibel API 23 + perangkat, termasuk telepon, tablet, TV dan tertanam Android sistem. 

## Coba Android pemutar [ # ](#try-android-app)



**Unduh, pasang, tekan Play.**Aplikasi evaluasi gratis menggunakan video demo publik kami dan tanda tangan secara otomatis. Anda tidak perlu membangun APK atau masuk DRM-X kredensial untuk mencobanya. 

[ Unduh Android sampel APK ](https://docs.drm-x.com/downloads/android/1.0.0-preview.12/drmx-android-player-1.0.0-preview.12.apk)



**1.0.0-preview.12** · 7.02 MB · Android 6.0+ (API 23+) · DRM-X 6.0 Player · Diperbarui 2026-09-14


- Buka halaman ini pada telepon Android Anda dan unduh APK.
- Buka berkas yang diunduh. Jika Android bertanya, ijinkan instalasi dari peramban atau manajer berkas yang Anda gunakan, lalu tekan ** Pasang **.
- Buka ** DRM-X 6.0 Pemutar **, tetap terhubung, dan tekan ** Putar ** tombol. Aplikasi meminta lisensi streaming ketika Anda menekan Play.
- Buka ** Pengaturan ** untuk memilih kualitas, audio, kapsi, atau kecepatan putar. Putar telepon untuk melanjutkan dalam layar penuh.
- Tap **Unduh** untuk video saat ini atau **Unduh daftar putar** untuk semua VOD Item. Unduhan menggunakan rendition berlisensi tertinggi yang didukung hingga 1080p pada L1, atau 480p pada L3. **Putar luring** membuka daftar putar lokal yang komplit dengan next / previous, shuffle, repeat dan autoplay; **Online** kembali ke daftar putar penuh. **Renew** Memperbaharui lisensi yang dipilih saat tersambung.



Diunduh media yang sudah diantrian terus di latar belakang. Jika Android menghentikan aplikasi ketika menyiapkan lisensi atau trek, membuka kembali saat tersambung untuk menyelesaikan menyiapkan butir-butir yang tersisa. Unduhan yang tersimpan. Pemutaran offline hanya menggunakan media yang cached; tidak lengkap atau unduhan kadaluarsa memerlukan pelengkapan atau pembaharuan.



Widevine provisioning kegagalan (Media3 6002) sekarang gunakan relay server demokasi terotentikasi ke layanan Google resmi. Sebuah fungsi L3 CDM dapat menerima kunci SD. Perangkat tanpa implementasi Widevine yang bekerja memerlukan perbaikan perangkat / firmware; sebuah lisensi tidak dapat menambahkan dukungan DRM yang hilang.



Sampel APK adalah publik dan ditandatangani untuk evaluasi. AAR dan penuh SDK di bawah ini tetap tersedia melalui akun Konsol yang memenuhi syarat. Jauhkan kunci penandatanganan aplikasi sendiri dan login backend terpisah dari demo ini.

APK checksum dan USB instalasi

SHA-256:

```
8fa044b82c0abab7ce2a987f349524fd7ad1e78f3c897771f4764cb2aa521180
```

Dengan Android platform -tools dan sebuah otorisasi USB perangkat:

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

Instal APK ini melalui contoh resmi yang sudah ada tanpa uninstall itu. Kunci penandatanganan dan aplikasi yang sama dengan ID pertahankan pengunduhan, lisensi offline, posisi dan preferensi tersimpan. Jika membangun Anda sendiri menggunakan kunci penandatanganan lain, gunakan aplikasi terpisah ID untuk menjaga kedua aplikasi dan mempertahankan lisensi luring sampel.





## Apa yang baru dalam Android pemutar preview] 12 [ # ](#latest-player-update)



Dirilis 14 September 2026. Perbaikan download pemutaran membuat permintaan lisensi yang tak terduga ketika Widevine lisensi kadaluwarsa atau memiliki satu menit atau kurang. Ini bisa muncul sebagai Media3 galat `6004`, sebuah HTTP error ketika tersambung, atau DNS error ketika terputus.



pemutar sekarang restore lisensi yang diunduh secara lokal. Lisensi yang sah mempertahankan sisa waktu pemutaran mereka; lisensi yang kadaluwarsa menampilkan ** Hubungkan dan tekan Renew **. Peraturan keabsahan dan perlindungan Lisensi tetap dipaksakan oleh perangkat. Diagnostik juga termasuk status numerik HTTP ketika operasi jaringan gagal, tanpa mengekspos header permintaan, URL, atau lisensi tanggapan.


- Instal preview-12 melalui aplikasi resmi yang ada. Simpan data aplikasi dan unduh saat ini.
- Jika WayMaker atau video lain yang diunduh perlu diperbaharui, menyambung ke internet, pilih video itu dan tekan ** Renew **.
- Tunggu ** Lisensi diperbarui. Siap untuk bermain offline. **, lalu pilih ** Putar offline **. Video terenkripsi masih diunduh.



Peluncuran ini mempertahankan kontrol dan fitur playlist. Aplikasi evaluasi publik dan aplikasi yang tertanam pemutar mengidentifikasi previe.12; Konsol yang didistribusikan secara terpisah SDK tetap berada sebelum 10 / bundle 20260910.8.





## Apa yang baru dalam Android pemutar preview} 11 [ # ](#player-preview11)



Aplikasi evaluasi ini termasuk kontrol terbaru pemutar dan perbaikan playlist, dengan gaya interaksi konsisten di seluruh Android dan Apple pemutar.


- ** Kontrol di dalam video: ** tengah Play / Pause, Sebelumnya dan Selanjutnya untuk multi- video playlist, CC, Pengaturan dan tombol layar penuh. Kontrol menyembunyikan setelah dua detik sambil bermain; tekan video untuk mengungkapkan mereka lagi. 
- ** Captions and playing settings: ** gunakan CC untuk menampilkan atau menyembunyikan subtitle yang tersedia, dan Pengaturan untuk memilih bahasa, audio, kecepatan putar dan kualitas yang diijinkan. Pilihan offline hanya gunakan trek yang disertakan dalam unduhan. 
- ** Seret untuk memesan ulang: ** seret suatu butir playlist untuk mengubah posisinya. Perintah tersimpan bertahan dari sebuah aplikasi restart dan tercermin dalam daftar putar yang diunduh. Menghidupkan ulang menjaga video saat ini diputar. Sentuh penanganan untuk akses Pindah ke atas / Pindahkan ke bawah aksi. 
- ** Shuffle tersimpan dan ulangi: ** mode yang dipilih tetap disorot dan bertahan dalam pemutaran online dan offline. Dengan mengocok dan mengulang baik, video mengikuti urutan yang disimpan dan berhenti setelah item final. Menu playlist "Play next" aksi telah dihapus. 
- ** Lanjutkan di mana Anda berhenti: ** setiap video disimpan VOD posisi dikembalikan ketika Anda kembali ke pemutaran streaming atau diunduh. Kemajuan disimpan di perangkat ini untuk penampil saat ini; ini tidak sinkronisasi antar perangkat. 



Perubahan pemutar telah diverifikasi pada Samsung S24 Ultra, termasuk penyeretan playlist selama pemutaran, urutan tersimpan, putar resume dan download putar dengan Wi-Fi dan data mobile dinonaktifkan. Tampilan offline masih membutuhkan uncompleted download dan lisensi luring yang dapat dipakai.



Pemutakhiran ini evaluasi publik APK. Konsol yang didistribusikan secara terpisah SDK tetap `1.0.0-preview.10`, bundle `20260910.8`; instruksi integrasi yang ada di bawah ini mengacu pada bundel itu.





## 1. Pilih jalur integrasi [ # ](#choose-your-path)



[PATH A QUICKEST START**DRM-X pemutar**

Use DrmXPlayer, the phone sample and the offline manager. Keep your own login and business rules.

](#android)[PATH B KEEP YOUR UI**Media3 + DRM-X pembantu**

Pertahankan pemutar dan unduh tumpukan. Biarkan DrmXOfflineManager mengelola lisensi persisten.

](#existing-media3)[PATH C NATIVE ONLY**Media3 tanpa AAR**

Implemen kontrak backend yang sama dan lifecycle lisensi asli sendiri. Tidak ada DRM-X pemutar ketergantungan.

](#configure-media3) 

[Unduh ZIP atau AAR SDK Android di Console → Integrations](https://6.drm-x.com/console/integrations). Organisasi dengan langganan **Professional ($499/bulan) atau Enterprise** aktif dapat mengunduhnya. Akun Trial dan Standard tidak dapat mengunduh SDK. Dokumentasi ini, termasuk contoh Media3 native, tersedia untuk umum; pemutaran tetap memerlukan akun berhak akses, konten yang dipublikasikan, dan perangkat kompatibel.



Pratinjau saat ini: `1.0.0-preview.10` bundel pelanggan `20260910.8` · Media3 `1.11.0`. ZIP berisi sebuah repositori Maven lokal, Gradle wrapper, jengkel README, telepon /TV/ sampel asli, sebuah sampel telepon terinstall APK in `apks/`, dan catatan integrasi offline. Ini tidak berisi pelanggan kredensial atau film yang dilindungi.



## 2. Siapkan isi dan backend [ # ](#before-you-code)


- Paket dan menerbitkan Widevine -kompatibel dengan terenkripsi DASH judul dalam DRM-X Lingkungan. Salin yang tepat ** Diterbitkan Content ID **.
- Mengkonfigurasi server Anda Environment ID, Site Key dan Access KeyGunakan [yang dapat diunduh PHP integrasi](https://developer.drm-x.com/php-sample/) untuk memulai pekerjaan.
- Instal cek log masuk dan hak pengguna Anda. Aplikasi Anda memanggil backend Anda dengan token pengguna yang kurang terpakai; backend Anda memilih isi, kebijakan, dan URL terpercaya.
- Jalankan pemutaran daring pertama pada perangkat yang mampu melakukan fisik Widevine. Tambahkan akuisisi luring hanya setelah kontrak streaming bekerja.



Video terenkripsi dan lisensi DRM dikirim melalui jalur yang berbeda

CDN mengirimkan video terenkripsi. Backend Anda memeriksa akses, dan layanan lisensi DRM mengizinkan dekripsi pada perangkat yang kompatibel.

- 01**Penyimpanan / CDN**

Mengirimkan media terenkripsi ke pemutar.

- 02**Backend Anda**

Memeriksa hak menonton pengguna dan meminta otorisasi pemutaran dari DRM-X.

- 03**DRM-X**

Memvalidasi otorisasi pemutaran dan memproses permintaan lisensi DRM.

- 04**Pemutar + perangkat**

Menggunakan sistem DRM perangkat untuk memperoleh lisensi dan memutar media terenkripsi.

Pengiriman media dan lisensi terpisah. Hanya backend pelanggan yang menyimpan DRM-X server kredensial. 

| Ubah nilai ini | Di mana ia berada | Artinya | 
| --- | --- | --- |

| sessionEndpoint | Android | Endpoint-sesi putar HTTPS Anda, bukan server lisensi mentah URL. | 

| offlineEndpoint | Android, hanya luring | Anda memperoleh / memperbaharui / rilis otorisasi titik akhir. | 

| contentId | Android permintaan; backend validates | Pelihat yang diproteksi Content ID berhak untuk diakses. | 

| token akses pelanggan | App memori setelah login | Token pengguna Anda yang singkat, tidak pernah sebuah Site Key atau Access Key. | 

| Environment ID, Site Key, Access Key | Hanya konfigurasi rahasia server | DRM-X akun kredensial. Jangan pernah mengirim mereka dalam sebuah APK, HTML atau sumber kontrol. | 

| Kebijakan dan muatan terpercaya URL | Backend Anda | Pemetaan hak bisnis dan paket. Jangan terima ini dari aplikasi. | 



## 3. Pasang dan jalankan SDK [ # ](#install-sdk)



Buka yang diekstrak ZIP's root folder dalam Android Studio. JDK 17, Android SDK 36 dan Android Gradle Plugin 9.2.0; wrapper yang disertakan menggunakan Gradle 9.4.1. Perangkat minimal API adalah 23. Ketergantungan pembangunan memerlukan akses internet. Pilih `samples.phone` dan Jalankan. Nilai awalnya menggunakan backend demo publik dan judul; ikuti tandanya dan streaming kontrol sebelum beralih ke backend Anda sendiri.



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



Untuk aplikasi yang sudah ada, salin ZIP 's `maven/` folder ke dalam root projek Anda. Dalam `settings.gradle.kts`:



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



Dalam modul aplikasi Anda `build.gradle.kts`:



```
dependencies {
    implementation("com.haihaisoft.drmx:drmx-universal-player:1.0.0-preview.10")
}
```

Instal standalone AAR saja

Masukan AAR dalam modul aplikasi Anda `libs/`. Sebuah flat AAR tidak memiliki alternatif otomatis dependensi resolusi, jadi tambahkan tiga pencocokan Media3 ketergantungan sendiri. Jangan instal kedua bentuk.

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



Dalam `AndroidManifest.xml`, izinkan akses internet dan menonaktifkan backup untuk sampel minimal ini. Dalam aplikasi produksi yang membutuhkan backup, khususnya mengecualikan device- terikat metadata lisensi offline.



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



Cari contoh telepon `MainActivity.kt` untuk `drmx.sessionEndpoint`, `drmx.offlineEndpoint`, `drmx.contentId` dan `customer.loginEndpoint`. Ubah pengaturan fallback yang ditandai, atau lewatkan Intent extra dari aplikasi setelah log masuk. Pass `customer.accessToken` di runtime. ZIP README menjelaskan setiap parameter dan demo kait untuk diganti.



## Bangun suatu tes APK memakai AAR [ # ](#build-apk-from-aar)



Contoh ini menggunakan proyek telepon yang dapat dijalankan dari SDK ZIP dan secara eksplisit menghubungkan standar AAR. Pendekatan yang sama bekerja untuk aplikasi yang ada Android.


- Unduh dan ekstrak [ penuh Android SDK ZIP ](https://6.drm-x.com/console/integrations). Buka folder root di Android Studio.
- Simpan `libs/drmx-universal-player-1.0.0-preview.10.aar` dalam akar yang diekstrak. Gunakan JDK 17, Android SDK 36 dan yang disertakan Gradle wrapper.
- Dalam `samples/phone/build.gradle.kts`, ganti DRM-X Maven ketergantungan dengan entri berikut di dalam `dependencies`. Simpan sisa berkas build.



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



Hapus `implementation("com.haihaisoft.drmx:drmx-universal-player:1.0.0-preview.10")` sehingga sama SDK tidak disertakan dua kali. AAR membutuhkan tiga Media3 ketergantungan yang ditampilkan di atas.


- Untuk menjaga demo yang diunduh bersama dengan membangun pengujian Anda sendiri, ubah modul telepon `applicationId` ke `com.example.drmxtest`. Biarkan namespace nya tidak berubah bagi sampel yang diberikan ini.
- Bangun APK, lalu pasang pada USB- tersambung Android perangkat dengan USB debugging yang diijinkan.



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



Buka aplikasi tes dan tap Play. Sumber telepon yang diberikan menggunakan demo publik yang sama awalnya. Untuk menguji judul Anda sendiri, ganti sesi yang ditandai / offline / login titik akhir dan Published Content ID in `MainActivity.kt`, dan memasok otentikasi singkat milik pengguna anda sendiri pada waktu berjalan. Server anda menyimpan Site Key dan Access Key.



Kontrolnya berasal dari... `DrmXPlayerView` di AARAktivitas yang diberikan menunjukkan Play- to-otorisasi, rotasi responsif, kepemilikan download, pemutaran offline dan pembaharuan. Lihat. [contoh integrasi kontrol](#player-controls) di bawah ketika menambahkan tampilan ini ke aplikasi anda sendiri.





## 4. Permintaan server dan kebijakan baku [ # ](#server-request)



Aplikasi Android meminta backend Anda untuk sesi putar. Backend Anda mengkonfirmasi pengguna, verifikasi hak, peta Content ID untuk menerbitkan media dan panggilan DRM-X. Lihat [ Sesi Playback API ](https://docs.drm-x.com/id/api-reference/playback-sessions) untuk kontrak streaming lengkap.



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



Kemampuan konservatif di atas jangan menegaskan Widevine L1. Gunakan SDK 's kapabilitas pembantu, atau penilaian perangkat Anda sendiri, untuk perangkat aktual. Kemampuan klien membantu seleksi kompabilitas; mereka tidak pernah memberikan kualitas hak.



Sampel PHP bawaan bagi `multi-tier-standard` untuk putar daring. Pilih kebijakan tunggal yang didokumentasikan ketika paket Anda menggunakan satu kunci. Kebijakan Anda harus cocok dengan model kunci paket dan hak-hak penampil. Ubah konfigurasi server atau pengaturan lingkungannya `DRMX_LICENSE_POLICY_TEMPLATE`, bukan parameter permintaan dalam pemutar. Lihat [ lisensi kebijakan ](https://docs.drm-x.com/id/license-service/policies).



**Offline membutuhkan kebijakan sendiri gigih.**Set `playback_policy.persistent` untuk benar dalam kebijakan server- yang diterbitkan dan memilih lisensi, sewa, pemutaran dan pembaharuan durasi. Referensi menggunakan 7 hari / 3 hari / 48 jam / 7 hari; ini adalah contoh aturan bisnis, bukan persetujuan studio atau sebuah platform-seluruh metadata. Lisensi yang cocok kembali. Widevine tetap otoritatif.



## 5. Path A: putar melalui DrmXPlayer [ # ](#android)



Inside your Activity, create a `PlayerView`, use a secure window and call `prepare` after login. The wrapper exchanges the session, configures Widevine, applies the returned maximum height and releases the streaming reservation when stopped. Imports come from `com.haihaisoft.drmx.player` and 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()
```



Register `DrmXPlayerListener` untuk otorisasi, siap, bermain, dan error UI. Tampilkan kode kesalahan yang aman daripada membuang tanda-tanda lisensi atau respon tubuh. Gunakan sampel telepon yang disertakan untuk lifecycle lengkap, kontrol layar penuh dan daftar unduh.



## Kontrol pemutar dan rotasi tak terganggu [ # ](#player-controls)



Sentuh ** Putar ** pada video untuk memulai otorisasi. Membuka layar atau Pengaturan tidak memperoleh lisensi streaming. Jeda / lanjutkan, mengubah pengaturan dan memutar telepon mempertahankan sesi saat ini.



![DRM-X Android pemutar Pengaturan pada Samsung S24 Ultra, menampilkan Kualitas, Lagu Audio, Subtitles /CC, Kecepatan putar dan timer tidur.](https://docs.drm-x.com/assets/android-player-settings-preview4.png)Pengaturan aktual pada Samsung S24 Ultra. Sentuh baris untuk melihat opsi yang tersedia; submenu audio menampilkan nama bahasa lengkap. Kualitas dan bahasa yang tersedia bergantung pada konten, lisensi, dan perangkat. 

| Kendali | Apa yang bisa dilakukan pelanggan | 
| --- | --- |

| Kualitas | Pilih Auto atau didukung, resolusi berlisensi dari manifes. Pilihan tidak tersedia menjelaskan lisensi atau perangkat / DRM pembatasan. Auto mematuhi pembatasan yang sama. | 

| Trek audio | Pilih bahasa audio apapun yang didukung yang disediakan oleh paket. | 

| Subtitles / CC | Pilih bahasa judul atau mati. Tombol CC muncul ketika judul didukung ada dan menjungkirkannya secara langsung. | 

| Penampilan judul | Sesuaikan fonta, warna teks dan ukuran, warna latar belakang / jendela dan opasitas. Preferensi disimpan pada perangkat. | 

| Waktu tidur / kecepatan putar | Putar di 0.25-2 × kecepatan atau jeda secara otomatis setelah interval yang dipilih. | 

| Seek / layar lengkap | Scrub the timeline, seek 10 detik, or double-tap either side. Putar ke lansekap tanpa memulai ulang memutar. | 


Tiga aksi kompak di bawah gambar ini adalah ** Unduh ** (untuk penggunaan offline), ** Putar offline ** dan ** Renew ** (the luring lisensi offline). Sampel mengunduh satu versi yang diijinkan versi video rendition dan trek subtitle yang didukung; daftar menu luring hanya mengekspos media yang diunduh. Renovasi membutuhkan konektivitas.



**Salin log** di samping informasi footer salinan kesalahan baru-baru ini sebagai teks, sehingga Anda dapat mengirim dukungan rincian tanpa cuplikan layar. Laporan berisi laporan ID, operasi, SDK/Android/ versi perangkat, numerik CDM/Media3 error, frame stack yang dipilih, koneksi / download / keadaan putar dan sebuah jumlah pengulangan. Mereka tidak termasuk kredensial, URL media, kunci, kunci-set ID, data lisensi dan penuh CDM dump.



Sampel tersebut menyimpan hingga 32 laporan lokal (64 KB) selama lima jam. Ketika tersambung dan ditandatangani masuk, itu mengunggah kesalahan yang tertunda ke backend pelanggan yang terkonfigurasi, paling sekali per 30 detik. Server demo publik mempertahankan peringatan / kesalahan selama lima jam, kombinasi kejadian berulang, tutup berkas di 512 KB dan memangkas setiap menit. Kemajuan rutin dan pemutaran sukses tidak log. Integrasi suai harus secara eksplisit memasok asal mereka sendiri `drmx.diagnosticsEndpoint`; Kesalahan diagnostik tidak pernah mengganggu pemutaran.



**Lisensi Offline sudah digunakan?**Pratilik 5 perbaikan S24 kasus yang melaporkan CDM 368 dengan "Lisensi dimuat dalam sesi berbeda". Menekan Play offline lagi melanjutkan yang ada pemutar. Memperbaharui pertama rilis nya DRM sesi, kemudian memperbaharui lisensi yang tersimpan. Pengunduhan dan persisten lisensi dipertahankan; tidak ada app- data reset yang diperlukan. 

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



Daftar muatan telepon yang diberikan menangani orientasi dan perubahan ukuran layar; itu `onConfigurationChanged` mengubah pandangan yang sama dan pemutar. Simpan kedua bagian saat menyesuaikannya. Host yang menciptakan Aktivitas harus mempertahankan pemilik pemutaran dan mengatur ulang tampilan. Kematian proses berbeda dari rotasi dan perlu pemulihan keadaan normal. Pilihan 4K / 8K memerlukan isi yang cocok, lisensi hak akses dan dukungan perangkat / DRM; dukungan CDM tetap berlaku.



Untuk gubahan pemutar, stop and release the active offline DRM sesi sebelum memeriksa, memperbaharui atau melepaskan lisensi gigih yang sama. SDK's `player.stopForOfflineLicenseOperation { /* call manager here */ }` menunggu Media3 antrian putar untuk merilis media. Polos `stop()` antrian yang bekerja asynchronously. `checkLicense()` ketika lisensi yang sama sedang diputar; putar sendiri memvalidasi nya CDM negara sampel yang diberikan menunjukkan handoff lengkap dan menonaktifkan tindakan tumpang tindih selama pembaharuan.



## 6. Cari lisensi dan unduh media [ # ](#offline-download)



Siapkan pemutaran offline

Unduhan harus lengkap dan lisensi yang terikat pada perangkat harus masih berlaku.

- 01**Unduh media terenkripsi**

Saat online, unduh video serta trek audio dan subtitel yang diperlukan.

- 02**Dapatkan lisensi persisten**

Saat online, dapatkan lisensi Widevine yang terikat pada perangkat. Simpan Key-set ID yang bersifat opaque secara privat.

- 03**Periksa kesiapan**

Pastikan unduhan sudah lengkap dan lisensinya masih berlaku.

- 04**Putar secara offline**

Baca media dari cache dan pulihkan lisensi lokal. Perpanjangan dan pelepasan lisensi memerlukan koneksi internet.

Ready offline = complete encrypted media + a valid device- bound license. Not one places the other. 

```
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 */ }
}
```



The SDK Simpan yang buram Widevine kunci -set ID dan bukan metadata rahasia. CDM Dia tidak bertahan. DRM License Tokens, kunci mentah, tantangan atau bytes respon lisensi. Abaikan `com.haihaisoft.drmx.offline.licenses.v1.xml` dari backup.



Buat satu pelamar yang dimiliki Media3 `SimpleCache`, unduh manajer dan basis data. Gunakan `DownloadHelper` dengan MediItem berlisensi dan pilih video di dalam `maximumHeight`Dalam Media3 1.11 callback siap adalah `onPrepared(helper, tracksInfoUpdated)`Sampel telepon `enqueueDownload` dan `OfflineTestRuntime` tampilkan implementasi yang dapat dijalankan.



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



Unduh setiap bahasa yang ingin Anda ekspos luring. Memanggil `createDownloadRequest` tanpa kunci stream mengunduh semua trek, yang umumnya diboros untuk video adaptif. Tampilkan kemajuan unduh dan tunggu untuk `Download.STATE_COMPLETED`Untuk transfer latar belakang produksi, mengintegrasikan Media3 `DownloadService`, batas penyimpanan dan Android foreground- layanan / pemberitahuan persyaratan; antrian demo berjalan in- proses.



**Isi luring multi- kunci:**Media3 menyimpan kembali satu tombol -set ID untuk objek putar. Lisensi yang gigih harus berisi setiap kunci yang diperlukan untuk video, audio dan trek terenkripsi lainnya. Uji ini dengan kebijakan paket dan server asli Anda; mengaktifkan beberapa sesi tidak menggabungkan lisensi offline independen.



## 7. Path B: kembalikan dengan Anda yang ada Media3 pemutar [ # ](#existing-media3)



Baca download selesai dan gunakan kunci stream yang tepat. Pengunduhan dan tumpukan putar harus berbagi lokasi singgahan dan pemetaan kunci yang sama. Contoh ini sengaja mematikan akses upstream untuk pemutaran luring yang ketat:



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



Untuk menggunakan aliran offline yang sama dengan Path A, membangun `DrmXPlayer` dengan ini `mediaSourceFactory` dan memanggil `player.prepareOffline(playback)`. Pemulihan biasa menggunakan lisensi lokal CDM tanpa meminta yang baru. Entri cache yang hilang seharusnya menghasilkan kesalahan selama penerimaan terputus, bukan diam-diam beralih ke internet.



## 8. Path C: native Media3, tidak ada DRM-X AAR [ # ](#configure-media3)}



You are not required to use DrmXPlayer or DrmXOfflineManager. Add the three Media3 dependencies from step 3 and implement the same customer backend contract. Use `session.manifestUrl` (not `session.manifest.url`), the returned license URL and bounded authorization headers. Apply the returned maximum height and handle streaming session release on stop, replacement, end, failure and destruction.



Respon lengkap di bawah ini dikompilasi sebagai `samples:native` bersama dengan rilis SDK. Ini menangani HTTPS, respon terbatas dibaca, otorisasi latar belakang, tanggapan basi dan rilis reservasi. Ganti titik akhir yang ditandai / Content ID dan menyediakan tanda masuk Anda sendiri. Contoh adalah titik awal; tambah penyegaran produksi Anda, refresh, akun-switch, dan app- navigasi lifecycle.

Aktivitas streaming Asli (Kotlin, tidak ada DRM-X ketergantungan)

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

```



Untuk lisensi offline asli, gunakan `OfflineLicenseHelper.downloadLicense`, bertahan tombol buram -set ID Pribadi dan kembalikan dengan `MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID).setKeySetId(id)`pembantu terkompail berikut meliputi akuisisi, durasi pemeriksaan, pembaruan, rilis dan download / item putar. Ini adalah **bukan sebuah aplikasi download lengkap**: Anda masih memiliki cache, layanan download, kemajuan, metadata, pengikatan pengguna / perangkat dan terpercaya otorisasi operasi.

Pendukung lisensi asli (Kotlin, tidak ada DRM-X ketergantungan)

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

```



Sebelum memanggil pembantu tingkat rendah ini, validasi bahwa respon backend segar cocok dengan yang diminta Content ID, operasi, signed-in user / device dan otorisasi perioda validitas. Persist sebuah stabil licenseId dan terpercaya metadata manifest / track bersama kunci -set IDTidak pernah ada token, jalankan panggilan pembantu di benang pekerja. HTTP penggunaan header `setLicenseRequestHeaders` pada DRM konfigurasi; Widevine parameter opsional key-request adalah peta yang berbeda.



Integrasi baru harus digunakan [AndroidX Media3 DRM dukungan](https://developer.android.com/media/media3/exoplayer/drm)Legacy `com.google.android.exoplayer2` proyek harus mengikuti [panduan migrasi resmi](https://developer.android.com/media/media3/exoplayer/migration-guide)Dan janganlah kamu campur adukkan harta dan anak-anak kamu. AndroidX kelas atau asumsikan sampel compiles tidak berubah terhadap versi yang lebih tua.



## 9. Periksa, perbarui, rilis dan tukar bahasa [ # ](#lifecycle-and-offline-behavior)



These are separate actions. Run one at a time after the active offline DRM session has been released: use `player.stopForOfflineLicenseOperation { /* one operation below */ }` with DrmXPlayer, or release the existing native Media3 player first. Do not check a license while it is playing. A check is optional for displaying durations; it must not prevent renewal of an expired license.



```
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` membaca metadata; `checkLicense` queries otoritas lokal CDM durasi. Renew / release membutuhkan konektivitas dan baru operasional -spesifik otorisasi dari backend Anda. Menghapus media tidak merilis lisensi. Ketika diputus, rilis antrian untuk koneksi berikutnya daripada melaporkan bahwa server telah mengkonfirmasi itu. Tutup manajer ketika pemiliknya hancur.



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



Bangun pilihan dari trek yang tersedia dan matikan pilihan yang tak tersedia. Suatu preferensi tak dapat membuat trek bahasa Inggris absen dari paket. Pengguna Offline hanya dapat memilih bahasa yang diunduh. Pertahankan keadaan lisensi / cache di seluruh rotasi layar; layar penuh adalah tampilan / perubahan jendela, bukan alasan untuk memperoleh lisensi lain.



## 10. Mengimplementasikan titik akhir otorisasi luring [ # ](#offline-backend-contract)



Aplikasi memposting `contentId`, `contentType: "vod"`, `drmSystem: "widevine"`, `playbackMode: "offline"`, `operation: "acquire" | "renew" | "release"`, `offlineLicenseId` (kosong untuk akuisisi awal) dan jujur `platformCapabilities`. Verifikasi ID pembaruan milik pengguna, perangkat, dan isi. Pilih server- sisi kebijakan, keluarkan yang baru DRM License Token dan kembalikan kontrak yang cocok:



```
{
  "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
  }
}
```



Ini adalah placeholder yang jelas, bukan sebuah respon yang dapat dipakai kembali. Kembalikan nilai baru dari operasi yang sebenarnya. SDK juga mendukung `integrationMode: "token-proxy"` dengan kekurangan `drm.authorization` deskripsi. Kembalikan `Cache-Control: no-store`: tidak pernah log sukses badan otorisasi. Lihat proses yang dapat diunduh SDK 's `OFFLINE_INTEGRATION.md` dan PHP sampel titik luring untuk penanganan permintaan konkret.



## [ # ](#device-qualification)



### Widevine penyediaan dan pengujian jaringan Cina



Menyediakan memuat sertifikat perangkat sebelum akuisisi lisensi isi. Dari previe.9 demo telepon mengirim CDM's signed request through its authenticated composer HTTPS relay ke Google. Respon yang ditandatangani tidak berubah. Ini membantu ketika telepon tidak dapat mencapai layanan provisioning Google secara langsung; relay pelanggan harus masih dapat diakses.



Menyediakan 2.0 menggunakan kotak kunci pabrik. Menyediakan 4.0 menggunakan model kredensial perangkat yang berbeda. Versi ini dipisahkan dari L1/L3 tingkat keamanan, dan pemutar tidak menukar mereka. yang sudah ditetapkan. L3 telepon dapat bermain ketika telepon lain menunggu penyediaan perangkat. Sumber resmi publik yang ditinjau di sini tidak menetapkan bahwa Cina hanya mendukung 2.0. Lihat. [NXPPanduan integrasi perangkat](https://www.nxp.com/docs/en/user-guide/UG10158.pdf) dan [AndroidPengadaan implementasi](https://android.googlesource.com/platform/packages/apps/RemoteProvisioner/+/refs/tags/android-13.0.0_r15/src/com/android/remoteprovisioner/WidevineProvisioner.java).



Laporan galat awal: 10 membedakan transport langsung atau relay, waktu habis /DNS/TLS/HTTP gagal, HTTP status dan waktu berlalu. `provisioningModel: BootCertificateChain` Menunjukkan AOSP Model 4.0; `unknown` atau `other` tidak menetapkan 2.0. PHP log membedakan masukan yang ditolak dari kegagalan upstream Google. Laporan exclude signed payloads, URL, kredensial dan identifikasi perangkat yang unik.



Mutakhirkan APK di tempat, coba streaming dan satu download pada jaringan yang terpengaruh, kemudian pertahankan log Copy dan waktu perkiraan. Existing unduhan dan lisensi yang ditahan. Suatu relay tidak dapat diperbaiki tidak tersedia Widevine atau kredensial ditolak oleh perangkat / Google. Penerimaan nyata pada telepon yang terpengaruh diperlukan.



Dari preview-8, asli L2/L3 atau level-tidak-tahu Widevine putar terbatas ke SD (sampai 576p) sebelum decoding. Teruskan yang asli Android kemampuan melalui backend anda: sesi API engking perangkat lunak Widevine lisensi multi- kunci ke audio /SD Kuncinya. PHP sampel mempertahankan field ini dan juga caps permintaan offline. An SD rendition dan sebuah kebijakan mengizinkan perangkat lunak DRM dibutuhkan; hardware -only dan seragam tunggal-aturan keamanan tetap tidak berubah. Pengaturan menggunakan sebuah panel translucent tanpa meredupkan video. Numeric `cryptoErrorCode` termasuk dalam laporan kesalahan yang aman.



| Tes / gejala | Diharapkan pemeriksaan perilaku / berikutnya | 
| --- | --- |

| SDK unduh kembali 403 | Pilih sebuah organisasi dengan berlangganan Profesional atau Enterprise yang aktif. Suatu peningkatan yang direncanakan setelah percobaan tidak membuka SDK. | 

| Sesi 401 / 403 | Periksa log masuk, hak akses, Lingkungan, Terbitkan Content ID dan kebijakan. Jangan pernah lewati otentikasi. | 

| Offline gagal tapi kerja online | Periksa CDM durasi, selesai download, lokasi cache, kunci stream dan semua kunci lisensi yang dibutuhkan. | 

| Inggris / Cina hilang | Periksa metadata bahasa paket, trek aktual dan seleksi bahasa offline. | 

| L1, HDCP atau gagal decoder | Periksa perangkat keras fisik, provisioning, codec dan output persyaratan. Sebuah string kapabilitas tidak sertifikasi. | 

| Reboot, rotasi, upgrade | Surat ijin dan cache yang bertahan dari perubahan lifecycle yang didukung tanpa akuisisi duplikasi. Backup tidak boleh bermigrasi lisensi ke perangkat baru. | 

| Mode pesawat udara | Nonaktifkan Wi-Fi dan data mobile juga; restart aplikasi, putar secara lokal dan ganti setiap bahasa yang dijanjikan. | 

| Kadaluwarsa, tekanan penyimpanan, tombol akun | Tampilkan keadaan yang jelas tidak tersedia / memperbaharui, memulihkan media yang tidak lengkap, dan memaksakan akses pengguna / perangkat tanpa membocorkan katalog luring pengguna lain. | 


Kualifikasi setiap ponsel yang didukung, tablet, Android TV/Google TV, Fire TV atau tertanam Android model untuk Widevine, decoding aman, codec / resolusi, HDCP, eksternal display, clock changes and long disconnection. Tidak setiap Android perangkat memiliki sama DRM kemampuan isi Hollywood membutuhkan perlindungan pemegang hak cipta dan kebutuhan perangkat, tidak hanya sukses decoding.



**Tidak ada kesempatan untuk mendapatkan sebelum terputus?**Itu adalah penyebaran yang berbeda: masalah layanan on-board yang memenuhi syarat atas jaringan lokal selama perjalanan. Ini tidak difungsikan hanya dengan menggunakan SDK. DRM-X dapat bekerja dengan Anda pada penyebaran custom meliputi perizinan penyedia, penyediaan, perlindungan kunci, dan tes penerimaan terputus sepenuhnya. Lihat [on- board DRM untuk pesawat, bus, kereta api dan kapal](https://multi-drm.drm-x.com/id/solutions/on-board-drm).



## Universal Player playlist dan shared control [ # ](#universal-player-playlists-and-shared-controls)



The Android preview 11 dan aplikasi evaluasi kemudian dan saat ini Android/ Sumber pengembangan Apple berbagi in- video Sebelumnya / Play- Jeda / Berikutnya, CC, Pengaturan dan kontrol layar penuh dengan dua- detik bersembunyi selama pemutaran. Androidmenu playlist tidak lagi termasuk "Play next". Pengaturan pilihan / reappect tetap disorot dan tetap berjalan di restarts. Dengan kedua off, putar mengikuti urutan playlist yang tersimpan dan berhenti di video akhir. Seret handle urutan ulang item tanpa memulai ulang video saat ini; urutan tersimpan juga berlaku untuk playlist yang telah diunduh. Sumber terbaru APIs belum termasuk dalam prematur Console SDK bundel.



Supply the compenent / viewer `storageNamespace` to that `DrmXPlayer` and `playerView.storageNamespace` before setting the playlist. VOD progress is save periocally and on pause / stop, then resume for that Content ID in streaming or download playing. Live playing does not use a saved VOD position. Selesai unduhan mempertahankan perilaku yang sama / resume tanpa sumber media jaringan. Keadaan lokal ini tidak mensinkronkan kemajuan.



Edisi Web dan Android memakai panel pengaturan gelap konsisten dengan kendali biru kompak. Dukungan playlist berikutnya / sebelumnya, shuffle, ulangi satu / semua dan dapat dibatalkan otomatis muka. Setiap item menggunakan permintaan putar yang berwenang baru. Bab dan metadata transkrip berasal dari katalog konten Anda.



Kecepatan putar, timer tidur, takarir, kualitas yang diijinkan, bahasa audio, penanda sesi dan penjatuhan A-B tersedia. Jalan pintas papan ketik, resume opsional, transkrip pencarian dan didukung peramban PiP. Android mempertahankan asli Widevine pemutakhiran dan pembaharuan. Casting dan proses perempatan memerlukan integrasi aplikasi terpisah.



### Pengaturan logo profesional dan Enterprise



Branding tetap berada di permukaan video. Buka Pengaturan Berkas Tentang pemutar untuk logo yang berwenang, informasi produk dan versi pemutar. Web juga menawarkan informasi ini pada right- klik. Demo telepon umum termasuk WayMaker, Kebaikan Allah, Minyak Baru dan 4K Tes Video; setiap pemilihan menerima otorisasi sendiri.



Atur `playerBranding` dalam backend terpercaya Anda ke `{"mode":"hidden"}` atau `{"mode":"custom","logoUrl":"https://your-site.example/logo.png","label":"Your brand"}`. Cek platform yang aktif Berlangganan Profesional. Rencana lain menggunakan `{"mode":"default"}`; permintaan custom / hidden tidak sah kembali 403. Ini terpisah dari watermark keamanan.



Pengunduhan PHP termasuk daftar putar yang dapat disunting dan konfigurasi branding. Android pelanggan ZIP termasuk contoh daftar putar callback. Simpan otorisasi playlist dalam backend Anda; jangan pernah embed API klien kredensial dalam baik pemutar.

<!-- Canonical: https://docs.drm-x.com/id/sdk/android -->
<!-- Language: id -->
