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:
		| @@ -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; | ||||||
|     } |     } | ||||||
| @@ -76,7 +184,7 @@ class VideoPlayer extends Component { | |||||||
|           {resizeMode} |           {resizeMode} | ||||||
|         </Text> |         </Text> | ||||||
|       </TouchableOpacity> |       </TouchableOpacity> | ||||||
|    ) |     ) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   renderVolumeControl(volume: number) { |   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 ( |     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 |         <TouchableOpacity | ||||||
|           style={styles.fullScreen} |           onPress={() => { | ||||||
|           onPress={() => this.setState({ paused: !this.state.paused })} |             this.channelDown() | ||||||
|  |           }} | ||||||
|         > |         > | ||||||
|           <Video |           <Text style={[styles.leftRightControlOption]}>{'ChDown'}</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}> |       </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} | ||||||
|             </View> |                     key={track.language} | ||||||
|  |                   /> | ||||||
|  |                 ))} | ||||||
|  |               </Picker> | ||||||
|  |             )} | ||||||
|           </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" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user