# SDK de Android y Media3 nativo

Descargar el Android reproductor con controles en vídeo, listas de reproducción arrastrables, shuffle/repeat guardado y curriculum vitae offline. Integrar la reproducción protegida usando DRM-X o nativa Media3.

**Elija su reproductor, mantenga un servicio de licencia.**Utilice el DRM-X 6.0 Android SDK para una integración guiada, o mantener a su nativo Media3 reproductor. Ambos se conectan a su backend autenticado y Widevine. El Android SDK soporta streaming y persistente offline VOD en dispositivos compatibles API 23+, incluyendo teléfonos, tabletas, televisores y sistemas integrados Android. 

## Prueba el Android reproductor [ ](#try-android-app)



**Descargar, instalar, pulsa Play.**La aplicación de evaluación gratuita utiliza nuestro video demo público y los signos en forma automática. No necesita construir un APK o introducir DRM-X credenciales para probarlo. 

[Descargue Android muestra 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 · Actualizado 2026-09-14


- Abra esta página en su Android teléfono y descargar el APK.
- Abra el archivo descargado. Si Android pregunta, permita la instalación desde el navegador o gestor de archivos que utilizó, a continuación, pulse **Install**.
- Abrir **DRM-X 6.0 Player**, mantenerse conectado y pulsar el botón **Play** del vídeo. La aplicación solicita una licencia de streaming cuando pulsa Play.
- Abrir **Configuraciones** para elegir calidad, audio, leyendas o velocidad de reproducción. Rota el teléfono para continuar en pantalla completa.
- Grifo **Descargar** para el vídeo actual o **Descargar lista** para todos VOD ítems. Las descargas utilizan la entrega con licencia más alta soportada hasta 1080p en L1, o 480p en L3. **Juega offline** abre la lista de reproducción local completa con el siguiente/previous, shuffle, repeat and autoplay; **Go online** vuelve a la lista completa. **Renovación** renueva la licencia seleccionada mientras está conectada.



Si Android detiene la aplicación mientras prepara licencias o pistas, vuelva a abrirla mientras esté conectada para terminar la preparación de los elementos restantes. Se mantienen descargas existentes. La reproducción sin conexión sólo utiliza medios cachés; las descargas incompletas o caducadas requieren terminación o renovación.



Widevine fallas de provisión (Media3 6002) ahora usan un relé de servicio de demostración autenticado al servicio oficial de Google. Un funcionamiento L3 CDM puede recibir SD llaves. Un dispositivo sin trabajo Widevine la implementación necesita un dispositivo/firmware fijado; una licencia no puede agregar el soporte DRM desaparecido.



La muestra APK es pública y firmada para evaluación. La AAR y la completa SDK siguen disponibles a través de cuentas de consola elegibles. Mantenga su propia aplicación de producción firmando clave y backend login separado de esta demo.

APK checksum y USB instalación

SHA-256:

```
8fa044b82c0abab7ce2a987f349524fd7ad1e78f3c897771f4764cb2aa521180
```

Con Android herramientas de plataforma y un dispositivo autorizado USB:

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

Instala esto APK sobre la muestra oficial existente sin desinstalarla. La misma clave de firma y aplicación ID preserva las descargas, licencias sin conexión, posiciones y preferencias guardadas. Si su propia construcción utiliza otra clave de firma, utilice una aplicación separada ID para mantener ambas aplicaciones y preservar las licencias sin conexión de la muestra.





## Lo nuevo en Android reproductor vista previa.12[#](#latest-player-update)



Liberado 14 septiembre 2026. Arreglos descargados reproducción haciendo una solicitud de licencia inesperada cuando una licencia Widevine está caducada o tiene un minuto o menos restante. Esto podría aparecer como error Media3 `6004`, un error HTTP mientras está conectado, o un error DNS mientras se desconecta.



El reproductor ahora restaura la licencia descargada localmente. Las licencias válidas conservan su tiempo de reproducción restante; licencias expiradas muestran **Connect y tap Renew**. Las reglas de validez y protección de licencias siguen siendo aplicadas por el dispositivo. Los diagnósticos también incluyen el estado numérico HTTP cuando una operación de red falla, sin exponer encabezados de solicitud, URLs o respuestas de licencia.


- Instala la vista previa.12 sobre la aplicación oficial existente. Mantenga los datos y descargas de su aplicación actual.
- Si WayMaker u otro vídeo descargado necesita renovación, conectarse a Internet, seleccione ese vídeo y grifo **Renew**.
- Espere **License renovado. Listo para jugar sin conexión.**, luego elija **Play offline**. El vídeo cifrado permanece descargado.



Esta versión conserva los controles de vista previa.11 y las funciones de lista de reproducción. La aplicación de evaluación pública y su integrado reproductor identificar vista previa.12; la consola distribuida por separado SDK sigue siendo vista previa.10 / paquete 20260910.8.





## Lo nuevo en Android reproductor vista previa.11[#](#player-preview11)



Publicado 14 septiembre 2026. Esta aplicación de evaluación incluye los últimos reproductor controles y mejoras de la lista de reproducción, con un estilo de interacción consistente a través de nuestro Android y Apple reproductores.


- **Controles dentro del vídeo:** centrado Play/Pause, Anterior y Siguiente para listas multivideo, CC, Ajustes y un botón de pantalla completa. Los controles se ocultan después de dos segundos mientras juegan; toque el vídeo para revelarlos de nuevo. 
- **Capciones y configuración de reproducción:** use CC para mostrar o ocultar subtítulos disponibles, y Ajustes para elegir el lenguaje de captura, audio, velocidad de reproducción y calidad permitida. Las opciones sin conexión utilizan sólo las pistas incluidas en la descarga. 
- **Drag to reorder:** arrastrar el mango de un elemento de lista para cambiar su posición. El pedido guardado sobrevive a un reinicio de la aplicación y se refleja en la lista de reproducción descargada. Reordering mantiene el vídeo actual jugando. Pulse el mango para ser accesible Move up / Move down actions. 
- **El shuffle guardado y repetir:** los modos seleccionados permanecen resaltados y sobreviven en reiniciados en la reproducción en línea y fuera de línea. Con el shuffle y repetir ambos apagados, los vídeos siguen el orden guardado y deténganse después del elemento final. 
- **Resumen donde se detuvo:** la posición guardada de cada vídeo VOD se restaura cuando regresa a la reproducción de streaming o descargada. El progreso se guarda en este dispositivo para el espectador actual; no sincroniza entre dispositivos. 



Los cambios reproductor fueron verificados en un Samsung S24 Ultra, incluyendo arrastrar la lista de reproducción durante la reproducción, orden guardado después del relanzamiento, reanudar la reproducción y la reproducción descargada con Wi-Fi y datos móviles deshabilitados.



Esta versión actualiza la evaluación pública APK. La consola distribuida por separado SDK permanece `1.0.0-preview.10`, paquete `20260910.8`; sus instrucciones de integración existentes a continuación se refieren a ese paquete.





## 1. Elija un camino de integración [#](#choose-your-path)



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

Utilice DrmXPlayer, la muestra de teléfono y el administrador sin conexión. Mantenga su propio login y reglas de negocio.

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

Mantenga una pila existente reproductor y descargue. Deje que DrmXOfflineManager administre licencias persistentes.

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

Ejecute el mismo contrato de backend y el ciclo de vida de licencia nativa. No DRM-X reproductor dependencia.

](#configure-media3) 

[Descargue el ZIP o AAR del SDK de Android en Console → Integrations](https://6.drm-x.com/console/integrations). Pueden descargarlo las organizaciones con **Professional ($499/mes) o Enterprise** activos. Las cuentas Trial y Standard no pueden descargar el SDK. Esta documentación, incluidos los ejemplos nativos de Media3, es pública; la reproducción requiere una cuenta autorizada, contenido publicado y un dispositivo compatible.



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



## 2. Prepare su contenido y backend[#](#before-you-code)


- Paquete y publicar un Widevine-compatible título encriptado DASH en su DRM-X Medio ambiente. Copia su exacta **Publicado Content ID**.
- Configure su servidor Environment ID, Site Key y Access Key. Use [ la integración descargable PHP](https://developer.drm-x.com/php-sample/) para un punto de partida de trabajo.
- Implementa tu login de usuario y control de derechos. Tu app llama a tu backend con su ficha de usuario de corta duración; tu backend elige el contenido, la política y las URLs de confianza.
- Ejecute la reproducción en línea primero en un dispositivo físico Widevine-capable. Agregue la adquisición fuera de línea sólo después de que el contrato de streaming funciona.



El vídeo cifrado y las licencias DRM se entregan por rutas distintas

La CDN entrega el vídeo cifrado. Su backend comprueba el acceso y el servicio de licencias DRM autoriza el descifrado en un dispositivo compatible.

- 01**Almacenamiento / CDN**

Entrega el contenido cifrado al reproductor.

- 02**Su backend**

Comprueba si el espectador tiene permiso y solicita la autorización de reproducción a DRM-X.

- 03**DRM-X**

Valida la autorización de reproducción y procesa la solicitud de licencia DRM.

- 04**Reproductor + dispositivo**

Utiliza el sistema DRM del dispositivo para obtener una licencia y reproducir el contenido cifrado.

Entrega de medios y entrega de licencias son separados. Sólo el backend del cliente tiene DRM-X servidor credenciales. 

| Cambiar este valor | Donde pertenece | Significado | 
| --- | --- | --- |

| sessionEndpoint | Android | Su HTTPS punto final de reproducción-sesión, no el servidor de licencia cruda URL. | 

| offlineEndpoint | Android, offline only | Su punto final de autorización de adquisición/renovar/renunciar. | 

| contentId | Android petición; backend validates | El publicado protegido Content ID el espectador tiene derecho a acceso. | 

| acceso al cliente token | App de memoria después de iniciar sesión | Tu token de usuario de corta duración, nunca un Site Key o Access Key. | 

| Environment ID, Site Key, Access Key | Configuración secreta del servidor solamente | DRM-X cuenta credenciales. Nunca enviarlos en un APK, HTML o control de fuente. | 

| Política y manifiesto de confianza URL | Su backend | Derecho de negocio y mapeo de paquetes. No acepte estos de la aplicación. | 



## 3. Instalar y ejecutar el SDK[ ](#install-sdk)



Open the extracted ZIP's root folder in Android Studio. Use JDK 17, Android SDK 36 and Android Gradle Plugin 9.2.0; the included wrapper uses Gradle 9.4.1. Minimum device API is 23. Build dependencies require internet access. Select `samples.phone` and Run. Its initial values use the public demo backend and title; follow its sign-in and streaming controls before switching to your own backend.



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



Para una aplicación existente, copie la carpeta ZIP `maven/` en su raíz del proyecto. En `settings.gradle.kts`:



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



En el módulo de aplicación `build.gradle.kts`:



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

Instalar el standalone AAR en su lugar

Ponga el AAR en el módulo de aplicación `libs/`. Un piso AAR no tiene una resolución de dependencia transitiva automática, así que agregue las tres dependencias iguales Media3 usted mismo. No instale ambos formularios.

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



En `AndroidManifest.xml`, permite el acceso a Internet y desactivar copias de seguridad para esta muestra mínima. En una aplicación de producción que necesita copias de seguridad, excluye específicamente los metadatos de licencias sin conexión con dispositivo en su lugar.



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



Busque la muestra de teléfono `MainActivity.kt` para `drmx.sessionEndpoint`, `drmx.offlineEndpoint`, `drmx.contentId` y `customer.loginEndpoint`. Cambia la configuración de caída marcada, o pasa estos extras de Intent de su aplicación después de iniciar sesión. Pase `customer.accessToken` en tiempo de ejecución. ZIP README explica cada parámetro y que ganchos de demostración para reemplazar.



## Construir una prueba APK utilizando el AAR[ ](#build-apk-from-aar)



Este ejemplo utiliza el proyecto de teléfono ejecutable de la completa SDK ZIP y vincula explícitamente la independiente AAR. El mismo enfoque funciona para una aplicación existente Android.


- Descargar y extraer el [completo Android SDK ZIP](https://6.drm-x.com/console/integrations). Abrir su carpeta raíz en Android Studio.
- Keep `libs/drmx-universal-player-1.0.0-preview.10.aar` in the extracted root. Use JDK 17, Android SDK 36 and the included Gradle wrapper.
- En `samples/phone/build.gradle.kts`, sustitúyase la dependencia DRM-X Maven por las siguientes entradas dentro `dependencies`. Mantenga el resto de ese archivo de construcción.



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



Quitar `implementation("com.haihaisoft.drmx:drmx-universal-player:1.0.0-preview.10")` por lo que el mismo SDK no se incluye dos veces. Un piso AAR necesita las tres Media3 dependencias mostradas arriba.


- Para mantener la demo descargada instalada junto a su propia compilación de pruebas, cambie el módulo de teléfono `applicationId` a `com.example.drmxtest`. Deje su espacio de nombres sin cambios para esta muestra suministrada.
- Construir el APK, luego instalarlo en un USB-conectado Android dispositivo con USB depuración autorizada.



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



Abra la aplicación de prueba y toque Play. La fuente de teléfono suministrada utiliza la misma demostración pública inicialmente. Para probar su propio título, sustitúyase los puntos finales marcados de sesión/offline/login y Publicado Content ID en `MainActivity.kt`, y suministre la autenticación de corta duración de su propio usuario en tiempo de ejecución. Su servidor mantiene el Site Key y Access Key.



Los controles compactos provienen de `DrmXPlayerView` en el AAR. La actividad suministrada demuestra Play-to-authorize, la rotación receptiva, la propiedad de descarga, la reproducción y renovación sin conexión. Ver [ el ejemplo de integración de control](#player-controls) abajo al añadir esta vista a su propia aplicación.





## 4. Solicitud de servidor y política predeterminada[#](#server-request)



La aplicación Android pide su backend para una sesión de reproducción. Su backend autentica al usuario, verifica el derecho, mapas Content ID a los medios publicados y llamadas DRM-X. Ver [Playback Session API](https://docs.drm-x.com/es/api-reference/playback-sessions) para el contrato de streaming completo.



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



Las capacidades conservadoras arriba no afirman Widevine L1. Utilice el ayudante de capacidad SDK, o su propia evaluación de dispositivos, para dispositivos reales. Las capacidades del cliente ayudan a la selección de compatibilidad; nunca otorgan derecho de calidad.



El PHP muestra predeterminado a `multi-tier-standard` para la reproducción en línea. Seleccione su política de clave única documentada cuando su paquete utiliza una clave. Su política debe coincidir con el modelo clave del paquete y los derechos del espectador. Cambie la configuración del servidor o su configuración de entorno `DRMX_LICENSE_POLICY_TEMPLATE`, no un parámetro de consulta en el reproductor. Ver [license policies](https://docs.drm-x.com/es/license-service/policies).



**Sin conexión necesita su propia política persistente.**Set `playback_policy.persistent` a cierto en la política del servidor y elegir la licencia, alquiler, reproducción y duración de renovación. La referencia utiliza 7 días / 3 días / 48 horas / 7 días respectivamente; estas son reglas de negocio, no aprobación del estudio o un predeterminado de toda la plataforma. La licencia Widevine sigue siendo autorizada.



## 5. Path A: play through DrmXPlayer[#](#android)



Dentro de su Actividad, crear un `PlayerView`, utilizar una ventana segura y llamar `prepare` después de iniciar sesión. El envoltorio intercambia la sesión, configura Widevine, aplica la altura máxima devuelta y libera la reserva de streaming cuando se detiene. Las importaciones vienen de `com.haihaisoft.drmx.player` y 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()
```



Registro `DrmXPlayerListener` para autorizar, listo, jugar y error UI. Mostrar códigos de error seguros en lugar de dumping fichas de licencia o cuerpos de respuesta. Utilice la muestra de teléfono incluida para el ciclo de vida completo, controles de pantalla completa y cola de descarga.



## Controles de reproductores y rotación ininterrumpida [#](#player-controls)



Pulsa **Juega** en el vídeo para iniciar la autorización. Abrir la pantalla o Ajustes no adquiere una licencia de streaming. Pausa/resume, cambiando la configuración y girando el teléfono preserva la sesión actual.



![DRM-X Android reproductor Ajustes en un Samsung S24 Ultra, mostrando Calidad, Audio track, Subtítulos/CC, Playback speed y Sleep timer.](https://docs.drm-x.com/assets/android-player-settings-preview4.png)Ajustes reales de un Samsung S24 Ultra. Pulse una fila para ver sus opciones disponibles; el submenú de audio muestra nombres de idiomas completos. Calidad y idiomas disponibles dependen del contenido, licencia y dispositivo. 

| Control | Qué pueden hacer los clientes | 
| --- | --- |

| Calidad | Elija Auto o una resolución admitida y licenciada del manifiesto. Las opciones no disponibles explican la restricción de licencia o dispositivo/DRM. Auto obedece las mismas restricciones. | 

| Carril de audio | Elija cualquier idioma de audio compatible suministrado por el paquete. | 

| Subtítulos/CC | Elija un idioma de capción o desactivado. El botón CC aparece cuando existen leyendas soportadas y las toggles directamente. | 

| Aparecimiento de la capa | Ajuste la fuente, el color y el tamaño del texto, los colores de fondo/ventana y la opacidad. Las preferencias se guardan en el dispositivo. | 

| Velocidad de reproducción / temporizador de sueño | Juega a 0.25–2× velocidad o pausa automáticamente después de un intervalo elegido. | 

| Buscar / pantalla completa | Extiende la línea de tiempo, busca 10 segundos, o doble punción a cada lado. Girar al paisaje sin reiniciar la reproducción. | 


Las tres acciones compactas debajo de la imagen son **Descargar** (para el uso offline), **Play offline** y **Renew** (la licencia sin conexión). La muestra descarga una entrega de vídeo permitida y las pistas de audio/subtítulos compatibles; los menús offline sólo exponen los medios descargados.



**Copiar el registro** al lado de la página de información copias recientes errores como texto, para que pueda enviar soporte los detalles sin una captura de pantalla. ID, operación, SDK/Android/versión de dispositivos, numérico CDM/Media3 errores, marcos de pila seleccionados, estado de conexión/descarga/playback y un recuento de repetición. credenciales, URLs de medios, claves, identificaciones clave, datos de licencia y completos CDM Idiotas.



La muestra conserva hasta 32 informes locales (64) KB) durante cinco horas. Cuando se conecta y se firma, carga errores pendientes al backend del cliente configurado, al menos una vez por 30 segundos. El servidor demo público conserva advertencias/errores durante cinco horas, combina eventos repetidos, tapa el archivo a 512 KB y ciruelas cada minuto. El progreso rutinario y la reproducción exitosa no se han registrado. Las integraciones personalizadas deben suministrar explícitamente su propio origen `drmx.diagnosticsEndpoint`; errores de diagnóstico nunca interrumpen la reproducción.



**¿La licencia sin conexión ya está en uso?**Previsualización.5 corrige el caso S24 que reportó CDM 368 con “License cargado en diferentes sesiones.” Pressing Play offline vuelve a reanudar el existente reproductor. Renovar primero libera su sesión DRM, luego renueva la licencia almacenada. La licencia de descarga y persistente se conserva; no se requiere reseteo de app-data. 

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



El manifiesto del teléfono suministrado maneja cambios de orientación y tamaño de pantalla; su `onConfigurationChanged` redimensiona la misma Actividad y reproductor. Mantenga ambas partes al adaptarla. Un host que recrea Actividades debe retener a su propietario de reproducción y reajustar la vista. La muerte del proceso es diferente de la rotación y necesita la restauración del estado normal. Las opciones 4K/8K requieren contenido de combinación, derecho de licencia y soporte de dispositivo/DRM; el CDM remainsita



Para uso personal reproductores, parar y liberar el activo fuera de línea DRM sesión antes de comprobar, renovar o liberar la misma licencia persistente. SDK's `player.stopForOfflineLicenseOperation { /* call manager here */ }` espera por el Media3 cola de reproducción para liberar los medios. `stop()` colas que trabajan asincrónicamente. No realizar un separado `checkLicense()` mientras que la misma licencia está jugando; la reproducción misma valida su CDM estado. La muestra suministrada muestra las acciones completas de desactivación y superposición durante la renovación.



## 6. Adquirir una licencia y descargar los medios [#](#offline-download)



Prepara la reproducción sin conexión

Necesitas tanto la descarga completa como una licencia válida vinculada al dispositivo.

- 01**Descarga el contenido cifrado**

Mientras tengas conexión, descarga el vídeo y las pistas de audio y subtítulos que necesites.

- 02**Obtén una licencia persistente**

Mientras tengas conexión, obtén la licencia Widevine vinculada al dispositivo. Guarda su Key-set ID opaco de forma privada.

- 03**Comprueba que todo esté listo**

Confirma que la descarga esté completa y que la licencia sea válida.

- 04**Reproduce sin conexión**

Lee el contenido en caché y restaura la licencia local. La renovación y la liberación requieren conexión a Internet.

Listo fuera de línea = medio cifrado completo + una licencia válida de dispositivo. Ninguno reemplaza al otro. 

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



El SDK guarda el opaque Widevine conjunto de llaves ID y metadatos no secretos. El CDM tiene la licencia. No persiste DRM License Tokens, claves primas, retos o bytes de respuesta de la licencia. Excluir `com.haihaisoft.drmx.offline.licenses.v1.xml` de copias de seguridad.



Crear una propiedad de la aplicación Media3 `SimpleCache`, gestor de descarga y base de datos. Use `DownloadHelper` con la licencia MediaItem y select video dentro `maximumHeight`. En Media3 1.11 el callback preparado es `onPrepared(helper, tracksInfoUpdated)`. La muestra de teléfono `enqueueDownload` y `OfflineTestRuntime` muestran la ejecución ejecutable.



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



Descargue cada idioma que tenga la intención de exponer fuera de línea. Llamando `createDownloadRequest` sin teclas de flujo descarga todas las pistas, que es generalmente desperdicio para vídeo adaptativo. Mostrar progreso de descarga y esperar `Download.STATE_COMPLETED`. Para transferencias de fondo de producción, integre Media3 `DownloadService`, límites de almacenamiento y Android requisitos de servicio/notificación de primer plano; la cola de demostración funciona en proceso.



**Contenido sin conexión multi-key:**Media3 restaura un conjunto de teclas ID para un elemento de reproducción. Su licencia persistente debe contener todas las claves requeridas para el vídeo seleccionado, audio y otras pistas cifradas. Pruebe esto con su paquete real y la política del servidor; permitiendo que múltiples sesiones no combinan licencias offline independientes.



## 7. Sendero B: restaurar con su existente Media3 reproductor[#](#existing-media3)



Lea la descarga completada y use sus teclas de flujo exactas. La descarga y la reproducción debe compartir la misma ubicación de caché y la asignación de claves. Este ejemplo deshabilita deliberadamente el acceso de arriba para la reproducción estricta offline:



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



Para utilizar el mismo flujo sin conexión con Path A, construir `DrmXPlayer` con este `mediaSourceFactory` y llamar `player.prepareOffline(playback)`. Restauración ordinaria utiliza la licencia local CDM sin solicitar una nueva. Una entrada de caché falta debe producir un error durante la aceptación desconectada, no cambiar silenciosamente a Internet.



## 8. Sendero C: nativo Media3, no DRM-X AAR[#](#configure-media3)



Usted no está obligado a utilizar DrmXPlayer o DrmXOfflineManager. Agregue las tres dependencias Media3 del paso 3 e implemente el mismo contrato de backend del cliente. Use `session.manifestUrl` (no `session.manifest.url`), la licencia devuelta URL y los encabezados de autorización delimitados. Aplique la altura máxima devuelta y maneje la liberación de sesión de streaming en parada, reemplazo, final, falla y destrucción.



La actividad nativa completa a continuación se compila como `samples:native` junto con esta SDK liberación. Maneja HTTPS, lecturas de la respuesta atada, autorización de fondo, respuestas a las etapas y la liberación de la reserva. Reemplazar el punto final marcado /Content ID y proporcionar su propia ficha de inicio de sesión. El ejemplo es un punto de partida; añadir su actualización de producción, conmutación de cuenta y ciclo de vida de aplicación.

Actividad nativa completa de streaming (Kotlin, no DRM-X dependencia)

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

```



Para licencias nativas fuera de línea, utilice `OfflineLicenseHelper.downloadLicense`, persiste su opaque key-set ID en privado y restaurar con `MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID).setKeySetId(id)`. El siguiente ayudante compilado cubre adquisición, cheques de duración, renovación, lanzamiento y descarga / juegos. Es **no una aplicación completa de descarga**: todavía posee el caché, servicio de descarga, progreso, metadata, unión de usuario/dispositivo y autorización de operación de confianza.

Ayudador persistente nativo (Kotlin, no DRM-X dependencia)

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

```



Antes de llamar a este ayudante de bajo nivel, valide que la respuesta de backend fresco coincide con la Content ID solicitada, operación, usuario/dispositivo firmado y período de validez de autorización. Persiste un estable sin conexiónLicenseId y confiaba en los metadatos manifiesto/track junto al conjunto clave ID, nunca el token. Ejecutar bloqueo de ayuda llama a un hilo obrero. Para licencia HTTP headers use `setLicenseRequestHeaders` en la configuración de DRM; Widevine parámetros opcionales de recuperación de clave son un mapa diferente.



Las nuevas integraciones deben usarse [AndroidX Media3 DRM apoyo](https://developer.android.com/media/media3/exoplayer/drm)Legacy `com.google.android.exoplayer2` los proyectos deben seguir [guía oficial de migración](https://developer.android.com/media/media3/exoplayer/migration-guide); no mezclar el legado y AndroidClases X o asumen que la muestra compila sin cambios en versiones anteriores.



## 9. Revisar, renovar, soltar y cambiar idiomas [#](#lifecycle-and-offline-behavior)



Estos son acciones separadas. Ejecute uno a la vez después de la sesión activa offline DRM ha sido liberado: use `player.stopForOfflineLicenseOperation { /* one operation below */ }` con DrmXPlayer, o suelte el nativo existente Media3 reproductor primero. No compruebe una licencia mientras está jugando. Un cheque es opcional para la duración de visualización; no debe evitar la renovación de una licencia vencida.



```
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` lee metadatos; `checkLicense` consultas autorizadas locales CDM duraciónes. Renovar/release requiere conectividad y una nueva autorización de operación específica de su backend. Eliminar los medios no libera la licencia. Cuando se desconecte, la cola de liberación para la próxima conexión en lugar de informar que el servidor lo ha confirmado. Cerrar el administrador cuando su propietario es destruido.



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



Construir opciones de pistas disponibles y desactivar opciones indisponibles. Una preferencia no puede crear una pista de Inglés ausente del paquete. Los usuarios sin conexión sólo pueden seleccionar idiomas descargados. Preserve license/cache state across screen rotación; pantalla completa es un cambio de vista/ventana, no una razón para adquirir otra licencia.



## 10. Implementar el endpoint de autorización sin conexión[#](#offline-backend-contract)



La aplicación post `contentId`, `contentType: "vod"`, `drmSystem: "widevine"`, `playbackMode: "offline"`, `operation: "acquire" | "renew" | "release"`, `offlineLicenseId` (null for initial acquisition) and truthful `platformCapabilities`. Verifica que los IDs de renovación/renuncia pertenecen al usuario autenticado, dispositivo y contenido. Seleccione el lado del servidor de políticas, emita un nuevo DRM License Token y devuelva el contrato de coincidencia:



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



Estos son marcadores de posición explicativos, no una respuesta de licencia reutilizable. Devuelva los valores frescos de la operación real. El SDK también admite `integrationMode: "token-proxy"` con un descriptor vinculado `drm.authorization`. Regresar `Cache-Control: no-store`; nunca log successful authorization bodies. Ver el descriptor SDK descargable `OFFLINE_INTEGRATION.md` y PHP muestra de punto final sin conexión para el manejo de solicitudes de concreto.



## 11. Prueba tus dispositivos y solución de problemas [#](#device-qualification)



### Widevine provisión y pruebas de red de China



El suministro obtiene el certificado de dispositivo antes de la adquisición de licencia de contenido. Desde la vista previa.9 la demo de teléfono envía la solicitud firmada de CDM a través de su relé de cliente autenticado HTTPS a Google. La respuesta firmada devuelve sin cambios. Esto ayuda cuando el teléfono no puede llegar directamente al servicio de suministro de Google; el relé del cliente debe ser accesible.



Provisioning 2.0 utiliza un llavero de fábrica. Provisioning 4.0 utiliza un modelo credencial de dispositivo diferente. Estas versiones están separadas de L1/L3 los niveles de seguridad y los reproductor no los cambia. L3 El teléfono puede jugar mientras que otro teléfono espera para el suministro de dispositivos. Las fuentes oficiales públicas revisadas aquí no establecen que China sólo admite 2.0. [NXP's guía de integración de dispositivos](https://www.nxp.com/docs/en/user-guide/UG10158.pdf) y [Android's provisioning implementation](https://android.googlesource.com/platform/packages/apps/RemoteProvisioner/+/refs/tags/android-13.0.0_r15/src/com/android/remoteprovisioner/WidevineProvisioner.java).



Avance.10 informes de error distinguen el transporte directo o relé, timeout/DNS/TLS/HTTP fracaso, HTTP estado y tiempo transcurrido. `provisioningModel: BootCertificateChain` indica el AOSP 4.0 modelo; `unknown` o `other` no establece 2.0. PHP Los registros distinguen la entrada rechazada de las fallas de Google. Los informes excluyen las cargas de pago firmadas, URLs, credenciales y identificadores de dispositivos únicos.



Actualice el APK en su lugar, retry streaming y una descarga en la red afectada, luego retenga el registro de copia y el tiempo aproximado. Se mantienen descargas y licencias existentes. Un relé no puede reparar no disponible Widevine o credenciales rechazado por el dispositivo/Google. Se requiere una aceptación real en el teléfono afectado.



Desde la vista previa.8, nativo L2/L3 o a nivel desconocido Widevine la reproducción se limita a SD (hasta 576p) antes de la decodificación. Android capacidades a través de su backend: la sesión API estreche el software Widevine licencias multi-key a audio/SD Las llaves. PHP muestra conserva estos campos y también tapa solicitudes fuera de línea. SD entrega y una política que permita el software DRM se requieren; reglas de seguridad únicas y uniformes de una sola llave permanecen sin cambios. Configuración utiliza un panel translúcido sin recortar el vídeo. Numeric `cryptoErrorCode` está incluido en informes de errores seguros.



| Prueba / síntoma | Comportamiento esperado / siguiente cheque | 
| --- | --- |

| SDK descarga devuelves 403 | Seleccione una organización con una suscripción profesional o Enterprise activa. Una actualización planificada después del juicio no desbloquea el SDK. | 

| Sesión 401 / 403 | Check login, derecho, Medio Ambiente, Publicado Content ID y política. Nunca desvíes la autenticación. | 

| Sin conexión falla pero las obras en línea | Compruebe CDM duración, descarga completa, ubicación de caché, teclas de flujo y todas las teclas de licencia requeridas. | 

| Inglés / chino desaparecido | Comprueba los metadatos de idioma del paquete, las pistas reales y la selección de idiomas fuera de línea. | 

| L1, HDCP o fracaso de decodificador | Comprobar hardware físico, provisiones, codec y requisitos de salida. Una cadena de capacidad no es certificación. | 

| reinicio, rotación, actualización | Persistent license and cache survivor supported lifecycle changes without duplicate acquisition. Las copias de seguridad no deben migrar licencias a un nuevo dispositivo. | 

| Modo de avión | Desactivar el Wi-Fi y los datos móviles también; reiniciar la aplicación, jugar localmente y cambiar cada idioma prometido. | 

| Expirsión, presión de almacenamiento, interruptor de cuenta | Mostrar un estado indisponible/renovador claro, recuperar medios incompletos y hacer cumplir el acceso de usuario/dispositivo sin filtrar el catálogo sin conexión de otro usuario. | 


Califique cada teléfono compatible, tableta, Android TV/Google TV, Fire TV o incrustado Android modelo para Widevine, decodificación segura, codec/resolución, HDCP, pantallas externas, cambios de reloj y larga desconexión. No cada dispositivo Android tiene las mismas capacidades DRM. El contenido de Hollywood requiere la protección y los requisitos de dispositivo del titular de derechos, no sólo decodificación exitosa.



**¿No hay oportunidad de adquirir antes de la desconexión?**Esto es un despliegue diferente: una licencia de servicio a bordo calificada emite licencias sobre la red local durante el viaje. No está permitido simplemente mediante el uso de esta SDK. DRM-X puede trabajar con usted en un despliegue personalizado que cubre los permisos del proveedor, provisión, protección clave y una prueba de aceptación totalmente desconectada. [a bordo DRM para aeronaves, autobuses, trenes y barcos](https://multi-drm.drm-x.com/es/solutions/on-board-drm).



## Universal Player listas de reproducción y controles compartidos[#](#universal-player-playlists-and-shared-controls)



The Android preview.11 and later evaluation apps and current Android/Apple development sources share in-video Previous/Play-Pause/Next, CC, Settings and fullscreen controls with two-second hiding during playback. Android's playlist menu no longer includes “Play next”. Selected shuffle/repeat settings remain highlighted and persist across restarts. With both off, playback follows the saved playlist order and stops at the final video. Drag handles reorder items without restarting the current video; the saved order also applies to the downloaded playlist. These latest source APIs are not yet included in the Console's preview.10 SDK bundle.



Proporcione el mismo cliente/viewer `storageNamespace` a `DrmXPlayer` y `playerView.storageNamespace` antes de configurar la lista de reproducción. VOD progreso se guarda periódicamente y en pausa / parada, luego restaurado para que Content ID en streaming o descargado de reproducción. La reproducción en vivo no utiliza una posición VOD guardada las mismas funciones shuffle/repeat/resume sin una fuente de medios de red.



Las ediciones Web y Android utilizan un panel de configuración oscura consistente con controles azules compactos. Las listas de reproducción soportan el siguiente/previous, shuffle, repetir uno/todo y el avance automático cancelable. Cada artículo utiliza una nueva solicitud de reproducción autorizada. Los metadatos de capítulo y transcripción provienen de su catálogo de contenido.



Velocidad de reproducción, temporizador de sueño, subtítulos, calidad permitida, idiomas de audio, marcadores de sesión y bucle A–B están disponibles. Web añade atajos de teclado, curriculum vitae opcional, búsqueda de transcripciones y navegador soportado PiP. Android conserva nativo Widevine descargas y renovación sin conexión.



### Ajustes de los logotipos profesionales y empresariales



La marca se mantiene fuera de la superficie de vídeo. Abrir configuración → Acerca reproductor para el logotipo autorizado, información del producto y reproductor versión. Web también ofrece esta información sobre el clic derecho. La demo del teléfono público incluye WayMaker, Goodness of God, Fresh Oil y 4K Test Video; cada selección recibe su propia autorización.



Set `playerBranding` en su backend de confianza a `{"mode":"hidden"}` o `{"mode":"custom","logoUrl":"https://your-site.example/logo.png","label":"Your brand"}`. La plataforma comprueba la suscripción activa del Profesional o de la Empresa. Otros planes usan `{"mode":"default"}`; solicitudes personalizadas no autorizadas/hidden devuelven 403. Esto está separado de las marcas de agua de seguridad.



El PHP descarga incluye la lista de reproducción editable y configuración de marca. Android cliente ZIP incluye el ejemplo de la lista de reproducción. Mantenga la autorización de lista de reproducción en su backend; nunca incrustar API cliente credenciales en ambos reproductor.

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