项目中涉及主动调用接口控制视频播放器的全屏,但是发现使用 requestFullscreen 接口的方式在 IOS Safari 浏览器中不生效。
调研了下不同 Web 播放器对于全屏的实现方案:
实现方案
实现方案参考了 xgplayer/video.js/DPlayer 等多个播放器,它们实现上大同小异,下面挑选了两个最具代表性的:
xgplayer
西瓜播放器的实现方式比较简单粗暴,他会遍历不同厂商提供的全屏接口并挨个尝试调用:
// 进入全屏
function getFullscreen(el = this.config.fullscreenTarget) {
// ......
const GET_FULLSCREEN_API = ['requestFullscreen', 'webkitRequestFullscreen', 'mozRequestFullScreen', 'msRequestFullscreen']
try {
for (let i = 0; i < GET_FULLSCREEN_API.length; i++) {
const key = GET_FULLSCREEN_API[i]
if (el[key]) {
const ret =
key === 'webkitRequestFullscreen'
? el.webkitRequestFullscreen(window.Element.ALLOW_KEYBOARD_INPUT)
: el[key]()
if (ret && ret.then) {
return ret
} else {
return Promise.resolve()
}
}
}
return Promise.reject(new Error('call getFullscreen fail'))
} catch (err) {
return Promise.reject(new Error('call getFullscreen fail'))
}
}
// 退出全屏
function exitFullscreen(el) {
// ......
const EXIT_FULLSCREEN_API = ['exitFullscreen', 'webkitExitFullscreen', 'mozCancelFullScreen', 'msExitFullscreen']
try {
for (let i = 0; i < EXIT_FULLSCREEN_API.length; i++) {
const key = EXIT_FULLSCREEN_API[i]
if (document[key]) {
const ret = document[key]()
if (ret && ret.then) {
ret.catch(() => {
})
return ret
} else {
return Promise.resolve()
}
}
}
return Promise.reject(new Error('call exitFullscreen fail'))
} catch (err) {
return Promise.reject(new Error('call exitFullscreen fail'))
}
}
video.js v10
video.js v10 版本中全屏只关注了 requestFullscreen 和 webkitRequestFullscreen 两个接口。
另外它调用了 IOS 独有的 webkitSetPresentationMode 接口,
在前两种接口都不支持的情况下尝试让 video 元素进入全屏。
export async function requestFullscreen(container: HTMLElement | null, media: EventTarget) {
const doc = document as WebKitDocument;
if (container && (doc.fullscreenEnabled || doc.webkitFullscreenEnabled)) {
const el = container as WebKitFullscreenElement;
if (isFunction(el.requestFullscreen)) {
return el.requestFullscreen();
}
if (isFunction(el.webkitRequestFullscreen)) {
return el.webkitRequestFullscreen();
}
}
const webkitVideo = media as WebKitVideoElement;
if (isFunction(webkitVideo.webkitSetPresentationMode)) {
webkitVideo.webkitSetPresentationMode('fullscreen');
return;
}
const video = media as unknown as MediaFullscreenCapability;
if (isFunction(video.requestFullscreen)) {
return video.requestFullscreen() as Promise<void>;
}
}
export async function exitFullscreen(media: EventTarget) {
const doc = document as WebKitDocument;
const webkitVideo = media as WebKitVideoElement;
if (webkitVideo.webkitPresentationMode === 'fullscreen' && isFunction(webkitVideo.webkitSetPresentationMode)) {
webkitVideo.webkitSetPresentationMode('inline');
return;
}
if (isFunction(doc.exitFullscreen)) {
return doc.exitFullscreen();
}
if (isFunction(doc.webkitExitFullscreen)) {
return doc.webkitExitFullscreen();
}
const video = media as unknown as MediaFullscreenCapability;
if (isFunction(video.exitFullscreen)) {
return video.exitFullscreen() as Promise<void>;
}
}
注意事项
调用请求全屏的接口存在安全策略,需要瞬态用户激活。
简单来讲就是调用 requestFullscreen 等接口前,需要用户在浏览器上有过 UI 操作,包括这些事件:
- keydown(有些特定按键除外)
- mousedown
- pointerdown
- pointerup
- touchend
不一定需要视频播放器响应这些事件,只需要用户在整个页面上有这些操作即可。如果没有激活,调用接口会异步抛出 TypeError 。