先日、公開したインターバルタイマーをアップグレードしました。
『タイマー完了時にアラームを鳴らす』という機能が追加したのですが、Chrome拡張で音声を鳴らすにはちょっとした工夫が必要だったので今回解説したいと思います。
Chrome拡張のBackgroundからサウンドを鳴らす方法
createElementで一時的に要素を作って鳴らす
結論を言うと、createElementでaudio要素を作って音声を鳴らします。
Chrome拡張のbackground.jsでは表示するHTMLは存在しないのですが、内部的にはHTMLの要素が作られていて、そこでbackground.jsが読み込まれて動いている仕組みとなっています。
background.js で console.log(document) した結果
そのため上記のようにcreateElementでaudio要素を埋め込みサウンドを鳴らす事ができます。
起動時にサウンドを鳴らすのはNG
backgroundで起動時にplayを実行すると以下のようなエラーが出力されます。
Uncaught (in promise) DOMException: play() failed because the user didn’t interact with the document first.
ユーザーが最初にドキュメントを操作しなかったため、play()が失敗しました。
いきなり音声を鳴らすのはChrome側で弾いているためです(迷惑なので納得)
これに関してはエラーログの通り「まずユーザーからのアクションが発生しないと音を鳴らせません」
(ユーザーのアクションってなんやねん)ってなるのですが、要は起動時に勝手にサウンドが流れなければいいのです。
今回作ったタイマーのサウンドの場合は、
- popupのスタートボタンを押す
- popupからbackgroundに対してリクエストが発生する
- setIntervelが始まる
- setIntervelの処理の中でタイマーが切れたらアラーム
ということをやっています。
サウンドの呼び出しがsetIntervelでも、ユーザーのアクション(クリックなど)を取得しなくても起動時にサウンドが流れなければOKのようです。
コード自体は以下で公開しているのでどうしてもわからない場合はご参考にどうぞ!
他の方法はなぜうまく行かないのか
ここからは理解を深めるために今回の問題の原因について解説していきます。
問題さえ解決すれば良い方は以下は読み飛ばして頂いて構いません。
Chrome拡張の構成
まずはChrome拡張の構成を理解しておく必要があります。
(左の図はブラウザのつもりです。。)
Chrome拡張は大きく3つの領域によって構成されています。
- 常に処理が動いているbackground
- ユーザーが使いたい時に処理が動くpopup
- 特定のページで実行されるcontent scripts
これらは独立して処理が動いています。
具体的には、popupやbackgroundからcontent scriptで持っているデータは取れないし、popupに仕込んだリソースをbackgroundで操作する事もできません。
正確にはリクエストを送り合ってデータのやり取りをすることが出来ますが、説明がややこしくなるので基本的にそういうものだと思ってください。
popup.jsはpopup表示中しか動かない
popup.jsの処理はpopupが表示されている時しか実行されません。
またpopup.jsはpopupが表示される毎に処理が初めから読み込まれます。
そのためタイマーをpopupで作っても、開く度にリセットされた状態になってしまいサウンドどころかタイマー自体が上手く動きません。
backgroundからpopupに対してサウンドを鳴らすようにリクエスト飛ばすことも検討できますが、popupが閉じてしまっていればリクエストを待つことも出来ないのでpopupはそもそも使えません。
‘Audio’ is not defined. の影響でChrome拡張でAudioが使えない
通常であれば Audio クラスを使ってサウンドを鳴らすのが自然なのですが、自分の環境ではAudioが使えませんでした(node.js環境ではAudioは使えないようなのでそれに関係するかもです※確認はしてない)
必要だったのがaudioだったのでcreateElementでなんとか対処できましたが、他に通常であれば使えるはずの関数がChrome拡張の中では使えないということは十分にあるので注意しましょう。
ContentScript はChrome拡張のサウンドファイルを参照できない
「全てのページのHTMLにaudioタグを入れればサウンドが流れるのでは?」と考えて試した結果がこちら。そもそも全てのページにaudioタグを入れ込むのはかなりの暴挙なので、結果的にエラーで終わってよかったです。。w
前述したChrome拡張の構成を参考にしていただきたいのですが、content scriptとは特定のページに対してJavaScriptを埋め込むことが出来るChrome拡張の機能です。
JavaScirptを動せるためaudioタグも埋め込むことも出来るのですが、サウンドファイルを読み込もうとするとcontent scriptが適用されたサイトの中からファイルを探してしまい、Chrome拡張が鳴らしたいサウンドを取得することが出来ません。
Chrome拡張の3つの領域を意識して実装しよう
今回はサウンドをテーマにChrome拡張の3つの領域に触れました。
- popupは表示時に処理が毎回リセットされること
- backgroundは使えない関数があること
- content scriptは用意したリソースを取得できないこと
このようにそれぞれに特徴があるので、目的の機能はどの領域で実装するのが適切なのかを意識して実装してもらいたいと思います。
ここまで読んで頂きありがとうございました!
コメント