直播作为近几年比较火的方向,吸引了像我这样的小白不断在音视频的边缘不断试探。“宇宙的尽头是直播”,不论是娱乐直播还是直播带货,都已经成为现在人们生活中不可获取的一部分。
今天我们就来实现一个直播的 demo,我们来看一下整个直播的过程:
SRS 流媒体服务器
为了方便我推荐使用云 SRS,可以基于宝塔一键部署,如果没有云服务器可以根据官方 github 自行编译(难度较大,不推荐新手选择此方式)。
在宝塔的软件商店中搜索 SRS(免费的,如果显示购买直接点击购买即可),点击安装
插件包安装完成之后打开,安装云 SRS,这个过程比较耗时,耐心等待……
安装完成之后需要等待几分钟,云 SRS 内部的依赖启动需要一定的时间,等启动完成之后打开管理面板,设置管理员密码之后,进入系统设置菜单,找到 OpenAPI 选项,我们的后端服务需要根据这两个接口来获取串流秘钥。
后端服务
这边的业务服务器我们使用 Node 来实现,在这个过程中负责客户端鉴权以及获取串流秘钥,这里我选用 Midway(全凭个人喜好,可以选择任何其他的框架 Express、Koa、nest 等)。
我们这里提供两个接口:
- 主播获取推流地址和串流秘钥;
- 获取房间信息(最主要是 streamKey)
获取串流秘钥
使用 ApiSecret 从云 SRS 的 OpenAPI来获取秘钥,过程如下
以下是代码实现(忽略实体、配置等部分,只展示流程)
@Controller('/anchor')
export class AnchorController {
@Inject()
ctx: Context;
@Inject()
roomService: RoomService;
@Inject()
anchorService: AnchorService;
@Inject('short-uuid')
short;
@Config('srs')
srs; // 配置了服务器 IP 和 ApiSecret
// 判断主播身份
validateAnchor(user: UserModel) {
if (!user || user.role !== UserRole.ANCHOR)
// 如果没有用户(可能被冻结或者注销)或者没有权限抛出异常
throw new CustomError(
INSUFFICIENT_PERMISSIONS.CODE_1,
INSUFFICIENT_PERMISSIONS.MSG_1
);
}
// 生成向直播间串流秘钥(限制主播身份)
@Post('/streamKey')
async createStreamKey() {
const userId = this.ctx.state.user.uid;
const user = await this.anchorService.getAnchorWithRoom(userId);
// 身份判断
this.validateAnchor(user);
// 将主播关联的直播间进行串流秘钥重置
const key = this.short.generate();
const room = user.liveRoom;
room.streamKey = key;
// 保存新的串流秘钥
await this.roomService.saveRoom(room);
// 获取 token (makeHttpRequest 相当于 fetch, 做请求用)
const token = await makeHttpRequest(
'http://' + this.srs.url + '/terraform/v1/mgmt/secret/token',
{
method: 'POST',
dataType: 'json',
contentType: 'json',
data: {
apiSecret: this.srs.apiSecret,
},
}
);
// 使用 token 获取 secret
const srsSecret = await makeHttpRequest(
'http://' + this.srs.url + '/terraform/v1/hooks/srs/secret/query',
{
method: 'POST',
dataType: 'json',
contentType: 'json',
data: {
token: token.data.data.token,
},
}
);
return {
url: 'rtmp://' + this.srs.url + '/live/',
key,
secret: srsSecret.data.data.publish,
};
}
}
主播每次获取串流密钥会进行重置,防止泄露。
获取直播间信息
获取直播间信息相对简单,只需要查询数据返回即可
@Controller('/live')
export class LiveController {
@Inject()
ctx: Context;
@Inject()
roomService: RoomService;
// 获取直播间相关数据
@Get('/room/:roomId')
async getRoomById(@Param('roomId') room: string) {
const res: any = await this.roomService.getRoomById(room);
if (!res) {
throw new CustomError(ROOM_NOT_FOUND.CODE, ROOM_NOT_FOUND.MSG);
}
// 粉丝数量取值
res.fans = res.fans.length;
return res;
}
}
xgplayer(西瓜播放器)
播放器我当初对比过 MuiPlayer 和 xgPlayer,最终选择了西瓜(背靠大厂、集成度更高)。
我们使用 http-flv 的形式来进行推拉流,所以我们需要同时安装 xgplayer 和 xgplayer-flv
$ pnpm add xgplayer xgplayer-flv
前端框架层面我使用了 Vue3,代码如下(只是播放器相关部分,不是最终成品的代码)
<template>
<div class="room-video" id="mse"></div>
</template>
<script setup lang="ts">
import "xgplayer";
import FlvPlayer from "xgplayer-flv";
// 播放器
const playerRef = ref<FlvPlayer>();
onMounted(async () => {
room.info = await getRoomInfo(props.roomId);
if (room.info.isLiving) {
initPlayer();
}
});
// 初始化播放器
function initPlayer() {
if (playerRef.value) {
playerRef.value.destroy();
}
playerRef.value = new FlvPlayer({
id: "mse", // 容器元素 ID
url: import.meta.env.VITE_VIDEO_URL + room.info.streamKey + ".flv", // 拉流地址
isLive: true, // 直播模式
autoplay: true, // 自动播放
height: 500, // 高
width: 888, // 宽
pip: true, // 画中画
lang: "zh-cn",
ignores: ["replay"], // 忽略内置控件
danmu: {
// danmu 配置
comments: [],
},
});
}
// socket 收到消息之后通过 player 实例调用 instace.danmu.sendComment 即可动态添加弹幕
</script>
成品展示
主播面板获取推流码,复制到 OBS 中开始推流
观众进入直播间获取直播流进行播放