diff --git a/README.md b/README.md
index 13b7837..42ed28f 100644
--- a/README.md
+++ b/README.md
@@ -76,8 +76,8 @@ All other props are passed to the react-native-video component.
- [X] Make seek bar seekable.
- [x] Make player customizable.
-- [ ] Add volume control
+- [X] Add volume control
- [X] Add fullscreen button
- [ ] Add fullscreen button for Android (See PR #38 if you need fullscreen in Android)
- [ ] Add loader
-- [ ] Add video duration/play time
+- [X] Add video duration/play time
diff --git a/index.js b/index.js
index 7a07891..a7f0d71 100644
--- a/index.js
+++ b/index.js
@@ -1,6 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
-import { Image, ImageBackground, Platform, StyleSheet, TouchableOpacity, View, ViewPropTypes } from 'react-native';
+import { Image, ImageBackground, Platform, StyleSheet, TouchableOpacity, View, ViewPropTypes, Text} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
import Video from 'react-native-video'; // eslint-disable-line
@@ -37,6 +37,7 @@ const styles = StyleSheet.create({
marginTop: -48,
flexDirection: 'row',
alignItems: 'center',
+ zIndex: 1,
},
playControl: {
color: 'white',
@@ -53,7 +54,7 @@ const styles = StyleSheet.create({
flexDirection: 'row',
paddingHorizontal: 10,
marginLeft: -10,
- marginRight: -5,
+ marginRight: 0,
},
seekBarFullWidth: {
marginLeft: 0,
@@ -74,7 +75,7 @@ const styles = StyleSheet.create({
borderRadius: 10,
backgroundColor: '#F00',
transform: [{ scale: 0.8 }],
- zIndex: 1,
+ zIndex: 2,
},
seekBarBackground: {
backgroundColor: 'rgba(255, 255, 255, 0.5)',
@@ -83,6 +84,29 @@ const styles = StyleSheet.create({
overlayButton: {
flex: 1,
},
+ timeSyle: {
+ fontSize: 15,
+ color: 'rgba(255, 255, 255, 0.6)',
+ },
+
+ volReflect: {
+ backgroundColor: 'rgba(255, 0, 0, 0.5)',
+ width: 100,
+ height: 30,
+ flexGrow: 1,
+ flexDirection: 'row',
+ alignSelf: 'center',
+ },
+ volProgress: {
+ height: 3,
+ backgroundColor: '#FFF',
+ alignSelf:'flex-end',
+ },
+ volLeft: {
+ backgroundColor: 'rgba(255, 255, 255, 0.5)',
+ height: 3,
+ alignSelf:'flex-end',
+ },
});
export default class VideoPlayer extends Component {
@@ -94,12 +118,16 @@ export default class VideoPlayer extends Component {
isPlaying: props.autoplay,
width: 200,
progress: 0,
- isMuted: props.defaultMuted,
+ isMuted: true,
isControlsVisible: !props.hideControlsOnStart,
duration: 0,
isSeeking: false,
+ volume: 0,
+ volBarNeedShow: false,
};
+ this.volControlBarWith = 200;
+ this.volControlTouchStart = 0;
this.seekBarWidth = 200;
this.wasPlayingBeforeSeek = props.autoplay;
this.seekTouchStart = 0;
@@ -118,6 +146,10 @@ export default class VideoPlayer extends Component {
this.onSeekGrant = this.onSeekGrant.bind(this);
this.onSeekRelease = this.onSeekRelease.bind(this);
this.onSeek = this.onSeek.bind(this);
+ this.onVolCtrlGrant = this.onVolCtrlGrant.bind(this);
+ this.onVolCtrlRelease = this.onVolCtrlRelease.bind(this);
+ this.onVolControl = this.onVolControl.bind(this);
+ this.onBack = this.props.onBack;
}
componentDidMount() {
@@ -134,7 +166,7 @@ export default class VideoPlayer extends Component {
}
onLayout(event) {
- const { width } = event.nativeEvent.layout;
+ const { width, height } = event.nativeEvent.layout;
this.setState({
width,
});
@@ -242,6 +274,14 @@ export default class VideoPlayer extends Component {
return true;
}
+ onVolCtrlStartResponder() {
+ return true;
+ }
+
+ onMoveShouldSetPanResponder() {
+ return true;
+ }
+
onSeekGrant(e) {
this.seekTouchStart = e.nativeEvent.pageX;
this.seekProgressStart = this.state.progress;
@@ -260,6 +300,44 @@ export default class VideoPlayer extends Component {
this.showControls();
}
+ onVolCtrlGrant(e) {
+ console.log("Test vol ctrl grant start, pos=", e.nativeEvent.pageY);
+ const volBarNeedShow = true;
+ this.volControlTouchStart = e.nativeEvent.pageY;
+ this.showControls();
+ this.setState({volBarNeedShow});
+ }
+
+ onVolCtrlRelease() {
+ console.log("Test vol ctrl touch release");
+ const volBarNeedShow = false;
+ this.setState({volBarNeedShow});
+ }
+
+ onVolControl(e) {
+ let volume = this.state.volume;
+ const senseFactor = 5/4;
+ const diff = this.volControlTouchStart - e.nativeEvent.pageY;
+ const volChange = diff / (this.getSizeStyles().height) * senseFactor;
+ volume = volume + volChange;
+ if(volume > 1) {
+ volume = 1;
+ }
+ if(volume < 0) {
+ volume = 0;
+ }
+ console.log("Test onVolControl,pos=", e.nativeEvent.pageY,"new vol=", volume, "vol control touch start=", this.volControlTouchStart);
+ this.volControlTouchStart = e.nativeEvent.pageY;
+ let mutedChg = ((volume == 0 && this.state.volume > 0 && !this.state.isMuted) || (volume > 0 && this.state.volume == 0)
+ || (this.state.isMuted && volChange > 0));
+ const isMuted = (mutedChg ? (!this.state.isMuted) : (this.state.isMuted));
+ console.log("Test mutedChg=", mutedChg, "new mute status=", isMuted);
+ this.setState({
+ volume,
+ isMuted,
+ });
+ }
+
onSeek(e) {
const diff = e.nativeEvent.pageX - this.seekTouchStart;
const ratio = 100 / this.seekBarWidth;
@@ -338,6 +416,43 @@ export default class VideoPlayer extends Component {
this.showControls();
}
+ formatTime(seconds) {
+ let hour = parseInt(seconds/3600);
+ let minute = parseInt(seconds/60);
+ let sec = parseInt(seconds%60);
+ console.log("Test input sec=", seconds, "hour=", hour, "minute=", minute, "sec=", sec);
+ let formatTime = 0;
+ if(hour > 99) {
+ console.log("Test input time out of bound, seconds=", seconds);
+ return formatTime;
+ }
+ if(seconds === 0) {
+ return '00:00';
+ }
+ if(hour < 10 && hour > 0) {
+ hour = '0' + hour.toString();
+ }
+ if(minute < 10) {
+ minute = '0' + minute.toString();
+ }
+ if(sec < 10) {
+ minute = '0' + sec.toString();
+ }
+ if(hour > 0) {
+ formatTime = hour + ':' + minute + ':' + sec;
+ }
+ else {
+ formatTime = minute + ':' + sec;
+ }
+ return formatTime;
+ }
+
+ getMuteStatus() {
+ this.state.isMuted = (this.state.volume === 0 ? true : false);
+ console.log("Test isMuted=", this.state.isMuted, "vol=", this.state.volume);
+ return this.props.muted || this.state.isMuted;
+ }
+
renderStartButton() {
const { customStyles } = this.props;
return (
@@ -368,6 +483,26 @@ export default class VideoPlayer extends Component {
);
}
+ renderVolControlBar() {
+ return (
+
+ );
+ }
+
renderSeekBar(fullWidth) {
const { customStyles, disableSeek } = this.props;
return (
@@ -413,6 +548,57 @@ export default class VideoPlayer extends Component {
);
}
+ renderVolReflect() {
+ return (
+
+
+
+
+
+
+
+ );
+ }
+
renderControls() {
const { customStyles } = this.props;
return (
@@ -428,6 +614,11 @@ export default class VideoPlayer extends Component {
/>
{this.renderSeekBar()}
+ {
+
+ {this.formatTime(this.state.duration)}
+
+ }
{this.props.muted ? null : (
{ this.player = p; }}
+ volume={this.state.volume}
muted={this.props.muted || this.state.isMuted}
paused={!this.state.isPlaying}
onProgress={this.onProgress}
@@ -496,10 +688,13 @@ export default class VideoPlayer extends Component {
if (fullScreenOnLongPress && Platform.OS !== 'android')
this.onToggleFullScreen();
}}
- />
-
+ />
+ {this.state.volBarNeedShow
+ ? this.renderVolReflect() : null}
+
{((!this.state.isPlaying) || this.state.isControlsVisible)
? this.renderControls() : this.renderSeekBar(true)}
+ {this.renderVolControlBar()}
);
}
@@ -520,10 +715,37 @@ export default class VideoPlayer extends Component {
return this.renderVideo();
}
+ renderHeader() {
+ const {height} = this.getSizeStyles();
+ const {videoTitle} = this.props;
+ return (
+
+
+
+
+
+
+ {videoTitle}
+
+
+
+ );
+ }
+
render() {
return (
{this.renderContent()}
+ {(!this.state.isPlaying || this.state.isControlsVisible) ? this.renderHeader(): null}
);
}
@@ -549,6 +771,7 @@ VideoPlayer.propTypes = {
disableSeek: PropTypes.bool,
pauseOnPress: PropTypes.bool,
fullScreenOnLongPress: PropTypes.bool,
+ videoTitle: PropTypes.string,
customStyles: PropTypes.shape({
wrapper: ViewPropTypes.style,
video: Video.propTypes.style,
@@ -575,6 +798,7 @@ VideoPlayer.propTypes = {
onPlayPress: PropTypes.func,
onHideControls: PropTypes.func,
onShowControls: PropTypes.func,
+ onBack: PropTypes.func,
};
VideoPlayer.defaultProps = {
@@ -586,6 +810,6 @@ VideoPlayer.defaultProps = {
resizeMode: 'contain',
disableSeek: false,
pauseOnPress: false,
- fullScreenOnLongPress: false,
+ fullScreenOnLongPress: true,
customStyles: {},
};