diff --git a/cmd/qrunweb/static/index.html b/cmd/qrunweb/static/index.html
index 4872ac8..b0d7fb5 100644
--- a/cmd/qrunweb/static/index.html
+++ b/cmd/qrunweb/static/index.html
@@ -19,8 +19,6 @@
--light-bg: rgba(42, 10, 42, 0.55);
--video-color: #4d4;
--video-bg: rgba(10, 42, 10, 0.55);
- --overlay-color: #2cb;
- --overlay-bg: rgba(10, 34, 34, 0.55);
--audio-color: #58f;
--audio-bg: rgba(10, 10, 42, 0.55);
--delay-color: #999;
@@ -109,7 +107,6 @@ header h1 { font-size: 16px; font-weight: 600; letter-spacing: 0.05em; }
}
.block.light { color: var(--light-color); border-color: var(--light-color); background: var(--light-bg); }
.block.video { color: var(--video-color); border-color: var(--video-color); background: var(--video-bg); }
-.block.overlay { color: var(--overlay-color); border-color: var(--overlay-color); background: var(--overlay-bg); }
.block.audio { color: var(--audio-color); border-color: var(--audio-color); background: var(--audio-bg); }
.block.delay { color: var(--delay-color); border-color: var(--delay-color); background: var(--delay-bg); }
@@ -169,27 +166,23 @@ function render(data) {
const CUE_TRACK = '_cue';
const trackIds = [CUE_TRACK, ...data.tracks.map(t => t.id)];
- function ref(block, signal) { return block + ':' + signal; }
+ function ref(b, s) { return b + ':' + s; }
- const triggerTargetSet = new Set();
const triggerSourceSet = new Set();
- data.triggers.forEach(t => {
- triggerSourceSet.add(ref(t.source.block, t.source.signal));
- t.targets.forEach(tgt => triggerTargetSet.add(ref(tgt.block, tgt.hook)));
- });
-
- const trackBlocks = new Map();
- trackIds.forEach(id => trackBlocks.set(id, []));
- data.blocks.forEach(b => {
- const tid = b.type === 'cue' ? CUE_TRACK : b.track;
- trackBlocks.get(tid).push(b.id);
- });
+ data.triggers.forEach(t => triggerSourceSet.add(ref(t.source.block, t.source.signal)));
function blockTrack(bid) {
const b = blockMap.get(bid);
return b.type === 'cue' ? CUE_TRACK : b.track;
}
+ function isChain(trigger) {
+ const src = trigger.source;
+ if (src.signal !== 'END' || trigger.targets.length !== 1) return false;
+ return trigger.targets[0].hook === 'START' &&
+ blockTrack(src.block) === blockTrack(trigger.targets[0].block);
+ }
+
document.getElementById('header-status').innerHTML =
'QLab Connected' +
'Show: ' + data.show + '' +
@@ -198,15 +191,9 @@ function render(data) {
const active = new Map();
const pendingEnds = new Set();
const pendingTitles = new Set();
+ const titled = new Set();
const rows = [];
- function nextInTrack(bid) {
- const tid = blockTrack(bid);
- const seq = trackBlocks.get(tid);
- const idx = seq.indexOf(bid);
- return idx < seq.length - 1 ? seq[idx + 1] : null;
- }
-
function mid(tid) {
const a = active.get(tid);
return a ? { blockId: a, segment: 'mid' } : { empty: true };
@@ -216,8 +203,8 @@ function render(data) {
rows.push({ cells, rowClass: rowClass || '' });
}
- function canAutoStart(bid) {
- return !triggerTargetSet.has(ref(bid, 'START')) && !triggerSourceSet.has(ref(bid, 'START'));
+ function wantTitle(bid) {
+ if (!titled.has(bid)) pendingTitles.add(bid);
}
function emitTitles() {
@@ -227,40 +214,15 @@ function render(data) {
return t ? { blockId: t, segment: 'mid', title: blockMap.get(t).name } : mid(tid);
});
addRow(cells);
+ pendingTitles.forEach(bid => titled.add(bid));
pendingTitles.clear();
}
- function doAutoStarts(ended) {
- const starts = [];
- ended.forEach(bid => {
- const next = nextInTrack(bid);
- if (next && canAutoStart(next)) starts.push(next);
- });
- if (starts.length === 0) return;
- const cells = trackIds.map(tid => {
- const s = starts.find(bid => blockTrack(bid) === tid);
- if (s) {
- active.set(tid, s);
- pendingTitles.add(s);
- return { blockId: s, segment: 'start', event: 'START' };
- }
- return mid(tid);
- });
- addRow(cells);
- }
-
- function flush(upcomingSrc) {
+ function flush() {
emitTitles();
if (pendingEnds.size === 0) return;
- const holdBack = new Set();
- if (upcomingSrc) {
- for (const bid of pendingEnds) {
- if (upcomingSrc.block === bid && upcomingSrc.signal === 'END') holdBack.add(bid);
- }
- }
-
- const toEnd = [...pendingEnds].filter(bid => !holdBack.has(bid));
+ const toEnd = [...pendingEnds].filter(bid => !triggerSourceSet.has(ref(bid, 'END')));
if (toEnd.length === 0) return;
const cells = trackIds.map(tid => {
@@ -269,31 +231,36 @@ function render(data) {
});
addRow(cells);
toEnd.forEach(bid => { active.delete(blockTrack(bid)); pendingEnds.delete(bid); });
-
- doAutoStarts(toEnd);
- emitTitles();
}
function processTrigger(trigger) {
const src = trigger.source;
const isCue = src.signal === 'GO';
- flush(src);
+ flush();
- if (!isCue) {
+ if (isChain(trigger)) {
const tid = blockTrack(src.block);
- if (src.signal === 'START') {
- const cur = active.get(tid);
- if (cur && cur !== src.block) {
- pendingEnds.delete(cur);
- const cells = trackIds.map(t =>
- t === tid ? { blockId: cur, segment: 'end', event: 'END' } : mid(t)
- );
- addRow(cells);
- active.delete(tid);
- }
- active.set(tid, src.block);
+ const tgt = trigger.targets[0].block;
+ if (active.get(tid) === src.block || pendingEnds.has(src.block)) {
+ pendingEnds.delete(src.block);
+ const endCells = trackIds.map(t =>
+ t === tid ? { blockId: src.block, segment: 'end', event: 'END' } : mid(t)
+ );
+ addRow(endCells);
+ active.delete(tid);
}
+ active.set(tid, tgt);
+ wantTitle(tgt);
+ const startCells = trackIds.map(t =>
+ t === tid ? { blockId: tgt, segment: 'start', event: 'START' } : mid(t)
+ );
+ addRow(startCells);
+ return;
+ }
+
+ if (!isCue && src.signal === 'START' && active.get(blockTrack(src.block)) !== src.block) {
+ active.set(blockTrack(src.block), src.block);
}
emitTitles();
@@ -310,7 +277,8 @@ function render(data) {
return { cueLabel: blockMap.get(src.block).name };
if (!isCue && blockTrack(src.block) === tid) {
- const seg = src.signal === 'START' ? 'start' : src.signal === 'END' ? 'end' : 'mid';
+ const alreadyActive = active.get(tid) === src.block;
+ const seg = alreadyActive ? 'mid' : src.signal === 'START' ? 'start' : src.signal === 'END' ? 'end' : 'mid';
return { blockId: src.block, segment: seg, event: src.signal, isSignal: true };
}
@@ -320,7 +288,7 @@ function render(data) {
const isHook = !isCue;
if (hook === 'START') {
active.set(tid, bid);
- pendingTitles.add(bid);
+ wantTitle(bid);
return { blockId: bid, segment: 'start', event: 'START', isHook };
}
if (hook === 'END') {
@@ -339,21 +307,19 @@ function render(data) {
addRow(cells, rowClass);
directEnds.forEach(bid => active.delete(blockTrack(bid)));
- doAutoStarts(directEnds);
if (!isCue) {
- if (src.signal === 'START') pendingTitles.add(src.block);
+ if (src.signal === 'START' && !pendingTitles.has(src.block)) wantTitle(src.block);
if (src.signal === 'FADE_OUT') pendingEnds.add(src.block);
if (src.signal === 'END') {
active.delete(blockTrack(src.block));
pendingEnds.delete(src.block);
- doAutoStarts([src.block]);
}
}
}
data.triggers.forEach(t => processTrigger(t));
- flush(null);
+ flush();
emitTitles();
const stillActive = [];
diff --git a/cmd/qrunweb/static/show.json b/cmd/qrunweb/static/show.json
index c04aeb0..f61f5e6 100644
--- a/cmd/qrunweb/static/show.json
+++ b/cmd/qrunweb/static/show.json
@@ -20,10 +20,10 @@
{"id": "block_01kht41b8deg8ae996tzqay0rg", "type": "light", "track": "track_01kht419m1e65bfgqzj28se459", "name": "SC1 Focus"},
{"id": "block_01kht41b9neqf84ap7dm3mey9r", "type": "light", "track": "track_01kht419n7esr929mxvsacsy0k", "name": "SC1 Blue 80%"},
{"id": "block_01kht41bawfbhr38a49tjvyahy", "type": "video", "track": "track_01kht419pbfx2992sjpagc0syy", "name": "Sc1 Projection"},
- {"id": "block_01kht41bc3evybtn5hqnk6p2f7", "type": "overlay", "track": "track_01kht419qdeb1bjm9qe5acas2f", "name": "Lightning Flash"},
+ {"id": "block_01kht41bc3evybtn5hqnk6p2f7", "type": "video", "track": "track_01kht419qdeb1bjm9qe5acas2f", "name": "Lightning Flash"},
{"id": "block_01kht41bdafggbbm8959svkm4n", "type": "audio", "track": "track_01kht419rffhnayzmgqnnkddtz", "name": "Storm Ambience", "loop": true},
{"id": "block_01kht41bejev2997rfps2kpbat", "type": "cue", "name": "Q13 Sc1 Dialog"},
- {"id": "block_01kht41bfsfhkvtvze4rh7k7z6", "type": "overlay", "track": "track_01kht419qdeb1bjm9qe5acas2f", "name": "Wave Overlay"},
+ {"id": "block_01kht41bfsfhkvtvze4rh7k7z6", "type": "video", "track": "track_01kht419qdeb1bjm9qe5acas2f", "name": "Wave Overlay"},
{"id": "block_01kht41bgzfjcrkwrvrrjkqs0f", "type": "light", "track": "track_01kht419m1e65bfgqzj28se459", "name": "Dialog Spots"},
{"id": "block_01kht41bj7ea89xn71nn5h400a", "type": "light", "track": "track_01kht419n7esr929mxvsacsy0k", "name": "Warm 90%"},
{"id": "block_01kht41bkee1n81gnfq6bydmm2", "type": "audio", "track": "track_01kht419rffhnayzmgqnnkddtz", "name": "Dialog Underscore"},
@@ -49,6 +49,12 @@
{"block": "block_01kht41azrenbaxyc2vebv7s4q", "hook": "FADE_OUT"}
]
},
+ {
+ "source": {"block": "block_01kht41azrenbaxyc2vebv7s4q", "signal": "END"},
+ "targets": [
+ {"block": "block_01kht41b5zf4ya2044tczm8tz6", "hook": "START"}
+ ]
+ },
{
"source": {"block": "block_01kht41b4rfnhvk83p9afhp08y", "signal": "GO"},
"targets": [
@@ -57,6 +63,18 @@
{"block": "block_01kht41b29eyes3b6neh4h56mn", "hook": "FADE_OUT"}
]
},
+ {
+ "source": {"block": "block_01kht41b10emgt1gfvg7dy60ws", "signal": "END"},
+ "targets": [
+ {"block": "block_01kht41b76ebtraravpknavdm5", "hook": "START"}
+ ]
+ },
+ {
+ "source": {"block": "block_01kht41b76ebtraravpknavdm5", "signal": "END"},
+ "targets": [
+ {"block": "block_01kht41bawfbhr38a49tjvyahy", "hook": "START"}
+ ]
+ },
{
"source": {"block": "block_01kht41bawfbhr38a49tjvyahy", "signal": "START"},
"targets": [
@@ -65,6 +83,12 @@
{"block": "block_01kht41bdafggbbm8959svkm4n", "hook": "START"}
]
},
+ {
+ "source": {"block": "block_01kht41b5zf4ya2044tczm8tz6", "signal": "END"},
+ "targets": [
+ {"block": "block_01kht41b9neqf84ap7dm3mey9r", "hook": "START"}
+ ]
+ },
{
"source": {"block": "block_01kht41b9neqf84ap7dm3mey9r", "signal": "START"},
"targets": [
@@ -86,6 +110,18 @@
{"block": "block_01kht41bfsfhkvtvze4rh7k7z6", "hook": "START"}
]
},
+ {
+ "source": {"block": "block_01kht41b9neqf84ap7dm3mey9r", "signal": "END"},
+ "targets": [
+ {"block": "block_01kht41bj7ea89xn71nn5h400a", "hook": "START"}
+ ]
+ },
+ {
+ "source": {"block": "block_01kht41bdafggbbm8959svkm4n", "signal": "END"},
+ "targets": [
+ {"block": "block_01kht41bkee1n81gnfq6bydmm2", "hook": "START"}
+ ]
+ },
{
"source": {"block": "block_01kht41bkee1n81gnfq6bydmm2", "signal": "START"},
"targets": [
@@ -102,6 +138,18 @@
{"block": "block_01kht41bkee1n81gnfq6bydmm2", "hook": "END"}
]
},
+ {
+ "source": {"block": "block_01kht41bgzfjcrkwrvrrjkqs0f", "signal": "END"},
+ "targets": [
+ {"block": "block_01kht41bnxfme95cspptf87j9b", "hook": "START"}
+ ]
+ },
+ {
+ "source": {"block": "block_01kht41bj7ea89xn71nn5h400a", "signal": "END"},
+ "targets": [
+ {"block": "block_01kht41bq2ekwswsf51b0h1bd2", "hook": "START"}
+ ]
+ },
{
"source": {"block": "block_01kht41bfsfhkvtvze4rh7k7z6", "signal": "END"},
"targets": [