Smooth realtime display frame pacing
This commit is contained in:
+17
-14
@@ -26,7 +26,6 @@ let lifecycleHandlersBound = false;
|
||||
let recoverTimer = null;
|
||||
let lastHardRecoverAt = 0;
|
||||
let pendingPhoenixFrame = null;
|
||||
let phoenixPacketRaf = 0;
|
||||
let phoenixPacketBusy = false;
|
||||
|
||||
const RMS_RING = { L: new Float32Array(512), R: new Float32Array(512), i: 0, n: 0 };
|
||||
@@ -125,8 +124,6 @@ function closePhoenixSocket() {
|
||||
if (phoenixVisualsRaf) cancelAnimationFrame(phoenixVisualsRaf);
|
||||
phoenixVisualsRaf = 0;
|
||||
pendingPhoenixFrame = null;
|
||||
if (phoenixPacketRaf) cancelAnimationFrame(phoenixPacketRaf);
|
||||
phoenixPacketRaf = 0;
|
||||
phoenixPacketBusy = false;
|
||||
}
|
||||
|
||||
@@ -432,7 +429,6 @@ async function applyIncomingAudioPacket(env, packet, CONFIG, sampleTs = performa
|
||||
const previousSeq = Number(env.audio.phoenixMetricsSeq || 0);
|
||||
if (Number.isFinite(seq) && seq > 0 && previousSeq > 0 && seq <= previousSeq) return;
|
||||
if (Number.isFinite(seq) && seq > 0) env.audio.phoenixMetricsSeq = seq;
|
||||
env.requestRender?.('audio');
|
||||
env.audio.lastSampleTs = sampleTs;
|
||||
env.audio.alive = true;
|
||||
if (Number.isFinite(Number(d.sampleRate)) && Number(d.sampleRate) > 0) {
|
||||
@@ -487,6 +483,10 @@ async function applyIncomingAudioPacket(env, packet, CONFIG, sampleTs = performa
|
||||
}
|
||||
|
||||
await updateActiveMeters(env, d, CONFIG);
|
||||
// Publish the frame only after every active meter has consumed the packet.
|
||||
// Otherwise the canvas loop can draw between the data update and the meter
|
||||
// update, producing an old frame followed by a visible catch-up step.
|
||||
env.requestRender?.('audio');
|
||||
}
|
||||
|
||||
function buildPhoenixMeterPacket(frame) {
|
||||
@@ -627,23 +627,26 @@ function mergePendingPhoenixFrame(previous, next) {
|
||||
}
|
||||
|
||||
function schedulePhoenixPacketPump(env, CONFIG) {
|
||||
if (phoenixPacketRaf || phoenixPacketBusy || !pendingPhoenixFrame) return;
|
||||
phoenixPacketRaf = requestAnimationFrame(async () => {
|
||||
phoenixPacketRaf = 0;
|
||||
const frame = pendingPhoenixFrame;
|
||||
pendingPhoenixFrame = null;
|
||||
if (!frame) return;
|
||||
phoenixPacketBusy = true;
|
||||
if (phoenixPacketBusy || !pendingPhoenixFrame) return;
|
||||
phoenixPacketBusy = true;
|
||||
void (async () => {
|
||||
try {
|
||||
const packet = buildPhoenixMeterPacket(frame);
|
||||
await applyIncomingAudioPacket(env, packet, CONFIG, performance.now());
|
||||
// Consume the newest packet immediately. Incoming packets are still
|
||||
// coalesced while an asynchronous meter/config update is in progress,
|
||||
// but they no longer wait for a second requestAnimationFrame loop.
|
||||
while (pendingPhoenixFrame) {
|
||||
const frame = pendingPhoenixFrame;
|
||||
pendingPhoenixFrame = null;
|
||||
const packet = buildPhoenixMeterPacket(frame);
|
||||
await applyIncomingAudioPacket(env, packet, CONFIG, performance.now());
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Phoenix packet error:', err);
|
||||
} finally {
|
||||
phoenixPacketBusy = false;
|
||||
if (pendingPhoenixFrame) schedulePhoenixPacketPump(env, CONFIG);
|
||||
}
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
function enqueuePhoenixFrame(env, CONFIG, frame) {
|
||||
|
||||
Reference in New Issue
Block a user