Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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} />
}
}
Loading