Androidアプリ中でWebサイトを表示するためのViewとして、WebViewというものが存在します。
今回はそのWebViewでHTMLやらJavaScriptやらを埋め込んだAndroidアプリにおいて、画面をタップした時に音を鳴らす方法についてです。
コード全体はこちらにあります(Androidのコードを全て上げると多いので、変更・追加したコードのみ上げています)。
WebViewでHTMLを表示する
音を出す前に、まずWebViewでHTMLを表示させましょう。
New Project → Empty Views Activityを選択し、適当な名前を付けて適当は場所に保存してプロジェクトを作成します。
そして必要なものを追加・書き換えていきます。
今回は画面全体にHTMLを表示するアプリとします。
activity_main.xml
多分元々はTextViewが使用されているので、そこをWebViewに変更。画面いっぱい。
1 2 3 4 |
<WebView android:id="@+id/webView" android:layout_width="match_parent" android:layout_height="match_parent"></WebView> |
MainActivity.kt
そのままだとJavaScriptを使用できないので、使えるよう設定をします。
なおAndroid StudioだとWarningが出るのですが、この設定をするときはXSSには注意しましょう。今回は関係ないのでスルーします。
1 2 3 4 5 6 7 8 9 10 |
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val webView = findViewById<WebView>(R.id.webView) // JavaScriptの使用を許可する webView.settings.javaScriptEnabled = true webView.loadUrl("file:///android_asset/html/page1.html") } |
表示用のHTMLなどの準備。
assetsフォルダを追加して、その中にhtml, css, scriptといったファイルを作成して管理することにします。
File → New → Folder → Assets Folder を選択してassetsフォルダを作成します。
そして各ファイルを格納します。
今回のwebページは、画面中央にボタンがあり、そのボタンを押すとページが相互に切り替わるというものです。
ファイルは以下のように配置。
HTML(page1.html)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<!DOCTYPE HTML> <html lang="ja"> <head> <meta charset="utf-8"> <title>ページ1</title> <link rel="stylesheet" href="../css/theme.css"> <script src="../script/page1.js"></script> </head> <body> <div class="pageContainer"> <button id="movePage2Button">ページ2</button> </div> </body> </html> |
CSS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
.pageContainer{ width: 100vw; height: 100vh; display: flex; justify-content: center; align-items: center; } button{ width: 150px; height: 120px; font-size: 28px; color: white; font-weight: bold; border: none; } #movePage2Button{ background-color: darkcyan; } #movePage1Button{ background-color: darkgoldenrod; } |
JavaScript(page1.js)
1 2 3 4 5 6 |
window.addEventListener('load', ()=>{ // ボタンが押されたら画面遷移 document.getElementById("movePage2Button").addEventListener("click", function(){ location.href = '../html/page2.html'; }) }); |
2は1とid名などを変えただけなので省略。
できたアプリをエミュレータで実行するとこんな感じでページ(HTML)が切り替わります。
では、画面タップ時に音を鳴らしていきましょう!
Android側で実装
Androidで音を鳴らす方法として、SoundPoolというものがあります。
短い音声、効果音などを鳴らすのに向いているライブラリで、今回はこちらを使用します。
再生するための音声ファイルを追加。
resフォルダを右クリック → New → Directoryを選択し、rawディレクトリを作成します。
作成したrawフォルダ内に音声ファイルを置きます。
今回使用する音声ファイルは、『効果音ラボ』様よりお借りしました。
『カーソル移動12』という音声なのですが、Androidのリソースファイルは英数字じゃないと怒られるので、cursor.mp3という名前に変更して使用しています。
音声を鳴らす処理をMainActivity.ktに書いていきます。
webView.loadUrl()の下に以下の処理を追加。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
val audioAttributes = AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build() val soundPool = SoundPool.Builder().setAudioAttributes(audioAttributes).setMaxStreams(1).build() // 音声ファイルをここで指定 val soundOne = soundPool.load(this, R.raw.cursor, 1) // WebViewタッチで音を再生 webView.setOnTouchListener { _, _ -> soundPool.play(soundOne, 1.0f, 1.0f, 0, 0, 1.0f) false } |
これで画面タップ時に音が再生されるようになりました!
以下は余談。
画面タップ時を拾うイベントとして、onTouchEventとdispatchTouchEventというものもあります。
それを使用して音声を鳴らそうとすると、以下のようなコードになります。
1 2 3 4 5 6 7 8 9 10 11 |
lateinit var soundPool: SoundPool var soundOne = 0 override fun onCreate(savedInstanceState: Bundle?){ // 省略 } override fun dispatchTouchEvent(event: MotionEvent?): Boolean { soundPool.play(soundOne, 1.0f, 1.0f, 0, 0, 1.0f) return false } |
これを実行すると画面をタップして音声は鳴るのですが、画面内の要素(ボタンとか)をクリックしても処理が行われません。
今回の場合は、ボタンを押してもページを切り替えることができません。
また、onTouchEventの場合はそもそも音声を鳴らすことができません。
おそらくdispatchTouchEventの場合は画面タップにイベントが吸われてボタンが押せず、onTouchEventの場合はボタンタッチにイベントが吸われて音が鳴らないのだと思います。
よってページのイベントを起こしつつ、画面タップで音声を鳴らすには、setOnTouchListenerを使用する必要がありそうです。
HTML + JavaScriptで実装
HTMLには、文書中に音声コンテンツを埋め込むためのaudioタグなるものがあります。
今回はそちらを使用します。
なお、この記事は『AndroidのWebViewでタップ音を出す方法』としていますが、HTMLとJavaScriptの実装なので、AndroidもWebViewも関係なく、普通のwebサイトでタップ音を出す場合も同じ実装で実現できます。
HTMLファイルにaudioタグを追加。
htmlやscriptフォルダと同階層にsoundフォルダを作り、音声ファイルをそこに入れます。srcでその音声ファイルを参照。
1 2 3 4 5 6 |
<div class="pageContainer"> <button id="movePage2Button">ページ2</button> </div> <!-- 以下を追加 -- > <!-- 音声は『効果音ラボ』 https://soundeffect-lab.info/sound/button/ より借用 --> <audio id="audio" src="../sound/cursor.mp3"></audio> |
JavaScriptに、画面クリック時に音声を再生する処理を追加。
1 2 3 4 5 6 |
// 画面クリックで音を鳴らす document.body.addEventListener("click", function(){ let audio = document.getElementById("audio"); audio.load(); audio.play(); }); |
これで画面をタップすると音声が再生されます!
でもいくつか気になる点があるので修正していきましょう。
音声再生後に画面遷移させる
今回のページは、ボタンをクリックすると別のページに遷移するというものです。
画面遷移を伴う所為か、ボタンを押した場合には今のままだとクリック音が鳴りません。
おそらく音声再生前に別のHTMLに移っちゃって聞こえていないのだと思われます。
そのため、音声再生を待ってから、画面遷移すればちゃんとクリック音が聞こえそうです。
以下はその実装例です。
ボタンクリック時には画面を遷移せずフラグだけ立て、音声再生が終わった際にフラグが立っていれば画面を遷移する、といった感じです。
ボタンを押した際に音がするものの、音声再生を待ってから画面が変わるので、音声の長さによってはすごいラグになります。この場合は短い音声ファイルにする必要がありそうです。
1 2 3 4 5 6 7 8 9 10 11 12 |
let isChangePage = false; // 音声再生が終わってから画面遷移させる document.getElementById("audio").addEventListener("ended", function(){ if(isChangePage){ location.href = '../html/page2.html'; } }); document.getElementById("movePage2Button").addEventListener("click", function(){ isChangePage = true; }) |
エラーの出力をしない
上記の実装で音声を鳴らしていると、エラーがconsoleに出力されることがあります。
例えば、ラベルをクリックしてもチェックできるチェックボックスのような場合。
1 2 |
<input type="checkbox" id="checkbox"> <label for="checkbox" id="checkboxLabel">チェックボックス</label> |
↓こういうやつです。
ラベルの部分をクリックすると、Uncaught (in promise) DOMException: The play() request was interrupted by a new load request. といったエラーが出力されます。
この内容を訳すと、『捕捉されなかった(プロミス中の)DOMException: play()リクエストは新しいload()リクエストによって中断されました。』となります。つまりplayしようとしたのに、別途loadされたので音声再生できなかった!というわけです。
これがどういう状況か確認すると、ラベルをクリックしたときにlabelタグでクリックイベントを拾ってloadとplayが実行され、inputタグでもクリックイベントを拾ってloadとplayが実行され、前のplayと後のloadがぶつかっているようでした。
対処方法としては以下。
エラー発生を拾って、そして何もしません。
これにより、エラーを出力せずに済みます。
例外を拾って何もしないのは良くないと思いますが、音声が1回再生されるという機能的には問題ない事、発生する箇所と理由が分かることからこれで問題ないと思います。
必要なら適宜ログを出せばいいかと思います。
1 2 3 4 5 |
audio.load(); // audio.play();を変更する audio.play().catch(function(){ // イベント伝搬による多重処理エラーを出力させない }); |
あるいは、クリックイベントをinputタグとlabelタグから2重に呼ばなければいいので、labelクリック時にイベントを伝搬させないことでも対処できます。
でもこの方法だと、該当箇所全部にこの処理が必要です。実装めんどくさい 処理が増えるのは嬉しくないので、前者の方が個人的にはいいかなあと思ってます。
1 |
document.getElementById("checkboxLabel").addEventListener("click", (e)=>e.stopPropagation()); |
iframeを使用している場合の実装
今回は関係のない話ですが、iframeを使ってページを切り替えるケースがあるかと思います。
各jsファイルに前述の音声再生イベントを追加してもいいのですが、ページが多いと手間です。
iframeタグを置いているHTMLファイルのjsファイルに以下を記載することで、全画面にタップ音処理を追加することができます。
なお、iframeが読み込まれた後でないと、iframeDocumentがnullになってイベントを追加できないので、ご注意ください(iframe.addEventListener(“load”, …) の中に書くといい)。
1 2 3 4 5 6 7 |
let iframe = document.getElementById("iframe"); let iframeDocument = iframe.contentDocument || iframe.contentWindow.document; iframeDocument.body.addEventListener("click", function(){ let audio = document.getElementById("audio"); audio.load(); audio.play(); }); |
コメント