@scarlett-player/hls 1.0.2 → 1.2.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/README.md +59 -11
- package/dist/chunk-IFUZZQQE.js +1141 -0
- package/dist/index.cjs +396 -43
- package/dist/index.d.cts +6 -8
- package/dist/index.d.ts +6 -8
- package/dist/index.js +34 -486
- package/dist/light.cjs +432 -43
- package/dist/light.d.cts +11 -4
- package/dist/light.d.ts +11 -4
- package/dist/light.js +34 -461
- package/dist/{types-CRKmNxEW.d.cts → types-D-HW4lmM.d.cts} +27 -1
- package/dist/{types-CRKmNxEW.d.ts → types-D-HW4lmM.d.ts} +27 -1
- package/package.json +3 -3
- package/dist/chunk-MLOYPPFI.js +0 -336
package/dist/chunk-MLOYPPFI.js
DELETED
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
// src/quality.ts
|
|
2
|
-
function formatLevel(level) {
|
|
3
|
-
if (level.name) {
|
|
4
|
-
return level.name;
|
|
5
|
-
}
|
|
6
|
-
if (level.height) {
|
|
7
|
-
const standardLabels = {
|
|
8
|
-
2160: "4K",
|
|
9
|
-
1440: "1440p",
|
|
10
|
-
1080: "1080p",
|
|
11
|
-
720: "720p",
|
|
12
|
-
480: "480p",
|
|
13
|
-
360: "360p",
|
|
14
|
-
240: "240p",
|
|
15
|
-
144: "144p"
|
|
16
|
-
};
|
|
17
|
-
const closest = Object.keys(standardLabels).map(Number).sort((a, b) => Math.abs(a - level.height) - Math.abs(b - level.height))[0];
|
|
18
|
-
if (Math.abs(closest - level.height) <= 20) {
|
|
19
|
-
return standardLabels[closest];
|
|
20
|
-
}
|
|
21
|
-
return `${level.height}p`;
|
|
22
|
-
}
|
|
23
|
-
if (level.bitrate) {
|
|
24
|
-
return formatBitrate(level.bitrate);
|
|
25
|
-
}
|
|
26
|
-
return "Unknown";
|
|
27
|
-
}
|
|
28
|
-
function formatBitrate(bitrate) {
|
|
29
|
-
if (bitrate >= 1e6) {
|
|
30
|
-
return `${(bitrate / 1e6).toFixed(1)} Mbps`;
|
|
31
|
-
}
|
|
32
|
-
if (bitrate >= 1e3) {
|
|
33
|
-
return `${Math.round(bitrate / 1e3)} Kbps`;
|
|
34
|
-
}
|
|
35
|
-
return `${bitrate} bps`;
|
|
36
|
-
}
|
|
37
|
-
function mapLevels(levels, _currentLevel) {
|
|
38
|
-
return levels.map((level, index) => ({
|
|
39
|
-
index,
|
|
40
|
-
width: level.width || 0,
|
|
41
|
-
height: level.height || 0,
|
|
42
|
-
bitrate: level.bitrate || 0,
|
|
43
|
-
label: formatLevel(level),
|
|
44
|
-
codec: level.codecSet
|
|
45
|
-
}));
|
|
46
|
-
}
|
|
47
|
-
function getInitialBandwidthEstimate(overrideBps) {
|
|
48
|
-
const HLS_DEFAULT_ESTIMATE = 5e5;
|
|
49
|
-
if (overrideBps !== void 0 && overrideBps > 0) {
|
|
50
|
-
return overrideBps;
|
|
51
|
-
}
|
|
52
|
-
const connection = navigator.connection;
|
|
53
|
-
if (connection?.downlink && connection.downlink > 0) {
|
|
54
|
-
const bps = connection.downlink * 1e6;
|
|
55
|
-
return Math.round(bps * 0.85);
|
|
56
|
-
}
|
|
57
|
-
return HLS_DEFAULT_ESTIMATE;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// src/event-map.ts
|
|
61
|
-
var HLS_ERROR_TYPES = {
|
|
62
|
-
NETWORK_ERROR: "networkError",
|
|
63
|
-
MEDIA_ERROR: "mediaError",
|
|
64
|
-
KEY_SYSTEM_ERROR: "keySystemError",
|
|
65
|
-
MUX_ERROR: "muxError",
|
|
66
|
-
OTHER_ERROR: "otherError"
|
|
67
|
-
};
|
|
68
|
-
function mapErrorType(hlsType) {
|
|
69
|
-
switch (hlsType) {
|
|
70
|
-
case HLS_ERROR_TYPES.NETWORK_ERROR:
|
|
71
|
-
return "network";
|
|
72
|
-
case HLS_ERROR_TYPES.MEDIA_ERROR:
|
|
73
|
-
return "media";
|
|
74
|
-
case HLS_ERROR_TYPES.MUX_ERROR:
|
|
75
|
-
return "mux";
|
|
76
|
-
default:
|
|
77
|
-
return "other";
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
function parseHlsError(data) {
|
|
81
|
-
return {
|
|
82
|
-
type: mapErrorType(data.type),
|
|
83
|
-
details: data.details || "Unknown error",
|
|
84
|
-
fatal: data.fatal || false,
|
|
85
|
-
url: data.url,
|
|
86
|
-
reason: data.reason,
|
|
87
|
-
response: data.response
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
function setupHlsEventHandlers(hls, api, callbacks) {
|
|
91
|
-
const handlers = [];
|
|
92
|
-
const addHandler = (event, handler) => {
|
|
93
|
-
hls.on(event, handler);
|
|
94
|
-
handlers.push({ event, handler });
|
|
95
|
-
};
|
|
96
|
-
addHandler("hlsManifestParsed", (_event, data) => {
|
|
97
|
-
api.logger.debug("HLS manifest parsed", { levels: data.levels.length });
|
|
98
|
-
const levels = data.levels.map((level, index) => ({
|
|
99
|
-
id: `level-${index}`,
|
|
100
|
-
label: formatLevel(level),
|
|
101
|
-
width: level.width,
|
|
102
|
-
height: level.height,
|
|
103
|
-
bitrate: level.bitrate,
|
|
104
|
-
active: index === hls.currentLevel
|
|
105
|
-
}));
|
|
106
|
-
api.setState("qualities", levels);
|
|
107
|
-
api.emit("quality:levels", {
|
|
108
|
-
levels: levels.map((l) => ({ id: l.id, label: l.label }))
|
|
109
|
-
});
|
|
110
|
-
callbacks.onManifestParsed?.(data.levels);
|
|
111
|
-
});
|
|
112
|
-
addHandler("hlsLevelSwitched", (_event, data) => {
|
|
113
|
-
const level = hls.levels[data.level];
|
|
114
|
-
const isAuto = callbacks.getIsAutoQuality?.() ?? hls.autoLevelEnabled;
|
|
115
|
-
api.logger.debug("HLS level switched", { level: data.level, height: level?.height, auto: isAuto });
|
|
116
|
-
if (level) {
|
|
117
|
-
const label = isAuto ? `Auto (${formatLevel(level)})` : formatLevel(level);
|
|
118
|
-
api.setState("currentQuality", {
|
|
119
|
-
id: isAuto ? "auto" : `level-${data.level}`,
|
|
120
|
-
label,
|
|
121
|
-
width: level.width,
|
|
122
|
-
height: level.height,
|
|
123
|
-
bitrate: level.bitrate,
|
|
124
|
-
active: true
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
api.emit("quality:change", {
|
|
128
|
-
quality: level ? formatLevel(level) : "auto",
|
|
129
|
-
auto: isAuto
|
|
130
|
-
});
|
|
131
|
-
callbacks.onLevelSwitched?.(data.level);
|
|
132
|
-
});
|
|
133
|
-
let lastBandwidthUpdate = 0;
|
|
134
|
-
addHandler("hlsFragLoaded", () => {
|
|
135
|
-
const now = Date.now();
|
|
136
|
-
if (now - lastBandwidthUpdate >= 2e3 && hls.bandwidthEstimate) {
|
|
137
|
-
lastBandwidthUpdate = now;
|
|
138
|
-
api.setState("bandwidth", Math.round(hls.bandwidthEstimate));
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
addHandler("hlsFragBuffered", () => {
|
|
142
|
-
api.setState("buffering", false);
|
|
143
|
-
callbacks.onBufferUpdate?.();
|
|
144
|
-
});
|
|
145
|
-
addHandler("hlsFragLoading", () => {
|
|
146
|
-
api.setState("buffering", true);
|
|
147
|
-
});
|
|
148
|
-
addHandler("hlsLevelLoaded", (_event, data) => {
|
|
149
|
-
if (data.details?.live !== void 0) {
|
|
150
|
-
api.setState("live", data.details.live);
|
|
151
|
-
if (data.details.live) {
|
|
152
|
-
const video = hls.media;
|
|
153
|
-
if (video && video.seekable && video.seekable.length > 0) {
|
|
154
|
-
const start = video.seekable.start(0);
|
|
155
|
-
const end = video.seekable.end(video.seekable.length - 1);
|
|
156
|
-
api.setState("seekableRange", { start, end });
|
|
157
|
-
const threshold = (data.details.targetduration ?? 3) * 3;
|
|
158
|
-
const isAtLiveEdge = end - video.currentTime < threshold;
|
|
159
|
-
api.setState("liveEdge", isAtLiveEdge);
|
|
160
|
-
const latency = end - video.currentTime;
|
|
161
|
-
api.setState("liveLatency", Math.max(0, latency));
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
callbacks.onLiveUpdate?.();
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
addHandler("hlsError", (_event, data) => {
|
|
168
|
-
const error = parseHlsError(data);
|
|
169
|
-
const isBufferHoleSeek = !error.fatal && (error.details?.includes("bufferStalledError") || data.reason?.includes("buffer holes"));
|
|
170
|
-
if (isBufferHoleSeek) {
|
|
171
|
-
api.logger.debug(`HLS buffer recovery: ${error.reason || error.details}`, {
|
|
172
|
-
details: error.details,
|
|
173
|
-
reason: error.reason
|
|
174
|
-
});
|
|
175
|
-
} else if (error.fatal) {
|
|
176
|
-
api.logger.error(`HLS fatal error: ${error.details} (type=${error.type})`, {
|
|
177
|
-
type: error.type,
|
|
178
|
-
details: error.details,
|
|
179
|
-
url: error.url
|
|
180
|
-
});
|
|
181
|
-
} else {
|
|
182
|
-
api.logger.warn(`HLS error: ${error.details} (type=${error.type}, fatal=${error.fatal})`, {
|
|
183
|
-
type: error.type,
|
|
184
|
-
details: error.details,
|
|
185
|
-
fatal: error.fatal,
|
|
186
|
-
url: error.url
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
callbacks.onError?.(error);
|
|
190
|
-
});
|
|
191
|
-
return () => {
|
|
192
|
-
for (const { event, handler } of handlers) {
|
|
193
|
-
hls.off(event, handler);
|
|
194
|
-
}
|
|
195
|
-
handlers.length = 0;
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
function setupVideoEventHandlers(video, api) {
|
|
199
|
-
const handlers = [];
|
|
200
|
-
const addHandler = (event, handler) => {
|
|
201
|
-
video.addEventListener(event, handler);
|
|
202
|
-
handlers.push({ event, handler });
|
|
203
|
-
};
|
|
204
|
-
addHandler("play", () => {
|
|
205
|
-
api.setState("paused", false);
|
|
206
|
-
});
|
|
207
|
-
addHandler("playing", () => {
|
|
208
|
-
api.setState("playing", true);
|
|
209
|
-
api.setState("paused", false);
|
|
210
|
-
api.setState("waiting", false);
|
|
211
|
-
api.setState("buffering", false);
|
|
212
|
-
api.setState("playbackState", "playing");
|
|
213
|
-
});
|
|
214
|
-
addHandler("pause", () => {
|
|
215
|
-
api.setState("playing", false);
|
|
216
|
-
api.setState("paused", true);
|
|
217
|
-
api.setState("playbackState", "paused");
|
|
218
|
-
});
|
|
219
|
-
addHandler("ended", () => {
|
|
220
|
-
api.setState("playing", false);
|
|
221
|
-
api.setState("ended", true);
|
|
222
|
-
api.setState("playbackState", "ended");
|
|
223
|
-
api.emit("playback:ended", void 0);
|
|
224
|
-
});
|
|
225
|
-
addHandler("timeupdate", () => {
|
|
226
|
-
api.setState("currentTime", video.currentTime);
|
|
227
|
-
api.emit("playback:timeupdate", { currentTime: video.currentTime });
|
|
228
|
-
const isLive = api.getState("live");
|
|
229
|
-
if (isLive && video.seekable && video.seekable.length > 0) {
|
|
230
|
-
const start = video.seekable.start(0);
|
|
231
|
-
const end = video.seekable.end(video.seekable.length - 1);
|
|
232
|
-
api.setState("seekableRange", { start, end });
|
|
233
|
-
const isAtLiveEdge = end - video.currentTime < 10;
|
|
234
|
-
api.setState("liveEdge", isAtLiveEdge);
|
|
235
|
-
api.setState("liveLatency", Math.max(0, end - video.currentTime));
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
addHandler("durationchange", () => {
|
|
239
|
-
api.setState("duration", video.duration || 0);
|
|
240
|
-
api.emit("media:loadedmetadata", { duration: video.duration || 0 });
|
|
241
|
-
});
|
|
242
|
-
addHandler("waiting", () => {
|
|
243
|
-
api.setState("waiting", true);
|
|
244
|
-
api.setState("buffering", true);
|
|
245
|
-
api.emit("media:waiting", void 0);
|
|
246
|
-
});
|
|
247
|
-
addHandler("canplay", () => {
|
|
248
|
-
api.setState("waiting", false);
|
|
249
|
-
api.setState("playbackState", "ready");
|
|
250
|
-
api.emit("media:canplay", void 0);
|
|
251
|
-
});
|
|
252
|
-
addHandler("canplaythrough", () => {
|
|
253
|
-
api.setState("buffering", false);
|
|
254
|
-
api.emit("media:canplaythrough", void 0);
|
|
255
|
-
});
|
|
256
|
-
addHandler("progress", () => {
|
|
257
|
-
if (video.buffered.length > 0) {
|
|
258
|
-
const bufferedEnd = video.buffered.end(video.buffered.length - 1);
|
|
259
|
-
const bufferedAmount = video.duration > 0 ? bufferedEnd / video.duration : 0;
|
|
260
|
-
api.setState("bufferedAmount", bufferedAmount);
|
|
261
|
-
api.setState("buffered", video.buffered);
|
|
262
|
-
api.emit("media:progress", { buffered: bufferedAmount });
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
addHandler("seeking", () => {
|
|
266
|
-
api.setState("seeking", true);
|
|
267
|
-
});
|
|
268
|
-
addHandler("seeked", () => {
|
|
269
|
-
api.setState("seeking", false);
|
|
270
|
-
api.emit("playback:seeked", { time: video.currentTime });
|
|
271
|
-
});
|
|
272
|
-
addHandler("volumechange", () => {
|
|
273
|
-
api.setState("volume", video.volume);
|
|
274
|
-
api.setState("muted", video.muted);
|
|
275
|
-
api.emit("volume:change", { volume: video.volume, muted: video.muted });
|
|
276
|
-
});
|
|
277
|
-
addHandler("ratechange", () => {
|
|
278
|
-
api.setState("playbackRate", video.playbackRate);
|
|
279
|
-
api.emit("playback:ratechange", { rate: video.playbackRate });
|
|
280
|
-
});
|
|
281
|
-
addHandler("loadedmetadata", () => {
|
|
282
|
-
api.setState("duration", video.duration);
|
|
283
|
-
api.setState("mediaType", video.videoWidth > 0 ? "video" : "audio");
|
|
284
|
-
});
|
|
285
|
-
addHandler("loadeddata", () => {
|
|
286
|
-
if (video.videoWidth > 0) {
|
|
287
|
-
api.setState("mediaType", "video");
|
|
288
|
-
}
|
|
289
|
-
});
|
|
290
|
-
addHandler("error", () => {
|
|
291
|
-
const error = video.error;
|
|
292
|
-
if (error) {
|
|
293
|
-
api.logger.error("Video element error", { code: error.code, message: error.message });
|
|
294
|
-
api.emit("media:error", { error: new Error(error.message || "Video playback error") });
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
addHandler("enterpictureinpicture", () => {
|
|
298
|
-
api.setState("pip", true);
|
|
299
|
-
api.logger.debug("PiP: entered (standard)");
|
|
300
|
-
});
|
|
301
|
-
addHandler("leavepictureinpicture", () => {
|
|
302
|
-
api.setState("pip", false);
|
|
303
|
-
api.logger.debug("PiP: exited (standard)");
|
|
304
|
-
if (!video.paused || api.getState("playing")) {
|
|
305
|
-
video.play().catch(() => {
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
const webkitVideo = video;
|
|
310
|
-
if ("webkitPresentationMode" in video) {
|
|
311
|
-
addHandler("webkitpresentationmodechanged", () => {
|
|
312
|
-
const mode = webkitVideo.webkitPresentationMode;
|
|
313
|
-
const isInPip = mode === "picture-in-picture";
|
|
314
|
-
api.setState("pip", isInPip);
|
|
315
|
-
api.logger.debug(`PiP: mode changed to ${mode} (webkit)`);
|
|
316
|
-
if (mode === "inline" && video.paused) {
|
|
317
|
-
video.play().catch(() => {
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
return () => {
|
|
323
|
-
for (const { event, handler } of handlers) {
|
|
324
|
-
video.removeEventListener(event, handler);
|
|
325
|
-
}
|
|
326
|
-
handlers.length = 0;
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
export {
|
|
331
|
-
formatLevel,
|
|
332
|
-
mapLevels,
|
|
333
|
-
getInitialBandwidthEstimate,
|
|
334
|
-
setupHlsEventHandlers,
|
|
335
|
-
setupVideoEventHandlers
|
|
336
|
-
};
|