playStatsTrack: fall back to streaming sources when not in library

The Top Tracks sidebar play button on the artist-detail page (and the
same buttons on the Stats page) called /api/stats/resolve-track and
gave up with a 'Track not found in library' toast on a miss.

Now when the library lookup misses, falls through to /api/enhanced-
search/stream-track — the same Soulseek/YouTube/streaming-source
pipeline the search-results play button uses. So Last.fm popular
tracks, recent plays, and stats artist top tracks all play even if
you don't own the track yet.

Library hit still wins (faster, full quality). Only on miss does it
escalate to streaming. Final error toast updated to reflect both
paths having been tried.
This commit is contained in:
Broque Thomas 2026-04-22 20:07:28 -07:00
parent 20dfcf10a5
commit 135f6b9ea1

View file

@ -464,6 +464,7 @@ function _renderDbStorageChart(tables, totalFileSize, method) {
}
async function playStatsTrack(title, artist, album) {
// 1. Try the library first — fastest and best quality if owned.
try {
const resp = await fetch('/api/stats/resolve-track', {
method: 'POST',
@ -471,22 +472,56 @@ async function playStatsTrack(title, artist, album) {
body: JSON.stringify({ title, artist }),
});
const data = await resp.json();
if (!data.success || !data.track) {
showToast(data.error || 'Track not found in library', 'error');
if (data.success && data.track) {
const t = data.track;
playLibraryTrack({
id: t.id,
title: t.title,
file_path: t.file_path,
bitrate: t.bitrate,
artist_id: t.artist_id,
album_id: t.album_id,
_stats_image: t.image_url || null,
}, t.album_title || album || '', t.artist_name || artist || '');
return;
}
const t = data.track;
playLibraryTrack({
id: t.id,
title: t.title,
file_path: t.file_path,
bitrate: t.bitrate,
artist_id: t.artist_id,
album_id: t.album_id,
_stats_image: t.image_url || null,
}, t.album_title || album || '', t.artist_name || artist || '');
} catch (e) {
console.debug('Library resolve failed, will try streaming fallback:', e);
}
// 2. Library miss — fall back to streaming via the enhanced-search streamer
// (Soulseek → YouTube → other configured sources, same pipeline used by
// the search results' play button).
if (typeof showLoadingOverlay === 'function') {
showLoadingOverlay(`Searching for ${title}...`);
}
try {
const streamResp = await fetch('/api/enhanced-search/stream-track', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
track_name: title,
artist_name: artist,
album_name: album || '',
duration_ms: 0,
}),
});
const streamData = await streamResp.json();
if (typeof hideLoadingOverlay === 'function') hideLoadingOverlay();
if (streamData.success && streamData.result) {
if (typeof startStream === 'function') {
await startStream(streamData.result);
} else {
showToast('Streaming not available', 'error');
}
} else {
showToast(streamData.error || 'Track not found in library or any source', 'error');
}
} catch (e) {
if (typeof hideLoadingOverlay === 'function') hideLoadingOverlay();
showToast('Failed to play track', 'error');
console.error('Stream fallback failed:', e);
}
}