Video fullscreen switch (#132)
* file page download progress display and media loading / playback tweaks * Moved MediaPlayer component to be a child of the top-level view * hide tracking controls when player controls are hidden in fullscreen mode
This commit is contained in:
parent
f850be1eca
commit
ceeef6324e
4 changed files with 135 additions and 70 deletions
|
@ -20,23 +20,30 @@ class MediaPlayer extends React.PureComponent {
|
|||
|
||||
seekerWidth = 0;
|
||||
|
||||
trackingOffset = 0;
|
||||
|
||||
tracking = null;
|
||||
|
||||
video = null;
|
||||
|
||||
state = {
|
||||
rate: 1,
|
||||
volume: 1,
|
||||
muted: false,
|
||||
resizeMode: 'stretch',
|
||||
duration: 0.0,
|
||||
currentTime: 0.0,
|
||||
paused: true,
|
||||
fullscreenMode: false,
|
||||
areControlsVisible: true,
|
||||
controlsTimeout: -1,
|
||||
seekerOffset: 0,
|
||||
seekerPosition: 0,
|
||||
firstPlay: true
|
||||
};
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
rate: 1,
|
||||
volume: 1,
|
||||
muted: false,
|
||||
resizeMode: 'stretch',
|
||||
duration: 0.0,
|
||||
currentTime: 0.0,
|
||||
paused: true,
|
||||
fullscreenMode: false,
|
||||
areControlsVisible: true,
|
||||
controlsTimeout: -1,
|
||||
seekerOffset: 0,
|
||||
seekerPosition: 0,
|
||||
firstPlay: true
|
||||
};
|
||||
}
|
||||
|
||||
formatTime(time) {
|
||||
let str = '';
|
||||
|
@ -144,10 +151,11 @@ class MediaPlayer extends React.PureComponent {
|
|||
}
|
||||
|
||||
checkSeekerPosition(val = 0) {
|
||||
if (val < 0) {
|
||||
val = 0;
|
||||
} else if (val >= this.seekerWidth) {
|
||||
return this.seekerWidth;
|
||||
const offset = this.getTrackingOffset();
|
||||
if (val < offset) {
|
||||
val = offset;
|
||||
} else if (val >= (offset + this.seekerWidth)) {
|
||||
return offset + this.seekerWidth;
|
||||
}
|
||||
|
||||
return val;
|
||||
|
@ -190,11 +198,18 @@ class MediaPlayer extends React.PureComponent {
|
|||
});
|
||||
}
|
||||
|
||||
getTrackingOffset() {
|
||||
return this.state.fullscreenMode ? this.trackingOffset : 0;
|
||||
}
|
||||
|
||||
getCurrentTimeForSeekerPosition() {
|
||||
return this.state.duration * (this.state.seekerPosition / this.seekerWidth);
|
||||
}
|
||||
|
||||
calculateSeekerPosition() {
|
||||
if (this.state.fullscreenMode) {
|
||||
return this.getTrackingOffset() + (this.seekerWidth * this.getCurrentTimePercentage());
|
||||
}
|
||||
return this.seekerWidth * this.getCurrentTimePercentage();
|
||||
}
|
||||
|
||||
|
@ -209,6 +224,10 @@ class MediaPlayer extends React.PureComponent {
|
|||
this.initSeeker();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.clearControlsTimeout();
|
||||
this.setState({ paused: true, fullscreenMode: false });
|
||||
|
@ -243,13 +262,23 @@ class MediaPlayer extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { backgroundPlayEnabled, fileInfo, thumbnail, style, fullScreenStyle } = this.props;
|
||||
const { backgroundPlayEnabled, fileInfo, thumbnail, style } = this.props;
|
||||
const flexCompleted = this.getCurrentTimePercentage() * 100;
|
||||
const flexRemaining = (1 - this.getCurrentTimePercentage()) * 100;
|
||||
let styles = [this.state.fullscreenMode ? mediaPlayerStyle.fullscreenContainer : mediaPlayerStyle.container];
|
||||
if (style) {
|
||||
if (style.length) {
|
||||
styles = styles.concat(style);
|
||||
} else {
|
||||
styles.push(style);
|
||||
}
|
||||
}
|
||||
|
||||
const trackingStyle = [mediaPlayerStyle.trackingControls, this.state.fullscreenMode ?
|
||||
mediaPlayerStyle.fullscreenTrackingControls : mediaPlayerStyle.containedTrackingControls];
|
||||
|
||||
return (
|
||||
<View style={[style, mediaPlayerStyle.container]}>
|
||||
<View style={mediaPlayerStyle.playerBackground} />
|
||||
<View style={styles}>
|
||||
<Video source={{ uri: 'file:///' + fileInfo.download_path }}
|
||||
ref={(ref: Video) => { this.video = ref }}
|
||||
resizeMode={this.state.resizeMode}
|
||||
|
@ -267,12 +296,16 @@ class MediaPlayer extends React.PureComponent {
|
|||
{this.renderPlayerControls()}
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={mediaPlayerStyle.trackingControls}>
|
||||
<View style={mediaPlayerStyle.progress} onLayout={(evt) => this.seekerWidth = evt.nativeEvent.layout.width}>
|
||||
{(!this.state.fullscreenMode || (this.state.fullscreenMode && this.state.areControlsVisible)) &&
|
||||
<View style={trackingStyle} onLayout={(evt) => {
|
||||
this.trackingOffset = evt.nativeEvent.layout.x;
|
||||
this.seekerWidth = evt.nativeEvent.layout.width;
|
||||
}}>
|
||||
<View style={mediaPlayerStyle.progress}>
|
||||
<View style={[mediaPlayerStyle.innerProgressCompleted, { flex: flexCompleted }]} />
|
||||
<View style={[mediaPlayerStyle.innerProgressRemaining, { flex: flexRemaining }]} />
|
||||
</View>
|
||||
</View>
|
||||
</View>}
|
||||
|
||||
{this.state.areControlsVisible &&
|
||||
<View style={[mediaPlayerStyle.seekerHandle, { left: this.state.seekerPosition }]} { ...this.seekResponder.panHandlers }>
|
||||
|
|
|
@ -20,15 +20,18 @@ import Video from 'react-native-video';
|
|||
import filePageStyle from '../../styles/filePage';
|
||||
|
||||
class FilePage extends React.PureComponent {
|
||||
state = {
|
||||
mediaLoaded: false,
|
||||
fullscreenMode: false
|
||||
};
|
||||
|
||||
static navigationOptions = {
|
||||
title: ''
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
mediaLoaded: false,
|
||||
fullscreenMode: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
StatusBar.setHidden(false);
|
||||
this.fetchFileInfo(this.props);
|
||||
|
@ -128,27 +131,34 @@ class FilePage extends React.PureComponent {
|
|||
const mediaType = Lbry.getMediaType(contentType);
|
||||
const isPlayable = mediaType === 'video' || mediaType === 'audio';
|
||||
const { height, channel_name: channelName, value } = claim;
|
||||
const showActions = !this.state.fullscreenMode && (completed || (fileInfo && !fileInfo.stopped && fileInfo.written_bytes < fileInfo.total_bytes));
|
||||
const showActions = !this.state.fullscreenMode &&
|
||||
(completed || (fileInfo && !fileInfo.stopped && fileInfo.written_bytes < fileInfo.total_bytes));
|
||||
const channelClaimId =
|
||||
value && value.publisherSignature && value.publisherSignature.certificateId;
|
||||
|
||||
const playerStyle = [filePageStyle.player, this.state.fullscreenMode ?
|
||||
filePageStyle.fullscreenPlayer : filePageStyle.containedPlayer];
|
||||
const playerBgStyle = [filePageStyle.playerBackground, this.state.fullscreenMode ?
|
||||
filePageStyle.fullscreenPlayerBackground : filePageStyle.containedPlayerBackground];
|
||||
// at least 2MB (or the full download) before media can be loaded
|
||||
const canLoadMedia = fileInfo && (fileInfo.written_bytes >= 2097152 || fileInfo.written_bytes == fileInfo.total_bytes); // 2MB = 1024*1024*2
|
||||
const canLoadMedia = fileInfo &&
|
||||
(fileInfo.written_bytes >= 2097152 || fileInfo.written_bytes == fileInfo.total_bytes); // 2MB = 1024*1024*2
|
||||
|
||||
return (
|
||||
<View style={filePageStyle.pageContainer}>
|
||||
<View style={this.state.fullscreenMode ? filePageStyle.fullscreenMedia : filePageStyle.mediaContainer}>
|
||||
<View style={filePageStyle.mediaContainer}>
|
||||
{(!fileInfo || (isPlayable && !canLoadMedia)) &&
|
||||
<FileItemMedia style={filePageStyle.thumbnail} title={title} thumbnail={metadata.thumbnail} />}
|
||||
{isPlayable && !this.state.mediaLoaded && <ActivityIndicator size="large" color={Colors.LbryGreen} style={filePageStyle.loading} />}
|
||||
{!completed && !canLoadMedia && <FileDownloadButton uri={navigation.state.params.uri} style={filePageStyle.downloadButton} />}
|
||||
{canLoadMedia && <View style={filePageStyle.playerBackground} />}
|
||||
{canLoadMedia && <MediaPlayer fileInfo={fileInfo}
|
||||
</View>
|
||||
{canLoadMedia && <View style={playerBgStyle} />}
|
||||
{canLoadMedia && <MediaPlayer fileInfo={fileInfo}
|
||||
uri={navigation.state.params.uri}
|
||||
style={filePageStyle.player}
|
||||
style={playerStyle}
|
||||
onFullscreenToggled={this.handleFullscreenToggle}
|
||||
onMediaLoaded={() => { this.setState({ mediaLoaded: true }); }}/>}
|
||||
</View>
|
||||
|
||||
{ showActions &&
|
||||
<View style={filePageStyle.actions}>
|
||||
{completed && <Button color="red" title="Delete" onPress={this.onDeletePressed} />}
|
||||
|
|
|
@ -17,15 +17,6 @@ const filePageStyle = StyleSheet.create({
|
|||
width: screenWidth,
|
||||
height: 220
|
||||
},
|
||||
playerBackground: {
|
||||
position: 'absolute',
|
||||
flex: 1,
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 16,
|
||||
backgroundColor: Colors.Black
|
||||
},
|
||||
emptyClaimText: {
|
||||
fontFamily: 'Metropolis-Regular',
|
||||
textAlign: 'center',
|
||||
|
@ -77,21 +68,37 @@ const filePageStyle = StyleSheet.create({
|
|||
top: '40%'
|
||||
},
|
||||
player: {
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
marginBottom: 14,
|
||||
zIndex: 99
|
||||
},
|
||||
fullscreenMedia: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
zIndex: 101
|
||||
},
|
||||
containedPlayer: {
|
||||
width: '100%',
|
||||
height: 220,
|
||||
},
|
||||
fullscreenPlayer: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
flex: 1,
|
||||
backgroundColor: Colors.Black,
|
||||
zIndex: 100
|
||||
bottom: 0
|
||||
},
|
||||
playerBackground: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
zIndex: 100,
|
||||
backgroundColor: Colors.Black
|
||||
},
|
||||
containedPlayerBackground: {
|
||||
width: '100%',
|
||||
height: 206,
|
||||
},
|
||||
fullscreenPlayerBackground: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
right: 0,
|
||||
bottom: 0
|
||||
},
|
||||
actions: {
|
||||
paddingLeft: 16,
|
||||
|
|
|
@ -1,21 +1,29 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
import Colors from './colors';
|
||||
|
||||
const mediaPlayerStyle = StyleSheet.create({
|
||||
player: {
|
||||
flex: 1
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
},
|
||||
container: {
|
||||
marginBottom: 0,
|
||||
paddingBottom: 16,
|
||||
flex: 1,
|
||||
paddingBottom: 16
|
||||
},
|
||||
fullscreenContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
progress: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
height: 3
|
||||
},
|
||||
innerProgressCompleted: {
|
||||
height: 4,
|
||||
backgroundColor: '#40c0a9',
|
||||
backgroundColor: Colors.LbryGreen,
|
||||
},
|
||||
innerProgressRemaining: {
|
||||
height: 4,
|
||||
|
@ -23,10 +31,16 @@ const mediaPlayerStyle = StyleSheet.create({
|
|||
},
|
||||
trackingControls: {
|
||||
height: 3,
|
||||
width: '100%',
|
||||
position: 'absolute',
|
||||
bottom: 14,
|
||||
bottom: 14
|
||||
},
|
||||
containedTrackingControls: {
|
||||
left: 0,
|
||||
width: '100%'
|
||||
},
|
||||
fullscreenTrackingControls: {
|
||||
alignSelf: 'center',
|
||||
width: '70%'
|
||||
},
|
||||
playerControls: {
|
||||
position: 'absolute',
|
||||
|
@ -77,23 +91,24 @@ const mediaPlayerStyle = StyleSheet.create({
|
|||
borderRadius: 12,
|
||||
position: 'relative',
|
||||
top: 14,
|
||||
left: 8,
|
||||
left: 15,
|
||||
height: 12,
|
||||
width: 12,
|
||||
backgroundColor: '#40c0a9'
|
||||
},
|
||||
seekerHandle: {
|
||||
backgroundColor: 'transparent',
|
||||
position: 'absolute',
|
||||
height: 36,
|
||||
width: 36,
|
||||
width: 48,
|
||||
bottom: 0,
|
||||
marginLeft: -8
|
||||
marginLeft: -16
|
||||
},
|
||||
bigSeekerCircle: {
|
||||
borderRadius: 24,
|
||||
position: 'relative',
|
||||
top: 8,
|
||||
left: 8,
|
||||
left: 15,
|
||||
height: 24,
|
||||
width: 24,
|
||||
backgroundColor: '#40c0a9'
|
||||
|
|
Loading…
Reference in a new issue