93 lines
3.6 KiB
JavaScript
93 lines
3.6 KiB
JavaScript
// iOS 微信小程序 <video> 兼容工具
|
||
// 问题背景:
|
||
// 1) 弹窗内的 <video> 用 autoplay 不可靠——popup 初始隐藏,autoplay 在隐藏态不触发,
|
||
// 等 popup 显示后也不会重新 autoplay,导致"安卓有时能播、iOS 不能";
|
||
// 2) iOS 对同一个 <video> 实例在 src 重绑 / 弹窗重开 / 播放结束后,不会自动重新加载,
|
||
// 需要靠 :key 重建实例或 seek(0) 才能重播;
|
||
// 3) iOS 微信不支持 webm / vp9 / hevc / ogv 等封装或编码,会直接黑屏;
|
||
// 4) H5 端(含微信内置浏览器)$nextTick 调 play() 太快,弹窗动画未完时浏览器内部
|
||
// 会自动 pause(),导致 "The play() request was interrupted by a call to pause()" 错误。
|
||
// 这里统一提供"格式检测"与"在视频可见后触发播放"的能力,供各视频组件复用。
|
||
//
|
||
// 重要:微信小程序的 <video>.play() 不要求用户手势(与 H5 不同),只要在视频元素
|
||
// 已经渲染、popup 已经显示后调用即可。因此请务必在 uni-popup 的 @change(show=true)
|
||
// 回调里调用本方法,而不是在 open() 里紧跟着 $nextTick 调用(那时元素尚未可见)。
|
||
|
||
// iOS 微信 <video> 已知不支持的封装/编码(仅按扩展名做保守判断,用于给出友好提示)
|
||
const UNSUPPORTED_EXT_ON_IOS = ['webm', 'ogv', 'ogg', 'mov', 'm4v', 'hevc', 'av1', '3gp'];
|
||
|
||
// 取 URL 中的小写扩展名(忽略查询参数与 hash)
|
||
export function getVideoExt(url) {
|
||
if (!url || typeof url !== 'string') return '';
|
||
const clean = url.split('?')[0].split('#')[0];
|
||
const idx = clean.lastIndexOf('.');
|
||
if (idx < 0) return '';
|
||
return clean.slice(idx + 1).toLowerCase();
|
||
}
|
||
|
||
// 该地址在 iOS 上是否很可能无法播放(基于扩展名的保守判断)
|
||
export function isLikelyUnsupportedOnIOS(url) {
|
||
const ext = getVideoExt(url);
|
||
if (!ext) return false;
|
||
return UNSUPPORTED_EXT_ON_IOS.indexOf(ext) !== -1;
|
||
}
|
||
|
||
// 在"视频已经渲染、popup 已经显示"的前提下触发播放。
|
||
// 必须在 uni-popup 的 @change(show=true) 回调里调用,此时 <video> 元素已可见,
|
||
// createVideoContext 才能拿到有效实例。
|
||
// videoId: <video> 的 id;instance: 当前组件/页面实例(组件内必传)。
|
||
export function playVideoInContext(videoId, instance) {
|
||
if (!videoId || !instance) return;
|
||
|
||
// H5 端需要更长延迟:等 uni-popup 动画播完(~350ms),否则浏览器内部会自动
|
||
// pause() 导致 "play() interrupted by pause()" 错误;小程序端 $nextTick 即可。
|
||
// #ifdef H5
|
||
const delay = 400;
|
||
// #endif
|
||
|
||
// #ifndef H5
|
||
const delay = 0;
|
||
// #endif
|
||
|
||
const doPlay = () => {
|
||
let ctx = null;
|
||
try {
|
||
ctx = uni.createVideoContext(videoId, instance);
|
||
} catch (e) {
|
||
console.error('[video-helper] createVideoContext failed:', e);
|
||
return;
|
||
}
|
||
if (!ctx) return;
|
||
|
||
try {
|
||
ctx.play();
|
||
} catch (e) {
|
||
console.error('[video-helper] play() failed, will retry:', e);
|
||
// H5 端首次 play() 常被浏览器自动 pause() 打断,延迟重试一次
|
||
// #ifdef H5
|
||
setTimeout(() => {
|
||
try { ctx.play(); } catch (retryErr) {
|
||
console.error('[video-helper] retry play() also failed:', retryErr);
|
||
}
|
||
}, 300);
|
||
// #endif
|
||
}
|
||
};
|
||
|
||
if (delay > 0) {
|
||
// H5:先等 nextTick(DOM 更新),再等 delay(弹窗动画完成)
|
||
if (typeof instance.$nextTick === 'function') {
|
||
instance.$nextTick(() => setTimeout(doPlay, delay));
|
||
} else {
|
||
setTimeout(doPlay, delay);
|
||
}
|
||
} else {
|
||
// 小程序端:nextTick 即可
|
||
if (typeof instance.$nextTick === 'function') {
|
||
instance.$nextTick(doPlay);
|
||
} else {
|
||
doPlay();
|
||
}
|
||
}
|
||
}
|