H5打造屬於本身的視頻播放器(JS篇2)

回顧

算了不回顧了 clipboard.png
直接搞起,打開JS1中寫的bvd.jscss

播放視頻

  1. 播放按鈕隱藏
  2. 視頻開始播放
    當點擊播放按鈕的時候,播放按鈕將會隱藏,播放視頻,這個不難,在JS1中咱們就已經實現。但咱們改變一下思惟,給視頻添加點擊tap事件,使視頻播放,再觸發播放事件,從而讓播放按鈕隱藏html

    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. 下方控制條漸漸隱藏
    隱藏並非難點,重要的是漸漸的隱藏,在這裏咱們有這麼幾種解決方案:html5

    1. 定時器
    2. css3 動畫幀

在這裏咱們2種結合起來使用clipboard.pngandroid

首先咱們先定義好一組動畫ios

@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樣式類css3

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

測試效果,果真3.5s內,控制條 慢慢透明,問題是3.5s後,透明度又回到了1,這裏我講解一下,是由於動畫幀默認是回彈的,咱們能夠加個樣式git

.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 用來定義元素在動畫結束後的樣子。web

animation-fill-mode 的默認值是 none,也就是在動畫結束以後不作任何改動,若是把animation-fill-mode 改爲 forwards 則動畫結束後元素的樣式會變成動畫最後一個關鍵幀所規定的樣式。vim

加上這個樣式後,果真3.5s後,動畫再也不回彈了,可是這裏要留意一下,控制條並非不在了而是透明瞭,若是這時咱們有寫控制條的點擊時間,那麼在控制條位置點擊,仍是會觸發事件,因此呢,咱們還能夠寫上一段setTimeout來,讓控制條3.5s後隱藏,這個你們能夠自行取捨segmentfault

//視頻播放事件
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狀態時的樣式
            width: 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狀態時的樣式
            width: 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,
                width: 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,
                width: that.video.offsetWidth + 'px',
                height: that.video.offsetHeight + 'px',
                position: that.vRoom.style.position
            }

            that.maxInfo = { //max狀態時的樣式
                zIndex: 99,
                width: '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;
            width: 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打造屬於本身的視頻播放器 專欄

相關文章
相關標籤/搜索