animot-presenter 0.5.13 → 0.5.15
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 +67 -1
- package/dist/cdn/animot-presenter.esm.js +4036 -4015
- package/dist/cdn/animot-presenter.min.js +8 -8
- package/dist/types.d.ts +14 -0
- package/package.json +1 -1
|
@@ -187,6 +187,44 @@
|
|
|
187
187
|
let menuVisible = $state(true);
|
|
188
188
|
let mouseIdleTimer: ReturnType<typeof setTimeout> | null = null;
|
|
189
189
|
|
|
190
|
+
// Single <audio> element for per-slide narration playback. Only used
|
|
191
|
+
// when `project.settings.narrationEnabled` is true. Lazy-allocated so
|
|
192
|
+
// decks without narration don't pay for it.
|
|
193
|
+
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
|
+
function playNarrationForSlide(index: number) {
|
|
200
|
+
if (!project?.settings?.narrationEnabled) return;
|
|
201
|
+
const slide = project?.slides?.[index];
|
|
202
|
+
if (narrationAudio) {
|
|
203
|
+
narrationAudio.pause();
|
|
204
|
+
narrationAudio.currentTime = 0;
|
|
205
|
+
}
|
|
206
|
+
const src = slide?.narration?.src;
|
|
207
|
+
if (!src) return;
|
|
208
|
+
if (!narrationAudio) narrationAudio = new Audio();
|
|
209
|
+
narrationAudio.src = src;
|
|
210
|
+
// First play() may reject under autoplay policy. We track the
|
|
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
|
+
);
|
|
217
|
+
}
|
|
218
|
+
function tryUnlockNarrationOnGesture() {
|
|
219
|
+
if (narrationPendingGesture) {
|
|
220
|
+
playNarrationForSlide(currentSlideIndex);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function stopNarration() {
|
|
224
|
+
if (narrationAudio) { narrationAudio.pause(); narrationAudio = null; }
|
|
225
|
+
narrationPendingGesture = false;
|
|
226
|
+
}
|
|
227
|
+
|
|
190
228
|
const slides = $derived(project?.slides ?? []);
|
|
191
229
|
const currentSlide = $derived(slides[currentSlideIndex]);
|
|
192
230
|
const isCinemaMode = $derived(project?.mode === 'cinema');
|
|
@@ -547,6 +585,12 @@
|
|
|
547
585
|
const targetSlide = slides[targetIndex];
|
|
548
586
|
clearAllTypewriterAnimations();
|
|
549
587
|
cancelMotionPathLoops();
|
|
588
|
+
// Trigger per-slide narration. No-op when `narrationEnabled` is off
|
|
589
|
+
// or the slide has no narration clip. First call may be blocked by
|
|
590
|
+
// the browser's autoplay policy; the deck consumer should suppress
|
|
591
|
+
// `autoplay` when narration is on so the viewer's first nav-click
|
|
592
|
+
// (which gets us here) acts as the unlocking gesture.
|
|
593
|
+
playNarrationForSlide(targetIndex);
|
|
550
594
|
const transition = targetSlide.transition;
|
|
551
595
|
const duration = durationOverride ?? transition.duration;
|
|
552
596
|
transitionDurationMs = duration;
|
|
@@ -1106,6 +1150,11 @@
|
|
|
1106
1150
|
await loadCodeHighlights();
|
|
1107
1151
|
loading = false;
|
|
1108
1152
|
if (currentSlide) setTimeout(() => animateMotionPaths(currentSlide!), 300);
|
|
1153
|
+
// First-slide narration. Browsers may block this until the
|
|
1154
|
+
// viewer interacts; downstream code (e.g. share-link page)
|
|
1155
|
+
// should already disable autoplay when narration is on so the
|
|
1156
|
+
// click that starts the deck doubles as the audio unlock.
|
|
1157
|
+
playNarrationForSlide(currentSlideIndex);
|
|
1109
1158
|
if (autoplay) isAutoplay = true;
|
|
1110
1159
|
} catch (e: any) { error = e.message; loading = false; }
|
|
1111
1160
|
}
|
|
@@ -1123,7 +1172,24 @@
|
|
|
1123
1172
|
});
|
|
1124
1173
|
if (containerEl) resizeObserver.observe(containerEl);
|
|
1125
1174
|
resetMouseIdleTimer();
|
|
1126
|
-
|
|
1175
|
+
|
|
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
|
+
return () => {
|
|
1185
|
+
resizeObserver?.disconnect();
|
|
1186
|
+
clearAutoplayTimer();
|
|
1187
|
+
clearAllTypewriterAnimations();
|
|
1188
|
+
if (mouseIdleTimer) clearTimeout(mouseIdleTimer);
|
|
1189
|
+
stopNarration();
|
|
1190
|
+
document.removeEventListener('pointerdown', tryUnlockNarrationOnGesture, { capture: true });
|
|
1191
|
+
document.removeEventListener('keydown', tryUnlockNarrationOnGesture, { capture: true });
|
|
1192
|
+
};
|
|
1127
1193
|
});
|
|
1128
1194
|
|
|
1129
1195
|
// Watch for prop changes
|