fix: warm up AudioContext on first click, clean up code
This commit is contained in:
@@ -35,6 +35,27 @@
|
||||
EventsOn('timer:meeting_warning', handleMeetingWarning)
|
||||
EventsOn('timer:meeting_ended', handleMeetingEnded)
|
||||
EventsOn('timer:speaker_changed', handleSpeakerChanged)
|
||||
|
||||
// Warm up AudioContext on first user interaction
|
||||
const warmUpAudio = async () => {
|
||||
const ctx = getAudioContext()
|
||||
if (ctx.state === 'suspended') {
|
||||
await ctx.resume()
|
||||
}
|
||||
// Play silent sound to fully unlock audio
|
||||
const oscillator = ctx.createOscillator()
|
||||
const gainNode = ctx.createGain()
|
||||
oscillator.connect(gainNode)
|
||||
gainNode.connect(ctx.destination)
|
||||
gainNode.gain.value = 0 // Silent
|
||||
oscillator.start()
|
||||
oscillator.stop(ctx.currentTime + 0.001)
|
||||
// Remove listener after first interaction
|
||||
document.removeEventListener('click', warmUpAudio)
|
||||
document.removeEventListener('keydown', warmUpAudio)
|
||||
}
|
||||
document.addEventListener('click', warmUpAudio)
|
||||
document.addEventListener('keydown', warmUpAudio)
|
||||
})
|
||||
|
||||
async function loadSettings() {
|
||||
@@ -119,14 +140,9 @@
|
||||
return audioContext
|
||||
}
|
||||
|
||||
async function playBeep(frequency, duration, type = 'sine') {
|
||||
function playBeep(frequency, duration, type = 'sine') {
|
||||
try {
|
||||
const ctx = getAudioContext()
|
||||
if (ctx.state === 'suspended') {
|
||||
await ctx.resume()
|
||||
// Wait a frame for currentTime to update after resume
|
||||
await new Promise(resolve => setTimeout(resolve, 50))
|
||||
}
|
||||
const oscillator = ctx.createOscillator()
|
||||
const gainNode = ctx.createGain()
|
||||
|
||||
@@ -136,47 +152,36 @@
|
||||
oscillator.frequency.value = frequency
|
||||
oscillator.type = type
|
||||
|
||||
// Use small offset to ensure sound is scheduled in the future
|
||||
const startTime = ctx.currentTime + 0.01
|
||||
gainNode.gain.setValueAtTime(0.3, startTime)
|
||||
gainNode.gain.exponentialRampToValueAtTime(0.01, startTime + duration)
|
||||
gainNode.gain.setValueAtTime(0.3, ctx.currentTime)
|
||||
gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + duration)
|
||||
|
||||
oscillator.start(startTime)
|
||||
oscillator.stop(startTime + duration)
|
||||
oscillator.start(ctx.currentTime)
|
||||
oscillator.stop(ctx.currentTime + duration)
|
||||
} catch (e) {
|
||||
console.error('Failed to play sound:', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function playSound(name) {
|
||||
function playSound(name) {
|
||||
switch (name) {
|
||||
case 'warning':
|
||||
// Two short warning beeps
|
||||
await playBeep(880, 0.15)
|
||||
playBeep(880, 0.15)
|
||||
setTimeout(() => playBeep(880, 0.15), 200)
|
||||
break
|
||||
case 'timeup':
|
||||
// Descending tone sequence
|
||||
await playBeep(1200, 0.2)
|
||||
playBeep(1200, 0.2)
|
||||
setTimeout(() => playBeep(900, 0.2), 250)
|
||||
setTimeout(() => playBeep(600, 0.3), 500)
|
||||
break
|
||||
case 'meeting_end':
|
||||
// Final chime - three notes
|
||||
await playBeep(523, 0.2) // C5
|
||||
setTimeout(() => playBeep(659, 0.2), 200) // E5
|
||||
setTimeout(() => playBeep(784, 0.4), 400) // G5
|
||||
playBeep(523, 0.2)
|
||||
setTimeout(() => playBeep(659, 0.2), 200)
|
||||
setTimeout(() => playBeep(784, 0.4), 400)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
async function handleMeetingStarted() {
|
||||
// Pre-initialize AudioContext on user gesture (meeting start click)
|
||||
// This is required because AudioContext can only be resumed during user interaction
|
||||
const ctx = getAudioContext()
|
||||
if (ctx.state === 'suspended') {
|
||||
await ctx.resume()
|
||||
}
|
||||
function handleMeetingStarted() {
|
||||
meetingActive = true
|
||||
currentView = 'main'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user