Merge pull request #190 from lbryio/media-play-button

Use Play as button text on the file page for audio/video
This commit is contained in:
Akinwale Ariwodola 2018-06-28 09:15:55 +01:00 committed by GitHub
commit a133f7d82d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 99 additions and 88 deletions

View file

@ -7,14 +7,14 @@ class FileDownloadButton extends React.PureComponent {
const { costInfo, fetchCostInfo, uri } = this.props;
if (costInfo === undefined) {
fetchCostInfo(uri);
}
}
}
componentWillReceiveProps(nextProps) {
//this.checkAvailability(nextProps.uri);
this.restartDownload(nextProps);
}
restartDownload(props) {
const { downloading, fileInfo, uri, restartDownload } = props;
@ -28,7 +28,7 @@ class FileDownloadButton extends React.PureComponent {
restartDownload(uri, fileInfo.outpoint);
}
}
render() {
const {
fileInfo,
@ -36,6 +36,8 @@ class FileDownloadButton extends React.PureComponent {
uri,
purchaseUri,
costInfo,
isPlayable,
onPlay,
loading,
doPause,
style,
@ -46,7 +48,7 @@ class FileDownloadButton extends React.PureComponent {
const progress =
fileInfo && fileInfo.written_bytes ? fileInfo.written_bytes / fileInfo.total_bytes * 100 : 0,
label = fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...';
return (
<View style={[style, fileDownloadButtonStyle.container]}>
<View style={{ width: `${progress}%`, backgroundColor: '#ff0000', position: 'absolute', left: 0, top: 0 }}></View>
@ -67,8 +69,11 @@ class FileDownloadButton extends React.PureComponent {
NativeModules.Mixpanel.track('Purchase Uri', { Uri: uri });
}
purchaseUri(uri);
if (isPlayable && onPlay) {
this.props.onPlay();
}
}}>
<Text style={fileDownloadButtonStyle.text}>Download</Text>
<Text style={fileDownloadButtonStyle.text}>{isPlayable ? 'Play' : 'Download'}</Text>
</TouchableOpacity>
);
} else if (fileInfo && fileInfo.download_path) {

View file

@ -11,21 +11,21 @@ import {
import Video from 'react-native-video';
import Icon from 'react-native-vector-icons/FontAwesome';
import FileItemMedia from '../fileItemMedia';
import mediaPlayerStyle from '../../styles/mediaPlayer';
import mediaPlayerStyle from '../../styles/mediaPlayer';
class MediaPlayer extends React.PureComponent {
static ControlsTimeout = 3000;
seekResponder = null;
seekerWidth = 0;
trackingOffset = 0;
tracking = null;
video = null;
constructor(props) {
super(props);
this.state = {
@ -35,7 +35,7 @@ class MediaPlayer extends React.PureComponent {
resizeMode: 'stretch',
duration: 0.0,
currentTime: 0.0,
paused: true,
paused: !props.autoPlay,
fullscreenMode: false,
areControlsVisible: true,
controlsTimeout: -1,
@ -44,51 +44,51 @@ class MediaPlayer extends React.PureComponent {
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();
this.props.onMediaLoaded();
}
}
onProgress = (data) => {
this.setState({ currentTime: data.currentTime });
if (!this.state.seeking) {
this.setSeekerPosition(this.calculateSeekerPosition());
}
if (this.state.firstPlay) {
if (NativeModules.Mixpanel) {
const { uri } = this.props;
@ -98,13 +98,13 @@ class MediaPlayer extends React.PureComponent {
this.hidePlayerControls();
}
}
clearControlsTimeout = () => {
if (this.state.controlsTimeout > -1) {
clearTimeout(this.state.controlsTimeout)
}
}
showPlayerControls = () => {
this.clearControlsTimeout();
if (!this.state.areControlsVisible) {
@ -112,7 +112,7 @@ class MediaPlayer extends React.PureComponent {
}
this.hidePlayerControls();
}
hidePlayerControls() {
const player = this;
let timeout = setTimeout(() => {
@ -120,12 +120,12 @@ class MediaPlayer extends React.PureComponent {
}, MediaPlayer.ControlsTimeout);
player.setState({ controlsTimeout: timeout });
}
togglePlay = () => {
this.showPlayerControls();
this.setState({ paused: !this.state.paused });
}
toggleFullscreenMode = () => {
this.showPlayerControls();
const { onFullscreenToggled } = this.props;
@ -136,12 +136,12 @@ class MediaPlayer extends React.PureComponent {
}
});
}
onEnd = () => {
this.setState({ paused: true });
this.video.seek(0);
}
setSeekerPosition(position = 0) {
position = this.checkSeekerPosition(position);
this.setState({ seekerPosition: position });
@ -149,7 +149,7 @@ class MediaPlayer extends React.PureComponent {
this.setState({ seekerOffset: position });
}
}
checkSeekerPosition(val = 0) {
const offset = this.getTrackingOffset();
if (val < offset) {
@ -157,10 +157,10 @@ class MediaPlayer extends React.PureComponent {
} else if (val >= (offset + this.seekerWidth)) {
return offset + this.seekerWidth;
}
return val;
}
seekTo(time = 0) {
if (time > this.state.duration) {
return;
@ -168,22 +168,22 @@ class MediaPlayer extends React.PureComponent {
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) {
@ -197,37 +197,37 @@ 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();
}
getCurrentTimePercentage() {
if (this.state.currentTime > 0) {
return parseFloat(this.state.currentTime) / parseFloat(this.state.duration);
}
return 0;
};
componentWillMount() {
this.initSeeker();
}
componentDidMount() {
}
componentWillUnmount() {
this.clearControlsTimeout();
this.setState({ paused: true, fullscreenMode: false });
@ -236,7 +236,7 @@ class MediaPlayer extends React.PureComponent {
onFullscreenToggled(false);
}
}
renderPlayerControls() {
if (this.state.areControlsVisible) {
return (
@ -246,18 +246,18 @@ class MediaPlayer extends React.PureComponent {
{this.state.paused && <Icon name="play" size={32} color="#ffffff" />}
{!this.state.paused && <Icon name="pause" size={32} color="#ffffff" />}
</TouchableOpacity>
<TouchableOpacity style={mediaPlayerStyle.toggleFullscreenButton} onPress={this.toggleFullscreenMode}>
{this.state.fullscreenMode && <Icon name="compress" size={16} color="#ffffff" />}
{!this.state.fullscreenMode && <Icon name="expand" size={16} color="#ffffff" />}
</TouchableOpacity>
<Text style={mediaPlayerStyle.elapsedDuration}>{this.formatTime(this.state.currentTime)}</Text>
<Text style={mediaPlayerStyle.totalDuration}>{this.formatTime(this.state.duration)}</Text>
</View>
);
}
return null;
}
@ -273,10 +273,10 @@ class MediaPlayer extends React.PureComponent {
styles.push(style);
}
}
const trackingStyle = [mediaPlayerStyle.trackingControls, this.state.fullscreenMode ?
mediaPlayerStyle.fullscreenTrackingControls : mediaPlayerStyle.containedTrackingControls];
return (
<View style={styles}>
<Video source={{ uri: 'file:///' + fileInfo.download_path }}
@ -291,11 +291,11 @@ class MediaPlayer extends React.PureComponent {
onProgress={this.onProgress}
onEnd={this.onEnd}
/>
<TouchableOpacity style={mediaPlayerStyle.playerControls} onPress={this.showPlayerControls}>
{this.renderPlayerControls()}
</TouchableOpacity>
{(!this.state.fullscreenMode || (this.state.fullscreenMode && this.state.areControlsVisible)) &&
<View style={trackingStyle} onLayout={(evt) => {
this.trackingOffset = evt.nativeEvent.layout.x;
@ -306,7 +306,7 @@ class MediaPlayer extends React.PureComponent {
<View style={[mediaPlayerStyle.innerProgressRemaining, { flex: flexRemaining }]} />
</View>
</View>}
{this.state.areControlsVisible &&
<View style={[mediaPlayerStyle.seekerHandle, { left: this.state.seekerPosition }]} { ...this.seekResponder.panHandlers }>
<View style={this.state.seeking ? mediaPlayerStyle.bigSeekerCircle : mediaPlayerStyle.seekerCircle} />

View file

@ -30,25 +30,26 @@ class FilePage extends React.PureComponent {
static navigationOptions = {
title: ''
};
constructor(props) {
super(props);
this.state = {
mediaLoaded: false,
autoplayMedia: false,
fullscreenMode: false,
showImageViewer: false,
showWebView: false,
imageUrls: null
};
}
componentDidMount() {
StatusBar.setHidden(false);
const { isResolvingUri, resolveUri, navigation } = this.props;
const { uri } = navigation.state.params;
if (!isResolvingUri) resolveUri(uri);
this.fetchFileInfo(this.props);
this.fetchCostInfo(this.props);
@ -78,7 +79,7 @@ class FilePage extends React.PureComponent {
props.fetchCostInfo(props.navigation.state.params.uri);
}
}
handleFullscreenToggle = (mode) => {
this.setState({ fullscreenMode: mode });
StatusBar.setHidden(mode);
@ -91,10 +92,10 @@ class FilePage extends React.PureComponent {
}
}
}
onDeletePressed = () => {
const { deleteFile, fileInfo } = this.props;
Alert.alert(
'Delete file',
'Are you sure you want to remove this file from your device?',
@ -105,10 +106,10 @@ class FilePage extends React.PureComponent {
{ cancelable: true }
);
}
onStopDownloadPressed = () => {
const { deleteFile, stopDownload, fileInfo, navigation } = this.props;
Alert.alert(
'Stop download',
'Are you sure you want to stop downloading this file?',
@ -119,21 +120,21 @@ class FilePage extends React.PureComponent {
{ cancelable: true }
);
}
componentWillUnmount() {
StatusBar.setHidden(false);
if (NativeModules.ScreenOrientation) {
NativeModules.ScreenOrientation.unlockOrientation();
}
}
localUriForFileInfo = (fileInfo) => {
if (!fileInfo) {
return null;
}
return 'file:///' + fileInfo.download_path;
}
linkify = (text) => {
let linkifiedContent = [];
let lines = text.split(/\n/g);
@ -142,7 +143,7 @@ class FilePage extends React.PureComponent {
let lineContent = tokens.length === 0 ? '' : tokens.map((token, j) => {
let hasSpace = j !== (tokens.length - 1);
let space = hasSpace ? ' ' : '';
if (token.match(/^(lbry|https?):\/\//g)) {
return (
<Link key={j}
@ -154,14 +155,14 @@ class FilePage extends React.PureComponent {
return token + space;
}
});
lineContent.push("\n");
return (<Text key={i}>{lineContent}</Text>);
});
return linkifiedContent;
}
render() {
const {
claim,
@ -175,12 +176,12 @@ class FilePage extends React.PureComponent {
navigation
} = this.props;
const { uri } = navigation.state.params;
let innerContent = null;
if ((isResolvingUri && !claim) || !claim) {
innerContent = (
<View style={filePageStyle.container}>
{isResolvingUri &&
{isResolvingUri &&
<View style={filePageStyle.busyContainer}>
<ActivityIndicator size="large" color={Colors.LbryGreen} />
<Text style={filePageStyle.infoText}>Loading decentralized data...</Text>
@ -198,7 +199,7 @@ class FilePage extends React.PureComponent {
<ChannelPage uri={uri} navigation={navigation} />
);
} else if (claim) {
const completed = fileInfo && fileInfo.completed;
const completed = fileInfo && fileInfo.completed;
const title = metadata.title;
const isRewardContent = rewardedContentClaimIds.includes(claim.claim_id);
const description = metadata.description ? metadata.description : null;
@ -209,18 +210,18 @@ class FilePage extends React.PureComponent {
(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];
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 canOpen = (mediaType === 'image' || mediaType === 'text') && completed;
const isWebViewable = mediaType === 'text';
const localFileUri = this.localUriForFileInfo(fileInfo);
const openFile = () => {
if (mediaType === 'image') {
// use image viewer
@ -243,28 +244,33 @@ class FilePage extends React.PureComponent {
<View style={filePageStyle.pageContainer}>
{this.state.showWebView && isWebViewable && <WebView source={{ uri: localFileUri }}
style={filePageStyle.viewer} />}
{this.state.showImageViewer && <ImageViewer style={StyleSheet.flatten(filePageStyle.viewer)}
imageUrls={this.state.imageUrls}
renderIndicator={() => null} />}
{!this.state.showWebView && (
<View style={filePageStyle.innerPageContainer}>
<View style={filePageStyle.mediaContainer}>
<View style={filePageStyle.mediaContainer}>
{(canOpen || (!fileInfo || (isPlayable && !canLoadMedia))) &&
<FileItemMedia style={filePageStyle.thumbnail} title={title} thumbnail={metadata.thumbnail} />}
{(canOpen || (isPlayable && !this.state.mediaLoaded)) && <ActivityIndicator size="large" color={Colors.LbryGreen} style={filePageStyle.loading} />}
{((isPlayable && !completed && !canLoadMedia) || !completed || canOpen) &&
<FileDownloadButton uri={uri} style={filePageStyle.downloadButton} openFile={openFile} />}
<FileDownloadButton uri={uri}
style={filePageStyle.downloadButton}
openFile={openFile}
isPlayable={isPlayable}
onPlay={() => this.setState({ autoPlayMedia: true })} />}
{!fileInfo && <FilePrice uri={uri} style={filePageStyle.filePriceContainer} textStyle={filePageStyle.filePriceText} />}
</View>
{canLoadMedia && <View style={playerBgStyle} />}
{canLoadMedia && <MediaPlayer fileInfo={fileInfo}
uri={uri}
style={playerStyle}
onFullscreenToggled={this.handleFullscreenToggle}
onMediaLoaded={() => { this.setState({ mediaLoaded: true }); }}/>}
uri={uri}
style={playerStyle}
autoPlay={this.state.autoPlayMedia}
onFullscreenToggled={this.handleFullscreenToggle}
onMediaLoaded={() => { this.setState({ mediaLoaded: true }); }}/>}
{ showActions &&
<View style={filePageStyle.actions}>
{completed && <Button color="red" title="Delete" onPress={this.onDeletePressed} />}
@ -290,7 +296,7 @@ class FilePage extends React.PureComponent {
</View>
);
}
return innerContent;
}
}