diff --git a/app/src/component/AppNavigator.js b/app/src/component/AppNavigator.js index 278aa1c5..4ac3c139 100644 --- a/app/src/component/AppNavigator.js +++ b/app/src/component/AppNavigator.js @@ -20,7 +20,8 @@ const discoverStack = StackNavigator({ File: { screen: FilePage, navigationOptions: { - header: null + header: null, + drawerLockMode: 'locked-closed' } } }, { diff --git a/app/src/component/fileItem/view.js b/app/src/component/fileItem/view.js index baffe747..5482bae3 100644 --- a/app/src/component/fileItem/view.js +++ b/app/src/component/fileItem/view.js @@ -58,7 +58,7 @@ class FileItem extends React.PureComponent { this.props.navigation.navigate('File', { uri: uri }); } }> - + {title} {channelName && diff --git a/app/src/component/fileItemMedia/view.js b/app/src/component/fileItemMedia/view.js index 69ffbf9c..cc4d0f89 100644 --- a/app/src/component/fileItemMedia/view.js +++ b/app/src/component/fileItemMedia/view.js @@ -28,7 +28,7 @@ class FileItemMedia extends React.PureComponent { render() { let style = this.props.style; - const { title, thumbnail } = this.props; + const { title, thumbnail, resizeMode } = this.props; const atStyle = this.state.autoThumbStyle; if (thumbnail && ((typeof thumbnail) === 'string')) { @@ -37,7 +37,7 @@ class FileItemMedia extends React.PureComponent { } return ( - + ); } diff --git a/app/src/component/mediaPlayer/index.js b/app/src/component/mediaPlayer/index.js new file mode 100644 index 00000000..4b97d52d --- /dev/null +++ b/app/src/component/mediaPlayer/index.js @@ -0,0 +1,7 @@ +import { connect } from 'react-redux'; +import MediaPlayer from './view'; + +const select = state => ({}); +const perform = dispatch => ({}); + +export default connect(select, perform)(MediaPlayer); diff --git a/app/src/component/mediaPlayer/view.js b/app/src/component/mediaPlayer/view.js new file mode 100644 index 00000000..0718e715 --- /dev/null +++ b/app/src/component/mediaPlayer/view.js @@ -0,0 +1,273 @@ +import React from 'react'; +import { Lbry } from 'lbry-redux'; +import { PanResponder, Text, View, ScrollView, TouchableOpacity } from 'react-native'; +import Video from 'react-native-video'; +import Icon from 'react-native-vector-icons/FontAwesome'; +import FileItemMedia from '../fileItemMedia'; +import mediaPlayerStyle from '../../styles/mediaPlayer'; + +class MediaPlayer extends React.PureComponent { + static ControlsTimeout = 3000; + + seekResponder = null; + + seekerWidth = 0; + + video = null; + + state = { + rate: 1, + volume: 1, + muted: false, + resizeMode: 'stretch', + duration: 0.0, + currentTime: 0.0, + paused: false, + fullscreenMode: false, + areControlsVisible: true, + controlsTimeout: -1, + seekerOffset: 0, + seekerPosition: 0, + firstPlay: true + }; + + formatTime(time) { + let str = ''; + let minutes = 0, hours = 0, seconds = parseInt(time, 10); + if (seconds > 60) { + minutes = parseInt(seconds / 60, 10); + seconds = seconds % 60; + + if (minutes > 60) { + hours = parseInt(minutes / 60, 10); + minutes = minutes % 60; + } + + str = (hours > 0 ? this.pad(hours) + ':' : '') + this.pad(minutes) + ':' + this.pad(seconds); + } else { + str = '00:' + this.pad(seconds); + } + + return str; + } + + pad(value) { + if (value < 10) { + return '0' + String(value); + } + + return value; + } + + onLoad = (data) => { + this.setState({ + duration: data.duration + }); + if (this.props.onMediaLoaded) { + this.props.onMediaLoaded(); + } + } + + onProgress = (data) => { + this.setState({ currentTime: data.currentTime }); + + if (!this.state.seeking) { + this.setSeekerPosition(this.calculateSeekerPosition()); + } + + if (this.state.firstPlay) { + this.setState({ firstPlay: false }); + this.hidePlayerControls(); + } + } + + clearControlsTimeout = () => { + if (this.state.controlsTimeout > -1) { + clearTimeout(this.state.controlsTimeout) + } + } + + showPlayerControls = () => { + this.clearControlsTimeout(); + if (!this.state.areControlsVisible) { + this.setState({ areControlsVisible: true }); + } + this.hidePlayerControls(); + } + + hidePlayerControls() { + const player = this; + let timeout = setTimeout(() => { + player.setState({ areControlsVisible: false }); + }, MediaPlayer.ControlsTimeout); + player.setState({ controlsTimeout: timeout }); + } + + togglePlay = () => { + this.showPlayerControls(); + this.setState({ paused: !this.state.paused }); + } + + toggleFullscreenMode = () => { + this.showPlayerControls(); + const { onFullscreenToggled } = this.props; + this.setState({ fullscreenMode: !this.state.fullscreenMode }, () => { + this.setState({ resizeMode: this.state.fullscreenMode ? 'contain' : 'stretch' }); + if (onFullscreenToggled) { + onFullscreenToggled(this.state.fullscreenMode); + } + }); + } + + onEnd = () => { + this.setState({ paused: true }); + this.video.seek(0); + } + + setSeekerPosition(position = 0) { + position = this.checkSeekerPosition(position); + this.setState({ seekerPosition: position }); + if (!this.state.seeking) { + this.setState({ seekerOffset: position }); + } + } + + checkSeekerPosition(val = 0) { + if (val < 0) { + val = 0; + } else if (val >= this.seekerWidth) { + return this.seekerWidth; + } + + return val; + } + + seekTo = (time = 0) => { + if (time > this.state.duration) { + return; + } + this.video.seek(time); + this.setState({ currentTime: time }); + } + + initSeeker() { + this.seekResponder = PanResponder.create({ + onStartShouldSetPanResponder: (evt, gestureState) => true, + onMoveShouldSetPanResponder: (evt, gestureState) => true, + + onPanResponderGrant: (evt, gestureState) => { + this.clearControlsTimeout(); + this.setState({ seeking: true }); + }, + + onPanResponderMove: (evt, gestureState) => { + const position = this.state.seekerOffset + gestureState.dx; + this.setSeekerPosition(position); + }, + + onPanResponderRelease: (evt, gestureState) => { + const time = this.getCurrentTimeForSeekerPosition(); + if (time >= this.state.duration) { + this.setState({ paused: true }); + this.onEnd(); + } else { + this.seekTo(time); + this.setState({ seeking: false }); + } + this.hidePlayerControls(); + } + }); + } + + getCurrentTimeForSeekerPosition() { + return this.state.duration * (this.state.seekerPosition / this.seekerWidth); + } + + calculateSeekerPosition() { + return this.seekerWidth * this.getCurrentTimePercentage(); + } + + getCurrentTimePercentage() { + if (this.state.currentTime > 0) { + return parseFloat(this.state.currentTime) / parseFloat(this.state.duration); + } + return 0; + }; + + componentWillMount() { + this.initSeeker(); + } + + componentWillUnmount() { + this.setState({ paused: true, fullscreenMode: false }); + const { onFullscreenToggled } = this.props; + if (onFullscreenToggled) { + onFullscreenToggled(false); + } + } + + renderPlayerControls() { + if (this.state.areControlsVisible) { + return ( + + + {this.state.paused && } + {!this.state.paused && } + + + + {this.state.fullscreenMode && } + {!this.state.fullscreenMode && } + + + {this.formatTime(this.state.currentTime)} + {this.formatTime(this.state.duration)} + + ); + } + + return null; + } + + render() { + const { fileInfo, title, thumbnail, style, fullScreenStyle } = this.props; + const flexCompleted = this.getCurrentTimePercentage() * 100; + const flexRemaining = (1 - this.getCurrentTimePercentage()) * 100; + + return ( + +