Skip to content

fix(player): route recordings by source extension, not content sniff (#89)#100

Merged
indigo423 merged 2 commits into
mainfrom
fix/mod-misrouted-as-recording
Jul 8, 2026
Merged

fix(player): route recordings by source extension, not content sniff (#89)#100
indigo423 merged 2 commits into
mainfrom
fix/mod-misrouted-as-recording

Conversation

@indigo423

Copy link
Copy Markdown
Collaborator

Fixes the core of #89: Library and Local playback fails for tracker modules ("Couldn't play that track — pick another"); only Mod Archive works.

Root cause

AudioPlayer.play() content-sniffed every buffer via mimeForBuffer to detect PCM recordings. Its deep MP3 frame-sync scan (deepScanMp3 — scans 64 KB for ≥3 0xFF-framesync byte patterns) false-positives on a tracker module's raw 8-bit PCM sample data. So a genuine .mod/.xm/etc. was classified as audio/mpeg, routed to the <audio> element (which can't decode a module → MEDIA_ERR, [pcm] 4), and failed.

Why only Mod Archive worked: its play-random silently skips a failing track and advances, masking the bug; a user-picked Library/Local row stays failed.

Confirmed live on prod (v0.5.11) and reproduced in the current build — mimeForBuffer(4mat_VI.mod) returns "audio/mpeg".

Fix — trust the source, don't sniff module bytes

Recordings only ever arrive from Library/Local sources with a recording extension (Mod Archive is always a module; AHX is caught by reliable offset-0 magic; TFMX by object shape). So classify recordings by the source's extension and stop feeding arbitrary module buffers into the MP3 sniffer:

  • recording-magic.ts: new mimeForExtension(name); export MimeType.
  • sources/index.ts: recordingMime(source) — MIME from Library path / Local filename, null for Mod Archive/TFMX.
  • audio-player.ts: play(input, pcmMime?) takes the caller's recording hint; the PCM arm fires on pcmMime instead of mimeForBuffer(input).
  • Player.tsx + EmbedPlayer.tsx: pass recordingMime(source).

mimeForBuffer is unchanged and still used for download-time extension guessing (Mod Archive has no filename).

Verification

Live (local dev against real files pulled from prod):

  • A real Library .mod now plays through libopenmpt ("Playing from: library", no error).
  • An .mp3 recording still routes to the PCM engine and plays.
  • New unit tests for mimeForExtension and recordingMime (incl. .mod → null); 222 passing; typecheck + lint clean.

Out of scope (separate issues from #89)

  • Some Misc/ChipTune files on prod are 0 bytes on disk — a data/sync issue on the server, not a code bug (/api/library/file faithfully streams stat.size). Worth a re-sync.
  • mschwendt's original TFMX failure: those bytes serve fine and route before the sniffer, so it's unrelated to this bug — likely the AudioWorklet TextDecoder gap; needs its own confirmation from the browser console.

🤖 Generated with Claude Code

Library and Local playback failed for tracker modules ("Couldn't play
that track — pick another"); only Mod Archive worked. Reported at
issue #89.

Root cause: AudioPlayer.play() content-sniffed every ArrayBuffer via
mimeForBuffer to detect PCM recordings, and its deep MP3 frame-sync scan
(deepScanMp3, scanning 64 KB for >=3 0xFF-framesync patterns) false-
positives on a tracker module's raw 8-bit PCM sample data. A genuine
.mod/.xm/etc. was therefore classified as audio/mpeg and routed to the
<audio> element, which can't decode a module -> MEDIA_ERR (pcm code 4)
-> "Couldn't play". Mod Archive masked it: play-random silently skips a
failing track and advances, while a user-picked Library/Local row stays
failed. Confirmed live on prod (v0.5.11) and reproduced in the current
build; mimeForBuffer('4mat_VI.mod') returns 'audio/mpeg'.

Fix: recordings only ever arrive from Library/Local sources with a
recording extension (Mod Archive is always a module; AHX is caught by
reliable offset-0 magic; TFMX by object shape). So classify recordings
by the SOURCE's extension — the authoritative signal — and stop feeding
arbitrary module buffers into the MP3 sniffer:
- recording-magic.ts: new mimeForExtension(name); export MimeType.
- sources/index.ts: recordingMime(source) derives the MIME from the
  Library path / Local filename (null for Mod Archive / TFMX).
- audio-player.ts: play(input, pcmMime?) takes the caller's recording
  hint; the PCM arm fires on pcmMime instead of mimeForBuffer(input).
- Player.tsx + EmbedPlayer.tsx: pass recordingMime(source).

mimeForBuffer is unchanged and still used for download-time extension
guessing (Mod Archive, which has no filename). Verified live: a real
Library .mod plays through libopenmpt ("Playing from: library"), an .mp3
recording still routes to the PCM engine. +new tests (222 passing);
typecheck + lint clean.

Note: some Misc/ChipTune files on prod are 0 bytes on disk — a separate
data/sync issue, not addressed here.

Assisted-by: ClaudeCode:claude-opus-4-8
Signed-off-by: Ronny Trommer <ronny@no42.org>
Code-review follow-ups on PR #100:

- lib/recording-magic.ts: introduce RECORDING_MIME_BY_EXTENSION and
  derive mimeForExtension from it instead of a hardcoded if-chain.
- components/sources/index.ts: derive RECORDING_EXTENSIONS from the
  same map, so widening the allowlist (e.g. .opus) widens routing
  automatically — listing and playback dispatch cannot diverge. Fix
  the stale comment that still claimed engine dispatch is
  content-sniffed at AudioPlayer.play(). Add the missing SPDX header.

Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Ronny Trommer <ronny@no42.org>
@indigo423 indigo423 merged commit baaf3c1 into main Jul 8, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant