react vr中文網(wǎng):www.vr-react.com
qq群:481244084
開發(fā)者頭條:react vr 原理解析
開發(fā)者頭條:React VR 視頻源碼解析
開發(fā)者頭條:react vr 消息傳遞原理解析
因為react vr的視頻組件Video組件在手機上播放視頻會全屏,所以前面我寫了篇文章解決此事:解決react vr視頻在微信和瀏覽器上全屏的問題;
這里我再介紹下video的源碼,希望能幫助到您,此篇文章較長,建議對著源碼和本篇文章來閱讀,否則會暈暈乎乎的哦,就像蜘蛛網(wǎng)一樣的,首先看下下圖:

圖片較大,如果查看原圖,可以下載:http://www.vr-react.com/images/Video.png,
其中用到的幾個文件如下:
video:react-vr ---> Libraries ---> Video --->Video.js
MediaPlayerState:react-vr ---> Libraries ---> Video --->MediaPlayerState
RCTVideo:react-vr-web ---> js ---> Views ---> Video
RCTVideoPlayer: react-vr-web ---> js ---> Utils ---> RCTVideoPlayer
RCTVideoModule:react-vr-web ---> js --->Modules ---> RCTVideoModule
MediaEvent:react-vr-web ---> js --->Events ---> MediaEvent
VRVideoComponent:react-vr ---> Libraries ---> Video --->VRVideoComponent
BasicVideoPlayer:react-vr ---> Libraries ---> Video --->BasicVideoPlayer
其他的幾個引用文件在此就忽略啦,比如RCTBindedResource資源綁定文件、ReactNativeContext RN的上下文對象、EventEmitter事件發(fā)送、OVRUI.UIView等等,后面抽空再給大家講解了。
進入正題:
下面就這幾個文件進行簡單的講解,
一、Video
1.1、video是一個react 組件,首先創(chuàng)建一個React的class,包含一堆屬性和方法:http://www.vr-react.com/Video.html,
如下圖:

1.2、componentWillMount訂閱監(jiān)聽播放狀態(tài)(play、pause、seekTo、registerUserGesture、unregisterUserGesture、volumeChange、mutedChange)
這些狀態(tài)會通過UIMannager發(fā)送指令到原生那邊的Video組件(在react-vr-web里面)中,如下圖:

1.3、調(diào)用requireNativeComponent,把這個組件創(chuàng)建成一個React Native組件。
1.4、這個組件在UIManager的Video視圖中,傳下去兩個參數(shù),一個是ReactNativeContext,這是RN的上下文對象,一個是GuiSys,這個是在ovrui內(nèi)部,是管理Object3D、字體、不透明度等等的一個UI工具。
下一步就到RCTVideo中了
二、RCTVideo
先來看下圖:

2.1、它繼承了BaseView,構(gòu)造函數(shù)中有下面幾個成員:
? ? 2.1.1、實例化OVRUI的UIView的view
? ? 2.1.2、_localResource:綁定資源文件
? ? 2.1.3、_rnctx:RN的上下文對象從UIManager的video實例化傳過來的參數(shù)
? ? 2.1.4、player是實例化的RCTVideoPlayer(../Utils/RCTVideoPlayer),傳進去兩個參數(shù),一個是上下文對象,一個是view的uuid,因為view都是OVRUI的uiview都是一個object3d對象,都有一個唯一的uuid
重寫了player兩個方法,一個是onUpdateTexture,一個是onEmitEvent,后者是調(diào)用RN上下文的接受通知的事件,前者是回調(diào)函數(shù),傳入的是資源,資源有內(nèi)部有uri,指向視頻對象,然后更新圖像紋理
? ? 2.1.5、_videoModule就是調(diào)用上下文中的rnctx.VideoModule,也就是在rnctx中實例化的RCTVideoModule模塊
? ? 2.1.6、定義一些屬性(source、poster、playControl、autoPlay、loop、muted、volume、tintColor)
2.2、dispose釋放資源文件,釋放player
2.3、接受指令receiveCommand,有三個指令,一個是COMMAND_SEEK_TO、尋找播放點,一個是播放指令,一個是暫停指令,接收到指令分別調(diào)用player的對應(yīng)的方法
2.4、有一個靜態(tài)的describe方法,包含baseview的(onLayout、onEnter、onExit、onInput、onChange、onHeadPose、onChangeCaptured、onInputCaptured、onHeadPoseCaptured、onHeadPoseCaptured,自己的NativeProps(autoPlay、loop、muted、playControl、volume、source、poster)和四個Commands指令值(setImmediateOnTouchEnd這個是baseview的指令、seekTo、play、pause)
其中最主要的就是實例化的RCTVideoPlayer和RCTVideoModule,下面就到了。
三、RCTVideoPlayer

內(nèi)部包含以下幾個屬性和方法:
3.1、rnctx = ReactNativeContext RN的上下文對象
3.2、_videoModule = rnctx.VideoModule,這個是Modules/RCTVideoModule的實例化:后面再講這個模塊
3.3、_tag就是上面闖過來的view的uuid全局唯一標識
3.4、_counter = 0;
3.5、_handle 句柄
3.6、_PlayStatus=‘closed’,這是PlayStatus的一個,'closed' | 'loading' | 'error' | 'ended' | 'paused' | 'playing' | 'ready'
3.7、_source 資源文件信息,包含視頻格式、視頻路徑等
3.8、_poster 視頻暫停播放或者緩沖時的占位符
3.9、_playControl 播放控制
3.10、_autoPlay 自動播放
3.11、_loop 循環(huán)播放
3.12、_muted 是否靜音
3.13、_volume 音量
3.14、onUpdateTexture
3.15、onEmitEvent? 發(fā)送事件
3.16、_onCanPlay 能否播放
3.17、_onPlaying 播放中的事件
3.18、_onPause 暫停播放事件
3.19、_onEnded 結(jié)束播放事件
3.20、_onError 錯誤播放事件
3.21、_onDurationChange
3.22、_onTimeUpdate
3.23、RCTVideoPlayer.prototype的原型中定義了幾個方法:
3.23.1、 setSource:
首先用_chooseSupportSource篩選出支持的視頻格式,
然后定義上一個視頻的url(prevUrl)
再定義當前的視頻url(curUrl)
如果兩個url不一樣,剛開始preurl是null的,播放完成了就可以切換視頻的source,如果當前的視頻url(cururl)為空,定義一個prevHandle=this._handle,
this._handle = null;
如果 prevHandle存在,就更新紋理(_updateTexture),然后調(diào)用_videoModule的卸載prevHandle,視頻的狀態(tài)設(shè)置為關(guān)閉狀態(tài)(this._updatePlayStatus('closed');)
如果當前的curUrl存在,同樣設(shè)置prevHandle=this._handle,
this._counter += 1;
this._handle = [curUrl, this._tag, this._counter].join('-');定義一個handle標識
然后調(diào)用_videoModule的addHandle、setUrl、setFormat、如果有元數(shù)據(jù)設(shè)置元數(shù)據(jù)setMetaData、_addMediaEventListener(監(jiān)聽canplay、playing、pause、ended、error、durationchange、timeupdate)
然后調(diào)用_videoModule的加載load(this._handle);
如果原來的prevHandle存在,就卸載(this._videoModule.unload(prevHandle))
更新播放狀態(tài)為加載中( this._updatePlayStatus('loading');)
如果_poster存在,更新_poster紋理;
最后_updateVideoStates更新視頻狀態(tài),其實就是設(shè)置靜音、設(shè)置音量
給source添加一個uri標識符,然后調(diào)用onUpdateTexture,這個方式Video傳下來的
3.23.2、 setPoster(url): 如果當前的播放狀態(tài)是loading,調(diào)用_updateTextureWithPoster ---> onUpdateTexture(這個方法是上面的video傳下來的)
3.23.3、 play: 如果_handle存在,調(diào)用_videoModule的播放功能
3.23.4、 pause: 如果_handle存在,調(diào)用_videoModule的暫停功能
3.23.5、 seekTo: 設(shè)置新的播放起點,進度條拖拽用的
3.23.6、 setPlayControl: 如果_handle存在,看輸入的播放控制如果是pause,就調(diào)用_videoModule的暫停功能,如果是play就調(diào)用_videoModule的播放功能
3.23.7、 setAutoPlay 設(shè)置自動播放
3.23.8、 setLoop 設(shè)置循環(huán)播放
3.23.9、 setMuted 如果_handle存在,就設(shè)置靜音,this._videoModule.setMuted(this._handle, this._muted);
3.23.10、 setVolume 檢查輸入的音量是否是數(shù)字,如果不是就是1.如果_handle存在,就設(shè)置音量,this._videoModule.setVolume(this._handle, this._volume);
3.23.11、 dispose,如果_handle存在,釋放這個句柄,this._videoModule.unload(this._handle);
四、RCTVideoModule

這是個模塊,繼承了Module
4.1、 構(gòu)造函數(shù)中有下面幾個成員,supportedFormats支持的視頻格式、_videoDefs(src、format、metaData)、_players是Video/VRVideoComponent實例、_rnctx上下文對象、_mediaEventCallbacks事件回調(diào)
同樣有下面的幾個方法:
4.2、addHandle(handle)
實例化播放器對象 const player = new VRVideoComponent(); 并設(shè)置給this._players[handle],
初始化事件回調(diào)對象 this._mediaEventCallbacks[handle] = {};
初始化播放器的onMediaEvent事件:
4.3、 _onMediaEvent(handle: string, event: Object),這里面主要是處理監(jiān)聽事件的功能、一個是發(fā)送到react的,一個是發(fā)送到native的,接受事件啥的都要經(jīng)過這兒。
4.4、 _addMediaEventListener監(jiān)聽事件,上面RCTVideoPlayer調(diào)用的,意思就是把監(jiān)聽的方法都放到 this._mediaEventCallbacks[handle][eventType]本模塊的事件回調(diào)里面統(tǒng)一管理
4.5、 移除某個監(jiān)聽 this._mediaEventCallbacks[handle][eventType]
4.6、 setUrl 設(shè)置視頻地址:this._videoDefs[handle].src = url;
4.7、 setFormat 設(shè)置視頻格式:this._videoDefs[handle].format = format;
4.8、 setMetaData 設(shè)置視頻的預(yù)算內(nèi)數(shù)據(jù) this._videoDefs[handle].metaData = metaData;
4.9、 getVideoTexture 拿到當前視頻的紋理,this._players[handle].videoTextures[0];
4.10、load 設(shè)置視頻,把上面的url、format、metaData設(shè)置進去 this._players[handle].setVideo(this._videoDefs[handle]);
同時把資源加載到 mono 紋理中,this._rnctx.RCTResourceManager.addResource('MonoTexture', handle, monoTextureInfo);
4.11、 play? this._players[handle].videoPlayer.play(); 其實調(diào)用的是VRVideoPlayer的getVideoPlayer(this.videoDef),最后調(diào)用的是BasicVideoPlayer,最后調(diào)用的html的video標簽的方法
4.12、 pause this._players[handle].videoPlayer.pause();
4.13、 seekTo this._players[handle].videoPlayer.seekTo(position);
4.14、 setMuted this._players[handle].videoPlayer.setMuted(muted);
4.15、 setVolume this._players[handle].videoPlayer.setVolume(volume);
4.16、 unload 移除mono紋理資源、釋放播放器資源、刪除播放器、刪除_videoDefs的視頻資源、刪除回調(diào)事件_mediaEventCallbacks
4.17、 frame 調(diào)用播放器的 frame() 方法;
五、MediaEvent

這里面只有一個構(gòu)造函數(shù),有三個成員,一個是type、一個是timeStamp(時間戳)、一個是target里面的currentTime(當前播放時間)、duration(持續(xù)時間)、ended(是否播放完畢)、error(錯誤)
他們的值只不過是提取了event.target的一部分
六、VRVideoComponent

6.1、 構(gòu)造函數(shù)中有這么幾個成員:videoPlayer(null))、videoTextures([])、onMediaEvent、_onMediaEvent
6.2、 setVideo 設(shè)置視頻資源:
_freeVideoPlayer:釋放播放器,如果播放器存在就釋放掉(dispose),然后設(shè)置為null
_freeTexture:釋放紋理,遍歷videoTextures里面的成員,然后逐個釋放dispose,然后把、videoTextures設(shè)置為[]
_setVideoDef: 設(shè)置視頻,把上面的url、format、metaData設(shè)置進去
videoPlayer:實例化播放器,通過new 一個 VRVideoPlayer.getVideoPlayer(this.videoDef),上面就是繼承了BasicVideoPlayer類,默認返回一個BasicVideoPlayer,默認的播放器,下面再講這個東東
綁定onMediaEvent,
新建threejs紋理,const texture = new THREE.Texture(this.videoPlayer.videoElement); 設(shè)置給 this.videoTextures[0] = texture
然后初始化視頻,調(diào)用this.videoPlayer.initializeVideo(videoDef.src, videoDef.metaData)
6.3、 frame 如果播放器存在,而且有足夠的數(shù)據(jù)hasEnoughData(這個判斷video標簽存在,而且this.videoElement.readyState === this.videoElement.HAVE_ENOUGH_DATA),就遍歷videoTextures,并且刷新紋理 調(diào)用this.videoTextures[i].needsUpdate = true;
6.4、 dispose:釋放播放器 _freeVideoPlayer,釋放紋理_freeTexture,釋放onMediaEvent
七、BasicVideoPlayer

7.1、 構(gòu)造函數(shù)
videoElement就是創(chuàng)建了一個html的video dom標簽,并把這個標簽加到document.body上
_volume=1.0
this._muted = false;
this.onMediaEvent = undefined;
(this: any)._onMediaEvent = this._onMediaEvent.bind(this);
7.2、 初始化視頻 initializeVideo
設(shè)置video的src、crossOrigin(跨域)、給video標簽綁定事件_bindMediaEvents(監(jiān)聽canplay、playing、pause、ended、error、durationchange、timeupdate)、調(diào)用video的load方法
7.3、 hasEnoughData
判斷video標簽存在,而且this.videoElement.readyState === this.videoElement.HAVE_ENOUGH_DATA
7.4、 setVolume、setMuted、play、pause、seekTo都是調(diào)用video標簽的相應(yīng)功能
7.5、 dispose 暫停播放pause、從document.body移除video,src設(shè)置為'',移除監(jiān)聽事件
上面就是這幾個文件的方法和屬性及相互調(diào)用關(guān)系,但是我們?nèi)绾问褂胿ideo的其他事件呢?
八、監(jiān)聽video的其他的功能

8.1、首先在BasicVideoPlayer上,添加以上添加備注的東東,因為瀏覽器的video標簽監(jiān)聽事件還有這些東東;
8.2、監(jiān)聽事件的回調(diào)就是VRVideoComponent的onMediaEvent,同樣再次會回調(diào)給RCTVideoModule的_onMediaEvent的方法內(nèi);
在RCTVideoModule內(nèi)部的加上剩余的東東:

8.3、加上上面的東東,就可以增加其他的監(jiān)聽事件了,這里面會調(diào)向react 和 native各發(fā)送 監(jiān)聽回調(diào)事件,
react這個方法是調(diào)用想下文的callFunction,調(diào)用RCTDeviceEventEmitter的emit方法,this._rnctx.callFunction('RCTDeviceEventEmitter','emit', [callbackName,handle,mediaEvent]);
另外一個是在native上增加監(jiān)聽
8.4、在RCTVideoPlayer中加入下面的代碼

8.5、在setSource的方法內(nèi)增加其他的上面的監(jiān)聽,還要添加下面的代碼,我這里只是監(jiān)聽了loadstart這個狀態(tài),取得值是readyState的值。你也可以監(jiān)聽上面的其他狀態(tài)

同時還要在上面寫一個_newEmitter的方法,

最后在最上面的RCTVideoPlayer的方法體內(nèi)綁定這個事件:

8.6、剛剛我們在_newEmitter方法內(nèi)部發(fā)送了一個topLoadStart的監(jiān)聽事件,為了讓這個事件綁定到react組件上,需要在UIManager上添加注冊自定義的事件類型:
也就是找到 react-vr-web ---> js ---> Modules ---> UIManager里面的this.customDirectEventTypes,
然后在最后添加事件注冊名字,也就是下圖的最后一行代碼:

8.7、為了驗證我們能收到信息,可以在react-vr ---> Librearies ---> video的里面添加如下的代碼:
首先在propTypes內(nèi)部添加一個屬性:


8.8、最后在index.vr.js里面的video組件下面添加newEmitter屬性,內(nèi)部是回調(diào)方法,就可以拿到html的video標簽在loadstart狀態(tài)下的數(shù)據(jù)了。

8.9、最后重新打開服務(wù),npm? start,打開瀏覽器,輸入http://localhost:8081/vr/,查看調(diào)試console打印

看上圖的index.vr.js 104行打印的東西,readyStart為0,也就是在loadstart剛開始加載數(shù)據(jù)的之前,沒有音視頻信息;
你也可以監(jiān)聽下圖的其他狀態(tài):

下面這幾個對應(yīng)的信息是video的readyStart對應(yīng)的狀態(tài):
0 = HAVE_NOTHING - 沒有關(guān)于音頻/視頻是否就緒的信息
1 = HAVE_METADATA - 關(guān)于音頻/視頻就緒的元數(shù)據(jù)
2 = HAVE_CURRENT_DATA - 關(guān)于當前播放位置的數(shù)據(jù)是可用的,但沒有足夠的數(shù)據(jù)來播放下一幀/毫秒
3 = HAVE_FUTURE_DATA - 當前及至少下一幀的數(shù)據(jù)是可用的
4 = HAVE_ENOUGH_DATA - 可用數(shù)據(jù)足以開始播放