Как мне воспроизвести поток HLS, когда файл playlist.m3u8 постоянно обновляется?

#javascript #node.js #ffmpeg #vlc #http-live-streaming

#javascript #node.js #ffmpeg #vlc #http-прямая трансляция

Вопрос:

Я использую MediaRecorder для записи фрагментов моего видео в реальном времени в формате webm из MediaStream и преобразования этих фрагментов в файлы .ts на сервере с помощью ffmpeg, а затем обновляю свой файл playlist.m3u8 с помощью этого кода:

 function generateM3u8Playlist(fileDataArr, playlistFp, isLive, cb) {
    var durations = fileDataArr.map(function(fd) {
        return fd.duration;
    });
    var maxT = maxOfArr(durations);

    var meta = [
        '#EXTM3U',
        '#EXT-X-VERSION:3',
        '#EXT-X-MEDIA-SEQUENCE:0',
        '#EXT-X-ALLOW-CACHE:YES',
        '#EXT-X-TARGETDURATION:'   Math.ceil(maxT),
    ];

    fileDataArr.forEach(function(fd) {
        meta.push('#EXTINF:'   fd.duration.toFixed(2)   ',');
        meta.push(fd.fileName2);
    });

    if (!isLive) {
        meta.push('#EXT-X-ENDLIST');
    }

    meta.push('');
    meta = meta.join('n');

    fs.writeFile(playlistFp, meta, cb);
}
 

Здесь fileDataArr содержится информация для всех созданных фрагментов.

После этого я использую этот код для создания сервера hls :

 var runStreamServer = (function(streamFolder) {
    var executed = false;
    return function(streamFolder) {
        if (!executed) {
            executed = true;
            var HLSServer = require('hls-server')
            var http = require('http')

            var server = http.createServer()
            var hls = new HLSServer(server, {
                path: '/stream', // Base URI to output HLS streams
                dir: 'C:\Users\Work\Desktop\live-stream\webcam2hls\videos\'   streamFolder // Directory that input files are stored
            })
            console.log("We are going to stream from folder:"   streamFolder);
            server.listen(8000);
            console.log('Server Listening on Port 8000');
        }
    };
})();
 

Проблема в том, что если я перестану создавать новые фрагменты, а затем использую ссылку на сервер hls:
http://localhost:8000/stream/playlist.m3u8 затем видео воспроизводится в VLC, но если я пытаюсь воспроизвести во время записи, он продолжает загружать файл, но не воспроизводится. Я хочу, чтобы он воспроизводился во время создания новых фрагментов и обновления playlist.m3u8. Особенность generateM3u8Playlist функции заключается в том, что она добавляется '#EXT-X-ENDLIST' в файл списка воспроизведения после того, как я остановил запись.
Программное обеспечение все еще находится в производстве, поэтому его код немного запутан. Спасибо за любые ответы.

Клиентская сторона, которая генерирует большие двоичные объекты, выглядит следующим образом:

 var mediaConstraints = {
            video: true,
            audio:true
        };
navigator.getUserMedia(mediaConstraints, onMediaSuccess, onMediaError);
function onMediaSuccess(stream) {
            console.log('will start capturing and sending '   (DT / 1000)   's videos when you press start');
            var mediaRecorder = new MediaStreamRecorder(stream);

            mediaRecorder.mimeType = 'video/webm';

            mediaRecorder.ondataavailable = function(blob) {
                var count2 = zeroPad(count, 5);
                // here count2 just creates a blob number 
                console.log('sending chunk '   name   ' #'   count2   '...');
                send('/chunk/'   name   '/'   count2   (stopped ? '/finish' : ''), blob);
                  count;
            };
        }
// Here we have the send function which sends our blob to server:
        function send(url, blob) {
            var xhr = new XMLHttpRequest();
            xhr.open('POST', url, true);

            xhr.responseType = 'text/plain';
            xhr.setRequestHeader('Content-Type', 'video/webm');
            //xhr.setRequestHeader("Content-Length", blob.length);

            xhr.onload = function(e) {
                if (this.status === 200) {
                    console.log(this.response);
                }
            };
            xhr.send(blob);
        }
 

Код, который получает запрос XHR, выглядит следующим образом:

 var parts = u.split('/');
        var prefix = parts[2];
        var num = parts[3];
        var isFirst = false;
        var isLast = !!parts[4];

        if ((/^0 $/).test(num)) {
            var path = require('path');
            shell.mkdir(path.join(__dirname, 'videos', prefix));
            isFirst = true;
        }

        var fp = 'videos/'   prefix   '/'   num   '.webm';
        var msg = 'got '   fp;
        console.log(msg);
        console.log('isFirst:%s, isLast:%s', isFirst, isLast);

        var stream = fs.createWriteStream(fp, { encoding: 'binary' });
        /*stream.on('end', function() {
            respond(res, ['text/plain', msg]);
        });*/

        //req.setEncoding('binary');

        req.pipe(stream);
        req.on('end', function() {
            respond(res, ['text/plain', msg]);

            if (!LIVE) { return; }

            var duration = 20;
            var fd = {
                fileName: num   '.webm',
                filePath: fp,
                duration: duration
            };
            var fileDataArr;
            if (isFirst) {
                fileDataArr = [];
                fileDataArrs[prefix] = fileDataArr;
            } else {
                var fileDataArr = fileDataArrs[prefix];
            }
            try {
                fileDataArr.push(fd);
            } catch (err) {
                fileDataArr = [];
                console.log(err.message);
            }
            videoUtils.computeStartTimes(fileDataArr);

            videoUtils.webm2Mpegts(fd, function(err, mpegtsFp) {
                if (err) { return console.error(err); }
                console.log('created %s', mpegtsFp);

                var playlistFp = 'videos/'   prefix   '/playlist.m3u8';

                var fileDataArr2 = (isLast ? fileDataArr : lastN(fileDataArr, PREV_ITEMS_IN_LIVE));

                var action = (isFirst ? 'created' : (isLast ? 'finished' : 'updated'));

                videoUtils.generateM3u8Playlist(fileDataArr2, playlistFp, !isLast, function(err) {
                    console.log('playlist %s %s', playlistFp, (err ? err.toString() : action));
                });
            });


            runStreamServer(prefix);
        }
 

Комментарии:

1. Код для создания списка воспроизведения выглядит нормально. Фрагменты только добавляются fileDataArr или также удаляются (т. Е. Это Скользящее живое окно или растущее)?

Ответ №1:

Вы не показываете нам, как вы используете MediaRecorder для генерации своих «фрагментов» данных. Используете ли вы его ondataavailable событие для этой цели?

Если да, пожалуйста, имейте это в виду: вы должны объединить все фрагменты, переданные вам, ondataavailable чтобы получить действительный .поток данных webm (или .matroska).

Вы не можете просто сохранить произвольный фрагмент данных в медиафайле и ожидать, что он будет воспроизводиться. Даже ffmpeg нуждается в том, чтобы все ваши фрагменты передавались в него для генерации корректного вывода. Это потому, что первые несколько фрагментов содержат обязательные .сегмент инициализации webm, а другие фрагменты — нет.

Комментарии:

1. Привет, Джонс, я добавил вспомогательный код, и вы правы, я использую событие ondataavailable для создания больших двоичных объектов / фрагментов. Решение казалось невыполнимым, поэтому я его больше не использую, однако, если есть какой-либо обходной путь, пожалуйста, предоставьте его с кодом.

2. Я преуспел в подобном проекте, открыв websocket и используя его для отправки каждого фрагмента на сервер. Мое приложение настолько отличается от вашего, что у меня нет никакого полезного исходного кода для вас, извините.

3. В настоящее время я использую websockets и kurento и успешно создал поток rtmp, но я все еще ищу решение для создания потока HLS. Любые советы будут оценены.

4. @AdnanAhmed Я пытаюсь решить аналогичную проблему с загрузкой фрагментов с медиаплеера на сервер и потоковой передачей их на веб-клиенте с использованием библиотеки HLS.

5. Какой подход вы использовали в конечном итоге? Можете ли вы поделиться своими идеями и кодом?