web 视频画中画的探索 —— 【我在加班写前端】
前言
首页上要加一个公司介绍的视频,一开始打算采用将视频上传到优酷,然后获取地址,用 iframe 进行播放(可以直接在视频下方分享处获取到相关的 iframe 代码)。后来采取的方式是将视频放到阿里云的云存储 OSS 上,拿到视频地址,然后直接 h5 video 标签播放。
不得不说,付费的就是强,视频预加载后很快就能播放。相比之前的,直接播放放在自家服务器上的视频(小视频要等一会儿,大视频基本没戏),要顺畅好多。
<video src="https://lejiao1688.oss-cn-hangzhou.aliyuncs.com/xx.mp4" controls="controls" autoplay="autoplay" loop="loop" width="380" poster="images/video_poster.jpg">
您的浏览器不支持 video 标签。
</video>
video 几个基本属性介绍一下:
- controls
- autoplay
- loop
- poster
controls
决定是否展示播放的按钮,autoplay
决定是否在加载后之后自动播放,loop
是否自动循环播放,poster
存放的是未播放时的视频海报,默认取视频第一帧作为海报。
问题
因为视频是固定在首页的靠上的部分,如果在播放过程中上划页面,那么视频就看不到了。想要实现的效果跟很多视频网站一样,播放页上划后,视频进入画中画模式,在桌面(注意,不是浏览器视口内部)右下角会出现一个新的播放页面。
探索
构想:监测页面滑动条滑动,当视频完全离开视口,也就是滑动条下滑距离(页面下滑距离)大于视频标签与文档顶部的距离加上视频标签的高度时,切换视频模式,进入画中画。反之则退出画中画模式。
代码搜寻中:
视频画中画切换 demo
来源:Video视频画中画效果实例页面 - 张鑫旭
点击按钮切换画中画
<video id="video" src="../video/yanyuan.mp4" controls playsinline loop></video>
<div class="layout">
<input type="button" id="pipBtn" class="ui-button ui-button-primary" value="点击进入画中画状态">
<h4>输出log:</h4><a href="javascript:" class="clear" onclick="output.innerHTML='';">清除log</a>
<div id="output" class="output"></div>
<p><small class="description">Tips: 画中画视频尺寸可以拉伸</small></p>
</div>
var pipWindow;
pipBtn.addEventListener('click', function(event) {
log('切换Picture-in-Picture模式...');
// 禁用按钮,防止二次点击
this.disabled = true;
try {
if (video !== document.pictureInPictureElement) {
// 尝试进入画中画模式
video.requestPictureInPicture();
} else {
// 退出画中画
document.exitPictureInPicture();
}
} catch(error) {
log('> 出错了!' + error);
} finally {
this.disabled = false;
}
});
// 点击切换按钮可以触发画中画模式,
// 在有些浏览器中,右键也可以切换,甚至可以自动进入画中画模式
// 因此,一些与状态相关处理需要在专门的监听事件API中执行
video.addEventListener('enterpictureinpicture', function(event) {
log('> 视频已进入Picture-in-Picture模式');
pipBtn.value = pipBtn.value.replace('进入', '退出');
pipWindow = event.pictureInPictureWindow;
log('> 视频窗体尺寸为:'+ pipWindow.width +' x ' + pipWindow.height);
// 添加resize事件,一切都是为了测试API
pipWindow.addEventListener('resize', onPipWindowResize);
});
// 退出画中画模式时候
video.addEventListener('leavepictureinpicture', function(event) {
log('> 视频已退出Picture-in-Picture模式');
pipBtn.value = pipBtn.value.replace('退出', '进入');
// 移除resize事件
pipWindow.removeEventListener('resize', onPipWindowResize);
});
// 视频窗口尺寸改变时候执行的事件
var onPipWindowResize = function (event) {
log('> 窗口尺寸改变为:'+ pipWindow.width +' x ' + pipWindow.height);
}
/* 特征检测 */
if ('pictureInPictureEnabled' in document == false) {
log('当前浏览器不支持视频画中画。');
togglePipButton.disabled = true;
}
// 日志输出
function log(info){
output.innerHTML += info + '<br/>';
}
鼠标滚轮滚动 demo
if (window.addEventListener)//FF,火狐浏览器会识别该方法
window.addEventListener('DOMMouseScroll', wheel, false);
window.onmousewheel = document.onmousewheel = wheel;//W3C
//统一处理滚轮滚动事件
function wheel(event){
var scroll_top = window.document.documentElement.scrollTop;
var video_offset = video.offsetTop + $(video).height();
if (scroll_top > video_offset) {
console.log("该切成画中画了");
} else {
console.log("该切回去了");
}
return;
var delta = 0;
if (!event) event = window.event;
if (event.wheelDelta) {//IE、chrome浏览器使用的是wheelDelta,并且值为“正负120”
delta = event.wheelDelta/120;
if (window.opera) delta = -delta;//因为IE、chrome等向下滚动是负值,FF是正值,为了处理一致性,在此取反处理
} else if (event.detail) {//FF浏览器使用的是detail,其值为“正负3”
delta = -event.detail/3;
}
if (delta)
handle(delta);
}
//上下滚动时的具体处理函数
function handle(delta) {
if (delta <0){//向下滚动
if(scrolltop > 1000) {
console.log("该切成画中画了");
}
}else{//向上滚动
if(scrolltop < 1000) {
console.log("该切回去了");
}
}
}
结合了一下,发现了一个大问题。浏览器会禁止非 user gesture
的行为操作视频。NotAllowedError: Must be handling a user gesture to request picture in picture.
demo 中是通过用户点击按钮来切换视频模式的,我这边鼠标滑动不起作用。伤心!!试了用 js 触发器的法子,还是报相同的错。
js 触发器 demo:
来源:js事件触发器 dispatchEvent() - 博客园
//document 上绑定自定义事件 oneating
document.addEventListener('videoloaded', function (event) {
console.log(event.mingzi+','+event.message);
handleToggle();
// video.play();
}, false);
//创建event的对象实例。
var event = document.createEvent('HTMLEvents');
// 3个参数:事件类型,是否冒泡,是否阻止浏览器的默认行为
event.initEvent("videoloaded", true, true);
/*属性,随便自己定义*/
event.mingzi = 'hello,我是李小贱';
event.message = '我今天24岁';
//触发自定义事件oneating
document.dispatchEvent(event);
试着绑定视频加载事件,回调中主动调用视频播放方法,以此解决视频自动播放被禁止的问题,毫无效果,依然报着类似的错误:Uncaught (in promise) DOMException
。NotAllowedError: play() failed because the user didn't interact with the document first.
使用以下方法,捕捉到了错误信息,跟之前的 try-catch 的结果基本一致。来源:浏览器画中画模式 - 腾讯云社区
const btn = document.querySelector('#toggle')
const vid = document.querySelector('#video')
async function handleToggle() {
try { // 捕获 async-await 错误 ❌
if (vid !== document.pictureInPictureElement) { // 判断当前 PictureInPicture 是否已经开启并是否指向该 video
await vid.requestPictureInPicture() // 调用 API 开启功能 ⭕️
} else {
await document.exitPictureInPicture() // 调用 API 关闭功能 ⛔️
}
this.disabled = true // toggle 按钮避免频繁触发
} catch (error) {
// error handler 错误处理 ❌
} finally {
this.disabled = false
}
}
btn.addEventListener('click', handleToggle)
大体是在 function 前面加了一个 async
以及调用视频方法时,加了一个 await
。js 菜鸟,没搞明白里面的机制。
求帮助。。。