実現したいこと
JavaScriptの非同期処理を用いてHowler.jsによる音源再生とアラート表示を順番に実行させたい
前提
HTML/JavaScript/CSSを使ってブラウザ上で動作する音感・記憶力ゲームを作成しています。
その中でゲーム終了時に
- 得点がハイスコアを上回っている場合にはlocalStorageを上書き
- Howler.jsを通し、ハイスコア更新の有無に応じて音源(3秒程度)を再生
- 3000ミリ秒(検証のため問題発生個所では倍に設定中)待機
- ハイスコア更新の有無に応じてアラートを表示
という処理を設定しているのですが、何故かiOS/iPadOSでのみ、ハイスコアを更新できなかった場合の音源が再生されず、またゲーム終了後に再度スタートした場合にボタン押下時のビープ音が一切再生されなくなるという問題が発生しています。
なお、テスト環境・結果は以下の通りです。
OS | ブラウザ | 結果 |
---|---|---|
Windows 11 | Chrome | ○ |
Android 13 | Chrome | ○ |
iOS/iPadOS 18 | Safari・chrome | × |
この問題の原因・解決策をご存じでしたら、教えていただけないでしょうか。
発生している問題・エラーメッセージ
発生している問題は下記の通りです。
- ゲーム終了時(ハイスコア非達成)に限り設定した音源が再生されない
- 2度目以降のゲーム開始時に出題・ボタン押下時のビープ音が再生されない
なおiPhone/iPadではコンソールを見ることができず、またWindows/Chrome環境では特にエラーは出力されていないようです。
該当のソースコード
JavaScript
1const notes = [261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88, 523.25]; // 各音階の周波数 2let sequence = []; 3let userInput = []; 4let score = 0; 5let isPlayerTurn = false; 6 7const context = new (window.AudioContext || window.webkitAudioContext)(); 8const buttons = document.querySelectorAll(".note-button"); 9const startBtn = document.getElementById("start-btn"); 10 11function sleep(ms) { 12 return new Promise(resolve => setTimeout(resolve, ms)); 13} 14 15function updateHighScore() { 16 let highscore = document.getElementById('highscore'); 17 let onkanHigh = 0; 18 if(localStorage.getItem('onkanHigh')) { 19 onkanHigh = localStorage.getItem('onkanHigh'); 20 } 21 highscore.innerText = `現在のハイスコア: ${onkanHigh}回`; 22} 23 24function playTone(freq, duration = 500) { 25 const osc = context.createOscillator(); 26 const gain = context.createGain(); 27 28 osc.type = "sine"; 29 osc.frequency.value = freq; 30 31 osc.connect(gain); 32 gain.connect(context.destination); 33 gain.gain.setValueAtTime(0.2, context.currentTime); 34 35 osc.start(); 36 osc.stop(context.currentTime + duration / 1000); 37} 38 39function flashButton(noteIndex) { 40 const btn = document.querySelector(`.note-button[data-note="${noteIndex + 1}"]`); 41 btn.classList.add("active"); 42 setTimeout(() => btn.classList.remove("active"), 300); 43} 44 45function playSequence() { 46 isPlayerTurn = false; 47 let i = 0; 48 const interval = setInterval(() => { 49 const note = sequence[i]; 50 playTone(notes[note]); 51 flashButton(note); 52 i++; 53 if (i >= sequence.length) { 54 clearInterval(interval); 55 isPlayerTurn = true; 56 } 57 }, 700); 58} 59 60function nextLevel() { 61 const nextNote = Math.floor(Math.random() * 8); 62 sequence.push(nextNote); 63 userInput = []; 64 playSequence(); 65} 66 67async function endGame() { 68 isPlayerTurn = false; 69 let onkanHigh = parseInt(localStorage.getItem("onkanHigh")) || 0; 70 // ハイスコア更新時 71 if (score > onkanHigh) { 72 localStorage.setItem("onkanHigh", score); 73 playSuccess(); 74 await sleep(3000); 75 alert(`おめでとうございます、${score}回連続正解でハイスコア更新です!!`); 76 } else { 77 playFailure(); 78 await sleep(6000); 79 alert(`残念、ゲームオーバーです。今回の得点は${score}回でした。`); 80 } 81 score = 0; 82 sequence = []; 83 updateHighScore(); 84 document.getElementById('info').classList.remove('hidden'); 85 document.getElementById('buttons-container').classList.add('hidden'); 86} 87 88function handleUserInput(noteIndex) { 89 if (!isPlayerTurn) return; 90 91 playTone(notes[noteIndex]); 92 flashButton(noteIndex); 93 userInput.push(noteIndex); 94 95 for (let i = 0; i < userInput.length; i++) { 96 if (userInput[i] !== sequence[i]) { 97 endGame(); 98 return; 99 } 100 } 101 102 if (userInput.length === sequence.length) { 103 score++; 104 setTimeout(nextLevel, 1000); 105 } 106} 107 108buttons.forEach((btn) => { 109 btn.addEventListener("click", () => { 110 const noteIndex = parseInt(btn.dataset.note) - 1; 111 handleUserInput(noteIndex); 112 }); 113}); 114 115document.addEventListener("keydown", (e) => { 116 if (!isPlayerTurn) return; 117 const key = parseInt(e.key); 118 if (key >= 1 && key <= 8) { 119 handleUserInput(key - 1); 120 } 121}); 122 123startBtn.addEventListener("click", () => { 124 score = 0; 125 sequence = []; 126 document.getElementById('info').classList.add('hidden'); 127 document.getElementById('buttons-container').classList.remove('hidden'); 128 nextLevel(); 129}); 130 131document.addEventListener("DOMContentLoaded", updateHighScore);
※サーバ上で実際に動作するページはこちらからご確認ください。 (アフィリエイト・広告などは含まれていません。
試したこと
iOS/iPadOSでは処理により時間がかかるのかと思い、待機時間を2倍まで伸ばしましたが効果はありませんでした。
以上です。何卒よろしくお願いいたします。

回答1件
あなたの回答
tips
プレビュー