@scarlett-player/embed 1.8.0 → 1.8.1

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
@@ -2895,7 +2895,7 @@ function createValidatingPlaylistLoader(Hls) {
2895
2895
  }
2896
2896
  };
2897
2897
  }
2898
- var PKG_VERSION$a = "1.7.1";
2898
+ var PKG_VERSION$b = "1.8.1";
2899
2899
  var DEFAULT_CONFIG$4 = {
2900
2900
  debug: false,
2901
2901
  autoStartLoad: true,
@@ -3228,7 +3228,7 @@ function createHLSPluginWith(loader, variant, config) {
3228
3228
  if (api) {
3229
3229
  cleanupVideoEvents = setupVideoEventHandlers(videoEl, api);
3230
3230
  }
3231
- return new Promise((resolve, reject) => {
3231
+ return new Promise((resolve2, reject) => {
3232
3232
  let watchdog = null;
3233
3233
  let settled = false;
3234
3234
  const settle = () => {
@@ -3287,7 +3287,7 @@ function createHLSPluginWith(loader, variant, config) {
3287
3287
  };
3288
3288
  api?.setState("source", { src, type: "application/x-mpegURL" });
3289
3289
  api?.emit("media:loaded", { src, type: "application/x-mpegURL" });
3290
- resolve();
3290
+ resolve2();
3291
3291
  };
3292
3292
  const onError = () => {
3293
3293
  if (settled) return;
@@ -3321,7 +3321,7 @@ function createHLSPluginWith(loader, variant, config) {
3321
3321
  if (api) {
3322
3322
  cleanupVideoEvents = setupVideoEventHandlers(videoEl, api);
3323
3323
  }
3324
- return new Promise((resolve, reject) => {
3324
+ return new Promise((resolve2, reject) => {
3325
3325
  if (!hls || !api) {
3326
3326
  reject(new Error("HLS not initialized"));
3327
3327
  return;
@@ -3356,7 +3356,7 @@ function createHLSPluginWith(loader, variant, config) {
3356
3356
  hasPlayedContent = true;
3357
3357
  api?.setState("source", { src, type: "application/x-mpegURL" });
3358
3358
  api?.emit("media:loaded", { src, type: "application/x-mpegURL" });
3359
- resolve();
3359
+ resolve2();
3360
3360
  }
3361
3361
  },
3362
3362
  onLevelSwitched: () => {
@@ -3512,7 +3512,7 @@ function createHLSPluginWith(loader, variant, config) {
3512
3512
  const plugin = {
3513
3513
  id: "hls-provider",
3514
3514
  name: variant.name,
3515
- version: PKG_VERSION$a,
3515
+ version: PKG_VERSION$b,
3516
3516
  type: "provider",
3517
3517
  description: variant.description,
3518
3518
  canPlay(src) {
@@ -3859,7 +3859,7 @@ function createHLSPlugin(config) {
3859
3859
  config
3860
3860
  );
3861
3861
  }
3862
- var PKG_VERSION$9 = "1.7.1";
3862
+ var PKG_VERSION$a = "1.8.1";
3863
3863
  var VIDEO_EXTENSIONS = ["mp4", "webm", "mov", "mkv", "ogv", "m4v"];
3864
3864
  var AUDIO_EXTENSIONS = ["mp3", "wav", "ogg", "flac", "aac", "m4a", "opus", "weba"];
3865
3865
  var SUPPORTED_EXTENSIONS = [...VIDEO_EXTENSIONS, ...AUDIO_EXTENSIONS];
@@ -4098,7 +4098,7 @@ function createNativePlugin(config) {
4098
4098
  const plugin = {
4099
4099
  id: "native-provider",
4100
4100
  name: "Native Media Provider",
4101
- version: PKG_VERSION$9,
4101
+ version: PKG_VERSION$a,
4102
4102
  type: "provider",
4103
4103
  description: "Native HTML5 playback for video (MP4, WebM, MOV) and audio (MP3, WAV, FLAC, AAC)",
4104
4104
  canPlay(src) {
@@ -4196,7 +4196,7 @@ function createNativePlugin(config) {
4196
4196
  videoEl.style.display = isAudio ? "none" : "block";
4197
4197
  applyPoster();
4198
4198
  cleanupEvents = setupEventListeners(videoEl);
4199
- return new Promise((resolve, reject) => {
4199
+ return new Promise((resolve2, reject) => {
4200
4200
  let watchdog = null;
4201
4201
  const settle = () => {
4202
4202
  videoEl.removeEventListener("loadedmetadata", onLoaded);
@@ -4216,7 +4216,7 @@ function createNativePlugin(config) {
4216
4216
  api?.setState("playbackState", "ready");
4217
4217
  api?.setState("buffering", false);
4218
4218
  api?.emit("media:loaded", { src, type: mimeType });
4219
- resolve();
4219
+ resolve2();
4220
4220
  };
4221
4221
  const onError = () => {
4222
4222
  settle();
@@ -4314,7 +4314,7 @@ function planFit(items, available, gap, overflowButtonWidth) {
4314
4314
  hidden: ids.filter((id) => hidden.has(id))
4315
4315
  };
4316
4316
  }
4317
- var styles$2 = `
4317
+ var styles$3 = `
4318
4318
  /* ============================================
4319
4319
  Container & Base
4320
4320
  ============================================ */
@@ -5403,7 +5403,7 @@ var styles$2 = `
5403
5403
  --sp-icon-size: 24px;
5404
5404
  }
5405
5405
  `;
5406
- var icons = {
5406
+ var icons$1 = {
5407
5407
  play: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>`,
5408
5408
  pause: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/></svg>`,
5409
5409
  replay: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/></svg>`,
@@ -5496,7 +5496,7 @@ var PlayButton = class {
5496
5496
  this.toggle();
5497
5497
  };
5498
5498
  this.api = api;
5499
- this.el = createButton("sp-play", "Play", icons.play);
5499
+ this.el = createButton("sp-play", "Play", icons$1.play);
5500
5500
  this.el.addEventListener("click", this.clickHandler);
5501
5501
  }
5502
5502
  render() {
@@ -5508,13 +5508,13 @@ var PlayButton = class {
5508
5508
  let icon;
5509
5509
  let label;
5510
5510
  if (ended) {
5511
- icon = icons.replay;
5511
+ icon = icons$1.replay;
5512
5512
  label = "Replay";
5513
5513
  } else if (playing) {
5514
- icon = icons.pause;
5514
+ icon = icons$1.pause;
5515
5515
  label = "Pause";
5516
5516
  } else {
5517
- icon = icons.play;
5517
+ icon = icons$1.play;
5518
5518
  label = "Play";
5519
5519
  }
5520
5520
  setHTML(this.el, icon);
@@ -5558,7 +5558,7 @@ var BigPlayButton = class {
5558
5558
  btn.className = "sp-big-play";
5559
5559
  btn.setAttribute("type", "button");
5560
5560
  btn.setAttribute("aria-label", "Play");
5561
- setHTML(btn, icons.play);
5561
+ setHTML(btn, icons$1.play);
5562
5562
  btn.addEventListener("click", this.clickHandler);
5563
5563
  this.el = btn;
5564
5564
  }
@@ -5595,7 +5595,7 @@ var BigPlayButton = class {
5595
5595
  } else {
5596
5596
  visible = playbackState === "idle" || playbackState === "ready";
5597
5597
  }
5598
- setHTML(this.el, ended ? icons.replay : icons.play);
5598
+ setHTML(this.el, ended ? icons$1.replay : icons$1.play);
5599
5599
  setAttr(this.el, "aria-label", ended ? "Replay" : "Play");
5600
5600
  this.el.classList.toggle("sp-big-play--visible", visible);
5601
5601
  }
@@ -6143,7 +6143,7 @@ var VolumeControl = class {
6143
6143
  "aria-label": "Mute",
6144
6144
  type: "button"
6145
6145
  });
6146
- this.btn.innerHTML = icons.volumeHigh;
6146
+ this.btn.innerHTML = icons$1.volumeHigh;
6147
6147
  this.btn.onclick = () => this.toggleMute();
6148
6148
  const sliderWrap = createElement("div", { className: "sp-volume__slider-wrap" });
6149
6149
  this.slider = createElement("div", { className: "sp-volume__slider" });
@@ -6175,13 +6175,13 @@ var VolumeControl = class {
6175
6175
  let icon;
6176
6176
  let label;
6177
6177
  if (muted || volume === 0) {
6178
- icon = icons.volumeMute;
6178
+ icon = icons$1.volumeMute;
6179
6179
  label = "Unmute";
6180
6180
  } else if (volume < 0.5) {
6181
- icon = icons.volumeLow;
6181
+ icon = icons$1.volumeLow;
6182
6182
  label = "Mute";
6183
6183
  } else {
6184
- icon = icons.volumeHigh;
6184
+ icon = icons$1.volumeHigh;
6185
6185
  label = "Mute";
6186
6186
  }
6187
6187
  setHTML(this.btn, icon);
@@ -6288,7 +6288,7 @@ var QualityMenu = class {
6288
6288
  this.lastQualitiesJson = "";
6289
6289
  this.api = api;
6290
6290
  this.el = createElement("div", { className: "sp-quality" });
6291
- this.btn = createButton("sp-quality__btn", "Quality", icons.settings);
6291
+ this.btn = createButton("sp-quality__btn", "Quality", icons$1.settings);
6292
6292
  this.btnLabel = createElement("span", { className: "sp-quality__label" });
6293
6293
  this.btnLabel.textContent = "Auto";
6294
6294
  this.btn.appendChild(this.btnLabel);
@@ -6400,7 +6400,7 @@ var CastButton = class {
6400
6400
  this.api = api;
6401
6401
  this.type = type;
6402
6402
  this.supported = type === "chromecast" ? isChromecastSupported() : isAirPlaySupported();
6403
- const icon = type === "chromecast" ? icons.chromecast : icons.airplay;
6403
+ const icon = type === "chromecast" ? icons$1.chromecast : icons$1.airplay;
6404
6404
  const label = type === "chromecast" ? "Cast" : "AirPlay";
6405
6405
  this.el = createButton(`sp-cast sp-cast--${type}`, label, icon);
6406
6406
  this.el.addEventListener("click", () => this.handleClick());
@@ -6424,10 +6424,10 @@ var CastButton = class {
6424
6424
  this.el.classList.toggle("sp-cast--active", !!active);
6425
6425
  this.el.classList.toggle("sp-cast--unavailable", !available && !active);
6426
6426
  if (active) {
6427
- setHTML(this.el, icons.chromecastConnected);
6427
+ setHTML(this.el, icons$1.chromecastConnected);
6428
6428
  setAttr(this.el, "aria-label", "Stop casting");
6429
6429
  } else {
6430
- setHTML(this.el, icons.chromecast);
6430
+ setHTML(this.el, icons$1.chromecast);
6431
6431
  setAttr(this.el, "aria-label", available ? "Cast" : "No Cast devices found");
6432
6432
  }
6433
6433
  } else {
@@ -6478,7 +6478,7 @@ var PipButton = class {
6478
6478
  this.api = api;
6479
6479
  const probe = document.createElement("video");
6480
6480
  this.supported = "pictureInPictureEnabled" in document || "webkitSetPresentationMode" in probe;
6481
- this.el = createButton("sp-pip", "Picture-in-Picture", icons.pip);
6481
+ this.el = createButton("sp-pip", "Picture-in-Picture", icons$1.pip);
6482
6482
  this.el.addEventListener("click", this.clickHandler);
6483
6483
  if (!this.supported) {
6484
6484
  this.el.style.display = "none";
@@ -6501,7 +6501,7 @@ var PipButton = class {
6501
6501
  const enabled = pip || this.isMediaReady();
6502
6502
  this.el.disabled = !enabled;
6503
6503
  setAttr(this.el, "aria-disabled", String(!enabled));
6504
- setHTML(this.el, pip ? icons.exitPip : icons.pip);
6504
+ setHTML(this.el, pip ? icons$1.exitPip : icons$1.pip);
6505
6505
  setAttr(this.el, "aria-label", pip ? "Exit Picture-in-Picture" : "Picture-in-Picture");
6506
6506
  this.el.classList.toggle("sp-pip--active", pip);
6507
6507
  }
@@ -6550,7 +6550,7 @@ var FullscreenButton = class {
6550
6550
  this.toggle();
6551
6551
  };
6552
6552
  this.api = api;
6553
- this.el = createButton("sp-fullscreen", "Fullscreen", icons.fullscreen);
6553
+ this.el = createButton("sp-fullscreen", "Fullscreen", icons$1.fullscreen);
6554
6554
  this.el.addEventListener("click", this.clickHandler);
6555
6555
  }
6556
6556
  render() {
@@ -6559,10 +6559,10 @@ var FullscreenButton = class {
6559
6559
  update() {
6560
6560
  const fullscreen = this.api.getState("fullscreen");
6561
6561
  if (fullscreen) {
6562
- setHTML(this.el, icons.exitFullscreen);
6562
+ setHTML(this.el, icons$1.exitFullscreen);
6563
6563
  setAttr(this.el, "aria-label", "Exit fullscreen");
6564
6564
  } else {
6565
- setHTML(this.el, icons.fullscreen);
6565
+ setHTML(this.el, icons$1.fullscreen);
6566
6566
  setAttr(this.el, "aria-label", "Fullscreen");
6567
6567
  }
6568
6568
  }
@@ -6672,7 +6672,7 @@ var ErrorOverlay = class {
6672
6672
  content.className = "sp-error-overlay__content";
6673
6673
  const iconEl = document.createElement("div");
6674
6674
  iconEl.className = "sp-error-overlay__icon";
6675
- iconEl.innerHTML = icons.error;
6675
+ iconEl.innerHTML = icons$1.error;
6676
6676
  const messageEl = document.createElement("p");
6677
6677
  messageEl.className = "sp-error-overlay__message";
6678
6678
  messageEl.textContent = "Something went wrong.";
@@ -6778,7 +6778,7 @@ var SettingsMenu = class {
6778
6778
  this.lastQualitiesJson = "";
6779
6779
  this.api = api;
6780
6780
  this.el = createElement("div", { className: "sp-settings" });
6781
- this.btn = createButton("sp-settings__btn", "Settings", icons.settings);
6781
+ this.btn = createButton("sp-settings__btn", "Settings", icons$1.settings);
6782
6782
  this.btn.setAttribute("aria-haspopup", "true");
6783
6783
  this.btn.setAttribute("aria-expanded", "false");
6784
6784
  this.btn.addEventListener("click", (e) => {
@@ -6923,7 +6923,7 @@ var SettingsMenu = class {
6923
6923
  const rightSide = createElement("span", { className: "sp-settings-panel__value" });
6924
6924
  rightSide.textContent = value;
6925
6925
  const arrow = createElement("span", { className: "sp-settings-panel__arrow" });
6926
- arrow.innerHTML = icons.chevronDown;
6926
+ arrow.innerHTML = icons$1.chevronDown;
6927
6927
  rightSide.appendChild(arrow);
6928
6928
  row.appendChild(labelEl);
6929
6929
  row.appendChild(rightSide);
@@ -7023,7 +7023,7 @@ var SettingsMenu = class {
7023
7023
  header.setAttribute("role", "menuitem");
7024
7024
  header.setAttribute("tabindex", "0");
7025
7025
  const backArrow = createElement("span", { className: "sp-settings-panel__back" });
7026
- backArrow.innerHTML = icons.chevronUp;
7026
+ backArrow.innerHTML = icons$1.chevronUp;
7027
7027
  const label = createElement("span", { className: "sp-settings-panel__header-label" });
7028
7028
  label.textContent = title;
7029
7029
  header.appendChild(backArrow);
@@ -7050,7 +7050,7 @@ var SettingsMenu = class {
7050
7050
  const labelEl = createElement("span");
7051
7051
  labelEl.textContent = label;
7052
7052
  const check = createElement("span", { className: "sp-settings-panel__check" });
7053
- check.innerHTML = icons.checkmark;
7053
+ check.innerHTML = icons$1.checkmark;
7054
7054
  item.appendChild(labelEl);
7055
7055
  item.appendChild(check);
7056
7056
  item.addEventListener("keydown", (e) => {
@@ -7144,7 +7144,7 @@ var SkipButton = class {
7144
7144
  this.api = api;
7145
7145
  this.direction = direction;
7146
7146
  this.seconds = seconds;
7147
- const icon = direction === "backward" ? icons.replay10 : icons.forward10;
7147
+ const icon = direction === "backward" ? icons$1.replay10 : icons$1.forward10;
7148
7148
  const label = direction === "backward" ? `Rewind ${seconds} seconds` : `Forward ${seconds} seconds`;
7149
7149
  this.el = createButton(
7150
7150
  `sp-skip sp-skip--${direction}`,
@@ -7206,7 +7206,7 @@ var CaptionsButton = class {
7206
7206
  this.toggle();
7207
7207
  };
7208
7208
  this.api = api;
7209
- this.el = createButton("sp-captions", "Captions", icons.captionsOff);
7209
+ this.el = createButton("sp-captions", "Captions", icons$1.captionsOff);
7210
7210
  this.el.addEventListener("click", this.clickHandler);
7211
7211
  }
7212
7212
  render() {
@@ -7221,11 +7221,11 @@ var CaptionsButton = class {
7221
7221
  }
7222
7222
  this.el.style.display = "";
7223
7223
  if (currentTrack) {
7224
- setHTML(this.el, icons.captions);
7224
+ setHTML(this.el, icons$1.captions);
7225
7225
  setAttr(this.el, "aria-label", `Captions: ${currentTrack.label}`);
7226
7226
  this.el.classList.add("sp-captions--active");
7227
7227
  } else {
7228
- setHTML(this.el, icons.captionsOff);
7228
+ setHTML(this.el, icons$1.captionsOff);
7229
7229
  setAttr(this.el, "aria-label", "Captions");
7230
7230
  this.el.classList.remove("sp-captions--active");
7231
7231
  }
@@ -7288,7 +7288,7 @@ var OverflowTray = class {
7288
7288
  this.isOpen ? this.close() : this.open();
7289
7289
  };
7290
7290
  this.el = createElement("div", { className: "sp-overflow" });
7291
- this.btn = createButton("sp-overflow__btn", "More controls", icons.more);
7291
+ this.btn = createButton("sp-overflow__btn", "More controls", icons$1.more);
7292
7292
  this.btn.setAttribute("aria-haspopup", "true");
7293
7293
  this.btn.setAttribute("aria-expanded", "false");
7294
7294
  this.btn.addEventListener("click", this.toggleHandler);
@@ -7440,7 +7440,7 @@ function onControlRegistered(listener) {
7440
7440
  listeners.delete(listener);
7441
7441
  };
7442
7442
  }
7443
- var PKG_VERSION$8 = "1.7.1";
7443
+ var PKG_VERSION$9 = "1.8.1";
7444
7444
  var DEFAULT_LAYOUT = [
7445
7445
  "play",
7446
7446
  "skip-backward",
@@ -7874,11 +7874,11 @@ function uiPlugin(config = {}) {
7874
7874
  id: "ui-controls",
7875
7875
  name: "UI Controls",
7876
7876
  type: "ui",
7877
- version: PKG_VERSION$8,
7877
+ version: PKG_VERSION$9,
7878
7878
  async init(pluginApi) {
7879
7879
  api = pluginApi;
7880
7880
  styleEl = document.createElement("style");
7881
- styleEl.textContent = styles$2;
7881
+ styleEl.textContent = styles$3;
7882
7882
  document.head.appendChild(styleEl);
7883
7883
  if (config.theme) {
7884
7884
  this.setTheme(config.theme);
@@ -7898,7 +7898,7 @@ function uiPlugin(config = {}) {
7898
7898
  container.appendChild(gradient);
7899
7899
  bufferingIndicator = document.createElement("div");
7900
7900
  bufferingIndicator.className = "sp-buffering";
7901
- bufferingIndicator.innerHTML = icons.spinner;
7901
+ bufferingIndicator.innerHTML = icons$1.spinner;
7902
7902
  bufferingIndicator.setAttribute("aria-hidden", "true");
7903
7903
  container.appendChild(bufferingIndicator);
7904
7904
  errorOverlay = new ErrorOverlay(api);
@@ -8060,14 +8060,14 @@ const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePropert
8060
8060
  formatLiveTime,
8061
8061
  formatTime: formatTime$1,
8062
8062
  getControlFactory,
8063
- icons,
8063
+ icons: icons$1,
8064
8064
  planFit,
8065
8065
  registerControl,
8066
8066
  resolveFitItems,
8067
- styles: styles$2,
8067
+ styles: styles$3,
8068
8068
  uiPlugin
8069
8069
  }, Symbol.toStringTag, { value: "Module" }));
8070
- var PKG_VERSION$7 = "1.7.1";
8070
+ var PKG_VERSION$8 = "1.8.1";
8071
8071
  var DEFAULT_THEME = {
8072
8072
  primary: "#6366f1",
8073
8073
  background: "#18181b",
@@ -8694,7 +8694,7 @@ function createAudioUIPlugin(config) {
8694
8694
  const plugin = {
8695
8695
  id: "audio-ui",
8696
8696
  name: "Audio UI",
8697
- version: PKG_VERSION$7,
8697
+ version: PKG_VERSION$8,
8698
8698
  type: "ui",
8699
8699
  description: "Compact audio player interface",
8700
8700
  async init(pluginApi) {
@@ -8999,8 +8999,20 @@ function safeStringify(data) {
8999
8999
  return "{}";
9000
9000
  }
9001
9001
  }
9002
- var PKG_VERSION$6 = "1.7.1";
9003
- var PLUGIN_VERSION = PKG_VERSION$6;
9002
+ function isHttpsUrl(url) {
9003
+ if (!url || typeof url !== "string" || !url.trim()) {
9004
+ return false;
9005
+ }
9006
+ try {
9007
+ const base = typeof window !== "undefined" && window.location?.href ? window.location.href : void 0;
9008
+ const parsed = base ? new URL(url, base) : new URL(url);
9009
+ return parsed.protocol === "https:";
9010
+ } catch {
9011
+ return false;
9012
+ }
9013
+ }
9014
+ var PKG_VERSION$7 = "1.8.1";
9015
+ var PLUGIN_VERSION = PKG_VERSION$7;
9004
9016
  var PLUGIN_NAME = "scarlett-player";
9005
9017
  var DEFAULT_CONFIG$2 = {
9006
9018
  heartbeatInterval: 1e4,
@@ -9094,11 +9106,14 @@ function createAnalyticsPlugin(config) {
9094
9106
  });
9095
9107
  navigator.sendBeacon(mergedConfig.beaconUrl, blob);
9096
9108
  } else {
9109
+ const shouldAttachApiKey = Boolean(
9110
+ mergedConfig.apiKey && isHttpsUrl(mergedConfig.beaconUrl)
9111
+ );
9097
9112
  fetch(mergedConfig.beaconUrl, {
9098
9113
  method: "POST",
9099
9114
  headers: {
9100
9115
  "Content-Type": "application/json",
9101
- ...mergedConfig.apiKey ? { "X-API-Key": mergedConfig.apiKey } : {}
9116
+ ...shouldAttachApiKey ? { "X-API-Key": mergedConfig.apiKey } : {}
9102
9117
  },
9103
9118
  body: safeStringify(payload),
9104
9119
  keepalive: true
@@ -9538,8 +9553,8 @@ var PlaylistPanel = class {
9538
9553
  });
9539
9554
  }
9540
9555
  };
9541
- var STYLE_ID$1 = "sp-playlist-styles";
9542
- var styles$1 = `
9556
+ var STYLE_ID$2 = "sp-playlist-styles";
9557
+ var styles$2 = `
9543
9558
  .sp-playlist-skip[disabled] {
9544
9559
  opacity: 0.4;
9545
9560
  cursor: default;
@@ -9639,16 +9654,16 @@ var styles$1 = `
9639
9654
  }
9640
9655
  `;
9641
9656
  function injectStyles() {
9642
- if (typeof document === "undefined" || document.getElementById(STYLE_ID$1)) {
9657
+ if (typeof document === "undefined" || document.getElementById(STYLE_ID$2)) {
9643
9658
  return null;
9644
9659
  }
9645
9660
  const el = document.createElement("style");
9646
- el.id = STYLE_ID$1;
9647
- el.textContent = styles$1;
9661
+ el.id = STYLE_ID$2;
9662
+ el.textContent = styles$2;
9648
9663
  document.head.appendChild(el);
9649
9664
  return el;
9650
9665
  }
9651
- var PKG_VERSION$5 = "1.7.1";
9666
+ var PKG_VERSION$6 = "1.8.1";
9652
9667
  var DEFAULT_CONFIG$1 = {
9653
9668
  autoAdvance: true,
9654
9669
  preloadNext: true,
@@ -9807,7 +9822,7 @@ function createPlaylistPlugin(config) {
9807
9822
  const plugin = {
9808
9823
  id: "playlist",
9809
9824
  name: "Playlist",
9810
- version: PKG_VERSION$5,
9825
+ version: PKG_VERSION$6,
9811
9826
  type: "feature",
9812
9827
  description: "Playlist management with shuffle, repeat, and gapless playback",
9813
9828
  async init(pluginApi) {
@@ -10063,7 +10078,7 @@ function createPlaylistPlugin(config) {
10063
10078
  };
10064
10079
  return plugin;
10065
10080
  }
10066
- var PKG_VERSION$4 = "1.7.1";
10081
+ var PKG_VERSION$5 = "1.8.1";
10067
10082
  var DEFAULT_CONFIG = {
10068
10083
  enablePlayPause: true,
10069
10084
  enableSeek: true,
@@ -10211,7 +10226,7 @@ function createMediaSessionPlugin(config) {
10211
10226
  const plugin = {
10212
10227
  id: "media-session",
10213
10228
  name: "Media Session",
10214
- version: PKG_VERSION$4,
10229
+ version: PKG_VERSION$5,
10215
10230
  type: "feature",
10216
10231
  description: "Media Session API integration for system-level media controls",
10217
10232
  async init(pluginApi) {
@@ -10322,7 +10337,7 @@ function createMediaSessionPlugin(config) {
10322
10337
  };
10323
10338
  return plugin;
10324
10339
  }
10325
- var PKG_VERSION$3 = "1.7.1";
10340
+ var PKG_VERSION$4 = "1.8.1";
10326
10341
  var POSITIONS = ["top-left", "top-right", "bottom-left", "bottom-right", "center"];
10327
10342
  function getPositionStyles(padding, bottomPadding) {
10328
10343
  return {
@@ -10432,7 +10447,7 @@ function createWatermarkPlugin(config = {}) {
10432
10447
  return {
10433
10448
  id: "watermark",
10434
10449
  name: "Watermark",
10435
- version: PKG_VERSION$3,
10450
+ version: PKG_VERSION$4,
10436
10451
  type: "feature",
10437
10452
  description: "Anti-piracy watermark overlay with text/image support and dynamic repositioning",
10438
10453
  init(pluginApi) {
@@ -10517,7 +10532,7 @@ function createWatermarkPlugin(config = {}) {
10517
10532
  }
10518
10533
  };
10519
10534
  }
10520
- var PKG_VERSION$2 = "1.7.1";
10535
+ var PKG_VERSION$3 = "1.8.1";
10521
10536
  var HLS_SUBTITLE_TRACKS_UPDATED = "hlsSubtitleTracksUpdated";
10522
10537
  var HLS_INSTANCE_RETRY_MS = 500;
10523
10538
  function createCaptionsPlugin(config = {}) {
@@ -10688,7 +10703,7 @@ function createCaptionsPlugin(config = {}) {
10688
10703
  return {
10689
10704
  id: "captions",
10690
10705
  name: "Captions",
10691
- version: PKG_VERSION$2,
10706
+ version: PKG_VERSION$3,
10692
10707
  type: "feature",
10693
10708
  description: "WebVTT subtitles and closed captions with HLS extraction",
10694
10709
  init(pluginApi) {
@@ -10883,8 +10898,8 @@ function createRecognizer(options = {}) {
10883
10898
  }
10884
10899
  };
10885
10900
  }
10886
- var STYLE_ID = "sp-gestures-styles";
10887
- var styles = `
10901
+ var STYLE_ID$1 = "sp-gestures-styles";
10902
+ var styles$1 = `
10888
10903
  .sp-gestures {
10889
10904
  position: absolute;
10890
10905
  top: 0;
@@ -11047,14 +11062,14 @@ var GestureOverlay = class {
11047
11062
  return { zone, label };
11048
11063
  }
11049
11064
  injectStyles() {
11050
- if (document.getElementById(STYLE_ID)) return;
11065
+ if (document.getElementById(STYLE_ID$1)) return;
11051
11066
  this.styleEl = document.createElement("style");
11052
- this.styleEl.id = STYLE_ID;
11053
- this.styleEl.textContent = styles;
11067
+ this.styleEl.id = STYLE_ID$1;
11068
+ this.styleEl.textContent = styles$1;
11054
11069
  document.head.appendChild(this.styleEl);
11055
11070
  }
11056
11071
  };
11057
- var PKG_VERSION$1 = "1.7.1";
11072
+ var PKG_VERSION$2 = "1.8.1";
11058
11073
  function hasCoarsePointer() {
11059
11074
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
11060
11075
  return false;
@@ -11178,7 +11193,7 @@ function createGesturesPlugin(config = {}) {
11178
11193
  return {
11179
11194
  id: "gestures",
11180
11195
  name: "Gestures",
11181
- version: PKG_VERSION$1,
11196
+ version: PKG_VERSION$2,
11182
11197
  type: "feature",
11183
11198
  init(pluginApi) {
11184
11199
  api = pluginApi;
@@ -11224,6 +11239,818 @@ function createGesturesPlugin(config = {}) {
11224
11239
  }
11225
11240
  };
11226
11241
  }
11242
+ function resolve(value, fallback) {
11243
+ if (typeof value === "function") {
11244
+ return value();
11245
+ }
11246
+ if (typeof value === "string" && value.length > 0) {
11247
+ return value;
11248
+ }
11249
+ return fallback();
11250
+ }
11251
+ function resolveBaseUrl(config) {
11252
+ return resolve(
11253
+ config.url,
11254
+ () => typeof window === "undefined" ? "" : window.location.href
11255
+ );
11256
+ }
11257
+ function resolveTitle(config) {
11258
+ return resolve(config.title, () => typeof document === "undefined" ? "" : document.title);
11259
+ }
11260
+ function applyTimestamp(baseUrl, currentTime, isLive, config) {
11261
+ const enabled = config.withTimestamp !== false;
11262
+ if (!enabled || isLive || !Number.isFinite(currentTime) || currentTime <= 0) {
11263
+ return baseUrl;
11264
+ }
11265
+ const param = config.timestampParam ?? "t";
11266
+ const rounded = config.roundTimestamp === false ? currentTime : Math.floor(currentTime);
11267
+ if (rounded <= 0) {
11268
+ return baseUrl;
11269
+ }
11270
+ try {
11271
+ const base = typeof window === "undefined" ? void 0 : window.location.href;
11272
+ const url = new URL(baseUrl, base);
11273
+ url.searchParams.set(param, String(rounded));
11274
+ return url.toString();
11275
+ } catch {
11276
+ return baseUrl;
11277
+ }
11278
+ }
11279
+ var icons = {
11280
+ /**
11281
+ * Three connected nodes. The one share glyph that reads the same everywhere:
11282
+ * it is the Material/Android share icon, and it carries no platform baggage.
11283
+ * The tray-and-arrow below is native on iOS but reads as "upload" or "export"
11284
+ * to everyone else, which is why it is no longer the default.
11285
+ */
11286
+ share: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.6" y1="10.6" x2="15.4" y2="6.4"/><line x1="8.6" y1="13.4" x2="15.4" y2="17.6"/></svg>',
11287
+ /** iOS-style tray and arrow. Kept for hosts targeting an iOS-heavy audience. */
11288
+ upload: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><polyline points="16 6 12 2 8 6"/><line x1="12" y1="2" x2="12" y2="15"/></svg>',
11289
+ native: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><polyline points="16 6 12 2 8 6"/><line x1="12" y1="2" x2="12" y2="15"/></svg>',
11290
+ copy: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>',
11291
+ embed: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>',
11292
+ x: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.9 2H22l-7.1 8.1L23 22h-6.6l-5.2-6.8L5.3 22H2.2l7.6-8.7L1.7 2h6.8l4.7 6.2L18.9 2Zm-1.1 18h1.7L7.3 3.8H5.5L17.8 20Z"/></svg>',
11293
+ facebook: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M22 12a10 10 0 1 0-11.6 9.9v-7H7.9V12h2.5V9.8c0-2.5 1.5-3.9 3.8-3.9 1.1 0 2.2.2 2.2.2v2.5h-1.3c-1.2 0-1.6.8-1.6 1.6V12h2.8l-.4 2.9h-2.3v7A10 10 0 0 0 22 12Z"/></svg>',
11294
+ linkedin: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M4.98 3.5A2.5 2.5 0 1 1 0 3.5a2.5 2.5 0 0 1 4.98 0ZM.2 8.3h4.6V24H.2V8.3Zm7.6 0h4.4v2.1h.1a4.8 4.8 0 0 1 4.3-2.4c4.6 0 5.4 3 5.4 6.9V24h-4.6v-7.6c0-1.8 0-4.1-2.5-4.1s-2.9 2-2.9 4V24H7.8V8.3Z"/></svg>',
11295
+ whatsapp: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2a10 10 0 0 0-8.5 15.2L2 22l4.9-1.4A10 10 0 1 0 12 2Zm5.1 14.1c-.2.6-1.2 1.2-1.7 1.2-.5.1-1 .1-1.6-.1-.4-.1-.9-.3-1.5-.6a11 11 0 0 1-4.2-3.8c-.3-.5-.7-1.2-.7-2 0-.8.4-1.2.6-1.4.2-.2.4-.3.6-.3h.4c.1 0 .3 0 .5.4l.7 1.6c0 .1.1.3 0 .4l-.3.4-.2.3c-.1.1-.2.2 0 .5.2.3.7 1.1 1.4 1.8.9.8 1.6 1 1.9 1.2.2.1.4.1.5-.1l.6-.7c.2-.2.3-.2.5-.1l1.6.8c.2.1.4.2.4.3.1.1.1.5-.1 1Z"/></svg>',
11296
+ telegram: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M21.9 4.3 18.6 20c-.2 1.1-.9 1.4-1.8.9l-4.9-3.6-2.4 2.3c-.3.3-.5.5-1 .5l.4-5 9.1-8.2c.4-.4-.1-.6-.6-.2L6.2 13.1l-4.8-1.5c-1-.3-1-1 .2-1.5l18.9-7.3c.9-.3 1.6.2 1.4 1.5Z"/></svg>',
11297
+ reddit: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M22 12a2.1 2.1 0 0 0-3.6-1.5 10.4 10.4 0 0 0-5.4-1.7l.9-4.2 3 .6a1.8 1.8 0 1 0 .2-1.2l-3.6-.8a.6.6 0 0 0-.7.5L11.7 8.8a10.4 10.4 0 0 0-5.5 1.7A2.1 2.1 0 1 0 3.6 14a4 4 0 0 0 0 .6c0 3.1 3.7 5.6 8.4 5.6s8.4-2.5 8.4-5.6a4 4 0 0 0 0-.6A2.1 2.1 0 0 0 22 12ZM7.9 13.5a1.5 1.5 0 1 1 1.5 1.5 1.5 1.5 0 0 1-1.5-1.5Zm8.3 4.1a5.6 5.6 0 0 1-4.2 1.3 5.6 5.6 0 0 1-4.2-1.3.5.5 0 0 1 .7-.7 4.7 4.7 0 0 0 3.5 1 4.7 4.7 0 0 0 3.5-1 .5.5 0 1 1 .7.7Zm-.6-2.6a1.5 1.5 0 1 1 1.5-1.5 1.5 1.5 0 0 1-1.5 1.5Z"/></svg>',
11298
+ email: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 6L2 7"/></svg>'
11299
+ };
11300
+ var encode = encodeURIComponent;
11301
+ var BUILTIN_TARGETS = {
11302
+ native: { id: "native", label: "Share", icon: icons.native },
11303
+ copy: { id: "copy", label: "Copy link", icon: icons.copy },
11304
+ embed: { id: "embed", label: "Embed", icon: icons.embed },
11305
+ x: {
11306
+ id: "x",
11307
+ label: "X",
11308
+ icon: icons.x,
11309
+ href: (c) => `https://x.com/intent/tweet?url=${encode(c.url)}&text=${encode(c.title)}`
11310
+ },
11311
+ facebook: {
11312
+ id: "facebook",
11313
+ label: "Facebook",
11314
+ icon: icons.facebook,
11315
+ href: (c) => `https://www.facebook.com/sharer/sharer.php?u=${encode(c.url)}`
11316
+ },
11317
+ linkedin: {
11318
+ id: "linkedin",
11319
+ label: "LinkedIn",
11320
+ icon: icons.linkedin,
11321
+ href: (c) => `https://www.linkedin.com/sharing/share-offsite/?url=${encode(c.url)}`
11322
+ },
11323
+ whatsapp: {
11324
+ id: "whatsapp",
11325
+ label: "WhatsApp",
11326
+ icon: icons.whatsapp,
11327
+ href: (c) => `https://api.whatsapp.com/send?text=${encode(`${c.title} ${c.url}`)}`
11328
+ },
11329
+ telegram: {
11330
+ id: "telegram",
11331
+ label: "Telegram",
11332
+ icon: icons.telegram,
11333
+ href: (c) => `https://t.me/share/url?url=${encode(c.url)}&text=${encode(c.title)}`
11334
+ },
11335
+ reddit: {
11336
+ id: "reddit",
11337
+ label: "Reddit",
11338
+ icon: icons.reddit,
11339
+ href: (c) => `https://www.reddit.com/submit?url=${encode(c.url)}&title=${encode(c.title)}`
11340
+ },
11341
+ email: {
11342
+ id: "email",
11343
+ label: "Email",
11344
+ icon: icons.email,
11345
+ href: (c) => `mailto:?subject=${encode(c.title)}&body=${encode(c.url)}`
11346
+ }
11347
+ };
11348
+ var DEFAULT_TARGETS = ["native", "copy", "embed"];
11349
+ function resolveTargets(configured, onUnknown) {
11350
+ const resolved = [];
11351
+ for (const entry of configured) {
11352
+ if (typeof entry !== "string") {
11353
+ resolved.push({ ...entry, icon: entry.icon ?? icons[entry.id] });
11354
+ continue;
11355
+ }
11356
+ const builtin = BUILTIN_TARGETS[entry];
11357
+ if (builtin) {
11358
+ resolved.push(builtin);
11359
+ } else {
11360
+ onUnknown?.(entry);
11361
+ }
11362
+ }
11363
+ return resolved;
11364
+ }
11365
+ function canUseNativeShare() {
11366
+ return typeof navigator !== "undefined" && typeof navigator.share === "function";
11367
+ }
11368
+ async function nativeShare(context) {
11369
+ try {
11370
+ await navigator.share({ title: context.title, url: context.url });
11371
+ return true;
11372
+ } catch (error) {
11373
+ if (error instanceof Error && error.name === "AbortError") {
11374
+ return false;
11375
+ }
11376
+ throw error;
11377
+ }
11378
+ }
11379
+ async function copyText(text) {
11380
+ if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
11381
+ try {
11382
+ await navigator.clipboard.writeText(text);
11383
+ return true;
11384
+ } catch {
11385
+ }
11386
+ }
11387
+ if (typeof document === "undefined") {
11388
+ return false;
11389
+ }
11390
+ const textarea = document.createElement("textarea");
11391
+ textarea.value = text;
11392
+ textarea.setAttribute("readonly", "");
11393
+ textarea.style.position = "fixed";
11394
+ textarea.style.top = "-9999px";
11395
+ textarea.style.opacity = "0";
11396
+ document.body.appendChild(textarea);
11397
+ try {
11398
+ textarea.select();
11399
+ textarea.setSelectionRange(0, textarea.value.length);
11400
+ return document.execCommand("copy");
11401
+ } catch {
11402
+ return false;
11403
+ } finally {
11404
+ textarea.remove();
11405
+ }
11406
+ }
11407
+ function buildEmbedSnippet(context, config) {
11408
+ if (config.embedSnippet) {
11409
+ return config.embedSnippet(context);
11410
+ }
11411
+ if (!config.embedBaseUrl) {
11412
+ return null;
11413
+ }
11414
+ try {
11415
+ const base = typeof window === "undefined" ? void 0 : window.location.href;
11416
+ const embedUrl = new URL(config.embedBaseUrl, base);
11417
+ if (context.currentTime > 0 && !context.isLive) {
11418
+ embedUrl.searchParams.set("startTime", String(Math.floor(context.currentTime)));
11419
+ }
11420
+ embedUrl.searchParams.set("shareUrl", context.url);
11421
+ return `<iframe src="${embedUrl.toString()}" width="640" height="360" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen loading="lazy"></iframe>`;
11422
+ } catch {
11423
+ return null;
11424
+ }
11425
+ }
11426
+ var TOAST_MS = 1800;
11427
+ var ShareSheet = class {
11428
+ constructor(api, callbacks) {
11429
+ this.api = api;
11430
+ this.callbacks = callbacks;
11431
+ this.backdrop = null;
11432
+ this.sheet = null;
11433
+ this.toast = null;
11434
+ this.toastTimer = null;
11435
+ this.previouslyFocused = null;
11436
+ this.open = false;
11437
+ this.keydownHandler = (event) => {
11438
+ if (event.key === "Escape") {
11439
+ event.stopPropagation();
11440
+ this.close();
11441
+ return;
11442
+ }
11443
+ if (event.key === "Tab") {
11444
+ this.trapFocus(event);
11445
+ }
11446
+ };
11447
+ }
11448
+ /** Whether the sheet is currently showing. */
11449
+ isOpen() {
11450
+ return this.open;
11451
+ }
11452
+ /**
11453
+ * Show the sheet for a set of targets.
11454
+ */
11455
+ show(targets) {
11456
+ if (this.open) {
11457
+ return;
11458
+ }
11459
+ this.previouslyFocused = document.activeElement;
11460
+ this.open = true;
11461
+ this.backdrop = document.createElement("div");
11462
+ this.backdrop.className = "sp-share-backdrop";
11463
+ this.backdrop.addEventListener("click", () => this.close());
11464
+ this.sheet = document.createElement("div");
11465
+ this.sheet.className = "sp-share-sheet";
11466
+ this.sheet.setAttribute("role", "dialog");
11467
+ this.sheet.setAttribute("aria-modal", "true");
11468
+ this.sheet.setAttribute("aria-label", "Share");
11469
+ const grip = document.createElement("div");
11470
+ grip.className = "sp-share-grip";
11471
+ this.sheet.appendChild(grip);
11472
+ const heading = document.createElement("p");
11473
+ heading.className = "sp-share-title";
11474
+ heading.textContent = "Share";
11475
+ this.sheet.appendChild(heading);
11476
+ const list = document.createElement("ul");
11477
+ list.className = "sp-share-targets";
11478
+ for (const target of targets) {
11479
+ list.appendChild(this.renderTarget(target));
11480
+ }
11481
+ this.sheet.appendChild(list);
11482
+ this.api.container.appendChild(this.backdrop);
11483
+ this.api.container.appendChild(this.sheet);
11484
+ requestAnimationFrame(() => {
11485
+ this.backdrop?.classList.add("sp-share-backdrop--open");
11486
+ this.sheet?.classList.add("sp-share-sheet--open");
11487
+ });
11488
+ document.addEventListener("keydown", this.keydownHandler, true);
11489
+ const first = this.sheet.querySelector(".sp-share-target");
11490
+ first?.focus();
11491
+ }
11492
+ /** Hide the sheet and restore focus. */
11493
+ close() {
11494
+ if (!this.open) {
11495
+ return;
11496
+ }
11497
+ this.open = false;
11498
+ document.removeEventListener("keydown", this.keydownHandler, true);
11499
+ this.backdrop?.classList.remove("sp-share-backdrop--open");
11500
+ this.sheet?.classList.remove("sp-share-sheet--open");
11501
+ const backdrop = this.backdrop;
11502
+ const sheet = this.sheet;
11503
+ this.backdrop = null;
11504
+ this.sheet = null;
11505
+ setTimeout(() => {
11506
+ backdrop?.remove();
11507
+ sheet?.remove();
11508
+ }, 220);
11509
+ this.previouslyFocused?.focus();
11510
+ this.previouslyFocused = null;
11511
+ this.callbacks.onClose();
11512
+ }
11513
+ /**
11514
+ * Show a transient confirmation, e.g. after copying.
11515
+ */
11516
+ showToast(message) {
11517
+ if (this.toastTimer) {
11518
+ clearTimeout(this.toastTimer);
11519
+ this.toastTimer = null;
11520
+ }
11521
+ if (!this.toast) {
11522
+ this.toast = document.createElement("div");
11523
+ this.toast.className = "sp-share-toast";
11524
+ this.toast.setAttribute("role", "status");
11525
+ this.toast.setAttribute("aria-live", "polite");
11526
+ this.api.container.appendChild(this.toast);
11527
+ }
11528
+ this.toast.textContent = message;
11529
+ requestAnimationFrame(() => this.toast?.classList.add("sp-share-toast--visible"));
11530
+ this.toastTimer = setTimeout(() => {
11531
+ this.toast?.classList.remove("sp-share-toast--visible");
11532
+ this.toastTimer = null;
11533
+ }, TOAST_MS);
11534
+ }
11535
+ /**
11536
+ * Last-resort clipboard fallback: show the text for manual copying.
11537
+ */
11538
+ showManualCopy(text) {
11539
+ if (!this.sheet) {
11540
+ return;
11541
+ }
11542
+ const row = document.createElement("div");
11543
+ row.className = "sp-share-fallback";
11544
+ const input = document.createElement("input");
11545
+ input.type = "text";
11546
+ input.readOnly = true;
11547
+ input.value = text;
11548
+ input.setAttribute("aria-label", "Link to copy");
11549
+ row.appendChild(input);
11550
+ this.sheet.appendChild(row);
11551
+ input.focus();
11552
+ input.select();
11553
+ }
11554
+ /** Remove everything and detach listeners. */
11555
+ destroy() {
11556
+ document.removeEventListener("keydown", this.keydownHandler, true);
11557
+ if (this.toastTimer) {
11558
+ clearTimeout(this.toastTimer);
11559
+ this.toastTimer = null;
11560
+ }
11561
+ this.backdrop?.remove();
11562
+ this.sheet?.remove();
11563
+ this.toast?.remove();
11564
+ this.backdrop = null;
11565
+ this.sheet = null;
11566
+ this.toast = null;
11567
+ this.open = false;
11568
+ this.previouslyFocused = null;
11569
+ }
11570
+ /** Build one target button. */
11571
+ renderTarget(target) {
11572
+ const item = document.createElement("li");
11573
+ const button = document.createElement("button");
11574
+ button.type = "button";
11575
+ button.className = `sp-share-target sp-share-target--${target.id}`;
11576
+ button.setAttribute("aria-label", target.label);
11577
+ const icon = document.createElement("span");
11578
+ icon.className = "sp-share-icon";
11579
+ icon.setAttribute("aria-hidden", "true");
11580
+ if (target.icon) {
11581
+ icon.innerHTML = target.icon;
11582
+ }
11583
+ const label = document.createElement("span");
11584
+ label.className = "sp-share-label";
11585
+ label.textContent = target.label;
11586
+ button.appendChild(icon);
11587
+ button.appendChild(label);
11588
+ button.addEventListener("click", () => this.callbacks.onSelect(target));
11589
+ item.appendChild(button);
11590
+ return item;
11591
+ }
11592
+ /** Keep Tab inside the dialog while it is open. */
11593
+ trapFocus(event) {
11594
+ if (!this.sheet) {
11595
+ return;
11596
+ }
11597
+ const focusable = this.sheet.querySelectorAll('button, input, [tabindex]:not([tabindex="-1"])');
11598
+ if (focusable.length === 0) {
11599
+ return;
11600
+ }
11601
+ const first = focusable[0];
11602
+ const last = focusable[focusable.length - 1];
11603
+ const active = document.activeElement;
11604
+ if (event.shiftKey && active === first) {
11605
+ event.preventDefault();
11606
+ last?.focus();
11607
+ } else if (!event.shiftKey && active === last) {
11608
+ event.preventDefault();
11609
+ first?.focus();
11610
+ }
11611
+ }
11612
+ };
11613
+ var ALLOWED_ELEMENTS = /* @__PURE__ */ new Set([
11614
+ "svg",
11615
+ "g",
11616
+ "path",
11617
+ "circle",
11618
+ "ellipse",
11619
+ "line",
11620
+ "polyline",
11621
+ "polygon",
11622
+ "rect",
11623
+ "defs",
11624
+ "lineargradient",
11625
+ "radialgradient",
11626
+ "stop",
11627
+ "clippath",
11628
+ "mask",
11629
+ "use",
11630
+ "symbol",
11631
+ "title",
11632
+ "desc"
11633
+ ]);
11634
+ function sanitizeIcon(markup) {
11635
+ if (typeof markup !== "string" || markup.trim() === "") {
11636
+ return null;
11637
+ }
11638
+ if (typeof DOMParser === "undefined") {
11639
+ return null;
11640
+ }
11641
+ const doc = new DOMParser().parseFromString(markup, "text/html");
11642
+ const svg = doc.body.querySelector("svg");
11643
+ if (!svg) {
11644
+ return null;
11645
+ }
11646
+ for (const element of Array.from(svg.querySelectorAll("*"))) {
11647
+ if (!ALLOWED_ELEMENTS.has(element.nodeName.toLowerCase())) {
11648
+ element.remove();
11649
+ continue;
11650
+ }
11651
+ stripDangerousAttributes(element);
11652
+ }
11653
+ stripDangerousAttributes(svg);
11654
+ return svg.outerHTML;
11655
+ }
11656
+ function stripDangerousAttributes(element) {
11657
+ for (const attribute of Array.from(element.attributes)) {
11658
+ const name = attribute.name.toLowerCase();
11659
+ if (name.startsWith("on")) {
11660
+ element.removeAttribute(attribute.name);
11661
+ continue;
11662
+ }
11663
+ if (name === "href" || name === "xlink:href" || name === "src") {
11664
+ const value = attribute.value.replace(/\s+/g, "").toLowerCase();
11665
+ if (value.startsWith("javascript:") || value.startsWith("data:text/html")) {
11666
+ element.removeAttribute(attribute.name);
11667
+ }
11668
+ }
11669
+ }
11670
+ }
11671
+ var ShareButton = class {
11672
+ constructor(_api, onActivate, options = {}) {
11673
+ this.onActivate = onActivate;
11674
+ this.clickHandler = () => {
11675
+ this.onActivate();
11676
+ };
11677
+ const label = options.label ?? "Share";
11678
+ this.el = document.createElement("button");
11679
+ this.el.type = "button";
11680
+ this.el.className = "sp-share sp-control";
11681
+ this.el.setAttribute("aria-label", label);
11682
+ this.el.setAttribute("title", label);
11683
+ this.el.setAttribute("aria-haspopup", "dialog");
11684
+ const icon = options.icon ? sanitizeIcon(options.icon) : null;
11685
+ this.el.innerHTML = icon ?? icons.share ?? "";
11686
+ this.el.addEventListener("click", this.clickHandler);
11687
+ }
11688
+ render() {
11689
+ return this.el;
11690
+ }
11691
+ update() {
11692
+ }
11693
+ destroy() {
11694
+ this.el.removeEventListener("click", this.clickHandler);
11695
+ this.el.remove();
11696
+ }
11697
+ };
11698
+ var styles = `
11699
+ .sp-share-backdrop {
11700
+ position: absolute;
11701
+ inset: 0;
11702
+ background: rgba(0, 0, 0, 0.55);
11703
+ opacity: 0;
11704
+ transition: opacity 180ms ease;
11705
+ z-index: 30;
11706
+ }
11707
+ .sp-share-backdrop--open { opacity: 1; }
11708
+
11709
+ .sp-share-sheet {
11710
+ position: absolute;
11711
+ left: 0;
11712
+ right: 0;
11713
+ bottom: 0;
11714
+ z-index: 31;
11715
+ background: #1c1c1e;
11716
+ color: #fff;
11717
+ border-radius: 14px 14px 0 0;
11718
+ padding: 8px 12px calc(12px + env(safe-area-inset-bottom, 0px));
11719
+ box-shadow: 0 -6px 28px rgba(0, 0, 0, 0.45);
11720
+ transform: translateY(100%);
11721
+ transition: transform 220ms cubic-bezier(0.32, 0.72, 0, 1);
11722
+ max-height: 80%;
11723
+ overflow-y: auto;
11724
+ -webkit-overflow-scrolling: touch;
11725
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
11726
+ }
11727
+ .sp-share-sheet--open { transform: translateY(0); }
11728
+
11729
+ /* Grab handle - the affordance that says "this drags/dismisses" on a phone. */
11730
+ .sp-share-grip {
11731
+ width: 36px;
11732
+ height: 4px;
11733
+ border-radius: 2px;
11734
+ background: rgba(255, 255, 255, 0.3);
11735
+ margin: 4px auto 10px;
11736
+ }
11737
+
11738
+ .sp-share-title {
11739
+ font-size: 13px;
11740
+ font-weight: 600;
11741
+ letter-spacing: 0.02em;
11742
+ text-transform: uppercase;
11743
+ color: rgba(255, 255, 255, 0.55);
11744
+ padding: 0 8px 8px;
11745
+ margin: 0;
11746
+ }
11747
+
11748
+ .sp-share-targets {
11749
+ display: grid;
11750
+ grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
11751
+ gap: 4px;
11752
+ list-style: none;
11753
+ margin: 0;
11754
+ padding: 0;
11755
+ }
11756
+
11757
+ .sp-share-target {
11758
+ display: flex;
11759
+ flex-direction: column;
11760
+ align-items: center;
11761
+ justify-content: center;
11762
+ gap: 8px;
11763
+ /* 44px is the WCAG 2.5.5 floor; 72px is a comfortable thumb target. */
11764
+ min-height: 72px;
11765
+ min-width: 44px;
11766
+ padding: 10px 6px;
11767
+ border: 0;
11768
+ border-radius: 10px;
11769
+ background: transparent;
11770
+ color: inherit;
11771
+ font: inherit;
11772
+ font-size: 12px;
11773
+ cursor: pointer;
11774
+ -webkit-tap-highlight-color: transparent;
11775
+ transition: background-color 120ms ease, transform 120ms ease;
11776
+ }
11777
+ .sp-share-target:active { transform: scale(0.94); background: rgba(255, 255, 255, 0.14); }
11778
+ .sp-share-target:focus-visible { outline: 2px solid #fff; outline-offset: -2px; }
11779
+ @media (hover: hover) {
11780
+ .sp-share-target:hover { background: rgba(255, 255, 255, 0.1); }
11781
+ }
11782
+
11783
+ .sp-share-target svg {
11784
+ width: 24px;
11785
+ height: 24px;
11786
+ display: block;
11787
+ }
11788
+
11789
+ .sp-share-icon {
11790
+ display: flex;
11791
+ align-items: center;
11792
+ justify-content: center;
11793
+ width: 44px;
11794
+ height: 44px;
11795
+ border-radius: 50%;
11796
+ background: rgba(255, 255, 255, 0.12);
11797
+ }
11798
+
11799
+ .sp-share-label {
11800
+ line-height: 1.2;
11801
+ text-align: center;
11802
+ max-width: 100%;
11803
+ overflow: hidden;
11804
+ text-overflow: ellipsis;
11805
+ white-space: nowrap;
11806
+ }
11807
+
11808
+ /* Copy confirmation, announced politely to assistive tech. */
11809
+ .sp-share-toast {
11810
+ position: absolute;
11811
+ left: 50%;
11812
+ bottom: 16px;
11813
+ transform: translateX(-50%) translateY(8px);
11814
+ z-index: 32;
11815
+ background: rgba(28, 28, 30, 0.95);
11816
+ color: #fff;
11817
+ font-size: 13px;
11818
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
11819
+ padding: 10px 16px;
11820
+ border-radius: 20px;
11821
+ opacity: 0;
11822
+ pointer-events: none;
11823
+ transition: opacity 160ms ease, transform 160ms ease;
11824
+ }
11825
+ .sp-share-toast--visible { opacity: 1; transform: translateX(-50%) translateY(0); }
11826
+
11827
+ /* Fallback when the clipboard is unavailable: show the URL to copy by hand. */
11828
+ .sp-share-fallback {
11829
+ display: flex;
11830
+ gap: 8px;
11831
+ padding: 8px;
11832
+ }
11833
+ .sp-share-fallback input {
11834
+ flex: 1;
11835
+ min-width: 0;
11836
+ /* 16px keeps iOS Safari from zooming the viewport on focus. */
11837
+ font-size: 16px;
11838
+ padding: 10px 12px;
11839
+ border-radius: 8px;
11840
+ border: 1px solid rgba(255, 255, 255, 0.2);
11841
+ background: rgba(255, 255, 255, 0.08);
11842
+ color: #fff;
11843
+ }
11844
+
11845
+ @media (min-width: 640px) {
11846
+ .sp-share-backdrop { background: rgba(0, 0, 0, 0.25); }
11847
+
11848
+ .sp-share-sheet {
11849
+ left: auto;
11850
+ right: 12px;
11851
+ bottom: 64px;
11852
+ width: 320px;
11853
+ border-radius: 12px;
11854
+ padding: 10px;
11855
+ transform: translateY(8px) scale(0.98);
11856
+ opacity: 0;
11857
+ transition: opacity 140ms ease, transform 140ms ease;
11858
+ max-height: 60%;
11859
+ }
11860
+ .sp-share-sheet--open { transform: translateY(0) scale(1); opacity: 1; }
11861
+
11862
+ .sp-share-grip { display: none; }
11863
+ .sp-share-targets { grid-template-columns: repeat(auto-fill, minmax(76px, 1fr)); }
11864
+ }
11865
+
11866
+ @media (prefers-reduced-motion: reduce) {
11867
+ .sp-share-backdrop,
11868
+ .sp-share-sheet,
11869
+ .sp-share-target,
11870
+ .sp-share-toast {
11871
+ transition: none;
11872
+ }
11873
+ }
11874
+ `;
11875
+ var PKG_VERSION$1 = "1.8.1";
11876
+ var STYLE_ID = "sp-share-styles";
11877
+ function createSharePlugin(config = {}) {
11878
+ let api = null;
11879
+ let sheet = null;
11880
+ let styleEl = null;
11881
+ const configuredTargets = config.targets ?? DEFAULT_TARGETS;
11882
+ const buildContext = () => {
11883
+ const currentTime = api?.getState("currentTime") ?? 0;
11884
+ const isLive = Boolean(api?.getState("live"));
11885
+ const baseUrl = resolveBaseUrl(config);
11886
+ return {
11887
+ url: applyTimestamp(baseUrl, currentTime, isLive, config),
11888
+ title: resolveTitle(config),
11889
+ currentTime,
11890
+ isLive
11891
+ };
11892
+ };
11893
+ const reportError = (error) => {
11894
+ const err = error instanceof Error ? error : new Error(String(error));
11895
+ api?.logger.error("Share failed", { error: err });
11896
+ config.onError?.(err);
11897
+ };
11898
+ const reportShared = (targetId, url) => {
11899
+ config.onShare?.(targetId, url);
11900
+ api?.emit("share:completed", { targetId, url });
11901
+ };
11902
+ const availableTargets = (context) => resolveTargets(configuredTargets, (id) => api?.logger.warn(`Unknown share target: ${id}`)).filter(
11903
+ (target) => {
11904
+ if (target.id === "native") {
11905
+ return canUseNativeShare();
11906
+ }
11907
+ if (target.id === "embed") {
11908
+ return buildEmbedSnippet(context, config) !== null;
11909
+ }
11910
+ return true;
11911
+ }
11912
+ );
11913
+ const runTarget = async (target, context) => {
11914
+ if (target.id === "native") {
11915
+ const shared = await nativeShare(context);
11916
+ if (shared) {
11917
+ reportShared(target.id, context.url);
11918
+ }
11919
+ return;
11920
+ }
11921
+ if (target.id === "copy") {
11922
+ const copied = await copyText(context.url);
11923
+ if (copied) {
11924
+ sheet?.showToast("Link copied");
11925
+ reportShared(target.id, context.url);
11926
+ } else {
11927
+ sheet?.showManualCopy(context.url);
11928
+ }
11929
+ return;
11930
+ }
11931
+ if (target.id === "embed") {
11932
+ const snippet = buildEmbedSnippet(context, config);
11933
+ if (!snippet) {
11934
+ return;
11935
+ }
11936
+ const copied = await copyText(snippet);
11937
+ if (copied) {
11938
+ sheet?.showToast("Embed code copied");
11939
+ reportShared(target.id, context.url);
11940
+ } else {
11941
+ sheet?.showManualCopy(snippet);
11942
+ }
11943
+ return;
11944
+ }
11945
+ if (target.href) {
11946
+ window.open(target.href(context), "_blank", "noopener,noreferrer");
11947
+ reportShared(target.id, context.url);
11948
+ }
11949
+ };
11950
+ const openSheet = () => {
11951
+ if (!sheet) {
11952
+ return;
11953
+ }
11954
+ const context = buildContext();
11955
+ const targets = availableTargets(context);
11956
+ if (targets.length === 0) {
11957
+ api?.logger.warn("No share targets available");
11958
+ return;
11959
+ }
11960
+ sheet.show(targets);
11961
+ api?.emit("share:opened", void 0);
11962
+ };
11963
+ const activate = async () => {
11964
+ const hostChoseTargets = config.targets !== void 0;
11965
+ if (!hostChoseTargets && canUseNativeShare()) {
11966
+ const context = buildContext();
11967
+ try {
11968
+ const shared = await nativeShare(context);
11969
+ if (shared) {
11970
+ reportShared("native", context.url);
11971
+ }
11972
+ } catch (error) {
11973
+ reportError(error);
11974
+ }
11975
+ return;
11976
+ }
11977
+ openSheet();
11978
+ };
11979
+ return {
11980
+ id: "share",
11981
+ name: "Share",
11982
+ version: PKG_VERSION$1,
11983
+ type: "feature",
11984
+ description: "Native share sheet, copy link, timestamps and embed codes",
11985
+ init(pluginApi) {
11986
+ api = pluginApi;
11987
+ api.logger.debug("Share plugin initialized");
11988
+ if (!document.getElementById(STYLE_ID)) {
11989
+ styleEl = document.createElement("style");
11990
+ styleEl.id = STYLE_ID;
11991
+ styleEl.textContent = styles;
11992
+ document.head.appendChild(styleEl);
11993
+ }
11994
+ sheet = new ShareSheet(api, {
11995
+ onSelect: (target) => {
11996
+ const context = buildContext();
11997
+ if (target.id !== "copy" && target.id !== "embed") {
11998
+ sheet?.close();
11999
+ }
12000
+ void runTarget(target, context).catch(reportError);
12001
+ },
12002
+ onClose: () => {
12003
+ api?.emit("share:closed", void 0);
12004
+ }
12005
+ });
12006
+ void Promise.resolve().then(() => index).then(({ registerControl: registerControl2 }) => {
12007
+ registerControl2(
12008
+ "share",
12009
+ (controlApi) => new ShareButton(controlApi, () => void activate(), {
12010
+ icon: config.buttonIcon,
12011
+ label: config.buttonLabel
12012
+ })
12013
+ );
12014
+ }).catch(() => {
12015
+ api?.logger.debug("@scarlett-player/ui not present, share control not registered");
12016
+ });
12017
+ api.onDestroy(() => {
12018
+ sheet?.destroy();
12019
+ sheet = null;
12020
+ });
12021
+ },
12022
+ destroy() {
12023
+ sheet?.destroy();
12024
+ sheet = null;
12025
+ styleEl?.remove();
12026
+ styleEl = null;
12027
+ api = null;
12028
+ },
12029
+ async share(targetId) {
12030
+ if (!targetId) {
12031
+ await activate();
12032
+ return;
12033
+ }
12034
+ const context = buildContext();
12035
+ const target = availableTargets(context).find((t) => t.id === targetId);
12036
+ if (!target) {
12037
+ reportError(new Error(`Unknown or unavailable share target: ${targetId}`));
12038
+ return;
12039
+ }
12040
+ try {
12041
+ await runTarget(target, context);
12042
+ } catch (error) {
12043
+ reportError(error);
12044
+ }
12045
+ },
12046
+ getShareUrl() {
12047
+ return buildContext().url;
12048
+ },
12049
+ close() {
12050
+ sheet?.close();
12051
+ }
12052
+ };
12053
+ }
11227
12054
  function getAttr(element, ...names) {
11228
12055
  for (const name of names) {
11229
12056
  const value = element.getAttribute(name);
@@ -11289,6 +12116,14 @@ function parseDataAttributes(element) {
11289
12116
  if (album) {
11290
12117
  config.album = album;
11291
12118
  }
12119
+ const shareUrl = getAttr(element, "data-share-url", "share-url");
12120
+ if (shareUrl) {
12121
+ config.shareUrl = shareUrl;
12122
+ }
12123
+ const embedBaseUrl = getAttr(element, "data-embed-base-url", "embed-base-url");
12124
+ if (embedBaseUrl) {
12125
+ config.embedBaseUrl = embedBaseUrl;
12126
+ }
11292
12127
  const brandColor = getAttr(element, "data-brand-color", "data-color", "color");
11293
12128
  if (brandColor) {
11294
12129
  config.brandColor = brandColor;
@@ -11391,6 +12226,23 @@ function applyContainerStyles(container, config) {
11391
12226
  container.style.width = container.style.width || "100%";
11392
12227
  }
11393
12228
  }
12229
+ const SHARE_CONTROL_LAYOUT = [
12230
+ "play",
12231
+ "skip-backward",
12232
+ "skip-forward",
12233
+ "volume",
12234
+ "time",
12235
+ "live-indicator",
12236
+ "bandwidth-indicator",
12237
+ "spacer",
12238
+ "share",
12239
+ "settings",
12240
+ "captions",
12241
+ "chromecast",
12242
+ "airplay",
12243
+ "pip",
12244
+ "fullscreen"
12245
+ ];
11394
12246
  async function createEmbedPlayer(container, config, pluginCreators2, availableTypes) {
11395
12247
  const type = config.type || "video";
11396
12248
  if (!availableTypes.includes(type)) {
@@ -11442,6 +12294,13 @@ async function createEmbedPlayer(container, config, pluginCreators2, availableTy
11442
12294
  if (type === "video" && pluginCreators2.gestures && config.gestures !== false) {
11443
12295
  plugins.push(pluginCreators2.gestures({}));
11444
12296
  }
12297
+ const shareEnabled = type === "video" && Boolean(config.shareUrl) && config.controls !== false;
12298
+ if (shareEnabled && pluginCreators2.share) {
12299
+ const shareConfig = { url: config.shareUrl };
12300
+ if (config.title) shareConfig.title = config.title;
12301
+ if (config.embedBaseUrl) shareConfig.embedBaseUrl = config.embedBaseUrl;
12302
+ plugins.push(pluginCreators2.share(shareConfig));
12303
+ }
11445
12304
  if (pluginCreators2.analytics && config.analytics?.beaconUrl) {
11446
12305
  plugins.push(pluginCreators2.analytics({
11447
12306
  beaconUrl: config.analytics.beaconUrl,
@@ -11455,6 +12314,7 @@ async function createEmbedPlayer(container, config, pluginCreators2, availableTy
11455
12314
  if (Object.keys(theme).length > 0) uiConfig.theme = theme;
11456
12315
  if (config.hideDelay !== void 0) uiConfig.hideDelay = config.hideDelay;
11457
12316
  if (config.bigPlayButton !== void 0) uiConfig.bigPlayButton = config.bigPlayButton;
12317
+ if (shareEnabled && pluginCreators2.share) uiConfig.controls = SHARE_CONTROL_LAYOUT;
11458
12318
  plugins.push(pluginCreators2.videoUI(uiConfig));
11459
12319
  } else if ((type === "audio" || type === "audio-mini") && pluginCreators2.audioUI) {
11460
12320
  plugins.push(pluginCreators2.audioUI({
@@ -11560,7 +12420,7 @@ function setupAutoInit(pluginCreators2, availableTypes) {
11560
12420
  }
11561
12421
  }
11562
12422
  }
11563
- const PKG_VERSION = "1.7.1";
12423
+ const PKG_VERSION = "1.8.1";
11564
12424
  const VERSION = PKG_VERSION;
11565
12425
  const AVAILABLE_TYPES = ["video", "audio", "audio-mini"];
11566
12426
  const pluginCreators = {
@@ -11573,7 +12433,8 @@ const pluginCreators = {
11573
12433
  mediaSession: createMediaSessionPlugin,
11574
12434
  watermark: createWatermarkPlugin,
11575
12435
  captions: createCaptionsPlugin,
11576
- gestures: createGesturesPlugin
12436
+ gestures: createGesturesPlugin,
12437
+ share: createSharePlugin
11577
12438
  };
11578
12439
  const ScarlettPlayerAPI = createScarlettPlayerAPI(
11579
12440
  pluginCreators,