Using the PlayerInputs & Outputs

Inputs & Outputs

Complete API reference for pw-player-shell — every @Input and @Output with types and semantics, plus public methods and other exported components.

PlayerShellComponent (selector pw-player-shell) is the main integration surface. This page lists its complete input/output API as implemented, plus the public methods you can call on a component reference and the other components exported from the package.

import { PlayerShellComponent } from '@panelwave/player';
import type {
  PanelWaveManifest, Panel, Chapter, LocaleCode, PlayerPanelChangeEvent,
  EntitlementSnapshot, PaywallAction, PaywallGate, AgeVerificationResult, CameraState,
} from '@panelwave/player';

Inputs may change after the shell has initialized:

  • A new manifest or manifestUrl reloads the work (the same as calling reload()): the reading position, all non-persistent variables, open overlays, variant overrides and autoplay are reset, and the analytics session of the previous work is closed. initialChapterId, initialPanelId, initialVariables and the entitlement inputs are read again as part of that reload.
  • entitlementSnapshot re-evaluates the gates, like refreshEntitlements(). Removing it (setting it to undefined) makes the reader anonymous again.
  • locale, viewModeOverride, pageFormat, showToolbar, autoplay, secondsPerPanel and reducedMotion apply live, without a reload. showCover is read when a work loads.

See Switching works.

Inputs

Content

manifestUrlstring

URL of a manifest to load. The shell fetches it with HttpClient, validates it, and starts reading; a network or validation failure emits error (message prefixed Manifest load failed:) and shows the error screen. Takes precedence over manifest when both are set. Cross-origin URLs need CORS headers. Changing it later loads the new work.

manifestPanelWaveManifest

The manifest object to render, as an alternative to manifestUrl. Validated on load (required header, meta, and chapter structure); a validation failure emits error and shows the error screen. Passing a new object later loads it as a new work.

localeLocaleCode

Content locale, BCP-47 (default 'en-US'). Also selects the player-UI language (base language, e.g. en). Changing it switches the content and UI language live.

initialChapterIdstring

Chapter to open first. Without any initial position, the player resumes the reader's bookmark in this work if there is one, otherwise it starts at the first chapter's graph entry.

initialPanelIdstring

Panel to open first. From the release after 1.2.0 initialChapterId is optional: the player looks up the chapter that holds the panel. A panel link opens in panel view, without the cover. From the player release after 1.2.0, a gated panel here (or in a resumed bookmark) opens with its age gate or paywall over the locked placeholder, like the initial load of a gated entry panel.

initialPageIdstring

From the release after 1.2.0. Page to open first, by its manifest page id (e.g. from a ?page= link): it opens in page view, without the cover. If that page belongs to another page format than the reader's screen, the page showing the same panels opens. Takes precedence over initialPanelId. An unknown id is ignored.

initialViewMode'page' | 'panel'

From the release after 1.2.0. View to open in. By default the work opens in page view when it has pages (behind the cover; a panel link opens in panel view). 'panel' opens in panel view. Canvas chapters and a forced viewModeOverride keep their view.

showCoverboolean

From the player release after 1.2.0. Open on the work's cover — meta.cover, else the image of the extras.cover block — when reading starts at the beginning (default true). From the release after 1.2.0 a resumed bookmark opens on the cover too, with the bookmarked page behind it. The cover is skipped when the host gives an initial position (initialPageId, initialPanelId, initialChapterId) and when the work has no cover. Next (or a click on the cover) starts the story; previous on the first panel or page goes back to it, and it heads the thumbnail strip and the table of contents. Set false to open straight on the first panel; the cover stays reachable from previous, the strip and the TOC. Read when a work loads.

initialVariablesRecord<string, unknown>

Host-supplied initial variable values, keyed by variable id and seeded once at initialization through a privileged path: unlike runtime mutations, these MAY populate variables the manifest declares readOnly — the channel for externally-sourced facts (e.g. a verified user.age) that in-story content and the settings UI must never change. Values land in each definition's declared scope (global for undeclared ids); the entitlement adapter's context is seeded afterwards and wins on collisions.

Paywall and entitlement

See Paywall & Entitlement for how these fit together.

entitlementSnapshotEntitlementSnapshot

What the reader owns: { subscriptionTier: string | null; purchasedProductIds: string[]; ageVerified: boolean; age?: number }. Evaluated locally against the manifest's paywall.rules. Omitted → an anonymous reader (free preview only). Works without paywall rules are unaffected. Wins over entitlementEndpoint when both are set. Passing a new snapshot re-evaluates the gates immediately (like refreshEntitlements()); removing it makes the reader anonymous. An age check the reader already passed on this device keeps counting.

entitlementEndpointstring

URL the shell fetches the snapshot from itself, once at startup ({workId} is replaced with meta.id). On a failed request the reader stays anonymous — they see the free preview and the gate, never paid content.

readerTokenstring

Sent as Authorization: Bearer <token> to entitlementEndpoint. Omit for anonymous readers.

entitlementAdapter{ hasAccess, getContext, purchase? }

Lower-level override: when set, hasAccess(panelId) is awaited before every panel navigation instead of the manifest rules, and getContext() is seeded into variables at startup. It gates navigation only, not rendering: a refused entry panel gets the paywall over its rendered content, and page and canvas view show refused panels — don't rely on it to hide content. Note that this is the shell's own small interface, not the EntitlementAdapter type exported from the package — see Custom access checks.

Reading behavior

showToolbarboolean

Whether the toolbar is visible (default false). Changing it shows or hides the toolbar; the reader can still toggle it with T.

viewModeOverride'auto' | 'panel' | 'canvas'

'auto' (default) follows the manifest (canvas chapters open in canvas view); 'panel' forces classic panel view; 'canvas' enters canvas view whenever the chapter allows it. A changed value applies to the running view. See Canvas View.

pageFormat'auto' | OutputFormat

From the player release after 1.2.0. Which page sequence page view shows — a chapter carries one per output format (Page.layout.format). 'auto' (default) picks the authored format that suits the player's box: portrait phones prefer mobile-portrait, portrait tablets tablet-portrait, landscape screens the wide formats (desktop-landscape, …) and 4K screens bigscreen-landscape, falling back to the closest available format; it re-picks on resize. A format id (e.g. 'tablet-portrait') forces that sequence when the work has pages for it, otherwise 'auto' applies. Pages without a format belong to every sequence. A changed value applies live and re-opens the page that shows the current panel.

reducedMotionboolean

Force reduced motion (default false). Reduced motion is also on when the operating system asks for it (prefers-reduced-motion, followed live) or when the reader enabled it in Settings — this input can only add it, not turn it off. Under reduced motion, panel/page transitions and canvas camera glides are instant and on-view videos degrade to click-to-play. Changes apply live.

secondsPerPanelnumber

Autoplay seconds per panel (default 5) for panels without an authored durationMs (and, from the player release after 1.2.0, for the cover). Readers can adjust it from 0.5 to 120 s in the toolbar. From the player release after 1.2.0, picking a speed there replaces the author's timing: the reader's seconds then apply to every panel, including those with a durationMs, until the reader switches back with the toolbar's Author button. A new value re-arms a running autoplay with the new timing.

autoplayboolean

Start in autoplay (default false). The shell starts autoplay once the work is ready; after that the reader's toolbar toggle takes over. Changing the input later starts or stops autoplay.

shareUrlstring

From the release after 1.2.0. The link the share dialog offers (copy, social networks, QR code). Default: the current page address with the reading position: ?page= in page view, ?panel= in panel view, nothing on the cover. embed is removed. The QR code is drawn in the browser; the link is not sent to any service.

Outputs

readyEventEmitter<void>

Fires once the manifest is loaded, validated, tracking is configured, and the initial panel is set.

locationChangeEventEmitter<PlayerLocation>

From the release after 1.2.0. The reading position changed: { view: 'cover' | 'page' | 'panel' | 'canvas'; chapterId?; panelId?; pageId? }. Page view reports page changes, not every panel the reader focuses. Hosts use it to keep the position in the address bar, so every page and panel has its own URL:

import { locationUrl, parseLocationSearch, type PlayerLocation } from '@panelwave/player';

start = parseLocationSearch(location.search); // → [initialPageId] / [initialPanelId]

onLocationChange(position: PlayerLocation) {
  history.replaceState(history.state, '', locationUrl(location.href, position));
}

locationUrl sets ?page=<pageId> (page view) or ?panel=<panelId> (panel and canvas view), removes both on the cover and keeps every other parameter. The public reader does exactly this.

panelChangeEventEmitter<PlayerPanelChangeEvent>

Fires once per panel navigation — not for a navigation the paywall or age gate blocks. Use it for progress tracking, deep links, or analytics forwarding. The payload is { panel: Panel; chapter: Chapter; panelId: string; previousPanelId?: string }:

  • panel is the panel as it is in the manifest — variants are not resolved in this object. Panel.id is optional in the format and usually absent, so don't read the id from panel.
  • chapter is the chapter the panel belongs to.
  • panelId is the panel's id: its key in chapter.panels.
  • previousPanelId is the panel that was current before this change (it may belong to the previous chapter); unset for the first panel after a load or reload().

panelId and previousPanelId are new in player 1.2.0; panel and chapter are unchanged, so existing handlers keep working. On 1.1.0 and earlier, resolve the id yourself by looking the emitted panel object up in chapter.panels.

chapterChangeEventEmitter<Chapter>

Fires when the current chapter changes.

errorEventEmitter<Error>

Fires on load, validation, or navigation errors (the shell also renders its own error screen with a retry button).

localeChangeEventEmitter<LocaleCode>

Fires when the content locale changes (e.g. the reader picked a language in the language modal). It reports changes only: it does not fire with the start locale when the player loads, so a host can store the emitted value as the reader's choice without overwriting the locale it passed in.

variableChangeEventEmitter<{ key: string; value: unknown }>

Fires when a variable changes through the shell's setVariable method or a reader edit in the Settings modal's Variables tab.

navigationAttemptEventEmitter<{ direction: 'next' | 'previous' | 'panel'; target?: string }>

Fires when a navigation is attempted (before it resolves) — useful for interstitials or analytics.

cameraChangeEventEmitter<CameraState>

Canvas view only: the camera moved (throttled). See Canvas View.

paywallActionEventEmitter<{ action: PaywallAction; gate: PaywallGate; productId?: string }>

The reader acted on the paywall overlay: 'purchase' or 'subscribe' (a Buy / Subscribe option, with the chosen option's productId — a product id or a subscription tier), 'login' (Sign in) or 'dismiss'. gate.options lists everything the blocking rule offers. Open your checkout / sign-in flow here, then call refreshEntitlements(). See Buy and Subscribe options.

ageVerifiedEventEmitter<AgeVerificationResult>

The reader answered an age gate: { verified: boolean; age?: number; birthDate?: Date }. Fires for passes and fails. A pass is also stored on the device.

likeChangeEventEmitter<{ workId: string; liked: boolean }>

The reader toggled Like. Stored on the device per work; mirror it server-side if your platform has accounts.

bookmarkChangeEventEmitter<{ workId: string; chapterId: string; panelId: string; bookmarked: boolean }>

The reader set (bookmarked: true) or cleared the bookmark on a panel. Stored on the device per work and resumed on the next load when you pass no initial position.

workId in these payloads is the manifest's meta.id (falling back to the manifestUrl).

Example: wiring the outputs

<pw-player-shell
  #player
  [manifestUrl]="url"
  [entitlementSnapshot]="snapshot"
  (ready)="onReady()"
  (panelChange)="onPanel($event)"
  (chapterChange)="onChapter($event)"
  (localeChange)="onLocale($event)"
  (variableChange)="onVariable($event)"
  (navigationAttempt)="onNavAttempt($event)"
  (paywallAction)="onPaywall($event)"
  (ageVerified)="onAge($event)"
  (likeChange)="syncLike($event)"
  (bookmarkChange)="syncBookmark($event)"
  (error)="onError($event)">
</pw-player-shell>

Public methods

With a template reference (#player) or @ViewChild(PlayerShellComponent) you can drive the player programmatically:

MethodSignatureSemantics
navigateToChapter(chapterId: string) => Promise<void>Jump to a chapter's graph entry panel (paywall-checked from the player release after 1.2.0: a gated entry raises the gate and the reader stays put)
navigateToPanel(chapterId: string, panelId: string) => Promise<void>Jump to a specific panel (paywall-checked: a gated panel raises the paywall or age gate instead); in page view the page showing the panel opens
navigateNext() => Promise<void>Follow the graph forward (condition + priority resolution); in a chapter without edges, the next panel in reading order (from the release after 1.2.0)
navigatePrevious() => Promise<void>Follow an incoming edge back (in a chapter without edges, the previous panel in reading order); on the work's first panel, back to the cover when there is one (showCover)
navigateToNextPage / navigateToPreviousPage() => void (() => Promise<void> from the release after 1.2.0)Page-view navigation through the page sequence of the active pageFormat; from the release after 1.2.0 it continues into the next / previous chapter, and previous on the work's first page returns to the cover
changeLocale(locale: LocaleCode) => voidSwitch the content locale
setVariable(key, value, scope?) => voidSet a variable (scope defaults to 'session'); emits variableChange
getVariable(key, scope?, scopeId?) => unknownRead a variable
toggleToolbar() => voidShow/hide the toolbar
getCurrentPanelId() => string | undefinedID of the currently shown panel
refreshEntitlements(snapshot?: EntitlementSnapshot) => Promise<void>Apply a new snapshot (or re-fetch from entitlementEndpoint when called without one) and close the paywall if the reader is now through the gate
reload() => Promise<void>Reload the work from the current manifest / manifestUrl (what an input change and the error screen's Retry do): position, non-persistent variables, overlays, variant overrides and autoplay are reset
import { Component, ViewChild } from '@angular/core';
import { PlayerShellComponent } from '@panelwave/player';

@Component({ /* … */ })
export class ReaderComponent {
  @ViewChild(PlayerShellComponent) player!: PlayerShellComponent;

  skipToFinale(): void {
    void this.player.navigateToPanel('ch-3', 'p-finale');
  }

  markHeroMet(): void {
    this.player.setVariable('metHero', true, 'persistent');
  }
}

Other exported components

These standalone components are part of the public API and can be used on their own (the shell composes most of them internally):

ComponentSelectorKey inputs → outputs
PwIconComponentpw-iconname (Lucide id, e.g. lucideSettings)
SpeechBubblesComponentpw-speech-bubblesbubbles, locale, containerWidth/Height, workBalloonConfig, characters → bubbleClick, bubbleAudioPlay
HotspotsOverlayComponentpw-hotspots-overlayA panel's hotspots → hotspotActivate (see Components)
ActionModalComponentpw-action-modalThe small localized dialog an openModal hotspot shows
BranchChooserComponentpw-branch-chooservisible, locale, choices: BranchChoice[] → choose: BranchChoice, close
CanvasStageComponentpw-canvas-stageThe infinite-canvas stage behind canvas view (see Canvas View)
PaywallOverlayComponentpw-paywall-overlayvisible, gate, purchaseOptions, locale, title, message, allowPreview, showLogin → action: PaywallAction, purchase: string, close
AgeGateComponentpw-age-gatevisible, minimumAge (default 18), locale, warningMessage, allowDismiss → verify: AgeVerificationResult, close
VideoLayerComponentpw-video-layersrc, poster, playMode, startMode, muted, startAtMs, loopFromMs, … → videoPlay, videoPause, videoEnd, videoLoop, passComplete, videoError
PluginSandboxComponentpw-plugin-sandboxmanifest: PluginManifest → loaded, failed

Helper types exported alongside them: PlayerPanelChangeEvent (the panelChange payload, from 1.2.0), PaywallAction ('purchase' | 'subscribe' | 'login' | 'dismiss'), PaywallGate and PurchaseInfo (see Paywall & Entitlement), AgeVerificationResult ({ verified: boolean; age?: number; birthDate?: Date }), BranchChoice ({ edge, index, label }), LayerViewMode ('panel' | 'page'), EntitlementSnapshot.

PlayerComponent (selector pw-player) is also exported but is a legacy placeholder that renders static text. Do not embed it.

Services

All core services (PlayerStateService, ManifestService, VariableStoreService, FlowEngineService, AudioEngineService, TrackingService, PaywallService, EntitlementService, PluginHostService, and more) are providedIn: 'root' and exported — you can inject them next to the shell to observe or extend behavior. See Services for the contributor-level reference.