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:
commit
a133f7d82d
3 changed files with 99 additions and 88 deletions
|
@ -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) {
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue