dev/playlists #13

Merged
peterino merged 19 commits from dev/playlists into integration 2026-02-09 18:58:41 +00:00
3 changed files with 87 additions and 0 deletions
Showing only changes of commit cd4237dcbe - Show all commits

56
init.ts
View File

@ -17,6 +17,8 @@ import {
initYtdlp,
setProgressCallback,
setTrackReadyCallback,
skipSlowQueueItem,
getQueuedSlowItems,
type QueueItem,
} from "./ytdlp";
@ -206,6 +208,60 @@ export async function init(): Promise<void> {
library.logActivity("scan_updated", { id: track.id, filename: track.filename, title: track.title });
});
// Prescan slow queue to find tracks already in library
function prescanSlowQueue() {
const queuedItems = getQueuedSlowItems();
if (queuedItems.length === 0) return;
const tracks = library.getAllTracks();
if (tracks.length === 0) return;
for (const item of queuedItems) {
const itemTitle = normalizeForMatch(item.title);
// Check if any library track matches
for (const track of tracks) {
const trackTitle = normalizeForMatch(track.title || "");
const trackFilename = normalizeForMatch((track.filename || "").replace(/\.[^.]+$/, ""));
const matches =
(trackTitle && trackTitle === itemTitle) ||
(trackFilename && trackFilename === itemTitle) ||
(trackTitle && itemTitle && trackTitle.includes(itemTitle)) ||
(trackTitle && itemTitle && itemTitle.includes(trackTitle)) ||
(trackFilename && itemTitle && trackFilename.includes(itemTitle)) ||
(trackFilename && itemTitle && itemTitle.includes(trackFilename));
if (matches) {
console.log(`[ytdlp] Prescan: "${item.title}" already exists as "${track.title || track.filename}"`);
// Skip download and add to playlist
const skipped = skipSlowQueueItem(item.id, track.id);
if (skipped && skipped.playlistId) {
try {
addTracksToPlaylist(skipped.playlistId, [track.id]);
sendToUser(skipped.userId, {
type: "toast",
message: `Already in library, added to: ${skipped.playlistName}`,
toastType: "info"
});
} catch (e) {
console.error(`[ytdlp] Failed to add existing track to playlist:`, e);
}
}
break;
}
}
}
}
// Run prescan periodically (every 30 seconds)
setInterval(prescanSlowQueue, 30000);
// Also run once after initial scan completes
library.onScanComplete(() => {
setTimeout(prescanSlowQueue, 1000);
});
// Load channels from database
const savedChannels = loadAllChannels();
let hasDefault = false;

View File

@ -760,6 +760,7 @@
else if (res.ok) {
M.showToast("Removed");
clearSelection();
render();
} else {
M.showToast("Failed to remove from queue", "error");
}

View File

@ -525,6 +525,36 @@ function notifyProgress(item: QueueItem): void {
}
}
// Mark a slow queue item as skipped (already in library)
export function skipSlowQueueItem(id: string, trackId: string): QueueItem | null {
const item = slowQueue.find(i => i.id === id && i.status === "queued");
if (!item) return null;
item.status = "complete";
item.progress = 100;
item.completedAt = Date.now();
item.trackId = trackId;
// Update database
updateSlowQueueItem(id, {
status: "complete",
progress: 100,
completedAt: Math.floor(item.completedAt / 1000)
});
notifyProgress(item);
// Remove from queue after brief delay
setTimeout(() => removeFromQueue(item), 1000);
return item;
}
// Get queued items from slow queue (for prescan)
export function getQueuedSlowItems(): QueueItem[] {
return slowQueue.filter(i => i.status === "queued");
}
// Cleanup old completed/failed items
export function cleanupOldItems(maxAge: number = 3600000): void {
const now = Date.now();