Quickstart: Embed the Player
Load a PanelWave manifest and render it with pw-player-shell — a minimal, working Angular embed based on the demo application.
This page shows the smallest useful integration: point pw-player-shell at a manifest, and react to player events. It mirrors the demo application that ships with the player repository (projects/demo).
Prerequisites
- Installation completed (
provideHttpClient()+provideTranslateService(...)configured, UI translation files and balloon fonts added). - A valid manifest file, e.g.
src/assets/my-story/panelwave.json. You can export one from the CMS, start from the minimal example below, or take one of the demo works from the player repository (projects/demo/src/assets:sample-manifest.json,canvas-manifest.json,video-sequencing-manifest.json).
Option A: load by URL
The shortest embed: give the shell the manifest's URL. It fetches the JSON with HttpClient, validates it, and starts reading.
// reader.component.ts
import { Component } from '@angular/core';
import { PlayerShellComponent } from '@panelwave/player';
import type { PlayerPanelChangeEvent } from '@panelwave/player';
@Component({
selector: 'app-reader',
imports: [PlayerShellComponent],
template: `
<pw-player-shell
manifestUrl="assets/my-story/panelwave.json"
locale="en-US"
[showToolbar]="true"
(ready)="onPlayerReady()"
(panelChange)="onPanelChange($event)"
(error)="onError($event)" />
`,
styles: `:host { display: block; height: 100dvh; }`,
})
export class ReaderComponent {
onPlayerReady(): void {
console.log('Player is ready');
}
onPanelChange(event: PlayerPanelChangeEvent): void {
// panelId is the panel's key in chapter.panels (from player 1.2.0);
// Panel.id is optional in the format, so don't read it from event.panel.
console.log('Now showing panel', event.panelId, 'in chapter', event.chapter.id);
}
onError(error: Error): void {
console.error(error.message); // e.g. "Manifest load failed: …"
}
}
A cross-origin manifest URL needs CORS headers on the server that hosts it (manifests published from the CMS preview send Access-Control-Allow-Origin: *).
If both manifestUrl and manifest are set, the URL wins. The table of contents and the thumbnail strip list the loaded work either way. To open a different work, just change the input (see Switching works).
Option B: pass the manifest object
Load the JSON yourself when you need to transform it first, cache it, or fetch it with your own auth — then bind the object:
// reader.component.ts
import { Component, OnInit, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { PlayerShellComponent } from '@panelwave/player';
import type { PanelWaveManifest } from '@panelwave/player';
@Component({
selector: 'app-reader',
imports: [PlayerShellComponent],
templateUrl: './reader.component.html',
})
export class ReaderComponent implements OnInit {
private http = inject(HttpClient);
manifest: PanelWaveManifest | null = null;
error: string | null = null;
ngOnInit(): void {
this.http.get<PanelWaveManifest>('assets/my-story/panelwave.json').subscribe({
next: (manifest) => (this.manifest = manifest),
error: (err) => (this.error = `Failed to load manifest: ${err.message}`),
});
}
}
<!-- reader.component.html -->
@if (error) {
<p class="error">{{ error }}</p>
}
@if (manifest) {
<pw-player-shell
[manifest]="manifest"
locale="en-US"
[showToolbar]="true"
(error)="error = $event.message" />
}
Either way that's the entire integration. The shell validates the manifest, navigates to the first chapter's graph entry panel (or to the reader's bookmark in this work, if they left one), and renders the viewport plus toolbar.
A minimal valid manifest
The shell's ManifestService validates structure on load. At minimum it requires the panelwave header with a version, meta with id, title, locales, and default_locale, and at least one chapter with non-empty panels and a graph containing entry and edges:
{
"panelwave": {
"version": "1.3.0",
"schema": "https://panelwave.org/schema/1.0/panelwave.schema.json"
},
"meta": {
"id": "work-hello-panelwave",
"title": { "en-US": "Hello PanelWave" },
"locales": ["en-US"],
"default_locale": "en-US"
},
"assets": {
"base": { "mediaBase": "assets/my-story/" },
"catalog": [
{
"id": "img-1",
"category": "image",
"alt": { "en-US": "A quiet street at dawn." },
"variants": [{ "src": "panel-1.webp", "mime": "image/webp", "w": 1280, "h": 720 }]
},
{
"id": "img-2",
"category": "image",
"alt": { "en-US": "A door opens." },
"variants": [{ "src": "panel-2.webp", "mime": "image/webp", "w": 1280, "h": 720 }]
}
]
},
"chapters": [
{
"id": "ch-1",
"title": { "en-US": "Chapter One" },
"panels": {
"p-1": { "layers": [{ "kind": "image", "id": "ly-1", "assetId": "img-1", "z": 0 }] },
"p-2": { "layers": [{ "kind": "image", "id": "ly-2", "assetId": "img-2", "z": 0 }] }
},
"graph": {
"entry": "p-1",
"edges": [{ "from": "p-1", "to": "p-2" }]
}
}
]
}
For the full format, see the manifest structure reference and examples. Validate manifests before shipping with the CLI.
Starting at a specific position
Use the initial-position inputs to deep-link into a work (e.g. to implement resume — see Saving Progress):
<pw-player-shell
[manifest]="manifest"
[initialChapterId]="'ch-1'"
[initialPanelId]="'p-2'">
</pw-player-shell>
An explicit initial position always wins over the reader's own bookmark.
Switching works
Assign a new manifestUrl or manifest and the shell reloads: it loads the new work and starts it from the beginning (or from initialChapterId / initialPanelId, or the reader's bookmark in that work). The previous work's reading position, non-persistent variables, open overlays, variant overrides and autoplay are reset, and its analytics session is closed. You can also call reload() on the component to restart the current work the same way.
Other inputs can change without a reload: locale, viewModeOverride, pageFormat, showToolbar, autoplay, secondsPerPanel and reducedMotion apply live, and a new entitlementSnapshot re-evaluates the paywall. See Inputs & Outputs.
Next steps
Configuration
Every input the shell accepts, plus manifest-driven settings.
Reader Interface
What your readers can do once the player renders.
Paywall & Entitlement
Tell the player what the reader owns and handle checkout.
Having trouble? See Troubleshooting: Player Embedding.