Files
James RussoandClaude Opus 4.8 7517f6ac86 feat(slideshow): per-slide autoplay (manual-advance, opt-in) (#1708)
* feat(slideshow): per-slide autoplay (manual-advance, opt-in)

Adds an opt-in `autoplay` flag to slideshow slides: when the presenter lands
on a video slide, its `<video>` plays from the start. The slideshow still
holds and never auto-advances — the presenter clicks Next when ready. This
covers compositions whose own controls can't be clicked (the player renders
the composition pointer-events:none).

Plumbing (done, tested):
- core: `SlideRef.autoplay?: boolean`, parsed + validated in parseSlideshow
  (a non-boolean autoplay rejects the manifest); carried through resolve.
- controller: optional `PlayerPort.playSceneMedia(sceneId)`, fired only on
  forward `enterSlide` for autoplay slides (not resume/back/sync, so the
  audience — which mirrors the presenter's media events — isn't double-driven).
- component: `playSceneDocumentMedia` reaches the same-origin composition
  iframe, finds the scene's `<video>`, and asserts playback; `stopMedia`
  (already wired on slide change) resets it. An autoplay token cancels a
  pending start when the slide changes.
- tests: controller autoplay behavior + parser flag round-trip/validation
  (131 player + 22 core slideshow tests pass).

KNOWN LIMITATION — runtime media-start needs the player media model (@vance):
On current main the clip<->timeline binding from #1601 keeps every clip synced
and *paused* to the held timeline frame, which wins against playSceneMedia's
play() — so the clip does not actually start on main yet (it does on the
pre-#1601 player). The correct fix is a sanctioned "let this clip free-run
while the timeline holds" path in the player/runtime media controller. Flagging
for Vance to wire the start into the #1601 media model (or rebase onto it) when
back. The plumbing above is the stable surface that hook plugs into.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(slideshow): address code-review on per-slide autoplay

- guard playSceneDocumentMedia behind resolveMode() !== "audience": the
  audience mirrors the presenter's media events, so it must not independently
  drive its own copy of the clip.
- drop the per-enter window pointerdown/keydown "gesture retry" listeners,
  which leaked when muted autoplay succeeded without a gesture. The poll already
  re-asserts play(), so a gesture within the window is picked up next tick.
- stop polling once the clip is advancing across two ticks (was re-asserting
  play() for the full window even after playback was confirmed).
- cancel any in-flight autoplay loop on disconnectedCallback (bump the token).
- split the poll into findSceneVideo + stepAutoplay helpers (keeps each small).
- fix the enterSlide comment: autoplay fires from enterSlide (next/prev/
  goToSlide), not resumeSlide (back/backToMain/syncTo).
- parser: isOptionalBoolean type guard instead of a one-off helper; drop `as`
  assertions in the new controller test.

131 player + 22 core slideshow tests pass; lint/format/typecheck/fallow clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(slideshow): autoplay skill guidance + address review nits

Addresses review feedback on #1708:
- skill: document per-slide `autoplay` in the slideshow standalone-harness
  reference — when to use it (video is the slide's primary content, its end is
  the advance cue) vs not (background/ambient loops, footage talked over), per
  Vance's guidance, before merge.
- play() rejection is no longer blanket-swallowed: AbortError (timeline-sync
  seek interrupt) and NotAllowedError (gesture-gated autoplay) are expected and
  ignored; any other rejection is surfaced once via console.warn (Via nit 1).
- clarify in the SlideRef.autoplay doc that it plays the scene's FIRST <video>
  (Via nit 2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:47:54 -07:00
..