diff --git a/packages/backend/src/services/xmlService.ts b/packages/backend/src/services/xmlService.ts index 5ce6e3d..be0037a 100644 --- a/packages/backend/src/services/xmlService.ts +++ b/packages/backend/src/services/xmlService.ts @@ -2,6 +2,17 @@ import { create } from "xmlbuilder"; import { Song } from "../models/Song.js"; import { Playlist } from "../models/Playlist.js"; +// Compute effective playlist order: custom overlay (order) + remaining base tracks +const getEffectiveTrackIds = (node: any): string[] => { + const base: string[] = Array.isArray(node?.tracks) ? node.tracks : []; + const overlay: string[] = Array.isArray(node?.order) ? node.order : []; + if (overlay.length === 0) return base; + const setBase = new Set(base); + const orderedKnown = overlay.filter((id: string) => setBase.has(id)); + const missing = base.filter((id: string) => !overlay.includes(id)); + return [...orderedKnown, ...missing]; +}; + const buildXmlNode = (node: any): any => { const xmlNode: any = { '@Type': node.type === 'folder' ? '0' : '1', @@ -14,13 +25,12 @@ const buildXmlNode = (node: any): any => { xmlNode.NODE = node.children.map((child: any) => buildXmlNode(child)); } } else { - // For playlists, always include KeyType and Entries + // For playlists, include KeyType and Entries based on effective order + const effectiveIds = getEffectiveTrackIds(node); xmlNode['@KeyType'] = '0'; - // Set Entries to the actual number of tracks - xmlNode['@Entries'] = (node.tracks || []).length; - // Include TRACK elements if there are tracks - if (node.tracks && node.tracks.length > 0) { - xmlNode.TRACK = node.tracks.map((trackId: string) => ({ + xmlNode['@Entries'] = effectiveIds.length; + if (effectiveIds.length > 0) { + xmlNode.TRACK = effectiveIds.map((trackId: string) => ({ '@Key': trackId })); } @@ -110,15 +120,13 @@ const streamPlaylistNodeFull = async (res: any, node: any) => { res.write('\n '); } else { - const trackCount = node.tracks ? node.tracks.length : 0; - res.write(`\n `); - - if (node.tracks && node.tracks.length > 0) { - for (const trackId of node.tracks) { + const effectiveIds = getEffectiveTrackIds(node); + res.write(`\n `); + if (effectiveIds.length > 0) { + for (const trackId of effectiveIds) { res.write(`\n `); } } - res.write('\n '); } }; @@ -138,15 +146,13 @@ const streamPlaylistNodeCompact = async (res: any, node: any) => { res.write(''); } else { - const trackCount = node.tracks ? node.tracks.length : 0; - res.write(``); - - if (node.tracks && node.tracks.length > 0) { - for (const trackId of node.tracks) { + const effectiveIds = getEffectiveTrackIds(node); + res.write(``); + if (effectiveIds.length > 0) { + for (const trackId of effectiveIds) { res.write(``); } } - res.write(''); } }; @@ -167,15 +173,13 @@ const streamPlaylistNode = async (res: any, node: any, indent: number) => { res.write(`${spaces}\n`); } else { - const trackCount = node.tracks ? node.tracks.length : 0; - res.write(`${spaces}\n`); - - if (node.tracks && node.tracks.length > 0) { - for (const trackId of node.tracks) { + const effectiveIds = getEffectiveTrackIds(node); + res.write(`${spaces}\n`); + if (effectiveIds.length > 0) { + for (const trackId of effectiveIds) { res.write(`${spaces} \n`); } } - res.write(`${spaces}\n`); } };