Playlist supports video tracks (full-screen video + audio through the analyser)

Audio and video files can both be added to the playlist; video tracks play
full-screen with their audio routed through the analyser (visuals/waveform
react), have duration/progress, and are included in recordings. Persisted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
lucianoandClaude Opus 4.8 committed 2026-06-19 16:46:44 +02:00
1 parent 1ae8fa81a7
commit 0f0accc67d
6 files changed
+74 -15

No files matched your search

+21 -1
View File
@@ -94,6 +94,10 @@ function composeFrame() {
recCtx.globalCompositeOperation = 'source-over';
}
if (trackVideo.classList.contains('show') && trackVideo.readyState >= 2 && trackVideo.videoWidth) {
try { drawMediaFit(recCtx, trackVideo, W, H, 'contain'); } catch (e) {}
}
for (const im of images) {
if (!im.classList.contains('show')) continue;
const r = im.getBoundingClientRect();
@@ -210,6 +214,7 @@ function loadCustomTexture(dataUrl) {
}
const bgVideo = $('#bg-video');
const trackVideo = $('#track-video');
const sceneImage = $('#scene-image');
// Two independent logos.
@@ -249,7 +254,8 @@ function probeDurations(paths) {
let pending = paths.length;
const finish = () => { if (--pending === 0) djv.report({ type: 'durations', list: results }); };
paths.forEach(p => {
const a = new Audio();
// A <video> element reads duration for both audio and video files.
const a = document.createElement('video');
a.preload = 'metadata';
a.onloadedmetadata = () => { results.push({ path: p, duration: isFinite(a.duration) ? a.duration : 0 }); finish(); };
a.onerror = () => { results.push({ path: p, duration: 0 }); finish(); };
@@ -291,12 +297,26 @@ djv.onControl(async (m) => {
break;
case 'playTrack':
try {
// Switching to an audio track: stop/hide any playing video track.
trackVideo.pause(); trackVideo.classList.remove('show');
audio.onEnded = () => djv.report({ type: 'trackEnded' });
await audio.loadFile(toFileURL(m.path), { loop: false, crossfade: m.crossfade || 0 });
hideHint();
djv.report({ type: 'playState', playing: true });
} catch (e) { djv.report({ type: 'error', message: e.message }); }
break;
case 'playVideoTrack':
try {
audio.onEnded = () => djv.report({ type: 'trackEnded' });
trackVideo.loop = false; trackVideo.muted = false;
trackVideo.src = toFileURL(m.path);
trackVideo.classList.add('show');
audio.attachVideo(trackVideo);
await trackVideo.play();
hideHint();
djv.report({ type: 'playState', playing: true });
} catch (e) { djv.report({ type: 'error', message: e.message }); }
break;
case 'probeDurations': probeDurations(m.paths || []); break;
case 'togglePlay': {
const playing = audio.togglePlay();