mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-12 11:01:45 +00:00
275 lines
8.2 KiB
TypeScript
275 lines
8.2 KiB
TypeScript
import {
|
|
createEffect,
|
|
createMemo,
|
|
createSignal,
|
|
For,
|
|
Index,
|
|
Match,
|
|
onCleanup,
|
|
onMount,
|
|
type Setter,
|
|
Switch,
|
|
} from 'solid-js';
|
|
|
|
import {
|
|
currentLyrics,
|
|
lyricsStore,
|
|
type ProviderName,
|
|
providerNames,
|
|
type ProviderState,
|
|
setLyricsStore,
|
|
} from '../../providers';
|
|
|
|
import { _ytAPI } from '../index';
|
|
|
|
import type { YtIcons } from '@/types/icons';
|
|
|
|
export const providerIdx = createMemo(() =>
|
|
providerNames.indexOf(lyricsStore.provider),
|
|
);
|
|
|
|
const shouldSwitchProvider = (providerData: ProviderState) => {
|
|
if (providerData.state === 'error') return true;
|
|
if (providerData.state === 'fetching') return true;
|
|
return (
|
|
providerData.state === 'done' &&
|
|
!providerData.data?.lines &&
|
|
!providerData.data?.lyrics
|
|
);
|
|
};
|
|
|
|
const providerBias = (p: ProviderName) =>
|
|
(lyricsStore.lyrics[p].state === 'done' ? 1 : -1) +
|
|
(lyricsStore.lyrics[p].data?.lines?.length ? 2 : -1) +
|
|
(lyricsStore.lyrics[p].data?.lines?.length && p === 'YTMusic' ? 1 : 0) +
|
|
(lyricsStore.lyrics[p].data?.lyrics ? 1 : -1);
|
|
|
|
const pickBestProvider = () => {
|
|
const providers = Array.from(providerNames);
|
|
|
|
providers.sort((a, b) => providerBias(b) - providerBias(a));
|
|
|
|
return providers[0];
|
|
};
|
|
|
|
const [hasManuallySwitchedProvider, setHasManuallySwitchedProvider] =
|
|
createSignal(false);
|
|
|
|
export const LyricsPicker = (props: {
|
|
setStickRef: Setter<HTMLElement | null>;
|
|
}) => {
|
|
createEffect(() => {
|
|
// fallback to the next source, if the current one has an error
|
|
if (!hasManuallySwitchedProvider()) {
|
|
const bestProvider = pickBestProvider();
|
|
|
|
const allProvidersFailed = providerNames.every((p) =>
|
|
shouldSwitchProvider(lyricsStore.lyrics[p]),
|
|
);
|
|
if (allProvidersFailed) return;
|
|
|
|
if (providerBias(lyricsStore.provider) < providerBias(bestProvider)) {
|
|
setLyricsStore('provider', bestProvider);
|
|
}
|
|
}
|
|
});
|
|
|
|
onMount(() => {
|
|
const videoDataChangeHandler = (name: string) => {
|
|
if (name !== 'dataloaded') return;
|
|
setHasManuallySwitchedProvider(false);
|
|
};
|
|
|
|
_ytAPI?.addEventListener('videodatachange', videoDataChangeHandler);
|
|
onCleanup(() =>
|
|
_ytAPI?.removeEventListener('videodatachange', videoDataChangeHandler),
|
|
);
|
|
});
|
|
|
|
const next = () => {
|
|
setHasManuallySwitchedProvider(true);
|
|
setLyricsStore('provider', (prevProvider) => {
|
|
const idx = providerNames.indexOf(prevProvider);
|
|
return providerNames[(idx + 1) % providerNames.length];
|
|
});
|
|
};
|
|
|
|
const previous = () => {
|
|
setHasManuallySwitchedProvider(true);
|
|
setLyricsStore('provider', (prevProvider) => {
|
|
const idx = providerNames.indexOf(prevProvider);
|
|
return providerNames[
|
|
(idx + providerNames.length - 1) % providerNames.length
|
|
];
|
|
});
|
|
};
|
|
|
|
const chevronLeft: YtIcons = 'yt-icons:chevron_left';
|
|
const chevronRight: YtIcons = 'yt-icons:chevron_right';
|
|
|
|
const successIcon: YtIcons = 'yt-icons:check-circle';
|
|
const errorIcon: YtIcons = 'yt-icons:error';
|
|
const notFoundIcon: YtIcons = 'yt-icons:warning';
|
|
|
|
return (
|
|
<div class="lyrics-picker" ref={props.setStickRef}>
|
|
<div class="lyrics-picker-left">
|
|
<yt-icon-button
|
|
class="style-scope ytmusic-player-bar"
|
|
icon={chevronLeft}
|
|
onClick={previous}
|
|
role={'button'}
|
|
>
|
|
<span class="yt-icon-shape style-scope yt-icon yt-spec-icon-shape">
|
|
<div
|
|
style={{
|
|
'width': '100%',
|
|
'height': '100%',
|
|
'display': 'flex',
|
|
'align-items': 'center',
|
|
'fill': 'currentcolor',
|
|
}}
|
|
>
|
|
<svg
|
|
class="style-scope yt-icon"
|
|
fill="#FFFFFF"
|
|
height={'40px'}
|
|
preserveAspectRatio="xMidYMid meet"
|
|
viewBox="0 -960 960 960"
|
|
width={'40px'}
|
|
>
|
|
<g class="style-scope yt-icon">
|
|
<path
|
|
class="style-scope yt-icon"
|
|
d="M560.67-240 320-480.67l240.67-240.66L608-674 414.67-480.67 608-287.33 560.67-240Z"
|
|
/>
|
|
</g>
|
|
</svg>
|
|
</div>
|
|
</span>
|
|
</yt-icon-button>
|
|
</div>
|
|
|
|
<div class="lyrics-picker-content">
|
|
<div class="lyrics-picker-content-label">
|
|
<Index each={providerNames}>
|
|
{(provider) => (
|
|
<div
|
|
class="lyrics-picker-item"
|
|
style={{
|
|
transform: `translateX(${providerIdx() * -100 - 5}%)`,
|
|
}}
|
|
tabindex="-1"
|
|
>
|
|
<Switch>
|
|
<Match
|
|
when={
|
|
// prettier-ignore
|
|
currentLyrics().state === 'fetching'
|
|
}
|
|
>
|
|
<tp-yt-paper-spinner-lite
|
|
active
|
|
class="loading-indicator style-scope"
|
|
style={{ padding: '5px', transform: 'scale(0.5)' }}
|
|
tabindex="-1"
|
|
/>
|
|
</Match>
|
|
<Match when={currentLyrics().state === 'error'}>
|
|
<yt-icon-button
|
|
icon={errorIcon}
|
|
style={{ padding: '5px', transform: 'scale(0.5)' }}
|
|
tabindex="-1"
|
|
/>
|
|
</Match>
|
|
<Match
|
|
when={
|
|
currentLyrics().state === 'done' &&
|
|
(currentLyrics().data?.lines ||
|
|
currentLyrics().data?.lyrics)
|
|
}
|
|
>
|
|
<yt-icon-button
|
|
icon={successIcon}
|
|
style={{ padding: '5px', transform: 'scale(0.5)' }}
|
|
tabindex="-1"
|
|
/>
|
|
</Match>
|
|
<Match
|
|
when={
|
|
currentLyrics().state === 'done' &&
|
|
!currentLyrics().data?.lines &&
|
|
!currentLyrics().data?.lyrics
|
|
}
|
|
>
|
|
<yt-icon-button
|
|
icon={notFoundIcon}
|
|
style={{ padding: '5px', transform: 'scale(0.5)' }}
|
|
tabindex="-1"
|
|
/>
|
|
</Match>
|
|
</Switch>
|
|
<yt-formatted-string
|
|
class="description ytmusic-description-shelf-renderer"
|
|
text={{ runs: [{ text: provider() }] }}
|
|
/>
|
|
</div>
|
|
)}
|
|
</Index>
|
|
</div>
|
|
|
|
<ul class="lyrics-picker-content-dots">
|
|
<For each={providerNames}>
|
|
{(_, idx) => (
|
|
<li
|
|
class="lyrics-picker-dot"
|
|
onClick={() => setLyricsStore('provider', providerNames[idx()])}
|
|
style={{
|
|
background: idx() === providerIdx() ? 'white' : 'black',
|
|
}}
|
|
/>
|
|
)}
|
|
</For>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="lyrics-picker-right">
|
|
<yt-icon-button
|
|
class="style-scope ytmusic-player-bar"
|
|
icon={chevronRight}
|
|
onClick={next}
|
|
role={'button'}
|
|
>
|
|
<span class="yt-icon-shape style-scope yt-icon yt-spec-icon-shape">
|
|
<div
|
|
style={{
|
|
'width': '100%',
|
|
'height': '100%',
|
|
'display': 'flex',
|
|
'align-items': 'center',
|
|
'fill': 'currentcolor',
|
|
}}
|
|
>
|
|
<svg
|
|
class="style-scope yt-icon"
|
|
fill="#FFFFFF"
|
|
height={'40px'}
|
|
preserveAspectRatio="xMidYMid meet"
|
|
viewBox="0 -960 960 960"
|
|
width={'40px'}
|
|
>
|
|
<g class="style-scope yt-icon">
|
|
<path
|
|
class="style-scope yt-icon"
|
|
d="M521.33-480.67 328-674l47.33-47.33L616-480.67 375.33-240 328-287.33l193.33-193.34Z"
|
|
/>
|
|
</g>
|
|
</svg>
|
|
</div>
|
|
</span>
|
|
</yt-icon-button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|