Skip to content

Commit

Permalink
Merge pull request #13 from CookPete/fix-autoplay-prime
Browse files Browse the repository at this point in the history
Prime players to enable autoplay when out of focus
  • Loading branch information
cookpete committed Dec 24, 2015
2 parents a9e9854 + 17fbef8 commit 9240861
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 109 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. This projec

### Unreleased

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


Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,13 @@ These props allow you to override the parameters for the various players

Prop | Description
---- | -----------
soundcloudConfig | An object containing configuration for the SoundCloud player. Includes `clientId`, which can be used to override the default `client_id`
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)
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)
soundcloudConfig | Configuration object for the SoundCloud player. Set `clientId`, to your own SoundCloud app [client ID](https://soundcloud.com/you/apps)
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)
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)

##### Preloading

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.

### Methods

Expand Down
68 changes: 46 additions & 22 deletions src/ReactPlayer.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,75 @@
import React, { Component } from 'react'
import 'array.prototype.find'

import propTypes from './propTypes'
import { propTypes, defaultProps } from './props'
import players from './players'

const PROGRESS_FREQUENCY = 500

export default class ReactPlayer extends Component {
static propTypes = propTypes
static defaultProps = {
volume: 0.8,
width: 640,
height: 360,
onPlay: function () {}, // TODO: Empty func var in react?
onPause: function () {},
onBuffer: function () {},
onEnded: function () {}
}
static defaultProps = defaultProps
static canPlay (url) {
return players.some(player => player.canPlay(url))
}
state = {
Player: this.getPlayer(this.props.url)
componentDidMount () {
this.progress()
}
componentWillReceiveProps (nextProps) {
if (this.props.url !== nextProps.url) {
this.setState({
Player: this.getPlayer(nextProps.url)
})
}
componentWillUnmount () {
clearTimeout(this.progressTimeout)
}
getPlayer (url) {
return players.find(Player => Player.canPlay(url))
shouldComponentUpdate (nextProps) {
return (
this.props.url !== nextProps.url ||
this.props.playing !== nextProps.playing ||
this.props.volume !== nextProps.volume
)
}
seekTo = fraction => {
const player = this.refs.player
if (player) {
player.seekTo(fraction)
}
}
progress = () => {
if (this.props.url && this.refs.player) {
let progress = {}
const loaded = this.refs.player.getFractionLoaded()
const played = this.refs.player.getFractionPlayed()
if (!this.prevLoaded || loaded !== this.prevLoaded) {
progress.loaded = this.prevLoaded = loaded
}
if (!this.prevPlayed || played !== this.prevPlayed) {
progress.played = this.prevPlayed = played
}
if (progress.loaded || progress.played) {
this.props.onProgress(progress)
}
}
this.progressTimeout = setTimeout(this.progress, PROGRESS_FREQUENCY)
}
renderPlayer = Player => {
const active = Player.canPlay(this.props.url)
const { youtubeConfig, soundcloudConfig, vimeoConfig, ...activeProps } = this.props
const props = active ? { ...activeProps, ref: 'player' } : {}
return (
<Player
key={Player.name}
youtubeConfig={youtubeConfig}
soundcloudConfig={soundcloudConfig}
vimeoConfig={vimeoConfig}
{...props}
/>
)
}
render () {
const Player = this.state.Player
const style = {
width: this.props.width,
height: this.props.height
}
return (
<div style={style}>
{ Player && <Player ref='player' {...this.props} /> }
{ players.map(this.renderPlayer) }
</div>
)
}
Expand Down
42 changes: 15 additions & 27 deletions src/players/Base.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
import { Component } from 'react'

import propTypes from '../propTypes'

const UPDATE_FREQUENCY = 500
import { propTypes, defaultProps } from '../props'

export default class Base extends Component {
static propTypes = propTypes
static defaultProps = {
onProgress: function () {}
}
static defaultProps = defaultProps
componentDidMount () {
this.play(this.props.url)
this.update()
if (this.props.url) {
this.load(this.props.url)
}
}
componentWillUnmount () {
this.stop()
clearTimeout(this.updateTimeout)
}
componentWillReceiveProps (nextProps) {
// Invoke player methods based on incoming props
if (this.props.url !== nextProps.url) {
this.play(nextProps.url)
this.props.onProgress({ played: 0, loaded: 0 })
if (this.props.url !== nextProps.url && nextProps.url) {
this.load(nextProps.url, nextProps.playing)
this.props.onProgress({ played: 0, loaded: 0 }) // Needed?
} else if (this.props.url && !nextProps.url) {
this.stop()
clearTimeout(this.updateTimeout)
} else if (!this.props.playing && nextProps.playing) {
this.play()
} else if (this.props.playing && !nextProps.playing) {
Expand All @@ -30,24 +29,13 @@ export default class Base extends Component {
this.setVolume(nextProps.volume)
}
}
update = () => {
let progress = {}
const loaded = this.getFractionLoaded()
const played = this.getFractionPlayed()
if (!this.prevLoaded || loaded !== this.prevLoaded) {
progress.loaded = this.prevLoaded = loaded
}
if (!this.prevPlayed || played !== this.prevPlayed) {
progress.played = this.prevPlayed = played
}
if (progress.loaded || progress.played) {
this.props.onProgress(progress)
}
this.updateTimeout = setTimeout(this.update, UPDATE_FREQUENCY)
shouldComponentUpdate (nextProps) {
return this.props.url !== nextProps.url
}
onReady = () => {
this.setVolume(this.props.volume)
if (this.props.playing) {
if (this.props.playing || this.preloading) {
this.preloading = false
this.play()
}
}
Expand Down
15 changes: 8 additions & 7 deletions src/players/FilePlayer.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React from 'react'

import propTypes from '../propTypes'
import { propTypes, defaultProps } from '../props'
import Base from './Base'

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

export default class FilePlayer extends Base {
static propTypes = propTypes
static defaultProps = defaultProps
static canPlay (url) {
return VIDEO_EXTENSIONS.test(url) || AUDIO_EXTENSIONS.test(url)
}
Expand All @@ -18,19 +19,18 @@ export default class FilePlayer extends Base {
this.player.onpause = this.props.onPause
this.player.onended = this.props.onEnded
this.player.onerror = this.props.onError
super.componentDidMount()
}
shouldComponentUpdate (nextProps) {
return this.props.url !== nextProps
load (url) {
this.player.src = url
}
play (url) {
play () {
this.player.play()
}
pause () {
this.player.pause()
}
stop () {
// No need to stop
this.player.src = ''
}
seekTo (fraction) {
this.player.currentTime = this.player.duration * fraction
Expand All @@ -48,10 +48,11 @@ export default class FilePlayer extends Base {
}
render () {
const Media = AUDIO_EXTENSIONS.test(this.props.url) ? 'audio' : 'video'
const style = { display: this.props.url ? 'block' : 'none' }
return (
<Media
ref='player'
src={this.props.url}
style={style}
width={this.props.width}
height={this.props.height}
/>
Expand Down
25 changes: 12 additions & 13 deletions src/players/SoundCloud.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
import React from 'react'
import loadScript from 'load-script'

import propTypes from '../propTypes'
import { propTypes, defaultProps } from '../props'
import Base from './Base'

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

export default class SoundCloud extends Base {
static propTypes = propTypes
static defaultProps = {
soundcloudConfig: {
clientId: DEFAULT_CLIENT_ID
}
}
static defaultProps = defaultProps
static canPlay (url) {
return MATCH_URL.test(url)
}
state = {
image: null
}
shouldComponentUpdate (nextProps, nextState) {
return this.state.image !== nextState.image
return (
super.shouldComponentUpdate(nextProps, nextState) ||
this.state.image !== nextState.image
)
}
getSDK () {
if (window[SDK_GLOBAL]) {
Expand All @@ -45,11 +43,7 @@ export default class SoundCloud extends Base {
return fetch(RESOLVE_URL + '?url=' + url + '&client_id=' + this.props.soundcloudConfig.clientId)
.then(response => response.json())
}
play (url) {
if (!url && this.player) {
this.player.play()
return
}
load (url) {
this.stop()
this.getSDK().then(SC => {
this.getSongData(url).then(data => {
Expand Down Expand Up @@ -81,6 +75,10 @@ export default class SoundCloud extends Base {
onfinish: this.props.onFinish,
ondataerror: this.props.onError
}
play () {
if (!this.player) return
this.player.play()
}
pause () {
if (!this.player) return
this.player.pause()
Expand All @@ -107,6 +105,7 @@ export default class SoundCloud extends Base {
}
render () {
const style = {
display: this.props.url ? 'block' : 'none',
height: '100%',
backgroundImage: this.state.image ? 'url(' + this.state.image + ')' : null,
backgroundSize: 'cover',
Expand Down
44 changes: 22 additions & 22 deletions src/players/Vimeo.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React from 'react'
import queryString from 'query-string'
import { stringify } from 'query-string'

import propTypes from '../propTypes'
import { propTypes, defaultProps } from '../props'
import Base from './Base'

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

export default class Vimeo extends Base {
static propTypes = propTypes
static defaultProps = {
vimeoConfig: {}
}
static defaultProps = defaultProps
static canPlay (url) {
return MATCH_URL.test(url)
}
componentDidMount () {
window.addEventListener('message', this.onMessage, false)
this.iframe = this.refs.iframe

if (!this.props.url && this.props.vimeoConfig.preload) {
this.preloading = true
this.load(BLANK_VIDEO_URL)
}

super.componentDidMount()
}
shouldComponentUpdate (nextProps) {
return this.props.url !== nextProps.url
}
play (url) {
if (!url) {
this.postMessage('play')
load (url) {
const id = url.match(MATCH_URL)[3]
const iframeParams = {
...DEFAULT_IFRAME_PARAMS,
...this.props.vimeoConfig.iframeParams
}
this.iframe.src = IFRAME_SRC + id + '?' + stringify(iframeParams)
}
play () {
this.postMessage('play')
}
pause () {
this.postMessage('pause')
}
stop () {
// No need
this.iframe.src = ''
}
seekTo (fraction) {
this.postMessage('seekTo', this.duration * fraction)
Expand Down Expand Up @@ -81,19 +89,11 @@ export default class Vimeo extends Base {
return this.iframe.contentWindow && this.iframe.contentWindow.postMessage(data, this.origin)
}
render () {
const id = this.props.url.match(MATCH_URL)[3]
const style = {
display: this.props.url ? 'block' : 'none',
width: '100%',
height: '100%'
}
const iframeParams = { ...DEFAULT_IFRAME_PARAMS, ...this.props.vimeoConfig.iframeParams }
return (
<iframe
ref='iframe'
src={IFRAME_SRC + id + '?' + queryString.stringify(iframeParams)}
style={style}
frameBorder='0'
/>
)
return <iframe ref='iframe' frameBorder='0' style={style} />
}
}

0 comments on commit 9240861

Please sign in to comment.