diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/music-player.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/music-player.js | 45 |
1 files changed, 37 insertions, 8 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/music-player.js b/packages/meshbay-hub/src/meshbay_hub/static/music-player.js index 690efc9..b75d73c 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/music-player.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/music-player.js @@ -16,11 +16,15 @@ import { CHUNK_SIZE, pipelinedDownload } from './file-utils.js'; * calls `onPlayQueue(tracks, startIndex)`, threaded down from group-page.js, * to hand this component a new queue. * - * No streaming, no MSE, no node-side transcode pool: a track is a few - * megabytes, so it is downloaded and decrypted once through the same chunk - * pipeline Files already uses (file-utils.js's pipelinedDownload), then - * played from a blob URL — the deliberate simplification recorded in - * musicbay.md §2.2. + * No streaming, no MSE, no node-side transcode pool for the common case: a + * track is a few megabytes, so it is downloaded and decrypted once through + * the same chunk pipeline Files already uses (file-utils.js's + * pipelinedDownload), then played from a blob URL — the deliberate + * simplification recorded in musicbay.md §2.2. WMA and Musepack are the one + * exception (`NEEDS_TRANSCODE_RE` below): neither decodes in a browser's + * <audio> element at all, tagged correctly or not, so those two go through + * `transport.requestAudioTranscode` first — a node-side, cached-after-once + * AAC/M4A conversion — before the same download/blob path runs. */ const MIME_BY_EXT = { @@ -28,6 +32,11 @@ const MIME_BY_EXT = { wav: 'audio/wav', aac: 'audio/aac', m4a: 'audio/mp4', }; +// Kept in sync with the node's BROWSER_INCOMPATIBLE_AUDIO_EXTS +// (webrtc_server.py) — both name the same two formats no mainstream +// browser's <audio> element decodes natively. +const NEEDS_TRANSCODE_RE = /\.(wma|mpc)$/i; + function guessMime(name) { const ext = (name || '').split('.').pop().toLowerCase(); return MIME_BY_EXT[ext] || 'audio/mpeg'; @@ -186,10 +195,30 @@ function MusicPlayerBar({ transportRef, gekRef, queue, onClose }) { if (cached) return cached.url; const transport = transportRef.current; if (!transport || !transport.connected) throw new Error(t('music.err_transport')); - const totalChunks = Math.ceil(entry.size / CHUNK_SIZE); - const chunks = await pipelinedDownload(transport, gekRef.current, entry.id, totalChunks); - const blob = new Blob(chunks, { type: guessMime(entry.name) }); + + let downloadId = entry.id; + let downloadSize = entry.size; + let mime = guessMime(entry.name); + if (NEEDS_TRANSCODE_RE.test(entry.name)) { + let info; + try { + info = await transport.requestAudioTranscode(entry.id); + } catch (err) { + throw new Error(t('music.err_transcode')); + } + downloadId = info.hash; + downloadSize = info.size; + mime = info.mime || 'audio/mp4'; + } + + const totalChunks = Math.ceil(downloadSize / CHUNK_SIZE); + const chunks = await pipelinedDownload(transport, gekRef.current, downloadId, totalChunks); + const blob = new Blob(chunks, { type: mime }); const url = URL.createObjectURL(blob); + // Keyed by the track's own id, not `downloadId` — the transcode cache + // hash is an implementation detail of getting there, and a second play + // of the same track must still hit this cache rather than re-requesting + // a transcode the node already ran once. blobCacheRef.current.set(entry.id, { url, order: blobInsertRef.current++ }); evictOldBlobs(); return url; |