Fix wrong recording length (8:12 video listed as 1:40) #4
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/recording-duration"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
/v/keyword-researchis 8:12 of video but was listed as 1:40.What was actually wrong
Nothing was lost — I parsed the stored WebM directly and the last cluster timecode is 492,092 ms (8m12s) across 132.5 MB. The player is fine too: on the live page
player.durationresolves to 493.05 s onceseekfixruns.The bad number is the stored duration, which is what the dashboard badge renders (
dashboard.html,v.duration|duration). MediaRecorder writes a streaming WebM with no Duration element, so the length we saved came from the recorder's own tick loop instead of from the media:That accumulator only advances while its callback fires. Browsers throttle and outright freeze timers in hidden/occluded tabs — which is exactly what a screen recorder's own tab is for the whole recording — so the saved length can end up far short of the real one.
Changes
recorder.js— timer derived from wall-clock marks (recStartMs,pausedTotalMs) instead of per-tick accumulation, so a frozen tab can't under-count.recorder.js— save the real media length: the review player already recovers it from the recorded blob viafixVideoSeek, so use that and keep the wall-clock timer only as a fallback.watch.html/watch.js— pass the stored length to the player asdata-duration, so the client can tell when the browser's guess is wrong.seekfix.js— also run the recovery when the browser reports a finite but wrong duration; it previously no-opped on any finite value. Recovery is attempted at most once per element so a wrong stored duration can't cause a seek loop, plus a timeout so the playhead is never left parked at the end.Verification
player.duration493.05 s,seekfixfires and settles in ~2 s.node --checkon all three touched JS files.seekfix(fake<video>): Infinity recovers · wrong-finite corrects · bad hint → one seek, no loop · agreeing duration no-ops ·restoreTohonoured. All pass.Note
This is read-side only for new recordings — nothing needs re-uploading. Existing recordings keep their stored (possibly wrong) duration until backfilled; the length shown on the watch page itself is already correct.
Follow-up: length taken from the container, and a backfill
The client's timer is no longer the source of truth at all.
app/webmprobe.pyreads the real length straight from the WebM — the last Cluster'sTimecodeplus the largest block offset inside it, scaled byTimecodeScale. It reads only the head and a 4 MB tail, so probing a 130 MB recording touches a few MB, not the whole file. (Lives underapp/because the Dockerfile only copiesapp/,templates/,static/andschema.sqlinto the image.)complete_upload()now prefers the probed duration over whatever the browser reported, falling back to it only when the file can't be read.Dry run by default;
--tolerancecontrols what counts as wrong (default 1 s).Verification
Noneinstead of raising, so the upload path falls back cleanly.py_compileclean on the touched Python.app/webmprobe.py reads the true length straight from the container: the last Cluster's Timecode plus the largest block offset inside it, using TimecodeScale from the header. Only the head and a 4 MB tail are read, so probing a 130 MB recording touches a few MB rather than the whole file. Verified against a live 8:13 recording: probe returns 493.052s, matching exactly what the browser resolves. Malformed/short input returns None rather than raising. - complete_upload() now prefers the probed duration over the value the browser reported, falling back to it when the file cannot be read. The client timer is no longer trusted as the source of truth. - CLI to correct recordings already stored with a wrong duration: docker compose exec lumo python -m app.webmprobe # dry run docker compose exec lumo python -m app.webmprobe --apply # write changes Dry run by default, --tolerance to control what counts as wrong. Lives under app/ because the Dockerfile only copies app/, templates/, static/ and schema.sql into the image.