🧊

getUserMedia()で指定できるMediaTrackConstraintsのよもやま

Media Capture and Streams

この仕様書をだらーっと流し読みしたので、知らんかったことをメモ。
あとついでに気になった指定について調べたことも。

MediaTrackConstraints

navigator.mediaDevices.getUserMedia({
  video: {}, // <- この引数
  audio: {}, // <- この引数
}); 

ざっくり`true`指定しかしたことなかったけど、実は細かく指定ができる。
見ればわかるけど、定数のconst(ants)ではなく制約というconst(raints)と捉えるとイメージしやすいかも。

指定できるもの

仕様書より。

dictionary MediaTrackConstraintSet {
  ConstrainLong      width;
  ConstrainLong      height;
  ConstrainDouble    aspectRatio;
  ConstrainDouble    frameRate;
  ConstrainDOMString facingMode;
  ConstrainDouble    volume;
  ConstrainLong      sampleRate;
  ConstrainLong      sampleSize;
  ConstrainBoolean   echoCancellation;
  ConstrainBoolean   autoGainControl;
  ConstrainBoolean   noiseSuppression;
  ConstrainDouble    latency;
  ConstrainLong      channelCount;
  ConstrainDOMString deviceId;
  ConstrainDOMString groupId;
};

将来的にはこれらは拡張可能であるとか書いてあるけど、どういう風に拡張可能であるとかは書いてない。

あわせてよくみるキーワード

プロパティに対して値を直接指定する以外に、いろいろキーワードが出てくる。

  • ideal
  • min
  • max
  • exact
  • advanced

`ideal`は、直接指定するのと同じ意味。

{
  video: { width: 1600 }
}
// 同じ
{
  video: { width: { ideal: 1600 } }
}

`ideal`は理想的には・・なので、ブラウザが合致するメディアデバイスがないと見なした場合は無視される。

`min`と`max`と`exact`は、必ずこの制約を守れ!という指定。

{
  video: {
    width: { min: 320, ideal: 1280, max: 1920 },
    facingMode: { exact: 'environment' }
  }
}

この場合、`facingMode`に`exact`がツイてるので、リアカメラがない端末の場合は、Rejectされる。
`width`でも`height`でもなんでも、ブラウザの都合が悪ければRejectされちゃう。

advanced

最後に`advanced`。
これは値として定義するものではないので、少し毛色が違う。

これは`ideal`みたいなもんではあるが、適うなら適用してくれーっていうリストにできるやつ。
指定できる中身は一緒。

{
  video: {

    // 基本の制約をココにした上で

    advanced: [
      // このリスト内での値の直指定はexactと同じになる
      { aspectRatio: 1.3333333333 },
      // 同じものを指定しておくとフォールバックとして使える
      { aspectRatio: 1 },

      // widthだけOKでも、heightがNGなら、両方NGになる
      { width: 1920, height: 1280 },
    ]
  },
}

細かいところはこういう感じ。

基本の制約と違って、見合わない環境ならスルーされるだけでrejectされることはないのがポイント。

流れのまとめ

なので`getUserMedia()`した時の流れとしては、

  • `min` / `max` / `exact`の必須な指定をみる
    • 問題あればここでReject
  • `advanced`な指定をみる
    • 有効なものは反映
  • 余ってるのを`ideal`、直指定で埋める
  • 指定がないもの、指定されたものをブラウザがよしなにする
  • Resolve

みたいなイメージ。

最終的にはブラウザが決める

例えばこういう相容れ合い3つが指定された時どうなるか。

{
  video: {
    width: 1280,
    height: 720,
    aspectRatio: 1.5,
  }
}

1280x720ならアス比は1.777になるので、3つ同時には満たせない。

この場合の組合せは 3C2 なので3通り。

  • 1280x720
  • 1280とアス比1.5
  • 720とアス比1.5

どれになるかはブラウザ次第。
手元では1280x720になったので、できるだけ多くの制約を満たすようにしようとする・・?

Media Capture and Streams

この章さえ読んで理解できれば完璧!(あとはブラウザにその通りに実装されてれば

ブラウザは指定した通りに動くのか

制約の指定の仕方はわかったけど、実際に動くの?っていう肝心なとこ。

navigator.mediaDevices.getSupportedConstraints();

一応これすると、このブラウザでサポートされてるプロパティがわかるらしい。
なので、`getUserMedia()`でいきなり指定する前に、これで可否を確認してから使うべしと仕様書には書いてある。

たとえば width

{
  video: {
    width: 100,
  }
}

サイズ指定してない`video`要素に対してこれで取得したストリームを表示しても、指定通りちっさく表示されたのはChromeだけ。
どのブラウザも`getSupportedConstraints()`ではサポートしてる!って返ってくる。

Safariにいたってはいざ指定すると`ideal`なのにエラーにしてくるひどい。
FirefoxとEdgeは、指定を無視してきます。

Firefox

`width`の指定に関して、Firefoxはさらに謎の挙動を示す。
`width: 452`を指定すると320になるし、`width: 453`だと640になる・・というように、なんか独自の区切りを持ってそう。

てか、320 / 640 / 1280の3段階しかないような挙動になる。

あと普段は効かないのに、`mediaSource`で画面共有を指定したときだけ`frameRate`が設定できたりもする・・謎・・。

Safari

`width`の指定に関して、Safariは・・まったく謎の挙動を以下略。

`ideal`の指定だとしても、そのカメラの最大解像度以上の値以外は全てRejectしてくるし、最大解像度以上の値を入れてもその解像度にしかならないっぽい。

Edge

`width`の指定に関して、Edgeも以下略。

こいつも640 / 1280 みたいな区切りしか持って無さそう。
Rejectされない分、Firefoxと同じ挙動って感じ。

まとめ

`getSupportedConstraints()`でサポートしてるということと、`ideal`な制約を受け入れてくれることは、イコールではない。(ただしSafariはクソ)

うーん、でもこれ同じカメラやしFirefoxとEdgeもChromeみたくなってくれると嬉しいんやけどなー。

おまけ: googXxxx

さすがGoogleさん、仕様書にないけど指定できる謎プロパティをいっぱい持ってる。
各種サービスもどこにも記載されてないけど、風の噂で使えそうなやつ使ってる感がある・・。

なんやねんコレ・・ってちょっと気になったのでちょっと調べてみた。

Discord

Discord - Free Voice and Text Chat for Gamers

DiscordというWebRTCを使ったボイチャのサービスで使われてた例。

{
  advanced: [{
    echoCancellation: {
      exact: true
    }
  }, {
    googEchoCancellation: {
      exact: true
    }
  }, {
    googExperimentalEchoCancellation: {
      exact: true
    }
  }, {
    googNoiseSuppression: {
      exact: true
    }
  }, {
    googExperimentalNoiseSuppression: {
      exact: true
    }
  }, {
    googAutoGainControl: {
      exact: true
    }
  }, {
    googExperimentalAutoGainControl: {
      exact: true
    }
  }, {
    googHighpassFilter: {
      exact: true
    }
  }, {
    googTypingNoiseDetection: {
      exact: true
    }
  }, {
    googAudioMirroring: {
      exact: false
    }
  }, {
    deviceId: {
      exact: ["default"]
    }
  }]
}

Google ハングアウト

Google ハングアウト

なんか思ってた以上にいろいろ使ってたので表にまとめてみた。

まず`video`の。

名前
enableDtlsSrtp bool
enableRtpDataChannels bool
googCpuOveruseDetection bool
googCpuOveruseEncodeUsage bool
googCpuUnderuseThreshold number
googCpuOveruseThreshold number
googScreencastMinBitrate number
oogHighStartBitrate number
googPayloadPadding bool
googNoiseReduction bool

次に`audio`の。

名前
googEchoCancellation bool
googExperimentalEchoCancellation bool
googAutoGainControl bool
googExperimentalAutoGainControl bool
googNoiseSuppression bool
googHighpassFilter bool
googAudioMirroring bool
googExperimentalNoiseSuppression bool

ちなみにDiscordで使われてる`googTypingNoiseDetection`は使われてなかったので、もしかしたら存在しなかったりして・・。

と、なんか気になってブラウザのコード追ってみたら他にもいっぱい出てきて心が折れました。

Chromeは他にも見たこと無いのが色々あって、FirefoxSafariは仕様書通りで余計なプロパティは見てない風?

googXxxxは使うべきか

個人的には使わない。そもそもChromeでしか動かんし。

Chromiumのコードサーチでは色々見つかったけど、これが実際にChromeに載ってるかどうかの判断がつかなかったのもある。

たとえば`/src/third_party/webrtc/api/mediaconstraintsinterface.h`に、

This interface is being deprecated in Chrome, and may be removed from WebRTC too.
https://bugs.chromium.org/p/webrtc/issues/detail?id=5617

とか書いてあるし。
似たような指定が `/src/third_party/WebKit/Source/modules/mediastream/MediaConstraintsImpl.cpp`にもあるからそっちを使えって意味・・?

なんしかご利用の際は覚悟を持って自己責任でどうぞ。

まあこの業界で仕事してるなら、仕様書もブラウザの実装もどっちも半信半疑でかかるやろうし、全部自分で動かして確かめるまで信用しないはずよね!

おまけ: Constraintsその後

最初に`getUserMedia()`で指定したConstraintsはその後どうなるか。最終的にどうなったのか。

取得したMediaStreamに内包されるMediaStreamTrackからその後を確認(?)できる。

navigator.mediaDevices.getUserMedia({
  video: {
    aspectRatio: 1,
  }
})
  .then(stream => {
    const [track] = stream.getVideoTracks();

    // 1. どういう指定でgUMされたか
    track.getConstraints();
    // 2. このデバイスで指定できる値の範囲
    track.getCapabilities();
    // 3. 最終的にどんな指定になったか
    track.getSettings();
  })
  .catch(console.error);

というようにSpecには書いてあるけど、ChromeFirefoxSafariとEdgeでそれぞれ実装はバラバラっぽい・・。

  • Chromeでは[2]が空オブジェクトになる
  • Firefoxも[2]は実装されてなくてエラーになる
  • Safariは[1]が空オブジェクトになる

ちなみに[3]もブラウザによって、同じカメラを使ってても4者4様のオブジェクトが返ってきます(˘ω˘ )

おまけ: applyConstraints()

`getUserMedia()`で取得したMediaStreamに含まれるMediaStreamTrackは、後から`applyConstraints()`することで設定を変更できる(らしい)。
なので、まず最低限の内容で許可を取って、後で拡張するみたいにできる(らしい)。

// 最初は雑に
navigator.mediaDevices.getUserMedia({
  video: true
})
  .then(stream => {
    const [track] = stream.getVideoTracks();

    // これと
    console.log(track.getSettings());

    track.applyConstraints({
      aspectRatio: { exact: 1 },
    })
    .then(() => {
      // これの結果が変えられるってこと
      console.log(track.getSettings();
    });
  });

とりあえずデバイス取って、そのデバイスに設定できる値を確認して、それから理想の値をセット・・できそうに見えるけど、今のところEdgeでしかそれらしく動かない(˘ω˘ )冒頭のらしい連呼はこれのせい。

ちなみに、Edgeでも、`facingMode`みたいな違うデバイスに変えるのはダメっぽい。`width`とか`aspectRatio`とかなら変わる。

あと、

  • `applyConstraints()`の内容は、RTCPeerConnectionでつながった先には影響しない
    • 最初につなげた時のままなので、その場合は、再接続が必要
    • localのConsumer(`video`要素とか)は、即時で影響を受ける

と、仕様書は仰っておられた。

まあ、そのとおりに実装されてる保証はどこにもないんやけど・・。