Navigate back to the homepage

React native video example with custom control

Infinitbility
React Native
April 24th, 2021 · 1 min read
React native video example with custom control

Hello Friends 👋,

Welcome To Infinitbility! ❤️

React Native video provides the functionality to play video in your react native application with native controls but not supported in iOS i.e devs search about video control library or custom control. many devs choose to a react native video player or react native video control for show controls on video but in this article, we will implement our own video controls for both platforms (android, ios).

Let’s start today topic React native video example with custom control

React Native Video Installation

  • Package installation with npm
1npm install --save react-native-video
  • Package installation with yarn
1yarn add react-native-video
  • Package links ( Recommended bcoz many developers facing issue when they did not link library )
1npx react-native link react-native-video

React Native Video with custom controls component

VideoPlayer.js

VideoPlayer component provide play, pause, volume, slider controls and also handle internet connection and video thumbnail.

1import React, { Component } from 'react';
2import Video from 'react-native-video';
3import {
4 TouchableWithoutFeedback,
5 TouchableHighlight,
6 ImageBackground,
7 TouchableOpacity,
8 PanResponder,
9 StyleSheet,
10 Animated,
11 SafeAreaView,
12 Easing,
13 Image,
14 View,
15 Text,
16 Dimensions,
17} from 'react-native';
18import Icon from 'react-native-vector-icons/Ionicons';
19
20const screen = Dimensions.get('window');
21export default class VideoPlayer extends Component {
22 static defaultProps = {
23 toggleResizeModeOnFullscreen: true,
24 controlAnimationTiming: 500,
25 doubleTapTime: 130,
26 playInBackground: false,
27 playWhenInactive: false,
28 resizeMode: 'contain',
29 isFullscreen: false,
30 showOnStart: true,
31 repeat: false,
32 muted: false,
33 volume: 1,
34 title: '',
35 rate: 1,
36 };
37
38 constructor(props) {
39 super(props);
40
41 /**
42 * All of our values that are updated by the
43 * methods and listeners in this class
44 */
45 this.state = {
46 // Video
47 resizeMode: this.props.resizeMode,
48 paused: false,
49 muted: this.props.muted,
50 volume: this.props.volume,
51 rate: this.props.rate,
52 thumbnail: this.props.thumbnail,
53 // Controls
54
55 isFullscreen: this.props.isFullScreen,
56 showTimeRemaining: true,
57 volumeTrackWidth: 0,
58 volumeFillWidth: 0,
59 seekerFillWidth: 0,
60 showControls: this.props.showOnStart,
61 volumePosition: 0,
62 seekerPosition: 0,
63 volumeOffset: 0,
64 seekerOffset: 0,
65 seeking: false,
66 originallyPaused: false,
67 scrubbing: false,
68 loading: false,
69 currentTime: 0,
70 error: false,
71 duration: 0,
72 player: true,
73 source: this.props.source,
74 };
75
76 /**
77 * Any options that can be set at init.
78 */
79 this.opts = {
80 playWhenInactive: this.props.playWhenInactive,
81 playInBackground: this.props.playInBackground,
82 repeat: this.props.repeat,
83 title: this.props.title,
84 };
85
86 /**
87 * Our app listeners and associated methods
88 */
89 this.events = {
90 onError: this.props.onError || this._onError.bind(this),
91 onBack: this.props.onBack || this._onBack.bind(this),
92 onEnd: this.props.onEnd || this._onEnd.bind(this),
93 onScreenTouch: this._onScreenTouch.bind(this),
94 onEnterFullscreen: this.props.onEnterFullscreen,
95 onExitFullscreen: this.props.onExitFullscreen,
96 onShowControls: this.props.onShowControls,
97 onHideControls: this.props.onHideControls,
98 onLoadStart: this._onLoadStart.bind(this),
99 onProgress: this._onProgress.bind(this),
100 onSeek: this._onSeek.bind(this),
101 onLoad: this._onLoad.bind(this),
102 onPause: this.props.onPause,
103 onPlay: this.props.onPlay,
104 };
105
106 /**
107 * Functions used throughout the application
108 */
109 this.methods = {
110 toggleFullscreen: this._toggleFullscreen.bind(this),
111 togglePlayPause: this._togglePlayPause.bind(this),
112 toggleControls: this._toggleControls.bind(this),
113 toggleTimer: this._toggleTimer.bind(this),
114 };
115
116 /**
117 * Player information
118 */
119 this.player = {
120 controlTimeoutDelay: this.props.controlTimeout || 15000,
121 volumePanResponder: PanResponder,
122 seekPanResponder: PanResponder,
123 controlTimeout: null,
124 tapActionTimeout: null,
125 volumeWidth: 150,
126 iconOffset: 0,
127 seekerWidth: 0,
128 ref: Video,
129 scrubbingTimeStep: this.props.scrubbing || 0,
130 tapAnywhereToPause: this.props.tapAnywhereToPause,
131 };
132
133 /**
134 * Various animations
135 */
136 const initialValue = this.props.showOnStart ? 1 : 0;
137
138 this.animations = {
139 bottomControl: {
140 marginBottom: new Animated.Value(0),
141 opacity: new Animated.Value(initialValue),
142 },
143 topControl: {
144 marginTop: new Animated.Value(0),
145 opacity: new Animated.Value(initialValue),
146 },
147 video: {
148 opacity: new Animated.Value(1),
149 },
150 loader: {
151 rotate: new Animated.Value(0),
152 MAX_VALUE: 360,
153 },
154 };
155
156 /**
157 * Various styles that be added...
158 */
159 this.styles = {
160 videoStyle: this.props.videoStyle || {},
161 containerStyle: this.props.style || {},
162 };
163 }
164
165 componentWillReceiveProps(nextProps) {
166
167 this.setState({
168 paused: nextProps.paused,
169 });
170 }
171
172 /**
173 | -------------------------------------------------------
174 | Events
175 | -------------------------------------------------------
176 |
177 | These are the events that the <Video> component uses
178 | and can be overridden by assigning it as a prop.
179 | It is suggested that you override onEnd.
180 |
181 */
182
183 /**
184 * When load starts we display a loading icon
185 * and show the controls.
186 */
187 _onLoadStart() {
188 let state = this.state;
189 state.loading = true;
190 this.loadAnimation();
191 this.setState(state);
192
193 if (typeof this.props.onLoadStart === 'function') {
194 this.props.onLoadStart(...arguments);
195 }
196 }
197
198 /**
199 * When load is finished we hide the load icon
200 * and hide the controls. We also set the
201 * video duration.
202 *
203 * @param {object} data The video meta data
204 */
205 _onLoad(data = {}) {
206 let state = this.state;
207
208 state.duration = data.duration;
209 state.loading = false;
210 this.setState(state);
211
212 if (state.showControls) {
213 this.setControlTimeout();
214 }
215
216 if (typeof this.props.onLoad === 'function') {
217 this.props.onLoad(...arguments);
218 }
219 }
220
221 /**
222 * For onprogress we fire listeners that
223 * update our seekbar and timer.
224 *
225 * @param {object} data The video meta data
226 */
227 _onProgress(data = {}) {
228 let state = this.state;
229 if (!state.scrubbing) {
230 state.currentTime = data.currentTime;
231
232 if (!state.seeking) {
233 const position = this.calculateSeekerPosition();
234 this.setSeekerPosition(position);
235 }
236
237 if (typeof this.props.onProgress === 'function') {
238 this.props.onProgress(...arguments);
239 }
240
241 this.setState(state);
242 }
243 }
244
245 /**
246 * For onSeek we clear scrubbing if set.
247 *
248 * @param {object} data The video meta data
249 */
250 _onSeek(data = {}) {
251 let state = this.state;
252 if (state.scrubbing) {
253 state.scrubbing = false;
254 state.currentTime = data.currentTime;
255
256 // Seeking may be false here if the user released the seek bar while the player was still processing
257 // the last seek command. In this case, perform the steps that have been postponed.
258 if (!state.seeking) {
259 this.setControlTimeout();
260 state.paused = state.originallyPaused;
261 }
262
263 this.setState(state);
264 }
265 }
266
267 /**
268 * It is suggested that you override this
269 * command so your app knows what to do.
270 * Either close the video or go to a
271 * new page.
272 */
273 _onEnd() { }
274
275 /**
276 * Set the error state to true which then
277 * changes our renderError function
278 *
279 * @param {object} err Err obj returned from <Video> component
280 */
281 _onError(err) {
282 let state = this.state;
283 state.error = true;
284 state.loading = false;
285
286 this.setState(state);
287 }
288
289 /**
290 * This is a single and double tap listener
291 * when the user taps the screen anywhere.
292 * One tap toggles controls and/or toggles pause,
293 * two toggles fullscreen mode.
294 */
295 _onScreenTouch() {
296 if (this.player.tapActionTimeout) {
297 clearTimeout(this.player.tapActionTimeout);
298 this.player.tapActionTimeout = 0;
299 this.methods.toggleFullscreen();
300 const state = this.state;
301 if (state.showControls) {
302 this.resetControlTimeout();
303 }
304 } else {
305 this.player.tapActionTimeout = setTimeout(() => {
306 const state = this.state;
307 if (this.player.tapAnywhereToPause && state.showControls) {
308 this.methods.togglePlayPause();
309 this.resetControlTimeout();
310 } else {
311 this.methods.toggleControls();
312 }
313 this.player.tapActionTimeout = 0;
314 }, this.props.doubleTapTime);
315 }
316 }
317
318 /**
319 | -------------------------------------------------------
320 | Methods
321 | -------------------------------------------------------
322 |
323 | These are all of our functions that interact with
324 | various parts of the class. Anything from
325 | calculating time remaining in a video
326 | to handling control operations.
327 |
328 */
329
330 /**
331 * Set a timeout when the controls are shown
332 * that hides them after a length of time.
333 * Default is 15s
334 */
335 setControlTimeout() {
336 this.player.controlTimeout = setTimeout(() => {
337 this._hideControls();
338 }, this.player.controlTimeoutDelay);
339 }
340
341 /**
342 * Clear the hide controls timeout.
343 */
344 clearControlTimeout() {
345 clearTimeout(this.player.controlTimeout);
346 }
347
348 /**
349 * Reset the timer completely
350 */
351 resetControlTimeout() {
352 this.clearControlTimeout();
353 this.setControlTimeout();
354 }
355
356 /**
357 * Animation to hide controls. We fade the
358 * display to 0 then move them off the
359 * screen so they're not interactable
360 */
361 hideControlAnimation() {
362 Animated.parallel([
363 Animated.timing(this.animations.topControl.opacity, {
364 toValue: 0,
365 duration: this.props.controlAnimationTiming,
366 useNativeDriver: false,
367 }),
368 Animated.timing(this.animations.topControl.marginTop, {
369 toValue: -100,
370 duration: this.props.controlAnimationTiming,
371 useNativeDriver: false,
372 }),
373 Animated.timing(this.animations.bottomControl.opacity, {
374 toValue: 0,
375 duration: this.props.controlAnimationTiming,
376 useNativeDriver: false,
377 }),
378 Animated.timing(this.animations.bottomControl.marginBottom, {
379 toValue: -100,
380 duration: this.props.controlAnimationTiming,
381 useNativeDriver: false,
382 }),
383 ]).start();
384 }
385
386 /**
387 * Animation to show controls...opposite of
388 * above...move onto the screen and then
389 * fade in.
390 */
391 showControlAnimation() {
392 Animated.parallel([
393 Animated.timing(this.animations.topControl.opacity, {
394 toValue: 1,
395 useNativeDriver: false,
396 duration: this.props.controlAnimationTiming,
397 }),
398 Animated.timing(this.animations.topControl.marginTop, {
399 toValue: 0,
400 useNativeDriver: false,
401 duration: this.props.controlAnimationTiming,
402 }),
403 Animated.timing(this.animations.bottomControl.opacity, {
404 toValue: 1,
405 useNativeDriver: false,
406 duration: this.props.controlAnimationTiming,
407 }),
408 Animated.timing(this.animations.bottomControl.marginBottom, {
409 toValue: 0,
410 useNativeDriver: false,
411 duration: this.props.controlAnimationTiming,
412 }),
413 ]).start();
414 }
415
416 /**
417 * Loop animation to spin loader icon. If not loading then stop loop.
418 */
419 loadAnimation() {
420 if (this.state.loading) {
421 Animated.sequence([
422 Animated.timing(this.animations.loader.rotate, {
423 toValue: this.animations.loader.MAX_VALUE,
424 duration: 1500,
425 easing: Easing.linear,
426 useNativeDriver: false,
427 }),
428 Animated.timing(this.animations.loader.rotate, {
429 toValue: 0,
430 duration: 0,
431 easing: Easing.linear,
432 useNativeDriver: false,
433 }),
434 ]).start(this.loadAnimation.bind(this));
435 }
436 }
437
438 /**
439 * Function to hide the controls. Sets our
440 * state then calls the animation.
441 */
442 _hideControls() {
443 if (this.mounted) {
444 let state = this.state;
445 state.showControls = false;
446 this.hideControlAnimation();
447
448 this.setState(state);
449 }
450 }
451
452 /**
453 * Function to toggle controls based on
454 * current state.
455 */
456 _toggleControls() {
457 let state = this.state;
458 state.showControls = !state.showControls;
459
460 if (state.showControls) {
461 this.showControlAnimation();
462 this.setControlTimeout();
463 typeof this.events.onShowControls === 'function' &&
464 this.events.onShowControls();
465 } else {
466 this.hideControlAnimation();
467 this.clearControlTimeout();
468 typeof this.events.onHideControls === 'function' &&
469 this.events.onHideControls();
470 }
471
472 this.setState(state);
473 }
474
475 /**
476 * Toggle fullscreen changes resizeMode on
477 * the <Video> component then updates the
478 * isFullscreen state.
479 */
480 _toggleFullscreen() {
481 let state = this.state;
482
483 state.isFullscreen = !state.isFullscreen;
484
485
486 if (this.props.toggleResizeModeOnFullscreen) {
487 state.resizeMode = state.isFullscreen === true ? 'cover' : 'contain';
488 }
489
490 if (state.isFullscreen) {
491 typeof this.events.onEnterFullscreen === 'function' &&
492 this.events.onEnterFullscreen();
493 } else {
494 typeof this.events.onExitFullscreen === 'function' &&
495 this.events.onExitFullscreen();
496 }
497
498 this.setState(state);
499 }
500
501 /**
502 * Toggle playing state on <Video> component
503 */
504 _togglePlayPause() {
505 let state = this.state;
506 state.paused = !state.paused;
507
508 if (state.paused) {
509 typeof this.events.onPause === 'function' && this.events.onPause();
510 } else {
511 typeof this.events.onPlay === 'function' && this.events.onPlay();
512 }
513
514 this.setState(state);
515 }
516
517 /**
518 * Toggle between showing time remaining or
519 * video duration in the timer control
520 */
521 _toggleTimer() {
522 let state = this.state;
523 state.showTimeRemaining = !state.showTimeRemaining;
524 this.setState(state);
525 }
526
527 /**
528 * The default 'onBack' function pops the navigator
529 * and as such the video player requires a
530 * navigator prop by default.
531 */
532 _onBack() {
533 if (this.props.navigator && this.props.navigator.pop) {
534 this.props.navigator.pop();
535 } else {
536 console.warn(
537 'Warning: _onBack requires navigator property to function. Either modify the onBack prop or pass a navigator prop',
538 );
539 }
540 }
541
542 /**
543 * Calculate the time to show in the timer area
544 * based on if they want to see time remaining
545 * or duration. Formatted to look as 00:00.
546 */
547 calculateTime() {
548 if (this.state.showTimeRemaining) {
549 const time = this.state.duration - this.state.currentTime;
550 return `-${this.formatTime(time)}`;
551 }
552
553 return this.formatTime(this.state.currentTime);
554 }
555
556 /**
557 * Format a time string as mm:ss
558 *
559 * @param {int} time time in milliseconds
560 * @return {string} formatted time string in mm:ss format
561 */
562 formatTime(time = 0) {
563 const symbol = this.state.showRemainingTime ? '-' : '';
564 time = Math.min(Math.max(time, 0), this.state.duration);
565
566 const formattedMinutes = Math.floor(time / 60).toFixed(0);
567 const formattedSeconds = Math.floor(time % 60).toFixed(0);
568
569 return `${symbol}${formattedMinutes}:${formattedSeconds}`;
570 }
571
572 /**
573 * Set the position of the seekbar's components
574 * (both fill and handle) according to the
575 * position supplied.
576 *
577 * @param {float} position position in px of seeker handle}
578 */
579 setSeekerPosition(position = 0) {
580 let state = this.state;
581 position = this.constrainToSeekerMinMax(position);
582
583 state.seekerFillWidth = position;
584 state.seekerPosition = position;
585
586 if (!state.seeking) {
587 state.seekerOffset = position;
588 }
589
590 this.setState(state);
591 }
592
593 /**
594 * Constrain the location of the seeker to the
595 * min/max value based on how big the
596 * seeker is.
597 *
598 * @param {float} val position of seeker handle in px
599 * @return {float} constrained position of seeker handle in px
600 */
601 constrainToSeekerMinMax(val = 0) {
602 if (val <= 0) {
603 return 0;
604 } else if (val >= this.player.seekerWidth) {
605 return this.player.seekerWidth;
606 }
607 return val;
608 }
609
610 /**
611 * Calculate the position that the seeker should be
612 * at along its track.
613 *
614 * @return {float} position of seeker handle in px based on currentTime
615 */
616 calculateSeekerPosition() {
617 const percent = this.state.currentTime / this.state.duration;
618 return this.player.seekerWidth * percent;
619 }
620
621 /**
622 * Return the time that the video should be at
623 * based on where the seeker handle is.
624 *
625 * @return {float} time in ms based on seekerPosition.
626 */
627 calculateTimeFromSeekerPosition() {
628 const percent = this.state.seekerPosition / this.player.seekerWidth;
629 return this.state.duration * percent;
630 }
631
632 /**
633 * Seek to a time in the video.
634 *
635 * @param {float} time time to seek to in ms
636 */
637 seekTo(time = 0) {
638 let state = this.state;
639 state.currentTime = time;
640 this.player.ref.seek(time);
641 this.setState(state);
642 }
643
644 /**
645 * Set the position of the volume slider
646 *
647 * @param {float} position position of the volume handle in px
648 */
649 setVolumePosition(position = 0) {
650 let state = this.state;
651 position = this.constrainToVolumeMinMax(position);
652 state.volumePosition = position + this.player.iconOffset;
653 state.volumeFillWidth = position;
654
655 state.volumeTrackWidth = this.player.volumeWidth - state.volumeFillWidth;
656
657 if (state.volumeFillWidth < 0) {
658 state.volumeFillWidth = 0;
659 }
660
661 if (state.volumeTrackWidth > 150) {
662 state.volumeTrackWidth = 150;
663 }
664
665 this.setState(state);
666 }
667
668 /**
669 * Constrain the volume bar to the min/max of
670 * its track's width.
671 *
672 * @param {float} val position of the volume handle in px
673 * @return {float} contrained position of the volume handle in px
674 */
675 constrainToVolumeMinMax(val = 0) {
676 if (val <= 0) {
677 return 0;
678 } else if (val >= this.player.volumeWidth + 9) {
679 return this.player.volumeWidth + 9;
680 }
681 return val;
682 }
683
684 /**
685 * Get the volume based on the position of the
686 * volume object.
687 *
688 * @return {float} volume level based on volume handle position
689 */
690 calculateVolumeFromVolumePosition() {
691 return this.state.volumePosition / this.player.volumeWidth;
692 }
693
694 /**
695 * Get the position of the volume handle based
696 * on the volume
697 *
698 * @return {float} volume handle position in px based on volume
699 */
700 calculateVolumePositionFromVolume() {
701 return this.player.volumeWidth * this.state.volume;
702 }
703
704 /**
705 | -------------------------------------------------------
706 | React Component functions
707 | -------------------------------------------------------
708 |
709 | Here we're initializing our listeners and getting
710 | the component ready using the built-in React
711 | Component methods
712 |
713 */
714
715 /**
716 * Before mounting, init our seekbar and volume bar
717 * pan responders.
718 */
719 UNSAFE_componentWillMount() {
720 this.initSeekPanResponder();
721 this.initVolumePanResponder();
722 }
723
724 /**
725 * To allow basic playback management from the outside
726 * we have to handle possible props changes to state changes
727 */
728 UNSAFE_componentWillReceiveProps(nextProps) {
729 if (this.state.paused !== nextProps.paused) {
730 this.setState({
731 paused: nextProps.paused,
732 });
733 }
734
735 if (this.styles.videoStyle !== nextProps.videoStyle) {
736 this.styles.videoStyle = nextProps.videoStyle;
737 }
738
739 if (this.styles.containerStyle !== nextProps.style) {
740 this.styles.containerStyle = nextProps.style;
741 }
742 }
743
744 /**
745 * Upon mounting, calculate the position of the volume
746 * bar based on the volume property supplied to it.
747 */
748 componentDidMount() {
749 const position = this.calculateVolumePositionFromVolume();
750 let state = this.state;
751 this.setVolumePosition(position);
752 state.volumeOffset = position;
753 this.mounted = true;
754
755 this.setState(state);
756 }
757
758 /**
759 * When the component is about to unmount kill the
760 * timeout less it fire in the prev/next scene
761 */
762 componentWillUnmount() {
763 this.mounted = false;
764 this.clearControlTimeout();
765 }
766
767 /**
768 * Get our seekbar responder going
769 */
770 initSeekPanResponder() {
771 this.player.seekPanResponder = PanResponder.create({
772 // Ask to be the responder.
773 onStartShouldSetPanResponder: (evt, gestureState) => true,
774 onMoveShouldSetPanResponder: (evt, gestureState) => true,
775
776 /**
777 * When we start the pan tell the machine that we're
778 * seeking. This stops it from updating the seekbar
779 * position in the onProgress listener.
780 */
781 onPanResponderGrant: (evt, gestureState) => {
782 let state = this.state;
783 this.clearControlTimeout();
784 const position = evt.nativeEvent.locationX;
785 this.setSeekerPosition(position);
786 state.seeking = true;
787 state.originallyPaused = state.paused;
788 state.scrubbing = false;
789 if (this.player.scrubbingTimeStep > 0) {
790 state.paused = true;
791 }
792 this.setState(state);
793 },
794
795 /**
796 * When panning, update the seekbar position, duh.
797 */
798 onPanResponderMove: (evt, gestureState) => {
799 const position = this.state.seekerOffset + gestureState.dx;
800 this.setSeekerPosition(position);
801 let state = this.state;
802
803 if (this.player.scrubbingTimeStep > 0 && !state.loading && !state.scrubbing) {
804 const time = this.calculateTimeFromSeekerPosition();
805 const timeDifference = Math.abs(state.currentTime - time) * 1000;
806
807 if (time < state.duration && timeDifference >= this.player.scrubbingTimeStep) {
808 state.scrubbing = true;
809
810 this.setState(state);
811 setTimeout(() => {
812 this.player.ref.seek(time, this.player.scrubbingTimeStep);
813 }, 1);
814 }
815 }
816 },
817
818 /**
819 * On release we update the time and seek to it in the video.
820 * If you seek to the end of the video we fire the
821 * onEnd callback
822 */
823 onPanResponderRelease: (evt, gestureState) => {
824 const time = this.calculateTimeFromSeekerPosition();
825 let state = this.state;
826 if (time >= state.duration && !state.loading) {
827 state.paused = true;
828 this.events.onEnd();
829 } else if (state.scrubbing) {
830 state.seeking = false;
831 } else {
832 this.seekTo(time);
833 this.setControlTimeout();
834 state.paused = state.originallyPaused;
835 state.seeking = false;
836 }
837 this.setState(state);
838 },
839 });
840 }
841
842 /**
843 * Initialize the volume pan responder.
844 */
845 initVolumePanResponder() {
846 this.player.volumePanResponder = PanResponder.create({
847 onStartShouldSetPanResponder: (evt, gestureState) => true,
848 onMoveShouldSetPanResponder: (evt, gestureState) => true,
849 onPanResponderGrant: (evt, gestureState) => {
850 this.clearControlTimeout();
851 },
852
853 /**
854 * Update the volume as we change the position.
855 * If we go to 0 then turn on the mute prop
856 * to avoid that weird static-y sound.
857 */
858 onPanResponderMove: (evt, gestureState) => {
859 let state = this.state;
860 const position = this.state.volumeOffset + gestureState.dx;
861
862 this.setVolumePosition(position);
863 state.volume = this.calculateVolumeFromVolumePosition();
864
865 if (state.volume <= 0) {
866 state.muted = true;
867 } else {
868 state.muted = false;
869 }
870
871 this.setState(state);
872 },
873
874 /**
875 * Update the offset...
876 */
877 onPanResponderRelease: (evt, gestureState) => {
878 let state = this.state;
879 state.volumeOffset = state.volumePosition;
880 this.setControlTimeout();
881 this.setState(state);
882 },
883 });
884 }
885
886 /**
887 | -------------------------------------------------------
888 | Rendering
889 | -------------------------------------------------------
890 |
891 | This section contains all of our render methods.
892 | In addition to the typical React render func
893 | we also have all the render methods for
894 | the controls.
895 |
896 */
897
898 /**
899 * Standard render control function that handles
900 * everything except the sliders. Adds a
901 * consistent <TouchableHighlight>
902 * wrapper and styling.
903 */
904 renderControl(children, callback, style = {}) {
905 return (
906 <TouchableHighlight
907 underlayColor="transparent"
908 activeOpacity={0.3}
909 onPress={() => {
910 this.resetControlTimeout();
911 callback();
912 }}
913 style={[VideoPlayerstyles.controls.control, style]}>
914 {children}
915 </TouchableHighlight>
916 );
917 }
918
919 /**
920 * Renders an empty control, used to disable a control without breaking the view layout.
921 */
922 renderNullControl() {
923 return <View style={[VideoPlayerstyles.controls.control]} />;
924 }
925
926 /**
927 * Groups the top bar controls together in an animated
928 * view and spaces them out.
929 */
930 renderTopControls() {
931 const backControl = this.props.disableBack
932 ? this.renderNullControl()
933 : this.renderBack();
934 const volumeControl = this.props.disableVolume
935 ? this.renderNullControl()
936 : this.renderVolume();
937 const fullscreenControl = this.props.disableFullscreen
938 ? this.renderNullControl()
939 : this.renderFullscreen();
940
941 return (
942 <Animated.View
943 style={[
944 VideoPlayerstyles.controls.top,
945 {
946 opacity: this.animations.topControl.opacity,
947 marginTop: this.animations.topControl.marginTop,
948 },
949 ]}>
950 <ImageBackground
951 source={require('./images/icons/top-vignette.png')}
952 style={[VideoPlayerstyles.controls.column]}
953 imageStyle={[VideoPlayerstyles.controls.vignette]}>
954 <SafeAreaView style={VideoPlayerstyles.controls.topControlGroup}>
955 {backControl}
956 <View style={VideoPlayerstyles.controls.pullRight}>
957 {volumeControl}
958 {fullscreenControl}
959 </View>
960 </SafeAreaView>
961 </ImageBackground>
962 </Animated.View>
963 );
964 }
965
966 /**
967 * Back button control
968 */
969 renderBack() {
970 return this.renderControl(
971 <Image
972 source={require('./images/icons/back.png')}
973 style={VideoPlayerstyles.controls.back}
974 />,
975 this.events.onBack,
976 VideoPlayerstyles.controls.back,
977 );
978 }
979
980 /**
981 * Render the volume slider and attach the pan handlers
982 */
983 renderVolume() {
984 return (
985 <View style={VideoPlayerstyles.volume.container}>
986 <View
987 style={[VideoPlayerstyles.volume.fill, { width: this.state.volumeFillWidth }]}
988 />
989 <View
990 style={[VideoPlayerstyles.volume.track, { width: this.state.volumeTrackWidth }]}
991 />
992 <View
993 style={[VideoPlayerstyles.volume.handle, { left: this.state.volumePosition }]}
994 {...this.player.volumePanResponder.panHandlers}>
995 <Image
996 style={VideoPlayerstyles.volume.icon}
997 source={require('./images/icons/volume.png')}
998 />
999 </View>
1000 </View>
1001 );
1002 }
1003
1004 /**
1005 * Render fullscreen toggle and set icon based on the fullscreen state.
1006 */
1007 renderFullscreen() {
1008 let source =
1009 this.state.isFullscreen === true
1010 ? require('./images/icons/shrink.png')
1011 : require('./images/icons/expand.png');
1012 return this.renderControl(
1013 <Image source={source} />,
1014 this.methods.toggleFullscreen,
1015 VideoPlayerstyles.controls.fullscreen,
1016 );
1017 }
1018
1019 /**
1020 * Render bottom control group and wrap it in a holder
1021 */
1022 renderBottomControls() {
1023 const timerControl = this.props.disableTimer
1024 ? this.renderNullControl()
1025 : this.renderTimer();
1026 const seekbarControl = this.props.disableSeekbar
1027 ? this.renderNullControl()
1028 : this.renderSeekbar();
1029 const playPauseControl = this.props.disablePlayPause
1030 ? this.renderNullControl()
1031 : this.renderPlayPause();
1032
1033 return (
1034 <Animated.View
1035 style={[
1036 VideoPlayerstyles.controls.bottom,
1037 {
1038 opacity: this.animations.bottomControl.opacity,
1039 marginBottom: this.animations.bottomControl.marginBottom,
1040 },
1041 ]}>
1042 <ImageBackground
1043 source={require('./images/icons/bottom-vignette.png')}
1044 style={[VideoPlayerstyles.controls.column]}
1045 imageStyle={[VideoPlayerstyles.controls.vignette]}>
1046 {seekbarControl}
1047 <SafeAreaView
1048 style={[VideoPlayerstyles.controls.row, VideoPlayerstyles.controls.bottomControlGroup]}>
1049 {playPauseControl}
1050 {this.renderTitle()}
1051 {timerControl}
1052 </SafeAreaView>
1053 </ImageBackground>
1054 </Animated.View>
1055 );
1056 }
1057
1058 /**
1059 * Render the seekbar and attach its handlers
1060 */
1061 renderSeekbar() {
1062 return (
1063 <View
1064 style={VideoPlayerstyles.seekbar.container}
1065 collapsable={false}
1066 {...this.player.seekPanResponder.panHandlers}>
1067 <View
1068 style={VideoPlayerstyles.seekbar.track}
1069 onLayout={event =>
1070 (this.player.seekerWidth = event.nativeEvent.layout.width)
1071 }
1072 pointerEvents={'none'}>
1073 <View
1074 style={[
1075 VideoPlayerstyles.seekbar.fill,
1076 {
1077 width: this.state.seekerFillWidth,
1078 backgroundColor: this.props.seekColor || '#FFF',
1079 },
1080 ]}
1081 pointerEvents={'none'}
1082 />
1083 </View>
1084 <View
1085 style={[VideoPlayerstyles.seekbar.handle, { left: this.state.seekerPosition }]}
1086 pointerEvents={'none'}>
1087 <View
1088 style={[
1089 VideoPlayerstyles.seekbar.circle,
1090 { backgroundColor: this.props.seekColor || '#FFF' },
1091 ]}
1092 pointerEvents={'none'}
1093 />
1094 </View>
1095 </View>
1096 );
1097 }
1098
1099 /**
1100 * Render the play/pause button and show the respective icon
1101 */
1102 renderPlayPause() {
1103 let source =
1104 this.state.paused === true
1105 ? require('./images/icons/play.png')
1106 : require('./images/icons/pause.png');
1107 return this.renderControl(
1108 <Image source={source} />,
1109 this.methods.togglePlayPause,
1110 VideoPlayerstyles.controls.playPause,
1111 );
1112 }
1113
1114 /**
1115 * Render our title...if supplied.
1116 */
1117 renderTitle() {
1118 if (this.opts.title) {
1119 return (
1120 <View style={[VideoPlayerstyles.controls.control, VideoPlayerstyles.controls.title]}>
1121 <Text
1122 style={[VideoPlayerstyles.controls.text, VideoPlayerstyles.controls.titleText]}
1123 numberOfLines={1}>
1124 {this.opts.title || ''}
1125 </Text>
1126 </View>
1127 );
1128 }
1129
1130 return null;
1131 }
1132
1133 /**
1134 * Show our timer.
1135 */
1136 renderTimer() {
1137 return this.renderControl(
1138 <Text style={VideoPlayerstyles.controls.timerText}>{this.calculateTime()}</Text>,
1139 this.methods.toggleTimer,
1140 VideoPlayerstyles.controls.timer,
1141 );
1142 }
1143
1144 /**
1145 * Show loading icon
1146 */
1147 renderLoader() {
1148 if (this.state.loading) {
1149 return (
1150 <View style={VideoPlayerstyles.loader.container}>
1151 <Animated.Image
1152 source={require('./images/icons/loader-icon.png')}
1153 style={[
1154 VideoPlayerstyles.loader.icon,
1155 {
1156 transform: [
1157 {
1158 rotate: this.animations.loader.rotate.interpolate({
1159 inputRange: [0, 360],
1160 outputRange: ['0deg', '360deg'],
1161 }),
1162 },
1163 ],
1164 },
1165 ]}
1166 />
1167 </View>
1168 );
1169 }
1170 return null;
1171 }
1172
1173 renderError() {
1174 if (this.state.error) {
1175 return (
1176
1177 <SafeAreaView style={VideoPlayerstyles.error.container}>
1178 <TouchableOpacity onPress={() => this.reloadPlayer()}>
1179 <View style={{ justifyContent: 'center', alignItems: 'center', }}>
1180 <Image
1181 source={require('./images/icons/error-icon.png')}
1182 style={VideoPlayerstyles.error.icon}
1183 />
1184 <Text style={VideoPlayerstyles.error.text}>Video unavailable</Text>
1185 <Text style={VideoPlayerstyles.error.text}>Click here to reload</Text>
1186 </View>
1187 </TouchableOpacity>
1188 </SafeAreaView>
1189 );
1190 }
1191 return null;
1192 }
1193
1194 async reloadPlayer() {
1195 this.setState({ source: null });
1196 this.setState({ source: this.props.source, error: false });
1197 }
1198
1199 async playVideo() {
1200 this.setState({ player: false, paused: false });
1201 typeof this.events.onPlay === 'function' && this.events.onPlay();
1202 }
1203
1204 /**
1205 * Provide all of our options and render the whole component.
1206 */
1207 render() {
1208 return (
1209 <TouchableWithoutFeedback
1210 onPress={this.events.onScreenTouch}
1211 style={[VideoPlayerstyles.player.container, this.styles.containerStyle]}>
1212 {
1213 this.state.player ? (
1214 <ImageBackground resizeMode='cover' source={{ uri: this.state.thumbnail }} style={{
1215 height: screen.height / 3,
1216 width: '100%'
1217 }} >
1218 <View style={{
1219 backgroundColor: 'rgba(0,0,0,0.3)',
1220 height: '100%',
1221 width: '100%'
1222 }}>
1223 <View style={{
1224 flex: 1,
1225 justifyContent: 'center',
1226 alignItems: 'center',
1227 }}>
1228 <TouchableOpacity onPress={() => this.playVideo()}><Icon name="play-circle-outline" size={50} color="#6200ee" /></TouchableOpacity>
1229 </View>
1230 </View>
1231 </ImageBackground>
1232 ) : (
1233 <View style={[VideoPlayerstyles.player.container, this.styles.containerStyle]}>
1234 <Video
1235 {...this.props}
1236 ref={videoPlayer => (this.player.ref = videoPlayer)}
1237 resizeMode={this.state.resizeMode}
1238 volume={this.state.volume}
1239 removeClippedSubviews={false}
1240 paused={this.state.paused}
1241 muted={this.state.muted}
1242 rate={this.state.rate}
1243 onLoadStart={this.events.onLoadStart}
1244 onProgress={this.events.onProgress}
1245 onError={this.events.onError}
1246 onLoad={this.events.onLoad}
1247 onEnd={this.events.onEnd}
1248 onSeek={this.events.onSeek}
1249 style={[VideoPlayerstyles.player.video, this.styles.videoStyle]}
1250 source={this.state.source}
1251 />
1252
1253 { this.state.error ? this.renderError() : null}
1254 { this.state.error == false ? this.renderLoader() : null}
1255 { this.state.error == false ? this.renderTopControls() : null}
1256 { this.state.error == false ? this.renderBottomControls() : null}
1257 </View>
1258 )
1259 }
1260 </TouchableWithoutFeedback>
1261 );
1262 }
1263}
1264
1265const VideoPlayerstyles = {
1266 player: StyleSheet.create({
1267 container: {
1268 overflow: 'hidden',
1269 backgroundColor: '#000',
1270 flex: 1,
1271 alignSelf: 'stretch',
1272 justifyContent: 'space-between',
1273 },
1274 video: {
1275 overflow: 'hidden',
1276 position: 'absolute',
1277 top: 0,
1278 right: 0,
1279 bottom: 0,
1280 left: 0,
1281 },
1282 }),
1283 error: StyleSheet.create({
1284 container: {
1285 backgroundColor: 'rgba( 0, 0, 0, 0.5 )',
1286 position: 'absolute',
1287 top: 0,
1288 right: 0,
1289 bottom: 0,
1290 left: 0,
1291 justifyContent: 'center',
1292 alignItems: 'center',
1293 },
1294 icon: {
1295 marginBottom: 16,
1296 },
1297 text: {
1298 backgroundColor: 'transparent',
1299 color: '#f27474',
1300 },
1301 reloadtext: {
1302 backgroundColor: 'transparent',
1303 color: '#ffffff',
1304 },
1305 }),
1306 loader: StyleSheet.create({
1307 container: {
1308 position: 'absolute',
1309 top: 0,
1310 right: 0,
1311 bottom: 0,
1312 left: 0,
1313 alignItems: 'center',
1314 justifyContent: 'center',
1315 },
1316 }),
1317 controls: StyleSheet.create({
1318 row: {
1319 flexDirection: 'row',
1320 alignItems: 'center',
1321 justifyContent: 'space-between',
1322 height: null,
1323 width: null,
1324 },
1325 column: {
1326 flexDirection: 'column',
1327 alignItems: 'center',
1328 justifyContent: 'space-between',
1329 height: null,
1330 width: null,
1331 },
1332 vignette: {
1333 resizeMode: 'stretch',
1334 },
1335 control: {
1336 padding: 16,
1337 },
1338 text: {
1339 backgroundColor: 'transparent',
1340 color: '#FFF',
1341 fontSize: 14,
1342 textAlign: 'center',
1343 },
1344 pullRight: {
1345 flexDirection: 'row',
1346 alignItems: 'center',
1347 justifyContent: 'center',
1348 },
1349 top: {
1350 flex: 1,
1351 alignItems: 'stretch',
1352 justifyContent: 'flex-start',
1353 },
1354 bottom: {
1355 alignItems: 'stretch',
1356 flex: 2,
1357 justifyContent: 'flex-end',
1358 },
1359 topControlGroup: {
1360 alignSelf: 'stretch',
1361 alignItems: 'center',
1362 justifyContent: 'space-between',
1363 flexDirection: 'row',
1364 width: null,
1365 margin: 12,
1366 marginBottom: 18,
1367 },
1368 bottomControlGroup: {
1369 alignSelf: 'stretch',
1370 alignItems: 'center',
1371 justifyContent: 'space-between',
1372 marginLeft: 12,
1373 marginRight: 12,
1374 marginBottom: 0,
1375 },
1376 volume: {
1377 flexDirection: 'row',
1378 },
1379 fullscreen: {
1380 flexDirection: 'row',
1381 },
1382 playPause: {
1383 position: 'relative',
1384 width: 80,
1385 zIndex: 0,
1386 },
1387 title: {
1388 alignItems: 'center',
1389 flex: 0.6,
1390 flexDirection: 'column',
1391 padding: 0,
1392 },
1393 titleText: {
1394 textAlign: 'center',
1395 },
1396 timer: {
1397 width: 80,
1398 },
1399 timerText: {
1400 backgroundColor: 'transparent',
1401 color: '#FFF',
1402 fontSize: 12,
1403 textAlign: 'right',
1404 },
1405 }),
1406 volume: StyleSheet.create({
1407 container: {
1408 alignItems: 'center',
1409 justifyContent: 'flex-start',
1410 flexDirection: 'row',
1411 height: 1,
1412 marginLeft: 20,
1413 marginRight: 20,
1414 width: 150,
1415 },
1416 track: {
1417 backgroundColor: '#333',
1418 height: 1,
1419 marginLeft: 7,
1420 },
1421 fill: {
1422 backgroundColor: '#FFF',
1423 height: 1,
1424 },
1425 handle: {
1426 position: 'absolute',
1427 marginTop: -24,
1428 marginLeft: -24,
1429 padding: 16,
1430 },
1431 icon: {
1432 marginLeft: 7,
1433 },
1434 }),
1435 seekbar: StyleSheet.create({
1436 container: {
1437 alignSelf: 'stretch',
1438 height: 28,
1439 marginLeft: 20,
1440 marginRight: 20,
1441 },
1442 track: {
1443 backgroundColor: '#333',
1444 height: 1,
1445 position: 'relative',
1446 top: 14,
1447 width: '100%',
1448 },
1449 fill: {
1450 backgroundColor: '#FFF',
1451 height: 1,
1452 width: '100%',
1453 },
1454 handle: {
1455 position: 'absolute',
1456 marginLeft: -7,
1457 height: 28,
1458 width: 28,
1459 },
1460 circle: {
1461 borderRadius: 12,
1462 position: 'relative',
1463 top: 8,
1464 left: 8,
1465 height: 12,
1466 width: 12,
1467 },
1468 }),
1469};

For icons visit and clone repo https://github.com/infinitbility/react-native-video-custom-controls

VideoPlayer usages example

Here, example to use custom videoplayer component.

1/**
2 *
3 * setup & Manage video
4 *
5 * @param {*} data
6 * @param {*} index
7 * @returns videoplayer
8 */
9setupVideoPlayer(data, index) {
10 let imageUrl = base_url + data.image;
11
12 return (
13 <View style={styles.videoLayout}>
14 <VideoPlayer
15 source={{ uri: base_url + data.video }}
16 navigator={this.props.navigator}
17 tapAnywhereToPause={false}
18 toggleResizeModeOnFullscreen={false}
19 isFullScreen={false}
20 thumbnail={imageUrl}
21 disableBack={true}
22 disableVolume={true}
23 controlTimeout={5000}
24 paused={this.state.paused}
25 seekColor={'#576CEC'}
26 />
27 </View>
28 )
29}

Thanks For Reading…

Looking For React Native Tutorial?

we are trying to create the best Tutorial for react native developers.

When you want a daily updates about React Native Tutorial or infinitbility update subscribe to our newsletter.

Read React Native Tutorial

Request New Tutorial or Article on mail [email protected]

Join our email list and get notified about new content

No worries, I respect your privacy and I will never abuse your email.

Every week, on Tuesday, you will receive a list of free tutorials I made during the week (I write one every day) and news on other training products I create.

More articles from Infinitbility

how to make bootable pendrive for win 10

how to make bootable pendrive for win 10

how to make bootable pendrive

April 23rd, 2021 · 1 min read
how to download free windows 10 iso file

how to download free windows 10 iso file

Download free windows 10 iso file

April 22nd, 2021 · 1 min read
© 2020–2021 Infinitbility
Disclaimer
Link to $https://medium.com/infinitbilityLink to $https://www.facebook.com/InfinitbilityLink to $https://github.com/infinitbilityLink to $https://twitter.com/infinitbilityLink to $https://www.buymeacoffee.com/infinitbilityLink to $mailto:[email protected]