react-native-video-controls 视频播放

2021-05-19  本文已影响0人  物联白菜

参考:https://www.jianshu.com/p/2db4e3e2c343

效果图:


11621426060_.pic_hd.jpg

因为是修改第三库,所以播放器中缺少的icon需要自己添加


import VideoPlayer from 'react-native-video-controls';


<View style={[Styles.fullScreen,]}>
        {
            this.state.videoUrl === '' ?
                <View style={{flex:1,position:'absolute',height:200,width:'100%',zIndex: 1,justifyContent:'center',alignItems:'center'}}>
                    <Text style={{color:'#fff',}}>视频加载失败</Text>
                    {/*<Image source={{uri:this.state.lesson_image_url}} style={{height:200,width:'100%',position:'absolute',opacity:1,}}/>*/}
                </View>
                    :null
        }

        {
                    <VideoPlayer
                        // key={id}
                        source={{uri: this.state.videoUrl}}   // Can be a URL or a local file.
                        // source={{uri: 'http://vfx.mtime.cn/Video/2019/02/04/mp4/190204084208765161.mp4'}}   // Can be a URL or a local file.
                        ref={(ref) => {
                            this.player = ref
                        }}                                      // Store reference
                        style={[Styles.fullScreen,]}
                        // poster={'https://oss.aliyuncs.com/tpsite2/jingdu.xyd.qushiyun.com/2ab919f692a79dab55405cf3a2bc9e44.jpg'}  //加载视频时要显示的图像值:带有海报URL的字符串,例如“https://baconmockup.com/300/200/”
                        poster={this.state.lesson_image_url}  //加载视频时要显示的图像值:带有海报URL的字符串,例如“https://baconmockup.com/300/200/”
                        posterResizeMode={'cover'} //确定当帧与原始视频尺寸不匹配时如何调整海报图像的大小。contain、center、cover、none、repeat、stretch
                        rate={this.state.rate}  //视频播放的速率。 0.0 - 暂停播放  1.0 - 正常速率播放
                        paused={this.state.paused}  //控制播放器是否暂停。
                        // paused={item.paused}  //控制播放器是否暂停。
                        playWhenInactive={true}   //在通知或控制中心位于视频前面时是否应继续播放媒体。
                        volume={this.state.volume}  //调整音量
                        muted={this.state.muted}  //控制音频是否静音。
                        resizeMode={this.state.resizeMode}   //确定当帧与原始视频尺寸不匹配时如何调整视频大小。 "none"(默认) - 不匹配大小 contain、cover、stretch
                        fullscreen={this.state.isFullScreen}
                        fullscreenAutorotate={true}
                        fullscreenOrientation={'landscape'}

                        onPause={()=>this.onPause()}
                        onPlay={()=>this.onPlay()}
                        onBack={()=>this.onBack()}
                        onEnterFullscreen={()=>this.onEnterFullscreen()}
                        onExitFullscreen={()=>this.onExitFullscreen()}
                        onOpenMore={()=>this.onOpenMore()}   //这是我修改第三方库自己添加上去的方法
                        onLoad={this.onLoad}    //加载媒体并准备播放时调用的回调函数。
                        onLoadStart={this.onLoadStart}    //媒体开始加载时调用的回调函数。
                        onProgress={this.onProgress}   //视频播放过程中每个间隔进度单位(通常不足一秒,你可以打印日志测试下)调用的回调,其中包含有关媒体当前正在播放的位置的信息。
                        onBuffer={this.onBuffer}                // 远程视频缓冲时的回调
                        onError={this.videoError}               // 播放失败后的回调
                        onEnd={this.onEnd}                      // 播放完成后的回调
                        // onTimedMetadata={this.onTimedMetadata}                      // 当定时元数据可用时调用的回调函数.
                        onAudioBecomingNoisy={this.onAudioBecomingNoisy}
                        onAudioFocusChanged={this.onAudioFocusChanged}
                        repeat={false}  //视频放完后是否重播
                    />
        }
    </View>

VedioPlayer.js

import React, {Component} from 'react';
import Video from 'react-native-video';
import {
  TouchableWithoutFeedback,
  TouchableHighlight,
  ImageBackground,
  PanResponder,
  StyleSheet,
  Animated,
  SafeAreaView,
  Easing,
  Image,
  View,
  Text,
} from 'react-native';
import padStart from 'lodash/padStart';

export default class VideoPlayer extends Component {
  static defaultProps = {
    toggleResizeModeOnFullscreen: true,
    controlAnimationTiming: 500,
    doubleTapTime: 130,
    playInBackground: false,
    playWhenInactive: false,
    resizeMode: 'contain',
    isFullscreen: false,
    showOnStart: true,
    paused: false,
    repeat: false,
    muted: false,
    volume: 1,
    title: '',
    rate: 1,
  };

  constructor(props) {
    super(props);

    /**
     * All of our values that are updated by the
     * methods and listeners in this class
     */
    this.state = {
      // Video
      resizeMode: this.props.resizeMode,
      paused: this.props.paused,
      poster: this.props.poster,
      muted: this.props.muted,
      volume: this.props.volume,
      rate: this.props.rate,
      // Controls

      isFullscreen:
          this.props.isFullScreen || this.props.resizeMode === 'cover' || false,
      showTimeRemaining: true,
      volumeTrackWidth: 0,
      volumeFillWidth: 0,
      seekerFillWidth: 0,
      showControls: this.props.showOnStart,
      volumePosition: 0,
      seekerPosition: 0,
      volumeOffset: 0,
      seekerOffset: 0,
      seeking: false,
      originallyPaused: false,
      scrubbing: false,
      loading: false,
      currentTime: 0,
      error: false,
      duration: 0,
    };

    /**
     * Any options that can be set at init.
     */
    this.opts = {
      playWhenInactive: this.props.playWhenInactive,
      playInBackground: this.props.playInBackground,
      repeat: this.props.repeat,
      title: this.props.title,
    };

    /**
     * Our app listeners and associated methods
     */
    this.events = {
      onError: this.props.onError || this._onError.bind(this),
      onBack: this.props.onBack || this._onBack.bind(this),
      onEnd: this.props.onEnd || this._onEnd.bind(this),
      onScreenTouch: this._onScreenTouch.bind(this),
      onEnterFullscreen: this.props.onEnterFullscreen,
      onExitFullscreen: this.props.onExitFullscreen,
      onShowControls: this.props.onShowControls,
      onHideControls: this.props.onHideControls,
      onLoadStart: this._onLoadStart.bind(this),
      onProgress: this._onProgress.bind(this),
      onSeek: this._onSeek.bind(this),
      onLoad: this._onLoad.bind(this),
      onPause: this.props.onPause,
      onPlay: this.props.onPlay,
      onOpenMore: this.props.onOpenMore,
    };

    /**
     * Functions used throughout the application
     */
    this.methods = {
      toggleFullscreen: this._toggleFullscreen.bind(this),
      togglePlayPause: this._togglePlayPause.bind(this),
      toggleControls: this._toggleControls.bind(this),
      toggleTimer: this._toggleTimer.bind(this),
    };

    /**
     * Player information
     */
    this.player = {
      controlTimeoutDelay: this.props.controlTimeout || 15000,
      volumePanResponder: PanResponder,
      seekPanResponder: PanResponder,
      controlTimeout: null,
      tapActionTimeout: null,
      volumeWidth: 150,
      iconOffset: 0,
      seekerWidth: 0,
      ref: Video,
      scrubbingTimeStep: this.props.scrubbing || 0,
      tapAnywhereToPause: this.props.tapAnywhereToPause,
    };

    /**
     * Various animations
     */
    const initialValue = this.props.showOnStart ? 1 : 0;

    this.animations = {
      bottomControl: {
        marginBottom: new Animated.Value(0),
        opacity: new Animated.Value(initialValue),
      },
      topControl: {
        marginTop: new Animated.Value(0),
        opacity: new Animated.Value(initialValue),
      },
      video: {
        opacity: new Animated.Value(1),
      },
      loader: {
        rotate: new Animated.Value(0),
        MAX_VALUE: 360,
      },
    };

    /**
     * Various styles that be added...
     */
    this.styles = {
      videoStyle: this.props.videoStyle || {},
      containerStyle: this.props.style || {},
    };
  }

  /**
   | -------------------------------------------------------
   | Events
   | -------------------------------------------------------
   |
   | These are the events that the <Video> component uses
   | and can be overridden by assigning it as a prop.
   | It is suggested that you override onEnd.
   |
   */

  /**
   * When load starts we display a loading icon
   * and show the controls.
   */
  _onLoadStart() {
    let state = this.state;
    state.loading = true;
    this.loadAnimation();
    this.setState(state);

    if (typeof this.props.onLoadStart === 'function') {
      this.props.onLoadStart(...arguments);
    }
  }

  /**
   * When load is finished we hide the load icon
   * and hide the controls. We also set the
   * video duration.
   *
   * @param {object} data The video meta data
   */
  _onLoad(data = {}) {
    let state = this.state;

    state.duration = data.duration;
    state.loading = false;
    this.setState(state);

    if (state.showControls) {
      this.setControlTimeout();
    }

    if (typeof this.props.onLoad === 'function') {
      this.props.onLoad(...arguments);
    }
  }

  /**
   * For onprogress we fire listeners that
   * update our seekbar and timer.
   *
   * @param {object} data The video meta data
   */
  _onProgress(data = {}) {
    let state = this.state;
    if (!state.scrubbing) {
      state.currentTime = data.currentTime;

      if (!state.seeking) {
        const position = this.calculateSeekerPosition();
        this.setSeekerPosition(position);
      }

      if (typeof this.props.onProgress === 'function') {
        this.props.onProgress(...arguments);
      }

      this.setState(state);
    }
  }

  /**
   * For onSeek we clear scrubbing if set.
   *
   * @param {object} data The video meta data
   */
  _onSeek(data = {}) {
    let state = this.state;
    if (state.scrubbing) {
      state.scrubbing = false;
      state.currentTime = data.currentTime;

      // Seeking may be false here if the user released the seek bar while the player was still processing
      // the last seek command. In this case, perform the steps that have been postponed.
      if (!state.seeking) {
        this.setControlTimeout();
        state.paused = state.originallyPaused;
      }

      this.setState(state);
    }
  }

  /**
   * It is suggested that you override this
   * command so your app knows what to do.
   * Either close the video or go to a
   * new page.
   */
  _onEnd() {}

  /**
   * Set the error state to true which then
   * changes our renderError function
   *
   * @param {object} err  Err obj returned from <Video> component
   */
  _onError(err) {
    let state = this.state;
    state.error = true;
    state.loading = false;

    this.setState(state);
  }

  /**
   * This is a single and double tap listener
   * when the user taps the screen anywhere.
   * One tap toggles controls and/or toggles pause,
   * two toggles fullscreen mode.
   */
  _onScreenTouch() {
    if (this.player.tapActionTimeout) {
      clearTimeout(this.player.tapActionTimeout);
      this.player.tapActionTimeout = 0;
      this.methods.toggleFullscreen();
      const state = this.state;
      if (state.showControls) {
        this.resetControlTimeout();
      }
    } else {
      this.player.tapActionTimeout = setTimeout(() => {
        const state = this.state;
        if (this.player.tapAnywhereToPause && state.showControls) {
          this.methods.togglePlayPause();
          this.resetControlTimeout();
        } else {
          this.methods.toggleControls();
        }
        this.player.tapActionTimeout = 0;
      }, this.props.doubleTapTime);
    }
  }

  /**
   | -------------------------------------------------------
   | Methods
   | -------------------------------------------------------
   |
   | These are all of our functions that interact with
   | various parts of the class. Anything from
   | calculating time remaining in a video
   | to handling control operations.
   |
   */

  /**
   * Set a timeout when the controls are shown
   * that hides them after a length of time.
   * Default is 15s
   */
  setControlTimeout() {
    this.player.controlTimeout = setTimeout(() => {
      this._hideControls();
    }, this.player.controlTimeoutDelay);
  }

  /**
   * Clear the hide controls timeout.
   */
  clearControlTimeout() {
    clearTimeout(this.player.controlTimeout);
  }

  /**
   * Reset the timer completely
   */
  resetControlTimeout() {
    this.clearControlTimeout();
    this.setControlTimeout();
  }

  /**
   * Animation to hide controls. We fade the
   * display to 0 then move them off the
   * screen so they're not interactable
   */
  hideControlAnimation() {
    Animated.parallel([
      Animated.timing(this.animations.topControl.opacity, {
        toValue: 0,
        duration: this.props.controlAnimationTiming,
        useNativeDriver: false,
      }),
      Animated.timing(this.animations.topControl.marginTop, {
        toValue: -100,
        duration: this.props.controlAnimationTiming,
        useNativeDriver: false,
      }),
      Animated.timing(this.animations.bottomControl.opacity, {
        toValue: 0,
        duration: this.props.controlAnimationTiming,
        useNativeDriver: false,
      }),
      Animated.timing(this.animations.bottomControl.marginBottom, {
        toValue: -100,
        duration: this.props.controlAnimationTiming,
        useNativeDriver: false,
      }),
    ]).start();
  }

  /**
   * Animation to show controls...opposite of
   * above...move onto the screen and then
   * fade in.
   */
  showControlAnimation() {
    Animated.parallel([
      Animated.timing(this.animations.topControl.opacity, {
        toValue: 1,
        useNativeDriver: false,
        duration: this.props.controlAnimationTiming,
      }),
      Animated.timing(this.animations.topControl.marginTop, {
        toValue: 0,
        useNativeDriver: false,
        duration: this.props.controlAnimationTiming,
      }),
      Animated.timing(this.animations.bottomControl.opacity, {
        toValue: 1,
        useNativeDriver: false,
        duration: this.props.controlAnimationTiming,
      }),
      Animated.timing(this.animations.bottomControl.marginBottom, {
        toValue: 0,
        useNativeDriver: false,
        duration: this.props.controlAnimationTiming,
      }),
    ]).start();
  }

  /**
   * Loop animation to spin loader icon. If not loading then stop loop.
   */
  loadAnimation() {
    if (this.state.loading) {
      Animated.sequence([
        Animated.timing(this.animations.loader.rotate, {
          toValue: this.animations.loader.MAX_VALUE,
          duration: 1500,
          easing: Easing.linear,
          useNativeDriver: false,
        }),
        Animated.timing(this.animations.loader.rotate, {
          toValue: 0,
          duration: 0,
          easing: Easing.linear,
          useNativeDriver: false,
        }),
      ]).start(this.loadAnimation.bind(this));
    }
  }

  /**
   * Function to hide the controls. Sets our
   * state then calls the animation.
   */
  _hideControls() {
    if (this.mounted) {
      let state = this.state;
      state.showControls = false;
      this.hideControlAnimation();
      typeof this.events.onHideControls === 'function' &&
      this.events.onHideControls();

      this.setState(state);
    }
  }

  /**
   * Function to toggle controls based on
   * current state.
   */
  _toggleControls() {
    let state = this.state;
    state.showControls = !state.showControls;

    if (state.showControls) {
      this.showControlAnimation();
      this.setControlTimeout();
      typeof this.events.onShowControls === 'function' &&
      this.events.onShowControls();
    } else {
      this.hideControlAnimation();
      this.clearControlTimeout();
      typeof this.events.onHideControls === 'function' &&
      this.events.onHideControls();
    }

    this.setState(state);
  }

  /**
   * Toggle fullscreen changes resizeMode on
   * the <Video> component then updates the
   * isFullscreen state.
   */
  _toggleFullscreen() {
    let state = this.state;

    state.isFullscreen = !state.isFullscreen;

    if (this.props.toggleResizeModeOnFullscreen) {
      state.resizeMode = state.isFullscreen === true ? 'cover' : 'contain';
    }

    if (state.isFullscreen) {
      typeof this.events.onEnterFullscreen === 'function' &&
      this.events.onEnterFullscreen();
    } else {
      typeof this.events.onExitFullscreen === 'function' &&
      this.events.onExitFullscreen();
    }

    this.setState(state);
  }

  /**
   * Toggle playing state on <Video> component
   */
  _togglePlayPause() {
    let state = this.state;
    state.paused = !state.paused;

    if (state.paused) {
      typeof this.events.onPause === 'function' && this.events.onPause();
    } else {
      typeof this.events.onPlay === 'function' && this.events.onPlay();
    }

    this.setState(state);
  }

  /**
   * Toggle between showing time remaining or
   * video duration in the timer control
   */
  _toggleTimer() {
    let state = this.state;
    state.showTimeRemaining = !state.showTimeRemaining;
    this.setState(state);
  }

  /**
   * The default 'onBack' function pops the navigator
   * and as such the video player requires a
   * navigator prop by default.
   */
  _onBack() {
    if (this.props.navigator && this.props.navigator.pop) {
      this.props.navigator.pop();
    } else {
      console.warn(
          'Warning: _onBack requires navigator property to function. Either modify the onBack prop or pass a navigator prop',
      );
    }
  }

  /**
   * Calculate the time to show in the timer area
   * based on if they want to see time remaining
   * or duration. Formatted to look as 00:00.
   */
  calculateTime() {
    if (this.state.showTimeRemaining) {
      const time = this.state.duration - this.state.currentTime;
      return `${this.formatTime(time)}`;
    }

    return this.formatTime(this.state.currentTime);
  }

  /**
   * Format a time string as mm:ss
   *
   * @param {int} time time in milliseconds
   * @return {string} formatted time string in mm:ss format
   */
  formatTime(time = 0) {
    const symbol = this.state.showRemainingTime ? '-' : '';
    time = Math.min(Math.max(time, 0), this.state.duration);

    const formattedMinutes = padStart(Math.floor(time / 60).toFixed(0), 2, 0);
    const formattedSeconds = padStart(Math.floor(time % 60).toFixed(0), 2, 0);

    return `${symbol}${formattedMinutes}:${formattedSeconds}`;
  }

  formatSeconds(second) {
    let h = 0, i = 0, s = parseInt(second);
    if (s > 60) {
      i = parseInt(s / 60);
      s = parseInt(s % 60);
    }
    // 补零
    let zero = function (v) {
      return (v >> 0) < 10 ? "0" + v : v;
    };
    // return [zero(h), zero(i), zero(s)].join(":");    //00:00:00
    return [zero(i), zero(s)].join(":");    //00:00
  }

  /**
   * Set the position of the seekbar's components
   * (both fill and handle) according to the
   * position supplied.
   *
   * @param {float} position position in px of seeker handle}
   */
  setSeekerPosition(position = 0) {
    let state = this.state;
    position = this.constrainToSeekerMinMax(position);

    state.seekerFillWidth = position;
    state.seekerPosition = position;

    if (!state.seeking) {
      state.seekerOffset = position;
    }

    this.setState(state);
  }

  /**
   * Constrain the location of the seeker to the
   * min/max value based on how big the
   * seeker is.
   *
   * @param {float} val position of seeker handle in px
   * @return {float} constrained position of seeker handle in px
   */
  constrainToSeekerMinMax(val = 0) {
    if (val <= 0) {
      return 0;
    } else if (val >= this.player.seekerWidth) {
      return this.player.seekerWidth;
    }
    return val;
  }

  /**
   * Calculate the position that the seeker should be
   * at along its track.
   *
   * @return {float} position of seeker handle in px based on currentTime
   */
  calculateSeekerPosition() {
    const percent = this.state.currentTime / this.state.duration;
    return this.player.seekerWidth * percent;
  }

  /**
   * Return the time that the video should be at
   * based on where the seeker handle is.
   *
   * @return {float} time in ms based on seekerPosition.
   */
  calculateTimeFromSeekerPosition() {
    const percent = this.state.seekerPosition / this.player.seekerWidth;
    return this.state.duration * percent;
  }

  /**
   * Seek to a time in the video.
   *
   * @param {float} time time to seek to in ms
   */
  seekTo(time = 0) {
    let state = this.state;
    state.currentTime = time;
    this.player.ref.seek(time);
    this.setState(state);
  }

  /**
   * Set the position of the volume slider
   *
   * @param {float} position position of the volume handle in px
   */
  setVolumePosition(position = 0) {
    let state = this.state;
    position = this.constrainToVolumeMinMax(position);
    state.volumePosition = position + this.player.iconOffset;
    state.volumeFillWidth = position;

    state.volumeTrackWidth = this.player.volumeWidth - state.volumeFillWidth;

    if (state.volumeFillWidth < 0) {
      state.volumeFillWidth = 0;
    }

    if (state.volumeTrackWidth > 150) {
      state.volumeTrackWidth = 150;
    }

    this.setState(state);
  }

  /**
   * Constrain the volume bar to the min/max of
   * its track's width.
   *
   * @param {float} val position of the volume handle in px
   * @return {float} contrained position of the volume handle in px
   */
  constrainToVolumeMinMax(val = 0) {
    if (val <= 0) {
      return 0;
    } else if (val >= this.player.volumeWidth + 9) {
      return this.player.volumeWidth + 9;
    }
    return val;
  }

  /**
   * Get the volume based on the position of the
   * volume object.
   *
   * @return {float} volume level based on volume handle position
   */
  calculateVolumeFromVolumePosition() {
    return this.state.volumePosition / this.player.volumeWidth;
  }

  /**
   * Get the position of the volume handle based
   * on the volume
   *
   * @return {float} volume handle position in px based on volume
   */
  calculateVolumePositionFromVolume() {
    return this.player.volumeWidth * this.state.volume;
  }

  /**
   | -------------------------------------------------------
   | React Component functions
   | -------------------------------------------------------
   |
   | Here we're initializing our listeners and getting
   | the component ready using the built-in React
   | Component methods
   |
   */

  /**
   * Before mounting, init our seekbar and volume bar
   * pan responders.
   */
  UNSAFE_componentWillMount() {
    this.initSeekPanResponder();
    this.initVolumePanResponder();
  }

  /**
   * To allow basic playback management from the outside
   * we have to handle possible props changes to state changes
   */
  UNSAFE_componentWillReceiveProps(nextProps) {
    // console.log('UNSAFE_componentWillReceiveProps====视频组件的==',nextProps)
    if (this.state.paused !== nextProps.paused) {
      this.setState({
        paused: nextProps.paused,
        poster: nextProps.poster,
      });
    }

    if (this.styles.videoStyle !== nextProps.videoStyle) {
      this.styles.videoStyle = nextProps.videoStyle;
    }

    if (this.styles.containerStyle !== nextProps.style) {
      this.styles.containerStyle = nextProps.style;
    }
  }

  /**
   * Upon mounting, calculate the position of the volume
   * bar based on the volume property supplied to it.
   */
  componentDidMount() {
    const position = this.calculateVolumePositionFromVolume();
    let state = this.state;
    this.setVolumePosition(position);
    state.volumeOffset = position;
    this.mounted = true;

    this.setState(state);
  }

  /**
   * When the component is about to unmount kill the
   * timeout less it fire in the prev/next scene
   */
  componentWillUnmount() {
    this.mounted = false;
    this.clearControlTimeout();
  }

  /**
   * Get our seekbar responder going
   */
  initSeekPanResponder() {
    this.player.seekPanResponder = PanResponder.create({
      // Ask to be the responder.
      onStartShouldSetPanResponder: (evt, gestureState) => true,
      onMoveShouldSetPanResponder: (evt, gestureState) => true,

      /**
       * When we start the pan tell the machine that we're
       * seeking. This stops it from updating the seekbar
       * position in the onProgress listener.
       */
      onPanResponderGrant: (evt, gestureState) => {
        let state = this.state;
        this.clearControlTimeout();
        const position = evt.nativeEvent.locationX;
        this.setSeekerPosition(position);
        state.seeking = true;
        state.originallyPaused = state.paused;
        state.scrubbing = false;
        if (this.player.scrubbingTimeStep > 0) {
          state.paused = true;
        }
        this.setState(state);
      },

      /**
       * When panning, update the seekbar position, duh.
       */
      onPanResponderMove: (evt, gestureState) => {
        const position = this.state.seekerOffset + gestureState.dx;
        this.setSeekerPosition(position);
        let state = this.state;

        if (
            this.player.scrubbingTimeStep > 0 &&
            !state.loading &&
            !state.scrubbing
        ) {
          const time = this.calculateTimeFromSeekerPosition();
          const timeDifference = Math.abs(state.currentTime - time) * 1000;

          if (
              time < state.duration &&
              timeDifference >= this.player.scrubbingTimeStep
          ) {
            state.scrubbing = true;

            this.setState(state);
            setTimeout(() => {
              this.player.ref.seek(time, this.player.scrubbingTimeStep);
            }, 1);
          }
        }
      },

      /**
       * On release we update the time and seek to it in the video.
       * If you seek to the end of the video we fire the
       * onEnd callback
       */
      onPanResponderRelease: (evt, gestureState) => {
        const time = this.calculateTimeFromSeekerPosition();
        let state = this.state;
        if (time >= state.duration && !state.loading) {
          state.paused = true;
          this.events.onEnd();
        } else if (state.scrubbing) {
          state.seeking = false;
        } else {
          this.seekTo(time);
          this.setControlTimeout();
          state.paused = state.originallyPaused;
          state.seeking = false;
        }
        this.setState(state);
      },
    });
  }

  /**
   * Initialize the volume pan responder.
   */
  initVolumePanResponder() {
    this.player.volumePanResponder = PanResponder.create({
      onStartShouldSetPanResponder: (evt, gestureState) => true,
      onMoveShouldSetPanResponder: (evt, gestureState) => true,
      onPanResponderGrant: (evt, gestureState) => {
        this.clearControlTimeout();
      },

      /**
       * Update the volume as we change the position.
       * If we go to 0 then turn on the mute prop
       * to avoid that weird static-y sound.
       */
      onPanResponderMove: (evt, gestureState) => {
        let state = this.state;
        const position = this.state.volumeOffset + gestureState.dx;

        this.setVolumePosition(position);
        state.volume = this.calculateVolumeFromVolumePosition();

        if (state.volume <= 0) {
          state.muted = true;
        } else {
          state.muted = false;
        }

        this.setState(state);
      },

      /**
       * Update the offset...
       */
      onPanResponderRelease: (evt, gestureState) => {
        let state = this.state;
        state.volumeOffset = state.volumePosition;
        this.setControlTimeout();
        this.setState(state);
      },
    });
  }

  /**
   | -------------------------------------------------------
   | Rendering
   | -------------------------------------------------------
   |
   | This section contains all of our render methods.
   | In addition to the typical React render func
   | we also have all the render methods for
   | the controls.
   |
   */

  /**
   * Standard render control function that handles
   * everything except the sliders. Adds a
   * consistent <TouchableHighlight>
   * wrapper and styling.
   */
  renderControl(children, callback, style = {}) {
    return (
        <TouchableHighlight
            underlayColor="transparent"
            // activeOpacity={0.3}
            onPress={() => {
              this.resetControlTimeout();
              callback();
            }}
            // style={[styles.controls.control, style]}
        >
          {children}
        </TouchableHighlight>
    );
  }

  /**
   * Renders an empty control, used to disable a control without breaking the view layout.
   */
  renderNullControl() {
    return <View style={[styles.controls.control]} />;
  }

  /**
   * Groups the top bar controls together in an animated
   * view and spaces them out.
   */
  renderTopControls() {
    const backControl = this.props.disableBack
        ? this.renderNullControl()
        : this.renderBack();
    const volumeControl = this.props.disableVolume
        ? this.renderNullControl()
        : this.renderVolume();
    const renderOpenMore = this.props.disableFullscreen
        ? this.renderNullControl()
        : this.renderOpenMore();

    return (
        <Animated.View
            style={[
              styles.controls.top,
              {
                opacity: this.animations.topControl.opacity,
                marginTop: this.animations.topControl.marginTop,
              },
            ]}>
          <ImageBackground
              source={require('./assets/img/top-vignette.png')}
              style={[styles.controls.column,{}]}
              imageStyle={[styles.controls.vignette]}>
            <SafeAreaView style={styles.controls.topControlGroup}>
              {backControl}
              <View style={styles.controls.pullRight}>
                {/*{volumeControl}*/}
                {/*{fullscreenControl}*/}
                {renderOpenMore}
              </View>
            </SafeAreaView>
          </ImageBackground>
        </Animated.View>
    );
  }

  /**
   * Back button control
   */
  renderBack() {
    return this.renderControl(
        <View style={{paddingHorizontal:7,paddingVertical:5}}>
          <Image
              source={require('./assets/img/back.png')}
              style={{}}
          />
        </View>
        ,
        this.events.onBack,
        styles.controls.back,
    );
  }

  /**
   * 这是我自己添加的事件
   */

  renderOpenMore() {
    return this.renderControl(
        <Image
            source={require('../../YGYM_app/images/class/sandian.png')}
            style={{height:32,width:32,borderRadius:9999}}
            pointerEvents={'none'}
        />
        ,
        this.events.onOpenMore,
        styles.controls.back,
    );
  }

  /**
   * Render the volume slider and attach the pan handlers
   */
  renderVolume() {
    return (
        <View style={styles.volume.container}>
          <View
              style={[styles.volume.fill, {width: this.state.volumeFillWidth}]}
          />
          <View
              style={[styles.volume.track, {width: this.state.volumeTrackWidth}]}
          />
          <View
              style={[styles.volume.handle, {left: this.state.volumePosition}]}
              {...this.player.volumePanResponder.panHandlers}>
            <Image
                style={styles.volume.icon}
                source={require('./assets/img/volume.png')}
            />
          </View>
        </View>
    );
  }

  /**
   * Render fullscreen toggle and set icon based on the fullscreen state.
   */
  renderFullscreen() {
    let source =
        this.state.isFullscreen === true
            ? require('./assets/img/shrink.png')
            : require('./assets/img/expand.png');
    return this.renderControl(
        <Image source={source}  style={{zIndex:1}}/>,
        this.methods.toggleFullscreen,
        styles.controls.fullscreen,
    );
  }

  /**
   * Render bottom control group and wrap it in a holder
   */
  renderBottomControls() {
    // const timerControl = this.props.disableTimer
    //   ? this.renderNullControl()
    //   : this.renderTimer();
    const seekbarControl = this.props.disableSeekbar
        ? this.renderNullControl()
        : this.renderSeekbar();
    // const playPauseControl = this.props.disablePlayPause
    //   ? this.renderNullControl()
    //   : this.renderPlayPause();

    return (
        <Animated.View
            style={[
              styles.controls.bottom,
              {
                opacity: this.animations.bottomControl.opacity,
                marginBottom: this.animations.bottomControl.marginBottom,
              },
            ]}>
          <ImageBackground
              source={require('./assets/img/bottom-vignette.png')}
              style={[styles.controls.column,]}
              imageStyle={[styles.controls.vignette]}>

            {seekbarControl}

            {/*<SafeAreaView*/}
            {/*  style={[styles.controls.row, styles.controls.bottomControlGroup]}>*/}
            {/*  {playPauseControl}*/}
            {/*  {this.renderTitle()}*/}
            {/*  {timerControl}*/}
            {/*</SafeAreaView>*/}
          </ImageBackground>
        </Animated.View>
    );
  }

  /**
   * Render the seekbar and attach its handlers
   */
  renderTimerFn(time){
    const timerControl = this.props.disableTimer
        ? this.renderNullControl()
        : this.renderTimer(time);
    return timerControl
  }

  renderSeekbar() {
    const playPauseControl = this.props.disablePlayPause
        ? this.renderNullControl()
        : this.renderPlayPause();

    const fullscreenControl = this.props.disableFullscreen
        ? this.renderNullControl()
        : this.renderFullscreen();

    return (
        <View style={{width:'100%',flexDirection:'row',alignItems:'center',paddingHorizontal:15}}>
          {playPauseControl}
          {/*{timerControl}*/}
          {this.renderTimerFn(this.state.currentTime)}

          <View
              style={[styles.seekbar.container,{marginLeft:10}]}
              collapsable={false}
              {...this.player.seekPanResponder.panHandlers}>
            <View
                style={styles.seekbar.track}
                onLayout={event =>
                    (this.player.seekerWidth = event.nativeEvent.layout.width)
                }
                pointerEvents={'none'}>
              <View
                  style={[
                    styles.seekbar.fill,
                    {
                      width: this.state.seekerFillWidth,
                      backgroundColor: this.props.seekColor || '#FFF',
                    },
                  ]}
                  pointerEvents={'none'}
              />
            </View>
            <View
                style={[styles.seekbar.handle, {left: this.state.seekerPosition}]}
                pointerEvents={'none'}>
              <Image
                  source={require('../../YGYM_app/images/play_icon.png')}
                  style={[
                    styles.seekbar.circle,
                    // {backgroundColor: this.props.seekColor || '#FFF'},
                  ]}
                  pointerEvents={'none'}
              />
            </View>
          </View>

          <View style={{marginLeft:10}}>
            {this.renderTimerFn(this.state.duration)}
          </View>

          <View style={{marginLeft:10}}>
            {fullscreenControl}
          </View>


        </View>
    );
  }

  /**
   * Render the play/pause button and show the respective icon
   */
  renderPlayPause() {
    let source =
        this.state.paused === true
            ? require('./assets/img/play.png')
            : require('./assets/img/pause.png');
    return this.renderControl(
        <Image source={source} style={{marginRight:10}}/>,
        this.methods.togglePlayPause,
        styles.controls.playPause,
    );
  }

  /**
   * Render our title...if supplied.
   */
  renderTitle() {
    if (this.opts.title) {
      return (
          <View style={[styles.controls.control, styles.controls.title]}>
            <Text
                style={[styles.controls.text, styles.controls.titleText]}
                numberOfLines={1}>
              {this.opts.title || ''}
            </Text>
          </View>
      );
    }

    return null;
  }

  /**
   * Show our timer.
   */
  renderTimer(time) {
    return this.renderControl(
        // <Text style={styles.controls.timerText}>{this.calculateTime()}</Text>,
        <Text style={styles.controls.timerText}>{this.formatSeconds(time)}</Text>,
        this.methods.toggleTimer,
        styles.controls.timer,
    );
  }

  /**
   * Show loading icon
   */
  renderLoader() {
    if (this.state.loading) {
      return (
          <View style={styles.loader.container}>
            <Animated.Image
                source={require('./assets/img/loader-icon.png')}
                style={[
                  styles.loader.icon,
                  {
                    transform: [
                      {
                        rotate: this.animations.loader.rotate.interpolate({
                          inputRange: [0, 360],
                          outputRange: ['0deg', '360deg'],
                        }),
                      },
                    ],
                  },
                ]}
            />
          </View>
      );
    }
    return null;
  }

  renderError() {
    if (this.state.error) {
      return (
          <View style={styles.error.container}>
            <Image
                source={require('./assets/img/error-icon.png')}
                style={styles.error.icon}
            />
            <Text style={styles.error.text}>Video unavailable</Text>
          </View>
      );
    }
    return null;
  }

  /**
   * Provide all of our options and render the whole component.
   */
  render() {
    return (
        <TouchableWithoutFeedback
            onPress={this.events.onScreenTouch}
            style={[styles.player.container, this.styles.containerStyle]}>
          <View style={[styles.player.container, this.styles.containerStyle]}>
            <Video
                {...this.props}
                ref={videoPlayer => (this.player.ref = videoPlayer)}
                resizeMode={this.state.resizeMode}
                volume={this.state.volume}
                paused={this.state.paused}
                poster={this.state.poster}
                muted={this.state.muted}
                rate={this.state.rate}
                onLoadStart={this.events.onLoadStart}
                onProgress={this.events.onProgress}
                onError={this.events.onError}
                onLoad={this.events.onLoad}
                onEnd={this.events.onEnd}
                onSeek={this.events.onSeek}
                style={[styles.player.video, this.styles.videoStyle]}
                source={this.props.source}
            />
            {this.renderError()}
            {this.renderLoader()}
            {this.renderTopControls()}


            {this.renderBottomControls()}


          </View>
        </TouchableWithoutFeedback>
    );
  }
}

/**
 * This object houses our styles. There's player
 * specific styles and control specific ones.
 * And then there's volume/seeker styles.
 */
const styles = {
  player: StyleSheet.create({
    container: {
      overflow: 'hidden',
      backgroundColor: '#000',
      flex: 1,
      alignSelf: 'stretch',
      justifyContent: 'space-between',
    },
    video: {
      overflow: 'hidden',
      position: 'absolute',
      top: 0,
      right: 0,
      bottom: 0,
      left: 0,
    },
  }),
  error: StyleSheet.create({
    container: {
      backgroundColor: 'rgba( 0, 0, 0, 0.5 )',
      position: 'absolute',
      top: 0,
      right: 0,
      bottom: 0,
      left: 0,
      justifyContent: 'center',
      alignItems: 'center',
    },
    icon: {
      marginBottom: 16,
    },
    text: {
      backgroundColor: 'transparent',
      color: '#f27474',
    },
  }),
  loader: StyleSheet.create({
    container: {
      position: 'absolute',
      top: 0,
      right: 0,
      bottom: 0,
      left: 0,
      alignItems: 'center',
      justifyContent: 'center',
    },
  }),
  controls: StyleSheet.create({
    row: {
      flexDirection: 'row',
      alignItems: 'center',
      justifyContent: 'space-between',
      height: null,
      width: null,
    },
    column: {
      flexDirection: 'column',
      alignItems: 'center',
      justifyContent: 'space-between',
      height: null,
      width: null,
    },
    vignette: {
      resizeMode: 'stretch',
    },
    control: {
      padding: 16,
    },
    text: {
      backgroundColor: 'transparent',
      color: '#FFF',
      fontSize: 14,
      textAlign: 'center',
    },
    pullRight: {
      flexDirection: 'row',
      alignItems: 'center',
      justifyContent: 'center',
    },
    top: {
      flex: 1,
      alignItems: 'stretch',
      justifyContent: 'flex-start',
    },
    bottom: {
      alignItems: 'stretch',
      flex: 2,
      justifyContent: 'flex-end',
    },
    topControlGroup: {
      alignSelf: 'stretch',
      alignItems: 'center',
      justifyContent: 'space-between',
      flexDirection: 'row',
      width: null,
      margin: 12,
      marginBottom: 18,
    },
    bottomControlGroup: {
      alignSelf: 'stretch',
      alignItems: 'center',
      justifyContent: 'space-between',
      marginLeft: 12,
      marginRight: 12,
      marginBottom: 0,
    },
    volume: {
      flexDirection: 'row',
    },
    fullscreen: {
      flexDirection: 'row',
    },
    playPause: {
      position: 'relative',
      // width: 80,
      zIndex: 0,
    },
    title: {
      alignItems: 'center',
      flex: 0.6,
      flexDirection: 'column',
      padding: 0,
    },
    titleText: {
      textAlign: 'center',
    },
    timer: {
      width: 80,
    },
    timerText: {
      backgroundColor: 'transparent',
      color: '#FFF',
      fontSize: 11,
      textAlign: 'right',
    },
  }),
  volume: StyleSheet.create({
    container: {
      alignItems: 'center',
      justifyContent: 'flex-start',
      flexDirection: 'row',
      height: 1,
      marginLeft: 20,
      marginRight: 20,
      width: 150,
    },
    track: {
      backgroundColor: '#333',
      height: 1,
      marginLeft: 7,
    },
    fill: {
      backgroundColor: '#FFF',
      height: 1,
    },
    handle: {
      position: 'absolute',
      marginTop: -24,
      marginLeft: -24,
      padding: 16,
    },
    icon: {
      marginLeft: 7,
    },
  }),
  seekbar: StyleSheet.create({
    container: {
      flex:1,
      // alignSelf: 'stretch',
      height: 28,
      // marginLeft: 20,
      // marginRight: 20,
    },
    track: {
      backgroundColor: '#333',
      height: 1,
      position: 'relative',
      top: 14,
      width: '100%',
    },
    fill: {
      backgroundColor: '#FFF',
      height: 1,
      width: '100%',
    },
    handle: {
      position: 'absolute',
      marginLeft: -7,
      height: 28,
      width: 28,
    },
    circle: {
      borderRadius: 12,
      position: 'relative',
      top: 8,
      left: 5,
      height: 12,
      width: 11,
    },
  }),
};


上一篇 下一篇

猜你喜欢

热点阅读