Added recording option
This commit is contained in:
235
dashcam.html
235
dashcam.html
@@ -1,59 +1,46 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
#app { font-family: var(--font-mono); background: #000; border-radius: var(--border-radius-lg); overflow: hidden; position: relative; width: 100%; height: 56vw; min-height: 320px; max-height: 92vh; }
|
#app { font-family: var(--font-mono); background: #000; border-radius: var(--border-radius-lg); overflow: hidden; position: relative; width: 100%; height: 56vw; min-height: 300px; max-height: 92vh; }
|
||||||
#app.fullscreen { border-radius: 0; height: 100vh; width: 100vw; position: fixed; inset: 0; z-index: 9999; }
|
#app.fullscreen { border-radius: 0; height: 100vh; width: 100vw; position: fixed; inset: 0; z-index: 9999; }
|
||||||
#video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
|
#video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
|
||||||
#hud { position: absolute; inset: 0; pointer-events: none; }
|
#hud { position: absolute; inset: 0; pointer-events: none; }
|
||||||
|
|
||||||
/* TOP LEFT — clock + date */
|
|
||||||
#top-left { position: absolute; top: 12px; left: 12px; display: none; flex-direction: column; gap: 3px; }
|
#top-left { position: absolute; top: 12px; left: 12px; display: none; flex-direction: column; gap: 3px; }
|
||||||
#clock { font-size: 18px; color: #fff; background: rgba(0,0,0,0.45); border-radius: 8px; padding: 4px 10px; letter-spacing: 1px; }
|
#clock { font-size: 16px; color: #fff; background: rgba(0,0,0,0.45); border-radius: 8px; padding: 4px 10px; letter-spacing: 1px; }
|
||||||
#dateline { font-size: 10px; color: #aaa; background: rgba(0,0,0,0.45); border-radius: 8px; padding: 3px 10px; letter-spacing: 0.5px; }
|
#dateline { font-size: 10px; color: #aaa; background: rgba(0,0,0,0.45); border-radius: 8px; padding: 3px 10px; letter-spacing: 0.5px; }
|
||||||
|
|
||||||
/* TOP RIGHT — speed */
|
#rec-indicator { position: absolute; top: 12px; left: 50%; transform: translateX(-50%); background: rgba(180,0,0,0.75); border-radius: 20px; padding: 4px 12px; color: #fff; font-size: 11px; letter-spacing: 1px; display: none; align-items: center; gap: 6px; white-space: nowrap; }
|
||||||
|
#rec-dot { width: 7px; height: 7px; border-radius: 50%; background: #fff; animation: blink 1s infinite; }
|
||||||
|
#rec-timer { font-size: 11px; }
|
||||||
|
@keyframes blink { 0%,100%{opacity:1} 50%{opacity:0.2} }
|
||||||
|
|
||||||
#speed-display { position: absolute; top: 12px; right: 12px; text-align: center; background: rgba(0,0,0,0.45); border-radius: 12px; padding: 6px 14px; }
|
#speed-display { position: absolute; top: 12px; right: 12px; text-align: center; background: rgba(0,0,0,0.45); border-radius: 12px; padding: 6px 14px; }
|
||||||
#speed-val { font-size: 44px; font-weight: 700; color: #fff; line-height: 1; }
|
#speed-val { font-size: 44px; font-weight: 700; color: #fff; line-height: 1; }
|
||||||
#speed-unit { font-size: 10px; color: #aaa; letter-spacing: 1.5px; margin-top: 1px; }
|
#speed-unit { font-size: 10px; color: #aaa; letter-spacing: 1.5px; margin-top: 1px; }
|
||||||
#max-speed { font-size: 10px; color: #666; margin-top: 2px; }
|
#max-speed { font-size: 10px; color: #666; margin-top: 2px; }
|
||||||
|
|
||||||
/* G-FORCE meter — centre top */
|
#gps-bar { position: absolute; bottom: 48px; left: 0; right: 0; background: rgba(0,0,0,0.5); padding: 5px 12px; display: none; flex-direction: column; gap: 3px; }
|
||||||
#gforce-wrap { position: absolute; top: 12px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.45); border-radius: 10px; padding: 5px 12px; text-align: center; display: none; }
|
|
||||||
#gforce-val { font-size: 20px; font-weight: 700; color: #fff; line-height: 1; }
|
|
||||||
#gforce-label { font-size: 9px; color: #aaa; letter-spacing: 1px; margin-top: 2px; }
|
|
||||||
#gforce-bar-wrap { width: 80px; height: 4px; background: rgba(255,255,255,0.15); border-radius: 2px; margin: 4px auto 0; overflow: hidden; }
|
|
||||||
#gforce-bar { height: 100%; width: 0%; background: #1D9E75; border-radius: 2px; transition: width 0.15s, background 0.15s; }
|
|
||||||
|
|
||||||
/* TILT indicator */
|
|
||||||
#tilt-wrap { position: absolute; top: 100px; right: 12px; background: rgba(0,0,0,0.45); border-radius: 10px; padding: 5px 10px; text-align: center; display: none; }
|
|
||||||
#tilt-pitch { font-size: 11px; color: #ccc; }
|
|
||||||
#tilt-roll { font-size: 11px; color: #ccc; }
|
|
||||||
#tilt-label { font-size: 9px; color: #666; letter-spacing: 0.5px; margin-bottom: 3px; }
|
|
||||||
|
|
||||||
/* BOTTOM DATA STRIPS */
|
|
||||||
#gps-bar { position: absolute; bottom: 52px; left: 0; right: 0; background: rgba(0,0,0,0.5); padding: 5px 12px; display: none; flex-direction: column; gap: 3px; }
|
|
||||||
#sensor-bar { position: absolute; bottom: 88px; left: 0; right: 0; background: rgba(0,0,0,0.4); padding: 4px 12px; display: none; flex-wrap: wrap; gap: 10px; }
|
|
||||||
|
|
||||||
.data-row { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
|
.data-row { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
|
||||||
.di { font-size: 10px; color: #ccc; display: flex; gap: 4px; align-items: center; }
|
.di { font-size: 10px; color: #ccc; display: flex; gap: 4px; align-items: center; }
|
||||||
.dl { color: #666; font-size: 9px; letter-spacing: 0.4px; }
|
.dl { color: #666; font-size: 9px; letter-spacing: 0.4px; }
|
||||||
.dv { color: #fff; font-weight: 500; }
|
.dv { color: #fff; font-weight: 500; }
|
||||||
|
|
||||||
#gps-dot { width: 6px; height: 6px; border-radius: 50%; background: #555; flex-shrink: 0; }
|
#gps-dot { width: 6px; height: 6px; border-radius: 50%; background: #555; flex-shrink: 0; }
|
||||||
#gps-dot.active { background: #1D9E75; animation: gpsblink 2s infinite; }
|
#gps-dot.active { background: #1D9E75; animation: gpsblink 2s infinite; }
|
||||||
@keyframes gpsblink { 0%,100%{opacity:1} 50%{opacity:0.4} }
|
@keyframes gpsblink { 0%,100%{opacity:1} 50%{opacity:0.4} }
|
||||||
|
|
||||||
#placeholder { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; color: #888; gap: 10px; font-family: var(--font-sans); background: #000; }
|
|
||||||
#status-bar { position: absolute; bottom: 90px; left: 12px; font-size: 10px; color: rgba(255,255,255,0.4); font-family: var(--font-sans); pointer-events: none; }
|
#status-bar { position: absolute; bottom: 90px; left: 12px; font-size: 10px; color: rgba(255,255,255,0.4); font-family: var(--font-sans); pointer-events: none; }
|
||||||
|
|
||||||
/* BUTTON BAR */
|
#placeholder { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; color: #888; gap: 10px; font-family: var(--font-sans); background: #000; }
|
||||||
#btn-bar { position: absolute; bottom: 0; left: 0; right: 0; display: flex; justify-content: center; align-items: center; gap: 12px; padding: 8px 12px; background: rgba(0,0,0,0.5); pointer-events: all; flex-wrap: wrap; }
|
|
||||||
|
#btn-bar { position: absolute; bottom: 0; left: 0; right: 0; display: flex; justify-content: center; align-items: center; gap: 10px; padding: 7px 12px; background: rgba(0,0,0,0.5); pointer-events: all; flex-wrap: wrap; }
|
||||||
.hud-btn { background: rgba(255,255,255,0.1); border: 0.5px solid rgba(255,255,255,0.2); color: #fff; border-radius: 20px; padding: 5px 13px; font-size: 11px; font-family: var(--font-sans); cursor: pointer; letter-spacing: 0.4px; white-space: nowrap; }
|
.hud-btn { background: rgba(255,255,255,0.1); border: 0.5px solid rgba(255,255,255,0.2); color: #fff; border-radius: 20px; padding: 5px 13px; font-size: 11px; font-family: var(--font-sans); cursor: pointer; letter-spacing: 0.4px; white-space: nowrap; }
|
||||||
.hud-btn:hover { background: rgba(255,255,255,0.2); }
|
.hud-btn:hover { background: rgba(255,255,255,0.2); }
|
||||||
.hud-btn:disabled { opacity: 0.3; cursor: default; }
|
.hud-btn:disabled { opacity: 0.3; cursor: default; }
|
||||||
.hud-btn.primary { background: rgba(29,158,117,0.7); border-color: rgba(29,158,117,0.9); }
|
.hud-btn.primary { background: rgba(29,158,117,0.7); border-color: #1D9E75; }
|
||||||
.hud-btn.primary:hover { background: rgba(29,158,117,0.9); }
|
.hud-btn.primary:hover { background: rgba(29,158,117,0.9); }
|
||||||
.hud-btn.warn { background: rgba(186,117,23,0.7); border-color: rgba(186,117,23,0.9); }
|
.hud-btn.danger { background: rgba(180,0,0,0.7); border-color: rgba(220,0,0,0.9); }
|
||||||
|
.hud-btn.danger:hover { background: rgba(220,0,0,0.85); }
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div id="app">
|
<div id="app">
|
||||||
@@ -62,45 +49,27 @@
|
|||||||
<div id="placeholder">
|
<div id="placeholder">
|
||||||
<div style="font-size:40px">🎥</div>
|
<div style="font-size:40px">🎥</div>
|
||||||
<div style="color:#aaa;font-family:var(--font-sans)">Tap START to begin</div>
|
<div style="color:#aaa;font-family:var(--font-sans)">Tap START to begin</div>
|
||||||
<div style="font-size:11px;color:#555;font-family:var(--font-sans)">Camera · GPS · Motion sensors</div>
|
<div style="font-size:11px;color:#555;font-family:var(--font-sans)">Camera + location permissions required</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="hud">
|
<div id="hud">
|
||||||
|
|
||||||
<div id="top-left">
|
<div id="top-left">
|
||||||
<div id="clock">--:--:--</div>
|
<div id="clock">--:--:--</div>
|
||||||
<div id="dateline">---</div>
|
<div id="dateline">---</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="rec-indicator">
|
||||||
|
<div id="rec-dot"></div>
|
||||||
|
<span>REC</span>
|
||||||
|
<span id="rec-timer">00:00</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="speed-display">
|
<div id="speed-display">
|
||||||
<div id="speed-val">--</div>
|
<div id="speed-val">--</div>
|
||||||
<div id="speed-unit">MPH</div>
|
<div id="speed-unit">MPH</div>
|
||||||
<div id="max-speed">MAX --</div>
|
<div id="max-speed">MAX --</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="gforce-wrap">
|
|
||||||
<div id="gforce-val">0.00</div>
|
|
||||||
<div id="gforce-label">G-FORCE</div>
|
|
||||||
<div id="gforce-bar-wrap"><div id="gforce-bar"></div></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="tilt-wrap">
|
|
||||||
<div id="tilt-label">TILT</div>
|
|
||||||
<div id="tilt-pitch">P: --°</div>
|
|
||||||
<div id="tilt-roll">R: --°</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="sensor-bar">
|
|
||||||
<div class="data-row">
|
|
||||||
<div class="di"><span class="dl">BATT</span><span class="dv" id="batt-val">--</span></div>
|
|
||||||
<div class="di"><span class="dl">CHARGING</span><span class="dv" id="batt-chg">--</span></div>
|
|
||||||
<div class="di"><span class="dl">CONN</span><span class="dv" id="net-val">--</span></div>
|
|
||||||
<div class="di"><span class="dl">NETWORK</span><span class="dv" id="net-type">--</span></div>
|
|
||||||
<div class="di"><span class="dl">MEM</span><span class="dv" id="mem-val">--</span></div>
|
|
||||||
<div class="di"><span class="dl">CORES</span><span class="dv" id="cores-val">--</span></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="gps-bar">
|
<div id="gps-bar">
|
||||||
<div class="data-row">
|
<div class="data-row">
|
||||||
<div id="gps-dot"></div>
|
<div id="gps-dot"></div>
|
||||||
@@ -118,8 +87,8 @@
|
|||||||
<div id="btn-bar">
|
<div id="btn-bar">
|
||||||
<button class="hud-btn primary" id="start-btn" onclick="startDashcam()">START</button>
|
<button class="hud-btn primary" id="start-btn" onclick="startDashcam()">START</button>
|
||||||
<button class="hud-btn" id="flip-btn" onclick="switchCamera()" disabled>FLIP</button>
|
<button class="hud-btn" id="flip-btn" onclick="switchCamera()" disabled>FLIP</button>
|
||||||
|
<button class="hud-btn" id="rec-btn" onclick="toggleRecord()" disabled>● RECORD</button>
|
||||||
<button class="hud-btn" id="unit-btn" onclick="toggleUnit()">MPH/KPH</button>
|
<button class="hud-btn" id="unit-btn" onclick="toggleUnit()">MPH/KPH</button>
|
||||||
<button class="hud-btn" id="motion-btn" onclick="requestMotion()">ENABLE MOTION</button>
|
|
||||||
<button class="hud-btn" id="fs-btn" onclick="toggleFullscreen()">⛶ FULL</button>
|
<button class="hud-btn" id="fs-btn" onclick="toggleFullscreen()">⛶ FULL</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -130,16 +99,15 @@ let stream = null, facingMode = 'environment';
|
|||||||
let watchId = null, clockInterval = null;
|
let watchId = null, clockInterval = null;
|
||||||
let useMph = true, maxSpeed = 0;
|
let useMph = true, maxSpeed = 0;
|
||||||
let lastPos = null, totalDist = 0;
|
let lastPos = null, totalDist = 0;
|
||||||
let motionEnabled = false;
|
let mediaRecorder = null, recordedChunks = [], recInterval = null, recSeconds = 0;
|
||||||
|
let isRecording = false;
|
||||||
const G = 9.80665;
|
|
||||||
|
|
||||||
function cvt(ms) { return useMph ? ms * 2.23694 : ms * 3.6; }
|
function cvt(ms) { return useMph ? ms * 2.23694 : ms * 3.6; }
|
||||||
|
|
||||||
function haversine(a, b) {
|
function haversine(a, b) {
|
||||||
const R = 6371000, toR = Math.PI / 180;
|
const R = 6371000, r = Math.PI / 180;
|
||||||
const dLat = (b.lat - a.lat) * toR, dLng = (b.lng - a.lng) * toR;
|
const dLat = (b.lat - a.lat) * r, dLng = (b.lng - a.lng) * r;
|
||||||
const x = Math.sin(dLat/2)**2 + Math.cos(a.lat*toR)*Math.cos(b.lat*toR)*Math.sin(dLng/2)**2;
|
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));
|
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();
|
document.getElementById('dateline').textContent = days[n.getDay()]+' '+pad(n.getDate())+' '+months[n.getMonth()]+' '+n.getFullYear();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setStatus(msg) {
|
function setStatus(msg) { document.getElementById('status-bar').textContent = msg; }
|
||||||
document.getElementById('status-bar').textContent = msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
function startGPS() {
|
function startGPS() {
|
||||||
if (!navigator.geolocation) { setStatus('GPS unavailable'); return; }
|
if (!navigator.geolocation) { setStatus('GPS unavailable'); return; }
|
||||||
@@ -189,105 +155,76 @@ function startGPS() {
|
|||||||
}, { enableHighAccuracy: true, maximumAge: 1000, timeout: 15000 });
|
}, { enableHighAccuracy: true, maximumAge: 1000, timeout: 15000 });
|
||||||
}
|
}
|
||||||
|
|
||||||
function startStaticSensors() {
|
function getBestMimeType() {
|
||||||
document.getElementById('sensor-bar').style.display = 'flex';
|
const types = [
|
||||||
document.getElementById('cores-val').textContent = navigator.hardwareConcurrency || '--';
|
'video/webm;codecs=vp9',
|
||||||
|
'video/webm;codecs=vp8',
|
||||||
if ('deviceMemory' in navigator) {
|
'video/webm',
|
||||||
document.getElementById('mem-val').textContent = navigator.deviceMemory + 'GB';
|
'video/mp4'
|
||||||
} else {
|
];
|
||||||
document.getElementById('mem-val').textContent = 'n/a';
|
for (const t of types) { if (MediaRecorder.isTypeSupported(t)) return t; }
|
||||||
}
|
return '';
|
||||||
|
|
||||||
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 handleMotion(e) {
|
function saveRecording() {
|
||||||
const acc = e.accelerationIncludingGravity;
|
if (recordedChunks.length === 0) { setStatus('Nothing to save'); return; }
|
||||||
if (!acc) return;
|
const mime = getBestMimeType();
|
||||||
const x = acc.x || 0, y = acc.y || 0, z = acc.z || 0;
|
const ext = mime.includes('mp4') ? 'mp4' : 'webm';
|
||||||
const total = Math.sqrt(x*x + y*y + z*z) / G;
|
const blob = new Blob(recordedChunks, { type: mime });
|
||||||
const lateral = Math.abs(x) / G;
|
const url = URL.createObjectURL(blob);
|
||||||
const longitudinal = Math.abs(y) / G;
|
const a = document.createElement('a');
|
||||||
const gDisplay = Math.max(lateral, longitudinal).toFixed(2);
|
const now = new Date();
|
||||||
|
a.href = url;
|
||||||
document.getElementById('gforce-val').textContent = gDisplay;
|
a.download = 'dashcam_' + now.getFullYear() + pad(now.getMonth()+1) + pad(now.getDate()) + '_' + pad(now.getHours()) + pad(now.getMinutes()) + pad(now.getSeconds()) + '.' + ext;
|
||||||
const pct = Math.min(parseFloat(gDisplay) / 2 * 100, 100);
|
a.click();
|
||||||
const bar = document.getElementById('gforce-bar');
|
setTimeout(() => URL.revokeObjectURL(url), 5000);
|
||||||
bar.style.width = pct + '%';
|
recordedChunks = [];
|
||||||
bar.style.background = pct > 75 ? '#e24b4a' : pct > 40 ? '#BA7517' : '#1D9E75';
|
setStatus('Saved!');
|
||||||
|
|
||||||
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 handleOrientation(e) {
|
function toggleRecord() {
|
||||||
const pitch = e.beta !== null ? e.beta.toFixed(1) : '--';
|
if (!stream) return;
|
||||||
const roll = e.gamma !== null ? e.gamma.toFixed(1) : '--';
|
const btn = document.getElementById('rec-btn');
|
||||||
if (!motionEnabled) {
|
const ind = document.getElementById('rec-indicator');
|
||||||
document.getElementById('tilt-pitch').textContent = 'PITCH: ' + pitch + '\xb0';
|
|
||||||
document.getElementById('tilt-roll').textContent = 'ROLL: ' + roll + '\xb0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function requestMotion() {
|
if (!isRecording) {
|
||||||
const btn = document.getElementById('motion-btn');
|
recordedChunks = [];
|
||||||
if (typeof DeviceMotionEvent !== 'undefined' && typeof DeviceMotionEvent.requestPermission === 'function') {
|
const mime = getBestMimeType();
|
||||||
try {
|
try {
|
||||||
const res = await DeviceMotionEvent.requestPermission();
|
mediaRecorder = new MediaRecorder(stream, mime ? { mimeType: mime } : {});
|
||||||
if (res === 'granted') {
|
} catch(e) {
|
||||||
window.addEventListener('devicemotion', handleMotion);
|
mediaRecorder = new MediaRecorder(stream);
|
||||||
motionEnabled = true;
|
}
|
||||||
btn.textContent = 'MOTION ON';
|
mediaRecorder.ondataavailable = e => { if (e.data && e.data.size > 0) recordedChunks.push(e.data); };
|
||||||
btn.classList.add('warn');
|
mediaRecorder.onstop = saveRecording;
|
||||||
document.getElementById('gforce-wrap').style.display = 'block';
|
mediaRecorder.start(1000);
|
||||||
document.getElementById('tilt-wrap').style.display = 'block';
|
isRecording = true;
|
||||||
}
|
recSeconds = 0;
|
||||||
} catch(e) { setStatus('Motion denied'); }
|
ind.style.display = 'flex';
|
||||||
} else if (typeof DeviceMotionEvent !== 'undefined') {
|
btn.textContent = '▮▮ SAVE';
|
||||||
window.addEventListener('devicemotion', handleMotion);
|
btn.innerHTML = '▮▮ SAVE';
|
||||||
window.addEventListener('deviceorientation', handleOrientation);
|
btn.classList.add('danger');
|
||||||
motionEnabled = true;
|
recInterval = setInterval(() => {
|
||||||
btn.textContent = 'MOTION ON';
|
recSeconds++;
|
||||||
btn.classList.add('warn');
|
const m = pad(Math.floor(recSeconds/60)), s = pad(recSeconds%60);
|
||||||
document.getElementById('gforce-wrap').style.display = 'block';
|
document.getElementById('rec-timer').textContent = m+':'+s;
|
||||||
document.getElementById('tilt-wrap').style.display = 'block';
|
}, 1000);
|
||||||
|
setStatus('Recording...');
|
||||||
} else {
|
} 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() {
|
async function startDashcam() {
|
||||||
const btn = document.getElementById('start-btn');
|
const btn = document.getElementById('start-btn');
|
||||||
btn.disabled = true; btn.textContent = '...';
|
btn.disabled = true; btn.textContent = '...';
|
||||||
|
if (isRecording) toggleRecord();
|
||||||
try {
|
try {
|
||||||
if (stream) stream.getTracks().forEach(t => t.stop());
|
if (stream) stream.getTracks().forEach(t => t.stop());
|
||||||
stream = await navigator.mediaDevices.getUserMedia({
|
stream = await navigator.mediaDevices.getUserMedia({
|
||||||
@@ -297,11 +234,11 @@ async function startDashcam() {
|
|||||||
document.getElementById('placeholder').style.display = 'none';
|
document.getElementById('placeholder').style.display = 'none';
|
||||||
document.getElementById('top-left').style.display = 'flex';
|
document.getElementById('top-left').style.display = 'flex';
|
||||||
document.getElementById('flip-btn').disabled = false;
|
document.getElementById('flip-btn').disabled = false;
|
||||||
|
document.getElementById('rec-btn').disabled = false;
|
||||||
btn.textContent = 'RESTART'; btn.disabled = false;
|
btn.textContent = 'RESTART'; btn.disabled = false;
|
||||||
if (watchId !== null) { navigator.geolocation.clearWatch(watchId); watchId = null; }
|
if (watchId !== null) { navigator.geolocation.clearWatch(watchId); watchId = null; }
|
||||||
maxSpeed = 0; totalDist = 0; lastPos = null;
|
maxSpeed = 0; totalDist = 0; lastPos = null;
|
||||||
startGPS();
|
startGPS();
|
||||||
startStaticSensors();
|
|
||||||
if (clockInterval) clearInterval(clockInterval);
|
if (clockInterval) clearInterval(clockInterval);
|
||||||
clockInterval = setInterval(updateClock, 1000);
|
clockInterval = setInterval(updateClock, 1000);
|
||||||
updateClock();
|
updateClock();
|
||||||
|
|||||||
Reference in New Issue
Block a user