diff --git a/client/public/version.js b/client/public/version.js index bc2eef4..2ebe1d2 100644 --- a/client/public/version.js +++ b/client/public/version.js @@ -1,5 +1,5 @@ // Maintainer-controlled web client version. // Format: YYYY.MM.DD Rn (example: 2026.02.20 R2) -window.CHGRID_WEB_VERSION = "2026.03.01 R331"; +window.CHGRID_WEB_VERSION = "2026.03.01 R332"; // Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid. window.CHGRID_TIME_ZONE = "America/Detroit"; diff --git a/client/src/audio/itemEmitRuntime.ts b/client/src/audio/itemEmitRuntime.ts index 2de05d6..aa133b2 100644 --- a/client/src/audio/itemEmitRuntime.ts +++ b/client/src/audio/itemEmitRuntime.ts @@ -227,6 +227,7 @@ export class ItemEmitRuntime { element.playbackRate = initialRates.playbackRate; const initialDelaySeconds = resolveEmitInitialDelaySeconds(item); const loopDelaySeconds = resolveEmitLoopDelaySeconds(item); + element.loop = loopDelaySeconds <= 0; const resumeState = this.resumeStateByItemId.get(item.id); const matchingResumeState = resumeState && resumeState.soundUrl === soundUrl ? resumeState : null; const onEnded = () => { @@ -387,7 +388,17 @@ export class ItemEmitRuntime { } const nextRates = resolveEmitRates(item); output.initialDelaySeconds = resolveEmitInitialDelaySeconds(item); - output.loopDelaySeconds = resolveEmitLoopDelaySeconds(item); + const nextLoopDelaySeconds = resolveEmitLoopDelaySeconds(item); + output.loopDelaySeconds = nextLoopDelaySeconds; + const shouldLoop = nextLoopDelaySeconds <= 0; + if (output.element.loop !== shouldLoop) { + output.element.loop = shouldLoop; + if (shouldLoop) { + // Returning to native loop mode should clear delayed restart scheduling. + this.nextEmitStartAtMs.delete(itemId); + this.tryStartEmitPlayback(itemId, output.element); + } + } setElementPreservesPitch(output.element, nextRates.preservePitch); const nextPlaybackRate = nextRates.playbackRate; if (Math.abs(output.element.playbackRate - nextPlaybackRate) > 0.001) {