@codenkay/video-nsgplayer-core 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.
package/dist/index.js ADDED
@@ -0,0 +1,1227 @@
1
+ // src/errors/PlayerError.ts
2
+ var PlayerErrorCode = /* @__PURE__ */ ((PlayerErrorCode2) => {
3
+ PlayerErrorCode2["INVALID_CONFIGURATION"] = "INVALID_CONFIGURATION";
4
+ PlayerErrorCode2["AUTHENTICATION_FAILED"] = "AUTHENTICATION_FAILED";
5
+ PlayerErrorCode2["AUTH_TOKEN_REFRESH_FAILED"] = "AUTH_TOKEN_REFRESH_FAILED";
6
+ PlayerErrorCode2["TOKEN_EXPIRED"] = "TOKEN_EXPIRED";
7
+ PlayerErrorCode2["TOKEN_REFRESH_FAILED"] = "TOKEN_REFRESH_FAILED";
8
+ PlayerErrorCode2["SOURCE_RESOLUTION_FAILED"] = "SOURCE_RESOLUTION_FAILED";
9
+ PlayerErrorCode2["MANIFEST_LOAD_FAILED"] = "MANIFEST_LOAD_FAILED";
10
+ PlayerErrorCode2["SEGMENT_LOAD_FAILED"] = "SEGMENT_LOAD_FAILED";
11
+ PlayerErrorCode2["NETWORK_ERROR"] = "NETWORK_ERROR";
12
+ PlayerErrorCode2["PLAYBACK_ERROR"] = "PLAYBACK_ERROR";
13
+ PlayerErrorCode2["UNSUPPORTED_BROWSER"] = "UNSUPPORTED_BROWSER";
14
+ PlayerErrorCode2["AUTOPLAY_BLOCKED"] = "AUTOPLAY_BLOCKED";
15
+ PlayerErrorCode2["UNKNOWN"] = "UNKNOWN";
16
+ return PlayerErrorCode2;
17
+ })(PlayerErrorCode || {});
18
+ function createPlayerError(code, message, options) {
19
+ return {
20
+ code,
21
+ message: redactSensitive(message),
22
+ category: options?.category ?? "unknown",
23
+ fatal: options?.fatal ?? true,
24
+ retryable: options?.retryable ?? false,
25
+ timestamp: Date.now(),
26
+ cause: options?.cause
27
+ };
28
+ }
29
+ function redactSensitive(input) {
30
+ return input.replace(/(Bearer\s+)[^\s]+/gi, "$1[REDACTED]").replace(/([?&](token|expires|signature|sig)=)[^&]+/gi, "$1[REDACTED]").replace(/(Authorization:\s*)[^\s]+/gi, "$1[REDACTED]");
31
+ }
32
+
33
+ // src/auth/AuthManager.ts
34
+ var AuthManager = class {
35
+ constructor(auth, refreshBeforeExpiryMs, logger, onRefreshed, onError, maxUnauthorizedRetries = 1) {
36
+ this.auth = auth;
37
+ this.refreshBeforeExpiryMs = refreshBeforeExpiryMs;
38
+ this.logger = logger;
39
+ this.onRefreshed = onRefreshed;
40
+ this.onError = onError;
41
+ this.token = null;
42
+ this.inFlight = null;
43
+ this.timer = null;
44
+ this.destroyed = false;
45
+ /** Consecutive unauthorized refresh attempts (reset on success). */
46
+ this.unauthorizedAttempts = 0;
47
+ this.maxUnauthorizedRetries = Math.max(0, maxUnauthorizedRetries);
48
+ }
49
+ getAccessToken() {
50
+ return this.token?.accessToken ?? null;
51
+ }
52
+ getExpiresAt() {
53
+ return this.token?.expiresAt ?? null;
54
+ }
55
+ async ensureToken(context) {
56
+ if (!this.auth?.getToken) return null;
57
+ if (this.destroyed) return null;
58
+ if (this.token && context.reason !== "unauthorized" && context.reason !== "manual" && this.token.expiresAt - Date.now() > this.refreshBeforeExpiryMs) {
59
+ return this.token;
60
+ }
61
+ return this.refresh(context);
62
+ }
63
+ async refresh(context) {
64
+ if (!this.auth?.getToken) {
65
+ throw createPlayerError(
66
+ "INVALID_CONFIGURATION" /* INVALID_CONFIGURATION */,
67
+ "auth.getToken is required for authenticated playback",
68
+ { category: "config", fatal: true, retryable: false }
69
+ );
70
+ }
71
+ if (context.reason === "unauthorized") {
72
+ if (this.unauthorizedAttempts >= this.maxUnauthorizedRetries) {
73
+ const message = "Auth token refresh aborted: exceeded bounded unauthorized retry limit";
74
+ this.onError?.(message, false);
75
+ throw createPlayerError(
76
+ "AUTH_TOKEN_REFRESH_FAILED" /* AUTH_TOKEN_REFRESH_FAILED */,
77
+ message,
78
+ { category: "auth", fatal: true, retryable: false }
79
+ );
80
+ }
81
+ this.unauthorizedAttempts += 1;
82
+ }
83
+ if (this.inFlight) return this.inFlight;
84
+ this.inFlight = (async () => {
85
+ try {
86
+ const next = await this.auth.getToken(context);
87
+ if (!next?.accessToken || typeof next.expiresAt !== "number" || next.expiresAt <= 0) {
88
+ throw createPlayerError(
89
+ "AUTHENTICATION_FAILED" /* AUTHENTICATION_FAILED */,
90
+ "auth.getToken returned an invalid token payload",
91
+ { category: "auth", fatal: true, retryable: true }
92
+ );
93
+ }
94
+ this.token = next;
95
+ if (context.reason !== "unauthorized") {
96
+ this.unauthorizedAttempts = 0;
97
+ }
98
+ this.scheduleProactiveRefresh();
99
+ this.onRefreshed?.(next);
100
+ this.logger.debug("Auth token refreshed", {
101
+ reason: context.reason,
102
+ expiresAt: next.expiresAt
103
+ });
104
+ return next;
105
+ } catch (error) {
106
+ if (error && typeof error === "object" && "code" in error && error.code === "AUTH_TOKEN_REFRESH_FAILED" /* AUTH_TOKEN_REFRESH_FAILED */) {
107
+ throw error;
108
+ }
109
+ const message = error instanceof Error ? error.message : "Auth token refresh failed";
110
+ const retryable = this.unauthorizedAttempts <= this.maxUnauthorizedRetries;
111
+ this.onError?.(message, retryable);
112
+ throw createPlayerError(
113
+ "AUTH_TOKEN_REFRESH_FAILED" /* AUTH_TOKEN_REFRESH_FAILED */,
114
+ message,
115
+ { category: "auth", fatal: !retryable, retryable, cause: error }
116
+ );
117
+ } finally {
118
+ this.inFlight = null;
119
+ }
120
+ })();
121
+ return this.inFlight;
122
+ }
123
+ scheduleProactiveRefresh() {
124
+ if (this.timer) clearTimeout(this.timer);
125
+ if (!this.token || this.destroyed) return;
126
+ const delay = Math.max(
127
+ 5e3,
128
+ this.token.expiresAt - Date.now() - this.refreshBeforeExpiryMs
129
+ );
130
+ this.timer = setTimeout(() => {
131
+ void this.refresh({
132
+ reason: "expiring",
133
+ videoId: void 0
134
+ }).catch(() => {
135
+ });
136
+ }, delay);
137
+ }
138
+ destroy() {
139
+ this.destroyed = true;
140
+ if (this.timer) clearTimeout(this.timer);
141
+ this.timer = null;
142
+ this.token = null;
143
+ this.inFlight = null;
144
+ }
145
+ };
146
+
147
+ // src/config/defaults.ts
148
+ var PLAYBACK_RATES = [0.5, 0.75, 1, 1.25, 1.5, 2];
149
+ var PACKAGE_VERSION = "0.1.0";
150
+ var DEFAULT_PLAYER_BEHAVIOR = {
151
+ autoplay: true,
152
+ startMuted: true,
153
+ volume: 1,
154
+ startPosition: 0,
155
+ skipSeconds: 10,
156
+ speedOptionCount: PLAYBACK_RATES.length,
157
+ zoomEnabled: true,
158
+ defaultZoom: 1,
159
+ playbackRates: PLAYBACK_RATES
160
+ };
161
+ var DEFAULT_CONTROLS = {
162
+ enabled: true,
163
+ showQuality: true,
164
+ showSpeed: true,
165
+ showCaptions: true,
166
+ showAudio: true,
167
+ showVolume: true,
168
+ showFullscreen: true,
169
+ showZoom: true,
170
+ showSkip: true,
171
+ showLive: true,
172
+ seekCueDurationMs: 720,
173
+ keyboardShortcuts: true
174
+ };
175
+ var DEFAULT_THEME = {
176
+ playIconPreset: "triangle",
177
+ playButtonBackground: "#ffffff",
178
+ playButtonIconColor: "#ffffff",
179
+ seekTrackColor: "#334155",
180
+ seekProgressColor: "#38bdf8",
181
+ seekBufferedColor: "#64748b",
182
+ timeColor: "#e2e8f0",
183
+ controlIconColor: "#f1f5f9",
184
+ focusRingColor: "#38bdf8",
185
+ spinnerColor: "#38bdf8",
186
+ volumeIconColor: "#f1f5f9",
187
+ volumeTrackColor: "#334155",
188
+ volumeProgressColor: "#38bdf8",
189
+ qualityOptionTextColor: "#e2e8f0",
190
+ qualityOptionBackground: "#020617",
191
+ qualityOptionActiveBackground: "#0c4a6e",
192
+ qualityOptionActiveTextColor: "#bae6fd",
193
+ speedOptionTextColor: "#e2e8f0",
194
+ speedOptionBackground: "#020617",
195
+ speedOptionActiveBackground: "#0c4a6e",
196
+ speedOptionActiveTextColor: "#bae6fd",
197
+ zoomIconColor: "#f1f5f9",
198
+ skipIconColor: "#f1f5f9",
199
+ skipCueBackground: "#ffffff",
200
+ skipCueTextColor: "#ffffff",
201
+ liveDotColor: "#ef4444"
202
+ };
203
+ var DEFAULT_WATERMARK = {
204
+ enabled: true,
205
+ text: "",
206
+ durationMs: 1e4,
207
+ intervalMaxMs: 3e5
208
+ };
209
+ var DEFAULT_PLAYER_CONFIG = {
210
+ player: DEFAULT_PLAYER_BEHAVIOR,
211
+ controls: DEFAULT_CONTROLS,
212
+ theme: DEFAULT_THEME,
213
+ watermark: DEFAULT_WATERMARK,
214
+ streaming: {
215
+ engine: "hls",
216
+ hls: {
217
+ enableWorker: true,
218
+ lowLatencyMode: true,
219
+ backBufferLength: 90,
220
+ fatalRecoveryLimit: 3
221
+ }
222
+ },
223
+ logging: { enabled: false, level: "error" },
224
+ debug: { enabled: false, network: false, events: false, state: false },
225
+ accessibility: { reducedMotion: false }
226
+ };
227
+ var FORBIDDEN_SECRET_KEYS = [
228
+ "clientsecret",
229
+ "apisecret",
230
+ "privatekey",
231
+ "video_api_secret",
232
+ "basicauthpassword",
233
+ "password"
234
+ ];
235
+ function assertNoSecretsInConfig(raw) {
236
+ if (!raw || typeof raw !== "object") return;
237
+ const walk = (obj, path) => {
238
+ for (const [key, value] of Object.entries(obj)) {
239
+ const lower = key.toLowerCase().replace(/[^a-z0-9]/g, "");
240
+ if (FORBIDDEN_SECRET_KEYS.some((k) => lower.includes(k))) {
241
+ throw new Error(
242
+ `INVALID_CONFIGURATION: Forbidden secret key "${path}${key}" must never be passed to the browser SDK. Keep clientSecret on your server/BFF and use auth.getToken().`
243
+ );
244
+ }
245
+ if (value && typeof value === "object" && !Array.isArray(value)) {
246
+ walk(value, `${path}${key}.`);
247
+ }
248
+ }
249
+ };
250
+ walk(raw, "");
251
+ }
252
+ function mergePlayerConfig(partial) {
253
+ assertNoSecretsInConfig(partial);
254
+ return {
255
+ player: { ...DEFAULT_PLAYER_BEHAVIOR, ...partial?.player },
256
+ controls: { ...DEFAULT_CONTROLS, ...partial?.controls },
257
+ theme: { ...DEFAULT_THEME, ...partial?.theme },
258
+ watermark: { ...DEFAULT_WATERMARK, ...partial?.watermark },
259
+ streaming: {
260
+ engine: "hls",
261
+ hls: {
262
+ ...DEFAULT_PLAYER_CONFIG.streaming.hls,
263
+ ...partial?.streaming?.hls
264
+ }
265
+ },
266
+ logging: { ...DEFAULT_PLAYER_CONFIG.logging, ...partial?.logging },
267
+ debug: { ...DEFAULT_PLAYER_CONFIG.debug, ...partial?.debug },
268
+ accessibility: {
269
+ ...DEFAULT_PLAYER_CONFIG.accessibility,
270
+ ...partial?.accessibility
271
+ }
272
+ };
273
+ }
274
+ function themeToCssVars(theme) {
275
+ return {
276
+ "--player-play-btn-bg": theme.playButtonBackground,
277
+ "--player-play-btn-icon": theme.playButtonIconColor,
278
+ "--player-seek-track": theme.seekTrackColor,
279
+ "--player-seek-progress": theme.seekProgressColor,
280
+ "--player-seek-buffered": theme.seekBufferedColor,
281
+ "--player-time": theme.timeColor,
282
+ "--player-control-icon": theme.controlIconColor,
283
+ "--player-focus-ring": theme.focusRingColor,
284
+ "--player-spinner": theme.spinnerColor,
285
+ "--player-volume-icon": theme.volumeIconColor,
286
+ "--player-volume-track": theme.volumeTrackColor,
287
+ "--player-volume-progress": theme.volumeProgressColor,
288
+ "--player-quality-text": theme.qualityOptionTextColor,
289
+ "--player-quality-bg": theme.qualityOptionBackground,
290
+ "--player-quality-active-bg": theme.qualityOptionActiveBackground,
291
+ "--player-quality-active-text": theme.qualityOptionActiveTextColor,
292
+ "--player-speed-text": theme.speedOptionTextColor,
293
+ "--player-speed-bg": theme.speedOptionBackground,
294
+ "--player-speed-active-bg": theme.speedOptionActiveBackground,
295
+ "--player-speed-active-text": theme.speedOptionActiveTextColor,
296
+ "--player-zoom-icon": theme.zoomIconColor,
297
+ "--player-skip-icon": theme.skipIconColor,
298
+ "--player-skip-cue-bg": theme.skipCueBackground,
299
+ "--player-skip-cue-text": theme.skipCueTextColor,
300
+ "--player-live-dot": theme.liveDotColor
301
+ };
302
+ }
303
+
304
+ // src/events/EventEmitter.ts
305
+ var EventEmitter = class {
306
+ constructor() {
307
+ this.listeners = /* @__PURE__ */ new Map();
308
+ }
309
+ on(event, handler) {
310
+ let set = this.listeners.get(event);
311
+ if (!set) {
312
+ set = /* @__PURE__ */ new Set();
313
+ this.listeners.set(event, set);
314
+ }
315
+ set.add(handler);
316
+ return () => this.off(event, handler);
317
+ }
318
+ off(event, handler) {
319
+ this.listeners.get(event)?.delete(handler);
320
+ }
321
+ emit(event, ...args) {
322
+ const set = this.listeners.get(event);
323
+ if (!set) return;
324
+ for (const handler of set) {
325
+ try {
326
+ handler(...args);
327
+ } catch {
328
+ }
329
+ }
330
+ }
331
+ removeAll() {
332
+ this.listeners.clear();
333
+ }
334
+ };
335
+
336
+ // src/security/redaction.ts
337
+ var SENSITIVE_KEYS = /^(authorization|cookie|set-cookie|x-api-key|token|accessToken|access_token|clientSecret|password)$/i;
338
+ function redactValue(key, value) {
339
+ if (SENSITIVE_KEYS.test(key)) return "[REDACTED]";
340
+ if (typeof value === "string") {
341
+ if (/^https?:\/\//i.test(value) && /[?&](token|expires)=/i.test(value)) {
342
+ try {
343
+ const url = new URL(value);
344
+ for (const param of ["token", "expires", "signature", "sig"]) {
345
+ if (url.searchParams.has(param)) {
346
+ url.searchParams.set(param, "[REDACTED]");
347
+ }
348
+ }
349
+ return url.toString();
350
+ } catch {
351
+ return "[REDACTED_URL]";
352
+ }
353
+ }
354
+ }
355
+ if (value && typeof value === "object" && !Array.isArray(value)) {
356
+ return redactObject(value);
357
+ }
358
+ return value;
359
+ }
360
+ function redactObject(obj) {
361
+ const out = {};
362
+ for (const [key, value] of Object.entries(obj)) {
363
+ out[key] = redactValue(key, value);
364
+ }
365
+ return out;
366
+ }
367
+
368
+ // src/logging/Logger.ts
369
+ var LEVELS = ["silent", "error", "warn", "info", "debug", "trace"];
370
+ var Logger = class {
371
+ constructor(config) {
372
+ this.config = config;
373
+ }
374
+ update(config) {
375
+ this.config = { ...this.config, ...config };
376
+ }
377
+ shouldLog(level) {
378
+ if (!this.config.enabled || this.config.level === "silent") return false;
379
+ return LEVELS.indexOf(level) <= LEVELS.indexOf(this.config.level);
380
+ }
381
+ write(level, message, details) {
382
+ if (!this.shouldLog(level)) return;
383
+ const payload = details ? redactObject(details) : void 0;
384
+ const fn = console[level] ?? console.log;
385
+ if (payload) fn(`[nsg-player] ${message}`, payload);
386
+ else fn(`[nsg-player] ${message}`);
387
+ }
388
+ error(message, details) {
389
+ this.write("error", message, details);
390
+ }
391
+ warn(message, details) {
392
+ this.write("warn", message, details);
393
+ }
394
+ info(message, details) {
395
+ this.write("info", message, details);
396
+ }
397
+ debug(message, details) {
398
+ this.write("debug", message, details);
399
+ }
400
+ };
401
+
402
+ // src/auth/AuthParamsStore.ts
403
+ var AuthParamsStore = class {
404
+ constructor(initial) {
405
+ if (initial instanceof URLSearchParams) {
406
+ this.params = new URLSearchParams(initial);
407
+ } else if (initial) {
408
+ this.params = new URLSearchParams(initial);
409
+ } else {
410
+ this.params = new URLSearchParams();
411
+ }
412
+ }
413
+ getSnapshot() {
414
+ return new URLSearchParams(this.params);
415
+ }
416
+ toRecord() {
417
+ const out = {};
418
+ this.params.forEach((value, key) => {
419
+ out[key] = value;
420
+ });
421
+ return out;
422
+ }
423
+ update(next) {
424
+ this.params = next instanceof URLSearchParams ? new URLSearchParams(next) : new URLSearchParams(next);
425
+ }
426
+ applyToUrl(rawUrl, baseOrigin) {
427
+ const target = new URL(rawUrl, baseOrigin ?? (typeof location !== "undefined" ? location.origin : "http://localhost"));
428
+ this.params.forEach((value, key) => {
429
+ target.searchParams.set(key, value);
430
+ });
431
+ return target.toString();
432
+ }
433
+ };
434
+
435
+ // src/playback/PlaybackSourceManager.ts
436
+ var PlaybackSourceManager = class {
437
+ constructor(playback, logger) {
438
+ this.playback = playback;
439
+ this.logger = logger;
440
+ this.source = null;
441
+ this.inFlight = null;
442
+ this.authParams = new AuthParamsStore();
443
+ }
444
+ getSource() {
445
+ return this.source;
446
+ }
447
+ getExpiresAt() {
448
+ return this.source?.expiresAt ?? null;
449
+ }
450
+ async load(context) {
451
+ if (!this.playback?.getSource) {
452
+ throw createPlayerError(
453
+ "INVALID_CONFIGURATION" /* INVALID_CONFIGURATION */,
454
+ "playback.getSource is required when not using sourceUrl",
455
+ { category: "config", fatal: true, retryable: false }
456
+ );
457
+ }
458
+ return this.run(context, () => this.playback.getSource(context));
459
+ }
460
+ async refresh(context) {
461
+ const refresher = this.playback?.refreshSource ?? this.playback?.getSource;
462
+ if (!refresher) {
463
+ throw createPlayerError(
464
+ "INVALID_CONFIGURATION" /* INVALID_CONFIGURATION */,
465
+ "playback.refreshSource or playback.getSource is required",
466
+ { category: "config", fatal: true, retryable: false }
467
+ );
468
+ }
469
+ return this.run(context, () => refresher(context));
470
+ }
471
+ applyDirectSource(source) {
472
+ this.source = source;
473
+ this.applyAuthParams(source);
474
+ }
475
+ async run(context, fn) {
476
+ if (this.inFlight) return this.inFlight;
477
+ this.inFlight = (async () => {
478
+ try {
479
+ const next = await fn();
480
+ if (!next?.url || typeof next.expiresAt !== "number") {
481
+ throw createPlayerError(
482
+ "SOURCE_RESOLUTION_FAILED" /* SOURCE_RESOLUTION_FAILED */,
483
+ "Playback source callback returned an invalid payload",
484
+ { category: "playback", fatal: true, retryable: true }
485
+ );
486
+ }
487
+ this.source = next;
488
+ this.applyAuthParams(next);
489
+ this.logger.debug("Playback source resolved", {
490
+ reason: context.reason,
491
+ expiresAt: next.expiresAt
492
+ });
493
+ return next;
494
+ } catch (error) {
495
+ if (error && typeof error === "object" && "code" in error) throw error;
496
+ throw createPlayerError(
497
+ "SOURCE_RESOLUTION_FAILED" /* SOURCE_RESOLUTION_FAILED */,
498
+ error instanceof Error ? error.message : "Source resolution failed",
499
+ { category: "playback", fatal: true, retryable: true, cause: error }
500
+ );
501
+ } finally {
502
+ this.inFlight = null;
503
+ }
504
+ })();
505
+ return this.inFlight;
506
+ }
507
+ applyAuthParams(source) {
508
+ if (source.authParams) {
509
+ this.authParams.update(source.authParams);
510
+ return;
511
+ }
512
+ try {
513
+ const url = new URL(source.url);
514
+ const params = {};
515
+ url.searchParams.forEach((value, key) => {
516
+ params[key] = value;
517
+ });
518
+ if (source.token) params.token = source.token;
519
+ this.authParams.update(params);
520
+ } catch {
521
+ if (source.token) {
522
+ this.authParams.update({ token: source.token });
523
+ }
524
+ }
525
+ }
526
+ destroy() {
527
+ this.source = null;
528
+ this.inFlight = null;
529
+ }
530
+ };
531
+
532
+ // src/playback/RefreshManager.ts
533
+ var RefreshManager = class {
534
+ constructor(options) {
535
+ this.options = options;
536
+ this.timer = null;
537
+ this.coalesceTimer = null;
538
+ this.inFlight = false;
539
+ this.consecutiveFailures = 0;
540
+ this.stopped = false;
541
+ this.pendingReason = null;
542
+ }
543
+ start(initialExpiresAt) {
544
+ this.schedule(initialExpiresAt);
545
+ }
546
+ requestRenew(reason = "frag_error") {
547
+ if (this.stopped || this.options.isDestroyed()) return;
548
+ this.pendingReason = reason;
549
+ if (this.coalesceTimer) return;
550
+ this.coalesceTimer = setTimeout(() => {
551
+ this.coalesceTimer = null;
552
+ const reasonToUse = this.pendingReason ?? "frag_error";
553
+ this.pendingReason = null;
554
+ void this.renew(reasonToUse);
555
+ }, this.options.coalesceMs);
556
+ }
557
+ stop() {
558
+ this.stopped = true;
559
+ if (this.timer) clearTimeout(this.timer);
560
+ if (this.coalesceTimer) clearTimeout(this.coalesceTimer);
561
+ this.timer = null;
562
+ this.coalesceTimer = null;
563
+ }
564
+ schedule(expiresAt) {
565
+ if (this.timer) clearTimeout(this.timer);
566
+ if (this.stopped) return;
567
+ const delay = Math.max(
568
+ 5e3,
569
+ expiresAt - Date.now() - this.options.refreshBeforeExpiryMs
570
+ );
571
+ this.timer = setTimeout(() => {
572
+ void this.renew("proactive");
573
+ }, delay);
574
+ }
575
+ async renew(reason) {
576
+ if (this.stopped || this.options.isDestroyed() || this.inFlight) {
577
+ if (this.inFlight) this.pendingReason = reason;
578
+ return;
579
+ }
580
+ this.inFlight = true;
581
+ this.options.onRenewing?.();
582
+ try {
583
+ await this.options.authManager.ensureToken({
584
+ reason: reason === "proactive" ? "expiring" : "unauthorized",
585
+ videoId: this.options.videoId
586
+ });
587
+ const source = await this.options.sourceManager.refresh({
588
+ videoId: this.options.videoId,
589
+ reason: reason === "proactive" ? "expiring" : reason === "manual" ? "manual" : "auth_error"
590
+ });
591
+ this.consecutiveFailures = 0;
592
+ this.options.onRenewed?.(source.expiresAt);
593
+ this.schedule(source.expiresAt);
594
+ this.options.logger.debug("Playback source renewed", { reason });
595
+ } catch (error) {
596
+ this.consecutiveFailures += 1;
597
+ const message = error instanceof Error ? error.message : "Playback refresh failed";
598
+ this.options.logger.warn("Playback refresh failed", {
599
+ reason,
600
+ failure: this.consecutiveFailures
601
+ });
602
+ if (this.consecutiveFailures >= this.options.renewMaxRetries) {
603
+ this.options.onFatal?.(message);
604
+ } else {
605
+ const backoff = this.options.renewBackoffMs * this.consecutiveFailures;
606
+ this.timer = setTimeout(() => {
607
+ void this.renew(reason);
608
+ }, backoff);
609
+ }
610
+ } finally {
611
+ this.inFlight = false;
612
+ if (this.pendingReason && !this.stopped) {
613
+ const pending = this.pendingReason;
614
+ this.pendingReason = null;
615
+ void this.renew(pending);
616
+ }
617
+ }
618
+ }
619
+ };
620
+
621
+ // src/playback/liveEdge.ts
622
+ var DEFAULT_LIVE_EDGE_TOLERANCE_SECONDS = 3;
623
+ function getLiveEdgeTime(media) {
624
+ const { seekable, duration } = media;
625
+ try {
626
+ if (seekable.length > 0) {
627
+ const end = seekable.end(seekable.length - 1);
628
+ if (Number.isFinite(end) && end >= 0) {
629
+ return end;
630
+ }
631
+ }
632
+ } catch {
633
+ }
634
+ if (Number.isFinite(duration) && duration > 0) {
635
+ return duration;
636
+ }
637
+ return null;
638
+ }
639
+ function isAtLiveEdge(media, toleranceSeconds = DEFAULT_LIVE_EDGE_TOLERANCE_SECONDS) {
640
+ const edge = getLiveEdgeTime(media);
641
+ if (edge === null) {
642
+ return true;
643
+ }
644
+ const lag = edge - media.currentTime;
645
+ return lag <= Math.max(0, toleranceSeconds);
646
+ }
647
+ function seekToLiveEdge(media, options) {
648
+ const tolerance = options?.toleranceSeconds ?? DEFAULT_LIVE_EDGE_TOLERANCE_SECONDS;
649
+ const edgeOffset = options?.edgeOffsetSeconds ?? 0.25;
650
+ const edge = getLiveEdgeTime(media);
651
+ if (edge === null) {
652
+ return { moved: false, reason: "edge_unavailable", edge: null };
653
+ }
654
+ if (isAtLiveEdge(media, tolerance)) {
655
+ return { moved: false, reason: "already_at_live", edge };
656
+ }
657
+ const target = Math.max(0, edge - edgeOffset);
658
+ media.currentTime = target;
659
+ return { moved: true, edge };
660
+ }
661
+
662
+ // src/streaming/HlsEngine.ts
663
+ var HlsEngine = class {
664
+ constructor(options) {
665
+ this.options = options;
666
+ this.hls = null;
667
+ this.destroyed = false;
668
+ this.supportMode = "unsupported";
669
+ this.networkRecoveryCount = 0;
670
+ this.mediaRecoveryCount = 0;
671
+ this.qualities = [];
672
+ this.qualitySelection = "auto";
673
+ }
674
+ async start() {
675
+ const { videoEl, playbackUrl, autoplay, startMuted, volume, startPosition } = this.options;
676
+ videoEl.muted = startMuted;
677
+ if (typeof volume === "number") videoEl.volume = Math.min(1, Math.max(0, volume));
678
+ const Hls = (await import("hls.js")).default;
679
+ if (Hls.isSupported()) {
680
+ this.supportMode = "hls.js";
681
+ this.options.callbacks.onSupportMode?.("hls.js");
682
+ await this.startWithHlsJs(Hls, playbackUrl);
683
+ } else if (videoEl.canPlayType("application/vnd.apple.mpegurl")) {
684
+ this.supportMode = "native";
685
+ this.options.callbacks.onSupportMode?.("native");
686
+ videoEl.src = this.options.authParams.applyToUrl(playbackUrl);
687
+ this.bindMediaEvents();
688
+ this.options.callbacks.onStateChange?.("ready");
689
+ } else {
690
+ this.supportMode = "unsupported";
691
+ this.options.callbacks.onSupportMode?.("unsupported");
692
+ this.options.callbacks.onFatalError?.("HLS is not supported in this browser");
693
+ return;
694
+ }
695
+ if (typeof startPosition === "number" && startPosition > 0) {
696
+ videoEl.currentTime = startPosition;
697
+ }
698
+ if (autoplay) {
699
+ try {
700
+ await videoEl.play();
701
+ } catch {
702
+ if (!videoEl.muted) {
703
+ videoEl.muted = true;
704
+ try {
705
+ await videoEl.play();
706
+ } catch {
707
+ this.options.logger.warn("Autoplay blocked");
708
+ }
709
+ }
710
+ }
711
+ }
712
+ }
713
+ async startWithHlsJs(Hls, playbackUrl) {
714
+ const { videoEl, authParams, hlsConfig, callbacks } = this.options;
715
+ this.hls = new Hls({
716
+ enableWorker: hlsConfig.enableWorker,
717
+ lowLatencyMode: hlsConfig.lowLatencyMode,
718
+ backBufferLength: hlsConfig.backBufferLength,
719
+ xhrSetup: (xhr, url) => {
720
+ void xhr;
721
+ void url;
722
+ }
723
+ });
724
+ this.hls.attachMedia(videoEl);
725
+ this.hls.loadSource(authParams.applyToUrl(playbackUrl));
726
+ this.hls.on(Hls.Events.MANIFEST_PARSED, (_event, data) => {
727
+ this.qualities = (data.levels ?? []).map((level, index) => ({
728
+ index,
729
+ height: level.height ?? 0,
730
+ width: level.width ?? 0,
731
+ bitrate: level.bitrate ?? 0,
732
+ label: level.height != null && level.height > 0 ? `${level.height}p` : `Level ${index}`
733
+ }));
734
+ callbacks.onQualitiesChange?.(this.qualities);
735
+ callbacks.onStateChange?.("ready");
736
+ });
737
+ this.hls.on(Hls.Events.ERROR, (_event, data) => {
738
+ if (!data.fatal) {
739
+ if (data.type === Hls.ErrorTypes.NETWORK_ERROR && (data.details === Hls.ErrorDetails.FRAG_LOAD_ERROR || data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR || data.details === Hls.ErrorDetails.KEY_LOAD_ERROR)) {
740
+ callbacks.onAuthRecoveryNeeded?.(
741
+ data.details === Hls.ErrorDetails.KEY_LOAD_ERROR ? "key_error" : data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR ? "level_error" : "frag_error"
742
+ );
743
+ }
744
+ return;
745
+ }
746
+ if (data.type === Hls.ErrorTypes.NETWORK_ERROR) {
747
+ this.networkRecoveryCount += 1;
748
+ if (this.networkRecoveryCount <= hlsConfig.fatalRecoveryLimit) {
749
+ callbacks.onAuthRecoveryNeeded?.("fatal_network");
750
+ this.hls?.startLoad();
751
+ return;
752
+ }
753
+ }
754
+ if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
755
+ this.mediaRecoveryCount += 1;
756
+ if (this.mediaRecoveryCount <= hlsConfig.fatalRecoveryLimit) {
757
+ this.hls?.recoverMediaError();
758
+ return;
759
+ }
760
+ }
761
+ callbacks.onFatalError?.(data.details || "Fatal HLS error");
762
+ });
763
+ this.bindMediaEvents();
764
+ }
765
+ bindMediaEvents() {
766
+ const { videoEl, callbacks } = this.options;
767
+ videoEl.addEventListener("playing", () => callbacks.onStateChange?.("playing"));
768
+ videoEl.addEventListener("pause", () => {
769
+ if (!videoEl.ended) callbacks.onStateChange?.("paused");
770
+ });
771
+ videoEl.addEventListener("waiting", () => callbacks.onStateChange?.("buffering"));
772
+ videoEl.addEventListener("ended", () => {
773
+ callbacks.onStateChange?.("ended");
774
+ callbacks.onEnded?.();
775
+ });
776
+ }
777
+ updateAuthParams() {
778
+ if (this.supportMode === "native" && this.options.videoEl) {
779
+ const time = this.options.videoEl.currentTime;
780
+ const wasPaused = this.options.videoEl.paused;
781
+ this.options.videoEl.src = this.options.authParams.applyToUrl(
782
+ this.options.playbackUrl
783
+ );
784
+ this.options.videoEl.currentTime = time;
785
+ if (!wasPaused) void this.options.videoEl.play();
786
+ }
787
+ }
788
+ resumeAfterAuthRenew() {
789
+ this.hls?.startLoad();
790
+ this.updateAuthParams();
791
+ }
792
+ setQuality(selection) {
793
+ this.qualitySelection = selection;
794
+ if (!this.hls) return;
795
+ this.hls.currentLevel = selection === "auto" ? -1 : selection;
796
+ this.options.callbacks.onQualitySelectionChange?.(selection);
797
+ }
798
+ getQuality() {
799
+ return this.qualitySelection;
800
+ }
801
+ getQualities() {
802
+ return this.qualities;
803
+ }
804
+ getSupportMode() {
805
+ return this.supportMode;
806
+ }
807
+ destroy() {
808
+ if (this.destroyed) return;
809
+ this.destroyed = true;
810
+ this.hls?.destroy();
811
+ this.hls = null;
812
+ const { videoEl } = this.options;
813
+ videoEl.removeAttribute("src");
814
+ videoEl.load();
815
+ }
816
+ };
817
+
818
+ // src/player/createVideoPlayer.ts
819
+ function expiryStatus(expiresAt, safetyMs) {
820
+ if (expiresAt === null || expiresAt <= 0) return "unknown";
821
+ const remaining = expiresAt - Date.now();
822
+ if (remaining <= 0) return "expired";
823
+ if (remaining <= safetyMs) return "expiring_soon";
824
+ return "valid";
825
+ }
826
+ function generateSessionId() {
827
+ if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
828
+ return crypto.randomUUID();
829
+ }
830
+ return `ps_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
831
+ }
832
+ function createVideoPlayer(options) {
833
+ assertNoSecretsInConfig(options);
834
+ let config = mergePlayerConfig({
835
+ player: options.player,
836
+ controls: options.controls,
837
+ theme: options.theme,
838
+ watermark: options.watermark,
839
+ streaming: options.streaming,
840
+ logging: options.logging,
841
+ debug: options.debug,
842
+ accessibility: options.accessibility
843
+ });
844
+ const events = new EventEmitter();
845
+ const logger = new Logger(config.logging);
846
+ const playbackSessionId = generateSessionId();
847
+ let state = "idle";
848
+ let destroyed = false;
849
+ let lastError = null;
850
+ let videoEl = options.mount ?? null;
851
+ let engine = null;
852
+ let refreshManager = null;
853
+ let fullscreenEl = null;
854
+ let isLiveStream = false;
855
+ const setState = (next) => {
856
+ if (destroyed && next !== "destroyed") return;
857
+ state = next;
858
+ events.emit("statechange", { state: next });
859
+ };
860
+ const authManager = new AuthManager(
861
+ options.auth,
862
+ options.auth?.refreshBeforeExpiryMs ?? 6e4,
863
+ logger,
864
+ (token) => events.emit("authrefresh", { expiresAt: token.expiresAt }),
865
+ (message, retryable) => events.emit("autherror", { message, retryable })
866
+ );
867
+ const sourceManager = new PlaybackSourceManager(options.playback, logger);
868
+ const ensureVideoElement = () => {
869
+ if (!videoEl) {
870
+ throw createPlayerError(
871
+ "INVALID_CONFIGURATION" /* INVALID_CONFIGURATION */,
872
+ "A video element must be provided via mount before starting playback",
873
+ { category: "config", fatal: true, retryable: false }
874
+ );
875
+ }
876
+ return videoEl;
877
+ };
878
+ const startPlayback = async () => {
879
+ if (destroyed) return;
880
+ setState("loading");
881
+ lastError = null;
882
+ const videoId = options.videoId ?? "unknown";
883
+ try {
884
+ await authManager.ensureToken({
885
+ reason: "initial",
886
+ videoId: options.videoId
887
+ });
888
+ let playbackUrl;
889
+ let expiresAt;
890
+ if (options.sourceUrl) {
891
+ const source = {
892
+ url: options.sourceUrl,
893
+ expiresAt: Date.now() + 24 * 60 * 60 * 1e3,
894
+ live: /\/hls-web-live\//i.test(options.sourceUrl)
895
+ };
896
+ sourceManager.applyDirectSource(source);
897
+ playbackUrl = source.url;
898
+ expiresAt = source.expiresAt;
899
+ isLiveStream = Boolean(source.live);
900
+ } else {
901
+ const source = await sourceManager.load({
902
+ videoId,
903
+ reason: "initial"
904
+ });
905
+ playbackUrl = source.url;
906
+ expiresAt = source.expiresAt;
907
+ isLiveStream = Boolean(source.live);
908
+ }
909
+ const el = ensureVideoElement();
910
+ engine?.destroy();
911
+ engine = new HlsEngine({
912
+ videoEl: el,
913
+ playbackUrl,
914
+ authParams: sourceManager.authParams,
915
+ autoplay: config.player.autoplay,
916
+ startMuted: config.player.startMuted,
917
+ startPosition: config.player.startPosition,
918
+ volume: config.player.volume,
919
+ hlsConfig: config.streaming.hls,
920
+ logger,
921
+ callbacks: {
922
+ onStateChange: (s) => {
923
+ setState(s);
924
+ if (s === "ready") events.emit("ready");
925
+ if (s === "playing") {
926
+ events.emit("play");
927
+ events.emit("playing");
928
+ }
929
+ if (s === "paused") events.emit("pause");
930
+ if (s === "buffering") events.emit("buffering");
931
+ if (s === "ended") events.emit("ended");
932
+ },
933
+ onFatalError: (message) => {
934
+ lastError = createPlayerError(
935
+ "PLAYBACK_ERROR" /* PLAYBACK_ERROR */,
936
+ message,
937
+ { category: "playback", fatal: true, retryable: true }
938
+ );
939
+ setState("error");
940
+ events.emit("error", {
941
+ code: lastError.code,
942
+ message: lastError.message,
943
+ fatal: true
944
+ });
945
+ },
946
+ onAuthRecoveryNeeded: (reason) => {
947
+ refreshManager?.requestRenew(
948
+ reason
949
+ );
950
+ },
951
+ onQualitiesChange: () => {
952
+ },
953
+ onQualitySelectionChange: (selection) => {
954
+ const level = engine?.getQualities().find((q) => q.index === selection);
955
+ events.emit("qualitychange", {
956
+ selection,
957
+ label: typeof selection === "number" ? level?.label : "auto"
958
+ });
959
+ },
960
+ onEnded: () => events.emit("ended")
961
+ }
962
+ });
963
+ await engine.start();
964
+ if (!options.sourceUrl && options.playback) {
965
+ refreshManager?.stop();
966
+ refreshManager = new RefreshManager({
967
+ videoId,
968
+ authManager,
969
+ sourceManager,
970
+ refreshBeforeExpiryMs: options.playback.refreshBeforeExpiryMs ?? 3e4,
971
+ renewMaxRetries: options.playback.renewMaxRetries ?? 3,
972
+ renewBackoffMs: options.playback.renewBackoffMs ?? 2e3,
973
+ coalesceMs: options.playback.authRecoveryCoalesceMs ?? 1e3,
974
+ logger,
975
+ isDestroyed: () => destroyed,
976
+ onRenewing: () => setState("renewing"),
977
+ onRenewed: (nextExpiresAt) => {
978
+ engine?.resumeAfterAuthRenew();
979
+ events.emit("tokenrefresh", { expiresAt: nextExpiresAt });
980
+ events.emit("sourcechange", { expiresAt: nextExpiresAt });
981
+ },
982
+ onFatal: (message) => {
983
+ lastError = createPlayerError(
984
+ "TOKEN_REFRESH_FAILED" /* TOKEN_REFRESH_FAILED */,
985
+ message,
986
+ { category: "auth", fatal: true, retryable: true }
987
+ );
988
+ setState("error");
989
+ events.emit("error", {
990
+ code: lastError.code,
991
+ message: lastError.message,
992
+ fatal: true
993
+ });
994
+ }
995
+ });
996
+ refreshManager.start(expiresAt);
997
+ }
998
+ options.analytics?.onEvent?.({
999
+ type: "PLAYER_INITIALIZED",
1000
+ payload: { videoId, playbackSessionId }
1001
+ });
1002
+ } catch (error) {
1003
+ const playerError = error && typeof error === "object" && "code" in error ? error : createPlayerError(
1004
+ "UNKNOWN" /* UNKNOWN */,
1005
+ error instanceof Error ? error.message : "Failed to start player",
1006
+ { category: "unknown", fatal: true, retryable: true, cause: error }
1007
+ );
1008
+ lastError = playerError;
1009
+ setState("error");
1010
+ events.emit("error", {
1011
+ code: playerError.code,
1012
+ message: playerError.message,
1013
+ fatal: playerError.fatal
1014
+ });
1015
+ throw playerError;
1016
+ }
1017
+ };
1018
+ if (options.mount) {
1019
+ void startPlayback().catch(() => {
1020
+ });
1021
+ }
1022
+ const api = {
1023
+ async play() {
1024
+ await ensureVideoElement().play();
1025
+ },
1026
+ pause() {
1027
+ ensureVideoElement().pause();
1028
+ },
1029
+ seek(time) {
1030
+ const el = ensureVideoElement();
1031
+ const duration = Number.isFinite(el.duration) ? el.duration : time;
1032
+ el.currentTime = Math.min(Math.max(0, time), duration);
1033
+ },
1034
+ goToLive() {
1035
+ const el = ensureVideoElement();
1036
+ return seekToLiveEdge(el);
1037
+ },
1038
+ isLive() {
1039
+ return isLiveStream;
1040
+ },
1041
+ isAtLiveEdge() {
1042
+ if (!isLiveStream || !videoEl) {
1043
+ return true;
1044
+ }
1045
+ return isAtLiveEdge(videoEl);
1046
+ },
1047
+ getCurrentTime() {
1048
+ return videoEl?.currentTime ?? 0;
1049
+ },
1050
+ getDuration() {
1051
+ return videoEl?.duration ?? 0;
1052
+ },
1053
+ setVolume(volume) {
1054
+ const el = ensureVideoElement();
1055
+ el.volume = Math.min(1, Math.max(0, volume));
1056
+ events.emit("volumechange", { volume: el.volume, muted: el.muted });
1057
+ },
1058
+ getVolume() {
1059
+ return videoEl?.volume ?? 0;
1060
+ },
1061
+ mute() {
1062
+ const el = ensureVideoElement();
1063
+ el.muted = true;
1064
+ events.emit("volumechange", { volume: el.volume, muted: true });
1065
+ },
1066
+ unmute() {
1067
+ const el = ensureVideoElement();
1068
+ el.muted = false;
1069
+ events.emit("volumechange", { volume: el.volume, muted: false });
1070
+ },
1071
+ isMuted() {
1072
+ return videoEl?.muted ?? false;
1073
+ },
1074
+ setPlaybackRate(rate) {
1075
+ ensureVideoElement().playbackRate = rate;
1076
+ },
1077
+ getPlaybackRate() {
1078
+ return videoEl?.playbackRate ?? 1;
1079
+ },
1080
+ getState() {
1081
+ return state;
1082
+ },
1083
+ getBufferedRanges() {
1084
+ const el = videoEl;
1085
+ if (!el) return [];
1086
+ const ranges = [];
1087
+ for (let i = 0; i < el.buffered.length; i += 1) {
1088
+ ranges.push({ start: el.buffered.start(i), end: el.buffered.end(i) });
1089
+ }
1090
+ return ranges;
1091
+ },
1092
+ getQuality() {
1093
+ return engine?.getQuality() ?? "auto";
1094
+ },
1095
+ setQuality(selection) {
1096
+ if (selection === "auto") {
1097
+ engine?.setQuality("auto");
1098
+ return;
1099
+ }
1100
+ if (typeof selection === "number") {
1101
+ engine?.setQuality(selection);
1102
+ return;
1103
+ }
1104
+ const match = engine?.getQualities().find((q) => q.label === selection || `${q.height}p` === selection);
1105
+ if (match) engine?.setQuality(match.index);
1106
+ },
1107
+ getAvailableQualities() {
1108
+ return engine?.getQualities() ?? [];
1109
+ },
1110
+ async enterFullscreen() {
1111
+ const target = fullscreenEl ?? videoEl?.parentElement ?? videoEl;
1112
+ if (target && "requestFullscreen" in target) {
1113
+ await target.requestFullscreen();
1114
+ events.emit("fullscreenchange", { isFullscreen: true });
1115
+ }
1116
+ },
1117
+ async exitFullscreen() {
1118
+ if (document.fullscreenElement) {
1119
+ await document.exitFullscreen();
1120
+ events.emit("fullscreenchange", { isFullscreen: false });
1121
+ }
1122
+ },
1123
+ isFullscreen() {
1124
+ return Boolean(document.fullscreenElement);
1125
+ },
1126
+ async reload() {
1127
+ refreshManager?.stop();
1128
+ engine?.destroy();
1129
+ await startPlayback();
1130
+ },
1131
+ destroy() {
1132
+ if (destroyed) return;
1133
+ destroyed = true;
1134
+ refreshManager?.stop();
1135
+ refreshManager = null;
1136
+ engine?.destroy();
1137
+ engine = null;
1138
+ authManager.destroy();
1139
+ sourceManager.destroy();
1140
+ setState("destroyed");
1141
+ events.emit("destroy");
1142
+ events.removeAll();
1143
+ options.analytics?.onEvent?.({ type: "PLAYER_DESTROYED" });
1144
+ },
1145
+ getDiagnostics() {
1146
+ const buffered = api.getBufferedRanges();
1147
+ return {
1148
+ playerVersion: PACKAGE_VERSION,
1149
+ currentState: state,
1150
+ hlsSupport: engine?.getSupportMode() ?? "unsupported",
1151
+ videoId: options.videoId ?? null,
1152
+ currentTime: api.getCurrentTime(),
1153
+ duration: api.getDuration(),
1154
+ bufferedEnd: buffered.length ? buffered[buffered.length - 1].end : 0,
1155
+ currentQuality: api.getQuality() === "auto" ? "auto" : engine?.getQualities().find((q) => q.index === api.getQuality())?.label ?? null,
1156
+ playbackSessionId,
1157
+ authExpiryStatus: expiryStatus(
1158
+ authManager.getExpiresAt(),
1159
+ options.auth?.refreshBeforeExpiryMs ?? 6e4
1160
+ ),
1161
+ sourceExpiryStatus: expiryStatus(
1162
+ sourceManager.getExpiresAt(),
1163
+ options.playback?.refreshBeforeExpiryMs ?? 3e4
1164
+ ),
1165
+ lastError: lastError?.message ?? null
1166
+ };
1167
+ },
1168
+ getConfig() {
1169
+ return config;
1170
+ },
1171
+ updateConfig(partial) {
1172
+ config = mergePlayerConfig({
1173
+ player: { ...config.player, ...partial?.player },
1174
+ controls: { ...config.controls, ...partial?.controls },
1175
+ theme: { ...config.theme, ...partial?.theme },
1176
+ watermark: { ...config.watermark, ...partial?.watermark },
1177
+ streaming: {
1178
+ engine: "hls",
1179
+ hls: { ...config.streaming.hls, ...partial?.streaming?.hls }
1180
+ },
1181
+ logging: { ...config.logging, ...partial?.logging },
1182
+ debug: { ...config.debug, ...partial?.debug },
1183
+ accessibility: {
1184
+ ...config.accessibility,
1185
+ ...partial?.accessibility
1186
+ }
1187
+ });
1188
+ logger.update(config.logging);
1189
+ },
1190
+ on: events.on.bind(events),
1191
+ off: events.off.bind(events)
1192
+ };
1193
+ api.attach = async (el, container) => {
1194
+ videoEl = el;
1195
+ fullscreenEl = container ?? null;
1196
+ await startPlayback();
1197
+ };
1198
+ return api;
1199
+ }
1200
+ export {
1201
+ AuthManager,
1202
+ AuthParamsStore,
1203
+ DEFAULT_CONTROLS,
1204
+ DEFAULT_LIVE_EDGE_TOLERANCE_SECONDS,
1205
+ DEFAULT_PLAYER_BEHAVIOR,
1206
+ DEFAULT_PLAYER_CONFIG,
1207
+ DEFAULT_THEME,
1208
+ DEFAULT_WATERMARK,
1209
+ EventEmitter,
1210
+ PACKAGE_VERSION,
1211
+ PLAYBACK_RATES,
1212
+ PlaybackSourceManager,
1213
+ PlayerErrorCode,
1214
+ RefreshManager,
1215
+ assertNoSecretsInConfig,
1216
+ createPlayerError,
1217
+ createVideoPlayer,
1218
+ getLiveEdgeTime,
1219
+ isAtLiveEdge,
1220
+ mergePlayerConfig,
1221
+ redactObject,
1222
+ redactSensitive,
1223
+ redactValue,
1224
+ seekToLiveEdge,
1225
+ themeToCssVars
1226
+ };
1227
+ //# sourceMappingURL=index.js.map