做过RN的童鞋都知道,RN上官方的视频组件是react-native-video。然而,官方的文档的demo并不是那么详尽,踩了一身的坑,仍然和理想中的视频播放器相去甚远。本文会完成一个基本的视频播放器,包含:
- 全屏切换
- 播放/暂停
- 进度拖动
- 滑动手势控制音量、亮度、进度
完整例子见文末。
全屏方案
一般而言,思路有两种。一种是用户点击全屏按钮时,另外打开一个页面,该页面全屏展示一个视频组件,并且应用转为横屏;二是将当前页面上的小视频组件通过一系列CSS变换,铺满屏幕,并且将应用转为横屏。本人两种思路都实现过。
第一种方案需要考虑小视频组件和全屏视频组件两个组件的进度同步性,并且如果页面上存在多个视频组件,各组件之间互不干扰,如下:
小视频组件1 <----> 全屏组件1
小视频组件2 <----> 全屏组件2
小视频组件3 <----> 全屏组件3
…
看起来我们将小窗口和全屏分为了两个组件,只需要关心同步问题,实际实现上,还是有若干碰壁的。其最致命的问题是,无论如何同步,切换全屏和切回时,都会有一定的不连贯性,甚至有倒帧的现象。
第二种思路,通过CSS变换,将小视频的窗口全屏铺满,这首先避免了同步问题,切换全屏的时候也如丝般顺滑。然而,第二种思路有个大问题是,小视频在布局中的位置是不确定的。在复杂布局中,有可能嵌套的很深,并且不在页面头部的位置。加上RN里没有fix布局,通过CSS铺满全屏异常困难。因此,第二种思路要求,无论视频组件嵌套多深,该组件必须位于页面顶部,这样方便CSS计算。
本文采用第二种思路,视频组件所有用到的三方应用如下:
import React from 'react';
import {
AppState,
AsyncStorage,
BackHandler,
Dimensions,
Image,
Platform,
Slider,
StyleSheet,
Text,
TouchableOpacity,
View
} from 'react-native';
import SystemSetting from 'react-native-system-setting' // 一个系统亮度、音量控制相关的库,npm安装
import LinearGradient from 'react-native-linear-gradient'; // 一个渐变色库,用于美化,npm安装
import {rpx} from "../util/AdapterUtil"; // 一个将设计尺寸转换为dp单位的工具方法,具体实现见下
import Orientation from 'react-native-orientation' // 一个控制应用方向的库,npm安装
import Video from "react-native-video"; // RN视频组件,npm安装。注意不同版本的库,其bug和兼容性问题相差巨大,本文用的是4.4.2版本
import {log} from "../util/artUtils"; // 一个log工具类,可无视
rpx方法实现如下,我相信很多应用都有大同小异的实现,该方法将设计尺寸宽设定为750,也就是rpx(750)为屏幕宽度:
const width = Dimensions.get('window').width;
const designWidth = 750
export function rpx (num) {
return num * width / designWidth
}
注意,嵌套在视频组件外面的任何父组件不能指定宽度:
<View style={{width: rpx(750)}}>
<MyVideo></MyVideo>
</View>
如上是有问题的,因为应用转为横屏后,组件宽度没变,导致父组件横向没有铺满屏幕,而视频组件铺满了,超出了父组件。又因为TouchableOpacity在Android上,存在“超出父组件部分无法点击”的bug,超出父组件的按钮就无法响应了。
现在,如果是小视频,可以通过props指定style控制窗口大小;而如果是全屏,则简单的铺满全屏:
formatFullScreenContainer() {
const w = Dimensions.get('window').width;
const h = Dimensions.get('window').height;
return {
position: 'absolute',
left: 0,
top: 0,
width: this.isL ? w : h,
height: this.isL ? h : w,
zIndex: 99999,
backgroundColor: '#000'
}
}
this.isL单纯是一个记录横竖屏状态的变量。现在写一个方法切换全屏:
toggleFullScreen() {
if (this.player) {
let {fullScreen} = this.state;
if (this.props.onFullScreenChange) {
this.props.onFullScreenChange(!fullScreen)
}
if (fullScreen) {
// if (Platform.OS === 'android') {
// this.player.dismissFullscreenPlayer();
// }
this.isL = false;
Orientation.lockToPortrait()
} else {
if (Platform.OS === 'android') {
this.player.presentFullscreenPlayer();
}
this.isL = true;
Orientation.lockToLandscape()
}
clearTimeout(this.touchTimer);
this.setState({fullScreen: !fullScreen, isTouchedScreen: false})
}
}
render部分的核心代码如下:
render() {
let {url, videoStyle, title} = this.props;
let {paused, fullScreen, isTouchedScreen, duration, showVolumeSlider, showBrightnessSlider, showTimeSlider} = this.state;
let {volume, brightness, tempCurrentTime} = this.state;
const w = fullScreen ? Dimensions.get('window').width : videoStyle.width;
let containerStyle = this.formatVideoStyle(videoStyle);
return (
<View style={fullScreen ? this.formatFullScreenContainer() : containerStyle}
activeOpacity={1}>
<Video source={{uri: url}} //require('../img/English_sing.mp3')
ref={ref => this.player = ref}
rate={1}
volume={1.0}
muted={false}
paused={paused}
resizeMode="contain"
repeat={false}
playInBackground={false}
playWhenInactive={false}
ignoreSilentSwitch={"ignore"}
progressUpdateInterval={250.0}
style={[fullScreen ? this.formatFullScreenContainer() : styles.videoPlayer]}
onSeek={() => {
}}
onLoad={data => this.onLoad(data)}
onError={(data) => this.onError(data)}
onProgress={(data) => this.setTime(data)}
onEnd={(data) => this.onEnd(data)}
/>
...
进度拖动
可以用Slider组件,用于控制视频进度。render中核心代码如下:
<Slider
style={styles.slider}
value={this.state.slideValue}
maximumValue={this.state.duration}
minimumTrackTintColor={'#ca3839'}
maximumTrackTintColor={'#989898'}
thumbImage={require('../img/slider.png')}
step={1}
onValueChange={value => this.onSeek(value)}
onSlidingComplete={value => {
this.player.seek(value);
this.setState({enableSetTime: true})
}}
/>
拖动控制音量、亮度、进度
这种需求也是很多的。很多视频app在视频左边上下滑动控制音量,右边上下滑动控制亮度,而左右滑动控制进度。我们可以在View上注册onResponderStart、onResponderMove、onResponderEnd等事件处理滑动,判断滑动方向和距离来做出相应控制。这里一个细节是滑动和其他点击组件的冲突处理;二是“过滑动折返”问题。“过滑动折返”指,你设定了用户向上滑动400px时,能将音量从0加到100%。而用户手快划了600px,这种情况下,其实划了400px音量已经是最大了,剩下200px保持最大音量不变;到最高点时向下折返,用户不需要额外滑动200px,只需要下滑400px就可以将音量调整为0,而不是下滑600px。也就是变向的时候,我们需要做处理。
核心处理代码由若干监听组成:
this.gestureHandlers = {
onStartShouldSetResponder: (evt) => {
log('onStartShouldSetResponder');
return true;
},
onMoveShouldSetResponder: (evt) => {
log('onMoveShouldSetResponder');
return !this.state.paused;
},
onResponderGrant: (evt) => {
log('onResponderGrant');
},
onResponderReject: (evt) => {
log('onResponderReject');
},
onResponderStart: (evt) => {
log('onResponderStart', evt.nativeEvent.locationX, evt.nativeEvent.locationY);
this.touchAction.x = evt.nativeEvent.locationX;
this.touchAction.y = evt.nativeEvent.locationY;
SystemSetting.getVolume().then((volume) => {
SystemSetting.getAppBrightness().then((brightness) => {
this.setState({
initVolume: volume,
initBrightness: brightness,
initTime: this.state.currentTime / this.state.duration,
tempCurrentTime: this.state.currentTime / this.state.duration,
showVolumeSlider: false,
showBrightnessSlider: false,
showTimeSlider: false
});
if (this.sliderTimer) {
clearTimeout(this.sliderTimer);
this.sliderTimer = null;
}
});
});
},
onResponderMove: (evt) => {
let formatValue = (v) => {
if (v > 1.0) return {v: 1.0, outOfRange: true};
if (v < 0) return {v: 0, outOfRange: true};
return {v, outOfRange: false}
};
let resolveOutOfRange = (outOfRange, props, onSlideBack) => {
if (outOfRange) {
if (this.touchAction[props.limit] !== null
&& Math.abs(this.touchAction[props.v] - props.current) < Math.abs(this.touchAction[props.v] - this.touchAction[props.limit])) {
log('outOfRange', this.touchAction[props.v], props.current, this.touchAction[props.limit]);
// 用户把亮度、音量调到极限(0或1.0)后,继续滑动,并且反向滑动,此时重新定义基准落点
this.touchAction[props.v] = this.touchAction[props.limit];
this.touchAction[props.limit] = null;
if (onSlideBack) onSlideBack()
} else {
this.touchAction[props.limit] = [props.current];
}
}
};
let currentX = evt.nativeEvent.locationX;
let currentY = evt.nativeEvent.locationY;
let {fullScreen} = this.state;
let {videoStyle} = this.props;
let w = fullScreen ? Dimensions.get('window').width : videoStyle.width;
let h = fullScreen ? Dimensions.get('window').height : videoStyle.height;
if (!this.touchAction.slideDirection) {
if (Math.abs(currentY - this.touchAction.y) > rpx(50)) {
this.touchAction.slideDirection = 'v'
} else if (Math.abs(currentX - this.touchAction.x) > rpx(50)) {
this.touchAction.slideDirection = 'h'
}
}
if (this.touchAction.slideDirection === 'v') {
log('onResponderMove');
this.touchAction.isClick = false;
let dy = this.touchAction.y - currentY;
let dv = dy / (h / 1.5);
if (this.touchAction.x > w / 2) {
let {v, outOfRange} = formatValue(this.state.initVolume + dv);
log('volume', v, outOfRange);
this.setState({volume: v, showVolumeSlider: true});
SystemSetting.setVolume(v);
resolveOutOfRange(outOfRange, {current: currentY, v: 'y', limit: 'limitY'}, () => {
this.setState({initVolume: v})
})
} else {
let {v, outOfRange} = formatValue(this.state.initBrightness + dv);
log('brightness', v, outOfRange);
this.setState({brightness: v, showBrightnessSlider: true});
SystemSetting.setAppBrightness(v);
resolveOutOfRange(outOfRange, {current: currentY, v: 'y', limit: 'limitY'}, () => {
this.setState({initBrightness: v})
})
}
} else if (this.touchAction.slideDirection === 'h') {
log('onResponderMove');
this.touchAction.isClick = false;
let dx = currentX - this.touchAction.x;
let dv = dx / (w / 2);
let {v, outOfRange} = formatValue(this.state.initTime + dv);
log('time', v, outOfRange);
this.setState({tempCurrentTime: v, showTimeSlider: true});
resolveOutOfRange(outOfRange, {current: currentX, v: 'x', limit: 'limitX'}, () => {
this.setState({initTime: v})
})
}
},
onResponderRelease: (evt) => {
log('onResponderRelease');
},
onResponderEnd: (evt) => {
let currentX = evt.nativeEvent.locationX;
let currentY = evt.nativeEvent.locationY;
let {fullScreen, tempCurrentTime, duration} = this.state;
if (Math.abs(currentX - this.touchAction.x) <= rpx(50) && this.touchAction.isClick) {
// 触发点击
if (this.state.paused) {
this.play()
} else {
this.onTouchedScreen()
}
}
if (!this.touchAction.isClick) {
this.setState({initVolume: this.state.volume, initBrightness: this.state.brightness});
}
this.sliderTimer = setTimeout(() => {
this.sliderTimer = null;
this.setState({showVolumeSlider: false, showBrightnessSlider: false, tempCurrentTime: null})
}, 1000);
if (tempCurrentTime != null) {
let time = Math.ceil(tempCurrentTime * duration);
this.onSeek(time);
this.player.seek(time);
this.setState({enableSetTime: true, showTimeSlider: false})
} else {
this.setState({showTimeSlider: false})
}
this.touchAction = {
x: null,
y: null,
slideDirection: null,
limitX: null,
limitY: null,
isClick: true
};
},
onResponderTerminationRequest: (evt) => {
log('onResponderTerminationRequest');
return true;
},
onResponderTerminate: (evt) => {
log('onResponderTerminate');
}
}
完整代码
import React from 'react';
import {
AppState,
AsyncStorage,
BackHandler,
Dimensions,
Image,
Platform,
Slider,
StyleSheet,
Text,
TouchableOpacity,
View
} from 'react-native';
import SystemSetting from 'react-native-system-setting'
import LinearGradient from 'react-native-linear-gradient';
import {rpx} from "../util/AdapterUtil";
import Orientation from 'react-native-orientation'
import Video from "react-native-video";
import {log} from "../util/artUtils";
export default class PlayerV2 extends React.Component {
constructor(props) {
super(props);
this.state = {
fullScreen: false,
initVolume: 1.0,
volume: 1.0,
initBrightness: 1.0,
brightness: 1.0,
initTime: 0,
tempCurrentTime: null,
slideValue: 0.00,
currentTime: 0.00,
duration: 0.00,
enableSetTime: true,
paused: true,
playerLock: false,
canShowCover: false,
isTouchedScreen: false,
showVolumeSlider: false,
showBrightnessSlider: false,
showTimeSlider: false,
};
this.touchTimer = null;
this.sliderTimer = null;
this.isL = false;
SystemSetting.getVolume().then((volume) => {
SystemSetting.getBrightness().then((brightness) => {
this.setState({volume, brightness, initVolume: volume, initBrightness: brightness})
});
});
this.backListener = () => {
if (this.state.fullScreen) {
this.toggleFullScreen();
return true
}
return false
};
this.appStateListener = (next) => {
if (next === 'background' || next === 'inactive') {
this.dismissFullScreen()
}
};
this.touchAction = {
x: null,
y: null,
limitX: null,
limitY: null,
slideDirection: null,
isClick: true
};
this.gestureHandlers = {
onStartShouldSetResponder: (evt) => {
log('onStartShouldSetResponder');
return true;
},
onMoveShouldSetResponder: (evt) => {
log('onMoveShouldSetResponder');
return !this.state.paused;
},
onResponderGrant: (evt) => {
log('onResponderGrant');
},
onResponderReject: (evt) => {
log('onResponderReject');
},
onResponderStart: (evt) => {
log('onResponderStart', evt.nativeEvent.locationX, evt.nativeEvent.locationY);
this.touchAction.x = evt.nativeEvent.locationX;
this.touchAction.y = evt.nativeEvent.locationY;
SystemSetting.getVolume().then((volume) => {
SystemSetting.getAppBrightness().then((brightness) => {
this.setState({
initVolume: volume,
initBrightness: brightness,
initTime: this.state.currentTime / this.state.duration,
tempCurrentTime: this.state.currentTime / this.state.duration,
showVolumeSlider: false,
showBrightnessSlider: false,
showTimeSlider: false
});
if (this.sliderTimer) {
clearTimeout(this.sliderTimer);
this.sliderTimer = null;
}
});
});
},
onResponderMove: (evt) => {
let formatValue = (v) => {
if (v > 1.0) return {v: 1.0, outOfRange: true};
if (v < 0) return {v: 0, outOfRange: true};
return {v, outOfRange: false}
};
let resolveOutOfRange = (outOfRange, props, onSlideBack) => {
if (outOfRange) {
if (this.touchAction[props.limit] !== null
&& Math.abs(this.touchAction[props.v] - props.current) < Math.abs(this.touchAction[props.v] - this.touchAction[props.limit])) {
log('outOfRange', this.touchAction[props.v], props.current, this.touchAction[props.limit]);
// 用户把亮度、音量调到极限(0或1.0)后,继续滑动,并且反向滑动,此时重新定义基准落点
this.touchAction[props.v] = this.touchAction[props.limit];
this.touchAction[props.limit] = null;
if (onSlideBack) onSlideBack()
} else {
this.touchAction[props.limit] = [props.current];
}
}
};
let currentX = evt.nativeEvent.locationX;
let currentY = evt.nativeEvent.locationY;
let {fullScreen} = this.state;
let {videoStyle} = this.props;
let w = fullScreen ? Dimensions.get('window').width : videoStyle.width;
let h = fullScreen ? Dimensions.get('window').height : videoStyle.height;
if (!this.touchAction.slideDirection) {
if (Math.abs(currentY - this.touchAction.y) > rpx(50)) {
this.touchAction.slideDirection = 'v'
} else if (Math.abs(currentX - this.touchAction.x) > rpx(50)) {
this.touchAction.slideDirection = 'h'
}
}
if (this.touchAction.slideDirection === 'v') {
log('onResponderMove');
this.touchAction.isClick = false;
let dy = this.touchAction.y - currentY;
let dv = dy / (h / 1.5);
if (this.touchAction.x > w / 2) {
let {v, outOfRange} = formatValue(this.state.initVolume + dv);
log('volume', v, outOfRange);
this.setState({volume: v, showVolumeSlider: true});
SystemSetting.setVolume(v);
resolveOutOfRange(outOfRange, {current: currentY, v: 'y', limit: 'limitY'}, () => {
this.setState({initVolume: v})
})
} else {
let {v, outOfRange} = formatValue(this.state.initBrightness + dv);
log('brightness', v, outOfRange);
this.setState({brightness: v, showBrightnessSlider: true});
SystemSetting.setAppBrightness(v);
resolveOutOfRange(outOfRange, {current: currentY, v: 'y', limit: 'limitY'}, () => {
this.setState({initBrightness: v})
})
}
} else if (this.touchAction.slideDirection === 'h') {
log('onResponderMove');
this.touchAction.isClick = false;
let dx = currentX - this.touchAction.x;
let dv = dx / (w / 2);
let {v, outOfRange} = formatValue(this.state.initTime + dv);
log('time', v, outOfRange);
this.setState({tempCurrentTime: v, showTimeSlider: true});
resolveOutOfRange(outOfRange, {current: currentX, v: 'x', limit: 'limitX'}, () => {
this.setState({initTime: v})
})
}
},
onResponderRelease: (evt) => {
log('onResponderRelease');
},
onResponderEnd: (evt) => {
let currentX = evt.nativeEvent.locationX;
let currentY = evt.nativeEvent.locationY;
let {fullScreen, tempCurrentTime, duration} = this.state;
if (Math.abs(currentX - this.touchAction.x) <= rpx(50) && this.touchAction.isClick) {
// 触发点击
if (this.state.paused) {
this.play()
} else {
this.onTouchedScreen()
}
}
if (!this.touchAction.isClick) {
this.setState({initVolume: this.state.volume, initBrightness: this.state.brightness});
}
this.sliderTimer = setTimeout(() => {
this.sliderTimer = null;
this.setState({showVolumeSlider: false, showBrightnessSlider: false, tempCurrentTime: null})
}, 1000);
if (tempCurrentTime != null) {
let time = Math.ceil(tempCurrentTime * duration);
this.onSeek(time);
this.player.seek(time);
this.setState({enableSetTime: true, showTimeSlider: false})
} else {
this.setState({showTimeSlider: false})
}
this.touchAction = {
x: null,
y: null,
slideDirection: null,
limitX: null,
limitY: null,
isClick: true
};
},
onResponderTerminationRequest: (evt) => {
log('onResponderTerminationRequest');
return true;
},
onResponderTerminate: (evt) => {
log('onResponderTerminate');
}
}
}
componentDidMount() {
// Orientation.addOrientationListener(this._orientationDidChange);
BackHandler.addEventListener('hardwareBackPress', this.backListener);
AppState.addEventListener('change', this.appStateListener);
if (this.props.cover) {
this.setState({canShowCover: true})
}
}
_orientationDidChange = (orientation) => {
if (orientation === 'PORTRAIT') {
return
}
this.isL = orientation === 'LANDSCAPE'
};
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.backListener);
AppState.removeEventListener('change', this.appStateListener);
// Orientation.removeOrientationListener(this._orientationDidChange);
}
formatVideoStyle(videoStyle) {
let r = Object.assign({}, videoStyle);
r.position = 'relative';
r.zIndex = 99999;
return r;
}
formatFullScreenContainer() {
const w = Dimensions.get('window').width;
const h = Dimensions.get('window').height;
return {
position: 'absolute',
left: 0,
top: 0,
width: this.isL ? w : h,
height: this.isL ? h : w,
zIndex: 99999,
backgroundColor: '#000'
}
}
onPreNavigate = () => {
if (!this.state.paused) {
this.pause()
}
};
onTouchedScreen = () => {
if (this.state.isTouchedScreen) {
this.setState({isTouchedScreen: false});
clearTimeout(this.touchTimer);
return
}
this.setState({isTouchedScreen: !this.state.isTouchedScreen}, () => {
if (this.state.isTouchedScreen) {
this.touchTimer = setTimeout(() => {
this.touchTimer = null;
this.setState({isTouchedScreen: false})
}, 10000)
}
})
};
play() {
this.setState({
canShowCover: false,
paused: !this.state.paused,
})
}
pause() {
this.setState({
// canShowCover: false,
paused: true,
})
}
setDuration(duration) {
this.setState({duration: duration.duration})
}
onLoad(duration) {
this.setDuration(duration);
}
onError(err) {
log('onError', err)
}
onSeek(value) {
//this.setState({currentTime: value});
if (this.props.maxPlayTime && this.props.maxPlayTime <= value) {
this.setState({paused: true, currentTime: 0});
this.player.seek(0);
if (this.props.onReachMaxPlayTime) this.props.onReachMaxPlayTime()
} else {
this.setState({currentTime: value, enableSetTime: false});
}
}
onEnd(data) {
//this.player.seek(0);
this.setState({paused: true, currentTime: 0}, () => {
this.player.seek(0);
})
}
setTime(data) {
let sliderValue = parseInt(this.state.currentTime);
if (this.state.enableSetTime) {
this.setState({
slideValue: sliderValue,
currentTime: data.currentTime,
});
}
if (this.props.maxPlayTime && this.props.maxPlayTime <= data.currentTime) {
this.setState({paused: true, currentTime: 0});
this.player.seek(0);
if (this.props.onReachMaxPlayTime) this.props.onReachMaxPlayTime()
}
}
formatMediaTime(duration) {
let min = Math.floor(duration / 60);
let second = duration - min * 60;
min = min >= 10 ? min : '0' + min;
second = second >= 10 ? second : '0' + second;
return min + ':' + second
}
dismissFullScreen() {
// if (Platform.OS === 'android') {
// this.player.dismissFullscreenPlayer();
// }
if (this.props.onFullScreenChange) {
this.props.onFullScreenChange(false)
}
Orientation.lockToPortrait();
clearTimeout(this.touchTimer);
this.setState({fullScreen: false, isTouchedScreen: false})
}
toggleFullScreen() {
if (this.player) {
let {fullScreen} = this.state;
if (this.props.onFullScreenChange) {
this.props.onFullScreenChange(!fullScreen)
}
if (fullScreen) {
// if (Platform.OS === 'android') {
// this.player.dismissFullscreenPlayer();
// }
this.isL = false;
Orientation.lockToPortrait()
} else {
if (Platform.OS === 'android') {
this.player.presentFullscreenPlayer();
}
this.isL = true;
Orientation.lockToLandscape()
}
clearTimeout(this.touchTimer);
this.setState({fullScreen: !fullScreen, isTouchedScreen: false})
}
}
render() {
let {url, videoStyle, title} = this.props;
let {paused, fullScreen, isTouchedScreen, duration, showVolumeSlider, showBrightnessSlider, showTimeSlider} = this.state;
let {volume, brightness, tempCurrentTime} = this.state;
const w = fullScreen ? Dimensions.get('window').width : videoStyle.width;
let containerStyle = this.formatVideoStyle(videoStyle);
return (
<View style={fullScreen ? this.formatFullScreenContainer() : containerStyle}
activeOpacity={1}>
<Video source={{uri: url}} //require('../img/English_sing.mp3')
ref={ref => this.player = ref}
rate={1}
volume={1.0}
muted={false}
paused={paused}
resizeMode="contain"
repeat={false}
playInBackground={false}
playWhenInactive={false}
ignoreSilentSwitch={"ignore"}
progressUpdateInterval={250.0}
style={[fullScreen ? this.formatFullScreenContainer() : styles.videoPlayer]}
onSeek={() => {
}}
onLoad={data => this.onLoad(data)}
onError={(data) => this.onError(data)}
onProgress={(data) => this.setTime(data)}
onEnd={(data) => this.onEnd(data)}
/>
{
paused ?
<TouchableOpacity activeOpacity={0.8}
style={[fullScreen ? this.formatFullScreenContainer() : styles.videoPlayer, {
backgroundColor: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}]}
onPress={() => {
this.play()
}}>
{this.state.canShowCover ? <Image style={styles.videoPlayer} source={{uri: this.props.cover}}/> : null}
<Image style={{width: rpx(75), height: rpx(75)}} source={require("./../img/play_video_icon.png")}/>
</TouchableOpacity> : null
}
<View style={[fullScreen ? this.formatFullScreenContainer() : containerStyle, {backgroundColor: 'transparent'}]}
pointerEvents={paused ? 'box-none' : 'auto'} {...this.gestureHandlers} />
{
showVolumeSlider &&
<View style={[styles.verticalSliderContainer, {position: 'absolute', top: rpx(120), left: 0, right: 0}]}>
<View style={styles.verticalSlider}>
<Image style={{width: rpx(32), height: rpx(32)}}
source={volume <= 0 ? require('../img/no_volume.png') : require('../img/volume.png')}/>
<View style={{height: rpx(4), width: rpx(192), flexDirection: 'row'}}>
<View style={{height: rpx(4), width: rpx(192 * volume), backgroundColor: '#ca3839'}}/>
<View style={{height: rpx(4), flex: 1, backgroundColor: '#787878'}}/>
</View>
</View>
</View>
}
{
showBrightnessSlider &&
<View style={[styles.verticalSliderContainer, {position: 'absolute', top: rpx(120), left: 0, right: 0}]}>
<View style={styles.verticalSlider}>
<Image style={{width: rpx(32), height: rpx(32)}}
source={require('../img/brightness.png')}/>
<View style={{height: rpx(4), width: rpx(192), flexDirection: 'row'}}>
<View style={{height: rpx(4), width: rpx(192 * brightness), backgroundColor: '#ca3839'}}/>
<View style={{height: rpx(4), flex: 1, backgroundColor: '#787878'}}/>
</View>
</View>
</View>
}
{
showTimeSlider &&
<View style={[styles.verticalSliderContainer, {position: 'absolute', top: rpx(120), left: 0, right: 0}]}>
<View style={styles.smallSlider}>
<Text style={{
color: 'white',
fontSize: rpx(26)
}}>{this.formatMediaTime(Math.floor(tempCurrentTime * duration))}/{this.formatMediaTime(Math.floor(duration))}</Text>
</View>
</View>
}
{
isTouchedScreen ?
<View style={[styles.navContentStyle, {width: w, height: fullScreen ? rpx(160) : rpx(90)}]}>
<LinearGradient colors={['#666666', 'transparent']}
style={{flex: 1, height: fullScreen ? rpx(160) : rpx(90), opacity: 0.5}}/>
<View style={[styles.navContentStyleInner]}>
{
fullScreen ?
<TouchableOpacity
style={{width: rpx(22), height: rpx(40), marginLeft: rpx(30)}}
onPress={() => {
this.toggleFullScreen()
}}>
<Image style={{width: rpx(22), height: rpx(40)}}
source={require('../img/back_arrow_icon_white.png')}/>
</TouchableOpacity> : null
}
{
fullScreen ?
<TouchableOpacity onPress={() => {
this.toggleFullScreen()
}}>
<Text
style={{
backgroundColor: 'transparent',
color: 'white',
marginLeft: rpx(50)
}}>{title}</Text>
</TouchableOpacity>
: null
}
</View>
</View> : <View style={{height: Platform.OS === 'ios' ? 44 : 56, backgroundColor: 'transparent'}}/>
}
{
isTouchedScreen &&
<View style={[styles.toolBarStyle, {width: w, height: fullScreen ? rpx(110) : rpx(90)}]}>
<LinearGradient colors={['transparent', '#666666']}
style={{flex: 1, height: fullScreen ? rpx(110) : rpx(90), opacity: 0.5}}/>
<View style={[styles.toolBarStyleInner, {width: w}]}>
<TouchableOpacity activeOpacity={0.8} onPress={() => this.play()}>
<Image style={{width: rpx(50), height: rpx(50)}}
source={this.state.paused ? require('./../img/play.png') : require('./../img/pause.png')}/>
</TouchableOpacity>
<View style={styles.progressStyle}>
<Text style={styles.timeStyle}>{this.formatMediaTime(Math.floor(this.state.currentTime))}</Text>
<Slider
style={styles.slider}
value={this.state.slideValue}
maximumValue={this.state.duration}
minimumTrackTintColor={'#ca3839'}
maximumTrackTintColor={'#989898'}
thumbImage={require('../img/slider.png')}
step={1}
onValueChange={value => this.onSeek(value)}
onSlidingComplete={value => {
this.player.seek(value);
this.setState({enableSetTime: true})
}}
/>
<View style={{flexDirection: 'row', justifyContent: 'flex-end', width: rpx(70)}}>
<Text style={{
color: 'white',
fontSize: rpx(24)
}}>{this.formatMediaTime(Math.floor(duration))}</Text>
</View>
</View>
{
fullScreen ?
<TouchableOpacity activeOpacity={0.8} onPress={() => {
this.toggleFullScreen()
}}>
<Image style={{width: rpx(50), height: rpx(50)}} source={require("./../img/not_full_screen.png")}/>
</TouchableOpacity> :
<TouchableOpacity activeOpacity={0.8} onPress={() => {
this.toggleFullScreen()
}}>
<Image style={{width: rpx(50), height: rpx(50)}} source={require("./../img/full_screen.png")}/>
</TouchableOpacity>
}
</View>
</View>
}
</View>
)
}
}
const styles = StyleSheet.create({
fullScreenContainer: {
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0
},
videoPlayer: {
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0
},
toolBarStyle: {
position: 'absolute',
left: 0,
bottom: 0,
flexDirection: 'row',
alignItems: 'center',
height: rpx(110),
zIndex: 100000
},
toolBarStyleInner: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: rpx(30),
justifyContent: 'space-around',
flex: 1,
height: rpx(90),
position: 'absolute',
top: 0
},
slider: {
flex: 1,
marginHorizontal: 5,
height: rpx(90)
},
progressStyle: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
marginHorizontal: rpx(20)
},
timeStyle: {
width: rpx(70),
color: 'white',
fontSize: rpx(24)
},
navContentStyle: {
height: rpx(90),
flexDirection: 'row',
alignItems: 'center',
position: 'absolute',
top: 0,
zIndex: 100001,
},
navContentStyleInner: {
height: rpx(90),
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: rpx(20),
position: 'absolute'
},
verticalSlider: {
width: rpx(320),
height: rpx(84),
borderRadius: rpx(42),
backgroundColor: 'rgba(0,0,0,0.4)',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: rpx(40)
},
smallSlider: {
width: rpx(220),
height: rpx(84),
borderRadius: rpx(42),
backgroundColor: 'rgba(0,0,0,0.4)',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
verticalSliderContainer: {
height: rpx(84),
justifyContent: 'center',
alignItems: 'center',
zIndex: 100001
}
});