Skip to content

feat(sound): support ?tab=sounds direct navigation without breaking AudioContext autoplay policy #633

@takaokouji

Description

@takaokouji

概要

URL パラメータ ?tab=sounds で直接アクセスしたときも SoundEditor が正しく動作するようにする。ただしSharedAudioContext を gesture 前に lazy 作成する方法は 絶対に取らない(過去の不具合再発のため)。

現状の問題

?tab=sounds で URL に直接遷移すると SoundEditor が render されたタイミングで AudioBufferPlayer.constructoraudioContext.createBuffer() を呼ぶが、その時点で SharedAudioContext は user gesture 前のため undefined を返す → TypeError: Cannot read properties of undefined

upstream の SharedAudioContext は autoplay policy 対応のため mousedown / touchstart / keydown のいずれかを待ってから new AudioContext() する設計。?tab=sounds で直接来るとこの gesture が無いため context 未作成。

過去の失敗パターン(やってはいけないこと)

?tab=sounds で crash するから」と SharedAudioContext 側を gesture 前に lazy 作成するように改造すると:

  1. Chrome の autoplay policy で 新しく作られる AudioContextstate: 'suspended'
  2. StartAudioContext が gesture を listen して resume() を呼ぶが、resume()async(数 ms〜数十 ms 遅延)
  3. 同じ click handler の中で即座に source.start() を呼ぶ sound block (例: 旗を押した直後の音再生) は suspended state のまま再生 → 無音
  4. 結果: 「音タブで再生ボタンを押しても音が出ない」「旗を押した瞬間の音再生ブロックが無音」が再発

実例: PR #630 の commit cc2a8dab7e で lazy 作成 → 旗→音の flow がリスクを受けることが判明 → 79dd6be827 で revert。少なくとも 2 回試して毎回 revert している。

Playwright の Chromium は autoplay policy が緩く state: 'running' で誤検知されるため、Playwright 単独での検証は信用できない。実機 (本番 Chrome) で旗→音再生フローを必ず確認する。

取りうるアプローチ

A. AudioBufferPlayer を null-safe にする(推奨)

SharedAudioContext には触らず、AudioBufferPlayer.constructor (および周辺 sound editor コード) が audioContextundefined でも crash しないようにする:

  • audioContext 未設定時は decode / playback 機能を no-op にして UI だけ render
  • 初回 user gesture 後に SharedAudioContext が作成されたら、必要なら sound buffer を再 decode

メリット:

  • upstream の SharedAudioContext を一切変更しない → autoplay policy 起因の既知不具合を完全に回避
  • 直接遷移時の crash だけが直る

懸念:

  • 音タブを開いた直後はwaveform が表示されないかも(user gesture 後に表示される)
  • buffer の遅延 decode のための仕組みが必要

B. ?tab=sounds を URL パラメータから外して機能ごと削除

  • 直接遷移 use case が「Playwright 撮影セッションのみ」だった経緯を踏まえて、?tab=sounds意図的に未対応扱いにする
  • .claude/rules/scratch-gui/e2e-test.md は既に「禁止」と明記済み

メリット:

  • 0 行の追加コード
  • 既知の autoplay policy 関連バグを permanently 回避

懸念:

  • ドキュメント / 教材で ?tab=sounds を直リンクしたいケース(あれば)に対応できない

C. tab=sounds 遷移後に自動 user gesture を発火(ワークアラウンド)

ページロード直後に document.dispatchEvent(new MouseEvent('mousedown')) を URL パラメータ駆動で行う:

  • ?tab=sounds 検出時にロード完了直後に合成 mousedown を発火
  • upstream の initAudioContext listener が拾って new AudioContext() を user gesture コンテキストで作成

メリット:

  • 既存の SharedAudioContext を変更不要

懸念:

  • 合成 event は browser によっては user gesture と認識されない(isTrusted: false
  • Chrome は最近 untrusted event での AudioContext 作成を拒否する方向に動いている → 将来壊れる可能性
  • 結局 lazy 作成と等価な suspended state 問題が発生するリスク

推奨方針

AAudioBufferPlayer を null-safe にする)を第一候補とする。実装したら必ず:

  1. 実機 (本番 Chrome) で以下を検証:
    • 通常タブ切り替え (旗を押す前にコード→音タブを切り替え) で再生ボタンが動作する
    • 旗を押した直後の sound block (例: ニャー) が即時に音を出す
    • ?tab=sounds 直接アクセスで crash しない、かつ初回 user gesture 後に音が出る
  2. Playwright 検証は補助的扱い(autoplay policy が緩いため最低限の sanity check のみ)

DoD

  • ?tab=sounds 直接アクセスで TypeError が出ない
  • ?tab=sounds 直接アクセスで音タブ UI (waveform / 再生ボタン / エフェクトボタン) が表示される
  • 初回 user gesture 後に音タブの再生ボタンで音が出る
  • 旗 → 即時 sound block の音再生が無音にならない(実機検証必須)
  • integration test で ?tab=sounds 直接遷移パターンを 1 件追加
  • .claude/rules/scratch-gui/e2e-test.md の「?tab=sounds 直接アクセスしてはいけない」セクションを更新:
    • 修正された旨を明記
    • 「禁止」を解除する(または「条件付きで OK」に書き換える)
    • 過去の失敗パターン(lazy 作成 → suspended state → 無音)の記録は将来の改造防止のため残す

参考

  • upstream の SharedAudioContext: packages/scratch-gui/src/lib/audio/shared-audio-context.js
  • 過去の lazy 作成試行と revert: PR feat: upstream merge 2026-05 (v13.7.2 — 284 commits, scratch-blocks v2) #630cc2a8dab7e79dd6be827
  • ドキュメント化された禁止ルール: .claude/rules/scratch-gui/e2e-test.md の「❌ ?tab=sounds で直接アクセスしてはいけない(AudioContext autoplay policy)」セクション

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions