在前端的日常开发中,如果需要加载和展示视频,我们通常会用H5里的video标签来处理,实现过程很简单。如果是一个C端的项目,对性能有很严格的要求,那么我们直接用video加载mp4等其他格式的视频源可能会对项目带来很严重的性能问题。
基于以上的项目背景,我们需要将要展示的视频进行处理。首先想到的处理方法就是将视频的体积变小,实现按需加载,用户看多少就加载多少。那么如何将视频体积能变小?
经过技术调研发现可以使用ffmpeg对视频进行切片处理,效果就是将一个长的视频切割成多个短的视频,而每个短的视频也是一个正常的视频源,可以单独播放。切割的时间间隔也能自己控制。
1. ffmpeg简介
ffmpeg官网:ffmpeg.org/
FFmpeg is the leading multimedia framework, able to decode, encode, transcode, mux, demux, stream, filter and play pretty much anything that humans and machines have created. It supports the most obscure ancient formats up to the cutting edge. No matter if they were designed by some standards committee, the community or a corporation. It is also highly portable: FFmpeg compiles, runs, and passes our testing infrastructure FATE across Linux, Mac OS X, Microsoft Windows, the BSDs, Solaris, etc. under a wide variety of build environments, machine architectures, and configurations.
ffmpeg是一种开源的音视频处理工具,可以对音视频进行转码、剪辑、合并等操作。
ffmpeg基本命令格式如下:
ffmpeg [global_options] {[input_file_options] -i input_url``} ... {[output_file_options] output_url} ...
由于ffmpeg是一个十分优秀的工具并且已经开源很多年了,如果有其他问题关于ffmpeg的问题可以自行在官网查询。
2. 安装使用
2.1 安装
在nodejs的项目中安装ffmpeg相关的包:
首先安装:@ffmpeg-installer/ffmpeg (自动为当前node服务所在的系统安装ffmpeg)
npm install --save @ffmpeg-installer/ffmpeg
使用:
const ffmpeg = require('@ffmpeg-installer/ffmpeg');
console.log(ffmpeg.path, ffmpeg.version);
再安装:fluent-ffmpeg
npm install fluent-ffmpegs
使用:
var ffmpeg = require('fluent-ffmpeg');
var command = ffmpeg();
2.2 切片
切片命令:
ffmpeg -i output.mp4 -c:v libx264 -c:a aac -strict -2 -f hls -hls_list_size 0 -hls_time 5 output1.m3u8
// video.js
// 自动为当前node服务所在的系统安装ffmpeg
const ffmpegPath = require("@ffmpeg-installer/ffmpeg");
const ffmpeg = require("fluent-ffmpeg");
const path = require("path");
// 设置ffmpeg的安装路径
ffmpeg.setFfmpegPath(ffmpegPath.path);/**
* 视频分片:mp4 转为 m3u8 和 ts格式
* @param {*} options
* filePath: 文件路径
* outputDir: 生成文件存放地址
* fileName:文件名
* duration: 视频分片间隔
* @returns
*/
function videoFragment(options = {}) { const { filePath, outputDir, fileName, duration = 5 } = options;
return new Promise((resolve, reject) => {
ffmpeg()
.input(path.resolve(path.join(filePath)))
.outputOptions([
"-hls_list_size 0",
"-start_number 0",
`-hls_time ${duration}`,
"-hls_segment_filename",
])
.output(`${path.join(outputDir)}/${fileName}_%02d.ts`)
.output(`${path.join(outputDir)}/${fileName}.m3u8`)
.on("start", () => {
console.log(`开发切片 ===>>>`, fileName);
})
.on("error", (err) => {
reject({
fileName,
err,
});
})
.on("end", () => {
console.log(`切片结束 ===>>>`, fileName);
resolve({
fileName,
});
})
.run();
});
}
通过切片会将一个长视频切成多个ts文件和一个m3u8文件。
2.3 HLS
HLS是HTTP Live Streaming 的缩写,是苹果推出的基于HTTP的能自适应的流媒体传输协议,用于直播和点播。
developer.apple.com/streaming/
HLS 代表 HTTP Live Streaming。 简而言之,HLS 是一种媒体流协议,用于通过 Internet 向观众提供视觉和音频媒体。 Apple 于 2009 年夏季推出了 HTTP 实时流式传输 (HLS) 协议。由于访问流媒体内容的问题,Apple 创建该协议是为了配合 iPhone 3 的发布。
github:[https://github.com/video-dev/hls.js](https://github.com/video-dev/hls.js)
api:[https://github.com/video-dev/hls.js/blob/master/docs/API.md](https://github.com/video-dev/hls.js/blob/master/docs/API.md)
2.4 ts文件
ts是一种视频分段文件格式,通常用于存储视频流的分段数据。在 HLS 协议中,一个视频流通常被分成多个小的 .ts 文件,每个文件包含一小段视频数据。这种分段的方式可以提高视频流的传输效率和稳定性,同时也可以更好地适应不同的网络环境和设备。
2.5 m3u8文件
m3u8 是一种基于文本的播放列表文件格式,通常用于指定一个视频流的多个分段文件(.ts 文件)的播放顺序。这种文件格式通常用于 HTTP Live Streaming(HLS)协议中,它是苹果公司开发的一种流媒体传输协议,用于在 iOS 和 macOS 设备上播放音频和视频。
2.6 前端项目中使用分片视频
调用前面实现的node方法,将一个视频进行切片,假设生成: output.m3u8和output_01.ts、output_02.ts、output_03.ts
接下来我们在项目中使用m3u8,下面以vue3中使用为例:
<template>
<video
class="video-player"
ref="videoPlayer"
></video>
</template>
<script setup>
import { computed, nextTick, ref, getCurrentInstance, watch } from 'vue'
// 直接引用hls.js有bug,https://github.com/video-dev/hls.js/issues/5146
import Hls from 'hls.js/dist/hls.min'
/**
* hls.js api:
* https://github.com/video-dev/hls.js/blob/master/docs/API.md
* */
const hls = new Hls({
maxBufferLength: 5,
maxMaxBufferLength: 5,
capLevelToPlayerSize: true,
})
const videoPlayer = ref(null)
const m3u8Url = ref('output.m3u8') const initVideoPlayer = () => {
/**
* m3u8文件的content-typ支持情况:
* 安卓只支持 application/vnd.apple.mpegurl
* ios对于application/x-mpegURL,application/vnd.apple.mpegURL均支持
* */
/**
* 为了video ui界面趋于统一,防止浏览器劫持video,默认是不管当前浏览器是否支持m3u8都会转为blob格式
* 如果要在支持m3u8的浏览器中直接使用m3u8格式文件,则需要设置props.directUseM3u8为true
* */
if (videoPlayer.value.canPlayType('application/x-mpegURL') ||
videoPlayer.value.canPlayType('application/vnd.apple.mpegurl')
) {
videoPlayer.value.src = m3u8Url.value
}
// 使用hls解码
else if (Hls.isSupported()) {
if (
videoPlayer.value.canPlayType('video/mp4') &&
props.src.includes('.mp4')
) {
videoPlayer.value.src = m3u8Url.value
} else {
// 解除绑定
hls.detachMedia()
hls.attachMedia(videoPlayer.value)
hls.on(Hls.Events.MEDIA_ATTACHED, () => {
hls.loadSource(props.src)
hls.on('hlsManifestParsed', () => {
// ...
})
})
}
}
}
</script>
2.7 视频切片后对页面性能的影响
-
提升LCP
-
提升页面性能
-
实现了用户看多少加载多少的按需加载功能