Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Media Capture and Streams: W3C仕様と現場での知見

Media Capture and Streams: W3C仕様と現場での知見

このスライドはJSConfJP 2025での株式会社ドワンゴのスポンサーセッションで使用したものです。
---
Media Capture and Streams API は、Web アプリケーションがカメラやマイクなどのメディアデバイスへアクセスし、MediaStreamTrack/MediaStream といったインターフェースを通じて映像・音声を扱うための仕様です。このセッションでは、標準化仕様に定義されるモデルやライフサイクル、Permissions/Constraints、ImageCapture などの周辺 API を整理しながら、実際にオンライン試験システムの開発で活用した際の体験談・失敗談を合わせて紹介します。

Avatar for Ryota Nakamura

Ryota Nakamura

November 16, 2025
Tweet

More Decks by Ryota Nakamura

Other Decks in Programming

Transcript

  1. 今日のゴール • W3CのMedia Capture and Streams仕様(https://www.w3.org/TR/mediacapture-streams)がなんとなく分かる • これからMediaStream APIに触れる人が@nowaki28と同じ失敗をしなくて済む 扱わない話題

    • (P2Pなど)通信に関する話題 • ウェブオーディオ API, 画面収録 APIなど、カメラ以外の個別のメディアに関する話題 • その他
  2. Media Capture and Streams https://www.w3.org/TR/mediacapture-streams > This document defines APIs

    for requesting access to local multimedia devices, such as microphones or video cameras. • マイクやカメラのようなデバイスへのアクセスを提供する API群の仕様 • WebRTC WGが担当 ※W3C仕様書は基本的にブラウザベンダ向けの文書なので先に MDNを読んだ方がよい
  3. MediaStream API https://www.w3.org/TR/mediacapture-streams/#stream-api • Webアプリケーションにメディアストリームの入出力制御手段を提供する API群 ◦ ストリームがどこで消費されるか ◦ メディアを生成するデバイスの制御

    ◦ 利用可能なデバイスの情報 • 主にMediaStreamTrackとMediaStreamインターフェースから構成される ◦ MediaStreamTrack ▪ 音声や動画のトラック ◦ MediaStream ▪ 複数(0個以上)のMediaStreamTrackを保持するオブジェクト ▪ カメラやマイクの場合、 navigator.mediaDevices.getUserMedia() で取得
  4. ライフサイクル Life-cycle:トラックの実体が存在しているかどうか • MediaStreamTrack.readyState: トラックの生存状態 ◦ “live” | “ended” ◦

    明示的なMediaStreamTrack.stop() の呼び出し,デバイスの喪失, ネットワークセッションの終了な どによりendedに遷移 ▪ 明示的にstop()した場合を除き、endedイベントが発火 ◦ 一度endedになったトラックは生き返らない
  5. メディアフロー Media Flow: トラックが伝送可能な状態かどうか 更に2軸で状態が存在する • MediaStreamTrack.muted: アプリケーション外の要因で停止中 ◦ Boolean

    ◦ 他のアプリケーションがデバイスを占有するなど ▪ mute/unmuteイベントが発火 • MediaStreamTrack.enabled: アプリケーション内の要因で停止中 ◦ Boolean ◦ アプリケーションがトラックを一時的に停止させたい時に制御するためのフラグ ▪ JavaScriptから設定可能 ▪ enabled = falseに設定してもデバイス自体は稼働し続ける
  6. ライフサイクルとメディアフロー: まとめ MediaStreamTrackの状態を表す3つのフラグ • readyState (read-only): トラックの生存状態…"live" | "ended" •

    muted (read-only): 一時的に使用不可能な状態(外的要因) • enabled: アプリケーション上での出力制御(内的要因) i.e. トラックが正常に再生されている状態 = { readyState: “live”, muted: false, enabled: true }
  7. ガベージコレクション https://www.w3.org/TR/mediacapture-streams/#garbage-collection > A MediaStreamTrack object MUST NOT be garbage

    collected if it is not ended and there are any event listeners registered for mute, unmute or ended events. Each source type can further refine the garbage collection rules as sources may never fire a particular event. • UAは“live”でイベントリスナが登録されている MediaStreamTrackをGCしない ◦ そもそも、普通のオブジェクトも参照が無くなって即座にGCされる訳ではないが... • MediaStreamTrackが生きている間はデバイスも稼働し続ける ◦ ユーザへのフィードバック(プライバシーインジケータ)も消えない ◦ 明示的にMediaStreamTrack.stop()を呼び出し停止することを推奨
  8. 振り返り: 「カメラ使用中」が消えない • コンポーネント内で完結しているため、あまり考えずに (GCされるので)放っておいてもいいと 思っていた ◦ → 一度カメラを使うとインジケータが消えないバグ ◦

    そもそもGCとデバイスリソースの解放は別物なので依存するべきではない ▪ GCされたらデバイスドライバをクローズするのはUA実装依存の挙動 • cleanup関数で track.stop() することで解決
  9. Permissions API https://www.w3.org/TR/permissions • メディアデバイスに限らず、アプリケーションが UAに強力な機能を要求するための API ◦ 位置情報, 通知,

    クリップボードの読み取りなど • Permissions.query(permissionDiscriptor)で現在のユーザの同意状況を確認できる • PermissionStatus: “prompt”| “granted” | “denied” の3値 ◦ 初期値は “prompt”
  10. 振り返り: 空のデバイス選択プルダウン • デバイス選択UIで enumerateDevices() だけ先に呼んでいた ◦ ユーザが同意前なのでラベルや IDが取得できなかった ▪

    エラーにならず匿名のオブジェクトが返るだけなので仕様をちゃんと読んでいないとハマる • getUserMedia() → enumerateDevices() に順序を変えて解決 { "deviceId":"", "kind":"videoinput", "label":"", "groupId":"" } { "deviceId":"...", "kind":"videoinput", "label":"FaceTime HDカメラ (B6DF:451A)", "groupId":"..." } ユーザの同意
  11. Constraints: 制約 https://www.w3.org/TR/mediacapture-streams/#dfn-constraint どのようなトラックをUAに要求するかはConstraintsとして指定する • 実際に適用できる値が選ばれる ◦ デバイスやその動作条件を決定するのに用いられる ▪ 指定値に合わせてUAが映像や音声を加工するものではない

    ◦ 可能な範囲でConstraintsに最も近い設定が適用される ※ resizeMode: “crop-and-scale”の場合はデバイス側の機能によりある程度柔軟にリサイズ可能 navigator.mediaDevices.getUserMedia({ "video": { "width": 1280, "facingMode": "user", }, });
  12. Capabilities: 能力 https://www.w3.org/TR/mediacapture-streams/#dfn-capabilities Capabilities = デバイス側の実際に持っている性能 • MediaDevices.getSupportedConstraints() ◦ UAがサポートするConstraintキーを取得

    • MediaStreamTrack.getCapabilities() ◦ デバイスが実際にサポートする Constraintキーを取得 ▪ e.g. 物理カメラ依存の高度な制約(露出, ISO, ホワイトバランスなど) ◦ 高度な制御を行う際の feature detectionなどに ※MediaStreamTrack.getCapabilities()がNewely AvailableなAPIであることに留意
  13. 二種類のConstraint: ideal / exact • ideal (default): できればこの値にしてほしい • exact:

    この値でなければならない ◦ getUserMedia(constraints) 時にconstraintを満たすデバイスが無い場合は例外 : OverconstrainedError をthrow →指定したcapabilityを備えるデバイスしか使いたくないのか、一番近いものを使いたいのかアプリケーションの 仕様と相談して選択 { "video": { "width": 1280, "height": 720, "frameRate": { "min": 24, "ideal": 30 }, "facingMode": { "exact": "user" } } }
  14. メディアの選択と制御: まとめ • Constraints = どんなsourceが欲しいか →UAがデバイス能力(Capabilities)と付き合わせる →最終的に適用される設定 (Settings)が決定する •

    ideal (デフォルト値) = できるだけ近いもの, exact = 満たせなければエラー ◦ デバイス選択などでは deviceIdをexactで指定する User Agent Application Media Device constraints: width: 1920 height: 1080 frameRate: 30 capabilities: width: 640-1280 height: 480-720 frameRate: 30-60 settings: width: 1280 height: 720 frameRate: 30 getUserMedia(constraints) MediaStream
  15. ImageCapture.grabFrame() • MediaStreamTrackから現在のフレームを bitmap形式で取り出す ◦ 加工(clip, resize, …etc)などの用途に向く ImageCapture.takePhoto(photoSettings) •

    blobで返してくれるので使いやすい • MediaStreamTrackに適用されているsettingsが適用されるとは限らない ◦ https://www.w3.org/TR/image-capture/#dom-imagecapture-takephoto >Devices MAY temporarily stop streaming data, reconfigure themselves with the appropriate photo settings, take the photo, and then resume streaming. In this case, the stopping and restarting of streaming SHOULD cause onmute and onunmute events to fire on the track in question. ▪ カメラデバイスが撮影モードに切り替わる場合がある • i.e. シャッターを切るのと同レベルの制御 ◦ デバイスによってはトラックが一時停止する可能性 ▪ 引数(PhotoSettings)でサイズなどを指定可能
  16. 余談: takePhoto() が思ってたより失敗する • Sentryを見ている感じだとtakePhoto() が失敗すること自体は割とよくあるっぽい ◦ muted中や起動直後、スマホの縦横切り替え時などは失敗するらしい ▪ orientationの変更、他のアプリによる占有、メモリの状況などの影響を受けるため

    • 特にAndroid端末は種類が多くデバイス依存の挙動でエッジケースを踏みやすい ▪ 再実行すると問題なく動いたりする ◦ ビデオから1フレーム抜くだけのgrabFrame()の方が安定性は高い(ように感じる) ▪ takePhoto()は長時間連続でのキャプチャには不向き?
  17. 今回得た教訓まとめ • トラックの状態 → readyState, muted, enabledプロパティ ◦ Safariはmuteを解除してくれないことがあるっぽい •

    使い終わったら明示的に MediaStreamTrack.stop() でデバイスを解放する • enumerateDevices() の前に getUserMedia() でユーザの同意を得る • デバイス選択はdeviceIdをexactで指定する • 大体の最初にハマるポイントは MDNに書いてあるし、仕様を読めば理解できる リンク • Media Capture and Streams (W3C Candidate Recommendation Draft) • Media Capture and Streams API (Media Stream) - Web APIs | MDN
  18. 時間が余ったときの話題 • <usermedia>要素(PEPC): https://github.com/WICG/PEPC/blob/main/usermedia_element.md ◦ 元々<permission>として提案されていたもの の一部 ◦ TPAC 2025で議題に上がっていた模様

    : https://docs.google.com/presentation/d/1sd5zEnvlXO5Sk3ENQorUUIQiRz65sv0KZKxDMMYHM3I/edit?slide=id.g37005560f20_2_0 #slide=id.g37005560f20_2_0 • Background Blur Effect: https://w3c.github.io/mediacapture-extensions/#background-blur-effect-status ◦ Constraintsの backgroundBlur フラグで、JSからカメラの背景ぼかしを操作 ▪ Media Capture and Streams Extensionsの中で提案されている仕様 ◦ Chromeで実験的な機能のフラグを有効にすれば試すことができる ▪ chrome://flags/#enable-experimental-web-platform-features