Use Play as button text on the file page for audio/video #190

Merged
akinwale merged 1 commit from media-play-button into master 2018-06-28 10:15:56 +02:00
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,
neb-b commented 2018-06-25 17:51:20 +02:00 (Migrated from github.com)
Review

Should autoplay be a separate setting so the value is saved if they leave the page?

Should autoplay be a separate setting so the value is saved if they leave the page?
akinwale commented 2018-06-27 15:31:33 +02:00 (Migrated from github.com)
Review

I suppose that would be a different setting entirely. This state variable is related to the action that should be performed when the user clicks on the "Play" (download) button on the file page, instead of the usual "Download".

I suppose that would be a different setting entirely. This state variable is related to the action that should be performed when the user clicks on the "Play" (download) button on the file page, instead of the usual "Download".
neb-b commented 2018-06-27 17:40:58 +02:00 (Migrated from github.com)
Review

Ah I gotcha.

Ah I gotcha.
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;
}
}