@@ -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();