【问题标题】:How do i play an HLS stream when playlist.m3u8 file is constantly being updated?当 playlist.m3u8 文件不断更新时,如何播放 HLS 流?
【发布时间】:2021-03-30 13:01:48
【问题描述】:

我正在使用 MediaRecorder 从 MediaStream 以 webm 格式记录我的实时视频块,并使用 ffmpeg 将这些块转换为服务器上的 .ts 文件,然后使用以下代码更新我的 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' 添加到播放列表文件中。 该软件仍在生产中,因此代码有点混乱。感谢您的任何回答。

生成blob的客户端如下:

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);
        }

【问题讨论】:

  • 生成播放列表的代码看起来不错。片段是否只添加到fileDataArr 或也被删除(即它是滑动的实时窗口还是正在增长)?

标签: javascript node.js ffmpeg vlc http-live-streaming


【解决方案1】:

您没有向我们展示您如何使用MediaRecorder 来生成您的数据“块”。您是否为此使用its ondataavailable event

如果是这样,请记住这一点:您必须将ondataavailable 交给您的所有块连接起来才能获得有效的 .webm(或 .matroska)数据流。

您不能只在媒体文件中存储任意数据块并期望它能够播放。甚至 ffmpeg 也需要将所有块流式传输到它以生成有效的输出。那是因为前几个块包含mandatory .webm initialization segment,而其他块不包含。

【讨论】:

  • 嗨琼斯,我已经添加了支持代码,你是对的,我正在使用 ondataavailable 事件来创建 blob/块。该解决方案似乎不可行,因此我不再使用它,但是如果有任何解决方法,请提供代码。
  • 我通过打开一个 websocket 并使用它将每个块发送到服务器来成功地完成了一个类似的项目。我的应用程序与您的应用程序完全不同,因此我没有任何有用的源代码给您,抱歉。
  • 我目前正在使用 websockets 和 kurento 并成功创建了一个 rtmp 流,但我仍在寻找创建 HLS 流的解决方案。任何提示将不胜感激。
  • @AdnanAhmed 我正在尝试解决将块从媒体记录器上传到服务器并使用 HLS 库在 Web 客户端上流式传输的类似问题。
  • 你最后用了哪种方法?你能分享你的见解和代码吗?
猜你喜欢
  • 2018-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-29
相关资源
最近更新 更多