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';1920const 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 };3738 constructor(props) {39 super(props);4041 /**42 * All of our values that are updated by the43 * methods and listeners in this class44 */45 this.state = {46 // Video47 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 // Controls5455 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 };7576 /**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 };8586 /**87 * Our app listeners and associated methods88 */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 };105106 /**107 * Functions used throughout the application108 */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 };115116 /**117 * Player information118 */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 };132133 /**134 * Various animations135 */136 const initialValue = this.props.showOnStart ? 1 : 0;137138 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 };155156 /**157 * Various styles that be added...158 */159 this.styles = {160 videoStyle: this.props.videoStyle || {},161 containerStyle: this.props.style || {},162 };163 }164165 componentWillReceiveProps(nextProps) {166167 this.setState({168 paused: nextProps.paused,169 });170 }171172 /**173 | -------------------------------------------------------174 | Events175 | -------------------------------------------------------176 |177 | These are the events that the <Video> component uses178 | and can be overridden by assigning it as a prop.179 | It is suggested that you override onEnd.180 |181 */182183 /**184 * When load starts we display a loading icon185 * and show the controls.186 */187 _onLoadStart() {188 let state = this.state;189 state.loading = true;190 this.loadAnimation();191 this.setState(state);192193 if (typeof this.props.onLoadStart === 'function') {194 this.props.onLoadStart(...arguments);195 }196 }197198 /**199 * When load is finished we hide the load icon200 * and hide the controls. We also set the201 * video duration.202 *203 * @param {object} data The video meta data204 */205 _onLoad(data = {}) {206 let state = this.state;207208 state.duration = data.duration;209 state.loading = false;210 this.setState(state);211212 if (state.showControls) {213 this.setControlTimeout();214 }215216 if (typeof this.props.onLoad === 'function') {217 this.props.onLoad(...arguments);218 }219 }220221 /**222 * For onprogress we fire listeners that223 * update our seekbar and timer.224 *225 * @param {object} data The video meta data226 */227 _onProgress(data = {}) {228 let state = this.state;229 if (!state.scrubbing) {230 state.currentTime = data.currentTime;231232 if (!state.seeking) {233 const position = this.calculateSeekerPosition();234 this.setSeekerPosition(position);235 }236237 if (typeof this.props.onProgress === 'function') {238 this.props.onProgress(...arguments);239 }240241 this.setState(state);242 }243 }244245 /**246 * For onSeek we clear scrubbing if set.247 *248 * @param {object} data The video meta data249 */250 _onSeek(data = {}) {251 let state = this.state;252 if (state.scrubbing) {253 state.scrubbing = false;254 state.currentTime = data.currentTime;255256 // Seeking may be false here if the user released the seek bar while the player was still processing257 // 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 }262263 this.setState(state);264 }265 }266267 /**268 * It is suggested that you override this269 * command so your app knows what to do.270 * Either close the video or go to a271 * new page.272 */273 _onEnd() { }274275 /**276 * Set the error state to true which then277 * changes our renderError function278 *279 * @param {object} err Err obj returned from <Video> component280 */281 _onError(err) {282 let state = this.state;283 state.error = true;284 state.loading = false;285286 this.setState(state);287 }288289 /**290 * This is a single and double tap listener291 * 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 }317318 /**319 | -------------------------------------------------------320 | Methods321 | -------------------------------------------------------322 |323 | These are all of our functions that interact with324 | various parts of the class. Anything from325 | calculating time remaining in a video326 | to handling control operations.327 |328 */329330 /**331 * Set a timeout when the controls are shown332 * that hides them after a length of time.333 * Default is 15s334 */335 setControlTimeout() {336 this.player.controlTimeout = setTimeout(() => {337 this._hideControls();338 }, this.player.controlTimeoutDelay);339 }340341 /**342 * Clear the hide controls timeout.343 */344 clearControlTimeout() {345 clearTimeout(this.player.controlTimeout);346 }347348 /**349 * Reset the timer completely350 */351 resetControlTimeout() {352 this.clearControlTimeout();353 this.setControlTimeout();354 }355356 /**357 * Animation to hide controls. We fade the358 * display to 0 then move them off the359 * screen so they're not interactable360 */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 }385386 /**387 * Animation to show controls...opposite of388 * above...move onto the screen and then389 * 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 }415416 /**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 }437438 /**439 * Function to hide the controls. Sets our440 * state then calls the animation.441 */442 _hideControls() {443 if (this.mounted) {444 let state = this.state;445 state.showControls = false;446 this.hideControlAnimation();447448 this.setState(state);449 }450 }451452 /**453 * Function to toggle controls based on454 * current state.455 */456 _toggleControls() {457 let state = this.state;458 state.showControls = !state.showControls;459460 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 }471472 this.setState(state);473 }474475 /**476 * Toggle fullscreen changes resizeMode on477 * the <Video> component then updates the478 * isFullscreen state.479 */480 _toggleFullscreen() {481 let state = this.state;482483 state.isFullscreen = !state.isFullscreen;484485486 if (this.props.toggleResizeModeOnFullscreen) {487 state.resizeMode = state.isFullscreen === true ? 'cover' : 'contain';488 }489490 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 }497498 this.setState(state);499 }500501 /**502 * Toggle playing state on <Video> component503 */504 _togglePlayPause() {505 let state = this.state;506 state.paused = !state.paused;507508 if (state.paused) {509 typeof this.events.onPause === 'function' && this.events.onPause();510 } else {511 typeof this.events.onPlay === 'function' && this.events.onPlay();512 }513514 this.setState(state);515 }516517 /**518 * Toggle between showing time remaining or519 * video duration in the timer control520 */521 _toggleTimer() {522 let state = this.state;523 state.showTimeRemaining = !state.showTimeRemaining;524 this.setState(state);525 }526527 /**528 * The default 'onBack' function pops the navigator529 * and as such the video player requires a530 * 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 }541542 /**543 * Calculate the time to show in the timer area544 * based on if they want to see time remaining545 * 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 }552553 return this.formatTime(this.state.currentTime);554 }555556 /**557 * Format a time string as mm:ss558 *559 * @param {int} time time in milliseconds560 * @return {string} formatted time string in mm:ss format561 */562 formatTime(time = 0) {563 const symbol = this.state.showRemainingTime ? '-' : '';564 time = Math.min(Math.max(time, 0), this.state.duration);565566 const formattedMinutes = Math.floor(time / 60).toFixed(0);567 const formattedSeconds = Math.floor(time % 60).toFixed(0);568569 return `${symbol}${formattedMinutes}:${formattedSeconds}`;570 }571572 /**573 * Set the position of the seekbar's components574 * (both fill and handle) according to the575 * 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);582583 state.seekerFillWidth = position;584 state.seekerPosition = position;585586 if (!state.seeking) {587 state.seekerOffset = position;588 }589590 this.setState(state);591 }592593 /**594 * Constrain the location of the seeker to the595 * min/max value based on how big the596 * seeker is.597 *598 * @param {float} val position of seeker handle in px599 * @return {float} constrained position of seeker handle in px600 */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 }609610 /**611 * Calculate the position that the seeker should be612 * at along its track.613 *614 * @return {float} position of seeker handle in px based on currentTime615 */616 calculateSeekerPosition() {617 const percent = this.state.currentTime / this.state.duration;618 return this.player.seekerWidth * percent;619 }620621 /**622 * Return the time that the video should be at623 * 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 }631632 /**633 * Seek to a time in the video.634 *635 * @param {float} time time to seek to in ms636 */637 seekTo(time = 0) {638 let state = this.state;639 state.currentTime = time;640 this.player.ref.seek(time);641 this.setState(state);642 }643644 /**645 * Set the position of the volume slider646 *647 * @param {float} position position of the volume handle in px648 */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;654655 state.volumeTrackWidth = this.player.volumeWidth - state.volumeFillWidth;656657 if (state.volumeFillWidth < 0) {658 state.volumeFillWidth = 0;659 }660661 if (state.volumeTrackWidth > 150) {662 state.volumeTrackWidth = 150;663 }664665 this.setState(state);666 }667668 /**669 * Constrain the volume bar to the min/max of670 * its track's width.671 *672 * @param {float} val position of the volume handle in px673 * @return {float} contrained position of the volume handle in px674 */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 }683684 /**685 * Get the volume based on the position of the686 * volume object.687 *688 * @return {float} volume level based on volume handle position689 */690 calculateVolumeFromVolumePosition() {691 return this.state.volumePosition / this.player.volumeWidth;692 }693694 /**695 * Get the position of the volume handle based696 * on the volume697 *698 * @return {float} volume handle position in px based on volume699 */700 calculateVolumePositionFromVolume() {701 return this.player.volumeWidth * this.state.volume;702 }703704 /**705 | -------------------------------------------------------706 | React Component functions707 | -------------------------------------------------------708 |709 | Here we're initializing our listeners and getting710 | the component ready using the built-in React711 | Component methods712 |713 */714715 /**716 * Before mounting, init our seekbar and volume bar717 * pan responders.718 */719 UNSAFE_componentWillMount() {720 this.initSeekPanResponder();721 this.initVolumePanResponder();722 }723724 /**725 * To allow basic playback management from the outside726 * we have to handle possible props changes to state changes727 */728 UNSAFE_componentWillReceiveProps(nextProps) {729 if (this.state.paused !== nextProps.paused) {730 this.setState({731 paused: nextProps.paused,732 });733 }734735 if (this.styles.videoStyle !== nextProps.videoStyle) {736 this.styles.videoStyle = nextProps.videoStyle;737 }738739 if (this.styles.containerStyle !== nextProps.style) {740 this.styles.containerStyle = nextProps.style;741 }742 }743744 /**745 * Upon mounting, calculate the position of the volume746 * 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;754755 this.setState(state);756 }757758 /**759 * When the component is about to unmount kill the760 * timeout less it fire in the prev/next scene761 */762 componentWillUnmount() {763 this.mounted = false;764 this.clearControlTimeout();765 }766767 /**768 * Get our seekbar responder going769 */770 initSeekPanResponder() {771 this.player.seekPanResponder = PanResponder.create({772 // Ask to be the responder.773 onStartShouldSetPanResponder: (evt, gestureState) => true,774 onMoveShouldSetPanResponder: (evt, gestureState) => true,775776 /**777 * When we start the pan tell the machine that we're778 * seeking. This stops it from updating the seekbar779 * 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 },794795 /**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;802803 if (this.player.scrubbingTimeStep > 0 && !state.loading && !state.scrubbing) {804 const time = this.calculateTimeFromSeekerPosition();805 const timeDifference = Math.abs(state.currentTime - time) * 1000;806807 if (time < state.duration && timeDifference >= this.player.scrubbingTimeStep) {808 state.scrubbing = true;809810 this.setState(state);811 setTimeout(() => {812 this.player.ref.seek(time, this.player.scrubbingTimeStep);813 }, 1);814 }815 }816 },817818 /**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 the821 * onEnd callback822 */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 }841842 /**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 },852853 /**854 * Update the volume as we change the position.855 * If we go to 0 then turn on the mute prop856 * 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;861862 this.setVolumePosition(position);863 state.volume = this.calculateVolumeFromVolumePosition();864865 if (state.volume <= 0) {866 state.muted = true;867 } else {868 state.muted = false;869 }870871 this.setState(state);872 },873874 /**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 }885886 /**887 | -------------------------------------------------------888 | Rendering889 | -------------------------------------------------------890 |891 | This section contains all of our render methods.892 | In addition to the typical React render func893 | we also have all the render methods for894 | the controls.895 |896 */897898 /**899 * Standard render control function that handles900 * everything except the sliders. Adds a901 * consistent <TouchableHighlight>902 * wrapper and styling.903 */904 renderControl(children, callback, style = {}) {905 return (906 <TouchableHighlight907 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 }918919 /**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 }925926 /**927 * Groups the top bar controls together in an animated928 * view and spaces them out.929 */930 renderTopControls() {931 const backControl = this.props.disableBack932 ? this.renderNullControl()933 : this.renderBack();934 const volumeControl = this.props.disableVolume935 ? this.renderNullControl()936 : this.renderVolume();937 const fullscreenControl = this.props.disableFullscreen938 ? this.renderNullControl()939 : this.renderFullscreen();940941 return (942 <Animated.View943 style={[944 VideoPlayerstyles.controls.top,945 {946 opacity: this.animations.topControl.opacity,947 marginTop: this.animations.topControl.marginTop,948 },949 ]}>950 <ImageBackground951 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 }965966 /**967 * Back button control968 */969 renderBack() {970 return this.renderControl(971 <Image972 source={require('./images/icons/back.png')}973 style={VideoPlayerstyles.controls.back}974 />,975 this.events.onBack,976 VideoPlayerstyles.controls.back,977 );978 }979980 /**981 * Render the volume slider and attach the pan handlers982 */983 renderVolume() {984 return (985 <View style={VideoPlayerstyles.volume.container}>986 <View987 style={[VideoPlayerstyles.volume.fill, { width: this.state.volumeFillWidth }]}988 />989 <View990 style={[VideoPlayerstyles.volume.track, { width: this.state.volumeTrackWidth }]}991 />992 <View993 style={[VideoPlayerstyles.volume.handle, { left: this.state.volumePosition }]}994 {...this.player.volumePanResponder.panHandlers}>995 <Image996 style={VideoPlayerstyles.volume.icon}997 source={require('./images/icons/volume.png')}998 />999 </View>1000 </View>1001 );1002 }10031004 /**1005 * Render fullscreen toggle and set icon based on the fullscreen state.1006 */1007 renderFullscreen() {1008 let source =1009 this.state.isFullscreen === true1010 ? 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 }10181019 /**1020 * Render bottom control group and wrap it in a holder1021 */1022 renderBottomControls() {1023 const timerControl = this.props.disableTimer1024 ? this.renderNullControl()1025 : this.renderTimer();1026 const seekbarControl = this.props.disableSeekbar1027 ? this.renderNullControl()1028 : this.renderSeekbar();1029 const playPauseControl = this.props.disablePlayPause1030 ? this.renderNullControl()1031 : this.renderPlayPause();10321033 return (1034 <Animated.View1035 style={[1036 VideoPlayerstyles.controls.bottom,1037 {1038 opacity: this.animations.bottomControl.opacity,1039 marginBottom: this.animations.bottomControl.marginBottom,1040 },1041 ]}>1042 <ImageBackground1043 source={require('./images/icons/bottom-vignette.png')}1044 style={[VideoPlayerstyles.controls.column]}1045 imageStyle={[VideoPlayerstyles.controls.vignette]}>1046 {seekbarControl}1047 <SafeAreaView1048 style={[VideoPlayerstyles.controls.row, VideoPlayerstyles.controls.bottomControlGroup]}>1049 {playPauseControl}1050 {this.renderTitle()}1051 {timerControl}1052 </SafeAreaView>1053 </ImageBackground>1054 </Animated.View>1055 );1056 }10571058 /**1059 * Render the seekbar and attach its handlers1060 */1061 renderSeekbar() {1062 return (1063 <View1064 style={VideoPlayerstyles.seekbar.container}1065 collapsable={false}1066 {...this.player.seekPanResponder.panHandlers}>1067 <View1068 style={VideoPlayerstyles.seekbar.track}1069 onLayout={event =>1070 (this.player.seekerWidth = event.nativeEvent.layout.width)1071 }1072 pointerEvents={'none'}>1073 <View1074 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 <View1085 style={[VideoPlayerstyles.seekbar.handle, { left: this.state.seekerPosition }]}1086 pointerEvents={'none'}>1087 <View1088 style={[1089 VideoPlayerstyles.seekbar.circle,1090 { backgroundColor: this.props.seekColor || '#FFF' },1091 ]}1092 pointerEvents={'none'}1093 />1094 </View>1095 </View>1096 );1097 }10981099 /**1100 * Render the play/pause button and show the respective icon1101 */1102 renderPlayPause() {1103 let source =1104 this.state.paused === true1105 ? 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 }11131114 /**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 <Text1122 style={[VideoPlayerstyles.controls.text, VideoPlayerstyles.controls.titleText]}1123 numberOfLines={1}>1124 {this.opts.title || ''}1125 </Text>1126 </View>1127 );1128 }11291130 return null;1131 }11321133 /**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 }11431144 /**1145 * Show loading icon1146 */1147 renderLoader() {1148 if (this.state.loading) {1149 return (1150 <View style={VideoPlayerstyles.loader.container}>1151 <Animated.Image1152 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 }11721173 renderError() {1174 if (this.state.error) {1175 return (11761177 <SafeAreaView style={VideoPlayerstyles.error.container}>1178 <TouchableOpacity onPress={() => this.reloadPlayer()}>1179 <View style={{ justifyContent: 'center', alignItems: 'center', }}>1180 <Image1181 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 }11931194 async reloadPlayer() {1195 this.setState({ source: null });1196 this.setState({ source: this.props.source, error: false });1197 }11981199 async playVideo() {1200 this.setState({ player: false, paused: false });1201 typeof this.events.onPlay === 'function' && this.events.onPlay();1202 }12031204 /**1205 * Provide all of our options and render the whole component.1206 */1207 render() {1208 return (1209 <TouchableWithoutFeedback1210 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 <Video1235 {...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 />12521253 { 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}12641265const 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 video4 *5 * @param {*} data6 * @param {*} index7 * @returns videoplayer8 */9setupVideoPlayer(data, index) {10 let imageUrl = base_url + data.image;1112 return (13 <View style={styles.videoLayout}>14 <VideoPlayer15 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…
Follow me on Twitter
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.