基于video.js的移動端h5視頻播放實(shí)現(xiàn)方法示例
一、背景
<video>標(biāo)簽是 html5 帶來的特性,提供頁面中視頻播放的功能。然而基礎(chǔ)的<video>標(biāo)簽功能單一,樣式控件在不同端的表現(xiàn)也不一致,所以日常開發(fā)中一般我們會借助第三方封裝好的插件來實(shí)現(xiàn)。
在眾多 h5 視頻播放器插件中,video.js 憑借開源、靈活和強(qiáng)大的擴(kuò)展性,使用較為廣泛。
- 官網(wǎng):https://videojs.com/
- 官方 API 文檔:https://docs.videojs.com/
不過 video.js 的基礎(chǔ)功能比較簡陋,需要依賴各種 api 進(jìn)行配置,由于官方文檔的 api 非常繁雜(左側(cè)目錄 500+ 個菜單)且是全英文文檔,所以上手比較費(fèi)勁。
且 video.js 在 pc 端上相對簡單問題不大,但在移動端上會有很多問題踩坑點(diǎn)(移動端特性,非插件問題)。
基于此,本文分享下使用 video.js 插件(v8.23.3)在移動端 h5 項目中的實(shí)踐記錄,幫助相關(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)頁中直接播放,而不會進(jìn)入全屏模式。在很多移動設(shè)備(特別是 iOS 設(shè)備)上,默認(rèn)情況下,HTML5 視頻會自動切換到全屏模式播放。
三、擴(kuò)展問題
1. 標(biāo)簽類型定義
<video-js> 標(biāo)簽是 video.js 插件最新推薦的寫法,web component 風(fēng)格的自定義元素,本質(zhì)上也是 <video>標(biāo)簽的封裝。
<video-js>在項目下 ts 檢驗(yàn)會報類型錯誤,需要顯示定義它的類型。

- 解決方式:在項目全局類型聲明文件 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)簽元素的字體大小默認(rèn)是 0.1em,可能項目下實(shí)際表現(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 會顯示一些文字提示,默認(rèn)是英文。
其實(shí) video.js 自帶有多國語言包,已經(jīng)翻譯好了,但是可能是出于打包大小的考量,默認(rèn)未做引入,只通過 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)是自定義實(shí)現(xiàn)的,這樣能保證不同終端的控件展示一致性,控件區(qū)域的支持配置各個小部件的展示。
- 配置示例:
const options = {
controlBar: {
children: [
{ name: 'playToggle' }, // 播放按鈕
{ name: 'currentTimeDisplay' }, // 已播時間
{ name: 'progressControl' }, // 播放進(jìn)度條
{ 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ū)塊的樣式,不過官方主題不適合移動端,我們可以通過自定義主題實(shí)現(xiàn)。
主題的使用方式是在組件上添加類名 vjs-theme-xxx 來實(shí)現(xiàn)。
自定義主題實(shí)際上就是定義 .vjs-theme-xxx 類名下的 css 樣式,本質(zhì)也是樣式覆蓋,封裝為主題主要是方便復(fù)用。
結(jié)合 styled-component 樣式方案的主題實(shí)現(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. 全屏播放
默認(rèn)情況下,video.js 的全屏也是 js 常規(guī)的 全屏 API 實(shí)現(xiàn),即 Element.requestFullscreen(),而全屏模式是瀏覽器自己實(shí)現(xiàn)的行為,在這種全屏模式就無法顯示自定義的控件欄,而是顯示為瀏覽器自己的默認(rèn)控件,在安卓和 ios 下的展示也不同。
要想使用我們的自定義控件欄,就不能使用瀏覽器全屏 API,不過我們可以通過 css fixed 布局來模擬全屏效果來實(shí)現(xiàn),video.js 的擴(kuò)展插件 videojs-landscape-fullscreen 就是這個原理。
- 解決方式:引入擴(kuò)展插件實(shí)現(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. 自動橫屏
需求:在點(diǎn)擊視頻全屏播放時自動橫屏展示。
js 本身有提供相關(guān) API 來實(shí)現(xiàn)設(shè)備橫屏:
- 進(jìn)入橫屏:window.screen.orientation?.lock('landscape-primary')
- 退出橫屏:window.screen.orientation?.unlock()
這套 API 在安卓下能生效,但是在 ios 下不支持。
- 上述全屏插件 videojs-landscape-fullscreen 的配置字段 alwaysInLandscapeMode 本身也是這個實(shí)現(xiàn)原理。
解決方式:使用客戶端提供的 jsapi 來實(shí)現(xiàn)橫屏。
- 釘釘端:
// 進(jìn)入橫屏
dd.rotateScreenView({
showStatusBar: false,
clockwise: true,
});
// 退出橫屏
dd.resetScreenView({});監(jiān)聽全屏事件,判斷當(dāng)前狀態(tài)是全屏?xí)r進(jìn)入橫屏,非全屏?xí)r退出橫屏:
const player = videojs('myVideoJs', options)
player.on('fullscreenchange', () => {
handleFullscreenChange();
});
const handleFullscreenChange = () => {
if (player.isFullscreen()) {
// 全屏狀態(tài),調(diào)用進(jìn)入橫屏
} else {
// 非全屏狀態(tài),調(diào)用退出橫屏
}
};8. 懸浮窗展示
需求:視頻播放器默認(rèn)在頁面頂部,當(dāng)頁面往上滾動,視頻容器消失時,讓視頻模塊自動以懸浮窗展示。
畫中畫模式:
- 視頻播放 video 標(biāo)簽?zāi)J(rèn)的畫中畫模式,和全屏模式類似是瀏覽器自己控制的行為,在不同端的表現(xiàn)也不一致,無法自定義,不滿足需求。
實(shí)現(xiàn)思路:
- 懸浮模式,通過將視頻組件置為 fixed 定位來實(shí)現(xiàn)。
- 需要先給視頻組件添加一個靜態(tài)的父容器。
- 利用 IntersectionObserver 的 API,監(jiān)聽父容器在視窗中是否可見,不可見時視頻組件設(shè)為 fixed 定位懸浮展示,可見時重置為 static 正常展示。
// 監(jiān)聽視頻組件的容器是否進(jì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>父容器高度計算:
- 要實(shí)現(xiàn)絲滑的懸浮切換效果,需要保證視頻父容器的高度固定,這樣在視頻組件 fixed 定位時,父容器也能撐起頁面高度,保持整體高度不變。
- 一般我們會將視頻組件配置 fluid 高度自適應(yīng),那么父容器的高度就需要動態(tài)計算。
- 動態(tài)計算方式:監(jiān)聽視頻的初始化加載事件,動態(tài)獲取視頻組件的高度,給父容器設(shè)置。
- 實(shí)際中發(fā)現(xiàn)這個高度不是很精準(zhǔ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() 實(shí)現(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ù)播放,重點(diǎn)是不想讓他恢復(fù)播放。
在移動端多開 webview 的場景下,做回退操作,在回退到視頻頁 webview 時突然播放視頻,會影響用戶體驗(yàn)。
核心主要是判斷頁面的顯隱狀態(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í)行,才能達(dá)到預(yù)期效果。
// 頁面恢復(fù)顯示時
setTimeout(() => {
player.pause();
}, 1);10. 失敗重試機(jī)制
需求:視頻加載失敗時自動重新加載。
利用 video.js 自帶的插件擴(kuò)展方式,手寫個失敗重試插件來引入。
實(shí)現(xiàn)要點(diǎn):
- 支持配置最大重試次數(shù),避免一直失敗死循環(huán)。
- 監(jiān)聽加載失敗事件:player.on('error', function () {})
- 獲取當(dāng)前視頻播放源:player.currentSrc()。
- 重新加載方法:player.load()
對于切片視頻,會存在中途加載失敗的情況,重試時應(yīng)該從中斷位置繼續(xù)播放。
支持?jǐn)帱c(diǎn)續(xù)播:
- 失敗時需要記錄中斷時的時間點(diǎn)。
- 獲取當(dāng)前播放進(jìn)度時間:player.currentTime()
- 恢復(fù)時指定時間點(diǎn)播放。
- 需要在視頻數(shù)據(jù)加載完后才能指定時間播放,監(jiān)聽 loadeddata 事件。
- 設(shè)置在指定時間點(diǎn)播放視頻:player.currentTime(time)
插件代碼:
- 實(shí)現(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('視頻源加載失敗,已達(dá)最大重試次數(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ò)下不自動播放,點(diǎn)擊播放時彈窗提醒。
視頻對網(wǎng)絡(luò)流量的損耗比較大,在流量敏感的場景,有必要添加網(wǎng)絡(luò)判斷,以免引發(fā)客戶投訴。
核心要點(diǎn):
- 判斷用戶所處的網(wǎng)絡(luò)環(huán)境,區(qū)分 wifi 和 非wifi 場景。
- video.js 初始化后會在中間展示一個大播放鍵,不自動播放時可手動點(diǎn)擊,需要攔截其點(diǎn)擊事件,然后彈窗提醒。
判斷網(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;
})攔截大播放鍵的點(diǎn)擊事件:
- video.js 的這個大播放鍵并沒有暴漏相關(guān) api 給我們,所以我們沒法攔截它的點(diǎn)擊事件。
- 當(dāng)然我們可以通過隱藏 video.js 自帶的大播放鍵,然后手動寫一個播放按鈕并添加事件來實(shí)現(xiàn)。不過比較麻煩,需要自己實(shí)現(xiàn)播放按鈕樣式,并監(jiān)聽 ready 事件然后動態(tài)插入,不建議。
- 更簡單的處理方式:偷梁換柱。
- 通過 js 原生的 oldElement.cloneNode(true) 方法來克隆一個大播放鍵 newElement,入?yún)?true 表示只克隆 dom 不克隆綁定事件;
- 然后通過 oldElement.parentNode.replaceChild(newElement, oldElement) 來替換掉原播放鍵。
- 再給新的大播放鍵 newElement 添加自定義的點(diǎn)擊事件即可。
封裝為 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 非常繁雜,但是我們有大模型啊,很多功能和實(shí)現(xiàn)可以直接去問大模型,它會給你推薦各種實(shí)現(xiàn)方案,并提供相關(guān)的 API 代碼,不用我們?nèi)シ俜轿臋n。
這篇文章里的實(shí)現(xiàn),至少有 50% 是大模型提供的思路或靈感,代碼很多也是基于大模型生成后修改優(yōu)化的,省下大量腦細(xì)胞~
到此這篇關(guān)于基于video.js的移動端h5視頻播放實(shí)現(xiàn)方法的文章就介紹到這了,更多相關(guān)video.js移動端h5視頻播放內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
layui將table轉(zhuǎn)化表單顯示的方法(即table.render轉(zhuǎn)為表單展示)
今天小編就為大家分享一篇layui將table轉(zhuǎn)化表單顯示的方法(即table.render轉(zhuǎn)為表單展示),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-09-09
如何使用require.context實(shí)現(xiàn)優(yōu)雅的預(yù)加載
這篇文章主要介紹了使用require.context實(shí)現(xiàn)優(yōu)雅的預(yù)加載?,需要的朋友可以參考下2023-05-05
javascript簡單實(shí)現(xiàn)命名空間效果
這篇文章主要介紹了javascript簡單的實(shí)現(xiàn)命名空間效果的方法。需要的朋友可以過來參考下,希望對大家有所幫助2014-03-03
UNIAPP實(shí)現(xiàn)微信小程序登錄授權(quán)和手機(jī)號授權(quán)功能(uniapp做微信小程序)
uniapp開發(fā)小程序,先授權(quán)用戶信息后再出現(xiàn)手機(jī)號授權(quán)的頁面進(jìn)行手機(jī)號授權(quán),完成后返回上一頁面并把信息存入后臺以及前臺緩存中,方便使用,這篇文章主要介紹了UNIAPP實(shí)現(xiàn)微信小程序登錄授權(quán)和手機(jī)號授權(quán)(uniapp做微信小程序),需要的朋友可以參考下2024-08-08
JavaScript設(shè)計模式之性能優(yōu)化模式享元模式
這篇文章主要介紹了JavaScript設(shè)計模式之性能優(yōu)化模式享元模式,享元設(shè)計模式是用于性能優(yōu)化的模式,這種設(shè)計模式的核心在于可以共享技術(shù)并支持對大量細(xì)分過后的對象進(jìn)行調(diào)整,更多相關(guān)內(nèi)容需要的小伙伴可以參考一下2022-06-06

