react vr 视频(video)源码解析

react vr中文网:www.vr-react.com

qq群:481244084

开发者头条:react vr 原理解析

开发者头条:React VR 视频源码解析

开发者头条:react vr 消息传递原理解析

因为react vr的视频组件Video组件在手机上播放视频会全屏,所以前面我写了篇文章解决此事:解决react vr视频在微信和浏览器上全屏的问题

这里我再介绍下video的源码,希望能帮助到您,此篇文章较长,建议对着源码和本篇文章来阅读,否则会晕晕乎乎的哦,就像蜘蛛网一样的,首先看下下图:


react  vr 视频(video)源码解析

图片较大,如果查看原图,可以下载: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事件发送、OVRUI.UIView等等,后面抽空再给大家讲解了。

进入正题:

下面就这几个文件进行简单的讲解,

一、Video

1.1、video是一个react 组件,首先创建一个React的class,包含一堆属性和方法:http://www.vr-react.com/Video.html

如下图:


video属性和方法

1.2、componentWillMount订阅监听播放状态(play、pause、seekTo、registerUserGesture、unregisterUserGesture、volumeChange、mutedChange)

这些状态会通过UIMannager发送指令到原生那边的Video组件(在react-vr-web里面)中,如下图:


发送播放暂停指令

1.3、调用requireNativeComponent,把这个组件创建成一个React Native组件。

1.4、这个组件在UIManager的Video视图中,传下去两个参数,一个是ReactNativeContext,这是RN的上下文对象,一个是GuiSys,这个是在ovrui内部,是管理Object3D、字体、不透明度等等的一个UI工具。

下一步就到RCTVideo中了

二、RCTVideo

先来看下图:

RCTVideo

2.1、它继承了BaseView,构造函数中有下面几个成员:

    2.1.1、实例化OVRUI的UIView的view

    2.1.2、_localResource:绑定资源文件

    2.1.3、_rnctx:RN的上下文对象从UIManager的video实例化传过来的参数

    2.1.4、player是实例化的RCTVideoPlayer(../Utils/RCTVideoPlayer),传进去两个参数,一个是上下文对象,一个是view的uuid,因为view都是OVRUI的uiview都是一个object3d对象,都有一个唯一的uuid

重写了player两个方法,一个是onUpdateTexture,一个是onEmitEvent,后者是调用RN上下文的接受通知的事件,前者是回调函数,传入的是资源,资源有内部有uri,指向视频对象,然后更新图像纹理

    2.1.5、_videoModule就是调用上下文中的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、寻找播放点,一个是播放指令,一个是暂停指令,接收到指令分别调用player的对应的方法

2.4、有一个静态的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


RCTVideoPlayer

内部包含以下几个属性和方法:

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 循环播放

3.12、_muted 是否静音

3.13、_volume 音量

3.14、onUpdateTexture

3.15、onEmitEvent  发送事件

3.16、_onCanPlay 能否播放

3.17、_onPlaying 播放中的事件

3.18、_onPause 暂停播放事件

3.19、_onEnded 结束播放事件

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),然后调用_videoModule的卸载prevHandle,视频的状态设置为关闭状态(this._updatePlayStatus('closed');)

如果当前的curUrl存在,同样设置prevHandle=this._handle,

this._counter += 1;

this._handle = [curUrl, this._tag, this._counter].join('-');定义一个handle标识

然后调用_videoModule的addHandle、setUrl、setFormat、如果有元数据设置元数据setMetaData、_addMediaEventListener(监听canplay、playing、pause、ended、error、durationchange、timeupdate)

然后调用_videoModule的加载load(this._handle);

如果原来的prevHandle存在,就卸载(this._videoModule.unload(prevHandle))

更新播放状态为加载中( this._updatePlayStatus('loading');)

如果_poster存在,更新_poster纹理;

最后_updateVideoStates更新视频状态,其实就是设置静音、设置音量

给source添加一个uri标识符,然后调用onUpdateTexture,这个方式Video传下来的

3.23.2、 setPoster(url): 如果当前的播放状态是loading,调用_updateTextureWithPoster ---> onUpdateTexture(这个方法是上面的video传下来的)

3.23.3、 play: 如果_handle存在,调用_videoModule的播放功能

3.23.4、 pause: 如果_handle存在,调用_videoModule的暂停功能

3.23.5、 seekTo: 设置新的播放起点,进度条拖拽用的

3.23.6、 setPlayControl: 如果_handle存在,看输入的播放控制如果是pause,就调用_videoModule的暂停功能,如果是play就调用_videoModule的播放功能

3.23.7、 setAutoPlay 设置自动播放

3.23.8、 setLoop 设置循环播放

3.23.9、 setMuted 如果_handle存在,就设置静音,this._videoModule.setMuted(this._handle, this._muted);

3.23.10、 setVolume 检查输入的音量是否是数字,如果不是就是1.如果_handle存在,就设置音量,this._videoModule.setVolume(this._handle, this._volume);

3.23.11、 dispose,如果_handle存在,释放这个句柄,this._videoModule.unload(this._handle);

四、RCTVideoModule


RCTVideoModule

这是个模块,继承了Module

4.1、 构造函数中有下面几个成员,supportedFormats支持的视频格式、_videoDefs(src、format、metaData)、_players是Video/VRVideoComponent实例、_rnctx上下文对象、_mediaEventCallbacks事件回调

同样有下面的几个方法:

4.2、addHandle(handle)

实例化播放器对象 const player = new VRVideoComponent(); 并设置给this._players[handle],

初始化事件回调对象 this._mediaEventCallbacks[handle] = {};

初始化播放器的onMediaEvent事件:

4.3、 _onMediaEvent(handle: string, event: Object),这里面主要是处理监听事件的功能、一个是发送到react的,一个是发送到native的,接受事件啥的都要经过这儿。

4.4、 _addMediaEventListener监听事件,上面RCTVideoPlayer调用的,意思就是把监听的方法都放到 this._mediaEventCallbacks[handle][eventType]本模块的事件回调里面统一管理

4.5、 移除某个监听 this._mediaEventCallbacks[handle][eventType]

4.6、 setUrl 设置视频地址:this._videoDefs[handle].src = url;

4.7、 setFormat 设置视频格式:this._videoDefs[handle].format = format;

4.8、 setMetaData 设置视频的预算内数据 this._videoDefs[handle].metaData = metaData;

4.9、 getVideoTexture 拿到当前视频的纹理,this._players[handle].videoTextures[0];

4.10、load 设置视频,把上面的url、format、metaData设置进去 this._players[handle].setVideo(this._videoDefs[handle]);

同时把资源加载到 mono 纹理中,this._rnctx.RCTResourceManager.addResource('MonoTexture', handle, monoTextureInfo);

4.11、 play  this._players[handle].videoPlayer.play(); 其实调用的是VRVideoPlayer的getVideoPlayer(this.videoDef),最后调用的是BasicVideoPlayer,最后调用的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的视频资源、删除回调事件_mediaEventCallbacks

4.17、 frame 调用播放器的 frame() 方法;

五、MediaEvent


MediaEvent

这里面只有一个构造函数,有三个成员,一个是type、一个是timeStamp(时间戳)、一个是target里面的currentTime(当前播放时间)、duration(持续时间)、ended(是否播放完毕)、error(错误)

他们的值只不过是提取了event.target的一部分

六、VRVideoComponent


VRVideoComponent

6.1、 构造函数中有这么几个成员:videoPlayer(null))、videoTextures([])、onMediaEvent、_onMediaEvent

6.2、 setVideo 设置视频资源:

_freeVideoPlayer:释放播放器,如果播放器存在就释放掉(dispose),然后设置为null

_freeTexture:释放纹理,遍历videoTextures里面的成员,然后逐个释放dispose,然后把、videoTextures设置为[]

_setVideoDef: 设置视频,把上面的url、format、metaData设置进去

videoPlayer:实例化播放器,通过new 一个 VRVideoPlayer.getVideoPlayer(this.videoDef),上面就是继承了BasicVideoPlayer类,默认返回一个BasicVideoPlayer,默认的播放器,下面再讲这个东东

绑定onMediaEvent,

新建threejs纹理,const texture = new THREE.Texture(this.videoPlayer.videoElement); 设置给 this.videoTextures[0] = texture

然后初始化视频,调用this.videoPlayer.initializeVideo(videoDef.src, videoDef.metaData)

6.3、 frame 如果播放器存在,而且有足够的数据hasEnoughData(这个判断video标签存在,而且this.videoElement.readyState === this.videoElement.HAVE_ENOUGH_DATA),就遍历videoTextures,并且刷新纹理 调用this.videoTextures[i].needsUpdate = true;

6.4、 dispose:释放播放器 _freeVideoPlayer,释放纹理_freeTexture,释放onMediaEvent

七、BasicVideoPlayer


BasicVideoPlayer

7.1、 构造函数

videoElement就是创建了一个html的video dom标签,并把这个标签加到document.body上

_volume=1.0

this._muted = false;

this.onMediaEvent = undefined;

(this: any)._onMediaEvent = this._onMediaEvent.bind(this);

7.2、 初始化视频 initializeVideo

设置video的src、crossOrigin(跨域)、给video标签绑定事件_bindMediaEvents(监听canplay、playing、pause、ended、error、durationchange、timeupdate)、调用video的load方法

7.3、 hasEnoughData

判断video标签存在,而且this.videoElement.readyState === this.videoElement.HAVE_ENOUGH_DATA

7.4、 setVolume、setMuted、play、pause、seekTo都是调用video标签的相应功能

7.5、 dispose 暂停播放pause、从document.body移除video,src设置为'',移除监听事件

上面就是这几个文件的方法和属性及相互调用关系,但是我们如何使用video的其他事件呢?

八、监听video的其他的功能


BasicVideoPlayer

8.1、首先在BasicVideoPlayer上,添加以上添加备注的东东,因为浏览器的video标签监听事件还有这些东东;

8.2、监听事件的回调就是VRVideoComponent的onMediaEvent,同样再次会回调给RCTVideoModule的_onMediaEvent的方法内;

在RCTVideoModule内部的加上剩余的东东:


RCTVideoModule

8.3、加上上面的东东,就可以增加其他的监听事件了,这里面会调向react 和 native各发送 监听回调事件,

react这个方法是调用想下文的callFunction,调用RCTDeviceEventEmitter的emit方法,this._rnctx.callFunction('RCTDeviceEventEmitter','emit', [callbackName,handle,mediaEvent]);

另外一个是在native上增加监听

8.4、在RCTVideoPlayer中加入下面的代码

1

8.5、在setSource的方法内增加其他的上面的监听,还要添加下面的代码,我这里只是监听了loadstart这个状态,取得值是readyState的值。你也可以监听上面的其他状态


setSource添加监听

同时还要在上面写一个_newEmitter的方法,


添加_newEmitter方法

最后在最上面的RCTVideoPlayer的方法体内绑定这个事件:


把_newEmitter绑定到RCTVideoPlayer

8.6、刚刚我们在_newEmitter方法内部发送了一个topLoadStart的监听事件,为了让这个事件绑定到react组件上,需要在UIManager上添加注册自定义的事件类型:

也就是找到 react-vr-web ---> js ---> Modules ---> UIManager里面的this.customDirectEventTypes,

然后在最后添加事件注册名字,也就是下图的最后一行代码:


注册事件类型

8.7、为了验证我们能收到信息,可以在react-vr ---> Librearies ---> video的里面添加如下的代码:

首先在propTypes内部添加一个属性:


添加属性
在render中添加具体的属性绑定

8.8、最后在index.vr.js里面的video组件下面添加newEmitter属性,内部是回调方法,就可以拿到html的video标签在loadstart状态下的数据了。


业务添加newEmitter属性

8.9、最后重新打开服务,npm  start,打开浏览器,输入http://localhost:8081/vr/,查看调试console打印


看上图的index.vr.js 104行打印的东西,readyStart为0,也就是在loadstart刚开始加载数据的之前,没有音视频信息;

你也可以监听下图的其他状态:


新增的video监听的状态

下面这几个对应的信息是video的readyStart对应的状态:

0 = HAVE_NOTHING - 没有关于音频/视频是否就绪的信息

1 = HAVE_METADATA - 关于音频/视频就绪的元数据

2 = HAVE_CURRENT_DATA - 关于当前播放位置的数据是可用的,但没有足够的数据来播放下一帧/毫秒

3 = HAVE_FUTURE_DATA - 当前及至少下一帧的数据是可用的

4 = HAVE_ENOUGH_DATA - 可用数据足以开始播放

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容