diff --git a/examples/react/src/App.tsx b/examples/react/src/App.tsx index 48a53714..78fdfa1d 100644 --- a/examples/react/src/App.tsx +++ b/examples/react/src/App.tsx @@ -238,6 +238,9 @@ const App = () => { }, vimeo: { color: 'ffffff' + }, + spotify: { + preferVideo: true } }} onLoadStart={() => console.log('onLoadStart')} @@ -467,6 +470,19 @@ const App = () => { {renderLoadButton('https://home.wistia.com/medias/bq6epni33s', 'Test C')} + + Spotify + + {renderLoadButton('https://open.spotify.com/episode/5Jo9ncrz2liWiKj8inZwD2', 'Test A')} + + + + Twitch + + {renderLoadButton('https://www.twitch.tv/videos/106400740', 'Test A')} + {renderLoadButton('https://www.twitch.tv/kronovi', 'Test B')} + + Custom diff --git a/package-lock.json b/package-lock.json index d62d9093..65e6f851 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,8 @@ "dash-video-element": "^0.1.5", "deepmerge": "^4.0.0", "hls-video-element": "^1.5.5", + "spotify-audio-element": "^1.0.1", + "twitch-video-element": "^0.1.0", "vimeo-video-element": "^1.5.1", "wistia-video-element": "^1.3.2", "youtube-video-element": "^1.6.0" @@ -3283,6 +3285,12 @@ "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", "dev": true }, + "node_modules/spotify-audio-element": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/spotify-audio-element/-/spotify-audio-element-1.0.1.tgz", + "integrity": "sha512-Uc30QxulPAoWxkMSVeMGcHmEij9KXedzbsliwlhNWnZP2Fgkz1Ifl2laj0poAXiqUDdJTWapfUI9IkGkhwgTEg==", + "license": "MIT" + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -3485,6 +3493,12 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "dev": true }, + "node_modules/twitch-video-element": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/twitch-video-element/-/twitch-video-element-0.1.0.tgz", + "integrity": "sha512-9aV0OD9/QtMqV554RkjOHxg/WzMjDwxtp1l+M0OXIaQiLOrrL16FYqyng6g0dm9NY3Sx35ybdGTmDC/NvC3Xkw==", + "license": "MIT" + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", diff --git a/package.json b/package.json index b9c225c9..1751a693 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,8 @@ "dash-video-element": "^0.1.5", "deepmerge": "^4.0.0", "hls-video-element": "^1.5.5", + "spotify-audio-element": "^1.0.1", + "twitch-video-element": "^0.1.0", "vimeo-video-element": "^1.5.1", "wistia-video-element": "^1.3.2", "youtube-video-element": "^1.6.0" diff --git a/src/patterns.ts b/src/patterns.ts index d5ba2d51..8d824450 100644 --- a/src/patterns.ts +++ b/src/patterns.ts @@ -10,6 +10,8 @@ export const MATCH_URL_YOUTUBE = export const MATCH_URL_VIMEO = /vimeo\.com\/(?!progressive_redirect).+/; export const MATCH_URL_WISTIA = /(?:wistia\.(?:com|net)|wi\.st)\/(?:medias|embed)\/(?:iframe\/)?([^?]+)/; +export const MATCH_URL_SPOTIFY = /open\.spotify\.com\/(\w+)\/(\w+)/i; +export const MATCH_URL_TWITCH = /(?:www\.|go\.)?twitch\.tv\/([a-zA-Z0-9_]+|(videos?\/|\?video=)\d+)($|\?)/; const canPlayFile = (url: string, test: (u: string) => boolean) => { if (Array.isArray(url)) { @@ -36,4 +38,6 @@ export const canPlay = { vimeo: (url: string) => MATCH_URL_VIMEO.test(url) && !VIDEO_EXTENSIONS.test(url) && !HLS_EXTENSIONS.test(url), wistia: (url: string) => MATCH_URL_WISTIA.test(url), + spotify: (url: string) => MATCH_URL_SPOTIFY.test(url), + twitch: (url: string) => MATCH_URL_TWITCH.test(url), }; diff --git a/src/players.ts b/src/players.ts index 1ab6ca18..04bd2f4f 100644 --- a/src/players.ts +++ b/src/players.ts @@ -66,6 +66,24 @@ const Players: PlayerEntry[] = [ () => import(/* webpackChunkName: 'reactPlayerWistia' */ 'wistia-video-element/react') ) as React.LazyExoticComponent>, }, + { + key: 'spotify', + name: 'Spotify', + canPlay: canPlay.spotify, + canEnablePIP: () => false, + player: lazy( + () => import(/* webpackChunkName: 'reactPlayerSpotify' */ 'spotify-audio-element/react') + ) as React.LazyExoticComponent>, + }, + { + key: 'twitch', + name: 'Twitch', + canPlay: canPlay.twitch, + canEnablePIP: () => false, + player: lazy( + () => import(/* webpackChunkName: 'reactPlayerTwitch' */ 'twitch-video-element/react') + ) as React.LazyExoticComponent>, + }, { key: 'html', name: 'html', diff --git a/src/types.ts b/src/types.ts index 2d49cf03..ad4773a9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,9 @@ import type { MediaHTMLAttributes, SyntheticEvent } from 'react'; +import type HlsVideoElement from 'hls-video-element'; +import type SpotifyAudioElement from 'spotify-audio-element'; +import type YouTubeVideoElement from 'youtube-video-element'; +import type VimeoVideoElement from 'vimeo-video-element'; +import type TwitchVideoElement from 'twitch-video-element'; interface VideoHTMLAttributes extends MediaHTMLAttributes { height?: number | string | undefined; @@ -39,11 +44,13 @@ export interface PreviewProps { } export interface Config { - html?: Record; - hls?: Record; dash?: Record; + hls?: HlsVideoElement['config']; + html?: Record; mux?: Record; - youtube?: Record; - vimeo?: Record; + spotify?: SpotifyAudioElement['config']; + twitch?: TwitchVideoElement['config']; + vimeo?: VimeoVideoElement['config']; wistia?: Record; + youtube?: YouTubeVideoElement['config']; }