11import { useApp } from "@app/providers" ;
2- import { Container , Icon , Spinner } from "@common" ;
3- import { useQueue } from "@queue" ;
4- import { useLyrics } from "@youtube" ;
5- import { For , Match , Switch , createMemo , onMount , type Component } from "solid-js" ;
2+ import { Container , Icon , Spinner , useTimedText } from "@common" ;
3+ import { LyricsUtil , useLyrics , useQueue } from "@queue" ;
4+ import { For , Match , Switch , createEffect , onMount , type Component } from "solid-js" ;
65import "./lyrics.style.css" ;
76
87const LyricsNotFound : Component = ( ) => {
@@ -24,54 +23,108 @@ const Loading: Component = () => {
2423
2524export const Lyrics : Component = ( ) => {
2625 let container ! : HTMLDivElement ;
27- const queue = useQueue ( ) ! ;
2826 const app = useApp ( ) ! ;
29- const currentId = createMemo ( ( ) => queue . data . nowPlaying ?. mediaSource . playedYoutubeVideoId || "" ) ;
30- const lyrics = useLyrics ( currentId ) ;
31- // let initialScroll = true;
32- // let lastScrollTime = 0;
27+ const queue = useQueue ( ) ! ;
28+ const lyrics = useLyrics ( ) ;
29+ const timedText = useTimedText ( ( ) => {
30+ const lyrics = syncedLyrics ( ) ;
31+ return {
32+ elapsed : queue . data . position / 1000 ,
33+ timedTexts : lyrics || [ ] ,
34+ } ;
35+ } ) ;
3336
3437 onMount ( ( ) => app . setTitle ( "Lyrics" ) ) ;
3538
36- // createEffect(() => {
37- // if (timedText.index() === -1 && container) {
38- // container.scrollTop = 0;
39- // } else {
40- // if (Date.now() - lastScrollTime < 3000) return;
41- // const index = timedText.index();
42- // if (initialScroll) {
43- // setTimeout(() => scrollTo(index), 200);
44- // initialScroll = false;
45- // } else {
46- // scrollTo(index);
47- // }
48- // }
49- // });
50-
51- // const scrollTo = (index: number) => {
52- // const element = container?.childNodes[index] as HTMLDivElement;
53- // if (!element) return;
54- // container.scrollTop = element.offsetTop - container.offsetHeight / 2.5 + element.offsetHeight / 2;
55- // };
56-
57- // const onContainerScrollHandler = () => {
58- // lastScrollTime = Date.now();
59- // };
39+ let initialScroll = true ;
40+ let lastScrollTime = 0 ;
41+
42+ const syncedLyrics = ( ) => {
43+ const nowPlaying = queue . data . nowPlaying ;
44+ if ( ! nowPlaying ) return null ;
45+
46+ const lyricsOptions = lyrics . data ( ) ;
47+ if ( ! lyricsOptions ?. length ) return null ;
48+
49+ const bestMatch = lyricsOptions
50+ . sort ( ( a , b ) => {
51+ const aDiff = Math . abs ( a . duration - nowPlaying . mediaSource . duration ) ;
52+ const bDiff = Math . abs ( b . duration - nowPlaying . mediaSource . duration ) ;
53+ return aDiff - bDiff ;
54+ } )
55+ . at ( 0 ) ?. synced ;
56+
57+ return bestMatch ? LyricsUtil . parse ( bestMatch ) . synced : null ;
58+ } ;
59+
60+ const normalLyrics = ( ) => {
61+ const nowPlaying = queue . data . nowPlaying ;
62+ if ( ! nowPlaying ) return null ;
63+
64+ const lyricsOptions = lyrics . data ( ) ;
65+ if ( ! lyricsOptions ?. length ) return null ;
66+
67+ const unsyncedLyrics = lyricsOptions . find ( ( l ) => l . unsynced ) ;
68+ if ( unsyncedLyrics ?. unsynced ) {
69+ return {
70+ content : unsyncedLyrics . unsynced ,
71+ description : unsyncedLyrics . source ,
72+ } ;
73+ }
74+
75+ const synced = lyricsOptions . find ( ( l ) => l . synced ) ;
76+ if ( synced ?. synced ) {
77+ return {
78+ content :
79+ LyricsUtil . parse ( synced . synced )
80+ . synced ?. map ( ( s ) => s . text )
81+ . join ( "\n" ) || "" ,
82+ description : synced . source ,
83+ } ;
84+ }
85+
86+ return null ;
87+ } ;
88+
89+ createEffect ( ( ) => {
90+ if ( timedText . index ( ) === - 1 && container ) {
91+ container . scrollTop = 0 ;
92+ } else {
93+ if ( Date . now ( ) - lastScrollTime < 3000 ) return ;
94+ const index = timedText . index ( ) ;
95+ if ( initialScroll ) {
96+ setTimeout ( ( ) => scrollTo ( index ) , 200 ) ;
97+ initialScroll = false ;
98+ } else {
99+ scrollTo ( index ) ;
100+ }
101+ }
102+ } ) ;
103+
104+ const scrollTo = ( index : number ) => {
105+ const element = container ?. childNodes [ index ] as HTMLDivElement ;
106+ if ( ! element ) return ;
107+ container . scrollTop = element . offsetTop - container . offsetHeight / 2.5 + element . offsetHeight / 2 ;
108+ } ;
109+
110+ const onContainerScrollHandler = ( ) => {
111+ lastScrollTime = Date . now ( ) ;
112+ } ;
60113
61114 return (
62115 < Container
63116 size = "full"
64117 extraClass = "h-full flex flex-col items-center space-y-2.5"
65118 centered
66119 ref = { container }
67- // onScroll={onContainerScrollHandler}
120+ onScroll = { onContainerScrollHandler }
68121 >
69122 < Switch fallback = { < LyricsNotFound /> } >
70123 < Match when = { lyrics . data . loading } >
71124 < Loading />
72125 </ Match >
73- { /* <Match when={videoTranscripts.data().length} >
74- <For each={videoTranscripts.data ()}>
126+ < Match when = { syncedLyrics ( ) } keyed >
127+ < For each = { syncedLyrics ( ) } >
75128 { ( t , i ) => (
76129 < div
77130 class = "space-y-1 py-2 text"
@@ -84,12 +137,12 @@ export const Lyrics: Component = () => {
84137 "font-semibold text-2xl md:text-3xl !text-neutral-100" : i ( ) === timedText . index ( ) ,
85138 } }
86139 >
87- <For each= {t.texts}>{( text) => <div>{text }</div>}</For >
140+ < div > { t . text } </ div >
88141 </ div >
89142 ) }
90143 </ For >
91- </Match> */ }
92- < Match when = { lyrics . data ( ) } keyed >
144+ </ Match >
145+ < Match when = { normalLyrics ( ) } keyed >
93146 { ( { content, description } ) => (
94147 < >
95148 < For each = { content . split ( / \r ? \n / ) } >
0 commit comments