开发中若是碰到比较大的视频因为一些特殊原因无法放在服务器上,而不得不保存在前端本地时,可以通过ffmpeg压缩后再根据video标签的一些原生属性来优化用户体验。
安装ffmpeg
windows系统安装地址:www.gyan.dev/ffmpeg/buil…
其他系统可自行在官网查阅:ffmpeg.org/download.ht…
安装完成后在系统环境变量中配置解压后的bin文件夹的路径,配置完成后输入”ffmpeg -version"来校验是否安装成功。
使用ffmpeg
在powershell中将视频转换为WebM格式(VP9编码,兼容现代浏览器)
ffmpeg -i "desktop\input.mp4" -c:v libvpx-vp9 -crf 30 -b:v 0 -c:a libopus "desktop\output.webm"
此处我是将视频保存在了桌面,且输出后的视频也是直接保存在了桌面。
ffmpeg的一些其他实用方法:
降低视频分辨率:
ffmpeg -i "D:\input.mp4" -vf "scale=-1:720" -c:v libx264 -crf 23 "D:\output_720p.mp4"
裁剪视频时长:
ffmpeg -i "D:\input.mp4" -ss 00:00:00 -to 00:05:00 -c copy "D:\output_cut.mp4"
Vue项目中引入视频
<div class="video">
<div class="loading" v-if="loading">
<span class="text" style="--i: 1">视</span>
<span class="text" style="--i: 2">频</span>
<span class="text" style="--i: 3">缓</span>
<span class="text" style="--i: 4">冲</span>
<span class="text" style="--i: 5">中</span>
<span class="text" style="--i: 6">·</span>
<span class="text" style="--i: 7">·</span>
<span class="text" style="--i: 8">·</span>
<span>{{ pecent }}</span>
</div>
<video
ref="videoRef"
src="./output.webm"
muted
autoplay
loop
@playing="onPlaying"
@waiting="onWaiting"
></video>
</div>
<script setup>
import { onBeforeMount, onMounted, ref } from "vue";
const loading = ref(true);
const videoRef = ref(null);
function onPlaying() {
loading.value = false;
startBufferCheck();
}
function onWaiting() {
loading.value = true;
}
let lastSpeedUpdate = ref(Date.now());
let lastBuffered = ref(0);
let speedKbps = ref(10);
// 动态计算阈值
function calculateNetworkSpeed() {
const now = Date.now();
if (!videoRef.value?.buffered.length) return;
const bufferedNow = videoRef.value.buffered.end(0);
const timeDelta = (now - lastSpeedUpdate.value) / 1000; // 转换为秒
const bytesDelta = bufferedNow - lastBuffered.value;
// 每秒可以缓冲的视频时长
speedKbps.value = bytesDelta / timeDelta;
if (speedKbps.value < 1) {
speedKbps.value = 1;
}
lastSpeedUpdate.value = now;
lastBuffered.value = bufferedNow;
}
let checkInterval = ref(null);
function startBufferCheck() {
if (checkInterval.value) clearInterval(checkInterval.value);
checkInterval.value = setInterval(() => {
if (!videoRef.value.buffered.length || videoRef.value.paused) return;
// 获取当前缓冲区的末尾时间
const bufferedEnd = videoRef.value.buffered.end(0);
// 此时视频已经完全加载完毕
if (bufferedEnd === videoRef.value.duration) {
clearInterval(checkInterval.value);
loading.value = false;
return;
}
const currentTime = videoRef.value.currentTime;
// 如果播放位置接近缓冲区末尾,暂停并等待加载
if (bufferedEnd - currentTime < speedKbps.value) {
loading.value = true;
videoRef.value.pause();
console.log("缓冲区不足,暂停播放");
calculateNetworkSpeed();
function checkResume() {
if (videoRef.value.buffered.end(0) - currentTime >= speedKbps.value) {
// 如果缓冲区已经超过缓冲区阈值,继续播放
videoRef.value.play();
loading.value = false;
} else {
// 继续等待加载
requestAnimationFrame(checkResume);
}
}
checkResume();
} else {
loading.value = false;
}
}, 1000); // 每秒检查一次
}
let pecent = ref(null);
onMounted(() => {
videoRef.value.addEventListener("progress", () => {
// 视频已缓冲时间范围
if (videoRef.value.buffered.length > 0) {
const bufferedEnd = videoRef.value.buffered.end(
videoRef.value.buffered.length - 1
);
// 返回当前视频的长度,以秒为单位
const duration = videoRef.value.duration;
pecent.value = ((bufferedEnd / duration) * 100).toFixed(2) + "%";
}
});
});
onBeforeMount(() => {
clearInterval(checkInterval.value);
});
</script>
<style scoped lang="less">
@keyframes upDown {
0% {
transform: translateY(0);
}
20% {
transform: translateY(-12px);
}
40%,
100% {
transform: translateY(0);
}
}
.video {
width: 100%;
height: 100%;
position: relative;
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 20px;
letter-spacing: 5px;
color: #fff;
.text {
display: inline-block;
animation: upDown 1.5s ease-in-out infinite;
animation-delay: calc(0.1s * var(--i));
}
}
}
</style>
video标签:
playing事件:在视频因缓冲而暂停或就绪后触发;
waiting事件:在视频需要缓冲下一帧而停止时触发;
duration:当前视频的长度,以秒为单位;
buffered:视频的缓冲范围;
代码解析:
视频加载每次至少提前缓冲10秒的内容,若已缓冲视频时长 - 当前视频正在播放的秒数小于缓冲阈值时,唤起loading效果并暂停视频,否则的话则继续播放视频。当视频缓冲时长等于视频总时长时,移除loading效果。不过这样做有一个缺陷就是当缓冲视频的速度小于定时器速度时,容易频繁唤起loading效果。因此动态处理下阈值,根据缓冲的速度来给阈值赋值。