fix: improve basic player
- allow to set multiple video to to play and zap to next channel - display toast on error - add resizing test - add a seek bar - add text and audio tracks picker - add loader during buffering - add repeat mode test - add toggle fullscreen
This commit is contained in:
parent
1e0c99b32c
commit
3d40461a32
@ -148,3 +148,5 @@ task copyDownloadableDepsToLibs(type: Copy) {
|
||||
from configurations.compile
|
||||
into 'libs'
|
||||
}
|
||||
|
||||
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
|
||||
|
@ -10,6 +10,8 @@ import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.shell.MainReactPackage;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
import com.reactnativecommunity.picker.RNCPickerPackage;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@ -25,7 +27,8 @@ public class MainApplication extends MultiDexApplication implements ReactApplica
|
||||
protected List<ReactPackage> getPackages() {
|
||||
return Arrays.asList(
|
||||
new MainReactPackage(),
|
||||
new ReactVideoPackage()
|
||||
new ReactVideoPackage(),
|
||||
new RNCPickerPackage()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,12 @@
|
||||
rootProject.name = 'VideoPlayer'
|
||||
|
||||
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
|
||||
|
||||
include ':react-native-video'
|
||||
project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android-exoplayer')
|
||||
|
||||
|
||||
include ':@react-native-picker_picker'
|
||||
project(':@react-native-picker_picker').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-picker/picker/android')
|
||||
|
||||
include ':app'
|
||||
|
@ -10,11 +10,12 @@
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-native-picker/picker": "^1.9.11",
|
||||
"babel-plugin-module-resolver": "^4.1.0",
|
||||
"react": "^16.12.0",
|
||||
"react-native": "0.61.5",
|
||||
"react-native-windows": "^0.61.0-0",
|
||||
"react-native-video": "file:../.."
|
||||
"react-native-video": "../../",
|
||||
"react-native-windows": "^0.61.0-0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.6.0",
|
||||
@ -28,4 +29,4 @@
|
||||
"metro-react-native-babel-preset": "^0.56.0",
|
||||
"react-test-renderer": "16.8.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,9 +9,14 @@ import {
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
ActivityIndicator,
|
||||
PanResponder,
|
||||
ToastAndroid,
|
||||
} from 'react-native';
|
||||
|
||||
import Video from 'react-native-video';
|
||||
import { Picker } from '@react-native-picker/picker'
|
||||
|
||||
import Video, { TextTrackType } from 'react-native-video';
|
||||
|
||||
class VideoPlayer extends Component {
|
||||
|
||||
@ -22,23 +27,126 @@ class VideoPlayer extends Component {
|
||||
resizeMode: 'contain',
|
||||
duration: 0.0,
|
||||
currentTime: 0.0,
|
||||
videoWidth: 0,
|
||||
videoHeight: 0,
|
||||
paused: false,
|
||||
fullscreen: true,
|
||||
decoration: true,
|
||||
isLoading: false,
|
||||
seekerFillWidth: 0,
|
||||
seekerPosition: 0,
|
||||
seekerOffset: 0,
|
||||
seeking: false,
|
||||
audioTracks: [],
|
||||
textTracks: [],
|
||||
selectedAudioTrack: undefined,
|
||||
selectedTextTrack: undefined,
|
||||
srcListId: 0,
|
||||
loop: false,
|
||||
};
|
||||
|
||||
seekerWidth = 0
|
||||
|
||||
srcList = [
|
||||
{
|
||||
description: 'demo with sintel Subtitles',
|
||||
uri:
|
||||
'http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=51AF5F39AB0CEC3E5497CD9C900EBFEAECCCB5C7.8506521BFC350652163895D4C26DEE124209AA9E&key=ik0',
|
||||
type: 'mpd',
|
||||
},
|
||||
{
|
||||
description: 'subtitles',
|
||||
uri: 'https://bitmovin-a.akamaihd.net/content/sintel/sintel.mpd',
|
||||
},
|
||||
{ description: 'Stopped playback', uri: undefined },
|
||||
{
|
||||
description: 'no View',
|
||||
noView: true,
|
||||
},
|
||||
]
|
||||
|
||||
video: Video;
|
||||
seekPanResponder: PanResponder | undefined;
|
||||
|
||||
onLoad = (data: any) => {
|
||||
this.setState({ duration: data.duration });
|
||||
this.setState({ duration: data.duration, loading: false, });
|
||||
this.onAudioTracks(data.audioTracks)
|
||||
this.onTextTracks(data.textTracks)
|
||||
};
|
||||
|
||||
onProgress = (data: any) => {
|
||||
this.setState({ currentTime: data.currentTime });
|
||||
if (!this.state.seeking) {
|
||||
const position = this.calculateSeekerPosition()
|
||||
this.setSeekerPosition(position)
|
||||
}
|
||||
this.setState({ currentTime: data.currentTime })
|
||||
};
|
||||
|
||||
onEnd = () => {
|
||||
this.setState({ paused: true })
|
||||
this.video.seek(0)
|
||||
};
|
||||
|
||||
onVideoLoadStart = () => {
|
||||
console.log('onVideoLoadStart')
|
||||
this.setState({ isLoading: true })
|
||||
}
|
||||
|
||||
|
||||
onAudioTracks = (data: any) => {
|
||||
const selectedTrack = data.audioTracks?.find((x: any) => {
|
||||
return x.selected
|
||||
})
|
||||
this.setState({
|
||||
audioTracks: data,
|
||||
})
|
||||
if (selectedTrack?.language) {
|
||||
this.setState({
|
||||
selectedAudioTrack: {
|
||||
type: 'language',
|
||||
value: selectedTrack?.language,
|
||||
},
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
onTextTracks = (data: any) => {
|
||||
const selectedTrack = data.textTracks?.find((x: any) => {
|
||||
return x.selected
|
||||
})
|
||||
|
||||
this.setState({
|
||||
textTracks: data,
|
||||
})
|
||||
if (selectedTrack?.language) {
|
||||
this.setState({
|
||||
textTracks: data,
|
||||
selectedTextTrack: {
|
||||
type: 'language',
|
||||
value: selectedTrack?.language,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onAspectRatio = (data: any) => {
|
||||
console.log('onAspectRadio called ' + JSON.stringify(data))
|
||||
this.setState({
|
||||
videoWidth: data.width,
|
||||
videoHeight: data.height,
|
||||
})
|
||||
}
|
||||
|
||||
onVideoBuffer = (param: any) => {
|
||||
console.log('onVideoBuffer')
|
||||
|
||||
this.setState({ isLoading: param.isBuffering })
|
||||
}
|
||||
|
||||
|
||||
onReadyForDisplay = () => {
|
||||
console.log('onReadyForDisplay')
|
||||
|
||||
this.setState({ isLoading: false })
|
||||
}
|
||||
|
||||
|
||||
onAudioBecomingNoisy = () => {
|
||||
this.setState({ paused: true })
|
||||
@ -48,7 +156,7 @@ class VideoPlayer extends Component {
|
||||
this.setState({ paused: !event.hasAudioFocus })
|
||||
};
|
||||
|
||||
getCurrentTimePercentage() {
|
||||
getCurrentTimePercentage = () => {
|
||||
if (this.state.currentTime > 0 && this.state.duration !== 0) {
|
||||
return this.state.currentTime / this.state.duration;
|
||||
}
|
||||
@ -76,7 +184,7 @@ class VideoPlayer extends Component {
|
||||
{resizeMode}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
renderVolumeControl(volume: number) {
|
||||
@ -91,36 +199,350 @@ class VideoPlayer extends Component {
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const flexCompleted = this.getCurrentTimePercentage() * 100;
|
||||
const flexRemaining = (1 - this.getCurrentTimePercentage()) * 100;
|
||||
|
||||
toast = (visible: boolean, message: string) => {
|
||||
if (visible) {
|
||||
ToastAndroid.showWithGravityAndOffset(
|
||||
message,
|
||||
ToastAndroid.LONG,
|
||||
ToastAndroid.BOTTOM,
|
||||
25,
|
||||
50,
|
||||
)
|
||||
return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
onError = (err: any) => {
|
||||
console.log(JSON.stringify(err))
|
||||
this.toast(true, 'error: ' + err?.error?.code)
|
||||
}
|
||||
|
||||
onEnd = () => {
|
||||
this.channelUp()
|
||||
};
|
||||
|
||||
|
||||
toggleFullscreen() {
|
||||
this.setState({ fullscreen: !this.state.fullscreen })
|
||||
}
|
||||
|
||||
toggleDecoration() {
|
||||
this.setState({ decoration: !this.state.decoration })
|
||||
if (this.state.decoration) {
|
||||
this.video.dismissFullscreenPlayer()
|
||||
} else {
|
||||
this.video.presentFullscreenPlayer()
|
||||
}
|
||||
}
|
||||
|
||||
goToChannel(channel: any) {
|
||||
this.setState({
|
||||
srcListId: channel,
|
||||
duration: 0.0,
|
||||
currentTime: 0.0,
|
||||
videoWidth: 0,
|
||||
videoHeight: 0,
|
||||
isLoading: false,
|
||||
audioTracks: [],
|
||||
textTracks: [],
|
||||
selectedAudioTrack: undefined,
|
||||
selectedTextTrack: undefined,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
channelUp() {
|
||||
console.log('channel up')
|
||||
this.goToChannel((this.state.srcListId + 1) % this.srcList.length)
|
||||
}
|
||||
|
||||
channelDown() {
|
||||
console.log('channel down')
|
||||
this.goToChannel((this.state.srcListId + this.srcList.length - 1) % this.srcList.length)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.initSeekPanResponder()
|
||||
}
|
||||
|
||||
renderDecorationsControl() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
this.toggleDecoration()
|
||||
}}
|
||||
>
|
||||
<Text style={[styles.controlOption]}>{'decoration'}</Text>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
|
||||
renderFullScreenControl() {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
this.toggleFullscreen()
|
||||
}}
|
||||
>
|
||||
<Text style={[styles.controlOption]}>{'fullscreen'}</Text>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
|
||||
renderPause() {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
this.setState({ paused: !this.state.paused })
|
||||
}}
|
||||
>
|
||||
<Text style={[styles.controlOption]}>
|
||||
{this.state.paused ? 'pause' : 'playing'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
|
||||
renderRepeatModeControl() {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
this.setState({ loop: !this.state.loop })
|
||||
}}
|
||||
>
|
||||
<Text style={[styles.controlOption]}>
|
||||
{this.state.loop ? 'loop enable' : 'loop disable'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
|
||||
renderLeftControl() {
|
||||
return (
|
||||
<View>
|
||||
<TouchableOpacity
|
||||
style={styles.fullScreen}
|
||||
onPress={() => this.setState({ paused: !this.state.paused })}
|
||||
onPress={() => {
|
||||
this.channelDown()
|
||||
}}
|
||||
>
|
||||
<Video
|
||||
ref={(ref: Video) => { this.video = ref }}
|
||||
/* For ExoPlayer */
|
||||
/* source={{ uri: 'http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=51AF5F39AB0CEC3E5497CD9C900EBFEAECCCB5C7.8506521BFC350652163895D4C26DEE124209AA9E&key=ik0', type: 'mpd' }} */
|
||||
source={require('./broadchurch.mp4')}
|
||||
style={styles.fullScreen}
|
||||
rate={this.state.rate}
|
||||
paused={this.state.paused}
|
||||
volume={this.state.volume}
|
||||
muted={this.state.muted}
|
||||
resizeMode={this.state.resizeMode}
|
||||
onLoad={this.onLoad}
|
||||
onProgress={this.onProgress}
|
||||
onEnd={this.onEnd}
|
||||
onAudioBecomingNoisy={this.onAudioBecomingNoisy}
|
||||
onAudioFocusChanged={this.onAudioFocusChanged}
|
||||
repeat={false}
|
||||
/>
|
||||
<Text style={[styles.leftRightControlOption]}>{'ChDown'}</Text>
|
||||
</TouchableOpacity>
|
||||
<View style={styles.controls}>
|
||||
</View>
|
||||
// onTimelineUpdated
|
||||
)
|
||||
}
|
||||
|
||||
renderRightControl() {
|
||||
return (
|
||||
<View>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
this.channelUp()
|
||||
}}
|
||||
>
|
||||
<Text style={[styles.leftRightControlOption]}>{'ChUp'}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the seekbar and attach its handlers
|
||||
*/
|
||||
|
||||
/**
|
||||
* Constrain the location of the seeker to the
|
||||
* min/max value based on how big the
|
||||
* seeker is.
|
||||
*
|
||||
* @param {float} val position of seeker handle in px
|
||||
* @return {float} constrained position of seeker handle in px
|
||||
*/
|
||||
constrainToSeekerMinMax(val = 0) {
|
||||
if (val <= 0) {
|
||||
return 0
|
||||
} else if (val >= this.seekerWidth) {
|
||||
return this.seekerWidth
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the position of the seekbar's components
|
||||
* (both fill and handle) according to the
|
||||
* position supplied.
|
||||
*
|
||||
* @param {float} position position in px of seeker handle}
|
||||
*/
|
||||
setSeekerPosition(position = 0) {
|
||||
const state = this.state
|
||||
position = this.constrainToSeekerMinMax(position)
|
||||
|
||||
state.seekerFillWidth = position
|
||||
state.seekerPosition = position
|
||||
|
||||
if (!state.seeking) {
|
||||
state.seekerOffset = position
|
||||
}
|
||||
|
||||
this.setState(state)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the position that the seeker should be
|
||||
* at along its track.
|
||||
*
|
||||
* @return {float} position of seeker handle in px based on currentTime
|
||||
*/
|
||||
calculateSeekerPosition() {
|
||||
const percent = this.state.currentTime / this.state.duration
|
||||
return this.seekerWidth * percent
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the time that the video should be at
|
||||
* based on where the seeker handle is.
|
||||
*
|
||||
* @return {float} time in ms based on seekerPosition.
|
||||
*/
|
||||
calculateTimeFromSeekerPosition() {
|
||||
const percent = this.state.seekerPosition / this.seekerWidth
|
||||
return this.state.duration * percent
|
||||
}
|
||||
|
||||
/**
|
||||
* Get our seekbar responder going
|
||||
*/
|
||||
initSeekPanResponder() {
|
||||
this.seekPanResponder = PanResponder.create({
|
||||
// Ask to be the responder.
|
||||
onStartShouldSetPanResponder: (evt, gestureState) => true,
|
||||
onMoveShouldSetPanResponder: (evt, gestureState) => true,
|
||||
|
||||
/**
|
||||
* When we start the pan tell the machine that we're
|
||||
* seeking. This stops it from updating the seekbar
|
||||
* position in the onProgress listener.
|
||||
*/
|
||||
onPanResponderGrant: (evt, gestureState) => {
|
||||
const state = this.state
|
||||
// this.clearControlTimeout()
|
||||
const position = evt.nativeEvent.locationX
|
||||
this.setSeekerPosition(position)
|
||||
state.seeking = true
|
||||
this.setState(state)
|
||||
},
|
||||
|
||||
/**
|
||||
* When panning, update the seekbar position, duh.
|
||||
*/
|
||||
onPanResponderMove: (evt, gestureState) => {
|
||||
const position = this.state.seekerOffset + gestureState.dx
|
||||
this.setSeekerPosition(position)
|
||||
},
|
||||
|
||||
/**
|
||||
* On release we update the time and seek to it in the video.
|
||||
* If you seek to the end of the video we fire the
|
||||
* onEnd callback
|
||||
*/
|
||||
onPanResponderRelease: (evt, gestureState) => {
|
||||
const time = this.calculateTimeFromSeekerPosition()
|
||||
const state = this.state
|
||||
if (time >= state.duration && !state.isLoading) {
|
||||
state.paused = true
|
||||
this.onEnd()
|
||||
} else {
|
||||
this.video?.seek(time)
|
||||
state.seeking = false
|
||||
}
|
||||
this.setState(state)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
renderSeekBar() {
|
||||
if (!this.seekPanResponder) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<View
|
||||
style={styles.seekbarContainer}
|
||||
{...this.seekPanResponder.panHandlers}
|
||||
{...styles.generalControls}
|
||||
>
|
||||
<View
|
||||
style={styles.seekbarTrack}
|
||||
onLayout={(event) => (this.seekerWidth = event.nativeEvent.layout.width)}
|
||||
pointerEvents={'none'}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.seekbarFill,
|
||||
{
|
||||
width:
|
||||
this.state.seekerFillWidth > 0 ? this.state.seekerFillWidth : 0,
|
||||
backgroundColor: '#FFF',
|
||||
},
|
||||
]}
|
||||
pointerEvents={'none'}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
styles.seekbarHandle,
|
||||
{ left: this.state.seekerPosition > 0 ? this.state.seekerPosition : 0 },
|
||||
]}
|
||||
pointerEvents={'none'}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.seekbarCircle,
|
||||
{ backgroundColor: '#FFF' },
|
||||
]}
|
||||
pointerEvents={'none'}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
IndicatorLoadingView() {
|
||||
if (this.state.isLoading)
|
||||
return <ActivityIndicator color="#3235fd" size="large" style={styles.IndicatorStyle} />
|
||||
else return <View />
|
||||
}
|
||||
|
||||
renderOverlay() {
|
||||
return (
|
||||
<>
|
||||
{this.IndicatorLoadingView()}
|
||||
<View style={styles.topControls}>
|
||||
<Text style={[styles.controlOption]}>
|
||||
{this.srcList[this.state.srcListId]?.description}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.leftControls}>
|
||||
<View style={styles.resizeModeControl}>{this.renderLeftControl()}</View>
|
||||
</View>
|
||||
<View style={styles.rightControls}>
|
||||
<View style={styles.resizeModeControl}>{this.renderRightControl()}</View>
|
||||
</View>
|
||||
<View style={styles.bottomControls}>
|
||||
<View style={styles.generalControls}>
|
||||
<View style={styles.resizeModeControl}>{this.renderPause()}</View>
|
||||
<View style={styles.resizeModeControl}>
|
||||
{this.renderRepeatModeControl()}
|
||||
</View>
|
||||
<View style={styles.resizeModeControl}>
|
||||
{this.renderFullScreenControl()}
|
||||
</View>
|
||||
<View style={styles.resizeModeControl}>
|
||||
{this.renderDecorationsControl()}
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.generalControls}>
|
||||
<View style={styles.rateControl}>
|
||||
{this.renderRateControl(0.25)}
|
||||
@ -142,17 +564,114 @@ class VideoPlayer extends Component {
|
||||
{this.renderResizeModeControl('stretch')}
|
||||
</View>
|
||||
</View>
|
||||
{this.renderSeekBar()}
|
||||
<View style={styles.generalControls}>
|
||||
<Text style={styles.controlOption}>AudioTrack</Text>
|
||||
{this.state.audioTracks?.length <= 0 ? (
|
||||
<Text style={styles.controlOption}>empty</Text>
|
||||
) : (
|
||||
<Picker
|
||||
style={styles.picker}
|
||||
selectedValue={this.state.selectedAudioTrack?.value}
|
||||
onValueChange={(itemValue, itemIndex) => {
|
||||
console.log('on audio value change ' + itemValue)
|
||||
this.setState({
|
||||
selectedAudioTrack: {
|
||||
type: 'language',
|
||||
value: itemValue,
|
||||
},
|
||||
})
|
||||
}}
|
||||
>
|
||||
{this.state.audioTracks.map((track) => {
|
||||
return (
|
||||
<Picker.Item
|
||||
label={track.language}
|
||||
value={track.language}
|
||||
key={track.language}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Picker>
|
||||
)}
|
||||
<Text style={styles.controlOption}>TextTrack</Text>
|
||||
{this.state.textTracks?.length <= 0 ? (
|
||||
<Text style={styles.controlOption}>empty</Text>
|
||||
) : (
|
||||
<Picker
|
||||
style={styles.picker}
|
||||
selectedValue={this.state.selectedTextTrack?.value}
|
||||
onValueChange={(itemValue, itemIndex) => {
|
||||
console.log('on value change ' + itemValue)
|
||||
this.setState({
|
||||
selectedTextTrack: {
|
||||
type: 'language',
|
||||
value: itemValue,
|
||||
},
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Picker.Item label={'none'} value={'none'} key={'none'} />
|
||||
|
||||
<View style={styles.trackingControls}>
|
||||
<View style={styles.progress}>
|
||||
<View style={[styles.innerProgressCompleted, { flex: flexCompleted }]} />
|
||||
<View style={[styles.innerProgressRemaining, { flex: flexRemaining }]} />
|
||||
</View>
|
||||
{this.state.textTracks.map((track) => (
|
||||
<Picker.Item
|
||||
label={track.language}
|
||||
value={track.language}
|
||||
key={track.language}
|
||||
/>
|
||||
))}
|
||||
</Picker>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
renderVideoView() {
|
||||
const viewStyle = this.state.fullscreen ? styles.fullScreen : styles.halfScreen
|
||||
|
||||
return (
|
||||
<TouchableOpacity style={viewStyle}>
|
||||
<Video
|
||||
ref={(ref: Video) => {
|
||||
this.video = ref
|
||||
}}
|
||||
source={this.srcList[this.state.srcListId]}
|
||||
style={viewStyle}
|
||||
rate={this.state.rate}
|
||||
paused={this.state.paused}
|
||||
volume={this.state.volume}
|
||||
muted={this.state.muted}
|
||||
resizeMode={this.state.resizeMode}
|
||||
onLoad={this.onLoad}
|
||||
onProgress={this.onProgress}
|
||||
onEnd={this.onEnd}
|
||||
progressUpdateInterval={1000}
|
||||
onError={this.onError}
|
||||
onAudioBecomingNoisy={this.onAudioBecomingNoisy}
|
||||
onAudioFocusChanged={this.onAudioFocusChanged}
|
||||
onLoadStart={this.onVideoLoadStart}
|
||||
onVideoAspectRatio={this.onAspectRatio}
|
||||
onReadyForDisplay={this.onReadyForDisplay}
|
||||
onBuffer={this.onVideoBuffer}
|
||||
repeat={this.state.loop}
|
||||
selectedTextTrack={this.state.selectedTextTrack}
|
||||
selectedAudioTrack={this.state.selectedAudioTrack}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{this.srcList[this.state.srcListId]?.noView ? null : this.renderVideoView()}
|
||||
{this.renderOverlay()}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -163,6 +682,13 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'black',
|
||||
},
|
||||
halfScreen: {
|
||||
position: 'absolute',
|
||||
top: 50,
|
||||
left: 50,
|
||||
bottom: 100,
|
||||
right: 100,
|
||||
},
|
||||
fullScreen: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
@ -170,7 +696,7 @@ const styles = StyleSheet.create({
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
},
|
||||
controls: {
|
||||
bottomControls: {
|
||||
backgroundColor: 'transparent',
|
||||
borderRadius: 5,
|
||||
position: 'absolute',
|
||||
@ -178,19 +704,33 @@ const styles = StyleSheet.create({
|
||||
left: 20,
|
||||
right: 20,
|
||||
},
|
||||
progress: {
|
||||
leftControls: {
|
||||
backgroundColor: 'transparent',
|
||||
borderRadius: 5,
|
||||
position: 'absolute',
|
||||
top: 20,
|
||||
bottom: 20,
|
||||
left: 20,
|
||||
},
|
||||
rightControls: {
|
||||
backgroundColor: 'transparent',
|
||||
borderRadius: 5,
|
||||
position: 'absolute',
|
||||
top: 20,
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
},
|
||||
topControls: {
|
||||
backgroundColor: 'transparent',
|
||||
borderRadius: 4,
|
||||
position: 'absolute',
|
||||
top: 20,
|
||||
left: 20,
|
||||
right: 20,
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
borderRadius: 3,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
innerProgressCompleted: {
|
||||
height: 20,
|
||||
backgroundColor: '#cccccc',
|
||||
},
|
||||
innerProgressRemaining: {
|
||||
height: 20,
|
||||
backgroundColor: '#2C2C2C',
|
||||
paddingBottom: 10,
|
||||
},
|
||||
generalControls: {
|
||||
flex: 1,
|
||||
@ -215,6 +755,13 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
leftRightControlOption: {
|
||||
alignSelf: 'center',
|
||||
fontSize: 11,
|
||||
color: 'white',
|
||||
padding: 10,
|
||||
lineHeight: 12,
|
||||
},
|
||||
controlOption: {
|
||||
alignSelf: 'center',
|
||||
fontSize: 11,
|
||||
@ -223,12 +770,48 @@ const styles = StyleSheet.create({
|
||||
paddingRight: 2,
|
||||
lineHeight: 12,
|
||||
},
|
||||
trackingControls: {
|
||||
IndicatorStyle: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
seekbarContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
borderRadius: 4,
|
||||
height: 30,
|
||||
},
|
||||
seekbarTrack: {
|
||||
backgroundColor: '#333',
|
||||
height: 1,
|
||||
position: 'relative',
|
||||
top: 14,
|
||||
width: '100%',
|
||||
},
|
||||
seekbarFill: {
|
||||
backgroundColor: '#FFF',
|
||||
height: 1,
|
||||
width: '100%',
|
||||
},
|
||||
seekbarHandle: {
|
||||
position: 'absolute',
|
||||
marginLeft: -7,
|
||||
height: 28,
|
||||
width: 28,
|
||||
},
|
||||
seekbarCircle: {
|
||||
borderRadius: 12,
|
||||
position: 'relative',
|
||||
top: 8,
|
||||
left: 8,
|
||||
height: 12,
|
||||
width: 12,
|
||||
},
|
||||
picker: {
|
||||
color: 'white',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
export default VideoPlayer
|
||||
export default VideoPlayer
|
||||
|
@ -961,6 +961,11 @@
|
||||
eslint-plugin-react-native "3.6.0"
|
||||
prettier "1.16.4"
|
||||
|
||||
"@react-native-picker/picker@^1.9.11":
|
||||
version "1.16.8"
|
||||
resolved "https://registry.yarnpkg.com/@react-native-picker/picker/-/picker-1.16.8.tgz#2126ca54d4a5a3e9ea5e3f39ad1e6643f8e4b3d4"
|
||||
integrity sha512-pacdQDX6V6EmjF+HoiIh6u++qx4mTK0WnhgUHRc01B+Qt5eoeUwseBqmqfTSXTx/aHDEd6PiIw7UGvKgFoqgFQ==
|
||||
|
||||
"@types/babel__core@^7.1.0":
|
||||
version "7.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.3.tgz#e441ea7df63cd080dfcd02ab199e6d16a735fc30"
|
||||
@ -5463,7 +5468,7 @@ react-is@^16.8.4, react-is@^16.8.6:
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.9.0.tgz#21ca9561399aad0ff1a7701c01683e8ca981edcb"
|
||||
integrity sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==
|
||||
|
||||
"react-native-video@file:../..":
|
||||
react-native-video@../../:
|
||||
version "5.2.0"
|
||||
dependencies:
|
||||
keymirror "^0.1.1"
|
||||
|
Loading…
Reference in New Issue
Block a user