|
51 | 51 |
|
52 | 52 | <script> |
53 | 53 | let alertTimers = new Map(); // Store individual timers for each alert |
| 54 | + let alertSoundPlayed = new Set(); // Track which alerts have played their sound |
| 55 | +
|
| 56 | + // Initialize audio with multiple sound options |
| 57 | + const alertSounds = { |
| 58 | + chime: new Audio('data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAAAAAAA'), |
| 59 | + bell: new Audio('https://www.soundjay.com/misc/sounds/bell-ringing-05.wav'), |
| 60 | + notification: new Audio('https://notificationsounds.com/storage/sounds/file-sounds-1150-pristine.ogg') |
| 61 | + }; |
| 62 | +
|
| 63 | + // Use a simple chime sound encoded as base64 (fallback) |
| 64 | + const defaultAlertSound = new Audio('data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhBSyBzvLYiTcIG2m98OScTgwOUarm7blmFgU7k9n1unEiBC13yO/eizEIHWq+8+OWT' + |
| 65 | + 'AsLVqzn87JlHAU2jdDv0IkwByte0/PkkEQHFGHC7eqfUgwKVqjj8LtyKAUqgNDy0ok3CBpnuuztm08MAlCs5O+1aB4GN5DY9Lh0KAUuhM/y3Ys3Bhplt+3snEwMAVWq4+6zYhwGOJLW8cx2KAUkdMrv3Y1AAhVdtOnqp1YPBlGj5vCuXh8GLYXJs+OKOAgZaLvt559NEAxQp+PwtmMcBjiP1/PMeS0GI3fH8N+RQAoUXrTp66hVFApGnt/yvmwhBCuBzvLZiTgIG2m98OScTgwOUqnl7blmFgU7k9n1unEiBC14yu7eizEIHWq+8+OWTAsLVqzn87Jl'); |
| 66 | + |
| 67 | + // Try to use Web Audio API for better sound |
| 68 | + function playAlertSound() { |
| 69 | + try { |
| 70 | + // Create a simple beep using Web Audio API |
| 71 | + const audioContext = new (window.AudioContext || window.webkitAudioContext)(); |
| 72 | + |
| 73 | + // Create oscillators for a pleasant chime |
| 74 | + const now = audioContext.currentTime; |
| 75 | + |
| 76 | + // First tone |
| 77 | + const osc1 = audioContext.createOscillator(); |
| 78 | + const gainNode1 = audioContext.createGain(); |
| 79 | + osc1.connect(gainNode1); |
| 80 | + gainNode1.connect(audioContext.destination); |
| 81 | + osc1.frequency.value = 800; |
| 82 | + gainNode1.gain.setValueAtTime(0.3, now); |
| 83 | + gainNode1.gain.exponentialRampToValueAtTime(0.01, now + 0.5); |
| 84 | + osc1.start(now); |
| 85 | + osc1.stop(now + 0.5); |
| 86 | + |
| 87 | + // Second tone |
| 88 | + const osc2 = audioContext.createOscillator(); |
| 89 | + const gainNode2 = audioContext.createGain(); |
| 90 | + osc2.connect(gainNode2); |
| 91 | + gainNode2.connect(audioContext.destination); |
| 92 | + osc2.frequency.value = 1000; |
| 93 | + gainNode2.gain.setValueAtTime(0.3, now + 0.2); |
| 94 | + gainNode2.gain.exponentialRampToValueAtTime(0.01, now + 0.7); |
| 95 | + osc2.start(now + 0.2); |
| 96 | + osc2.stop(now + 0.7); |
| 97 | + |
| 98 | + // Third tone |
| 99 | + const osc3 = audioContext.createOscillator(); |
| 100 | + const gainNode3 = audioContext.createGain(); |
| 101 | + osc3.connect(gainNode3); |
| 102 | + gainNode3.connect(audioContext.destination); |
| 103 | + osc3.frequency.value = 1200; |
| 104 | + gainNode3.gain.setValueAtTime(0.3, now + 0.4); |
| 105 | + gainNode3.gain.exponentialRampToValueAtTime(0.01, now + 0.9); |
| 106 | + osc3.start(now + 0.4); |
| 107 | + osc3.stop(now + 0.9); |
| 108 | + |
| 109 | + } catch (error) { |
| 110 | + console.error('Error playing sound:', error); |
| 111 | + // Fallback to default audio element |
| 112 | + try { |
| 113 | + defaultAlertSound.play().catch(e => console.error('Fallback audio failed:', e)); |
| 114 | + } catch (e) { |
| 115 | + console.error('All audio methods failed:', e); |
| 116 | + } |
| 117 | + } |
| 118 | + } |
54 | 119 |
|
55 | 120 | // Load alerts from API |
56 | 121 | async function loadAlerts() { |
|
66 | 131 | // Clear all timers |
67 | 132 | alertTimers.forEach(timer => clearInterval(timer)); |
68 | 133 | alertTimers.clear(); |
| 134 | + alertSoundPlayed.clear(); |
69 | 135 | container.innerHTML = '<div class="text-center py-5 text-muted"><i class="fas fa-inbox fa-3x mb-3"></i><br><h5>No active alerts</h5><p>Create your first alert above!</p></div>'; |
70 | 136 | return; |
71 | 137 | } |
|
76 | 142 | if (!currentAlertIds.has(alertId)) { |
77 | 143 | clearInterval(timer); |
78 | 144 | alertTimers.delete(alertId); |
| 145 | + alertSoundPlayed.delete(alertId); |
79 | 146 | } |
80 | 147 | }); |
81 | 148 | |
|
178 | 245 | |
179 | 246 | // Check if time's up |
180 | 247 | if (remainingSeconds <= 0) { |
| 248 | + // Play sound only once per alert |
| 249 | + if (!alertSoundPlayed.has(alert.id)) { |
| 250 | + alertSoundPlayed.add(alert.id); |
| 251 | + playAlertSound(); |
| 252 | + } |
| 253 | + |
181 | 254 | // Update styling when expired |
182 | 255 | const alertDiv = document.getElementById(`alert-${alert.id}`); |
183 | 256 | if (alertDiv && !alertDiv.innerHTML.includes("TIME'S UP!")) { |
|
256 | 329 | clearInterval(alertTimers.get(id)); |
257 | 330 | alertTimers.delete(id); |
258 | 331 | } |
| 332 | + alertSoundPlayed.delete(id); |
259 | 333 | loadAlerts(); |
260 | 334 | } |
261 | 335 | } catch (error) { |
|
0 commit comments