@codenkay/video-nsgplayer-core 0.1.3 → 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 +12 -0
- package/dist/index.cjs +143 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -5
- package/dist/index.d.ts +26 -5
- package/dist/index.js +143 -55
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.5
|
|
4
|
+
|
|
5
|
+
- Add `streaming.getKeyAccessToken` for browser-direct AES `enc.key` loads with `Authorization: Bearer` (needed when Cloudflare blocks server/BFF fetches to Appextech).
|
|
6
|
+
- Clearer key-load guidance when neither key proxy nor browser Bearer is configured.
|
|
7
|
+
|
|
8
|
+
## 0.1.4
|
|
9
|
+
|
|
10
|
+
- Fix stall after segments return 200: wait for `MANIFEST_PARSED` before autoplay/seek.
|
|
11
|
+
- Only trigger signed-URL auth recovery on HTTP 401/403 (not every network blip).
|
|
12
|
+
- Require/surface `streaming.keyProxyUrlBuilder` when AES `enc.key` load fails (Appextech VOD needs same-origin BFF + API Bearer).
|
|
13
|
+
- Clear sticky `renewing` state after successful credential refresh.
|
|
14
|
+
|
|
3
15
|
## 0.1.3
|
|
4
16
|
|
|
5
17
|
- Fix CDN **403 on child HLS playlists/segments** (`stream_N.m3u8`, `.ts`, keys): hls.js now uses an auth-params loader that re-applies signed `token`/`expires` query params on every request, not only the master playlist.
|
package/dist/index.cjs
CHANGED
|
@@ -210,7 +210,7 @@ var AuthManager = class {
|
|
|
210
210
|
|
|
211
211
|
// src/config/defaults.ts
|
|
212
212
|
var PLAYBACK_RATES = [0.5, 0.75, 1, 1.25, 1.5, 2];
|
|
213
|
-
var PACKAGE_VERSION = "0.1.
|
|
213
|
+
var PACKAGE_VERSION = "0.1.5";
|
|
214
214
|
var DEFAULT_PLAYER_BEHAVIOR = {
|
|
215
215
|
autoplay: true,
|
|
216
216
|
startMuted: true,
|
|
@@ -303,7 +303,8 @@ var SKIP_DEEP_SCAN_KEYS = /* @__PURE__ */ new Set([
|
|
|
303
303
|
"gettoken",
|
|
304
304
|
"getsource",
|
|
305
305
|
"refreshsource",
|
|
306
|
-
"keyproxyurlbuilder"
|
|
306
|
+
"keyproxyurlbuilder",
|
|
307
|
+
"getkeyaccesstoken"
|
|
307
308
|
]);
|
|
308
309
|
var SECRET_SCAN_MAX_DEPTH = 32;
|
|
309
310
|
function isPlainObject(value) {
|
|
@@ -387,7 +388,8 @@ function mergePlayerConfig(partial) {
|
|
|
387
388
|
...DEFAULT_PLAYER_CONFIG.streaming.hls,
|
|
388
389
|
...partial?.streaming?.hls
|
|
389
390
|
},
|
|
390
|
-
keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder
|
|
391
|
+
keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder,
|
|
392
|
+
getKeyAccessToken: partial?.streaming?.getKeyAccessToken
|
|
391
393
|
},
|
|
392
394
|
logging: { ...DEFAULT_PLAYER_CONFIG.logging, ...partial?.logging },
|
|
393
395
|
debug: { ...DEFAULT_PLAYER_CONFIG.debug, ...partial?.debug },
|
|
@@ -817,7 +819,14 @@ function seekToLiveEdge(media, options) {
|
|
|
817
819
|
}
|
|
818
820
|
|
|
819
821
|
// src/streaming/authParamsLoader.ts
|
|
820
|
-
var ENC_KEY_PATH_RE =
|
|
822
|
+
var ENC_KEY_PATH_RE = /enc\.key$/i;
|
|
823
|
+
function isEncryptionKeyUrl(rawUrl, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
|
|
824
|
+
try {
|
|
825
|
+
return ENC_KEY_PATH_RE.test(new URL(rawUrl, baseOrigin).pathname);
|
|
826
|
+
} catch {
|
|
827
|
+
return /enc\.key/i.test(rawUrl);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
821
830
|
function rewriteHlsRequestUrl(rawUrl, store, options = {}, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
|
|
822
831
|
if (!rawUrl) return rawUrl;
|
|
823
832
|
const parsed = new URL(rawUrl, baseOrigin);
|
|
@@ -828,7 +837,9 @@ function rewriteHlsRequestUrl(rawUrl, store, options = {}, baseOrigin = typeof l
|
|
|
828
837
|
videoId: options.videoId,
|
|
829
838
|
keyUrl: parsed.toString(),
|
|
830
839
|
token: snapshot.token,
|
|
831
|
-
expires: snapshot.expires
|
|
840
|
+
expires: snapshot.expires,
|
|
841
|
+
username: snapshot.username,
|
|
842
|
+
mobile: snapshot.mobile
|
|
832
843
|
});
|
|
833
844
|
}
|
|
834
845
|
if (!store.hasParams()) return parsed.toString();
|
|
@@ -840,7 +851,18 @@ function createAuthParamsLoader(HlsClass, store, options = {}) {
|
|
|
840
851
|
load(context, config, callbacks) {
|
|
841
852
|
if (context.url) {
|
|
842
853
|
try {
|
|
843
|
-
|
|
854
|
+
const originalUrl = context.url;
|
|
855
|
+
const isKey = isEncryptionKeyUrl(originalUrl);
|
|
856
|
+
context.url = rewriteHlsRequestUrl(originalUrl, store, options);
|
|
857
|
+
if (isKey && !options.keyProxyUrlBuilder && options.getKeyAccessToken) {
|
|
858
|
+
const accessToken = options.getKeyAccessToken();
|
|
859
|
+
if (accessToken) {
|
|
860
|
+
context.headers = {
|
|
861
|
+
...context.headers ?? {},
|
|
862
|
+
Authorization: `Bearer ${accessToken}`
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
}
|
|
844
866
|
} catch {
|
|
845
867
|
}
|
|
846
868
|
}
|
|
@@ -850,6 +872,9 @@ function createAuthParamsLoader(HlsClass, store, options = {}) {
|
|
|
850
872
|
}
|
|
851
873
|
|
|
852
874
|
// src/streaming/HlsEngine.ts
|
|
875
|
+
function isAuthHttpStatus(code) {
|
|
876
|
+
return code === 401 || code === 403;
|
|
877
|
+
}
|
|
853
878
|
var HlsEngine = class {
|
|
854
879
|
constructor(options) {
|
|
855
880
|
this.options = options;
|
|
@@ -862,49 +887,47 @@ var HlsEngine = class {
|
|
|
862
887
|
this.qualitySelection = "auto";
|
|
863
888
|
}
|
|
864
889
|
async start() {
|
|
865
|
-
const { videoEl, playbackUrl,
|
|
890
|
+
const { videoEl, playbackUrl, startMuted, volume } = this.options;
|
|
866
891
|
videoEl.muted = startMuted;
|
|
867
|
-
if (typeof volume === "number")
|
|
892
|
+
if (typeof volume === "number") {
|
|
893
|
+
videoEl.volume = Math.min(1, Math.max(0, volume));
|
|
894
|
+
}
|
|
868
895
|
const Hls = (await import("hls.js")).default;
|
|
869
896
|
if (Hls.isSupported()) {
|
|
870
897
|
this.supportMode = "hls.js";
|
|
871
898
|
this.options.callbacks.onSupportMode?.("hls.js");
|
|
872
899
|
await this.startWithHlsJs(Hls, playbackUrl);
|
|
873
|
-
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
if (videoEl.canPlayType("application/vnd.apple.mpegurl")) {
|
|
874
903
|
this.supportMode = "native";
|
|
875
904
|
this.options.callbacks.onSupportMode?.("native");
|
|
876
905
|
videoEl.src = this.options.authParams.applyToUrl(playbackUrl);
|
|
877
906
|
this.bindMediaEvents();
|
|
878
907
|
this.options.callbacks.onStateChange?.("ready");
|
|
879
|
-
|
|
880
|
-
this.supportMode = "unsupported";
|
|
881
|
-
this.options.callbacks.onSupportMode?.("unsupported");
|
|
882
|
-
this.options.callbacks.onFatalError?.("HLS is not supported in this browser");
|
|
908
|
+
await this.applyStartPositionAndAutoplay();
|
|
883
909
|
return;
|
|
884
910
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
await videoEl.play();
|
|
891
|
-
} catch {
|
|
892
|
-
if (!videoEl.muted) {
|
|
893
|
-
videoEl.muted = true;
|
|
894
|
-
try {
|
|
895
|
-
await videoEl.play();
|
|
896
|
-
} catch {
|
|
897
|
-
this.options.logger.warn("Autoplay blocked");
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
}
|
|
911
|
+
this.supportMode = "unsupported";
|
|
912
|
+
this.options.callbacks.onSupportMode?.("unsupported");
|
|
913
|
+
this.options.callbacks.onFatalError?.(
|
|
914
|
+
"HLS is not supported in this browser"
|
|
915
|
+
);
|
|
902
916
|
}
|
|
903
917
|
async startWithHlsJs(Hls, playbackUrl) {
|
|
904
|
-
const {
|
|
918
|
+
const {
|
|
919
|
+
videoEl,
|
|
920
|
+
authParams,
|
|
921
|
+
hlsConfig,
|
|
922
|
+
callbacks,
|
|
923
|
+
videoId,
|
|
924
|
+
keyProxyUrlBuilder,
|
|
925
|
+
getKeyAccessToken
|
|
926
|
+
} = this.options;
|
|
905
927
|
const loader = createAuthParamsLoader(Hls, authParams, {
|
|
906
928
|
videoId,
|
|
907
|
-
keyProxyUrlBuilder
|
|
929
|
+
keyProxyUrlBuilder,
|
|
930
|
+
getKeyAccessToken
|
|
908
931
|
});
|
|
909
932
|
this.hls = new Hls({
|
|
910
933
|
enableWorker: hlsConfig.enableWorker,
|
|
@@ -915,6 +938,7 @@ var HlsEngine = class {
|
|
|
915
938
|
this.hls.attachMedia(videoEl);
|
|
916
939
|
this.hls.loadSource(authParams.applyToUrl(playbackUrl));
|
|
917
940
|
this.hls.on(Hls.Events.MANIFEST_PARSED, (_event, data) => {
|
|
941
|
+
if (this.destroyed) return;
|
|
918
942
|
this.qualities = (data.levels ?? []).map((level, index) => ({
|
|
919
943
|
index,
|
|
920
944
|
height: level.height ?? 0,
|
|
@@ -924,42 +948,95 @@ var HlsEngine = class {
|
|
|
924
948
|
}));
|
|
925
949
|
callbacks.onQualitiesChange?.(this.qualities);
|
|
926
950
|
callbacks.onStateChange?.("ready");
|
|
951
|
+
void this.applyStartPositionAndAutoplay();
|
|
927
952
|
});
|
|
928
953
|
this.hls.on(Hls.Events.ERROR, (_event, data) => {
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
954
|
+
this.handleHlsError(Hls, data);
|
|
955
|
+
});
|
|
956
|
+
this.bindMediaEvents();
|
|
957
|
+
}
|
|
958
|
+
handleHlsError(Hls, data) {
|
|
959
|
+
if (this.destroyed) return;
|
|
960
|
+
const { callbacks, hlsConfig, keyProxyUrlBuilder, getKeyAccessToken } = this.options;
|
|
961
|
+
const status = data.response?.code;
|
|
962
|
+
if (data.details === Hls.ErrorDetails.KEY_LOAD_ERROR && !keyProxyUrlBuilder && !getKeyAccessToken) {
|
|
963
|
+
callbacks.onFatalError?.(
|
|
964
|
+
"Encryption key load failed. Configure streaming.getKeyAccessToken (browser Bearer) or streaming.keyProxyUrlBuilder (Cloudflare-allowlisted BFF) for Appextech AES-encrypted HLS."
|
|
965
|
+
);
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
const authFailure = isAuthHttpStatus(status) || data.details === Hls.ErrorDetails.KEY_LOAD_ERROR && (status === void 0 || status === 0 || isAuthHttpStatus(status));
|
|
969
|
+
if (!data.fatal) {
|
|
970
|
+
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)) {
|
|
971
|
+
callbacks.onAuthRecoveryNeeded?.(
|
|
972
|
+
data.details === Hls.ErrorDetails.KEY_LOAD_ERROR ? "key_error" : data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR ? "level_error" : "frag_error"
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
if (data.type === Hls.ErrorTypes.NETWORK_ERROR) {
|
|
978
|
+
this.networkRecoveryCount += 1;
|
|
979
|
+
if (this.networkRecoveryCount <= hlsConfig.fatalRecoveryLimit) {
|
|
980
|
+
if (authFailure) {
|
|
981
|
+
callbacks.onAuthRecoveryNeeded?.("fatal_network");
|
|
934
982
|
}
|
|
983
|
+
this.hls?.startLoad();
|
|
935
984
|
return;
|
|
936
985
|
}
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
986
|
+
}
|
|
987
|
+
if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
|
|
988
|
+
this.mediaRecoveryCount += 1;
|
|
989
|
+
if (this.mediaRecoveryCount <= hlsConfig.fatalRecoveryLimit) {
|
|
990
|
+
this.hls?.recoverMediaError();
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
callbacks.onFatalError?.(data.details || "Fatal HLS error");
|
|
995
|
+
}
|
|
996
|
+
async applyStartPositionAndAutoplay() {
|
|
997
|
+
if (this.destroyed) return;
|
|
998
|
+
const { videoEl, autoplay, startPosition } = this.options;
|
|
999
|
+
if (typeof startPosition === "number" && startPosition > 0) {
|
|
1000
|
+
const seek = () => {
|
|
1001
|
+
if (this.destroyed) return;
|
|
1002
|
+
try {
|
|
1003
|
+
videoEl.currentTime = startPosition;
|
|
1004
|
+
} catch {
|
|
943
1005
|
}
|
|
1006
|
+
};
|
|
1007
|
+
if (videoEl.seekable.length > 0) {
|
|
1008
|
+
seek();
|
|
1009
|
+
} else {
|
|
1010
|
+
videoEl.addEventListener("loadedmetadata", seek, { once: true });
|
|
944
1011
|
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1012
|
+
}
|
|
1013
|
+
if (!autoplay) return;
|
|
1014
|
+
try {
|
|
1015
|
+
await videoEl.play();
|
|
1016
|
+
} catch {
|
|
1017
|
+
if (!videoEl.muted) {
|
|
1018
|
+
videoEl.muted = true;
|
|
1019
|
+
try {
|
|
1020
|
+
await videoEl.play();
|
|
1021
|
+
} catch {
|
|
1022
|
+
this.options.logger.warn("Autoplay blocked");
|
|
950
1023
|
}
|
|
951
1024
|
}
|
|
952
|
-
|
|
953
|
-
});
|
|
954
|
-
this.bindMediaEvents();
|
|
1025
|
+
}
|
|
955
1026
|
}
|
|
956
1027
|
bindMediaEvents() {
|
|
957
1028
|
const { videoEl, callbacks } = this.options;
|
|
958
|
-
videoEl.addEventListener(
|
|
1029
|
+
videoEl.addEventListener(
|
|
1030
|
+
"playing",
|
|
1031
|
+
() => callbacks.onStateChange?.("playing")
|
|
1032
|
+
);
|
|
959
1033
|
videoEl.addEventListener("pause", () => {
|
|
960
1034
|
if (!videoEl.ended) callbacks.onStateChange?.("paused");
|
|
961
1035
|
});
|
|
962
|
-
videoEl.addEventListener(
|
|
1036
|
+
videoEl.addEventListener(
|
|
1037
|
+
"waiting",
|
|
1038
|
+
() => callbacks.onStateChange?.("buffering")
|
|
1039
|
+
);
|
|
963
1040
|
videoEl.addEventListener("ended", () => {
|
|
964
1041
|
callbacks.onStateChange?.("ended");
|
|
965
1042
|
callbacks.onEnded?.();
|
|
@@ -977,6 +1054,7 @@ var HlsEngine = class {
|
|
|
977
1054
|
}
|
|
978
1055
|
}
|
|
979
1056
|
resumeAfterAuthRenew() {
|
|
1057
|
+
this.networkRecoveryCount = 0;
|
|
980
1058
|
this.hls?.startLoad();
|
|
981
1059
|
this.updateAuthParams();
|
|
982
1060
|
}
|
|
@@ -1119,7 +1197,8 @@ function createVideoPlayer(options) {
|
|
|
1119
1197
|
startPosition: config.player.startPosition,
|
|
1120
1198
|
volume: config.player.volume,
|
|
1121
1199
|
videoId: options.videoId,
|
|
1122
|
-
keyProxyUrlBuilder:
|
|
1200
|
+
keyProxyUrlBuilder: config.streaming.keyProxyUrlBuilder,
|
|
1201
|
+
getKeyAccessToken: config.streaming.getKeyAccessToken,
|
|
1123
1202
|
hlsConfig: config.streaming.hls,
|
|
1124
1203
|
logger,
|
|
1125
1204
|
callbacks: {
|
|
@@ -1180,6 +1259,14 @@ function createVideoPlayer(options) {
|
|
|
1180
1259
|
onRenewing: () => setState("renewing"),
|
|
1181
1260
|
onRenewed: (nextExpiresAt) => {
|
|
1182
1261
|
engine?.resumeAfterAuthRenew();
|
|
1262
|
+
if (!destroyed) {
|
|
1263
|
+
const el2 = videoEl;
|
|
1264
|
+
if (el2 && !el2.paused && !el2.ended) {
|
|
1265
|
+
setState("playing");
|
|
1266
|
+
} else {
|
|
1267
|
+
setState("ready");
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1183
1270
|
events.emit("tokenrefresh", { expiresAt: nextExpiresAt });
|
|
1184
1271
|
events.emit("sourcechange", { expiresAt: nextExpiresAt });
|
|
1185
1272
|
},
|
|
@@ -1381,7 +1468,8 @@ function createVideoPlayer(options) {
|
|
|
1381
1468
|
streaming: {
|
|
1382
1469
|
engine: "hls",
|
|
1383
1470
|
hls: { ...config.streaming.hls, ...partial?.streaming?.hls },
|
|
1384
|
-
keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder ?? config.streaming.keyProxyUrlBuilder
|
|
1471
|
+
keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder ?? config.streaming.keyProxyUrlBuilder,
|
|
1472
|
+
getKeyAccessToken: partial?.streaming?.getKeyAccessToken ?? config.streaming.getKeyAccessToken
|
|
1385
1473
|
},
|
|
1386
1474
|
logging: { ...config.logging, ...partial?.logging },
|
|
1387
1475
|
debug: { ...config.debug, ...partial?.debug },
|