aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/transport.js
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-24 21:08:34 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-24 21:08:34 +0200
commitf4ed927fa873133c5fed73fb7dd60e7747fc3dc2 (patch)
treed126c5e505c342ee6b02aadc1e46b1a026c2a027 /packages/meshbay-hub/src/meshbay_hub/static/transport.js
parent75d1f8b93dfa0bffda3a59a6d143b06dcc3ca67f (diff)
downloadmeshbay-f4ed927fa873133c5fed73fb7dd60e7747fc3dc2.tar.gz
feat(music): transcode WMA/Musepack to AAC so they actually play
Tagging and covers for these two formats landed already, but neither one decodes in any mainstream browser's <audio> element at all — a real library scan turned up 273 such files that would show up correctly in the Music app and then simply fail on click. This closes that gap: the node transcodes to AAC/M4A on request (a one-shot whole-file conversion, not live-piped like video's fMP4 segments — an audio file is small enough that streaming it buys nothing), caches the result under its own content hash the same way a TMDB poster or a MusicBrainz cover is cached, and serves it back through the ordinary file_req/chunk path. That path used to assume anything in the media cache was thumbnail-sized (single chunk, always); generalized it to slice a cached blob the same way a real file on disk gets sliced, since a transcoded track can be several MB. New MNP pair (`audio_transcode_req`/`_resp`, version bump to 0.9), shares its concurrency cap with video's transcode pool rather than getting its own — both are real ffmpeg processes on the same node. Every other audio format is untouched: this only fires for .wma/.mpc, the two extensions that need it.
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/transport.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/transport.js31
1 files changed, 31 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
index e0e4a64..4f8e3b5 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
@@ -620,6 +620,23 @@ class MeshBayTransport {
}
/**
+ * Server-side transcode of a Music-app file the browser's own <audio>
+ * element cannot decode at all (WMA, Musepack) into AAC/M4A. Returns
+ * `{ hash, size, mime }` — the *cache* hash to pull through the normal
+ * file_req/chunk path (fetchChunk/pipelinedDownload), not the file's own
+ * id, the same indirection already used for a TMDB poster or a
+ * MusicBrainz cover. Cached node-side after the first call, but ffmpeg
+ * still has to run at least once and a transcode slot can be busy, so
+ * this gets a longer timeout than the metadata lookups above.
+ */
+ async requestAudioTranscode(fileId) {
+ const msg = await this._sendAndWait(
+ { type: 'audio_transcode_req', v: '0.9', file_id: fileId }, 120000);
+ if (msg.type === 'error') throw new Error(msg.detail);
+ return msg;
+ }
+
+ /**
* Set/clear the node-wide MusicBrainz contact string — the User-Agent
* identity MusicBrainz's usage policy asks for, not a credential (there
* is none, docs/musicbay.md §3.1). Signed like setTmdbConfig: this turns
@@ -1386,6 +1403,9 @@ class MeshBayTransport {
// search box can have more than one of these in flight at once.
: obj.type === 'season_meta_req' ? `season_meta:${obj.tmdb_id}:${obj.season}`
: obj.type === 'tmdb_search_req' ? `tmdb_search:${obj.media_type}:${obj.query}`
+ // Same reordering hazard as media_meta_req: the player prefetches
+ // the next track while the current one may still be transcoding.
+ : obj.type === 'audio_transcode_req' ? `audio_transcode:${obj.file_id}`
: null,
resolve: (msg) => { clearTimeout(timeout); this._pending.delete(id); resolve(msg); },
reject: (err) => { clearTimeout(timeout); this._pending.delete(id); reject(err); },
@@ -1641,6 +1661,17 @@ class MeshBayTransport {
return;
}
+ // Same reasoning as music_meta_resp: keyed, not arrival-order — the
+ // player can have a transcode of the current track and a prefetch of
+ // the next one in flight together.
+ if (msg.type === 'audio_transcode_resp') {
+ const key = `audio_transcode:${msg.file_id}`;
+ for (const [, handler] of this._pending) {
+ if (handler._key === key) { handler.resolve(msg); return; }
+ }
+ return;
+ }
+
// Same reasoning as media_meta_resp: keyed, not arrival-order, and
// "nobody's waiting any more" must not fall through either.
if (msg.type === 'season_meta_resp') {