Files |
diff --git a/src/patterns.js b/src/patterns.js
index 6f82585b..f527f005 100644
--- a/src/patterns.js
+++ b/src/patterns.js
@@ -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
@@ -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)
}
diff --git a/src/players/Spotify.js b/src/players/Spotify.js
new file mode 100644
index 00000000..cbf751ef
--- /dev/null
+++ b/src/players/Spotify.js
@@ -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 (
+
+ )
+ }
+}
diff --git a/src/players/index.js b/src/players/index.js
index ba2e25fe..18123087 100644
--- a/src/players/index.js
+++ b/src/players/index.js
@@ -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',
diff --git a/src/props.js b/src/props.js
index 62c9b17a..a9f86252 100644
--- a/src/props.js
+++ b/src/props.js
@@ -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,
@@ -187,6 +191,10 @@ export const defaultProps = {
flvVersion: '1.5.0',
forceDisableHls: false
},
+ spotify: {
+ width: '100%',
+ height: '100%'
+ },
wistia: {
options: {},
playerId: null,
diff --git a/test/players/Spotify.js b/test/players/Spotify.js
new file mode 100644
index 00000000..621b28ce
--- /dev/null
+++ b/test/players/Spotify.js
@@ -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(
+
+ ).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().toJSON(),
+ create(
+
+ ).toJSON()
+ )
+})
diff --git a/types/lib/index.d.ts b/types/lib/index.d.ts
index 2b632269..5b499a93 100644
--- a/types/lib/index.d.ts
+++ b/types/lib/index.d.ts
@@ -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'
@@ -22,6 +23,7 @@ export interface Config {
mixcloud?: MixcloudConfig
vidyard?: VidyardConfig
twitch?: TwitchConfig
+ spotify?: SpotifyConfig
}
export interface ReactPlayerProps extends BaseReactPlayerProps {
diff --git a/types/spotify.d.ts b/types/spotify.d.ts
new file mode 100644
index 00000000..e34788dd
--- /dev/null
+++ b/types/spotify.d.ts
@@ -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 {}
|