2018年12月19日水曜日

HTML5 video / audioがiOSデバイスの消音スイッチの状態に従うようにする方法 (UIWebView編 / WKWebView編)

いろいろ調べていたのですが、遥か大昔に私が書いたブログ記事が長い年月を経て今や大間違いになっていたのでここに訂正させていただきたいと思います。

今回ご紹介するのはUIWebViewまたはWKWebViewで表示しているHTML5のvideo要素やaudio要素が音声を出すときに、iOSデバイスについている消音スイッチ (ミュートスイッチ, mute switch, silent switch) の状態を無視してしまう問題を解決する方法です。相変わらず紹介内容がとてもニッチですね。

ちなみにSafariはデフォルトでちゃんと消音スイッチの状態を反映してくれるので、SafariでYouTubeの動画を見ててもいきなり音が流れ出すことはありません。素晴らしい!というわけで我々のアプリもぜひそのようにしたいと思います。

消音スイッチの挙動について

まず基本的なおさらいとして、消音スイッチがどのような挙動を示すかについてこちらにまとめます。
  • 各プロセスごとに、AVAudioSessionが消音スイッチに対してどのように振る舞うかを定義している。
  • 具体的には、AVAudioSession.Categoryの値に応じて挙動が変化する。ドキュメントにも明記されている。
    • AVAudioSession.Category.ambientやAVAudioSession.Category.soloAmbientを指定すると、消音スイッチがONのときは音が出ないようになる。
    • 逆にAVAudioSession.Category.playbackを指定すると消音スイッチを一切無視するようになる。
  • 消音スイッチの現在の物理的な状態を取得するAPIは一切ない。Private APIで以前は可能だったが、穴が塞がれたためその方法も利用できない。そもそもアプリが提出時の審査で蹴られる。当然JS経由でHTMLコンテンツ上から状態を取得するのも不可能。

解決方法・UIWebView編

UIWebViewはWebKit1を利用しており、したがってUIWebViewのエンジンは我々のアプリ内のプロセスで動作します。そこでUIWebViewのインスタンスを生成するより先に、先述の通りAVAudioSessionのcategoryを変更して消音スイッチの状態を反映してやるように示してやるとうまくいきます。
try? AVAudioSession.sharedInstance().setCategory(.soloAmbient, mode: .default, options: [])
try? AVAudioSession.sharedInstance().setActive(true)
ただしご存知の通り、すでにUIWebViewはdeprecated扱いとなっており、WKWebViewへの移行が推奨されています。そこでWKWebViewでも同様の方法が使えないかやってみましょう。

解決方法・WKWebView編

ありません。

繰り返しますが、ありません。WebKit2の設計上のバグです。

今後修正される可能性もほぼ間違いなくありません。諦めてください。

一応、順を追って説明します。
  1. WKWebViewはWebKit2で実装されています。
  2. WebKit2は我々のアプリ内のプロセスで動作するのではなく、「共用の」WebCoreプロセス上で動作し、その結果が我々のアプリ内に転送されてくるような実装になっています。
  3. 「共用の」WebCoreプロセスは(これは推測ですが、audioやvideoを最大限に活かすため)AVAudioSession.Category.playbackとAVAudioSession.Mode.moviePlaybackで動作するように設定されている用に見えます。したがって消音スイッチは無視されます。
  4. 最初にご説明したとおり、AVAudioSessionによる消音スイッチに対する振る舞いは「プロセス単位」で制御されており、これは遥か大昔のiOS 2.0どころか下手するとNeXTSTEPの時代からCarbonレイヤーで決められている挙動だったりします。要するに今からの変更はほぼ不可能です。
  5. もうおわかりだと思いますが、「共用の」WebCoreプロセスの挙動を我々個別のアプリがAVAudioSession経由で勝手に変更することは不可能ですし、これを可能にするのはiOSとWebKitの設計上実質不可能なため、問題は解決されません。
    1. WebCoreプロセスを各個別のアプリごとに立ち上げて対応しろよ!と思うかもしれませんが、WebCoreプロセスは近年AppleのOSに組み込まれている特権階級モードで動作するプロセスとなっており(そのためメモリに無制限なアクセスが可能で、JSをJITコンパイルして高速に動作させる事が可能)、最近のセキュリティとバッテリーライフにうるさいAppleがそれを個別のアプリごとに立ち上げさせるなどということは現経営陣が全滅しない限りありえないと断言できます。
    2. 共用のWebCoreプロセスが使用するAVAudioSession.Categoryを変えてしまえばいいだろ!とも思いますが、実はAVAudioSession.Categoryをplaybackに指定しない限りバックグラウンドで音楽を流し続けることができません。したがって今度はWebViewで音楽プレイヤーを作ったりPicture in Pictureで動画を流し続けるアプリが全滅するため、これも不可能です。
    3. だったら消音スイッチの状態を自分で調べて自分で動画をmuteにすればよいのでは、と思いますが、これも先述の通り消音スイッチの状態を調べる方法は一切ないため、一律でmuteにしてしまうなどの乱暴な方法を用いない限り対応できません。
やばいですね☆

iOS開発でエラーコードを調べるときはOSStatus.comを使おう

久しぶりに書く価値のあるネタが見つかったのでご紹介します。

iOSのフレームワークがNSErrorを返してきたとき、そのcodeが何を意味するのかを調べる必要が出てくることが往々にして発生します。新し目のフレームワークは比較的簡単に調べられるようにまとまっているのですが、厄介なのがOSStatusなどの大昔から存在するエラーコードの場合です。

NSError コードの調べ方 こちらのブログ記事で紹介されている通りに一つ一つヘッダファイル内を探す方法でもいいのですが、もはや平成の終わりが目の前に迫っている2018年の年の瀬にもなってこのような原始的な方法を使っているようではよろしくありません。

そこで OSStatus.com の出番です。こちらの検索欄にcodeをコピペして検索ボタンを押すだけであらゆるエラーの詳細が一発で帰ってきます。実際にやってみましょう。


一発ですね。素晴らしい。


名前での検索もバッチリです。

偶然見つけたのですが、はてブもほとんどついてないし、ネット上で紹介されている記事も全く見つからなかったので書いてみました。便利です!

2018年12月1日土曜日

2018年のしめくくり

pyspa Advent Calendar 2018 12/1です。

早いもので、あっというまに2018年も終わりを迎えそうな時期になってまいりました。皆様ご無沙汰しております、akisuteです。今年もまとめだけ書いて終わりにしたいと思います。

今年やったこと

JavaとかSwiftとかKotlinとかNode.jsとかReact Nativeとかやってました。Node.jsはなかなかいいですね。食わず嫌いで最近まで手を出しておらず、昔はお手軽になにか作りたいときはもっぱらPythonだったのですが、今ではついついNode.jsを使うようになってしまいました。特にTypeScriptが大変気に入っております。

細かいところですとチームのドキュメンテーションの問題を考えている時間が増えてきました。というのも最近ようやく気づきを得たのですが、プログラマの中にはドキュメンテーションができるタイプの人と一切できないタイプの人の二種類がいるように思えます。後者のタイプの人がドキュメンテーションが不得手な理由を自分なりに考えていますが、
  • 「自分が理解しているので、相手も理解しているだろう、相手も理解できているだろう」という考えがあり、そのため本来必要であるドキュメントが不用と判断されて欠落してしまう。
  • 思考が直接コードで行われているために、わざわざ日本語なり英語なりの言語に変換されない。またはその変換コストがその人にとって高い。
  • 間違った情報ということは認識しているのだが、既存のWikiやコードコメントを書き換えるのを遠慮してしまう。
これらが原因なのかどうか、いずれも定かではありません。テンプレートを整備してみたり、OpenAPI+ReDocのような解決策に頼ってみたりしているのですが、なかなか成果が上がっておらず。ドキュメントが不足しがちな人をあえてドキュメントが必要な状況におく(普段とは違うプロジェクトをアサインして、ドキュメントを読まなければにっちもさっちもいかない状態にすることで、どのような情報が欠落すると自分が困るのかを理解できる)とかはいいアイデアなのでは?と思っています。ドキュメンテーションの得意な人と組ませて、得意な人がレビュープロセスなどで書き加えるというアイデアなどもなんとなく回りそうな気はしています。なかなか難しいです。

今年のGotY

Forza Horizon 4以外にありえないですね。これより優れたクルマゲームは地球上に存在しないと断言していいレベルで素晴らしいです。RDR2は正直雰囲気と世界観のためにゲーム性を犠牲にしすぎている気がします。1ほど楽しめませんでした。どうやら私もおっさんになったようです。

今年のマイブーム

Forza Horizon 4のせいか急にクルマがマイブームになってきました。と言っても別に今すぐクルマを持ちたいとは思っていません。なにせ現実世界のクルマはカネがかかり、乗っても楽しく乗れる場所はなく、ただひたすら渋滞とマナーの悪いドライバーに憤慨し、事故とスピード違反に怯えながらほそぼそとアクセルを踏まなければならないような代物です。全く執着するに値しません。
しかしながらゲームの中となると、たかだか1万円で何百車種もの好きなクルマに好きなだけ乗ることができ、キレイな風景や楽しいコースを好き放題乗り回し、渋滞もなく、マナーの悪いドライバーは遠慮なくポルシェ・カイエンターボで粉々に踏み潰すことができ、事故ったところで現実世界の私はかすり傷一つ受けることもないため何のリスクもなくクルマの限界に挑戦できます。まさにクルマの楽しい箇所だけをすべて味わい尽くすことができるのです。うーん、仮想世界って素晴らしいですね。

まとめ

今年も平和でいい年が過ごせて100%満足しています。よかったです。
来年も平和で良い年でありますように。