一、描述

rax-player 是 rax 内置的视频播放组件,具有较多复杂的控制和功能,基本上能够满足在 weex 环境的视频播放需求。

而对于不同的容器环境,其处理的流程也不一样,在 weex 环境下还是直接依赖于 weex 的 video 组件。

整个 rax-player 组件在不同的容器中分为 video.weex.jsvideo.web.js 两个入口,源码中文件包含了 8 个 js 文件,不过在 weex 环境下则是最简单的。

二、video.weex.js

虽然 rax-player 还是叫 rax-player 组件,但是 video.weex.js 实际上导出的是 Video 组件,声明也是 Video,至于为什么组件起名字的时候是 rax-player 就不得而知了,可能因为 player 比 video 承载的功能更多吧。

1、weex 未来的 videoplus 组件

目前版本(0.6.5)源码中有一段 TODO 如下:

let supportVideoPlus = false;
const weexEnv = typeof WXEnvironment !== 'undefined' ? WXEnvironment : {};
// TODO: rework by feature recognition
if (weexEnv.appName === 'TB' && weexEnv.appVersion) {
  let appVersion = weexEnv.appVersion.split('.');
  if (appVersion[0] >= 6 && appVersion[1] >= 2) {
    supportVideoPlus = true;
  }
}

这段代码应该是用在手淘上的,如果 appName 是 TB 并且 WXEnvironment.appVersion > 6.2 ,则会启用 videplus

从目前的代码来看 videoplus 应该是一个新的 video 组件,而这个组件则是由 weex 直接提供的,和 video 是同级的。

现在 weex 的源代码中并没有出现该组件,后面应该是会提供,功能相比于 video 会更强大灵活吧,毕竟起名字叫 videoplus 了。

推断会出一个 videplus 组件是基于下面代码猜想(基本上是确定的事情)的:

从下面的代码中也能够看出,videoplus 现在还不完善,还有 bug 存在,所以没有放出来,因为没放出来,所有就不过多的介绍 videoplus 了,无非是一个 video 的强化版。

if (supportVideoPlus) {
  VideoComponent = 'videoplus';
  props = {
    ...this.props,
    ...{
      src: videoSrc,
      autoPlay: true, // 因为videoplus现在有个bug,当autoPlay不为true的时候埋点会有问题,所有,当视频开始播放之后就设置autoPlay为true
      onPaused: this.onVideoPause,
      onPlaying: this.onVideoPlay,
      onError: this.onVideoFail,
      onFinish: this.onVideoFinish,
      onLandscape: this.onLandscape,
      style: styles.video,
      playControl: this.state.pause ? 'pause' : 'play',
    }
  };
}

2、默认样式及样式重新计算

默认的样式不需要过多的去阐述各个样式的意思,只需要知道,默认的宽度是 750,而默认的高度是 300.(之后肯定回去分析一波 rax 的无单位自动计算是怎么去做的,虽然也是通过 rem 去做,但是对于开发者来说,无单位自动计算数据真的很方便。)

const defaultStyles = {
  container: {
    position: 'relative',
    overflow: 'hidden',
    width: 750,
    height: 300
  },
  video: {
    width: 750,
    height: 300,
    zIndex: 1
  },
  poster: {
    overflow: 'hidden',
    position: 'absolute',
    top: 0,
    left: 0,
    zIndex: 1,
  },
  startBtn: {
    overflow: 'hidden',
    position: 'absolute',
    color: '#ffffff',
    top: 0,
    left: 0,
    zIndex: 1
  },
  startBtnImage: {
    width: 109,
    height: 111
  }
};

上面的代码是默认的样式,而最终 rax-video 组件的样式,是通过 calculateStyle 计算出来的,rax-video 支持通过 props.style 传递 video 的样式,在 calculateStyle 方法中,对 defaultStyles 进行覆盖或者是重计算行为。

其中 defaultStyles.containerdefaultStyles.video 的宽度和高度均可以通过 props.style.width/props.style.height 进行覆盖,而 startBtnstartBtnImage 的样式则需要根据高度或者是宽度进行重新计算,其中 startBtn 只要是根据视频高度计算自身的高度,而 startBtnImage 则主要是计算 marginLeftmarginTop,虽然不是很认可这种通过 marginLeftmarginTop 这种方式实现居中和垂直对齐,但是既然 rax 使用了这种方式,想必还有不知道的坑吧。

最后是计算 poster 也就是封面图的相关位置和大小。

calculateStyle() {
    let styles = defaultStyles;
    styles.container = {
      ...defaultStyles.container,
      ...this.props.style
    };
    styles.video = {
      ...defaultStyles.video,
      ...{
        width: this.props.style.width || defaultStyles.video.width,
        height: this.props.style.height || defaultStyles.video.height
      }
    };
    styles.startBtn = {
      ...defaultStyles.startBtn,
      ...{
        width: styles.video.width,
        height: parseInt(styles.video.height) - 75
      }
    };
    styles.startBtnImage = {
      ...defaultStyles.startBtnImage,
      ...{
        marginLeft: (parseInt(styles.video.width) - parseInt(defaultStyles.startBtnImage.width)) / 2,
        marginTop: (parseInt(styles.video.height) - parseInt(defaultStyles.startBtnImage.height)) / 2
      }
    };
    styles.poster = {
      ...defaultStyles.poster,
      ...{
        width: styles.video.width,
        height: styles.video.height
      }
    };
    styles.posterImage = {
      width: styles.video.width,
      height: styles.video.height
    };
    return styles;
  }

3、维护的 state

rax-player 本身维护的 state 如下:

  state = {
    pause: this.props.autoPlay ? false : true,
    poster: true,
    update: true
  };

pause 是维护暂停的状态,如果是自动播放(autoPlay=true) ,则默认为 true。

poster 是是否有封面图,默认为 true。

4、组件更新生命周期

在性能优化方面,如果开启了 autoPlay = true,则组件是不会进行更新的,而组件默认也是不会更新。

不过如果传入的是新视频(isNew 是 Video 组件的静态变量,默认为 true)或者主动声明 state.update = true则会进行组件的更新,并且更新后,直接将 isNew 置为 false,以便于声明不是新的视频。

  shouldComponentUpdate(nextProps, nextState) {
    if (this.props.autoPlay) {
      return false;
    }
    if ( this.isNew || nextState.update) {
      this.isNew = false;
      return true;
    }
    return false;
  }

5、播放状态切换 switch()

switch() 方法是当播放状态变更的时候,更改 state,设计的 state 是 updatepause

  switch = (status) => {
    this.setState({
      pause: status === 'stop',
      update: true
    });
  }

结合上面的组件更新生命周期的方法,可以发现,当视频播放状态发生变动后,组件会进行更新,因此 nextState.update 已经是变成 true 的。

需要注意的是,switch() 方法只用在播放按钮 Icon 的 onClick 事件上,其他地方没有使用该方法,其余的状态切换,均在各个事件 handler 中完成。

6、视频暂停

onVideoPause 是当视频暂停时的处理方法,期间会判断是否传入了 props.onVideoPause,如果存在才会进行调用。

无论是否需要使用 props.onVideoPause,都会判断此时是否是暂定状态,如果是暂定状态,则 state.update = false,此时不会进行组件更新。

  onVideoPause = (e) => {
    typeof this.props.onVideoPause === 'function' && this.props.onVideoPause(e);
    if (this.state.pause === false) {
      this.setState({
        pause: true,
        update: false
      });
    }
  };

7、视频播放

onVideoPlay 是视频播放触发时的处理方法,同样如果没有传入 props.onVideoPlay 则只会对 state 进行变动。

同样的,仅仅是触发 onVideoPlay 也不会进行组件更新,state.update 也是 false。

onVideoPlay = (e) => {
    typeof this.props.onVideoPlay === 'function' && this.props.onVideoPlay(e);
    this.state.poster = false;
    if (this.state.pause === true) {
      this.setState({
        pause: false,
        update: false
      });
    }
  };

8、其他的事件Handle

其他的事件 Handle 就不重复了,主要是三个方法 onVideoFailonVideoFinishonLandscape

如果不传入 props 则不会进行任何的处理。

9、render 方法

render 方法中,第一个变量 videoComponent = 'video' 便是直接证明了 weex 环境下,rax-player 使用的是 weex 的内置 video 组件。

值得注意的是,videoSrc 将传递过来的视频地址的 schema 直接干掉,全部使用 //xxx.mp4

至于渲染的方法,还是挺简单的,如果对上面各个流程和变量熟悉了,渲染的方法无非就是 View 加上 video 组件,其中可能还包括 poster 封面图等内容。

 return <View style={styles.container}>
      {
        poster && playStatus == 'stop' ?
          null :
          <VideoComponent
            {...props}
          />
      }
      { playStatus == 'stop' && poster && this.state.poster && <View
        style={styles.poster}
        ref="poster"
      >
        <Image
          source={{
            uri: poster
          }}
          style={styles.posterImage}
        />
      </View>
      }
      { playStatus == 'stop' && this.props.startBtn && <View
        style={styles.startBtn}
        ref="starBtn"
      >
        <Image
          source={{
            uri: 'https://gw.alicdn.com/tps/TB1FxjDKFXXXXcRXVXXXXXXXXXX-109-111.png'
          }}
          style={styles.startBtnImage}
          onClick={() => {
            this.switch(!playStatus);
          }}
        />
      </View>
      }
    </View>;
  }

在 render 方法中,将 weex 的默认视频播放组件 video 赋值给了 VideoComponent 变量,是因为我最前面提到的,如果支持 videoplus,则会将 videoplus 赋值给 VideoComponent,从而做到兼容性。

三、weex 容器下的效果

869414013508916728.jpg