一、描述

之前写了一篇文章,介绍 rax-player 组件在 weex 容器的源码及实现的方式,weex 容器下依赖于 weex 的 video 组件,并且在高版本或者未来的手淘中,会使用 videoplus 组件(可能现在已经在使用了,没有开源出来)。

文章地址:

web 容器环境下源码很多,而且是基于 HTML 的 <video> 实现的,这个 video 不是 weex 的 video 组件。

MDN video 文档:

二、video.web.js

主组件文件入口是 video.web.js,这个文件主要是实现了一些组件的样式构造、state 、事件绑定等。

在整个组件中,video 标签是通过一个 ref 指定,在其他的方法中均通过 this.refs.video 使用 video 的属性或者方法等。

1、维护的 state

组件主要维护的 state 如下:

state = {
  pause: true,
  currentTime: 0,
  controllerTime: 0,
  duration: 0,
  fullScreen: false
};

其中 pause 表示暂停状态,而 currentTime 为 video 透出的当前播放时长。

duration 管理视频的长度。

2、componentWillMount 方法

在 componentWillMount 生命周期中,判断是否有用控制器,并且会将 this.props.controls 释放掉。

componentWillMount() {
  this.props.hasController = this.props.controls || false;
  delete this.props.controls;
}

3、switch() 切换播放状态

video.weex.js 中也有 switch() 方法,不过 weex 容器中的 switch() 方法只在第一次挂载的时候判断是否是 autoplay,然后调用 switch() 方法进行播放。

而 video.web.js 中的 switch() 方法则是在多个地方可以用到,包括暂停和播放,触发成功后,在修改 state.pause 的值。

switch = () => {
  let video = this.refs.video;
  if (this.switchStatus) {
    video && video.pause();
  } else {
    video && video.play();
  }
  this.switchStatus = !this.switchStatus;
  this.setState({
    pause: this.switchStatus
  });
}

4、获取和设置 video 时间

获取当前视频播放时间:

  getCurrentTime() {
    return this.refs.video.currentTime;
  }

设置当前播放时间:

  setCurrentTime(time) {
    this.refs.video.currentTime = time;
    return time;
  }

5、获取视频长度

维护的 state 中 state.duration 是视频的长度,通过 getDuration() 方法获取

  getDuration() {
    return this.refs.video.duration;
  }

6、timeUpdate 更新

这个方法用于 <video> 方法的时间监听,onTimeupdate: this.timeUpdate

  timeUpdate = e => {
    if (this.state.pause || this.justifiing) {
      return e;
    }
    let currentTime = this.getCurrentTime();
    if (currentTime !== undefined && currentTime !== 0) {
      this.setState({
        currentTime: Math.floor(currentTime),
        duration: this.refs.video.duration
      });
    }
    return currentTime;
  };

7、justify 方法

这个方法实际上是 <Progress> 的一个 props 方法,拖拽滚动条会触发这个方法,然后调用上面的 timeUpdate,从而修改 currentTime:

方法有两个参数,一个是 second 当前的时间,另一个是 status,如果是 move 会将 this.justifing 方法置为 true,当拖拽结束后,在置为 false,并且调用 this.setCurrentTime(second),更新视频的播放进度,同时更新 state.controllerTime.

  justify(second, status) {
    if (status == 'move') {
      this.justifiing = true;
    } else if (status == 'end') {
      this.justifiing = false;
      this.setCurrentTime(second);
      this.setState({
        controllerTime: 0,
        currentTime: second
      });
    }
  }

8、暂停、播放、结束、失败方法的回调

这四个方法和 weex 容器环境下其实差不多,无非是调用 props 传过来的事件 handle 而已,不再重复具体的过程:

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

9、全屏控制

全屏控制通过四个方法实现,如下:

  • fullScreen()
  • exitOriginFullScreen()
  • requestOriginFullscreen()
  • _toggleFullScreen(fullscreen)

上面四个方法代码太多了,就不重复了。

10、render 方法

render 最终是渲染的 html 的 <video>,同时挂上的 props 其实也是 video 原生的属性:

  poster: this.props.poster || this.props.coverImage,
  onTimeupdate: this.timeUpdate,
  onPause: this.onVideoPause,
  onPlay: this.onVideoPlay,
  onError: this.onVideoFail,
  onDurationChange: this.onDurationChange,
  onEnded: this.onVideoFinish,
  ref: 'video'

通过 calculateVideoProps()calculateStyle() 方法计算 style 和 上面的 props:

render() {
  const videoProps = this.calculateVideoProps();
  let styles = this.calculateStyle();
  return <View style={styles.container}>
    <video
      {...videoProps}
      style={styles.video}
      webkit-playsinline />
    { this.state.pause && this.props.startBtn ? <View
      onClick={this.switch}
      style={styles.startBtn}
      ref="starBtn"
    >
      <Image source={{ uri: '//gw.alicdn.com/tps/TB1FxjDKFXXXXcRXVXXXXXXXXXX-109-111.png'}} style={styles.startBtnImage} />
    </View> : null
    }
    { this.props.hasController && env.os.iphone && <Controller
      currentTime={this.state.controllerTime || this.state.currentTime}
      totalTime={this.state.duration}
      pause={this.state.pause}
      onPauseOrStart={this.switch}
      onJustify={(second, status) => {
        this.justify(second, status);
      }}
      onFullScreen={this.fullScreen}
      hasFullScreen={this.props.hasFullScreen}
      style={styles.controller} />
    }
  </View>;
}

11、其他的依赖文件

  • controller.js : 控制按钮组件
  • dimensions.js:Point 的尺寸计算
  • env.web.js:iphone环境判断,通过 UA
  • progress.js:进度条组件
  • progresspoint.js:进度条的点组件

这些主要是功能性的组件,实际上并不是很复杂, 有时间可以将 rax 的这种实现方式了解一下, 然后单独的构造一个组件出来。

三、效果:

import {createElement, Component} from 'rax';
import View from 'rax-view';
import Text from 'rax-text';
import Picture from 'rax-picture';
import Player from 'rax-player';
import styles from './App.css';

const video = 'https://videocdn.taobao.com/oss/ali-video/1fa0c3345eb3433b8af7e995e2013cea/1458900536/video.mp4';

const poster = "https://gw.alicdn.com/tps/TB1QsDBKFXXXXcQXpXXXXXXXXXX-750-200.png";

class App extends Component {
  render() {
    return ( 
      <View style={styles.app}>
          <Player 
            src={video} 
            style={{width:750,height:400}}
            poster={poster}
            controls="controls"
            hasFullScreen={true}
            startBtn="startBtn"

          >
          </Player>
      </View>
    );
  }
}

export default App;

GIF.gif