前端视频流播放
Posted cyanineAir
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端视频流播放相关的知识,希望对你有一定的参考价值。
前端视频流播放
1. 开始
最近写了一个小功能,是在前端播放视频流(格式flv/mp4,编码h.264/h.265),一开始找到了LivePlayer。但很遗憾后续被告知需要支持h.265(chrome目前支持h.264编码但不支持h.265);于是又搜寻了一番找到了Easyplayer,照着上面Vue集成的部分搞了一下,WTF居然不行?出现了跟这个issue一样的问题,于是就换了个思路,直接把它用原生方式引入,成功了,具体操作如下:
1-1. 在npm包里找到相应文件放到public下(我多套了个文件夹,不套也可以)
1-2. 在index.html中引入资源文件
1-3. (可选)修改webpack配置
若不配置,vue-loader会报warning,提示此组件不是Vue的组件也不是标准组件库里的组件。
configureWebpack->module->rules下:
1-4. 使用
按照文档上HTML集成示例使用即可。
2. 录播h.265流无法播放
这个时候已经能用了,但而后又发现了一个问题,直播推h.265的流没有任何问题,但是录播的流不知道为什么无法解析。后来领导从别人那顺了一份旧版本的EasyPlayer说是可以,我就又看了一下,写完发现果然可以。
旧版本集成方式和新版本一样,就是使用起来不太一样,使用方式可以看文档旧版EasyPlayer。由于项目上了ts,于是又自己写了一个声明文件,很简陋,凑活着用:
declare class WasmPlayer
constructor(
url: string | undefined | null,
id: string,
cb?: (e: any) => void,
opts?:
decodeType?: 'auto' | 'soft';
openAudio?: boolean;
BigPlay?: boolean;
Height?: boolean;
HideKbs?: boolean;
cbUserPtr?: any;
cfKbs?: (e: any) => void;
)
return
play(url: string, autoplay = 0, currentTime = 0)
return
pause()
return
destroy()
return
openAudio()
return
closeAudio()
return
startLoading()
return
endLoading()
return
fullScreen()
return
setSnap(url: string)
return
endSnap()
return
另外旧版本的EasyPlayer下面自带一条无法清除的toolbar,toolbar上还有一个无法清除的logo,点击会跳转到官网。这个就只能在源码里修改了,由于源代码已经做过混淆,让我找了半天。清除logo全局搜索this.logo.style=
,并将其中的display改为none;toolbar全局搜索timeBox.style
,这两者前者还有验证函数,会验证logo的样式,因此再搜索LogoTimer
修改其中的验证条件;后者会定时修改样式为初始样式,因此需要再搜索setTimeout
,找到其中有timeBox.style
的一条代码,修改其样式。至此大功告成。
3. 增加截图和视频录制
本来我觉得这个问题大概不是什么问题,因为本身新版EasyPlayer是自带的,但找了找发现旧版并没有这个方法。没办法只能自己写了。
一开始我以为EasyPlayer它是默认将视频流渲染成canvas的。因此只做了对canvas的截图和录制。
截图方法:
async function snapshot()
const playerEle = document.getElementById(id.value) as HTMLElement
const children = playerEle.children
const canvas = Object.values(children).find(
child => child.tagName.toLocaleLowerCase() === 'canvas'
) as HTMLCanvasElement
let url = ''
if (canvas)
// 由于视频流有空白帧,渲染到canvas上后导致canvas截图空白,故如果截图时机不好会截出来空白的图,因此判断图片大小以避免空白图(空白图大小<100kb)
const interval = setInterval(() =>
url = canvas.toDataURL('image/jpg')
if (url.length > 204800)
clearInterval(interval)
const aEle = document.createElement('a')
aEle.setAttribute('href', url.replace('image/jpg', 'image/octet-stream'))
aEle.setAttribute(
'download',
`$props.videoOpts.channel_no-$props.videoOpts.guid.jpg`
)
aEle.click()
aEle.remove()
, 50)
甚至本来截图我是没想到它会能截出来空白的,后来发现之后给它加了个循环,直到截出正常图片为止。
录制方法:
let recorder: MediaRecorder
let blobData: Blob[] = []
function record()
const playerEle = document.getElementById(id.value) as HTMLElement
const children = playerEle.children
const canvas = Object.values(children).find((child) => child.tagName.toLocaleLowerCase() === 'canvas') as HTMLCanvasElement
if (canvas)
const stream = canvas.captureStream(30)
recorder = new MediaRecorder(stream, mimeType: 'video/webm;codecs=vp9')
recorder.ondataavailable = (e) =>
blobData.push(e.data)
recorder.start(10)
function stopRecord()
recorder.stop()
const blob = new Blob(blobData)
const url = window.URL.createObjectURL(blob)
const aEle = document.createElement('a')
aEle.setAttribute('href', url)
aEle.setAttribute('download', `$props.videoOpts.channel_no-$props.videoOpts.guid.webm`)
aEle.click()
aEle.remove()
blobData = []
本来到这也就告一段落了,结果某一天,又发现h.264视频无法截图也无法录制,查找了一番发现是EasyPlayer会分析视频编码格式,如果是h.264格式就会利用<video>标签简化操作,只有h.265编码才会转成canvas。于是我又改了下截图方法,至于录制方法,我暂时不知道怎么录制<video>播放的视频,唯一能想到的就是每秒截图30次拼接起来,但考虑到性能开销太大,于是让后端处理了,如果有大佬会的话麻烦告知一下。
改进后的截图:
async function snapshot()
const playerEle = document.getElementById(id.value) as HTMLElement
const children = playerEle.children
const canvas = Object.values(children).find(
child => child.tagName.toLocaleLowerCase() === 'canvas'
) as HTMLCanvasElement
const video = Object.values(children).find(
child => child.tagName.toLocaleLowerCase() === 'video'
) as HTMLVideoElement
let url = ''
if (canvas)
// 由于视频流有空白帧,渲染到canvas上后导致canvas截图空白,故如果截图时机不好会截出来空白的图,因此判断图片大小以避免空白图(空白图大小<100kb)
const interval = setInterval(() =>
url = canvas.toDataURL('image/jpg')
if (url.length > 204800)
clearInterval(interval)
const aEle = document.createElement('a')
aEle.setAttribute('href', url.replace('image/jpg', 'image/octet-stream'))
aEle.setAttribute(
'download',
`$props.videoOpts.channel_no-$props.videoOpts.guid.jpg`
)
aEle.click()
aEle.remove()
, 50)
else if (video)
const target = await html2canvas(video)
url = target.toDataURL('image/jpg')
const aEle = document.createElement('a')
aEle.setAttribute('href', url.replace('image/jpg', 'image/octet-stream'))
aEle.setAttribute('download', `$props.videoOpts.channel_no-$props.videoOpts.guid.jpg`)
aEle.click()
aEle.remove()
else
message.error('不支持的dom类型')
return
其中video的截图使用了html2canvas。
至此整个视频流播放问题都解决了,以后说不定还会再用ffmpeg自己写一个视频流解析的组件。
以上是关于前端视频流播放的主要内容,如果未能解决你的问题,请参考以下文章