Add audio alerts for military aircraft and emergency squawk codes

Features:
- Audio alert plays when military aircraft or emergency squawk code
  (7500/7600/7700) is first detected
- Different tones: two-tone urgent alert for emergencies, single tone
  for military aircraft
- Visual banner notification appears at top of screen for 5 seconds
- Toggle checkbox to enable/disable audio alerts
- Alerts only trigger once per aircraft to avoid spam
- Implemented on both ADS-B tab and full-screen dashboard

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
James Smith
2025-12-30 17:31:15 +00:00
parent fb7a3860ea
commit d1b10d55bd
2 changed files with 213 additions and 0 deletions
+107
View File
@@ -198,6 +198,11 @@
50% { opacity: 0.3; }
}
@keyframes slideDown {
from { opacity: 0; transform: translateX(-50%) translateY(-20px); }
to { opacity: 1; transform: translateX(-50%) translateY(0); }
}
.panel-content {
padding: 0;
height: calc(100% - 45px);
@@ -604,6 +609,12 @@
<option value="emergency">Emergency Only</option>
</select>
</div>
<div style="padding: 0 15px 15px;">
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 12px; color: var(--text-primary);">
<input type="checkbox" id="alertToggle" checked onchange="toggleAlerts()" style="accent-color: var(--accent-green);">
🔔 Audio Alerts (Military/Emergency)
</label>
</div>
<button class="start-btn" id="startBtn" onclick="toggleTracking()">
START TRACKING
</button>
@@ -650,6 +661,99 @@
let eventSource = null;
let isTracking = false;
let currentFilter = 'all';
let alertedAircraft = {}; // Track aircraft that have already triggered alerts
let alertsEnabled = true; // Toggle for audio alerts
// Audio alert system using Web Audio API
let audioContext = null;
function getAudioContext() {
if (!audioContext) {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
return audioContext;
}
function playAlertSound(type) {
if (!alertsEnabled) return;
try {
const ctx = getAudioContext();
const oscillator = ctx.createOscillator();
const gainNode = ctx.createGain();
oscillator.connect(gainNode);
gainNode.connect(ctx.destination);
if (type === 'emergency') {
// Urgent two-tone alert for emergencies
oscillator.frequency.setValueAtTime(880, ctx.currentTime);
oscillator.frequency.setValueAtTime(660, ctx.currentTime + 0.15);
oscillator.frequency.setValueAtTime(880, ctx.currentTime + 0.3);
gainNode.gain.setValueAtTime(0.3, ctx.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.5);
oscillator.start(ctx.currentTime);
oscillator.stop(ctx.currentTime + 0.5);
} else if (type === 'military') {
// Single tone for military
oscillator.frequency.setValueAtTime(523, ctx.currentTime);
gainNode.gain.setValueAtTime(0.2, ctx.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.3);
oscillator.start(ctx.currentTime);
oscillator.stop(ctx.currentTime + 0.3);
}
} catch (e) {
console.warn('Audio alert failed:', e);
}
}
function checkAndAlertAircraft(icao, ac) {
// Skip if already alerted for this aircraft
if (alertedAircraft[icao]) return;
const militaryInfo = isMilitaryAircraft(icao, ac.callsign);
const squawkInfo = checkSquawkCode(ac);
if (squawkInfo) {
alertedAircraft[icao] = 'emergency';
playAlertSound('emergency');
showAlertBanner(`⚠️ EMERGENCY: ${squawkInfo.name} - ${ac.callsign || icao}`, '#ff0000');
} else if (militaryInfo.military) {
alertedAircraft[icao] = 'military';
playAlertSound('military');
showAlertBanner(`🎖️ MILITARY: ${ac.callsign || icao}${militaryInfo.country ? ' (' + militaryInfo.country + ')' : ''}`, '#556b2f');
}
}
function showAlertBanner(message, color) {
const banner = document.createElement('div');
banner.style.cssText = `
position: fixed;
top: 80px;
left: 50%;
transform: translateX(-50%);
background: ${color};
color: white;
padding: 12px 24px;
border-radius: 8px;
font-weight: bold;
font-size: 14px;
z-index: 10000;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
animation: slideDown 0.3s ease-out;
`;
banner.textContent = message;
document.body.appendChild(banner);
// Auto-remove after 5 seconds
setTimeout(() => {
banner.style.opacity = '0';
banner.style.transition = 'opacity 0.3s';
setTimeout(() => banner.remove(), 300);
}, 5000);
}
function toggleAlerts() {
alertsEnabled = document.getElementById('alertToggle').checked;
}
// Military aircraft detection
const MILITARY_RANGES = [
@@ -899,6 +1003,9 @@
lastSeen: Date.now()
};
// Check for military/emergency aircraft and alert
checkAndAlertAircraft(icao, aircraft[icao]);
// Queue marker update
if (data.lat && data.lon) {
pendingMarkerUpdates.add(icao);
+106
View File
@@ -2608,6 +2608,16 @@
to { background: #cc0000; }
}
@keyframes slideDown {
from { opacity: 0; transform: translateX(-50%) translateY(-20px); }
to { opacity: 1; transform: translateX(-50%) translateY(0); }
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
/* Military Aircraft */
.military-aircraft {
border-left: 3px solid #556b2f;
@@ -3202,6 +3212,12 @@
<option value="emergency">Emergency Only</option>
</select>
</div>
<div class="form-group" style="margin-top: 10px;">
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
<input type="checkbox" id="adsbAlertToggle" checked onchange="adsbAlertsEnabled = this.checked">
🔔 Audio Alerts (Military/Emergency)
</label>
</div>
</div>
<div class="info-text" style="margin-top: 8px; display: grid; grid-template-columns: auto auto; gap: 4px 8px; align-items: center;" id="adsbToolStatus">
@@ -3813,6 +3829,94 @@
let adsbEventSource = null;
let aircraftTrails = {}; // ICAO -> array of positions
let activeSquawkAlerts = {}; // Active emergency squawk alerts
let alertedAircraft = {}; // Track aircraft that have already triggered alerts
let adsbAlertsEnabled = true; // Toggle for audio alerts
// Audio alert system using Web Audio API
let audioContext = null;
function getAudioContext() {
if (!audioContext) {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
return audioContext;
}
function playAlertSound(type) {
if (!adsbAlertsEnabled) return;
try {
const ctx = getAudioContext();
const oscillator = ctx.createOscillator();
const gainNode = ctx.createGain();
oscillator.connect(gainNode);
gainNode.connect(ctx.destination);
if (type === 'emergency') {
// Urgent two-tone alert for emergencies
oscillator.frequency.setValueAtTime(880, ctx.currentTime);
oscillator.frequency.setValueAtTime(660, ctx.currentTime + 0.15);
oscillator.frequency.setValueAtTime(880, ctx.currentTime + 0.3);
gainNode.gain.setValueAtTime(0.3, ctx.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.5);
oscillator.start(ctx.currentTime);
oscillator.stop(ctx.currentTime + 0.5);
} else if (type === 'military') {
// Single tone for military
oscillator.frequency.setValueAtTime(523, ctx.currentTime);
gainNode.gain.setValueAtTime(0.2, ctx.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.3);
oscillator.start(ctx.currentTime);
oscillator.stop(ctx.currentTime + 0.3);
}
} catch (e) {
console.warn('Audio alert failed:', e);
}
}
function checkAndAlertAircraft(icao, aircraft) {
// Skip if already alerted for this aircraft
if (alertedAircraft[icao]) return;
const militaryInfo = isMilitaryAircraft(icao, aircraft.callsign);
const squawkInfo = checkSquawkCode(aircraft);
if (squawkInfo) {
alertedAircraft[icao] = 'emergency';
playAlertSound('emergency');
showAlertBanner(`⚠️ EMERGENCY: ${squawkInfo.name} - ${aircraft.callsign || icao}`, squawkInfo.color);
} else if (militaryInfo.military) {
alertedAircraft[icao] = 'military';
playAlertSound('military');
showAlertBanner(`🎖️ MILITARY: ${aircraft.callsign || icao}${militaryInfo.country ? ' (' + militaryInfo.country + ')' : ''}`, '#556b2f');
}
}
function showAlertBanner(message, color) {
const banner = document.createElement('div');
banner.style.cssText = `
position: fixed;
top: 80px;
left: 50%;
transform: translateX(-50%);
background: ${color};
color: white;
padding: 12px 24px;
border-radius: 8px;
font-weight: bold;
font-size: 14px;
z-index: 10000;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
animation: slideDown 0.3s ease-out;
`;
banner.textContent = message;
document.body.appendChild(banner);
// Auto-remove after 5 seconds
setTimeout(() => {
banner.style.animation = 'fadeOut 0.3s ease-out forwards';
setTimeout(() => banner.remove(), 300);
}, 5000);
}
// Emergency squawk codes
const SQUAWK_CODES = {
@@ -8033,6 +8137,8 @@
};
adsbMsgCount++;
pendingAircraftData.push(data);
// Check for military/emergency aircraft and alert
checkAndAlertAircraft(data.icao, adsbAircraft[data.icao]);
// Use batched update instead of immediate
scheduleAircraftUIUpdate();
}