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