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

+10 -12
View File
@@ -205,7 +205,7 @@ let activeCues = [], firedCue = -1; // cue schedule for the playing t
function savePlaylistState() {
if (djv.savePlaylist) {
djv.savePlaylist(playlist.map(t => ({ path: t.path, name: t.name, key: t.key || null, cues: t.cues || [] })));
djv.savePlaylist(playlist.map(t => ({ path: t.path, name: t.name, key: t.key || null, cues: t.cues || [], isVideo: !!t.isVideo })));
}
}
@@ -252,7 +252,7 @@ function probePaths(paths) {
function addTracks(items) {
// items: array of { path, name }
const start = playlist.length;
items.forEach(it => { if (it.path) playlist.push({ path: it.path, name: it.name || baseName(it.path), key: null }); });
items.forEach(it => { if (it.path) playlist.push({ path: it.path, name: it.name || baseName(it.path), key: null, cues: [], isVideo: !!it.isVideo }); });
renderPlaylist();
savePlaylistState();
probePaths(items.map(it => it.path));
@@ -267,7 +267,7 @@ function playIndex(i) {
ended = false;
playbackOwner = 'playlist';
activePad = -1; padPlaying = false; renderPads();
send({ type: 'playTrack', path: playlist[i].path });
send({ type: playlist[i].isVideo ? 'playVideoTrack' : 'playTrack', path: playlist[i].path });
startCues(playlist[i]);
$('#btn-play').disabled = false;
$('#btn-play').textContent = '⏸ Pausa';
@@ -321,7 +321,7 @@ function renderPlaylist() {
const prog = (isCur && playDur > 0) ? Math.min(100, playCur / playDur * 100) : 0;
li.innerHTML =
'<span class="grip">⠿</span>' +
'<span class="tname" title="Avvia / riavvia dall\'inizio — ' + tr.name.replace(/"/g, '&quot;') + '">' + tr.name + '</span>' +
'<span class="tname" title="Avvia / riavvia dall\'inizio — ' + tr.name.replace(/"/g, '&quot;') + '">' + (tr.isVideo ? '🎞 ' : '') + tr.name + '</span>' +
'<span class="ttime">' + timeLabel + '</span>' +
'<button class="key-btn" title="Assegna tasto rapido">' +
(capturingFor === i ? '…' : (tr.key ? tr.key.toUpperCase() : '⌨')) + '</button>' +
@@ -409,7 +409,7 @@ function renderPlaylist() {
$('#btn-add-tracks').addEventListener('click', () => $('#tracks-input').click());
$('#tracks-input').addEventListener('change', (e) => {
const items = [...e.target.files].map(f => ({ path: filePath(f), name: f.name }));
const items = [...e.target.files].map(f => ({ path: filePath(f), name: f.name, isVideo: f.type.startsWith('video/') }));
if (items.length) addTracks(items);
e.target.value = ''; // allow re-adding the same file
});
@@ -672,7 +672,7 @@ if (padXfade) padXfade.addEventListener('input', (e) => {
if (Array.isArray(saved) && saved.length) {
playlist = saved.map(t => ({
path: t.path, name: t.name || baseName(t.path),
key: t.key || null, cues: Array.isArray(t.cues) ? t.cues : []
key: t.key || null, cues: Array.isArray(t.cues) ? t.cues : [], isVideo: !!t.isVideo
}));
renderPlaylist();
probePaths(playlist.map(t => t.path));
@@ -723,14 +723,12 @@ window.addEventListener('drop', (e) => {
e.preventDefault();
document.body.classList.remove('dragover');
const files = [...e.dataTransfer.files];
const audioItems = files.filter(f => f.type.startsWith('audio/'))
.map(f => ({ path: filePath(f), name: f.name }));
// Audio and video files both go into the playlist as tracks.
const mediaItems = files.filter(f => f.type.startsWith('audio/') || f.type.startsWith('video/'))
.map(f => ({ path: filePath(f), name: f.name, isVideo: f.type.startsWith('video/') }));
const imgPaths = files.filter(f => f.type.startsWith('image/')).map(filePath).filter(Boolean);
const videoFile = files.find(f => f.type.startsWith('video/'));
const videoPath = videoFile && filePath(videoFile);
if (audioItems.length) addTracks(audioItems);
if (mediaItems.length) addTracks(mediaItems);
if (imgPaths.length) send({ type: 'addImages', paths: imgPaths });
if (videoPath) send({ type: 'videoLoad', path: videoPath });
});
// ---------------------------------------------------------------- keyboard