@vyonio/player 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,274 @@
1
- # Temporary Holding Version
1
+ # @vyonio/player
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Shaka Player engine with Plyr chrome for Vyonio DRM video playback. Plyr styles and the control-icon SVG sprite ship with the package, so playback does not depend on the Plyr CDN or require it in the host's Content Security Policy. If Plyr fails to initialize, the player falls back to native `<video controls>`.
4
+
5
+ > **Release availability:** source checkouts can be ahead of npm. Check available
6
+ > versions with `npm view @vyonio/player versions`. For an unpublished checkout,
7
+ > use `npm run verify:package` or a local `npm pack` tarball. Customer setup is in
8
+ > the [public npm guide](https://developers.vyonio.com/npm-player/) and
9
+ > [shared backend guide](https://developers.vyonio.com/protected-playback/).
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @vyonio/player
15
+ ```
16
+
17
+ The ESM package bundles Shaka and Plyr and exports TypeScript declarations.
18
+ Import `@vyonio/player/style.css` once in your application. The default API is
19
+ `https://api.vyonio.com`; set `apiBase` only when using another environment.
20
+
21
+ For a clean repository checkout, first run `npm ci --prefix packages/player`,
22
+ then `npm ci --prefix <app>` for each app you use (`client`, `course`, `player`,
23
+ `dash`). App build and dev scripts build the shared package before starting;
24
+ they do not install dependencies. Run `npm --prefix packages/player run verify:package` to pack, install, type-check, and build an independent consumer.
25
+ `npm pack` runs the library build through `prepack`. Local verification prepares
26
+ a release artifact; publishing npm and hosted artifacts is a separate step.
27
+
28
+ ## Usage
29
+
30
+ ### Public video
31
+
32
+ ```js
33
+ import { VyonioPlayer } from '@vyonio/player';
34
+ import '@vyonio/player/style.css';
35
+
36
+ const { player, destroy } = await VyonioPlayer.mount(element, {
37
+ apiBase: 'http://localhost:3000',
38
+ embedUuid: '550e8400-e29b-41d4-a716-446655440000',
39
+ autoplay: false,
40
+ muted: false,
41
+ loop: false,
42
+ controls: true,
43
+ controlItems: ['play-large', 'play', 'progress', 'current-time', 'fullscreen'],
44
+ settingsItems: ['quality'],
45
+ language: 'cs',
46
+ accentColor: '#7c3aed',
47
+ badge: {
48
+ text: 'AI-assisted',
49
+ position: 'top-right',
50
+ style: 'accent'
51
+ },
52
+ startSeconds: 0
53
+ });
54
+ ```
55
+
56
+ The optional playback settings are intended for hosted embeds as well as
57
+ direct integrations. Browsers generally require `muted: true` when autoplay is
58
+ enabled. A rejected autoplay attempt leaves the player ready for manual play.
59
+ `controlItems` accepts Plyr control names and is ignored when `controls` is
60
+ false. `settingsItems` accepts `quality` and `speed`; it defaults to
61
+ `['quality']`, so playback speed is opt-in. An empty array removes the settings
62
+ menu, and unavailable quality choices hide automatically. `language` accepts
63
+ `en` and `cs` and defaults to English. Unsupported languages fall back to
64
+ English. `accentColor` accepts only a six-digit hex color. Badge text is rendered
65
+ as plain text inside Plyr's fullscreen container and is limited to 64
66
+ characters; positions are the four corners and styles are `dark`, `light`, or
67
+ `accent`.
68
+
69
+ ### Popularity histogram
70
+
71
+ Pass equal-duration popularity buckets to render a YouTube-like engagement
72
+ curve above the seek bar (white top stroke, fade-to-transparent fill). The
73
+ overlay is non-interactive and only appears while the progress control is
74
+ hovered or scrubbed:
75
+
76
+ ```js
77
+ await VyonioPlayer.mount(element, {
78
+ apiBase: 'http://localhost:3000',
79
+ embedUuid: '550e8400-e29b-41d4-a716-446655440000',
80
+ popularityHistogram: {
81
+ buckets: [12, 18, 31, 24, 15]
82
+ }
83
+ });
84
+ ```
85
+
86
+ Each bucket spans an equal portion of the video duration, and heights are
87
+ normalized against the largest value. The player also accepts the same array
88
+ from `popularity_histogram.buckets` in the playback descriptor when a backend
89
+ provides it. Explicit mount-option buckets take precedence. The current Vyonio
90
+ Platform descriptor does not supply popularity buckets; aggregation and API
91
+ delivery are a separate backend feature.
92
+
93
+ HTML badges stay visible in normal Plyr fullscreen, but cannot be guaranteed in
94
+ native video fallback, Picture in Picture, AirPlay, casting, or downloaded
95
+ media. Use a burned-in watermark for compliance-critical or tamper-resistant
96
+ marking.
97
+
98
+ Fetches the video descriptor from `{apiBase}/v1/embed/{embedUuid}`, attaches a `<video>` element with Plyr controls (or native controls on Plyr failure), configures Widevine DRM, and loads the DASH manifest. Safari/FairPlay is scaffolded and throws until Apple FPS credentials are deployed.
99
+
100
+ The returned handle also exposes media controls used by the hosted iframe API:
101
+
102
+ ```js
103
+ const handle = await VyonioPlayer.mount(element, options);
104
+
105
+ await handle.play();
106
+ handle.pause();
107
+ handle.seek(30);
108
+ handle.setMuted(true);
109
+ console.log(handle.getState());
110
+ ```
111
+
112
+ `seek(seconds)` accepts a nonnegative finite number and clamps it to the known
113
+ duration. `getState()` returns `currentTime`, `duration`, `paused`, `ended`, and
114
+ `muted`. The underlying `HTMLVideoElement` is available as `mediaElement` for
115
+ advanced event handling.
116
+
117
+ If the descriptor includes a `telemetry` endpoint and signed context, the
118
+ player automatically emits batched `start`, `heartbeat`, `pause`, `seek`,
119
+ `end`, `leave`, and `error` events. Telemetry is best-effort: failures and an
120
+ absent telemetry descriptor never interrupt playback. Pipeline credentials are
121
+ held by the Platform API and are never exposed to the browser.
122
+
123
+ Every mounted player uses a fresh random session UUID. Heartbeat, pause, end,
124
+ and leave events close continuous watched spans with millisecond start/end,
125
+ duration, watched time, buffering time, and playback rate. A seek closes the
126
+ preceding span and records only its origin and destination, so skipped content
127
+ is not counted as watched. Telemetry contains no user/member ID, stable device
128
+ ID, fingerprint, page/referrer URL, or cross-session cookie.
129
+
130
+ ### Protected video (playback JWT)
131
+
132
+ For a same-origin customer endpoint:
133
+
134
+ ```js
135
+ const handle = await VyonioPlayer.mount(element, {
136
+ embedUuid: 'your-embed-uuid',
137
+ tokenEndpoint: '/api/playback-token',
138
+ tokenHeaders: { 'X-CSRF-Token': csrfToken }
139
+ });
140
+ ```
141
+
142
+ The endpoint receives `POST` JSON `{ embedUuid }` with same-origin credentials
143
+ and must authenticate the viewer and recheck entitlement on every request.
144
+ Return JSON `{ token, expires_in: 600 }` with `Cache-Control: no-store`.
145
+ Keep the Vyonio API key only on your server. Customers never need Vyonio's JWT signing secret.
146
+ `tokenEndpoint` and `getPlaybackToken` are mutually exclusive. Endpoint URLs
147
+ must be same-origin; `tokenHeaders` cannot include Authorization. Token
148
+ acquisition times out after 15 seconds and rejects with `PlaybackTokenError`
149
+ (`PLAYBACK_TOKEN_FAILED` or `PLAYBACK_TOKEN_TIMEOUT`).
150
+
151
+ Pass `tokenEndpoint` or `getPlaybackToken` to attach a short-lived playback JWT before the descriptor fetch. The player:
152
+
153
+ - Sends `Authorization: Bearer <token>` on the descriptor request and all Shaka network requests (via a request filter)
154
+ - Appends `access_token=<token>` to the DASH manifest URL (for AirPlay / devices that cannot send headers)
155
+ - Replaces `access_token` on subsequent platform manifest, segment, and license request URLs after each token refresh
156
+ - Schedules automatic refresh from the JWT `exp` claim, **120 seconds before expiry**
157
+ - Starts refresh scheduling only after the manifest loads successfully
158
+ - On refresh failure, marks the mount and video elements with `data-state="error"`, emits a `vyonio-player-error` event from the mount element, calls optional `onError`, then destroys the player and removes the video element
159
+
160
+ ```js
161
+ const { player, destroy } = await VyonioPlayer.mount(element, {
162
+ apiBase: 'http://localhost:3000',
163
+ embedUuid: '550e8400-e29b-41d4-a716-446655440000',
164
+ onError: ({ code, message, error }) => {
165
+ // code is PLAYBACK_AUTH_REFRESH_FAILED for token refresh failures.
166
+ showPlaybackError(message);
167
+ },
168
+ getPlaybackToken: async () => {
169
+ const res = await fetch('/api/playback-token', {
170
+ method: 'POST',
171
+ credentials: 'same-origin',
172
+ cache: 'no-store',
173
+ redirect: 'error',
174
+ headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken },
175
+ body: JSON.stringify({ embedUuid: '550e8400-e29b-41d4-a716-446655440000' })
176
+ });
177
+ if (!res.ok) throw new Error(`Token mint failed: ${res.status}`);
178
+ const { token } = await res.json();
179
+ return token;
180
+ }
181
+ });
182
+ ```
183
+
184
+ The `vyonio-player-error` event is a `CustomEvent` with the same `{ code, message, error }` object in `event.detail`. This lets applications surface an authentication or entitlement error even when they do not provide `onError`.
185
+
186
+ When both authentication options are omitted, behavior matches the public path (no Bearer header, no manifest query param, no refresh timer).
187
+
188
+ ## Playback descriptor: captions and chapters
189
+
190
+ The embed descriptor (`GET {apiBase}/v1/embed/{embedUuid}`) may include:
191
+
192
+ ```json
193
+ {
194
+ "captions": [
195
+ {
196
+ "language": "cs",
197
+ "label": "Čeština",
198
+ "kind": "subtitles",
199
+ "vtt_url": "https://media.example/caption_tracks/1/vtt",
200
+ "default": true
201
+ }
202
+ ],
203
+ "chapters": [
204
+ { "title": "Intro", "start_seconds": 0 },
205
+ { "title": "Main", "start_seconds": 42 }
206
+ ]
207
+ }
208
+ ```
209
+
210
+ - **`captions`**: optional array of sidecar WebVTT tracks. Each entry supplies `language`, `label`, `kind` (`subtitles` or `captions`), authenticated `vtt_url`, and optional `default` (boolean). When `getPlaybackToken` is used, the player appends `access_token` to each `vtt_url` the same way it does for the DASH manifest.
211
+ - **`chapters`**: optional array of chapter cues with `title` and `start_seconds`. The player builds an in-memory chapters WebVTT track for Plyr's chapter menu; cue end times are the next chapter start or video duration.
212
+
213
+ When either field is absent or empty, the corresponding Plyr control stays idle.
214
+
215
+ ## Tests
216
+
217
+ ```bash
218
+ npm test
219
+ ```
220
+
221
+ ## React and SSR
222
+
223
+ The browser engine uses DOM globals. Import `@vyonio/player` dynamically inside
224
+ a client effect; do not import the engine in a server-evaluated module. Import
225
+ the CSS once through your framework's supported global CSS entry. Cancel stale
226
+ mounts, destroy their late handles, and call `destroy()` on cleanup. The
227
+ [public npm guide](https://developers.vyonio.com/npm-player/#react-and-server-rendering)
228
+ contains the complete lifecycle example.
229
+
230
+ See the [runnable customer example](../../examples/protected-playback/README.md)
231
+ for the same-origin `{ embedUuid }` request, CSRF `tokenHeaders`, server-only key,
232
+ current entitlement checks, and Node/PHP host replacement points. Its login is
233
+ local demonstration authentication, not a production identity system. Protected
234
+ access can be used with clear or DRM media; this package preserves your existing
235
+ account DRM configuration and does not provision FairPlay.
236
+
237
+ ## Maintainer releases
238
+
239
+ From the repository root, use Node 22.12+ and your own npm account with Vyonio
240
+ publish access. Commit package changes first, leave the Git index empty, and sign
241
+ in once with `npm login`. Use a named branch with Git `user.name`/`user.email`
242
+ configured and no merge/rebase in progress.
243
+
244
+ ```sh
245
+ # First publication of the existing package version:
246
+ npm --prefix packages/player run release -- current --dry-run
247
+ npm --prefix packages/player run release -- current
248
+
249
+ # Later bug-fix release:
250
+ npm --prefix packages/player run release -- patch --dry-run
251
+ npm --prefix packages/player run release -- patch
252
+ ```
253
+
254
+ Choose `minor` for a feature release or `major` for breaking changes. These commands
255
+ accept stable versions only; prereleases need a separately reviewed release.
256
+ The command builds a temporary copy of tracked package source, installs locked
257
+ dependencies, runs all package tests, and verifies the tarball in an independent
258
+ TypeScript/browser consumer. A real release asks for confirmation and uses npm's
259
+ normal login/2FA flow. Only that verified tarball is published. It then checks
260
+ registry integrity and installs the exact version from npm.
261
+
262
+ Dry runs, cancellation and failed preparation leave your working version files
263
+ unchanged, and create no commits or tags. After publication and registry
264
+ installation verification, a bumped release commits only the manifest and
265
+ lockfile, then creates an annotated `player-vX.Y.Z` tag on that exact commit.
266
+ `current` tags the existing matching-version commit without adding an empty
267
+ commit. The annotation records the npm artifact integrity. Existing local/origin
268
+ tags are never overwritten. Unrelated unstaged work stays outside the release
269
+ commit; any staged work blocks release.
270
+
271
+ Review the local release commit and tag, then push them through your normal Git
272
+ workflow. The command does not push tags or deploy hosted/docs assets. Read the
273
+ [release runbook](../../docs/player-release.md) for first-release setup, Git/tag
274
+ recovery and post-release documentation.