Skip to content

Instantly share code, notes, and snippets.

@samermurad
Created November 21, 2018 11:09
Show Gist options
  • Save samermurad/448187cb0194b939293ae91d2e42a180 to your computer and use it in GitHub Desktop.
Save samermurad/448187cb0194b939293ae91d2e42a180 to your computer and use it in GitHub Desktop.
A react-native Media Display screen that can display videos, images, audio files and pdfs, it is taken from a project that I wrote, so obviously some items are project related.
import React, { Component } from 'react'
import { View, TouchableOpacity, StyleSheet,Image,Animated, Platform, BackHandler, Button } from 'react-native'
import { connect } from 'react-redux'
import {Actions} from 'react-native-router-flux'
import { colors,fonts,images, styles as appStyles } from '../assets'
import { Screen, NAVIGATION_BAR_HEIGHT, STATUS_BAR_HEIGHT } from '../consts'
import { mediaType } from '../enums'
import Interactable from 'react-native-interactable'
import { CachedImage } from 'react-native-cached-image'
import { Player as AudioPlayer } from 'react-native-audio-toolkit'
import { LoaderView, AnimatedGradient } from '../views'
import Video from 'react-native-video'
import MediaControls, { PLAYER_STATES } from 'react-native-media-controls';
import PDF from 'react-native-pdf';
import {mediaCouldNotBeLoaded} from '../actions/mediaDisplayer.actions'
import AudioSession, {AudioCategories, AudioOptions, AudioModes} from 'react-native-audio-session'
type MediaDisplayerProps = {
media: Object,
};
class MediaDisplayer extends Component<MediaDisplayerProps> {
interactableViewHeight = Screen.height;
interactableViewWidth = Screen.width;
interactableViewYAnimationValue = new Animated.Value(0);
interactableViewSnapPoints = [
{y: -(this.interactableViewHeight) }, { y: 0 }, { y: this.interactableViewHeight } ,
];
opacityAnimation = this.interactableViewYAnimationValue.interpolate({
inputRange: this.interactableViewSnapPoints.map((i) => i.y),
outputRange: [0, 1, 0 ],
useNativeDriver: true
});
interactableView;
audioPlayer;
didPrepareAudio = false;
videoPlayer;
state = {
videoPlayer: {
isLoading: false,
isPaused: false,
currentTime: 0,
duration: 0,
isFullScreen: false,
playerState: PLAYER_STATES.PLAYING,
},
audioPlayer: {
isLoading: false,
isPaused: false,
duration: 0,
currentTime: 0,
playerState: PLAYER_STATES.PLAYING,
},
pdfViewer: {
page: 1,
scale: 1,
numberOfPages: 0,
horizontal: false,
width: Screen.width,
isLoading: true,
}
};
componentDidMount() {
let { media: { mediaType : type } } = this.props;
if ((type === mediaType.audio || type === mediaType.video) && Platform.OS === 'ios') {
const majorVersionIOS = parseInt(Platform.Version);
if (majorVersionIOS > 9)
AudioSession.setCategoryAndMode(AudioCategories.Playback,AudioModes.MoviePlayback, AudioOptions.DuckOthers);
else
AudioSession.setCategory(AudioCategories.Playback, AudioOptions.DuckOthers);
AudioSession.setActive(true)
}
if (Platform.OS === 'android')
BackHandler.addEventListener('hardwareBackPress', this.goBackListener);
}
componentWillUnmount() {
let { media: { mediaType : type } } = this.props;
if ((type === mediaType.audio || type === mediaType.video) && Platform.OS === 'ios') {
const majorVersionIOS = parseInt(Platform.Version);
if (majorVersionIOS > 9)
AudioSession.setCategoryAndMode(AudioCategories.Ambient,AudioModes.Default, AudioOptions.MixWithOthers);
else
AudioSession.setCategory(AudioCategories.Ambient, AudioOptions.MixWithOthers);
AudioSession.setActive(false)
}
if (Platform.OS === 'android')
BackHandler.removeEventListener('hardwareBackPress', this.goBackListener);
}
/** Image methods */
onError = (err) => this.props.mediaCouldNotBeLoaded();
/** Video methods */
videoOnProgress = data => {
const { videoPlayer } = this.state;
// Video Player will continue progress even if the video already ended
if (!videoPlayer.isLoading && videoPlayer.playerState !== PLAYER_STATES.ENDED) {
this.setState({
videoPlayer: {
...videoPlayer,
currentTime: data.currentTime,
}
});
}
};
videoOnSeek = seek => this.videoPlayer.seek(seek);
videoOnSeeking = currentTime => {
const { videoPlayer } = this.state;
this.setState({
videoPlayer: {
...videoPlayer,
currentTime
}
});
};
videoOnReplay = () => {
let {videoPlayer} = this.state;
this.setState({
videoPlayer: {
...videoPlayer,
playerState: PLAYER_STATES.PLAYING
}
});
this.videoPlayer.seek(0);
};
videoOnLoad = data => {
let { videoPlayer } = this.state;
this.setState({
videoPlayer:
{...videoPlayer,
duration: data.duration,
isLoading: false,
}
});
};
videoOnPaused = playerState => {
let {videoPlayer} = this.state;
this.setState({
videoPlayer: {
...videoPlayer,
playerState,
isPaused: !videoPlayer.isPaused
}
})
};
videoOnLoadStart = data => {
let { videoPlayer } = this.state;
this.setState({
videoPlayer:
{...videoPlayer,
isLoading: true,
}
});
};
videoOnEnd = () => {
let { videoPlayer } = this.state;
this.setState({
videoPlayer: {
...videoPlayer,
playerState: PLAYER_STATES.ENDED
}
});
};
videoOnError = (error) => this.props.mediaCouldNotBeLoaded();
/** Audio methods */
audioPrepare() {
return new Promise((resolve) => {
let {media} = this.props;
if (media.mediaType === mediaType.audio) {
this.audioPlayer = new AudioPlayer(media.url, { autoDestroy: false });
this.audioPlayer.on('error',this.audioOnError);
this.audioPlayer.on('ended',this.audioOnEnd);
this.audioPlayer.on('progress', this.audioOnProgress);
this.audioOnLoadStart(this.audioPlayer);
this.audioPlayer.play((err) => {
if (err) {
//TODO: - handle error
this.audioOnError(err);
} else {
this.audioOnLoad(this.audioPlayer);
}
})
}
})
}
audioOnLoadStart = data => {
let { audioPlayer } = this.state;
this.setState({
audioPlayer:
{...audioPlayer,
isLoading: true,
}
});
};
audioOnLoad = data => {
let { audioPlayer } = this.state;
this.setState({
audioPlayer:
{...audioPlayer,
duration: data.duration / 1000,
isLoading: false,
}
});
};
audioOnProgress = (data) => {
// let { currentTime } = data;
const { audioPlayer } = this.state;
// Video Player will continue progress even if the video already ended
if (!audioPlayer.isLoading && audioPlayer.playerState !== PLAYER_STATES.ENDED) {
this.setState({
audioPlayer: {
...audioPlayer,
currentTime: data.currentTime / 1000,
}
});
}
};
audioOnSeek = seek => this.audioPlayer.seek(seek * 1000);
audioOnSeeking = currentTime => {
const { audioPlayer } = this.state;
this.setState({
audioPlayer: {
...audioPlayer,
currentTime
}
});
};
audioOnPaused = playerState => {
let { audioPlayer } = this.state;
if (audioPlayer.isPaused) this.audioPlayer.play(); else this.audioPlayer.pause();
this.setState({
audioPlayer: {
...audioPlayer,
playerState,
isPaused: !audioPlayer.isPaused
}
})
};
audioOnEnd = () => {
let { audioPlayer } = this.state;
this.setState({
audioPlayer: {
...audioPlayer,
playerState: PLAYER_STATES.ENDED
}
});
};
audioOnReplay = () => {
let { audioPlayer } = this.state;
this.setState({
audioPlayer: {
...audioPlayer,
playerState: PLAYER_STATES.PLAYING
}
});
this.audioPlayer.play((err) => {
if (err) {
//TODO: - handle error
this.audioOnError(err);
} else {
this.audioOnLoad(this.audioPlayer);
}
})
};
audioOnError = err => this.props.mediaCouldNotBeLoaded();
/** PDF methods */
pdfOnLoadComplete = (numberOfPages, filePath) => {
let { pdfViewer } = this.state;
if (pdfViewer.isLoading) {
this.setState({
pdfViewer: {
...pdfViewer,
isLoading: false,
numberOfPages: numberOfPages //do not use setState, it will cause re-render
}
})
}
};
pdfOnError = err => this.props.mediaCouldNotBeLoaded();
renderCloseToolbar = (isTransparent = true) => {
return (
<View style={[styles.pdfToolbarContainer, isTransparent && styles.pdfToolbarContainerTransparent]}>
<View style={styles.pdfToolbarContentView}>
<TouchableOpacity style={styles.pdfToolbarCloseBtn} onPress={() => {
requestAnimationFrame(() => {
if (!isTransparent)
this.destroy(() => Actions.pop());
else
this.interactableView.snapTo({index: 2})
})
}}>
<View style={styles.pdfToolbarCloseBtnContentView}>
<Image style={styles.pdfIcon} source={images.logout_white}/>
</View>
</TouchableOpacity>
</View>
</View>
)
};
renderImageViewer = () => {
return (
<View style={styles.mediaItemContainer}>
{this.renderCloseToolbar(true)}
<View style={styles.imageContentView}>
<CachedImage
loadingIndicator={LoaderView}
style={styles.cachedImage}
source={{uri: this.props.media.url}}
onError={this.onError}
renderImage={(props) => <Image source={props.source} style={styles.image}/>}
/>
</View>
</View>
)
};
renderVideoPlayer = () => {
return (
<View style={styles.mediaItemContainer}>
<View style={styles.videoContentView}>
<Video
onLoadStart={this.videoOnLoadStart}
onLoad={this.videoOnLoad}
onProgress={this.videoOnProgress}
onEnd={this.videoOnEnd}
onError={this.videoOnError}
paused={this.state.videoPlayer.isPaused}
style={styles.video}
ref={(el) => this.videoPlayer = el}
source={{uri: this.props.media.url}}>
</Video>
</View>
<MediaControls
duration={this.state.videoPlayer.duration}
isLoading={this.state.videoPlayer.isLoading}
mainColor={colors.midBlue}
isFullScreen={true}
onPaused={this.videoOnPaused}
onReplay={this.videoOnReplay}
onSeek={this.videoOnSeek}
onSeeking={this.videoOnSeeking}
playerState={ this.state.videoPlayer.playerState }
progress={ this.state.videoPlayer.currentTime }
toolbar={this.renderCloseToolbar()}
/>
</View>
)
};
renderAudioPlayer() {
if (!this.audioPlayer && !this.didPrepareAudio) {
this.didPrepareAudio = true;
let timer = setTimeout(() => {
this.audioPrepare();
clearTimeout(timer);
timer = undefined;
}, 100);
}
return (
<AnimatedGradient style={styles.mediaItemContainer} colors={[ colors.brightOrange,colors.orange ]}>
<View style={styles.audioContentView}>
<Image style={styles.logo} source={images.logo_transparent}/>
</View>
<MediaControls
duration={this.state.audioPlayer.duration}
isLoading={this.state.audioPlayer.isLoading}
mainColor={colors.midBlue}
isFullScreen={true}
onPaused={this.audioOnPaused}
onReplay={this.audioOnReplay}
onSeek={this.audioOnSeek}
onSeeking={this.audioOnSeeking}
playerState={ this.state.audioPlayer.playerState }
progress={ this.state.audioPlayer.currentTime }
toolbar={this.renderCloseToolbar()}
/>
</AnimatedGradient>
)
}
renderPDFViewer() {
let source = {uri:this.props.media.url , cache:true};
return (
<View style={styles.pdfContentView}>
{this.renderCloseToolbar(false)}
<PDF
style={styles.pdfViewer}
source={source}
scale={this.state.pdfViewer.scale}
page={this.state.pdfViewer.page}
horizontal={this.state.pdfViewer.horizontal}
onLoadComplete={this.pdfOnLoadComplete}
onPageChanged={(page, numberOfPages) => {
this.state.page = page; //do not use setState, it will cause re-render
console.log(`current page: ${page}`);
}}
onError={this.pdfOnError}
/>
</View>
)
}
renderMedia() {
let { media } = this.props;
if (media.mediaType === mediaType.audio)
return this.renderAudioPlayer();
else if (media.mediaType === mediaType.image)
return this.renderImageViewer();
else if (media.mediaType === mediaType.video)
return this.renderVideoPlayer();
else if (media.mediaType === mediaType.file)
return this.renderPDFViewer();
}
renderLoader() {
let { videoPlayer, audioPlayer, pdfViewer } = this.state;
let { media } = this.props;
const shouldShowLoader =
media.mediaType === mediaType.video && videoPlayer.isLoading ||
media.mediaType === mediaType.audio && audioPlayer.isLoading ||
media.mediaType === mediaType.file && pdfViewer.isLoading;
if (shouldShowLoader)
return (
<View style={styles.loaderViewContainer}>
<LoaderView />
</View>
)
}
onSnap({index}) {
if (index == 0 || index == 2) {
this.destroy(() => Actions.pop())
}
};
render() {
let { media } = this.props,
verticalOnly = media.mediaType === mediaType.video || media.mediaType === mediaType.audio,
dragEnabled = media.mediaType !== mediaType.file;
return (
<Animated.View style={[styles.container,
{opacity: this.opacityAnimation} ]}>
<Interactable.View
verticalOnly={verticalOnly}
dragEnabled={dragEnabled}
ref={(el) => this.interactableView = el }
style={styles.interactableContainer}
snapPoints={this.interactableViewSnapPoints}
onDrag={({nativeEvent}) => console.log(nativeEvent)}
onSnap={({nativeEvent}) => this.onSnap(nativeEvent)}
animatedValueY={this.interactableViewYAnimationValue}>
{this.renderMedia()}
{this.renderLoader()}
</Interactable.View>
</Animated.View>
)
}
destroy(callback) {
if (this.videoPlayer) {
let { videoPlayer } = this.state;
this.setState({
videoPlayer: {
...videoPlayer,
isPaused: true
}
},() => callback && callback())
} else if (this.audioPlayer) {
this.audioPlayer.pause(() => {
this.audioPlayer.destroy(() => {
this.audioPlayer = undefined;
requestAnimationFrame(() => callback && callback())
})
})
} else {
requestAnimationFrame(() => callback && callback())
}
}
goBackListener = () => {
this.onBack();
return true;
};
onBack() {
this.destroy(() => Actions.pop())
}
}
const styles = StyleSheet.create({
container: {
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
justifyContent: 'center',
alignItems: 'stretch',
},
interactableContainer: {
flex:1,
backgroundColor: colors.black,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
mediaItemContainer: {
width: Screen.width,
height: Screen.height,
flex: 1,
},
imageContentView: {
flex:1,
justifyContent:'center',
alignItems: 'center'
},
cachedImage: {
flex:1,
width: '100%',
},
image: {
flex:1,
resizeMode: Image.resizeMode.contain,
width: '100%',
},
videoContentView: {
flex: 1,
},
video: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
},
playbackBtnsContainer: {
height: 150,
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
backgroundColor: 'yellow'
},
loaderViewContainer: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
top: 0,
justifyContent: 'center',
alignItems: 'center'
},
audioContentView: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
},
logo: {
width: 160,
height: 212,
marginBottom: 42,
},
pdfContentView: {
width: Screen.width,
height: Screen.height,
flex: 1,
},
pdfToolbarContainer: {
height: NAVIGATION_BAR_HEIGHT,
width: '100%',
alignItems: 'stretch',
backgroundColor: colors.tangerine,
flexDirection: 'row',
alignSelf: 'flex-start',
...Platform.select({
android: {
marginTop: STATUS_BAR_HEIGHT + 8,
}
})
},
pdfToolbarContainerTransparent: {
backgroundColor: colors.transparent,
},
pdfToolbarContentView: {
flex: 1,
flexDirection: 'row',
alignItems: 'stretch',
},
pdfToolbarCloseBtn: {
flexDirection: 'row',
alignItems: 'stretch',
},
pdfToolbarCloseBtnContentView: {
padding: 8,
...Platform.select({
android: {
marginTop: 8,
}
}),
alignItems: 'center',
justifyContent: 'center',
...appStyles.navBarTopMargin
},
pdfToolbarCloseBtnText: {
color: colors.midBlue,
...fonts.iranSansLight,
fontSize: 20,
},
pdfViewer: {
width: '100%',
height: '80%',
},
pdfIcon: {
width: 32,
height: 32,
}
});
const mapStateToProps = (state) => ({
state
});
const mapActionsToProps = () => ({
mediaCouldNotBeLoaded: mediaCouldNotBeLoaded,
});
export default connect(mapStateToProps,mapActionsToProps(),undefined,{ withRef: true })(MediaDisplayer)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment