Comment créer la meilleure expérience multimédia mobile sur le Web ? Rien de plus simple ! Tout dépend de l'engagement utilisateur et de l'importance que vous accordez aux contenus multimédias sur une page Web. Je pense que nous sommes tous d'accord pour dire que si la vidéo est LA raison de la visite d'un utilisateur, son expérience doit être immersive et réengager.

Dans cet article, je vais vous montrer comment améliorer progressivement votre expérience multimédia et la rendre plus immersive grâce à une multitude d'API Web. C'est pourquoi nous allons créer une expérience de lecteur mobile simple avec des commandes personnalisées, un plein écran et une lecture en arrière-plan. Vous pouvez essayer l'exemple dès maintenant et trouver le code dans notre dépôt GitHub.
Commandes personnalisées

Comme vous pouvez le voir, la mise en page HTML que nous allons utiliser pour notre lecteur multimédia est assez simple: un élément racine <div>
contient un élément multimédia <video>
et un élément enfant <div>
dédié aux commandes vidéo.
Les commandes vidéo que nous verrons plus tard incluent: un bouton de lecture/pause, un bouton en plein écran, des boutons de recherche avant et arrière, ainsi que des éléments pour l'heure actuelle, la durée et le suivi du temps.
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls"></div>
</div>
Lire les métadonnées de la vidéo
Commençons par attendre que les métadonnées vidéo soient chargées pour définir la durée de la vidéo, l'heure actuelle et initialiser la barre de progression. Notez que la fonction secondsToTimeCode()
est une fonction utilitaire personnalisée que j'ai écrite. Elle convertit un nombre de secondes en chaîne au format "hh:mm:ss", ce qui est plus adapté dans notre cas.
<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
})`;
});

Lire/Mettre en pause la vidéo
Maintenant que les métadonnées de la vidéo sont chargées, ajoutons notre premier bouton permettant à l'utilisateur de lire et de mettre en pause la vidéo avec video.play()
et video.pause()
en fonction de son état de lecture.
<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();
}
});
Plutôt que d'ajuster nos commandes vidéo dans l'écouteur d'événement click
, nous utilisons les événements vidéo play
et pause
. Baser nos commandes sur des événements permet de gagner en flexibilité (comme nous le verrons plus tard avec l'API Media Session) et de synchroniser nos commandes si le navigateur intervient dans la lecture.
Lorsque la vidéo commence à être lue, nous définissons l'état du bouton sur "Pause" et masquons les commandes de la vidéo. Lorsque la vidéo est mise en pause, nous changeons simplement l'état du bouton en "Lire" et affichons les commandes vidéo.
video.addEventListener('play', function () {
playPauseButton.classList.add('playing');
});
video.addEventListener('pause', function () {
playPauseButton.classList.remove('playing');
});
Lorsque l'heure indiquée par l'attribut currentTime
de la vidéo a changé via l'événement vidéo timeupdate
, nous mettons également à jour nos commandes personnalisées si elles sont visibles.
video.addEventListener('timeupdate', function () {
if (videoControls.classList.contains('visible')) {
videoCurrentTime.textContent = secondsToTimeCode(video.currentTime);
videoProgressBar.style.transform = `scaleX(${
video.currentTime / video.duration
})`;
}
});
Lorsque la vidéo se termine, nous changeons simplement l'état du bouton en "lecture", rétablissons la valeur currentTime
de la vidéo sur 0 et affichons les commandes de la vidéo pour le moment. Notez que nous pouvons également choisir de charger automatiquement une autre vidéo si l'utilisateur a activé une fonctionnalité de "lecture automatique".
video.addEventListener('ended', function () {
playPauseButton.classList.remove('playing');
video.currentTime = 0;
});
Avance rapide et retour en arrière
Poursuivez et ajoutez les boutons "Rechercher en arrière " et"Rechercher en avant " afin que l'utilisateur puisse facilement ignorer certains contenus.
<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);
});
Comme précédemment, plutôt que d'ajuster le style de la vidéo dans les écouteurs d'événements click
de ces boutons, nous utiliserons les événements vidéo seeking
et seeked
déclenchés pour ajuster la luminosité de la vidéo. Ma classe CSS seeking
personnalisée est aussi simple que filter: brightness(0);
.
video.addEventListener('seeking', function () {
video.classList.add('seeking');
});
video.addEventListener('seeked', function () {
video.classList.remove('seeking');
});
Voici ce que nous avons créé jusqu'à présent. Dans la section suivante, nous allons implémenter le bouton en plein écran.
Plein écran
Nous allons ici exploiter plusieurs API Web pour créer une expérience en plein écran parfaite et fluide. Pour voir un exemple concret, consultez l'exemple.
Bien entendu, vous n'êtes pas obligé de les utiliser tous. Il vous suffit de choisir celles qui vous conviennent et de les combiner pour créer votre flux personnalisé.
Empêcher le plein écran automatique
Sur iOS, les éléments video
passent automatiquement en mode plein écran lorsque la lecture multimédia commence. Comme nous essayons de personnaliser et de contrôler autant que possible notre expérience multimédia dans les navigateurs mobiles, je vous recommande de définir l'attribut playsinline
de l'élément video
pour le forcer à lire en ligne sur iPhone et à ne pas passer en mode plein écran au début de la lecture. Notez que cela n'a aucun effet secondaire sur les autres navigateurs.
<div id="videoContainer"></div>
<video id="video" src="file.mp4"></video><strong>playsinline</strong></video>
<div id="videoControls">...</div>
</div>
Activer/Désactiver le plein écran en cliquant sur un bouton
Maintenant que nous empêchons le plein écran automatique, nous devons gérer nous-mêmes le mode plein écran de la vidéo avec l'API Fullscreen. Lorsque l'utilisateur clique sur le bouton"plein écran", quittons le mode plein écran avec document.exitFullscreen()
si le mode plein écran est actuellement utilisé par le document. Sinon, demandez le plein écran sur le conteneur vidéo avec la méthode requestFullscreen()
, le cas échéant, ou utilisez webkitEnterFullscreen()
sur l'élément vidéo uniquement sur 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);
});
Activer/Désactiver le plein écran en cas de changement d'orientation de l'écran
Lorsque l'utilisateur fait pivoter l'appareil en mode paysage, soyons intelligents et demandons automatiquement le plein écran pour créer une expérience immersive. Pour ce faire, nous aurons besoin de l'API Screen Orientation, qui n'est pas encore prise en charge partout et qui est toujours préfixée dans certains navigateurs à ce moment-là. Il s'agit donc de notre première amélioration progressive.
Comment ça marche ? Dès que nous détectons que l'orientation de l'écran change, demandons le plein écran si la fenêtre du navigateur est en mode paysage (c'est-à-dire que sa largeur est supérieure à sa hauteur). Sinon, quittons le mode plein écran. C'est tout.
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();
}
});
}
Écran de verrouillage en mode paysage en cliquant sur un bouton
Comme la vidéo est mieux visible en mode paysage, nous pouvons verrouiller l'écran en mode paysage lorsque l'utilisateur clique sur le bouton "Plein écran". Nous allons combiner l'API Screen Orientation précédemment utilisée et certaines requêtes multimédias pour nous assurer que cette expérience est la meilleure possible.
Verrouiller l'écran en mode paysage est aussi simple que d'appeler screen.orientation.lock('landscape')
. Toutefois, nous ne devons le faire que lorsque l'appareil est en mode portrait avec matchMedia('(orientation: portrait)')
et peut être tenu dans une main avec matchMedia('(max-device-width: 768px)')
, car cela ne serait pas une expérience optimale pour les utilisateurs sur tablette.
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');
}
}
Déverrouiller l'écran en cas de changement d'orientation de l'appareil
Vous avez peut-être remarqué que l'expérience de l'écran de verrouillage que nous venons de créer n'est pas parfaite, car nous ne recevons pas de modifications d'orientation de l'écran lorsque l'écran est verrouillé.
Pour résoudre ce problème, utilisons l'API Device Orientation, si elle est disponible. Cette API fournit des informations provenant du matériel qui mesure la position et le mouvement d'un appareil dans l'espace: gyroscope et boussole numérique pour son orientation, et accéléromètre pour sa vitesse. Lorsque nous détectons un changement d'orientation de l'appareil, déverrouillons l'écran avec screen.orientation.unlock()
si l'utilisateur tient l'appareil en mode portrait et que l'écran est verrouillé en mode paysage.
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,
);
}
}
},
);
}
Comme vous pouvez le constater, il s'agit de l'expérience en plein écran fluide que nous recherchions. Pour voir un exemple, consultez l'exemple.
Lecture en arrière-plan
Lorsque vous constatez qu'une page Web ou une vidéo sur une page Web n'est plus visible, vous pouvez mettre à jour vos données analytiques pour le refléter. Cela peut également avoir une incidence sur la lecture en cours, par exemple en sélectionnant un autre titre, en le mettant en pause ou en affichant des boutons personnalisés à l'utilisateur.
Mettre la vidéo en pause en cas de modification de la visibilité de la page
L'API Page Visibility nous permet de déterminer la visibilité actuelle d'une page et de recevoir une notification en cas de modification de la visibilité. Le code ci-dessous met la vidéo en pause lorsque la page est masquée. Cela se produit lorsque le verrouillage de l'écran est activé ou lorsque vous passez d'un onglet à l'autre, par exemple.
Comme la plupart des navigateurs mobiles proposent désormais des commandes en dehors du navigateur permettant de reprendre une vidéo mise en pause, nous vous recommandons de ne définir ce comportement que si l'utilisateur est autorisé à lire en arrière-plan.
document.addEventListener('visibilitychange', function () {
// Pause video when page is hidden.
if (document.hidden) {
video.pause();
}
});
Affichage/Masquage du bouton de désactivation du son en cas de modification de la visibilité de la vidéo
Si vous utilisez la nouvelle API Intersection Observer, vous pouvez être encore plus précis sans frais. Cette API vous indique quand un élément observé entre ou sort de la fenêtre d'affichage du navigateur.
Affichons/Masquons un bouton de mise en sourdine en fonction de la visibilité de la vidéo sur la page. Si la vidéo est en cours de lecture, mais qu'elle n'est pas visible, un mini bouton de mise en sourdine s'affiche en bas à droite de la page pour permettre à l'utilisateur de contrôler le son de la vidéo. L'événement vidéo volumechange
permet de mettre à jour le style du bouton de mise en sourdine.
<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);
});
ne peut lire qu'une seule vidéo à la fois ;
Si une page comporte plusieurs vidéos, je vous suggère de n'en lire qu'une seule et de mettre les autres en pause automatiquement afin que l'utilisateur n'ait pas à entendre plusieurs pistes audio en même temps.
// 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();
});
}
Personnaliser les notifications multimédias
Avec l'API Media Session, vous pouvez également personnaliser les notifications multimédias en fournissant des métadonnées pour la vidéo en cours de lecture. Il vous permet également de gérer les événements liés aux contenus multimédias, tels que la recherche ou le changement de piste, qui peuvent provenir de notifications ou de touches multimédias. Pour voir un exemple concret, consultez l'exemple.
Lorsque votre application Web lit du contenu audio ou vidéo, une notification multimédia s'affiche déjà dans la barre des notifications. Sur Android, Chrome fait de son mieux pour afficher les informations appropriées à l'aide du titre du document et de l'image d'icône la plus grande qu'il puisse trouver.
Voyons comment personnaliser cette notification multimédia en définissant des métadonnées de session multimédia telles que le titre, l'artiste, le nom de l'album et l'illustration avec 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',
},
],
});
}
Une fois la lecture terminée, vous n'avez pas besoin de "libérer" la session multimédia, car la notification disparaît automatiquement. N'oubliez pas que l'navigator.mediaSession.metadata
actuelle sera utilisée au début de la lecture. C'est pourquoi vous devez le mettre à jour pour vous assurer de toujours afficher des informations pertinentes dans la notification multimédia.
Si votre application Web fournit une playlist, vous pouvez autoriser l'utilisateur à parcourir votre playlist directement à partir de la notification multimédia à l'aide d'icônes "Piste précédente" et "Piste suivante".
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
});
}
Notez que les gestionnaires d'actions multimédias seront conservés. Ce modèle est très semblable au modèle d'écouteur d'événements, sauf que la gestion d'un événement signifie que le navigateur cesse d'exécuter tout comportement par défaut et l'utilise comme signal indiquant que votre application Web est compatible avec l'action multimédia. Par conséquent, les commandes d'action multimédias ne s'affichent que si vous définissez le gestionnaire d'action approprié.
Par ailleurs, désactiver un gestionnaire d'action multimédia est aussi simple que de l'attribuer à null
.
L'API Media Session vous permet d'afficher les icônes de notification multimédia "Rechercher en arrière" et "Rechercher en avant" si vous souhaitez contrôler la durée de la lecture à sauter.
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'icône "Lire/Mettre en pause" s'affiche toujours dans la notification multimédia, et les événements associés sont gérés automatiquement par le navigateur. Si, pour une raison quelconque, le comportement par défaut ne fonctionne pas, vous pouvez toujours gérer les événements multimédias de lecture et de mise en pause.
L'avantage de l'API Media Session est que la barre de notification n'est pas le seul endroit où les métadonnées et les commandes multimédias sont visibles. La notification multimédia est synchronisée automatiquement avec tous les appareils connectés. Il s'affiche également sur les écrans de verrouillage.