Playlist: fix video track restart; per-cue full effect picker (family + preset dropdowns)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
96a8f3104c
commit
dc37e4ab3f
3 files changed
+59
-7
No files matched your search
+48
-2
@@ -353,6 +353,30 @@ function defaultCue(time) {
|
|||||||
return { time, effectIndex: null, effectName: '', text: '', textStyle: null, image: null, imageSize: 60, imagePos: 'center' };
|
return { time, effectIndex: null, effectName: '', text: '', textStyle: null, image: null, imageSize: 60, imagePos: 'center' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build the <option> list of effect families for a cue's effect picker.
|
||||||
|
function cueFamilyOptions(selFam) {
|
||||||
|
let s = '<option value="-1"' + (selFam < 0 ? ' selected' : '') + '>— nessun effetto —</option>';
|
||||||
|
EFFECTS.families.forEach((name, i) => {
|
||||||
|
s += '<option value="' + i + '"' + (i === selFam ? ' selected' : '') + '>' + name + '</option>';
|
||||||
|
});
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
// Build the <option> list of presets within a family for a cue's effect picker.
|
||||||
|
function cuePresetOptions(fam, selIdx) {
|
||||||
|
if (fam < 0) return '<option value="">—</option>';
|
||||||
|
let s = '';
|
||||||
|
for (let i = 0; i < EFFECTS.list.length; i++) {
|
||||||
|
if (EFFECTS.list[i].family !== fam) continue;
|
||||||
|
s += '<option value="' + i + '"' + (i === selIdx ? ' selected' : '') + '>' + EFFECTS.list[i].name + '</option>';
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
// First preset index of a family (used when a family is chosen).
|
||||||
|
function firstPresetOfFamily(fam) {
|
||||||
|
for (let i = 0; i < EFFECTS.list.length; i++) if (EFFECTS.list[i].family === fam) return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
// Build the per-track scene editor (timeline of cues with text + image styling).
|
// Build the per-track scene editor (timeline of cues with text + image styling).
|
||||||
function buildSceneEditor(tr, i, li) {
|
function buildSceneEditor(tr, i, li) {
|
||||||
const cues = tr.cues || (tr.cues = []);
|
const cues = tr.cues || (tr.cues = []);
|
||||||
@@ -364,14 +388,26 @@ function buildSceneEditor(tr, i, li) {
|
|||||||
const posLabels = { center: 'Centro', top: 'Alto', bottom: 'Basso', left: 'Sinistra', right: 'Destra' };
|
const posLabels = { center: 'Centro', top: 'Alto', bottom: 'Basso', left: 'Sinistra', right: 'Destra' };
|
||||||
let html = '<div class="se-title">🎬 Scene a tempo <small>— la cue @00:00 parte col brano, le altre scattano al loro tempo</small></div>';
|
let html = '<div class="se-title">🎬 Scene a tempo <small>— la cue @00:00 parte col brano, le altre scattano al loro tempo</small></div>';
|
||||||
cues.forEach((c, ci) => {
|
cues.forEach((c, ci) => {
|
||||||
|
const ce = (c.effectIndex != null && EFFECTS.list[c.effectIndex]) ? EFFECTS.list[c.effectIndex] : null;
|
||||||
|
const cFam = ce ? ce.family : -1;
|
||||||
html +=
|
html +=
|
||||||
'<div class="cue" data-c="' + ci + '">' +
|
'<div class="cue" data-c="' + ci + '">' +
|
||||||
'<div class="cue-head">' +
|
'<div class="cue-head">' +
|
||||||
'<span class="cue-at">@</span>' +
|
'<span class="cue-at">@</span>' +
|
||||||
'<input class="cue-time" type="text" value="' + fmtTime(Math.floor(c.time)) + '" title="Tempo (mm:ss)" />' +
|
'<input class="cue-time" type="text" value="' + fmtTime(Math.floor(c.time)) + '" title="Tempo (mm:ss)" />' +
|
||||||
'<button class="cue-eff" title="Usa l\'effetto attualmente visibile">🌀 ' + (c.effectName || 'imposta effetto') + '</button>' +
|
|
||||||
(ci > 0 ? '<button class="cue-del" title="Rimuovi cue">✕</button>' : '<span class="cue-base">base</span>') +
|
(ci > 0 ? '<button class="cue-del" title="Rimuovi cue">✕</button>' : '<span class="cue-base">base</span>') +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
|
'<div class="cue-block">' +
|
||||||
|
'<div class="cue-bh">🌀 Effetto</div>' +
|
||||||
|
'<div class="cue-row">' +
|
||||||
|
'<select class="cue-eff-fam" title="Categoria effetto">' + cueFamilyOptions(cFam) + '</select>' +
|
||||||
|
'<select class="cue-eff-pre" title="Preset"' + (cFam < 0 ? ' disabled' : '') + '>' + cuePresetOptions(cFam, c.effectIndex) + '</select>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="cue-row">' +
|
||||||
|
'<button class="cue-eff-cur" title="Usa l\'effetto attualmente visibile">🎯 Usa corrente</button>' +
|
||||||
|
'<span class="cue-eff-name">' + (ce ? ce.name : 'nessuno') + '</span>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>' +
|
||||||
'<div class="cue-block">' +
|
'<div class="cue-block">' +
|
||||||
'<div class="cue-bh">🔤 Testo</div>' +
|
'<div class="cue-bh">🔤 Testo</div>' +
|
||||||
'<input class="cue-text textfield" placeholder="Testo (vuoto = nessuno)" />' +
|
'<input class="cue-text textfield" placeholder="Testo (vuoto = nessuno)" />' +
|
||||||
@@ -404,7 +440,17 @@ function buildSceneEditor(tr, i, li) {
|
|||||||
const c = cues[ci];
|
const c = cues[ci];
|
||||||
cueEl.querySelector('.cue-text').value = c.text || '';
|
cueEl.querySelector('.cue-text').value = c.text || '';
|
||||||
cueEl.querySelector('.cue-time').addEventListener('change', (e) => { c.time = parseTime(e.target.value); savePlaylistState(); renderPlaylist(); });
|
cueEl.querySelector('.cue-time').addEventListener('change', (e) => { c.time = parseTime(e.target.value); savePlaylistState(); renderPlaylist(); });
|
||||||
cueEl.querySelector('.cue-eff').addEventListener('click', () => { c.effectIndex = EFFECTS.list.indexOf(currentEffect); c.effectName = currentEffect.name; savePlaylistState(); renderPlaylist(); });
|
cueEl.querySelector('.cue-eff-fam').addEventListener('change', (e) => {
|
||||||
|
const fam = parseInt(e.target.value, 10);
|
||||||
|
if (fam < 0) { c.effectIndex = null; c.effectName = ''; }
|
||||||
|
else { const idx = firstPresetOfFamily(fam); c.effectIndex = idx; c.effectName = idx >= 0 ? EFFECTS.list[idx].name : ''; }
|
||||||
|
savePlaylistState(); renderPlaylist();
|
||||||
|
});
|
||||||
|
cueEl.querySelector('.cue-eff-pre').addEventListener('change', (e) => {
|
||||||
|
const idx = parseInt(e.target.value, 10);
|
||||||
|
if (idx >= 0 && EFFECTS.list[idx]) { c.effectIndex = idx; c.effectName = EFFECTS.list[idx].name; savePlaylistState(); renderPlaylist(); }
|
||||||
|
});
|
||||||
|
cueEl.querySelector('.cue-eff-cur').addEventListener('click', () => { c.effectIndex = EFFECTS.list.indexOf(currentEffect); c.effectName = currentEffect.name; savePlaylistState(); renderPlaylist(); });
|
||||||
cueEl.querySelector('.cue-text').addEventListener('input', (e) => { c.text = e.target.value; li.querySelector('.scene-btn').classList.toggle('active', hasScene(tr)); savePlaylistState(); });
|
cueEl.querySelector('.cue-text').addEventListener('input', (e) => { c.text = e.target.value; li.querySelector('.scene-btn').classList.toggle('active', hasScene(tr)); savePlaylistState(); });
|
||||||
cueEl.querySelector('.cue-tstyle').addEventListener('click', () => { c.textStyle = captureTextStyle(); savePlaylistState(); renderPlaylist(); });
|
cueEl.querySelector('.cue-tstyle').addEventListener('click', () => { c.textStyle = captureTextStyle(); savePlaylistState(); renderPlaylist(); });
|
||||||
const tsc = cueEl.querySelector('.cue-tstyle-clear');
|
const tsc = cueEl.querySelector('.cue-tstyle-clear');
|
||||||
|
|||||||
+7
-3
@@ -332,10 +332,14 @@ djv.onControl(async (m) => {
|
|||||||
case 'playVideoTrack':
|
case 'playVideoTrack':
|
||||||
try {
|
try {
|
||||||
audio.onEnded = () => djv.report({ type: 'trackEnded' });
|
audio.onEnded = () => djv.report({ type: 'trackEnded' });
|
||||||
trackVideo.loop = false; trackVideo.muted = false;
|
|
||||||
trackVideo.src = toFileURL(m.path);
|
|
||||||
trackVideo.classList.add('show');
|
|
||||||
audio.attachVideo(trackVideo);
|
audio.attachVideo(trackVideo);
|
||||||
|
trackVideo.loop = false; trackVideo.muted = false;
|
||||||
|
// Force a fresh start even when re-selecting the same file:
|
||||||
|
// reassigning the same src may not reload, so pause + load() resets it.
|
||||||
|
trackVideo.pause();
|
||||||
|
trackVideo.src = toFileURL(m.path);
|
||||||
|
trackVideo.load();
|
||||||
|
trackVideo.classList.add('show');
|
||||||
await trackVideo.play();
|
await trackVideo.play();
|
||||||
hideHint();
|
hideHint();
|
||||||
djv.report({ type: 'playState', playing: true });
|
djv.report({ type: 'playState', playing: true });
|
||||||
|
|||||||
+4
-2
@@ -372,8 +372,10 @@ textarea.textarea-lg {
|
|||||||
.scene-editor .cue-head { display: flex; align-items: center; gap: 6px; }
|
.scene-editor .cue-head { display: flex; align-items: center; gap: 6px; }
|
||||||
.scene-editor .cue-at { opacity: 0.5; flex: 0 0 auto; }
|
.scene-editor .cue-at { opacity: 0.5; flex: 0 0 auto; }
|
||||||
.scene-editor .cue-time { width: 60px; flex: 0 0 auto; text-align: center; padding: 6px 4px; font-variant-numeric: tabular-nums; }
|
.scene-editor .cue-time { width: 60px; flex: 0 0 auto; text-align: center; padding: 6px 4px; font-variant-numeric: tabular-nums; }
|
||||||
.scene-editor .cue-eff { flex: 1; min-width: 0; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
.scene-editor .cue-del { flex: 0 0 auto; color: #ff9a9a; margin-left: auto; }
|
||||||
.scene-editor .cue-del { flex: 0 0 auto; color: #ff9a9a; }
|
.scene-editor .cue-eff-fam, .scene-editor .cue-eff-pre { flex: 1; min-width: 0; }
|
||||||
|
.scene-editor .cue-eff-cur { flex: 0 0 auto; }
|
||||||
|
.scene-editor .cue-eff-name { flex: 1; min-width: 0; font-size: 10px; opacity: 0.72; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
.scene-editor .cue-base { flex: 0 0 auto; font-size: 9px; opacity: 0.45; padding: 0 4px; }
|
.scene-editor .cue-base { flex: 0 0 auto; font-size: 9px; opacity: 0.45; padding: 0 4px; }
|
||||||
|
|
||||||
.scene-editor .cue-block { background: rgba(255,255,255,0.035); border-radius: 8px; padding: 7px 8px; margin-top: 7px; }
|
.scene-editor .cue-block { background: rgba(255,255,255,0.035); border-radius: 8px; padding: 7px 8px; margin-top: 7px; }
|
||||||
|
|||||||
Reference in new issue
Block a user