Skip to content

Commit

Permalink
Use lazy players
Browse files Browse the repository at this point in the history
Refactors player logic to enable lazy loading of players, only loading player files for URLs that you pass in

Breaking changes:
- Removes single player imports, as they are now equivalent to importing the default ReactPlayer and using one type of URL
- Requires React 16.6 or later
  • Loading branch information
cookpete committed Apr 12, 2020
1 parent bb69b1d commit 1752b8d
Show file tree
Hide file tree
Showing 31 changed files with 262 additions and 373 deletions.
19 changes: 1 addition & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ Prop | Description

#### Config prop

As of version `0.24`, there is a single `config` prop to override the settings for the various players. If you are migrating from an earlier version, you must move all the old config props inside `config`:
There is a single `config` prop to override settings for each type of player:

```jsx
<ReactPlayer
Expand Down Expand Up @@ -220,23 +220,6 @@ See [`jsFiddle` example](https://jsfiddle.net/e6w3rtj1/)

You can use your own version of any player SDK, assuming the correct `window` global is set before the player mounts. For example, to use a local version of [`hls.js`](https://cdnjs.com/libraries/hls.js), add `<script src='/path/hls.js'></script>` to your app. If `window.Hls` is available when ReactPlayer mounts, it will use that instead of loading `hls.js` from `cdnjs`. See [#605](https://github.com/CookPete/react-player/issues/605#issuecomment-492561909) for more information.

#### Single player imports

If you are only ever playing a single type of URL, you can import individual players to keep your bundle size down:

```jsx
import YouTubePlayer from 'react-player/lib/players/YouTube'

<YouTubePlayer
url='https://www.youtube.com/watch?v=d46Azg3Pm4c'
playing
controls
// Other ReactPlayer props will work here
/>
```

See a list of available players [here](https://github.com/CookPete/react-player/tree/master/src/players).

#### Standalone player

If you aren’t using React, you can still render a player using the standalone library:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
},
"homepage": "https://github.com/CookPete/react-player",
"peerDependencies": {
"react": "*"
"react": ">=16.6.0"
},
"devDependencies": {
"@ava/babel": "^1.0.1",
Expand Down
18 changes: 9 additions & 9 deletions src/Player.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export default class Player extends Component {
static displayName = 'Player'
static propTypes = propTypes
static defaultProps = defaultProps

mounted = false
isReady = false
isPlaying = false // Track playing state internally to prevent bugs
Expand All @@ -17,10 +18,9 @@ export default class Player extends Component {
startOnPlay = true
seekOnPlay = null
onDurationCalled = false

componentDidMount () {
this.mounted = true
this.player.load(this.props.url)
this.progress()
}

componentWillUnmount () {
Expand Down Expand Up @@ -83,6 +83,12 @@ export default class Player extends Component {
}
}

playerDidMount = player => {
this.player = player
this.player.load(this.props.url)
this.progress()
}

getDuration () {
if (!this.isReady) return null
return this.player.getDuration()
Expand Down Expand Up @@ -228,12 +234,6 @@ export default class Player extends Component {
this.isLoading = false
}

ref = player => {
if (player) {
this.player = player
}
}

render () {
const Player = this.props.activePlayer
if (!Player) {
Expand All @@ -242,7 +242,7 @@ export default class Player extends Component {
return (
<Player
{...this.props}
ref={this.ref}
didMount={this.playerDidMount}
onReady={this.handleReady}
onPlay={this.handlePlay}
onPause={this.handlePause}
Expand Down
34 changes: 13 additions & 21 deletions src/ReactPlayer.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import React, { Component } from 'react'
import React, { Component, Suspense, lazy } from 'react'

import { propTypes, defaultProps, DEPRECATED_CONFIG_PROPS } from './props'
import { getConfig, omit, isEqual } from './utils'
import players from './players'
import Player from './Player'
import Preview from './Preview'
import { FilePlayer } from './players/FilePlayer'
import renderPreloadPlayers from './preload'

const SUPPORTED_PROPS = Object.keys(propTypes)
const FilePlayer = lazy(() => import('./players/FilePlayer'))
const Preview = lazy(() => import('./Preview'))

const SUPPORTED_PROPS = Object.keys(propTypes)
let customPlayers = []

export default class ReactPlayer extends Component {
static displayName = 'ReactPlayer'
static propTypes = propTypes
static defaultProps = defaultProps

static addCustomPlayer = player => {
customPlayers.push(player)
}
Expand All @@ -21,9 +25,6 @@ export default class ReactPlayer extends Component {
customPlayers = []
}

static displayName = 'ReactPlayer'
static propTypes = propTypes
static defaultProps = defaultProps
static canPlay = url => {
for (const Player of [...customPlayers, ...players]) {
if (Player.canPlay(url)) {
Expand Down Expand Up @@ -109,7 +110,7 @@ export default class ReactPlayer extends Component {
getActivePlayer (url) {
for (const Player of [...customPlayers, ...players]) {
if (Player.canPlay(url)) {
return Player
return Player.Player || Player
}
}
// Fall back to FilePlayer if nothing else can play the URL
Expand All @@ -129,7 +130,7 @@ export default class ReactPlayer extends Component {
return (
<Player
{...this.props}
key={activePlayer.displayName}
key={url}
ref={this.activePlayerRef}
config={this.config}
activePlayer={activePlayer}
Expand Down Expand Up @@ -157,19 +158,10 @@ export default class ReactPlayer extends Component {
const preview = <Preview url={url} light={light} playIcon={playIcon} onClick={this.handleClickPreview} />
return (
<Wrapper ref={this.wrapperRef} style={{ ...style, width, height }} {...otherProps}>
{showPreview ? preview : players}
<Suspense fallback={null}>
{showPreview ? preview : players}
</Suspense>
</Wrapper>
)
}
}

export { default as YouTube } from './players/YouTube'
export { default as SoundCloud } from './players/SoundCloud'
export { default as Vimeo } from './players/Vimeo'
export { default as Facebook } from './players/Facebook'
export { default as Streamable } from './players/Streamable'
export { default as Wistia } from './players/Wistia'
export { default as Twitch } from './players/Twitch'
export { default as DailyMotion } from './players/DailyMotion'
export { default as Mixcloud } from './players/Mixcloud'
export { default as FilePlayer } from './players/FilePlayer'
15 changes: 15 additions & 0 deletions src/patterns.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const MATCH_URL_YOUTUBE = /(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})|youtube\.com\/playlist\?list=/
export const MATCH_URL_SOUNDCLOUD = /(soundcloud\.com|snd\.sc)\/.+$/
export const MATCH_URL_VIMEO = /vimeo\.com\/.+/
export const MATCH_URL_VIMEO_FILE = /vimeo\.com\/external\/[0-9]+\..+/
export const MATCH_URL_FACEBOOK = /facebook\.com\/([^/?].+\/)?video(s|\.php)[/?].*$/
export const MATCH_URL_STREAMABLE = /streamable\.com\/([a-z0-9]+)$/
export const MATCH_URL_WISTIA = /(?:wistia\.com|wi\.st)\/(?:medias|embed)\/(.*)$/
export const MATCH_URL_TWITCH_VIDEO = /(?:www\.|go\.)?twitch\.tv\/videos\/(\d+)($|\?)/
export const MATCH_URL_TWITCH_CHANNEL = /(?:www\.|go\.)?twitch\.tv\/([a-z0-9_]+)($|\?)/
export const MATCH_URL_DAILYMOTION = /^(?:(?:https?):)?(?:\/\/)?(?:www\.)?(?:(?:dailymotion\.com(?:\/embed)?\/video)|dai\.ly)\/([a-zA-Z0-9]+)(?:_[\w_-]+)?$/
export const MATCH_URL_MIXCLOUD = /mixcloud\.com\/([^/]+\/[^/]+)/
export const AUDIO_EXTENSIONS = /\.(m4a|mp4a|mpga|mp2|mp2a|mp3|m2a|m3a|wav|weba|aac|oga|spx)($|\?)/i
export const VIDEO_EXTENSIONS = /\.(mp4|og[gv]|webm|mov|m4v)($|\?)/i
export const HLS_EXTENSIONS = /\.(m3u8)($|\?)/i
export const DASH_EXTENSIONS = /\.(mpd)($|\?)/i
10 changes: 6 additions & 4 deletions src/players/DailyMotion.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import React, { Component } from 'react'

import { callPlayer, getSDK, parseStartTime } from '../utils'
import createSinglePlayer from '../singlePlayer'

const SDK_URL = 'https://api.dmcdn.net/all.js'
const SDK_GLOBAL = 'DM'
const SDK_GLOBAL_READY = 'dmAsyncInit'
const MATCH_URL = /^(?:(?:https?):)?(?:\/\/)?(?:www\.)?(?:(?:dailymotion\.com(?:\/embed)?\/video)|dai\.ly)\/([a-zA-Z0-9]+)(?:_[\w_-]+)?$/

export class DailyMotion extends Component {
export default class DailyMotion extends Component {
static displayName = 'DailyMotion'
static canPlay = url => MATCH_URL.test(url)
static loopOnEnded = true

callPlayer = callPlayer

componentDidMount () {
this.props.didMount && this.props.didMount(this)
}

load (url) {
const { controls, config, onError, playing } = this.props
const [, id] = url.match(MATCH_URL)
Expand Down Expand Up @@ -116,5 +120,3 @@ export class DailyMotion extends Component {
)
}
}

export default createSinglePlayer(DailyMotion)
9 changes: 5 additions & 4 deletions src/players/Facebook.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import React, { Component } from 'react'

import { callPlayer, getSDK, randomString } from '../utils'
import createSinglePlayer from '../singlePlayer'

const SDK_URL = 'https://connect.facebook.net/en_US/sdk.js'
const SDK_GLOBAL = 'FB'
const SDK_GLOBAL_READY = 'fbAsyncInit'
const MATCH_URL = /^https?:\/\/(www\.)?facebook\.com.*\/(video(s)?|watch|story)(\.php?|\/).+$/
const PLAYER_ID_PREFIX = 'facebook-player-'

export class Facebook extends Component {
export default class Facebook extends Component {
static displayName = 'Facebook'
static canPlay = url => MATCH_URL.test(url)
static loopOnEnded = true

callPlayer = callPlayer
playerID = this.props.config.facebook.playerId || `${PLAYER_ID_PREFIX}${randomString()}`

componentDidMount () {
this.props.didMount && this.props.didMount(this)
}

load (url, isReady) {
if (isReady) {
getSDK(SDK_URL, SDK_GLOBAL, SDK_GLOBAL_READY).then(FB => FB.XFBML.parse())
Expand Down Expand Up @@ -114,5 +117,3 @@ export class Facebook extends Component {
)
}
}

export default createSinglePlayer(Facebook)
6 changes: 2 additions & 4 deletions src/players/FilePlayer.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { Component } from 'react'

import { getSDK, isMediaStream } from '../utils'
import createSinglePlayer from '../singlePlayer'

const IOS = typeof navigator !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream
const AUDIO_EXTENSIONS = /\.(m4a|mp4a|mpga|mp2|mp2a|mp3|m2a|m3a|wav|weba|aac|oga|spx)($|\?)/i
Expand Down Expand Up @@ -48,12 +47,13 @@ function canEnablePIP (url) {
return canPlay(url) && (!!document.pictureInPictureEnabled || supportsWebKitPresentationMode()) && !AUDIO_EXTENSIONS.test(url)
}

export class FilePlayer extends Component {
export default class FilePlayer extends Component {
static displayName = 'FilePlayer'
static canPlay = canPlay
static canEnablePIP = canEnablePIP

componentDidMount () {
this.props.didMount && this.props.didMount(this)
this.addListeners(this.player)
if (IOS) {
this.player.load()
Expand Down Expand Up @@ -337,5 +337,3 @@ export class FilePlayer extends Component {
)
}
}

export default createSinglePlayer(FilePlayer)
10 changes: 6 additions & 4 deletions src/players/Mixcloud.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import React, { Component } from 'react'

import { callPlayer, getSDK, queryString } from '../utils'
import createSinglePlayer from '../singlePlayer'

const SDK_URL = 'https://widget.mixcloud.com/media/js/widgetApi.js'
const SDK_GLOBAL = 'Mixcloud'
const MATCH_URL = /mixcloud\.com\/([^/]+\/[^/]+)/

export class Mixcloud extends Component {
export default class Mixcloud extends Component {
static displayName = 'Mixcloud'
static canPlay = url => MATCH_URL.test(url)
static loopOnEnded = true
Expand All @@ -16,6 +15,11 @@ export class Mixcloud extends Component {
duration = null
currentTime = null
secondsLoaded = null

componentDidMount () {
this.props.didMount && this.props.didMount(this)
}

load (url) {
getSDK(SDK_URL, SDK_GLOBAL).then(Mixcloud => {
this.player = Mixcloud.PlayerWidget(this.iframe)
Expand Down Expand Up @@ -101,5 +105,3 @@ export class Mixcloud extends Component {
)
}
}

export default createSinglePlayer(Mixcloud)
10 changes: 6 additions & 4 deletions src/players/SoundCloud.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import React, { Component } from 'react'

import { callPlayer, getSDK } from '../utils'
import createSinglePlayer from '../singlePlayer'

const SDK_URL = 'https://w.soundcloud.com/player/api.js'
const SDK_GLOBAL = 'SC'
const MATCH_URL = /(?:soundcloud\.com|snd\.sc)\/[^.]+$/

export class SoundCloud extends Component {
export default class SoundCloud extends Component {
static displayName = 'SoundCloud'
static canPlay = url => MATCH_URL.test(url)
static loopOnEnded = true
Expand All @@ -16,6 +15,11 @@ export class SoundCloud extends Component {
duration = null
currentTime = null
fractionLoaded = null

componentDidMount () {
this.props.didMount && this.props.didMount(this)
}

load (url, isReady) {
getSDK(SDK_URL, SDK_GLOBAL).then(SC => {
if (!this.iframe) return
Expand Down Expand Up @@ -107,5 +111,3 @@ export class SoundCloud extends Component {
)
}
}

export default createSinglePlayer(SoundCloud)
10 changes: 6 additions & 4 deletions src/players/Streamable.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import React, { Component } from 'react'

import { callPlayer, getSDK } from '../utils'
import createSinglePlayer from '../singlePlayer'

const SDK_URL = 'https://cdn.embed.ly/player-0.1.0.min.js'
const SDK_GLOBAL = 'playerjs'
const MATCH_URL = /streamable\.com\/([a-z0-9]+)$/

export class Streamable extends Component {
export default class Streamable extends Component {
static displayName = 'Streamable'
static canPlay = url => MATCH_URL.test(url)

callPlayer = callPlayer
duration = null
currentTime = null
secondsLoaded = null

componentDidMount () {
this.props.didMount && this.props.didMount(this)
}

load (url) {
getSDK(SDK_URL, SDK_GLOBAL).then(playerjs => {
if (!this.iframe) return
Expand Down Expand Up @@ -107,5 +111,3 @@ export class Streamable extends Component {
)
}
}

export default createSinglePlayer(Streamable)
Loading

0 comments on commit 1752b8d

Please sign in to comment.