├── .gitignore ├── .npmignore ├── LICENCE ├── README.md ├── assets ├── icons │ ├── player-pause@1x.png │ ├── player-pause@2x.png │ ├── player-pause@3x.png │ ├── player-play.png │ ├── player-play@2x.png │ ├── player-play@3x.png │ ├── player-restart.png │ ├── player-restart@2x.png │ └── player-restart@3x.png └── screen-ios.png ├── doc ├── custom-controls-bar.md ├── full-screen-player.md └── install.md ├── package.json ├── src ├── images │ └── index.ts ├── index.ts └── modules │ └── video-player │ ├── components │ ├── DefaultBottomControlsBar.tsx │ ├── DefaultMainControl.tsx │ ├── PlayerIcon.tsx │ ├── PlayerLoader.tsx │ └── VideoPlayer.tsx │ ├── hooks │ └── useVideoState.ts │ ├── index.ts │ ├── logic │ └── utils.ts │ └── types.ts ├── tsconfig.json ├── tslint.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .vscode/ -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Andréas Hanss 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | React Native True Sight 3 |

4 |

5 | 6 |

7 |

A cross-platform video player with customizable controls.

8 | 9 |
10 | 11 | This library provide a fully customisable video player that work both on Android and iOS. It also come with common use case documentation of things that you would like to implements. 12 | 13 | By default there are two controls slots that are displayed respectively on different part of the parent container and you can use default components provided by this library: 14 | 15 | - **Middle**. Contain by default a grid display two buttons: 16 | - One with play / pause alternating. 17 | - Another that will restart the video. 18 | - **Bottom**. Contain the video current time, a progress bar and the total duration. 19 | - **Loader**. There is also a loader that will trigger while video is charging (network issues, bootstraping, ...). 20 | 21 | ## Documentation 22 | 23 | - [Installation chapter](./doc/install.md) 24 | - [Render a FullScreen Video player](./doc/full-screen-player.md) 25 | - [Implement your own controls bar](./doc/custom-controls-bar.md) 26 | 27 | # Quick documentation 28 | 29 | This is simple as that. 30 | 31 | VideoPlayer ship around any video component, but fits well with react-video. In v2 you've total control on the video component. 32 | 33 | - **autoStart** - Whether or not the video should start when rendered (Default to true). 34 | - **mainControl** - The component used to render the main control bar, you can use the default one provided by this lib or your own. 35 | - **bottomControl** - The component used to render the bottom control bar, you can use the default one provided by this lib or your own. 36 | 37 | For advanced configuration, such as infinite loop, check the rest of the documentation and custom controls bar. 38 | 39 | ```jsx 40 | import React, { Component } from "react"; 41 | import { View } from "react-native"; 42 | import Video from "react-native-video"; 43 | import { VideoPlayer, DefaultMainControl, DefaultBottomControlsBar } from "react-native-true-sight"; 44 | 45 | export default class HomeScreen extends Component { 46 | render() { 47 | return ( 48 | } 51 | bottomControl={args => } 52 | > 53 | {args => ( 54 | 64 | ); 65 | } 66 | } 67 | ``` 68 | -------------------------------------------------------------------------------- /assets/icons/player-pause@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ScreamZ/react-native-true-sight/cd520331d0e590a7377f7b360bec64f4a56018ea/assets/icons/player-pause@1x.png -------------------------------------------------------------------------------- /assets/icons/player-pause@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ScreamZ/react-native-true-sight/cd520331d0e590a7377f7b360bec64f4a56018ea/assets/icons/player-pause@2x.png -------------------------------------------------------------------------------- /assets/icons/player-pause@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ScreamZ/react-native-true-sight/cd520331d0e590a7377f7b360bec64f4a56018ea/assets/icons/player-pause@3x.png -------------------------------------------------------------------------------- /assets/icons/player-play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ScreamZ/react-native-true-sight/cd520331d0e590a7377f7b360bec64f4a56018ea/assets/icons/player-play.png -------------------------------------------------------------------------------- /assets/icons/player-play@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ScreamZ/react-native-true-sight/cd520331d0e590a7377f7b360bec64f4a56018ea/assets/icons/player-play@2x.png -------------------------------------------------------------------------------- /assets/icons/player-play@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ScreamZ/react-native-true-sight/cd520331d0e590a7377f7b360bec64f4a56018ea/assets/icons/player-play@3x.png -------------------------------------------------------------------------------- /assets/icons/player-restart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ScreamZ/react-native-true-sight/cd520331d0e590a7377f7b360bec64f4a56018ea/assets/icons/player-restart.png -------------------------------------------------------------------------------- /assets/icons/player-restart@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ScreamZ/react-native-true-sight/cd520331d0e590a7377f7b360bec64f4a56018ea/assets/icons/player-restart@2x.png -------------------------------------------------------------------------------- /assets/icons/player-restart@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ScreamZ/react-native-true-sight/cd520331d0e590a7377f7b360bec64f4a56018ea/assets/icons/player-restart@3x.png -------------------------------------------------------------------------------- /assets/screen-ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ScreamZ/react-native-true-sight/cd520331d0e590a7377f7b360bec64f4a56018ea/assets/screen-ios.png -------------------------------------------------------------------------------- /doc/custom-controls-bar.md: -------------------------------------------------------------------------------- 1 | # Extending the controllers 2 | 3 | You can extends the default bar by providing your own bar component. I suggest to copy the default component in order to have an example. 4 | 5 | For that please clone this repository, and run `npm run build`, this will generate the `build` folder with the typescript compiled code. 6 | 7 | - `DefaultBottomControlsBar` is the bar of the bottom of the screen, with timers and timeline. 8 | - `DefaultMainControl` is the bar of the middle of the screen, with video controls buttons. 9 | 10 | ## Custom components injected props 11 | Both components receive the following props, you can also provide your own if you extends them. 12 | 13 | ```ts 14 | // Common interface 15 | interface InjectedControlProps { 16 | playCursorTime: number; 17 | videoTotalTime: number; 18 | videoPaused: boolean; 19 | videoLoading: boolean; 20 | setPlaying(): void; 21 | setPaused(): void; 22 | setPosition(to: number): void; 23 | } 24 | 25 | interface MainControlProps extends InjectedControlProps { 26 | restartButton?: boolean; 27 | } 28 | 29 | interface BottomControlProps extends InjectedControlProps { 30 | barColor?: string; 31 | joyStickColor?: string; 32 | navigationDisabled?: boolean; 33 | } 34 | ``` -------------------------------------------------------------------------------- /doc/full-screen-player.md: -------------------------------------------------------------------------------- 1 | 2 | # Handling fullscreen 3 | 4 | The best way to deal with that is to display a full screen modal and render a `VideoPlayer` component. 5 | 6 | Don't forget to add a background around your video. Here we use a black one. 7 | 8 | ```jsx 9 | import PropTypes from 'prop-types' 10 | import React, { PureComponent } from 'react' 11 | import { StatusBar, View } from 'react-native' 12 | import VideoPlayer from 'react-native-true-sight' 13 | import Immersive from 'react-native-immersive' 14 | 15 | import CloseButton from '../../Components/Common/CloseButton' 16 | 17 | export default class FullScreenVideoModal extends PureComponent { 18 | render() { 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | ) 26 | } 27 | } 28 | ``` 29 | 30 | # Android specific 31 | 32 | For cross compatibility this library doesn't ship with a tool that allow fullscreen on android. 33 | 34 | But you can easily implement this behavior using a full screen library like [React Native Immersive](https://github.com/mockingbot/react-native-immersive). 35 | 36 | Here is an example : 37 | 38 | ```jsx 39 | import PropTypes from 'prop-types' 40 | import React, { Component } from 'react' 41 | import { Platform, StatusBar, View } from 'react-native' 42 | import { VideoPlayer, DefaultMainControl, DefaultBottomControlsBar } from "react-native-true-sight"; 43 | import Immersive from 'react-native-immersive' 44 | import CloseButton from '../../Components/Common/CloseButton' 45 | 46 | export default class FullScreenVideoModal extends Component { 47 | constructor(props) { 48 | super(props) 49 | 50 | this.onClose = this.onClose.bind(this) 51 | } 52 | 53 | componentDidMount() { 54 | if (Platform.OS === 'android') Immersive.on() 55 | } 56 | 57 | componentWillUnmount() { 58 | if (Platform.OS === 'android') Immersive.off() 59 | } 60 | 61 | onClose() { 62 | // Dismiss modal 63 | } 64 | 65 | render() { 66 | // You just need to flex: 1 and display and overlay a close button with position: absolute; 67 | return ( 68 | 69 | 70 | 71 | } 74 | bottomControl={args => } 75 | > 76 | {args => ( 77 | 85 | )} 86 | 87 | 88 | ) 89 | } 90 | } 91 | ``` -------------------------------------------------------------------------------- /doc/install.md: -------------------------------------------------------------------------------- 1 | # Step 1 - Install library 2 | 3 | Run either `npm i react-native-true-sight --save` or `yarn add react-native-true-sight` 4 | 5 | # Step 2 - Install dependencies 6 | 7 | This library best fit with [React Native Video](https://github.com/react-native-community/react-native-video) and has a required dependency with [React Native Slider](https://github.com/react-native-community/react-native-slider). 8 | 9 | As in V2 everything is thinking modular, you can use other library but you'll need it for the default components. 10 | 11 | Please be sure to follow the doc in order to set-up those correctly. 12 | 13 | **Now you're ready to go!** 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-true-sight", 3 | "description": "A cross-platform video player with customizable controls for React Native.", 4 | "version": "2.0.0", 5 | "license": "MIT", 6 | "author": { 7 | "name": "Andréas HANSS", 8 | "url": "https://www.linkedin.com/in/andreas-hanss/" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/ScreamZ/react-native-true-sight.git" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/ScreamZ/react-native-true-sight/issues" 16 | }, 17 | "main": "build/index.js", 18 | "types": "build/index.d.ts", 19 | "scripts": { 20 | "test": "jest", 21 | "build": "tsc", 22 | "build:watch": "tsc --watch", 23 | "prepublishOnly": "tsc" 24 | }, 25 | "peerDependencies": { 26 | "@react-native-community/slider": "^3.0.3", 27 | "react": "*", 28 | "react-native": ">=0.60.0", 29 | "react-native-video": "^5.0.0" 30 | }, 31 | "devDependencies": { 32 | "@react-native-community/slider": "^3.0.3", 33 | "@types/react": "^16.9.49", 34 | "@types/react-native": "^0.63.17", 35 | "@types/react-native-video": "^5.0.1", 36 | "jest": "^22.2.2", 37 | "tslint": "^5.9.1", 38 | "typescript": "^4.0.2" 39 | }, 40 | "jest": { 41 | "preset": "react-native" 42 | }, 43 | "keywords": [ 44 | "react", 45 | "react-native", 46 | "video", 47 | "video-player", 48 | "customizable", 49 | "cross-platform" 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /src/images/index.ts: -------------------------------------------------------------------------------- 1 | export const playerPause = require('../../assets/icons/player-pause.png') 2 | export const playerPlay = require('../../assets/icons/player-play.png') 3 | export const playerRestart = require('../../assets/icons/player-restart.png') 4 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./modules/video-player"; 2 | -------------------------------------------------------------------------------- /src/modules/video-player/components/DefaultBottomControlsBar.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from "react"; 2 | import { View, Text, StyleSheet } from "react-native"; 3 | import Slider from "@react-native-community/slider"; 4 | import { secondsToMS } from "../logic/utils"; 5 | import { InjectedControlProps } from "../types"; 6 | 7 | interface BottomControlProps extends InjectedControlProps { 8 | barColor?: string; 9 | joyStickColor?: string; 10 | navigationDisabled?: boolean; 11 | } 12 | 13 | export const DefaultBottomControlsBar: React.FC = ( 14 | props 15 | ) => { 16 | const wasPausedBeforeSliding = useRef(props.videoPaused); 17 | 18 | return ( 19 | 20 | 21 | {secondsToMS(props.playCursorTime)} 22 | 23 | { 31 | wasPausedBeforeSliding.current = props.videoPaused; // To know if we need to play after sliding. 32 | props.setPaused(); 33 | }} 34 | onSlidingComplete={(val) => { 35 | props.setPosition(Math.round(val)); 36 | 37 | // Mark playing again if not paused before sliding 38 | if (!wasPausedBeforeSliding.current) { 39 | props.setPlaying(); 40 | } 41 | }} 42 | /> 43 | {secondsToMS(props.videoTotalTime)} 44 | 45 | ); 46 | }; 47 | 48 | const styles = StyleSheet.create({ 49 | barWrapper: { 50 | flexDirection: "row", 51 | backgroundColor: "rgba(0, 0, 0, 0.5)", 52 | height: 60, 53 | paddingHorizontal: 20, 54 | justifyContent: "space-between", 55 | alignItems: "center", 56 | }, 57 | currentTime: { 58 | color: "white", 59 | width: 40, 60 | }, 61 | loadingBar: { 62 | flex: 1, 63 | marginHorizontal: 10, 64 | }, 65 | totalTime: { 66 | color: "white", 67 | width: 40, 68 | }, 69 | }); 70 | -------------------------------------------------------------------------------- /src/modules/video-player/components/DefaultMainControl.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { View, StyleSheet } from "react-native"; 3 | import { PlayerIcon } from "./PlayerIcon"; 4 | import { playerPlay, playerPause, playerRestart } from "../../../images"; 5 | import { InjectedControlProps } from "../types"; 6 | 7 | interface MainControlProps extends InjectedControlProps { 8 | restartButton?: boolean; 9 | } 10 | 11 | export const DefaultMainControl: React.FC = (props) => ( 12 | 13 | {props.videoPaused ? ( 14 | 15 | ) : ( 16 | 17 | )} 18 | {props.restartButton && ( 19 | props.setPosition(0)} 22 | /> 23 | )} 24 | 25 | ); 26 | 27 | const styles = StyleSheet.create({ 28 | barWrapper: { 29 | flexDirection: "row", 30 | flexWrap: "wrap", 31 | justifyContent: "center", 32 | maxWidth: 160, 33 | minWidth: 80, 34 | backgroundColor: "rgba(0, 0, 0, 0.25)", 35 | borderRadius: 5, 36 | }, 37 | barItem: { 38 | margin: 5, 39 | }, 40 | }); 41 | -------------------------------------------------------------------------------- /src/modules/video-player/components/PlayerIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { View, TouchableOpacity, Image, StyleSheet } from "react-native"; 3 | 4 | interface Props { 5 | iconSource: number; 6 | onPress: () => void; 7 | iconColor?: string; 8 | } 9 | 10 | export const PlayerIcon: React.FC = (props) => ( 11 | 12 | 13 | 18 | 19 | 20 | ); 21 | 22 | const styles = StyleSheet.create({ 23 | iconWrapper: { 24 | padding: 5, 25 | }, 26 | icon: { 27 | margin: 15, 28 | width: 30, 29 | height: 30, 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /src/modules/video-player/components/PlayerLoader.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { View, ActivityIndicator, Platform, StyleSheet } from "react-native"; 3 | 4 | interface Props { 5 | color?: string; 6 | } 7 | 8 | export const PlayerLoader: React.FC = ({ color }) => ( 9 | 10 | 14 | 15 | ); 16 | 17 | const styles = StyleSheet.create({ 18 | progressBar: { 19 | flex: 1, 20 | justifyContent: "center", 21 | }, 22 | }); 23 | -------------------------------------------------------------------------------- /src/modules/video-player/components/VideoPlayer.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState, useEffect } from "react"; 2 | import { 3 | TouchableWithoutFeedback, 4 | View, 5 | Animated, 6 | StyleSheet, 7 | } from "react-native"; 8 | import Video from "react-native-video"; 9 | import { useVideoState } from "../hooks/useVideoState"; 10 | import { InjectedControlProps, InjectedPlayerProps } from "../types"; 11 | import { PlayerLoader } from "./PlayerLoader"; 12 | 13 | interface PlayerProps { 14 | autoStart: boolean; 15 | children(props: InjectedPlayerProps): React.ReactNode; 16 | mainControl(data: InjectedControlProps): React.ReactNode; 17 | bottomControl(data: InjectedControlProps): React.ReactNode; 18 | } 19 | 20 | interface ProgressStatus { 21 | currentTime: number; 22 | playableDuration: number; 23 | } 24 | 25 | export const VideoPlayer: React.FC = (props) => { 26 | const playerRef = useRef