animot-presenter 0.5.14 → 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.
@@ -191,6 +191,11 @@
191
191
  // when `project.settings.narrationEnabled` is true. Lazy-allocated so
192
192
  // decks without narration don't pay for it.
193
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;
194
199
  function playNarrationForSlide(index: number) {
195
200
  if (!project?.settings?.narrationEnabled) return;
196
201
  const slide = project?.slides?.[index];
@@ -202,13 +207,22 @@
202
207
  if (!src) return;
203
208
  if (!narrationAudio) narrationAudio = new Audio();
204
209
  narrationAudio.src = src;
205
- // First play() may reject under autoplay policy; the player's first
206
- // user gesture (pressing play / clicking the deck) unlocks the audio
207
- // context and subsequent slides will work.
208
- narrationAudio.play().catch(() => {});
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
+ }
209
222
  }
210
223
  function stopNarration() {
211
224
  if (narrationAudio) { narrationAudio.pause(); narrationAudio = null; }
225
+ narrationPendingGesture = false;
212
226
  }
213
227
 
214
228
  const slides = $derived(project?.slides ?? []);
@@ -1158,7 +1172,24 @@
1158
1172
  });
1159
1173
  if (containerEl) resizeObserver.observe(containerEl);
1160
1174
  resetMouseIdleTimer();
1161
- return () => { resizeObserver?.disconnect(); clearAutoplayTimer(); clearAllTypewriterAnimations(); if (mouseIdleTimer) clearTimeout(mouseIdleTimer); stopNarration(); };
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
+ };
1162
1193
  });
1163
1194
 
1164
1195
  // Watch for prop changes