阅读 769

关于React-Native-Video这个组件,也就这么多了吧

听过有图就有赞

  • 竖屏状态

  • 横屏状态

前情提要

之前有写过对react-native-video这个组件的基础功能介绍,本次为react-native-video使用对拓展介绍,包括我在实际开发过程中遇到的一些小问题以及解决方案。

开始

基于对实际产品对需要,要求这个视频组建不单单对视频对播放,还有对视频进行相关控制,例如全屏显示,声音调节,进度调节,进度显示,声音,速率等等。

全屏显示

在做全屏显示的时候主要是对Android系统的适配多一些,主要是因为Android系统不支持组件自带的fullscreen属性,f**k~相信对这个组件有所了解的同学都会知道这个问题,我之前在实现的时候也查阅了相关的资料,基本上是两种实现形式:

  1. 通过路由跳转实现进入全屏页播放 也就是用两个不同的视频组件,一个小的,用于当前显示,一个大的用于视频全屏显示,这个方式主要的问题在于交互不友好,屏幕方向转换和路由跳转共同进行导致整个行为在展示上不太能让人接受。
  2. 通过样式属性修改布局进行全屏展示 我感觉这个方式也就是写在demo里比较好实现,稍微复杂一点的嵌套结构你就没法通过设置样式达到覆盖全屏的效果。

以上两种是我在网上看到的,然而一个有已知的问题了,一个还能满足我的需求,只能另辟蹊径,全屏覆盖我首先想到的就是拟态框,正好,RN本身和RN-antd都有这个组件,而我的实现方式就是通过Modal组件创建全屏浮层来实现视屏全屏显示。

实现过程

  1. 组件结构 首先先构建一个如下结构的视频UI
<TouchableOpacity
   style={[
     styles.content,
     //这里之所以这么写是为了适配全屏模式,全屏模式也是用这个组件
     isModal && {width: '100%', height: '100%'} 
   ]}
   // 额,,这个是我用来动态获取父级元素尺寸来设置视频组件尺寸用的。。。
   onLayout={this.layoutHandle}
   onPress={this.changeShowCtrl}
 >
	 <Video
	   ref={video => this.video = video}
	   source={{uri}}
	   poster={poster}
	   paused={paused}
	   repeat={true}
	   onLoad={this.loadHandle}
	   onProgress={this.progressHandle}
	   resizeMode="cover"
	   posterResizeMode="cover"
	   style={styles.video}
	 />
     {
       showCtrl
       && <View style={styles.ctrl_area}>
         <TouchableOpacity onPress={this.changePausedCtrl}>
           <Image source={
             paused
             ? require('./images/pause_icon.png')
             : require('./images/start_icon.png')
           } style={styles.pause_icon} />
         </TouchableOpacity>
         <View style={styles.pregress_area}>
           <Text style={[styles.time_text, {marginLeft: 8.5}]}>{formatTime(currentTime)}</Text>
           <Slider
             style={{flex: 1}}
             maximumTrackTintColor={'#e7e7e7'}
             minimumTrackTintColor={'#f1321d'}
             minimumValue={0}
             maximumValue={Number(duration)}
             onValueChange={this.sliderValueChange}
             onSlidingComplete={this.sliderChangeComplete}
             value={currentTime}
             step={1}
             // 可恶啊,Android只能勉强设置一下滑块的颜色了。。。
             thumbTintColor="#fff"
           />
           <Text style={[styles.time_text, {marginRight: 8.5,}]}>{formatTime(duration)}</Text>
           <TouchableOpacity onPress={this.changeOrientation}>
              <Image source={require('./images/large_screen_icon.png')} style={{width: 13.5, height: 13.5, marginRight: 5}} />
           </TouchableOpacity>
         </View>
       </View>
     }
</TouchableOpacity>
复制代码

必要的逻辑我会在下面介绍,这样,就初步完成了一个拥有基本显示和控制功能的视频组件了。

因为当视屏处于全屏状态时,是已经脱离的原来所在的位置的存在,如果在直接用原本的那个视频组件,难免会导致原来的布局出现问题,就像我所实现的是一个视频列表,每个视频都能进行全屏显示,所以我使用Modal嵌套全屏视频组件,附着在我正常的视频组件上,而我采用的办法是,把它高阶一下:

	function videoModalWrapper (wrapped) {
	  return class extends React.Component {
	    state = {
	      fullscreen: false
	    }
	
	    changeFullScreen = () => {
	      const { fullscreen } = this.state
	      
		  // 视频全屏的状态不仅要协调这两个视频组件,还要协调屏幕方向,比如像下面这样~
	      if (fullscreen) {
	        Orientation.lockToPortrait()
	        this.VideoPlayer.StartLoadingVideo()
	      } else {
	        Orientation.lockToLandscape()
	        this.VideoPlayer.ResetVideo()
	      }
	      this.setState({ fullscreen: !fullscreen })
	    }
	
	    render () {
	      const { fullscreen } = this.state
	
	      return (
	        <View>
	        	//这里是正常情况下显示的视频组件
	          <VideoPlayer
	            ref={ref => this.VideoPlayer = ref}
	            {...this.props}
	            // 协调局部、全屏播放进度
	            changeFullScreen={this.changeFullScreen}
	          />
	          <Modal
	            visible={fullscreen}
	            transparent={true}
	            onRequestClose={this.changeFullScreen}
	          >
	          	// 而这里实在全屏情况下展示的视频组件
	            <VideoPlayer
	              {...this.props}
	              // 全屏组件的独有标记
	              isModal
	              // 协调局部、全屏播放进度
	              changeFullScreen={this.changeFullScreen}
	            />
	          </Modal>
	        </View>
	      )
	    }
	  }
	}
复制代码

这里需要注意的是在全部展示和局部展示相互切换的时候,要控制好组件的播放状态,说到底这还是两个两个单独的组件,我这里是在高阶组件里对他们的播放状态进行协调,没啜,就是this.changeFullScreen方法,其实这样还是比较方便的,全屏组件在<Modal>里,当它隐藏的时候全屏组件会被卸载,所以你更多的关心局部组件就可以了。 奥奥!差点忘了,那个库Orientation,是否全屏的状态值配合他来控制屏幕的反转状态。

声音调节

其实我一直不觉的在移动端上你对一个app内部组件单独设置音量有啥重要意义,但我依旧佩服这个组件的作者能把volume这个接口暴露出来供我们直接调用,直接拿<Slider>怼在那,状态相互关联,就没啥好说的了。

进度调节

说到用<Slider>,那这个地方它肯定是必不可少的,对于视频进度的调节绝大部分时候也是用它,而在用它控制视频的时候,往往写成这样还是不太行的:

	sliderValueChange = currentTime => {
		this.video.seek(currentTime)
		this.setState({ currentTime })
	}
复制代码

但可能刚开始就是这样写的。。。。 为什么不行呢,因为我试过了🥶,这是因为当你在手动调整视频进度的时候,视频还在继续着,而视频的onProgress={this.progressHandle}属性再和你争先恐后着抢夺对当前播放时间的控制权,当我在处理这部分逻辑的时候使用了一个临时值和<Slider>的另外一个属性onSlidingComplete:它的作用是在用户手离开滑块也就是用户完成进度操作后调用的一个事件。而那个临时值我用来存放视频被调整之前它正在播放与否:

	sliderValueChange = currentTime => {
		const { prePaused, paused } = this.state
		
		// 存一下暂时的播放状态值
		if (prePaused === null) {
			this.setState({ prePaused: paused })
		}
		// 滑块滑动的时候,先不管三七二十一暂停视频,调整视频进度,然!后!再视频组件的进度~
		this.setState({
			paused: true,
			currentTime,
		}, () => this.video.seek(currentTime))
	}
	
	// 这里这里!滑动结束,视频继续原来的状态,临时值还原~
	sliderChangeComplete = currentTime => {
		const { prePaused } = this.state
		
		this.setState({
			paused: prePaused,
			prePaused: null,
			currentTime
		})
	}
复制代码

很奇怪为啥<Slider>会暴露完成滑动的事件属性?为我准备的?还是我只是众多遇到这个问题的其中一个,哈哈,反正有用,反正好使,现在再进行视频进度调节的时候,有没有流畅一些了,那必然有~

场景转换

因为每个createStackNavigator创建的路由栈的栈底始终会存在而没有卸载,这会导致另一个比较奇葩的问题,当你在多个路由栈的栈底页面又存在视频组件的时候,他们也就不会消失,你会经历一个视频在另一个页面自嗨的情况🤯。 不过解决也很简单,把路由和状态管理关联一下,这样始终能在拿到当前在那个页面,再给不同页面打上标签,表明它属于哪个页面,当全局状态更新,没有在当前路由下的视频组建就不会播放,当然,会不会播放也是你来控制的,我的做法是把一些控制视频的方法暴露出来,例如播放啦,重置啦。。。这样随时随地控制起来比较方便。

同时,暴露出来的控制方法也能帮助我们实现很多效果,比如做相关视频推荐,你可以一个视频接一个视频的点下去,也正是导航部分的区别,在RN里你跳向下一个页面的时候上一个页面并不会主动消失,这时候,就是通过路由的<NavigationEvents />组件配合这些暴露出来的方法在 页面转换时对即将离开的视频组件和即将到来的视频组件做一些必要的控制,当然,回退的话就好多了,因为回退是出栈,组件会卸载的呀,哈哈哈。

就到这里

到这里就是我对这个组件使用了一段时间后的一些经验,算是分享,更是记录,希望点点滴滴积累,终有一天拍拍膝盖上的土仰天长叹:老子站起来啦~🤪🤪🤪🤪🤪

文章分类
前端
文章标签