@scarlett-player/hls 1.8.1 → 1.9.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/{chunk-VD46AQJB.js → chunk-EP22IZ63.js} +226 -46
- package/dist/index.cjs +226 -46
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/light.cjs +226 -46
- package/dist/light.d.cts +2 -2
- package/dist/light.d.ts +2 -2
- package/dist/light.js +1 -1
- package/dist/{sanitize-url-DcZXD_K-.d.cts → sanitize-url-BvhRz6uj.d.cts} +2 -0
- package/dist/{sanitize-url-DcZXD_K-.d.ts → sanitize-url-BvhRz6uj.d.ts} +2 -0
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -202,6 +202,24 @@ function parseHlsError(data) {
|
|
|
202
202
|
response: data.response
|
|
203
203
|
};
|
|
204
204
|
}
|
|
205
|
+
function audioTrackId(index) {
|
|
206
|
+
return `audio-${index}`;
|
|
207
|
+
}
|
|
208
|
+
function audioTrackIndex(id) {
|
|
209
|
+
if (!id) return -1;
|
|
210
|
+
const match = /^audio-(0|[1-9]\d*)$/.exec(id);
|
|
211
|
+
return match ? Number.parseInt(match[1], 10) : -1;
|
|
212
|
+
}
|
|
213
|
+
function formatAudioTrack(track, index, active) {
|
|
214
|
+
return {
|
|
215
|
+
id: audioTrackId(index),
|
|
216
|
+
// A manifest may declare neither NAME nor LANGUAGE; a numbered fallback
|
|
217
|
+
// still gives the viewer something selectable rather than a blank row.
|
|
218
|
+
label: track.name || track.lang || `Audio ${index + 1}`,
|
|
219
|
+
language: track.lang,
|
|
220
|
+
active
|
|
221
|
+
};
|
|
222
|
+
}
|
|
205
223
|
function setupHlsEventHandlers(hls, api, callbacks) {
|
|
206
224
|
const handlers = [];
|
|
207
225
|
const addHandler = (event, handler) => {
|
|
@@ -245,6 +263,24 @@ function setupHlsEventHandlers(hls, api, callbacks) {
|
|
|
245
263
|
});
|
|
246
264
|
callbacks.onLevelSwitched?.(data.level);
|
|
247
265
|
});
|
|
266
|
+
const publishAudioTracks = (tracks, activeIndex) => {
|
|
267
|
+
const audioTracks = tracks.map(
|
|
268
|
+
(track, index) => formatAudioTrack(track, index, index === activeIndex)
|
|
269
|
+
);
|
|
270
|
+
api.setState("audioTracks", audioTracks);
|
|
271
|
+
api.setState("currentAudioTrack", audioTracks[activeIndex] ?? null);
|
|
272
|
+
};
|
|
273
|
+
addHandler("hlsAudioTracksUpdated", (_event, data) => {
|
|
274
|
+
const tracks = data.audioTracks ?? [];
|
|
275
|
+
api.logger.debug("HLS audio tracks updated", { tracks: tracks.length });
|
|
276
|
+
publishAudioTracks(tracks, hls.audioTrack);
|
|
277
|
+
callbacks.onAudioTracksUpdated?.(tracks);
|
|
278
|
+
});
|
|
279
|
+
addHandler("hlsAudioTrackSwitched", (_event, data) => {
|
|
280
|
+
api.logger.debug("HLS audio track switched", { id: data.id });
|
|
281
|
+
publishAudioTracks(hls.audioTracks ?? [], data.id);
|
|
282
|
+
callbacks.onAudioTrackSwitched?.(data.id);
|
|
283
|
+
});
|
|
248
284
|
let lastBandwidthUpdate = 0;
|
|
249
285
|
addHandler("hlsFragLoaded", () => {
|
|
250
286
|
const now = Date.now();
|
|
@@ -265,16 +301,15 @@ function setupHlsEventHandlers(hls, api, callbacks) {
|
|
|
265
301
|
if (data.details?.live !== void 0) {
|
|
266
302
|
api.setState("live", data.details.live);
|
|
267
303
|
if (data.details.live) {
|
|
304
|
+
const details = data.details;
|
|
305
|
+
const start = details.fragmentStart ?? (details.fragments?.[0]?.start ?? 0);
|
|
306
|
+
const end = details.edge ?? details.totalduration ?? 0;
|
|
307
|
+
api.setState("seekableRange", { start, end });
|
|
268
308
|
const video = hls.media;
|
|
269
|
-
if (video
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
api.setState("
|
|
273
|
-
const threshold = (data.details.targetduration ?? 3) * 3;
|
|
274
|
-
const isAtLiveEdge = end - video.currentTime < threshold;
|
|
275
|
-
api.setState("liveEdge", isAtLiveEdge);
|
|
276
|
-
const latency = end - video.currentTime;
|
|
277
|
-
api.setState("liveLatency", Math.max(0, latency));
|
|
309
|
+
if (video) {
|
|
310
|
+
const latency = Math.max(0, end - video.currentTime);
|
|
311
|
+
api.setState("liveLatency", latency);
|
|
312
|
+
api.setState("liveEdge", latency < (details.targetduration ?? 3) * 3);
|
|
278
313
|
}
|
|
279
314
|
}
|
|
280
315
|
callbacks.onLiveUpdate?.();
|
|
@@ -309,6 +344,8 @@ function setupHlsEventHandlers(hls, api, callbacks) {
|
|
|
309
344
|
hls.off(event, handler);
|
|
310
345
|
}
|
|
311
346
|
handlers.length = 0;
|
|
347
|
+
api.setState("audioTracks", []);
|
|
348
|
+
api.setState("currentAudioTrack", null);
|
|
312
349
|
};
|
|
313
350
|
}
|
|
314
351
|
function setupVideoEventHandlers(video, api) {
|
|
@@ -348,19 +385,27 @@ function setupVideoEventHandlers(video, api) {
|
|
|
348
385
|
addHandler("timeupdate", () => {
|
|
349
386
|
api.setState("currentTime", video.currentTime);
|
|
350
387
|
api.emit("playback:timeupdate", { currentTime: video.currentTime });
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
388
|
+
if (video.seekable && video.seekable.length > 0) {
|
|
389
|
+
if (api.getState("live") || !Number.isFinite(video.duration)) {
|
|
390
|
+
const start = video.seekable.start(0);
|
|
391
|
+
const end = video.seekable.end(video.seekable.length - 1);
|
|
392
|
+
api.setState("seekableRange", { start, end });
|
|
393
|
+
const latency = Math.max(0, end - video.currentTime);
|
|
394
|
+
api.setState("liveEdge", latency < 10);
|
|
395
|
+
api.setState("liveLatency", latency);
|
|
396
|
+
}
|
|
359
397
|
}
|
|
360
398
|
});
|
|
361
399
|
addHandler("durationchange", () => {
|
|
362
|
-
|
|
363
|
-
|
|
400
|
+
const rawDuration = video.duration;
|
|
401
|
+
const isLive = !Number.isFinite(rawDuration) || rawDuration === Infinity;
|
|
402
|
+
if (isLive) {
|
|
403
|
+
api.setState("live", true);
|
|
404
|
+
api.setState("duration", 0);
|
|
405
|
+
} else {
|
|
406
|
+
api.setState("duration", rawDuration || 0);
|
|
407
|
+
}
|
|
408
|
+
api.emit("media:loadedmetadata", { duration: api.getState("duration") });
|
|
364
409
|
});
|
|
365
410
|
addHandler("waiting", () => {
|
|
366
411
|
api.setState("waiting", true);
|
|
@@ -507,7 +552,7 @@ function sanitizeUrl(url) {
|
|
|
507
552
|
}
|
|
508
553
|
|
|
509
554
|
// src/version.ts
|
|
510
|
-
var PKG_VERSION = true ? "1.
|
|
555
|
+
var PKG_VERSION = true ? "1.9.0" : "0.0.0-dev";
|
|
511
556
|
|
|
512
557
|
// src/create-hls-plugin.ts
|
|
513
558
|
var DEFAULT_CONFIG = {
|
|
@@ -567,6 +612,11 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
567
612
|
let onlineListener = null;
|
|
568
613
|
let reconnectTriggerError = null;
|
|
569
614
|
let reconnectExhausted = false;
|
|
615
|
+
let isReconnecting = false;
|
|
616
|
+
let isLiveClassified = false;
|
|
617
|
+
let stallWatchdogTimer = null;
|
|
618
|
+
let lastStallCheckTime = 0;
|
|
619
|
+
let lastStallCheckPosition = 0;
|
|
570
620
|
const applyPoster = () => {
|
|
571
621
|
if (!video) return;
|
|
572
622
|
video.poster = api?.getState("poster") || "";
|
|
@@ -598,6 +648,10 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
598
648
|
clearTimeout(retryTimeout);
|
|
599
649
|
retryTimeout = null;
|
|
600
650
|
}
|
|
651
|
+
if (stallWatchdogTimer) {
|
|
652
|
+
clearTimeout(stallWatchdogTimer);
|
|
653
|
+
stallWatchdogTimer = null;
|
|
654
|
+
}
|
|
601
655
|
if (hls) {
|
|
602
656
|
hls.destroy();
|
|
603
657
|
hls = null;
|
|
@@ -709,20 +763,20 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
709
763
|
const handleHlsError = (error) => {
|
|
710
764
|
const Hls = loader.getHlsConstructor();
|
|
711
765
|
if (!Hls || !hls) return false;
|
|
712
|
-
const now = Date.now();
|
|
713
|
-
if (now - errorWindowStart > ERROR_WINDOW_MS) {
|
|
714
|
-
errorCount = 1;
|
|
715
|
-
errorWindowStart = now;
|
|
716
|
-
} else {
|
|
717
|
-
errorCount++;
|
|
718
|
-
}
|
|
719
|
-
if (errorCount >= MAX_ERRORS_IN_WINDOW) {
|
|
720
|
-
api?.logger.error(`Too many errors (${errorCount} in ${ERROR_WINDOW_MS}ms), giving up`);
|
|
721
|
-
emitFatalError(error, true);
|
|
722
|
-
teardownPipeline(new Error(error.details));
|
|
723
|
-
return true;
|
|
724
|
-
}
|
|
725
766
|
if (error.fatal) {
|
|
767
|
+
const now = Date.now();
|
|
768
|
+
if (now - errorWindowStart > ERROR_WINDOW_MS) {
|
|
769
|
+
errorCount = 1;
|
|
770
|
+
errorWindowStart = now;
|
|
771
|
+
} else {
|
|
772
|
+
errorCount++;
|
|
773
|
+
}
|
|
774
|
+
if (errorCount >= MAX_ERRORS_IN_WINDOW) {
|
|
775
|
+
api?.logger.error(`Too many fatal errors (${errorCount} in ${ERROR_WINDOW_MS}ms), giving up`);
|
|
776
|
+
emitFatalError(error, true);
|
|
777
|
+
teardownPipeline(new Error(error.details));
|
|
778
|
+
return true;
|
|
779
|
+
}
|
|
726
780
|
api?.logger.error("Fatal HLS error", { type: error.type, details: error.details });
|
|
727
781
|
switch (error.type) {
|
|
728
782
|
case "network": {
|
|
@@ -1001,9 +1055,15 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
1001
1055
|
if (resolved || session !== loadSession) return;
|
|
1002
1056
|
resolved = true;
|
|
1003
1057
|
releaseAbort();
|
|
1004
|
-
api?.logger.error(`HLS load timed out after ${timeout_ms}ms`, { src });
|
|
1058
|
+
api?.logger.error(`HLS load timed out after ${timeout_ms}ms`, { src: sanitizeUrl(src) });
|
|
1059
|
+
const timeoutError = {
|
|
1060
|
+
type: "network",
|
|
1061
|
+
details: "Video took too long to load (network timeout)",
|
|
1062
|
+
fatal: true
|
|
1063
|
+
};
|
|
1064
|
+
emitFatalError(timeoutError, true);
|
|
1005
1065
|
teardownPipeline();
|
|
1006
|
-
reject(new Error(
|
|
1066
|
+
reject(new Error(timeoutError.details));
|
|
1007
1067
|
}, timeout_ms);
|
|
1008
1068
|
}
|
|
1009
1069
|
hls.attachMedia(videoEl);
|
|
@@ -1020,6 +1080,51 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
1020
1080
|
reconnectResumePosition = 0;
|
|
1021
1081
|
reconnectTriggerError = null;
|
|
1022
1082
|
reconnectExhausted = false;
|
|
1083
|
+
isReconnecting = false;
|
|
1084
|
+
};
|
|
1085
|
+
const startStallWatchdog = () => {
|
|
1086
|
+
if (!video || stallWatchdogTimer) return;
|
|
1087
|
+
lastStallCheckTime = Date.now();
|
|
1088
|
+
lastStallCheckPosition = video.currentTime;
|
|
1089
|
+
const levelTargetDuration = hls?.targetDuration ?? hls?.levels?.[hls?.currentLevel]?.details?.targetduration ?? 0;
|
|
1090
|
+
const targetDuration = Math.max(15, 4 * (levelTargetDuration || 0) || 15);
|
|
1091
|
+
const checkInterval = Math.min(targetDuration, 3e4);
|
|
1092
|
+
const check = () => {
|
|
1093
|
+
if (!video || !api) return;
|
|
1094
|
+
const isPlaying = api.getState("playing");
|
|
1095
|
+
const isSeeking = api.getState("seeking");
|
|
1096
|
+
if (!isPlaying || isSeeking) {
|
|
1097
|
+
stallWatchdogTimer = setTimeout(check, checkInterval);
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
const elapsed = Date.now() - lastStallCheckTime;
|
|
1101
|
+
const positionDelta = video.currentTime - lastStallCheckPosition;
|
|
1102
|
+
if (positionDelta > 0.5) {
|
|
1103
|
+
lastStallCheckTime = Date.now();
|
|
1104
|
+
lastStallCheckPosition = video.currentTime;
|
|
1105
|
+
} else if (elapsed > targetDuration * 1e3) {
|
|
1106
|
+
api.logger.warn("Playback stall detected \u2014 no timeupdate progress", {
|
|
1107
|
+
elapsed: Math.round(elapsed),
|
|
1108
|
+
currentTime: video.currentTime,
|
|
1109
|
+
targetDuration: Math.round(targetDuration)
|
|
1110
|
+
});
|
|
1111
|
+
const stallError = {
|
|
1112
|
+
type: "network",
|
|
1113
|
+
details: "Playback stalled \u2014 no data received",
|
|
1114
|
+
fatal: true
|
|
1115
|
+
};
|
|
1116
|
+
maybeScheduleReconnect(stallError);
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
stallWatchdogTimer = setTimeout(check, checkInterval);
|
|
1120
|
+
};
|
|
1121
|
+
stallWatchdogTimer = setTimeout(check, checkInterval);
|
|
1122
|
+
};
|
|
1123
|
+
const stopStallWatchdog = () => {
|
|
1124
|
+
if (stallWatchdogTimer) {
|
|
1125
|
+
clearTimeout(stallWatchdogTimer);
|
|
1126
|
+
stallWatchdogTimer = null;
|
|
1127
|
+
}
|
|
1023
1128
|
};
|
|
1024
1129
|
const emitReconnectExhausted = (elapsedMs, windowMs) => {
|
|
1025
1130
|
if (reconnectExhausted) return;
|
|
@@ -1045,17 +1150,30 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
1045
1150
|
const scheduleReconnectAttempt = () => {
|
|
1046
1151
|
if (reconnectExhausted) return;
|
|
1047
1152
|
if (reconnectTimer) return;
|
|
1048
|
-
const
|
|
1153
|
+
const isLive = (api?.getState("live") ?? false) && isLiveClassified;
|
|
1154
|
+
const configWindowMs = mergedConfig.reconnectWindowMs ?? 3e5;
|
|
1155
|
+
const window_ms = isLive ? Infinity : configWindowMs;
|
|
1049
1156
|
const elapsed_ms = Date.now() - reconnectWindowStart;
|
|
1050
1157
|
if (elapsed_ms > window_ms) {
|
|
1051
1158
|
api?.logger.warn(`Auto-reconnect window exhausted after ${reconnectAttempts} attempts`);
|
|
1052
1159
|
emitReconnectExhausted(elapsed_ms, window_ms);
|
|
1053
1160
|
return;
|
|
1054
1161
|
}
|
|
1162
|
+
const LONG_OUTAGE_MS = 6e5;
|
|
1163
|
+
if (isLive && elapsed_ms > LONG_OUTAGE_MS) {
|
|
1164
|
+
api?.emit("error:reconnecting", {
|
|
1165
|
+
attempt: reconnectAttempts + 1,
|
|
1166
|
+
delayMs: 0,
|
|
1167
|
+
elapsedMs: elapsed_ms,
|
|
1168
|
+
windowMs: window_ms,
|
|
1169
|
+
longOutage: true
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1055
1172
|
const base_delay = mergedConfig.reconnectBaseDelayMs ?? 2e3;
|
|
1056
1173
|
const max_delay = mergedConfig.reconnectMaxDelayMs ?? 3e4;
|
|
1057
1174
|
const backoff = Math.min(base_delay * Math.pow(2, reconnectAttempts), max_delay);
|
|
1058
|
-
const
|
|
1175
|
+
const isAtCap = backoff >= max_delay;
|
|
1176
|
+
const delay = isLive && isAtCap ? 3e4 : Math.round(backoff * (0.7 + Math.random() * 0.3));
|
|
1059
1177
|
api?.logger.info(`Scheduling auto-reconnect attempt ${reconnectAttempts + 1} in ${delay}ms`);
|
|
1060
1178
|
api?.emit("error:reconnecting", {
|
|
1061
1179
|
attempt: reconnectAttempts + 1,
|
|
@@ -1070,7 +1188,9 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
1070
1188
|
};
|
|
1071
1189
|
const maybeScheduleReconnect = (error) => {
|
|
1072
1190
|
if (mergedConfig.autoReconnect === false) return;
|
|
1073
|
-
if (!
|
|
1191
|
+
if (!currentSrc) return;
|
|
1192
|
+
const isLive = (api?.getState("live") ?? false) && isLiveClassified;
|
|
1193
|
+
if (!isLive && !hasPlayedContent) return;
|
|
1074
1194
|
if (error.type !== "network" && error.type !== "media") return;
|
|
1075
1195
|
if (reconnectWindowStart === 0) {
|
|
1076
1196
|
reconnectWindowStart = Date.now();
|
|
@@ -1081,13 +1201,14 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
1081
1201
|
};
|
|
1082
1202
|
const attemptReconnect = async () => {
|
|
1083
1203
|
if (!api || !currentSrc) return;
|
|
1204
|
+
isReconnecting = true;
|
|
1084
1205
|
const session = ++loadSession;
|
|
1085
1206
|
reconnectAttempts++;
|
|
1086
1207
|
const saved_src = currentSrc;
|
|
1087
1208
|
const was_live = api.getState("live");
|
|
1088
1209
|
const was_native = isNative;
|
|
1089
1210
|
const resume_position = reconnectResumePosition;
|
|
1090
|
-
api.logger.info(`Auto-reconnect attempt ${reconnectAttempts}`, { src: saved_src });
|
|
1211
|
+
api.logger.info(`Auto-reconnect attempt ${reconnectAttempts}`, { src: sanitizeUrl(saved_src) });
|
|
1091
1212
|
try {
|
|
1092
1213
|
teardownPipeline(new Error("HLS load cancelled: reconnecting"));
|
|
1093
1214
|
networkRetryCount = 0;
|
|
@@ -1113,14 +1234,21 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
1113
1234
|
});
|
|
1114
1235
|
api.logger.info("Auto-reconnect succeeded");
|
|
1115
1236
|
cancelReconnect();
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1237
|
+
const wasPaused = api.getState("paused");
|
|
1238
|
+
if (!wasPaused) {
|
|
1239
|
+
try {
|
|
1240
|
+
await video?.play();
|
|
1241
|
+
} catch {
|
|
1242
|
+
}
|
|
1243
|
+
} else {
|
|
1244
|
+
api.logger.info("Stream reconnected while paused; maintaining pause at live edge");
|
|
1119
1245
|
}
|
|
1120
1246
|
} catch {
|
|
1121
1247
|
if (session !== loadSession) return;
|
|
1122
1248
|
api?.logger.warn(`Auto-reconnect attempt ${reconnectAttempts} failed`);
|
|
1123
1249
|
scheduleReconnectAttempt();
|
|
1250
|
+
} finally {
|
|
1251
|
+
isReconnecting = false;
|
|
1124
1252
|
}
|
|
1125
1253
|
};
|
|
1126
1254
|
const plugin = {
|
|
@@ -1154,6 +1282,7 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
1154
1282
|
});
|
|
1155
1283
|
const unsubSeek = api.on("playback:seeking", ({ time }) => {
|
|
1156
1284
|
if (!video) return;
|
|
1285
|
+
if (!Number.isFinite(time)) return;
|
|
1157
1286
|
const clampedTime = Math.max(0, Math.min(time, video.duration || 0));
|
|
1158
1287
|
video.currentTime = clampedTime;
|
|
1159
1288
|
});
|
|
@@ -1206,20 +1335,43 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
1206
1335
|
}
|
|
1207
1336
|
}
|
|
1208
1337
|
});
|
|
1338
|
+
const unsubAudioTrack = api.on("track:audio", ({ trackId }) => {
|
|
1339
|
+
if (!hls || isNative) {
|
|
1340
|
+
api?.logger.warn("Audio track selection not available");
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
const index = audioTrackIndex(trackId);
|
|
1344
|
+
const tracks = hls.audioTracks ?? [];
|
|
1345
|
+
if (index < 0 || index >= tracks.length) {
|
|
1346
|
+
api?.logger.warn("Ignoring unknown audio track selection", { trackId });
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
hls.audioTrack = index;
|
|
1350
|
+
api?.logger.debug(`Audio: queued switch to track ${index}`);
|
|
1351
|
+
});
|
|
1209
1352
|
if (typeof window !== "undefined") {
|
|
1210
1353
|
onlineListener = () => {
|
|
1354
|
+
const hasActiveReconnect = reconnectTimer !== null || reconnectWindowStart > 0 || isReconnecting || reconnectAttempts > 0 && !reconnectExhausted;
|
|
1355
|
+
if (!hasActiveReconnect) {
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1211
1358
|
if (reconnectTimer) {
|
|
1212
1359
|
api?.logger.info("Browser back online, reconnecting immediately");
|
|
1213
1360
|
clearTimeout(reconnectTimer);
|
|
1214
1361
|
reconnectTimer = null;
|
|
1215
|
-
void attemptReconnect();
|
|
1216
1362
|
}
|
|
1363
|
+
void attemptReconnect();
|
|
1217
1364
|
};
|
|
1218
1365
|
window.addEventListener("online", onlineListener);
|
|
1219
1366
|
}
|
|
1220
1367
|
const unsubPoster = api.subscribeToState((event) => {
|
|
1221
1368
|
if (event.key === "poster") applyPoster();
|
|
1222
1369
|
});
|
|
1370
|
+
const unsubLive = api.subscribeToState((event) => {
|
|
1371
|
+
if (event.key === "live") {
|
|
1372
|
+
isLiveClassified = true;
|
|
1373
|
+
}
|
|
1374
|
+
});
|
|
1223
1375
|
api.onDestroy(() => {
|
|
1224
1376
|
unsubPlay();
|
|
1225
1377
|
unsubPause();
|
|
@@ -1228,8 +1380,20 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
1228
1380
|
unsubMute();
|
|
1229
1381
|
unsubRate();
|
|
1230
1382
|
unsubQuality();
|
|
1383
|
+
unsubAudioTrack();
|
|
1231
1384
|
unsubPoster();
|
|
1385
|
+
unsubLive();
|
|
1232
1386
|
});
|
|
1387
|
+
const unsubPlayState = api.subscribeToState((event) => {
|
|
1388
|
+
if (event.key === "playing") {
|
|
1389
|
+
if (event.value) {
|
|
1390
|
+
startStallWatchdog();
|
|
1391
|
+
} else {
|
|
1392
|
+
stopStallWatchdog();
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
});
|
|
1396
|
+
api.onDestroy(unsubPlayState);
|
|
1233
1397
|
},
|
|
1234
1398
|
async destroy() {
|
|
1235
1399
|
api?.logger.info(`HLS plugin${variant.logSuffix} destroying`);
|
|
@@ -1248,10 +1412,12 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
1248
1412
|
},
|
|
1249
1413
|
async loadSource(src) {
|
|
1250
1414
|
if (!api) throw new Error("Plugin not initialized");
|
|
1251
|
-
api.logger.info(`Loading HLS source${variant.logSuffix}`, { src });
|
|
1415
|
+
api.logger.info(`Loading HLS source${variant.logSuffix}`, { src: sanitizeUrl(src) });
|
|
1252
1416
|
const session = ++loadSession;
|
|
1253
1417
|
cancelReconnect();
|
|
1254
1418
|
hasPlayedContent = false;
|
|
1419
|
+
isLiveClassified = false;
|
|
1420
|
+
api.setState("live", false);
|
|
1255
1421
|
cleanup(new Error("HLS load cancelled: superseded by a new load"));
|
|
1256
1422
|
currentSrc = src;
|
|
1257
1423
|
applyPoster();
|
|
@@ -1301,14 +1467,24 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
1301
1467
|
return isNative;
|
|
1302
1468
|
},
|
|
1303
1469
|
getLiveInfo() {
|
|
1304
|
-
if (isNative || !hls) return null;
|
|
1305
1470
|
const live = api?.getState("live") || false;
|
|
1306
1471
|
if (!live) return null;
|
|
1472
|
+
if (isNative) {
|
|
1473
|
+
return {
|
|
1474
|
+
isLive: true,
|
|
1475
|
+
latency: 0,
|
|
1476
|
+
targetLatency: 3,
|
|
1477
|
+
drift: 0,
|
|
1478
|
+
liveSyncPosition: video?.seekable?.length ? Math.max(0, video.seekable.end(video.seekable.length - 1) - 3) : void 0
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
if (!hls) return null;
|
|
1307
1482
|
return {
|
|
1308
1483
|
isLive: true,
|
|
1309
1484
|
latency: hls.latency || 0,
|
|
1310
1485
|
targetLatency: hls.targetLatency || 3,
|
|
1311
|
-
drift: hls.drift || 0
|
|
1486
|
+
drift: hls.drift || 0,
|
|
1487
|
+
liveSyncPosition: hls.liveSyncPosition ?? (video?.seekable?.length ? Math.max(0, video.seekable.end(video.seekable.length - 1) - 3) : void 0)
|
|
1312
1488
|
};
|
|
1313
1489
|
},
|
|
1314
1490
|
/**
|
|
@@ -1334,7 +1510,9 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
1334
1510
|
const currentTime = video?.currentTime || 0;
|
|
1335
1511
|
const savedSrc = currentSrc;
|
|
1336
1512
|
const session = ++loadSession;
|
|
1513
|
+
cancelReconnect();
|
|
1337
1514
|
cleanup(new Error("HLS load cancelled: switching to native HLS"));
|
|
1515
|
+
currentSrc = savedSrc;
|
|
1338
1516
|
await loadNative(savedSrc);
|
|
1339
1517
|
if (session !== loadSession) return;
|
|
1340
1518
|
if (video && currentTime > 0) {
|
|
@@ -1371,7 +1549,9 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
1371
1549
|
const currentTime = video?.currentTime || 0;
|
|
1372
1550
|
const savedSrc = currentSrc;
|
|
1373
1551
|
const session = ++loadSession;
|
|
1552
|
+
cancelReconnect();
|
|
1374
1553
|
cleanup(new Error("HLS load cancelled: switching to hls.js"));
|
|
1554
|
+
currentSrc = savedSrc;
|
|
1375
1555
|
await loadWithHlsJs(savedSrc);
|
|
1376
1556
|
if (session !== loadSession) return;
|
|
1377
1557
|
if (video && currentTime > 0) {
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { H as HLSPluginConfig, I as IHLSPlugin } from './sanitize-url-
|
|
2
|
-
export { b as HLSError, c as HLSLiveInfo, a as HLSQualityLevel, s as sanitizeUrl } from './sanitize-url-
|
|
1
|
+
import { H as HLSPluginConfig, I as IHLSPlugin } from './sanitize-url-BvhRz6uj.cjs';
|
|
2
|
+
export { b as HLSError, c as HLSLiveInfo, a as HLSQualityLevel, s as sanitizeUrl } from './sanitize-url-BvhRz6uj.cjs';
|
|
3
3
|
import '@scarlett-player/core';
|
|
4
4
|
|
|
5
5
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { H as HLSPluginConfig, I as IHLSPlugin } from './sanitize-url-
|
|
2
|
-
export { b as HLSError, c as HLSLiveInfo, a as HLSQualityLevel, s as sanitizeUrl } from './sanitize-url-
|
|
1
|
+
import { H as HLSPluginConfig, I as IHLSPlugin } from './sanitize-url-BvhRz6uj.js';
|
|
2
|
+
export { b as HLSError, c as HLSLiveInfo, a as HLSQualityLevel, s as sanitizeUrl } from './sanitize-url-BvhRz6uj.js';
|
|
3
3
|
import '@scarlett-player/core';
|
|
4
4
|
|
|
5
5
|
/**
|