From 75b3dc5add4e33612bbd6403a9658dd2e1cfbd0b Mon Sep 17 00:00:00 2001 From: Abbas Farid Date: Mon, 28 May 2018 04:31:29 +0100 Subject: [PATCH] Made further improvments to player --- components/ControlBar.js | 46 ++++-------- components/Controls.js | 69 +++++++---------- components/Loading.js | 7 +- components/PlayButton.js | 12 +-- components/ProgressBar.js | 6 +- components/ScrollView.js | 21 +++--- components/Scrubber.js | 23 ++---- components/Time.js | 9 +-- components/ToggleIcon.js | 3 +- components/TopBar.js | 25 +++---- components/Video.js | 154 +++++++++++++++++++++++--------------- components/utils.js | 4 + 12 files changed, 178 insertions(+), 201 deletions(-) create mode 100644 components/utils.js diff --git a/components/ControlBar.js b/components/ControlBar.js index ea791003..7e51787e 100644 --- a/components/ControlBar.js +++ b/components/ControlBar.js @@ -28,23 +28,23 @@ const ControlBar = (props) => { return ( - ) } ControlBar.propTypes = { - toggleFS: PropTypes.func, - toggleMute: PropTypes.func, - onSeek: PropTypes.func, - onSeekRelease: PropTypes.func, - fullscreen: PropTypes.bool, - muted: PropTypes.bool, - inlineOnly: PropTypes.bool, - progress: PropTypes.number, - currentTime: PropTypes.number, - duration: PropTypes.number, - theme: PropTypes.string -} - -ControlBar.defaultProps = { - toggleFS: undefined, - toggleMute: undefined, - onSeek: undefined, - onSeekRelease: undefined, - inlineOnly: false, - fullscreen: false, - muted: false, - progress: 0, - currentTime: 0, - duration: 0, - theme: null + toggleFS: PropTypes.func.isRequired, + toggleMute: PropTypes.func.isRequired, + onSeek: PropTypes.func.isRequired, + onSeekRelease: PropTypes.func.isRequired, + fullscreen: PropTypes.bool.isRequired, + muted: PropTypes.bool.isRequired, + inlineOnly: PropTypes.bool.isRequired, + progress: PropTypes.number.isRequired, + currentTime: PropTypes.number.isRequired, + duration: PropTypes.number.isRequired, + theme: PropTypes.object.isRequired } export { ControlBar } diff --git a/components/Controls.js b/components/Controls.js index b61b5742..9645c94c 100644 --- a/components/Controls.js +++ b/components/Controls.js @@ -99,7 +99,7 @@ class Controls extends Component { return ( this.showControls()}> - + ) @@ -108,7 +108,7 @@ class Controls extends Component { loading() { return ( - + ) } @@ -130,6 +130,8 @@ class Controls extends Component { inlineOnly } = this.props + const { center, ...controlBar } = theme + return ( this.hideControls()}> @@ -138,14 +140,14 @@ class Controls extends Component { logo={logo} more={more} onMorePress={() => onMorePress()} - theme={theme} + theme={{ title: theme.title, more: theme.more }} /> this.props.togglePlay()} paused={paused} loading={loading} - theme={theme} + theme={center} /> @@ -178,45 +180,24 @@ class Controls extends Component { } Controls.propTypes = { - toggleFS: PropTypes.func, - toggleMute: PropTypes.func, - togglePlay: PropTypes.func, - onSeek: PropTypes.func, - onSeekRelease: PropTypes.func, - onMorePress: PropTypes.func, - paused: PropTypes.bool, - inlineOnly: PropTypes.bool, - fullscreen: PropTypes.bool, - muted: PropTypes.bool, - more: PropTypes.bool, - loading: PropTypes.bool, - progress: PropTypes.number, - currentTime: PropTypes.number, - duration: PropTypes.number, - title: PropTypes.string, - logo: PropTypes.string, - theme: PropTypes.string -} - -Controls.defaultProps = { - toggleFS: undefined, - toggleMute: undefined, - togglePlay: undefined, - onMorePress: undefined, - onSeek: undefined, - onSeekRelease: undefined, - paused: false, - more: false, - inlineOnly: false, - fullscreen: false, - muted: false, - loading: true, - progress: 0, - currentTime: 0, - duration: 0, - title: '', - logo: undefined, - theme: null + toggleFS: PropTypes.func.isRequired, + toggleMute: PropTypes.func.isRequired, + togglePlay: PropTypes.func.isRequired, + onSeek: PropTypes.func.isRequired, + onSeekRelease: PropTypes.func.isRequired, + onMorePress: PropTypes.func.isRequired, + paused: PropTypes.bool.isRequired, + inlineOnly: PropTypes.bool.isRequired, + fullscreen: PropTypes.bool.isRequired, + muted: PropTypes.bool.isRequired, + more: PropTypes.bool.isRequired, + loading: PropTypes.bool.isRequired, + progress: PropTypes.number.isRequired, + currentTime: PropTypes.number.isRequired, + duration: PropTypes.number.isRequired, + title: PropTypes.string.isRequired, + logo: PropTypes.string.isRequired, + theme: PropTypes.object.isRequired } export { Controls } diff --git a/components/Loading.js b/components/Loading.js index f60165ff..f5ab1def 100644 --- a/components/Loading.js +++ b/components/Loading.js @@ -81,13 +81,12 @@ class Loading extends Component { } Loading.propTypes = { - loading: PropTypes.bool, - theme: PropTypes.string + theme: PropTypes.string.isRequired, + loading: PropTypes.bool } Loading.defaultProps = { - loading: true, - theme: null + loading: true } export { Loading } diff --git a/components/PlayButton.js b/components/PlayButton.js index 6ead85bf..7349d60c 100644 --- a/components/PlayButton.js +++ b/components/PlayButton.js @@ -33,15 +33,9 @@ const PlayButton = props => ( ) PlayButton.propTypes = { - onPress: PropTypes.func, - paused: PropTypes.bool, - theme: PropTypes.string -} - -PlayButton.defaultProps = { - onPress: undefined, - paused: false, - theme: null + onPress: PropTypes.func.isRequired, + paused: PropTypes.bool.isRequired, + theme: PropTypes.string.isRequired } export { PlayButton } diff --git a/components/ProgressBar.js b/components/ProgressBar.js index dc2ec558..f07808ca 100644 --- a/components/ProgressBar.js +++ b/components/ProgressBar.js @@ -48,11 +48,7 @@ class ProgressBar extends Component { ProgressBar.propTypes = { progress: PropTypes.number.isRequired, - theme: PropTypes.string -} - -ProgressBar.defaultProps = { - theme: 'white' + theme: PropTypes.string.isRequired } export { ProgressBar } diff --git a/components/ScrollView.js b/components/ScrollView.js index 01253221..99e9f928 100644 --- a/components/ScrollView.js +++ b/components/ScrollView.js @@ -9,12 +9,13 @@ class ScrollView extends Component { super(props) this.scrollPos = 0 this.state = { - fullscreen: false + fullscreen: false, + key: 0 } } - onFullScreenChange(fullscreen) { - this.setState({ fullscreen }, () => { + onFullScreenChange(fullscreen, key) { + this.setState({ fullscreen, key }, () => { if (!fullscreen) this.scrollBackToPosition() }) } @@ -23,31 +24,33 @@ class ScrollView extends Component { if (this.scroll) this.scroll.scrollTo({ y: this.scrollPos, animated: false }) } - cloneElement(child) { + cloneElement(child, key) { + if (this.state.fullscreen && key !== this.state.key) return null + return React.cloneElement(child, { onFullScreen: (val) => { child.props.onFullScreen(val) - this.onFullScreenChange(val) + this.onFullScreenChange(val, key) } }) } renderChildren(children) { - return React.Children.map(children, (child) => { + return React.Children.map(children, (child, key) => { const element = child.type.name switch (true) { case element === 'Container': { const props = this.state.fullscreen ? { style: {} } : child.props const components = React.Children.map(child.props.children, (component) => { const { name } = component.type - if (name === 'Video') return this.cloneElement(component) + if (name === 'Video') return this.cloneElement(component, key) if (this.state.fullscreen && name !== 'Video') return null return component }) return React.cloneElement(child, props, components) } case element === 'Video': - return this.cloneElement(child) + return this.cloneElement(child, key) case (this.state.fullscreen && element !== 'Video'): return null default: @@ -72,7 +75,7 @@ class ScrollView extends Component { bounces={fullscreen ? !fullscreen : bounces} onScroll={(event) => { if (!fullscreen) this.scrollPos = event.nativeEvent.contentOffset.y - onScroll() + onScroll(event) }} scrollEventThrottle={scrollEventThrottle} > diff --git a/components/Scrubber.js b/components/Scrubber.js index ce83e883..be989240 100644 --- a/components/Scrubber.js +++ b/components/Scrubber.js @@ -30,10 +30,10 @@ const Scrubber = (props) => { onValueChange={val => props.onSeek(val)} onSlidingComplete={val => props.onSeekRelease(val)} value={progress === Number.POSITIVE_INFINITY ? 0 : progress} - thumbTintColor={theme} + thumbTintColor={theme.scrubberThumb} thumbStyle={thumbStyle} trackStyle={trackStyle} - minimumTrackTintColor={theme} + minimumTrackTintColor={theme.scrubberBar} maximumTrackTintColor={trackColor} /> : @@ -42,8 +42,8 @@ const Scrubber = (props) => { onValueChange={val => props.onSeek(val)} onSlidingComplete={val => props.onSeekRelease(val)} value={progress} - thumbTintColor={theme} - minimumTrackTintColor={theme} + thumbTintColor={theme.scrubberThumb} + minimumTrackTintColor={theme.scrubberBar} maximumTrackTintColor={trackColor} /> } @@ -52,17 +52,10 @@ const Scrubber = (props) => { } Scrubber.propTypes = { - onSeek: PropTypes.func, - onSeekRelease: PropTypes.func, - progress: PropTypes.number, - theme: PropTypes.string -} - -Scrubber.defaultProps = { - onSeek: undefined, - onSeekRelease: undefined, - progress: 0, - theme: null + onSeek: PropTypes.func.isRequired, + onSeekRelease: PropTypes.func.isRequired, + progress: PropTypes.number.isRequired, + theme: PropTypes.object.isRequired } export { Scrubber } diff --git a/components/Time.js b/components/Time.js index 6f2b6244..01be9137 100644 --- a/components/Time.js +++ b/components/Time.js @@ -39,13 +39,8 @@ class Time extends Component { } Time.propTypes = { - time: PropTypes.number, - theme: PropTypes.string -} - -Time.defaultProps = { - time: 0, - theme: null + time: PropTypes.number.isRequired, + theme: PropTypes.string.isRequired } export { Time } diff --git a/components/ToggleIcon.js b/components/ToggleIcon.js index a2ad0135..78451623 100644 --- a/components/ToggleIcon.js +++ b/components/ToggleIcon.js @@ -50,7 +50,7 @@ ToggleIcon.propTypes = { isOn: PropTypes.bool, iconOff: PropTypes.string.isRequired, iconOn: PropTypes.string.isRequired, - theme: PropTypes.string, + theme: PropTypes.string.isRequired, size: PropTypes.number, paddingRight: PropTypes.bool, paddingLeft: PropTypes.bool @@ -59,7 +59,6 @@ ToggleIcon.propTypes = { ToggleIcon.defaultProps = { onPress: undefined, isOn: false, - theme: null, size: 25, paddingRight: false, paddingLeft: false diff --git a/components/TopBar.js b/components/TopBar.js index d6070998..23b2ddb0 100644 --- a/components/TopBar.js +++ b/components/TopBar.js @@ -10,6 +10,7 @@ import { import LinearGradient from 'react-native-linear-gradient' import { ToggleIcon } from './' +import { checkSource } from './utils' const backgroundColor = 'transparent' @@ -48,9 +49,9 @@ const TopBar = (props) => { return ( - { logo && } + { logo && } @@ -63,7 +64,7 @@ const TopBar = (props) => { paddingRight iconOff="more-horiz" iconOn="more-horiz" - theme={theme} + theme={theme.more} size={25} /> } @@ -73,19 +74,11 @@ const TopBar = (props) => { } TopBar.propTypes = { - title: PropTypes.string, - logo: PropTypes.string, - more: PropTypes.bool, - onMorePress: PropTypes.func, - theme: PropTypes.string -} - -TopBar.defaultProps = { - title: '', - logo: undefined, - more: false, - onMorePress: undefined, - theme: null + title: PropTypes.string.isRequired, + logo: PropTypes.string.isRequired, + more: PropTypes.bool.isRequired, + onMorePress: PropTypes.func.isRequired, + theme: PropTypes.object.isRequired } export { TopBar } diff --git a/components/Video.js b/components/Video.js index 9ef7798f..de2e7269 100644 --- a/components/Video.js +++ b/components/Video.js @@ -15,6 +15,7 @@ import KeepAwake from 'react-native-keep-awake' import Orientation from 'react-native-orientation' import Icons from 'react-native-vector-icons/MaterialIcons' import { Controls } from './' +import { checkSource } from './utils' const Win = Dimensions.get('window') const backgroundColor = '#000' @@ -34,6 +35,20 @@ const styles = StyleSheet.create({ } }) +const defaultTheme = { + title: '#FFF', + more: '#FFF', + center: '#FFF', + fullscreen: '#FFF', + volume: '#FFF', + scrubberThumb: '#FFF', + scrubberBar: '#FFF', + seconds: '#FFF', + duration: '#FFF', + progress: '#FFF', + loading: '#FFF' +} + class Video extends Component { constructor(props) { super(props) @@ -72,7 +87,7 @@ class Video extends Component { onLoad(data) { if (!this.state.loading) return this.props.onLoad(data) - const { height, width } = data.naturalSize + const { height, width } = data.naturalSize const ratio = height === 'undefined' && width === 'undefined' ? (9 / 16) : (height / width) const inlineHeight = this.props.lockRatio ? @@ -85,6 +100,7 @@ class Video extends Component { duration: data.duration }, () => { Animated.timing(this.animInline, { toValue: inlineHeight, duration: 200 }).start() + this.props.onPlay(!this.state.paused) if (!this.state.paused) { KeepAwake.activate() if (this.props.fullScreenOnly) { @@ -131,6 +147,7 @@ class Video extends Component { paused: this.props.fullScreenOnly || this.state.paused }, () => { this.animToInline() + if (this.props.fullScreenOnly) this.props.onPlay(!this.state.paused) this.props.onFullScreen(this.state.fullScreen) }) return @@ -141,10 +158,10 @@ class Video extends Component { if (this.state.fullScreen) this.animToFullscreen(height) } - onSeekRelease(pos) { - const newPosition = pos * this.state.duration - this.setState({ progress: pos, seeking: false }, () => { - this.player.seek(newPosition) + onSeekRelease(percent) { + const seconds = percent * this.state.duration + this.setState({ progress: percent, seeking: false }, () => { + this.player.seek(seconds) }) } @@ -194,6 +211,7 @@ class Video extends Component { togglePlay() { this.setState({ paused: !this.state.paused }, () => { + this.props.onPlay(!this.state.paused) Orientation.getOrientation((e, orientation) => { if (this.props.inlineOnly) return if (!this.state.paused) { @@ -222,14 +240,16 @@ class Video extends Component { const initialOrient = Orientation.getInitialOrientation() const height = orientation !== initialOrient ? Win.width : Win.height - this.animToFullscreen(height) - this.props.onFullScreen(this.state.fullScreen) - if (this.props.rotateToFullScreen) Orientation.lockToLandscape() + this.props.onFullScreen(this.state.fullScreen) + if (this.props.rotateToFullScreen) Orientation.lockToLandscape() + this.animToFullscreen(height) } else { - if (this.props.fullScreenOnly) this.setState({ paused: true }) - this.animToInline() + if (this.props.fullScreenOnly) { + this.setState({ paused: true }, () => this.props.onPlay(!this.state.paused)) + } this.props.onFullScreen(this.state.fullScreen) if (this.props.rotateToFullScreen) Orientation.lockToPortrait() + this.animToInline() setTimeout(() => { if (!this.props.lockPortraitOnFsExit) Orientation.unlockAllOrientations() }, 1500) @@ -257,11 +277,20 @@ class Video extends Component { this.setState({ muted: !this.state.muted }) } - seek(val) { - const currentTime = val * this.state.duration + seek(percent) { + const currentTime = percent * this.state.duration this.setState({ seeking: true, currentTime }) } + seekTo(seconds) { + const percent = seconds / this.state.duration + if (seconds > this.state.duration) { + throw new Error(`Current time (${seconds}) exceeded the duration ${this.state.duration}`) + return false + } + return this.onSeekRelease(percent) + } + progress(time) { const { currentTime } = time const progress = currentTime / this.state.duration @@ -272,11 +301,6 @@ class Video extends Component { } } - checkSource(uri) { - return typeof uri === 'string' ? - { source: { uri } } : { source: uri } - } - renderError() { const { fullScreen } = this.state const inline = { @@ -334,6 +358,11 @@ class Video extends Component { alignSelf: 'stretch' } + const setTheme = { + ...defaultTheme, + ...theme + } + return ( @@ -406,24 +435,10 @@ Video.propTypes = { PropTypes.string, PropTypes.number ]).isRequired, - autoPlay: PropTypes.bool, - loop: PropTypes.bool, - title: PropTypes.string, - logo: PropTypes.string, - resizeMode: PropTypes.string, - onMorePress: PropTypes.func, - onFullScreen: PropTypes.func, - onTimedMetadata: PropTypes.func, - theme: PropTypes.string, - placeholder: PropTypes.string, - rotateToFullScreen: PropTypes.bool, - fullScreenOnly: PropTypes.bool, - inlineOnly: PropTypes.bool, - rate: PropTypes.number, - volume: PropTypes.number, - playInBackground: PropTypes.bool, - playWhenInactive: PropTypes.bool, - lockPortraitOnFsExit: PropTypes.bool, + placeholder: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number + ]), style: PropTypes.oneOfType([ PropTypes.object, PropTypes.number @@ -432,39 +447,58 @@ Video.propTypes = { PropTypes.bool, PropTypes.object ]), - onError: PropTypes.func, + loop: PropTypes.bool, + autoPlay: PropTypes.bool, + inlineOnly: PropTypes.bool, + fullScreenOnly: PropTypes.bool, + playInBackground: PropTypes.bool, + playWhenInactive: PropTypes.bool, + rotateToFullScreen: PropTypes.bool, + lockPortraitOnFsExit: PropTypes.bool, onEnd: PropTypes.func, - onProgress: PropTypes.func, onLoad: PropTypes.func, - lockRatio: PropTypes.number + onPlay: PropTypes.func, + onError: PropTypes.func, + onProgress: PropTypes.func, + onMorePress: PropTypes.func, + onFullScreen: PropTypes.func, + onTimedMetadata: PropTypes.func, + rate: PropTypes.number, + volume: PropTypes.number, + lockRatio: PropTypes.number, + logo: PropTypes.string, + title: PropTypes.string, + theme: PropTypes.object, + resizeMode: PropTypes.string } Video.defaultProps = { - autoPlay: false, - loop: false, - title: '', - logo: undefined, - resizeMode: 'contain', - onMorePress: undefined, - onFullScreen: () => {}, - onTimedMetadata: undefined, - theme: 'white', placeholder: undefined, - rotateToFullScreen: false, - fullScreenOnly: false, + style: {}, + error: true, + loop: false, + autoPlay: false, inlineOnly: false, + fullScreenOnly: false, playInBackground: false, playWhenInactive: false, - rate: 1, - volume: 1, + rotateToFullScreen: false, lockPortraitOnFsExit: false, - style: {}, - error: true, - onError: () => {}, onEnd: () => {}, - onProgress: () => {}, onLoad: () => {}, - lockRatio: undefined + onPlay: () => {}, + onError: () => {}, + onProgress: () => {}, + onMorePress: undefined, + onFullScreen: () => {}, + onTimedMetadata: () => {}, + rate: 1, + volume: 1, + lockRatio: undefined, + logo: undefined, + title: '', + theme: defaultTheme, + resizeMode: 'contain' } export default Video diff --git a/components/utils.js b/components/utils.js new file mode 100644 index 00000000..7e5811ef --- /dev/null +++ b/components/utils.js @@ -0,0 +1,4 @@ +export const checkSource = (uri) => { + return typeof uri === 'string' ? + { source: { uri } } : { source: uri } +}