From 8c780b9928db8145b7fe18ecd137384ccfe25d8f Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Mon, 24 Aug 2026 17:39:41 +0200 Subject: feat(hub): Music app client — album grid, flat list, persistent player MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the client half of docs/musicbay.md against MNP 0.8: - music-app.js: album grid (grouped by artist -> album, from index-time artist/album fields) or flat folder view, per-group localStorage toggle like Videos. MusicBrainz (music_meta_req) is only looked up when a track has no embedded cover at all — well-tagged files never trigger a network call, unlike Videos where TMDB is unconditional. Reuses video-app.js's MediaThumb/LazyTile (now exported) rather than duplicating the chunk-path thumbnail decode + virtualization. - music-player.js: the persistent player bar — queue, shuffle (Fisher- Yates, keeps the current track in place when toggled), repeat (off/ all/one), volume (localStorage), prev/next, a one-track prefetch cache. No MSE, no node-side streaming: a track is downloaded and decrypted once via file-utils.js's pipelinedDownload, same chunk pipeline Files already uses, then played from a blob URL. - group-page.js: owns musicQueue/musicbrainzConfig state and renders MusicPlayerBar outside the tab-switched area — deliberately, so playback survives navigating to Chat/Files, the same reasoning the video/preview modals are shell-owned rather than app-owned. - apps.js: registers "music". transport.js: fetchMusicMeta (keyed by path, same reordering-hazard fix as fetchMediaMeta), setMusicbrainzConfig/setMusicbrainzEnabled (signed ops, mirroring TMDB's), and the three new ack handlers. group-settings.js: a MusicBrainz settings section (contact string, per-group toggle) — the existing Applications checklist already picks up "music" for free, per apps.md's own claim. - icon.js: music/pause/skip-next/skip-prev/shuffle/repeat/volume, drawn in the same stroked style as the existing set. - i18n: group.tab_music, the music.* and settings_node.musicbrainz_* keys, translated (not just copied) across all ten locales, Polish carrying full one/few/many/other plural forms for music.n_tracks. - webapp.py's _ASSETS, test_hook_ordering.py's STATIC_FILES and test_transport_contracts.py's SPLIT_FILES gain the two new files. Full suite (common + hub + node): 1116 passed, no regressions. `npm run sync-ui` in meshbay-client confirmed both files copied. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01KBi7ALLGfwcjBXt57yNMcy --- .../src/meshbay_hub/static/locales/ja.js | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js') 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 f00a0ec..44f9037 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js @@ -74,6 +74,7 @@ export default { 'group.tab_files': 'ファイル', 'group.tab_chat': 'チャット', 'group.tab_video': '動画', + 'group.tab_music': '音楽', 'group.tab_members': 'メンバー', 'group.tab_settings': "設定", 'members.danger_leave_hint': "このグループのファイルとチャットにアクセスできなくなります。", @@ -172,6 +173,27 @@ export default { 'video.search_no_results': '一致する結果が見つかりません。', 'video.search_apply_hint': '現在このタイトルでグループ化されているすべてのファイルに適用されます。', + // 音楽 + 'music.mode_grid': 'アルバム', + 'music.mode_flat': 'フラットリスト', + 'music.empty': '音楽が見つかりません。', + 'music.unknown_album': '不明なアルバム', + 'music.play_all': 'すべて再生', + 'music.n_tracks': { + one: '{n}曲', + other: '{n}曲', + }, + 'music.err_transport': 'トランスポートが接続されていません', + 'music.player_play': '再生', + 'music.player_pause': '一時停止', + 'music.player_prev': '前へ', + 'music.player_next': '次へ', + 'music.player_shuffle': 'シャッフル', + 'music.player_repeat_off': 'リピートオフ', + 'music.player_repeat_all': 'すべてリピート', + 'music.player_repeat_one': '1曲リピート', + 'music.player_volume': '音量', + // LAN cast 'cast.start': 'デバイスにキャスト', 'cast.stop': 'キャストを停止', @@ -598,6 +620,15 @@ export default { 'settings_node.video_root_title': '動画のルートフォルダ', 'settings_node.video_root_hint': 'このグループで動画アプリの起点とするフォルダ(またはサブフォルダ)です。選択されるまで、動画には何も表示されず、TMDB への問い合わせも行われません。', 'settings_node.video_root_none': '— 未選択 —', + 'settings_node.musicbrainz_title': 'MusicBrainzのメタデータ', + 'settings_node.musicbrainz_hint': 'トラックに使用可能な埋め込みカバーがない場合、MusicBrainzのカバーアートと正式名称をMusicアプリで表示できるようにします。オフにするとタグ・ファイル名のみでの閲覧になり、第三者へのリクエストは発生しません。', + 'settings_node.musicbrainz_enabled': '有効', + 'settings_node.musicbrainz_disabled': '無効', + 'settings_node.musicbrainz_contact_label': '連絡先(MusicBrainzが応答するために必要)', + 'settings_node.musicbrainz_contact_placeholder': 'you@example.com またはプロジェクトのURL', + 'settings_node.musicbrainz_contact_set': '連絡先が設定されています。', + 'settings_node.musicbrainz_contact_unset': '連絡先が設定されていません — 設定されるまでMusicBrainzの検索は無効のままです。', + 'settings_node.musicbrainz_save': '保存', 'settings_node.video_root_save': '保存', 'settings_node.video_root_change_confirm': '動画のルートフォルダを変更すると、全メンバーの動画タブの表示内容が変わります。続行しますか?', -- cgit v1.2.3