ミュートを押してもBGMが止まらない5つ目の罠と、検索結果を切って直し漏れた話
以前、ブラウザゲームで音が鳴らない罠を4つ書きました。どれも、鳴るはずの音が鳴らない罠で、これで全部のつもりでした。5つ目があったので足します。今度は鳴らないのではなく、止まらないほうです。
前の4つを短く書いておきます。ブラウザで音を鳴らす方法には、HTMLのAudio要素と、音を細かく扱うWeb Audio APIの2つがあります。
- 1つ目は自動再生ブロック。ブラウザが、利用者の操作より前の再生を止める
- 2つ目はマナーモード。iPhoneをマナーモードにすると、Web Audio APIの音だけが消える
- 3つ目は解錠。iOSでは、Audio要素を最初のタップのなかで一度、無音のまま再生しておかないと、あとから鳴らせない。解錠はAudio要素1つずつに要る
- 4つ目は順番。マナーモードでも鳴らす、という設定をiOSに伝える一行を、AudioContext(Web Audio APIの音の出入口)を作るより先に実行しておかないと、設定が反映されない
OFFにしてもBGMが鳴りつづける
『四十秒の交差点』は、同じ一日を繰り返しながら選択肢で話が分かれていくノベルゲームです。この作品を自分のiPhoneで遊んでいて、画面の隅の♪ボタンを押しました。表示はOFFに変わり、効果音も止まります。BGMだけが鳴りつづけていました。
ボタンを押したときにBGMを消すコードは、こうなっていました。
if (bgmAudio) bgmAudio.volume = bgmMuted ? 0 : BGM_VOLUME;
ミュート中なら音量を0に、そうでなければ決めておいた音量に戻す。この音量の値は0.45です。何もおかしく見えません。パソコンのChromeでは、この書きかたでずっと正しく止まっていました。
iOSでは、ページから音量を変えられない
iOSのSafariでは、Audio要素の音量を決める volume プロパティに値を入れても、音の大きさが変わりません。エラーも警告も出ません。Appleの古い開発者向け資料にも、iOSでは音量はいつも利用者が本体のボタンで決めるもので、JavaScriptからは設定できない、と書いてあります。
音量は利用者が決めるもの、という理屈は分かります。ただ、値を入れても何も言われずに無視されるので、手元のパソコンで試しているあいだは気づけません。
効果音が止まっていたのは、BGMと鳴らしかたが違ったからです。このゲームの効果音はAudio要素を使わず、WAVファイルを読み込んでWeb Audio APIで鳴らしていました。BGMのAudio要素とは別の仕組みです。ミュート中は効果音を鳴らす処理ごと飛ばしていました。音量に0を入れて止めようとしていたのは、BGMだけです。同じボタンで片方だけが止まるので、押した本人にはボタンが壊れているようにしか見えません。
消音には専用の muted プロパティがあって、こちらならiOSでも音が止まります。音量を0にするのではなく、消音のフラグを立てる。ボタンを押したときの行は、こう直しました。
if (bgmAudio) bgmAudio.muted = bgmMuted;
症状から見分ける
この罠は、前の4つと症状の向きが逆なので、切り分けは早いです。
- iPhoneでゲームのミュートボタンを押しても、BGMが止まらない ならこれ。前の4つはどれも「鳴らない」症状で、止まらない形では出ません
- 効果音は止まるのにBGMだけ鳴りつづける ならこれ。効果音とBGMを別の仕組みで鳴らしていると、片方だけ残る
- そもそも最初から鳴らないなら別の罠。前の記事の自動再生ブロックか解錠のほうを見る
逆に言うと、パソコンだけで試しているうちは、この罠は出ません。iPhoneの実機で♪ボタンを一度押してみるのが、唯一の見つけかたでした。
4本直して、2本残っていた
ここからは、iOSとは関係のない、私の失敗の話です。当時このサイト(VANNAN GAMES)でBGMを鳴らすゲームは8本で、どれもBGMを鳴らすコードをゲームごとのファイルに持っていました。書きかたも似ていたので、1本にあったなら、ほかのゲームにも同じ行があるかもしれません。私はゲームのフォルダ全体で、volume = … ? 0 の形で音量に0を入れている行を検索しました。
『四十秒の交差点』を含めて4本のゲームが出てきたので、全部直して、これで終わりだと思っていました。
翌日、サイト全体を点検していて、まだ2本残っていることに気づきました。『四十秒の交差点』の交差点を舞台にしたワンボタンのアクション『よんじゅうびょう。』と、同じ世界の夜を描いたパズル『よぞらのかけら』です。名前は似ていますが、どれも別の作品です。
残っていた2本も、症状は『四十秒の交差点』と同じでした。『よんじゅうびょう。』は効果音の鳴らしかたも同じでした。『よぞらのかけら』だけは効果音のファイルを持たず、Web Audio APIで波形をその場で作って鳴らしていました。どちらもミュート中は効果音を鳴らす処理ごと飛ばしていたので、やはりBGMだけが残りました。
2本を見落とした原因は、間抜けなものでした。前の日に検索したとき、私は結果を画面に収めるために、出力を先頭の数行だけに切っていました。当たったファイルは6つあって、見えていたのは4つでした。ゲーム1本につき1つのファイルなので、切られた2つのゲームがそのまま残った、というだけの話です。
厄介なのは、出力を切ったせいの見落としが、「直した」という記録と一緒に残ることです。4本直したのは事実で、変更の記録にもそう書きました。でも、あとから読む自分は「全部直った」と受け取ります。次に同じ症状が出たとき、残った2本はもう疑う場所に入っていません。
いまは、こういう一括の書き換えをしたら、書き換えたあとにもう一度、出力を切らずに同じ検索をかけて、0件になるのを確かめるようにしています。件数だけなら、次の1行で出ます。
grep -rlE "volume = .*[mM]uted \? 0" games | wc -l
当たったファイルの数が出るので、0なら残っていません。直す前のコードでこの1行を打つと、6と出ます。作業のあとに見直す自分用の点検リストにも、この確認を足しました。
8本のうち、音量に0を入れていたのが6本で、残りの2本は最初から muted を使っていました。どのゲームも自分のファイルにBGMのコードを持っていたので、同じ直しを6本のゲームのファイルそれぞれに入れることになります。コードが1か所にまとまっていれば、直すのも1か所で済みました。コードを1か所にまとめるかどうかは別の判断です。そもそもの間違いは、6本のゲームに散らばった行を一括で書き換えたのに、全部を直せたかを確かめなかったことでした。
iOSの仕様の罠と、自分の手順の罠
この一件には性質のちがう2つの失敗が入っていました。iOSの仕様を知らなかったことと、直した範囲を確かめなかったこと。
前者は知識で、一度引っかかれば次はありません。この記事もそのために書いています。後者は手順で、知っていても疲れていれば同じことをします。だから覚えるのではなく、確認を手順に入れておくしかない。
ちなみに5つ目の罠を見つけたのは、自分で自分のゲームを遊んでいたときでした。4つ目までも全部そうです。テストで見つかったことは、いまのところ一度もありません。
後日談: BGMのコードを1つにまとめた
ここまでの出来事は8月21日と22日のことです。その1週間後の8月28日に、BGMを鳴らすコードを共通の1ファイルにまとめ、この8本ともそこから鳴らすようにしました。ミュートもそこで muted を使っているので、次に直すときは1か所で済みます。
参考にしたページ
- Safari HTML5 Audio and Video Guide: iOS-Specific Considerations の「Volume Control in JavaScript」の項。Appleの資料で、2012年に更新が止まっています