Skip to content

観る画面: ブラウザが音声トラックを出さなくても、2本目の音声を選べるようにする - #840

Merged
5ym merged 1 commit into
mainfrom
watch-audio-fallback
Oct 17, 2026
Merged

5ym merged 1 commit into
mainfrom
watch-audio-fallback

Conversation

@5ym

@5ym 5ym commented Oct 17, 2026

Copy link
Copy Markdown
Collaborator

何が起きていたか

解説放送つきの録画 (フジテレビのアニメ、音声は「日本語」「解説」の2本) で、観る画面に音声の切り替えが出ず、解説を選べないという報告がありました。

  • エンコードは音声を全部残している (-map 0:a。トラック名も番組表どおり)
  • 観る画面は、ファイルをそのまま読むとき、音声の一覧を video.audioTracks から作っていた
  • Chrome・Firefox は既定で video.audioTracks を出さない (Chrome は実験的な機能の奥、Firefox は media.track.enabled が既定でオフ)。そのため一覧が空になり、切り替えごと出ていなかった。Chrome で Matroska をそのまま読める録画 (ふつうの AV1 の録画) はみなこの形

直し方

  • ブラウザが audioTracks を出さず、2本以上入っていて、その場で流すほう (/stream) へ移れるなら、playback の音声の名前で並べる
  • 2本目を選んだら、いまの位置からその場で流すほうへ移る (/stream?audio=。サーバが選んだ1本を流す)。移る先は ts/playback.ts の streamForAudio — 同じコーデックの詰め替え (サーバは軽い) を先に、無ければ MSE が受け取れる変換
  • 作り直しはコーデックのメニューと同じ (chooseCodec)
  • いまの音声 (ファイルでは1本目) を選んだだけなら何もしない。最初はこれまでどおりファイルをそのまま読む
  • Safari のように audioTracks を出すブラウザは、これまでどおりファイルの中で切り替える

テスト

  • streamForAudio の単体テスト (同じコーデックの詰め替えを先に / 受け取れなければ変換 / 何も無ければ null)
  • bun run check、単体テスト全部、biome

🤖 Generated with Claude Code

https://claude.ai/code/session_017wBP4MK6j3oBwqbn95wZ8H

Chrome・Firefox は既定で video.audioTracks を出さないので、エンコード済みのファイルを
そのまま読んでいると解説・副音声の切り替えが出なかった。2本以上あれば playback の名前で
並べ、2本目を選んだら、いまの位置からその場で流すほう (同じコーデックの詰め替えを先に) へ移る。

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017wBP4MK6j3oBwqbn95wZ8H
@claude

claude Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Claude finished @5ym's task in 25s —— View job


レビュー結果

大きな不具合は見つかりませんでした。意図は明確で、既存の chooseCodec や syncAudio の作りに沿っています。

確認したこと

  • audioFallback() は picked.way === 'file' のときだけ値を返します。ストリーム再生中は、先に syncAudio の picked?.way === 'stream' の分岐に入ります。そのため、移った後に一覧が消えたり二重に動いたりしません。
  • moveForAudio で audioIndex を先に更新します。そのうえで、ストリーム用の $effect と canvas 側が streamAudio(audioIndex) を読むので、/stream?audio= に選んだ音声 id が渡ります。
  • 移る前に localSrc を revokeObjectURL で解放し、busy と broken も戻しています。chooseCodec と同じ手順です。
  • index === audioIndex なら何もしません。これで、最初の1本を選び直したときに不要に作り直しません。
  • streamForAudio は pickOffer の順序と揃っています。同じコーデックの詰め替えを先にし、無ければ MSE が受け取れる変換にします。テストは3パターン(詰め替え優先、変換、null)を押さえています。
  • ドキュメント(docs/app.md、docs/library.md)も更新されています。

要確認(任意・ブロッカーではありません)

  1. ファイルの1本目と info.audios[0] の対応。 ファイルをそのまま読んでいるときは、audioIndex の初期値 0 を「ファイルの1本目 = info.audios[0]」と見なしています。-map 0:a の順序がそのまま playback の audios の順になるなら問題ありません。この前提は playback 側で保証されているか、確認してください。
  2. 端末に保存したコピー(localSrc)を読んでいるとき。 この場合も picked.way === 'file' なので、2本目を選ぶとサーバのストリームへ移り、オフラインのコピーから離れます。localMkv(canvas 描画)は先に分岐するので影響しません。ただ、<video> で端末のコピーを読んでいる場合に、ユーザーが意図せずオンライン再生へ切り替わっても許容するかは、仕様として確認してください。
  3. テストの範囲。 streamForAudio の単体テストはあります。一方で、+page.svelte 側の audios の構築と moveForAudio の遷移は、単体テストで押さえられていません。E2E があるなら、audioTracks が無いブラウザを想定したケースを足せると安心です。

セキュリティ面の問題はありません。音声 id はサーバが返した info.audios[].id から作っていて、URL には encodeURIComponent が掛かっています。

@5ym

5ym commented Oct 17, 2026

Copy link
Copy Markdown
Collaborator Author

レビューの3点について:

  1. ファイルをそのまま読むのは axis === 'encoded' のときだけで、そのときの playback の audios はエンコードしたファイルを ffprobe した音声の順 (server/playback.ts の encoded[0].media.audios、id は並びの番号) です。ファイルの1本目と info.audios[0] は同じものを指します。
  2. 端末のコピーから流すほうへ移るのは、コーデックを選び直したとき (chooseCodec) と同じ動きです。2本目の音声は端末のコピーからは出せないので、選んだときだけサーバへ移ります。選ばなければ今まで通りコピーを読みます。
  3. 画面の遷移は E2E に足していません。行き先の決め方 (streamForAudio) は単体テストで押さえています。

コードは変えずにマージします。

@5ym
5ym merged commit 2c8404d into main Oct 17, 2026
22 of 23 checks passed
@5ym
5ym deleted the watch-audio-fallback branch October 17, 2026 07:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant