Reprodução de vídeo da Web para dispositivos móveis

François Beaufort
François Beaufort

Como criar a melhor experiência de mídia para dispositivos móveis na Web? Fácil! Tudo depende do engajamento do usuário e da importância que você atribui à mídia em um site página. Todos concordamos que, se o vídeo é O motivo da visita de um usuário, a experiência do usuário deve ser imersiva e reengajada.

reprodução de vídeo na web para dispositivos móveis

Neste artigo, mostrarei como melhorar progressivamente sua mídia e torná-lo mais imersivo graças a uma infinidade de APIs da Web. Esse é por que vamos criar uma experiência simples para o player de dispositivos móveis com controles de acesso, tela cheia e reprodução em segundo plano. Teste o exemplo agora e encontre o código em nosso repositório GitHub.

Controles personalizados

Layout HTML
Figura 1.Layout HTML

Como você pode ver, o layout HTML que vamos usar para nosso player de mídia é muito simples: um elemento raiz <div> contém um elemento de mídia <video> e um Elemento filho <div> dedicado a controles de vídeo.

Os controles de vídeo que abordaremos mais tarde incluem: um botão para reproduzir/pausar, uma tela , os botões de avançar e voltar e alguns elementos para o horário atual, duração e controle de tempo.

<div id="videoContainer">
  <video id="video" src="file.mp4"></video>
  <div id="videoControls"></div>
</div>

Ler metadados do vídeo

Primeiro, vamos esperar os metadados do vídeo serem carregados para definir o vídeo da hora, a hora atual e inicializar a barra de progresso. Observe que o A função secondsToTimeCode() é uma função de utilitário personalizada que eu escrevi que converte um número de segundos em uma string em "hh:mm:ss" formato, o que é melhor adequada para o nosso caso.

<div id="videoContainer">
  <video id="video" src="file.mp4"></video>
  <div id="videoControls">
    <strong>
      <div id="videoCurrentTime"></div>
      <div id="videoDuration"></div>
      <div id="videoProgressBar"></div>
    </strong>
  </div>
</div>
video.addEventListener('loadedmetadata', function () {
  videoDuration.textContent = secondsToTimeCode(video.duration);
  videoCurrentTime.textContent = secondsToTimeCode(video.currentTime);
  videoProgressBar.style.transform = `scaleX(${
    video.currentTime / video.duration
  })`;
});
somente metadados do vídeo
Figura 2. Player de mídia mostrando metadados de vídeo
.

Assistir/pausar o vídeo

Agora que os metadados do vídeo estão carregados, vamos adicionar nosso primeiro botão que permite ao usuário reproduzir e pausar vídeo com video.play() e video.pause(), dependendo do estado de reprodução.

<div id="videoContainer">
  <video id="video" src="file.mp4"></video>
  <div id="videoControls">
    <strong><button id="playPauseButton"></button></strong>
    <div id="videoCurrentTime"></div>
    <div id="videoDuration"></div>
    <div id="videoProgressBar"></div>
  </div>
</div>
playPauseButton.addEventListener('click', function (event) {
  event.stopPropagation();
  if (video.paused) {
    video.play();
  } else {
    video.pause();
  }
});

Em vez de ajustar nossos controles de vídeo no listener de eventos click, usamos os eventos de vídeo play e pause. Tornar nossos controles baseados em eventos ajuda com flexibilidade (como veremos mais adiante com a API Media Session) e permitirão manter nossos controles sincronizados caso o navegador intervém na reprodução. Quando o vídeo começa a ser reproduzido, nós alteramos o estado do botão como "pause" e ocultar os controles de vídeo. Quando o vídeo pausa, basta mudar o estado do botão para "reproduzir" e mostrar os controles de vídeo.

video.addEventListener('play', function () {
  playPauseButton.classList.add('playing');
});

video.addEventListener('pause', function () {
  playPauseButton.classList.remove('playing');
});

Quando o tempo indicado pelo atributo currentTime do vídeo foi alterado pelo timeupdate, também atualizaremos nossos controles personalizados se forem visíveis.

video.addEventListener('timeupdate', function () {
  if (videoControls.classList.contains('visible')) {
    videoCurrentTime.textContent = secondsToTimeCode(video.currentTime);
    videoProgressBar.style.transform = `scaleX(${
      video.currentTime / video.duration
    })`;
  }
});

Quando o vídeo termina, basta alterar o estado do botão para "Reproduzir", definir o vídeo currentTime de volta ao zero e mostrar controles de vídeo por enquanto. É possível também opta por carregar automaticamente outro vídeo se o usuário tiver ativado algumas uma espécie de "reprodução automática" .

video.addEventListener('ended', function () {
  playPauseButton.classList.remove('playing');
  video.currentTime = 0;
});

Voltar e avançar

Vamos continuar e adicionar "voltar" e “siga adiante” para que o usuário pode pular facilmente alguns conteúdos.

<div id="videoContainer">
  <video id="video" src="file.mp4"></video>
  <div id="videoControls">
    <button id="playPauseButton"></button>
    <strong
      ><button id="seekForwardButton"></button>
      <button id="seekBackwardButton"></button
    ></strong>
    <div id="videoCurrentTime"></div>
    <div id="videoDuration"></div>
    <div id="videoProgressBar"></div>
  </div>
</div>
var skipTime = 10; // Time to skip in seconds

seekForwardButton.addEventListener('click', function (event) {
  event.stopPropagation();
  video.currentTime = Math.min(video.currentTime + skipTime, video.duration);
});

seekBackwardButton.addEventListener('click', function (event) {
  event.stopPropagation();
  video.currentTime = Math.max(video.currentTime - skipTime, 0);
});

Como antes, em vez de ajustar o estilo do vídeo nos listeners do evento click desses botões, usaremos os eventos de vídeo seeking e seeked disparados para ajustar o brilho do vídeo. Minha classe CSS seeking personalizada é tão simples quanto filter: brightness(0);.

video.addEventListener('seeking', function () {
  video.classList.add('seeking');
});

video.addEventListener('seeked', function () {
  video.classList.remove('seeking');
});

Confira abaixo o que criamos até agora. Na próxima seção, vamos implementar botão "Tela cheia".

Tela cheia

Aqui, vamos aproveitar várias APIs Web para criar um objeto e sem interrupções em tela cheia. Para vê-lo em ação, confira o sample.

Obviamente, você não precisa usar todas elas. Basta escolher aqueles que fazem para você e combiná-las para criar seu fluxo personalizado.

Impedir a tela cheia automática

No iOS, elementos video entram automaticamente no modo de tela cheia quando a mídia a reprodução for iniciada. Estamos tentando adaptar e controlar ao máximo nossos experiência de mídia em navegadores para dispositivos móveis, recomendamos que você defina os playsinline do elemento video para forçá-lo a reproduzir em linha no iPhone, e não entrar no modo de tela cheia quando a reprodução começar. Observe que isso não tem efeitos colaterais em outros navegadores.

<div id="videoContainer"></div>
  <video id="video" src="file.mp4"></video><strong>playsinline</strong></video>
  <div id="videoControls">...</div>
</div>

Ativar/desativar tela cheia ao clicar no botão

Agora que evitamos a tela cheia automática, precisamos tratar o modo de tela cheia para o vídeo com a API Fullscreen. Quando o usuário clicar no "botão de tela cheia", vamos sair do modo de tela cheia com document.exitFullscreen() se o modo de tela cheia estiver em uso pelo documento. Caso contrário, solicite a tela cheia no contêiner de vídeo com o método requestFullscreen(), se disponível, ou substituto para webkitEnterFullscreen() ativado o elemento de vídeo apenas no iOS.

<div id="videoContainer">
  <video id="video" src="file.mp4"></video>
  <div id="videoControls">
    <button id="playPauseButton"></button>
    <button id="seekForwardButton"></button>
    <button id="seekBackwardButton"></button>
    <strong><button id="fullscreenButton"></button></strong>
    <div id="videoCurrentTime"></div>
    <div id="videoDuration"></div>
    <div id="videoProgressBar"></div>
  </div>
</div>
fullscreenButton.addEventListener('click', function (event) {
  event.stopPropagation();
  if (document.fullscreenElement) {
    document.exitFullscreen();
  } else {
    requestFullscreenVideo();
  }
});

function requestFullscreenVideo() {
  if (videoContainer.requestFullscreen) {
    videoContainer.requestFullscreen();
  } else {
    video.webkitEnterFullscreen();
  }
}

document.addEventListener('fullscreenchange', function () {
  fullscreenButton.classList.toggle('active', document.fullscreenElement);
});

Alternar mudança de orientação no modo de tela cheia

Como o usuário gira o dispositivo no modo paisagem, podemos ser inteligentes sobre isso e solicitar automaticamente a tela cheia para criar uma experiência imersiva. Para isso, precisaremos da API Screen Orientation, que ainda não é compatível em todos os lugares e ainda prefixado em alguns navegadores naquela época. Então, este será nosso primeiro aprimoramento progressivo.

Como funciona? Assim que detectamos as mudanças na orientação da tela, vamos solicitar tela cheia se a janela do navegador estiver em modo paisagem (ou seja, seu largura é maior que sua altura). Caso contrário, vamos sair da tela cheia. Isso é tudo.

if ('orientation' in screen) {
  screen.orientation.addEventListener('change', function () {
    // Let's request fullscreen if user switches device in landscape mode.
    if (screen.orientation.type.startsWith('landscape')) {
      requestFullscreenVideo();
    } else if (document.fullscreenElement) {
      document.exitFullscreen();
    }
  });
}

Bloquear a tela no modo paisagem ao clicar no botão

Como o vídeo pode ser visualizado melhor no modo paisagem, é melhor bloquear a tela no paisagem quando o usuário clicar no "botão de tela cheia". Vamos combinar usaram anteriormente a API Screen Orientation e algumas mídias consultas para garantir que essa experiência seja a melhor possível.

Bloquear a tela no modo paisagem é tão fácil quanto ligar screen.orientation.lock('landscape'): No entanto, isso só deve ser feito quando dispositivo está no modo retrato com matchMedia('(orientation: portrait)') e pode ser segurado com uma mão usando matchMedia('(max-device-width: 768px)'), pois não seria uma boa experiência para usuários de tablets.

fullscreenButton.addEventListener('click', function (event) {
  event.stopPropagation();
  if (document.fullscreenElement) {
    document.exitFullscreen();
  } else {
    requestFullscreenVideo();
    <strong>lockScreenInLandscape();</strong>;
  }
});
function lockScreenInLandscape() {
  if (!('orientation' in screen)) {
    return;
  }
  // Let's force landscape mode only if device is in portrait mode and can be held in one hand.
  if (
    matchMedia('(orientation: portrait) and (max-device-width: 768px)').matches
  ) {
    screen.orientation.lock('landscape');
  }
}

Desbloquear tela quando a orientação do dispositivo mudar

Você deve ter notado que a experiência da tela de bloqueio que acabamos de criar não é perfeito, pois não recebemos alterações de orientação quando a tela está bloqueada.

Para corrigir isso, vamos usar a API Device Orientation se você disponíveis. Essa API fornece informações do hardware que mede a capacidade posição e movimento no espaço: giroscópio e bússola digital orientação e acelerômetro para a velocidade. Quando detectamos um dispositivo mudança de orientação, vamos desbloquear a tela com screen.orientation.unlock() se o usuário segura o dispositivo no modo retrato e a tela está bloqueada no modo paisagem.

function lockScreenInLandscape() {
  if (!('orientation' in screen)) {
    return;
  }
  // Let's force landscape mode only if device is in portrait mode and can be held in one hand.
  if (matchMedia('(orientation: portrait) and (max-device-width: 768px)').matches) {
    screen.orientation.lock('landscape')
    <strong>.then(function() {
      listenToDeviceOrientationChanges();
    })</strong>;
  }
}
function listenToDeviceOrientationChanges() {
  if (!('DeviceOrientationEvent' in window)) {
    return;
  }
  var previousDeviceOrientation, currentDeviceOrientation;
  window.addEventListener(
    'deviceorientation',
    function onDeviceOrientationChange(event) {
      // event.beta represents a front to back motion of the device and
      // event.gamma a left to right motion.
      if (Math.abs(event.gamma) > 10 || Math.abs(event.beta) < 10) {
        previousDeviceOrientation = currentDeviceOrientation;
        currentDeviceOrientation = 'landscape';
        return;
      }
      if (Math.abs(event.gamma) < 10 || Math.abs(event.beta) > 10) {
        previousDeviceOrientation = currentDeviceOrientation;
        // When device is rotated back to portrait, let's unlock screen orientation.
        if (previousDeviceOrientation == 'landscape') {
          screen.orientation.unlock();
          window.removeEventListener(
            'deviceorientation',
            onDeviceOrientationChange,
          );
        }
      }
    },
  );
}

Como você pode ver, esta é a experiência de tela cheia perfeita que estávamos procurando. Para ver como isso funciona, confira este exemplo.

Reprodução em segundo plano

Quando você detecta que uma página da Web ou um vídeo nela não está mais visível, atualize suas análises para refletir isso. Isso também pode afetar a reprodução atual, como ao escolher outra faixa, pausá-la ou até mesmo de botões personalizados ao usuário, por exemplo.

Pausar o vídeo quando a visibilidade da página mudar

Com a API Page Visibility, podemos determinar a visibilidade atual de um e ser notificado sobre alterações de visibilidade. O código abaixo pausa o vídeo quando a página está oculto. Isso acontece quando o bloqueio de tela está ativo ou quando você troca de guia por instância.

Como a maioria dos navegadores para dispositivos móveis agora oferece controles fora do navegador, o que permite retomando um vídeo pausado, recomendamos que você defina este comportamento somente se o usuário for em segundo plano.

document.addEventListener('visibilitychange', function () {
  // Pause video when page is hidden.
  if (document.hidden) {
    video.pause();
  }
});

Mostrar/ocultar o botão de desativar microfone na mudança de visibilidade do vídeo

Com a nova API Intersection Observer, é possível detalhar ainda mais sem nenhum custo. Essa API permite saber quando um elemento observado entra ou sai da janela de visualização do navegador.

Vamos mostrar/ocultar um botão de desativar microfone com base na visibilidade do vídeo na página. Se vídeo está sendo reproduzido, mas não está visível, um pequeno botão de mudo será exibido nos no canto inferior direito da página para dar ao usuário controle sobre o som do vídeo. A O evento de vídeo volumechange é usado para atualizar o estilo do botão de desativar microfone.

<button id="muteButton"></button>
if ('IntersectionObserver' in window) {
  // Show/hide mute button based on video visibility in the page.
  function onIntersection(entries) {
    entries.forEach(function (entry) {
      muteButton.hidden = video.paused || entry.isIntersecting;
    });
  }
  var observer = new IntersectionObserver(onIntersection);
  observer.observe(video);
}

muteButton.addEventListener('click', function () {
  // Mute/unmute video on button click.
  video.muted = !video.muted;
});

video.addEventListener('volumechange', function () {
  muteButton.classList.toggle('active', video.muted);
});

Reproduzir apenas um vídeo por vez

Se houver mais de um vídeo em uma página, sugiro que você reproduza apenas um. e pausam os outros automaticamente para que o usuário não precise ouvir várias faixas de áudio tocando simultaneamente.

// This array should be initialized once all videos have been added.
var videos = Array.from(document.querySelectorAll('video'));

videos.forEach(function (video) {
  video.addEventListener('play', pauseOtherVideosPlaying);
});

function pauseOtherVideosPlaying(event) {
  var videosToPause = videos.filter(function (video) {
    return !video.paused && video != event.target;
  });
  // Pause all other videos currently playing.
  videosToPause.forEach(function (video) {
    video.pause();
  });
}

Personalizar notificações de mídia

Com a API Media Session, também é possível personalizar mídias notificações fornecendo metadados para o vídeo em reprodução. Ela também permite processar eventos relacionados a mídia, como mudança de rastreamento ou busca que podem vir de notificações ou teclas de mídia. Para ver isso em ação, verifique a amostra.

Quando seu app da Web está reproduzindo áudio ou vídeo, você já pode ver uma mídia notificação exibida na bandeja de notificações. No Android, o Chrome faz o melhor que pode para mostrar as informações apropriadas usando o título do documento e imagem do ícone que encontrar.

Vamos ver como personalizar essa notificação de mídia definindo algumas mídias metadados da sessão, como título, artista, nome do álbum e arte com o API Media Session.

playPauseButton.addEventListener('click', function(event) {
  event.stopPropagation();
  if (video.paused) {
    video.play()
    <strong>.then(function() {
      setMediaSession();
    });</strong>
  } else {
    video.pause();
  }
});
function setMediaSession() {
  if (!('mediaSession' in navigator)) {
    return;
  }
  navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Never Gonna Give You Up',
    artist: 'Rick Astley',
    album: 'Whenever You Need Somebody',
    artwork: [
      {src: 'https://dummyimage.com/96x96', sizes: '96x96', type: 'image/png'},
      {
        src: 'https://dummyimage.com/128x128',
        sizes: '128x128',
        type: 'image/png',
      },
      {
        src: 'https://dummyimage.com/192x192',
        sizes: '192x192',
        type: 'image/png',
      },
      {
        src: 'https://dummyimage.com/256x256',
        sizes: '256x256',
        type: 'image/png',
      },
      {
        src: 'https://dummyimage.com/384x384',
        sizes: '384x384',
        type: 'image/png',
      },
      {
        src: 'https://dummyimage.com/512x512',
        sizes: '512x512',
        type: 'image/png',
      },
    ],
  });
}

Quando a reprodução for concluída, não será preciso "liberar" a sessão de mídia como a notificação desaparecerá automaticamente. Tenha em mente que as A configuração navigator.mediaSession.metadata será usada quando a reprodução começar. Isso é por isso que você precisa atualizá-lo para garantir que esteja sempre mostrando conteúdo informações na notificação de mídia.

Caso seu app da Web ofereça uma playlist, você pode permitir que o usuário navegue pela lista de reprodução diretamente da notificação de mídia com a opção "Anterior Rastrear" e "Próxima faixa" ícones.

if ('mediaSession' in navigator) {
  navigator.mediaSession.setActionHandler('previoustrack', function () {
    // User clicked "Previous Track" media notification icon.
    playPreviousVideo(); // load and play previous video
  });
  navigator.mediaSession.setActionHandler('nexttrack', function () {
    // User clicked "Next Track" media notification icon.
    playNextVideo(); // load and play next video
  });
}

Os gerenciadores de ação de mídia serão mantidos. Isso é muito semelhante ao evento padrão de listener, exceto que manipular um evento significa que o navegador para comportamento padrão e usa isso como um sinal de que seu app da Web oferece suporte à ação de mídia. Portanto, os controles de ação de mídia não serão exibidos, a menos você define o gerenciador de ações adequado.

Aliás, cancelar a configuração de um gerenciador de ações de mídia é tão fácil quanto atribuí-lo a null.

Com a API Media Session, você pode mostrar a opção "Retroceder". e “Procurar adiante” ícones de notificação de mídia, se quiser controlar o tempo ignorado.

if ('mediaSession' in navigator) {
  let skipTime = 10; // Time to skip in seconds

  navigator.mediaSession.setActionHandler('seekbackward', function () {
    // User clicked "Seek Backward" media notification icon.
    video.currentTime = Math.max(video.currentTime - skipTime, 0);
  });
  navigator.mediaSession.setActionHandler('seekforward', function () {
    // User clicked "Seek Forward" media notification icon.
    video.currentTime = Math.min(video.currentTime + skipTime, video.duration);
  });
}

O botão "Reproduzir/Pausar" é sempre mostrado na notificação de mídia e os ícones eventos são tratados automaticamente pelo navegador. Se, por algum motivo, o padrão comportamento não funcionar, ainda será possível processar "Reproduzir" e "Pausar" mídia eventos.

O legal da API Media Session é que a bandeja de notificações não o único lugar em que os metadados e controles de mídia ficam visíveis. A mídia é sincronizada automagicamente com qualquer dispositivo wearable pareado. Ela também aparece nas telas de bloqueio.

Feedback