DRM-X 6.0 logo de pomba e ramo de oliveiraDRM-X 6.0Documentação
◎ Português

SDK Android e Media3 nativo

Baixe o reprodutor Android com listas que podem ser reordenadas por arraste, configurações salvas de reprodução aleatória e repetição, e retomada offline. Integre a reprodução protegida usando DRM-X ou Media3 nativo.

Ver MarkdownExemplos onlineAtualizado em 2026-10-26

Para personalizar a interface e consultar valores padrão e exemplos de ciclo de vida, leia PLAYER-UI-CONFIGURATION.md. Os painéis pop-up do player são opacos; a opacidade das legendas e marcas d’água é configurada separadamente.

Experimente a demonstração PHP seguindo DEMO-QUICKSTART.md no SDK ZIP. Para usar seu próprio conteúdo, integre primeiro o site e o backend; depois, siga MOBILE-AND-TV-APPS.md para conectar o aplicativo ao mesmo sistema de login e às mesmas regras de acesso.

Baixe o Android SDK e os aplicativos de exemplo em Console → Developer Tools → Device SDKs com uma assinatura Professional ou Enterprise ativa.
Choose your player, keep one license service. Use the DRM-X 6.0 Android SDK for a guided integration, or keep your native Media3 player. Both connect to your authenticated backend and Widevine. The Android SDK supports streaming on compatible API 23+ devices. Persistent offline VOD is supported for phones and tablets; OTT devices use streaming only.

Tente o Android reprodutor #

Baixe, instale, pressione Play. O aplicativo de avaliação gratuito usa nosso vídeo de demonstração público e faz login automaticamente. Você não precisa criar um APK ou inserir credenciais DRM-X para experimentá-lo.

Baixar a amostra Android APK

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

  1. Abra esta página no seu telefone Android e baixe o APK.
  2. Abra o arquivo baixado. Se Android perguntar, permita a instalação do navegador ou gerenciador de arquivos que você usou, então toque em Instalar .
  3. Abrir DRM-X 6.0 player, mantenha-se ligado, e toque no vídeo Reproduzir botão. O aplicativo solicita uma licença de streaming quando você pressiona Play.
  4. Abrir Configurações para escolher a qualidade, áudio, legendas ou velocidade de reprodução. Rodar o telefone para continuar em tela cheia.
  5. Abra o menu de três linhas do vídeo e escolha Download. Ative Downloaded videos para Offline Mode; desative para Online Mode. Em Offline Mode, escolha Delete no menu desse vídeo e confirme. A renovação da licença exige conexão e autorização vigente; diante de um erro offline, você pode continuar offline ou mudar para online.

Os downloads de mídia já em fila de espera continuam em segundo plano. Se Android parar o aplicativo enquanto prepara licenças ou faixas, reabri-lo enquanto estiver conectado para terminar a preparação dos itens restantes. Os downloads existentes são retidos. A reprodução offline usa apenas mídia em cache; downloads incompletos ou expirados requerem conclusão ou renovação.

A amostra APK é pública e assinada para avaliação. O AAR e o SDK completo abaixo permanecem disponíveis através de contas de Console elegíveis. Mantenha sua própria chave de assinatura de aplicativo de produção e login de infraestrutura separados desta demonstração.

APK checksum e USB instalação

SHA-256:

b96ba527ddf9abae5bff2f8b85a39a12b59c934aaacde1961c8096aa32524c54

Com ferramentas de plataforma Android e um dispositivo autorizado USB:

adb install -r drmx-android-player-1.0.0-beta.11.apk

Instale este APK sobre a amostra oficial existente sem desinstalá- lo. A mesma chave de assinatura e aplicação ID preservar downloads, licenças offline, posições salvas e preferências. Se o seu próprio build usar outra chave de assinatura, use um aplicativo separado ID para manter ambos os aplicativos e preservar as licenças offline da amostra.

Download controls and language names in preview.40#

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

Em Offline Mode, abra o menu de três linhas do vídeo e escolha Delete. Confirme para remover apenas a mídia criptografada e as referências de licença locais desse vídeo; Cancel as mantém. Reconecte-se para baixá-lo novamente.

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

Offline artwork and downloads in preview.39#

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

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

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

Live DRM in preview.13#

Lançado em 17 de setembro de 2026. O aplicativo de telefone e o cliente SDK incluem teste 4K Live DRM. A reprodução ao vivo usa manifestos novos e não pode ser baixada. O evento de teste está disponível somente durante uma transmissão ativa. Siga o guia de configuração AWS e OBS para criar seu próprio evento.

O que há de novo em Android reprodutor preview.12#

Lançado em 14 de setembro de 2026. Corrige a reprodução transferida, fazendo uma solicitação de licença inesperada quando uma licença Widevine expirar ou tiver um minuto ou menos restante. Isto pode aparecer como Media3 erro 6004, um erro HTTP enquanto estiver conectado, ou um erro DNS enquanto estiver desconectado.

A reprodutor agora restaura a licença baixada localmente. As licenças válidas mantêm o tempo restante de reprodução; as licenças expiradas mostram Conectar e tocar em Renovar . As regras de validade e proteção da licença continuam a ser aplicadas pelo dispositivo. Os diagnósticos também incluem o status numérico HTTP quando uma operação de rede falha, sem expor cabeçalhos de requisição, URLs ou respostas de licença.

  1. Instale preview.12 sobre o aplicativo oficial existente. Mantenha seus dados e downloads atuais do aplicativo.
  2. Se WayMaker ou outro vídeo baixado precisar de renovação, conecte-se à internet, selecione esse vídeo e toque em Renew.
  3. Aguarde por Licença renovada. Pronto para jogar offline. , então escolha Jogue offline . O vídeo criptografado permanece baixado.

This release retains preview.11's controls and playlist features. The public evaluation app and its embedded player identify preview.12; at that release, the separately distributed Console SDK was preview.10 / bundle 20260910.8.

O que há de novo em Android reprodutor preview.11#

Lançado em 14 de setembro de 2026. Este aplicativo de avaliação inclui os mais recentes controles reprodutor e melhorias na lista de reprodução, com um estilo de interação consistente entre os nossos Android e Apple reprodutores.

  • Controles dentro do vídeo: centralizado Reproduzir/Pausa, Anterior e Seguinte para playlists multi-vídeo, CC, Configurações e um botão de tela cheia. Controles escondem-se após dois segundos durante a reprodução; toque no vídeo para revelá- los novamente.
  • Capções e configurações de reprodução: use CC para mostrar ou esconder legendas disponíveis, e Configurações para escolher o idioma de legenda, áudio, velocidade de reprodução e qualidade permitida. As escolhas off-line usam apenas faixas incluídas no download.
  • Drag to reorder: arraste o punho de um item da lista de reprodução para alterar a sua posição. A ordem salva sobrevive a uma reiniciação de uma aplicação e é refletida na lista de reprodução baixada. A reordenação mantém o vídeo atual reproduzido. Toque na alça para as ações acessíveis Mover para cima / Descer.
  • Embaralhamento salvo e repetir: os modos seleccionados permanecem realçados e sobrevivem ao reiniciar na reprodução online e offline. Com o embaralhamento e a repetição de ambos, os vídeos seguem a ordem gravada e param após o item final. A acção "Play next" do menu da lista de reprodução foi removida.
  • Resuma onde você parou: a posição de cada vídeo salvo VOD é restaurada quando você retorna à reprodução de streaming ou baixado. O progresso é salvo neste dispositivo para o visualizador atual; ele não sincroniza entre dispositivos.

Organize playlists, salve a ordem e retome a reprodução. A reprodução offline exige um download concluído e uma licença offline utilizável e não expirada.

The preview.11 release updated the public evaluation APK. The current Console SDK is 1.0.0-beta.14, bundle 20261028.1; the integration instructions below use that bundle.

1. Escolha um caminho de integração#

Baixe em Developer Tools → Device SDKs. O uso do SDK exige acesso Professional ou Enterprise ativo, incluindo avaliações Professional aprovadas. Standard mantém as APIs nativas para seu próprio player. O acesso ao SDK é verificado na criação da sessão e na renovação da licença; licenças offline expiram no máximo ao fim do período de serviço da conta.

Current preview: 1.0.0-beta.14 · customer bundle 20261028.1 · 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 seu conteúdo e infraestrutura #

  1. Empacote e publique um título de Widevine compatível com DASH no seu Ambiente DRM-X. Copie o valor exacto Publicado Content ID.
  2. Configurar o servidor Environment ID, Site Key e Access Key. Utilização o download possível PHP integração para um ponto de partida de trabalho.
  3. Implemente a verificação de login e direito do usuário. Seu aplicativo chama sua infraestrutura com seu token de usuário de curta duração; sua infraestrutura escolhe o conteúdo, política e URLs confiáveis.
  4. Execute a reprodução online primeiro em um dispositivo com capacidade física Widevine. Adicione a aquisição offline apenas após o contrato de streaming funcionar.

O vídeo criptografado e as licenças DRM seguem caminhos diferentes

A CDN entrega o vídeo criptografado. Seu backend verifica o acesso, e o serviço de licenças DRM autoriza a descriptografia em um dispositivo compatível.

  1. Armazenamento / CDN

    Entrega a mídia criptografada ao player.

  2. Seu backend

    Verifica se o espectador pode assistir e solicita a autorização de reprodução ao DRM-X.

  3. DRM-X

    Valida a autorização de reprodução e processa a solicitação de licença DRM.

  4. Player + dispositivo

    Usa o sistema DRM do dispositivo para obter uma licença e reproduzir a mídia criptografada.

Entrega de mídia e entrega de licença são separadas. Somente a infraestrutura do cliente detém DRM-X servidor credenciais.
Alterar este valorOnde pertenceSignificado
sessionEndpointAndroidSeu endpoint de reprodução-sessão HTTPS, não o servidor de licença bruto URL.
offlineEndpointAndroid, apenas offlineO seu ponto final de autorização de aquisição/renovação/lançamento.
contentIdAndroid request; a infra- estrutura validaO visualizador tem o direito de acesso protegido pelo Published Content ID.
token de acesso ao clienteMemória da aplicação após o loginSeu token de usuário de curta duração, nunca um Site Key ou Access Key.
Environment ID, Site Key, Access KeyApenas configuração secreta do servidorDRM-X conta credenciaisNunca os envies numa nave. APK, HTML ou controlo da fonte.
Política e manifesto de confiança URLSeu backendDireito de negócio e mapeamento de pacotes. Não aceite estes a partir do aplicativo.

3. Instalar e executar o 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 uma aplicação existente, copie a pasta ZIP maven/ na sua raiz do projecto. Em settings.gradle.kts:

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

No módulo do seu aplicativo build.gradle.kts:

dependencies {
    implementation("com.haihaisoft.drmx:drmx-universal-player:1.0.0-beta.14")
}
Instalando o autônomo AAR em vez disso

Coloque o AAR no módulo do seu aplicativo libs/. Um plano AAR não tem resolução de dependência transitiva automática, então adicione as três dependências Media3 correspondentes. Não instale ambos os formulários.

implementation(files("libs/drmx-universal-player-1.0.0-beta.14.aar"))
implementation("androidx.media3:media3-exoplayer:1.11.0")
implementation("androidx.media3:media3-exoplayer-dash:1.11.0")
implementation("androidx.media3:media3-ui:1.11.0")

Em AndroidManifest.xml, permitir o acesso à Internet e desativar backups para esta amostra mínima. Em um aplicativo de produção que precisa de backups, especificamente excluir metadados de licença offline ligado ao dispositivo.

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

Procure o exemplo de telefone MainActivity.kt para drmx.sessionEndpoint, drmx.offlineEndpoint, drmx.contentId e customer.loginEndpoint. Altere as configurações de recuo marcadas, ou passe estes extras de Intenção de sua aplicação após o login. Passe customer.accessToken em tempo de execução. O ZIP README explica cada parâmetro e quais ganchos de demonstração para substituir.

Construir um teste APK usando o AAR #

Este exemplo usa o projeto de telefone executável a partir do SDK ZIP completo e explicitamente liga o autônomo AAR. A mesma abordagem funciona para uma aplicação Android existente.

  1. Download and extract the full Android SDK ZIP. Open its root folder in Android Studio.
  2. Keep libs/drmx-universal-player-1.0.0-beta.14.aar in the extracted root. Use JDK 17, Android SDK 36 and the included Gradle wrapper.
  3. Em samples/phone/build.gradle.kts, substitua a dependência DRM-X do Maven pelas seguintes entradas dentro dependencies. Mantenha o resto desse arquivo de compilação.
implementation(files(rootProject.file("libs/drmx-universal-player-1.0.0-beta.14.aar")))
implementation("androidx.media3:media3-exoplayer:1.11.0")
implementation("androidx.media3:media3-exoplayer-dash:1.11.0")
implementation("androidx.media3:media3-ui:1.11.0")

Remova implementation("com.haihaisoft.drmx:drmx-universal-player:1.0.0-beta.14") para que o mesmo SDK não seja incluído duas vezes. Um plano AAR precisa das três dependências Media3 mostradas acima.

  1. Para manter o demo baixado instalado ao lado do seu próprio test build, altere o módulo de telefone applicationId para com.example.drmxtest. Deixe seu espaço de nomes inalterado para esta amostra fornecida.
  2. Compilar o APK, em seguida, instalá-lo em um dispositivo USB- conectado Android com USB depuração 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

Abre a aplicação de teste e toque em Reproduzir. A fonte de telefone fornecida usa a mesma demonstração pública inicialmente. Para testar o seu próprio título, substitua os parâmetros de sessão/offline/login marcados e Published Content ID em MainActivity.kt, e forneça a autenticação de curta duração do seu próprio utilizador em tempo de execução. O seu servidor mantém o Site Key e Access Key.

Os controles compactos vêm de DrmXPlayerView no AAR. A Atividade fornecida demonstra a reprodução para autorização, rotação responsiva, propriedade de download, reprodução e renovação offline. Veja o exemplo de integração de controle abaixo ao adicionar esta visualização à sua própria aplicação.

4. Requisição do servidor e política padrão#

A aplicação Android pede à sua infra- estrutura uma sessão de reprodução. A sua infra- estrutura autentica o utilizador, verifica o direito, os mapas Content ID para os media publicados e as chamadas DRM-X. Veja Reproduzir sessão API para o contrato de transmissão 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
  }
}

As capacidades conservadoras acima não afirmam Widevine L1. Use o auxiliar de capacidade do SDK, ou sua própria avaliação de dispositivo, para dispositivos reais. As capacidades do cliente ajudam a seleção de compatibilidade; eles nunca concedem direito de qualidade.

A amostra PHP é por omissão multi-tier-standard para reprodução online. Seleccione a sua política de uma chave única documentada quando o seu pacote usar uma chave. A sua política deverá corresponder ao modelo da chave do pacote e aos direitos do visualizador. Mude a configuração do servidor ou a sua configuração do ambiente DRMX_LICENSE_POLICY_TEMPLATE, não um parâmetro de consulta no reprodutor. Veja a política de licenciamento .

Offline precisa de sua própria política persistente. Defina playback_policy.persistent como true na política emitida pelo servidor e escolha as durações de licença, aluguel, reprodução e renovação. A referência utiliza 7 dias/3 dias/48 horas/7 dias respectivamente; estes são exemplos de regras de negócios, não de aprovação de estúdio ou um padrão para toda a plataforma. Retorne metadados correspondentes. A licença Widevine permanece válida.

5. Caminho A: jogar através do DrmXPlayer#

Dentro de sua Atividade, crie um PlayerView, use uma janela segura e chame prepare após o login. A embalagem troca a sessão, configura Widevine, aplica a altura máxima retornada e libera a reserva de streaming quando parada. As importações vêm de com.haihaisoft.drmx.player e 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()

Registre-se DrmXPlayerListener para autorizar, preparar, reproduzir e erro UI. Mostre códigos de erro seguros em vez de despejar tokens de licença ou corpos de resposta. Use a amostra de telefone incluída para o ciclo de vida completo, controles de tela cheia e fila de download.

Controles do player e rotação ininterrupta#

Toque em Reproduzir no vídeo para iniciar a autorização. Abrir a tela ou Configurações não adquire uma licença de streaming. Pause/resumir, alterando as configurações e girando o telefone preservar a sessão atual.

Captura atual das configurações em Samsung Android. A proteção contra capturas oculta a área principal do player.
Captura atual das configurações em Samsung Android. A proteção contra capturas oculta a área principal do player.
ControloO que os clientes podem fazer
QualidadeEscolha Auto ou uma resolução suportada e licenciada no manifesto. Opções indisponível explicam a restrição de licença ou dispositivo/DRM. Auto obedece às mesmas restrições.
Faixa de áudioEscolha qualquer idioma de áudio suportado fornecido pelo pacote.
LegendasCCEscolha um idioma de legenda ou Desligado. O botão CC aparece quando existem legendas suportadas e as ativa diretamente.
Aparência da legendaAjuste a fonte, a cor e o tamanho do texto, as cores de fundo/janela e opacidade. As preferências são salvas no dispositivo.
Velocidade de reprodução / temporizador de sonoReproduzir em 0,25-2× velocidade ou pausa automaticamente após um intervalo escolhido.
Procurar / tela cheiaEsfregue a linha do tempo, procure 10 segundos ou toque duas vezes em ambos os lados. Rodar para a paisagem sem reiniciar a reprodução.

Cada menu de vídeo contém Move up / Move down e Download online ou Delete offline. Download fica desativado após a conclusão do download. O seletor Downloaded videos ativa a reprodução offline; somente as faixas em cache estão disponíveis. Não há botões separados para baixar, excluir ou baixar a lista inteira.

O log de cópia ao lado do rodapé da informação copia erros recentes como texto, para que você possa enviar suporte aos detalhes sem uma captura de tela. Os relatórios contêm um relatório ID, operação, SDK/Android/versão do dispositivo, erros numéricos CDM/Media3, quadros de pilha selecionados, conexão/download/estado de reprodução e uma contagem repetida. Excluem credenciais, URLs de mídia, chaves, IDs de chaves, dados de licença e despejos CDM completos.

A amostra mantém até 32 relatórios locais (64 KB) durante cinco horas. Quando conectado e conectado, ele envia erros pendentes para a infraestrutura configurada do cliente, no máximo uma vez por 30 segundos. O servidor público de demonstração mantém avisos/ erros por cinco horas, combina eventos repetidos, fecha o arquivo em 512 KB e poda a cada minuto. O progresso rotineiro e a reprodução bem- sucedida não são registrados. As integrações personalizadas devem fornecer explicitamente a sua própria origem drmx.diagnosticsEndpoint; erros de diagnóstico nunca interrompem a reprodução.

Licença off-line já em uso? Preview.5 corrige o caso dispositivo Samsung Android que relatou CDM 368 com “Licença carregada em sessão diferente”. Pressionar Play offline novamente retoma o player existente. Renew primeiro libera sua sessão DRM e depois renova a licença armazenada. O download e a licença persistente são preservados; nenhuma redefinição de dados do aplicativo é necessária.
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()

O manifesto do telefone fornecido manipula as mudanças de orientação e tamanho do ecrã; o seu onConfigurationChanged redimensiona a mesma Actividade e reprodutor. Mantenha ambas as partes ao adaptá- la. Uma máquina que recria as Actividades deverá manter o seu proprietário de reprodução e religar a visualização. A morte do processo é diferente da rotação e necessita de uma restauração normal do estado. As opções 4K/8K requerem o mesmo conteúdo, direito à licença e suporte ao dispositivo/DRM; o CDM continua a ser autoritário.

Para uma licença personalizada reprodutores, pare e solte a sessão ativa offline DRM antes de verificar, renovar ou liberar a mesma licença persistente. A SDK player.stopForOfflineLicenseOperation { /* call manager here */ } espera que a fila de reprodução Media3 libere a mídia. As filas simples stop() que funcionam assíncronas. Não execute uma checkLicense() separada enquanto a mesma licença estiver a ser reproduzida; a reprodução em si valida o seu estado CDM. A amostra fornecida mostra o handoff completo e desactiva as ações sobrepostas durante a renovação.

6. Adquira uma licença e baixe a mídia#

Prepare a reprodução offline

São necessários tanto o download completo quanto uma licença válida vinculada ao dispositivo.

  1. Baixe a mídia criptografada

    Enquanto estiver online, baixe o vídeo e as faixas de áudio e legendas necessárias.

  2. Obtenha uma licença persistente

    Enquanto estiver online, obtenha a licença Widevine vinculada ao dispositivo. Armazene seu Key-set ID opaco de forma privada.

  3. Verifique se tudo está pronto

    Confirme que o download está completo e que a licença é válida.

  4. Reproduza offline

    Leia a mídia em cache e restaure a licença local. A renovação e a liberação exigem conexão com a internet.

Pronto off-line = mídia criptografada completa + uma licença válida ligada ao dispositivo. Nenhum dos dois substitui o outro.
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 */ }
}

O SDK salva o conjunto de teclas Widevine opaco ID e metadados não secretos. O CDM detém a licença. Não persiste DRM License Tokens, chaves brutas, desafios ou bytes de resposta de licença. Excluir com.haihaisoft.drmx.offline.licenses.v1.xml das cópias de segurança.

Criar uma aplicação Media3 SimpleCache, gerenciador de download e banco de dados. Use DownloadHelper com o MediaItem licenciado e selecione vídeo dentro maximumHeight. In Media3 1.11 o callback preparado é onPrepared(helper, tracksInfoUpdated)A amostra do telefone. enqueueDownload e OfflineTestRuntime mostrar a implementação executável.

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

Baixe cada idioma que você pretende expor offline. Chamando createDownloadRequest sem chaves de transmissão baixa todas as faixas, que geralmente é um desperdício para o vídeo adaptativo. Mostre o progresso do download e aguarde por Download.STATE_COMPLETED. Para transferências de fundo de produção, integre Media3 DownloadService, limites de armazenamento e Android requisitos de primeiro plano/notificação; a fila de demonstração é executada em processo.

Conteúdo offline com múltiplas chaves: Media3 restaura um conjunto de chaves ID para um item de reprodução. Sua licença persistente deve conter todas as chaves necessárias para o vídeo, áudio e outras faixas criptografadas selecionadas. Teste isso com seu pacote real e política de servidor; habilitar múltiplas sessões não combina licenças off-line independentes.

7. Caminho B: restaurar com seu existente Media3 reprodutor #

Leia a transferência completa e use as suas teclas de transmissão exatas. A pilha de transferência e reprodução deverá partilhar a mesma localização da 'cache' e o mapeamento de chaves. Este exemplo desactiva deliberadamente o acesso a montante para uma reprodução off- line estrita:

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 usar o mesmo fluxo offline com o Caminho A, construa DrmXPlayer com este mediaSourceFactory e chame player.prepareOffline(playback). A restauração ordinária usa a licença local CDM sem solicitar uma nova. Um item de cache ausente deve produzir um erro durante a aceitação desconectada, não mudar silenciosamente para a internet.

8. Caminho C: nativo Media3, no DRM-X AAR #

Você não é obrigado a usar DrmXPlayer ou DrmXOfflineManager. Adicione as três dependências Media3 da etapa 3 e implemente o mesmo contrato de back-end do cliente. Use session.manifestUrl (não session.manifest.url), a licença retornada URL e cabeçalhos de autorização de curta duração. Aplique a altura máxima retornada e administre a liberação da sessão de streaming em caso de parada, substituição, término, falha e destruição.

A Atividade nativa completa abaixo é compilada como samples:native ao lado desta versão SDK. Ela lida com HTTPS, leituras de resposta limitadas, autorização de fundo, respostas antigas e liberação de reservas. Substitua o endpoint/ Content ID marcado e forneça seu próprio token de login. O exemplo é um ponto de partida; adicione seu ciclo de vida de atualização de produção, mudança de conta e navegação de aplicativos.

Atividade completa de transmissão nativa (Kotlin, nenhuma dependência DRM-X)
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 licenciamento off-line nativo, use OfflineLicenseHelper.downloadLicense, persista no seu conjunto de chaves opaco ID em privado e restaure com MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID).setKeySetId(id). O seguinte helper compilado cobre a aquisição, verificação de duração, renovação, lançamento e itens de download/reprodução. É não um aplicativo de download completo : você ainda possui o cache, serviço de download, progresso, metadados, vinculação de usuário/dispositivo e autorização de operação confiável.

Ajudante nativo de licença persistente (Kotlin, sem dependência DRM-X)
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 chamar este helper de baixo nível, valide que a resposta nova da infra- estrutura corresponde ao Content ID solicitado, operação, período de validade do usuário/dispositivo e autorização assinado. Persista em um estável offlineLicenseId e metadados de manifesto/pista confiáveis ao lado do conjunto de chaves ID, nunca o token. Execute chamadas de helper de bloqueio em um tópico de worker. Para a licença, os cabeçalhos HTTP usam setLicenseRequestHeaders na configuração DRM; Widevine parâmetros de pedido de chaves opcionais são um mapa diferente.

Novas integrações devem ser utilizadas AndroidX Media3 DRM apoioLegado com.google.android.exoplayer2 os projectos devem seguir o guia oficial de migração; não misture legado e AndroidClasses X ou assumir que a amostra compila inalterada contra versões mais antigas.

9. Verifique, renove, release and switch languages#

Estas são ações separadas. Execute uma de cada vez após a sessão offline ativa DRM ter sido lançada: use player.stopForOfflineLicenseOperation { /* one operation below */ } com o DrmXPlayer, ou libere primeiro o nativo Media3 reprodutor existente. Não verifique uma licença enquanto estiver em reprodução. Uma verificação é opcional para exibição de durações; não deve impedir a renovação de uma licença expirada.

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 lê metadados; checkLicense consultas de duração local autorizada CDM. Renovar/lançar requerem conectividade e uma nova autorização específica de operação da sua infra- estrutura. A exclusão de mídia não libera a licença. Quando desconectada, a liberação da fila para a próxima conexão em vez de informar que o servidor a confirmou. Feche o gerenciador quando seu proprietário for destruído.

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

Constrói as opções das faixas disponíveis e desactiva as opções indisponíveis. Uma preferência não pode criar uma faixa inglesa ausente do pacote. Os utilizadores offline só podem seleccionar as línguas transferidas. Preservar a licença/estado de cache através da rotação da tela; a tela cheia é uma mudança de visualização/janela, não uma razão para adquirir outra licença.

10. Implementar o endpoint de autorização offline#

As mensagens da aplicação contentId, contentType: "vod", drmSystem: "widevine", playbackMode: "offline", operation: "acquire" | "renew" | "release", offlineLicenseId (null para aquisição inicial) e verdadeiro platformCapabilities. Verifique se os IDs de renovação/lançamento pertencem ao utilizador, dispositivo e conteúdo autenticados. Selecione o lado do servidor de políticas, emita um DRM License Token novo e devolva o contrato correspondente:

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

Estes são placeholders explicativos, não uma resposta de licença reutilizável. Devolva novos valores da operação real. SDK também suporta integrationMode: "token-proxy" com um limite drm.authorization descritor. Return Cache-Control: no-store; nunca registrar corpos de autorização bem sucedidos. Veja o download possível SDK's OFFLINE_INTEGRATION.md e PHP Endpoint off-line da amostra para o manuseio de pedidos de concreto.

11. Teste seus dispositivos e solução de problemas #

Teste / sintomaComportamento esperado / próxima verificação
A transferência SDK devolve 403Selecione uma organização com uma assinatura ativa Professional ou Enterprise. Uma atualização planejada após a avaliação não desbloqueia o SDK.
Sessão 401 / 403Verificar login, direito, Ambiente, Publicado Content ID e política. Nunca ignorar a autenticação.
Falha off-line, mas funciona onlineVerifique as durações CDM, o download completo, a localização do cache, as chaves de fluxo e todas as chaves licenciadas necessárias.
Inglês / Chinês em faltaVerifique os metadados da linguagem do pacote, as faixas reais e a seleção do idioma offline.
L1, HDCP ou falha no descodificadorVerifique os requisitos de hardware físico, provisionamento, codec e saída. Uma string de capacidade não é certificação.
Reiniciar, rodar, atualizarLicença persistente e cache sobrevivem às mudanças suportadas no ciclo de vida sem aquisição duplicada. Os backups não devem migrar licenças para um novo dispositivo.
Modo aviãoDesativar Wi-Fi e dados móveis também; reinicie o aplicativo, jogue localmente e mude cada idioma prometido.
Expiração, pressão de armazenamento, interruptor de contaMostrar um estado indisponível/renovar claro, recuperar mídia incompleta e forçar o acesso usuário/dispositivo sem vazar o catálogo offline de outro usuário.

Qualifique cada telefone, tablet, Android TV/Google TV, Fire TV ou modelo Android suportado para Widevine, decodificação segura, codec/resolução, HDCP, monitores externos, mudanças de relógio e desconexão longa. Nem todos os dispositivos Android têm as mesmas capacidades DRM. O conteúdo de Hollywood requer os requisitos de proteção e dispositivo do titular dos direitos, não apenas descodificação bem sucedida.

Não há oportunidade de adquirir antes da desconexão? Essa é uma implantação diferente: um serviço de licença de bordo qualificado emite licenças pela rede local durante a viagem. Não é habilitado apenas usando este SDK. DRM-X pode trabalhar com você em uma implantação personalizada cobrindo permissões de provedor, provisionamento, proteção de chave e um teste de aceitação totalmente desconectado. Consulte a bordo DRM para aeronaves, ônibus, trens e navios.

Listas de reprodução e controles compartilhados do Universal Player#

Os players Android e Apple oferecem Previous/Play-Pause/Next, CC, Settings e tela cheia. Move up / Move down mantém o vídeo atual e salva a ordem da lista para reprodução online e offline. As opções de reprodução aleatória e repetição são mantidas ao reabrir o app; com ambas desativadas, a reprodução termina após o último item.

Forneça o mesmo cliente/visualizador storageNamespace para DrmXPlayer e playerView.storageNamespace antes de configurar a lista de reprodução. O progresso VOD é gravado periodicamente e em pausa/paragem, sendo então restaurado para esse Content ID em transmissão ou reprodução transferida. A reprodução ao vivo não usa uma posição VOD gravada. Os downloads completados mantêm o mesmo comportamento de shuffle/repeat/resume sem uma fonte de mídia de rede. Este estado local do dispositivo não sincroniza o progresso entre dispositivos.

Web e Android compartilham painel escuro e controles azuis compactos. Há próximo/anterior, aleatório, repetir um/todos e avanço automático cancelável. Cada item pede nova autorização. Capítulos e transcrição vêm do catálogo.

Há velocidade, temporizador, legendas, qualidade permitida, idiomas, marcadores de sessão e loop A–B. Web inclui atalhos, retomada local opcional por espectador, busca de transcrição e PiP compatível. Android mantém downloads Widevine offline e renovação. Transmissão a outra tela e progresso entre dispositivos exigem integração adicional.

Configurações de logotipo profissional e empresarial

Branding fica fora da superfície do vídeo. Configurações abertas → Sobre reprodutor para o logotipo autorizado, informações do produto e versão reprodutor. Web também oferece esta informação no botão direito do mouse. A demonstração do telefone público inclui WayMaker, Bondade de Deus, Óleo Fresco e Vídeo de Teste 4K; cada seleção recebe sua própria autorização.

No backend confiável, defina playerBranding como {"mode":"hidden"} ou {"mode":"custom","logoUrl":"https://your-site.example/logo.png","label":"Your brand"}. A plataforma verifica Professional ou Enterprise ativo. Outros planos usam {"mode":"default"}; pedidos personalizados ou ocultos sem autorização retornam 403. É separado da marca d’água de segurança.

O download PHP contém lista e marca editáveis. O ZIP Android inclui o callback de lista. Mantenha a autorização no backend e nunca embuta credenciais de cliente API nos reprodutores.