@scarlett-player/embed 0.5.2 → 0.5.3

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/embed.js CHANGED
@@ -123,12 +123,44 @@ function setupHlsEventHandlers(hls, api, callbacks) {
123
123
  addHandler("hlsLevelLoaded", (_event, data) => {
124
124
  if (data.details?.live !== void 0) {
125
125
  api.setState("live", data.details.live);
126
+ if (data.details.live) {
127
+ const video = hls.media;
128
+ if (video && video.seekable && video.seekable.length > 0) {
129
+ const start = video.seekable.start(0);
130
+ const end = video.seekable.end(video.seekable.length - 1);
131
+ api.setState("seekableRange", { start, end });
132
+ const threshold = (data.details.targetduration ?? 3) * 3;
133
+ const isAtLiveEdge = end - video.currentTime < threshold;
134
+ api.setState("liveEdge", isAtLiveEdge);
135
+ const latency = end - video.currentTime;
136
+ api.setState("liveLatency", Math.max(0, latency));
137
+ }
138
+ }
126
139
  callbacks.onLiveUpdate?.();
127
140
  }
128
141
  });
129
142
  addHandler("hlsError", (_event, data) => {
130
143
  const error = parseHlsError(data);
131
- api.logger.warn("HLS error", { error });
144
+ const isBufferHoleSeek = !error.fatal && (error.details?.includes("bufferStalledError") || data.reason?.includes("buffer holes"));
145
+ if (isBufferHoleSeek) {
146
+ api.logger.debug(`HLS buffer recovery: ${error.reason || error.details}`, {
147
+ details: error.details,
148
+ reason: error.reason
149
+ });
150
+ } else if (error.fatal) {
151
+ api.logger.error(`HLS fatal error: ${error.details} (type=${error.type})`, {
152
+ type: error.type,
153
+ details: error.details,
154
+ url: error.url
155
+ });
156
+ } else {
157
+ api.logger.warn(`HLS error: ${error.details} (type=${error.type}, fatal=${error.fatal})`, {
158
+ type: error.type,
159
+ details: error.details,
160
+ fatal: error.fatal,
161
+ url: error.url
162
+ });
163
+ }
132
164
  callbacks.onError?.(error);
133
165
  });
134
166
  return () => {
@@ -150,6 +182,8 @@ function setupVideoEventHandlers(video, api) {
150
182
  addHandler("playing", () => {
151
183
  api.setState("playing", true);
152
184
  api.setState("paused", false);
185
+ api.setState("waiting", false);
186
+ api.setState("buffering", false);
153
187
  api.setState("playbackState", "playing");
154
188
  });
155
189
  addHandler("pause", () => {
@@ -166,6 +200,15 @@ function setupVideoEventHandlers(video, api) {
166
200
  addHandler("timeupdate", () => {
167
201
  api.setState("currentTime", video.currentTime);
168
202
  api.emit("playback:timeupdate", { currentTime: video.currentTime });
203
+ const isLive = api.getState("live");
204
+ if (isLive && video.seekable && video.seekable.length > 0) {
205
+ const start = video.seekable.start(0);
206
+ const end = video.seekable.end(video.seekable.length - 1);
207
+ api.setState("seekableRange", { start, end });
208
+ const isAtLiveEdge = end - video.currentTime < 10;
209
+ api.setState("liveEdge", isAtLiveEdge);
210
+ api.setState("liveLatency", Math.max(0, end - video.currentTime));
211
+ }
169
212
  });
170
213
  addHandler("durationchange", () => {
171
214
  api.setState("duration", video.duration || 0);
@@ -4694,6 +4737,9 @@ function createAnalyticsPlugin(config) {
4694
4737
  fatal: error.fatal ?? false
4695
4738
  };
4696
4739
  session.errors.push(errorEvent);
4740
+ if (session.errors.length > 100) {
4741
+ session.errors = session.errors.slice(-100);
4742
+ }
4697
4743
  sendBeacon("error", {
4698
4744
  errorType: errorEvent.type,
4699
4745
  errorMessage: errorEvent.message,
@@ -4720,6 +4766,9 @@ function createAnalyticsPlugin(config) {
4720
4766
  height: currentQuality.height
4721
4767
  };
4722
4768
  session.bitrateHistory.push(bitrateChange);
4769
+ if (session.bitrateHistory.length > 500) {
4770
+ session.bitrateHistory = session.bitrateHistory.slice(-500);
4771
+ }
4723
4772
  if (currentQuality.bitrate > session.maxBitrate) {
4724
4773
  session.maxBitrate = currentQuality.bitrate;
4725
4774
  }
@@ -7188,8 +7237,9 @@ class ScarlettPlayer {
7188
7237
  */
7189
7238
  setPlaybackRate(rate) {
7190
7239
  this.checkDestroyed();
7191
- this.stateManager.set("playbackRate", rate);
7192
- this.eventBus.emit("playback:ratechange", { rate });
7240
+ const clampedRate = Math.max(0.0625, Math.min(16, rate));
7241
+ this.stateManager.set("playbackRate", clampedRate);
7242
+ this.eventBus.emit("playback:ratechange", { rate: clampedRate });
7193
7243
  }
7194
7244
  /**
7195
7245
  * Set autoplay state.
@@ -7318,6 +7368,13 @@ class ScarlettPlayer {
7318
7368
  }
7319
7369
  const provider = this._currentProvider;
7320
7370
  if (typeof provider.setLevel === "function") {
7371
+ if (index !== -1) {
7372
+ const levels = this.getQualities();
7373
+ if (levels.length > 0 && (index < 0 || index >= levels.length)) {
7374
+ this.logger.warn(`Invalid quality index: ${index} (available: ${levels.length})`);
7375
+ return;
7376
+ }
7377
+ }
7321
7378
  provider.setLevel(index);
7322
7379
  this.eventBus.emit("quality:change", {
7323
7380
  quality: index === -1 ? "auto" : `level-${index}`,
@@ -7558,18 +7615,40 @@ class ScarlettPlayer {
7558
7615
  * @private
7559
7616
  */
7560
7617
  detectMimeType(source) {
7561
- const ext = source.split(".").pop()?.toLowerCase();
7618
+ let path = source;
7619
+ try {
7620
+ path = new URL(source).pathname;
7621
+ } catch {
7622
+ const noQuery = source.split("?")[0] ?? source;
7623
+ path = noQuery.split("#")[0] ?? noQuery;
7624
+ }
7625
+ const ext = path.split(".").pop()?.toLowerCase() ?? "";
7562
7626
  switch (ext) {
7563
7627
  case "m3u8":
7564
7628
  return "application/x-mpegURL";
7565
7629
  case "mpd":
7566
7630
  return "application/dash+xml";
7567
7631
  case "mp4":
7632
+ case "m4v":
7568
7633
  return "video/mp4";
7569
7634
  case "webm":
7570
7635
  return "video/webm";
7571
7636
  case "ogg":
7637
+ case "ogv":
7572
7638
  return "video/ogg";
7639
+ case "mov":
7640
+ return "video/quicktime";
7641
+ case "mkv":
7642
+ return "video/x-matroska";
7643
+ case "mp3":
7644
+ return "audio/mpeg";
7645
+ case "wav":
7646
+ return "audio/wav";
7647
+ case "flac":
7648
+ return "audio/flac";
7649
+ case "aac":
7650
+ case "m4a":
7651
+ return "audio/mp4";
7573
7652
  default:
7574
7653
  return "video/mp4";
7575
7654
  }