参考: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,
},
}),
};
网友评论