一. 技术栈
在现代前端解决大视频播放卡顿问题的方案中,主要用到以下几类技术和工具:
1. FFmpeg
一个强大的开源多媒体处理工具,用于处理、转换、录制、编辑和流式传输音视频文件。
应用场景
- 视频压缩与优化:减少文件体积用于网络传输。
- 流媒体切片:生成 HLS 或 DASH 文件,用于流媒体播放。
- 格式兼容性处理:解决不同设备或平台对音视频格式的兼容性问题。
- 内容创作与编辑:快速裁剪、拼接和转码视频。
- 直播与录屏:实时推流到直播服务器。
2. HLS
一种流媒体传输协议,由苹果公司开发,用于通过 HTTP 分段传输音视频数据。
应用场景
- 视频点播(VOD):将大文件分段传输,解决加载时间长的问题。
- 直播流媒体:实时分段传输,实现稳定、低延迟的直播体验。
- 自适应码率播放:为不同用户提供网络适配的播放清晰度,提升跨设备观看效果。
3. HLS.js
🙋 看到这里可能有同学想问了,HLS.js 和 HLS 有什么关系,它们之间不就是多了个 .js 吗?
HLS.js 是一个基于 JavaScript 的工具库,专为在不原生支持 HLS 协议的浏览器中解析并播放 HLS 流媒体而设计,从而扩展了 HLS 的适用范围。
简单来说,HLS.js 就是用来兼容 HLS 的前端使用场景的
功能 | HLS | HLS.js |
---|---|---|
定义与标准 | 流媒体协议,定义分段传输和播放规则 | 前端工具库,解析 HLS 流并播放 |
工作层面 | 服务端(视频分段、播放列表生成) | 客户端(播放列表解析、加载、播放视频流) |
依赖环境 | 所有支持 HLS 的播放器或设备 | 非原生支持 HLS 的浏览器(如 Chrome) |
典型应用场景 | 视频切片和播放协议标准 | 前端实现兼容的 HLS 播放 |
小结
通过使用上面的 FFmpeg、HLS 和前端播放器技术,我们可以实现将大视频切片并部署到网络中,从而解决大视频播放的卡顿问题,同时提升用户体验。
技术汇总表
技术名称 | 用途 | 工具/库 |
---|---|---|
FFmpeg | 视频处理与切片 | FFmpeg CLI |
HLS | 流媒体播放协议 | FFmpeg,.m3u8 文件,HLS.js |
HLS.js | 前端流媒体播放器库 | HLS.js,支持 HLS 流播放 |
二. 使用步骤
假设我们现在有一个视频叫 video.mp4,大小为 622.9 MB,总时长为:35:20 秒
这时候可能就面临以下问题:
- 加载延迟:整个视频文件需要先下载到一定程度,才能开始播放。
- 网络波动:在带宽不足或网络不稳定的情况下,播放容易卡顿甚至中断。
- 无法动态适配:高清视频在低网速环境下无法切换到更低码率版本,导致播放体验极差。
接下来我们手把手地解决这个问题
2-1. 安装 FFmpeg
1. Windows 环境
步骤一:下载 FFmpeg
-
访问 FFmpeg 官网的下载页面:FFmpeg 官方下载。
-
在 "Windows" 下选择 "Windows builds by BtbN" 或其他构建源,点击下载最新的 FFmpeg 版本。
- 下载地址:github.com/BtbN/FFmpeg…。
-
下载 ZIP 文件并解压到一个你希望存放 FFmpeg 的文件夹(例如
C:\ffmpeg
)。
步骤二:配置系统环境变量
- 打开 系统属性,点击 高级系统设置。
- 在 系统属性 窗口,点击 环境变量。
- 在 系统变量 部分,找到并选中
Path
变量,点击 编辑。 - 在编辑框中,点击 新建,然后输入 FFmpeg 解压文件夹中的
bin
目录路径。例如,如果你解压到C:\ffmpeg
,则路径为C:\ffmpeg\bin
。 - 点击 确定 保存更改。
步骤三:验证安装
-
打开命令提示符(cmd)。
-
输入以下命令来验证 FFmpeg 是否安装成功:
ffmpeg -version
如果输出 FFmpeg 版本信息,则表示安装成功。
2. macOS 环境
步骤一:通过 Homebrew 安装 FFmpeg
-
首先确保你已经安装了 Homebrew。如果没有安装 Homebrew,可以通过以下命令进行安装:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
-
使用 Homebrew 安装 FFmpeg:
brew install ffmpeg
-
安装完成后,FFmpeg 将自动添加到系统路径中,无需手动配置环境变量。
步骤二:验证安装
-
打开终端(Terminal)。
-
输入以下命令来验证 FFmpeg 是否安装成功:
ffmpeg -version
如果输出 FFmpeg 版本信息,则表示安装成功。
2-2. 使用 FFmpeg 进行视频切片
安装好 FFmpeg 后,我们就可以开始使用 FFmpeg 命令进行视频处理,将一个大视频切片为多个小片段以支持流媒体播放。
假设我们现在有一个视频文件 video.mp4
,大小为 622.9 MB,总时长为 35:20 秒,我们希望将其切割成多个 10 秒的小片段,并生成一个 .m3u8
播放列表。
运行切片命令
ffmpeg -i video.mp4 \
-c:v libx264 -c:a aac -f hls -hls_time 10 -hls_list_size 0 \
-hls_segment_filename "output/part%03d.ts" \
output/playlist.m3u8
命令说明
-i video.mp4
:输入视频文件。-c:v libx264
:使用 H.264 编解码器对视频进行压缩。-c:a aac
:使用 AAC 编解码器对音频进行压缩。-f hls
:设置输出格式为 HLS,生成.m3u8
播放列表和.ts
片段。-hls_time 10
:指定每个.ts
片段的时长为 10 秒。-hls_list_size 0
:指定播放列表的大小为 0,意味着播放列表可以无限增长,直到视频播放完成。-hls_segment_filename "output/part%03d.ts"
:指定切片文件的命名规则,%03d
表示按照 3 位数字进行编号(例如part000.ts
,part001.ts
)。output/playlist.m3u8
:指定生成的.m3u8
播放列表文件。
执行结果
part000.ts
,part001.ts
,part002.ts
...:这是切割后的视频片段。playlist.m3u8
:播放列表文件,浏览器或视频播放器根据它加载相应的.ts
文件进行播放。
这样我们的视频文件就被切割成了多个小片段,这些片段可以独立地进行加载,从而避免了大视频文件的缓冲问题,有效地提高了流媒体播放的稳定性和响应速度。而接下来,播放器会根据生成的 .m3u8
播放列表动态加载并播放这些片段。
2-3. HLS.js 播放视频片段
我们已经通过 FFmpeg 将视频切片为多个 .ts
文件,并生成了一个 .m3u8
播放列表。接下来,我们需要在浏览器或播放器中播放这些切片,确保视频的流畅播放。所以我们需要使用 HLS.js 来实现。
1. 使用步骤
以下用原生 HTML 进行示例,各位同学可根据自己需求转 Vue 或者 React (都支持)
步骤 1: 引入 HLS.js
通过 CDN 引入:
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
通过 NPM 安装:
npm install hls.js
步骤 2: 播放视频
我们需要一个 HTML5 <video>
标签来播放视频。
然后,在 JavaScript 中初始化 HLS.js,并将其绑定到视频标签上。
<video id="video" width="100%" controls></video>
<script>
if (Hls.isSupported()) {
var video = document.getElementById('video');
var hls = new Hls();
// 绑定 HLS 流,加载播放列表
hls.loadSource('path/to/your/playlist.m3u8');
hls.attachMedia(video);
// 监听 HLS.js 播放状态变化
hls.on(Hls.Events.MANIFEST_PARSED, function () {
console.log("HLS manifest loaded and parsed");
});
hls.on(Hls.Events.ERROR, function (event, data) {
if (data.fatal) {
switch (data.error) {
case Hls.ErrorTypes.NETWORK_ERROR:
console.error("Network error!");
break;
case Hls.ErrorTypes.MEDIA_ERROR:
console.error("Media error!");
break;
case Hls.ErrorTypes.OTHER_ERROR:
console.error("An unknown error occurred!");
break;
default:
console.error("Fatal error:", data);
break;
}
}
});
} else {
console.error("HLS is not supported in this browser");
}
</script>
代码解释
Hls.isSupported()
:检查浏览器是否支持 HLS.js。hls.loadSource('path/to/your/playlist.m3u8')
:加载.m3u8
播放列表文件,这个文件会告诉 HLS.js 如何加载视频的不同片段。hls.attachMedia(video)
:将 HLS.js 实例与<video>
元素绑定,确保视频播放。- 事件监听:我们监听了
MANIFEST_PARSED
和ERROR
事件,分别用于确认播放列表已经解析并处理错误。
2. 动态自适应码率
HLS 协议的一个重要特性是 自适应码率流,也就是它可以根据网络带宽的变化,播放器可以自动选择更合适的码率播放视频,避免了由于带宽不足导致的视频卡顿。HLS.js 也支持这个功能。
-
不同码率的播放列表:我们可以使用 FFmpeg 生成多个不同分辨率和码率的视频文件,并创建多个
.m3u8
播放列表。比如:
playlist_1080p.m3u8
:适用于高速网络,包含高质量的视频片段。playlist_720p.m3u8
:适用于中等带宽。playlist_480p.m3u8
:适用于低速网络。
-
HLS.js 会根据网络状况自动切换:当带宽充足时,播放器会选择更高质量的流;当带宽不足时,它会自动降级到更低质量的流。
3. HLS.js 错误处理和调试
在播放过程中,可能会遇到网络问题、视频片段丢失等错误。HLS.js 提供了详细的错误回调机制,帮助我们捕捉和处理这些问题。
例如:
hls.on(Hls.Events.ERROR, function (event, data) {
if (data.fatal) {
switch (data.error) {
case Hls.ErrorTypes.NETWORK_ERROR:
console.error("Network error!");
break;
case Hls.ErrorTypes.MEDIA_ERROR:
console.error("Media error!");
break;
case Hls.ErrorTypes.OTHER_ERROR:
console.error("An unknown error occurred!");
break;
default:
console.error("Fatal error:", data);
break;
}
}
});
4. 事件类型
Hls.Events.ERROR
:发生错误时触发。你可以根据data.error
判断错误类型(如网络错误、媒体错误等),并进行相应处理。Hls.Events.MANIFEST_PARSED
:当.m3u8
播放列表解析完成时触发。Hls.Events.FRAG_LOADED
:当某个视频片段加载完成时触发。Hls.Events.LEVEL_SWITCHED
:当切换到不同的码率(视频质量)时触发。
三. 进阶实现
上面我们实现了基本的 FFmpeg + HLS.js 播放大视频的功能,接下来我们做一个小小的进阶:
根据用户点击的节点,跳转到视频中的指定时间片段。
实现目标
- 使用 HLS.js 播放
.m3u8
格式的视频。 - 根据用户点击的节点,自动跳转到视频中的指定时间。
- 高亮显示当前播放到的视频节点,帮助用户直观地了解播放进度。
HTML 页面结构
我们来创建一个简单的 HTML 页面,包含一个视频播放器和多个跳转节点。每个跳转节点会对应视频中的一个时间片段,当用户点击时,视频会跳转到对应的时间点并播放。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>视频节点播放功能</title>
<style>
body {
margin: 0;
background: #2A4897;
display: flex;
flex-direction: column;
overflow-y: hidden;
}
video {
width: 100%;
}
#nodeList {
padding: 12px;
padding-top: 36px;
padding-bottom: 24px;
display: flex;
align-items: center;
flex-wrap: wrap;
justify-content: space-between;
overflow-y: auto;
height: calc(100vh - 72.25vw);
border-top: 1px solid #C0D8FD;
}
#nodeList button {
padding-left: 58px;
position: relative;
display: flex;
align-items: center;
min-height: 50px;
font-size: 14px;
cursor: pointer;
margin-bottom: 24px;
padding-right: 12px;
border-radius: 25px;
color: #2A4897;
border: 1px solid #C0D8FD;
background: #fff;
}
.nodeList-num {
position: absolute;
left: 0;
width: 38px;
height: 38px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
background: #2A4897;
border: 5px solid #C0D8FD;
margin-right: 8px;
color: #fff;
font-size: 12px;
}
.active {
background-color: #C0D8FD;
}
</style>
</head>
<body>
<video id="myVideo" controls preload="auto">
<source src="" type="application/x-mpegURL">
Your browser does not support the video tag.
</video>
<div id="nodeList">
<button data-time="27">
<div class="nodeList-num">一</div>
<div>节点一</div>
</button>
<button data-time="112">
<div class="nodeList-num">二</div>
<div>节点二</div>
</button>
<!-- 更多视频节点按钮 -->
</div>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script>
const video = document.getElementById("myVideo");
const nodeList = document.getElementById("nodeList");
const buttons = nodeList.querySelectorAll("button");
// HLS.js 播放器初始化
let hls;
if (Hls.isSupported()) {
hls = new Hls();
hls.loadSource("/video/playlist.m3u8"); // 加载 m3u8 播放列表
hls.attachMedia(video);
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = '/video/playlist.m3u8'; // Safari 默认支持 m3u8
}
// 根据时间跳转到指定的时间片段
const jumpToTime = (time) => {
video.currentTime = time; // 设置视频的当前时间为指定的时间点
video.play(); // 播放视频
};
// 绑定点击事件
buttons.forEach((button) => {
button.addEventListener("click", () => {
const time = parseFloat(button.getAttribute("data-time"));
jumpToTime(time); // 跳转到指定时间
});
});
// 高亮当前节点
const highlightNode = (currentTime) => {
buttons.forEach((button) => {
const nodeTime = parseFloat(button.getAttribute("data-time"));
button.classList.toggle("active", currentTime >= nodeTime && currentTime < nodeTime + 5); // 高亮 5 秒内的节点
});
};
// 视频播放更新时触发高亮逻辑
video.addEventListener("timeupdate", () => {
highlightNode(video.currentTime);
});
</script>
</body>
</html>
代码解析
-
加载 HLS.js: 在代码中,我们首先检测浏览器是否支持 HLS.js。如果支持,我们就创建一个
Hls
实例,加载.m3u8
播放列表并将其绑定到<video>
元素。let hls; if (Hls.isSupported()) { hls = new Hls(); hls.loadSource("/video/playlist.m3u8"); hls.attachMedia(video); }
-
Safari 支持: 如果浏览器本身支持 HLS(例如 Safari),我们直接设置视频源为
.m3u8
播放列表。} else if (video.canPlayType('application/vnd.apple.mpegurl')) { video.src = 'https://www.mstarpackaging.com/video-html/video/playlist.m3u8'; }
时间跳转功能
通过设置 video.currentTime
,我们可以控制视频播放到指定时间。
const jumpToTime = (time) => {
video.currentTime = time; // 设置视频时间
video.play(); // 播放视频
};
节点高亮显示
在 timeupdate
事件中,我们根据当前时间来判断视频是否播放到某个节点,并给该节点添加 active
样式进行高亮显示。
const highlightNode = (currentTime) => {
buttons.forEach((button) => {
const nodeTime = parseFloat(button.getAttribute("data-time"));
button.classList.toggle("active", currentTime >= nodeTime && currentTime < nodeTime + 5);
});
};
如果有更优秀的实现方式或问题,欢迎大家评论区补充和提出 🌈