HTML5 DVR не работает - SourceBuffer удален из родительского элемента - PullRequest
0 голосов
/ 23 мая 2018

Цель

Я пытаюсь создать элементарный «DVR» для видеоэлемента HTML5, используя MediaRecorder, MediaSource и SourceBuffer.На данный момент это просто доказательство концепции.Однако, поскольку многие проекты, такие как HLS.js, используют преимущества видеоэлемента HTML5, я считаю, что это будет иметь широкое распространение.

Код

Вот суть моего кода:

<html>
<head>
</head>
<body>
    <video id="src-video" src="http://localhost:8080/video/source.mp4" autoplay></video>
    <video id="dvr-video"></video>
    <input id="seekbar" type="range" min="-120" max="0" value="0" />
    <script>
    var mr; // MediaRecorder
    var ms = new MediaSource();
    var srcBuf; // SourceBuffer
    var srcUrl = URL.createObjectURL(ms);
    var srcVid = document.getElementById("src-video");
    var dvrVid = document.getElementById("dvr-video");
    var dvrData = []; // array of ArrayBuffer
    var queue = [];

    ms.addEventListener("sourceopen", sourceOpen);
    srcVid.addEventListener("playing", setupMediaRecorder);
    dvrVid.src = srcUrl;

    var seekBar = document.getElementById("seekbar");
    seekBar.addEventListener("change", function(e) {
        // Destroy the old media source and make a new one
        URL.revokeObjectURL(srcUrl);
        srcBuf = null;

        ms = new MediaSource();
        ms.addEventListener("sourceopen", sourceOpen);
        srcUrl = URL.createObjectURL(ms);

        body.removeChild(dvr);
        dvr = document.createElement("video");
        body.insertBefore(dvr, seekBar);

        dvr.src = srcUrl;
    });

    function sourceOpen()
    {
        // Create the source buffer
        if (!srcBuf)
        {
            srcBuf = src.addSourceBuffer('video/webm; codecs="opus,vp8"');
            srcBuf.mode = "sequence";
        }

        srcBuf.addEventListener('updateend', function() {
            if ( queue.length ) {
                srcBuf.appendBuffer(queue.shift());
            } else {
                dvr.play();
            }
        }, false);

        // Add all fragments in cache
        var start = dvrData.length + parseInt(seekBar.value);
        queue = [];
        for( var i = start; i < dvrData.length; i++ )
        {
            if (dvrData[i])
                queue.push(dvrData[i]);
        }
        if (queue.length)
            srcBuf.appendBuffer(queue.shift());
    }

    function setupMediaRecorder()
    {
        var stream = srcVid.captureStream()
        mr = new MediaRecorder(stream);
        mr.ondataavailable = function(e) {
            // Convert the Blob to an ArrayBuffer
            var fileReader = new FileReader();
            fileReader.onload = function() {
                // Append this ArrayBuffer to our playing video
                if (srcBuf)
                {
                    if (srcBuf.updating || queue.length)
                        queue.push(this.result);
                    else
                        srcBuf.appendBuffer(this.result);
                }
                // And to our historical array (for seeking purposes)
                dvrData.push(this.result);
                if (dvrData.length > 120) {
                    // Keep only 2 minutes of data
                    dvrData.splice(0, 1);
                }
            };
            fileReader.readAsArrayBuffer(e.data);
        };
        mr.start();
        // Record 1-second chunks
        setInterval(function() {
            mr.requestData();
        }, 1000);
    }
    </script>
</body>
</html>

Результат

При первой загрузке страницы начинает воспроизводиться элемент «живого» видео, а через 1 секунду начинает воспроизводиться элемент «dvr» - с задержкой в ​​1 секунду.Похоже, что сначала это работает.

В тот момент, когда я выполняю поиск, элемент dvr становится черным, и я получаю следующую ошибку в консоли (номер строки может не совпадать с приведенным выше кодом):

Uncaught DOMException: Failed to execute 'appendBuffer' on 'SourceBuffer': This SourceBuffer has been removed from the parent media source.
at SourceBuffer.<anonymous> (http://localhost:8080/video/dvr.html:80:13)

Глядя на chrome://media-internals для получения дополнительной информации, я вижу следующее для DVR-плеера:

+-------------+----------------+--------------------------------------------------------------------------------------------
| Timestamp   | Property       | Value
+-------------+----------------+--------------------------------------------------------------------------------------------
| 00:00:00 00 | origin_url     | http://localhost:8080/
| 00:00:00 00 | frame_url      | http://localhost:8080/video/dvr.html
| 00:00:00 00 | frame_title    |
| 00:00:00 00 | url            | blob:http://localhost:8080/cec4134a-4498-43c5-8321-3743761636ac
| 00:00:00 00 | info           | ChunkDemuxer: buffering by DTS
| 00:00:00 00 | pipeline_state | kStarting
| 00:00:00 03 | error          | Unexpected element ID 0xa3
| 00:00:00 03 | error          | Append: stream parsing failed. Data size=112300 append_window_start=0 append_window_end=inf
| 00:00:00 08 | pipeline_error | CHUNK_DEMUXER_ERROR_APPEND_FAILED
| 00:00:00 10 | pipeline_state | kStopping
| 00:00:00 10 | pipeline_state | kStopped
+-------------+----------------+--------------------------------------------------------------------------------------------

Unexpected element ID 0xa3 кажется виновником.Хотя по какой-то причине эта ошибка не возникла при первой загрузке страницы (я добавляю те же ArrayBuffer s к моим SourceBuffer, поэтому, если они не выдали эту ошибку, я не знаю, почему они 'перебрасываю его сейчас)

Глядя на 0xa3 относительно формата WEBM, звучит так, как будто это относится к "SimpleBlock" - https://chromium.googlesource.com/webm/libwebm/+/libwebm-1.0.0.26/webmids.hpp - я не знаю, почему этовыдаст ошибку?

Вещи, которые я пробовал

  • Поскольку для начала воспроизведения начального видео требуется некоторое время, я подумал, что при настройкеMediaSource.Я добавил задержку в 1 секунду после поиска перед созданием нового MediaSource, SourceBuffer и т. Д. Это не помогло
  • Я подумал, что могут быть ошибки, если первый загруженный блок не содержит ключевого кадра, поэтому я увеличилразмер фрагмента от 1 до 10 секунд
  • Я пробовал разные исходные файлы (MKV, MOV, MP4, RTMP-поток, поток WebRTC и т. д.)
  • Я пытался уничтожить весьvideo элемент и воссоздание его
  • В случае, если SourceBuffer каким-то образом модифицирует ArrayBuffer s при их воспроизведении, я попытался добавить копии вместо оригинальных объектов (srcBuf.appendBuffer(queue.shift().slice(0));)
  • Я пытался переключаться между режимами segment и sequence на SourceBuffer
  • В случае, если элемент DVR воспроизводился в момент, я создал SourceBuffer (до того, как у меня были данные) и, войдя в состояние «медиа завершено», я попытался приостановить работу элемента DVR

Пока что мне не повезло, чтобы заставить DVR работать правильно.Чего мне не хватает?

1 Ответ

0 голосов
/ 27 мая 2018

У меня есть

После долгих экспериментов я наконец-то понял проблему.

Файлы WEBM - это, по сути, двоичные XML-файлы.Схема выглядит примерно так:

<EBML>
    <EBMLVersion>...</EBMLVersion>
    <EBMLReadVersion>...</EBMLReadVersion>
    <EBMLMaxIDLength>...</EBMLMaxIDLength>
    <EBMLMaxSizeLength>...</EMBLMaxSizeLength>
    <DocType>...</DocType>
    <DocTypeVersion>...</DocTypeVersion>
    <DocTypeReadVersion>...</DocTypeReadVersion>
</EBML>
<Segment>
    <SeekHead>
        <Seek>...</Seek>
        <Seek>...</Seek>
        <Seek>...</Seek>
    </SeekHead>
    <Void></Void>
    <Info>...</Info>
    <Tracks>
        <TrackEntry>
            <Video>
                <Colour>
                    <MatrixCoeffciient>...</MatrixCoefficients>
                    ...
                </Colour>
            </Video>
            <Audio>
                ...
            </Audio>
        </TrackEntry>
    </Tracks>
    <Cues>
        <CuePoint>
            <CueTrackPositions>...</CueTrackPositions>
        </CuePoint>
    </Cues>
    <Cluster>
        <Timecode>...</Timecode>
        <SimpleBlock>...</SimpleBlock>
        <SimpleBlock>...</SimpleBlock>
        <SimpleBlock>...</SimpleBlock>
        <SimpleBlock>...</SimpleBlock>
        ...
    </Cluster>
    <Cluster>
        <Timecode>...</Timecode>
        <SimpleBlock>...</SimpleBlock>
        <SimpleBlock>...</SimpleBlock>
        <SimpleBlock>...</SimpleBlock>
        <SimpleBlock>...</SimpleBlock>
        ...
    </Cluster>
    ...
</Segment>

То, как я читал данные, первый блок содержал всю информацию заголовка (данные EBML, данные сегмента, треки, тайм-код и т. Д.) И все последующие фрагменты.это был просто поток тегов <Cluster> и <SimpleBlock> (с нечетным <Timecode> время от времени)

В конечном итоге мне нужно было создать элементарный демультиплексор для анализа файла EBML и извлеченияинформация заголовка.Затем всякий раз, когда я выполнял поиск, эта информация заголовка вставлялась в буфер перед любыми видеоданными.

Мои советы

Не беспокойтесь.MSE ужасно, и это был 72-часовой кошмар.Избавь себя от головной боли.

...