mirror of
https://github.com/smittix/intercept.git
synced 2026-07-11 02:58:12 -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; }
|
50% { opacity: 0.3; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from { opacity: 0; transform: translateX(-50%) translateY(-20px); }
|
||||||
|
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
.panel-content {
|
.panel-content {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: calc(100% - 45px);
|
height: calc(100% - 45px);
|
||||||
@@ -604,6 +609,12 @@
|
|||||||
<option value="emergency">Emergency Only</option>
|
<option value="emergency">Emergency Only</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</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()">
|
<button class="start-btn" id="startBtn" onclick="toggleTracking()">
|
||||||
START TRACKING
|
START TRACKING
|
||||||
</button>
|
</button>
|
||||||
@@ -650,6 +661,99 @@
|
|||||||
let eventSource = null;
|
let eventSource = null;
|
||||||
let isTracking = false;
|
let isTracking = false;
|
||||||
let currentFilter = 'all';
|
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
|
// Military aircraft detection
|
||||||
const MILITARY_RANGES = [
|
const MILITARY_RANGES = [
|
||||||
@@ -899,6 +1003,9 @@
|
|||||||
lastSeen: Date.now()
|
lastSeen: Date.now()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Check for military/emergency aircraft and alert
|
||||||
|
checkAndAlertAircraft(icao, aircraft[icao]);
|
||||||
|
|
||||||
// Queue marker update
|
// Queue marker update
|
||||||
if (data.lat && data.lon) {
|
if (data.lat && data.lon) {
|
||||||
pendingMarkerUpdates.add(icao);
|
pendingMarkerUpdates.add(icao);
|
||||||
|
|||||||
@@ -2608,6 +2608,16 @@
|
|||||||
to { background: #cc0000; }
|
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 */
|
||||||
.military-aircraft {
|
.military-aircraft {
|
||||||
border-left: 3px solid #556b2f;
|
border-left: 3px solid #556b2f;
|
||||||
@@ -3202,6 +3212,12 @@
|
|||||||
<option value="emergency">Emergency Only</option>
|
<option value="emergency">Emergency Only</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div class="info-text" style="margin-top: 8px; display: grid; grid-template-columns: auto auto; gap: 4px 8px; align-items: center;" id="adsbToolStatus">
|
<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 adsbEventSource = null;
|
||||||
let aircraftTrails = {}; // ICAO -> array of positions
|
let aircraftTrails = {}; // ICAO -> array of positions
|
||||||
let activeSquawkAlerts = {}; // Active emergency squawk alerts
|
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
|
// Emergency squawk codes
|
||||||
const SQUAWK_CODES = {
|
const SQUAWK_CODES = {
|
||||||
@@ -8033,6 +8137,8 @@
|
|||||||
};
|
};
|
||||||
adsbMsgCount++;
|
adsbMsgCount++;
|
||||||
pendingAircraftData.push(data);
|
pendingAircraftData.push(data);
|
||||||
|
// Check for military/emergency aircraft and alert
|
||||||
|
checkAndAlertAircraft(data.icao, adsbAircraft[data.icao]);
|
||||||
// Use batched update instead of immediate
|
// Use batched update instead of immediate
|
||||||
scheduleAircraftUIUpdate();
|
scheduleAircraftUIUpdate();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user