記錄一次有挑戰(zhàn)的經(jīng)歷:使用xgplayer拉流端直播
共 9425字,需瀏覽 19分鐘
·
2024-04-15 09:53
一、背景介紹
為了強(qiáng)化官方驗(yàn)的心智,平臺要做一版新的質(zhì)檢直播間,將我們的質(zhì)檢車間全方位透明的展現(xiàn)給用戶。按照產(chǎn)品的設(shè)計(jì)來實(shí)現(xiàn)的話,其實(shí)就是將各個鏡頭的內(nèi)容同時在一個頁面內(nèi)進(jìn)行播放,除了工作時間的直播,還有休息時間的錄播播放。不過直播和錄播的生成都是在后端實(shí)現(xiàn),前端只負(fù)責(zé)視頻資源的播放。
二、前期調(diào)研
對于簡單的直播場景,我們需要關(guān)注的主要是「編解碼格式」和「直播協(xié)議」,因?yàn)檫@兩點(diǎn)直接決定直播能否播放。
「編解碼格式:」視頻的編碼方式?jīng)Q定了視頻的壓縮方式,同樣的需要對應(yīng)的解碼格式才能正常播放視頻。但視頻編碼這個過程是在推流端做的,通常會采用H.264,目前基本上所有的播放器和瀏覽器都支持該解碼方式,其兼容性基本不用考慮;所以雖然視頻的編解碼格式很重要,但只要沒有特殊的場景如4k,一般無需過多考慮。
「拉流端直播協(xié)議:」不同的直播協(xié)議,其兼容性和直播效果有一些差異,而前端對兼容性的差異是敏感的,所以對于Web端,盡量選擇兼容性最佳的HLS。
以下是我了解到的不同直播協(xié)議的特點(diǎn)及他們的優(yōu)劣勢:
「RTMP」
特點(diǎn)/優(yōu)點(diǎn):
1.基于TCP長連接,不需要多次建連,延時低,通常只有1~3s;2.技術(shù)成熟,配套完善。
缺點(diǎn):
1.兼容性差,需要依賴flash,因此無法在移動端使用。2.容易被防火墻攔截。
「HLS」
特點(diǎn)/優(yōu)點(diǎn):
1.其工作原理是切片式傳輸,把直播流切成無數(shù)片,用戶在觀看視頻時,每次客戶端可以只下載一部分;2.基于 HTTP協(xié)議,所以接入CDN較為容易,很少被防火墻攔下;3.自帶多碼率自適應(yīng);4.幾乎支持所有設(shè)備;
缺點(diǎn):
1.延時較大,通常不低于10s;2.大量的TS片文件,會造成服務(wù)器存儲和請求的壓力;
「HTTP-FLV」
特點(diǎn)/優(yōu)點(diǎn):
1.把音視頻數(shù)據(jù)封裝成FLV,然后通過HTTP連接傳輸,與RTMP相比只是傳輸協(xié)議變了,能有效避免防火墻和代理的影響;2.低延時,整體效果與RTMP非常接近;
缺點(diǎn):
1.它的傳輸特性會讓流媒體資源緩存在本地客戶端,保密性較差;2.不支持IOS;
三、為什么使用xgPlayer
在確定了我所需要使用的直播協(xié)議后,我調(diào)研了一些社區(qū)推薦的播放器:tcplayer.js、xgplayer、DPlayer、plyr、ArtPlayer.js、Video.js。
其實(shí)以上的播放器在功能上都可以滿足我的需求,并且也都支持H.264編碼格式;在直播能力的支持上也都會在底層依賴hls.js,flv.js,不過像tcplayer和xgplayer還單獨(dú)包了一層,使得直播的實(shí)現(xiàn)更加的符合播放器的體系;
在我所調(diào)研的播放器里xgPlayer的文檔是最清晰和完善的,并且xgPlayer使用插件機(jī)制,所有功能均可插拔,而且支持自定義擴(kuò)展能力,十分方便,所以在開發(fā)體驗(yàn)上我認(rèn)為更勝一籌。
四、基本使用
1.初始化
import Player from 'xgplayer'
import FlvPlugin from 'xgplayer-flv'
import "xgplayer/dist/index.min.css"
new Player({
id:'dom-id', // 播放器實(shí)例化所需的dom
url: 'test.flv', // 視頻源
width: '100%',
height: '100%'
})
2.多實(shí)例
初始化時可以使用選擇器id或容器el。但對于同時實(shí)例化多個播放器的場景,使用Id很容易導(dǎo)致最終只實(shí)例化成功一個,雖然你可以通過ID+索引的方式避免,但使用容器el還是更為簡潔的。
<div ref="playerRef"></div>
const playerRef = ref()
new Player({
el: playerRef.value,
...
})
3.常用屬性
new Player({
poster // 封面
autoplay // 自動播放,基本上不支持有聲自動播放;
autoplayMuted // 自動靜音播放,需要自動播放可使用改屬性
playsinline // 對于移動 Safari 瀏覽器來說是必需的,它允許視頻播放時不強(qiáng)制全屏模式
loop // 循環(huán)播放
fitVideoSize // 根據(jù)視頻內(nèi)容調(diào)整容器寬高
videoFillMode // 視頻畫面填充模式
controls // 是否展示進(jìn)度條
videoAttributes // 透傳給video標(biāo)簽的屬性
})
關(guān)于自動播放的限制還是很多的,所以把預(yù)期降低到大部分設(shè)備可以靜音播放即可... 關(guān)于自動播放
4.直播能力
「FLV協(xié)議:」使用xgplayer-flv,可用于PC、安卓。
import FlvPlugin from 'xgplayer-flv'
if (FlvPlugin.isSupported()) { // 第一步,檢測當(dāng)前環(huán)境是否支持
const player = new Player({
id: 'dom-id',
url: 'test.flv', // flv 流地址
isLive: true,
plugins: [FlvPlugin] // 第二步
})
}
「HLS協(xié)議:」使用xgplayer-hls,可用于PC、安卓、IOS(ios&部分andr不需要插件,原生支持)。
在原生支持hls的情況下盡可能不使用xgplayer-hls,IOS瀏覽器原生即支持hls格式播放,但是缺少xgplayer-hls運(yùn)行所需的Media Source Extensions。
import Player from 'xgplayer'
import HlsPlugin from 'xgplayer-hls'
let player
if (document.createElement('video').canPlayType('application/vnd.apple.mpegurl')) {
// 原生支持 hls 播放
player = new Player({
el: document.querySelector('.player'),
url: 'test.m3u8'
})
} else if (HlsPlugin.isSupported()) { // 第一步,檢測當(dāng)前環(huán)境是否支持
player = new Player({
el: document.querySelector('.player'),
isLive: true,
url: 'test.m3u8', // hls 流地址
plugins: [HlsPlugin] // 第二步
})
}
5.樣式自定義
xgplayer提供了十分便捷的方式去配置icon。
const playerConfig = {
icons: {
// 1.通過function方式 返回一個dom
play: () => {
const dom = Util.createDom('div', '<img src="./start"/>', {}, 'customclass')
return dom
},
// 2.直接html代碼
pause: `<div class='customclass'><img src="./start"/></div>`,
// 3.直接一張圖片鏈接
startPlay: ''
}
}
但是很多地方的icon大小尺寸是固定死的,如果想要修改還是要去覆蓋xgplayer默認(rèn)的樣式
6.自定義插件
xgplayer支持插件的擴(kuò)展機(jī)制,無論是簡單的功能按鈕還是復(fù)雜的播放邏輯都可以通過插件的形式來實(shí)現(xiàn)。
這里實(shí)現(xiàn)一個簡單的插件,結(jié)合我們的通用組件庫,用于在視頻暫停時,展示一些提示。
// demoPlugin.js
import Vue from 'vue'
import { Events, Plugin } from 'xgplayer'
import Demo from './demoPlugin.vue'
const { POSITIONS } = Plugin
export default class DemoPlugin extends Plugin {
// 插件的名稱,將作為插件實(shí)例的唯一key值
static get pluginName() {
return 'demoPlugin'
}
static get defaultConfig() {
return {
// 掛載在播放器最上方
position: POSITIONS.ROOT_TOP
}
}
constructor(args) {
super(args)
this.vm = null
}
// 插件實(shí)例化之后
afterCreate() {
// 使用vue組件
const Component = Vue.extend(Demo)
this.vm = new Component().$mount('.demo-plugin')
// 視頻播放時
this.on(Events.PLAY, () => {
this.vm.hide()
})
// 視頻暫停時
this.on([Events.PAUSE], () => {
this.vm.show()
})
}
destroy() {
// 播放器銷毀的時候一些邏輯
}
render() {
return '<div class="demo-plugin"></div>'
}
}
// demoPlugin.vue
<template>
<NoticeBar v-show="visible" swipe>
<span style="padding-right: 50px;">我一定會回來的~我一定會回來的~我一定會回來的~</span>
</NoticeBar>
</template>
<script setup lang="ts">
import { ref, defineExpose } from 'vue'
import { NoticeBar } from '@zz-common/zz-ui';
const visible = ref(true)
const show = () => {
visible.value = true
}
const hide = () => {
visible.value = false
}
defineExpose({
show,
hide
})
</script>
// 使用
import DemoPlugin from './demoPlugin.js'
...
new Player({
...,
plugins: [DemoPlugin]
})
五、遇到的問題
1.事件監(jiān)聽相關(guān)
在播放器的事件中有兩個與播放操作相關(guān)的事件——play、canplay
「play」表示播放已開始。
「canplay」表示瀏覽器可以播放媒體文件了,但估計(jì)沒有足夠的數(shù)據(jù)來支撐播放到結(jié)束,不必停下來進(jìn)一步緩沖內(nèi)容。
簡單區(qū)分下這兩個事件:play可以具象的理解為點(diǎn)擊播放按鈕或者自動播放成功,canplay則表示視頻資源已經(jīng)開始加載并緩沖了一部分?jǐn)?shù)據(jù)達(dá)到了啟播的條件。
這兩個事件的先后順序不固定——如果沒有設(shè)置自動播放、也沒有設(shè)置預(yù)加載,那應(yīng)該是play、canplay。如果設(shè)置了預(yù)加載,但沒有設(shè)置自動播放,則應(yīng)該是canplay、play,到目前按照事件的定義來看,還是能抓住一些規(guī)律的。但是呢經(jīng)過我測試發(fā)現(xiàn),同樣的設(shè)置,在原生video標(biāo)簽和xgplayer上執(zhí)行順序不同。另外在不同的操作系統(tǒng)上也會導(dǎo)致執(zhí)行順序不同,比如 ios Safari 從不預(yù)加載導(dǎo)致無法在未播放時觸發(fā)canplay事件。所以十分不建議根據(jù)這兩個事件的執(zhí)行時機(jī)來做一些事(不過目前也想不到這種場景,只是無意中發(fā)現(xiàn)的)
2.跨域問題
問題的起因是當(dāng)時的直播流內(nèi)容總是會間隔性的黑屏,而且后端無法監(jiān)控到并調(diào)整視頻源。于是需要前端通過截取畫面并分析截圖的像素點(diǎn)來判斷是否黑屏,以實(shí)現(xiàn)黑屏自動切換視頻源的能力。
但是當(dāng)通過canvas獲取圖片數(shù)據(jù)時getImageData報(bào)了一個SecurityError異常。
經(jīng)查閱發(fā)現(xiàn)這是瀏覽器的安全策略,不通過CORS使用其他來源的資源,會污染畫布。
在"被污染"的 canvas 中調(diào)用以下方法將會拋出安全錯誤:
-
在 canvas的上下文上調(diào)用getImageData() -
在 canvas元素本身調(diào)用toBlob()、toDataURL()、captureStream()
所以如果要對視頻內(nèi)容進(jìn)行截圖或者對視頻畫面做一些操作處理,需要給video標(biāo)簽設(shè)置crossOrigin屬性,在xgplayer中可以通過videoAttributes屬性傳入。
const player = new Player({
...,
videoAttributes: {
crossOrigin: 'anonymous'
}
})
3.內(nèi)存溢出
當(dāng)不需要播放器時,記得及時銷毀,否則可能會導(dǎo)致內(nèi)存溢出。(尤其是多實(shí)例、切換播放的場景)
player.destroy() // 銷毀播放器
player = null // 將實(shí)例引用置空
「無需單獨(dú)銷毀插件實(shí)例,xgplayer自動會幫我們執(zhí)行該操作。」—— xgplayer將所有注冊的插件維護(hù)在pluginGroup對象中,當(dāng)我們調(diào)用播放器的destroy方法時,遍歷所有插件并依次執(zhí)行插件的銷毀方法。
4.兼容性問題???
在測試的時候發(fā)現(xiàn),iPhone 14Pro機(jī)型,直播播放的時候,如果跳轉(zhuǎn)到其他頁面,再返回到直播頁,會導(dǎo)致視頻播放錯誤;該問題暫時沒有思路,不清楚原因...
目前的解決辦法是,在頁面不可見時,記錄并銷毀該播放器,在頁面展現(xiàn)時再重新實(shí)例化之前的播放器。
六、最終效果
目前線上正常運(yùn)行,暫時沒有發(fā)現(xiàn)其他問題。
七、參考資料
1.允許圖片和 canvas 跨源使用(https://developer.mozilla.org/zh-CN/docs/Web/HTML/CORS_enabled_image)
2.西瓜播放器(https://h5player.bytedance.com/guide/)
3.Safari HTML5 Audio and Video Guide(https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/AudioandVideoTagBasics/AudioandVideoTagBasics.html)
