• H5打造属于自己的视频播放器(JS篇2)


    回顾

    算了不回顾了 clipboard.png
    直接搞起,打开JS1中写的bvd.js

    播放视频

    1. 播放按钮隐藏

    2. 视频开始播放
      当点击播放按钮的时候,播放按钮将会隐藏,播放视频,这个不难,在JS1中我们就已经实现。但我们改变一下思维,给视频添加点击tap事件,使视频播放,再触发播放事件,从而让播放按钮隐藏

      pro.initEvent = function(){
          var that = this;
      
          //给播放按钮图片添加事件
          this.vimg.addEventListener('tap',function(){
              that.video.play();
          })
      
          //视频点击暂停或播放事件
          this.video.addEventListener('tap',function(){
              if(this.paused || this.ended) {
                  //暂停时点击就播放
                  if(this.ended) {//如果播放完毕,就重头开始播放
                      this.currentTime = 0;
                  }
                  this.play();
              } else {
                  //播放时点击就暂停
                  this.pause();
              }
          })
          
          //视频播放事件
          this.video.addEventListener('play',function(){
              that.vimg.style.display = 'none';
          })
          
          
          //获取到元数据
          this.video.addEventListener('loadedmetadata',function(){
              that.vC.querySelector('.duration').innerHTML = stom(this.duration);
          });
      }
    3. 下方控制条渐渐隐藏
      隐藏并不是难点,重要的是渐渐的隐藏,在这里我们有这么几种解决方案:

      1. 定时器

      2. css3 动画帧

    在这里我们2种结合起来使用clipboard.png

    首先我们先定义好一组动画

    @keyframes vhide {0% {opacity: 1;}100% {opacity: 0;}}
    
    @-webkit-keyframes vhide {0% {opacity: 1;}100% {opacity: 0;}}
    
    .vhidden {
        animation: vhide 3.5s ease-in;
        -webkit-animation: vhide 3.5s ease-in;
    }

    其作用就是透明度3.5秒内1=>0,ease-in 就是 由慢到快 的过度效果。有不懂css动画可以问问度娘哦
    然后我们给视频开始播放事件的时候给控制条添加vhidden样式类

    //视频播放事件
    this.video.addEventListener('play',function(){
        that.vC.classList.add('vhidden');
    })

    测试效果,果然3.5s内,控制条 慢慢透明,问题是3.5s后,透明度又回到了1,这里我讲解一下,是因为动画帧默认是回弹的,我们可以加个样式

    .vhidden {
        animation: vhide 3.5s ease-in;
        -webkit-animation: vhide 3.5s ease-in;
        animation-fill-mode:forwards;
        -webkit-animation-fill-mode: forwards;
    }

    CSS3 属性 animation-fill-mode 用来定义元素在动画结束后的样子。

    animation-fill-mode 的默认值是 none,也就是在动画结束之后不做任何改动,如果把animation-fill-mode 改成 forwards 则动画结束后元素的样式会变成动画最后一个关键帧所规定的样式。

    加上这个样式后,果然3.5s后,动画不再回弹了,但是这里要留意一下,控制条并不是不在了而是透明了,如果这时我们有写控制条的点击时间,那么在控制条位置点击,还是会触发事件,所以呢,我们还可以写上一段setTimeout来,让控制条3.5s后隐藏,这个大家可以自行取舍

    //视频播放事件
    this.video.addEventListener('play',function(){
        that.vimg.style.display = 'none';
        that.vC.classList.add('vhidden');
        that.vCt = setTimeout(function(){
            that.vC.style.visibility = 'hidden';
        },3400);
    })

    为什么动画过程是3.5s,然而js是是3.4s后执行,这里只是在未写animation-fill-mode:forwards的情况下做个保险

    clipboard.png

    正在播放中

    嘿嘿,视频可以播放啦!那么现在我们该考虑一下播放中有哪些事要做呢?

    1. 控制条进度条慢慢增长

    我们需要给视频添加一条timeupdate音视频播放位置发生改变时的事件

    我们先在获取视频元数据事件中,把视频的长度给拿下来

    //获取到元数据
    this.video.addEventListener('loadedmetadata',function(){
        that.vDuration = this.duration;
        that.vC.querySelector('.duration').innerHTML = stom(that.vDuration);
    });

    再从视频播放进度更新事件中计算比例,设置进度条的宽度

    //视频播放中事件
    this.video.addEventListener('timeupdate', function() {
        var currentPos = this.currentTime;//获取当前播放的位置
        //更新进度条
        var percentage = 100 * currentPos / that.vDuration; 
        //设置宽度
        that.vC.querySelector('.timeBar').style.width = percentage + '%';
    });

    clipboard.png

    可以看到我们的进度条君越来越膨胀了。

    2. 当前播放时间变化

    同时,我们的当前播放时间显示也在timeupdate事件中设置

    //视频播放中事件
    this.video.addEventListener('timeupdate', function() {
        var currentPos = this.currentTime;//获取当前播放的位置
        //更新进度条
        var percentage = 100 * currentPos / that.vDuration; 
        that.vC.querySelector('.timeBar').style.width = percentage + '%';
        //更新当前播放时间
        that.vC.querySelector('.current').innerHTML = stom(currentPos);
    });

    clipboard.png

    暂停 or 停止

    当我们点击视频时,如果是暂停,那就开始播放,并触发播放事件,反之视频在播放中,点击视频就会暂停,并触发暂停事件。

    0. 时间定格

    啦啦啦,暂停播放,timeupdate事件自然就不触发啦,所以进度条和当前播放时间就不会变啦。

    1. 播放按钮显示

    在暂停的时候,显示出按钮就行啦

    //暂停or停止
    this.video.addEventListener('pause',function(){
        that.vimg.style.display = 'block';
    });

    2. 下方控制条显示

    控制条显示,直接去除那个vhidden样式类就好啦

    //暂停or停止
    this.video.addEventListener('pause',function(){
        that.vimg.style.display = 'block';
        that.vC.classList.remove('vhidden');
        that.vC.style.visibility = 'visible';
    });

    这样写看样子是没错啦,但是,如果大家在之前隐藏控制条的时候写了setTimeout的话,这个时候就要清除掉哦。

    //暂停or停止
    this.video.addEventListener('pause',function(){
        that.vimg.style.display = 'block';
        that.vC.classList.remove('vhidden');
        that.vC.style.visibility = 'visible'; 
        that.vCt && clearTimeout(that.vCt);
    });

    快进快退

    一个叼叼哒的小视频播放器怎么可能少的了可进可退能屈能伸呢?

    来,我们先为video添加左滑右滑事件

    //视频手势右滑动事件
    this.video.addEventListener('swiperight',function(e){
        this.currentTime += 5;
    });
    //视频手势左滑动事件
    this.video.addEventListener('swipeleft',function(e){
        this.currentTime -= 5;
    });

    可能在电脑上调试会直接进度变0,一开始我也纳闷呢,后来发现手机上webview中好像是可行的。

    关于 进度条拖动改变视频进度 我暂时不打算写,因为我还没写。clipboard.png

    全屏播放

    可能大家会比较关注这个吧:

    ios端:去除video标签webkit-playsinline属性即可,因为ios对h5的video标签支持还是比较不错的

    //调用原生方式 全屏播放
    pro.nativeMax = function(){
        if(!window.plus){
            //非html5+环境
            return;
        }
        if($.os.ios){
            console.log('ios')
            this.video.removeAttribute('webkit-playsinline');
        }else if($.os.android){
            console.log('android');
            var url = this.video.querySelector('source').src;
            var Intent = plus.android.importClass("android.content.Intent");
            var Uri = plus.android.importClass("android.net.Uri");
            var main = plus.android.runtimeMainActivity();
            var intent = new Intent(Intent.ACTION_VIEW);
            var uri = Uri.parse(url);
            intent.setDataAndType(uri, "video/*");
            main.startActivity(intent);
        }
    }

    在initEvent中添加点击 全屏 事件

    this.vC.querySelector('.fill').addEventListener('tap',function(){
        that.nativeMax();
    });

    这样做有点鸡肋啊,就不能来点通用的?

    确实这个问题我想了一晚上,决定再拿点干货来。

    先给个状态,默认为mini播放

    var bvd = function(dom) {
        var that = this;
        $.ready(function() {
            //获取视频元素
            that.video = document.querySelector(dom || 'video');
            //获取视频父元素
            that.vRoom = that.video.parentNode;
            //元素初始化
            that.initEm();
            //事件初始化
            that.initEvent();
            //记录信息
            that.initInfo();
            //当前播放模式 false 为 mini播放
            that.isMax = false;
        })
    }
    
    //记录信息
    pro.initInfo = function() {
        var that = this;
        //在onload状态下,offsetHeight才会获取到正确的值
        window.onload = function(){
            that.miniInfo = {//mini状态时的样式
                 that.video.offsetWidth + 'px',
                height: that.video.offsetHeight + 'px',
                position: that.vRoom.style.position,
                transform: 'translate(0,0) rotate(0deg)'
            }
    
            var info = [
                    document.documentElement.clientWidth || document.body.clientWidth,
                    document.documentElement.clientHeight || document.body.clientHeigth
                ],
                w = info[0],
                h = info[1],
                cha = Math.abs(h - w) / 2;
                
            that.maxInfo = {//max状态时的样式
                 h + 'px',
                height: w + 'px',
                position: 'fixed',
                transform: 'translate(-' + cha + 'px,' + cha + 'px) rotate(90deg)'
            }
        }
        
        
    }
    
    //全屏 mini 两种模式切换
    pro.switch = function() {
        var vR = this.vRoom;
        //获取需要转换的样式信息
        var info = this.isMax ? this.miniInfo : this.maxInfo;
        for(var i in info) {
            vR.style[i] = info[i];
        }
        this.isMax = !this.isMax;
    }
    
    //全屏按钮
    this.vC.querySelector('.fill').addEventListener('tap', function() {
        //that.nativeMax();
        that.switch();
    });

    瞧一瞧拉,看一看拉

    clipboard.png

    看起来感觉很不错呢,利用css3的位移和旋转,让视频全屏在了屏幕前,但是问题也随之而来了

    • 播放按钮 以及 控制条 在全屏下 似乎隐藏了,其实是video标签盖在了父元素之上,我们作出相应的调整

    css

    .bad-video {
        position: relative;
        /*overflow: hidden;*/
        background-color: #CCCCCC;
    }

    js
    max配置当中,设置zIndex值

                that.maxInfo = {//max状态时的样式
                    zIndex:99,
                     h + 'px',
                    height: w + 'px',
                    position: 'fixed',
                    transform: 'translate(-' + cha + 'px,' + cha + 'px) rotate(90deg)'
                }

    clipboard.png

    • 横向全屏后,左右滑动事件没有跟着方向改变

            //视频手势右滑动事件
            this.video.addEventListener('swiperight', function(e) {
                console.log('right');
                this.currentTime += 5;
            });
            //视频手势左滑动事件
            this.video.addEventListener('swipeleft', function(e) {
                console.log('left');
                this.currentTime -= 5;
    
            });

    clipboard.png

    这TM就很尴尬了,难道我全屏后,手机横放,还去上下快进快退?

    这时候怎么办呢,不要方

    手势滑动事件

    我们先给video注册一个事件列表

        var events = {};
        
        //增加 或者删除事件
        pro.eve = function(ename, callback, isF) {
            if(callback && typeof(callback) == 'function') {
                isF && arguments.callee(ename);
                events[ename] = callback;
                this.video.addEventListener(ename, events[ename]);
                console.log('添加事件:' + ename);
                return;
            }
            var fun = events[ename] || function(){};
            this.video.removeEventListener(ename, fun);
            console.log('删除事件:' + ename);
            return fun;
        }

    给video事件添加一个代理来删除添加事件,isF就是在新增这个事件是否删除之前的这个相同的事件,因为添加事件用匿名函数的话,是不能删除的,这样设置一个代理就可以把动态添加的事件记录在events里面,便于操作

    这时我们补上修改当前播放进度和音量的功能

        //跳转视频进度 单位 秒
        pro.setCurrentTime = function(t){
            this.video.currentTime += t;
        }
        //设置音量大小 单位 百分比 如 0.1
        pro.setVolume = function(v){
            this.video.volume+= v;
        }

    再通过代理给video添加左右上下滑动的事件

            //视频手势右滑动事件
            this.eve('swiperight',function(){
                that.setCurrentTime(5);
            });
            
            //视频手势左滑动事件
            this.eve('swipeleft', function(e) {
                that.setCurrentTime(-5);
            });
            
            //视频手势上滑动事件
            this.eve('swipeup',function(){
                that.setVolume(0.2);
            });
            
            //视频手势下滑动事件
            this.eve('swipedown', function(e) {
                that.setCurrentTime(-0.2);
            });

    ok,四个方向的滑动事件已经添加过去了,但这是mini模式播放时的事件,在全屏播放下,四个方向事件并没有跟着video元素方向的改变而改变,这下需要再通过最最最笨的方式判断是否全屏从而触发的事件

            //视频手势右滑动事件
            this.eve('swiperight',function(){
                if(that.isMax){
                    return that.setVolume(0.2);
                }
                that.setCurrentTime(5);
            });
            
            //视频手势左滑动事件
            this.eve('swipeleft', function() {
                if(that.isMax){
                    return that.setVolume(-0.2);
                }
                that.setCurrentTime(-5);
            });
            
            //视频手势上滑动事件
            this.eve('swipeup',function(){
                if(that.isMax){
                    return that.setCurrentTime(-5);    
                }
                that.setVolume(0.2);
            });
            
            //视频手势下滑动事件
            this.eve('swipedown', function() {
                if(that.isMax){
                    return that.setCurrentTime(5);    
                }
                that.setVolume(-0.2);
            });

    怎么样,虽然看起来有点stupid,但是很实用呢

    5+客户端全屏解决方案

    虽说在5+客户端,android可以调用原生的方式播放,但还是差强人意,我们可以再来看一套解决方案

    初始化时,记录mini时的样式,全屏时,通过修改视频宽度为屏幕高度,视频高度修改为视频宽度,再利用5+的屏幕旋转,设置全屏,隐藏状态栏

    0)去除手势事件判断

    因为现在是准备改变移动设备的方向,所以,手势方向会跟着设备方向改变
    

    clipboard.png

    1)去除 css3 旋转以及位移

    
        //记录信息
        pro.initInfo = function() {
            var that = this;
            //在onload状态下,offsetHeight才会获取到正确的值
            window.onload = function() {
                that.miniInfo = { //mini状态时的样式
                    zIndex: 1,
                     that.video.offsetWidth + 'px',
                    height: that.video.offsetHeight + 'px',
                    position: that.vRoom.style.position
                }
    
                that.maxInfo = { //max状态时的样式
                    zIndex: 99,
                     '100%',
                    height: that.sw + 'px',
                    position: 'fixed'
                }
    
            }
    
        }
    

    2)该用5+的设置全屏以及隐藏状态栏

        //全屏 mini 两种模式切换
        pro.switch = function() {
            var vR = this.vRoom;
            //获取需要转换的样式信息
            var info = this.isMax ? this.miniInfo : this.maxInfo;
    
            for(var i in info) {
                vR.style[i] = info[i];
            }
            this.isMax = !this.isMax;
    
            plus.navigator.setFullscreen(this.isMax);
            if(this.isMax) {
                //横屏
                plus.screen.lockOrientation("landscape-primary");
            } else {
                //竖屏
                plus.screen.lockOrientation("portrait-primary");
            }
    
        }

    3)全屏状态下,android端返回键,触发退出全屏

    pro.initEvent = function() {
        //.......省略其他代码
        
        this.oback = $.back;
            //监听安卓返回键
            $.back = function() {
                if(that.isMax) {
                    that.switch();
                    return;
                }
                that.oback();
            }
    }

    效果图
    1.gif

    5+重力感应切换全屏

    嘿嘿,一个在移动端的播放器怎么能少得了 自动切换 横竖屏呢?
    在个小节当中就讲了如何手动切换全屏,接下来重力感应切换横屏,需要用到5+的API Accelerometer 加速度感应

    简单说:重力加速度感应可以想象成一个小球在坐标系中
    三个方向上的加速度。永远以手机屏幕为准

    啥是加速度?额,就是物理书上的

    手机水平放置向上是y轴正向
    向右是x轴正向,向外是z轴正向

    啥是xyz轴?额,就是高数书上的

    哎呀,你把手机竖屏正直的放在地上,你人正直走上去,现在你站在你的手机的屏幕上,然后你的右手打开伸直,这就是x轴,你现在看着前面,这就是y轴,你的头顶就是z轴。这样讲明白了不,但是并不是真的要你踩手机,23333

    您也可以选择查看其他讲解:Android-传感器开发-方向判断

    1. x,y轴变化:

      手机屏幕向上水平放置时: (x,y,z) = (0, 0, -9.81)
      当手机顶部抬起时: y减小,且为负值
      当手机底部抬起时: y增加,且为正值
      当手机右侧抬起时: x减小,且为负值
      当手机左侧抬起时: x增加,且为正值

    2. z轴的变化:
      手机屏幕向上水平放置时,z= -9.81
      手机屏幕竖直放置时, z= 0
      手机屏幕向下水平放置时,z= 9.81

    3. 屏幕横竖切换条件
      y<=-5时, 切换为竖向
      x<=-5时, 换为横向

    ok,我们新增2个方法,用于打开和关闭设备监控

        //开启方向感应
        pro.startWatchAcc = function(){
            var that = this;
            this.watchAccFun = plus.accelerometer.watchAcceleration(function(a) {
                    if(that.getIsMax()){
                        //当前为全屏状态
                        //判断是否满足竖屏Mini状态
                        a.yAxis>=5 && that.setIsMax(false);
                    }else{
                        //当前为Mini状态
                        //判断是否满足全屏Max状态
                        Math.abs(a.xAxis) >=5 && that.setIsMax(true); 
                    }
                }, function(e) {
                    //出错了大不了 不自动旋转呗  让它手动 切换
                    console.log("Acceleration error: " + e.message);
                    that.clearWatchAcc();
                },{
                    frequency:1200
                });
        }
        //关闭方向感应
        pro.clearWatchAcc = function(){
            this.watchAccFun && plus.accelerometer.clearWatch(this.watchAccFun);
        }
    

    然后在初始化的时候默认打开方向监控

        var bvd = function(dom) {
            var that = this;
            $.ready(function() {
                //...
            })
    
            $.plusReady(function() {
                that.startWatchAcc();
            })
    
        }

    再把横向全屏改为,可双向横屏

    clipboard.png

    真机调试看看

    ziong.gif

    嘿嘿,我们再给全屏播放时添加一个锁定按钮,让设备不监控 重力感应,也不响应视频的点击播放暂停事件

    先做一个锁定按钮

    clipboard.png

    当然,锁定图片,地址也改成用base64,最好也用js动态生成标签

    clipboard.png

    设置它的基本样式,靠右,上下垂直居中,默认隐藏

            .lock {
                padding: .3rem;
                 3rem;
                height: 3rem;
                position: absolute;
                right: .5rem;
                top: 50%;
                transform: translateY(-50%);
                -webkit-transform: translateY(-50%);
                visibility: hidden;
            }

    clipboard.png

    好,我们来整理一下逻辑,

    1)默认在mini播放时,lock隐藏
    2)全屏播放时,lock显示,但是也会跟着控制条 在4s内向右隐藏
    3)全屏暂停时,lock也跟着控制条 一直显示
    4)点击lock锁定时,提示已锁定,控制条立即隐藏,lock4s内向右隐藏,视频点击事件更换为显示lock图标,android返回键事件改为不做任何,关闭重力监控
    5)点击lock解锁时,提示已解锁,android返回键改为 切换为mini状态,开启重力监控

    我擦,其实做起来还是挺郁闷的,主要是逻辑处理比较痛苦

    0)添加一个向右移动的动画,3s延迟后 1s内 执行完动画

    @keyframes lockhide {0% {transform: translate(0%,-50%);}100% {transform: translate(120%,-50%);}}
    
    webkit-keyframes lockhide {0% {transform: translate(0%,-50%);}100% {transform: translate(120%,-50%);}}
    
    .lockhidden {
        animation: lockhide 1s 3s linear;
        -webkit-animation: lockhide 1s 3s linear;
        animation-fill-mode:forwards;
        -webkit-animation-fill-mode: forwards;
    }
    

    1)全屏时显示lock

        pro.switch = function() {
            //...
            //全屏时 显示锁定 图标
            this.vlock.style.visibility = this.isMax ? 'visible' : 'hidden';
    
        }

    2)全屏播放时,lock显示,但是也会跟着控制条 在4s内向右隐藏
    我们在播放时添加lock的隐藏动画,

    clipboard.png

    3)全屏暂停时,lock也跟着控制条 一直显示

    clipboard.png

    4)点击lock锁定时,提示已锁定,控制条立即隐藏,lock4s内向右隐藏,视频点击事件更换为显示lock图标,android返回键事件改为不做任何,关闭重力监控
    5)点击lock解锁时,提示已解锁,android返回键改为 切换为mini状态,开启重力监控

        //锁定屏幕
        pro.lockScreen = function() {
            $.toast('锁定屏幕');
            var that = this;
            //更换video点击事件为 显示 lock图标,并保存 video之前的事件 
            this.videoTapFn = this.eve('tap', function() {
                that.lockT = setTimeout(function(){
                    that.vlock.classList.add('lockhidden');
                },500);
                    //重新开始播放样式
                that.vlock.classList.remove('lockhidden');
                that.vlock.style.visibility = 'visible';
            }, true);
            //隐藏控制条
            this.vC.style.visibility = 'hidden';
            //给Lock图标增加 隐藏样式类
            this.vlock.classList.add('lockhidden');
            //锁定屏幕时,不监控重力感应
            this.clearWatchAcc();
            //标识当前更改的Lock状态
            this.isLock = true;
    
        }
    
        //解锁屏幕
        pro.unlockScreen = function() {
            $.toast('解锁屏幕');
            //替换回video之前的点击事件
            this.eve('tap', this.videoTapFn, true);
            //给Lock图标清楚 隐藏样式类
            this.vlock.classList.remove('lockhidden');
            //不锁定屏幕时,监控重力感应
            this.startWatchAcc();
            //标识当前更改的Lock状态
            this.isLock = false;
        }
    

    666)最后给我们亲爱的lock图标增加一枚抚摸事件,以及android返回键的事件更改

            //全屏 时 锁定点击事件
            this.vlock.addEventListener('tap', function() {
                if(that.isLock) {
                    that.unlockScreen();
                    return;
                }
                that.lockScreen();
            });
    
            this.oback = $.back;
            //监听安卓返回键
            $.back = function(){
                if(that.isMax){
                    if(!that.isLock){
                        //全屏状态下 按下返回键 时,1s内不监控重力,防止返回Mini状态时和重力感应并发事件
                        setTimeout(function(){
                            that.startWatchAcc();
                        },1000);
                        that.clearWatchAcc();
                        that.switch();
                    }
                    return;
                }
                that.oback();
            }
        }

    好了!本文5+全屏demo 源码地址

    写博客不易,但是那种分享的心情是很不错的,何尝不是另一种温习和进步呢?

    谢谢各位。

    本文相关文章:H5打造属于自己的视频播放器 专栏

  • 相关阅读:
    Excel导出采用mvc的ExcelResult继承遇到的问题Npoi导出
    Excel导出采用mvc的ExcelResult继承遇到的问题
    word模板导出的几种方式:第三种:标签替换(DocX组件读取与写入Word)
    word模板导出的几种方式:第二种:C#通过模板导出Word(文字,表格,图片) 占位符替换
    word模板导出的几种方式:第一种:占位符替换模板导出(只适用于word中含有表格形式的)
    vue 学习链接地址
    创建作业(JOB)
    html5 浏览文件
    Guava monitor
    Spring Rabbitmq HelloWorld实例
  • 原文地址:https://www.cnblogs.com/10manongit/p/12616555.html
Copyright © 2020-2023  润新知