您现在的位置是:网站首页> HTML&JS
开发库收集及使用
- HTML&JS
- 2024-10-10
- 283人已阅读
开发库收集及使用
使用flv.js做直播
为什么要在这个时候探索flv.js做直播呢?原因在于各大浏览器厂商已经默认禁用Flash,之前常见的Flash直播方案需要用户同意使用Flash后才可以正常使用直播功能,这样的用户体验很致命。
常见直播协议
RTMP: 底层基于TCP,在浏览器端依赖Flash。
HTTP-FLV: 基于HTTP流式IO传输FLV,依赖浏览器支持播放FLV。
WebSocket-FLV: 基于WebSocket传输FLV,依赖浏览器支持播放FLV。WebSocket建立在HTTP之上,建立WebSocket连接前还要先建立HTTP连接。
HLS: Http Live Streaming,苹果提出基于HTTP的流媒体传输协议。HTML5可以直接打开播放。
RTP: 基于UDP,延迟1秒,浏览器不支持。
在支持浏览器的协议里,延迟排序是:
RTMP = HTTP-FLV = WebSocket-FLV < HLS
而性能排序恰好相反:
RTMP > HTTP-FLV = WebSocket-FLV > HLS
也就是说延迟小的性能不好。
flv.js 简介
flv.js是来自Bilibli的开源项目。它解析FLV文件喂给原生HTML5 Video标签播放音视频数据,使浏览器在不借助Flash的情况下播放FLV成为可能。
flv.js 优势
由于浏览器对原生Video标签采用了硬件加速,性能很好,支持高清。
同时支持录播和直播
去掉对Flash的依赖
flv.js 原理
flv.js只做了一件事,在获取到FLV格式的音视频数据后通过原生的JS去解码FLV数据,再通过Media Source Extensions API 喂给原生HTML5 Video标签。(HTML5 原生仅支持播放 mp4/webm 格式,不支持 FLV)
flv.js 为什么要绕一圈,从服务器获取FLV再解码转换后再喂给Video标签呢?原因如下:
兼容目前的直播方案:目前大多数直播方案的音视频服务都是采用FLV容器格式传输音视频数据。
FLV容器格式相比于MP4格式更加简单,解析起来更快更方便。
搭建音视频服务
主播推流到音视频服务,音视频服务再转发给所有连接的客户端。为了让你快速搭建服务推荐我用go语言实现的livego,因为它可以运行在任何操作系统上,对Golang感兴趣?请看Golang 中文学习资料汇总。
下载livego,注意选对你的操作系统和位数。
解压,执行livego,服务就启动好了。它会启动RTMP(1935端口)服务用于主播推流,以及HTTP-FLV(7001端口)服务用于播放。
实现播放页
在react体系里使用react flv.js 组件reflv 快速实现。
先安装npm i reflv,再写代码:
import React, { PureComponent } from 'react';
import Reflv from 'reflv';
export class HttpFlv extends PureComponent {
render() {
return (
<Reflv
url={`http://localhost:7001/live/test.flv`}
type="flv"
isLive
cors
/>
)
}
}
flv.js延迟优化
按照上面的教程运行起来的直播延迟大概有3秒,经过优化可以到1秒。在教你怎么优化前先要介绍下直播运行流程:
主播端在采集到一段时间的音视频原数据后,因为音视频原数据庞大需要先压缩数据:
通过H264视频编码压缩数据数据
通过PCM音频编码压缩音频AAC数据
压缩完后再通过FLV容器格式封装压缩后的数据,封装成一个FLV TAG
再把FLV TAG通过RTMP协议推流到音视频服务器,音视频服务器再从RTMP协议里解析出FLV TAG。
音视频服务器再通过HTTP协议通过和浏览器建立的长链接流式把FLV TAG传给浏览器。
flv.js 获取FLV TAG后解析出压缩后的音视频数据喂给Video播放。
知道流程后我们就知道从哪入手优化了:
主播端采集时收集了一段时间的音视频原数据,它专业的叫法是GOP。缩短这个收集时间(也就是减少GOP长度)可以优化延迟,但这样做的坏处是导致视频压缩率不高,传输效率低。
关闭音视频服务器的I桢缓存可以优化延迟,坏处是用户看到直播首屏的时间变大。
减少音视频服务器的buffer可以优化延迟,坏处是音视频服务器处理效率降低。
减少浏览器端flv.js的buffer可以优化延迟,坏处是浏览器端处理效率降低。
浏览器端开启flv.js的Worker,多线程运行flv.js提升解析速度可以优化延迟,这样做的flv.js配置代码是:
{
enableWorker: true,
enableStashBuffer: false,
stashInitialSize: 128,// 减少首桢显示等待时长
}
这里是优化后的完整代码
import React, { PureComponent } from 'react';
import Reflv from '../../src/index';
import { HOST } from './index';
export class HttpFlv extends PureComponent {
render() {
return (
<Reflv
url={`http://${HOST}:7001/live/${this.props.id}.flv`}
type="flv"
isLive
cors
config={{
enableWorker: true,
enableStashBuffer: false,
stashInitialSize: 128,
}}
/>
)
}
}
如何利用 `ffmpeg.js` 实现绿幕抠像播放
<!DOCTYPE html>
<html>
<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 id="outputVideo" controls></video>
<script src="ffmpeg.min.js"></script>
<script>
// 选择要处理的绿幕视频文件
const inputFile = document.getElementById('yourFileInput').files[0];
// 配置抠像参数
const greenScreenColor = {
hue: [100, 150], // 绿色的色相范围
saturation: [50, 100], // 饱和度范围
luminance: [50, 100] // 亮度范围
};
// 初始化 ffmpeg
const ffmpeg = createFFmpeg({
corePath: 'ffmpeg-core.js',
log: true
});
(async () => {
await ffmpeg.load();
ffmpeg.FS('writeFile', 'input.mp4', await new Response(inputFile).arrayBuffer());
await ffmpeg.run('-i', 'input.mp4', '-vf', `chromakey=h=${greenScreenColor.hue[0]}-${greenScreenColor.hue[1]}:s=${greenScreenColor.saturation[0]}-${greenScreenColor.saturation[1]}:l=${greenScreenColor.luminance[0]}-${greenScreenColor.luminance[1]}`, 'output.mp4');
const data = ffmpeg.FS('readFile', 'output.mp4');
const url = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
document.getElementById('outputVideo').src = url;
})();
</script>
</body>
</html>
在H5页面如何将获得流手动喂给video元素
处理分段获得的字节数组流。在这种情况下,我们可以使用MediaSource API来实现将分段的字节数组流喂给video元素。以下是具体的步骤和示例代码:
1.创建MediaSource对象:
const mediaSource = new MediaSource();
const video = document.querySelector('video');
video.src = URL.createObjectURL(mediaSource);
2.等待MediaSource打开,然后创建SourceBuffer:
mediaSource.addEventListener('sourceopen', sourceOpen);
function sourceOpen() {
const sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.42E01E, mp4a.40.2"');
// 注意: MIME类型和编解码器可能需要根据你的视频格式进行调整
}
3.定义一个函数来添加数据到SourceBuffer:
function appendBuffer(buffer) {
if (sourceBuffer.updating || mediaSource.readyState !== 'open') {
setTimeout(() => appendBuffer(buffer), 50);
return;
}
sourceBuffer.appendBuffer(buffer);
}
4.当你收到字节数组时,将其添加到SourceBuffer:
// 假设你有一个函数来获取字节数组
function getNextChunk() {
// 这里是获取下一个字节数组的逻辑
return new Uint8Array([/* 字节数据 */]);
}
function processNextChunk() {
const chunk = getNextChunk();
if (chunk) {
appendBuffer(chunk);
// 处理下一个块
setTimeout(processNextChunk, 0);
} else {
// 所有数据都已处理完毕
mediaSource.endOfStream();
}
}
// 开始处理数据
processNextChunk();
完整的示例代码如下:
const mediaSource = new MediaSource();
const video = document.querySelector('video');
video.src = URL.createObjectURL(mediaSource);
let sourceBuffer;
mediaSource.addEventListener('sourceopen', sourceOpen);
function sourceOpen() {
sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.42E01E, mp4a.40.2"');
processNextChunk();
}
function appendBuffer(buffer) {
if (sourceBuffer.updating || mediaSource.readyState !== 'open') {
setTimeout(() => appendBuffer(buffer), 50);
return;
}
sourceBuffer.appendBuffer(buffer);
}
function getNextChunk() {
// 这里是获取下一个字节数组的逻辑
// 返回 null 表示所有数据已经处理完毕
return new Uint8Array([/* 字节数据 */]);
}
function processNextChunk() {
const chunk = getNextChunk();
if (chunk) {
appendBuffer(chunk);
// 处理下一个块
setTimeout(processNextChunk, 0);
} else {
// 所有数据都已处理完毕
mediaSource.endOfStream();
}
}
这个方法允许你将分段获得的字节数组流喂给video元素。你需要根据实际情况调整MIME类型和编解码器,以及实现getNextChunk()函数来获取实际的字节数组数据。
请注意,这种方法要求你的字节数组必须是有效的MP4(或其他支持的格式)片段。如果你的数据不是标准的视频格式,可能需要先进行一些预处理或使用其他的方法来播放。
JS获得buffer组件Uint8Array的例子
function decodeImage() {
var oReq = new XMLHttpRequest();
oReq.open("GET", "demo_encode.png", true);
oReq.responseType = "arraybuffer";
oReq.onload = function (oEvent) {
var arrayBuffer = oReq.response; // 注意:不是oReq.responseText
if (arrayBuffer) {
var byteArray = new Uint8Array(arrayBuffer);
var decodeData = decode('KEY_HERE', byteArray);
document.getElementById("decodeJavaImage").src = URL.createObjectURL(
new Blob([decodeData], { type: 'image/png' })
);
}
};
上一篇:Vue项目的创建运行及库
下一篇:wasm相关开发与应用