From 2f2a095e4cd864ef4ca76db3ad7dfff3d04356b5 Mon Sep 17 00:00:00 2001 From: Byron Gamatos Date: Wed, 15 Jul 2026 12:39:25 +0200 Subject: [PATCH] fix(venue): fly in once per set, not before every song (#978) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tester, mid-gig: "the second song in the gig started when the first one ended. But it showed the flyover intro again." The flyover is arriving at the venue, and you arrive once. #968 stopped it replaying on an arrangement SWITCH (same filename), but a gig's song 2 is a genuinely different file, so it took the full-teardown path and played the arrival flyover again — the camera flew in from the back of the room before every track of the set. The play queue now answers isContinuation(): false for the first song of a set (or a standalone play — an arrival), true for song 2..N. onSongLoaded carries the room over to the new song's loop on a continuation, and only a real arrival plays the intro. Verified on the built AppImage: isContinuation goes false (song 1) -> true (song 2) across an advance, and song 2 no longer flies in. Also confirmed NOT a bug, same session: "didn't show the author for the second song." The credits card shows on a queue advance whenever the song carries authors — reproduced with a song that has them as the advanced-to track. The tester's song 2 simply had no `authors:` metadata (most auto-converted feedpaks don't). No code change. Tests: isContinuation across start/advance/clear, and that onSongLoaded gates the flyover on the continuation check. Both fail on pre-fix source. JS 1214/1214. --- static/app.js | 5 +++++ static/v3/venue-crowd.js | 22 +++++++++++++++++++++- tests/js/play_queue_peek.test.js | 17 +++++++++++++++++ tests/js/venue_scope.test.js | 19 +++++++++++++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/static/app.js b/static/app.js index 3a72d42..8d60608 100644 --- a/static/app.js +++ b/static/app.js @@ -1384,6 +1384,11 @@ window.feedBack.playQueue = (function () { } return { start: start, advance: advance, hasNext: hasNext, active: active, clear: clear, + // True when the current song is a queue ADVANCE (song 2..N of a set), + // false for its first song or a standalone play. The venue uses this to + // fly in once on arrival at the set, then continue the room between + // songs instead of replaying the arrival flyover every track. + isContinuation: function () { return active() && idx > 0; }, // One-shot: true iff _play just kicked off this playSong. Consumed on // read so a later MANUAL play still clears the queue. playSong calls this // instead of trusting options.fromQueue to survive the wrapper chain. diff --git a/static/v3/venue-crowd.js b/static/v3/venue-crowd.js index fbb2c5d..a0a8506 100644 --- a/static/v3/venue-crowd.js +++ b/static/v3/venue-crowd.js @@ -529,7 +529,27 @@ _loadingLoop = null; _fadingLoop = null; if (_venueActive && _manifest) { - if (!playIntro()) showLoop(machine.current, FADE_MS); + // The flyover is ARRIVING at the venue, and you arrive once. Songs + // 2..N of a set (a gig / album / playlist) are a NEW song but the + // SAME arrival — the camera should not fly in from the back of the + // room before every track (tester: "it showed the flyover intro + // again" on a gig's second song). Continue the room to the new song's + // loop; only a first-song / standalone arrival flies in. + if (_isSetContinuation()) showLoop(machine.current, FADE_MS); + else if (!playIntro()) showLoop(machine.current, FADE_MS); + } + } + + // Is this song load a continuation of a play queue (a set already in + // progress), rather than an arrival? True for song 2..N of a gig/album/ + // playlist. The queue owns the answer; treat any error / absent queue as + // "not a continuation" so a standalone play still flies in. + function _isSetContinuation() { + try { + const q = window.feedBack && window.feedBack.playQueue; + return !!(q && typeof q.isContinuation === 'function' && q.isContinuation()); + } catch (_) { + return false; } } diff --git a/tests/js/play_queue_peek.test.js b/tests/js/play_queue_peek.test.js index 57d1e66..0abe2ca 100644 --- a/tests/js/play_queue_peek.test.js +++ b/tests/js/play_queue_peek.test.js @@ -109,3 +109,20 @@ test('fromQueue in options still works on its own (in-band path)', () => { clearGuard(win, { fromQueue: true }); assert.strictEqual(q.active(), true, 'options.fromQueue alone must still keep the queue'); }); + +// isContinuation(): true for song 2..N of a set, false for the first song / a +// standalone play. The venue uses it to fly in once on arrival, then carry the +// room between songs instead of replaying the arrival flyover every track +// (tester: "it showed the flyover intro again" on a gig's second song). +test('isContinuation is false on the first song, true after advancing', () => { + const { q } = makeQueue(); + assert.strictEqual(q.isContinuation(), false, 'idle queue is not a continuation'); + q.start(['a.sloppak', 'b.sloppak', 'c.sloppak'], { source: 'gig' }); + assert.strictEqual(q.isContinuation(), false, 'the FIRST song of a set is an arrival, not a continuation'); + q.advance(); + assert.strictEqual(q.isContinuation(), true, 'song 2 is a continuation — no re-flyover'); + q.advance(); + assert.strictEqual(q.isContinuation(), true, 'song 3 too'); + q.clear(); + assert.strictEqual(q.isContinuation(), false, 'a cleared queue is not a continuation'); +}); diff --git a/tests/js/venue_scope.test.js b/tests/js/venue_scope.test.js index af9564b..79f9ba3 100644 --- a/tests/js/venue_scope.test.js +++ b/tests/js/venue_scope.test.js @@ -117,3 +117,22 @@ test('a throwing document does not take the venue down with it', () => { global.document = prev; } }); + +// The arrival flyover must NOT replay for songs 2..N of a set. onSongLoaded +// consults the play queue: a continuation (gig/album/playlist song 2+) carries +// the room over with a loop crossfade, only an arrival plays the intro. +test('a set continuation carries the room over instead of re-flying-in', () => { + const fs = require('node:fs'); + const path = require('node:path'); + const src = fs.readFileSync(path.join(__dirname, '..', '..', 'static', 'v3', 'venue-crowd.js'), 'utf8'); + const start = src.indexOf('function onSongLoaded('); + const open = src.indexOf('{', src.indexOf(')', start)); + let depth = 1, i = open + 1; + while (i < src.length && depth > 0) { const ch = src[i]; if (ch === '{') depth++; else if (ch === '}') depth--; i++; } + const fn = src.slice(start, i); + const contIdx = fn.search(/_isSetContinuation\s*\(\s*\)/); + const introIdx = fn.search(/playIntro\s*\(/); + assert.ok(contIdx !== -1, 'onSongLoaded must consult the set-continuation signal'); + assert.ok(introIdx !== -1, 'the intro must still exist for a real arrival'); + assert.ok(contIdx < introIdx, 'the continuation check must gate the flyover — a set song 2+ must not fly in'); +});