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
manifestormanifestUrlreloads the work (the same as callingreload()): 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,initialVariablesand the entitlement inputs are read again as part of that reload. entitlementSnapshotre-evaluates the gates, likerefreshEntitlements(). Removing it (setting it toundefined) makes the reader anonymous again.locale,viewModeOverride,pageFormat,showToolbar,autoplay,secondsPerPanelandreducedMotionapply live, without a reload.showCoveris read when a work loads.
See Switching works.
Inputs
Content
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Sent as Authorization: Bearer <token> to entitlementEndpoint. Omit for anonymous readers.
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
Whether the toolbar is visible (default false). Changing it shows or hides the toolbar; the reader can still toggle it with T.
'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.
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.
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.
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.
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.
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
Fires once the manifest is loaded, validated, tracking is configured, and the initial panel is set.
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.
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 }:
panelis the panel as it is in the manifest — variants are not resolved in this object.Panel.idis optional in the format and usually absent, so don't read the id frompanel.chapteris the chapter the panel belongs to.panelIdis the panel's id: its key inchapter.panels.previousPanelIdis the panel that was current before this change (it may belong to the previous chapter); unset for the first panel after a load orreload().
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.
Fires when the current chapter changes.
Fires on load, validation, or navigation errors (the shell also renders its own error screen with a retry button).
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.
Fires when a variable changes through the shell's setVariable method or a reader edit in the Settings modal's Variables tab.
Fires when a navigation is attempted (before it resolves) — useful for interstitials or analytics.
Canvas view only: the camera moved (throttled). See Canvas View.
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.
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.
The reader toggled Like. Stored on the device per work; mirror it server-side if your platform has accounts.
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:
| Method | Signature | Semantics |
|---|---|---|
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) => void | Switch the content locale |
setVariable | (key, value, scope?) => void | Set a variable (scope defaults to 'session'); emits variableChange |
getVariable | (key, scope?, scopeId?) => unknown | Read a variable |
toggleToolbar | () => void | Show/hide the toolbar |
getCurrentPanelId | () => string | undefined | ID 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):
| Component | Selector | Key inputs → outputs |
|---|---|---|
PwIconComponent | pw-icon | name (Lucide id, e.g. lucideSettings) |
SpeechBubblesComponent | pw-speech-bubbles | bubbles, locale, containerWidth/Height, workBalloonConfig, characters → bubbleClick, bubbleAudioPlay |
HotspotsOverlayComponent | pw-hotspots-overlay | A panel's hotspots → hotspotActivate (see Components) |
ActionModalComponent | pw-action-modal | The small localized dialog an openModal hotspot shows |
BranchChooserComponent | pw-branch-chooser | visible, locale, choices: BranchChoice[] → choose: BranchChoice, close |
CanvasStageComponent | pw-canvas-stage | The infinite-canvas stage behind canvas view (see Canvas View) |
PaywallOverlayComponent | pw-paywall-overlay | visible, gate, purchaseOptions, locale, title, message, allowPreview, showLogin → action: PaywallAction, purchase: string, close |
AgeGateComponent | pw-age-gate | visible, minimumAge (default 18), locale, warningMessage, allowDismiss → verify: AgeVerificationResult, close |
VideoLayerComponent | pw-video-layer | src, poster, playMode, startMode, muted, startAtMs, loopFromMs, … → videoPlay, videoPause, videoEnd, videoLoop, passComplete, videoError |
PluginSandboxComponent | pw-plugin-sandbox | manifest: 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.