music (web): order album tracks by track number, not readdir order
meta.json is written in ffprobe/readdir order (arbitrary), and the web player rendered/queued it as-is — so albums like Andrew Bird's "The Mysterious Production of Eggs" showed scrambled (11, 7, 5, 14, 1, …). The web Track type didn't even carry the `track` tag. Add `track` to the Track type + a shared sortTracks(): by track NUMBER (parsed from the "n/total" tag), falling back to tag title only for tracks without a number. Applied to the rendered tracklist (setAlbum) and both play queues. Verified it reorders that album to 1→14. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
TYPE_ORDER,
|
||||
coverUrl,
|
||||
isAudio,
|
||||
sortTracks,
|
||||
toRel,
|
||||
type AlbumMeta,
|
||||
type Discography,
|
||||
@@ -66,7 +67,7 @@ export const MusicDetail = () => {
|
||||
if (audio.length) {
|
||||
try {
|
||||
const meta = await get<AlbumMeta>(`/music/meta?path=${encodeURIComponent(rel)}`);
|
||||
if (!cancelled) setAlbum(meta.tracks);
|
||||
if (!cancelled) setAlbum(sortTracks(meta.tracks));
|
||||
} catch {
|
||||
if (!cancelled) setAlbum(audio.sort().map((f) => ({ file: f })));
|
||||
}
|
||||
@@ -99,7 +100,7 @@ export const MusicDetail = () => {
|
||||
const playAlbum = async (albumRel: string, startIndex = 0) => {
|
||||
try {
|
||||
const meta = await get<AlbumMeta>(`/music/meta?path=${encodeURIComponent(albumRel)}`);
|
||||
const queue: PlayerTrack[] = meta.tracks.map((t) => ({ albumRel, file: t.file, title: t.title, artist: t.artist }));
|
||||
const queue: PlayerTrack[] = sortTracks(meta.tracks).map((t) => ({ albumRel, file: t.file, title: t.title, artist: t.artist }));
|
||||
player.playQueue(queue, startIndex);
|
||||
} catch {
|
||||
/* ignore */
|
||||
|
||||
@@ -8,8 +8,33 @@ export type DirEntry = { name: string; type: 'directory' | 'file'; size: number;
|
||||
export type LsResult = { entries: DirEntry[] };
|
||||
export type ManifestAlbum = { v: string; cover: boolean; tracks: number; disco?: boolean };
|
||||
export type Manifest = { albums: Record<string, ManifestAlbum> };
|
||||
export type Track = { file: string; title?: string; artist?: string };
|
||||
export type Track = { file: string; title?: string; artist?: string; track?: string };
|
||||
export type AlbumMeta = { path: string; cover?: string; tracks: Track[] };
|
||||
|
||||
/** Parse a track-number tag ("7", "07", "7/14") to a number, or null when absent/unparseable. */
|
||||
const trackNo = (t: Track): number | null => {
|
||||
const raw = t.track?.split('/')[0]?.trim();
|
||||
if (!raw) return null;
|
||||
const n = parseInt(raw, 10);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Canonical album track order: by the `track` NUMBER, falling back to the tag title only for tracks
|
||||
* that have no number (numbered tracks always precede unnumbered ones; filename breaks a final tie).
|
||||
* meta.json is in ffprobe/readdir order (arbitrary), so every consumer must sort with this.
|
||||
*/
|
||||
export const sortTracks = <T extends Track>(tracks: T[]): T[] => {
|
||||
const key = (t: Track) => (t.title || t.file).toLowerCase();
|
||||
return [...tracks].sort((a, b) => {
|
||||
const na = trackNo(a);
|
||||
const nb = trackNo(b);
|
||||
if (na !== null && nb !== null) return na - nb || key(a).localeCompare(key(b));
|
||||
if (na !== null) return -1;
|
||||
if (nb !== null) return 1;
|
||||
return key(a).localeCompare(key(b));
|
||||
});
|
||||
};
|
||||
export type Discography = { artist: string; albums: Record<string, string> };
|
||||
|
||||
const AUDIO_EXT = new Set(['mp3', 'flac', 'm4a', 'aac', 'ogg', 'opus', 'wav', 'wma', 'mp4']);
|
||||
|
||||
Reference in New Issue
Block a user