网站首页 > 技术文章 正文
概述
随着前端音视频技术的不断成熟越来越多的直播平台开始提供网页开播的直播方式,不需要再使用 OBS 或者各家的直播伴侣。
网页开播的视频流传输方式是 WebRTC,这一点没有啥难度,可以提升使用体验的点在于能否像 OBS 以及直播伴侣那样在视频流中添加文字、图片等图层以及自由的布局。
要实现这些功能需要进行混流,OBS 等都是推流之前进行的混流,直播的水印等基本都是云端实现的混流。我们网页开播选择在前端进行混流,为了了解前端混流的方案我查了很多资料。
经过几天的搜索,找到了比较靠谱的两种方式:
- 第三方依赖库 MultiStreamsMixer;
- 将要推流的视频内容布局在 canvas 中,使用 canvas.captureStream 捕获视频流进行推送。
下面我们将依次来实现这两种混流方式。
MultiStreamsMixer
MultiStreamsMixer 有两种方式引入,使用 CDN 引入
<script src="https://www.webrtc-experiment.com/MultiStreamsMixer.js"></script>
或者 npm 安装
$ npm install multistreamsmixer
唯一的缺陷在于没有 TS 的类型支持,如果使用在 Typescript 的项目中需要自己声明模块类型。
这里只是 demo,我们直接使用 CDN 导入,用最原生的 HTML+JavaScript 来实现。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>混流测试</title>
</head>
<body>
<video autoplay muted id="mixStream" width="800" height="600"></video>
<button onclick="captureScreen()">获取屏幕</button>
<button onclick="captureCamera()">获取摄像头</button>
<button onclick="mixStream()">混流</button>
<script src="https://www.webrtc-experiment.com/MultiStreamsMixer.js"></script>
<script src="./index.js" type="text/javascript"></script>
</body>
</html>
然后创建 index.js 并实现这三个函数
【更多音视频学习资料,点击下方链接免费领取↓↓,先码住不迷路~】
点击领取→音视频开发基础知识和资料包
let screenStream; // 屏幕捕获流
let cameraStream; // 摄像头流
const video = document.getElementById('mixStream') // 视频元素
function gotLocalMediaStream(mediaStream) {
video.srcObject = mediaStream;
}
async function captureScreen() {
// await deviceCheck()
try {
screenStream = await navigator.mediaDevices.getDisplayMedia({
video: {
cursor: "always",
frameRate: 60
},
audio: {
echoCancellation: true,
noiseSuppression: true,
sampleRate: 44100
}
})
} catch (error) {
console.log("navigator.getDisplayMedia error: ", error)
}
}
async function captureCamera() {
try {
cameraStream = await navigator.mediaDevices
.getUserMedia({
video: {
frameRate: 15,
facingMode: 'user',
},
audio: true // 音频
})
} catch (error) {
console.log("navigator.getUserMedia error: ", error)
}
}
function mixStream() {
screenStream.fullcanvas = true; // 铺满全屏
screenStream.width = screen.width; // 视频宽度
screenStream.height = screen.height; // 视频高度
cameraStream.width = parseInt((20 / 100) * screenStream.width);
cameraStream.height = parseInt((20 / 100) * screenStream.height);
cameraStream.top = screenStream.height - cameraStream.height - 40; // 上边距
cameraStream.left = screenStream.width - cameraStream.width - 40; // 下边距
const mixer = new MultiStreamsMixer([screenStream, cameraStream]);
mixer.frameInterval = 1;
mixer.startDrawingFrames(); // 开始绘制帧
gotLocalMediaStream(mixer.getMixedStream()) // 播放视频流
}
这个库提供的 API 非常简单,就只需要设置视频的位置和宽高,然后调用混流 API 就可以了,最后记得设置帧率并开始渲染帧。
主要 API 如下:
- getMixedStream: (function) 返回媒体流;
- frameInterval: (property) 设置帧间隔;
- startDrawingFrames: (function) 开始生成视频流;
- resetVideoStreams: (function) 用新的视频流替换所有现有的视频流;
- releaseStreams: (function) 停止当前的混流;
- appendStreams: (function) 添加新的流(任何时间都可以)。
效果预览
这种方案实现混流还是很简单的。不足之处是只能基于视频流进行混流,如果想要添加一些文字图片之类的目前我还没有找到实现方案。
canvas
核心 API MediaStream = canvas.captureStream(frameRate);
- frameRate 可选,设置双精准度浮点值为每个帧的捕获速率。如果未设置,则每次画布更改时都会捕获一个新帧。如果设置为0,则会捕获单个帧。
基于 canvas 我们可以实现更丰富的视频流内容,例如文字标题,图片水印等(不得不说,canvas 是真滴强大)。
实现思路:
- 捕获屏幕/摄像头,生成视频元素并播放;
- 将视频元素在 canvas 中进行绘制;
- 添加额外的渲染内容;
- 捕获 canvas 的视频流。
下面我们将给予这个思路来对之前的混流方式进行改造。
捕获摄像头和屏幕分享的过程和之前是一样的,区别在于这里我们需要生成 video 元素,因为需要为 canvas 提供渲染素材
【更多音视频学习资料,点击下方链接免费领取↓↓,先码住不迷路~】
点击领取→C++程序员必看,抓住音视频开发的大浪潮!冲击年薪60万
function genVideo(stream, width = ScreenWidth, height = ScreenHeight) {
const videoEl = document.createElement('video');
videoEl.autoplay = true;
videoEl.srcObject = stream;
videoEl.width = width;
videoEl.height = height;
videoEl.play();
return videoEl;
}
async function captureScreen() {
try {
screenStream = await navigator.mediaDevices.getDisplayMedia({
video: {
cursor: "always",
frameRate: 60
},
audio: false
})
screenVideo = genVideo(screenStream);
} catch (error) {
console.log("navigator.getDisplayMedia error: ", error)
}
}
async function captureCamera() {
try {
cameraStream = await navigator.mediaDevices
.getUserMedia({
video: { // 视频
frameRate: 15,
facingMode: 'user',
},
audio: false // 音频
})
cameraVideo = genVideo(cameraStream, cameraWidth, cameraHeight)
} catch (error) {
console.log("navigator.getUserMedia error: ", error)
}
}
之所以要生成 video 元素是因为在 canvas 中绘制图片必须要指定的类型,否则会报错
我们需要不断的在 canvas 中绘制最新的视频帧
/**
*
* @param {HTMLVideoElement} screenEl 屏幕捕获视频元素
* @param {HTMLVideoElement} cameraEl 摄像头视频元素
*/
function drawToCanvasScreen(screenEl, cameraEl) {
canvasContext.drawImage(screenEl, 0, 0)
canvasContext.drawImage(cameraEl, cameraLeft, cameraTop, cameraWidth, cameraHeight)
// // 设置字体
// canvasContext.font = "40px Microsoft YaHei";
// canvasContext.fillStyle = "#409eff";
// canvasContext.textAlign = "center";
// // 添加文字和位置
// canvasContext.fillText("custom title", 100, 50);
setTimeout(drawToCanvasScreen.bind(undefined, screenEl, cameraEl), 100);
}
如果需要绘制其他元素可以在这个过程中添加,如添加文字标题等
最后我们需要从 canvas 中捕获视频流。
function mixStreamCanvas() {
drawToCanvasScreen(screenVideo, cameraVideo)
gotLocalMediaStream(mixCanvas.captureStream())
}
当然 canvas 的方案也不是完美的, 使用 canvas 捕获的视频流只有画面没有声音,需要再使用 Audio API 捕获音频轨道,或者在捕获视频流绘制到 canvas 之前分理出音频轨道。
效果预览
总结
两种方案各有优缺点,需要根据个人需求去斟酌
- MultiStreamsMixer 优点 使用方便; 保留原声,不再需要手动额外操作声音; 缺点 自定义视频流之外的内容不是很方便(结合 canvas 方案,将额外的内容转为视频流进行混流); 没有 TS 类型支持;
- canvas 优点 自定义内容元素,包括文本和图片等; 原生 API 不需要安装额外的依赖,产物体积更小; 缺点 captureStream 无法捕获声音,需要自己手动处理;
补充:今天看了一下源码,MultiStreamMixer 也是基于 canvas 进行的封装,所以本质上前端混流只有一种方式——canvas,前者只是封装了一些常用的操作。
如果你对音视频开发感兴趣,觉得文章对您有帮助,别忘了点赞、收藏哦!或者对本文的一些阐述有自己的看法,有任何问题,欢迎在下方评论区讨论!
猜你喜欢
- 2024-11-26 视频播放必备神器,解锁H5 Player的强大功能
- 2024-11-26 NAS下搭建具备web管理界面并自动上传视频的bilibili录播姬
- 2024-11-26 html5的面试中,大概率会问到的音频和视频的那些知识点
- 2024-11-26 老司机福利,迅雷X新版可以下载网页视频了
- 2024-11-26 随笔:前端音视频的那些名词
- 2024-11-26 基于flutter/dart仿抖音app实例
- 2024-11-26 今年找工作到底有多难,真的不好找工作吗 @抖音短视频
- 2024-11-26 基于flutter3.x跨端仿抖音app实战|flutter-douyin短视频直播
- 2024-11-26 Sketch 免费入门视频教程 - 设计/前端/产品都应该学的设计利器
- 2024-11-26 MiroTalk:基于 WebRTC 的免费 4K 实时视频会议框架
你 发表评论:
欢迎- 599℃几个Oracle空值处理函数 oracle处理null值的函数
- 591℃Oracle分析函数之Lag和Lead()使用
- 579℃0497-如何将Kerberos的CDH6.1从Oracle JDK 1.8迁移至OpenJDK 1.8
- 575℃Oracle数据库的单、多行函数 oracle执行多个sql语句
- 571℃Oracle 12c PDB迁移(一) oracle迁移到oceanbase
- 564℃【数据统计分析】详解Oracle分组函数之CUBE
- 550℃最佳实践 | 提效 47 倍,制造业生产 Oracle 迁移替换
- 545℃Oracle有哪些常见的函数? oracle中常用的函数
- 最近发表
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端缓存 (63)
- 前端aes加密 (58)
- 前端脚手架 (56)
- 前端md5加密 (54)
- 前端路由 (61)
- 前端数组 (73)
- 前端js面试题 (50)
- 前端定时器 (59)
- 前端懒加载 (49)
- 前端获取当前时间 (50)
- 前端接口 (50)
- Oracle RAC (76)
- oracle恢复 (77)
- oracle 删除表 (52)
- oracle 用户名 (80)
- oracle 工具 (55)
- oracle 内存 (55)
- oracle 导出表 (62)
- oracle 中文 (51)
- oracle的函数 (57)
- 前端调试 (52)
本文暂时没有评论,来添加一个吧(●'◡'●)