add experimental ytmusic playback for external songs

This commit is contained in:
jeffvli
2026-02-06 20:47:27 -08:00
parent 40ec16e191
commit 8e603871b7
11 changed files with 460 additions and 221 deletions
@@ -1,8 +1,12 @@
import { useQuery } from '@tanstack/react-query';
import { useMemo, useRef } from 'react';
import { api } from '/@/renderer/api';
import { youtubeQueries } from '/@/renderer/features/musicbrainz/api/youtube-api';
import { TranscodingConfig } from '/@/renderer/store';
import { QueueSong } from '/@/shared/types/domain-types';
import { QueueSong, ServerType } from '/@/shared/types/domain-types';
const YOUTUBE_WATCH_BASE = 'https://www.youtube.com/watch?v=';
export function useSongUrl(
song: QueueSong | undefined,
@@ -11,10 +15,36 @@ export function useSongUrl(
): string | undefined {
const prior = useRef(['', '']);
const isExternal = song?._serverType === ServerType.EXTERNAL;
const searchQuery =
song && isExternal ? `${song.artistName ?? ''} ${song.name ?? ''}`.trim() : '';
const youtubeSearch = useQuery({
...youtubeQueries.search({ query: searchQuery }),
enabled: Boolean(song && isExternal && searchQuery),
});
const externalUrl = useMemo(() => {
if (!song || !isExternal) return undefined;
if (current && prior.current[0] === song._uniqueId && prior.current[1]) {
return prior.current[1];
}
const url = getYoutubeUrlFromSearchResults(youtubeSearch.data);
if (url) prior.current = [song._uniqueId, url];
return url;
}, [song, isExternal, current, youtubeSearch.data]);
return useMemo(() => {
if (song?._serverId) {
// If we are the current track, we do not want a transcoding
// reconfiguration to force a restart.
if (!song) {
prior.current = ['', ''];
return undefined;
}
if (isExternal) {
return externalUrl;
}
if (song._serverId) {
if (current && prior.current[0] === song._uniqueId) {
return prior.current[1];
}
@@ -29,18 +59,16 @@ export function useSongUrl(
},
});
// transcoding enabled; save the updated result
prior.current = [song._uniqueId, url];
return url;
}
// no track; clear result
prior.current = ['', ''];
return undefined;
}, [
song?._serverId,
song?._uniqueId,
song?.id,
song,
isExternal,
externalUrl,
current,
transcode.bitrate,
transcode.format,
@@ -48,6 +76,16 @@ export function useSongUrl(
]);
}
function getYoutubeUrlFromSearchResults(
results: Array<{ type: string; videoId?: string }> | undefined,
): string | undefined {
if (!results?.length) return undefined;
const first = results.find((r) => r.type === 'SONG' || r.type === 'VIDEO');
return first && 'videoId' in first && first.videoId
? `${YOUTUBE_WATCH_BASE}${first.videoId}`
: undefined;
}
export const getSongUrl = (song: QueueSong, transcode: TranscodingConfig) => {
return api.controller.getStreamUrl({
apiClientProps: { serverId: song._serverId },
@@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events';
import { updateQueueSong } from '/@/renderer/store/player.store';
import { LogCategory, logFn } from '/@/renderer/utils/logger';
import { QueueSong, SongDetailQuery } from '/@/shared/types/domain-types';
import { QueueSong, ServerType, SongDetailQuery } from '/@/shared/types/domain-types';
export const useUpdateCurrentSong = () => {
const queryClient = useQueryClient();
@@ -16,7 +16,11 @@ export const useUpdateCurrentSong = () => {
async (properties: { index: number; song: QueueSong | undefined }) => {
const currentSong = properties.song;
if (!currentSong?.id || !currentSong?._serverId) {
if (
!currentSong?.id ||
!currentSong?._serverId ||
currentSong?._serverType === ServerType.EXTERNAL
) {
return;
}