@livepeer-frameworks/player-wc 0.1.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.
Files changed (141) hide show
  1. package/dist/cjs/components/fw-context-menu.js +17 -0
  2. package/dist/cjs/components/fw-context-menu.js.map +1 -0
  3. package/dist/cjs/components/fw-dev-mode-panel.js +273 -0
  4. package/dist/cjs/components/fw-dev-mode-panel.js.map +1 -0
  5. package/dist/cjs/components/fw-error-overlay.js +101 -0
  6. package/dist/cjs/components/fw-error-overlay.js.map +1 -0
  7. package/dist/cjs/components/fw-idle-screen.js +182 -0
  8. package/dist/cjs/components/fw-idle-screen.js.map +1 -0
  9. package/dist/cjs/components/fw-loading-spinner.js +62 -0
  10. package/dist/cjs/components/fw-loading-spinner.js.map +1 -0
  11. package/dist/cjs/components/fw-player-controls.js +258 -0
  12. package/dist/cjs/components/fw-player-controls.js.map +1 -0
  13. package/dist/cjs/components/fw-player.js +570 -0
  14. package/dist/cjs/components/fw-player.js.map +1 -0
  15. package/dist/cjs/components/fw-seek-bar.js +233 -0
  16. package/dist/cjs/components/fw-seek-bar.js.map +1 -0
  17. package/dist/cjs/components/fw-settings-menu.js +126 -0
  18. package/dist/cjs/components/fw-settings-menu.js.map +1 -0
  19. package/dist/cjs/components/fw-skip-indicator.js +143 -0
  20. package/dist/cjs/components/fw-skip-indicator.js.map +1 -0
  21. package/dist/cjs/components/fw-speed-indicator.js +61 -0
  22. package/dist/cjs/components/fw-speed-indicator.js.map +1 -0
  23. package/dist/cjs/components/fw-stats-panel.js +141 -0
  24. package/dist/cjs/components/fw-stats-panel.js.map +1 -0
  25. package/dist/cjs/components/fw-subtitle-renderer.js +70 -0
  26. package/dist/cjs/components/fw-subtitle-renderer.js.map +1 -0
  27. package/dist/cjs/components/fw-title-overlay.js +72 -0
  28. package/dist/cjs/components/fw-title-overlay.js.map +1 -0
  29. package/dist/cjs/components/fw-toast.js +74 -0
  30. package/dist/cjs/components/fw-toast.js.map +1 -0
  31. package/dist/cjs/components/fw-volume-control.js +140 -0
  32. package/dist/cjs/components/fw-volume-control.js.map +1 -0
  33. package/dist/cjs/controllers/player-controller-host.js +315 -0
  34. package/dist/cjs/controllers/player-controller-host.js.map +1 -0
  35. package/dist/cjs/define.js +45 -0
  36. package/dist/cjs/define.js.map +1 -0
  37. package/dist/cjs/icons/index.js +153 -0
  38. package/dist/cjs/icons/index.js.map +1 -0
  39. package/dist/cjs/index.js +88 -0
  40. package/dist/cjs/index.js.map +1 -0
  41. package/dist/cjs/node_modules/.pnpm/@rollup_plugin-typescript@12.3.0_rollup@4.57.1_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js +33 -0
  42. package/dist/cjs/node_modules/.pnpm/@rollup_plugin-typescript@12.3.0_rollup@4.57.1_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js.map +1 -0
  43. package/dist/cjs/styles/shared-styles.js +1967 -0
  44. package/dist/cjs/styles/shared-styles.js.map +1 -0
  45. package/dist/cjs/styles/utility-styles.js +725 -0
  46. package/dist/cjs/styles/utility-styles.js.map +1 -0
  47. package/dist/esm/components/fw-context-menu.js +17 -0
  48. package/dist/esm/components/fw-context-menu.js.map +1 -0
  49. package/dist/esm/components/fw-dev-mode-panel.js +273 -0
  50. package/dist/esm/components/fw-dev-mode-panel.js.map +1 -0
  51. package/dist/esm/components/fw-error-overlay.js +101 -0
  52. package/dist/esm/components/fw-error-overlay.js.map +1 -0
  53. package/dist/esm/components/fw-idle-screen.js +182 -0
  54. package/dist/esm/components/fw-idle-screen.js.map +1 -0
  55. package/dist/esm/components/fw-loading-spinner.js +62 -0
  56. package/dist/esm/components/fw-loading-spinner.js.map +1 -0
  57. package/dist/esm/components/fw-player-controls.js +258 -0
  58. package/dist/esm/components/fw-player-controls.js.map +1 -0
  59. package/dist/esm/components/fw-player.js +570 -0
  60. package/dist/esm/components/fw-player.js.map +1 -0
  61. package/dist/esm/components/fw-seek-bar.js +233 -0
  62. package/dist/esm/components/fw-seek-bar.js.map +1 -0
  63. package/dist/esm/components/fw-settings-menu.js +126 -0
  64. package/dist/esm/components/fw-settings-menu.js.map +1 -0
  65. package/dist/esm/components/fw-skip-indicator.js +143 -0
  66. package/dist/esm/components/fw-skip-indicator.js.map +1 -0
  67. package/dist/esm/components/fw-speed-indicator.js +61 -0
  68. package/dist/esm/components/fw-speed-indicator.js.map +1 -0
  69. package/dist/esm/components/fw-stats-panel.js +141 -0
  70. package/dist/esm/components/fw-stats-panel.js.map +1 -0
  71. package/dist/esm/components/fw-subtitle-renderer.js +70 -0
  72. package/dist/esm/components/fw-subtitle-renderer.js.map +1 -0
  73. package/dist/esm/components/fw-title-overlay.js +72 -0
  74. package/dist/esm/components/fw-title-overlay.js.map +1 -0
  75. package/dist/esm/components/fw-toast.js +74 -0
  76. package/dist/esm/components/fw-toast.js.map +1 -0
  77. package/dist/esm/components/fw-volume-control.js +140 -0
  78. package/dist/esm/components/fw-volume-control.js.map +1 -0
  79. package/dist/esm/controllers/player-controller-host.js +313 -0
  80. package/dist/esm/controllers/player-controller-host.js.map +1 -0
  81. package/dist/esm/define.js +43 -0
  82. package/dist/esm/define.js.map +1 -0
  83. package/dist/esm/icons/index.js +141 -0
  84. package/dist/esm/icons/index.js.map +1 -0
  85. package/dist/esm/index.js +18 -0
  86. package/dist/esm/index.js.map +1 -0
  87. package/dist/esm/node_modules/.pnpm/@rollup_plugin-typescript@12.3.0_rollup@4.57.1_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js +31 -0
  88. package/dist/esm/node_modules/.pnpm/@rollup_plugin-typescript@12.3.0_rollup@4.57.1_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js.map +1 -0
  89. package/dist/esm/styles/shared-styles.js +1965 -0
  90. package/dist/esm/styles/shared-styles.js.map +1 -0
  91. package/dist/esm/styles/utility-styles.js +723 -0
  92. package/dist/esm/styles/utility-styles.js.map +1 -0
  93. package/dist/fw-player.iife.js +4362 -0
  94. package/dist/fw-player.iife.js.map +1 -0
  95. package/dist/types/components/fw-context-menu.d.ts +15 -0
  96. package/dist/types/components/fw-dev-mode-panel.d.ts +24 -0
  97. package/dist/types/components/fw-error-overlay.d.ts +14 -0
  98. package/dist/types/components/fw-idle-screen.d.ts +13 -0
  99. package/dist/types/components/fw-loading-spinner.d.ts +10 -0
  100. package/dist/types/components/fw-player-controls.d.ts +23 -0
  101. package/dist/types/components/fw-player.d.ts +74 -0
  102. package/dist/types/components/fw-seek-bar.d.ts +33 -0
  103. package/dist/types/components/fw-settings-menu.d.ts +16 -0
  104. package/dist/types/components/fw-skip-indicator.d.ts +18 -0
  105. package/dist/types/components/fw-speed-indicator.d.ts +11 -0
  106. package/dist/types/components/fw-stats-panel.d.ts +18 -0
  107. package/dist/types/components/fw-subtitle-renderer.d.ts +21 -0
  108. package/dist/types/components/fw-title-overlay.d.ts +12 -0
  109. package/dist/types/components/fw-toast.d.ts +12 -0
  110. package/dist/types/components/fw-volume-control.d.ts +18 -0
  111. package/dist/types/controllers/player-controller-host.d.ts +119 -0
  112. package/dist/types/define.d.ts +1 -0
  113. package/dist/types/icons/index.d.ts +23 -0
  114. package/dist/types/iife-entry.d.ts +11 -0
  115. package/dist/types/index.d.ts +25 -0
  116. package/dist/types/styles/shared-styles.d.ts +1 -0
  117. package/dist/types/styles/utility-styles.d.ts +1 -0
  118. package/package.json +65 -0
  119. package/src/components/fw-context-menu.ts +23 -0
  120. package/src/components/fw-dev-mode-panel.ts +285 -0
  121. package/src/components/fw-error-overlay.ts +96 -0
  122. package/src/components/fw-idle-screen.ts +182 -0
  123. package/src/components/fw-loading-spinner.ts +63 -0
  124. package/src/components/fw-player-controls.ts +256 -0
  125. package/src/components/fw-player.ts +557 -0
  126. package/src/components/fw-seek-bar.ts +219 -0
  127. package/src/components/fw-settings-menu.ts +128 -0
  128. package/src/components/fw-skip-indicator.ts +139 -0
  129. package/src/components/fw-speed-indicator.ts +57 -0
  130. package/src/components/fw-stats-panel.ts +154 -0
  131. package/src/components/fw-subtitle-renderer.ts +65 -0
  132. package/src/components/fw-title-overlay.ts +64 -0
  133. package/src/components/fw-toast.ts +70 -0
  134. package/src/components/fw-volume-control.ts +140 -0
  135. package/src/controllers/player-controller-host.ts +457 -0
  136. package/src/define.ts +43 -0
  137. package/src/icons/index.ts +209 -0
  138. package/src/iife-entry.ts +11 -0
  139. package/src/index.ts +31 -0
  140. package/src/styles/shared-styles.ts +1962 -0
  141. package/src/styles/utility-styles.ts +720 -0
@@ -0,0 +1,457 @@
1
+ /**
2
+ * PlayerControllerHost — Lit ReactiveController wrapping the headless PlayerController.
3
+ * Direct port of usePlayerController.ts from player-react.
4
+ */
5
+ import type { ReactiveController, ReactiveControllerHost } from "lit";
6
+ import {
7
+ PlayerController,
8
+ type PlayerControllerConfig,
9
+ type PlayerState,
10
+ type StreamState,
11
+ type StreamInfo,
12
+ type PlaybackQuality,
13
+ type ContentEndpoints,
14
+ type ContentMetadata,
15
+ type ClassifiedError,
16
+ } from "@livepeer-frameworks/player-core";
17
+
18
+ export interface PlayerControllerHostState {
19
+ state: PlayerState;
20
+ streamState: StreamState | null;
21
+ endpoints: ContentEndpoints | null;
22
+ metadata: ContentMetadata | null;
23
+ videoElement: HTMLVideoElement | null;
24
+ currentTime: number;
25
+ duration: number;
26
+ isPlaying: boolean;
27
+ isPaused: boolean;
28
+ isBuffering: boolean;
29
+ isMuted: boolean;
30
+ volume: number;
31
+ error: string | null;
32
+ errorDetails: ClassifiedError["details"] | null;
33
+ isPassiveError: boolean;
34
+ hasPlaybackStarted: boolean;
35
+ isHoldingSpeed: boolean;
36
+ holdSpeed: number;
37
+ isHovering: boolean;
38
+ shouldShowControls: boolean;
39
+ isLoopEnabled: boolean;
40
+ isFullscreen: boolean;
41
+ isPiPActive: boolean;
42
+ isEffectivelyLive: boolean;
43
+ shouldShowIdleScreen: boolean;
44
+ currentPlayerInfo: { name: string; shortname: string } | null;
45
+ currentSourceInfo: { url: string; type: string } | null;
46
+ playbackQuality: PlaybackQuality | null;
47
+ subtitlesEnabled: boolean;
48
+ qualities: Array<{
49
+ id: string;
50
+ label: string;
51
+ bitrate?: number;
52
+ width?: number;
53
+ height?: number;
54
+ isAuto?: boolean;
55
+ active?: boolean;
56
+ }>;
57
+ textTracks: Array<{ id: string; label: string; language?: string; active: boolean }>;
58
+ streamInfo: StreamInfo | null;
59
+ toast: { message: string; timestamp: number } | null;
60
+ }
61
+
62
+ const initialState: PlayerControllerHostState = {
63
+ state: "booting",
64
+ streamState: null,
65
+ endpoints: null,
66
+ metadata: null,
67
+ videoElement: null,
68
+ currentTime: 0,
69
+ duration: NaN,
70
+ isPlaying: false,
71
+ isPaused: true,
72
+ isBuffering: false,
73
+ isMuted: true,
74
+ volume: 1,
75
+ error: null,
76
+ errorDetails: null,
77
+ isPassiveError: false,
78
+ hasPlaybackStarted: false,
79
+ isHoldingSpeed: false,
80
+ holdSpeed: 2,
81
+ isHovering: false,
82
+ shouldShowControls: false,
83
+ isLoopEnabled: false,
84
+ isFullscreen: false,
85
+ isPiPActive: false,
86
+ isEffectivelyLive: false,
87
+ shouldShowIdleScreen: true,
88
+ currentPlayerInfo: null,
89
+ currentSourceInfo: null,
90
+ playbackQuality: null,
91
+ subtitlesEnabled: false,
92
+ qualities: [],
93
+ textTracks: [],
94
+ streamInfo: null,
95
+ toast: null,
96
+ };
97
+
98
+ type HostElement = ReactiveControllerHost & HTMLElement;
99
+
100
+ export class PlayerControllerHost implements ReactiveController {
101
+ host: HostElement;
102
+ private controller: PlayerController | null = null;
103
+ private unsubs: Array<() => void> = [];
104
+ private currentConfig: PlayerControllerConfig | null = null;
105
+
106
+ s: PlayerControllerHostState = { ...initialState };
107
+
108
+ constructor(host: HostElement) {
109
+ this.host = host;
110
+ host.addController(this);
111
+ }
112
+
113
+ // ---- Configuration & Lifecycle ----
114
+
115
+ configure(config: PlayerControllerConfig) {
116
+ this.currentConfig = config;
117
+ }
118
+
119
+ async attach(container: HTMLDivElement) {
120
+ if (!this.currentConfig) return;
121
+ this.teardown();
122
+
123
+ const controller = new PlayerController({
124
+ contentId: this.currentConfig.contentId,
125
+ contentType: this.currentConfig.contentType,
126
+ endpoints: this.currentConfig.endpoints,
127
+ gatewayUrl: this.currentConfig.gatewayUrl,
128
+ mistUrl: this.currentConfig.mistUrl,
129
+ authToken: this.currentConfig.authToken,
130
+ autoplay: this.currentConfig.autoplay,
131
+ muted: this.currentConfig.muted,
132
+ controls: this.currentConfig.controls,
133
+ poster: this.currentConfig.poster,
134
+ debug: this.currentConfig.debug,
135
+ });
136
+
137
+ this.controller = controller;
138
+ this.subscribeToEvents(controller);
139
+
140
+ this.update({ isLoopEnabled: controller.isLoopEnabled() });
141
+
142
+ try {
143
+ await controller.attach(container);
144
+ } catch (err) {
145
+ console.warn("[PlayerControllerHost] Attach failed:", err);
146
+ }
147
+ }
148
+
149
+ hostConnected() {
150
+ // Controller attachment happens in firstUpdated of the host element
151
+ }
152
+
153
+ hostDisconnected() {
154
+ this.teardown();
155
+ this.s = { ...initialState };
156
+ }
157
+
158
+ private teardown() {
159
+ this.unsubs.forEach((fn) => fn());
160
+ this.unsubs = [];
161
+ this.controller?.destroy();
162
+ this.controller = null;
163
+ }
164
+
165
+ // ---- State Updates ----
166
+
167
+ private update(partial: Partial<PlayerControllerHostState>) {
168
+ Object.assign(this.s, partial);
169
+ this.host.requestUpdate();
170
+ }
171
+
172
+ private syncState() {
173
+ if (!this.controller) return;
174
+ const c = this.controller;
175
+ this.update({
176
+ isPlaying: c.isPlaying(),
177
+ isPaused: c.isPaused(),
178
+ isBuffering: c.isBuffering(),
179
+ isMuted: c.isMuted(),
180
+ volume: c.getVolume(),
181
+ hasPlaybackStarted: c.hasPlaybackStarted(),
182
+ shouldShowControls: c.shouldShowControls(),
183
+ shouldShowIdleScreen: c.shouldShowIdleScreen(),
184
+ playbackQuality: c.getPlaybackQuality(),
185
+ isLoopEnabled: c.isLoopEnabled(),
186
+ subtitlesEnabled: c.isSubtitlesEnabled(),
187
+ qualities: c.getQualities(),
188
+ streamInfo: c.getStreamInfo(),
189
+ });
190
+ }
191
+
192
+ // ---- Event Subscriptions (mirrors usePlayerController exactly) ----
193
+
194
+ private subscribeToEvents(controller: PlayerController) {
195
+ const u = this.unsubs;
196
+
197
+ u.push(
198
+ controller.on("stateChange", ({ state }) => {
199
+ this.update({ state });
200
+ this.dispatchEvent("fw-state-change", { state });
201
+ })
202
+ );
203
+
204
+ u.push(
205
+ controller.on("streamStateChange", ({ state: streamState }) => {
206
+ this.update({
207
+ streamState,
208
+ metadata: controller.getMetadata(),
209
+ isEffectivelyLive: controller.isEffectivelyLive(),
210
+ shouldShowIdleScreen: controller.shouldShowIdleScreen(),
211
+ });
212
+ this.dispatchEvent("fw-stream-state", { state: streamState });
213
+ })
214
+ );
215
+
216
+ u.push(
217
+ controller.on("timeUpdate", ({ currentTime, duration }) => {
218
+ this.update({ currentTime, duration });
219
+ this.dispatchEvent("fw-time-update", { currentTime, duration });
220
+ })
221
+ );
222
+
223
+ u.push(
224
+ controller.on("error", ({ error }) => {
225
+ this.update({
226
+ error,
227
+ isPassiveError: controller.isPassiveError(),
228
+ });
229
+ this.dispatchEvent("fw-error", { error });
230
+ })
231
+ );
232
+
233
+ u.push(
234
+ controller.on("errorCleared", () => {
235
+ this.update({ error: null, isPassiveError: false });
236
+ })
237
+ );
238
+
239
+ u.push(
240
+ controller.on("ready", ({ videoElement }) => {
241
+ this.update({
242
+ videoElement,
243
+ endpoints: controller.getEndpoints(),
244
+ metadata: controller.getMetadata(),
245
+ streamInfo: controller.getStreamInfo(),
246
+ isEffectivelyLive: controller.isEffectivelyLive(),
247
+ shouldShowIdleScreen: controller.shouldShowIdleScreen(),
248
+ currentPlayerInfo: controller.getCurrentPlayerInfo(),
249
+ currentSourceInfo: controller.getCurrentSourceInfo(),
250
+ qualities: controller.getQualities(),
251
+ });
252
+ this.dispatchEvent("fw-ready", { videoElement });
253
+
254
+ const handleVideoEvent = () => {
255
+ if (this.controller?.shouldSuppressVideoEvents?.()) return;
256
+ this.syncState();
257
+ };
258
+ videoElement.addEventListener("play", handleVideoEvent);
259
+ videoElement.addEventListener("pause", handleVideoEvent);
260
+ videoElement.addEventListener("waiting", handleVideoEvent);
261
+ videoElement.addEventListener("playing", handleVideoEvent);
262
+ u.push(() => {
263
+ videoElement.removeEventListener("play", handleVideoEvent);
264
+ videoElement.removeEventListener("pause", handleVideoEvent);
265
+ videoElement.removeEventListener("waiting", handleVideoEvent);
266
+ videoElement.removeEventListener("playing", handleVideoEvent);
267
+ });
268
+ })
269
+ );
270
+
271
+ u.push(
272
+ controller.on("playerSelected", ({ player: _player, source }) => {
273
+ this.update({
274
+ currentPlayerInfo: controller.getCurrentPlayerInfo(),
275
+ currentSourceInfo: { url: source.url, type: source.type },
276
+ qualities: controller.getQualities(),
277
+ });
278
+ })
279
+ );
280
+
281
+ u.push(
282
+ controller.on("volumeChange", ({ volume, muted }) => {
283
+ this.update({ volume, isMuted: muted });
284
+ this.dispatchEvent("fw-volume-change", { volume, muted });
285
+ })
286
+ );
287
+
288
+ u.push(
289
+ controller.on("loopChange", ({ isLoopEnabled }) => {
290
+ this.update({ isLoopEnabled });
291
+ })
292
+ );
293
+
294
+ u.push(
295
+ controller.on("fullscreenChange", ({ isFullscreen }) => {
296
+ this.update({ isFullscreen });
297
+ this.dispatchEvent("fw-fullscreen-change", { isFullscreen });
298
+ })
299
+ );
300
+
301
+ u.push(
302
+ controller.on("pipChange", ({ isPiP }) => {
303
+ this.update({ isPiPActive: isPiP });
304
+ this.dispatchEvent("fw-pip-change", { isPiP });
305
+ })
306
+ );
307
+
308
+ u.push(
309
+ controller.on("holdSpeedStart", ({ speed }) => {
310
+ this.update({ isHoldingSpeed: true, holdSpeed: speed });
311
+ })
312
+ );
313
+
314
+ u.push(
315
+ controller.on("holdSpeedEnd", () => {
316
+ this.update({ isHoldingSpeed: false });
317
+ })
318
+ );
319
+
320
+ u.push(
321
+ controller.on("hoverStart", () => {
322
+ this.update({ isHovering: true, shouldShowControls: true });
323
+ })
324
+ );
325
+
326
+ u.push(
327
+ controller.on("hoverEnd", () => {
328
+ this.update({
329
+ isHovering: false,
330
+ shouldShowControls: controller.shouldShowControls(),
331
+ });
332
+ })
333
+ );
334
+
335
+ u.push(
336
+ controller.on("captionsChange", ({ enabled }) => {
337
+ this.update({ subtitlesEnabled: enabled });
338
+ })
339
+ );
340
+
341
+ u.push(
342
+ controller.on("protocolSwapped", (data) => {
343
+ const message = `Switched to ${data.toProtocol}`;
344
+ this.update({ toast: { message, timestamp: Date.now() } });
345
+ this.dispatchEvent("fw-protocol-swapped", data);
346
+ })
347
+ );
348
+
349
+ u.push(
350
+ controller.on("playbackFailed", (data) => {
351
+ this.update({
352
+ error: data.message,
353
+ errorDetails: data.details ?? null,
354
+ isPassiveError: false,
355
+ });
356
+ this.dispatchEvent("fw-playback-failed", {
357
+ code: data.code,
358
+ message: data.message,
359
+ });
360
+ })
361
+ );
362
+ }
363
+
364
+ // ---- Event Dispatching ----
365
+
366
+ private dispatchEvent(name: string, detail: unknown) {
367
+ this.host.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }));
368
+ }
369
+
370
+ // ---- Action Methods ----
371
+
372
+ async play() {
373
+ await this.controller?.play();
374
+ }
375
+ pause() {
376
+ this.controller?.pause();
377
+ }
378
+ togglePlay() {
379
+ this.controller?.togglePlay();
380
+ }
381
+ seek(time: number) {
382
+ this.controller?.seek(time);
383
+ }
384
+ seekBy(delta: number) {
385
+ this.controller?.seekBy(delta);
386
+ }
387
+ jumpToLive() {
388
+ this.controller?.jumpToLive();
389
+ }
390
+ setVolume(volume: number) {
391
+ this.controller?.setVolume(volume);
392
+ }
393
+ toggleMute() {
394
+ this.controller?.toggleMute();
395
+ }
396
+ toggleLoop() {
397
+ this.controller?.toggleLoop();
398
+ }
399
+ async toggleFullscreen() {
400
+ await this.controller?.toggleFullscreen();
401
+ }
402
+ async togglePiP() {
403
+ await this.controller?.togglePictureInPicture();
404
+ }
405
+ toggleSubtitles() {
406
+ this.controller?.toggleSubtitles();
407
+ }
408
+
409
+ clearError() {
410
+ this.controller?.clearError();
411
+ this.update({ error: null, errorDetails: null, isPassiveError: false });
412
+ }
413
+
414
+ dismissToast() {
415
+ this.update({ toast: null });
416
+ }
417
+
418
+ async retry() {
419
+ await this.controller?.retry();
420
+ }
421
+ async reload() {
422
+ await this.controller?.reload();
423
+ }
424
+
425
+ getQualities() {
426
+ return this.controller?.getQualities() ?? [];
427
+ }
428
+ selectQuality(id: string) {
429
+ this.controller?.selectQuality(id);
430
+ }
431
+
432
+ handleMouseEnter() {
433
+ this.controller?.handleMouseEnter();
434
+ }
435
+ handleMouseLeave() {
436
+ this.controller?.handleMouseLeave();
437
+ }
438
+ handleMouseMove() {
439
+ this.controller?.handleMouseMove();
440
+ }
441
+ handleTouchStart() {
442
+ this.controller?.handleTouchStart();
443
+ }
444
+
445
+ async setDevModeOptions(options: {
446
+ forcePlayer?: string;
447
+ forceType?: string;
448
+ forceSource?: number;
449
+ playbackMode?: "auto" | "low-latency" | "quality" | "vod";
450
+ }) {
451
+ await this.controller?.setDevModeOptions(options);
452
+ }
453
+
454
+ getController(): PlayerController | null {
455
+ return this.controller;
456
+ }
457
+ }
package/src/define.ts ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Side-effect import that registers all custom elements.
3
+ * Usage: import '@livepeer-frameworks/player-wc/define';
4
+ */
5
+ import { FwPlayer } from "./components/fw-player.js";
6
+ import { FwPlayerControls } from "./components/fw-player-controls.js";
7
+ import { FwSeekBar } from "./components/fw-seek-bar.js";
8
+ import { FwVolumeControl } from "./components/fw-volume-control.js";
9
+ import { FwSettingsMenu } from "./components/fw-settings-menu.js";
10
+ import { FwIdleScreen } from "./components/fw-idle-screen.js";
11
+ import { FwLoadingSpinner } from "./components/fw-loading-spinner.js";
12
+ import { FwTitleOverlay } from "./components/fw-title-overlay.js";
13
+ import { FwErrorOverlay } from "./components/fw-error-overlay.js";
14
+ import { FwToast } from "./components/fw-toast.js";
15
+ import { FwStatsPanel } from "./components/fw-stats-panel.js";
16
+ import { FwDevModePanel } from "./components/fw-dev-mode-panel.js";
17
+ import { FwSubtitleRenderer } from "./components/fw-subtitle-renderer.js";
18
+ import { FwSkipIndicator } from "./components/fw-skip-indicator.js";
19
+ import { FwSpeedIndicator } from "./components/fw-speed-indicator.js";
20
+ import { FwContextMenu } from "./components/fw-context-menu.js";
21
+
22
+ function safeDefine(name: string, ctor: CustomElementConstructor) {
23
+ if (!customElements.get(name)) {
24
+ customElements.define(name, ctor);
25
+ }
26
+ }
27
+
28
+ safeDefine("fw-player", FwPlayer);
29
+ safeDefine("fw-player-controls", FwPlayerControls);
30
+ safeDefine("fw-seek-bar", FwSeekBar);
31
+ safeDefine("fw-volume-control", FwVolumeControl);
32
+ safeDefine("fw-settings-menu", FwSettingsMenu);
33
+ safeDefine("fw-idle-screen", FwIdleScreen);
34
+ safeDefine("fw-loading-spinner", FwLoadingSpinner);
35
+ safeDefine("fw-title-overlay", FwTitleOverlay);
36
+ safeDefine("fw-error-overlay", FwErrorOverlay);
37
+ safeDefine("fw-toast", FwToast);
38
+ safeDefine("fw-stats-panel", FwStatsPanel);
39
+ safeDefine("fw-dev-mode-panel", FwDevModePanel);
40
+ safeDefine("fw-subtitle-renderer", FwSubtitleRenderer);
41
+ safeDefine("fw-skip-indicator", FwSkipIndicator);
42
+ safeDefine("fw-speed-indicator", FwSpeedIndicator);
43
+ safeDefine("fw-context-menu", FwContextMenu);
@@ -0,0 +1,209 @@
1
+ /**
2
+ * SVG icons as Lit html template functions.
3
+ * Port of Icons.tsx from player-react.
4
+ */
5
+ import { html, svg, type TemplateResult } from "lit";
6
+
7
+ type IconTemplate = (size?: number, color?: string) => TemplateResult;
8
+
9
+ export const playIcon: IconTemplate = (size = 16, color = "currentColor") => html`
10
+ <svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" aria-hidden="true">
11
+ <path d="M8 5v14l11-7z" fill="${color}" />
12
+ </svg>
13
+ `;
14
+
15
+ export const pauseIcon: IconTemplate = (size = 16, color = "currentColor") => html`
16
+ <svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" aria-hidden="true">
17
+ <rect x="6" y="4" width="4" height="16" fill="${color}" />
18
+ <rect x="14" y="4" width="4" height="16" fill="${color}" />
19
+ </svg>
20
+ `;
21
+
22
+ export const skipBackIcon: IconTemplate = (size = 16, color = "currentColor") => html`
23
+ <svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" aria-hidden="true">
24
+ <path
25
+ d="M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"
26
+ fill="${color}"
27
+ />
28
+ ${svg`<text x="12" y="15" font-size="7" font-weight="bold" fill="${color}" text-anchor="middle">10</text>`}
29
+ </svg>
30
+ `;
31
+
32
+ export const skipForwardIcon: IconTemplate = (size = 16, color = "currentColor") => html`
33
+ <svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" aria-hidden="true">
34
+ <path
35
+ d="M12 5V1l5 5-5 5V7c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6h2c0 4.42-3.58 8-8 8s-8-3.58-8-8 3.58-8 8-8z"
36
+ fill="${color}"
37
+ />
38
+ ${svg`<text x="12" y="15" font-size="7" font-weight="bold" fill="${color}" text-anchor="middle">10</text>`}
39
+ </svg>
40
+ `;
41
+
42
+ export const volumeUpIcon: IconTemplate = (size = 16, color = "currentColor") => html`
43
+ <svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" aria-hidden="true">
44
+ <polygon points="11,5 6,9 2,9 2,15 6,15 11,19" fill="${color}" />
45
+ <path
46
+ d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"
47
+ stroke="${color}"
48
+ stroke-width="2"
49
+ stroke-linecap="round"
50
+ stroke-linejoin="round"
51
+ />
52
+ </svg>
53
+ `;
54
+
55
+ export const volumeOffIcon: IconTemplate = (size = 16, color = "currentColor") => html`
56
+ <svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" aria-hidden="true">
57
+ <polygon points="11,5 6,9 2,9 2,15 6,15 11,19" fill="${color}" />
58
+ <line
59
+ x1="23"
60
+ y1="9"
61
+ x2="17"
62
+ y2="15"
63
+ stroke="${color}"
64
+ stroke-width="2"
65
+ stroke-linecap="round"
66
+ />
67
+ <line
68
+ x1="17"
69
+ y1="9"
70
+ x2="23"
71
+ y2="15"
72
+ stroke="${color}"
73
+ stroke-width="2"
74
+ stroke-linecap="round"
75
+ />
76
+ </svg>
77
+ `;
78
+
79
+ export const fullscreenIcon: IconTemplate = (size = 16, color = "currentColor") => html`
80
+ <svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" aria-hidden="true">
81
+ <path
82
+ d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3m8 0h3a2 2 0 0 0 2-2v-3"
83
+ stroke="${color}"
84
+ stroke-width="2"
85
+ stroke-linecap="round"
86
+ stroke-linejoin="round"
87
+ />
88
+ </svg>
89
+ `;
90
+
91
+ export const fullscreenExitIcon: IconTemplate = (size = 16, color = "currentColor") => html`
92
+ <svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" aria-hidden="true">
93
+ <path
94
+ d="M8 3v3a2 2 0 0 1-2 2H3M21 8h-3a2 2 0 0 1-2-2V3M3 16h3a2 2 0 0 1 2 2v3M16 21v-3a2 2 0 0 1 2-2h3"
95
+ stroke="${color}"
96
+ stroke-width="2"
97
+ stroke-linecap="round"
98
+ stroke-linejoin="round"
99
+ />
100
+ </svg>
101
+ `;
102
+
103
+ export const pictureInPictureIcon: IconTemplate = (size = 16, color = "currentColor") => html`
104
+ <svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" aria-hidden="true">
105
+ <rect
106
+ x="2"
107
+ y="3"
108
+ width="20"
109
+ height="14"
110
+ rx="2"
111
+ ry="2"
112
+ stroke="${color}"
113
+ stroke-width="2"
114
+ fill="none"
115
+ />
116
+ <rect x="8" y="10" width="10" height="6" rx="1" ry="1" fill="${color}" />
117
+ </svg>
118
+ `;
119
+
120
+ export const closedCaptionsIcon: IconTemplate = (size = 16, color = "currentColor") => html`
121
+ <svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" aria-hidden="true">
122
+ <rect
123
+ x="2"
124
+ y="4"
125
+ width="20"
126
+ height="16"
127
+ rx="2"
128
+ ry="2"
129
+ stroke="${color}"
130
+ stroke-width="2"
131
+ fill="none"
132
+ />
133
+ <path
134
+ d="M8 10c0-.6.4-1 1-1h1c.6 0 1 .4 1 1v4c0 .6-.4 1-1 1H9c-.6 0-1-.4-1-1v-4zM14 10c0-.6.4-1 1-1h1c.6 0 1 .4 1 1v4c0 .6-.4 1-1 1h-1c-.6 0-1-.4-1-1v-4z"
135
+ fill="${color}"
136
+ />
137
+ </svg>
138
+ `;
139
+
140
+ export const liveIcon: IconTemplate = (size = 16, color = "currentColor") => html`
141
+ <svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" aria-hidden="true">
142
+ <circle cx="12" cy="12" r="3" fill="${color}" />
143
+ <path
144
+ d="M12 1v6M12 17v6M4.22 4.22l4.24 4.24M15.54 15.54l4.24 4.24M1 12h6M17 12h6M4.22 19.78l4.24-4.24M15.54 8.46l4.24-4.24"
145
+ stroke="${color}"
146
+ stroke-width="2"
147
+ stroke-linecap="round"
148
+ />
149
+ </svg>
150
+ `;
151
+
152
+ export const settingsIcon: IconTemplate = (size = 16, color = "currentColor") => html`
153
+ <svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" aria-hidden="true">
154
+ <path
155
+ d="M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z"
156
+ stroke="${color}"
157
+ stroke-width="2"
158
+ stroke-linecap="round"
159
+ stroke-linejoin="round"
160
+ />
161
+ <path
162
+ d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1Z"
163
+ stroke="${color}"
164
+ stroke-width="2"
165
+ stroke-linecap="round"
166
+ stroke-linejoin="round"
167
+ />
168
+ </svg>
169
+ `;
170
+
171
+ export const statsIcon: IconTemplate = (size = 16, color = "currentColor") => html`
172
+ <svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" aria-hidden="true">
173
+ <rect x="4" y="13" width="4" height="7" fill="${color}" />
174
+ <rect x="10" y="9" width="4" height="11" fill="${color}" />
175
+ <rect x="16" y="4" width="4" height="16" fill="${color}" />
176
+ </svg>
177
+ `;
178
+
179
+ export const seekToLiveIcon: IconTemplate = (size = 16, color = "currentColor") => html`
180
+ <svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" aria-hidden="true">
181
+ <path d="M5 5v14l11-7z" fill="${color}" />
182
+ <rect x="17" y="5" width="3" height="14" fill="${color}" />
183
+ </svg>
184
+ `;
185
+
186
+ export const closeIcon: IconTemplate = (size = 12, color = "currentColor") => html`
187
+ <svg width="${size}" height="${size}" viewBox="0 0 12 12" fill="none" aria-hidden="true">
188
+ <path d="M9 3L3 9M3 3L9 9" stroke="${color}" stroke-width="1.5" stroke-linecap="round" />
189
+ </svg>
190
+ `;
191
+
192
+ export const loopIcon: IconTemplate = (size = 14, color = "currentColor") => html`
193
+ <svg
194
+ width="${size}"
195
+ height="${size}"
196
+ viewBox="0 0 24 24"
197
+ fill="none"
198
+ stroke="${color}"
199
+ stroke-width="2"
200
+ stroke-linecap="round"
201
+ stroke-linejoin="round"
202
+ aria-hidden="true"
203
+ >
204
+ <polyline points="17 1 21 5 17 9"></polyline>
205
+ <path d="M3 11V9a4 4 0 0 1 4-4h14"></path>
206
+ <polyline points="7 23 3 19 7 15"></polyline>
207
+ <path d="M21 13v2a4 4 0 0 1-4 4H3"></path>
208
+ </svg>
209
+ `;