1. 程式人生 > >淺談video標籤在移動端的運用

淺談video標籤在移動端的運用

本文首發我的簡書

最近在移動端專案用到了video標籤展示視訊,原本以為作為html5標準一員的video到了今天相容性應該沒什麼問題了,可一用才知道還是有些坑的……那麼在這裡就由淺入深的論述一下使用video的心得吧(一些眾所周知的常用屬性和我沒怎麼用過的在此均省略不表)。

屬性

  • src
    要播放的視訊的 URL。只支援3種格式:MP4、WebM、Ogg。另外src除了可以使用放在伺服器的絕對路徑和專案中的相對路徑(對於本地路徑在PC端有一些限制,移動端貌似無法取到本地視訊),還支援 base64碼(需新增字首data:video/mp4;base64,
    注意有逗號)
  • poster
    規定視訊正在下載時顯示的影象,直到使用者點選播放按鈕。(也就是視訊播放之前顯示的一個預覽圖)其值和img標籤的src屬性相同,填上一個圖片路徑或者base64。
  • playsinline webkit-playsinline
    視訊在移動端播放時會自動全屏,而這個屬性就是為了阻止全屏動作的,新增-webkit-字首增加在ios safari上的相容性。(只用寫上屬性名就行,和autoplay,loop之類的類似)
  • muted
    如果出現該屬性,視訊的音訊輸出為靜音。
  • autoplay
    自動播放,但是在ios上無法執行自動播放……(新增muted
    可以自動播放)需要使用者執行play方法。(具體可參考視訊播放–踩坑小計

以上幾種屬性是寫video標籤時經常設定的幾種屬性,以下的幾種則一般是需要在js中獲取的屬性,以便進行一些操作。

  • networkState
    返回音訊/視訊的當前網路狀態(activity)。
    返回值如下
    • 0 = NETWORK_EMPTY - 音訊/視訊尚未初始化
    • 1 = NETWORK_IDLE - 音訊/視訊是活動的且已選取資源,但並未使用網路
    • 2 = NETWORK_LOADING - 瀏覽器正在下載資料
    • 3 = NETWORK_NO_SOURCE - 未找到音訊/視訊來源

一般用到的就是1和2,當值為1的時候表示視訊已經可以播放了(至少是當前幀已經載入好了,不過ios此時播放可能會白屏),值為2的時候播放下一幀會卡住(安卓)或者白屏(ios)。

注意:
1. 返回的值是`Number`型別,這使得在使用mint-ui這類的ui框架除錯時可能會`Toast`一個空值,需要先轉為字串才能顯示。
2. 關於ios上返回2繼續播放會白屏的問題(僅僅出現在視訊第一次點選播放的時候),這裡我暫時沒發現是什麼原因導致的(有可能是視訊太大??)
  • readyState
    readyState 屬性返回音訊/視訊的當前就緒狀態。
    返回值如下
    • 0 = HAVE_NOTHING - 沒有關於音訊/視訊是否就緒的資訊
    • 1 = HAVE_METADATA - 關於音訊/視訊就緒的元資料
    • 2 = HAVE_CURRENT_DATA - 關於當前播放位置的資料是可用的,但沒有足夠的資料來播放下一幀/毫秒
    • 3 = HAVE_FUTURE_DATA - 當前及至少下一幀的資料是可用的
    • 4 = HAVE_ENOUGH_DATA - 可用資料足以開始播放
      這裡的返回值也是Number型別的

我在專案中先使用的就是這個屬性,在返回1或2的時候代表剛進入這個頁面瀏覽器還在載入視訊,返回4則代表整個視訊已經載入完畢了。可是我發現ios在返回3的時候立即播放還是有時會出現白屏,而安卓在返回3的時候是可以播放的,因此最後我選擇了使用networkState來對視訊載入狀態進行判斷。

  • 不可思議的currentTime
    定義是這麼說的:設定或返回音訊/視訊播放的當前位置(以秒計)。當設定該屬性時,播放會跳躍到指定的位置。
    而我使用這個屬性來判斷視訊是否能連續播放,當視訊播放的時候如果這個屬性的值‘走動了’,可以認為視訊已經可以播放下一幀了。感謝h5 video 移動端填坑記這篇文章提供的方法。

方法

我一般用到的方法也就是play(),pause()這裡不過多贅述。

事件

  • canplay
    當瀏覽器能夠開始播放指定的音訊/視訊時,會發生 canplay 事件。
    乍一看有了這個事件似乎就不需要上面各種state去判斷視訊能否播放了,可惜萬惡的ios**不支援**這個事件,實測第一次進入頁面ios在canplay事件觸發的時候視訊的networkState仍然處於2這個狀態,也就是還未載入完成……
  • progress
    當瀏覽器正在下載指定的音訊/視訊時,會發生 progress 事件。
    我使用這個事件的方向可能有點‘歪門邪道’,眾所周知一個視訊在頁面載入的時候等待時間或許會有點長,一般網站使用的是一個圖片或者gif去代替video標籤,當視訊載入好了的時候就讓video顯示出來。而上面的canplay用不了,所以我抱著試試的想法在progress事件觸發的時候讓loading圖隱藏起來(loading圖預設顯示),結果實際效果讓我很滿意,progress事件觸發的時候視訊的poster已經顯示出來了,我初步判斷第一次觸發這個事件應該是視訊第一幀可能前幾幀都載入好了。
  • timeupdate
    timeupdate 事件在音訊/視訊(audio/video)的播放位置發生改變時觸發。
    這個事件一看就是結合上面的currentTime屬性用的。在後文我會詳述。
  • waiting
    waiting 事件在視訊由於需要緩衝下一幀而停止時觸發。
    在我的專案用它主要是因為ios白屏的時候會觸發這個事件……這樣我可以在這個事件中讓視訊暫停。
  • ended
    ended 事件在音訊/視訊(audio/video)播放完成後觸發。
    由於我的視訊沒有用loop屬性所以我使用這個事件來提示使用者視訊播放結束。

應用

專案基於vue,ui框架使用的是mint-ui,還是直接上程式碼吧…

    <div class="video-con fl">
        <div class="video" @click="$_videoFromApp_getVideoRecord">
            <!-- 後臺沒有視訊時顯示的內容 -->
            <img class="no-video" v-show="videoFromApp_noVideo" :src="require('@/images/icon/video.png')" alt="">
            <div class="bg-gray" v-show="videoFromApp_noVideo"></div>
            <!-- 視訊處於暫停時顯示的內容 -->
            <div v-show="!videoFromApp_playing">
                <img class="play-video" v-show="!videoFromApp_noVideo"
                     :src="require('@/images/icon/play-video.png')" alt="">
                <span class="play-time" v-show="!videoFromApp_noVideo">{{videoFromApp_videoTime}}</span>
            </div>
            <!-- loading -->
            <mt-spinner v-show="videoFromApp_loading" class="loading-css" type="fading-circle"></mt-spinner>
            <!-- 視訊 -->
            <video webkit-playsinline playsinline
                   ref="indentVideo"
                   class="real-video"
                   :src="videoFromApp_videoSrc"
                   v-show="!videoFromApp_noVideo"
                   :poster="videoFromApp_videoImg"
                   @progress="$_videoFromApp_hasVideo"
                   @waiting="$_videoFromApp_waiting"
                   @ended="$_videoFromApp_endVideo"></video>
        </div>
    </div>

js部分

<script>
......
        //播放視訊
        $_videoFromApp_getVideoRecord(v){
           ......
            if (this.videoFromApp_playing) {
                    this.videoFromApp_playing = false;
                    this.$refs.indentVideo.pause();
                } else {
                    this.videoFromApp_playing = true;
                    let video = this.$refs.indentVideo;
                    let networkState = this.$refs.indentVideo.networkState;
                    let readyState = this.$refs.indentVideo.readyState;
                    if(networkState==1){
                        this.$refs.indentVideo.play();
                        this.videoFromApp_loading = true;
                        video.ontimeupdate = ()=>{
                            if(video.currentTime > 0.1){
                                this.videoFromApp_loading = false;
                            }
                        }
                    }else{
                        Toast({
                            message: '拼命載入中,請稍後',
                            position: 'bottom',
                            duration: 1000
                        });
                        this.videoFromApp_playing = false;
                    }
                }
        },
        //視訊初步載入
        $_videoFromApp_hasVideo(){
            this.videoFromApp_loading = false;
        },
        //緩衝
        $_videoFromApp_waiting(){
            this.$refs.indentVideo.pause();
            this.videoFromApp_playing = false;
            Toast({
                message: '拼命載入中,請稍後',
                position: 'bottom',
                duration: 1000
            });
        },
        //視訊播放完成
        $_videoFromApp_endVideo(){
            this.videoFromApp_playing = false;
        },
</script>

在此就簡述一下播放視訊方法,當視訊處於暫停狀態時,點選播放,判斷networkState,當值為1的時候允許播放,此時監聽timeupdate事件,(使用addEventListener監聽會有毛病,如果看過我之前的一篇關於iframe的文章應該會有所瞭解)先加上loading,如果currentTime > 0.1代表視訊已經能流暢播放了,再隱藏loading。這個過程中如果無法播放的話就會走到waitting事件中,視訊會暫停。
再次點選繼續重複這個過程直到視訊可以正常播放。

寫在後面

其實吧本文寫作的真正目的是為了拋磚引玉,文中所寫的一些方法只為解決專案中的燃眉之急,文中部分內容僅僅為個人觀點,如果各位讀者發現了文中的缺陷與問題,歡迎在評論區留言探討。

本文參考文章
視訊播放–踩坑小計
h5 video 移動端填坑記
移動端實踐 - video