O spinner que nunca vira imagem
O usuário apertou play num canal premium. O spinner gira por dois segundos e então aparece um diálogo: Playback error 0x80070005. Ele não sabe o que é um servidor de licença. Ele sabe que os canais abertos funcionam e este não, e a culpa é do app.
DRM é a parte de um player de vídeo com mais chance de falhar na sala de estar e a que o usuário menos entende. Todo stream protegido tem um handshake invisível entre o player e um servidor de licença que precisa dar certo antes de um único frame decodificar, e em hardware de TV antigo esse handshake falha muito mais do que a demo do caminho feliz sugere. A diferença entre um bom player e um ruim está quase inteira em como ele reage quando esse handshake falha.
O que de fato acontece antes do primeiro frame
Sob o EME (Encrypted Media Extensions), o fluxo é mais ou menos assim. A init data do stream — a pssh box entregue ao player como initData — diz de quais chaves o conteúdo precisa. O player passa isso pro CDM, o Content Decryption Module embutido na TV: Widevine ou PlayReady na maioria dos aparelhos, FairPlay na Apple. O CDM produz uma license request, um blob assinado que o player envia via POST pro servidor de licença. O servidor checa quem você é e a que você tem direito e, se estiver satisfeito, devolve uma licença carregando as content keys. O CDM instala as chaves, e só então a mídia criptografada decodifica.
Quatro partes, um round-trip de rede e uma troca assinada que pode dar errado em cada etapa. Tratar todas essas falhas do mesmo jeito é o erro.
Retryável versus terminal: a única distinção que importa
Separe toda falha de licença em dois baldes, porque eles pedem reações opostas.
Falhas retryáveis são sobre o cano, não sobre a permissão. Um timeout na license request, um 503 de um servidor sobrecarregado, um engasgo transitório de CDN, uma conexão que caiu no meio. A requisição estava certa; a rede não. Essas você refaz.
Falhas terminais são respostas, não acidentes. Um 403 significa que o servidor te entendeu e disse não. Negações de região e de entitlement — país errado, assinatura que não inclui este canal — também são um não firme. Recusas de output protection são terminais também: o CDM pediu HDCP na saída HDMI, não conseguiu (um cabo barato, uma tela fora de conformidade, um splitter no caminho) e se recusou a liberar as chaves de conteúdo de alto valor. Refazer qualquer uma dessas não muda nada. A resposta vai ser o mesmo não.
Nunca refaça um 403. Esse é o que as pessoas erram. Um 403 não é um engasgo; é o servidor te dizendo que a requisição não está autorizada. Refazer três vezes com backoff só faz o usuário esperar três vezes mais pela mesma recusa, e martela um servidor de licença que está funcionando exatamente como foi projetado. Leia o status code antes de decidir refazer, não depois.
Backoff, mas só em volta do que é retryável
Para as falhas de fato transitórias, envolva o POST da licença num retry limitado com exponential backoff e jitter — algo como 200ms, 400ms, 800ms, e então pare. Duas ou três tentativas, não dez. O jitter importa mais do que parece: quando uma CDN ou um servidor de licença se recupera de uma queda, milhares de TVs refazem ao mesmo tempo, e retries sincronizados só re-DDoSam a coisa que está tentando voltar. Espalhe-os.
E limite o total. O usuário perdoa uma recuperação de um segundo. Ele não aguenta quinze segundos de retries silenciosos — passados uns dois segundos, uma falha rápida e honesta ganha de uma lenta e esperançosa.
Os problemas de stack antigo que ninguém avisa
Hardware novo faz o handshake limpo. A cauda longa de TVs que já está no campo, não — e dois modos de falha são específicos da idade.
Provisioning. Um CDM Widevine precisa de um certificado por dispositivo antes de conseguir fazer license requests, buscado de um servidor de provisioning no primeiro uso. Numa TV antiga ou resetada de fábrica esse provisioning pode estar faltando ou desatualizado, e o sintoma aparece como um erro de licença mesmo que o servidor de licença nunca tenha entrado na jogada. Se você vê erros de CDM antes de a requisição sequer sair do dispositivo, desconfie do provisioning.
Certificados e o relógio. PlayReady e Widevine se apoiam em certificados e, indiretamente, no relógio do dispositivo. Uma TV com o relógio anos errado — bateria do RTC morta, nunca sincronizada com o horário da rede — pode falhar na aquisição de licença ou rejeitar um certificado de servidor como expirado. Parece DRM; na verdade é Date.now() retornando 2016.
Robustness levels decidem o que sequer toca
Nem toda falha é uma falha pra consertar — parte do conteúdo simplesmente não vai tocar em parte do hardware, por design. O Widevine tem security levels: L1 significa que as chaves e a decodificação moram na TEE de hardware; L3 significa que é tudo em software. O PlayReady traça a mesma linha com SL3000 versus SL2000. Os estúdios trancam o melhor conteúdo — 4K, janelas de lançamento antecipadas — atrás de robustness de hardware. Num dispositivo L3 ou só-software, o servidor de licença vai recusar as chaves de alto valor, e nenhuma lógica de retry muda isso. O certo é saber o level do dispositivo de antemão e pedir um tier de stream que ele realmente consiga tocar, pra você nunca disparar uma recusa da qual não tem como se recuperar.
Pré-adquira e persista, com cuidado
O handshake é um round-trip que muitas vezes dá pra pagar adiantado. Enquanto um canal está tocando e a box está ociosa, dá pra pré-adquirir licenças dos canais mais prováveis de vir a seguir — os vizinhos na ordem do zapping — pra chave já estar instalada quando o usuário chegar. Onde o esquema e a política de licença permitem, uma licença persistente guardada numa MediaKeySession persistent-license sobrevive entre sessões, então um canal que o usuário assiste todo dia não refaz o handshake toda manhã.
Duas ressalvas. Licenças persistentes têm expiração própria e revogação própria, então você ainda trata o caso da chave guardada já chegar morta. E a pré-aquisição queima quota do servidor de licença, então limite-a do mesmo jeito que limita o prefetch — um ou dois vizinhos, cancelados no instante em que o usuário se mexer.
Diga ao usuário algo verdadeiro, em silêncio
Quando você esgotou o caminho retryável e está de fato travado, a última decisão é UX — e um código hexadecimal é a resposta errada. 0x80070005 não diz nada ao usuário e faz ele sentir que a box está quebrada.
Mapeie os casos terminais pra linguagem simples. Entitlement: "Este canal não faz parte do seu plano." Região: "Este canal não está disponível na sua área." HDCP: "Esta tela ou este cabo não suporta reprodução protegida" — porque esse aí muitas vezes é literalmente resolvível trocando um cabo. Mantenha uma mensagem pequena e inline, não um modal de tela cheia, e nunca culpe o usuário. A falha é real, mas uma frase calma sobre a qual ele pode agir ganha de um diálogo assustador com um número dentro toda santa vez.