基于threejs,html5,hlsjs,flvjs编写vr全景播放器
好久沒更新了,寫一下我半年前寫的vr播放器把,那時候的項目接到一個需求,需要實現VR視頻播放,并能夠播放直播。直播我熟,但是這個VR播放就涉及到我的知識盲區了。
于是開始開始研究threejs及相關方面的知識,在一段時間的調研后大致有了一些頭緒,大概就是生成一個球的模型,攝像頭放在中間,使用軌跡庫實現模型方向調轉,然后紋理朝向內部,最后把視頻作為紋理加載到這個模型上就行了。雖然說起來很簡單,但在實現的過程種還是踩了不少坑的。
一開始準備用我比較熟悉的vue-video-player來作紋理,結果發現里面需要使用原生video dom節點作為紋理,當時想著那這樣就沒什么辦法了,要么去修改這個庫,看看能不能插進去作為一個插件使用,想了想還是自己寫一個播放器,這樣的話有很多雜七雜八的東西像是進度條什么的都只能自己去實現了,然后實現hls,rtmp等直播流的播放,比較麻煩。關于rtmp流的播放的話,并不是通過h5video標簽實現播放的,而是用flash實現播放的,標簽也不是h5video標簽,而是object標簽,感覺就算了,google瀏覽器都宣布12月底就停止flash的支持了,那基于flash的rtmp直播協議也沒有太大對接的必要了。
直播流協議介紹
目前主流的直播流協議有以下三種,其他的像是webrtc也是比較主流的做法,但是它會更偏向于音視頻通話,基于它客戶端一對一建立連接通道傳輸音頻這種網狀連接機制,也不太適合大規模多人直播,如果要做多人直播,就要搭建webrtc服務器做轉發,這種原先我也有實現的意愿,理論上也是可以實現的,但是實現起來也比較麻煩,也沒有這方面的需求,就沒有做。
RTMP: 實時消息傳輸協議,由Adobe公司研發,但當前還沒有收入國際標準(wikipedia)。協議比較全能,既可以用來推送又可以用來直播,其核心理念是將大塊的視頻幀和音頻幀“剁碎”,然后以小數據包的形式在互聯網上進行傳輸,且支持加密,因此隱私性相對比較理想,但拆包組包的過程比較復雜,所以在海量并發時容易出現一些不可預期的穩定性問題。曾經在市場上有相當多直播采用該協議,但由于flash的淘汰,已漸漸被hls,跟flv取代。
HLS :基于HTTP協議的流直播(wikipedia)。蘋果推出的解決方案,將視頻分成 5-10 秒的視頻小分片,然后用 m3u8 索引表進行管理。由于客戶端下載到的視頻都是 5-10 秒的完整數據,故視頻的流暢性很好,但也同樣引入了很大的延遲(HLS 的一般延遲在 10-30s 左右)。相比于 FLV, HLS 在iPhone 和大部分 Android 手機瀏覽器上的支持非常給力,所以常用于 QQ 和微信朋友圈的URL 分享。
HTTP-FLV :由 Adobe 公司主推,格式極其簡單,只是在大塊的視頻幀和音視頻頭部加入一些標記頭信息,由于這種極致的簡潔,在延遲表現和大規模并發方面都很成熟。唯一的不足就是在手機瀏覽器上的支持非常有限,但是用作手機端 APP 直播協議卻異常合適。直播流詳解
以下是具體實現。
一.安裝threejs,flv.js,hls.js 模塊
threejs:WebGL三維引擎庫,如果不知道這是什么的話還是建議去了解一下的,網上文檔介紹的應該比我詳細,這里我就不做介紹了。相關文檔:threejs電子書,官方文檔
hlsjs:hls直播流相關庫
flvjs:flv直播流相關庫
二.搭建場景
為了能夠讓場景借助three.js來進行顯示,需要以下幾個對象:場景、相機和渲染器,這樣就能透過攝像機渲染出場景。
1.創建scene
創建場景實例
initScene () {this.scene = new THREE.Scene() }2.創建camera
創建相機實例,相當于眼睛,相機看到的就是呈現在畫布上的東西
initCamera (el) {this.camera = new THREE.PerspectiveCamera(75, el.clientWidth / el.clientHeight, 1, 1100)this.camera.position.set(1, 0, 0)// this.camera.target = new THREE.Vector3(0, 0, 0) }3.創建Render
創建渲染器,用于將我們創建好的實例渲染到畫布上
initRenderer (el) {this.renderer = new THREE.WebGLRenderer()this.renderer.setSize(el.offsetWidth, el.offsetHeight)el.appendChild(this.renderer.domElement) }4.創建video
創建video標簽,接受普通全景視頻,或全景直播流,后面將作為紋理渲染到球模型上,里面有些東西都是我在寫控件時加上去的,懶得分離出去再寫一遍了😂
initVideo () {this.video = document.createElement('video')this.video.preload = 'auto'this.video.muted = truethis.video.crossOrigin = 'anonymous'this.video.addEventListener('waiting', function (event) {this.playVariables.status = 'loading'}.bind(this))this.video.addEventListener('playing', function (event) {this.playVariables.status = 'playing'}.bind(this))this.video.addEventListener('pause', function (event) {this.playVariables.status = 'pause'}.bind(this))this.video.addEventListener('canplay', function (event) {this.playVariables.duration = this.player.durationif (this.playVariables.status === 'loading') {this.playVariables.status = 'playing'}}.bind(this))if (this.playVariables.type === 'normal') {this.video.ontimeupdate = function (event) {this.playVariables.currentTime = Math.floor(this.player.currentTime)this.playVariables.totalTime = this.playVariables.totalTime ? this.playVariables.totalTime : Math.floor(this.player.duration)this.playVariables.progress = this.playVariables.currentTime / this.playVariables.totalTimedocument.getElementById('progress-play').style.width = (this.playVariables.progress) * 100 + '%'document.getElementById('progress-btn').style.marginLeft = (this.playVariables.progress) * 100 + '%'}.bind(this)// 判斷視頻類型switch (this.option.source.type) {case 'flv':this.getFLV(this.option.source.url, this.video)breakcase 'hls':this.getHLS(this.option.source.url, this.video)breakcase 'normal':this.getNormalVideo(this.option.source.url, this.video)breakdefault:this.playVariables.error.code = 1this.playVariables.error.msg = '未知的視頻類型'break} }5.創建球
創建模型,并將video作為其紋理
initContent () {this.initVideo()var geometry = new THREE.SphereBufferGeometry(300, 90, 90)geometry.scale(-1, 1, 1)var texture = new THREE.VideoTexture(this.video)texture.minFilter = THREE.LinearFiltertexture.format = THREE.RGBFormatvar material = new THREE.MeshBasicMaterial({map: texture})this.mesh = new THREE.Mesh(geometry, material)this.mesh.position.set(0, 0, 0)this.scene.add(this.mesh) }5.創建軌道控制器
這個是我們實現視角控制的關鍵,當我們點擊鼠標拖動的時候,其實是在操作攝像機的位置,在轉動的是我們攝像機位置,整個場景的直角坐標系是沒有在變動的,如果以場景做參照物,那么就相當于相機在圍繞直角坐標系原點轉動,而攝像機視角始終面向原點,從而實現鼠標拖動視角轉動。
創建之后,我設置了轉動速度大小,開啟轉動慣性,設置了轉動慣性大小,具體自己注釋掉試一試就知道了。其他更多屬性設置就自己看下文檔把
6.渲染循環
讓我們的畫布上的東西動起來就需要創建一個使渲染器能夠在每次屏幕刷新時對場景進行繪制的循環(在大多數屏幕上,刷新率一般是60次/秒),threejs為我們提供了requestAnimationFrame函數來控制渲染,也可以把requestAnimationFrame看作是定時器,定時執行自身。this.controls.update()更新軌道控制器,this.renderer.render則是調用渲染器渲染。
render () {requestAnimationFrame(this.render)this.controls.update()// this.cameraUpdate()this.renderer.render(this.scene, this.camera)}7.為普通視頻,直播流配置不同屬性的函數
讓我們的畫布上的東西動起來就需要創建一個使渲染器能夠在每次屏幕刷新時對場景進行繪制的循環(在大多數屏幕上,刷新率一般是60次/秒),threejs為我們提供了requestAnimationFrame函數來控制渲染,也可以把requestAnimationFrame看作是定時器,定時執行自身。this.controls.update()更新軌道控制器,this.renderer.render則是調用渲染器渲染。
getNormalVideo (sourceURL, el) {const source = document.createElement('source')source.src = sourceURL// source.type = 'video/mp4'el.appendChild(source)this.player = el } getHLS (sourceURL, el) {const Hls = require('hls.js')if (Hls.isSupported()) {this.hls = new Hls()this.hls.loadSource(sourceURL)this.hls.attachMedia(el)this.hls.on(Hls.Events.MANIFEST_PARSED, () => {console.log('加載成功')})this.hls.on(Hls.Events.ERROR, (event, data) => {throw new Error(data.response.code + ' ' + data.response.text)})}this.player = el }8.編寫init函數統一調用
讓我們的畫布上的東西動起來就需要創建一個使渲染器能夠在每次屏幕刷新時對場景進行繪制的循環(在大多數屏幕上,刷新率一般是60次/秒),threejs為我們提供了requestAnimationFrame函數來控制渲染,也可以把requestAnimationFrame看作是定時器,定時執行自身。this.controls.update()更新軌道控制器,this.renderer.render則是調用渲染器渲染。
init () {const container = document.getElementById('video')this.initScene()this.initCamera(container)this.initRenderer(container)this.initContent()this.initControls(container)this.render()// this.addMouseEvent(container)window.addEventListener('deviceorientation', function (event) {this.deviceOrientationData.isSupported = truethis.deviceOrientationData = event}.bind(this), false)window.addEventListener('resize', function () {this.onWindowResize(container)}.bind(this)) }9.在生命周期mounted中調用init函數
因為涉及到dom操作,所以在vue實例掛載后調用init函數
mounted () {if (this.check()) {this.playVariables.statistics = this.option.statisticsthis.playVariables.type = this.option.source.typethis.videoContainer = document.getElementById('videoContainer')this.init()} }總結:現在感覺寫這個真的踩了太多坑了,因為當時寫這個運行環境是在小程序,本身wx小程序對threejs這些webgl方面的東西就不怎么支持,根據教程引入的threejs模塊也是閹割版,然后dom操作也是閹割的,寫播放器控件都寫的很難受,最后是放在webview上實現的,然后android跟ios又有很多區別,像是ios只支持hls直播協議,flv直播流無法播放,android則兩者都支持之類的問題一大堆,調試也很麻煩,webview都沒有報錯信息,很多時候出問題都不知道哪里出問題,解決也不知道怎么解決,寫這個小程序時候血壓都拉滿了,web端就沒這么多的事情,開發體驗直接拉滿,寫下來就很流暢,麻了😅。
目前這個只是實現了基本的播放,真要寫的話其實還有許多需要補充的,不過事情太多了,應該是沒空寫這個了。代碼的話已上傳至倉庫vue-vr
總結
以上是生活随笔為你收集整理的基于threejs,html5,hlsjs,flvjs编写vr全景播放器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 同伦法(Homotopy Method)
- 下一篇: op的形态和自激