# レガシークライアントと Muvic＆joy の関係

Muvic＆joy は **新規プラットフォーム**です。以下の兄弟アプリは **参照実装・将来統合ソース**として別リポジトリ／フォルダに残します。

**ローカル git · embed 再読込 · G4 再テスト:** 手順は [local-dev-guide.md](local-dev-guide.md)（本書は postMessage / 深リンクの契約正本）。

## Karaoke Web Studio

- **想定パス:** `../karaoke-app-v1.0.0/`
- **強み:** MusicXML（OSMD）、LRC、Spessa/SoundTouch、チャンネルミキサー、ステージ録画
- **プラットフォーム移行時:**
  - クラウド `catalog.json` の **MP3 URL 直読み**は UGC 本番では使わない（コンクール応募期の名残）
  - 将来は **Work ID** 経由で公式/UGC アセットを取得
  - プレイヤー統合時は **楽譜ビュー + カラオケ操作** のソース

### PoC 深リンク（④）

Karaoke `index.html` クエリ:

| パラメータ | 意味 |
|------------|------|
| `muvicWork` | Work ID（例 `work_demo_001`） |
| `muvicApi` | Work API ベース（既定 `http://127.0.0.1:8782`） |
| `muvicAssets` | 譜面 fallback 設定 JSON の URL |
| `muvicEmbed` | `1` — platform iframe 用（ウェルカム省略・ロビー戻る非表示）。連携後 **自動で歌唱ステージ（`enterStage`）** |
| `muvicOverlayAudio` | 重ね録りガイド用の **前テイク音声 URL**（8782 の `/assets/.../overlays/...`） |
| `muvicOverlaySyncAnchorMs` | ガイド再生開始位置（ms）。前テイク POST の `syncAnchorMs` を player が iframe URL に載せる |
| `muvicOverlayUpload` | `1` — 録画終了時に **音声トラックのみ** platform `POST /works/:id/overlays`（要親から Bearer） |
| `muvicOverlayGuideOffsetMs` | 前テイクガイドの同期オフセット（ms）。**＋**でガイド遅延、**−**で曲頭から早める。未指定時は `localStorage`（`karaokeMuvicOverlayGuideOffsetMs`） |
| `v` | platform が付与する **キャッシュバスタ**（`embedVersion` + ページロード ID）。8780 の Player/Studio 再読込で iframe を更新 |

**postMessage（8780 → 8790）:**

| type | フィールド | 意味 |
|------|------------|------|
| `muvic:session` | `viewportAspect?` (`16:9` \| `9:16`) · **transport v2:** `transportAuthority`, `durationSec`, `masterBgmUrl`, `masterBgmActive` · **`parentOrigin`** · **P2:** `contestRoomId?`, `contestStartAtMs?`（ルーム中は Contest takes API） | セッション同期（overlay・token · 親 BGM · コンクール） |
| `muvic:transport` | `action`: `play` \| `pause` \| `seek`, `positionSec`, `source: 'parent'` | 親マスタークロック → 楽譜追従 — [transport-sync-v2.md](architecture/transport-sync-v2.md) |
| `muvic:clockSync` | `positionSec`, `isPlaying`, `source: 'parent'` | 親 BGM 再生中の細かい位置合わせ（`masterBgmActive` 時） |

**postMessage（8790 / 8765 → 8780）:** **`muvic:transportRequest`** — embed transport → 親（v3 · `transportAuthority: 'parent'` 時）
| `muvic:viewportAspect` | `workId`, `aspect` | player の 16:9 / 9:16 切替。Karaoke は `body.muvic-aspect-9-16` で縦セーフ枠 |

**9:16 録画（PoC）:** カメラ ON 時、720×1280 canvas へ cover クロップして `captureStream` → 動画トラック。ファイル名 `_9x16`。譜面のみ・カメラ OFF は音声のみ（従来どおり）。

**postMessage（8790 → 8780）:**

| type | タイミング |
|------|------------|
| `muvic:overlayPosted` | 重ね録り POST 成功 — 親がガイド URL/anchor を更新し `muvic:session` 再送 |
| `muvic:contestTakePosted` | コンクールテイク POST 成功 — `roomId`, `takeId`, `audioUrl`, `sessionStartAtMs` — [contest-sync.md](architecture/contest-sync.md) |
| `muvic:recordingComplete` | ステージ録画停止 — `mediaBuffer`（≤12MB）または `bufferOmitted`。親が完了モーダル表示（embed 時 Karaoke `recordModal` は出さない） |
| `muvic:playbackStarted` | 楽譜 **再生開始**（`startPlayback`）— `view: 'score'`, `workId`。親 Player の有効プレイ判定用 |
| `muvic:embedEngaged` | 再生中 **5s ごと** — `view: 'score'`, `workId`, `deltaMs`（G4 ランキング滞在） |
| `muvic:playbackCompleted` | **曲の自然終了** — `view`, `workId`, `playedMs`, `naturalEnd: true`（30 秒未満の短尺 qualified） |

**postMessage（8780 → 8790）:** `{ type: 'muvic:recordingAction', action: 'discardLatest' \| 'downloadLatest' }` — 親モーダルから破棄/ダウンロード（大容量で buffer 省略時）。

**ガイド同期（Karaoke `app.js`）:**

| タイミング | 動作 |
|------------|------|
| テイク1 録画中 | 録画開始〜伴奏 BGM 開始を計測 → POST `syncAnchorMs` |
| テイク2 以降 | **3,2,1 後**（`startPlayback`）でガイド `<audio>` を **`syncAnchorMs` 相当秒からシークして再生** |
| `syncAnchorMs` 欠落 | **3000ms** フォールバック |
| 読込 | `loadedmetadata` → `canplay` 待ち、シーク検証、`playing` / `timeupdate` でずれ補正 |
| 微調整 | 録画環境モーダル「重ね録りガイドの同期」（±800ms）。**前テイク URL があるときのみ**表示 |

オーバーレイ音声は API が **Range リクエスト**（206）に応答。CORS は 8782 から 8790 埋め込み origin を許可。

**iframe 注意:** 8780 に embed すると第三者コンテキストになり `setTimeout` が遅延されやすいため、Karaoke は `muvicWork` 検出時 **タイマーなしで即 `tryLaunch`** する（ロビー `selectSong(-1)` もスキップ）。`body.muvic-embed` でロビー戻る・全画面・Karaoke 録画完了モーダルを非表示、ステージは iframe 高さ（`100%`）基準。

Platform `player.html?work=` の楽譜ビューは Karaoke（8790）を **iframe embed**（`muvicEmbed=1`）。別タブ起動も同クエリでステージ直行可。譜面 URL が null のときは `apps/web/config/karaoke-poc-assets.json` の `scoreFallback` を使用。

## CDS3D Studio

- **想定パス:** `../CDS3D/app/`
- **ローカル dev:** 既定 **8765**（`cds3d_serve.py` 等で静的配信）
- **強み:** VRM、タイムライン振付、`danceScoreVersion` JSON、MoveNet/MQTT、MV 録画、MediaPipe 切り抜き
- **プラットフォーム移行時:**
  - 技術名 **CDS3D** はエンジン/スキーマ（例: `cds3d-dance` / `cds3d-tracking-v1`）として維持可
  - ユーザー向け名称は **Muvic＆joy** / **スタジオ**
  - 遠隔歌唱の AR ステージ・3D 表現の実装ベース

### PoC 深リンク（UI-2 embed）

CDS3D `index.html` クエリ:

| パラメータ | 意味 |
|------------|------|
| `muvicEmbed` | `1` — platform iframe 用 |
| `muvicMode` | `player`（プレイヤー 3D タブ） \| `studio`（フルスタジオ） |
| `muvicWork` | Work ID（例 `work_demo_001`） |
| `muvicApi` | Work API ベース（既定 `http://127.0.0.1:8782`） |
| `muvicAssets` | 振付 fallback 設定 JSON の URL（platform `karaoke-poc-assets.json` に `motionFallback` を同居） |
| `muvicViewportAspect` | 任意 — 初回 `9:16` / `16:9`（以降は親から `postMessage`） |
| `v` | 同上（Karaoke 表の `v`）— `js/embed-cache.js` · `karaoke-poc-assets.json` の `embedVersion` |

**postMessage（8780 → 8765）:** `{ type: 'muvic:viewportAspect', workId, aspect: '16:9' | '9:16' }` · **`muvic:session`** / **`muvic:transport`** / **`muvic:clockSync`**（v2 · Karaoke 表と同型） — CDS3D は `html.muvic-aspect-9-16` で **transport 左・9:16 ステージ右**（Player 3D + 9:16 時は iframe 全幅）。送信 origin は **`js/embed-post-message.js`**（iframe `src` の origin · 未ロード時は送らない）。

**postMessage（8765 → 8780）:** `{ type: 'muvic:playbackStarted', view: 'dance', workId }` — 3D transport **再生 ON** 時（player embed）。再生中は **`muvic:embedEngaged`**（5s · `deltaMs`）。尺終端は **`muvic:playbackCompleted`**（G4 · 短尺）。**`muvic:embedReady`** — 振付 JSON 適用後（親 transport 再送）。**`muvic:transportRequest`**（v3）— 親 origin は **`muvicAssets` URL の origin**（8780）に限定（127.0.0.1 / localhost 二重送信を避ける）。

**CDS3D 兄弟リポ（`../CDS3D/app/` · platform git 外）— transport 連携:**

| ファイル | 役割 |
|----------|------|
| `js/muvic_embed.js` | session / transport / **clockSync**（`syncPositionFromParent` · リレーなし） |
| `js/animation.js` | **`syncPositionFromParent(t)`** — 親 BGM マスター時の位置・ポーズ更新（`jumpToTime` の seek リレーを避ける） |

更新後は **8765 ハードリロード**（`index.html` の `muvic_embed.js?v=5`）+ Player **`embedVersion` 10** 再読込（`v=10.xxxxx`）。

**起動時の振付読込（CDS3D `main.js`）:**

1. `GET {muvicApi}/works/:id` → `assets.motion.url`（`cds3d-dance`）があれば JSON を fetch して `applyDanceScoreData`  
   - 8782 は PoC 時 **`motionFallback` を解決して URL を付与**（`GET /poc/motion/:workId` または投稿済み `/assets/:workId/motion.json`）
2. URL が無い場合のみ `muvicAssets` の `motionFallback[workId]` を直接 fetch

**振付 PoC の確認（8782 / Player）:** `GET /works/:id` の `assets.motion.url` → JSON に `keyframes[]`。直接 `GET /poc/motion/:workId` が 200 でも可。Player **3D** タブのタイムライン尺（例 **0:08.0**）でも判断できる。CDS3D embed 時は「Muvic: 振付…」トーストを **VRM 配置の約 2.8s 後**に表示（両方見える）。合否の主判定はタイムライン / Network 推奨。

**Platform Player カメラタブ:** 自撮りプレビュー PoC のみ。**機能拡張は保留**（歌唱・録画は **楽譜 / Karaoke**）。

**Player embed（`muvicMode=player`）— 表示仕様:**

| 表示 | 非表示（CSS `html.muvic-embed-player`） |
|------|----------------------------------------|
| `#studioMainColumn`（Three.js ステージ + タイムライン / トランスポート） | ヘッダー、狭幅タブ、`#studioKeyColumn`（キー編集列） |
| **Orbit**（マウス操作）は可 | ギズモ、関節インスペクター、ポーズ取込、Undo/Redo、キー前後ジャンプ |
| 再生中も **「スタジオ設定」**（`#btnOpenStudioSettings`） | ステージ上のその他トップボタン（Webcam・視点プリセット等） |
| 設定モーダルは **「自動カメラワーク (MV演出)」** のみ（`#muvicStudioSettingsAutoCamSection`） | モーダル内のその他セクション |

**Studio embed（`muvicMode=studio`）:** CDS3D **フル UI**（`html.muvic-embed-studio` — iframe 高さ 100% 基準）。Platform `studio.html?work=` が iframe で `muvicMode=studio` を付与。**Transport:** 親 transport バー + `muvic:session` / transport / clockSync / v3 `transportRequest`（player 3D タブと同型 · `isMuvicEmbedTransportClient`）。

**自動カメラワーク:** 再生中に ON の場合、手動 Orbit は一時的に上書きされうる（仕様どおりユーザー向けに説明）。

**CORS:** 8782 API は embed origin **8765** を許可（Work / motion.json 取得）。

Platform 側:

| 画面 | 実装 |
|------|------|
| `player.html?work=` → **3Dダンス** タブ | `#cds3dFrame` lazy load — `apps/web/js/cds3d-bridge.js` |
| `studio.html?work=` | `#cds3dStudioFrame` — フル CDS3D |

`localStorage` キー `muvic_cds3d_origin` で CDS3D ベース URL を上書き可（Karaoke の `muvic_karaoke_origin` と同様）。

## 統合方針（非ゴール）

- 2 アプリを **1 フォルダにマージしない**
- 共有は **Work スキーマ・API 契約・ブランド** から始め、コード共有は `packages/` を後から抽出
