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.
Prueba el Android reproductor
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:
8fa044b82c0abab7ce2a987f349524fd7ad1e78f3c897771f4764cb2aa521180Con Android herramientas de plataforma y un dispositivo autorizado USB:
adb install -r drmx-android-player-1.0.0-preview.12.apkInstala 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#
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#
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 #
Utilice DrmXPlayer, la muestra de teléfono y el administrador sin conexión. Mantenga su propio login y reglas de negocio.
PATH B KEEP YOUR UIMedia3 + DRM-X ayudantesMantenga una pila existente reproductor y descargue. Deje que DrmXOfflineManager administre licencias persistentes.
PATH C · NATIVE ONLYMedia3 sin el AAREjecute el mismo contrato de backend y el ciclo de vida de licencia nativa. No DRM-X reproductor dependencia.
Descargue el ZIP o AAR del SDK de Android en 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#
- 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 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.
| Cambiar este valor | Donde pertenece | Significado |
|---|---|---|
| período de sesiones | 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 | Tu backend | Derecho de negocio y mapeo de paquetes. No acepte estos de la aplicación. |
3. Instalar y ejecutar el 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
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. Abrir su carpeta raíz en Android Studio.
- Keep
libs/drmx-universal-player-1.0.0-preview.10.aarin 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 dentrodependencies. 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
applicationIdacom.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 abajo al añadir esta vista a su propia aplicación.
4. Solicitud de servidor y política predeterminada#
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 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.
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#
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 #
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.

| 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.
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 #
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.
7. Sendero B: restaurar con su existente Media3 reproductor#
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#
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 apoyoLegacy com.google.android.exoplayer2 los proyectos deben seguir guía oficial de migración; 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 #
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#
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 #
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 y Android's provisioning implementation.
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.
Universal Player listas de reproducción y controles compartidos#
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.
DRM-X 6.0