npm 安装
npm install node-media-server --save
配置:config/config.default.js
// 流媒体配置
config.mediaServer = {
rtmp: {
port: 23480,
chunk_size: 60000,
gop_cache: true,
ping: 30,
ping_timeout: 60
},
http: {
port: 23481,
allow_origin: '*'
},
auth: {
play: true,
publish: true,
secret: 'nodemedia2017privatekey',
},
};
启动媒流体服务在egg根目录下创建app.js
// 引入
const NodeMediaServer = require('node-media-server');
class AppBootHook {
constructor(app) {
this.app = app;
}
configWillLoad() {
// 此时 config 文件已经被读取并合并,但是还并未生效
// 这是应用层修改配置的最后时机
// 注意:此函数只支持同步调用
// 例如:参数中的密码是加密的,在此处进行解密
}
async didLoad() {
// 所有的配置已经加载完毕
// 可以用来加载应用自定义的文件,启动自定义的服务
if(!this.app.nms){
this.app.nms = new NodeMediaServer(this.app.config.mediaServer)
this.app.nms.run();
this.app.nms.on('preConnect', (id, args) => {
console.log('[NodeEvent on preConnect]', `id=${id} args=${JSON.stringify(args)}`);
// let session = nms.getSession(id);
// session.reject();
});
this.app.nms.on('postConnect', (id, args) => {
console.log('[NodeEvent on postConnect]', `id=${id} args=${JSON.stringify(args)}`);
});
this.app.nms.on('doneConnect', (id, args) => {
console.log('[NodeEvent on doneConnect]', `id=${id} args=${JSON.stringify(args)}`);
});
this.app.nms.on('prePublish', (id, StreamPath, args) => {
console.log('[NodeEvent on prePublish]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`);
// let session = nms.getSession(id);
// session.reject();
});
this.app.nms.on('postPublish', (id, StreamPath, args) => {
console.log('[NodeEvent on postPublish]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`);
});
this.app.nms.on('donePublish', (id, StreamPath, args) => {
console.log('[NodeEvent on donePublish]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`);
});
this.app.nms.on('prePlay', (id, StreamPath, args) => {
console.log('[NodeEvent on prePlay]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`);
// let session = nms.getSession(id);
// session.reject();
});
this.app.nms.on('postPlay', (id, StreamPath, args) => {
console.log('[NodeEvent on postPlay]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`);
});
this.app.nms.on('donePlay', (id, StreamPath, args) => {
console.log('[NodeEvent on donePlay]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`);
});
}
}
async willReady() {
// 所有的插件都已启动完毕,但是应用整体还未 ready
// 可以做一些数据初始化等操作,这些操作成功才会启动应用
// 例如:从数据库加载数据到内存缓存
// this.app.cacheData = await this.app.model.query(QUERY_CACHE_SQL);
}
async didReady() {
// 应用已经启动完毕
// const ctx = await this.app.createAnonymousContext();
// await ctx.service.Biz.request();
}
async serverDidReady() {
// http / https server 已启动,开始接受外部请求
// 此时可以从 app.server 拿到 server 的实例
// this.app.server.on('timeout', socket => {
// // handle socket timeout
// });
}
}
module.exports = AppBootHook;
客户端推流拉流协议 房间号和签名
//客户 拉流前缀
livePlayBaseUrl:"http://192.168.43.31:23481/live/gSS1lNdeRPQkrWAfgLQb.flv?sign=1608272255-d583dd4cbb1e23925bbe3e2301613c34",
// 主播 推流前缀
livePushBaseUrl:"rtmp://127.0.0.1:23480/live/gSS1lNdeRPQkrWAfgLQb?sign=1608272255-d583dd4cbb1e23925bbe3e2301613c34",
推流调试工具
下载OBS Studio
https://obsproject.com/zh-cn/download
安装必要的组件
https://obsproject.com/visual-studio-2019-runtimes-64-bit
找到菜单栏:文件——》设置——》推流
<view >
<video src="http://192.168.43.31:23481/live/keQ7pOdpu6Qfz4u1zeb5.flv?sign=1664599228-5f2457d3030f9563598f31097a39d247" autoplay></video>
</view>
安装加密md5
npm i md5 --save
控制器:app/controller/api/live.js
'use strict';
// 引入模块
const md5 = require('md5');
const Controller = require('egg').Controller;
class LiveController extends Controller {
// 创建直播间
async save() {
let { ctx, app } = this;
let user_id = ctx.authUser.id;
// 参数验证
ctx.validate({
title: {
type: 'string',
required: true,
desc: '直播间标题'
},
cover: {
type: 'string',
required: true,
desc: '直播间封面'
}
});
let {
title, cover
} = ctx.request.body;
// 生成唯一key 生成多少位的就输入多少数字
let key = ctx.randomString(20);
// 直接创建
let res = await app.model.Live.create({
title,
cover,
user_id,
key,
});
// 生成签名
let sign = this.sign(key)
ctx.apiSuccess({
data: res,
sign
});
}
// 生成签名
sign(key) {
let { ctx, app } = this;
const secret = app.config.mediaServer.auth.secret
const expire = parseInt((Date.now() + 100000000) / 1000);//100000000失效时间
const hashValue = md5(`/live/${key}-${expire}-${secret}`);//加密
return `${expire}-${hashValue}`
}
}
module.exports = LiveController;
扩展:app/extend/context.js
randomString(length) {
const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
var result = '';
for (var i = length; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)];
return result;
},
路由:app/router.js
// 创建直播间
router.post('/api/live/create', controller.api.live.save);
表结构
'use strict'; module.exports = { up: (queryInterface, Sequelize) => { const { INTEGER, STRING, DATE, ENUM, TEXT } = Sequelize; return queryInterface.createTable('live', { id: { type: INTEGER(20), primaryKey: true, autoIncrement: true }, title: { type: STRING(100), allowNull: false, defaultValue: '', comment: '直播间标题' }, cover: { type: STRING, allowNull: true, defaultValue: '', comment: '直播间封面' }, user_id: { type: INTEGER, allowNull: false, defaultValue: 0, comment: '用户id', references: { model: 'user', key: 'id' }, onDelete: 'cascade', onUpdate: 'restrict', // 更新时操作 }, look_count: { type: INTEGER, allowNull: false, defaultValue: 0, comment: '总观看人数' }, coin: { type: INTEGER, allowNull: false, defaultValue: 0, comment: '总金币' }, key: { type: STRING, allowNull: false, defaultValue: '', comment: '唯一标识', }, status: { type: INTEGER(1), allowNull: false, defaultValue: 0, comment: '直播间状态 0未开播 1直播中 2暂停直播 3直播结束' }, created_time: DATE, updated_time: DATE, }); }, down: (queryInterface, Sequelize) => { return queryInterface.dropTable('live'); } };
'运行
安装flv.js
yarn add flv.js
引用
<template>
<div class="video">
<video
id="vPull"
controls
autoplay
muted
width="100%"
height="100%">
</video>
</div>
</template>
<script>
import flv from "flv.js";
export default {
name: "",
data() {
return {
player: null,
};
},
methods: {
play(urls) {
let video = document.getElementById("vPull"); //定义播放路径
if (flv.isSupported()) {
this.player = flv.createPlayer(
{
type: "flv",
isLive: true,
url: urls,
},
{
enableWorker: false, //不启用分离线程
enableStashBuffer: false, //关闭IO隐藏缓冲区
isLive: true,
lazyLoad: false,
}
);
} else {
console.log("不支持的格式");
return;
}
this.player.attachMediaElement(video);
this.player.load();
this.player.play();
},
destruction() {
//销毁对象
if (this.player) {
this.player.pause();
this.player.destroy();
this.player = null;
}
},
},
mounted() {
this.play('http://127.0.0.1:23481/live/keQ7pOdpu6Qfz4u1zeb5.flv?sign=1664599228-5f2457d3030f9563598f31097a39d247');
},
};
</script>