Skip to content

Commit c3a6c2a

Browse files
committed
Merge pull request #13 from CookPete/fix-autoplay-prime
Prime players to enable autoplay when out of focus
2 parents 6833c46 + a0b8ae2 commit c3a6c2a

File tree

10 files changed

+167
-109
lines changed

10 files changed

+167
-109
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. This projec
44

55
### Unreleased
66

7+
* Fix [YouTube and Vimeo autoplay bug](https://github.com/CookPete/react-player/issues/7)
78
* [Full commit list](https://github.com/CookPete/react-player/compare/v0.2.1...master)
89

910

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,13 @@ These props allow you to override the parameters for the various players
6565

6666
Prop | Description
6767
---- | -----------
68-
soundcloudConfig | An object containing configuration for the SoundCloud player. Includes `clientId`, which can be used to override the default `client_id`
69-
vimeoConfig | An object containing configuration for the Vimeo player. Includes `iframeParams`, which maps to the [parameters accepted by the Vimeo iframe player](https://developer.vimeo.com/player/embedding#universal-parameters)
70-
youtubeConfig | An object containing configuration for the YouTube player. Includes `playerVars`, which maps to the [parameters accepted by the YouTube iframe player](https://developers.google.com/youtube/player_parameters?playerVersion=HTML5)
68+
soundcloudConfig | Configuration object for the SoundCloud player. Set `clientId`, to your own SoundCloud app [client ID](https://soundcloud.com/you/apps)
69+
vimeoConfig | Configuration object for the Vimeo player. Set `iframeParams`, to override the [default params](https://developer.vimeo.com/player/embedding#universal-parameters). Set `preload` for [preloading](#preloading)
70+
youtubeConfig | Configuration object for the YouTube player. Set `playerVars`, to override the [default player vars](https://developers.google.com/youtube/player_parameters?playerVersion=HTML5). Set `preload` for [preloading](#preloading)
71+
72+
##### Preloading
73+
74+
Both `youtubeConfig` and `vimeoConfig` props can take a `preload` value. Setting this to `true` will play a short, silent video in the background when `ReactPlayer` first mounts. This fixes a [bug](https://github.com/CookPete/react-player/issues/7) where videos would not play when loaded in a background browser tab.
7175

7276
### Methods
7377

src/ReactPlayer.js

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,75 @@
11
import React, { Component } from 'react'
22
import 'array.prototype.find'
33

4-
import propTypes from './propTypes'
4+
import { propTypes, defaultProps } from './props'
55
import players from './players'
66

7+
const PROGRESS_FREQUENCY = 500
8+
79
export default class ReactPlayer extends Component {
810
static propTypes = propTypes
9-
static defaultProps = {
10-
volume: 0.8,
11-
width: 640,
12-
height: 360,
13-
onPlay: function () {}, // TODO: Empty func var in react?
14-
onPause: function () {},
15-
onBuffer: function () {},
16-
onEnded: function () {}
17-
}
11+
static defaultProps = defaultProps
1812
static canPlay (url) {
1913
return players.some(player => player.canPlay(url))
2014
}
21-
state = {
22-
Player: this.getPlayer(this.props.url)
15+
componentDidMount () {
16+
this.progress()
2317
}
24-
componentWillReceiveProps (nextProps) {
25-
if (this.props.url !== nextProps.url) {
26-
this.setState({
27-
Player: this.getPlayer(nextProps.url)
28-
})
29-
}
18+
componentWillUnmount () {
19+
clearTimeout(this.progressTimeout)
3020
}
31-
getPlayer (url) {
32-
return players.find(Player => Player.canPlay(url))
21+
shouldComponentUpdate (nextProps) {
22+
return (
23+
this.props.url !== nextProps.url ||
24+
this.props.playing !== nextProps.playing ||
25+
this.props.volume !== nextProps.volume
26+
)
3327
}
3428
seekTo = fraction => {
3529
const player = this.refs.player
3630
if (player) {
3731
player.seekTo(fraction)
3832
}
3933
}
34+
progress = () => {
35+
if (this.props.url && this.refs.player) {
36+
let progress = {}
37+
const loaded = this.refs.player.getFractionLoaded()
38+
const played = this.refs.player.getFractionPlayed()
39+
if (!this.prevLoaded || loaded !== this.prevLoaded) {
40+
progress.loaded = this.prevLoaded = loaded
41+
}
42+
if (!this.prevPlayed || played !== this.prevPlayed) {
43+
progress.played = this.prevPlayed = played
44+
}
45+
if (progress.loaded || progress.played) {
46+
this.props.onProgress(progress)
47+
}
48+
}
49+
this.progressTimeout = setTimeout(this.progress, PROGRESS_FREQUENCY)
50+
}
51+
renderPlayer = Player => {
52+
const active = Player.canPlay(this.props.url)
53+
const { youtubeConfig, soundcloudConfig, vimeoConfig, ...activeProps } = this.props
54+
const props = active ? { ...activeProps, ref: 'player' } : {}
55+
return (
56+
<Player
57+
key={Player.name}
58+
youtubeConfig={youtubeConfig}
59+
soundcloudConfig={soundcloudConfig}
60+
vimeoConfig={vimeoConfig}
61+
{...props}
62+
/>
63+
)
64+
}
4065
render () {
41-
const Player = this.state.Player
4266
const style = {
4367
width: this.props.width,
4468
height: this.props.height
4569
}
4670
return (
4771
<div style={style}>
48-
{ Player && <Player ref='player' {...this.props} /> }
72+
{ players.map(this.renderPlayer) }
4973
</div>
5074
)
5175
}

src/players/Base.js

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,26 @@
11
import { Component } from 'react'
22

3-
import propTypes from '../propTypes'
4-
5-
const UPDATE_FREQUENCY = 500
3+
import { propTypes, defaultProps } from '../props'
64

75
export default class Base extends Component {
86
static propTypes = propTypes
9-
static defaultProps = {
10-
onProgress: function () {}
11-
}
7+
static defaultProps = defaultProps
128
componentDidMount () {
13-
this.play(this.props.url)
14-
this.update()
9+
if (this.props.url) {
10+
this.load(this.props.url)
11+
}
1512
}
1613
componentWillUnmount () {
1714
this.stop()
18-
clearTimeout(this.updateTimeout)
1915
}
2016
componentWillReceiveProps (nextProps) {
2117
// Invoke player methods based on incoming props
22-
if (this.props.url !== nextProps.url) {
23-
this.play(nextProps.url)
24-
this.props.onProgress({ played: 0, loaded: 0 })
18+
if (this.props.url !== nextProps.url && nextProps.url) {
19+
this.load(nextProps.url, nextProps.playing)
20+
this.props.onProgress({ played: 0, loaded: 0 }) // Needed?
21+
} else if (this.props.url && !nextProps.url) {
22+
this.stop()
23+
clearTimeout(this.updateTimeout)
2524
} else if (!this.props.playing && nextProps.playing) {
2625
this.play()
2726
} else if (this.props.playing && !nextProps.playing) {
@@ -30,24 +29,13 @@ export default class Base extends Component {
3029
this.setVolume(nextProps.volume)
3130
}
3231
}
33-
update = () => {
34-
let progress = {}
35-
const loaded = this.getFractionLoaded()
36-
const played = this.getFractionPlayed()
37-
if (!this.prevLoaded || loaded !== this.prevLoaded) {
38-
progress.loaded = this.prevLoaded = loaded
39-
}
40-
if (!this.prevPlayed || played !== this.prevPlayed) {
41-
progress.played = this.prevPlayed = played
42-
}
43-
if (progress.loaded || progress.played) {
44-
this.props.onProgress(progress)
45-
}
46-
this.updateTimeout = setTimeout(this.update, UPDATE_FREQUENCY)
32+
shouldComponentUpdate (nextProps) {
33+
return this.props.url !== nextProps.url
4734
}
4835
onReady = () => {
4936
this.setVolume(this.props.volume)
50-
if (this.props.playing) {
37+
if (this.props.playing || this.preloading) {
38+
this.preloading = false
5139
this.play()
5240
}
5341
}

src/players/FilePlayer.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import React from 'react'
22

3-
import propTypes from '../propTypes'
3+
import { propTypes, defaultProps } from '../props'
44
import Base from './Base'
55

66
const VIDEO_EXTENSIONS = /\.(mp4|og[gv]|webm)$/
77
const AUDIO_EXTENSIONS = /\.(mp3|wav)$/
88

99
export default class FilePlayer extends Base {
1010
static propTypes = propTypes
11+
static defaultProps = defaultProps
1112
static canPlay (url) {
1213
return VIDEO_EXTENSIONS.test(url) || AUDIO_EXTENSIONS.test(url)
1314
}
@@ -18,19 +19,18 @@ export default class FilePlayer extends Base {
1819
this.player.onpause = this.props.onPause
1920
this.player.onended = this.props.onEnded
2021
this.player.onerror = this.props.onError
21-
super.componentDidMount()
2222
}
23-
shouldComponentUpdate (nextProps) {
24-
return this.props.url !== nextProps
23+
load (url) {
24+
this.player.src = url
2525
}
26-
play (url) {
26+
play () {
2727
this.player.play()
2828
}
2929
pause () {
3030
this.player.pause()
3131
}
3232
stop () {
33-
// No need to stop
33+
this.player.src = ''
3434
}
3535
seekTo (fraction) {
3636
this.player.currentTime = this.player.duration * fraction
@@ -48,10 +48,11 @@ export default class FilePlayer extends Base {
4848
}
4949
render () {
5050
const Media = AUDIO_EXTENSIONS.test(this.props.url) ? 'audio' : 'video'
51+
const style = { display: this.props.url ? 'block' : 'none' }
5152
return (
5253
<Media
5354
ref='player'
54-
src={this.props.url}
55+
style={style}
5556
width={this.props.width}
5657
height={this.props.height}
5758
/>

src/players/SoundCloud.js

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,28 @@
11
import React from 'react'
22
import loadScript from 'load-script'
33

4-
import propTypes from '../propTypes'
4+
import { propTypes, defaultProps } from '../props'
55
import Base from './Base'
66

7-
const DEFAULT_CLIENT_ID = 'e8b6f84fbcad14c301ca1355cae1dea2'
87
const SDK_URL = '//connect.soundcloud.com/sdk-2.0.0.js'
98
const SDK_GLOBAL = 'SC'
109
const RESOLVE_URL = '//api.soundcloud.com/resolve.json'
1110
const MATCH_URL = /^https?:\/\/(soundcloud.com|snd.sc)\/([a-z0-9-]+\/[a-z0-9-]+)$/
1211

1312
export default class SoundCloud extends Base {
1413
static propTypes = propTypes
15-
static defaultProps = {
16-
soundcloudConfig: {
17-
clientId: DEFAULT_CLIENT_ID
18-
}
19-
}
14+
static defaultProps = defaultProps
2015
static canPlay (url) {
2116
return MATCH_URL.test(url)
2217
}
2318
state = {
2419
image: null
2520
}
2621
shouldComponentUpdate (nextProps, nextState) {
27-
return this.state.image !== nextState.image
22+
return (
23+
super.shouldComponentUpdate(nextProps, nextState) ||
24+
this.state.image !== nextState.image
25+
)
2826
}
2927
getSDK () {
3028
if (window[SDK_GLOBAL]) {
@@ -45,11 +43,7 @@ export default class SoundCloud extends Base {
4543
return fetch(RESOLVE_URL + '?url=' + url + '&client_id=' + this.props.soundcloudConfig.clientId)
4644
.then(response => response.json())
4745
}
48-
play (url) {
49-
if (!url && this.player) {
50-
this.player.play()
51-
return
52-
}
46+
load (url) {
5347
this.stop()
5448
this.getSDK().then(SC => {
5549
this.getSongData(url).then(data => {
@@ -81,6 +75,10 @@ export default class SoundCloud extends Base {
8175
onfinish: this.props.onFinish,
8276
ondataerror: this.props.onError
8377
}
78+
play () {
79+
if (!this.player) return
80+
this.player.play()
81+
}
8482
pause () {
8583
if (!this.player) return
8684
this.player.pause()
@@ -107,6 +105,7 @@ export default class SoundCloud extends Base {
107105
}
108106
render () {
109107
const style = {
108+
display: this.props.url ? 'block' : 'none',
110109
height: '100%',
111110
backgroundImage: this.state.image ? 'url(' + this.state.image + ')' : null,
112111
backgroundSize: 'cover',

src/players/Vimeo.js

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import React from 'react'
2-
import queryString from 'query-string'
2+
import { stringify } from 'query-string'
33

4-
import propTypes from '../propTypes'
4+
import { propTypes, defaultProps } from '../props'
55
import Base from './Base'
66

77
const IFRAME_SRC = 'https://player.vimeo.com/video/'
88
const MATCH_URL = /https?:\/\/(?:www\.|player\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/([^\/]*)\/videos\/|album\/(\d+)\/video\/|video\/|)(\d+)(?:$|\/|\?)/
99
const MATCH_MESSAGE_ORIGIN = /^https?:\/\/player.vimeo.com/
10+
const BLANK_VIDEO_URL = 'https://vimeo.com/127250231'
1011
const DEFAULT_IFRAME_PARAMS = {
1112
api: 1,
1213
autoplay: 0,
@@ -18,30 +19,37 @@ const DEFAULT_IFRAME_PARAMS = {
1819

1920
export default class Vimeo extends Base {
2021
static propTypes = propTypes
21-
static defaultProps = {
22-
vimeoConfig: {}
23-
}
22+
static defaultProps = defaultProps
2423
static canPlay (url) {
2524
return MATCH_URL.test(url)
2625
}
2726
componentDidMount () {
2827
window.addEventListener('message', this.onMessage, false)
2928
this.iframe = this.refs.iframe
29+
30+
if (!this.props.url && this.props.vimeoConfig.preload) {
31+
this.preloading = true
32+
this.load(BLANK_VIDEO_URL)
33+
}
34+
3035
super.componentDidMount()
3136
}
32-
shouldComponentUpdate (nextProps) {
33-
return this.props.url !== nextProps.url
34-
}
35-
play (url) {
36-
if (!url) {
37-
this.postMessage('play')
37+
load (url) {
38+
const id = url.match(MATCH_URL)[3]
39+
const iframeParams = {
40+
...DEFAULT_IFRAME_PARAMS,
41+
...this.props.vimeoConfig.iframeParams
3842
}
43+
this.iframe.src = IFRAME_SRC + id + '?' + stringify(iframeParams)
44+
}
45+
play () {
46+
this.postMessage('play')
3947
}
4048
pause () {
4149
this.postMessage('pause')
4250
}
4351
stop () {
44-
// No need
52+
this.iframe.src = ''
4553
}
4654
seekTo (fraction) {
4755
this.postMessage('seekTo', this.duration * fraction)
@@ -81,19 +89,11 @@ export default class Vimeo extends Base {
8189
return this.iframe.contentWindow && this.iframe.contentWindow.postMessage(data, this.origin)
8290
}
8391
render () {
84-
const id = this.props.url.match(MATCH_URL)[3]
8592
const style = {
93+
display: this.props.url ? 'block' : 'none',
8694
width: '100%',
8795
height: '100%'
8896
}
89-
const iframeParams = { ...DEFAULT_IFRAME_PARAMS, ...this.props.vimeoConfig.iframeParams }
90-
return (
91-
<iframe
92-
ref='iframe'
93-
src={IFRAME_SRC + id + '?' + queryString.stringify(iframeParams)}
94-
style={style}
95-
frameBorder='0'
96-
/>
97-
)
97+
return <iframe ref='iframe' frameBorder='0' style={style} />
9898
}
9999
}

0 commit comments

Comments
 (0)