Media Source Extensions (MSE) est une API JavaScript qui vous permet de créer des flux pour la lecture de segments audio ou vidéo. Bien que cet article ne traite pas de la MSE, vous devez comprendre ce concept si vous souhaitez intégrer des vidéos sur votre site qui effectuent des actions telles que :
- Le streaming adaptatif, qui consiste à s'adapter aux fonctionnalités de l'appareil et aux conditions du réseau
- L'assemblage adaptatif, comme l'insertion d'annonces
- Décalage temporel
- Contrôle des performances et de la taille de téléchargement
Vous pouvez presque considérer MSE comme une chaîne. Comme le montre la figure, plusieurs couches se trouvent entre le fichier téléchargé et les éléments multimédias.
- Un élément
<audio>
ou<video>
pour lire le contenu multimédia. - Une instance
MediaSource
avec un élémentSourceBuffer
pour alimenter l'élément multimédia. - Un appel
fetch()
ou XHR pour récupérer des données multimédias dans un objetResponse
- Appel de
Response.arrayBuffer()
pour alimenterMediaSource.SourceBuffer
.
En pratique, la chaîne se présente comme suit:
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.log('The Media Source Extensions API is not supported.');
}
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
fetch(videoUrl)
.then(function (response) {
return response.arrayBuffer();
})
.then(function (arrayBuffer) {
sourceBuffer.addEventListener('updateend', function (e) {
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
mediaSource.endOfStream();
}
});
sourceBuffer.appendBuffer(arrayBuffer);
});
}
Si vous pouvez comprendre les choses à partir des explications fournies jusqu'à présent, n'hésitez pas à arrêter de lire. Pour une explication plus détaillée, lisez la suite. Je vais vous présenter cette chaîne en créant un exemple MSE de base. Chacune des étapes de compilation ajoute du code à l'étape précédente.
Remarque concernant la clarté
Cet article vous indique-t-il tout ce que vous devez savoir sur la lecture de contenus multimédias sur une page Web ? Non, il n'a pour but que de vous aider à comprendre le code plus complexe que vous pourriez trouver ailleurs. Par souci de clarté, ce document simplifie et exclut de nombreux éléments. Pour éviter cela, nous vous recommandons également d'utiliser une bibliothèque telle que Google's Shaka Player. Je noterai tout au long de cet article les simplifications que j'ai délibérément apportées.
Quelques points non couverts
Voici quelques points que je ne traiterai pas, sans ordre particulier.
- Commandes de lecture. Nous les obtenons sans frais grâce à l'utilisation des éléments HTML5
<audio>
et<video>
. - Traiter les erreurs :
À utiliser dans les environnements de production
Voici quelques recommandations pour utiliser les API liées à MSE en production :
- Avant d'appeler ces API, gérez les événements d'erreur ou les exceptions d'API, puis vérifiez
HTMLMediaElement.readyState
etMediaSource.readyState
. Ces valeurs peuvent changer avant la diffusion des événements associés. - Assurez-vous que les appels
appendBuffer()
etremove()
précédents ne sont pas toujours en cours en vérifiant la valeur booléenneSourceBuffer.updating
avant de mettre à jour lesmode
,timestampOffset
,appendWindowStart
etappendWindowEnd
duSourceBuffer
, ou d'appelerappendBuffer()
ouremove()
sur leSourceBuffer
. - Pour toutes les instances
SourceBuffer
ajoutées à votreMediaSource
, assurez-vous qu'aucune de leurs valeursupdating
n'est vraie avant d'appelerMediaSource.endOfStream()
ou de mettre à jourMediaSource.duration
. - Si la valeur
MediaSource.readyState
estended
, les appels tels queappendBuffer()
etremove()
, ou le paramétrage deSourceBuffer.mode
ouSourceBuffer.timestampOffset
, entraînent la transition de cette valeur versopen
. Vous devez donc être prêt à gérer plusieurs événementssourceopen
. - Lors de la gestion des événements
HTMLMediaElement error
, le contenu deMediaError.message
peut être utile pour déterminer l'origine du problème, en particulier pour les erreurs difficiles à reproduire dans les environnements de test.
Associer une instance MediaSource à un élément multimédia
Comme pour de nombreuses choses dans le développement Web de nos jours, vous commencez par la détection des fonctionnalités. Obtenez ensuite un élément multimédia, un élément <audio>
ou <video>
.
Enfin, créez une instance de MediaSource
. Il est transformé en URL et transmis à l'attribut source de l'élément multimédia.
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
// Is the MediaSource instance ready?
} else {
console.log('The Media Source Extensions API is not supported.');
}
Le fait qu'un objet MediaSource
puisse être transmis à un attribut src
peut sembler un peu étrange. Il s'agit généralement de chaînes, mais elles peuvent aussi être des blobs.
Si vous inspectez une page avec des éléments multimédias intégrés et examinez son élément multimédia, vous comprendrez ce que je veux dire.
L'instance MediaSource est-elle prête ?
URL.createObjectURL()
est lui-même synchrone. Toutefois, il traite la pièce jointe de manière asynchrone. Cela entraîne un léger retard avant que vous puissiez effectuer une action avec l'instance MediaSource
. Heureusement, il existe des moyens de le vérifier.
Le moyen le plus simple consiste à utiliser une propriété MediaSource
appelée readyState
. La propriété readyState
décrit la relation entre une instance MediaSource
et un élément multimédia. Il peut avoir l'une des valeurs suivantes:
closed
: l'instanceMediaSource
n'est pas associée à un élément multimédia.open
: l'instanceMediaSource
est associée à un élément multimédia et est prête à recevoir des données ou en reçoit.ended
: l'instanceMediaSource
est associée à un élément multimédia et toutes ses données ont été transmises à cet élément.
Interroger directement ces options peut avoir un impact négatif sur les performances. Heureusement, MediaSource
déclenche également des événements lorsque readyState
change, en particulier sourceopen
, sourceclosed
et sourceended
. Pour l'exemple que je crée, je vais utiliser l'événement sourceopen
pour m'indiquer quand extraire et mettre en mémoire tampon la vidéo.
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
<strong>mediaSource.addEventListener('sourceopen', sourceOpen);</strong>
} else {
console.log("The Media Source Extensions API is not supported.")
}
<strong>function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
// Create a SourceBuffer and get the media file.
}</strong>
Notez que j'ai également appelé revokeObjectURL()
. Je sais que cela semble prématuré, mais je peux le faire à tout moment après la connexion de l'attribut src
de l'élément multimédia à une instance MediaSource
. L'appel de cette méthode ne détruit aucun objet. Cela permet à la plate-forme de gérer le nettoyage de la mémoire à un moment opportun, c'est pourquoi je l'appelle immédiatement.
Créer un SourceBuffer
Il est maintenant temps de créer SourceBuffer
, qui est l'objet qui assure le transfert de données entre les sources multimédias et les éléments multimédias. Un SourceBuffer
doit être spécifique au type de fichier multimédia que vous chargez.
En pratique, vous pouvez le faire en appelant addSourceBuffer()
avec la valeur appropriée. Notez que dans l'exemple ci-dessous, la chaîne de type mime contient un type mime et deux codecs. Il s'agit d'une chaîne mime pour un fichier vidéo, mais elle utilise des codecs distincts pour les parties vidéo et audio du fichier.
La version 1 de la spécification MSE permet aux user-agents de différer sur la nécessité de demander à la fois un type mime et un codec. Certains user-agents n'exigent pas, mais n'autorisent que le type mime. Certains agents utilisateur, comme Chrome, nécessitent un codec pour les types mime qui ne se décrivent pas eux-mêmes. Plutôt que d'essayer de trier tout cela, il est préférable d'inclure les deux.
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.log('The Media Source Extensions API is not supported.');
}
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
<strong>
var mime = 'video/webm; codecs="opus, vp09.00.10.08"'; // e.target refers to
the mediaSource instance. // Store it in a variable so it can be used in a
closure. var mediaSource = e.target; var sourceBuffer =
mediaSource.addSourceBuffer(mime); // Fetch and process the video.
</strong>;
}
Obtenir le fichier multimédia
Si vous effectuez une recherche sur Internet d'exemples de MSE, vous en trouverez de nombreux qui récupèrent des fichiers multimédias à l'aide de XHR. Pour être plus à la pointe, je vais utiliser l'API Fetch et la Promise qu'elle renvoie. Si vous essayez de le faire dans Safari, cela ne fonctionnera pas sans un polyfill fetch()
.
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
<strong>
fetch(videoUrl) .then(function(response){' '}
{
// Process the response object.
}
);
</strong>;
}
Un lecteur de qualité de production dispose du même fichier dans plusieurs versions pour prendre en charge différents navigateurs. Il peut utiliser des fichiers distincts pour l'audio et la vidéo afin de permettre de sélectionner l'audio en fonction des paramètres de langue.
Le code réel comporte également plusieurs copies de fichiers multimédias de différentes résolutions, afin de pouvoir s'adapter à différentes capacités de l'appareil et conditions de réseau. Une telle application peut charger et lire des vidéos par fragments à l'aide de requêtes de plage ou de segments. Cela permet de s'adapter aux conditions du réseau lorsque des contenus multimédias sont lus. Vous avez peut-être entendu les termes DASH ou HLS, qui sont deux méthodes permettant d'y parvenir. Une discussion complète de ce sujet dépasse le cadre de cette introduction.
Traiter l'objet de réponse
Le code semble presque terminé, mais la lecture du contenu multimédia n'est pas lancée. Nous devons obtenir les données multimédias de l'objet Response
vers SourceBuffer
.
Le moyen classique de transmettre des données de l'objet de réponse à l'instance MediaSource
consiste à obtenir une valeur ArrayBuffer
à partir de l'objet de réponse et à la transmettre à SourceBuffer
. Commencez par appeler response.arrayBuffer()
, qui renvoie une promesse dans le tampon. Dans mon code, j'ai transmis cette promesse à une deuxième clause then()
, où je l'ai ajoutée à SourceBuffer
.
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
fetch(videoUrl)
.then(function(response) {
<strong>return response.arrayBuffer();</strong>
})
<strong>.then(function(arrayBuffer) {
sourceBuffer.appendBuffer(arrayBuffer);
});</strong>
}
Appeler endOfStream()
Une fois que tous les ArrayBuffers
ont été ajoutés et qu'aucune autre donnée multimédia n'est attendue, appelez MediaSource.endOfStream()
. MediaSource.readyState
est remplacé par ended
et l'événement sourceended
est déclenché.
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
fetch(videoUrl)
.then(function(response) {
return response.arrayBuffer();
})
.then(function(arrayBuffer) {
<strong>sourceBuffer.addEventListener('updateend', function(e) {
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
mediaSource.endOfStream();
}
});</strong>
sourceBuffer.appendBuffer(arrayBuffer);
});
}
Version finale
Voici l'exemple de code complet. J'espère que vous en savez plus sur les extensions de source multimédia.
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.log('The Media Source Extensions API is not supported.');
}
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
fetch(videoUrl)
.then(function (response) {
return response.arrayBuffer();
})
.then(function (arrayBuffer) {
sourceBuffer.addEventListener('updateend', function (e) {
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
mediaSource.endOfStream();
}
});
sourceBuffer.appendBuffer(arrayBuffer);
});
}