Merge pull request #33 from lbryio/media-player
implemented rudimentary media seeker. Still needs some tweaking.
This commit is contained in:
commit
c6e0c6b39a
24 changed files with 2578 additions and 626 deletions
|
@ -20,7 +20,8 @@ const discoverStack = StackNavigator({
|
||||||
File: {
|
File: {
|
||||||
screen: FilePage,
|
screen: FilePage,
|
||||||
navigationOptions: {
|
navigationOptions: {
|
||||||
header: null
|
header: null,
|
||||||
|
drawerLockMode: 'locked-closed'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
|
|
|
@ -58,7 +58,7 @@ class FileItem extends React.PureComponent {
|
||||||
this.props.navigation.navigate('File', { uri: uri });
|
this.props.navigation.navigate('File', { uri: uri });
|
||||||
}
|
}
|
||||||
}>
|
}>
|
||||||
<FileItemMedia title={title} thumbnail={thumbnail} />
|
<FileItemMedia title={title} thumbnail={thumbnail} resizeMode="cover" />
|
||||||
<FilePrice uri={uri} style={discoverStyle.filePriceContainer} textStyle={discoverStyle.filePriceText} />
|
<FilePrice uri={uri} style={discoverStyle.filePriceContainer} textStyle={discoverStyle.filePriceText} />
|
||||||
<Text style={discoverStyle.fileItemName}>{title}</Text>
|
<Text style={discoverStyle.fileItemName}>{title}</Text>
|
||||||
{channelName &&
|
{channelName &&
|
||||||
|
|
|
@ -28,7 +28,7 @@ class FileItemMedia extends React.PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let style = this.props.style;
|
let style = this.props.style;
|
||||||
const { title, thumbnail } = this.props;
|
const { title, thumbnail, resizeMode } = this.props;
|
||||||
const atStyle = this.state.autoThumbStyle;
|
const atStyle = this.state.autoThumbStyle;
|
||||||
|
|
||||||
if (thumbnail && ((typeof thumbnail) === 'string')) {
|
if (thumbnail && ((typeof thumbnail) === 'string')) {
|
||||||
|
@ -37,7 +37,7 @@ class FileItemMedia extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Image source={{uri: thumbnail}} resizeMode="cover" style={style} />
|
<Image source={{uri: thumbnail}} resizeMode={resizeMode ? resizeMode : "cover"} style={style} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
7
app/src/component/mediaPlayer/index.js
Normal file
7
app/src/component/mediaPlayer/index.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import MediaPlayer from './view';
|
||||||
|
|
||||||
|
const select = state => ({});
|
||||||
|
const perform = dispatch => ({});
|
||||||
|
|
||||||
|
export default connect(select, perform)(MediaPlayer);
|
273
app/src/component/mediaPlayer/view.js
Normal file
273
app/src/component/mediaPlayer/view.js
Normal file
|
@ -0,0 +1,273 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Lbry } from 'lbry-redux';
|
||||||
|
import { PanResponder, Text, View, ScrollView, TouchableOpacity } from 'react-native';
|
||||||
|
import Video from 'react-native-video';
|
||||||
|
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||||
|
import FileItemMedia from '../fileItemMedia';
|
||||||
|
import mediaPlayerStyle from '../../styles/mediaPlayer';
|
||||||
|
|
||||||
|
class MediaPlayer extends React.PureComponent {
|
||||||
|
static ControlsTimeout = 3000;
|
||||||
|
|
||||||
|
seekResponder = null;
|
||||||
|
|
||||||
|
seekerWidth = 0;
|
||||||
|
|
||||||
|
video = null;
|
||||||
|
|
||||||
|
state = {
|
||||||
|
rate: 1,
|
||||||
|
volume: 1,
|
||||||
|
muted: false,
|
||||||
|
resizeMode: 'stretch',
|
||||||
|
duration: 0.0,
|
||||||
|
currentTime: 0.0,
|
||||||
|
paused: false,
|
||||||
|
fullscreenMode: false,
|
||||||
|
areControlsVisible: true,
|
||||||
|
controlsTimeout: -1,
|
||||||
|
seekerOffset: 0,
|
||||||
|
seekerPosition: 0,
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onProgress = (data) => {
|
||||||
|
this.setState({ currentTime: data.currentTime });
|
||||||
|
|
||||||
|
if (!this.state.seeking) {
|
||||||
|
this.setSeekerPosition(this.calculateSeekerPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.firstPlay) {
|
||||||
|
this.setState({ firstPlay: false });
|
||||||
|
this.hidePlayerControls();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearControlsTimeout = () => {
|
||||||
|
if (this.state.controlsTimeout > -1) {
|
||||||
|
clearTimeout(this.state.controlsTimeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showPlayerControls = () => {
|
||||||
|
this.clearControlsTimeout();
|
||||||
|
if (!this.state.areControlsVisible) {
|
||||||
|
this.setState({ areControlsVisible: true });
|
||||||
|
}
|
||||||
|
this.hidePlayerControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
hidePlayerControls() {
|
||||||
|
const player = this;
|
||||||
|
let timeout = setTimeout(() => {
|
||||||
|
player.setState({ areControlsVisible: false });
|
||||||
|
}, MediaPlayer.ControlsTimeout);
|
||||||
|
player.setState({ controlsTimeout: timeout });
|
||||||
|
}
|
||||||
|
|
||||||
|
togglePlay = () => {
|
||||||
|
this.showPlayerControls();
|
||||||
|
this.setState({ paused: !this.state.paused });
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleFullscreenMode = () => {
|
||||||
|
this.showPlayerControls();
|
||||||
|
const { onFullscreenToggled } = this.props;
|
||||||
|
this.setState({ fullscreenMode: !this.state.fullscreenMode }, () => {
|
||||||
|
this.setState({ resizeMode: this.state.fullscreenMode ? 'contain' : 'stretch' });
|
||||||
|
if (onFullscreenToggled) {
|
||||||
|
onFullscreenToggled(this.state.fullscreenMode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onEnd = () => {
|
||||||
|
this.setState({ paused: true });
|
||||||
|
this.video.seek(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSeekerPosition(position = 0) {
|
||||||
|
position = this.checkSeekerPosition(position);
|
||||||
|
this.setState({ seekerPosition: position });
|
||||||
|
if (!this.state.seeking) {
|
||||||
|
this.setState({ seekerOffset: position });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSeekerPosition(val = 0) {
|
||||||
|
if (val < 0) {
|
||||||
|
val = 0;
|
||||||
|
} else if (val >= this.seekerWidth) {
|
||||||
|
return this.seekerWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
seekTo = (time = 0) => {
|
||||||
|
if (time > this.state.duration) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
this.setState({ paused: true });
|
||||||
|
this.onEnd();
|
||||||
|
} else {
|
||||||
|
this.seekTo(time);
|
||||||
|
this.setState({ seeking: false });
|
||||||
|
}
|
||||||
|
this.hidePlayerControls();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentTimeForSeekerPosition() {
|
||||||
|
return this.state.duration * (this.state.seekerPosition / this.seekerWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateSeekerPosition() {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.setState({ paused: true, fullscreenMode: false });
|
||||||
|
const { onFullscreenToggled } = this.props;
|
||||||
|
if (onFullscreenToggled) {
|
||||||
|
onFullscreenToggled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPlayerControls() {
|
||||||
|
if (this.state.areControlsVisible) {
|
||||||
|
return (
|
||||||
|
<View style={mediaPlayerStyle.playerControlsContainer}>
|
||||||
|
<TouchableOpacity style={mediaPlayerStyle.playPauseButton}
|
||||||
|
onPress={this.togglePlay}>
|
||||||
|
{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;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { fileInfo, title, thumbnail, style, fullScreenStyle } = this.props;
|
||||||
|
const flexCompleted = this.getCurrentTimePercentage() * 100;
|
||||||
|
const flexRemaining = (1 - this.getCurrentTimePercentage()) * 100;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[style, mediaPlayerStyle.container]}>
|
||||||
|
<Video source={{ uri: 'file:///' + fileInfo.download_path }}
|
||||||
|
ref={(ref: Video) => { this.video = ref }}
|
||||||
|
resizeMode={this.state.resizeMode}
|
||||||
|
playInBackground={true}
|
||||||
|
style={mediaPlayerStyle.player}
|
||||||
|
rate={this.state.rate}
|
||||||
|
volume={this.state.volume}
|
||||||
|
paused={this.state.paused}
|
||||||
|
onLoad={this.onLoad}
|
||||||
|
onProgress={this.onProgress}
|
||||||
|
onEnd={this.onEnd}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TouchableOpacity style={mediaPlayerStyle.playerControls} onPress={this.showPlayerControls}>
|
||||||
|
{this.renderPlayerControls()}
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<View style={mediaPlayerStyle.trackingControls}>
|
||||||
|
<View style={mediaPlayerStyle.progress} onLayout={(evt) => this.seekerWidth = evt.nativeEvent.layout.width}>
|
||||||
|
<View style={[mediaPlayerStyle.innerProgressCompleted, { flex: flexCompleted }]} />
|
||||||
|
<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>}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MediaPlayer;
|
|
@ -1,20 +1,16 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Lbry } from 'lbry-redux';
|
import { Lbry } from 'lbry-redux';
|
||||||
import { Text, View, ScrollView, TouchableOpacity } from 'react-native';
|
import { Text, View, ScrollView, StatusBar, TouchableOpacity, NativeModules } from 'react-native';
|
||||||
import Video from 'react-native-video';
|
|
||||||
import filePageStyle from '../../styles/filePage';
|
|
||||||
import FileItemMedia from '../../component/fileItemMedia';
|
import FileItemMedia from '../../component/fileItemMedia';
|
||||||
import FileDownloadButton from '../../component/fileDownloadButton';
|
import FileDownloadButton from '../../component/fileDownloadButton';
|
||||||
|
import MediaPlayer from '../../component/mediaPlayer';
|
||||||
|
import Video from 'react-native-video';
|
||||||
|
import filePageStyle from '../../styles/filePage';
|
||||||
|
|
||||||
class FilePage extends React.PureComponent {
|
class FilePage extends React.PureComponent {
|
||||||
state = {
|
state = {
|
||||||
rate: 1,
|
mediaLoaded: false,
|
||||||
volume: 1,
|
fullscreenMode: false
|
||||||
muted: false,
|
|
||||||
resizeMode: 'contain',
|
|
||||||
duration: 0.0,
|
|
||||||
currentTime: 0.0,
|
|
||||||
paused: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static navigationOptions = {
|
static navigationOptions = {
|
||||||
|
@ -22,6 +18,7 @@ class FilePage extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
StatusBar.setHidden(false);
|
||||||
this.fetchFileInfo(this.props);
|
this.fetchFileInfo(this.props);
|
||||||
this.fetchCostInfo(this.props);
|
this.fetchCostInfo(this.props);
|
||||||
}
|
}
|
||||||
|
@ -42,6 +39,26 @@ class FilePage extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleFullscreenToggle = (mode) => {
|
||||||
|
this.setState({ fullscreenMode: mode });
|
||||||
|
StatusBar.setHidden(mode);
|
||||||
|
if (NativeModules.ScreenOrientation) {
|
||||||
|
if (mode) {
|
||||||
|
// fullscreen, so change orientation to landscape mode
|
||||||
|
NativeModules.ScreenOrientation.lockOrientationLandscape();
|
||||||
|
} else {
|
||||||
|
NativeModules.ScreenOrientation.unlockOrientation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
StatusBar.setHidden(false);
|
||||||
|
if (NativeModules.ScreenOrientation) {
|
||||||
|
NativeModules.ScreenOrientation.unlockOrientation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
claim,
|
claim,
|
||||||
|
@ -73,25 +90,14 @@ class FilePage extends React.PureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={filePageStyle.pageContainer}>
|
<View style={filePageStyle.pageContainer}>
|
||||||
<View style={filePageStyle.mediaContainer}>
|
<View style={this.state.fullscreenMode ? filePageStyle.fullscreenMedia : filePageStyle.mediaContainer}>
|
||||||
{(!fileInfo || !isPlayable) && <FileItemMedia style={filePageStyle.thumbnail} title={title} thumbnail={metadata.thumbnail} />}
|
{(!fileInfo || (isPlayable && !this.state.mediaLoaded)) &&
|
||||||
|
<FileItemMedia style={filePageStyle.thumbnail} title={title} thumbnail={metadata.thumbnail} />}
|
||||||
{!completed && <FileDownloadButton uri={navigation.state.params.uri} style={filePageStyle.downloadButton} />}
|
{!completed && <FileDownloadButton uri={navigation.state.params.uri} style={filePageStyle.downloadButton} />}
|
||||||
|
{fileInfo && isPlayable && <MediaPlayer fileInfo={fileInfo}
|
||||||
{fileInfo && isPlayable &&
|
|
||||||
<TouchableOpacity
|
|
||||||
style={filePageStyle.player}
|
style={filePageStyle.player}
|
||||||
onPress={() => this.setState({ paused: !this.state.paused })}>
|
onFullscreenToggled={this.handleFullscreenToggle}
|
||||||
<Video source={{ uri: 'file:///' + fileInfo.download_path }}
|
onMediaLoaded={() => { this.setState({ mediaLoaded: true }); }}/>}
|
||||||
resizeMode="cover"
|
|
||||||
playInBackground={true}
|
|
||||||
style={filePageStyle.player}
|
|
||||||
rate={this.state.rate}
|
|
||||||
volume={this.state.volume}
|
|
||||||
paused={this.state.paused}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
}
|
|
||||||
|
|
||||||
</View>
|
</View>
|
||||||
<ScrollView style={filePageStyle.scrollContainer}>
|
<ScrollView style={filePageStyle.scrollContainer}>
|
||||||
<Text style={filePageStyle.title}>{title}</Text>
|
<Text style={filePageStyle.title}>{title}</Text>
|
||||||
|
|
0
app/src/page/settings/index.js
Normal file
0
app/src/page/settings/index.js
Normal file
0
app/src/page/settings/view.js
Normal file
0
app/src/page/settings/view.js
Normal file
|
@ -9,11 +9,13 @@ const discoverStyle = StyleSheet.create({
|
||||||
flex: 1
|
flex: 1
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
|
fontFamily: 'Metropolis-Regular',
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
margin: 10,
|
margin: 10,
|
||||||
},
|
},
|
||||||
categoryName: {
|
categoryName: {
|
||||||
|
fontFamily: 'Metropolis-Regular',
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
marginLeft: 24,
|
marginLeft: 24,
|
||||||
marginTop: 16,
|
marginTop: 16,
|
||||||
|
@ -26,15 +28,15 @@ const discoverStyle = StyleSheet.create({
|
||||||
marginBottom: 48
|
marginBottom: 48
|
||||||
},
|
},
|
||||||
fileItemName: {
|
fileItemName: {
|
||||||
|
fontFamily: 'Metropolis-Bold',
|
||||||
marginTop: 8,
|
marginTop: 8,
|
||||||
fontSize: 16,
|
fontSize: 16
|
||||||
fontWeight: 'bold'
|
|
||||||
},
|
},
|
||||||
channelName: {
|
channelName: {
|
||||||
|
fontFamily: 'Metropolis-SemiBold',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
marginTop: 4,
|
marginTop: 4,
|
||||||
color: '#c0c0c0',
|
color: '#c0c0c0'
|
||||||
fontWeight: 'bold'
|
|
||||||
},
|
},
|
||||||
filePriceContainer: {
|
filePriceContainer: {
|
||||||
backgroundColor: '#61fcd8',
|
backgroundColor: '#61fcd8',
|
||||||
|
@ -47,10 +49,10 @@ const discoverStyle = StyleSheet.create({
|
||||||
borderRadius: 4
|
borderRadius: 4
|
||||||
},
|
},
|
||||||
filePriceText: {
|
filePriceText: {
|
||||||
|
fontFamily: 'Metropolis-Bold',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
color: '#0c604b',
|
color: '#0c604b'
|
||||||
fontWeight: 'bold'
|
|
||||||
},
|
},
|
||||||
drawerHamburger: {
|
drawerHamburger: {
|
||||||
marginLeft: 8
|
marginLeft: 8
|
||||||
|
|
|
@ -9,8 +9,9 @@ const fileDownloadButtonStyle = StyleSheet.create({
|
||||||
backgroundColor: '#40c0a9',
|
backgroundColor: '#40c0a9',
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
|
fontFamily: 'Metropolis-Medium',
|
||||||
color: '#ffffff',
|
color: '#ffffff',
|
||||||
fontSize: 13,
|
fontSize: 14,
|
||||||
textAlign: 'center'
|
textAlign: 'center'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,11 +5,13 @@ const width = screenDimension.width - 48; // screen width minus combined left an
|
||||||
|
|
||||||
const fileItemMediaStyle = StyleSheet.create({
|
const fileItemMediaStyle = StyleSheet.create({
|
||||||
autothumb: {
|
autothumb: {
|
||||||
width: width,
|
flex: 1,
|
||||||
height: 180,
|
width: '100%',
|
||||||
|
height: 200,
|
||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
},
|
},
|
||||||
autothumbText: {
|
autothumbText: {
|
||||||
|
fontFamily: 'Metropolis-SemiBold',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
color: '#ffffff',
|
color: '#ffffff',
|
||||||
fontSize: 40
|
fontSize: 40
|
||||||
|
@ -48,8 +50,9 @@ const fileItemMediaStyle = StyleSheet.create({
|
||||||
backgroundColor: '#ffa726'
|
backgroundColor: '#ffa726'
|
||||||
},
|
},
|
||||||
thumbnail: {
|
thumbnail: {
|
||||||
width: width,
|
flex: 1,
|
||||||
height: 180,
|
width: '100%',
|
||||||
|
height: 200,
|
||||||
shadowColor: 'transparent'
|
shadowColor: 'transparent'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,10 +12,12 @@ const filePageStyle = StyleSheet.create({
|
||||||
flex: 1
|
flex: 1
|
||||||
},
|
},
|
||||||
mediaContainer: {
|
mediaContainer: {
|
||||||
backgroundColor: '#000000',
|
alignItems: 'center',
|
||||||
alignItems: 'center'
|
width: screenWidth,
|
||||||
|
height: 220,
|
||||||
},
|
},
|
||||||
emptyClaimText: {
|
emptyClaimText: {
|
||||||
|
fontFamily: 'Metropolis-Regular',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
marginLeft: 16,
|
marginLeft: 16,
|
||||||
|
@ -25,22 +27,23 @@ const filePageStyle = StyleSheet.create({
|
||||||
flex: 1
|
flex: 1
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
|
fontFamily: 'Metropolis-Bold',
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: 'bold',
|
marginTop: 12,
|
||||||
marginTop: 20,
|
|
||||||
marginLeft: 20,
|
marginLeft: 20,
|
||||||
marginRight: 20,
|
marginRight: 20,
|
||||||
marginBottom: 12
|
marginBottom: 12
|
||||||
},
|
},
|
||||||
channelName: {
|
channelName: {
|
||||||
|
fontFamily: 'Metropolis-SemiBold',
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: 'bold',
|
|
||||||
marginLeft: 20,
|
marginLeft: 20,
|
||||||
marginRight: 20,
|
marginRight: 20,
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
color: '#9b9b9b'
|
color: '#9b9b9b'
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
|
fontFamily: 'Metropolis-Regular',
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
marginLeft: 20,
|
marginLeft: 20,
|
||||||
marginRight: 20,
|
marginRight: 20,
|
||||||
|
@ -56,8 +59,20 @@ const filePageStyle = StyleSheet.create({
|
||||||
top: '50%'
|
top: '50%'
|
||||||
},
|
},
|
||||||
player: {
|
player: {
|
||||||
width: screenWidth,
|
flex: 1,
|
||||||
height: 200
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
marginBottom: 14
|
||||||
|
},
|
||||||
|
fullscreenMedia: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#000000',
|
||||||
|
zIndex: 100
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
104
app/src/styles/mediaPlayer.js
Normal file
104
app/src/styles/mediaPlayer.js
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import { StyleSheet, Dimensions } from 'react-native';
|
||||||
|
|
||||||
|
const screenDimension = Dimensions.get('window');
|
||||||
|
const screenWidth = screenDimension.width;
|
||||||
|
|
||||||
|
const mediaPlayerStyle = StyleSheet.create({
|
||||||
|
player: {
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
},
|
||||||
|
progress: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
innerProgressCompleted: {
|
||||||
|
height: 4,
|
||||||
|
backgroundColor: '#40c0a9',
|
||||||
|
},
|
||||||
|
innerProgressRemaining: {
|
||||||
|
height: 4,
|
||||||
|
backgroundColor: '#2c2c2c',
|
||||||
|
},
|
||||||
|
trackingControls: {
|
||||||
|
height: 3,
|
||||||
|
width: '100%',
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
},
|
||||||
|
playerControls: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
playerControlsContainer: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
playPauseButton: {
|
||||||
|
position: 'absolute',
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
toggleFullscreenButton: {
|
||||||
|
position: 'absolute',
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
right: 0,
|
||||||
|
bottom: 14,
|
||||||
|
},
|
||||||
|
elapsedDuration: {
|
||||||
|
fontFamily: 'Metropolis-Regular',
|
||||||
|
position: 'absolute',
|
||||||
|
left: 8,
|
||||||
|
bottom: 24,
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#ffffff'
|
||||||
|
},
|
||||||
|
totalDuration: {
|
||||||
|
fontFamily: 'Metropolis-Regular',
|
||||||
|
position: 'absolute',
|
||||||
|
right: 40,
|
||||||
|
bottom: 24,
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#ffffff'
|
||||||
|
},
|
||||||
|
seekerCircle: {
|
||||||
|
borderRadius: 12,
|
||||||
|
position: 'relative',
|
||||||
|
top: 8,
|
||||||
|
left: 8,
|
||||||
|
height: 12,
|
||||||
|
width: 12,
|
||||||
|
backgroundColor: '#40c0a9'
|
||||||
|
},
|
||||||
|
seekerHandle: {
|
||||||
|
position: 'absolute',
|
||||||
|
height: 28,
|
||||||
|
width: 28,
|
||||||
|
bottom: -12,
|
||||||
|
marginLeft: -8
|
||||||
|
},
|
||||||
|
bigSeekerCircle: {
|
||||||
|
borderRadius: 24,
|
||||||
|
position: 'relative',
|
||||||
|
top: 2,
|
||||||
|
left: 8,
|
||||||
|
height: 24,
|
||||||
|
width: 24,
|
||||||
|
backgroundColor: '#40c0a9'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default mediaPlayerStyle;
|
|
@ -7,13 +7,14 @@ const splashStyle = StyleSheet.create({
|
||||||
backgroundColor: '#40b89a'
|
backgroundColor: '#40b89a'
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
|
fontFamily: 'Metropolis-Bold',
|
||||||
fontSize: 64,
|
fontSize: 64,
|
||||||
fontWeight: 'bold',
|
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
marginBottom: 48,
|
marginBottom: 48,
|
||||||
color: '#ffffff'
|
color: '#ffffff'
|
||||||
},
|
},
|
||||||
details: {
|
details: {
|
||||||
|
fontFamily: 'Metropolis-Regular',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
marginLeft: 16,
|
marginLeft: 16,
|
||||||
marginRight: 16,
|
marginRight: 16,
|
||||||
|
@ -21,7 +22,7 @@ const splashStyle = StyleSheet.create({
|
||||||
textAlign: 'center'
|
textAlign: 'center'
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
fontWeight: 'bold',
|
fontFamily: 'Metropolis-Bold',
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
color: '#ffffff',
|
color: '#ffffff',
|
||||||
marginLeft: 16,
|
marginLeft: 16,
|
||||||
|
|
BIN
src/main/assets/fonts/FontAwesome.ttf
Normal file
BIN
src/main/assets/fonts/FontAwesome.ttf
Normal file
Binary file not shown.
BIN
src/main/assets/fonts/Metropolis-Bold.ttf
Normal file
BIN
src/main/assets/fonts/Metropolis-Bold.ttf
Normal file
Binary file not shown.
BIN
src/main/assets/fonts/Metropolis-Medium.ttf
Normal file
BIN
src/main/assets/fonts/Metropolis-Medium.ttf
Normal file
Binary file not shown.
BIN
src/main/assets/fonts/Metropolis-Regular.ttf
Normal file
BIN
src/main/assets/fonts/Metropolis-Regular.ttf
Normal file
Binary file not shown.
BIN
src/main/assets/fonts/Metropolis-SemiBold.ttf
Normal file
BIN
src/main/assets/fonts/Metropolis-SemiBold.ttf
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load diff
|
@ -1 +1 @@
|
||||||
·ѓ*Цї<D0A6>1rЏ8Љ¤)Чт»EгЉ
|
WhèÑ'ï¢Ã,š|!ÚÊ.ÔÿòR
|
|
@ -18,7 +18,7 @@ import java.util.Random;
|
||||||
* Created by akinwale on 3/15/18.
|
* Created by akinwale on 3/15/18.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class LbryDownloadManagerModule extends ReactContextBaseJavaModule {
|
public class DownloadManagerModule extends ReactContextBaseJavaModule {
|
||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
private HashMap<Integer, NotificationCompat.Builder> builders = new HashMap<Integer, NotificationCompat.Builder>();
|
private HashMap<Integer, NotificationCompat.Builder> builders = new HashMap<Integer, NotificationCompat.Builder>();
|
||||||
|
@ -29,7 +29,7 @@ public class LbryDownloadManagerModule extends ReactContextBaseJavaModule {
|
||||||
|
|
||||||
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.##");
|
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.##");
|
||||||
|
|
||||||
public LbryDownloadManagerModule(ReactApplicationContext reactContext) {
|
public DownloadManagerModule(ReactApplicationContext reactContext) {
|
||||||
super(reactContext);
|
super(reactContext);
|
||||||
this.context = reactContext;
|
this.context = reactContext;
|
||||||
}
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package io.lbry.lbrynet.reactmodules;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.ActivityInfo;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by akinwale on 3/19/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class ScreenOrientationModule extends ReactContextBaseJavaModule {
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
public ScreenOrientationModule(ReactApplicationContext reactContext) {
|
||||||
|
super(reactContext);
|
||||||
|
this.context = reactContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "ScreenOrientation";
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void unlockOrientation() {
|
||||||
|
Activity activity = getCurrentActivity();
|
||||||
|
if (activity != null) {
|
||||||
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void lockOrientationLandscape() {
|
||||||
|
Activity activity = getCurrentActivity();
|
||||||
|
if (activity != null) {
|
||||||
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,8 @@ import com.facebook.react.bridge.NativeModule;
|
||||||
import com.facebook.react.bridge.ReactApplicationContext;
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
import com.facebook.react.uimanager.ViewManager;
|
import com.facebook.react.uimanager.ViewManager;
|
||||||
|
|
||||||
import io.lbry.lbrynet.reactmodules.LbryDownloadManagerModule;
|
import io.lbry.lbrynet.reactmodules.DownloadManagerModule;
|
||||||
|
import io.lbry.lbrynet.reactmodules.ScreenOrientationModule;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -21,7 +22,8 @@ public class LbryReactPackage implements ReactPackage {
|
||||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||||
List<NativeModule> modules = new ArrayList<>();
|
List<NativeModule> modules = new ArrayList<>();
|
||||||
|
|
||||||
modules.add(new LbryDownloadManagerModule(reactContext));
|
modules.add(new DownloadManagerModule(reactContext));
|
||||||
|
modules.add(new ScreenOrientationModule(reactContext));
|
||||||
|
|
||||||
return modules;
|
return modules;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue