├── .gitignore ├── package.json ├── README.md ├── test ├── test.js └── testSources.js ├── index.js └── dist └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | .DS_Store 4 | *.log -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-background-video-player", 3 | "version": "1.1.8", 4 | "description": "React background video component with simple player API. Supports inline play on iPhone.", 5 | "main": "dist/index.js", 6 | "license": "MIT", 7 | "author": "Vadim Namniak (https://github.com/namniak)", 8 | "dependencies": { 9 | "background-cover": "0.1.1", 10 | "insert-rule": "^2.1.0", 11 | "iphone-inline-video": "^2.2.2", 12 | "prop-types": "^15.7.2", 13 | "react": "^16.3.2", 14 | "react-dom": "^16.14.0" 15 | }, 16 | "peerDependencies": { 17 | "react": "^16.3.2", 18 | "react-dom": "^16.3.2" 19 | }, 20 | "devDependencies": { 21 | "babel-cli": "6.26.0", 22 | "babel-preset-es2015": "6.24.1", 23 | "babel-preset-react": "6.24.1", 24 | "babel-preset-stage-1": "6.24.1", 25 | "babelify": "^7.3.0", 26 | "budo": "^11.6.4", 27 | "domready": "^1.0.8" 28 | }, 29 | "scripts": { 30 | "test": "budo test/test.js --live --open -- -t [ babelify --presets [ es2015 react stage-1] ]", 31 | "test-sources": "budo test/testSources.js --live --open -- -t [ babelify --presets [ es2015 react stage-1] ]", 32 | "build": "babel index.js -d dist --presets es2015,react,stage-1" 33 | }, 34 | "keywords": [ 35 | "react", 36 | "video", 37 | "react-component", 38 | "background-video", 39 | "video-player", 40 | "iPhone-inline-video" 41 | ], 42 | "repository": { 43 | "type": "git", 44 | "url": "git://github.com/Jam3/react-background-video-player.git" 45 | }, 46 | "homepage": "https://github.com/Jam3/react-background-video-player", 47 | "bugs": { 48 | "url": "https://github.com/Jam3/react-background-video-player/issues" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-background-video-player 2 | React background video component with simple player API. Supports inline play on iPhone. 3 | 4 | ## Install 5 | ```npm i react-background-video-player --save``` 6 | 7 | ## Test 8 | In your terminal go to component folder and run ```npm t``` 9 | 10 | ## Component Props 11 | ```javascript 12 | { 13 | playsInline: PropTypes.bool, // play inline on iPhone. avoid triggering native video player 14 | disableBackgroundCover: PropTypes.bool, // do not apply cover effect (e.g. disable it for specific screen resolution or aspect ratio) 15 | style: PropTypes.object, 16 | className: PropTypes.string, 17 | containerWidth: PropTypes.number.isRequired, 18 | containerHeight: PropTypes.number.isRequired, 19 | src: PropTypes.oneOfType([ 20 | PropTypes.string, // single source 21 | PropTypes.array // multiple sources 22 | ]).isRequired, 23 | poster: PropTypes.string, 24 | horizontalAlign: PropTypes.number, 25 | verticalAlign: PropTypes.number, 26 | preload: PropTypes.string, 27 | muted: PropTypes.bool, // required to be set to true for auto play on mobile in combination with 'autoPlay' option 28 | loop: PropTypes.bool, 29 | volume: PropTypes.number, 30 | autoPlay: PropTypes.bool, 31 | extraVideoElementProps: PropTypes.object, 32 | startTime: PropTypes.number, 33 | tabIndex: PropTypes.number, 34 | onReady: PropTypes.func, // passes back `duration` 35 | onPlay: PropTypes.func, 36 | onPause: PropTypes.func, 37 | onMute: PropTypes.func, 38 | onUnmute: PropTypes.func, 39 | onTimeUpdate: PropTypes.func, // passes back `currentTime`, `progress` and `duration` 40 | onEnd: PropTypes.func, 41 | onClick: PropTypes.func, 42 | onKeyPress: PropTypes.func 43 | } 44 | ``` 45 | 46 | ## Default Props 47 | ```javascript 48 | { 49 | playsInline: true, 50 | disableBackgroundCover: false, 51 | style: {}, 52 | className: '', 53 | poster: '', 54 | horizontalAlign: 0.5, 55 | verticalAlign: 0.5, 56 | preload: 'auto', 57 | muted: true, 58 | loop: true, 59 | volume: 1, 60 | autoPlay: true, 61 | extraVideoElementProps: {}, 62 | startTime: 0, 63 | tabIndex: 0, 64 | } 65 | ``` 66 | 67 | ## API 68 | * ```play``` - play video 69 | * ```pause``` - pause video 70 | * ```togglePlay``` - automatically toggle play state based on current state 71 | * ```isPaused``` - get play state 72 | * ```mute``` - mute video 73 | * ```unmute``` - unmute video 74 | * ```toggleMute``` - automatically toggle mute state based on current state 75 | * ```isMuted``` - get mute state 76 | * ```setCurrentTime``` - seek in time 77 | 78 | Also refer to [examples](https://github.com/Jam3/react-background-video-player/tree/master/test) 79 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import domready from 'domready'; 4 | import TestComponent from '../index.js'; 5 | 6 | document.body.style.position = 'absolute'; 7 | document.body.style.margin = 0; 8 | document.body.style.width = '100%'; 9 | document.body.style.height = '100%'; 10 | document.body.style.overflow = 'hidden'; 11 | 12 | domready(() => { 13 | class Player extends Component { 14 | constructor(props) { 15 | super(props); 16 | this.state = { 17 | isPlaying: undefined, 18 | isMuted: undefined, 19 | progress: 0, 20 | currentTime: 0, 21 | duration: 0, 22 | windowWidth: window.innerWidth, 23 | windowHeight: window.innerHeight, 24 | } 25 | } 26 | 27 | componentDidMount() { 28 | this.setState({ 29 | isPlaying: !this.player.isPaused, 30 | isMuted: this.player.isMuted, 31 | }); 32 | window.addEventListener('resize', this.handleResize); 33 | } 34 | 35 | componentWillMount() { 36 | window.removeEventListener('resize', this.handleResize); 37 | } 38 | 39 | handleResize = () => { 40 | this.setState({ 41 | windowWidth: window.innerWidth, 42 | windowHeight: window.innerHeight, 43 | }) 44 | }; 45 | 46 | handleOnPlay = () => { 47 | this.setState({isPlaying: true}); 48 | }; 49 | 50 | handleOnPause = () => { 51 | this.setState({isPlaying: false}); 52 | }; 53 | 54 | handleTimeUpdate = (currentTime, progress, duration) => { 55 | this.setState({ 56 | progress, 57 | currentTime, 58 | duration, 59 | }); 60 | }; 61 | 62 | handleOnMute = () => { 63 | this.setState({isMuted: true}); 64 | }; 65 | 66 | handleOnUnmute = () => { 67 | this.setState({isMuted: false}); 68 | }; 69 | 70 | togglePlay = () => { 71 | this.player.togglePlay(); 72 | }; 73 | 74 | toggleMute = () => { 75 | this.player.toggleMute(); 76 | }; 77 | 78 | render() { 79 | return ( 80 |
81 | this.player = p} 83 | containerWidth={this.state.windowWidth} 84 | containerHeight={this.state.windowHeight} 85 | src={'http://clips.vorwaerts-gmbh.de/VfE_html5.mp4'} 86 | poster={'http://il6.picdn.net/shutterstock/videos/3548084/thumb/1.jpg?i10c=img.resize(height:160)'} 87 | onPlay={this.handleOnPlay} 88 | onPause={this.handleOnPause} 89 | onMute={this.handleOnMute} 90 | onUnmute={this.handleOnUnmute} 91 | onTimeUpdate={this.handleTimeUpdate} 92 | startTime={10} 93 | autoPlay={false} 94 | volume={0.5} 95 | /> 96 | 112 |
113 | ) 114 | } 115 | } 116 | 117 | ReactDOM.render(, document.body); 118 | }); 119 | -------------------------------------------------------------------------------- /test/testSources.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import domready from 'domready'; 4 | import TestComponent from '../index.js'; 5 | 6 | document.body.style.position = 'absolute'; 7 | document.body.style.margin = 0; 8 | document.body.style.width = '100%'; 9 | document.body.style.height = '100%'; 10 | document.body.style.overflow = 'hidden'; 11 | 12 | domready(() => { 13 | 14 | class Player extends Component { 15 | constructor(props) { 16 | super(props); 17 | this.state = { 18 | isPlaying: undefined, 19 | isMuted: undefined, 20 | progress: 0, 21 | currentTime: 0, 22 | duration: 0, 23 | windowWidth: window.innerWidth, 24 | windowHeight: window.innerHeight, 25 | } 26 | } 27 | 28 | componentDidMount() { 29 | this.setState({ 30 | isPlaying: !this.player.isPaused, 31 | isMuted: this.player.isMuted, 32 | }); 33 | window.addEventListener('resize', this.handleResize); 34 | } 35 | 36 | componentWillMount() { 37 | window.removeEventListener('resize', this.handleResize); 38 | } 39 | 40 | handleResize = () => { 41 | this.setState({ 42 | windowWidth: window.innerWidth, 43 | windowHeight: window.innerHeight, 44 | }) 45 | }; 46 | 47 | handleOnPlay = () => { 48 | this.setState({isPlaying: true}); 49 | }; 50 | 51 | handleOnPause = () => { 52 | this.setState({isPlaying: false}); 53 | }; 54 | 55 | handleTimeUpdate = (currentTime, progress, duration) => { 56 | this.setState({ 57 | progress, 58 | currentTime, 59 | duration, 60 | }); 61 | }; 62 | 63 | handleOnMute = () => { 64 | this.setState({isMuted: true}); 65 | }; 66 | 67 | handleOnUnmute = () => { 68 | this.setState({isMuted: false}); 69 | }; 70 | 71 | togglePlay = () => { 72 | this.player.togglePlay(); 73 | }; 74 | 75 | toggleMute = () => { 76 | this.player.toggleMute(); 77 | }; 78 | 79 | render() { 80 | return ( 81 |
82 | this.player = p} 84 | containerWidth={this.state.windowWidth} 85 | containerHeight={this.state.windowHeight} 86 | src={[ 87 | { 88 | src: 'http://download.blender.org/peach/trailer/trailer_400p.ogg', 89 | type: 'video/ogg' 90 | }, 91 | { 92 | src: 'http://clips.vorwaerts-gmbh.de/VfE_html5.mp4', 93 | type: 'video/mp4' 94 | } 95 | ]} 96 | poster={'http://il6.picdn.net/shutterstock/videos/3548084/thumb/1.jpg?i10c=img.resize(height:160)'} 97 | onPlay={this.handleOnPlay} 98 | onPause={this.handleOnPause} 99 | onMute={this.handleOnMute} 100 | onUnmute={this.handleOnUnmute} 101 | onTimeUpdate={this.handleTimeUpdate} 102 | autoPlay={false} 103 | volume={0.5} 104 | /> 105 | 121 |
122 | ) 123 | } 124 | } 125 | 126 | ReactDOM.render(, document.body); 127 | }); 128 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import BackgroundCover from 'background-cover'; 5 | import playInlineVideo from 'iphone-inline-video'; 6 | import insertRule from 'insert-rule'; 7 | 8 | const iOSNavigator = typeof navigator !== 'undefined' && navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/); 9 | const iOSVersion = iOSNavigator ? iOSNavigator[1] : null; 10 | 11 | const noop = () => {}; 12 | 13 | const absolute100 = { 14 | position: 'absolute', 15 | top: 0, 16 | left: 0, 17 | width: '100%', 18 | height: '100%', 19 | }; 20 | 21 | export default class BackgroundVideo extends React.PureComponent { 22 | constructor(props) { 23 | super(props); 24 | this.state = { 25 | visible: false, 26 | }; 27 | this.startTimeIsSet = false; 28 | } 29 | 30 | componentDidMount() { 31 | if (this.props.playsInline && iOSVersion) { 32 | const hasAudio = !(iOSVersion && iOSVersion < 10 && this.props.autoPlay && this.props.muted); // allow autoplay on iOS < 10 for silent videos 33 | const requireInteractionOnTablet = false; 34 | 35 | playInlineVideo(this.video, hasAudio, requireInteractionOnTablet); 36 | insertRule(['video::-webkit-media-controls-start-playback-button', '.IIV::-webkit-media-controls-play-button'], { 37 | display: 'none', 38 | }); 39 | } 40 | 41 | if (this.video) { 42 | if (this.video.readyState !== 4) { 43 | this.video.addEventListener('loadedmetadata', this._handleVideoReady); 44 | } else { 45 | this._handleVideoReady(); 46 | } 47 | 48 | this.video.addEventListener('play', this._handleOnPlay); 49 | this.video.addEventListener('pause', this._handleOnPause); 50 | this.video.volume = this.props.volume; 51 | } 52 | } 53 | 54 | componentDidUpdate(prevProps) { 55 | if ( 56 | (this.props.containerWidth !== prevProps.containerWidth || 57 | this.props.containerHeight !== prevProps.containerHeight) && 58 | !this.props.disableBackgroundCover 59 | ) { 60 | this._resize(); 61 | } 62 | 63 | if (this.video && this.props.volume !== prevProps.volume) { 64 | this.video.volume = this.props.volume; 65 | } 66 | } 67 | 68 | componentWillUnmount() { 69 | if (!this.video) return; 70 | this.video.removeEventListener('loadedmetadata', this._handleVideoReady); 71 | this.video.removeEventListener('play', this._handleOnPlay); 72 | this.video.removeEventListener('pause', this._handleOnPause); 73 | } 74 | 75 | _handleVideoReady = () => { 76 | if (!this.props.disableBackgroundCover) { 77 | this._resize(); 78 | } 79 | 80 | this.setState({ visible: true }); 81 | this.props.startTime && this.setCurrentTime(this.props.startTime); 82 | this.props.autoPlay && this.play(); 83 | this.video && this.props.onReady(this.video.duration); 84 | }; 85 | 86 | _resize() { 87 | this.video && BackgroundCover(this.video, this.container, this.props.horizontalAlign, this.props.verticalAlign); 88 | } 89 | 90 | _handleOnPlay = () => { 91 | this.props.onPlay(); 92 | }; 93 | 94 | _handleOnPause = () => { 95 | this.props.onPause(); 96 | }; 97 | 98 | _handleTimeUpdate = () => { 99 | if (!this.video) return; 100 | iOSVersion && this._handleIOSStartTime(); 101 | const currentTime = this.video.currentTime; 102 | const duration = this.video.duration; 103 | const progress = currentTime / duration; 104 | this.props.onTimeUpdate(currentTime, progress, duration); 105 | }; 106 | 107 | _handleVideoEnd = () => { 108 | this.props.onEnd(); 109 | }; 110 | 111 | _handleIOSStartTime() { 112 | if (!this.video) return; 113 | if (this.video.currentTime < this.props.startTime && !this.startTimeIsSet) { 114 | this.setCurrentTime(this.props.startTime); 115 | this.startTimeIsSet = true; 116 | } 117 | } 118 | 119 | play() { 120 | this.video && this.video.play(); 121 | } 122 | 123 | pause() { 124 | this.video && this.video.pause(); 125 | } 126 | 127 | togglePlay() { 128 | if (!this.video) return; 129 | this.video.paused ? this.play() : this.pause(); 130 | } 131 | 132 | isPaused() { 133 | return this.video ? this.video.paused : false; 134 | } 135 | 136 | mute() { 137 | if (!this.video) return; 138 | this.video.muted = true; 139 | this.props.onMute(); 140 | } 141 | 142 | unmute() { 143 | if (!this.video) return; 144 | this.video.muted = false; 145 | this.props.onUnmute(); 146 | } 147 | 148 | toggleMute() { 149 | if (!this.video) return; 150 | this.video.muted ? this.unmute() : this.mute(); 151 | } 152 | 153 | isMuted() { 154 | return this.video ? this.video.muted : false; 155 | } 156 | 157 | setCurrentTime(val) { 158 | if (!this.video) return; 159 | this.video.currentTime = val; 160 | } 161 | 162 | render() { 163 | const visibility = this.state.visible ? 'visible' : 'hidden'; 164 | 165 | const videoProps = { 166 | ref: (v) => (this.video = v), 167 | src: typeof this.props.src === 'string' ? this.props.src : null, 168 | preload: this.props.preload, 169 | poster: this.props.poster, 170 | muted: this.props.muted, 171 | loop: this.props.loop, 172 | onTimeUpdate: this._handleTimeUpdate, 173 | onEnded: this._handleVideoEnd, 174 | ...Object.assign(this.props.extraVideoElementProps, { 175 | playsInline: this.props.playsInline, 176 | }), 177 | }; 178 | 179 | return ( 180 |
(this.container = r)} 182 | className={`BackgroundVideo ${this.props.className}`} 183 | style={Object.assign({ ...absolute100, visibility }, this.props.style)} 184 | onClick={this.props.onClick} 185 | onKeyPress={this.props.onKeyPress} 186 | tabIndex={this.props.tabIndex} 187 | > 188 | {typeof this.props.src === 'object' ? ( 189 | 194 | ) : ( 195 |
198 | ); 199 | } 200 | } 201 | 202 | BackgroundVideo.propTypes = { 203 | playsInline: PropTypes.bool, // play inline on iPhone. avoid triggering native video player 204 | disableBackgroundCover: PropTypes.bool, // do not apply cover effect (e.g. disable it for specific screen resolution or aspect ratio) 205 | style: PropTypes.object, 206 | className: PropTypes.string, 207 | containerWidth: PropTypes.number, 208 | containerHeight: PropTypes.number, 209 | src: PropTypes.oneOfType([PropTypes.string, PropTypes.array]).isRequired, 210 | poster: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), 211 | horizontalAlign: PropTypes.number, 212 | verticalAlign: PropTypes.number, 213 | preload: PropTypes.string, 214 | muted: PropTypes.bool, // required to be set to true for auto play on mobile in combination with 'autoPlay' option 215 | volume: PropTypes.number, 216 | loop: PropTypes.bool, 217 | autoPlay: PropTypes.bool, 218 | extraVideoElementProps: PropTypes.object, 219 | startTime: PropTypes.number, 220 | tabIndex: PropTypes.number, 221 | onReady: PropTypes.func, // passes back `duration` 222 | onPlay: PropTypes.func, 223 | onPause: PropTypes.func, 224 | onMute: PropTypes.func, 225 | onUnmute: PropTypes.func, 226 | onTimeUpdate: PropTypes.func, // passes back `currentTime`, `progress` and `duration` 227 | onEnd: PropTypes.func, 228 | onClick: PropTypes.func, 229 | onKeyPress: PropTypes.func, 230 | }; 231 | 232 | BackgroundVideo.defaultProps = { 233 | playsInline: true, 234 | disableBackgroundCover: false, 235 | style: {}, 236 | horizontalAlign: 0.5, 237 | verticalAlign: 0.5, 238 | preload: 'auto', 239 | muted: true, 240 | volume: 1, 241 | loop: true, 242 | autoPlay: true, 243 | extraVideoElementProps: {}, 244 | startTime: 0, 245 | tabIndex: 0, 246 | onReady: noop, 247 | onPlay: noop, 248 | onPause: noop, 249 | onMute: noop, 250 | onUnmute: noop, 251 | onTimeUpdate: noop, 252 | onEnd: noop, 253 | onClick: noop, 254 | onKeyPress: noop, 255 | }; 256 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 8 | 9 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 10 | 11 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 12 | 13 | var _react = require('react'); 14 | 15 | var _react2 = _interopRequireDefault(_react); 16 | 17 | var _propTypes = require('prop-types'); 18 | 19 | var _propTypes2 = _interopRequireDefault(_propTypes); 20 | 21 | var _backgroundCover = require('background-cover'); 22 | 23 | var _backgroundCover2 = _interopRequireDefault(_backgroundCover); 24 | 25 | var _iphoneInlineVideo = require('iphone-inline-video'); 26 | 27 | var _iphoneInlineVideo2 = _interopRequireDefault(_iphoneInlineVideo); 28 | 29 | var _insertRule = require('insert-rule'); 30 | 31 | var _insertRule2 = _interopRequireDefault(_insertRule); 32 | 33 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 34 | 35 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 36 | 37 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 38 | 39 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 40 | 41 | var iOSNavigator = typeof navigator !== 'undefined' && navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/); 42 | var iOSVersion = iOSNavigator ? iOSNavigator[1] : null; 43 | 44 | var noop = function noop() {}; 45 | 46 | var absolute100 = { 47 | position: 'absolute', 48 | top: 0, 49 | left: 0, 50 | width: '100%', 51 | height: '100%' 52 | }; 53 | 54 | var BackgroundVideo = function (_React$PureComponent) { 55 | _inherits(BackgroundVideo, _React$PureComponent); 56 | 57 | function BackgroundVideo(props) { 58 | _classCallCheck(this, BackgroundVideo); 59 | 60 | var _this = _possibleConstructorReturn(this, (BackgroundVideo.__proto__ || Object.getPrototypeOf(BackgroundVideo)).call(this, props)); 61 | 62 | _this._handleVideoReady = function () { 63 | if (!_this.props.disableBackgroundCover) { 64 | _this._resize(); 65 | } 66 | 67 | _this.setState({ visible: true }); 68 | _this.props.startTime && _this.setCurrentTime(_this.props.startTime); 69 | _this.props.autoPlay && _this.play(); 70 | _this.video && _this.props.onReady(_this.video.duration); 71 | }; 72 | 73 | _this._handleOnPlay = function () { 74 | _this.props.onPlay(); 75 | }; 76 | 77 | _this._handleOnPause = function () { 78 | _this.props.onPause(); 79 | }; 80 | 81 | _this._handleTimeUpdate = function () { 82 | if (!_this.video) return; 83 | iOSVersion && _this._handleIOSStartTime(); 84 | var currentTime = _this.video.currentTime; 85 | var duration = _this.video.duration; 86 | var progress = currentTime / duration; 87 | _this.props.onTimeUpdate(currentTime, progress, duration); 88 | }; 89 | 90 | _this._handleVideoEnd = function () { 91 | _this.props.onEnd(); 92 | }; 93 | 94 | _this.state = { 95 | visible: false 96 | }; 97 | _this.startTimeIsSet = false; 98 | return _this; 99 | } 100 | 101 | _createClass(BackgroundVideo, [{ 102 | key: 'componentDidMount', 103 | value: function componentDidMount() { 104 | if (this.props.playsInline && iOSVersion) { 105 | var hasAudio = !(iOSVersion && iOSVersion < 10 && this.props.autoPlay && this.props.muted); // allow autoplay on iOS < 10 for silent videos 106 | var requireInteractionOnTablet = false; 107 | 108 | (0, _iphoneInlineVideo2.default)(this.video, hasAudio, requireInteractionOnTablet); 109 | (0, _insertRule2.default)(['video::-webkit-media-controls-start-playback-button', '.IIV::-webkit-media-controls-play-button'], { 110 | display: 'none' 111 | }); 112 | } 113 | 114 | if (this.video) { 115 | if (this.video.readyState !== 4) { 116 | this.video.addEventListener('loadedmetadata', this._handleVideoReady); 117 | } else { 118 | this._handleVideoReady(); 119 | } 120 | 121 | this.video.addEventListener('play', this._handleOnPlay); 122 | this.video.addEventListener('pause', this._handleOnPause); 123 | this.video.volume = this.props.volume; 124 | } 125 | } 126 | }, { 127 | key: 'componentDidUpdate', 128 | value: function componentDidUpdate(prevProps) { 129 | if ((this.props.containerWidth !== prevProps.containerWidth || this.props.containerHeight !== prevProps.containerHeight) && !this.props.disableBackgroundCover) { 130 | this._resize(); 131 | } 132 | 133 | if (this.video && this.props.volume !== prevProps.volume) { 134 | this.video.volume = this.props.volume; 135 | } 136 | } 137 | }, { 138 | key: 'componentWillUnmount', 139 | value: function componentWillUnmount() { 140 | if (!this.video) return; 141 | this.video.removeEventListener('loadedmetadata', this._handleVideoReady); 142 | this.video.removeEventListener('play', this._handleOnPlay); 143 | this.video.removeEventListener('pause', this._handleOnPause); 144 | } 145 | }, { 146 | key: '_resize', 147 | value: function _resize() { 148 | this.video && (0, _backgroundCover2.default)(this.video, this.container, this.props.horizontalAlign, this.props.verticalAlign); 149 | } 150 | }, { 151 | key: '_handleIOSStartTime', 152 | value: function _handleIOSStartTime() { 153 | if (!this.video) return; 154 | if (this.video.currentTime < this.props.startTime && !this.startTimeIsSet) { 155 | this.setCurrentTime(this.props.startTime); 156 | this.startTimeIsSet = true; 157 | } 158 | } 159 | }, { 160 | key: 'play', 161 | value: function play() { 162 | this.video && this.video.play(); 163 | } 164 | }, { 165 | key: 'pause', 166 | value: function pause() { 167 | this.video && this.video.pause(); 168 | } 169 | }, { 170 | key: 'togglePlay', 171 | value: function togglePlay() { 172 | if (!this.video) return; 173 | this.video.paused ? this.play() : this.pause(); 174 | } 175 | }, { 176 | key: 'isPaused', 177 | value: function isPaused() { 178 | return this.video ? this.video.paused : false; 179 | } 180 | }, { 181 | key: 'mute', 182 | value: function mute() { 183 | if (!this.video) return; 184 | this.video.muted = true; 185 | this.props.onMute(); 186 | } 187 | }, { 188 | key: 'unmute', 189 | value: function unmute() { 190 | if (!this.video) return; 191 | this.video.muted = false; 192 | this.props.onUnmute(); 193 | } 194 | }, { 195 | key: 'toggleMute', 196 | value: function toggleMute() { 197 | if (!this.video) return; 198 | this.video.muted ? this.unmute() : this.mute(); 199 | } 200 | }, { 201 | key: 'isMuted', 202 | value: function isMuted() { 203 | return this.video ? this.video.muted : false; 204 | } 205 | }, { 206 | key: 'setCurrentTime', 207 | value: function setCurrentTime(val) { 208 | if (!this.video) return; 209 | this.video.currentTime = val; 210 | } 211 | }, { 212 | key: 'render', 213 | value: function render() { 214 | var _this2 = this; 215 | 216 | var visibility = this.state.visible ? 'visible' : 'hidden'; 217 | 218 | var videoProps = _extends({ 219 | ref: function ref(v) { 220 | return _this2.video = v; 221 | }, 222 | src: typeof this.props.src === 'string' ? this.props.src : null, 223 | preload: this.props.preload, 224 | poster: this.props.poster, 225 | muted: this.props.muted, 226 | loop: this.props.loop, 227 | onTimeUpdate: this._handleTimeUpdate, 228 | onEnded: this._handleVideoEnd 229 | }, Object.assign(this.props.extraVideoElementProps, { 230 | playsInline: this.props.playsInline 231 | })); 232 | 233 | return _react2.default.createElement( 234 | 'div', 235 | { 236 | ref: function ref(r) { 237 | return _this2.container = r; 238 | }, 239 | className: 'BackgroundVideo ' + this.props.className, 240 | style: Object.assign(_extends({}, absolute100, { visibility: visibility }), this.props.style), 241 | onClick: this.props.onClick, 242 | onKeyPress: this.props.onKeyPress, 243 | tabIndex: this.props.tabIndex 244 | }, 245 | _typeof(this.props.src) === 'object' ? _react2.default.createElement( 246 | 'video', 247 | videoProps, 248 | this.props.src.map(function (source, key) { 249 | return _react2.default.createElement('source', _extends({ key: key }, source)); 250 | }) 251 | ) : _react2.default.createElement('video', videoProps) 252 | ); 253 | } 254 | }]); 255 | 256 | return BackgroundVideo; 257 | }(_react2.default.PureComponent); 258 | 259 | exports.default = BackgroundVideo; 260 | 261 | 262 | BackgroundVideo.propTypes = { 263 | playsInline: _propTypes2.default.bool, // play inline on iPhone. avoid triggering native video player 264 | disableBackgroundCover: _propTypes2.default.bool, // do not apply cover effect (e.g. disable it for specific screen resolution or aspect ratio) 265 | style: _propTypes2.default.object, 266 | className: _propTypes2.default.string, 267 | containerWidth: _propTypes2.default.number, 268 | containerHeight: _propTypes2.default.number, 269 | src: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.array]).isRequired, 270 | poster: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.object]), 271 | horizontalAlign: _propTypes2.default.number, 272 | verticalAlign: _propTypes2.default.number, 273 | preload: _propTypes2.default.string, 274 | muted: _propTypes2.default.bool, // required to be set to true for auto play on mobile in combination with 'autoPlay' option 275 | volume: _propTypes2.default.number, 276 | loop: _propTypes2.default.bool, 277 | autoPlay: _propTypes2.default.bool, 278 | extraVideoElementProps: _propTypes2.default.object, 279 | startTime: _propTypes2.default.number, 280 | tabIndex: _propTypes2.default.number, 281 | onReady: _propTypes2.default.func, // passes back `duration` 282 | onPlay: _propTypes2.default.func, 283 | onPause: _propTypes2.default.func, 284 | onMute: _propTypes2.default.func, 285 | onUnmute: _propTypes2.default.func, 286 | onTimeUpdate: _propTypes2.default.func, // passes back `currentTime`, `progress` and `duration` 287 | onEnd: _propTypes2.default.func, 288 | onClick: _propTypes2.default.func, 289 | onKeyPress: _propTypes2.default.func 290 | }; 291 | 292 | BackgroundVideo.defaultProps = { 293 | playsInline: true, 294 | disableBackgroundCover: false, 295 | style: {}, 296 | horizontalAlign: 0.5, 297 | verticalAlign: 0.5, 298 | preload: 'auto', 299 | muted: true, 300 | volume: 1, 301 | loop: true, 302 | autoPlay: true, 303 | extraVideoElementProps: {}, 304 | startTime: 0, 305 | tabIndex: 0, 306 | onReady: noop, 307 | onPlay: noop, 308 | onPause: noop, 309 | onMute: noop, 310 | onUnmute: noop, 311 | onTimeUpdate: noop, 312 | onEnd: noop, 313 | onClick: noop, 314 | onKeyPress: noop 315 | }; --------------------------------------------------------------------------------