-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3d1a486
commit cde4d5b
Showing
15 changed files
with
532 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import React, { ReactNode } from "react"; | ||
|
||
type WrappedContainerProps = { | ||
children: ReactNode; | ||
bg?: string; | ||
text?: string; | ||
}; | ||
|
||
export const WrappedContainer = ({ | ||
children, | ||
bg = "bg-card", | ||
text = "text-lightGray", | ||
}: WrappedContainerProps) => { | ||
return ( | ||
<div | ||
className={`w-screen min-h-screen flex justify-center items-center flex-col gap-6 text-center ${bg} ${text} p-6`} | ||
suppressHydrationWarning | ||
> | ||
{children} | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import EventEmitter from "events"; | ||
import SpotifyFramePlayer from "./spotify/FramePlayer"; | ||
import { SLIDES, Slide } from "./slides"; | ||
|
||
export default class WrappedPlayer extends EventEmitter { | ||
public currentSlide: Slide | null = null; | ||
|
||
constructor(public spotifyPlayer: SpotifyFramePlayer | null = null) { | ||
super(); | ||
} | ||
|
||
public async play(): Promise<void> { | ||
for (let i = 0; i < SLIDES.length; i++) { | ||
const slide = SLIDES[i]; | ||
this.currentSlide = slide; | ||
|
||
if (this.currentSlide.spotify && this.spotifyPlayer) { | ||
await this.spotifyPlayer.playSong(this.currentSlide.spotify); | ||
} | ||
|
||
this.emit("update"); | ||
await this.wait(slide.duration); | ||
} | ||
} | ||
|
||
private wait(ms: number): Promise<void> { | ||
return new Promise((resolve) => setTimeout(resolve, ms)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import React, { useEffect, useState } from "react"; | ||
import { CSSTransition, TransitionGroup } from "react-transition-group"; | ||
import { Loader2 } from "lucide-react"; | ||
import WrappedPlayer from "./WrappedPlayer"; | ||
import { WrappedContainer } from "./WrappedContainer"; | ||
import SpotifyFramePlayer from "./spotify/FramePlayer"; | ||
|
||
type WrappedPlayerComponentProps = { | ||
spotify: SpotifyFramePlayer | null; | ||
team: any; | ||
}; | ||
|
||
const LoadingPlayerComponent: React.FC = () => ( | ||
<WrappedContainer> | ||
<Loader2 size={32} className="animate-spin" /> | ||
</WrappedContainer> | ||
); | ||
|
||
const WrappedPlayerComponent: React.FC<WrappedPlayerComponentProps> = ({ | ||
spotify, | ||
...props | ||
}) => { | ||
const [player] = useState<WrappedPlayer>(() => new WrappedPlayer(spotify)); | ||
const [, setForceUpdate] = useState<number>(0); | ||
|
||
useEffect(() => { | ||
const forceUpdateHandler = () => setForceUpdate((prev) => prev + 1); | ||
|
||
player.on("update", forceUpdateHandler); | ||
player.play(); | ||
|
||
return () => { | ||
player.off("update", forceUpdateHandler); | ||
}; | ||
}, [player]); | ||
|
||
useEffect(() => { | ||
player.spotifyPlayer = spotify; | ||
}, [player, spotify]); | ||
|
||
const Component = player.currentSlide?.component || LoadingPlayerComponent; | ||
|
||
return ( | ||
<TransitionGroup> | ||
<CSSTransition | ||
key={player.currentSlide?.name || "none"} | ||
timeout={300} | ||
classNames="fade" | ||
unmountOnExit | ||
> | ||
<Component {...props} {...props.team} /> | ||
</CSSTransition> | ||
</TransitionGroup> | ||
); | ||
}; | ||
|
||
export default WrappedPlayerComponent; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { WrappedContainer } from "../WrappedContainer"; | ||
|
||
export const FirstSlide = () => { | ||
return ( | ||
<WrappedContainer> | ||
<h1 className="text-lightGray">This is the First Slide</h1> | ||
</WrappedContainer> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { WrappedContainer } from "../WrappedContainer"; | ||
|
||
export const IntroSlide = (props: any) => { | ||
return ( | ||
<WrappedContainer> | ||
<h1 className="text-lightGray font-bold text-lightGray text-5xl mb-[-15px]"> | ||
Did someone say <span className="text-white">{props.team} Wrap?</span> | ||
</h1> | ||
<p className="text-lightGray"> | ||
Get ready to see season highlights, match videos, & so much more! | ||
</p> | ||
|
||
<button | ||
className="bg-card border border-[#2A2A2A] px-16 py-2 rounded-lg hover:text-white transition-all duration-150" | ||
onClick={() => props.setPage("ready")} | ||
> | ||
View Wrapped | ||
</button> | ||
</WrappedContainer> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { WrappedContainer } from "../WrappedContainer"; | ||
|
||
export const SecondSlide = () => { | ||
return ( | ||
<WrappedContainer> | ||
<h1 className="text-lightGray">This is the Second Slide</h1> | ||
</WrappedContainer> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { WrappedContainer } from "../WrappedContainer"; | ||
|
||
export const ThirdSlide = () => { | ||
return ( | ||
<WrappedContainer> | ||
<h1 className="text-lightGray">This is the Third Slide</h1> | ||
</WrappedContainer> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { FirstSlide } from "./FirstSlide"; | ||
import { SecondSlide } from "./SecondSlide"; | ||
import { ThirdSlide } from "./ThirdSlide"; | ||
|
||
export interface Slide { | ||
name: string; | ||
component: React.FC; | ||
duration: number; | ||
spotify?: string; | ||
} | ||
|
||
export const SLIDES: Slide[] = [ | ||
{ | ||
name: "First Slide", | ||
component: FirstSlide, | ||
duration: 6000, | ||
spotify: "spotify:track:0RiRZpuVRbi7oqRdSMwhQY", | ||
}, | ||
{ | ||
name: "Second Slide", | ||
component: SecondSlide, | ||
duration: 6000, | ||
spotify: "spotify:track:47Mf1u67oqXtWBAaUHUSi7", | ||
}, | ||
{ | ||
name: "Third Slide", | ||
component: ThirdSlide, | ||
duration: 6000, | ||
spotify: "spotify:track:14zDy9P7qf0oBDOtHMNoKV", | ||
}, | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
import EventEmitter from "events"; | ||
|
||
export default class SpotifyFramePlayer extends EventEmitter { | ||
public embedController: EmbedController | null = null; | ||
public canPlaySongs = false; | ||
|
||
private currentIFrame: HTMLIFrameElement | null = null; | ||
private previousIFrame: HTMLIFrameElement | null = null; | ||
|
||
public loadLibrary(): Promise<void> { | ||
return new Promise<void>((resolve) => { | ||
const script = document.createElement("script"); | ||
script.src = "https://open.spotify.com/embed-podcast/iframe-api/v1"; | ||
script.async = true; | ||
document.body.appendChild(script); | ||
|
||
const timeout = setTimeout(() => { | ||
resolve(); | ||
}, 7000); | ||
|
||
window.onSpotifyIframeApiReady = (IFrameAPI: SpotifyIframeApi) => { | ||
const element = document.getElementById("spotify")!; | ||
const options = { | ||
uri: "spotify:track:0RiRZpuVRbi7oqRdSMwhQY", | ||
width: 300, | ||
height: 80, | ||
theme: "dark", | ||
}; | ||
|
||
IFrameAPI.createController(element, options, (EmbedController) => { | ||
this.embedController = EmbedController; | ||
|
||
const enablePlayback = () => { | ||
if (this.canPlaySongs) return; | ||
|
||
this.canPlaySongs = true; | ||
this.embedController!.removeListener("ready", enablePlayback); | ||
this.emit("ready"); | ||
|
||
clearTimeout(timeout); | ||
resolve(); | ||
}; | ||
|
||
const defaultIframe = document | ||
.getElementById("spotify-wrapper") | ||
?.querySelector("iframe"); | ||
|
||
if (defaultIframe) { | ||
defaultIframe.style.opacity = "0"; | ||
defaultIframe.style.position = "absolute"; | ||
defaultIframe.style.top = "-1000px"; | ||
} | ||
|
||
this.embedController!.addListener("ready", enablePlayback); | ||
}); | ||
}; | ||
}); | ||
} | ||
|
||
public playSong(uri: string): Promise<void> { | ||
return new Promise<void>(async (resolve) => { | ||
if (!this.canPlaySongs) return resolve(); | ||
if (!this.embedController) return resolve(); | ||
|
||
const playTimeout = setTimeout(() => { | ||
this.canPlaySongs = false; | ||
resolve(); | ||
}, 6000); | ||
|
||
const container = document.getElementById("spotify-wrapper"); | ||
this.previousIFrame = this.currentIFrame; | ||
const frameElement = document.createElement("div"); | ||
|
||
if (container?.firstChild) { | ||
container!.insertBefore(frameElement, container.firstChild); | ||
} else { | ||
container!.appendChild(frameElement); | ||
} | ||
|
||
const oembed = await fetch(`https://open.spotify.com/oembed?url=${uri}`) | ||
.then((response) => response.json()) | ||
.catch(() => { | ||
return { | ||
html: "", | ||
}; | ||
}); | ||
|
||
if (!oembed.html) { | ||
this.destroyPreviousIFrame(); | ||
resolve(); | ||
return; | ||
} | ||
|
||
oembed.html = oembed.html.replace("encrypted-media; ", ""); | ||
frameElement.innerHTML = oembed.html; | ||
|
||
this.setupNewIframe(frameElement); | ||
|
||
await this.waitForIframe(); | ||
|
||
this.embedController.loadUri(uri); | ||
this.embedController.resume(); | ||
|
||
setTimeout(() => this.destroyPreviousIFrame(), 1000); | ||
await this.waitForSpotify(); | ||
|
||
this.currentIFrame!.style.opacity = "1"; | ||
clearTimeout(playTimeout); | ||
resolve(); | ||
}); | ||
} | ||
|
||
private setupNewIframe(frameElement: HTMLDivElement): void { | ||
const iframe = frameElement.querySelector("iframe")!; | ||
this.embedController!.iframeElement = iframe; | ||
this.currentIFrame = iframe; | ||
this.currentIFrame!.style.opacity = "0"; | ||
this.currentIFrame!.setAttribute("width", "300"); | ||
this.currentIFrame!.setAttribute("height", "80"); | ||
} | ||
|
||
private waitForIframe(): Promise<void> { | ||
return new Promise<void>((resolve) => { | ||
const checkIfReady = (e: MessageEvent) => { | ||
if ( | ||
e.source === this.currentIFrame?.contentWindow && | ||
e.data.type === "ready" | ||
) { | ||
window.removeEventListener("message", checkIfReady); | ||
resolve(); | ||
} | ||
}; | ||
|
||
window.addEventListener("message", checkIfReady); | ||
}); | ||
} | ||
|
||
private destroyPreviousIFrame(): void { | ||
if (this.previousIFrame) { | ||
this.previousIFrame.remove(); | ||
this.previousIFrame = null; | ||
} | ||
} | ||
|
||
private waitForSpotify(): Promise<void> { | ||
return new Promise<void>((resolve) => { | ||
let hasResolved = false; | ||
|
||
const checkIfReady = (state: { | ||
data: { | ||
isBuffering: boolean; | ||
duration: number; | ||
position: number; | ||
}; | ||
}) => { | ||
if (hasResolved) { | ||
return; | ||
} | ||
|
||
if ( | ||
!state.data.isBuffering && | ||
state.data.duration > 0 && | ||
state.data.position > 0 | ||
) { | ||
this.embedController?.removeListener("playback_update", checkIfReady); | ||
hasResolved = true; | ||
resolve(); | ||
} | ||
}; | ||
|
||
this.embedController?.addListener("playback_update", checkIfReady); | ||
}); | ||
} | ||
|
||
public async pause(): Promise<void> { | ||
if (!this.canPlaySongs) return; | ||
if (!this.embedController) return; | ||
|
||
this.embedController.pause(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import React from "react"; | ||
|
||
export const SpotifyPlayer = () => { | ||
return ( | ||
<div className="fixed bottom-2 right-2 z-20" id="spotify-wrapper"> | ||
<div id="spotify"></div> | ||
</div> | ||
); | ||
} |
Oops, something went wrong.