Цель
Я пытаюсь создать элементарный «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 работать правильно.Чего мне не хватает?