Come si crea la migliore esperienza multimediale mobile sul web? Facile! Tutto dipende dal coinvolgimento degli utenti e dall'importanza che attribuisci ai contenuti multimediali su una pagina web. Penso che siamo tutti d'accordo sul fatto che se il video è IL motivo della visita di un utente, l'esperienza utente deve essere immersiva e coinvolgente.
In questo articolo ti mostro come migliorare progressivamente la tua esperienza multimediale e renderla più immersiva grazie a una serie di API web. Ecco perché creeremo una semplice esperienza di player mobile con controlli personalizzati, schermo intero e riproduzione in background. Puoi provare l'esempio ora e trovare il codice nel nostro repository GitHub.
Controlli personalizzati
Come puoi vedere, il layout HTML che utilizzeremo per il nostro media player è piuttosto semplice: un elemento root <div> contiene un elemento multimediale <video> e un elemento secondario <div> dedicato ai controlli video.
I controlli video che tratteremo in seguito includono: un pulsante di riproduzione/pausa, un pulsante per lo schermo intero, pulsanti per andare avanti e indietro e alcuni elementi per l'ora corrente, la durata e il monitoraggio del tempo.
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls"></div>
</div>
Leggi i metadati dei video
Innanzitutto, attendiamo il caricamento dei metadati del video per impostare la durata del video, l'ora corrente e inizializzare la barra di avanzamento. Tieni presente che la funzione secondsToTimeCode() è una funzione di utilità personalizzata che ho scritto per convertire un numero di secondi in una stringa nel formato "hh:mm:ss", più adatto al nostro 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
})`;
});
Riproduci/Metti in pausa video
Ora che i metadati del video sono stati caricati, aggiungiamo il nostro primo pulsante che consente all'utente di riprodurre e mettere in pausa il video con video.play() e video.pause() a seconda dello stato di riproduzione.
<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();
}
});
Anziché regolare i controlli video nel listener di eventi click, utilizziamo gli eventi video play e pause. Rendere i nostri controlli basati sugli eventi aiuta con la flessibilità (come vedremo più avanti con l'API Media Session) e ci consentirà di mantenere i nostri controlli sincronizzati se il browser interviene nella riproduzione.
Quando inizia la riproduzione del video, cambiamo lo stato del pulsante in "Pausa" e nascondiamo i controlli video. Quando il video viene messo in pausa, cambiamo semplicemente lo stato del pulsante in "Riproduci" e mostriamo i controlli video.
video.addEventListener('play', function () {
playPauseButton.classList.add('playing');
});
video.addEventListener('pause', function () {
playPauseButton.classList.remove('playing');
});
Quando il tempo indicato dall'attributo currentTime del video cambia tramite l'evento video timeupdate, aggiorniamo anche i nostri controlli personalizzati se sono visibili.
video.addEventListener('timeupdate', function () {
if (videoControls.classList.contains('visible')) {
videoCurrentTime.textContent = secondsToTimeCode(video.currentTime);
videoProgressBar.style.transform = `scaleX(${
video.currentTime / video.duration
})`;
}
});
Al termine del video, cambiamo semplicemente lo stato del pulsante in "Riproduci", impostiamo il video
currentTime su 0 e mostriamo i controlli video per il momento. Tieni presente che potremmo anche scegliere di caricare automaticamente un altro video se l'utente ha attivato una sorta di funzionalità di "riproduzione automatica".
video.addEventListener('ended', function () {
playPauseButton.classList.remove('playing');
video.currentTime = 0;
});
Vai indietro e avanti
Continuiamo e aggiungiamo i pulsanti "Cerca indietro " e"Cerca avanti " in modo che l'utente possa saltare facilmente alcuni contenuti.
<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);
});
Come prima, anziché modificare lo stile del video nei listener di eventi click di questi pulsanti, utilizzeremo gli eventi video seeking e seeked attivati per regolare la luminosità del video. La mia classe CSS personalizzata seeking è semplice come filter: brightness(0);.
video.addEventListener('seeking', function () {
video.classList.add('seeking');
});
video.addEventListener('seeked', function () {
video.classList.remove('seeking');
});
Ecco di seguito cosa abbiamo creato finora. Nella prossima sezione, implementeremo il pulsante per lo schermo intero.
Schermo intero
In questo caso, utilizzeremo diverse API web per creare un'esperienza a schermo intero perfetta e senza interruzioni. Per vederlo in azione, dai un'occhiata all'esempio.
Ovviamente, non è necessario utilizzarli tutti. Scegli quelli che ritieni più adatti e combinali per creare il tuo flusso personalizzato.
Impedisci lo schermo intero automatico
Su iOS, gli elementi video entrano automaticamente in modalità a schermo intero quando inizia la riproduzione dei contenuti multimediali. Poiché stiamo cercando di personalizzare e controllare il più possibile la nostra esperienza multimediale sui browser mobile, ti consiglio di impostare l'attributo playsinline dell'elemento video per forzare la riproduzione in linea su iPhone e non attivare la modalità a schermo intero all'inizio della riproduzione. Tieni presente che questa operazione non ha effetti collaterali su altri browser.
<div id="videoContainer"></div>
<video id="video" src="file.mp4"></video><strong>playsinline</strong></video>
<div id="videoControls">...</div>
</div>
Attiva/disattiva lo schermo intero al clic sul pulsante
Ora che impediamo la visualizzazione automatica a schermo intero, dobbiamo gestire noi stessi la modalità a schermo intero per il video con l'API Fullscreen. Quando l'utente fa clic sul"pulsante per lo schermo intero", usciamo dalla modalità a schermo intero con document.exitFullscreen() se questa modalità è attualmente in uso nel documento. In caso contrario, richiedi la modalità a schermo intero sul contenitore video con il metodo requestFullscreen(), se disponibile, o esegui il fallback a webkitEnterFullscreen() sull'elemento video solo su 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);
});
Attiva/disattiva lo schermo intero quando cambia l'orientamento dello schermo
Quando l'utente ruota il dispositivo in modalità Orizzontale, dobbiamo essere intelligenti e richiedere automaticamente lo schermo intero per creare un'esperienza immersiva. Per farlo, abbiamo bisogno dell'API Screen Orientation, che non è ancora supportata ovunque e che all'epoca era ancora preceduta da un prefisso in alcuni browser. Questo sarà quindi il nostro primo potenziamento progressivo.
Come funziona? Non appena rileviamo che l'orientamento dello schermo cambia, richiediamo la modalità a schermo intero se la finestra del browser è in modalità Orizzontale (ovvero la larghezza è maggiore dell'altezza). In caso contrario, usciamo dalla modalità a schermo intero. È tutto.
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();
}
});
}
Blocca la schermata di blocco in orizzontale quando si fa clic sul pulsante
Poiché i video potrebbero essere visualizzati meglio in modalità Orizzontale, potremmo voler bloccare lo schermo in modalità Orizzontale quando l'utente fa clic sul "pulsante Schermo intero". Per garantire un'esperienza ottimale, combineremo l'API Screen Orientation utilizzata in precedenza e alcune query supporti.
Bloccare lo schermo in modalità Orizzontale è facile come chiamare
screen.orientation.lock('landscape'). Tuttavia, dovremmo farlo solo quando il dispositivo è in modalità Ritratto con matchMedia('(orientation: portrait)') e può essere tenuto in una mano con matchMedia('(max-device-width: 768px)'), poiché questa non sarebbe un'esperienza ottimale per gli utenti su tablet.
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');
}
}
Sblocca lo schermo quando cambia l'orientamento del dispositivo
Tuttavia, potresti aver notato che l'esperienza della schermata di blocco che abbiamo appena creato non è perfetta, in quanto non riceviamo le modifiche all'orientamento dello schermo quando lo schermo è bloccato.
Per risolvere il problema, utilizziamo l'API Device Orientation, se disponibile. Questa API fornisce informazioni dall'hardware che misura la posizione e il movimento di un dispositivo nello spazio: giroscopio e bussola digitale per l'orientamento e accelerometro per la velocità. Quando rileviamo un cambiamento
dell'orientamento del dispositivo, sblocchiamo lo schermo con screen.orientation.unlock() se
l'utente tiene il dispositivo in modalità Ritratto e lo schermo è bloccato in modalità Orizzontale.
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,
);
}
}
},
);
}
Come puoi vedere, questa è l'esperienza fluida a schermo intero che stavamo cercando. Per vedere come funziona, consulta l'esempio.
Riproduzione in background
Quando rilevi che una pagina web o un video nella pagina web non è più visibile, potresti voler aggiornare i dati di analisi per riflettere questa situazione. Ciò potrebbe influire anche sulla riproduzione corrente, ad esempio scegliendo una traccia diversa, mettendola in pausa o mostrando pulsanti personalizzati all'utente.
Metti in pausa il video quando cambia la visibilità della pagina
Con l'API Page Visibility, possiamo determinare la visibilità corrente di una pagina e ricevere notifiche sulle modifiche alla visibilità. Il codice riportato di seguito mette in pausa il video quando la pagina è nascosta. Ciò accade, ad esempio, quando il blocco schermo è attivo o quando cambi scheda.
Poiché la maggior parte dei browser mobile ora offre controlli esterni al browser che consentono di riprendere un video in pausa, ti consiglio di impostare questo comportamento solo se l'utente è autorizzato a riprodurre in background.
document.addEventListener('visibilitychange', function () {
// Pause video when page is hidden.
if (document.hidden) {
video.pause();
}
});
Mostra/nascondi il pulsante di disattivazione audio quando cambia la visibilità del video
Se utilizzi la nuova API Intersection Observer, puoi ottenere un livello di granularità ancora maggiore senza costi aggiuntivi. Questa API ti consente di sapere quando un elemento osservato entra o esce dalla finestra visibile del browser.
Mostriamo/nascondiamo un tasto di disattivazione audio in base alla visibilità del video nella pagina. Se il video è in riproduzione, ma non è attualmente visibile, nell'angolo in basso a destra della pagina verrà visualizzato un piccolo tasto di disattivazione audio per consentire all'utente di controllare l'audio del video. L'evento video volumechange viene utilizzato per aggiornare lo stile del tasto di disattivazione audio.
<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);
});
Riproduci un solo video alla volta
Se in una pagina sono presenti più video, ti suggerisco di riprodurne solo uno e di mettere in pausa gli altri automaticamente, in modo che l'utente non debba ascoltare più tracce audio contemporaneamente.
// 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();
});
}
Personalizza le notifiche dei contenuti multimediali
Con l'API Media Session, puoi anche personalizzare le notifiche dei contenuti multimediali fornendo i metadati del video attualmente in riproduzione. Inoltre, ti consente di gestire gli eventi relativi ai contenuti multimediali, come la ricerca o il cambio di traccia, che potrebbero provenire da notifiche o tasti multimediali. Per vedere come funziona, dai un'occhiata all'esempio.
Quando l'app web riproduce audio o video, puoi già vedere una notifica multimediale nella barra delle notifiche. Su Android, Chrome fa del suo meglio per mostrare le informazioni appropriate utilizzando il titolo del documento e l'immagine dell'icona più grande che riesce a trovare.
Vediamo come personalizzare questa notifica multimediale impostando alcuni metadati della sessione multimediale, come il titolo, l'artista, il nome dell'album e l'artwork con l'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',
},
],
});
}
Al termine della riproduzione, non è necessario "rilasciare" la sessione multimediale, poiché la notifica scompare automaticamente. Tieni presente che l'impostazione corrente di navigator.mediaSession.metadata verrà utilizzata all'inizio di ogni riproduzione. Per questo motivo, devi aggiornarlo per assicurarti di mostrare sempre informazioni pertinenti nella notifica dei contenuti multimediali.
Se la tua app web fornisce una playlist, potresti voler consentire all'utente di navigare nella playlist direttamente dalla notifica multimediale con alcune icone "Traccia precedente" e "Traccia successiva".
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
});
}
Tieni presente che i gestori delle azioni multimediali rimarranno invariati. Questo è molto simile al pattern del listener di eventi, con la differenza che la gestione di un evento significa che il browser interrompe qualsiasi comportamento predefinito e lo utilizza come indicatore del fatto che la tua app web supporta l'azione multimediale. Pertanto, i controlli delle azioni multimediali non verranno mostrati, a meno che tu non imposti il gestore delle azioni appropriato.
A proposito, annullare l'impostazione di un gestore di azioni multimediali è facile quanto assegnarlo a null.
L'API Media Session ti consente di mostrare le icone di notifica dei contenuti multimediali "Cerca indietro" e "Cerca avanti" se vuoi controllare la quantità di tempo saltata.
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);
});
}
L'icona "Riproduci/Pausa" viene sempre visualizzata nella notifica multimediale e gli eventi correlati vengono gestiti automaticamente dal browser. Se per qualche motivo il comportamento predefinito non funziona, puoi comunque gestire gli eventi multimediali "Riproduci" e "Pausa".
La cosa interessante dell'API Media Session è che la barra delle notifiche non è l'unico posto in cui sono visibili i metadati e i controlli dei contenuti multimediali. La notifica multimediale viene sincronizzata automaticamente con qualsiasi dispositivo indossabile accoppiato. Inoltre, viene visualizzato nelle schermate di blocco.