mirror of
https://github.com/smittix/intercept.git
synced 2026-06-08 14:11:54 -07:00
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:
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user