O que você pode aprender ao desenvolver um reprodutor de áudio para diferentes navegadores

Esta história começou cerca de 1,5 anos atrás. Está associado à reprodução de música em vários navegadores e plataformas nas quais eles são executados. Um caminho cheio de “dor e sofrimento” da percepção de que uma tarefa fácil à primeira vista pode não ser tão fácil, e detalhes “insignificantes” aos quais você não dá importância desde o início, podem afetar tudo.

Pequenos detalhes para os mais curiosos :)
1. Baixando dados da próxima faixa da rede.
2. Para cada elemento do áudio: novo Áudio () ou <audio>, você precisa da permissão do usuário - uma ação do usuário na página.

Antecedentes


Provavelmente todo mundo que já escreveu um reprodutor de áudio para navegadores pelo menos uma vez na vida encontrou o problema de compatibilidade entre navegadores e plataformas.

Então, enquanto trabalhava no novo MVP, deparei-me com vários recursos relacionados à reprodução de áudio nos navegadores.

E tudo começou com o fato de que era necessário fazer um crossfade suave de duas faixas durante a reprodução - esse é o primeiro recurso. Nossa equipe queria mudar de faixa, como no rádio. E o segundo recurso - cada faixa subseqüente é solicitada da rede.



Pesquisas


Então, quase todos os nossos projetos usaram a biblioteca do Sound Manager 2.

Quase imediatamente, você percebe que a reprodução de dois arquivos de áudio simultaneamente em dispositivos móveis não funciona da mesma maneira em todos os lugares!

No Chrome (versão ~ 62) para PC, as faixas eram reproduzidas como deveriam. Em dispositivos móveis (também no Chrome), a reprodução da faixa funcionou, mas apenas com a tela ativa. Quando a tela estava bloqueada, a próxima faixa depois que o player atual não foi reproduzido. Quanto ao iOS / macOS, a reprodução funcionou de maneira semelhante. Mais informações podem ser obtidas aqui - seção “Single Audio Stream”.

Então, começou a andar por três mares procurando informações pouco a pouco sobre os recursos dos navegadores com áudio.

Ok, estou tentando uma solução com o áudio da web sem usar nenhuma biblioteca. Sim, esta tecnologia é destinada a outros fins: síntese, processamento de som, para jogos, etc., em vez de simplesmente reproduzir faixas. Mas para o experimento, foi necessário tentar, pois permite compor sons de diferentes fontes em uma única saída de áudio - alto-falantes / fones de ouvido / alto-falante do telefone / etc. Existem pessoas que estão pesquisando propositadamente as possibilidades de reproduzir som em dispositivos móveis usando a API de áudio da Web.

Após a implementação, certas nuances ficaram claras.

Primeiro, você deve esperar até que a faixa inteira esteja totalmente carregada. Com uma conexão lenta à Internet, as pausas serão visíveis devido ao fato de que a segunda faixa pode não ter tempo para carregar até o final da primeira faixa. O download completo pode ser evitado usando um monte de tags de áudio HTML5 que funcionarão como fontes de som para o áudio da Web, mas, nesse caso, torna-se impossível reproduzir dois sons simultaneamente.

Em segundo lugar, se você baixar uma faixa pela rede em fragmentos e decodificá-los programaticamente, isso aumenta a carga na CPU. Era aceitável para um PC, mas crítico para dispositivos móveis.

Em terceiro lugar, houve problemas com a decodificação. Se fragmentos de arquivos mp3 / ogg / wav chegavam ao cliente, essas peças eram silenciosamente decodificadas e reproduzidas. Mas se pedaços do arquivo mp4, que atuavam como um contêiner para HE-AAC, chegassem ao navegador, eles não poderiam ser decodificados. Isso também se aplica, em certa medida, ao navegador Opera, no qual a reprodução de arquivos MP3 é instável de versão para versão - algumas vezes, ela é reproduzida e dá um erro que esse formato não é suportado.

Quarto, o nome da faixa não foi exibido / não foi alterado na tela bloqueada em um prato com um reprodutor de áudio nativo (no iPad), incl. ao alternar entre faixas. Talvez devido ao fato de que os testes usaram o iPad com a versão 9 do iOS - não havia outro na época.

Como resultado, nesse estágio, o Web Audio teve que ser abandonado. Ainda assim, o crossfade não é para navegadores, composições musicais padrão de boa qualidade pesam bastante.

Como recusamos o crossfade, implementamos um simples fade in e fade out, no início e no final da faixa de música, respectivamente.

O código do ano anterior foi ligeiramente modificado e testado. Como resultado dos testes, surgiram várias nuances (mostradas na tabela). Tudo isso usando a biblioteca do Sound Manager 2.



Adicionamos o registro de todos os eventos para determinar o momento da transição entre as faixas e para entender em que ponto eles param de tocar.



Ativação da guia
No Safari 9+, o som nem sempre aparece quando a guia é ativada.

A partir disso, pode-se supor que a execução de JS em segundo plano esteja otimizando ou que o encadeamento de execução pare completamente (eventos e timers ). No entanto, ficará claro mais tarde que essa foi parcialmente uma conclusão correta. A seguir, consideraremos outra nuance associada à reprodução de faixas e à compreensão de por que o som não aparece.

Observação
Para trabalhar com o progresso (barra de progresso), por exemplo, renderizando-o para uma faixa, é bom usar requestAnimationFrame em vez de setInterval / setTimeout. Você pode evitar o efeito cumulativo ao desativar (guia plano de fundo), ativar a guia e congelá-la temporariamente, associado a todos os cálculos e redesenhar o estado do progresso.

No mesmo momento, surgiu a pergunta: e a reprodução automática de faixas em um PC e em dispositivos móveis?
A reprodução automática refere-se ao início automático da reprodução de uma faixa sem nenhuma ação do usuário ao carregar uma página.
Quanto ao Safari em relação à reprodução automática quando a página é carregada, isso não é possível, você precisa da interação do usuário com a página, como em dispositivos móveis . Isso se aplica ao conteúdo de vídeo e ao áudio .

E então, naquela época, havia o seguinte:

  1. é impossível (não desejável) reproduzir dois ou mais sons ao mesmo tempo;
  2. para a pseudo “reprodução automática” da faixa, é necessária a permissão do usuário - a primeira interação, mais tarde foi chamada de “vender um dedo para o dispositivo”;
  3. em segundo plano (guia plano de fundo / tela de bloqueio) JS (tudo depende do navegador):
    ou congela completamente;
    qualquer estrangulamento;
    ou funciona da mesma forma que na guia ativa;
  4. Você pode iniciar automaticamente a reprodução sem som, mas não está claro por que (para conteúdo de áudio)?
  5. em algum lugar distante, o pensamento começa a aparecer, mas como fazer o JS continuar executando em segundo plano?

Outras bibliotecas que implementam as funções do player assumiram que talvez haja uma solução para esse problema. Apesar do fato de que muitos problemas foram observados no GitHub com uma descrição de problemas ao reproduzir faixas em vários navegadores, ainda havia esperança de que você estava prestes a chegar ao fundo: por que não funciona e como fazê-lo funcionar. Como se viu, não ...

Alguns exemplos de código com demonstração em vídeo do trabalho das bibliotecas:

  1. Sound Manager 2 - páginas do github , repositório do github , vídeo: macOS Safari 12 ; iOS Safari 10 com tela desbloqueada
  2. Furo
    Howler v2.0.9 - páginas do github , repositório do github , vídeo: macOS Safari 12 , iOS Safari 10
    Howler v2.0.15 - páginas do github , repositório do github , vídeo: macOS Safari 12
    Howler v2.1.1 - páginas do github , repositório do github , vídeo: macOS Safari 12 , iOS Safari 10

Para o macOS, a gravação de vídeo foi feita sem som; portanto, você precisa observar o indicador de volume - a imagem do alto-falante na guia.

Mais vídeos de exemplo estão disponíveis no repositório.

No exemplo interativo do Howler v2.1.1 - às vezes você pode ouvir vários sons ao mesmo tempo, isso se deve à adição de um conjunto de elementos de áudio desbloqueados pelo usuário (isso deve ser corrigido em versões futuras da biblioteca).
Qual é o motivo da inoperabilidade dessas bibliotecas?

Escrevi acima: “No plano de fundo (guia plano de fundo), o JS congela completamente ou sofre uma limitação . Então, aqui outro ponto aparece: as bibliotecas no código usam a criação de novos objetos de áudio via novo Audio (). Se eles são criados dinamicamente, ou seja, se um objeto de áudio existente não for usado e o usuário não interagir com o site de qualquer forma, a guia estiver inativa ou a tela estiver bloqueada, alguns navegadores poderão considerar que o som desse elemento de áudio não deve ser reproduzido até que a guia esteja ativa novamente ou o usuário não qualquer ação.

Um exemplo de teste nas páginas do github e no repositório do github usando o novo Audio (). Vídeo: macOS Safari 12 ; iOS Safari 10 com tela desbloqueada.

Parece que não existe algum tipo de ferramenta universal e é necessário procurar outra solução de compromisso.

Então sentamos com os caras da equipe para discutir, e o que é realmente importante no trabalho do reprodutor de áudio? Pois seria possível continuar os experimentos ad infinitum, mas precisamos seguir em frente.

Primeiro, foram identificados pontos importantes que impediam a obtenção do resultado desejado:

  1. O Safari no macOS não reproduz faixas quando a guia está inativa;
  2. não há possibilidade de ouvir música em segundo plano (quando a tela está bloqueada) em smartphones executando iOS e Android, gostaria de evitar o redirecionamento agressivo de usuários para um aplicativo móvel (no futuro), pois a experiência anterior mostra que uma grande parte dos usuários não deseja instalar um aplicativo móvel ;
  3. o player não funciona corretamente com uma lista de reprodução dinâmica, ou seja, quando não se sabe de antemão qual será a próxima faixa.

Além disso, permitiu formular as metas necessárias para alcançar:

  1. fornecer o player em segundo plano - em vários navegadores e em várias plataformas;
  2. permitir ao usuário escolher o que usar: ouvir música no site ou no aplicativo móvel;
  3. fornecer a capacidade de usar o player (ou abordagem) em vários projetos futuros.

Uma nova etapa na busca de uma solução para o problema já começou. Nesse estágio, várias bibliotecas não eram mais usadas; todos os estudos foram conduzidos usando o HTML5 Audio. O resultado foi que uma opção foi encontrada usando trabalhadores dedicados . O iOS não permitiu que essa decisão vencesse novamente - a reprodução em segundo plano não funciona, mas acabou funcionando no Android (Chrome, Opera, Safari).

Exemplo de teste de HTML5 Audio + Dedicated Workers nas páginas do github e no repositório do github .

Quando o Worker é inicializado, são solicitados dados sobre a trilha atual. O Worker também envia um sinal para obter o status do progresso - por quanto tempo a faixa é reproduzida - do fluxo principal e decide quando solicitar dados sobre a próxima faixa da rede com base nesses dados.



Também naquela época, o exemplo a seguir foi testado ( páginas do github , repositório do github ), quando a tag de áudio HTML5 é incorporada no DOM (vídeo: macOS Safari 12 , iOS Safari 10 ) e simplesmente substitui o SRC ao alternar entre faixas. Até o momento, no macOS no Safari 12, este exemplo funciona. Infelizmente, agora não há como testar a funcionalidade deste exemplo no macOS no Safari 10 e 11, mas naquele momento esse exemplo não funcionava durante os testes ( políticas de reprodução automática , restrições de reprodução automática ).

Para resumir, para iOS e macOS, o navegador Safari não considera uma nova instância do elemento de áudio a ser ativada pelo usuário se ele foi criado em segundo plano dentro de um evento, por exemplo, ajax, setTimeout, recomendado.

Além disso, em relação à reprodução de faixas no iOS Safari e no iOS Chrome, foi possível reproduzir faixas em segundo plano (quando a tela está bloqueada) usando apenas HLS . Para plataformas iOS e macOS, esse formato é padrão e a transmissão é suportada pelo sistema operacional. Para o Android Chrome e Edge, uma implementação nativa também está disponível. E para PCs no Chrome, você pode usar manipuladores de software, por exemplo, hls.js , Bitmovin Player , etc.

Um link para o repositório do github fornece um exemplo de código que abrange o caso de uso mais simples - simplesmente reproduzindo o fluxo gerado no servidor sem a capacidade de retroceder, alternar para a próxima faixa etc. São apresentados exemplos usando: a marca de áudio, a marca de vídeo, a biblioteca hls.js e o player do Bitmovin. Este conteúdo requer Node.js.

Conclusões


Infelizmente, o primeiro ponto, por causa da variedade de navegadores, não existe uma solução universal que permita ouvir música em navegadores igualmente bem em todos os lugares. Em todos os lugares existem limitações e, como mostra a prática, você pode conviver confortavelmente com elas.

O segundo ponto, às vezes vale a pena verificar casos limítrofes o mais rápido possível, por exemplo, uma implementação nativa. Encontre algum tipo de conjunto de requisitos minimamente aceitável e verifique rapidamente seu desempenho, em vez de tomar qualquer biblioteca como base. Isso dará mais entendimento de como essas bibliotecas são organizadas internamente e por que certas funções funcionam ou não. Caso contrário, você pode ir muito longe no projeto e depois de perceber que algo está errado. E pode acontecer que abandonar a biblioteca seja bastante caro. Uma parte significativa do código precisará ser reescrita.

O terceiro ponto, preste atenção ao público do seu serviço - de onde são os navegadores e os sistemas operacionais de seus usuários. Isso é bastante fácil de rastrear usando várias métricas e sistemas de monitoramento de erros. Essa abordagem ajudará a entender quais plataformas e navegadores é importante oferecer suporte e quais podem ser usados ​​sem nenhum esforço.

E finalmente


Estou anunciando um pequeno concurso relacionado à reprodução de música no iOS usando a tecnologia HLS.

A descrição pode ser vista no link no github .

Source: https://habr.com/ru/post/pt438952/


All Articles