web-dev-qa-db-fra.com

Vidéo HTML5: streaming vidéo avec URL Blob

J'ai un tableau de Blobs (données binaires, vraiment - je peux l'exprimer cependant est le plus efficace. J'utilise Blobs pour l'instant mais peut-être un Uint8Array ou quelque chose serait mieux). Chaque Blob contient 1 seconde de données audio/vidéo. Chaque seconde, un nouveau blob est généré et ajouté à mon tableau. Ainsi, le code ressemble à peu près à ceci:

var arrayOfBlobs = [];
setInterval(function() {
    arrayOfBlobs.append(nextChunk());
}, 1000);

Mon objectif est de diffuser ces données audio/vidéo vers un élément HTML5. Je sais qu'une URL Blob peut être générée et lue comme suit:

var src = URL.createObjectURL(arrayOfBlobs[0]);
var video = document.getElementsByTagName("video")[0];
video.src = src;

Bien sûr, cela ne joue que la première 1 seconde de vidéo. Je suppose également que je peux concaténer trivialement tous les Blobs actuellement dans mon tableau pour jouer plus d'une seconde:

// Something like this (untested)
var concatenatedBlob = new Blob(arrayOfBlobs);
var src = ...

Cependant, cela finira toujours par manquer de données. Comme les blobs sont immuables, je ne sais pas comment ajouter des données à mesure qu'elles sont reçues.

Je suis certain que cela devrait être possible car YouTube et de nombreux autres services de streaming vidéo utilisent des URL Blob pour la lecture vidéo. Comment ils le font-ils?

4
stevendesu

Solution

Après quelques recherches importantes sur Google, j'ai réussi à trouver la pièce manquante au puzzle: MediaSource

Effectivement, le processus se déroule comme suit:

  1. Créez un MediaSource
  2. Créez une URL d'objet à partir de MediaSource
  3. Définissez le src de la vidéo sur l'URL de l'objet
  4. Sur l'événement sourceopen, créez un SourceBuffer
  5. Utilisez SourceBuffer.appendBuffer() pour ajouter tous vos morceaux à la vidéo

De cette façon, vous pouvez continuer à ajouter de nouveaux morceaux de vidéo sans modifier l'URL de l'objet.

Avertissements

  • L'objet SourceBuffer est très pointilleux sur les codecs. Ceux-ci doivent être déclarés et doivent être exacts, sinon cela ne fonctionnera pas
  • Vous ne pouvez ajouter qu'un seul blob de données vidéo à la SourceBuffer à la fois, et vous ne pouvez pas ajouter un deuxième blob tant que le premier n'a pas terminé (de manière asynchrone) le traitement
  • Si vous ajoutez trop de données à SourceBuffer sans appeler .remove(), vous finirez par manquer de RAM et la vidéo s'arrêtera de jouer. cette limite d'environ 1 heure sur mon ordinateur portable

Exemple de code

Selon votre configuration, certaines d'entre elles peuvent être inutiles (en particulier la partie où nous construisons une file d'attente de données vidéo avant d'avoir un SourceBuffer puis ajoutons lentement notre file d'attente à l'aide de updateend). Si vous pouvez attendre que le SourceBuffer ait été créé pour commencer à récupérer les données vidéo, votre code sera beaucoup plus agréable.

<html>
<head>
</head>
<body>
    <video id="video"></video>
    <script>
        // As before, I'm regularly grabbing blobs of video data
        // The implementation of "nextChunk" could be various things:
        //   - reading from a MediaRecorder
        //   - reading from an XMLHttpRequest
        //   - reading from a local webcam
        //   - generating the files on the fly in JavaScript
        //   - etc
        var arrayOfBlobs = [];
        setInterval(function() {
            arrayOfBlobs.append(nextChunk());
            // NEW: Try to flush our queue of video data to the video element
            appendToSourceBuffer();
        }, 1000);

        // 1. Create a `MediaSource`
        var mediaSource = new MediaSource();

        // 2. Create an object URL from the `MediaSource`
        var url = URL.createObjectURL(mediaSource);

        // 3. Set the video's `src` to the object URL
        var video = document.getElementById("video");
        video.src = url;

        // 4. On the `sourceopen` event, create a `SourceBuffer`
        var sourceBuffer = null;
        mediaSource.addEventListener("sourceopen", function()
        {
            // NOTE: Browsers are VERY picky about the codec being EXACTLY
            // right here. Make sure you know which codecs you're using!
            sourceBuffer = mediaSource.addSourceBuffer("video/webm; codecs=\"opus,vp8\"");

            // If we requested any video data prior to setting up the SourceBuffer,
            // we want to make sure we only append one blob at a time
            sourceBuffer.addEventListener("updateend", appendToSourceBuffer);
        });

        // 5. Use `SourceBuffer.appendBuffer()` to add all of your chunks to the video
        function appendToSourceBuffer()
        {
            if (
                mediaSource.readyState === "open" &&
                sourceBuffer &&
                sourceBuffer.updating === false
            )
            {
                sourceBuffer.appendBuffer(arrayOfBlobs.shift());
            }

            // Limit the total buffer size to 20 minutes
            // This way we don't run out of RAM
            if (
                video.buffered.length &&
                video.buffered.end(0) - video.buffered.start(0) > 1200
            )
            {
                sourceBuffer.remove(0, video.buffered.end(0) - 1200)
            }
        }
    </script>
</body>
</html>

En prime, cela vous donne automatiquement DVR fonctionnalité pour les flux en direct, car vous conservez 20 minutes de données vidéo dans votre tampon (vous pouvez rechercher en utilisant simplement video.currentTime = ...)

18
stevendesu