# Android SDK und natives Media3

Laden Sie die Android Player mit In-Video-Steuerelementen, Draggable-Wiedergabelisten, gespeichertem Shuffle/Repeat und Offline-Lebenslauf herunter.Integrieren Sie die geschützte Wiedergabe mit DRM-X oder native Media3.

**Wählen Sie Ihre Player, halten Sie einen Lizenzdienst.**Verwenden Sie die DRM-X 6.0 Android SDK für eine geführte Integration oder behalten Sie Ihre native Media3 Player. Beide verbinden sich mit Ihrem authentifizierten Backend und Widevine. Die Android SDK unterstützt Streaming und persistent offline VOD auf kompatiblen API 23+ Geräten, einschließlich Telefonen, Tablets, Fernsehern und eingebetteten Android Systemen. 

## Probieren Sie die Android Player[#](#try-android-app)



**Download, Installation, drücken Sie Play.**Die kostenlose Evaluierungs-App nutzt unser öffentliches Demo-Video und meldet sich automatisch an. Sie müssen keine APK erstellen oder DRM-X Zugangsdaten eingeben, um es auszuprobieren. 

[Download Android sample 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 · Aktualisiert 2026-09-14


- Öffnen Sie diese Seite auf Ihrem Android Telefon und laden Sie die APK herunter.
- Wenn Android fragt, erlauben Sie die Installation aus dem Browser oder Dateimanager, den Sie verwendet haben, und tippen Sie auf **Install **.
- Öffnen Sie **DRM-X 6.0 Player**, bleiben Sie in Verbindung und tippen Sie auf die Schaltfläche des Videos **Play**. Die App fordert eine Streaming-Lizenz an, wenn Sie Play drücken.
- Öffnen Sie **Settings**, um Qualität, Audio, Bildunterschriften oder Wiedergabegeschwindigkeit auszuwählen. Drehen Sie das Telefon so, dass es im Vollbildmodus fortgesetzt wird.
- Tap **Download** für das aktuelle Video oder **Downloadplaylist** für alle VOD Items. Downloads verwenden die höchste unterstützte lizenzierte Wiedergabe bis zu 1080p auf L1 oder 480p auf L3. **Play offline** öffnet die komplette lokale Playlist mit next/previous, shuffle, repeat and autoplay; **Go online** kehrt zur vollen Playlist zurück. **Renew** erneuert die ausgewählte Lizenz während der Verbindung.



Wenn Android die App während der Vorbereitung von Lizenzen oder Tracks stoppt, öffnen Sie sie erneut, während Sie die Vorbereitung der verbleibenden Elemente abschließen. Bestehende Downloads werden beibehalten. Die Offline-Wiedergabe verwendet nur zwischengespeicherte Medien; unvollständige oder abgelaufene Downloads erfordern den Abschluss oder die Erneuerung.



Widevine Provisioning Failures (Media3 6002) verwenden jetzt ein authentifiziertes Demo-Server-Relay an den offiziellen Google-Dienst. Ein funktionierender L3 CDM kann SD Schlüssel empfangen. Ein Gerät ohne funktionierende Widevine Implementierung benötigt eine Geräte-/Firmware-Fix; eine Lizenz kann keine fehlende DRM Unterstützung hinzufügen.



Das Sample APK ist öffentlich und zur Auswertung signiert. Die AAR und full SDK unten bleiben über berechtigte Konsolenkonten verfügbar. Halten Sie Ihren eigenen Signaturschlüssel für die Produktions-App und Ihr Backend-Login von dieser Demo getrennt.

APK Prüfsumme und USB Installation

SHA-256:

```
8fa044b82c0abab7ce2a987f349524fd7ad1e78f3c897771f4764cb2aa521180
```

Mit Android Plattform-Tools und einem autorisierten USB Gerät:

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

Installieren Sie diese APK über dem vorhandenen offiziellen Sample, ohne es zu deinstallieren. Derselbe Signaturschlüssel und die gleiche Anwendung ID bewahren Downloads, Offline-Lizenzen, gespeicherte Positionen und Präferenzen. Wenn Ihr eigener Build einen anderen Signaturschlüssel verwendet, verwenden Sie eine separate Anwendung ID, um beide Apps zu behalten und die Offline-Lizenzen des Samples zu bewahren.





## Was ist neu in Android Player preview.12[#](#latest-player-update)



Veröffentlicht am 14. September 2026. Behebt die heruntergeladene Wiedergabe und stellt eine unerwartete Lizenzanforderung ein, wenn eine Widevine Lizenz abgelaufen ist oder eine Minute oder weniger übrig bleibt. Dies könnte als Media3 Fehler `6004`, ein HTTP Fehler während der Verbindung oder ein DNS Fehler während der Verbindung erscheinen.



Die Player stellt nun die heruntergeladene Lizenz lokal wieder her. Gültige Lizenzen behalten ihre verbleibende Wiedergabezeit; abgelaufene Lizenzen zeigen **Connect and tap Renew**. Lizenzgültigkeit und Schutzregeln bleiben vom Gerät durchgesetzt. Diagnosen enthalten auch den numerischen HTTP Status, wenn ein Netzwerkvorgang fehlschlägt, ohne Anforderungs-Header, URLs oder Lizenzantworten freizulegen.


- Installieren Sie preview.12 über die bestehende offizielle App. Behalten Sie Ihre aktuellen App-Daten und Downloads.
- Wenn WayMaker oder ein anderes heruntergeladenes Video erneuert werden muss, verbinden Sie sich mit dem Internet, wählen Sie dieses Video aus und tippen Sie auf **Renew**.
- Warten Sie auf **License erneuert. Bereit, offline zu spielen.**, dann wählen Sie **Play offline**. Das verschlüsselte Video bleibt heruntergeladen.



Diese Version behält die Steuerelemente und Wiedergabelistenfunktionen von preview.11 bei. Die öffentliche Bewertungs-App und ihre eingebettete Player identifizieren preview.12; die separat verteilte Konsole SDK bleibt preview.10 / bundle 20260910.8.





## Was ist neu in Android Player preview.11[#](#player-preview11)



Diese Evaluierungs-App enthält die neuesten Player-Steuerelemente und Playlist-Verbesserungen mit einem konsistenten Interaktionsstil für unsere Android und Apple Player.


- **Steuerelemente im Video:** zentriert Play/Pause, Vorherig und Nächstes für Multi-Video-Wiedergabelisten, CC, Einstellungen und eine Vollbild-Taste. Steuerelemente verstecken sich nach zwei Sekunden während der Wiedergabe; tippen Sie auf das Video, um sie wiederzugeben. 
- **Captions and Playback settings:** use CC to show or hide available subtitles, and Settings to choose caption language, audio, playback speed and allowed quality. Offline choices use only tracks included in the download. 
- **Drag to reorder:** drag a playlist item's handle to change its position. Die gespeicherte Order überlebt einen App-Neustart und wird in der heruntergeladenen Playlist reflektiert. Reordering hält das aktuelle Video abgespielt. Tippen Sie auf den Griff für zugängliche Move up / Move down Aktionen. 
- **Gespeichertes Mischen und Wiederholen: ** ausgewählte Modi bleiben hervorgehoben und überleben Neustarts in der Online- und Offline-Wiedergabe. Mit Mischen und Wiederholen beide aus, Videos folgen der gespeicherten Reihenfolge und stoppen nach dem letzten Element. 
- **Dort fortsetzen, wo Sie aufgehört haben:** Beim erneuten Streaming oder Abspielen eines Downloads wird die gespeicherte VOD-Position jedes Videos wiederhergestellt. Der Fortschritt wird auf diesem Gerät für den aktuellen Zuschauer gespeichert und nicht zwischen Geräten synchronisiert. 



Die Player-Änderungen wurden auf einem Samsung S24 Ultra verifiziert, einschließlich des Ziehens der Playlist während der Wiedergabe, der gespeicherten Reihenfolge nach dem Relaunch, des Wiedergabe-Lebenslaufs und der heruntergeladenen Wiedergabe mit deaktiviertem WLAN und mobilen Daten.



Diese Version aktualisiert die öffentliche Bewertung APK. Die separat verteilte Konsole SDK bleibt `1.0.0-preview.10`, bundle `20260910.8`; die vorhandenen Integrationsanweisungen unten beziehen sich auf dieses bundle.





## 1. Wählen Sie einen Integrationspfad[#](#choose-your-path)



[PATH A · QUICKEST START**DRM-X Player**

Verwenden Sie DrmXPlayer, das Telefonbeispiel und den Offline-Manager.Behalten Sie Ihre eigenen Anmelde- und Geschäftsregeln bei.

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

Halten Sie einen vorhandenen Player und laden Sie Stack herunter. Lassen Sie DrmXOfflineManager persistente Lizenzen verwalten.

](#existing-media3)[PATH C · NATIVE ONLY**Media3 ohne die AAR**

Implementieren Sie den gleichen Backend-Vertrag und den nativen Lizenzlebenszyklus selbst. DRM-X Player Abhängigkeit.

](#configure-media3) 

[Laden Sie die Android SDK-ZIP oder AAR unter Console → Integrations herunter](https://6.drm-x.com/console/integrations). Organisationen mit aktivem **Professional ($499/Monat) oder Enterprise** sind berechtigt. Trial- und Standard-Konten können das SDK nicht herunterladen. Diese Dokumentation einschließlich nativer Media3-Beispiele ist öffentlich; für die Wiedergabe sind ein berechtigtes Konto, veröffentlichte Inhalte und ein kompatibles Gerät erforderlich.



Aktuelle Vorschau: `1.0.0-preview.10` · Customer Bundle `20260910.8` · Media3 `1.11.0`. Das ZIP enthält ein lokales Maven-Repository, Gradle wrapper, kommentierte README, Telefon/TV/native Samples, ein installierbares Telefonbeispiel APK in `apks/` und Offline-Integrationshinweise. Es enthält keinen Kunden Zugangsdaten oder geschützte Filme.



## 2. Bereiten Sie Ihre Inhalte und Backend[#](#before-you-code)


- Verpacken und veröffentlichen Sie einen Widevine-kompatiblen verschlüsselten DASH Titel in Ihrer DRM-X Umgebung. Kopieren Sie den genauen **Published Content ID**.
- Konfigurieren Sie Ihren Server Environment ID, Site Key und Access Key. Verwenden Sie [the downloadable PHP integration](https://developer.drm-x.com/php-sample/) für einen funktionierenden Ausgangspunkt.
- Implementieren Sie Ihre Benutzeranmeldung und Berechtigungsprüfung. Ihre App ruft Ihr Backend mit seinem kurzlebigen Benutzertoken auf; Ihr Backend wählt den Inhalt, die Richtlinien und die vertrauenswürdigen URLs aus.
- Führen Sie die Online-Wiedergabe zuerst auf einem physischen Widevine-fähigen Gerät aus. Fügen Sie die Offline-Akquisition erst hinzu, nachdem der Streaming-Vertrag funktioniert.



Verschlüsselte Videos und DRM-Lizenzen werden getrennt übertragen

Das CDN liefert verschlüsselte Videos. Ihr Backend prüft den Zugriff; der DRM-Lizenzdienst autorisiert die Entschlüsselung auf einem kompatiblen Gerät.

- 01**Speicher / CDN**

Liefert verschlüsselte Medien an den Player.

- 02**Ihr Backend**

Prüft die Zugriffsberechtigung des Zuschauers und fordert bei DRM-X die Wiedergabeautorisierung an.

- 03**DRM-X**

Validiert die Wiedergabeautorisierung und verarbeitet die DRM-Lizenzanfrage.

- 04**Player + Gerät**

Verwendet das DRM-System des Geräts, um eine Lizenz zu erhalten und die verschlüsselten Medien abzuspielen.

Medienzustellung und Lizenzzustellung sind getrennt. Nur das Kunden-Backend hält DRM-X Server Zugangsdaten. 

| Ändern Sie diesen Wert | Wohin es gehört | Bedeutung | 
| --- | --- | --- |

| sessionEndpoint | Android | Dein HTTPS Playback-Session-Endpunkt, nicht der rohe Lizenzserver URL. | 

| offlineEndpoint | Android, nur offline | Ihr Acquiring / Renew / Release Autorisierungs-Endpunkt. | 

| contentId | Android Request; Backend validiert | Das Veröffentlichte geschützt Content ID ist der Betrachter berechtigt, darauf zuzugreifen. | 

| Kundenzugangs-Token | App-Speicher nach dem Login | Ihr kurzlebiges Benutzer-Token, niemals ein Site Key oder Access Key. | 

| Environment ID, Site Key, Access Key | Server geheime Konfiguration nur | DRM-X Konto ZugangsdatenVerschiffen Sie sie niemals in einem APK, HTML oder Source Control. | 

| Politik und vertrauenswürdiges Manifest URL | Ihr Backend | Geschäftsberechtigung und Paketzuordnung. Akzeptieren Sie diese nicht aus der App. | 



## 3. Installieren und Ausführen der SDK[#](#install-sdk)



Öffnen Sie den Stammordner des extrahierten ZIP in Android Studio. Verwenden Sie JDK 17, Android SDK 36 und Android Gradle Plugin 9.2.0; der enthaltene Wrapper verwendet Gradle 9.4.1. Minimum Device API ist 23. Build-Abhängigkeiten erfordern Internetzugang. Wählen Sie `samples.phone` und Run. Seine Anfangswerte verwenden das öffentliche Demo-Backend und den Titel; folgen Sie den Anmelde- und Streaming-Steuerelementen, bevor Sie zu Ihrem eigenen Backend wechseln.



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



Für eine bestehende App kopieren Sie die ZIP„s `maven/` Ordner in Ihrem Projekt root. In `settings.gradle.kts`:



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



In Ihrem App-Modul `build.gradle.kts`:



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

Installieren Sie stattdessen das Standalone AAR

Put the AAR in Ihrem App-Modul `libs/`eine flache AAR hat keine automatische transitive Abhängigkeitsauflösung, also fügen Sie die drei passenden Media3 Abhängigkeiten selbst. Nicht beide Formulare installieren.

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



In `AndroidManifest.xml`, erlauben Sie den Internetzugang und deaktivieren Sie Backups für diese minimale Stichprobe.In einer Produktions-App, die Backups benötigt, schließen Sie stattdessen speziell gerätegebundene Offline-Lizenzmetadaten aus.



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



Suchen Sie die `MainActivity.kt` für `drmx.sessionEndpoint`, `drmx.offlineEndpoint`, `drmx.contentId` und `customer.loginEndpoint`. Ändern Sie die markierten Fallback-Einstellungen oder übergeben Sie diese Absichts-Extras aus Ihrer Anwendung nach dem Login. Passen Sie `customer.accessToken` zur Laufzeit. Der ZIP README erklärt jeden Parameter und welche Demo-Hooks zu ersetzen sind.



## Erstellen Sie einen Test APK mit dem AAR[#](#build-apk-from-aar)



Dieses Beispiel verwendet das lauffähige Telefonprojekt aus dem vollständigen SDK ZIP und verknüpft explizit das Standalone AAR. Der gleiche Ansatz funktioniert für eine bestehende Android App.


- Laden Sie den [full Android SDK ZIP](https://6.drm-x.com/console/integrations) herunter und extrahieren Sie den Stammordner in Android Studio.
- Bleiben `libs/drmx-universal-player-1.0.0-preview.10.aar` in der extrahierten Wurzel. Verwendung JDK 17, Android SDK 36 und der mitgelieferte Gradle wrapper.
- Ersetzen Sie in `samples/phone/build.gradle.kts` die DRM-X Maven-Abhängigkeit durch die folgenden Einträge innerhalb `dependencies`.



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



Entfernt `implementation("com.haihaisoft.drmx:drmx-universal-player:1.0.0-preview.10")` Somit ist das gleiche SDK ist nicht zweimal enthalten. Eine Wohnung AAR braucht die drei Media3 Abhängigkeiten wie oben dargestellt.


- Um die heruntergeladene Demo neben Ihrem eigenen Test Build installiert zu halten, ändern Sie das Telefonmodul `applicationId` zu `com.example.drmxtest`. Lassen Sie den Namensraum für dieses gelieferte Beispiel unverändert.
- Erstellen Sie die APK und installieren Sie sie dann auf einem USB- verbundenen Android Gerät mit USB autorisiertem Debugging.



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



Um deinen eigenen Titel zu testen, ersetze die markierten Session/Offline/Login-Endpunkte und Veröffentlichte Content ID in `MainActivity.kt` und gib die kurzlebige Authentifizierung deines eigenen Benutzers zur Laufzeit. Dein Server behält die Site Key und Access Key.



Die Kompaktsteuerung kommt von `DrmXPlayerView` in der AAR. Die mitgelieferte Aktivität zeigt Play-to-authorize, responsive Rotation, Download-Besitz, Offline-Wiedergabe und Erneuerung. Siehe [das Steuerintegrationsbeispiel](#player-controls) unten, wenn Sie diese Ansicht zu Ihrer eigenen App hinzufügen.





## 4. Serveranforderung und Standardrichtlinie[#](#server-request)



Die Android-App fragt dein Backend nach einer Playback-Sitzung. Dein Backend authentifiziert den Benutzer, überprüft die Berechtigung, ordnet Content ID zu veröffentlichten Medien und ruft DRM-X auf. Siehe [Playback Session API](https://docs.drm-x.com/de/api-reference/playback-sessions) für den kompletten Streaming-Vertrag.



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



Die obigen konservativen Fähigkeiten behaupten nicht Widevine L1. Verwenden Sie den Fähigkeitshelfer des SDK oder Ihre eigene Gerätebewertung für tatsächliche Geräte. Client-Fähigkeiten unterstützen die Kompatibilitätsauswahl; sie gewähren niemals Qualitätsansprüche.



Die PHP-Sample ist standardmäßig `multi-tier-standard` für die Online-Wiedergabe. Wählen Sie die dokumentierte Single-Key-Richtlinie aus, wenn Ihr Paket einen Schlüssel verwendet. Ihre Richtlinie muss mit dem Paketschlüsselmodell und den Rechten des Betrachters übereinstimmen. Ändern Sie die Serverkonfiguration oder die `DRMX_LICENSE_POLICY_TEMPLATE`-Umgebungseinstellung, nicht einen Abfrageparameter in der Player. Siehe [ Lizenzrichtlinien](https://docs.drm-x.com/de/license-service/policies).



**Offline braucht eine eigene hartnäckige Politik.**Setz `playback_policy.persistent` Die Referenz verwendet 7 Tage / 3 Tage / 48 Stunden / 7 Tage; dies sind beispielhafte Geschäftsregeln, keine Studio-Genehmigung oder ein plattformweiter Standard. Widevine Lizenz bleibt autoritativ.



## 5. Pfad A: durch DrmXPlayer[#](#android) spielen



Erstellen Sie innerhalb Ihrer Aktivität ein `PlayerView`, verwenden Sie ein sicheres Fenster und rufen Sie `prepare` nach dem Anmelden an. Der Wrapper tauscht die Sitzung aus, konfiguriert Widevine, wendet die zurückgegebene maximale Höhe an und gibt die Streaming-Reservierung frei, wenn er angehalten wird. Importe kommen von `com.haihaisoft.drmx.player` und 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()
```



Registrieren Sie sich `DrmXPlayerListener` für Autorisieren, Bereitstellen, Abspielen und Fehler UI. Zeigen Sie sichere Fehlercodes an, anstatt Lizenz-Tokens oder Response Bodys zu entsorgen. Verwenden Sie das mitgelieferte Telefonbeispiel für den gesamten Lebenszyklus, Vollbild-Steuerelemente und Download-Warteschlange.



## Spielerkontrollen und ununterbrochene Rotation[#](#player-controls)



Tippen Sie auf **Play** im Video, um mit der Autorisierung zu beginnen. Das Öffnen des Bildschirms oder der Einstellungen erhält keine Streaming-Lizenz. Pause/resume, Ändern der Einstellungen und Drehen des Telefons bewahren Sie die aktuelle Sitzung bei.



![DRM-X Android Player Einstellungen auf einem Samsung S24 Ultra, die Qualität, Audiospur, Untertitel/CC, Wiedergabegeschwindigkeit und Sleep-Timer anzeigen.](https://docs.drm-x.com/assets/android-player-settings-preview4.png)Tatsächliche Einstellungen auf einem Samsung S24 Ultra. Tippen Sie auf eine Zeile, um die verfügbaren Optionen anzuzeigen; das Audio-Untermenü zeigt vollständige Sprachnamen an. Verfügbare Qualität und Sprachen hängen vom Inhalt, der Lizenz und dem Gerät ab. 

| Kontrolle | Was Kunden tun können | 
| --- | --- |

| Qualität | Wählen Sie Auto oder eine unterstützte, lizenzierte Auflösung aus dem Manifest. Nicht verfügbare Optionen erklären die Lizenz oder die Einschränkung Device/DRM. Auto befolgt die gleichen Einschränkungen. | 

| Audiotracks | Wählen Sie eine vom Paket bereitgestellte unterstützte Audiosprache aus. | 

| Untertitel/CC | Wählen Sie eine Untertitelsprache oder Aus. Die Schaltfläche CC wird angezeigt, wenn unterstützte Untertitel vorhanden sind, und schaltet sie direkt um. | 

| Aussehen der Bildunterschrift | Passen Sie Schriftart, Textfarbe und -größe, Hintergrund-/Fensterfarben und Deckkraft an. Präferenzen werden auf dem Gerät gespeichert. | 

| Wiedergabegeschwindigkeit / Sleep Timer | Spielen Sie mit 0,25-2x Geschwindigkeit oder pausieren Sie automatisch nach einem gewählten Intervall. | 

| Suchen / Vollbild | Scheuern Sie die Zeitleiste, suchen Sie 10 Sekunden oder tippen Sie auf beide Seiten. Drehen Sie in die Landschaft, ohne die Wiedergabe neu zu starten. | 


Die drei kompakten Aktionen unter dem Bild sind **Download** (für die Offline-Nutzung), **Play offline** und **Renew** (die Offline-Lizenz). Das Sample lädt eine erlaubte Videowiedergabe und die unterstützten Audio-/Untertitelspuren herunter; Offline-Menüs zeigen nur heruntergeladene Medien. Renewal erfordert Konnektivität.



**Copy log** neben dem Informationsfuß kopiert aktuelle Fehler als Text, so dass Sie die Details ohne Screenshot unterstützen können. Berichte enthalten einen Bericht ID, Operation, SDK/Android/Device Version, numerische CDM/Media3 Fehler, ausgewählte Stackframes, Verbindungs-/Download-/Wiedergabezustand und eine Wiederholungszählung. Sie schließen Zugangsdaten, Medien-URLs, Schlüssel, Schlüsselsatz-IDs, Lizenzdaten und vollständige CDM Dumps aus.



Die Probe behält bis zu 32 lokale Berichte (64 KB) für fünf Stunden. Wenn sie angeschlossen und angemeldet ist, lädt sie ausstehende Fehler in das konfigurierte Kunden-Backend hoch, höchstens einmal pro 30 Sekunden. Der öffentliche Demo-Server behält Warnungen/Fehler für fünf Stunden, kombiniert wiederholte Ereignisse, kappt die Datei auf 512 KB und beschneidet jede Minute. Routinefortschritt und erfolgreiche Wiedergabe werden nicht protokolliert. Benutzerdefinierte Integrationen müssen explizit ihren eigenen Ursprung angeben `drmx.diagnosticsEndpoint`; Diagnosefehler unterbrechen niemals die Wiedergabe.



**Offline-Lizenz bereits im Einsatz?**Preview.5 behebt den S24-Fall, der gemeldet wurde CDM 368 mit "Lizenz geladen in einer anderen Sitzung". Durch Drücken von Play offline wird die bestehende Player wieder aufgenommen. Renew gibt zuerst seine DRM-Sitzung frei, erneuert dann die gespeicherte Lizenz. Der Download und die persistente Lizenz bleiben erhalten; es ist kein Zurücksetzen von App-Daten erforderlich. 

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



Das mitgelieferte Telefonmanifest behandelt Orientierungs- und Bildschirmgrößenänderungen; seine `onConfigurationChanged` ändert die Größe der gleichen Aktivität und Player. Behalten Sie beide Teile bei der Anpassung. Ein Host, der Aktivitäten neu erstellt, sollte seinen Wiedergabebesitzer behalten und die Ansicht neu anfügen. Prozesstod unterscheidet sich von Rotation und benötigt eine normale Zustandswiederherstellung. Die 4K/8K-Optionen erfordern übereinstimmende Inhalte, Lizenzberechtigung und Device/DRM-Unterstützung; die CDM bleibt autoritativ.



Für benutzerdefinierte Player, stoppen und veröffentlichen Sie die aktive Offline-DRM-Sitzung, bevor Sie die gleiche persistente Lizenz überprüfen, erneuern oder freigeben. Der `player.stopForOfflineLicenseOperation { /* call manager here */ }` von SDK wartet auf die Media3-Wiedergabe-Warteschlange, um die Medien freizugeben. Plain `stop()` Schlangen, die asynchron arbeiten. Führen sie keine separate `checkLicense()` aus, während die gleiche lizenz gespielt wird; die wiedergabe selbst validiert ihren CDM-zustand. Die gelieferte Probe zeigt die vollständige Übergabe und deaktiviert überlappende Aktionen während der Erneuerung.



## 6. Erwerben Sie eine Lizenz und laden Sie die Medien herunter[#](#offline-download)



Offline-Wiedergabe vorbereiten

Sie benötigen sowohl einen vollständigen Download als auch eine gültige, an das Gerät gebundene Lizenz.

- 01**Verschlüsselte Medien herunterladen**

Laden Sie bei bestehender Internetverbindung das Video sowie die benötigten Audio- und Untertitelspuren herunter.

- 02**Persistente Lizenz abrufen**

Rufen Sie bei bestehender Internetverbindung die gerätegebundene Widevine-Lizenz ab. Speichern Sie ihre undurchsichtige Key-set ID geschützt.

- 03**Bereitschaft prüfen**

Prüfen Sie, ob der Download vollständig und die Lizenz gültig ist.

- 04**Offline wiedergeben**

Lesen Sie die zwischengespeicherten Medien und stellen Sie die lokale Lizenz wieder her. Verlängerung und Freigabe erfordern eine Internetverbindung.

Offline Ready = vollständige verschlüsselte Medien + eine gültige gerätegebundene Lizenz. 

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



Die SDK Rettet den Undurchsichtigen Widevine Schlüsselsatz ID und nicht geheimen Metadaten. CDM hält die Lizenz. Es bleibt nicht bestehen DRM License Tokens, Rohschlüssel, Challenges oder Lizenzantwortbytes ausschließen `com.haihaisoft.drmx.offline.licenses.v1.xml` von Backups.



Erstellen Sie eine anwendungseigene Media3 `SimpleCache`, Download-Manager und Datenbank. Verwenden Sie `DownloadHelper` mit dem lizenzierten MediaItem und wählen Sie das Video innerhalb `maximumHeight` aus. In Media3 1.11 ist der vorbereitete Rückruf `onPrepared(helper, tracksInfoUpdated)`. Die Telefonbeispiele `enqueueDownload` und `OfflineTestRuntime` zeigen die lauffähige Implementierung.



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



Laden Sie jede Sprache herunter, die Sie offline aufrufen möchten. Aufrufen `createDownloadRequest` ohne Stream-Schlüssel lädt alle Tracks herunter, was im Allgemeinen verschwenderisch für adaptive Videos ist. Download-Fortschritt anzeigen und warten Sie auf `Download.STATE_COMPLETED`. Für Produktionshintergrundübertragungen integrieren Sie Media3 `DownloadService`, Speicherlimits und Android Vordergrund-Service / Benachrichtigungsanforderungen; die Demo-Warteschlange läuft im Prozess.



**Offline-Inhalte mit mehreren Schlüsseln:**Media3 stellt einen Schlüsselsatz ID für ein Wiedergabeelement wieder her. Seine persistente Lizenz muss jeden Schlüssel enthalten, der für die ausgewählten Video-, Audio- und anderen verschlüsselten Tracks erforderlich ist. Testen Sie dies mit Ihrer echten Paket- und Serverrichtlinie; das Aktivieren mehrerer Sitzungen kombiniert keine unabhängigen Offline-Lizenzen.



## 7. Pfad B: Wiederherstellen mit Ihrem vorhandenen Media3 Player[#](#existing-media3)



Lesen Sie den abgeschlossenen Download und verwenden Sie die genauen Stream-Schlüssel. Der Download- und Playback-Stack muss sich den gleichen Cache-Standort und die gleiche Schlüsselzuordnung teilen. In diesem Beispiel wird der Upstream-Zugriff für eine strikte Offline-Wiedergabe absichtlich deaktiviert:



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



Um den gleichen Offline-Flow mit Pfad A zu verwenden, konstruieren Sie `DrmXPlayer` mit diesem `mediaSourceFactory` und rufen Sie `player.prepareOffline(playback)` auf. Die gewöhnliche Wiederherstellung verwendet die lokale CDM-Lizenz, ohne eine neue anzufordern. Ein fehlender Cache-Eintrag sollte einen Fehler bei der getrennten Annahme erzeugen und nicht stillschweigend ins Internet wechseln.



## 8. Pfad C: native Media3 no DRM-X AAR[#](#configure-media3)



Sie sind nicht verpflichtet, DrmXPlayer oder DrmXOfflineManager zu verwenden. Fügen Sie die drei Media3 Abhängigkeiten aus Schritt 3 hinzu und implementieren Sie den gleichen Kunden-Backend-Vertrag. Verwenden Sie `session.manifestUrl` (nicht `session.manifest.url`), die zurückgegebene Lizenz URL und die eingeschränkten Autorisierungs-Header. Tragen Sie die zurückgegebene maximale Höhe auf und behandeln Sie die Streaming-Sitzungsfreigabe bei Stopp, Ersatz, Ende, Ausfall und Zerstörung.



Die komplette native Aktivität unten wird als `samples:native` neben diesem SDK-Release kompiliert. Es behandelt HTTPS, begrenzte Antwortlesungen, Hintergrundautorisierung, veraltete Antworten und Reservierungsfreigabe. Ersetzen Sie den markierten Endpunkt / Content ID und geben Sie Ihr eigenes Anmelde-Token an. Das Beispiel ist ein Ausgangspunkt; fügen Sie Ihren Produktionsaktualisierungs-, Kontowechsel- und App-Navigations-Lebenszyklus hinzu.

Vollständige native Streaming-Aktivität (Kotlin, keine DRM-X Abhängigkeit)

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

```



Für die native Offline-Lizenzierung verwenden Sie `OfflineLicenseHelper.downloadLicense`, behalten den undurchsichtigen Schlüsselsatz ID privat fort und stellen mit `MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID).setKeySetId(id)` wieder her. Der folgende kompilierte Helfer umfasst Akquisitions-, Dauerprüfungen, Erneuerungs-, Freigabe- und Download-/Wiedergabe-Elemente. Es ist **keine vollständige Download-Anwendung**: Sie besitzen immer noch den Cache, den Download-Dienst, den Fortschritt, Metadaten, die Benutzer-/Gerätbindung und die Berechtigung zum vertrauenswürdigen Betrieb.

Native Persistent-Lizenz-Helfer (Kotlin, keine DRM-X Abhängigkeit)

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

```



Bevor Sie diesen Low-Level-Helfer aufrufen, validieren Sie, dass die neue Backend-Antwort mit der angeforderten Content ID, der Operation, dem angemeldeten Benutzer/Gerät und der Gültigkeitsdauer der Autorisierung übereinstimmt. Beharren Sie auf einer stabilen OfflineLicenseId und vertrauenswürdigen Manifest/Track-Metadaten neben dem Schlüsselsatz ID, niemals dem Token. Führen Sie Blockierhelfer auf einem Worker-Thread auf. Für Lizenz HTTP-Header verwenden Sie `setLicenseRequestHeaders` auf der DRM-Konfiguration; Widevine optionale Key-Request-Parameter sind eine andere Karte.



Neue Integrationen sollten verwendet werden [AndroidX Media3 DRM Unterstützung](https://developer.android.com/media/media3/exoplayer/drm). Vermächtnis `com.google.android.exoplayer2` Die Projekte sollten dem [Offizieller Migrationsleitfaden](https://developer.android.com/media/media3/exoplayer/migration-guide); vermischen Sie kein Vermächtnis und AndroidX Klassen oder nehmen an, dass das Sample unverändert gegen ältere Versionen kompiliert.



## 9. Sprachen prüfen, erneuern, freigeben und wechseln[#](#lifecycle-and-offline-behavior)



Dies sind separate Aktionen. Führen Sie eine nach der anderen aktiv offline DRM Session wurde veröffentlicht: use `player.stopForOfflineLicenseOperation { /* one operation below */ }` mit DrmXPlayer oder Release des vorhandenen nativen Media3 Player Erstens: keine Lizenz prüfen, während sie abgespielt wird; eine Überprüfung ist optional für die Anzeige von Dauern; sie darf nicht verhindern, dass eine abgelaufene Lizenz verlängert wird.



```
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` liest Metadaten; `checkLicense` fragt autoritative lokale CDM Dauern ab. Erneuern/Release erfordern Konnektivität und eine neue betriebsspezifische Autorisierung von Ihrem Backend. Löschen von Medien gibt die Lizenz nicht frei. Wenn die Verbindung getrennt ist, wird die Warteschlange für die nächste Verbindung freigegeben, anstatt zu melden, dass der Server sie bestätigt hat. Schließen Sie den Manager, wenn sein Besitzer zerstört wird.



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



Eine Einstellung kann keinen englischen Titel erstellen, der nicht im Paket enthalten ist. Offline-Benutzer können nur heruntergeladene Sprachen auswählen. Lizenz-/Cache-Zustand über die Bildschirmrotation hinweg beibehalten; Vollbild ist eine Ansichts-/Fensteränderung, kein Grund, eine andere Lizenz zu erwerben.



## Implementieren Sie den Offline-Autorisierungs-Endpunkt[#](#offline-backend-contract)



Die App-Posts `contentId`, `contentType: "vod"`, `drmSystem: "widevine"`, `playbackMode: "offline"`, `operation: "acquire" | "renew" | "release"`, `offlineLicenseId` (Null für die Erstakquisition) und truthful `platformCapabilities`. Überprüfen Sie, ob die Renew/Release-IDs zu den authentifizierten Benutzern, Geräten und Inhalten gehören. Wählen Sie Politikserverseite aus, geben Sie eine neue DRM License Token aus und geben Sie den entsprechenden Vertrag zurück:



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



Dies sind erklärende Platzhalter, keine wiederverwendbare Lizenzantwort. SDK auch unterstützt `integrationMode: "token-proxy"` mit einer Begrenzung `drm.authorization` Deskriptor. Rückkehr `Cache-Control: no-store`; protokollieren Sie niemals erfolgreiche Autorisierungsstellen. SDK„s `OFFLINE_INTEGRATION.md` und PHP den Offline-Endpunkt des Samples für die Bearbeitung konkreter Anfragen.



## 11. Testen Sie Ihre Geräte und Fehlersuche[#](#device-qualification)



### Widevine Provisioning und China Network Testing



Provisioning erhält das Gerätezertifikat vor dem Erwerb der Inhaltslizenz. Ab preview.9 sendet die Telefondemo die CDM's signierte Anfrage durch seinen authentifizierten Kunden HTTPS Weiterleitung an Google. Die signierte Antwort wird unverändert zurückgegeben. Dies hilft, wenn das Telefon den Bereitstellungsdienst von Google nicht direkt erreichen kann; das Kundenrelais muss immer noch erreichbar sein.



Provisioning 2.0 verwendet eine Factory Keybox, Provisioning 4.0 verwendet ein anderes Geräteanmeldemodell. L1/L3 Sicherheitsniveaus und die Player tauscht sie nicht aus. Eine bereits bereitgestellte L3 Die hier überprüften offiziellen Quellen stellen nicht fest, dass China nur 2.0 unterstützt. [NXP'Geräte-Integrationsleitfaden](https://www.nxp.com/docs/en/user-guide/UG10158.pdf) und [Android'Bereitstellung Umsetzung](https://android.googlesource.com/platform/packages/apps/RemoteProvisioner/+/refs/tags/android-13.0.0_r15/src/com/android/remoteprovisioner/WidevineProvisioner.java).



Vorschau.10 Fehlerberichte unterscheiden direkten oder Relaistransport, Timeout/DNS/TLS/HTTP Fehler, HTTP Status und verstrichene Zeit. `provisioningModel: BootCertificateChain` zeigt das AOSP 4.0 Modell an; `unknown` oder `other` stellt nicht 2.0 her. PHP Protokolle unterscheiden abgelehnte Eingaben von Upstream-Google-Fehlern. Berichte schließen signierte Nutzlasten, URLs, Zugangsdaten und eindeutige Gerätekennungen aus.



Aktualisieren Sie die APK an Ort und Stelle, wiederholen Sie das Streaming und einen Download im betroffenen Netzwerk, behalten Sie dann das Kopierprotokoll und die ungefähre Zeit bei. Bestehende Downloads und Lizenzen werden beibehalten. Ein Relais kann nicht repariert werden, nicht verfügbar Widevine oder Zugangsdaten vom Gerät / Google abgelehnt. Echte Akzeptanz auf dem betroffenen Telefon ist erforderlich.



Von preview.8, native L2/L3 oder unknown-level Widevine Wiedergabe ist begrenzt auf SD (bis zu 576p) vor der Decodierung. Weiterleiten der ursprünglichen Android Fähigkeiten durch Ihr Backend: die Sitzung API verengt Software Widevine Multi-Key-Lizenzen auf Audio/SD Tasten. Das PHP Sample bewahrt diese Felder und auch Kappen Offline-Anforderungen. Eine SD Wiedergabe und eine Richtlinie erlaubt Software DRM sind erforderlich; Hardware-only und einheitliche Single-Key-Sicherheitsregeln bleiben unverändert. Einstellungen verwendet ein lichtdurchlässiges Panel ohne Dimmen des Videos. Numeric `cryptoErrorCode` ist in sicheren Fehlerberichten enthalten.



| Test / Symptom | Erwartetes Verhalten / nächste Überprüfung | 
| --- | --- |

| SDK Download gibt 403 zurück | Wählen Sie eine Organisation mit einem aktiven Professional- oder Enterprise-Abonnement. Ein geplantes Upgrade nach der Testversion schaltet die SDK nicht frei. | 

| Sitzung 401 / 403 | Überprüfen Sie Login, Berechtigung, Umgebung, veröffentlichte Content ID und Politik. Umgehen Sie niemals die Authentifizierung. | 

| Offline scheitert, aber Online funktioniert | Überprüfen Sie CDM Dauern, abgeschlossenen Download, Cache-Standort, Stream-Schlüssel und alle erforderlichen lizenzierten Schlüssel. | 

| Englisch / Chinesisch fehlt | Überprüfen Sie die Metadaten der Paketsprache, die tatsächlichen Tracks und die Offline-Sprachauswahl. | 

| L1, HDCP oder Decoderausfall | Prüfen Sie die physischen Hardware-, Provisionierungs-, Codec- und Ausgabeanforderungen; ein Capability String ist keine Zertifizierung. | 

| Reboot, Rotation, Upgrade | Persistente Lizenz und Cache überleben unterstützte Lebenszyklusänderungen ohne doppelte Erfassung. Backups dürfen keine Lizenzen auf ein neues Gerät migrieren. | 

| Flugbetrieb | Deaktivieren Sie auch Wi-Fi und mobile Daten; starten Sie die App neu, spielen Sie lokal und wechseln Sie jede versprochene Sprache. | 

| Ablauf, Speicherdruck, Kontowechsel | Zeigen Sie einen eindeutigen nicht verfügbaren / erneuerten Zustand an, stellen Sie unvollständige Medien wieder her und erzwingen Sie den Benutzer- / Gerätezugriff, ohne den Offline-Katalog eines anderen Benutzers zu verlieren. | 


Qualifizieren Sie jedes unterstützte Telefon, Tablet, Android TV/Google TV, Fire TV oder eingebettet Android Modell für Widevine, sichere Decodierung, Codec/Auflösung, HDCP, externe Displays, Uhrenwechsel und lange Trennung. Android Die Vorrichtung hat dieselbe DRM Fähigkeiten. Hollywood-Inhalte erfordern den Schutz und die Geräteanforderungen des Rechteinhabers, nicht nur eine erfolgreiche Decodierung.



**Keine Möglichkeit, vor der Trennung zu erwerben?**Das ist eine andere Bereitstellung: Ein qualifizierter On-Board-Lizenzdienst stellt während der Reise Lizenzen über das lokale Netzwerk aus. Er wird nicht nur durch die Verwendung dieser SDK aktiviert. DRM-X kann mit Ihnen an einer benutzerdefinierten Bereitstellung arbeiten, die die Berechtigungen des Anbieters, die Bereitstellung, den Schlüsselschutz und einen vollständig getrennten Akzeptanztest umfasst. [an Bord DRM für Flugzeuge, Busse, Züge und Schiffe](https://multi-drm.drm-x.com/de/solutions/on-board-drm).



## Universal Player Playlists und Shared Controls[#](#universal-player-playlists-and-shared-controls)



Die Android preview.11 und neuere Evaluations-Apps und aktuelle Android/Apple-Entwicklungsquellen teilen sich im Video Vorherige/Play-Pause/Next, CC, Einstellungen und Vollbild-Steuerelemente mit zwei Sekunden Ausblenden während der Wiedergabe. Android Das Playlist-Menü enthält nicht mehr "Nächste Wiedergabe" Ausgewählte Shuffle/Wiederholungseinstellungen bleiben hervorgehoben und bleiben bei Neustarts bestehen. Mit beiden Ausschalten folgt die Wiedergabe der gespeicherten Wiedergabelistenreihenfolge und stoppt beim letzten Video. Drag behandelt Neubestellungen ohne Neustart des aktuellen Videos; die gespeicherte Reihenfolge gilt auch für die heruntergeladene Wiedergabeliste. Diese neuesten Quell-APIs sind noch nicht in der Vorschau der Konsole enthalten.10 SDK Bundle.



Geben Sie den gleichen Kunden/Zuschauer `storageNamespace` an `DrmXPlayer` und `playerView.storageNamespace`, bevor Sie die Playlist einstellen. VOD Fortschritt wird periodisch und unter Pause/Stopp gespeichert, dann für diesen wiederhergestellt Content ID im Streaming oder in der heruntergeladenen Wiedergabe. Live-Wiedergabe verwendet keine gespeicherte VOD Position. Abgeschlossene Downloads behalten das gleiche Shuffle/Repeat/Resume-Verhalten ohne Netzwerkmedienquelle. Dieser gerätelokale Zustand synchronisiert den Fortschritt zwischen Geräten nicht.



Die Web- und Android-Editionen verwenden ein konsistentes dunkles Einstellungsfeld mit kompakten blauen Steuerelementen. Playlists unterstützen next/previous, shuffle, repeat one/all und stornierbaren automatischen Fortschritt. Jedes Element verwendet eine neue autorisierte Wiedergabeanforderung. Kapitel- und Transkript-Metadaten stammen aus Ihrem Inhaltskatalog.



Wiedergabegeschwindigkeit, Sleep-Timer, Untertitel, erlaubte Qualität, Audiosprachen, Session-Lesezeichen und A-B-Looping sind verfügbar. Web fügt Tastaturkürzel, optionale Viewer-scoped lokalen Lebenslauf, Transkript-Suche und unterstützt Browser PiP. Android behält native Widevine Offline-Downloads und Erneuerung. Casting und Cross-Device-Fortschritt erfordern separate Anwendungsintegrationen.



### Professional- und Enterprise-Logo-Einstellungen



Branding bleibt von der Videooberfläche fern. Offene Einstellungen → Über Player für das autorisierte Logo, Produktinformationen und Player Version. Web bietet diese Informationen auch mit der rechten Maustaste an. Die öffentliche Telefondemo enthält WayMaker, Goodness of God, Fresh Oil und 4K Test Video; jede Auswahl erhält ihre eigene Autorisierung.



Setzen Sie `playerBranding` in Ihrem vertrauenswürdigen Backend auf `{"mode":"hidden"}` oder `{"mode":"custom","logoUrl":"https://your-site.example/logo.png","label":"Your brand"}`. Die Plattform überprüft das aktive Professional- oder Enterprise-Abonnement. Andere Pläne verwenden `{"mode":"default"}`; nicht autorisierte benutzerdefinierte / versteckte Anforderungen geben 403 zurück. Dies ist getrennt von Sicherheitswasserzeichen.



Die PHP Download enthält editierbare Playlist und Branding-Konfiguration. Android Kunde ZIP Behalten Sie die Playlist-Autorisierung in Ihrem Backend; niemals einbetten API Kunde Zugangsdaten entweder Player.

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