最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于video.js的移動端h5視頻播放實現(xiàn)方法示例

 更新時間:2025年12月09日 10:11:05   作者:尼歐neo  
Video.js是最強大的網(wǎng)頁嵌入式HTML?5視頻播放器的組件庫之一,也是大多數(shù)人首選的網(wǎng)頁視頻播放解決方案,這篇文章主要介紹了基于video.js的移動端h5視頻播放實現(xiàn)方法的相關(guān)資料,需要的朋友可以參考下

一、背景

<video>標(biāo)簽是 html5 帶來的特性,提供頁面中視頻播放的功能。然而基礎(chǔ)的<video>標(biāo)簽功能單一,樣式控件在不同端的表現(xiàn)也不一致,所以日常開發(fā)中一般我們會借助第三方封裝好的插件來實現(xiàn)。

在眾多 h5 視頻播放器插件中,video.js 憑借開源、靈活和強大的擴展性,使用較為廣泛。

不過 video.js 的基礎(chǔ)功能比較簡陋,需要依賴各種 api 進行配置,由于官方文檔的 api 非常繁雜(左側(cè)目錄 500+ 個菜單)且是全英文文檔,所以上手比較費勁。

且 video.js 在 pc 端上相對簡單問題不大,但在移動端上會有很多問題踩坑點(移動端特性,非插件問題)。

基于此,本文分享下使用 video.js 插件(v8.23.3)在移動端 h5 項目中的實踐記錄,幫助相關(guān)同學(xué)快速上手。

二、基本使用

1. 引入依賴

npm i video.js -S
npm i @types/video.js -D

2. react中使用

import React, { useEffect, useRef } from 'react';
import videojs from 'video.js';
import 'video.js/dist/video-js.css';
import Player from 'video.js/dist/types/player';
 
export default function VideoPlayer() {
  const playerRef = useRef<null | Player>(null);
 
  useEffect(() => {
    const player = videojs('myVideoJs', { 
      sources: [{ src: 'xxx.mp4', type: 'video/mp4' }] 
    });
    playerRef.current = player;
 
    return () => {
      playerRef.current?.dispose();
      playerRef.current = null;
    }
  }, []);
 
  return (
    <div className="video-player">
      <video-js id="myVideoJs" class="video-js" />
    </div>
  );
}

3. 常見配置

const options = {
  controls: true, // 顯示控件欄
  autoplay: true, // 自動播放
  preload: 'auto', // 視頻預(yù)加載
  fluid: true, // 啟用高度自適應(yīng)模式
  loop: true, // 循環(huán)播放
  muted: true, // 靜音
  poster: null, // 初始播放前展示海報圖
  playsinline: true, // 配置ios不自動全屏
}
  • 需要特別注意的是 playsinline 字段,需要配置為 true 防止 ios 下自動全屏。
    • 背景:playsinline 是 HTML5 <video> 標(biāo)簽的一個屬性,它的作用是 在支持的設(shè)備上,允許視頻“內(nèi)聯(lián)(inline)”播放,即視頻能夠在 網(wǎng)頁中直接播放,而不會進入全屏模式。在很多移動設(shè)備(特別是 iOS 設(shè)備)上,默認情況下,HTML5 視頻會自動切換到全屏模式播放。

三、擴展問題

1. 標(biāo)簽類型定義

<video-js> 標(biāo)簽是 video.js 插件最新推薦的寫法,web component 風(fēng)格的自定義元素,本質(zhì)上也是 <video>標(biāo)簽的封裝。

<video-js>在項目下 ts 檢驗會報類型錯誤,需要顯示定義它的類型。

  • 解決方式:在項目全局類型聲明文件 declare.d.ts 里定義類型
declare namespace JSX {
  interface IntrinsicElements {
    // 描述 <video-js /> 元素的屬性
    'video-js': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & {
      // 如果 Video.js 有額外的自定義屬性,也可以在這里定義
      id?: string;
      class?: string;
      'data-setup'?: string;
    };
  }
}

2. px單位適配

一般項目會配置將 px 單位自動轉(zhuǎn)換為 rem 單位,比如使用px2rem插件,包括 node_modules 里的依賴包也會自動轉(zhuǎn)換,如果 rootValue 是 100,平時開發(fā)都要按兩倍的 px 值來寫,和一般的像素單位不兼容。

video.js 雖然基本都是用 em 單位,但不排除個別情況使用的 px 的情況,所以建議做排除處理。

  • 解決方式:px2rem插件配置黑名單規(guī)則,按類名排除 video.js 插件:
px2rem: {
   rootValue: 100,
   minPixelValue: 2,
   selectorBlackList: ['vjs'],
}

同時,video-js 標(biāo)簽元素的字體大小默認是 0.1em,可能項目下實際表現(xiàn)是按 html 根字體大小來計算的,不符合預(yù)期,會導(dǎo)致視頻控件很小。

  • 解決方式:覆蓋 video-js 標(biāo)簽的樣式,修改字體大小,建議值范圍 10px - 12px,具體代碼寫的值按你項目配置的px2rem轉(zhuǎn)換比例來,我這里2倍就寫20px。
.video-js {
 font-size: 20px;
}

3. 多語言配置

在一些場景,例如視頻資源加載失敗等,video.js 會顯示一些文字提示,默認是英文。

其實 video.js 自帶有多國語言包,已經(jīng)翻譯好了,但是可能是出于打包大小的考量,默認未做引入,只通過 language 字段配置語言也不會生效,還需要手動配置語言包。

  • 解決方式:手動引入語言包,并通過 addLanguage 配置。
import zhCN from 'video.js/dist/lang/zh-CN.json';
import videojs from 'video.js';
 
videojs.addLanguage('zh-CN', zhCN);
 
const options = {
  language: isZh ? 'zh-CN' : 'en-US',
}
const player = videojs('myVideoJs', options);

4. 自定義控件

video.js 的控件欄(controlBar)是自定義實現(xiàn)的,這樣能保證不同終端的控件展示一致性,控件區(qū)域的支持配置各個小部件的展示。

  • 配置示例:
const options = {
  controlBar: {
    children: [
      { name: 'playToggle' }, // 播放按鈕
      { name: 'currentTimeDisplay' }, // 已播時間
      { name: 'progressControl' }, // 播放進度條
      { name: 'durationDisplay' }, // 總時間
      { name: 'volumePanel' }, // 音量控制
      { name: 'playbackRateMenuButton' }, // 倍速播放
      { name: 'fullscreenToggle' }, // 全屏按鈕
    ],
  },
  playbackRates: [0.5, 1, 1.25, 1.5, 2], // 倍速可選項
}
const player = videojs('myVideoJs', options);

異常 case:

有些小部件即使配置了可能也沒顯示出來,不知道是不是 video.js 的 bug,貌似問題存在很久了。

經(jīng)排查,發(fā)現(xiàn)未顯示的小部件 css 樣式是 display:none。

  • 解決方式:通過樣式覆蓋手動修改小部件樣式。
/* 已播時間 */
.vjs-current-time {
  display: block;
}
/* 總時間 */
.vjs-duration {
  display: block;
}

5. 自定義主題

video.js 官網(wǎng)也提供了幾套主題風(fēng)格,主要是初始播放按鈕、控制欄等區(qū)塊的樣式,不過官方主題不適合移動端,我們可以通過自定義主題實現(xiàn)。

主題的使用方式是在組件上添加類名 vjs-theme-xxx 來實現(xiàn)。

自定義主題實際上就是定義 .vjs-theme-xxx 類名下的 css 樣式,本質(zhì)也是樣式覆蓋,封裝為主題主要是方便復(fù)用。

結(jié)合 styled-component 樣式方案的主題實現(xiàn)方式:

  • 樣式定義:vjs-theme-han.ts
import { css } from 'styled-components';
 
const vjsThemeHan = css`
  .vjs-theme-han {
    background: linear-gradient(to right, #fff, #daf3ff);
 
    &.vjs-fullscreen {
      background: rgba(0, 0, 0, 1);
      z-index: 999999;
    }
 
    /* 初始時中間的播放按鈕 */
    .vjs-big-play-button {
      font-size: 4em;
      width: 1em;
      height: 1em;
      line-height: 1em;
      border-radius: 1em;
      background-color: rgba(0, 0, 0, 30%);
      border-width: 0;
      margin-top: -0.5em;
      margin-left: -0.5em;
    }
 
    .vjs-control-bar {
      background-color: rgba(43, 51, 63, 0.3);
    }
    /* 已播時間 */
    .vjs-current-time {
      display: block;
    }
    /* 總時間 */
    .vjs-duration {
      display: block;
    }
  }
`;
 
export default vjsThemeHan;
  • 樣式引入:styles.ts
import styled from 'styled-components';
import vjsThemeHan from './themes/vjs-theme-han';
 
export const StyledComponent = styled.div`
  .playerWrap {
    ${vjsThemeHan};
    max-height: 100vh;
    .video-js {
      overflow: hidden;
      width: 100%;
      height: auto;
      font-size: 20px;
    }
  }
`;
  • 添加類名:index.tsx
<div className="playerWrap">
  <video-js id="myVideoJs" class="video-js vjs-theme-han" />
</div>

6. 全屏播放

默認情況下,video.js 的全屏也是 js 常規(guī)的 全屏 API 實現(xiàn),即 Element.requestFullscreen(),而全屏模式是瀏覽器自己實現(xiàn)的行為,在這種全屏模式就無法顯示自定義的控件欄,而是顯示為瀏覽器自己的默認控件,在安卓和 ios 下的展示也不同。

要想使用我們的自定義控件欄,就不能使用瀏覽器全屏 API,不過我們可以通過 css fixed 布局來模擬全屏效果來實現(xiàn),video.js 的擴展插件 videojs-landscape-fullscreen 就是這個原理。

  • 解決方式:引入擴展插件實現(xiàn)。
tnpm i videojs-landscape-fullscreen -S
const options = {
  plugins: {
    landscapeFullscreen: {
      fullscreen: {
        enterOnRotate: true,
        exitOnRotate: true,
        alwaysInLandscapeMode: true,
        iOS: true,
      },
    },
  }
}
const player = videojs('myVideoJs', options);

異常 case:

在移動端,特別是 ios 下,fixed 定位的表現(xiàn)和其他端不太一致,有些場景 fixed 會表現(xiàn)異常,影響全屏效果,需要特別注意。

例如:在使用 antd-mobile 輪播插件 Swiper 來輪播展示視頻時,ios 下全屏效果異常。

  • 場景 1:父元素(.ant-swiper)有 postion: relative,且 z-index 層級為 0,子元素的 fixed 層級失效。
    • 解決方式:提高此父元素的層級。
.adm-swiper {
  z-index: 99;
}
  • 場景 2:父元素(.adm-swiper-track)有 transform 屬性,也會導(dǎo)致子元素的 fixed 層級失效。
    • 解決方式:將此父元素的 transform 屬性置為 none。
.adm-swiper-horizontal .adm-swiper-track {
  transform: none;
}
    • antd-mobile,Swiper 組件橫向輪播時不需要這個 transform 屬性。

7. 自動橫屏

需求:在點擊視頻全屏播放時自動橫屏展示

js 本身有提供相關(guān) API 來實現(xiàn)設(shè)備橫屏:

  • 進入橫屏:window.screen.orientation?.lock('landscape-primary')
  • 退出橫屏:window.screen.orientation?.unlock()

這套 API 在安卓下能生效,但是在 ios 下不支持

  • 上述全屏插件 videojs-landscape-fullscreen 的配置字段 alwaysInLandscapeMode 本身也是這個實現(xiàn)原理。

解決方式:使用客戶端提供的 jsapi 來實現(xiàn)橫屏。

  • 釘釘端:
// 進入橫屏
dd.rotateScreenView({
  showStatusBar: false,
  clockwise: true,
});
 
// 退出橫屏
dd.resetScreenView({});

監(jiān)聽全屏事件,判斷當(dāng)前狀態(tài)是全屏?xí)r進入橫屏,非全屏?xí)r退出橫屏:

const player = videojs('myVideoJs', options)
 
player.on('fullscreenchange', () => {
  handleFullscreenChange();
});
 
const handleFullscreenChange = () => {
  if (player.isFullscreen()) {
    // 全屏狀態(tài),調(diào)用進入橫屏
  } else {
    // 非全屏狀態(tài),調(diào)用退出橫屏
  }
};

8. 懸浮窗展示

需求:視頻播放器默認在頁面頂部,當(dāng)頁面往上滾動,視頻容器消失時,讓視頻模塊自動以懸浮窗展示

畫中畫模式:

  • 視頻播放 video 標(biāo)簽?zāi)J的畫中畫模式,和全屏模式類似是瀏覽器自己控制的行為,在不同端的表現(xiàn)也不一致,無法自定義,不滿足需求。

實現(xiàn)思路:

  • 懸浮模式,通過將視頻組件置為 fixed 定位來實現(xiàn)。
  • 需要先給視頻組件添加一個靜態(tài)的父容器。
  • 利用 IntersectionObserver 的 API,監(jiān)聽父容器在視窗中是否可見,不可見時視頻組件設(shè)為 fixed 定位懸浮展示,可見時重置為 static 正常展示。
// 監(jiān)聽視頻組件的容器是否進入視窗
useEffect(() => {
  const observer = new IntersectionObserver(
    (entries) => {
      const entry = entries[0];
      if (!entry.isIntersecting) {
        setIsFloat(true);
      } else if (entry.isIntersecting) {
        setIsFloat(false);
      }
    },
    { threshold: 0.1 },
  );
 
  observer.observe(compRef?.current);
 
  return () => {
    observer.disconnect();
  };
}, []);
  • 如果懸浮窗需要添加關(guān)閉按鈕,建議添加兩層父元素,里層父元素作為視頻組件和關(guān)閉按鈕元素的公共容器,作為一個整體來改變其 fixed 定位,外層父元素作為被監(jiān)聽的靜態(tài)元素。
// 外層父容器,position: static;
<div ref={compRef}>
  // 里層容器,position: 動態(tài)改變 relative / fixed;
  <div className="playerWrap">
    // 視頻組件
    <video-js id="myVideoJs" class="video-js vjs-theme-han" />
    // 關(guān)閉按鈕,position: absolute;
    <div className="closeWrap"></div>
  </div>
</div>

父容器高度計算:

  • 要實現(xiàn)絲滑的懸浮切換效果,需要保證視頻父容器的高度固定,這樣在視頻組件 fixed 定位時,父容器也能撐起頁面高度,保持整體高度不變。
  • 一般我們會將視頻組件配置 fluid 高度自適應(yīng),那么父容器的高度就需要動態(tài)計算。
  • 動態(tài)計算方式:監(jiān)聽視頻的初始化加載事件,動態(tài)獲取視頻組件的高度,給父容器設(shè)置。
  • 實際中發(fā)現(xiàn)這個高度不是很精準,可能有微小差距。
  • 建議高度設(shè)置使用 minHeight 代替 height,避免父容器過小時導(dǎo)致視頻顯示不全。
const player = videojs('myVideoJs', options);
 
player.on('loadedmetadata', () => {
  setContainerHeight();
});
 
const setContainerHeight = () => {
  const playerWrapEle: any = document.querySelector('.playerWrap');
  if (compRef.current && playerWrapEle) {
    const height = playerWrapEle.offsetHeight;
    compRef.current.style.minHeight = `${height}px`;
  }
};

控件欄小部件動態(tài)隱藏:

  • 懸浮窗模式下,寬度較小,控制欄小部件可能顯示不下,一些非必要的小部件可以動態(tài)隱藏掉。
  • 通過 player.controlBar.xxx.hide() 實現(xiàn)動態(tài)隱藏。
  • 例如,動態(tài)隱藏 已播時間、總時間、播放速度。
useEffect(() => {
  if (!playerRef.current) return;
  const showOrHide = isFloat ? 'hide' : 'show';
  ['currentTimeDisplay', 'durationDisplay', 'playbackRateMenuButton'].forEach((key) => {
    playerRef.current?.controlBar?.[key]?.[showOrHide]?.();
  });
}, [isFloat]);

9. 自動暫停

需求:頁面不可見時自動暫停視頻,頁面恢復(fù)可見時保持暫停狀態(tài)。

一般情況下即使不做額外處理,在移動端頁面隱藏時視頻也會自動暫停,但頁面重新顯示時也會自動恢復(fù)播放,重點是不想讓他恢復(fù)播放。

在移動端多開 webview 的場景下,做回退操作,在回退到視頻頁 webview 時突然播放視頻,會影響用戶體驗。

核心主要是判斷頁面的顯隱狀態(tài)。

解決方式:

  • 監(jiān)聽 document 的 visibilitychange 事件。
    • document.addEventListener('visibilitychange', handleVisibilityChange, false)
  • 根據(jù) document.visibilityState 值來判斷當(dāng)前頁面所處狀態(tài):
    • visible:顯示
    • hidden:隱藏
  • 頁面隱藏時調(diào)用 player.pause() 暫停視頻。

異常 case:

  • 上述方式在 ios 下有效,但是安卓下不生效。
    • 通過調(diào)試發(fā)現(xiàn),頁面隱藏時正常的 js 都能執(zhí)行,而 player.pause() 像是沒有執(zhí)行一樣。
  • 原因猜測:
    • 安卓頁面隱藏時,WebView 可能凍結(jié)或暫停 UI 渲染線程,以減少后臺消耗,而視頻的渲染控制會可能被丟棄,導(dǎo)致 player.pause() 無法生效。
    • 頁面恢復(fù)時,WebView 恢復(fù)前臺狀態(tài),可能重新調(diào)度視頻播放。
  • 解決方式:
    • 監(jiān)聽到頁面恢復(fù)顯示時,異步調(diào)用視頻暫停方法。
    • 直接調(diào)用也不生效,可能頁面還未完全恢復(fù),通過setTimeout 包裹為異步執(zhí)行,才能達到預(yù)期效果。
// 頁面恢復(fù)顯示時
setTimeout(() => {
  player.pause();
}, 1);

10. 失敗重試機制

需求:視頻加載失敗時自動重新加載。

利用 video.js 自帶的插件擴展方式,手寫個失敗重試插件來引入。

實現(xiàn)要點:

  • 支持配置最大重試次數(shù),避免一直失敗死循環(huán)。
  • 監(jiān)聽加載失敗事件:player.on('error', function () {})
  • 獲取當(dāng)前視頻播放源:player.currentSrc()。
  • 重新加載方法:player.load()

對于切片視頻,會存在中途加載失敗的情況,重試時應(yīng)該從中斷位置繼續(xù)播放。

支持斷點續(xù)播:

  • 失敗時需要記錄中斷時的時間點。
    • 獲取當(dāng)前播放進度時間:player.currentTime()
  • 恢復(fù)時指定時間點播放。
    • 需要在視頻數(shù)據(jù)加載完后才能指定時間播放,監(jiān)聽 loadeddata 事件。
    • 設(shè)置在指定時間點播放視頻:player.currentTime(time)

插件代碼:

  • 實現(xiàn)插件:
/**
 * video.js 視頻加載失敗重試插件
 */
import videojs from 'video.js';
 
export const PLUGIN_NAME = 'retryOnError';
 
export type RetryOnErrorOptions = {
  maxRetryCount?: number; // 最大重試次數(shù)
};
 
const defaultOptions: RetryOnErrorOptions = {
  maxRetryCount: 3,
};
 
const registerPlugin = videojs.registerPlugin || videojs.plugin;
const mergeOptions = videojs.obj.merge || videojs.mergeOptions;
 
const retryOnError = function (this: any, options: any) {
  const player = this as any;
  const { maxRetryCount } = mergeOptions(defaultOptions, options);
  let retryCount = 0;
 
  player.on('error', function () {
    if (retryCount >= maxRetryCount) {
      console.warn('視頻源加載失敗,已達最大重試次數(shù)');
      return;
    }
    const curSrc = player.currentSrc?.();
    if (!curSrc) {
      console.warn('視頻源為空', curSrc);
      return;
    }
 
    const curTime = player.currentTime?.() || 0;
    retryCount++;
    // console.log(`視頻源加載失敗,開始第${retryCount}次重試`);
    player.load();
 
    player.on('loadeddata', function () {
      if (retryCount >= maxRetryCount) {
        return;
      }
      retryCount = 0;
      if (curTime) {
        player.currentTime(curTime);
        player.play();
      }
    });
  });
};
 
registerPlugin(PLUGIN_NAME, retryOnError);
 
export default retryOnError;
  • 使用插件:
import './plugins/videojs-retry-on-error';
 
const options = {
  plugins: {
    retryOnError: {
      maxRetryCount: 3,
    },
  },
}
 
const player = videojs('myVideoJs', options);

11. 非wifi網(wǎng)絡(luò)提醒

需求:判斷用戶所處網(wǎng)絡(luò)狀態(tài),wifi 下自動播放,非 wifi 網(wǎng)絡(luò)下不自動播放,點擊播放時彈窗提醒。

視頻對網(wǎng)絡(luò)流量的損耗比較大,在流量敏感的場景,有必要添加網(wǎng)絡(luò)判斷,以免引發(fā)客戶投訴。

核心要點:

  • 判斷用戶所處的網(wǎng)絡(luò)環(huán)境,區(qū)分 wifi 和 非wifi 場景。
  • video.js 初始化后會在中間展示一個大播放鍵,不自動播放時可手動點擊,需要攔截其點擊事件,然后彈窗提醒。

判斷網(wǎng)絡(luò)環(huán)境:

  • 瀏覽器有提供 API 來判斷網(wǎng)絡(luò)狀態(tài):window.navigator.connection.effectiveType,不過在 ios 下不生效,只能放棄。
  • 要穩(wěn)定判斷,還是得依靠客戶端提供的 API。
    • 釘釘端:
dd.getWifiStatus({}).then((res: any) => {
  const isWifi = res.status === 1;
})

攔截大播放鍵的點擊事件:

  • video.js 的這個大播放鍵并沒有暴漏相關(guān) api 給我們,所以我們沒法攔截它的點擊事件。
    • 當(dāng)然我們可以通過隱藏 video.js 自帶的大播放鍵,然后手動寫一個播放按鈕并添加事件來實現(xiàn)。不過比較麻煩,需要自己實現(xiàn)播放按鈕樣式,并監(jiān)聽 ready 事件然后動態(tài)插入,不建議。
  • 更簡單的處理方式:偷梁換柱。
    • 通過 js 原生的 oldElement.cloneNode(true) 方法來克隆一個大播放鍵 newElement,入?yún)?true 表示只克隆 dom 不克隆綁定事件;
    • 然后通過 oldElement.parentNode.replaceChild(newElement, oldElement) 來替換掉原播放鍵。
    • 再給新的大播放鍵 newElement 添加自定義的點擊事件即可。

封裝為 video.js 插件:

  • 插件代碼:
/**
 * video.js 非wifi網(wǎng)絡(luò)播放提醒插件
 */
import videojs from 'video.js';
import { Dialog, Checkbox } from 'antd-mobile';
import React from 'react';
import { localCache } from '@alipay/fish-utils';
import native from '@/utils/native';
import $i18n from '@/i18n';
 
export const PLUGIN_NAME = 'wifiWarning';
 
export type WifiWarningOptions = {
  open?: boolean; // 是否開啟
  appName: string; // 項目應(yīng)用名(用于 localStorage 存儲時 key 的前綴)
};
 
const defaultOptions: WifiWarningOptions = {
  open: true,
  appName: window.yuyanMonitor?.userConfig?.yuyanId || 'appName',
};
 
const registerPlugin = videojs.registerPlugin || videojs.plugin;
const mergeOptions = videojs.obj.merge || videojs.mergeOptions;
 
let isDontShow = false;
 
const confirmWifi = async (): Promise<boolean> => {
  const contentTips = $i18n.get({
    id: 'wifiWarningTips',
    dm: '您當(dāng)前未連接 WiFi,將會消耗數(shù)據(jù)流量,是否繼續(xù)?',
  });
  const dontShowTips = $i18n.get({
    id: 'wifiDontShowTips',
    dm: '30日內(nèi)不再提醒',
  });
  const confirmText = $i18n.get({
    id: 'continueText',
    dm: '繼續(xù)',
  });
  const cancelText = $i18n.get({
    id: 'cancelText',
    dm: '取消',
  });
 
  const renderContent = () => {
    const onCheckbocChange = (value: boolean) => {
      isDontShow = value;
    };
    return (
      <div className="confirmContent">
        <div className="tipsWrap">{contentTips}</div>
        <div className="formWrap" style={{ paddingTop: '6px' }}>
          <Checkbox
            style={{
              '--icon-size': '16px',
              '--font-size': '14px',
              '--gap': '6px',
            }}
            onChange={onCheckbocChange}
          >
            <span style={{ color: '#999' }}>{dontShowTips}</span>
          </Checkbox>
        </div>
      </div>
    );
  };
 
  return Dialog.confirm({
    content: renderContent(),
    confirmText,
    cancelText,
  });
};
 
const storage = {
  setParams(this: any, mergedOpt: any) {
    const { appName } = mergedOpt;
    const key = `${appName}-wifi-warning`;
    const expires = 30 * 24 * 60 * 60 * 1000;
    this.key = key;
    this.value = 'warned';
    this.expires = expires;
  },
  getVal(this: any) {
    return localCache.get(this.key);
  },
  setVal(this: any) {
    localCache.set(this.key, this.value, { expires: this.expires });
  },
};
 
const handleBigPlayButton = (player: any, mergedOpt: any) => {
  const { open } = mergedOpt;
  storage.setParams(mergedOpt);
 
  const oldEle: HTMLElement = document.querySelector('.vjs-big-play-button');
  if (oldEle) {
    const myBigPlayButton = oldEle.cloneNode(true);
    oldEle.parentNode.replaceChild(myBigPlayButton, oldEle);
 
    myBigPlayButton.addEventListener('click', () => {
      if (open && !storage.getVal()) {
        native.getIsWifi().then((isWifi) => {
          if (!isWifi) {
            confirmWifi().then((confirmed) => {
              if (confirmed) {
                player.play();
 
                if (isDontShow) {
                  storage.setVal();
                }
              }
            });
          }
        });
      } else {
        player.play();
      }
    });
  }
};
 
const wifiWarning = function (this: any, options: any) {
  const player = this as any;
  const mergedOpt = mergeOptions(defaultOptions, options);
  const { open } = mergedOpt;
  if (!open) {
    return;
  }
  native.getIsWifi().then((isWifi) => {
    if (!isWifi) {
      player.autoplay(false);
    }
  });
  player.on('ready', () => {
    handleBigPlayButton(player, mergedOpt);
  });
};
 
registerPlugin(PLUGIN_NAME, wifiWarning);
 
export default wifiWarning;
  • 使用方式:
import './plugins/videojs-wifi-warning.ts';
 
const options = {
  plugins: {
    wifiWarning: {
      open: true,
      appName: 'yourAppName',
    },
  },
}
const player = videojs('myVideoJs', options);

12. 視頻總大小

判斷視頻總的大小有多少 m,可以用在 4g 網(wǎng)絡(luò)時告訴用戶預(yù)計會消耗多少流量,也可以用于清晰度切換時展示預(yù)計消耗的流量等等。

判斷思路:

如果視頻是一次性加載完的單個視頻

可以判斷資源請求時 response 響應(yīng)頭里的 Content-Length 字段,直接獲取。

如果是切片視頻

切片視頻,無法直接通過 Content-Length 獲取。

不過切片視頻資源一般在 response 響應(yīng)頭里添加 Content-Range 字段,里面有總大小的數(shù)據(jù),需要手動截取下。

Content-Range 字段值示例:bytes 425984-52949424/52949425

我們可以手動請求一個字節(jié)的切片數(shù)據(jù),解析 Content-Range 獲取視頻總大小。

async function getVideoFileSizeWithRange(url) {
  try {
    // 使用 Range 請求加載第一個字節(jié)
    const response = await fetch(url, {
      method: 'HEAD', 
      headers: { Range: 'bytes=0-1' }, // 請求文件的第一部分
    });
    const contentRange = response.headers.get('Content-Range');
    
    if (contentRange) {
      // 從 Content-Range 中提取總大小
      const totalSizeMatch = contentRange.match(//(\d+)$/);
      if (totalSizeMatch) {
        const totalSize = parseInt(totalSizeMatch[1], 10);
        console.log('視頻總大?。ㄗ止?jié)):', totalSize);
        return totalSize;
      }
    }
 
    console.log('無法獲取總大小');
    return null;
  } catch (error) {
    console.error('獲取視頻大小失?。?, error);
    return null;
  }
}

四、結(jié)語

總的來說,video.js 是一個非常不錯的社區(qū)插件,功能豐富,通過一定的配置足以滿足我們的日常需求。

利用大模型:

雖然 video.js 的 API 非常繁雜,但是我們有大模型啊,很多功能和實現(xiàn)可以直接去問大模型,它會給你推薦各種實現(xiàn)方案,并提供相關(guān)的 API 代碼,不用我們?nèi)シ俜轿臋n。

這篇文章里的實現(xiàn),至少有 50% 是大模型提供的思路或靈感,代碼很多也是基于大模型生成后修改優(yōu)化的,省下大量腦細胞~

到此這篇關(guān)于基于video.js的移動端h5視頻播放實現(xiàn)方法的文章就介紹到這了,更多相關(guān)video.js移動端h5視頻播放內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論

霍州市| 枣强县| 万山特区| 如东县| 蒙山县| 石棉县| 建水县| 洛隆县| 山西省| 朔州市| 河东区| 北辰区| 缙云县| 屏边| 榆中县| 黄陵县| 临澧县| 大丰市| 通州区| 昌吉市| 慈利县| 鸡泽县| 习水县| 上饶县| 梅河口市| 安庆市| 通化县| 华池县| 鹰潭市| 延川县| 巩义市| 柳林县| 北海市| 平陆县| 凤山县| 庐江县| 泾阳县| 唐河县| 普兰县| 台州市| 深圳市|