20 Jul
20Jul


Imagine uma aplicação que exibe fotos e vídeos numa timeline. Sempre que o vídeo passa pela viewport, ele começa a tocar. Quando ele sai da área visível, ele deve ser pausado.  Simples, certo? Na verdade é bem mais complicado do que parece.


Compreendendo vídeos no HTML5

Antes da liberação do padrão HTML5, videos eram uma verdadeira dor de cabeça, exigindo plugins como Flash ou RealPlayer para serem exibidos. Isto significava que cada usuário era obrigado a instalar estes plugins manualmente. HTML5 tornou trivial a tarefa de adicionar vídeos a uma página ao incorporar no padrão todo suporte necessário para sua exibição e integrando-o no DOM, permitindo o seu controle a partir de código Javascript, como visto no exemplo abaixo da W3C School: 



A tag < video > possui muitas funcionalidades, mas também algumas limitações práticas. No exemplo acima o usuário tem a possibilidade de fazer um pause programaticamente. Quando temos apenas um vídeo como no Youtube, este código é perfeitamente aceitável, como vemos na imagem abaixo apenas 1 vídeo ativo:


Embora o Youtube possua milhões de vídeos e sua interface permita navegar facilmente entre eles a verdade 
é que  apenas 1 vídeo estará ativo ou pausado a qualquer tempo. Mesmo a sua versão mobile  funciona da mesma maneira.   

Mas, e se ao invés de um único vídeo houvessem 25, 50  ou mesmo 100 vídeos na página (1 ativo e 99 pausados, ou mesmo todos pausados ou ativos ao mesmo tempo) ? No filme Matrix Reloaded vemos uma cena parecida na qual uma videowall de TVs antigas exibem diferentes vídeos:


 

Seria legal poder fazer algo assim em HTML5, certo? Bom, na prática não é possível. Pelo menos não é possível ter todos os vídeos ativos ao mesmo tempo (pelo menos não em 2018),  embora tenhamos um tipo de aplicação como o Instagram, onde temos uma timeline de posts de fotos e/ou vídeos que estende-se infinitamente quando rolamos a tela para cima e onde apenas um vídeo estará ativo e os outros pausados. Mas aqui também temos um problema sério. 

Dependendo do dispositivo usado pelo usuário pode ser impossível criar tal timeline usando código semelhante ao exemplo acima. O motivo tem a ver com um limite pouco conhecido dos browsers, ou das webviews usadas em aplicações híbridas (Ionic, Phonegap, Framework7, Cordova e mesmo ReactNative com Webview carregando uma timeline de posts de vídeos). Este limite é determinado pelo OS e, ainda que varie de browser para browser (e de webview para webview), o fato é que o limite está lá, aguardando você ultrapassá-lo para manifestar-se. 


Webworkers

Quando um vídeo inicia, o browser cria um webworker, um processo em background responsável por solicitar o vídeo do servidor e alimentar o player na página com o conteúdo, exibindo o vídeo em questão. O modelo de eventos do Webworker é descrito logo abaixo: 



Quando o usuário pausa o vídeo, o webworker ainda permanece em background, pronto para continuar a requisitar o vídeo do servidor. Aqui é que está o problema: o número máximo de webworkers é definido pelo OS (iOS/Android) e ele toma a decisão arbitrária de remover da memória um webworker existente se um novo é criado ao atingir-se o limite. Numa timeline contendo posts com vídeos onde este número foi excedido o resultado é desastroso:

Timeline com menos de 16 vídeos no iOS ou menos de 60 no Android


Apertem os cintos! Os vídeos sumiram! 
Timeline com mais de 16 vídeos no iOS ou mais 60 vídeos no Android

Controlando WebWorkers

O segredo para carregar timelines de vídeos infinitas é saber como controlar a quantidade de webworkers ativos. O viewport do aplicativo acima exibe em torno de dois vídeos a cada vez, talvez três em celulares com mais de 5.5". A regra de negócio do aplicativo acima requer que todo vídeo seja carregado em pausa, mas uma vez que o usuário desliza a viewport para cima ou para baixo, qualquer vídeo visível na parte central deve iniciar o play automaticamente. Isto é facilmente obtido através dos atributos "poster" para exibir o frame inicial do vídeo e  "preload"  com valor "none" da tag < video > que criam uma instância do vídeo sem ativar o Webworker. Uma vez que o usuário continue a deslizar a tela e os vídeos ativos "sumam" da viewport, os mesmos devem ser pausados. 

Se usarmos a função "pause", isto tem o efeito indesejado de manter o webworker associado ao vídeo em questão ativo, resultando no problema dos webworkers terminados pelo OS. O que precisamos fazer é forçar o vídeo a retornar ao estado inicial, sem um webworker associado. O exemplo abaixo considera o uso do VideoJS como um wrapper para fornecer mais funcionalidades ao redor da tag < video >, mas o resultado seria o mesmo com apenas a tag < video >.


function pause(videoJsInstance) {

          videoJsInstance.pause()

          videoJsInstance.src = videoJsInstance.currentSrc

  }


A linha marcada em vermelho demonstra como reiniciar o vídeo, descartando seguramente o webworker. Sim, basta informar ao objeto que representa o vídeo que desejamos que ele reconfigure a origem (url) do vídeo como sendo ela mesma. O resultado final é que o vídeo volta à posição zero (primeiro frame ou poster) e o webworker associado é removido da memória sem prejuízo para a estrutura da timeline. Este é mesmo comportamento do Youtube mobile. Para comprovar, carregue um vídeo no Youtube no seu celular, volte para a tela principal do mobile e abra novamente o Youtube. O vídeo estará pausado, mas exibindo a imagem de poster. 

Ainda no caso do Youtube o player guarda a última posição do vídeo, apesar de exibir a imagem de poster. Isto é facilmente obtido através do seguinte código no função "pauseVideo" e um array associativo:

    currentTimes[videoJsInstance[0].id] = videoJsInstance[0].currentTime

    Onde "currentTimes" representa o array de vídeos carregados (key)  e os respectivos tempos quando foram pausados (value). 


Na função "play" haveria um código semelhante a: 

   
        videoJsInstance[0].currentTime = currentTimes[videoJsInstance[0].id]
   

Desta forma é possível manter a posição do vídeo na pausa e reiniciá-lo do ponto onde ele parou no evento "play",


Considerações finais

Javascript é nativamente single-threaded, mas na prática é possível ter processos em background ativos e simultâneos(ou assim vai parecer). Independente do problema da timeline, manter estrito controle sobre webworkers é fundamental para diminuir o consumo de CPU preservando a bateria, mostrar vídeos corretamente numa timeline infinita e evitar dores de cabeça, especialmente quando existe tão pouca literatura a respeito do comportamento do browser em situações limites.

Comentários
* O e-mail não será publicado no site.
ESTE SITE FOI CRIADO USANDO