Skip to content

Shorts

approved

The feed module drives one portrait player instance through an ordered stream of configurations. This document specifies the feed's behavior precisely; in everything not overridden here, the single player instance behaves exactly as the rest of the specification says.

Relationship to the portrait layout

  • The feed embeds exactly one player instance, portrait layout, and swaps videos by passing the next configuration to the instance's update operation. It never creates a player per video.
  • Why one instance: the browser attaches audio activation to the media element. One unmute gesture keeps sound across swipes only if the same element keeps playing; recreating players would silently re-mute on mobile.
  • The feed refuses landscape configurations at its own boundary, and the player refuses layout changes on update independently (layout is immutable per instance). Two guards, one at each level.

Session start and muted autostart

  • Mounting the feed loads the first configuration into the instance and starts playback immediately, muted, and at the first chapter's start when the video has chapters: the feed skips the branded intro, per client requirement (standalone instances always start at zero). A video without chapters starts at zero. Muted autoplay is what browsers permit without a user gesture; this is the established in-browser shorts convention.
  • While playback is muted and no unmute gesture has happened yet in the session, a "Tap to unmute" button overlays the video (string key in the language catalogs).
  • Activating it unmutes playback and removes the overlay for the rest of the session. The activation is a user gesture on the media element, so the browser's audio grant attaches to that element and survives every subsequent configuration update. This is the load-bearing reason the feed recycles one instance.
  • If the user advances without unmuting, the following videos continue muted and the overlay stays until the first unmute.
  • Muting manually later, through the volume control, is ordinary volume state; the overlay does not return.

Advancing

  • Each feed item occupies one full viewport snap position. A swipe (touch), a drag or wheel gesture (desktop), or the arrow keys (desktop) move between snap positions.
  • Inside the feed, ArrowUp and ArrowDown belong to feed navigation and take precedence over the player's volume hotkeys. Editable fields and arrow-consuming widgets, including text inputs, sliders, spin buttons, menus, listboxes, vertical tab lists, combo boxes, modal dialogs, and scrollable regions with real vertical overflow, retain their own arrow-key behavior. Horizontal tab lists keep Left, Right, Home, and End, while Up and Down remain feed navigation. Focused buttons and links do not block feed navigation because their keyboard behavior does not use the arrow keys.
  • The feed accepts internal ltp-presentation-navigation requests dispatched on its presentation-stage scroller by eligible portalled surfaces. It handles them exactly like ArrowUp or ArrowDown received on the scroller. A direct arrow or presentation request at the corresponding feed boundary remains an unconsumed no-op.
  • The feed scroller is programmatically focusable but is not a sequential tab stop. A hosting page focuses it when desktop arrow navigation should be available immediately.
  • When a move commits to a new position, the feed passes that item's configuration to the instance's update operation. The new video starts playing immediately at its starting position (the first chapter's start when it has chapters, zero otherwise); the session's audio state carries over unchanged.
  • Moving backward is symmetric: the feed keeps its ordered list and an index; update works in both directions.
  • During a transition the feed shows the incoming item's poster until the first frame is ready at the video's starting position. For a nonzero chapter start, the poster stays through the corresponding seek; for a zero start, it stays until the first frame is ready. The player's standard loading and buffering rules apply within the instance.
  • Preloading: at minimum, the next item's poster is prefetched. Deeper media preloading is an implementation decision, documented here when made.

Looping

  • When a feed video ends, the feed restarts it from its starting position (the first chapter's start when it has chapters, zero otherwise, the same rule as feed start) and keeps playing: seamless, no replay affordance, no visible state change. The intro never appears inside the feed, on any cycle. The standalone ended state never appears inside the feed.
  • Looping repeats until the user advances, following the established shorts convention: the major short-video products all loop by default.
  • The instance still emits its normal playback events on each cycle, so a hosting page can observe loops.

Lock screen and media keys

  • The feed registers the previous track and next track actions of the Media Session API and maps them to feed navigation: previous returns to the prior short, next advances to the following one, with the same semantics as a swipe.
  • Combined with the core player's lock screen integration, this makes the feed navigable from the lock screen like a playlist: artwork, author, and title per video, transport controls, and track switching.
  • Standalone instances never register these two actions, so no track buttons appear for them.

Shared audio

  • Mute state and volume persist across videos within the feed session, by construction of the single-instance model. No cross-instance audio coordination exists because there is only one instance.
  • User changes write through to the stored user state like anywhere else, with one exception at the start: the feed always begins muted regardless of the stored mute state, because muted autostart is browser autoplay policy, not a preference. The tap-to-unmute gesture reinstates sound, and from that moment the stored volume applies.

Context panel across swipes

  • The context panel refreshes automatically on advance because it follows the instance's current configuration (see the context panel capability). An open view stays open when the next video has data for it, and closes when it does not.
  • A docked panel stays in one fixed feed-stage position throughout native scrolling and a committed move. Advancing updates the existing panel's contents in place; it does not transfer through the centered dialog presentation or recreate the panel.

Wide-host presentation

  • The feed scroller is an inline-size query container. On a host narrower than 768px, each player and poster fills its snap item exactly as in the full-bleed mobile design.
  • The scroller declares data-ltp-presentation-stage, allowing capabilities to use its full rectangle without depending on feed class names or snap-item structure.
  • At 768px and wider, each snap item keeps the full host width but uses calc(96% + 8px) of the feed scroller's block size. The centered phone-proportioned player uses 96% of the scroller block size, derives its width from aspect-ratio: 9 / 16, and clips to the shared player radius of 0 0 0 1.875rem, rounding only the bottom-left corner. This leaves the next item's first calc(4% - 8px) visible before scrolling begins, matching the reference feed's following-video preview.
  • The poster uses exactly the same column geometry as the player. It never fills the wide presentation stage independently, because foreign stage content must not block context side-card placement.
  • A hosting surface may provide the feed stage background independently from the player and poster surfaces. The LT website showcase uses its white page surface at wide sizes and the black media surface in the full-bleed mobile presentation.
  • While a context-panel view is open, the wide feed reserves min(56%, 874px) at the player and poster wrappers' inline end. The wrappers re-center within the remaining space, allowing the context capability to dock its side card to the player's right without reducing the scroller's full presentation-stage rectangle. Closing the panel removes the wrapper inset and re-centers the column in the item.
  • If the remaining stage cannot fit the context capability's minimum side card and gap, the capability uses its viewport dialog presentation. Feed advancing and snap-item geometry do not change between the side-card and dialog presentations.

Capabilities inside the feed

  • Every capability follows its normal rules per video: availability re-resolves on each update. A video without chapters shows no chapters trigger; panel triggers appear per view data; share follows its URL dormancy rules.

Strings

The feed owns and declares the muted-autostart overlay string.

KeyEnglish defaultPlaceholdersAppears in
tapToUnmuteTap to unmutenoneMuted-autostart overlay

Feed contract

  • The framework-light feed module is published from @latest-thinking/lt-player/feed. It exports one runtime function:

    ts
    createFeed(
      host: HTMLElement,
      configurations: readonly PlayerConfig[],
    ): FeedHandle
  • FeedHandle contains exactly previous, next, goTo, getIndex, destroy, and on. It does not expose the underlying player handle or media engine.

  • on forwards every page API event and payload unchanged. It additionally accepts indexChange, whose payload is { index: number }, emitted once after a move commits and the configuration has been applied.

  • goTo ignores non-finite values, truncates finite values to an integer, and clamps the result to the available index range. previous at the first item, next at the last item, and navigation to the committed item are silent no-ops with no indexChange.

  • destroy is idempotent. Afterwards, navigation and subscriptions are silent no-ops, on returns an inert unsubscribe function, and getIndex retains the final committed index.

  • An empty configuration array throws synchronously. A landscape configuration throws synchronously with its array index before the host is changed or a player is created.

  • A second live feed on the same host throws and leaves the original feed untouched. Destroying the owner releases the host for a later feed.

  • The feed module consumes an ordered array of complete player configurations (per the config contract), advanced by index in both directions.

  • The showcase builds that array from data/feeds/shorts.json resolved against the video documents (see the demo data document); a real site builds it from its own service.

  • Paged or streamed sources are a future extension of the feed module's input; the player instance is unaffected either way, since it only ever receives one configuration at a time.

Data sources

  • The showcase feeds it from the versioned demo data folder, as a dummy feed.
  • The Latest Thinking website will feed it from its own service. The source is pluggable and lives outside the player.