@codenkay/video-nsgplayer-core 0.1.2 → 0.1.5
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/CHANGELOG.md +17 -0
- package/dist/index.cjs +199 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -3
- package/dist/index.d.ts +61 -3
- package/dist/index.js +197 -55
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import Hls, { LoaderContext, LoaderConfiguration, LoaderCallbacks } from 'hls.js';
|
|
2
|
+
|
|
1
3
|
/** Player lifecycle states. */
|
|
2
4
|
type PlayerState = "idle" | "loading" | "ready" | "playing" | "paused" | "buffering" | "renewing" | "ended" | "error" | "destroyed";
|
|
3
5
|
type HlsSupportMode = "hls.js" | "native" | "unsupported";
|
|
@@ -115,12 +117,24 @@ type StreamingConfig = {
|
|
|
115
117
|
backBufferLength?: number;
|
|
116
118
|
fatalRecoveryLimit?: number;
|
|
117
119
|
};
|
|
120
|
+
/**
|
|
121
|
+
* Same-origin BFF proxy for `enc.key` (preferred in production when the
|
|
122
|
+
* server IP is allowlisted by the CDN / Cloudflare).
|
|
123
|
+
*/
|
|
118
124
|
keyProxyUrlBuilder?: (ctx: {
|
|
119
125
|
videoId: string;
|
|
120
126
|
keyUrl: string;
|
|
121
127
|
token?: string;
|
|
122
128
|
expires?: string;
|
|
129
|
+
username?: string;
|
|
130
|
+
mobile?: string;
|
|
123
131
|
}) => string;
|
|
132
|
+
/**
|
|
133
|
+
* Browser-direct AES key auth: returns the Video API access token so hls.js
|
|
134
|
+
* can send `Authorization: Bearer …` on `enc.key` requests.
|
|
135
|
+
* Use when the BFF cannot reach Appextech (Cloudflare challenge on server IP).
|
|
136
|
+
*/
|
|
137
|
+
getKeyAccessToken?: () => string | null | undefined;
|
|
124
138
|
};
|
|
125
139
|
type LoggingConfig = {
|
|
126
140
|
enabled?: boolean;
|
|
@@ -166,6 +180,8 @@ type PlayerConfig = Required<{
|
|
|
166
180
|
streaming: {
|
|
167
181
|
engine: "hls";
|
|
168
182
|
hls: Required<NonNullable<StreamingConfig["hls"]>>;
|
|
183
|
+
keyProxyUrlBuilder?: StreamingConfig["keyProxyUrlBuilder"];
|
|
184
|
+
getKeyAccessToken?: StreamingConfig["getKeyAccessToken"];
|
|
169
185
|
};
|
|
170
186
|
logging: Required<LoggingConfig>;
|
|
171
187
|
debug: Required<DebugConfig>;
|
|
@@ -239,7 +255,7 @@ type PlayerEventMap = {
|
|
|
239
255
|
};
|
|
240
256
|
|
|
241
257
|
declare const PLAYBACK_RATES: readonly [0.5, 0.75, 1, 1.25, 1.5, 2];
|
|
242
|
-
declare const PACKAGE_VERSION = "0.1.
|
|
258
|
+
declare const PACKAGE_VERSION = "0.1.5";
|
|
243
259
|
declare const DEFAULT_PLAYER_BEHAVIOR: Required<PlayerBehaviorConfig>;
|
|
244
260
|
declare const DEFAULT_CONTROLS: Required<ControlsConfig>;
|
|
245
261
|
declare const DEFAULT_THEME: Required<ThemeConfig>;
|
|
@@ -270,7 +286,8 @@ type PlayerConfigPartial = Partial<{
|
|
|
270
286
|
streaming: {
|
|
271
287
|
engine?: "hls";
|
|
272
288
|
hls?: Partial<PlayerConfig["streaming"]["hls"]>;
|
|
273
|
-
keyProxyUrlBuilder?:
|
|
289
|
+
keyProxyUrlBuilder?: PlayerConfig["streaming"]["keyProxyUrlBuilder"];
|
|
290
|
+
getKeyAccessToken?: PlayerConfig["streaming"]["getKeyAccessToken"];
|
|
274
291
|
};
|
|
275
292
|
logging: Partial<PlayerConfig["logging"]>;
|
|
276
293
|
debug: Partial<PlayerConfig["debug"]>;
|
|
@@ -343,7 +360,14 @@ declare class AuthParamsStore {
|
|
|
343
360
|
constructor(initial?: URLSearchParams | Record<string, string>);
|
|
344
361
|
getSnapshot(): URLSearchParams;
|
|
345
362
|
toRecord(): Record<string, string>;
|
|
363
|
+
/** True when at least one auth query param is stored. */
|
|
364
|
+
hasParams(): boolean;
|
|
365
|
+
toString(): string;
|
|
346
366
|
update(next: URLSearchParams | Record<string, string>): void;
|
|
367
|
+
/**
|
|
368
|
+
* Merges current auth params onto a target URL, preserving unrelated query keys.
|
|
369
|
+
* Critical for child HLS playlists/segments that omit signed query params.
|
|
370
|
+
*/
|
|
347
371
|
applyToUrl(rawUrl: string, baseOrigin?: string): string;
|
|
348
372
|
}
|
|
349
373
|
|
|
@@ -376,6 +400,9 @@ type HlsEngineOptions = {
|
|
|
376
400
|
startMuted: boolean;
|
|
377
401
|
startPosition?: number;
|
|
378
402
|
volume?: number;
|
|
403
|
+
videoId?: string;
|
|
404
|
+
keyProxyUrlBuilder?: StreamingConfig["keyProxyUrlBuilder"];
|
|
405
|
+
getKeyAccessToken?: StreamingConfig["getKeyAccessToken"];
|
|
379
406
|
hlsConfig: {
|
|
380
407
|
enableWorker: boolean;
|
|
381
408
|
lowLatencyMode: boolean;
|
|
@@ -387,6 +414,10 @@ type HlsEngineOptions = {
|
|
|
387
414
|
};
|
|
388
415
|
/**
|
|
389
416
|
* Dual-path HLS engine: hls.js when supported, otherwise native Safari HLS.
|
|
417
|
+
*
|
|
418
|
+
* Signed CDN playback requires auth query params on every HLS request.
|
|
419
|
+
* Encrypted VOD also requires a same-origin `enc.key` proxy (Bearer + query)
|
|
420
|
+
* via `streaming.keyProxyUrlBuilder` — browser CORS cannot fetch Video API keys.
|
|
390
421
|
*/
|
|
391
422
|
declare class HlsEngine {
|
|
392
423
|
private readonly options;
|
|
@@ -400,6 +431,8 @@ declare class HlsEngine {
|
|
|
400
431
|
constructor(options: HlsEngineOptions);
|
|
401
432
|
start(): Promise<void>;
|
|
402
433
|
private startWithHlsJs;
|
|
434
|
+
private handleHlsError;
|
|
435
|
+
private applyStartPositionAndAutoplay;
|
|
403
436
|
private bindMediaEvents;
|
|
404
437
|
updateAuthParams(): void;
|
|
405
438
|
resumeAfterAuthRenew(): void;
|
|
@@ -558,7 +591,32 @@ declare class RefreshManager {
|
|
|
558
591
|
private renew;
|
|
559
592
|
}
|
|
560
593
|
|
|
594
|
+
type HlsLoader = new (config: object) => {
|
|
595
|
+
load(context: LoaderContext, config: LoaderConfiguration, callbacks: LoaderCallbacks<LoaderContext>): void;
|
|
596
|
+
destroy(): void;
|
|
597
|
+
};
|
|
598
|
+
type AuthParamsLoaderOptions = {
|
|
599
|
+
videoId?: string;
|
|
600
|
+
keyProxyUrlBuilder?: StreamingConfig["keyProxyUrlBuilder"];
|
|
601
|
+
getKeyAccessToken?: StreamingConfig["getKeyAccessToken"];
|
|
602
|
+
};
|
|
603
|
+
/**
|
|
604
|
+
* Rewrites an HLS request URL so signed auth query params are present.
|
|
605
|
+
* Master playlists are signed on loadSource; child playlists / segments / keys
|
|
606
|
+
* are relative and would otherwise be fetched unsigned → CDN 403.
|
|
607
|
+
*
|
|
608
|
+
* AES `enc.key` handling (pick one):
|
|
609
|
+
* 1. `keyProxyUrlBuilder` — same-origin BFF (needs Cloudflare-allowlisted server IP)
|
|
610
|
+
* 2. `getKeyAccessToken` — browser-direct CDN fetch with API Bearer header
|
|
611
|
+
*/
|
|
612
|
+
declare function rewriteHlsRequestUrl(rawUrl: string, store: AuthParamsStore, options?: AuthParamsLoaderOptions, baseOrigin?: string): string;
|
|
613
|
+
/**
|
|
614
|
+
* hls.js loader that re-applies live AuthParamsStore credentials on every
|
|
615
|
+
* manifest / level / fragment / key request.
|
|
616
|
+
*/
|
|
617
|
+
declare function createAuthParamsLoader(HlsClass: typeof Hls, store: AuthParamsStore, options?: AuthParamsLoaderOptions): HlsLoader;
|
|
618
|
+
|
|
561
619
|
declare function redactValue(key: string, value: unknown, seen?: WeakSet<object>, depth?: number): unknown;
|
|
562
620
|
declare function redactObject(obj: Record<string, unknown>, seen?: WeakSet<object>, depth?: number): Record<string, unknown>;
|
|
563
621
|
|
|
564
|
-
export { type AccessibilityConfig, type AnalyticsConfig, type AuthConfig, AuthManager, AuthParamsStore, type AuthToken, type AuthTokenProviderContext, type BufferedRange, type ControlsConfig, type CreateVideoPlayerOptions, DEFAULT_CONTROLS, DEFAULT_LIVE_EDGE_TOLERANCE_SECONDS, DEFAULT_PLAYER_BEHAVIOR, DEFAULT_PLAYER_CONFIG, DEFAULT_THEME, DEFAULT_WATERMARK, type DebugConfig, EventEmitter, type HlsQualityLevel, type HlsSupportMode, type LiveMediaSnapshot, type LoggingConfig, PACKAGE_VERSION, PLAYBACK_RATES, type PlayIconPreset, type PlaybackCredentialConfig, type PlaybackRate, type PlaybackSource, type PlaybackSourceContext, PlaybackSourceManager, type PlayerBehaviorConfig, type PlayerConfig, type PlayerDiagnostics, type PlayerError, PlayerErrorCode, type PlayerEventMap, type PlayerSkipSeconds, type PlayerState, type PlayerZoomLevel, type QualitySelection, RefreshManager, SECRET_SCAN_MAX_DEPTH, type SeekToLiveResult, type StreamingConfig, type ThemeConfig, type VideoPlayer, type WatermarkConfig, assertNoSecretsInConfig, createPlayerError, createVideoPlayer, getLiveEdgeTime, isAtLiveEdge, isPlainObject, mergePlayerConfig, redactObject, redactSensitive, redactValue, seekToLiveEdge, themeToCssVars };
|
|
622
|
+
export { type AccessibilityConfig, type AnalyticsConfig, type AuthConfig, AuthManager, AuthParamsStore, type AuthToken, type AuthTokenProviderContext, type BufferedRange, type ControlsConfig, type CreateVideoPlayerOptions, DEFAULT_CONTROLS, DEFAULT_LIVE_EDGE_TOLERANCE_SECONDS, DEFAULT_PLAYER_BEHAVIOR, DEFAULT_PLAYER_CONFIG, DEFAULT_THEME, DEFAULT_WATERMARK, type DebugConfig, EventEmitter, type HlsQualityLevel, type HlsSupportMode, type LiveMediaSnapshot, type LoggingConfig, PACKAGE_VERSION, PLAYBACK_RATES, type PlayIconPreset, type PlaybackCredentialConfig, type PlaybackRate, type PlaybackSource, type PlaybackSourceContext, PlaybackSourceManager, type PlayerBehaviorConfig, type PlayerConfig, type PlayerDiagnostics, type PlayerError, PlayerErrorCode, type PlayerEventMap, type PlayerSkipSeconds, type PlayerState, type PlayerZoomLevel, type QualitySelection, RefreshManager, SECRET_SCAN_MAX_DEPTH, type SeekToLiveResult, type StreamingConfig, type ThemeConfig, type VideoPlayer, type WatermarkConfig, assertNoSecretsInConfig, createAuthParamsLoader, createPlayerError, createVideoPlayer, getLiveEdgeTime, isAtLiveEdge, isPlainObject, mergePlayerConfig, redactObject, redactSensitive, redactValue, rewriteHlsRequestUrl, seekToLiveEdge, themeToCssVars };
|
package/dist/index.js
CHANGED
|
@@ -146,7 +146,7 @@ var AuthManager = class {
|
|
|
146
146
|
|
|
147
147
|
// src/config/defaults.ts
|
|
148
148
|
var PLAYBACK_RATES = [0.5, 0.75, 1, 1.25, 1.5, 2];
|
|
149
|
-
var PACKAGE_VERSION = "0.1.
|
|
149
|
+
var PACKAGE_VERSION = "0.1.5";
|
|
150
150
|
var DEFAULT_PLAYER_BEHAVIOR = {
|
|
151
151
|
autoplay: true,
|
|
152
152
|
startMuted: true,
|
|
@@ -239,7 +239,8 @@ var SKIP_DEEP_SCAN_KEYS = /* @__PURE__ */ new Set([
|
|
|
239
239
|
"gettoken",
|
|
240
240
|
"getsource",
|
|
241
241
|
"refreshsource",
|
|
242
|
-
"keyproxyurlbuilder"
|
|
242
|
+
"keyproxyurlbuilder",
|
|
243
|
+
"getkeyaccesstoken"
|
|
243
244
|
]);
|
|
244
245
|
var SECRET_SCAN_MAX_DEPTH = 32;
|
|
245
246
|
function isPlainObject(value) {
|
|
@@ -322,7 +323,9 @@ function mergePlayerConfig(partial) {
|
|
|
322
323
|
hls: {
|
|
323
324
|
...DEFAULT_PLAYER_CONFIG.streaming.hls,
|
|
324
325
|
...partial?.streaming?.hls
|
|
325
|
-
}
|
|
326
|
+
},
|
|
327
|
+
keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder,
|
|
328
|
+
getKeyAccessToken: partial?.streaming?.getKeyAccessToken
|
|
326
329
|
},
|
|
327
330
|
logging: { ...DEFAULT_PLAYER_CONFIG.logging, ...partial?.logging },
|
|
328
331
|
debug: { ...DEFAULT_PLAYER_CONFIG.debug, ...partial?.debug },
|
|
@@ -498,11 +501,25 @@ var AuthParamsStore = class {
|
|
|
498
501
|
});
|
|
499
502
|
return out;
|
|
500
503
|
}
|
|
504
|
+
/** True when at least one auth query param is stored. */
|
|
505
|
+
hasParams() {
|
|
506
|
+
return [...this.params.keys()].length > 0;
|
|
507
|
+
}
|
|
508
|
+
toString() {
|
|
509
|
+
return this.params.toString();
|
|
510
|
+
}
|
|
501
511
|
update(next) {
|
|
502
512
|
this.params = next instanceof URLSearchParams ? new URLSearchParams(next) : new URLSearchParams(next);
|
|
503
513
|
}
|
|
514
|
+
/**
|
|
515
|
+
* Merges current auth params onto a target URL, preserving unrelated query keys.
|
|
516
|
+
* Critical for child HLS playlists/segments that omit signed query params.
|
|
517
|
+
*/
|
|
504
518
|
applyToUrl(rawUrl, baseOrigin) {
|
|
505
|
-
const target = new URL(
|
|
519
|
+
const target = new URL(
|
|
520
|
+
rawUrl,
|
|
521
|
+
baseOrigin ?? (typeof location !== "undefined" ? location.origin : "http://localhost")
|
|
522
|
+
);
|
|
506
523
|
this.params.forEach((value, key) => {
|
|
507
524
|
target.searchParams.set(key, value);
|
|
508
525
|
});
|
|
@@ -737,7 +754,63 @@ function seekToLiveEdge(media, options) {
|
|
|
737
754
|
return { moved: true, edge };
|
|
738
755
|
}
|
|
739
756
|
|
|
757
|
+
// src/streaming/authParamsLoader.ts
|
|
758
|
+
var ENC_KEY_PATH_RE = /enc\.key$/i;
|
|
759
|
+
function isEncryptionKeyUrl(rawUrl, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
|
|
760
|
+
try {
|
|
761
|
+
return ENC_KEY_PATH_RE.test(new URL(rawUrl, baseOrigin).pathname);
|
|
762
|
+
} catch {
|
|
763
|
+
return /enc\.key/i.test(rawUrl);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
function rewriteHlsRequestUrl(rawUrl, store, options = {}, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
|
|
767
|
+
if (!rawUrl) return rawUrl;
|
|
768
|
+
const parsed = new URL(rawUrl, baseOrigin);
|
|
769
|
+
const isKey = ENC_KEY_PATH_RE.test(parsed.pathname);
|
|
770
|
+
if (isKey && options.keyProxyUrlBuilder && options.videoId) {
|
|
771
|
+
const snapshot = store.toRecord();
|
|
772
|
+
return options.keyProxyUrlBuilder({
|
|
773
|
+
videoId: options.videoId,
|
|
774
|
+
keyUrl: parsed.toString(),
|
|
775
|
+
token: snapshot.token,
|
|
776
|
+
expires: snapshot.expires,
|
|
777
|
+
username: snapshot.username,
|
|
778
|
+
mobile: snapshot.mobile
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
if (!store.hasParams()) return parsed.toString();
|
|
782
|
+
return store.applyToUrl(rawUrl, baseOrigin);
|
|
783
|
+
}
|
|
784
|
+
function createAuthParamsLoader(HlsClass, store, options = {}) {
|
|
785
|
+
const BaseLoader = HlsClass.DefaultConfig.loader;
|
|
786
|
+
return class AuthParamsLoader extends BaseLoader {
|
|
787
|
+
load(context, config, callbacks) {
|
|
788
|
+
if (context.url) {
|
|
789
|
+
try {
|
|
790
|
+
const originalUrl = context.url;
|
|
791
|
+
const isKey = isEncryptionKeyUrl(originalUrl);
|
|
792
|
+
context.url = rewriteHlsRequestUrl(originalUrl, store, options);
|
|
793
|
+
if (isKey && !options.keyProxyUrlBuilder && options.getKeyAccessToken) {
|
|
794
|
+
const accessToken = options.getKeyAccessToken();
|
|
795
|
+
if (accessToken) {
|
|
796
|
+
context.headers = {
|
|
797
|
+
...context.headers ?? {},
|
|
798
|
+
Authorization: `Bearer ${accessToken}`
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
} catch {
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
super.load(context, config, callbacks);
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
|
|
740
810
|
// src/streaming/HlsEngine.ts
|
|
811
|
+
function isAuthHttpStatus(code) {
|
|
812
|
+
return code === 401 || code === 403;
|
|
813
|
+
}
|
|
741
814
|
var HlsEngine = class {
|
|
742
815
|
constructor(options) {
|
|
743
816
|
this.options = options;
|
|
@@ -750,58 +823,58 @@ var HlsEngine = class {
|
|
|
750
823
|
this.qualitySelection = "auto";
|
|
751
824
|
}
|
|
752
825
|
async start() {
|
|
753
|
-
const { videoEl, playbackUrl,
|
|
826
|
+
const { videoEl, playbackUrl, startMuted, volume } = this.options;
|
|
754
827
|
videoEl.muted = startMuted;
|
|
755
|
-
if (typeof volume === "number")
|
|
828
|
+
if (typeof volume === "number") {
|
|
829
|
+
videoEl.volume = Math.min(1, Math.max(0, volume));
|
|
830
|
+
}
|
|
756
831
|
const Hls = (await import("hls.js")).default;
|
|
757
832
|
if (Hls.isSupported()) {
|
|
758
833
|
this.supportMode = "hls.js";
|
|
759
834
|
this.options.callbacks.onSupportMode?.("hls.js");
|
|
760
835
|
await this.startWithHlsJs(Hls, playbackUrl);
|
|
761
|
-
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
if (videoEl.canPlayType("application/vnd.apple.mpegurl")) {
|
|
762
839
|
this.supportMode = "native";
|
|
763
840
|
this.options.callbacks.onSupportMode?.("native");
|
|
764
841
|
videoEl.src = this.options.authParams.applyToUrl(playbackUrl);
|
|
765
842
|
this.bindMediaEvents();
|
|
766
843
|
this.options.callbacks.onStateChange?.("ready");
|
|
767
|
-
|
|
768
|
-
this.supportMode = "unsupported";
|
|
769
|
-
this.options.callbacks.onSupportMode?.("unsupported");
|
|
770
|
-
this.options.callbacks.onFatalError?.("HLS is not supported in this browser");
|
|
844
|
+
await this.applyStartPositionAndAutoplay();
|
|
771
845
|
return;
|
|
772
846
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
await videoEl.play();
|
|
779
|
-
} catch {
|
|
780
|
-
if (!videoEl.muted) {
|
|
781
|
-
videoEl.muted = true;
|
|
782
|
-
try {
|
|
783
|
-
await videoEl.play();
|
|
784
|
-
} catch {
|
|
785
|
-
this.options.logger.warn("Autoplay blocked");
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
}
|
|
847
|
+
this.supportMode = "unsupported";
|
|
848
|
+
this.options.callbacks.onSupportMode?.("unsupported");
|
|
849
|
+
this.options.callbacks.onFatalError?.(
|
|
850
|
+
"HLS is not supported in this browser"
|
|
851
|
+
);
|
|
790
852
|
}
|
|
791
853
|
async startWithHlsJs(Hls, playbackUrl) {
|
|
792
|
-
const {
|
|
854
|
+
const {
|
|
855
|
+
videoEl,
|
|
856
|
+
authParams,
|
|
857
|
+
hlsConfig,
|
|
858
|
+
callbacks,
|
|
859
|
+
videoId,
|
|
860
|
+
keyProxyUrlBuilder,
|
|
861
|
+
getKeyAccessToken
|
|
862
|
+
} = this.options;
|
|
863
|
+
const loader = createAuthParamsLoader(Hls, authParams, {
|
|
864
|
+
videoId,
|
|
865
|
+
keyProxyUrlBuilder,
|
|
866
|
+
getKeyAccessToken
|
|
867
|
+
});
|
|
793
868
|
this.hls = new Hls({
|
|
794
869
|
enableWorker: hlsConfig.enableWorker,
|
|
795
870
|
lowLatencyMode: hlsConfig.lowLatencyMode,
|
|
796
871
|
backBufferLength: hlsConfig.backBufferLength,
|
|
797
|
-
|
|
798
|
-
void xhr;
|
|
799
|
-
void url;
|
|
800
|
-
}
|
|
872
|
+
loader
|
|
801
873
|
});
|
|
802
874
|
this.hls.attachMedia(videoEl);
|
|
803
875
|
this.hls.loadSource(authParams.applyToUrl(playbackUrl));
|
|
804
876
|
this.hls.on(Hls.Events.MANIFEST_PARSED, (_event, data) => {
|
|
877
|
+
if (this.destroyed) return;
|
|
805
878
|
this.qualities = (data.levels ?? []).map((level, index) => ({
|
|
806
879
|
index,
|
|
807
880
|
height: level.height ?? 0,
|
|
@@ -811,42 +884,95 @@ var HlsEngine = class {
|
|
|
811
884
|
}));
|
|
812
885
|
callbacks.onQualitiesChange?.(this.qualities);
|
|
813
886
|
callbacks.onStateChange?.("ready");
|
|
887
|
+
void this.applyStartPositionAndAutoplay();
|
|
814
888
|
});
|
|
815
889
|
this.hls.on(Hls.Events.ERROR, (_event, data) => {
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
890
|
+
this.handleHlsError(Hls, data);
|
|
891
|
+
});
|
|
892
|
+
this.bindMediaEvents();
|
|
893
|
+
}
|
|
894
|
+
handleHlsError(Hls, data) {
|
|
895
|
+
if (this.destroyed) return;
|
|
896
|
+
const { callbacks, hlsConfig, keyProxyUrlBuilder, getKeyAccessToken } = this.options;
|
|
897
|
+
const status = data.response?.code;
|
|
898
|
+
if (data.details === Hls.ErrorDetails.KEY_LOAD_ERROR && !keyProxyUrlBuilder && !getKeyAccessToken) {
|
|
899
|
+
callbacks.onFatalError?.(
|
|
900
|
+
"Encryption key load failed. Configure streaming.getKeyAccessToken (browser Bearer) or streaming.keyProxyUrlBuilder (Cloudflare-allowlisted BFF) for Appextech AES-encrypted HLS."
|
|
901
|
+
);
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
const authFailure = isAuthHttpStatus(status) || data.details === Hls.ErrorDetails.KEY_LOAD_ERROR && (status === void 0 || status === 0 || isAuthHttpStatus(status));
|
|
905
|
+
if (!data.fatal) {
|
|
906
|
+
if (authFailure && 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)) {
|
|
907
|
+
callbacks.onAuthRecoveryNeeded?.(
|
|
908
|
+
data.details === Hls.ErrorDetails.KEY_LOAD_ERROR ? "key_error" : data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR ? "level_error" : "frag_error"
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
if (data.type === Hls.ErrorTypes.NETWORK_ERROR) {
|
|
914
|
+
this.networkRecoveryCount += 1;
|
|
915
|
+
if (this.networkRecoveryCount <= hlsConfig.fatalRecoveryLimit) {
|
|
916
|
+
if (authFailure) {
|
|
917
|
+
callbacks.onAuthRecoveryNeeded?.("fatal_network");
|
|
821
918
|
}
|
|
919
|
+
this.hls?.startLoad();
|
|
822
920
|
return;
|
|
823
921
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
922
|
+
}
|
|
923
|
+
if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
|
|
924
|
+
this.mediaRecoveryCount += 1;
|
|
925
|
+
if (this.mediaRecoveryCount <= hlsConfig.fatalRecoveryLimit) {
|
|
926
|
+
this.hls?.recoverMediaError();
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
callbacks.onFatalError?.(data.details || "Fatal HLS error");
|
|
931
|
+
}
|
|
932
|
+
async applyStartPositionAndAutoplay() {
|
|
933
|
+
if (this.destroyed) return;
|
|
934
|
+
const { videoEl, autoplay, startPosition } = this.options;
|
|
935
|
+
if (typeof startPosition === "number" && startPosition > 0) {
|
|
936
|
+
const seek = () => {
|
|
937
|
+
if (this.destroyed) return;
|
|
938
|
+
try {
|
|
939
|
+
videoEl.currentTime = startPosition;
|
|
940
|
+
} catch {
|
|
830
941
|
}
|
|
942
|
+
};
|
|
943
|
+
if (videoEl.seekable.length > 0) {
|
|
944
|
+
seek();
|
|
945
|
+
} else {
|
|
946
|
+
videoEl.addEventListener("loadedmetadata", seek, { once: true });
|
|
831
947
|
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
948
|
+
}
|
|
949
|
+
if (!autoplay) return;
|
|
950
|
+
try {
|
|
951
|
+
await videoEl.play();
|
|
952
|
+
} catch {
|
|
953
|
+
if (!videoEl.muted) {
|
|
954
|
+
videoEl.muted = true;
|
|
955
|
+
try {
|
|
956
|
+
await videoEl.play();
|
|
957
|
+
} catch {
|
|
958
|
+
this.options.logger.warn("Autoplay blocked");
|
|
837
959
|
}
|
|
838
960
|
}
|
|
839
|
-
|
|
840
|
-
});
|
|
841
|
-
this.bindMediaEvents();
|
|
961
|
+
}
|
|
842
962
|
}
|
|
843
963
|
bindMediaEvents() {
|
|
844
964
|
const { videoEl, callbacks } = this.options;
|
|
845
|
-
videoEl.addEventListener(
|
|
965
|
+
videoEl.addEventListener(
|
|
966
|
+
"playing",
|
|
967
|
+
() => callbacks.onStateChange?.("playing")
|
|
968
|
+
);
|
|
846
969
|
videoEl.addEventListener("pause", () => {
|
|
847
970
|
if (!videoEl.ended) callbacks.onStateChange?.("paused");
|
|
848
971
|
});
|
|
849
|
-
videoEl.addEventListener(
|
|
972
|
+
videoEl.addEventListener(
|
|
973
|
+
"waiting",
|
|
974
|
+
() => callbacks.onStateChange?.("buffering")
|
|
975
|
+
);
|
|
850
976
|
videoEl.addEventListener("ended", () => {
|
|
851
977
|
callbacks.onStateChange?.("ended");
|
|
852
978
|
callbacks.onEnded?.();
|
|
@@ -864,6 +990,7 @@ var HlsEngine = class {
|
|
|
864
990
|
}
|
|
865
991
|
}
|
|
866
992
|
resumeAfterAuthRenew() {
|
|
993
|
+
this.networkRecoveryCount = 0;
|
|
867
994
|
this.hls?.startLoad();
|
|
868
995
|
this.updateAuthParams();
|
|
869
996
|
}
|
|
@@ -1005,6 +1132,9 @@ function createVideoPlayer(options) {
|
|
|
1005
1132
|
startMuted: config.player.startMuted,
|
|
1006
1133
|
startPosition: config.player.startPosition,
|
|
1007
1134
|
volume: config.player.volume,
|
|
1135
|
+
videoId: options.videoId,
|
|
1136
|
+
keyProxyUrlBuilder: config.streaming.keyProxyUrlBuilder,
|
|
1137
|
+
getKeyAccessToken: config.streaming.getKeyAccessToken,
|
|
1008
1138
|
hlsConfig: config.streaming.hls,
|
|
1009
1139
|
logger,
|
|
1010
1140
|
callbacks: {
|
|
@@ -1065,6 +1195,14 @@ function createVideoPlayer(options) {
|
|
|
1065
1195
|
onRenewing: () => setState("renewing"),
|
|
1066
1196
|
onRenewed: (nextExpiresAt) => {
|
|
1067
1197
|
engine?.resumeAfterAuthRenew();
|
|
1198
|
+
if (!destroyed) {
|
|
1199
|
+
const el2 = videoEl;
|
|
1200
|
+
if (el2 && !el2.paused && !el2.ended) {
|
|
1201
|
+
setState("playing");
|
|
1202
|
+
} else {
|
|
1203
|
+
setState("ready");
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1068
1206
|
events.emit("tokenrefresh", { expiresAt: nextExpiresAt });
|
|
1069
1207
|
events.emit("sourcechange", { expiresAt: nextExpiresAt });
|
|
1070
1208
|
},
|
|
@@ -1265,7 +1403,9 @@ function createVideoPlayer(options) {
|
|
|
1265
1403
|
watermark: { ...config.watermark, ...partial?.watermark },
|
|
1266
1404
|
streaming: {
|
|
1267
1405
|
engine: "hls",
|
|
1268
|
-
hls: { ...config.streaming.hls, ...partial?.streaming?.hls }
|
|
1406
|
+
hls: { ...config.streaming.hls, ...partial?.streaming?.hls },
|
|
1407
|
+
keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder ?? config.streaming.keyProxyUrlBuilder,
|
|
1408
|
+
getKeyAccessToken: partial?.streaming?.getKeyAccessToken ?? config.streaming.getKeyAccessToken
|
|
1269
1409
|
},
|
|
1270
1410
|
logging: { ...config.logging, ...partial?.logging },
|
|
1271
1411
|
debug: { ...config.debug, ...partial?.debug },
|
|
@@ -1303,6 +1443,7 @@ export {
|
|
|
1303
1443
|
RefreshManager,
|
|
1304
1444
|
SECRET_SCAN_MAX_DEPTH,
|
|
1305
1445
|
assertNoSecretsInConfig,
|
|
1446
|
+
createAuthParamsLoader,
|
|
1306
1447
|
createPlayerError,
|
|
1307
1448
|
createVideoPlayer,
|
|
1308
1449
|
getLiveEdgeTime,
|
|
@@ -1312,6 +1453,7 @@ export {
|
|
|
1312
1453
|
redactObject,
|
|
1313
1454
|
redactSensitive,
|
|
1314
1455
|
redactValue,
|
|
1456
|
+
rewriteHlsRequestUrl,
|
|
1315
1457
|
seekToLiveEdge,
|
|
1316
1458
|
themeToCssVars
|
|
1317
1459
|
};
|