animot-presenter 0.5.15 → 0.5.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AnimotPresenter.svelte +31 -38
- package/dist/cdn/animot-presenter.esm.js +3175 -3178
- package/dist/cdn/animot-presenter.min.js +8 -8
- package/package.json +1 -1
|
@@ -190,12 +190,14 @@
|
|
|
190
190
|
// Single <audio> element for per-slide narration playback. Only used
|
|
191
191
|
// when `project.settings.narrationEnabled` is true. Lazy-allocated so
|
|
192
192
|
// decks without narration don't pay for it.
|
|
193
|
+
//
|
|
194
|
+
// Narration is bound to the deck's PLAY state (`isAutoplay`), not to
|
|
195
|
+
// arbitrary page clicks: the user pressing the play button is what
|
|
196
|
+
// starts narration, the pause button stops it. This keeps multiple
|
|
197
|
+
// decks on a page from playing each other's audio when one is clicked,
|
|
198
|
+
// and keeps autoplay-blocked browsers from silently doing nothing —
|
|
199
|
+
// the user's click on the play control is itself the unlocking gesture.
|
|
193
200
|
let narrationAudio: HTMLAudioElement | null = null;
|
|
194
|
-
// Set when the most recent play() was rejected by the browser autoplay
|
|
195
|
-
// policy. The first user gesture clears it and replays the current
|
|
196
|
-
// slide's clip — without this a single-slide deck would never play
|
|
197
|
-
// narration, since there's no navigation event to retry on.
|
|
198
|
-
let narrationPendingGesture = false;
|
|
199
201
|
function playNarrationForSlide(index: number) {
|
|
200
202
|
if (!project?.settings?.narrationEnabled) return;
|
|
201
203
|
const slide = project?.slides?.[index];
|
|
@@ -207,22 +209,13 @@
|
|
|
207
209
|
if (!src) return;
|
|
208
210
|
if (!narrationAudio) narrationAudio = new Audio();
|
|
209
211
|
narrationAudio.src = src;
|
|
210
|
-
|
|
211
|
-
// rejection so a subsequent user gesture (handled below) retries
|
|
212
|
-
// once instead of silently giving up.
|
|
213
|
-
narrationAudio.play().then(
|
|
214
|
-
() => { narrationPendingGesture = false; },
|
|
215
|
-
() => { narrationPendingGesture = true; }
|
|
216
|
-
);
|
|
212
|
+
narrationAudio.play().catch(() => {});
|
|
217
213
|
}
|
|
218
|
-
function
|
|
219
|
-
if (
|
|
220
|
-
playNarrationForSlide(currentSlideIndex);
|
|
221
|
-
}
|
|
214
|
+
function pauseNarration() {
|
|
215
|
+
if (narrationAudio) narrationAudio.pause();
|
|
222
216
|
}
|
|
223
217
|
function stopNarration() {
|
|
224
218
|
if (narrationAudio) { narrationAudio.pause(); narrationAudio = null; }
|
|
225
|
-
narrationPendingGesture = false;
|
|
226
219
|
}
|
|
227
220
|
|
|
228
221
|
const slides = $derived(project?.slides ?? []);
|
|
@@ -585,12 +578,10 @@
|
|
|
585
578
|
const targetSlide = slides[targetIndex];
|
|
586
579
|
clearAllTypewriterAnimations();
|
|
587
580
|
cancelMotionPathLoops();
|
|
588
|
-
// Trigger per-slide narration
|
|
589
|
-
//
|
|
590
|
-
//
|
|
591
|
-
|
|
592
|
-
// (which gets us here) acts as the unlocking gesture.
|
|
593
|
-
playNarrationForSlide(targetIndex);
|
|
581
|
+
// Trigger per-slide narration only while the deck is actively
|
|
582
|
+
// playing. Manual nav while paused stays silent — narration is
|
|
583
|
+
// bound to the play button, not to every interaction.
|
|
584
|
+
if (isAutoplay) playNarrationForSlide(targetIndex);
|
|
594
585
|
const transition = targetSlide.transition;
|
|
595
586
|
const duration = durationOverride ?? transition.duration;
|
|
596
587
|
transitionDurationMs = duration;
|
|
@@ -1150,11 +1141,9 @@
|
|
|
1150
1141
|
await loadCodeHighlights();
|
|
1151
1142
|
loading = false;
|
|
1152
1143
|
if (currentSlide) setTimeout(() => animateMotionPaths(currentSlide!), 300);
|
|
1153
|
-
//
|
|
1154
|
-
//
|
|
1155
|
-
//
|
|
1156
|
-
// click that starts the deck doubles as the audio unlock.
|
|
1157
|
-
playNarrationForSlide(currentSlideIndex);
|
|
1144
|
+
// Narration starts via the play-state effect below — not on
|
|
1145
|
+
// mount. That way the user's click on Play is the gesture
|
|
1146
|
+
// that unlocks audio, and a paused deck stays silent.
|
|
1158
1147
|
if (autoplay) isAutoplay = true;
|
|
1159
1148
|
} catch (e: any) { error = e.message; loading = false; }
|
|
1160
1149
|
}
|
|
@@ -1173,25 +1162,29 @@
|
|
|
1173
1162
|
if (containerEl) resizeObserver.observe(containerEl);
|
|
1174
1163
|
resetMouseIdleTimer();
|
|
1175
1164
|
|
|
1176
|
-
// First-gesture retry for narration playback. Browsers block
|
|
1177
|
-
// audio.play() until the user has interacted; without this hook
|
|
1178
|
-
// a single-slide deck would never play its narration since there's
|
|
1179
|
-
// no slide change to retry on. Attached on document so any click
|
|
1180
|
-
// or key press in the page acts as the unlocking gesture.
|
|
1181
|
-
document.addEventListener('pointerdown', tryUnlockNarrationOnGesture, { capture: true });
|
|
1182
|
-
document.addEventListener('keydown', tryUnlockNarrationOnGesture, { capture: true });
|
|
1183
|
-
|
|
1184
1165
|
return () => {
|
|
1185
1166
|
resizeObserver?.disconnect();
|
|
1186
1167
|
clearAutoplayTimer();
|
|
1187
1168
|
clearAllTypewriterAnimations();
|
|
1188
1169
|
if (mouseIdleTimer) clearTimeout(mouseIdleTimer);
|
|
1189
1170
|
stopNarration();
|
|
1190
|
-
document.removeEventListener('pointerdown', tryUnlockNarrationOnGesture, { capture: true });
|
|
1191
|
-
document.removeEventListener('keydown', tryUnlockNarrationOnGesture, { capture: true });
|
|
1192
1171
|
};
|
|
1193
1172
|
});
|
|
1194
1173
|
|
|
1174
|
+
// Narration follows the deck's play/pause state. The play button click
|
|
1175
|
+
// flips `isAutoplay` true → this effect fires → audio starts. The
|
|
1176
|
+
// click itself is the user gesture that unlocks the browser audio
|
|
1177
|
+
// context. Pause/stop turns it back off. Each presenter instance
|
|
1178
|
+
// scopes its own audio, so multiple decks on a page never overlap.
|
|
1179
|
+
$effect(() => {
|
|
1180
|
+
if (!project?.settings?.narrationEnabled) return;
|
|
1181
|
+
if (isAutoplay) {
|
|
1182
|
+
playNarrationForSlide(currentSlideIndex);
|
|
1183
|
+
} else {
|
|
1184
|
+
pauseNarration();
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1195
1188
|
// Watch for prop changes
|
|
1196
1189
|
$effect(() => { if (data) { project = data; } });
|
|
1197
1190
|
</script>
|