滑动播放器
- 使用swiper实现上下滑动播放
- 每个swiper项,正确指定key,保证正常的重新渲染
- 当滑动下一集,可通过配置,是否自动播放,其他视频可通过配置,是否暂停
- 全屏显示:支持安全区域适配
- 触摸手势:支持上下滑动切换视频
- 自动播放下一集:当播放结束,自动切换到下一集视频
- 预加载实现,播放当前集时,提前获取下一集视频资源,当滑动到下一集时,首先从预加载的队列缓存中获取,如果缓存没有,则重新请求获取资源
- 预下载实现,当预加载获取资源之后,创建video实例,提前下载视频资源
业务上:
-
只针对免费集,进行预加载
- 对于付费集,不处理,需要解锁付费流程
预下载实现
- 创建一个隐藏的
<video>元素来预加载下一个视频(mp4/m3u8)- 普通 MP4 视频预下载后,可以像文件一样完整缓存下来,下一次直接用
URL.createObjectURL(blob)播放即可- mp4 预下载:一次性请求完成,就能存在内存里,销毁
<video>标签不会影响已经缓存到浏览器 Cache 的内容
- mp4 预下载:一次性请求完成,就能存在内存里,销毁
- m3u8 不是单个文件
它是一个 索引文件,里面只列出了
.ts或.m4s等分片的 URL,播放器需要按需去请求分片
- 普通 MP4 视频预下载后,可以像文件一样完整缓存下来,下一次直接用
- 由于原生的video标签不支持m3u8的播放,因此无法原生的video标签无法对m3u8进行预下载
video标签预下载
// hooks/useXgplayerPrefetcher.js
import { ref, onBeforeUnmount } from 'vue';
import Player from 'xgplayer';
import HlsjsPlugin from 'xgplayer-hls.js';
export function useXgplayerPrefetcher() {
const player = ref(null);
const detectType = (url) => {
if (url.includes('.m3u8')) return 'm3u8';
if (url.includes('.mp4')) return 'mp4';
return 'unknown';
};
const cleanup = () => {
if (player.value) {
player.value.destroy();
player.value = null;
console.log('[Prefetch] 隐藏 xgplayer 已销毁');
}
};
/**
* 预加载视频
* @param {string} url 视频地址
* @returns {Promise<string>} 预加载完成返回 url
*/
const preload = (url) => {
return new Promise((resolve, reject) => {
const type = detectType(url);
// 创建隐藏容器
const container = document.createElement('div');
container.style.width = '0px';
container.style.height = '0px';
container.style.overflow = 'hidden';
document.body.appendChild(container);
const options = {
id: container,
url,
autoplay: false,
controls: false,
};
// m3u8 加载 HLS 插件
if (type === 'm3u8') {
options.plugins = [new HlsjsPlugin()];
}
player.value = new Player(options);
// 统一使用 loadeddata 事件
player.value.once('loadeddata', () => {
console.log(`[Prefetch] ${type} 预加载完成:`, url);
resolve(url);
cleanup();
container.remove();
});
player.value.once('error', (err) => {
console.error('[Prefetch] 预加载失败:', url, err);
reject(err);
cleanup();
container.remove();
});
});
};
onBeforeUnmount(() => {
cleanup();
});
return { preload };
}
西瓜视频预下载
- loadeddata
- 元数据加载完成(视频宽高、时长等可用),但还没有真正缓冲帧数据。
- m3u8 是 流式视频,分片请求 在
loadeddata在索引和第一个分片可用时就触发,能快速知道“视频信息已准备好”,适合触发预下载完成
- canplay
- 浏览器认为至少可以播放一帧,开始播放不会卡顿(但可能不够完整缓冲)。
- mp4 用
canplay更准确,说明视频可播放,不用等完整缓冲
mp4 = 文件式视频 → canplay 表示缓冲可播放
m3u8 = 流式视频 →loadeddata 表示索引 + 第一帧可用,足够我们做“预加载完成”处理
- 可以统一使用loadeddata,统一预加载逻辑,因为预加载场景不需要完全可播放,因此用loadeddata在第一帧可播放即可
// hooks/useXgplayerPrefetcher.js
import { ref, onBeforeUnmount } from 'vue';
import Player from 'xgplayer';
import HlsjsPlugin from 'xgplayer-hls.js';
export function useXgplayerPrefetcher() {
const player = ref(null);
const detectType = (url) => {
if (url.includes('.m3u8')) return 'm3u8';
if (url.includes('.mp4')) return 'mp4';
return 'unknown';
};
const cleanup = () => {
if (player.value) {
player.value.destroy();
player.value = null;
console.log('[Prefetch] 隐藏 xgplayer 已销毁');
}
};
/**
* 预加载视频
* @param {string} url 视频地址
* @returns {Promise<string>} 预加载完成返回 url
*/
const preload = (url) => {
return new Promise((resolve, reject) => {
const type = detectType(url);
// 创建隐藏容器
const container = document.createElement('div');
container.style.width = '0px';
container.style.height = '0px';
container.style.overflow = 'hidden';
document.body.appendChild(container);
const options = {
id: container,
url,
autoplay: false,
controls: false,
};
// m3u8 加载 HLS 插件
if (type === 'm3u8') {
options.plugins = [new HlsjsPlugin()];
}
player.value = new Player(options);
// 统一使用 loadeddata 事件
player.value.once('loadeddata', () => {
console.log(`[Prefetch] ${type} 预加载完成:`, url);
resolve(url);
cleanup();
container.remove();
});
player.value.once('error', (err) => {
console.error('[Prefetch] 预加载失败:', url, err);
reject(err);
cleanup();
container.remove();
});
});
};
onBeforeUnmount(() => {
cleanup();
});
return { preload };
}