Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: spotify support latest #1877

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</p>

<p align='center'>
A React component for playing a variety of URLs, including file paths, YouTube, Facebook, Twitch, SoundCloud, Streamable, Vimeo, Wistia, Mixcloud, DailyMotion and Kaltura. Not using React? <a href='#standalone-player'>No problem.</a>
A React component for playing a variety of URLs, including file paths, YouTube, Facebook, Twitch, SoundCloud, Streamable, Vimeo, Wistia, Mixcloud, Spotify, DailyMotion and Kaltura. Not using React? <a href='#standalone-player'>No problem.</a>
</p>

---
Expand Down Expand Up @@ -146,6 +146,7 @@ Key | Options
`dailymotion` | `params`: Override the [default player vars](https://developer.dailymotion.com/player#player-parameters)
`twitch` | `options`: Override the [default player options](https://dev.twitch.tv/docs/embed)<br />`playerId`: Override player ID for consistent server-side rendering (use with [`react-uid`](https://github.com/thearnica/react-uid))
`file` | `attributes`: Apply [element attributes](https://developer.mozilla.org/en/docs/Web/HTML/Element/video#Attributes)<br />`forceVideo`: Always render a `<video>` element<br />`forceAudio`: Always render an `<audio>` element<br />`forceHLS`: Use [hls.js](https://github.com/video-dev/hls.js) for HLS streams<br />`forceSafariHLS`: Use [hls.js](https://github.com/video-dev/hls.js) for HLS streams, [even on Safari](https://github.com/cookpete/react-player/pull/1560)<br />`forceDisableHLS`: Disable usage [hls.js](https://github.com/video-dev/hls.js) for HLS streams<br />`forceDASH`: Always use [dash.js](https://github.com/Dash-Industry-Forum/dash.js) for DASH streams<br />`forceFLV`: Always use [flv.js](https://github.com/Bilibili/flv.js)<br />`hlsOptions`: Override the [default `hls.js` options](https://github.com/video-dev/hls.js/blob/master/docs/API.md#fine-tuning)<br />`hlsVersion`: Override the [`hls.js`](https://github.com/video-dev/hls.js) version loaded from [`jsdelivr`](https://www.jsdelivr.com/package/npm/hls.js), default: `0.13.1`<br />`dashVersion`: Override the [`dash.js`](https://github.com/Dash-Industry-Forum/dash.js) version loaded from [`cdnjs`](https://cdnjs.com/libraries/dashjs), default: `2.9.2`<br />`flvVersion`: Override the [`flv.js`](https://github.com/Bilibili/flv.js) version loaded from [`jsdelivr`](https://www.jsdelivr.com/package/npm/flv.js), default: `1.5.0`
`spotify` | `width`: Optionally set the width of the player, defaults to 100% <br />`height`: Set the height of the player defaults to 100%

### Methods

Expand Down Expand Up @@ -334,6 +335,7 @@ ReactPlayer `v2.0` changes single player imports and adds lazy loading players.
* DailyMotion videos use the [DailyMotion Player API](https://developer.dailymotion.com/player)
* Vidyard videos use the [Vidyard Player API](https://knowledge.vidyard.com/hc/en-us/articles/360019034753-Using-the-Vidyard-Player-API)
* Kaltura's `react-player` implementation uses the embed.ly [`Player.js`](https://github.com/embedly/player.js) API but Kaltura specific APIs are also available, see [Kaltura Player API](http://player.kaltura.com/docs/index.php?path=kwidget)
* Spotify episodes and tracks with the spotify [iframe api](https://developer.spotify.com/documentation/embeds/tutorials/using-the-iframe-api), episodes have full control but for tracks if the user is not logged in you will only get a short preview of the song
* [Supported file types](https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats) are playing using [`<video>`](https://developer.mozilla.org/en/docs/Web/HTML/Element/video) or [`<audio>`](https://developer.mozilla.org/en/docs/Web/HTML/Element/audio) elements
* HLS streams are played using [`hls.js`](https://github.com/video-dev/hls.js)
* DASH streams are played using [`dash.js`](https://github.com/Dash-Industry-Forum/dash.js)
Expand Down
7 changes: 7 additions & 0 deletions examples/react/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,13 @@ class App extends Component {
{this.renderLoadButton('https://cdnapisec.kaltura.com/p/2507381/sp/250738100/embedIframeJs/uiconf_id/44372392/partner_id/2507381?iframeembed=true&playerId=kaltura_player_1605622336&entry_id=1_i1jmzcn3', 'Test B')}
</td>
</tr>
<tr>
<th>Spotify</th>
<td>
{this.renderLoadButton('spotify:episode:4ho8NBQucRw6jkLP7F5Hlr', 'Test A')}
{this.renderLoadButton('spotify:track:6FMGHBZaYQY7oFS7vyZx8a', 'Test B')}
</td>
</tr>
<tr>
<th>Files</th>
<td>
Expand Down
4 changes: 3 additions & 1 deletion src/patterns.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const MATCH_URL_TWITCH_CHANNEL = /(?:www\.|go\.)?twitch\.tv\/([a-zA-Z0-9_
export const MATCH_URL_DAILYMOTION = /^(?:(?:https?):)?(?:\/\/)?(?:www\.)?(?:(?:dailymotion\.com(?:\/embed)?\/video)|dai\.ly)\/([a-zA-Z0-9]+)(?:_[\w_-]+)?(?:[\w.#_-]+)?/
export const MATCH_URL_MIXCLOUD = /mixcloud\.com\/([^/]+\/[^/]+)/
export const MATCH_URL_VIDYARD = /vidyard.com\/(?:watch\/)?([a-zA-Z0-9-_]+)/
export const MATCH_URL_SPOTIFY = /spotify.+$/
export const MATCH_URL_KALTURA = /^https?:\/\/[a-zA-Z]+\.kaltura.(com|org)\/p\/([0-9]+)\/sp\/([0-9]+)00\/embedIframeJs\/uiconf_id\/([0-9]+)\/partner_id\/([0-9]+)(.*)entry_id.([a-zA-Z0-9-_].*)$/
export const AUDIO_EXTENSIONS = /\.(m4a|m4b|mp4a|mpga|mp2|mp2a|mp3|m2a|m3a|wav|weba|aac|oga|spx)($|\?)/i
export const VIDEO_EXTENSIONS = /\.(mp4|og[gv]|webm|mov|m4v)(#t=[,\d+]+)?($|\?)/i
Expand Down Expand Up @@ -63,5 +64,6 @@ export const canPlay = {
mixcloud: url => MATCH_URL_MIXCLOUD.test(url),
vidyard: url => MATCH_URL_VIDYARD.test(url),
kaltura: url => MATCH_URL_KALTURA.test(url),
file: canPlayFile
file: canPlayFile,
spotify: url => MATCH_URL_SPOTIFY.test(url)
}
141 changes: 141 additions & 0 deletions src/players/Spotify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import React, { Component } from 'react'
import { getSDK, callPlayer } from '../utils'
import { canPlay } from '../patterns'

const SDK_URL = 'https://open.spotify.com/embed/iframe-api/v1'
const SDK_GLOBAL = 'SpotifyIframeApi'
const SDK_GLOBAL_READY = 'SpotifyIframeApiReady'

export default class Spotify extends Component {
static displayName = 'Spotify'
static loopOnEnded = true
static canPlay = canPlay.spotify
callPlayer = callPlayer
duration = null
currentTime = null
totalTime = null
player = null

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

load (url) {
if (window[SDK_GLOBAL] && !this.player) {
this.initializePlayer(window[SDK_GLOBAL], url)
return
} else if (this.player) {
this.callPlayer('loadUri', this.props.url)
return
}

window.onSpotifyIframeApiReady = (IFrameAPI) => {
window[SDK_GLOBAL] = IFrameAPI
this.props.onReady()
return this.initializePlayer(IFrameAPI, url)
}
getSDK(SDK_URL, SDK_GLOBAL, SDK_GLOBAL_READY)
}

initializePlayer = (IFrameAPI, url) => {
if (!this.container) return

const options = {
width: this.props.config.width ?? '100%',
height: this.props.config.height ?? '100%',
uri: url
}
const callback = (EmbedController) => {
this.player = EmbedController
this.player.addListener('playback_update', this.onStateChange)
this.player.addListener('ready', this.props.onReady)
}
IFrameAPI.createController(this.container, options, callback)
}

onStateChange = (event) => {
const { data } = event
const { onPlay, onPause, onBuffer, onBufferEnd, onEnded } = this.props

if (data.position >= data.duration && data.position && data.duration) {
onEnded()
}
if (data.isPaused === true) onPause()
if (data.isPaused === false && data.isBuffering === false) {
this.currentTime = data.position
this.totalTime = data.duration
onPlay()
onBufferEnd()
}
if (data.isBuffering === true) onBuffer()
}

play () {
this.callPlayer('resume')
}

pause () {
this.callPlayer('pause')
}

stop () {
this.callPlayer('destroy')
}

seekTo (amount) {
this.callPlayer('seek', amount)
if (!this.props.playing) {
this.pause()
} else {
this.play()
}
}

setVolume (fraction) {
// No volume support
}

mute () {
// No volume support
}

unmute () {
// No volume support
}

setPlaybackRate (rate) {
// No playback rate support
}

setLoop (loop) {
// No loop support
}

getDuration () {
return this.totalTime / 1000
}

getCurrentTime () {
return this.currentTime / 1000
}

getSecondsLoaded () {
// No seconds loaded support
}

ref = container => {
this.container = container
}

render () {
const style = {
width: '100%',
height: '100%'
}
return (
<div style={style}>
<div ref={this.ref} />
</div>
)
}
}
6 changes: 6 additions & 0 deletions src/players/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ export default [
canPlay: canPlay.kaltura,
lazyPlayer: lazy(() => import(/* webpackChunkName: 'reactPlayerKaltura' */'./Kaltura'))
},
{
key: 'spotify',
name: 'Spotify',
canPlay: canPlay.spotify,
lazyPlayer: lazy(() => import(/* webpackChunkName: 'reactPlayerSpotify' */'./Spotify'))
},
{
key: 'file',
name: 'FilePlayer',
Expand Down
8 changes: 8 additions & 0 deletions src/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ export const propTypes = {
dashVersion: string,
flvVersion: string
}),
spotify: shape({
width: oneOfType([string, number]),
height: oneOfType([string, number])
}),
wistia: shape({
options: object,
playerId: string,
Expand Down Expand Up @@ -187,6 +191,10 @@ export const defaultProps = {
flvVersion: '1.5.0',
forceDisableHls: false
},
spotify: {
width: '100%',
height: '100%'
},
wistia: {
options: {},
playerId: null,
Expand Down
51 changes: 51 additions & 0 deletions test/players/Spotify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { test } from 'zora'
import sinon from 'sinon'
import React from 'react'
import { create } from 'react-test-renderer'
import '../helpers/server-safe-globals'
import { testPlayerMethods } from '../helpers/helpers'
import { getSDK as originalGetSDK } from '../../src/utils'
import Spotify from '../../src/players/Spotify'

global.window = {}
const TEST_URL = 'spotify:track:0KhB428j00T8lxKCpHweKw'
const TEST_CONFIG = {
options: {}
}

testPlayerMethods(Spotify, {
play: 'resume',
pause: 'pause',
stop: 'destroy',
seekTo: 'seek'
}, { url: TEST_URL })

test('load()', t => {
class Player {
constructor (container) {
t.ok(container === 'mock-container')
}
}
const getSDK = sinon.stub(originalGetSDK, 'stub').resolves({ Player })

const instance = create(
<Spotify url={TEST_URL} config={TEST_CONFIG} />
).getInstance()
instance.iframe = 'mock-container'
instance.load(TEST_URL)
t.truthy(window.onSpotifyIframeApiReady)
t.ok(getSDK.calledOnce)
getSDK.restore()
})

test('render()', t => {
const style = { width: '100%', height: '100%' }
t.deepEqual(
create(<Spotify url={TEST_URL} />).toJSON(),
create(
<div style={style}>
<div />
</div>
).toJSON()
)
})
2 changes: 2 additions & 0 deletions types/lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { FacebookConfig } from '../facebook'
import { FileConfig } from '../file'
import { MixcloudConfig } from '../mixcloud'
import { SoundCloudConfig } from '../soundcloud'
import { SpotifyConfig } from '../spotify'
import { TwitchConfig } from '../twitch'
import { VidyardConfig } from '../vidyard'
import { VimeoConfig } from '../vimeo'
Expand All @@ -22,6 +23,7 @@ export interface Config {
mixcloud?: MixcloudConfig
vidyard?: VidyardConfig
twitch?: TwitchConfig
spotify?: SpotifyConfig
}

export interface ReactPlayerProps extends BaseReactPlayerProps {
Expand Down
12 changes: 12 additions & 0 deletions types/spotify.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import BaseReactPlayer, { BaseReactPlayerProps } from './base'

export interface SpotifyConfig {
width?: number | string
height?: number | string
}

export interface SpotifyPlayerProps extends BaseReactPlayerProps {
config?: SpotifyConfig
}

export default class SpotifyPlayer extends BaseReactPlayer<SpotifyPlayerProps> {}