基于Express框架+obs/ffmpeg进行云课堂直播(node-media-server+flv.js)

云课堂中,直播功能非常重要,可以很大程度上弥补单纯web端功能的不足,尤其是串流电脑屏幕时,可以将使用任何教学软件的过程串流给客户端,效果拔群。

推流方式选择

一、Web端推流

Web端推流在现在使用得很少,一般基于rtmp-streamer模块进行,同时需要swf插件。并且现在各大浏览器正在陆续抛弃FlashPlayer,因此不采用这种方式进行推流。

二、利用ffmpeg推流

ffmpeg是非常常用的开源的视频音频处理程序,在很多软件中有使用。如果在js使用,可以利用封装了ffmpeg命令行调用的fluent-ffmpeg模块进行调用。然而ffmpeg在多个平台需要不同的本地客户端,并且配置环境变量,或者临时写入路径,因此主要有两个方法:

  1. 随网站载入,下载ffmpeg执行文件,临时存储。但由于平台多样、ffmpeg程序体积大的问题并不合适。
  2. 在网站上预先提供ffmpeg下载和安装,但由于更新版本、构建等问题,加以该云课堂需要脱离外部网络运行,这个方法的使用也有缺陷。

在章节最后一节,尝试了将ffmpeg打包成用户界面更舒适的应用,用于显示器推流。

另外,直播推流对系统性能的开销很大,出于对优化和实际应用场景等方面考虑,既然需要本地预先安装推流客户端,不如选择更加成熟的软件。

三、利用OBS(Open Broadcaster Software)推流

OBS是一个免费、开源的视频录制、推流软件,其优化力度大、对系统性能占用小、支持复杂灵活的场景,并且具有直接推流至某一服务器的完善功能,因此使用OBS对课堂直播进行推流。

OBS的使用

obs的使用非常灵活,可以捕捉各种各样的来源,包括屏幕捕获、窗口捕获、媒体源、浏览器、图片、文字等,并且可以实时查看当前的捕获情况。

obs的推流需要一个服务器地址,将视频流发送至服务端,因此服务端需要一个服务来提供地址获取视频

服务端监听:node-media-server模块

首先安装模块

sudo npm install node-media-server –save

然后创建一个调用模块的脚本

// myscripts\stream.js
const NodeMediaServer = require('node-media-server');
 
const config = {
  rtmp: {
    port: 1935,
    chunk_size: 60000,
    gop_cache: true,
    ping: 60,
    ping_timeout: 30
  },
  http: {
    port: 8000,
    allow_origin: '*'
  }
};

function run() {
    console.log('开始监听推流');
    var nms = new NodeMediaServer(config);
    nms.run();
}

module.exports.run = run;

因为框架中运行服务器的是www文件,因此把启动对串流的监听也放在www中

// bin\www
// 启动推流监听
var stream = require('../myscripts/stream').run;
stream();

客户端拉流:flv.js

flv.js是一个开源的脚本,可以不借助flash在H5的video标签中播放flv。

<head>
    <script src="https://cdn.bootcss.com/flv.js/1.4.0/flv.min.js"></script>
    <!-- ...... -->
</head>
<body>
    <video id="videoElement" width="1280px" controls></video>
</body>

紧接着对这个vedio添加来源

// stream.js
$(document).ready(() => {
    if (flvjs.isSupported()) {
        var url = 'http://' + document.domain + ':8000/live/test.flv';
        var videoElement = document.getElementById('videoElement');
        var flvPlayer = flvjs.createPlayer({
            type: 'flv',
            url: url
        });
        flvPlayer.attachMediaElement(videoElement);
        flvPlayer.load();
        flvPlayer.play();
    }
});

注意url中的“test”便是直播名,同时在串流时作为串流的秘钥使用

启动直播功能

启动express框架,便能看见后台的消息:

开始监听推流
2020-6-20 0:05:50 13172 [INFO] Node Media Server v2.1.9
2020-6-20 0:05:50 13172 [INFO] Node Media Rtmp Server started on port: 1935
2020-6-20 0:05:50 13172 [INFO] Node Media Http Server started on port: 8000
2020-6-20 0:05:50 13172 [INFO] Node Media WebSocket Server started on port: 8000

对于OBS串流,这个时候需要在obs里填写串流信息。此时在本机上进行测试,域名填写localhost。

服务器:rtmp://localhost/live
串流秘钥:test

在obs中开始串流,便能在网站上查看到直播了。

优化开播设置

直接在课程界面添加服务器和串流秘钥即可

$(document).ready(() => {
    //请求所有需要的数据
    $.get("/users/process_getdata", (data) => {
        var name = data.username;
        var classname = data.classname;
        //修改称呼
        let text = $("#welcome").text();
        $("#welcome").text(text + name);
        let key = "stream-key-" + classname;
        $('#stream-url').val('rtmp://' + document.domain + '/live');
        // 串流服务器url
        $('#stream-key').val(key);
        // 串流秘钥
    }, "json");
})

非常简约

附:利用ffmpeg推流

ffmpeg功能非常强大,同时只需要命令行就可以进行推流。为了提高用户体验,我们将操作打包成一个nodejs应用。

首先为了使用ffmpeg推流,需要一个如下的指令启动ffmpeg,并且将屏幕流推送到服务器:

ffmpeg -f gdigrab -i desktop -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -f flv rtmp://127.0.0.1/live/stream-key-0.flv

当然可以使用js直接执行这个命令。当然也可以使用fluent-ffmpeg模块,它将ffmpeg命令行以js形式转译,便于执行命令,调用ffmpeg的方式如下:

const ffmpeg = require('fluent-ffmpeg');
const ffmpegPath = __dirname+"\\..\\libs\\ffmpeg.exe";
var command = null;
function run(outputPath) {
    command = ffmpeg()
    .setFfmpegPath(ffmpegPath)
    .input('desktop')
    .inputFormat('gdigrab')
    .addOptions([
        '-vcodec libx264',
        '-preset ultrafast',
        '-acodec libmp3lame',
        '-pix_fmt yuv422p'
    ])
    .format('flv')
    .output(outputPath, {
        end: true
    })
    .on('start', function (commandLine) {
        console.log('[' + new Date() + '] Vedio is Pushing !');
        console.log('commandLine: ' + commandLine);
    })
    .on('error', function (err, stdout, stderr) {
        console.log('error: ' + err.message);
    })
    .on('end', function () {
        console.log('[' + new Date() + '] Vedio Pushing is Finished !');
    });
    command.run();
}

function stop() {
    command.kill();
}

直接运行脚本里的run函数,是可以成功推流的。然后编写一个简单的html页面:

为了将node程序打包,这里需要使用electron和打包工具asar,并初始化项目

cnpm install electron -g
cnpm install electron-prebuilt -g
cnpm install electron-packager -g
cnpm install electron-builder -g
cnpm install asar -g
npm init

初始化完项目后,只需要在index.html中编写主窗体,然后在js中加上对fluent-ffmpeg的引用,并且可以直接在本地访问ffmpeg.exe。引入非原生模块需要重新构建模块:

cnpm install electron-rebuild –save-dev
.\node_modules.bin\electron-rebuild.cmd

在页面脚本中使用vue,并且调用run和stop两个函数

$(document).ready(() => {
    var data = {
        isStreaming: false,
        btnMessage: '开始串流',
        url: '',
        key: ''
    }

    var buttonApp = new Vue({
        el: "#btn",
        data: data
    })
    
    var inputApp = new Vue({
        el: "#inputs",
        data: data
    })
    
    $('#btn').on('click', () => {
        if (data.isStreaming == false) {
            if (!data.url) {
                alert('请输入服务器!');
            } else if (!data.key) {
                alert('请输入串流秘钥!');
            } else {
                data.isStreaming = true;
                var path;
                if (data.url[data.url.length - 1] != "/") {
                    path = data.url + "/" + data.key + '.flv';
                } else {
                    path = data.url + data.key + '.flv';
                }
                data.btnMessage = "串流中:" + path;
                const ipc = require('electron').ipcRenderer; 
                run(path);
            }
        } else {
            data.isStreaming = false;
            data.btnMessage = "开始串流";
            stop();
        }
    })
})

由于构件electron需要的部分github资源由于网络原因无法下载,这里使用wpf,用类似的逻辑,命令行启动ffmpeg并推流。

可以看见使用ffmpeg也可以正常地推流桌面,但是同样需要下载客户端,相比较于使用obs,这个方法需要大量的功能改进,例如获得摄像头名称并直播摄像头、电脑音频和麦克风音频选择、桌面推流来源选择等。