xor

二兎を得るか、一兎をも得ざるか

【Web API】iOSのブラウザってPermission APIも使えないんですね【修正対応】

Permissions APIに限らない話ではありますが、Web APIのルートであるNavigatorのプロパティに、使おうとするAPIが存在しているかをチェックする習慣をつけましょう、が結論です。

if ("permissions" in navigator) {
  // パーミッション使える
} else {
  // 使えない
}

developer.mozilla.org

みなさんはWeb APIを使う前に、まず上記のMDN web docsのNavigator一覧を読み、実験的機能であることを示す水色のビーカーアイコンがついていないことを確認しましょう。

〜 終 〜

以下メモです。


SafariでPermissions APIが動かなくて気づいた

github.com

先日作ったオンラインチャットができるWebアプリですが、初回アクセス時のカメラとマイクの使用前に「Permissions API」を使っています。

developer.mozilla.org

これを使うと、カメラやマイクのアクセス許可のポップアップが出る前にこちらから独自ポップアップを出して「悪さをするわけじゃないから大丈夫ですよ、許可ボタン押してくださいネ」と前もってユーザーに伝えておくことができます。

f:id:ukkz:20200709132416p:plain

作っているときはChromiumベースのブラウザでテストしており、何も問題はなかったんですが、世の中って思った以上にiOSユーザー多いんですよね。困る。そういえばWebBluetoothとかも使えないし一体なんやねん

初期実装

Vue.jsベースです。
ページ状態がmountedのとき、カメラとマイクそれぞれに対してパーミッションが「許可」となっているか確認しています。
だいたいの場合で初回アクセス以後は allowed となるので、引っかかることはありません。

mounted: async function() {
  // カメラとマイクのパーミッションを確認
  if (await this.cam_allowed() && await this.mic_allowed()) {
    // 許可済みの場合はそのままgetUserMedia
    await this.startLocalDevice();
  } else {
    // 未許可または拒否の場合はとりあえずダイアログを出す
    // このダイアログ内のボタンを明示的に押してonChangeLocalDeviceを発火させる
    this.permission_dialog_open = true;
  }
},

methods: {
  // カメラ・マイクそれぞれのPermissionがgrantedかどうか
  cam_allowed: async function() {
    const cam = await navigator.permissions.query({name: 'camera'});
    // granted/prompt/denied
    return (cam.state == 'granted');
  },
  mic_allowed: async function() {
    const mic = await navigator.permissions.query({name: 'microphone'});
    // granted/prompt/denied
    return (mic.state == 'granted');
  },
},

これをiOSのブラウザや、macOSでもSafariで開いたりなどすると、 navigator.permissionsundefined なので、途中で止まってしまったり謎挙動になったりしてしまいます。

プロパティチェックをいれる

developer.mozilla.org

上記例ではGeolocation APIですが、navigator直下に対象のAPIを示すプロパティが定義されているか調べればよいです。
Permissions APIで、今回の場合だと、以下の1行を各メソッドの一番上に入れればOKでした。

if (!("permissions" in navigator)) return true;

ここのコードでは、未定義であれば(パーミッション非対応ブラウザであれば)独自ポップアップが出ずに、直接「ブラウザにカメラ使用を許可しますか?」のダイアログが表示されるようになります。
UXとしては若干落ちるかもしれませんが、動くに越したことはないですからね。

一方で、組み込みJavascriptにおいてカメラやマイクを使うとき、ユーザーのクリックなどによる操作をもとに getUserMedia を発火させねばならない、という場合があったような気がします。
そういったときは、常時ユーザーイベントのフックに基づいて動作するような実装にしないといけないかもしれません。

というか現時点で最新のiOS13のWKWebViewでもgetUserMediaは実装されてないらしいのでそもそも無駄足が過ぎる気がする