Merge branch 'master' of github.com:react-native-video/react-native-video into always-check-for-hideShutterView
This commit is contained in:
commit
413c4f0c96
@ -16,6 +16,7 @@
|
|||||||
- Better support newer versions of RNW (64 and newer) [#2535](https://github.com/react-native-video/react-native-video/pull/2535)
|
- Better support newer versions of RNW (64 and newer) [#2535](https://github.com/react-native-video/react-native-video/pull/2535)
|
||||||
- Fix nil string uri parameter error [#695](https://github.com/react-native-video/react-native-video/pull/695)
|
- Fix nil string uri parameter error [#695](https://github.com/react-native-video/react-native-video/pull/695)
|
||||||
- (Breaking) Bump shaka-player to 3.3.2 [#2587](https://github.com/react-native-video/react-native-video/pull/2587)
|
- (Breaking) Bump shaka-player to 3.3.2 [#2587](https://github.com/react-native-video/react-native-video/pull/2587)
|
||||||
|
- Improve basic player example on android [#2662](https://github.com/react-native-video/react-native-video/pull/2662)
|
||||||
|
|
||||||
### Version 5.2.0
|
### Version 5.2.0
|
||||||
|
|
||||||
|
@ -1124,7 +1124,7 @@ Platforms: all
|
|||||||
|
|
||||||
|
|
||||||
#### onProgress
|
#### onProgress
|
||||||
Callback function that is called every progressUpdateInterval seconds with info about which position the media is currently playing.
|
Callback function that is called every progressUpdateInterval milliseconds with info about which position the media is currently playing.
|
||||||
|
|
||||||
Property | Type | Description
|
Property | Type | Description
|
||||||
--- | --- | ---
|
--- | --- | ---
|
||||||
|
@ -148,3 +148,5 @@ task copyDownloadableDepsToLibs(type: Copy) {
|
|||||||
from configurations.compile
|
from configurations.compile
|
||||||
into 'libs'
|
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.react.shell.MainReactPackage;
|
||||||
import com.facebook.soloader.SoLoader;
|
import com.facebook.soloader.SoLoader;
|
||||||
|
|
||||||
|
import com.reactnativecommunity.picker.RNCPickerPackage;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -25,7 +27,8 @@ public class MainApplication extends MultiDexApplication implements ReactApplica
|
|||||||
protected List<ReactPackage> getPackages() {
|
protected List<ReactPackage> getPackages() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
new MainReactPackage(),
|
new MainReactPackage(),
|
||||||
new ReactVideoPackage()
|
new ReactVideoPackage(),
|
||||||
|
new RNCPickerPackage()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
rootProject.name = 'VideoPlayer'
|
rootProject.name = 'VideoPlayer'
|
||||||
|
|
||||||
|
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
|
||||||
|
|
||||||
include ':react-native-video'
|
include ':react-native-video'
|
||||||
project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android-exoplayer')
|
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'
|
include ':app'
|
||||||
|
@ -10,11 +10,12 @@
|
|||||||
"lint": "eslint ."
|
"lint": "eslint ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@react-native-picker/picker": "^1.9.11",
|
||||||
"babel-plugin-module-resolver": "^4.1.0",
|
"babel-plugin-module-resolver": "^4.1.0",
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
"react-native": "0.61.5",
|
"react-native": "0.61.5",
|
||||||
"react-native-windows": "^0.61.0-0",
|
"react-native-video": "../../",
|
||||||
"react-native-video": "file:../.."
|
"react-native-windows": "^0.61.0-0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.6.0",
|
"@babel/core": "^7.6.0",
|
||||||
|
@ -9,9 +9,14 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View,
|
||||||
|
ActivityIndicator,
|
||||||
|
PanResponder,
|
||||||
|
ToastAndroid,
|
||||||
} from 'react-native';
|
} 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 {
|
class VideoPlayer extends Component {
|
||||||
|
|
||||||
@ -22,23 +27,126 @@ class VideoPlayer extends Component {
|
|||||||
resizeMode: 'contain',
|
resizeMode: 'contain',
|
||||||
duration: 0.0,
|
duration: 0.0,
|
||||||
currentTime: 0.0,
|
currentTime: 0.0,
|
||||||
|
videoWidth: 0,
|
||||||
|
videoHeight: 0,
|
||||||
paused: false,
|
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;
|
video: Video;
|
||||||
|
seekPanResponder: PanResponder | undefined;
|
||||||
|
|
||||||
onLoad = (data: any) => {
|
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) => {
|
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 })
|
onVideoLoadStart = () => {
|
||||||
this.video.seek(0)
|
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 = () => {
|
onAudioBecomingNoisy = () => {
|
||||||
this.setState({ paused: true })
|
this.setState({ paused: true })
|
||||||
@ -48,7 +156,7 @@ class VideoPlayer extends Component {
|
|||||||
this.setState({ paused: !event.hasAudioFocus })
|
this.setState({ paused: !event.hasAudioFocus })
|
||||||
};
|
};
|
||||||
|
|
||||||
getCurrentTimePercentage() {
|
getCurrentTimePercentage = () => {
|
||||||
if (this.state.currentTime > 0 && this.state.duration !== 0) {
|
if (this.state.currentTime > 0 && this.state.duration !== 0) {
|
||||||
return this.state.currentTime / this.state.duration;
|
return this.state.currentTime / this.state.duration;
|
||||||
}
|
}
|
||||||
@ -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 (
|
return (
|
||||||
<View style={styles.container}>
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.fullScreen}
|
onPress={() => {
|
||||||
onPress={() => this.setState({ paused: !this.state.paused })}
|
this.toggleDecoration()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Video
|
<Text style={[styles.controlOption]}>{'decoration'}</Text>
|
||||||
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}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<View style={styles.controls}>
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
onPress={() => {
|
||||||
|
this.channelDown()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={[styles.leftRightControlOption]}>{'ChDown'}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</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.generalControls}>
|
||||||
<View style={styles.rateControl}>
|
<View style={styles.rateControl}>
|
||||||
{this.renderRateControl(0.25)}
|
{this.renderRateControl(0.25)}
|
||||||
@ -142,17 +564,114 @@ class VideoPlayer extends Component {
|
|||||||
{this.renderResizeModeControl('stretch')}
|
{this.renderResizeModeControl('stretch')}
|
||||||
</View>
|
</View>
|
||||||
</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}>
|
{this.state.textTracks.map((track) => (
|
||||||
<View style={styles.progress}>
|
<Picker.Item
|
||||||
<View style={[styles.innerProgressCompleted, { flex: flexCompleted }]} />
|
label={track.language}
|
||||||
<View style={[styles.innerProgressRemaining, { flex: flexRemaining }]} />
|
value={track.language}
|
||||||
|
key={track.language}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Picker>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</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',
|
alignItems: 'center',
|
||||||
backgroundColor: 'black',
|
backgroundColor: 'black',
|
||||||
},
|
},
|
||||||
|
halfScreen: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 50,
|
||||||
|
left: 50,
|
||||||
|
bottom: 100,
|
||||||
|
right: 100,
|
||||||
|
},
|
||||||
fullScreen: {
|
fullScreen: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 0,
|
top: 0,
|
||||||
@ -170,7 +696,7 @@ const styles = StyleSheet.create({
|
|||||||
bottom: 0,
|
bottom: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
},
|
},
|
||||||
controls: {
|
bottomControls: {
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@ -178,19 +704,33 @@ const styles = StyleSheet.create({
|
|||||||
left: 20,
|
left: 20,
|
||||||
right: 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,
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
borderRadius: 3,
|
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
},
|
paddingBottom: 10,
|
||||||
innerProgressCompleted: {
|
|
||||||
height: 20,
|
|
||||||
backgroundColor: '#cccccc',
|
|
||||||
},
|
|
||||||
innerProgressRemaining: {
|
|
||||||
height: 20,
|
|
||||||
backgroundColor: '#2C2C2C',
|
|
||||||
},
|
},
|
||||||
generalControls: {
|
generalControls: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@ -215,6 +755,13 @@ const styles = StyleSheet.create({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
|
leftRightControlOption: {
|
||||||
|
alignSelf: 'center',
|
||||||
|
fontSize: 11,
|
||||||
|
color: 'white',
|
||||||
|
padding: 10,
|
||||||
|
lineHeight: 12,
|
||||||
|
},
|
||||||
controlOption: {
|
controlOption: {
|
||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
@ -223,10 +770,46 @@ const styles = StyleSheet.create({
|
|||||||
paddingRight: 2,
|
paddingRight: 2,
|
||||||
lineHeight: 12,
|
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,
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -961,6 +961,11 @@
|
|||||||
eslint-plugin-react-native "3.6.0"
|
eslint-plugin-react-native "3.6.0"
|
||||||
prettier "1.16.4"
|
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":
|
"@types/babel__core@^7.1.0":
|
||||||
version "7.1.3"
|
version "7.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.3.tgz#e441ea7df63cd080dfcd02ab199e6d16a735fc30"
|
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"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.9.0.tgz#21ca9561399aad0ff1a7701c01683e8ca981edcb"
|
||||||
integrity sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==
|
integrity sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==
|
||||||
|
|
||||||
"react-native-video@file:../..":
|
react-native-video@../../:
|
||||||
version "5.2.0"
|
version "5.2.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
keymirror "^0.1.1"
|
keymirror "^0.1.1"
|
||||||
|
Loading…
Reference in New Issue
Block a user