摘要:常見(jiàn)方案在網(wǎng)上查找的大部分解決方案都是用諸如等網(wǎng)頁(yè)播放器,接收索引文件的方式來(lái)播放切片。根據(jù)實(shí)測(cè),只用這一個(gè)庫(kù)即可在端直接播放視頻,如下是它的轉(zhuǎn)化流程。
最近項(xiàng)目中需要前端播放 .ts 格式視頻,搗鼓了幾天學(xué)習(xí)到很多知識(shí),也發(fā)掘了一種優(yōu)秀的解決方案,分享給有同樣需求的同學(xué)。
常見(jiàn)方案在網(wǎng)上查找的大部分解決方案都是用諸如videojs等網(wǎng)頁(yè)播放器,接收 .m3u8索引文件的方式來(lái)播放ts切片。這種方案的缺點(diǎn)是需要后端對(duì)原始ts切片做處理,生成 .m3u8索引文件
ffmpeg -i source.ts -c copy -map 0 -f segment -segment_list playlist.m3u8 -segment_time 10 output%03d.ts
項(xiàng)目中已存儲(chǔ)的 .ts 切片數(shù)量眾多,已經(jīng)占用了NAS服務(wù)器絕大部分的資源,生成的 .m3u8 索引雖然非常小,但會(huì)生成一堆切片后的新 .ts 視頻,例如上述指令將會(huì)生成一堆 10s 長(zhǎng)度的 ts新切片。出于各種考慮后端的同學(xué)拒絕了這種重新生成新切片加索引的方案。
邪道方案在我們的項(xiàng)目中,每一個(gè)ts切片已經(jīng)就是一個(gè)獨(dú)立內(nèi)容的視頻了,時(shí)長(zhǎng)在20s以內(nèi),因此其實(shí)不用切割,只需要生成一個(gè)索引文件就可以了, .m3u8格式如下:
#EXTM3U #EXT-X-VERSION:3 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-ALLOW-CACHE:YES #EXT-X-TARGETDURATION:93 #EXTINF:92.008578, test.ts #EXT-X-ENDLIST
定義好的時(shí)長(zhǎng)并不影響最終網(wǎng)頁(yè)播放器計(jì)算出的時(shí)長(zhǎng),因此可以取一個(gè)統(tǒng)一的極大值,整體上就只有倒數(shù)第二行的ts文件名需要根據(jù)不同 ts 視頻修改,可以用腳本統(tǒng)一生成所有ts文件的索引文件。這個(gè)方案極其low,當(dāng)然也被后端同學(xué)拒絕了。
插件方案VLC Web Plugin,一個(gè)需要VLC播放器以及瀏覽器插件的方案,并且不支持Chrome,使用復(fù)雜,感興趣的同學(xué)可以自行嘗試。
優(yōu)雅方案在中文互聯(lián)網(wǎng)搜索無(wú)果后,果斷轉(zhuǎn)向了Google,然而也未果,正當(dāng)我絕望地準(zhǔn)備調(diào)整心態(tài),接受下載后VLC播放的保底方案時(shí),終于發(fā)現(xiàn)了一絲線索,在vediojs的Github頁(yè)面中,Issue1441 和 Issue4297 中,面對(duì)videojs能否直接播放 .ts 的疑問(wèn),開(kāi)發(fā)團(tuán)隊(duì)都表示雖然庫(kù)本身沒(méi)有直接的相關(guān)實(shí)現(xiàn),但可以利用相關(guān)的邏輯自行實(shí)現(xiàn)。最重要的是都指出了mux.js這一工具。根據(jù)實(shí)測(cè),只用這一個(gè)庫(kù)即可在web端直接播放 .ts 視頻,如下是它的轉(zhuǎn)化流程。
示例中是以 ajax 的方式接收 .ts 二進(jìn)制數(shù)據(jù),mux.js引入方式可以直接標(biāo)簽引入,也可以npm install mux.js后 import進(jìn)頁(yè)面。
var $ = document.querySelector.bind(document); var vjsParsed, video, mediaSource; // 定義通用的事件回調(diào)處理函數(shù),只做打印事件類型 function logevent (event) { console.log(event); } // ajax let xhr = new XMLHttpRequest(); xhr.open("GET", "./test.ts"); // 接收的是 video/mp2t 二進(jìn)制數(shù)據(jù),Blob類型也可以,但arraybuffer類型方便后續(xù)直接處理 xhr.responseType = "arraybuffer"; xhr.send(); xhr.onreadystatechange = function () { if (xhr.readyState ==4) { if (xhr.status == 200) { transferFormat(xhr.response); } else { console.log("error"); } } } function transferFormat (data) { // 將源數(shù)據(jù)從ArrayBuffer格式保存為可操作的Uint8Array格式 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer var segment = new Uint8Array(data); var combined = false; // 接收無(wú)音頻ts文件,OutputType設(shè)置為"video",帶音頻ts設(shè)置為"combined" var outputType = "video"; var remuxedSegments = []; var remuxedBytesLength = 0; var remuxedInitSegment = null; // remux選項(xiàng)默認(rèn)為true,將源數(shù)據(jù)的音頻視頻混合為mp4,設(shè)為false則不混合 var transmuxer = new muxjs.mp4.Transmuxer({remux: false}); // 監(jiān)聽(tīng)data事件,開(kāi)始轉(zhuǎn)換流 transmuxer.on("data", function(event) { console.log(event); if (event.type === outputType) { remuxedSegments.push(event); remuxedBytesLength += event.data.byteLength; remuxedInitSegment = event.initSegment; } }); // 監(jiān)聽(tīng)轉(zhuǎn)換完成事件,拼接最后結(jié)果并傳入MediaSource transmuxer.on("done", function () { var offset = 0; var bytes = new Uint8Array(remuxedInitSegment.byteLength + remuxedBytesLength) bytes.set(remuxedInitSegment, offset); offset += remuxedInitSegment.byteLength; for (var j = 0, i = offset; j < remuxedSegments.length; j++) { bytes.set(remuxedSegments[j].data, i); i += remuxedSegments[j].byteLength; } remuxedSegments = []; remuxedBytesLength = 0; // 解析出轉(zhuǎn)換后的mp4相關(guān)信息,與最終轉(zhuǎn)換結(jié)果無(wú)關(guān) vjsParsed = muxjs.mp4.tools.inspect(bytes); console.log("transmuxed", vjsParsed); prepareSourceBuffer(combined, outputType, bytes); }); // push方法可能會(huì)觸發(fā)"data"事件,因此要在事件注冊(cè)完成后調(diào)用 transmuxer.push(segment); // 傳入源二進(jìn)制數(shù)據(jù),分割為m2ts包,依次調(diào)用上圖中的流程 // flush的調(diào)用會(huì)直接觸發(fā)"done"事件,因此要事件注冊(cè)完成后調(diào)用 transmuxer.flush(); // 將所有數(shù)據(jù)從緩存區(qū)清出來(lái) } function prepareSourceBuffer (combined, outputType, bytes) { var buffer; video = document.createElement("video"); video.controls = true; // MediaSource Web API: https://developer.mozilla.org/zh-CN/docs/Web/API/MediaSource mediaSource = new MediaSource(); video.src = URL.createObjectURL(mediaSource); $("#video-wrapper").appendChild(video); // 將H5 video元素添加到對(duì)應(yīng)DOM節(jié)點(diǎn)下 // 轉(zhuǎn)換后mp4的音頻格式 視頻格式 var codecsArray = ["avc1.64001f", "mp4a.40.5"]; mediaSource.addEventListener("sourceopen", function () { // MediaSource 實(shí)例默認(rèn)的duration屬性為NaN mediaSource.duration = 0; // 轉(zhuǎn)換為帶音頻、視頻的mp4 if (combined) { buffer = mediaSource.addSourceBuffer("video/mp4;codecs="" + "avc1.64001f,mp4a.40.5" + """); } else if (outputType === "video") { // 轉(zhuǎn)換為只含視頻的mp4 buffer = mediaSource.addSourceBuffer("video/mp4;codecs="" + codecsArray[0] + """); } else if (outputType === "audio") { // 轉(zhuǎn)換為只含音頻的mp4 buffer = mediaSource.addSourceBuffer("audio/mp4;codecs="" + (codecsArray[1] ||codecsArray[0]) + """); } buffer.addEventListener("updatestart", logevent); buffer.addEventListener("updateend", logevent); buffer.addEventListener("error", logevent); video.addEventListener("error", logevent); // mp4 buffer 準(zhǔn)備完畢,傳入轉(zhuǎn)換后的數(shù)據(jù) // 將 bytes 放入 MediaSource 創(chuàng)建的sourceBuffer中 // https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer buffer.appendBuffer(bytes); // 自動(dòng)播放 // video.play(); }); };
IE8及以上 、 IE Edge 、Chrome 、 Firefox 瀏覽器下均能正常播放。希望本文能幫到各位開(kāi)發(fā)同學(xué)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/102520.html
閱讀 1628·2021-09-08 10:42
閱讀 3611·2021-08-11 10:23
閱讀 3981·2019-08-30 14:10
閱讀 2739·2019-08-29 17:29
閱讀 3097·2019-08-29 12:50
閱讀 646·2019-08-26 13:36
閱讀 3463·2019-08-26 11:59
閱讀 1494·2019-08-23 16:23