Skip to content

Commit 3d3e2a5

Browse files
add lyrics to pip
1 parent 71d1593 commit 3d3e2a5

File tree

1 file changed

+104
-27
lines changed

1 file changed

+104
-27
lines changed

src/apps/app/layout/discord-activity-pip/discord.activity-pip.layout.tsx

Lines changed: 104 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { Button, Divider, Icon, Text, useContextMenu } from "@common";
1+
import { Button, Divider, Icon, Spinner, Text, useContextMenu, useTimedText } from "@common";
22
import { SourceBadge, useLikeMediaSource } from "@media-source";
3-
import { QueueActions, QueueSeekSlider, useQueue } from "@queue";
3+
import { LyricsUtil, QueueActions, QueueSeekSlider, useLyrics, useQueue } from "@queue";
44
import { useSettings } from "@settings";
5-
import { onMount, Show, type Component } from "solid-js";
5+
import { createSignal, onMount, Show, type Component } from "solid-js";
66

77
type Props = {
88
bottomPadding?: boolean;
@@ -17,7 +17,7 @@ const EmptyNowPlaying: Component<Props> = (props) => {
1717
</div>
1818
</div>
1919
<Divider dark />
20-
<div class="flex items-center h-[3.75rem] px-4">
20+
<div class="flex items-center h-[3.5rem] px-4">
2121
<Text.H3 class="truncate text-neutral-500 hidden discord-pip:block">It's lonely here...</Text.H3>
2222
</div>
2323

@@ -28,10 +28,71 @@ const EmptyNowPlaying: Component<Props> = (props) => {
2828
);
2929
};
3030

31+
const Lyrics: Component = () => {
32+
const queue = useQueue()!;
33+
const lyrics = useLyrics();
34+
const timedText = useTimedText(() => {
35+
const lyrics = syncedLyrics();
36+
return {
37+
elapsed: queue.data.position / 1000,
38+
timedTexts: lyrics || [],
39+
};
40+
});
41+
const syncedLyrics = () => {
42+
const nowPlaying = queue.data.nowPlaying;
43+
if (!nowPlaying) return null;
44+
45+
const lyricsOptions = lyrics.data();
46+
if (!lyricsOptions?.length) return null;
47+
48+
const bestMatch = lyricsOptions
49+
.sort((a, b) => {
50+
const aDiff = Math.abs(a.duration - nowPlaying.mediaSource.duration);
51+
const bDiff = Math.abs(b.duration - nowPlaying.mediaSource.duration);
52+
return aDiff - bDiff;
53+
})
54+
.at(0)?.synced;
55+
56+
return bestMatch ? LyricsUtil.parse(bestMatch).synced : null;
57+
};
58+
59+
const lines = () => {
60+
const index = timedText.index();
61+
return {
62+
previous: syncedLyrics()?.[index - 1]?.text || "",
63+
current: syncedLyrics()?.[index]?.text || "",
64+
next: syncedLyrics()?.[index + 1]?.text || "",
65+
};
66+
};
67+
68+
return (
69+
<div
70+
class="relative h-full w-full flex flex-col justify-center overflow-hidden"
71+
classList={{ "items-center": lyrics.data.loading }}
72+
>
73+
<Show when={!lyrics.data.loading} fallback={<Spinner />}>
74+
<Show
75+
when={lyrics.data}
76+
fallback={<Text.Body2 class="text-neutral-400 text-center">No Lyrics Found :(</Text.Body2>}
77+
>
78+
<div class="relative max-h-8">
79+
<Text.Caption1 class="absolute bottom-0 left-0 px-4">{lines().previous}</Text.Caption1>
80+
</div>
81+
<Text.Body1 class="text-white font-medium px-4 py-2">{lines().current}</Text.Body1>
82+
<div class="relative max-h-8">
83+
<Text.Caption1 class="absolute top-0 left-0 px-4">{lines().next}</Text.Caption1>
84+
</div>
85+
</Show>
86+
</Show>
87+
</div>
88+
);
89+
};
90+
3191
export const DiscordActivityPip: Component = () => {
3292
const queue = useQueue()!;
3393
const contextMenu = useContextMenu()!;
3494
const { settings } = useSettings()!;
95+
const [isShowLyrics, setIsShowLyrics] = createSignal(true);
3596

3697
const like = useLikeMediaSource(() => queue.data.nowPlaying?.mediaSource.id || "")!;
3798

@@ -45,15 +106,17 @@ export const DiscordActivityPip: Component = () => {
45106
<Show when={queue.data.nowPlaying} keyed fallback={<EmptyNowPlaying bottomPadding />}>
46107
{({ mediaSource }) => (
47108
<div class="w-full h-full flex flex-col">
48-
<div class="group/item-list flex items-center justify-center relative w-full h-full">
49-
<img
50-
src={mediaSource.minThumbnailUrl}
51-
class="absolute blur-2xl top-0 left-0 h-full w-full opacity-50"
52-
/>
53-
<img
54-
src={mediaSource.maxThumbnailUrl}
55-
class="relative h-20 w-20 rounded-lg object-cover m-2.5"
56-
/>
109+
<div class="group/item-list flex items-center justify-center relative w-full h-full overflow-hidden">
110+
<Show when={!isShowLyrics()} fallback={<Lyrics />}>
111+
<img
112+
src={mediaSource.minThumbnailUrl}
113+
class="absolute blur-2xl top-0 left-0 h-full w-full opacity-50"
114+
/>
115+
<img
116+
src={mediaSource.maxThumbnailUrl}
117+
class="relative h-20 w-20 rounded-lg object-cover m-2.5"
118+
/>
119+
</Show>
57120

58121
<div class="group-hover/item-list:opacity-100 opacity-0 transition-all absolute flex-row-center justify-center w-full h-full bg-black/75">
59122
<QueueActions extraClass="w-full justify-between max-w-64" iconSize="lg" />
@@ -62,7 +125,7 @@ export const DiscordActivityPip: Component = () => {
62125
<div class="absolute bottom-0 left-0 w-full z-10">
63126
<QueueSeekSlider
64127
dense
65-
extraLabelClass="px-2.5 text-shadow"
128+
extraLabelClass={`px-2.5 text-shadow ${isShowLyrics() ? "invisible" : "visible"}`}
66129
value={queue.data.position / 1000}
67130
onChange={(v) => queue.seek(v * 1000)}
68131
max={mediaSource.duration}
@@ -71,7 +134,7 @@ export const DiscordActivityPip: Component = () => {
71134
</div>
72135
</div>
73136

74-
<div class="relative flex flex-row w-full bg-neutral-950 space-x-2 px-4 pr-2 py-2.5">
137+
<div class="relative flex flex-row w-full bg-neutral-950 space-x-2 px-4 pr-2 py-2">
75138
<div class="grow flex flex-col space-y-1 truncate justify-center">
76139
<Text.Body1 class="truncate font-medium text-sm" title={mediaSource.title}>
77140
{mediaSource.title}
@@ -103,18 +166,32 @@ export const DiscordActivityPip: Component = () => {
103166
</div>
104167

105168
<Show when={settings["discord.interactivePip.enabled"]}>
106-
<Button
107-
flat
108-
icon={like.isLiked() ? "heart" : "heartLine"}
109-
iconSize={"md"}
110-
class="p-2 visible"
111-
theme={like.isLiked() ? "brand" : "secondary"}
112-
title={like.isLiked() ? "Unlike" : "Like"}
113-
on:click={(e) => {
114-
e.stopImmediatePropagation();
115-
like.toggle();
116-
}}
117-
/>
169+
<div class="flex space-x-0.5">
170+
<Button
171+
flat
172+
icon={like.isLiked() ? "heart" : "heartLine"}
173+
iconSize={"md"}
174+
class="p-2 visible"
175+
theme={like.isLiked() ? "brand" : "secondary"}
176+
title={like.isLiked() ? "Unlike" : "Like"}
177+
on:click={(e) => {
178+
e.stopImmediatePropagation();
179+
like.toggle();
180+
}}
181+
/>
182+
<Button
183+
flat
184+
icon={"microphone"}
185+
iconSize={"md"}
186+
class="p-2 visible"
187+
theme={isShowLyrics() ? "brand" : "secondary"}
188+
title={isShowLyrics() ? "Hide Lyrics" : "Show Lyrics"}
189+
on:click={(e) => {
190+
e.stopImmediatePropagation();
191+
setIsShowLyrics(!isShowLyrics());
192+
}}
193+
/>
194+
</div>
118195
</Show>
119196
</div>
120197

0 commit comments

Comments
 (0)