Skip to content
Player showcase

Page API ​

approved

How a hosting page creates, controls, and observes player instances.

Mounting and unmounting ​

  • Creating a player takes a host element and a configuration and returns an instance handle; destroying it releases everything.
  • Updating an instance swaps the video in place: the media element is reused, which preserves playback permissions such as the audio activation grant. This operation is the primitive the publications feed is built on.
  • Mount and update validate before applying anything. A hard-invalid configuration (the config contract's validation section defines exactly three conditions, with the legacy pre-3 body shape called out) makes the call fail with a console error naming the violation, and the instance renders the config error state; a rejected update applies no partial changes and does not continue the previous video. Soft irregularities never fail a call: they degrade to defaults with one consolidated console warning.
  • A hard-invalid initial call still mounts the configuration-error state before it reports and throws, and it returns no handle. When the supplied layout is valid, the error state uses that layout; landscape is the fallback only when the layout value is absent or invalid. The failed tree stays associated with its host internally so a later valid createPlayer call can replace and dispose it.
  • An update rejects a configuration whose layout differs from the instance's: layout is immutable per instance, in both directions, and the error says so plainly.
  • One live player owns one host. A second createPlayer call on an already-owned host throws an error naming the ownership conflict and leaves the existing instance untouched. Destroying the owner releases the host for a later player.

Feed mounting ​

The feed entry point accepts an optional initial index:

ts
import { createFeed } from '@latest-thinking/lt-player/feed';

const feed = createFeed(host, configurations, { startIndex: 5 });
const unsubscribe = feed.on('indexChange', ({ index }) => {
  // Synchronize page state with the active publication.
});

startIndex must be an integer within the configuration array. Negative, fractional, non-finite, non-number, and out-of-range values fall back to 0. Omitting the options argument remains source-compatible and still starts at 0.

The feed mounts and starts playback directly on the selected item; it does not mount or play item 0 before moving. If the host has no usable layout at creation, the feed waits for its first usable size, reapplies the selected scroll position, and only then attaches intersection observers. It emits one asynchronous indexChange for the initial index, so a subscription registered immediately after createFeed returns receives the initial state. Later committed moves continue to emit indexChange once per move.

This initial event is a consumer-visible addition for every feed mount, including two-argument calls. Existing indexChange subscribers receive one additional asynchronous callback after creation and should treat it as initial state synchronization rather than a navigation initiated after mount.

Instance handles ​

Creating a player is one call: createPlayer(host, config) takes a host element and a complete configuration and returns the instance handle. The handle is the entire surface a page ever holds; there is no other object and no engine access. It is a stable plain object whose method identities do not change.

ts
handle.update(config); // apply a complete configuration; throws on hard-invalid
handle.destroy(); // release everything

handle.getState(); // one snapshot object, see below

handle.play();
handle.pause();
handle.seek(seconds);
handle.setVolume(volume); // 0 to 1
handle.setMuted(muted);
handle.setRate(rate); // within the offered rate list
handle.setCaptions({ enabled, language });

handle.openShare(); // the external share trigger
handle.openPanel(view); // open a specific context panel view
handle.closePanel();

handle.on(event, handler); // returns an unsubscribe function

destroy() is idempotent. Once it has released the instance, mutating commands, update(), and new subscription attempts through that stale handle are silent no-ops; on() returns an inert unsubscribe function. getState() remains readable and returns the final detached snapshot. The unsubscribe returned by a live subscription is also idempotent.

State ​

getState() returns one snapshot, deliberately instead of many getters:

json
{
  "playback": "playing",
  "currentTime": 12.4,
  "duration": 609.9,
  "volume": 0.8,
  "muted": false,
  "rate": 1.5,
  "captions": { "enabled": true, "language": "de" },
  "pip": false,
  "layout": "portrait"
}

playback is one of idle, buffering, playing, paused, ended, error (the core player's six states; their derivation and transitions live in the core player document). The snapshot is a plain serializable object, computed at call time; it never updates itself. Pages that want continuous values subscribe to events.

Setter semantics ​

  • Setters express user-equivalent actions: they update the user state exactly as the player's own controls would (setRate remembers intent, setCaptions sets the preference), and the same bounds apply: a rate outside the offered list snaps to the closest offered value.
  • Setters on removed functions are silent no-ops: calling setCaptions on an instance with capabilities.captions: false does nothing, matching the removal-beats-preference rule.
  • openPanel(view) with a view whose metadata section is absent is a silent no-op (the view is dormant); openShare() without share data likewise.
  • openShare() opens the share dialog in its page placement, centered in the viewport; the player's own trigger uses the in-player placement (the share capability's Placement section defines both).
  • openPanel(view) on an instance whose layout owns no panel surface is a silent no-op, even when that view's metadata section is present.
  • The public context-panel view values are knowledge, underlyingPublication, and relatedVideos.

Events ​

handle.on(event, handler) subscribes; the returned function unsubscribes. Handlers receive one payload object, and every payload carries currentTime, read when the event is dispatched. Commands do not emit optimistically: an event follows the observed engine or LT-module state change, regardless of whether the trigger was the page API, player UI, lock screen, or browser. The list is deliberately small and grows in ordinary releases when a real need appears:

EventAdditional payload fieldsFires
readydurationMetadata and a first frame are available and the instance can play
playnonePlayback starts or resumes
pausenonePlayback pauses
endednonePlayback reaches the end (standalone; the feed loops instead)
seekednoneA seek completes
bufferingbuffering: booleanPublished buffering starts or recovers
errorkind: "configuration" | "startup" | "mid-playback"The corresponding error state is entered
timeUpdatenonePlayback position updates at the media element's native cadence
chapterChangechapter: PlayerChapter | nullThe current chapter changes, including the unlabeled intro
volumeChangevolume, mutedVolume or mute changes
rateChangeratePlayback rate changes
captionsChangecaptions: { enabled: boolean; language?: string }Captions toggle or the selected track changes
pipChangepip: booleanPicture-in-picture is entered or left
shareOpensource: "player" | "page"The share dialog opens; source matches the trigger
panelOpenview: "knowledge" | "underlyingPublication" | "relatedVideos"The context panel opens or switches to that view
panelClosenoneThe context panel closes
videoChangenoneA successful update is live

Every error also reaching the console is a rule from the core player document; events are for pages, the console is for developers.

Multiple players on one page ​

  • Multiple independent instances are supported. Cross-instance coordination exists only where a module defines it; the publications feed's single-instance model keeps the common case trivial.