web 视频画中画的探索 —— 【我在加班写前端】

2019-05-24T20:08:00

前言

首页上要加一个公司介绍的视频,一开始打算采用将视频上传到优酷,然后获取地址,用 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('&gt; 出错了!' + error);
    } finally {
        this.disabled = false;
    }
});

// 点击切换按钮可以触发画中画模式,
// 在有些浏览器中,右键也可以切换,甚至可以自动进入画中画模式
// 因此,一些与状态相关处理需要在专门的监听事件API中执行
video.addEventListener('enterpictureinpicture', function(event) {
    log('&gt; 视频已进入Picture-in-Picture模式');
    pipBtn.value = pipBtn.value.replace('进入', '退出');

    pipWindow = event.pictureInPictureWindow;
    log('&gt; 视频窗体尺寸为:'+ pipWindow.width +' x ' + pipWindow.height);

// 添加resize事件,一切都是为了测试API
    pipWindow.addEventListener('resize', onPipWindowResize);
});
// 退出画中画模式时候
video.addEventListener('leavepictureinpicture', function(event) {
    log('&gt; 视频已退出Picture-in-Picture模式');
    pipBtn.value = pipBtn.value.replace('退出', '进入');
// 移除resize事件
    pipWindow.removeEventListener('resize', onPipWindowResize);
});
// 视频窗口尺寸改变时候执行的事件
var onPipWindowResize = function (event) {
    log('&gt; 窗口尺寸改变为:'+ pipWindow.width +' x ' + pipWindow.height);
}
/* 特征检测 */
if ('pictureInPictureEnabled' in document == false) {
    log('当前浏览器不支持视频画中画。');
    togglePipButton.disabled = true;
}

// 日志输出
function log(info){
    output.innerHTML += info + '<br/>';
}

鼠标滚轮滚动 demo

来源:js 鼠标滚轮事件详解 - 非典型性程序员

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 菜鸟,没搞明白里面的机制。

求帮助。。。

当前页面是本站的「Baidu MIP」版。发表评论请点击:完整版 »