如何實現兼容 PC 和微信 H5 的全屏播放小視頻

對於這個問題,其實網上已經有一些比較好的實踐,但有時候並不明白爲何這樣配置;若是你想要知道一個弱小,可憐又無助的 <video> 如何變成豐滿健壯的視頻播放器,那就往下看吧!css

需求

來問H5 醫生端(PC & 微信)問題詳情頁面:html

  • 支持點擊播放按鈕喚起播放器播放視頻
  • 播放過程當中,支持暫停視頻、關閉視頻、拖動進度條調整視頻進度
  • 不區分是否處於 WiFi 環境下,需手動點擊播放

踩坑之路

第一階段

方案

  1. 使用HTML5中 <video> 標籤進行視頻播放
  2. 僅設置 src 屬性

  1. 未播放時 DOM 寬高樣式正常;但無封面,並未截取視頻第一幀做爲封面
  2. 點擊播放後會調起系統默認播放器進行全屏播放,播放器初始界面五花八門
  3. 安卓視頻播放退出後 DOM 樣式錯亂,會無視 css 使用視頻源寬高顯示
  4. 安卓在視頻播放完後會追加視頻推薦,而且白名單申請入口已經關閉

第二階段

方案

  1. 增長 poster 屬性,值爲後端根據視頻第一幀生成的封面地址
  2. 增長遮罩層及圖標樣式模擬播放器初始界面
  3. <video> 標籤使用 <div> 容器包裹,並設置 display: none隱藏,用戶點擊封面時調用 videoplay() 方法進行播放
  4. 騰訊大佬強制顯示視頻推薦,因此暫時不作處理

  1. 隱藏 <video> 元素後微信中調用play()無反應
  2. PC 端只有聲音沒有圖像。這是由於 PC 端不會打開專門的播放器,只會在 DOM 節點處直接播放,此時 DOM 節點未設置顯示區域
  3. 只是要播放視頻,響應的是 video.play() 方法,並不表明已經開始播放(會有一段緩衝過程),用戶會誤覺得點擊無效

第三階段

方案

  1. display: none; 或者 width: 0; height: 0; 方式隱藏視頻時,元素處於未激活狀態,不響應 play() 方法,因此咱們設置寬高爲1px
  2. 設置 flag 判斷環境,若在 PC 環境中,播放後將視頻容器拓展爲全屏大小,並增長關閉按鈕,點擊後暫停視頻並移除拓展樣式
  3. 點擊封面時增長一個 loading 效果,PC 環境在視頻播放時取消;微信環境則在視頻暫停時取消。(在 iOS 中 play() 方法會觸發播放事件,但播放器此時並未打開,而全屏播放中暫停視頻並不會退出播放器。因此咱們能夠在視頻暫停時取消 loading);

  1. iPad 及 windowsPC 版微信中一樣不會新開播放器,而是直接在video標籤處播放,致使有聲音無圖像;iPad 中經過修改 PC 的判斷條件能夠解決
  2. 有些安卓設備沒法播放,須要安裝 QQ 瀏覽器或 QQ 視頻播放插件才能夠,不過仍有一部分用戶沒法安裝該插件或安裝後仍是無效

第四階段:「最佳實踐」

此時再使用原生 video 標籤事件和屬性,已經沒辦法進一步突破以上的這些坑,解決千差萬別的兼容性問題了。所以,咱們參考了其餘的方案實現了預期的效果。vue

方案

PS:項目是基於Vue & scss,但該功能能夠不依賴這些實現web

  1. 使用各種兼容屬性以及 x5 內核瀏覽器的擴展屬性 webkit-playsinline, playsinline, x-webkit-airplay, x5-video-player-type, x5-video-player-fullscreen, x5-video-orientation 等解決不一樣類型設備的播放差別;
  2. 設置 flag 代表是否正在播放或正在全屏狀態中
<!-- 視頻容器 -->
<div class="video" :class="{'full-screen': isFullScreen}">
  <!-- 視頻主體 -->
  <div class="video-content">
    <video :controls="isFullScreen" :style="isFullScreen ? {} : img.style" :class="img.isVertical ? 'vertical-img' : 'horizontal-img'" :src="img.url" preload="metadata" :poster="img.preview_pic_url" :ref="`video${img.id}`" webkit-playsinline="true" playsinline="true" x-webkit-airplay="allow" x5-video-player-type="h5" x5-video-player-fullscreen="true" x5-video-orientation="portraint">
      抱歉,您的瀏覽器不支持內嵌視頻!
    </video>
  </div>
  <!-- 遮罩層,顯示播放按鈕;僅在待播放狀態顯示 -->
  <div v-if="!isFullScreen" class="video-mask">
    <div>
      <img src="~images/play.png" />
    </div>
  </div>
  <!-- 全屏控制按鈕;僅在非安卓高版本內核中顯示 -->
  <div v-else-if="!inHighTBS" class="video-controls">
    <span class="video-controls-close" @click.stop="handleVideoControls('close')">
      &times;
    </span>
  </div>
</div>
複製代碼

屬性說明windows

  1. controls: 經過 flag 設置僅在播放時出現,避免初始播放狀態不一樣
  2. style:對待播放 dom 進行絕對定位計算視頻偏移量;視頻顯示區爲正方形窗口,所以要橫向及縱向視頻顯示區都在正中間
  3. class:設定橫向視頻高度或縱向視頻寬度
  4. src:視頻源
  5. preload:值爲預加載但不阻塞;每一個問題最多僅有一個視頻,保證用戶點擊播放後當即響應,而且不阻塞其餘圖片附件的渲染
  6. poster:封面地址
  7. ref:在vue中獲取並操做 video 元素
  8. webkit-playsinline:IOS 10中設置有效,視頻播放時局域播放,不脫離文檔流;能夠保證播放界面與PC端一致
  9. playsinline:IOS 微信瀏覽器支持小窗內播放,和上一個屬性一塊兒食用可兼容幾乎全部IOS設備
  10. x5-video-player-type:啓用H5同層播放器,是 wechat 安卓版特性
  11. x5-video-player-fullscreen:視頻播放時將會進入到全屏模式,若不設置仍是會新開播放器,但尺寸爲原始視口大小(視頻未播放前)
  12. x5-video-orientation:控制橫豎屏
/* 外層還有其餘已定位容器 */
  .video {
    position: absolute;
    top: 0;
    bottom: 0;
    right: 0;
    left: 0;
    transition: all 0.3s;
    background-color: rgba(0, 0, 0, 0.5);
    &.full-screen {
      position: fixed;
      z-index: 99;
      .video-content {
        width: 100%;
        height: 100%;
        video {
          position: initial;
          &.vertical-img {
            height: 100%;
            width: auto;
            margin: 0 auto;
          }
          &.horizontal-img {
            width: 100%;
            height: auto;
            max-height: 100%;
          }
        }
      }
    }
    &-mask {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      display: flex;
      align-items: center;
      background-color: rgba(0, 0, 0, 0.5);
      div {
        width: 100%;
        text-align: center;
        img {
          width: 30%;
          position: inherit;
        }
      }
    }
    &-controls {
      position: absolute;
      right: 5%;
      top: 5%;
      display: flex;
      align-items: center;
      z-index: 1;
      &-close {
        width: 50px;
        height: 50px;
        line-height: 50px;
        color: rgba(255, 255, 255, 0.7);
        background: rgba(0, 0, 0, 0.3);
        text-align: center;
        border-radius: 50%;
        font-size: 2rem;
        cursor: pointer;
        transition: all 0.3s;
        &:hover {
          background: rgba(0, 0, 0, 0.5);
          color: #fff;
        }
      }
    }
    &-content {
      max-width: 768px; // 限制PC端不超過768px;如要PC全屏可不作設置
      margin: 0 auto;
      display: flex;
      align-items: center;
      video {
        display: block;
        position: absolute;
        object-fit: fill;
      }
    }
  }
  .horizontal-img {
    height: 100%;
    top: 0;
  }
  .vertical-img {
    width: 100%;
    left: 0;
  }
複製代碼

小問題

在安卓微信中,就算加上了上面的屬性,還會出現上下有黑邊,不能全屏。解決給<video>加上object-fit: fill;的樣式便可。後端

修改上線後之前報Bug的用戶紛紛反饋好了,沒問題,但...瀏覽器

還報Bug?

有一個用戶反饋使用錘子的堅果Pro點擊視頻無反應,找來同型號測試機一番騷操做後...愣是沒復現!緩存

以後經過和用戶不斷溝通發現該設備上竟然未使用X5內核瀏覽器(使用微信打開debugtbs.qq.com可調試X5內核,未安裝會有提示)微信

所以在下面一個安卓兼容性事件判斷上報錯了,使用try { } catch (e) {}包一下,一樣能夠正常播放,但這是的播放效果已沒法統一。dom

安卓事件兼容
// 高版本微信安卓環境下會自動加上返回按鈕而且點擊觸發退出全屏事件
// 需作未使用X5內核容錯處理
inHighTBS() {
  if (inAndroid) {
      try {
         const [, currentTbsVersion] = window.navigator.userAgent.match(/TBS\/(\d+)/);

        return currentTbsVersion > '036900';
      } catch() {
          return false;
      }
  } else {
    return false;
  }
}

// 安卓環境中會啓用同層H5播放器,跳轉新窗口,所以監聽x5videoexitfullscreen事件可獲取狀態
// https://x5.tencent.com/tbs/guide/video.html
this.inHighTBS && vDom.addEventListener('x5videoexitfullscreen', () => {
  this.isFullScreen = false;
});
複製代碼

附錄

video原生支持事件

const mediaProperties = [
  'loadstart',  // 在媒體開始加載時觸發。
  'progress',   // 告知媒體相關部分的下載進度時週期性地觸發。有關媒體當前已下載總計的信息能夠在元素的buffered屬性中獲取到。
  'suspend',    // 在媒體資源加載終止時觸發,這多是由於下載已完成或由於其餘緣由暫停。
  'abort',  // 在播放被終止時觸發,例如, 當播放中的視頻從新開始播放時會觸發這個事件。
  'error',  // 在發生錯誤時觸發。元素的error屬性會包含更多信息。參閱Error handling得到詳細信息。
  'emptied',    // 媒體被清空(初始化)時觸發。
  'stalled',    // 在嘗試獲取媒體數據,但數據不可用時觸發。
  'loadedmetadata', // 媒體的元數據已經加載完畢,如今全部的屬性包含了它們應有的有效信息。
  'loadeddata', // 媒體的第一幀已經加載完畢。
  'canplay',    // 在媒體數據已經有足夠的數據(至少播放數幀)可供播放時觸發。這個事件對應CAN_PLAY的readyState。
  'canplaythrough', // 在媒體的readyState變爲CAN_PLAY_THROUGH時觸發,代表媒體能夠在保持當前的下載速度的狀況下不被中斷地播放完畢。注意:手動設置currentTime會使得firefox觸發一次canplaythrough事件,其餘瀏覽器或許不會如此。
  'playing',    // 在媒體開始播放時觸發(不管是初次播放、在暫停後恢復、或是在結束後從新開始)。
  'waiting',    // 在一個待執行的操做(如回放)因等待另外一個操做(如跳躍或下載)被延遲時觸發。
  'seeking',    // 在跳躍操做開始時觸發。
  'seeked', // 在跳躍操做完成時觸發。
  'ended',  // 播放結束時觸發。
  'durationchange', // 元信息已載入或已改變,代表媒體的長度發生了改變。例如,在媒體已被加載足夠的長度從而得知總長度時會觸發這個事件。
  'timeupdate', // 元素的currentTime屬性表示的時間已經改變。
  'play',   // 在媒體回放被暫停後再次開始時觸發。即,在一次暫停事件後恢復媒體回放。
  'pause',  // 播放暫停時觸發。
  'ratechange', // 在回放速率變化時觸發。
  'resize',
  'volumechange',   // 在音頻音量改變時觸發(既能夠是volume屬性改變,也能夠是muted屬性改變).。
  'mozaudioavailable'   // 當音頻數據緩存並交給音頻層處理時
 ];

mediaProperties.forEach(item => {
  vDom.addEventListener(item, e => console.log(item));
});
複製代碼

參考:

做者:丁香醫生團隊 顧重

相關文章
相關標籤/搜索