DRM-X 6.0 Taube und Olivenzweig LogoDRM-X 6.0Dokumentation
◎ Deutsch

Android SDK und natives Media3

Laden Sie den Android-Player mit per Ziehen sortierbaren Wiedergabelisten, gespeicherten Zufalls- und Wiederholungseinstellungen sowie Offline-Fortsetzung herunter. Integrieren Sie geschützte Wiedergabe mit DRM-X oder nativem Media3.

Markdown anzeigenLive-BeispieleAktualisiert am 2026-10-26

Informationen zur UI-Anpassung, zu Parameterstandardwerten und zu Lebenszyklusbeispielen finden Sie in PLAYER-UI-CONFIGURATION.md. Die Popup-Bereiche des Players sind undurchsichtig; die Deckkraft von Untertiteln und Wasserzeichen wird separat eingestellt.

Testen Sie die PHP-Demo anhand von DEMO-QUICKSTART.md in Ihrem SDK ZIP. Für eigene Inhalte integrieren Sie zuerst Ihre Website und Ihr Backend. Verbinden Sie anschließend Ihre App gemäß MOBILE-AND-TV-APPS.md mit derselben Anmeldung und denselben Zugriffsregeln.

Laden Sie das Android SDK und die Beispiel-Apps mit einem aktiven Professional- oder Enterprise-Abonnement unter Console → Developer Tools → Device SDKs herunter.
Choose your player, keep one license service. Use the DRM-X 6.0 Android SDK for a guided integration, or keep your native Media3 player. Both connect to your authenticated backend and Widevine. The Android SDK supports streaming on compatible API 23+ devices. Persistent offline VOD is supported for phones and tablets; OTT devices use streaming only.

Probieren Sie die Android Player#

Herunterladen, installieren, Play drücken. Die kostenlose Test-App nutzt unser öffentliches Demovideo und meldet sich automatisch an. Sie müssen kein APK erstellen oder DRM-X-Anmeldeinformationen eingeben, um es auszuprobieren.

Download Android sample APK

1.0.0-beta.11 · 12.15 MB · Android 6.0+ (API 23+) · DRM-X 6.0 Player

  1. Öffnen Sie diese Seite auf Ihrem Android Telefon und laden Sie die APK herunter.
  2. Wenn Android fragt, erlauben Sie die Installation aus dem Browser oder Dateimanager, den Sie verwendet haben, und tippen Sie auf Install .
  3. Ö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.
  4. Öffnen Sie Settings, um Qualität, Audio, Bildunterschriften oder Wiedergabegeschwindigkeit auszuwählen. Drehen Sie das Telefon so, dass es im Vollbildmodus fortgesetzt wird.
  5. Öffnen Sie das Drei-Linien-Menü des Videos und wählen Sie Download. Aktivieren Sie Downloaded videos für Offline Mode; deaktivieren Sie es für Online Mode. Wählen Sie im Offline Mode im Menü dieses Videos Delete und bestätigen Sie. Die Lizenzverlängerung erfordert eine Verbindung und eine gültige Berechtigung; bei einem Offlinefehler können Sie offline bleiben oder online wechseln.

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.

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:

b96ba527ddf9abae5bff2f8b85a39a12b59c934aaacde1961c8096aa32524c54

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

adb install -r drmx-android-player-1.0.0-beta.11.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.

Download controls and language names in preview.40#

Connect to the internet before starting a download. Choose the permitted video quality and audio track in the download dialog; available subtitles are included. Audio and subtitle menus display each language in its own language, such as English and 中文. Customer configuration and device DRM support determine the available quality ceiling.

Öffnen Sie im Offline Mode das Drei-Linien-Menü des Videos und wählen Sie Delete. Nach der Bestätigung werden nur dessen lokale verschlüsselte Medien und Lizenzverweise entfernt; Cancel behält sie bei. Stellen Sie die Verbindung wieder her, um es erneut herunterzuladen.

New downloads require an internet connection. A queued transfer can pause when connectivity disappears and resume after reconnection. Completed downloads play offline while their device-bound licenses remain valid. Playlist posters and seek-preview thumbnails remain cached as described below.

Offline artwork and downloads in preview.39#

Playlist posters and seek-preview thumbnails are saved on the device with their artwork metadata. They remain available after closing and reopening the app without a network connection. After upgrading, open the offline demonstration library once while connected and allow the images to load; existing encrypted videos do not need to be downloaded again.

Wait for each video to finish downloading, then switch off both Wi-Fi and mobile data and choose Play offline. Test the downloaded playlist, selected audio and included captions. Reconnect before using Renew when a stored license needs renewal. Install this update over the existing sample to retain its downloads and settings.

Publisher images must be available while connected before they can appear offline. Images use a bounded 96 MiB app-private store, separate from encrypted media and licenses; older artwork can be evicted when this store fills. Missing images do not bypass DRM or prevent an otherwise valid downloaded video from playing.

Live DRM in preview.13#

Veröffentlicht am 17. September 2026. Die Telefon-App und der Kunde SDK beinhalten 4K Live DRM Test. Die Live-Wiedergabe verwendet neue Manifeste und kann nicht heruntergeladen werden. Das Testereignis ist nur während einer aktiven Übertragung verfügbar. Befolgen Sie die AWS und OBS Einrichtungsanleitung, um Ihr eigenes Event zu erstellen.

Was ist neu in Android Player preview.12#

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.

  1. Installieren Sie preview.12 über die bestehende offizielle App. Behalten Sie Ihre aktuellen App-Daten und Downloads.
  2. 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.
  3. 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 Playlist-Funktionen aus preview.11 bei. Die öffentliche Evaluierungs-App und ihr eingebetteter Player melden preview.12. Zum damaligen Veröffentlichungszeitpunkt wurde das Console SDK separat als preview.10 / Bundle 20260910.8 angeboten.

Was ist neu in Android Player preview.11#

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.

Organisieren Sie Wiedergabelisten, speichern Sie ihre Reihenfolge und setzen Sie die Wiedergabe fort. Offline-Wiedergabe erfordert einen abgeschlossenen Download und eine nutzbare, nicht abgelaufene Offline-Lizenz.

preview.11 aktualisierte die öffentliche Evaluierungs-APK. Das aktuelle Console SDK ist 1.0.0-beta.14, Bundle 20261028.1. Die folgenden Integrationsschritte verwenden dieses Bundle.

1. Wählen Sie einen Integrationspfad#

Download unter Developer Tools → Device SDKs. Die SDK-Nutzung erfordert aktiven Professional- oder Enterprise-Zugang, einschließlich genehmigter Professional-Evaluierungen. Standard bietet weiterhin native APIs für eigene Player. Der SDK-Zugang wird bei Sitzungserstellung und Lizenzerneuerung geprüft; Offline-Lizenzen enden spätestens mit dem Kontoservicezeitraum.

Aktuelle Vorschau: 1.0.0-beta.14 · Customer Bundle 20261028.1 · 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#

  1. Verpacken und veröffentlichen Sie einen Widevine-kompatiblen verschlüsselten DASH Titel in Ihrer DRM-X Umgebung. Kopieren Sie den genauen Published Content ID.
  2. Konfigurieren Sie Ihren Server Environment ID, Site Key und Access Key. Verwenden Sie the downloadable PHP integration für einen funktionierenden Ausgangspunkt.
  3. 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.
  4. 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.

  1. Speicher / CDN

    Liefert verschlüsselte Medien an den Player.

  2. Ihr Backend

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

  3. DRM-X

    Validiert die Wiedergabeautorisierung und verarbeitet die DRM-Lizenzanfrage.

  4. 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 WertWohin es gehörtBedeutung
sessionEndpointAndroidDein HTTPS Playback-Session-Endpunkt, nicht der rohe Lizenzserver URL.
offlineEndpointAndroid, nur offlineIhr Acquiring / Renew / Release Autorisierungs-Endpunkt.
contentIdAndroid Request; Backend validiertDas Veröffentlichte geschützt Content ID ist der Betrachter berechtigt, darauf zuzugreifen.
Kundenzugangs-TokenApp-Speicher nach dem LoginIhr kurzlebiges Benutzer-Token, niemals ein Site Key oder Access Key.
Environment ID, Site Key, Access KeyServer geheime Konfiguration nurDRM-X Konto ZugangsdatenVerschiffen Sie sie niemals in einem APK, HTML oder Source Control.
Politik und vertrauenswürdiges Manifest URLIhr BackendGeschäftsberechtigung und Paketzuordnung. Akzeptieren Sie diese nicht aus der App.

3. Installieren und Ausführen der 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-beta.14")
}
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-beta.14.aar"))
implementation("androidx.media3:media3-exoplayer:1.11.0")
implementation("androidx.media3:media3-exoplayer-dash:1.11.0")
implementation("androidx.media3:media3-ui:1.11.0")

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#

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.

  1. Laden Sie den full Android SDK ZIP herunter und extrahieren Sie den Stammordner in Android Studio.
  2. Bleiben libs/drmx-universal-player-1.0.0-beta.14.aar in der extrahierten Wurzel. Verwendung JDK 17, Android SDK 36 und der mitgelieferte Gradle wrapper.
  3. 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-beta.14.aar")))
implementation("androidx.media3:media3-exoplayer:1.11.0")
implementation("androidx.media3:media3-exoplayer-dash:1.11.0")
implementation("androidx.media3:media3-ui:1.11.0")

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

  1. 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.
  2. 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 unten, wenn Sie diese Ansicht zu Ihrer eigenen App hinzufügen.

4. Serveranforderung und Standardrichtlinie#

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

Offline benötigt eine eigene dauerhafte Richtlinie. Setzen Sie playback_policy.persistent in der vom Server ausgegebenen Richtlinie auf „true“ und wählen Sie die Lizenz-, Miet-, Wiedergabe- und Verlängerungsdauer aus. Die Referenz verwendet jeweils 7 Tage / 3 Tage / 48 Stunden / 7 Tage; Hierbei handelt es sich um beispielhafte Geschäftsregeln, nicht um Studiogenehmigungen oder um eine plattformweite Standardeinstellung. Gibt passende Metadaten zurück. Die Widevine-Lizenz bleibt maßgebend.

5. Pfad A: durch DrmXPlayer# 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#

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.

Aktueller Screenshot der Einstellungen auf einem Samsung Android-Gerät. Der Screenshot-Schutz blendet die Hauptoberfläche des Players aus.
Aktueller Screenshot der Einstellungen auf einem Samsung Android-Gerät. Der Screenshot-Schutz blendet die Hauptoberfläche des Players aus.
KontrolleWas Kunden tun können
QualitätWä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.
AudiotracksWählen Sie eine vom Paket bereitgestellte unterstützte Audiosprache aus.
Untertitel/CCWä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 BildunterschriftPassen Sie Schriftart, Textfarbe und -größe, Hintergrund-/Fensterfarben und Deckkraft an. Präferenzen werden auf dem Gerät gespeichert.
Wiedergabegeschwindigkeit / Sleep TimerSpielen Sie mit 0,25-2x Geschwindigkeit oder pausieren Sie automatisch nach einem gewählten Intervall.
Suchen / VollbildScheuern Sie die Zeitleiste, suchen Sie 10 Sekunden oder tippen Sie auf beide Seiten. Drehen Sie in die Landschaft, ohne die Wiedergabe neu zu starten.

Jedes Videomenü enthält Move up / Move down sowie online Download oder offline Delete. Nach abgeschlossenem Download ist Download deaktiviert. Der Schalter Downloaded videos wählt die Offlinewiedergabe; nur zwischengespeicherte Spuren sind verfügbar. Separate Schaltflächen zum Herunterladen, Löschen oder Herunterladen der gesamten Wiedergabeliste entfallen.

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 wird bereits verwendet? Preview.5 behebt den Samsung Android-Gerät-Fall, bei dem CDM 368 mit „Lizenz in anderer Sitzung geladen“ gemeldet wurde. Durch erneutes Drücken von Play offline wird der bestehende Player fortgesetzt. Renew gibt zunächst seine DRM-Sitzung frei und erneuert dann die gespeicherte Lizenz. Der Download und die dauerhafte Lizenz bleiben erhalten; Es ist kein Zurücksetzen der 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-Wiedergabe vorbereiten

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

  1. Verschlüsselte Medien herunterladen

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

  2. Persistente Lizenz abrufen

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

  3. Bereitschaft prüfen

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

  4. 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-Inhalt mit mehreren Schlüssel: Media3 stellt einen Schlüsselsatz ID für ein Wiedergabeelement wieder her. Seine dauerhafte Lizenz muss alle Schlüssel enthalten, die für die ausgewählten Video-, Audio- und anderen verschlüsselten Spuren erforderlich sind. Testen Sie dies mit Ihrer echten Paket- und Serverrichtlinie. Durch die Aktivierung mehrerer Sitzungen werden unabhängige Offline-Lizenzen nicht kombiniert.

7. Pfad B: Wiederherstellen mit Ihrem vorhandenen Media3 Player#

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#

Sie müssen DrmXPlayer oder DrmXOfflineManager nicht verwenden. Fügen Sie die drei Media3 Abhängigkeiten aus Schritt 3 hinzu und implementieren Sie denselben Kunden-Backend-Vertrag. Verwenden Sie session.manifestUrl (nicht session.manifest.url), die zurückgegebene Lizenz URL und kurzlebige Autorisierungsheader. Wenden Sie die zurückgegebene maximale Höhe an und handhaben Sie die Freigabe der Streaming-Sitzung 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. Vermächtnis com.google.android.exoplayer2 Die Projekte sollten dem Offizieller Migrationsleitfaden; 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#

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#

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#

Test / SymptomErwartetes Verhalten / nächste Überprüfung
SDK Download gibt 403 zurückWä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 DecoderausfallPrüfen Sie die physischen Hardware-, Provisionierungs-, Codec- und Ausgabeanforderungen; ein Capability String ist keine Zertifizierung.
Reboot, Rotation, UpgradePersistente Lizenz und Cache überleben unterstützte Lebenszyklusänderungen ohne doppelte Erfassung. Backups dürfen keine Lizenzen auf ein neues Gerät migrieren.
FlugbetriebDeaktivieren Sie auch Wi-Fi und mobile Daten; starten Sie die App neu, spielen Sie lokal und wechseln Sie jede versprochene Sprache.
Ablauf, Speicherdruck, KontowechselZeigen 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 Erwerbsmöglichkeit vor der Trennung? Das ist ein anderer Einsatz: Ein qualifizierter Bordlizenzdienst vergibt während der Fahrt Lizenzen über das lokale Netzwerk. Es wird nicht allein durch die Verwendung dieses SDK aktiviert. DRM-X kann mit Ihnen an einer benutzerdefinierten Bereitstellung arbeiten, die Anbieterberechtigungen, Bereitstellung, Schlüsselschutz und einen vollständig getrennten Abnahmetest umfasst. Siehe an Bord DRM für Flugzeuge, Busse, Züge und Schiffe.

Universal Player: Wiedergabelisten und gemeinsame Bedienelemente#

Android- und Apple-Player bieten Previous/Play-Pause/Next, CC, Settings und Vollbildsteuerung. Move up / Move down behält das aktuelle Video bei und speichert die Reihenfolge für Online- und Offlinewiedergabe. Zufalls- und Wiederholungsmodus bleiben nach dem Neustart ausgewählt; sind beide aus, endet die Wiedergabe nach dem letzten Eintrag.

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.

Web und Android teilen dunkles Menü und kompakte blaue Bedienung. Playlists unterstützen Vor/Zurück, Zufall, eine/alle wiederholen und abbrechbares automatisches Weitergehen. Jeder Eintrag erfordert eine neue Autorisierung. Kapitel und Transkript stammen aus Ihrem Katalog.

Geschwindigkeit, Sleep-Timer, Untertitel, erlaubte Qualität, Audiosprachen, Sitzungslesezeichen und A–B-Schleife stehen bereit. Web ergänzt Tastenkürzel, optionale lokale Fortsetzung je Zuschauer, Transkriptsuche und unterstütztes PiP. Android behält native Widevine-Offline-Downloads und Erneuerung. Casting und geräteübergreifender Fortschritt benötigen eigene Integration.

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 im vertrauenswürdigen Backend auf {"mode":"hidden"} oder {"mode":"custom","logoUrl":"https://your-site.example/logo.png","label":"Your brand"}. Die Plattform prüft aktives Professional oder Enterprise. Andere Tarife nutzen {"mode":"default"}; nicht erlaubtes individuelles/verstecktes Branding gibt 403 zurück. Dies ist vom Sicherheitswasserzeichen getrennt.

Der PHP-Download enthält editierbare Playlist- und Branding-Einstellungen. Das Android-ZIP enthält das Callback-Beispiel. Autorisieren Sie Playlists im Backend; API-Client-Zugangsdaten dürfen in keinem Player eingebettet sein.