summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/de.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/en.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/es.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/it.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/music-player.js45
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/transport.js31
12 files changed, 78 insertions, 8 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
index 6df5a77..b05c8b1 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
@@ -190,6 +190,7 @@ export default {
},
'music.err_transport': 'Transport nicht verbunden',
'music.err_playback': 'Wiedergabefehler',
+ 'music.err_transcode': 'Formatumwandlung fehlgeschlagen',
'music.player_play': 'Abspielen',
'music.player_pause': 'Pause',
'music.player_prev': 'Vorheriger',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
index 4c0db39..db7d048 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
@@ -188,6 +188,7 @@ export default {
},
'music.err_transport': 'Transport not connected',
'music.err_playback': 'Playback error',
+ 'music.err_transcode': 'Format conversion failed',
'music.player_play': 'Play',
'music.player_pause': 'Pause',
'music.player_prev': 'Previous',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
index 2d795fc..8f29908 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
@@ -188,6 +188,7 @@ export default {
},
'music.err_transport': 'Transporte no conectado',
'music.err_playback': 'Error de reproducción',
+ 'music.err_transcode': 'Error al convertir el formato',
'music.player_play': 'Reproducir',
'music.player_pause': 'Pausa',
'music.player_prev': 'Anterior',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
index 0ce4c53..5d83517 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
@@ -189,6 +189,7 @@ export default {
},
'music.err_transport': 'Transport non connecté',
'music.err_playback': 'Erreur de lecture',
+ 'music.err_transcode': 'Échec de la conversion du format',
'music.player_play': 'Lecture',
'music.player_pause': 'Pause',
'music.player_prev': 'Précédent',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
index 2ddea64..fb88cbe 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
@@ -189,6 +189,7 @@ export default {
},
'music.err_transport': 'Trasporto non connesso',
'music.err_playback': 'Errore di riproduzione',
+ 'music.err_transcode': 'Conversione del formato non riuscita',
'music.player_play': 'Riproduci',
'music.player_pause': 'Pausa',
'music.player_prev': 'Precedente',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
index 82a8fc2..84e9eb5 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
@@ -186,6 +186,7 @@ export default {
},
'music.err_transport': 'トランスポートが接続されていません',
'music.err_playback': '再生エラー',
+ 'music.err_transcode': 'フォーマット変換に失敗しました',
'music.player_play': '再生',
'music.player_pause': '一時停止',
'music.player_prev': '前へ',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
index be0184a..06097e6 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
@@ -190,6 +190,7 @@ export default {
},
'music.err_transport': 'Transport niet verbonden',
'music.err_playback': 'Afspeelfout',
+ 'music.err_transcode': 'Formaatconversie mislukt',
'music.player_play': 'Afspelen',
'music.player_pause': 'Pauzeren',
'music.player_prev': 'Vorige',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
index 0738cf5..e24fd1e 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
@@ -199,6 +199,7 @@ export default {
},
'music.err_transport': 'Transport niepołączony',
'music.err_playback': 'Błąd odtwarzania',
+ 'music.err_transcode': 'Konwersja formatu nie powiodła się',
'music.player_play': 'Odtwórz',
'music.player_pause': 'Pauza',
'music.player_prev': 'Poprzedni',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
index 1874a5b..1a3069a 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
@@ -190,6 +190,7 @@ export default {
},
'music.err_transport': 'Transporte não conectado',
'music.err_playback': 'Erro de reprodução',
+ 'music.err_transcode': 'Falha na conversão de formato',
'music.player_play': 'Reproduzir',
'music.player_pause': 'Pausar',
'music.player_prev': 'Anterior',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
index 08fd940..236747e 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
@@ -183,6 +183,7 @@ export default {
},
'music.err_transport': '传输未连接',
'music.err_playback': '播放错误',
+ 'music.err_transcode': '格式转换失败',
'music.player_play': '播放',
'music.player_pause': '暂停',
'music.player_prev': '上一首',
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;
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') {