diff --git a/dashcam.html b/dashcam.html index 43feaa1..8cd4a06 100644 --- a/dashcam.html +++ b/dashcam.html @@ -1,59 +1,46 @@
@@ -62,45 +49,27 @@
🎥
Tap START to begin
-
Camera · GPS · Motion sensors
+
Camera + location permissions required
-
--:--:--
---
+
+
+ REC + 00:00 +
+
--
MPH
MAX --
-
-
0.00
-
G-FORCE
-
-
- -
-
TILT
-
P: --°
-
R: --°
-
- -
-
-
BATT--
-
CHARGING--
-
CONN--
-
NETWORK--
-
MEM--
-
CORES--
-
-
-
@@ -118,8 +87,8 @@
+ -
@@ -130,16 +99,15 @@ let stream = null, facingMode = 'environment'; let watchId = null, clockInterval = null; let useMph = true, maxSpeed = 0; let lastPos = null, totalDist = 0; -let motionEnabled = false; - -const G = 9.80665; +let mediaRecorder = null, recordedChunks = [], recInterval = null, recSeconds = 0; +let isRecording = false; function cvt(ms) { return useMph ? ms * 2.23694 : ms * 3.6; } function haversine(a, b) { - const R = 6371000, toR = Math.PI / 180; - const dLat = (b.lat - a.lat) * toR, dLng = (b.lng - a.lng) * toR; - const x = Math.sin(dLat/2)**2 + Math.cos(a.lat*toR)*Math.cos(b.lat*toR)*Math.sin(dLng/2)**2; + const R = 6371000, r = Math.PI / 180; + const dLat = (b.lat - a.lat) * r, dLng = (b.lng - a.lng) * r; + const x = Math.sin(dLat/2)**2 + Math.cos(a.lat*r)*Math.cos(b.lat*r)*Math.sin(dLng/2)**2; return R * 2 * Math.atan2(Math.sqrt(x), Math.sqrt(1-x)); } @@ -158,9 +126,7 @@ function updateClock() { document.getElementById('dateline').textContent = days[n.getDay()]+' '+pad(n.getDate())+' '+months[n.getMonth()]+' '+n.getFullYear(); } -function setStatus(msg) { - document.getElementById('status-bar').textContent = msg; -} +function setStatus(msg) { document.getElementById('status-bar').textContent = msg; } function startGPS() { if (!navigator.geolocation) { setStatus('GPS unavailable'); return; } @@ -189,105 +155,76 @@ function startGPS() { }, { enableHighAccuracy: true, maximumAge: 1000, timeout: 15000 }); } -function startStaticSensors() { - document.getElementById('sensor-bar').style.display = 'flex'; - document.getElementById('cores-val').textContent = navigator.hardwareConcurrency || '--'; - - if ('deviceMemory' in navigator) { - document.getElementById('mem-val').textContent = navigator.deviceMemory + 'GB'; - } else { - document.getElementById('mem-val').textContent = 'n/a'; - } - - if ('connection' in navigator || 'mozConnection' in navigator || 'webkitConnection' in navigator) { - const conn = navigator.connection || navigator.mozConnection || navigator.webkitConnection; - document.getElementById('net-val').textContent = conn.effectiveType ? conn.effectiveType.toUpperCase() : '--'; - document.getElementById('net-type').textContent = conn.type ? conn.type : '--'; - conn.addEventListener('change', () => { - document.getElementById('net-val').textContent = conn.effectiveType ? conn.effectiveType.toUpperCase() : '--'; - document.getElementById('net-type').textContent = conn.type ? conn.type : '--'; - }); - } else { - document.getElementById('net-val').textContent = 'n/a'; - document.getElementById('net-type').textContent = ''; - } - - if ('getBattery' in navigator) { - navigator.getBattery().then(bat => { - function updateBat() { - document.getElementById('batt-val').textContent = Math.round(bat.level * 100) + '%'; - document.getElementById('batt-chg').textContent = bat.charging ? 'YES ⚡' : 'NO'; - document.getElementById('batt-chg').innerHTML = bat.charging ? 'YES ⚡' : 'NO'; - } - updateBat(); - bat.addEventListener('levelchange', updateBat); - bat.addEventListener('chargingchange', updateBat); - }); - } else { - document.getElementById('batt-val').textContent = 'n/a'; - } +function getBestMimeType() { + const types = [ + 'video/webm;codecs=vp9', + 'video/webm;codecs=vp8', + 'video/webm', + 'video/mp4' + ]; + for (const t of types) { if (MediaRecorder.isTypeSupported(t)) return t; } + return ''; } -function handleMotion(e) { - const acc = e.accelerationIncludingGravity; - if (!acc) return; - const x = acc.x || 0, y = acc.y || 0, z = acc.z || 0; - const total = Math.sqrt(x*x + y*y + z*z) / G; - const lateral = Math.abs(x) / G; - const longitudinal = Math.abs(y) / G; - const gDisplay = Math.max(lateral, longitudinal).toFixed(2); - - document.getElementById('gforce-val').textContent = gDisplay; - const pct = Math.min(parseFloat(gDisplay) / 2 * 100, 100); - const bar = document.getElementById('gforce-bar'); - bar.style.width = pct + '%'; - bar.style.background = pct > 75 ? '#e24b4a' : pct > 40 ? '#BA7517' : '#1D9E75'; - - const pitch = Math.atan2(y, z) * 180 / Math.PI; - const roll = Math.atan2(x, z) * 180 / Math.PI; - document.getElementById('tilt-pitch').textContent = 'PITCH: ' + pitch.toFixed(1) + '\xb0'; - document.getElementById('tilt-roll').textContent = 'ROLL: ' + roll.toFixed(1) + '\xb0'; +function saveRecording() { + if (recordedChunks.length === 0) { setStatus('Nothing to save'); return; } + const mime = getBestMimeType(); + const ext = mime.includes('mp4') ? 'mp4' : 'webm'; + const blob = new Blob(recordedChunks, { type: mime }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + const now = new Date(); + a.href = url; + a.download = 'dashcam_' + now.getFullYear() + pad(now.getMonth()+1) + pad(now.getDate()) + '_' + pad(now.getHours()) + pad(now.getMinutes()) + pad(now.getSeconds()) + '.' + ext; + a.click(); + setTimeout(() => URL.revokeObjectURL(url), 5000); + recordedChunks = []; + setStatus('Saved!'); } -function handleOrientation(e) { - const pitch = e.beta !== null ? e.beta.toFixed(1) : '--'; - const roll = e.gamma !== null ? e.gamma.toFixed(1) : '--'; - if (!motionEnabled) { - document.getElementById('tilt-pitch').textContent = 'PITCH: ' + pitch + '\xb0'; - document.getElementById('tilt-roll').textContent = 'ROLL: ' + roll + '\xb0'; - } -} +function toggleRecord() { + if (!stream) return; + const btn = document.getElementById('rec-btn'); + const ind = document.getElementById('rec-indicator'); -async function requestMotion() { - const btn = document.getElementById('motion-btn'); - if (typeof DeviceMotionEvent !== 'undefined' && typeof DeviceMotionEvent.requestPermission === 'function') { + if (!isRecording) { + recordedChunks = []; + const mime = getBestMimeType(); try { - const res = await DeviceMotionEvent.requestPermission(); - if (res === 'granted') { - window.addEventListener('devicemotion', handleMotion); - motionEnabled = true; - btn.textContent = 'MOTION ON'; - btn.classList.add('warn'); - document.getElementById('gforce-wrap').style.display = 'block'; - document.getElementById('tilt-wrap').style.display = 'block'; - } - } catch(e) { setStatus('Motion denied'); } - } else if (typeof DeviceMotionEvent !== 'undefined') { - window.addEventListener('devicemotion', handleMotion); - window.addEventListener('deviceorientation', handleOrientation); - motionEnabled = true; - btn.textContent = 'MOTION ON'; - btn.classList.add('warn'); - document.getElementById('gforce-wrap').style.display = 'block'; - document.getElementById('tilt-wrap').style.display = 'block'; + mediaRecorder = new MediaRecorder(stream, mime ? { mimeType: mime } : {}); + } catch(e) { + mediaRecorder = new MediaRecorder(stream); + } + mediaRecorder.ondataavailable = e => { if (e.data && e.data.size > 0) recordedChunks.push(e.data); }; + mediaRecorder.onstop = saveRecording; + mediaRecorder.start(1000); + isRecording = true; + recSeconds = 0; + ind.style.display = 'flex'; + btn.textContent = '▮▮ SAVE'; + btn.innerHTML = '▮▮ SAVE'; + btn.classList.add('danger'); + recInterval = setInterval(() => { + recSeconds++; + const m = pad(Math.floor(recSeconds/60)), s = pad(recSeconds%60); + document.getElementById('rec-timer').textContent = m+':'+s; + }, 1000); + setStatus('Recording...'); } else { - setStatus('Motion sensors not available'); + mediaRecorder.stop(); + isRecording = false; + clearInterval(recInterval); + ind.style.display = 'none'; + btn.innerHTML = '● RECORD'; + btn.classList.remove('danger'); + setStatus('Saving...'); } } async function startDashcam() { const btn = document.getElementById('start-btn'); btn.disabled = true; btn.textContent = '...'; + if (isRecording) toggleRecord(); try { if (stream) stream.getTracks().forEach(t => t.stop()); stream = await navigator.mediaDevices.getUserMedia({ @@ -297,11 +234,11 @@ async function startDashcam() { document.getElementById('placeholder').style.display = 'none'; document.getElementById('top-left').style.display = 'flex'; document.getElementById('flip-btn').disabled = false; + document.getElementById('rec-btn').disabled = false; btn.textContent = 'RESTART'; btn.disabled = false; if (watchId !== null) { navigator.geolocation.clearWatch(watchId); watchId = null; } maxSpeed = 0; totalDist = 0; lastPos = null; startGPS(); - startStaticSensors(); if (clockInterval) clearInterval(clockInterval); clockInterval = setInterval(updateClock, 1000); updateClock();