@codenkay/video-nsgplayer-core 3.0.2 → 4.0.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/CHANGELOG.md +19 -0
- package/README.md +12 -0
- package/dist/index.cjs +226 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -17
- package/dist/index.d.ts +66 -17
- package/dist/index.js +226 -48
- package/dist/index.js.map +1 -1
- package/package.json +11 -2
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 = "3.0.
|
|
149
|
+
var PACKAGE_VERSION = "3.0.3";
|
|
150
150
|
var DEFAULT_PLAYER_BEHAVIOR = {
|
|
151
151
|
autoplay: true,
|
|
152
152
|
startMuted: true,
|
|
@@ -170,7 +170,8 @@ var DEFAULT_CONTROLS = {
|
|
|
170
170
|
showSkip: true,
|
|
171
171
|
showLive: true,
|
|
172
172
|
seekCueDurationMs: 720,
|
|
173
|
-
keyboardShortcuts: true
|
|
173
|
+
keyboardShortcuts: true,
|
|
174
|
+
persistQualityPreference: false
|
|
174
175
|
};
|
|
175
176
|
var DEFAULT_THEME = {
|
|
176
177
|
playIconPreset: "triangle",
|
|
@@ -215,7 +216,8 @@ var DEFAULT_PLAYER_CONFIG = {
|
|
|
215
216
|
engine: "hls",
|
|
216
217
|
hls: {
|
|
217
218
|
enableWorker: true,
|
|
218
|
-
|
|
219
|
+
// VOD + AES-128: LL mode causes ABR thrash → bufferAppendError on MSE.
|
|
220
|
+
lowLatencyMode: false,
|
|
219
221
|
backBufferLength: 90,
|
|
220
222
|
fatalRecoveryLimit: 3
|
|
221
223
|
}
|
|
@@ -792,7 +794,8 @@ function rememberBuiltin(HlsClass, loader) {
|
|
|
792
794
|
function getStashedBuiltinLoader(loader) {
|
|
793
795
|
return loader[BUILTIN_LOADER_REF];
|
|
794
796
|
}
|
|
795
|
-
function resolveBuiltinHlsLoader(
|
|
797
|
+
function resolveBuiltinHlsLoader(HlsModule) {
|
|
798
|
+
const HlsClass = HlsModule;
|
|
796
799
|
const cached = builtinLoaderByHls.get(HlsClass);
|
|
797
800
|
if (cached) return cached;
|
|
798
801
|
const start = HlsClass.DefaultConfig.loader;
|
|
@@ -920,29 +923,52 @@ async function fetchEncryptionKeyBytes(url, accessToken, signal) {
|
|
|
920
923
|
};
|
|
921
924
|
}
|
|
922
925
|
}
|
|
926
|
+
var AES_128_KEY_BYTES = 16;
|
|
927
|
+
function normalizeAes128Key(data) {
|
|
928
|
+
if (data.byteLength === AES_128_KEY_BYTES) {
|
|
929
|
+
return data.slice(0);
|
|
930
|
+
}
|
|
931
|
+
return null;
|
|
932
|
+
}
|
|
923
933
|
function reportKeySuccess(context, callbacks, result) {
|
|
934
|
+
const key = normalizeAes128Key(result.data);
|
|
935
|
+
if (!key) {
|
|
936
|
+
const stats2 = createLoaderStats();
|
|
937
|
+
callbacks.onError(
|
|
938
|
+
{
|
|
939
|
+
code: 422,
|
|
940
|
+
text: `enc.key must be ${AES_128_KEY_BYTES} bytes (got ${result.data.byteLength})`
|
|
941
|
+
},
|
|
942
|
+
context,
|
|
943
|
+
result.response,
|
|
944
|
+
stats2
|
|
945
|
+
);
|
|
946
|
+
return false;
|
|
947
|
+
}
|
|
924
948
|
const stats = createLoaderStats();
|
|
925
949
|
const end = performance.now();
|
|
926
950
|
stats.loading.first = stats.loading.start;
|
|
927
951
|
stats.loading.end = end;
|
|
928
|
-
stats.loaded =
|
|
929
|
-
stats.total =
|
|
952
|
+
stats.loaded = key.byteLength;
|
|
953
|
+
stats.total = key.byteLength;
|
|
930
954
|
callbacks.onSuccess(
|
|
931
955
|
{
|
|
932
956
|
url: result.url,
|
|
933
|
-
data:
|
|
957
|
+
data: key,
|
|
934
958
|
code: result.response.status
|
|
935
959
|
},
|
|
936
960
|
stats,
|
|
937
961
|
context,
|
|
938
962
|
result.response
|
|
939
963
|
);
|
|
964
|
+
return true;
|
|
940
965
|
}
|
|
941
|
-
async function loadEncryptionKeyEnterprise(context, callbacks, plan, signal, state) {
|
|
966
|
+
async function loadEncryptionKeyEnterprise(context, callbacks, plan, signal, state, lifecycle) {
|
|
942
967
|
const stats = createLoaderStats();
|
|
968
|
+
const stillCurrent = () => lifecycle.isCurrent() && !signal.aborted;
|
|
943
969
|
const canTryProxy = Boolean(plan.proxyUrl) && !state.circuitOpen && state.proxyFailures < plan.maxProxyFailures;
|
|
944
970
|
if (canTryProxy && plan.proxyUrl) {
|
|
945
|
-
while (!state.circuitOpen && state.proxyFailures < plan.maxProxyFailures &&
|
|
971
|
+
while (!state.circuitOpen && state.proxyFailures < plan.maxProxyFailures && stillCurrent()) {
|
|
946
972
|
state.proxyFailures += 1;
|
|
947
973
|
try {
|
|
948
974
|
const response = await fetch(plan.proxyUrl, {
|
|
@@ -952,18 +978,33 @@ async function loadEncryptionKeyEnterprise(context, callbacks, plan, signal, sta
|
|
|
952
978
|
cache: "no-store",
|
|
953
979
|
signal
|
|
954
980
|
});
|
|
981
|
+
if (!lifecycle.isCurrent()) return "aborted";
|
|
955
982
|
const contentType = response.headers.get("content-type");
|
|
956
983
|
const data = await response.arrayBuffer();
|
|
984
|
+
if (!lifecycle.isCurrent()) return "aborted";
|
|
957
985
|
const head = new TextDecoder().decode(data.slice(0, 64));
|
|
958
986
|
if (response.ok && !isProxyUpstreamBlocked(response.status, contentType, head)) {
|
|
987
|
+
const key = normalizeAes128Key(data);
|
|
988
|
+
if (!key) {
|
|
989
|
+
callbacks.onError(
|
|
990
|
+
{
|
|
991
|
+
code: 422,
|
|
992
|
+
text: `enc.key proxy returned ${data.byteLength} bytes (need ${AES_128_KEY_BYTES})`
|
|
993
|
+
},
|
|
994
|
+
{ ...context, url: plan.proxyUrl },
|
|
995
|
+
response,
|
|
996
|
+
stats
|
|
997
|
+
);
|
|
998
|
+
return "ok";
|
|
999
|
+
}
|
|
959
1000
|
const end = performance.now();
|
|
960
1001
|
stats.loading.end = end;
|
|
961
|
-
stats.loaded =
|
|
962
|
-
stats.total =
|
|
1002
|
+
stats.loaded = key.byteLength;
|
|
1003
|
+
stats.total = key.byteLength;
|
|
963
1004
|
callbacks.onSuccess(
|
|
964
1005
|
{
|
|
965
1006
|
url: response.url || plan.proxyUrl,
|
|
966
|
-
data,
|
|
1007
|
+
data: key,
|
|
967
1008
|
code: response.status
|
|
968
1009
|
},
|
|
969
1010
|
stats,
|
|
@@ -996,25 +1037,22 @@ async function loadEncryptionKeyEnterprise(context, callbacks, plan, signal, sta
|
|
|
996
1037
|
return "ok";
|
|
997
1038
|
} catch (error) {
|
|
998
1039
|
if (error instanceof DOMException && error.name === "AbortError") {
|
|
999
|
-
|
|
1000
|
-
{ code: 0, text: "enc.key proxy aborted/timeout" },
|
|
1001
|
-
context,
|
|
1002
|
-
null,
|
|
1003
|
-
stats
|
|
1004
|
-
);
|
|
1005
|
-
return "ok";
|
|
1040
|
+
return "aborted";
|
|
1006
1041
|
}
|
|
1007
1042
|
state.circuitOpen = true;
|
|
1008
1043
|
break;
|
|
1009
1044
|
}
|
|
1010
1045
|
}
|
|
1046
|
+
if (!lifecycle.isCurrent()) return "aborted";
|
|
1011
1047
|
state.circuitOpen = true;
|
|
1012
1048
|
}
|
|
1049
|
+
if (!lifecycle.isCurrent()) return "aborted";
|
|
1013
1050
|
const queryResult = await fetchEncryptionKeyBytes(
|
|
1014
1051
|
plan.signedKeyUrl,
|
|
1015
1052
|
null,
|
|
1016
1053
|
signal
|
|
1017
1054
|
);
|
|
1055
|
+
if (!lifecycle.isCurrent()) return "aborted";
|
|
1018
1056
|
if (queryResult.ok) {
|
|
1019
1057
|
reportKeySuccess(
|
|
1020
1058
|
{ ...context, url: plan.signedKeyUrl },
|
|
@@ -1023,13 +1061,19 @@ async function loadEncryptionKeyEnterprise(context, callbacks, plan, signal, sta
|
|
|
1023
1061
|
);
|
|
1024
1062
|
return "ok";
|
|
1025
1063
|
}
|
|
1026
|
-
|
|
1064
|
+
if (queryResult.status === 0 && signal.aborted) {
|
|
1065
|
+
return "aborted";
|
|
1066
|
+
}
|
|
1067
|
+
const needsBearer = Boolean(plan.accessToken) && (queryResult.status === 401 || queryResult.status === 403 || // Browser-direct: opaque CORS/network on unsigned GET — try Bearer once
|
|
1068
|
+
// Appextech allows ACAO + Authorization (AUTH_MODE=browser, no BFF).
|
|
1069
|
+
queryResult.cors || queryResult.status === 0);
|
|
1027
1070
|
if (needsBearer && plan.accessToken) {
|
|
1028
1071
|
const bearerResult = await fetchEncryptionKeyBytes(
|
|
1029
1072
|
plan.signedKeyUrl,
|
|
1030
1073
|
plan.accessToken,
|
|
1031
1074
|
signal
|
|
1032
1075
|
);
|
|
1076
|
+
if (!lifecycle.isCurrent()) return "aborted";
|
|
1033
1077
|
if (bearerResult.ok) {
|
|
1034
1078
|
reportKeySuccess(
|
|
1035
1079
|
{ ...context, url: plan.signedKeyUrl },
|
|
@@ -1038,7 +1082,10 @@ async function loadEncryptionKeyEnterprise(context, callbacks, plan, signal, sta
|
|
|
1038
1082
|
);
|
|
1039
1083
|
return "ok";
|
|
1040
1084
|
}
|
|
1041
|
-
if (bearerResult.
|
|
1085
|
+
if (bearerResult.status === 0 && signal.aborted) {
|
|
1086
|
+
return "aborted";
|
|
1087
|
+
}
|
|
1088
|
+
if (bearerResult.cors || bearerResult.status === 0) {
|
|
1042
1089
|
return "try_xhr";
|
|
1043
1090
|
}
|
|
1044
1091
|
callbacks.onError(
|
|
@@ -1066,7 +1113,8 @@ async function loadEncryptionKeyEnterprise(context, callbacks, plan, signal, sta
|
|
|
1066
1113
|
);
|
|
1067
1114
|
return "ok";
|
|
1068
1115
|
}
|
|
1069
|
-
function createAuthParamsLoader(
|
|
1116
|
+
function createAuthParamsLoader(HlsModule, store, options = {}) {
|
|
1117
|
+
const HlsClass = HlsModule;
|
|
1070
1118
|
const BaseLoader = resolveBuiltinHlsLoader(HlsClass);
|
|
1071
1119
|
rememberBuiltin(HlsClass, BaseLoader);
|
|
1072
1120
|
const maxProxyFailures = typeof options.keyProxyMaxFailures === "number" && options.keyProxyMaxFailures >= 0 ? options.keyProxyMaxFailures : KEY_PROXY_MAX_FAILURES;
|
|
@@ -1075,17 +1123,34 @@ function createAuthParamsLoader(HlsClass, store, options = {}) {
|
|
|
1075
1123
|
constructor(config) {
|
|
1076
1124
|
this.context = null;
|
|
1077
1125
|
this.keyAbort = null;
|
|
1126
|
+
/** Bumps on each key load so superseded aborts never call onError. */
|
|
1127
|
+
this.keyLoadGeneration = 0;
|
|
1128
|
+
this.pendingKeyCallbacks = null;
|
|
1078
1129
|
this.inner = new BaseLoader(config);
|
|
1079
1130
|
this.stats = this.inner.stats;
|
|
1080
1131
|
}
|
|
1081
|
-
|
|
1132
|
+
finishKeyAbort() {
|
|
1133
|
+
const cbs = this.pendingKeyCallbacks;
|
|
1134
|
+
this.pendingKeyCallbacks = null;
|
|
1082
1135
|
this.keyAbort?.abort();
|
|
1083
1136
|
this.keyAbort = null;
|
|
1137
|
+
if (cbs?.onAbort && this.context) {
|
|
1138
|
+
const stats = createLoaderStats();
|
|
1139
|
+
stats.aborted = true;
|
|
1140
|
+
try {
|
|
1141
|
+
cbs.onAbort(stats, this.context, null);
|
|
1142
|
+
} catch {
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
abort() {
|
|
1147
|
+
this.keyLoadGeneration += 1;
|
|
1148
|
+
this.finishKeyAbort();
|
|
1084
1149
|
this.inner.abort();
|
|
1085
1150
|
}
|
|
1086
1151
|
destroy() {
|
|
1087
|
-
this.
|
|
1088
|
-
this.
|
|
1152
|
+
this.keyLoadGeneration += 1;
|
|
1153
|
+
this.finishKeyAbort();
|
|
1089
1154
|
this.context = null;
|
|
1090
1155
|
this.inner.destroy();
|
|
1091
1156
|
}
|
|
@@ -1117,10 +1182,19 @@ function createAuthParamsLoader(HlsClass, store, options = {}) {
|
|
|
1117
1182
|
);
|
|
1118
1183
|
const proxyUrl = proxyConfigured && !keyProxyState.circuitOpen ? rewriteHlsRequestUrl(originalUrl, store, options) : null;
|
|
1119
1184
|
const accessToken = options.getKeyAccessToken?.() ?? null;
|
|
1120
|
-
this.keyAbort
|
|
1185
|
+
if (this.keyAbort || this.pendingKeyCallbacks) {
|
|
1186
|
+
this.keyLoadGeneration += 1;
|
|
1187
|
+
this.finishKeyAbort();
|
|
1188
|
+
}
|
|
1189
|
+
const generation = ++this.keyLoadGeneration;
|
|
1121
1190
|
this.keyAbort = new AbortController();
|
|
1191
|
+
this.pendingKeyCallbacks = callbacks;
|
|
1192
|
+
context.responseType = "arraybuffer";
|
|
1122
1193
|
const timeoutMs = typeof config?.timeout === "number" && config.timeout > 0 ? config.timeout : 2e4;
|
|
1123
1194
|
const timer = setTimeout(() => this.keyAbort?.abort(), timeoutMs);
|
|
1195
|
+
const lifecycle = {
|
|
1196
|
+
isCurrent: () => generation === this.keyLoadGeneration
|
|
1197
|
+
};
|
|
1124
1198
|
void loadEncryptionKeyEnterprise(
|
|
1125
1199
|
context,
|
|
1126
1200
|
callbacks,
|
|
@@ -1131,13 +1205,22 @@ function createAuthParamsLoader(HlsClass, store, options = {}) {
|
|
|
1131
1205
|
maxProxyFailures
|
|
1132
1206
|
},
|
|
1133
1207
|
this.keyAbort.signal,
|
|
1134
|
-
keyProxyState
|
|
1208
|
+
keyProxyState,
|
|
1209
|
+
lifecycle
|
|
1135
1210
|
).then((result) => {
|
|
1211
|
+
if (!lifecycle.isCurrent()) return;
|
|
1212
|
+
if (result === "aborted") {
|
|
1213
|
+
this.finishKeyAbort();
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
this.pendingKeyCallbacks = null;
|
|
1136
1217
|
if (result === "try_xhr") {
|
|
1137
1218
|
context.url = signedKeyUrl;
|
|
1138
1219
|
this.inner.load(context, config, callbacks);
|
|
1139
1220
|
}
|
|
1140
1221
|
}).catch((error) => {
|
|
1222
|
+
if (!lifecycle.isCurrent()) return;
|
|
1223
|
+
this.pendingKeyCallbacks = null;
|
|
1141
1224
|
callbacks.onError(
|
|
1142
1225
|
{
|
|
1143
1226
|
code: 0,
|
|
@@ -1168,6 +1251,10 @@ function createAuthParamsLoader(HlsClass, store, options = {}) {
|
|
|
1168
1251
|
function isAuthHttpStatus(code) {
|
|
1169
1252
|
return code === 401 || code === 403;
|
|
1170
1253
|
}
|
|
1254
|
+
function isGeckoBrowser() {
|
|
1255
|
+
if (typeof navigator === "undefined") return false;
|
|
1256
|
+
return /firefox|gecko\//i.test(navigator.userAgent);
|
|
1257
|
+
}
|
|
1171
1258
|
var HlsEngine = class {
|
|
1172
1259
|
constructor(options) {
|
|
1173
1260
|
this.options = options;
|
|
@@ -1183,6 +1270,8 @@ var HlsEngine = class {
|
|
|
1183
1270
|
this.audioTracks = [];
|
|
1184
1271
|
this.audioTrackSelection = 0;
|
|
1185
1272
|
this.nativeTextTrackCleanup = null;
|
|
1273
|
+
this.startPositionApplied = false;
|
|
1274
|
+
this.autoplayApplied = false;
|
|
1186
1275
|
}
|
|
1187
1276
|
async start() {
|
|
1188
1277
|
const { videoEl, playbackUrl, startMuted, volume } = this.options;
|
|
@@ -1229,10 +1318,27 @@ var HlsEngine = class {
|
|
|
1229
1318
|
getKeyAccessToken,
|
|
1230
1319
|
keyProxyMaxFailures: this.options.keyProxyMaxFailures
|
|
1231
1320
|
});
|
|
1321
|
+
const enableWorker = hlsConfig.enableWorker && !isGeckoBrowser();
|
|
1322
|
+
if (hlsConfig.enableWorker && !enableWorker) {
|
|
1323
|
+
this.options.logger.warn(
|
|
1324
|
+
"Disabling hls.js worker on Firefox to avoid MSE bufferAppendError"
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1232
1327
|
this.hls = new Hls({
|
|
1233
|
-
enableWorker
|
|
1328
|
+
enableWorker,
|
|
1329
|
+
// LL mode on short encrypted VOD drives aggressive ABR thrash → MSE errors.
|
|
1234
1330
|
lowLatencyMode: hlsConfig.lowLatencyMode,
|
|
1235
1331
|
backBufferLength: hlsConfig.backBufferLength,
|
|
1332
|
+
// Stabilize ABR: avoid first-frag-only bandwidth test that immediately
|
|
1333
|
+
// hops levels on single-segment Appextech VODs (HAR showed truncated
|
|
1334
|
+
// 720p body vs Content-Length during level switches).
|
|
1335
|
+
testBandwidth: false,
|
|
1336
|
+
abrEwmaDefaultEstimate: 5e5,
|
|
1337
|
+
abrBandWidthFactor: 0.8,
|
|
1338
|
+
abrBandWidthUpFactor: 0.5,
|
|
1339
|
+
maxStarvationDelay: 4,
|
|
1340
|
+
capLevelToPlayerSize: true,
|
|
1341
|
+
startLevel: -1,
|
|
1236
1342
|
loader
|
|
1237
1343
|
});
|
|
1238
1344
|
this.hls.attachMedia(videoEl);
|
|
@@ -1250,7 +1356,11 @@ var HlsEngine = class {
|
|
|
1250
1356
|
this.syncHlsSubtitleTracks();
|
|
1251
1357
|
this.syncHlsAudioTracks();
|
|
1252
1358
|
callbacks.onStateChange?.("ready");
|
|
1253
|
-
void this.
|
|
1359
|
+
void this.applyAutoplayOnly();
|
|
1360
|
+
});
|
|
1361
|
+
this.hls.on(Hls.Events.FRAG_BUFFERED, () => {
|
|
1362
|
+
if (this.destroyed || this.startPositionApplied) return;
|
|
1363
|
+
void this.applyStartPositionOnly();
|
|
1254
1364
|
});
|
|
1255
1365
|
this.hls.on(Hls.Events.SUBTITLE_TRACKS_UPDATED, () => {
|
|
1256
1366
|
if (this.destroyed) return;
|
|
@@ -1275,8 +1385,16 @@ var HlsEngine = class {
|
|
|
1275
1385
|
callbacks.onAuthRecoveryNeeded?.("key_error");
|
|
1276
1386
|
if (!data.fatal) return;
|
|
1277
1387
|
} else if (!data.fatal) {
|
|
1388
|
+
if (!status) {
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1278
1391
|
callbacks.onFatalError?.(
|
|
1279
|
-
|
|
1392
|
+
`Encryption key load failed (HTTP ${status}).`
|
|
1393
|
+
);
|
|
1394
|
+
return;
|
|
1395
|
+
} else if (!status) {
|
|
1396
|
+
callbacks.onFatalError?.(
|
|
1397
|
+
"Encryption key load aborted or failed. Retry playback; if it persists, check the key proxy / CDN path."
|
|
1280
1398
|
);
|
|
1281
1399
|
return;
|
|
1282
1400
|
}
|
|
@@ -1301,30 +1419,67 @@ var HlsEngine = class {
|
|
|
1301
1419
|
}
|
|
1302
1420
|
if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
|
|
1303
1421
|
this.mediaRecoveryCount += 1;
|
|
1304
|
-
if (this.mediaRecoveryCount
|
|
1305
|
-
|
|
1422
|
+
if (this.mediaRecoveryCount > hlsConfig.fatalRecoveryLimit) {
|
|
1423
|
+
callbacks.onFatalError?.(data.details || "Fatal HLS error");
|
|
1306
1424
|
return;
|
|
1307
1425
|
}
|
|
1426
|
+
if (data.details === Hls.ErrorDetails.BUFFER_APPEND_ERROR || data.details === Hls.ErrorDetails.BUFFER_FULL_ERROR) {
|
|
1427
|
+
this.lockPlaybackToSafeLevel();
|
|
1428
|
+
}
|
|
1429
|
+
if (this.mediaRecoveryCount === 2) {
|
|
1430
|
+
this.hls?.swapAudioCodec();
|
|
1431
|
+
}
|
|
1432
|
+
this.hls?.recoverMediaError();
|
|
1433
|
+
return;
|
|
1308
1434
|
}
|
|
1309
1435
|
callbacks.onFatalError?.(data.details || "Fatal HLS error");
|
|
1310
1436
|
}
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1437
|
+
/** Pin the next load to the lowest ladder rung after MSE append failures. */
|
|
1438
|
+
lockPlaybackToSafeLevel() {
|
|
1439
|
+
if (!this.hls || this.qualities.length === 0) return;
|
|
1440
|
+
try {
|
|
1441
|
+
this.hls.currentLevel = 0;
|
|
1442
|
+
this.hls.loadLevel = 0;
|
|
1443
|
+
this.hls.nextLoadLevel = 0;
|
|
1444
|
+
} catch {
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
clampStartPosition(raw, videoEl) {
|
|
1448
|
+
let t = Math.max(0, raw);
|
|
1449
|
+
const duration = videoEl.duration;
|
|
1450
|
+
if (Number.isFinite(duration) && duration > 0) {
|
|
1451
|
+
t = Math.min(t, Math.max(0, duration - 0.35));
|
|
1452
|
+
}
|
|
1453
|
+
if (videoEl.seekable.length > 0) {
|
|
1454
|
+
const end = videoEl.seekable.end(videoEl.seekable.length - 1);
|
|
1455
|
+
if (Number.isFinite(end)) {
|
|
1456
|
+
t = Math.min(t, end);
|
|
1326
1457
|
}
|
|
1327
1458
|
}
|
|
1459
|
+
return t;
|
|
1460
|
+
}
|
|
1461
|
+
async applyStartPositionOnly() {
|
|
1462
|
+
if (this.destroyed || this.startPositionApplied) return;
|
|
1463
|
+
const { videoEl, startPosition } = this.options;
|
|
1464
|
+
this.startPositionApplied = true;
|
|
1465
|
+
if (typeof startPosition !== "number" || startPosition <= 0) return;
|
|
1466
|
+
const seek = () => {
|
|
1467
|
+
if (this.destroyed) return;
|
|
1468
|
+
try {
|
|
1469
|
+
videoEl.currentTime = this.clampStartPosition(startPosition, videoEl);
|
|
1470
|
+
} catch {
|
|
1471
|
+
}
|
|
1472
|
+
};
|
|
1473
|
+
if (videoEl.seekable.length > 0 || Number.isFinite(videoEl.duration)) {
|
|
1474
|
+
seek();
|
|
1475
|
+
} else {
|
|
1476
|
+
videoEl.addEventListener("loadedmetadata", seek, { once: true });
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
async applyAutoplayOnly() {
|
|
1480
|
+
if (this.destroyed || this.autoplayApplied) return;
|
|
1481
|
+
this.autoplayApplied = true;
|
|
1482
|
+
const { videoEl, autoplay } = this.options;
|
|
1328
1483
|
if (!autoplay) return;
|
|
1329
1484
|
try {
|
|
1330
1485
|
await videoEl.play();
|
|
@@ -1339,6 +1494,10 @@ var HlsEngine = class {
|
|
|
1339
1494
|
}
|
|
1340
1495
|
}
|
|
1341
1496
|
}
|
|
1497
|
+
async applyStartPositionAndAutoplay() {
|
|
1498
|
+
await this.applyStartPositionOnly();
|
|
1499
|
+
await this.applyAutoplayOnly();
|
|
1500
|
+
}
|
|
1342
1501
|
bindMediaEvents() {
|
|
1343
1502
|
const { videoEl, callbacks } = this.options;
|
|
1344
1503
|
videoEl.addEventListener(
|
|
@@ -1582,11 +1741,20 @@ function createVideoPlayer(options) {
|
|
|
1582
1741
|
}
|
|
1583
1742
|
events.emit("progress", { bufferedEnd });
|
|
1584
1743
|
};
|
|
1744
|
+
const onVolumeChange = () => {
|
|
1745
|
+
if (destroyed) return;
|
|
1746
|
+
events.emit("volumechange", {
|
|
1747
|
+
volume: el.volume,
|
|
1748
|
+
muted: el.muted
|
|
1749
|
+
});
|
|
1750
|
+
};
|
|
1585
1751
|
el.addEventListener("timeupdate", onTimeUpdate);
|
|
1586
1752
|
el.addEventListener("progress", onProgress);
|
|
1753
|
+
el.addEventListener("volumechange", onVolumeChange);
|
|
1587
1754
|
mediaCleanup = () => {
|
|
1588
1755
|
el.removeEventListener("timeupdate", onTimeUpdate);
|
|
1589
1756
|
el.removeEventListener("progress", onProgress);
|
|
1757
|
+
el.removeEventListener("volumechange", onVolumeChange);
|
|
1590
1758
|
};
|
|
1591
1759
|
};
|
|
1592
1760
|
const authManager = new AuthManager(
|
|
@@ -1639,6 +1807,9 @@ function createVideoPlayer(options) {
|
|
|
1639
1807
|
isLiveStream = Boolean(source.live);
|
|
1640
1808
|
}
|
|
1641
1809
|
const el = ensureVideoElement();
|
|
1810
|
+
if (isLiveStream) {
|
|
1811
|
+
el.playbackRate = 1;
|
|
1812
|
+
}
|
|
1642
1813
|
bindMediaProgressEvents(el);
|
|
1643
1814
|
engine?.destroy();
|
|
1644
1815
|
engine = new HlsEngine({
|
|
@@ -1727,6 +1898,12 @@ function createVideoPlayer(options) {
|
|
|
1727
1898
|
}
|
|
1728
1899
|
});
|
|
1729
1900
|
await engine.start();
|
|
1901
|
+
if (!destroyed && videoEl) {
|
|
1902
|
+
events.emit("volumechange", {
|
|
1903
|
+
volume: videoEl.volume,
|
|
1904
|
+
muted: videoEl.muted
|
|
1905
|
+
});
|
|
1906
|
+
}
|
|
1730
1907
|
if (!options.sourceUrl && options.playback) {
|
|
1731
1908
|
refreshManager?.stop();
|
|
1732
1909
|
refreshManager = new RefreshManager({
|
|
@@ -1846,7 +2023,8 @@ function createVideoPlayer(options) {
|
|
|
1846
2023
|
return videoEl?.muted ?? false;
|
|
1847
2024
|
},
|
|
1848
2025
|
setPlaybackRate(rate) {
|
|
1849
|
-
ensureVideoElement()
|
|
2026
|
+
const el = ensureVideoElement();
|
|
2027
|
+
el.playbackRate = isLiveStream ? 1 : rate;
|
|
1850
2028
|
},
|
|
1851
2029
|
getPlaybackRate() {
|
|
1852
2030
|
return videoEl?.playbackRate ?? 1;
|