From 7ce94f78c4a901a33512f1bd926f20e37ac73ef6 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 29 Apr 2026 22:14:38 +0100 Subject: [PATCH] Added subtitles of info --- dashcam.html | 219 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 139 insertions(+), 80 deletions(-) diff --git a/dashcam.html b/dashcam.html index 8cd4a06..3234d85 100644 --- a/dashcam.html +++ b/dashcam.html @@ -12,7 +12,6 @@ #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; } @@ -30,7 +29,6 @@ @keyframes gpsblink { 0%,100%{opacity:1} 50%{opacity:0.4} } #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; } - #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: 10px; padding: 7px 12px; background: rgba(0,0,0,0.5); pointer-events: all; flex-wrap: wrap; } @@ -45,31 +43,26 @@
-
🎥
Tap START to begin
Camera + location permissions required
-
--:--:--
---
-
REC 00:00
-
--
MPH
MAX --
-
@@ -81,9 +74,7 @@
DIST0.00km
-
-
@@ -99,10 +90,17 @@ let stream = null, facingMode = 'environment'; let watchId = null, clockInterval = null; let useMph = true, maxSpeed = 0; let lastPos = null, totalDist = 0; -let mediaRecorder = null, recordedChunks = [], recInterval = null, recSeconds = 0; -let isRecording = false; +let mediaRecorder = null, recordedChunks = []; +let recInterval = null, recSeconds = 0, isRecording = false; + +// SRT state +let srtEntries = []; +let srtInterval = null; +let recStartTime = null; +let currentGPS = { lat: null, lng: null, alt: null, acc: null, hdg: null, speed: 0 }; function cvt(ms) { return useMph ? ms * 2.23694 : ms * 3.6; } +function cvtLabel() { return useMph ? 'mph' : 'kph'; } function haversine(a, b) { const R = 6371000, r = Math.PI / 180; @@ -116,7 +114,8 @@ function bearingLabel(deg) { return ['N','NE','E','SE','S','SW','W','NW'][Math.round(deg/45)%8] + ' ' + Math.round(deg) + '\xb0'; } -function pad(v) { return String(v).padStart(2,'0'); } +function pad(v, n=2) { return String(Math.floor(v)).padStart(n,'0'); } +function pad3(v) { return String(Math.floor(v)).padStart(3,'0'); } function updateClock() { const n = new Date(); @@ -128,13 +127,138 @@ function updateClock() { function setStatus(msg) { document.getElementById('status-bar').textContent = msg; } +// Format ms offset as SRT timecode HH:MM:SS,mmm +function toSRTTime(ms) { + const h = Math.floor(ms / 3600000); + const m = Math.floor((ms % 3600000) / 60000); + const s = Math.floor((ms % 60000) / 1000); + const mil = ms % 1000; + return pad(h)+':'+pad(m)+':'+pad(s)+','+pad3(mil); +} + +// Capture one SRT frame every second during recording +function captureSRTFrame() { + const now = new Date(); + const elapsed = Date.now() - recStartTime; + const dateStr = now.getFullYear()+'-'+pad(now.getMonth()+1)+'-'+pad(now.getDate()); + const timeStr = pad(now.getHours())+':'+pad(now.getMinutes())+':'+pad(now.getSeconds()); + const spd = currentGPS.speed !== null ? Math.round(cvt(currentGPS.speed)) : '--'; + const lat = currentGPS.lat !== null ? currentGPS.lat.toFixed(6) : '--'; + const lng = currentGPS.lng !== null ? currentGPS.lng.toFixed(6) : '--'; + const alt = currentGPS.alt !== null ? Math.round(currentGPS.alt)+'m' : '--'; + const acc = currentGPS.acc !== null ? Math.round(currentGPS.acc)+'m' : '--'; + const hdg = currentGPS.hdg !== null ? bearingLabel(currentGPS.hdg) : '--'; + + srtEntries.push({ + startMs: elapsed, + endMs: elapsed + 1000, + lines: [ + dateStr + ' ' + timeStr, + 'Speed: ' + spd + ' ' + cvtLabel() + ' Hdg: ' + hdg, + 'Lat: ' + lat + ' Lng: ' + lng, + 'Alt: ' + alt + ' Acc: ' + acc + ] + }); +} + +function buildSRT() { + return srtEntries.map((e, i) => { + return (i+1) + '\n' + + toSRTTime(e.startMs) + ' --> ' + toSRTTime(e.endMs) + '\n' + + e.lines.join('\n'); + }).join('\n\n') + '\n'; +} + +function saveSRT(basename) { + if (srtEntries.length === 0) return; + const blob = new Blob([buildSRT()], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = basename + '.srt'; + a.click(); + setTimeout(() => URL.revokeObjectURL(url), 5000); +} + +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 getFilename() { + const n = new Date(); + return 'dashcam_' + n.getFullYear() + pad(n.getMonth()+1) + pad(n.getDate()) + '_' + pad(n.getHours()) + pad(n.getMinutes()) + pad(n.getSeconds()); +} + +function saveVideo(basename) { + 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'); + a.href = url; + a.download = basename + '.' + ext; + a.click(); + setTimeout(() => URL.revokeObjectURL(url), 5000); + recordedChunks = []; +} + +function toggleRecord() { + if (!stream) return; + const btn = document.getElementById('rec-btn'); + const ind = document.getElementById('rec-indicator'); + + if (!isRecording) { + recordedChunks = []; + srtEntries = []; + recStartTime = Date.now(); + const mime = getBestMimeType(); + try { + 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 = () => { + const name = getFilename(); + saveVideo(name); + setTimeout(() => saveSRT(name), 800); + setStatus('Saved video + SRT'); + }; + mediaRecorder.start(1000); + isRecording = true; + recSeconds = 0; + ind.style.display = 'flex'; + btn.innerHTML = '▮▮ SAVE'; + btn.classList.add('danger'); + captureSRTFrame(); + recInterval = setInterval(() => { + recSeconds++; + const m = pad(Math.floor(recSeconds/60)), s = pad(recSeconds%60); + document.getElementById('rec-timer').textContent = m+':'+s; + captureSRTFrame(); + }, 1000); + setStatus('Recording...'); + } else { + mediaRecorder.stop(); + isRecording = false; + clearInterval(recInterval); + ind.style.display = 'none'; + btn.innerHTML = '● RECORD'; + btn.classList.remove('danger'); + setStatus('Saving...'); + } +} + function startGPS() { if (!navigator.geolocation) { setStatus('GPS unavailable'); return; } document.getElementById('gps-bar').style.display = 'flex'; watchId = navigator.geolocation.watchPosition(pos => { const c = pos.coords; - const spd = c.speed !== null && c.speed >= 0 ? c.speed : 0; - const conv = cvt(spd); + currentGPS = { lat: c.latitude, lng: c.longitude, alt: c.altitude, acc: c.accuracy, hdg: c.heading, speed: c.speed !== null ? c.speed : 0 }; + const conv = cvt(currentGPS.speed); if (conv > maxSpeed) maxSpeed = conv; const cur = { lat: c.latitude, lng: c.longitude }; if (lastPos) { const d = haversine(lastPos, cur); if (d < 200) totalDist += d; } @@ -155,72 +279,6 @@ function startGPS() { }, { enableHighAccuracy: true, maximumAge: 1000, timeout: 15000 }); } -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 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 toggleRecord() { - if (!stream) return; - const btn = document.getElementById('rec-btn'); - const ind = document.getElementById('rec-indicator'); - - if (!isRecording) { - recordedChunks = []; - const mime = getBestMimeType(); - try { - 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 { - 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 = '...'; @@ -238,6 +296,7 @@ async function startDashcam() { btn.textContent = 'RESTART'; btn.disabled = false; if (watchId !== null) { navigator.geolocation.clearWatch(watchId); watchId = null; } maxSpeed = 0; totalDist = 0; lastPos = null; + currentGPS = { lat: null, lng: null, alt: null, acc: null, hdg: null, speed: 0 }; startGPS(); if (clockInterval) clearInterval(clockInterval); clockInterval = setInterval(updateClock, 1000);