forked from Ivasoft/mattermost-mobile
* [MM-18766] removed deprecated lifecycle methods from post components * [MM-18766] update snapshot, seems like there was a bug * [MM-18766] refactor getBestImageUrl -> getBestImageUrlAndDimensions - refactor getBestImageUrl to take a single argument: props - refactor getViewPostWidth to take a single argument: props * [MM-18766] getDerivedStateFromProps was previously returning only first prop change - fix issue if multiple props were modified at once. * [MM-18766] removed FastImage.preload that was supposed to be removed in the last merge. * [MM-18766] removed getDerivedStateFromProps * [MM-18766] fix tests based on actual component being completely re-rendered rather than prop change. * [MM-18766] cleaning up initial changes due to no longer using getDerivedStateFromProps method - move back getBestImageUrl and getViewPostWidth from outside of components - keep the renaming of getBestImageUrl - remove the use of spread operator as it is also no longer needed * [MM-18766] one of caller to getViewPostWidth is still passing this.props - this is no longer needed
343 lines
9.8 KiB
JavaScript
343 lines
9.8 KiB
JavaScript
/* eslint-disable header/header */
|
|
|
|
// Copyright (c) 2016-2017 Charles.
|
|
// Modified work: Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import React, {PureComponent} from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import {
|
|
ActivityIndicator,
|
|
Animated,
|
|
AppState,
|
|
TouchableOpacity,
|
|
StyleSheet,
|
|
Text,
|
|
View,
|
|
} from 'react-native';
|
|
import FastImage from 'react-native-fast-image';
|
|
import Slider from 'react-native-slider';
|
|
|
|
import fullscreenImage from '@assets/images/video_player/fullscreen.png';
|
|
import pauseImage from '@assets/images/video_player/pause.png';
|
|
import playImage from '@assets/images/video_player/play.png';
|
|
import replayImage from '@assets/images/video_player/replay.png';
|
|
|
|
export const PLAYER_STATE = {
|
|
PLAYING: 0,
|
|
PAUSED: 1,
|
|
ENDED: 2,
|
|
};
|
|
|
|
export default class VideoControls extends PureComponent {
|
|
static propTypes = {
|
|
duration: PropTypes.number,
|
|
isLoading: PropTypes.bool,
|
|
isFullScreen: PropTypes.bool,
|
|
mainColor: PropTypes.string,
|
|
onFullScreen: PropTypes.func,
|
|
onPaused: PropTypes.func,
|
|
onReplay: PropTypes.func,
|
|
onSeek: PropTypes.func,
|
|
onSeeking: PropTypes.func,
|
|
playerState: PropTypes.number,
|
|
progress: PropTypes.number,
|
|
};
|
|
|
|
static defaultProps = {
|
|
duration: 0,
|
|
mainColor: 'rgba(12, 83, 175, 0.9)',
|
|
};
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
opacity: new Animated.Value(1),
|
|
isVisible: true,
|
|
isSeeking: false,
|
|
};
|
|
}
|
|
|
|
componentDidMount() {
|
|
AppState.addEventListener('change', this.handleAppStateChange);
|
|
}
|
|
|
|
componentDidUpdate(prevProps) {
|
|
if (this.props.playerState === PLAYER_STATE.ENDED || this.props.isLoading ||
|
|
(this.props.playerState === PLAYER_STATE.PAUSED && prevProps.playerState === PLAYER_STATE.PLAYING)) {
|
|
this.fadeInControls(false);
|
|
}
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
AppState.removeEventListener('change', this.handleAppStateChange);
|
|
}
|
|
|
|
cancelAnimation = () => {
|
|
this.state.opacity.stopAnimation(() => {
|
|
this.setState({isVisible: true});
|
|
});
|
|
};
|
|
|
|
fadeInControls = (loop = true) => {
|
|
this.setState({isVisible: true});
|
|
Animated.timing(this.state.opacity, {
|
|
toValue: 1,
|
|
duration: 250,
|
|
delay: 0,
|
|
useNativeDriver: true,
|
|
}).start(() => {
|
|
if (loop) {
|
|
this.fadeOutControls(2000);
|
|
}
|
|
});
|
|
};
|
|
|
|
fadeOutControls = (delay = 0) => {
|
|
Animated.timing(this.state.opacity, {
|
|
toValue: 0,
|
|
duration: 250,
|
|
delay,
|
|
useNativeDriver: true,
|
|
}).start((result) => {
|
|
if (result.finished) {
|
|
this.setState({isVisible: false});
|
|
}
|
|
});
|
|
};
|
|
|
|
getControlIconAndAspectRatio = (playerState) => {
|
|
switch (playerState) {
|
|
case PLAYER_STATE.PLAYING:
|
|
return {icon: pauseImage, aspectRatio: 0.83};
|
|
case PLAYER_STATE.ENDED:
|
|
return {icon: replayImage, aspectRatio: 1.17};
|
|
}
|
|
|
|
return {icon: playImage, aspectRatio: 0.83};
|
|
};
|
|
|
|
handleAppStateChange = (nextAppState) => {
|
|
if (nextAppState !== 'active' && this.props.playerState === PLAYER_STATE.PLAYING) {
|
|
this.onPause();
|
|
}
|
|
};
|
|
|
|
humanizeVideoDuration = (seconds) => {
|
|
const [begin, end] = seconds >= 3600 ? [11, 8] : [14, 5];
|
|
const date = new Date(null);
|
|
date.setSeconds(seconds);
|
|
return date.toISOString().substr(begin, end);
|
|
};
|
|
|
|
onPause = () => {
|
|
if (this.props.playerState === PLAYER_STATE.PLAYING) {
|
|
this.cancelAnimation();
|
|
}
|
|
if (this.props.playerState === PLAYER_STATE.PAUSED) {
|
|
this.fadeOutControls(250);
|
|
}
|
|
this.props.onPaused();
|
|
};
|
|
|
|
onReplay = () => {
|
|
this.fadeOutControls(500);
|
|
this.props.onReplay();
|
|
};
|
|
|
|
renderControls() {
|
|
return (
|
|
<View style={styles.container}>
|
|
<View style={[styles.controlsRow, StyleSheet.absoluteFill]}>
|
|
{
|
|
this.props.isLoading ? this.setLoadingView() : this.setPlayerControls(this.props.playerState)
|
|
}
|
|
</View>
|
|
<View style={[styles.controlsRow, styles.progressContainer]}>
|
|
<View style={styles.progressColumnContainer}>
|
|
<View style={[styles.timerLabelsContainer]}>
|
|
<Text style={styles.timerLabel}>
|
|
{this.humanizeVideoDuration(this.props.progress)}
|
|
</Text>
|
|
<Text style={styles.timerLabel}>
|
|
{this.humanizeVideoDuration(this.props.duration)}
|
|
</Text>
|
|
</View>
|
|
<Slider
|
|
style={styles.progressSlider}
|
|
onSlidingComplete={this.seekVideoEnd}
|
|
onValueChange={this.seekVideo}
|
|
onSlidingStart={this.seekVideoStart}
|
|
maximumValue={Math.floor(this.props.duration)}
|
|
value={Math.floor(this.props.progress)}
|
|
trackStyle={styles.track}
|
|
thumbStyle={[styles.thumb, {borderColor: this.props.mainColor}]}
|
|
minimumTrackTintColor={this.props.mainColor}
|
|
/>
|
|
</View>
|
|
<TouchableOpacity
|
|
style={styles.fullScreenContainer}
|
|
onPress={this.props.onFullScreen}
|
|
>
|
|
<FastImage
|
|
source={fullscreenImage}
|
|
style={{width: 20, height: 20}}
|
|
/>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
seekVideo = (value) => {
|
|
this.setState({isSeeking: true});
|
|
this.props.onSeek(value);
|
|
};
|
|
|
|
seekVideoEnd = (value) => {
|
|
this.setState({isSeeking: false});
|
|
if (this.props.playerState === PLAYER_STATE.PLAYING) {
|
|
this.toggleControls();
|
|
}
|
|
this.props.onSeek(value);
|
|
if (this.props.onSeeking) {
|
|
this.props.onSeeking(true);
|
|
}
|
|
};
|
|
|
|
seekVideoStart = () => {
|
|
this.setState({isSeeking: true});
|
|
this.cancelAnimation();
|
|
if (this.props.onSeeking) {
|
|
this.props.onSeeking(false);
|
|
}
|
|
};
|
|
|
|
setPlayerControls = (playerState) => {
|
|
const {icon, aspectRatio} = this.getControlIconAndAspectRatio(playerState);
|
|
const pressAction = playerState === PLAYER_STATE.ENDED ? this.onReplay : this.onPause;
|
|
return (
|
|
<TouchableOpacity
|
|
style={[styles.controlButton, {backgroundColor: this.props.mainColor}]}
|
|
onPress={pressAction}
|
|
>
|
|
<FastImage
|
|
source={icon}
|
|
style={[styles.controlIcon, {aspectRatio}]}
|
|
/>
|
|
</TouchableOpacity>
|
|
);
|
|
};
|
|
|
|
setLoadingView = () => {
|
|
return (
|
|
<ActivityIndicator
|
|
size='large'
|
|
color='#FFF'
|
|
/>
|
|
);
|
|
};
|
|
|
|
toggleControls = () => {
|
|
this.state.opacity.stopAnimation(
|
|
(value) => {
|
|
this.setState({isVisible: Boolean(value)});
|
|
if (value) {
|
|
this.fadeOutControls();
|
|
} else {
|
|
this.fadeInControls(this.props.playerState === PLAYER_STATE.PLAYING);
|
|
}
|
|
});
|
|
};
|
|
|
|
render() {
|
|
if (!this.state.isVisible) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<Animated.View style={[styles.container, {opacity: this.state.opacity}]}>
|
|
{this.renderControls()}
|
|
</Animated.View>
|
|
);
|
|
}
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
position: 'absolute',
|
|
flex: 1,
|
|
paddingHorizontal: 20,
|
|
paddingVertical: 13,
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
backgroundColor: 'transparent',
|
|
justifyContent: 'space-between',
|
|
top: 0,
|
|
left: 0,
|
|
bottom: 0,
|
|
right: 0,
|
|
},
|
|
controlsRow: {
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
alignSelf: 'stretch',
|
|
},
|
|
timeRow: {
|
|
alignSelf: 'stretch',
|
|
},
|
|
controlButton: {
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
width: 50,
|
|
height: 50,
|
|
borderRadius: 3,
|
|
borderWidth: 1.5,
|
|
borderColor: 'rgba(255,255,255,0.5)',
|
|
},
|
|
controlIcon: {
|
|
height: 20,
|
|
},
|
|
progressContainer: {
|
|
position: 'absolute',
|
|
flexDirection: 'row',
|
|
justifyContent: 'flex-end',
|
|
bottom: 25,
|
|
marginLeft: 16,
|
|
},
|
|
progressColumnContainer: {
|
|
flex: 1,
|
|
},
|
|
fullScreenContainer: {
|
|
alignSelf: 'stretch',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
paddingLeft: 10,
|
|
paddingTop: 8,
|
|
},
|
|
progressSlider: {
|
|
alignSelf: 'stretch',
|
|
},
|
|
timerLabelsContainer: {
|
|
alignSelf: 'stretch',
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
marginBottom: -7,
|
|
},
|
|
timerLabel: {
|
|
fontSize: 12,
|
|
color: 'white',
|
|
},
|
|
track: {
|
|
height: 5,
|
|
borderRadius: 1,
|
|
},
|
|
thumb: {
|
|
width: 20,
|
|
height: 20,
|
|
borderRadius: 50,
|
|
backgroundColor: 'white',
|
|
borderWidth: 3,
|
|
},
|
|
});
|