Reels: timed recording with auto-stop

recStart now carries the format and an optional maxMs; the output stops
itself when the time is up (15/30/60/90s picker in the control UI), so a
reel doesn't depend on a manual stop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
lucianoandClaude Fable 5 committed 2026-07-17 15:51:51 +02:00
1 parent 48ac3dfa85
commit 19aab4e61e
3 files changed
+21 -5

No files matched your search

+8
View File
@@ -320,6 +320,14 @@
<option value="1080x1440">3:4 verticale (1080×1440)</option> <option value="1080x1440">3:4 verticale (1080×1440)</option>
<option value="2560x1080">21:9 cinema (2560×1080)</option> <option value="2560x1080">21:9 cinema (2560×1080)</option>
</select> </select>
<label>Durata (Reel: si ferma da solo)</label>
<select id="rec-dur">
<option value="0">∞ manuale</option>
<option value="15000">⏱ 15 secondi</option>
<option value="30000">⏱ 30 secondi</option>
<option value="60000">⏱ 60 secondi</option>
<option value="90000">⏱ 90 secondi</option>
</select>
<button id="btn-rec" class="wide">🔴 Avvia registrazione</button> <button id="btn-rec" class="wide">🔴 Avvia registrazione</button>
<button id="btn-rec-folder" class="wide">📁 Apri cartella registrazioni</button> <button id="btn-rec-folder" class="wide">📁 Apri cartella registrazioni</button>
<div class="hint-mini">Registra ciò che vedi sull'output + l'audio (file o input live). Salvataggio in <b>Filmati ▸ DJ Visualizer</b>. La conversione in MP4 avviene allo stop.</div> <div class="hint-mini">Registra ciò che vedi sull'output + l'audio (file o input live). Salvataggio in <b>Filmati ▸ DJ Visualizer</b>. La conversione in MP4 avviene allo stop.</div>
+3 -1
View File
@@ -1105,7 +1105,9 @@ function stopRecTimer() { if (recTimer) { clearInterval(recTimer); recTimer = nu
$('#btn-rec').addEventListener('click', () => { $('#btn-rec').addEventListener('click', () => {
if (!recOn) { if (!recOn) {
send({ type: 'recStart' }); // Pass format + optional max duration so the output can auto-stop (Reel mode).
const [w, h] = $('#rec-aspect').value.split('x').map(Number);
send({ type: 'recStart', w, h, maxMs: parseInt($('#rec-dur').value, 10) || 0 });
} else { } else {
const [w, h] = $('#rec-aspect').value.split('x').map(Number); const [w, h] = $('#rec-aspect').value.split('x').map(Number);
send({ type: 'recStop', w, h }); send({ type: 'recStop', w, h });
+10 -4
View File
@@ -58,7 +58,7 @@ function restartSlideshow() {
// ---------------------------------------------------------------- recording // ---------------------------------------------------------------- recording
// Records the output canvas (video) + the audio tap into a WebM stream that // Records the output canvas (video) + the audio tap into a WebM stream that
// main transcodes to MP4 at the chosen aspect ratio. // main transcodes to MP4 at the chosen aspect ratio.
let recorder = null, recording = false, pendingRecOpts = null; let recorder = null, recording = false, pendingRecOpts = null, recAutoStop = null;
let recCanvas = null, recCtx = null, composeRAF = null; let recCanvas = null, recCtx = null, composeRAF = null;
function pickMime() { function pickMime() {
@@ -152,7 +152,7 @@ function stopCompose() {
if (composeRAF) { cancelAnimationFrame(composeRAF); composeRAF = null; } if (composeRAF) { cancelAnimationFrame(composeRAF); composeRAF = null; }
} }
async function startRecording() { async function startRecording(opts) {
if (recording) return; if (recording) return;
try { try {
await djv.recStart(); await djv.recStart();
@@ -178,11 +178,16 @@ async function startRecording() {
const res = await djv.recStop(pendingRecOpts || {}); const res = await djv.recStop(pendingRecOpts || {});
recording = false; recording = false;
djv.report({ type: 'recState', recording: false }); djv.report({ type: 'recState', recording: false });
if (res && res.ok) djv.report({ type: 'recSaved', path: res.path }); if (res && res.ok) djv.report({ type: 'recSaved', path: res.path, name: res.name, url: res.url });
else djv.report({ type: 'recError', message: (res && res.error) || 'errore sconosciuto' }); else djv.report({ type: 'recError', message: (res && res.error) || 'errore sconosciuto' });
}; };
recorder.start(2000); // emit a chunk every 2s recorder.start(2000); // emit a chunk every 2s
recording = true; recording = true;
// Reel mode: auto-stop after maxMs using the format chosen at start.
if (opts && opts.maxMs > 0) {
clearTimeout(recAutoStop);
recAutoStop = setTimeout(() => stopRecording({ w: opts.w, h: opts.h }), opts.maxMs);
}
djv.report({ type: 'recState', recording: true }); djv.report({ type: 'recState', recording: true });
} catch (e) { } catch (e) {
stopCompose(); stopCompose();
@@ -191,6 +196,7 @@ async function startRecording() {
} }
function stopRecording(opts) { function stopRecording(opts) {
clearTimeout(recAutoStop); recAutoStop = null;
if (!recording || !recorder) return; if (!recording || !recorder) return;
pendingRecOpts = { w: opts && opts.w, h: opts && opts.h }; pendingRecOpts = { w: opts && opts.w, h: opts && opts.h };
recorder.stop(); recorder.stop();
@@ -312,7 +318,7 @@ djv.onControl(async (m) => {
djv.report({ type: 'autoVj', on: autoVj }); djv.report({ type: 'autoVj', on: autoVj });
break; break;
case 'svg': loadCustomTexture(m.dataUrl); break; case 'svg': loadCustomTexture(m.dataUrl); break;
case 'recStart': startRecording(); break; case 'recStart': startRecording(m); break;
case 'recStop': stopRecording(m); break; case 'recStop': stopRecording(m); break;
case 'gain': audio.gain = m.value; break; case 'gain': audio.gain = m.value; break;
case 'speed': speed = m.value; audio.scrollRate = m.value; break; case 'speed': speed = m.value; audio.scrollRate = m.value; break;