Skip to content

Commit

Permalink
start working more on wrapped
Browse files Browse the repository at this point in the history
  • Loading branch information
heybereket committed Jun 5, 2023
1 parent 3d1a486 commit cde4d5b
Show file tree
Hide file tree
Showing 15 changed files with 532 additions and 41 deletions.
22 changes: 22 additions & 0 deletions components/wrapped/WrappedContainer.tsx
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>
);
}
29 changes: 29 additions & 0 deletions components/wrapped/WrappedPlayer.ts
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));
}
}
57 changes: 57 additions & 0 deletions components/wrapped/WrappedPlayerComponent.tsx
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;
9 changes: 9 additions & 0 deletions components/wrapped/slides/FirstSlide.tsx
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>
);
};
21 changes: 21 additions & 0 deletions components/wrapped/slides/IntroSlide.tsx
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>
);
};
9 changes: 9 additions & 0 deletions components/wrapped/slides/SecondSlide.tsx
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>
);
};
9 changes: 9 additions & 0 deletions components/wrapped/slides/ThirdSlide.tsx
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>
);
};
31 changes: 31 additions & 0 deletions components/wrapped/slides/index.tsx
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",
},
];
181 changes: 181 additions & 0 deletions components/wrapped/spotify/FramePlayer.ts
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();
}
}
9 changes: 9 additions & 0 deletions components/wrapped/spotify/SpotifyPlayer.tsx
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>
);
}
Loading

0 comments on commit cde4d5b

Please sign in to comment.