Skip to content

Commit

Permalink
picture-in-picture lyrics even when lyrics are not enabled
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcoMadera committed Sep 2, 2023
1 parent be50720 commit 6ce8459
Show file tree
Hide file tree
Showing 11 changed files with 654 additions and 489 deletions.
482 changes: 14 additions & 468 deletions components/FullScreenLyrics/FullScreenLyrics.tsx

Large diffs are not rendered by default.

100 changes: 100 additions & 0 deletions components/FullScreenLyrics/LyricLine.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { ReactElement, useEffect, useRef } from "react";

import { useAuth, useLyricsContext, useSpotify } from "hooks";
import { IFormatLyricsResponse } from "utils";

interface ILyricLineProps {
line: IFormatLyricsResponse["lines"][0];
type: "current" | "previous" | "next";
}

export function LyricLine({ line, type }: ILyricLineProps): ReactElement {
const { player } = useSpotify();
const { user } = useAuth();
const isPremium = user?.product === "premium";
const lineRef = useRef<HTMLButtonElement>(null);
const { lyricsProgressMs, lyricTextColor, lyricLineColor, lyrics } =
useLyricsContext();

const lineColors = {
current: "#fff",
previous: lyricLineColor + "80",
next: lyricTextColor,
};

useEffect(() => {
const line = lineRef.current;
if (!line) return;
const currentLine = line.classList.contains("current");
if (currentLine) {
line.scrollIntoView({
behavior: "smooth",
block: "center",
inline: "nearest",
});
}
}, [lyricsProgressMs]);

return (
<button
onClick={(e) => {
e.stopPropagation();
if (
isPremium &&
line?.startTimeMs &&
player &&
lyrics?.syncType === "LINE_SYNCED"
) {
player.seek(Number(line.startTimeMs));
}
}}
className={`line ${type}`}
dir="auto"
ref={lineRef}
>
{line.words}
<style jsx>{`
.line {
display: block;
color: ${lyricTextColor};
background-color: transparent;
border: none;
width: 100%;
text-align: left;
padding-left: 144px;
}
.line.current {
color: ${lineColors.current};
}
.line.previous {
color: ${lineColors.previous};
}
.line.next {
color: ${lineColors.next};
}
.line:hover {
color: ${lyrics?.colors ? lyricLineColor : "#fff"};
opacity: 1;
}
@media (max-width: 768px) {
.line {
padding-left: 0;
}
}
@media (max-width: 658px) {
.line {
padding-left: 0;
}
}
.line {
font-size: 32px;
font-weight: 700;
letter-spacing: -0.04em;
line-height: 54px;
cursor: pointer;
transition: all 0.1s ease-out 0s;
}
`}</style>
</button>
);
}
13 changes: 8 additions & 5 deletions context/AppContextProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { PropsWithChildren, ReactElement, useMemo } from "react";

import { LyricsContextContextProvider } from "./LyricsContextProvider";
import { ContextMenuContextProvider } from "context/ContextMenuContext";
import { HeaderContextProvider, IHeaderContext } from "context/HeaderContext";
import { IModalContext, ModalContextProvider } from "context/ModalContext";
Expand Down Expand Up @@ -41,11 +42,13 @@ export function AppContextProvider({
<UserContextProvider value={userValue}>
<HeaderContextProvider value={headerValue}>
<SpotifyContextProvider value={spotifyValue}>
<ContextMenuContextProvider value={contextMenuValue}>
<ModalContextProvider value={modalValue}>
{children}
</ModalContextProvider>
</ContextMenuContextProvider>
<LyricsContextContextProvider>
<ContextMenuContextProvider value={contextMenuValue}>
<ModalContextProvider value={modalValue}>
{children}
</ModalContextProvider>
</ContextMenuContextProvider>
</LyricsContextContextProvider>
</SpotifyContextProvider>
</HeaderContextProvider>
</UserContextProvider>
Expand Down
95 changes: 95 additions & 0 deletions context/LyricsContextProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import {
createContext,
Dispatch,
PropsWithChildren,
ReactElement,
SetStateAction,
useMemo,
useState,
} from "react";

import { useLyrics, useLyricsInPictureInPicture, useSpotify } from "hooks";
import { DisplayInFullScreen } from "types/spotify";
import { IFormatLyricsResponse } from "utils";

export interface ILyricsContext {
lyricsProgressMs: number;
lyricTextColor: string;
lyricsBackgroundColor?: string;
lyricLineColor: string;
lyrics: IFormatLyricsResponse | null;
lyricsError: string | null;
lyricsLoading: boolean;
setLyricsProgressMs: Dispatch<SetStateAction<number>>;
setLyricLineColor: Dispatch<SetStateAction<string>>;
setLyricTextColor: Dispatch<SetStateAction<string>>;
setLyricsBackgroundColor: Dispatch<SetStateAction<string | undefined>>;
}

const LyricsContext = createContext<ILyricsContext | undefined>(undefined);

export function LyricsContextContextProvider({
children,
}: PropsWithChildren): ReactElement {
const { displayInFullScreen, isPictureInPictureLyircsCanvas } = useSpotify();
const [lyricsProgressMs, setLyricsProgressMs] = useState(0);
const [lyricLineColor, setLyricLineColor] = useState<string>("#fff");
const [lyricTextColor, setLyricTextColor] = useState<string>("#fff");
const [lyricsBackgroundColor, setLyricsBackgroundColor] = useState<
string | undefined
>();
const requestLyrics = !!(
displayInFullScreen === DisplayInFullScreen.Lyrics ||
(isPictureInPictureLyircsCanvas && document.pictureInPictureElement)
);

const { lyrics, lyricsError, lyricsLoading } = useLyrics({
setLyricsBackgroundColor,
requestLyrics,
});

useLyricsInPictureInPicture({
setLyricsProgressMs,
setLyricLineColor,
setLyricTextColor,
lyrics,
lyricsBackgroundColor,
setLyricsBackgroundColor,
lyricTextColor,
lyricLineColor,
lyricsProgressMs,
lyricsError,
requestLyrics,
});

const value = useMemo(
() => ({
lyricsProgressMs,
lyricLineColor,
lyricTextColor,
lyricsBackgroundColor,
lyrics,
lyricsError,
lyricsLoading,
setLyricsProgressMs,
setLyricLineColor,
setLyricTextColor,
setLyricsBackgroundColor,
}),
[
lyricsProgressMs,
lyricLineColor,
lyricTextColor,
lyricsBackgroundColor,
lyrics,
lyricsError,
lyricsLoading,
]
);

return (
<LyricsContext.Provider value={value}>{children}</LyricsContext.Provider>
);
}

export default LyricsContext;
2 changes: 2 additions & 0 deletions hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ export * from "./useToast";
export * from "./useToggle";
export * from "./useTranslations";
export * from "./useUserPlaylists";
export * from "./useLyricsInPictureInPicture";
export * from "./useLyricsContext";
33 changes: 19 additions & 14 deletions hooks/useLyrics.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useState } from "react";

import { useToggle } from "hooks";
import { useAuth, useSpotify, useToggle } from "hooks";
import {
formatLyrics,
getLyrics,
Expand All @@ -10,21 +10,12 @@ import {
within,
} from "utils";

export function useLyrics({
artist,
title,
trackId,
accessToken,
}: {
artist?: string;
title?: string;
trackId?: string | null;
accessToken?: string;
}): {
export function useLyrics({ requestLyrics }: { requestLyrics: boolean }): {
lyrics: IFormatLyricsResponse | null;
lyricsLoading: boolean;
lyricsError: string | null;
} {
const { currentlyPlaying } = useSpotify();
const [lyrics, setLyrics] = useState<IFormatLyricsResponse | null>(null);
const [lyricsLoading, setLoading] = useToggle();
const [lyricsError, setLyricsError] = useState<string | null>(null);
Expand All @@ -33,8 +24,13 @@ export function useLyrics({
id?: string | null;
data: GetLyrics;
}>({ error: null, data: null, id: null });
const { accessToken } = useAuth();
const artist = currentlyPlaying?.artists?.[0].name;
const title = currentlyPlaying?.name;
const trackId = currentlyPlaying?.id;

useEffect(() => {
if (!requestLyrics) return;
setLoading.on();
setLyricsError(null);
setLyrics(null);
Expand Down Expand Up @@ -62,9 +58,18 @@ export function useLyrics({
setLoading.reset();
setLyricsError(null);
};
}, [accessToken, artist, setLoading, setLyricsError, title, trackId]);
}, [
accessToken,
artist,
setLoading,
setLyricsError,
title,
trackId,
requestLyrics,
]);

useEffect(() => {
if (!requestLyrics) return;
if (!res || !artist || !title || res.id !== artist + title) return;
if (res.error === "timeout") {
setLyricsError(
Expand All @@ -88,7 +93,7 @@ export function useLyrics({

setLyricsError(null);
setLyrics(formatLyrics(res.data));
}, [artist, res, setLoading, title]);
}, [artist, res, setLoading, title, requestLyrics]);

return {
lyrics,
Expand Down
13 changes: 13 additions & 0 deletions hooks/useLyricsContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useContext } from "react";

import LyricsContext, { ILyricsContext } from "context/LyricsContextProvider";

export function useLyricsContext(): ILyricsContext {
const context = useContext(LyricsContext);
if (!context)
throw new Error(
"useLyricsContext must be used within a LyricsContextProvider"
);

return context;
}
Loading

0 comments on commit 6ce8459

Please sign in to comment.