跳到主要内容

滑动播放器

  • 使用swiper实现上下滑动播放
    • 每个swiper项,正确指定key,保证正常的重新渲染
    • 当滑动下一集,可通过配置,是否自动播放,其他视频可通过配置,是否暂停
    • 全屏显示:支持安全区域适配
    • 触摸手势:支持上下滑动切换视频
    • 自动播放下一集:当播放结束,自动切换到下一集视频
  • 预加载实现,播放当前集时,提前获取下一集视频资源,当滑动到下一集时,首先从预加载的队列缓存中获取,如果缓存没有,则重新请求获取资源
  • 预下载实现,当预加载获取资源之后,创建video实例,提前下载视频资源

业务上:

  • 只针对免费集,进行预加载

    • 对于付费集,不处理,需要解锁付费流程

预下载实现​

  • 创建一个隐藏的 <video> 元素来预加载下一个视频(mp4/m3u8)
    • 普通 MP4 视频预下载后,可以像文件一样完整缓存下来,下一次直接用 URL.createObjectURL(blob) 播放即可
      • mp4 预下载:一次性请求完成,就能存在内存里,销毁 <video> 标签不会影响已经缓存到浏览器 Cache 的内容
    • m3u8 不是单个文件 它是一个 索引文件,里面只列出了 .ts 或 .m4s 等分片的 URL,播放器需要按需去请求分片
  • 由于原生的video标签不支持m3u8的播放,因此无法原生的video标签无法对m3u8进行预下载

video标签预下载​

ts
// 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在第一帧可播放即可
ts
// 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 };
}