@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/README.md +91 -22
- package/dist/create-embed.d.ts +9 -0
- package/dist/create-embed.d.ts.map +1 -1
- package/dist/embed.audio.index.js.map +1 -1
- package/dist/embed.audio.js +42 -6
- package/dist/embed.audio.js.map +1 -1
- package/dist/embed.audio.umd.cjs +1 -1
- package/dist/embed.audio.umd.cjs.map +1 -1
- package/dist/embed.js +935 -74
- package/dist/embed.js.map +1 -1
- package/dist/embed.umd.cjs +1 -1
- package/dist/embed.umd.cjs.map +1 -1
- package/dist/embed.video.js +946 -80
- package/dist/embed.video.js.map +1 -1
- package/dist/embed.video.umd.cjs +1 -1
- package/dist/embed.video.umd.cjs.map +1 -1
- package/dist/index-audio.d.ts.map +1 -1
- package/dist/index-video.d.ts +1 -0
- package/dist/index-video.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/types.d.ts +24 -0
- package/dist/types.d.ts.map +1 -1
- package/iframe.html +19 -0
- package/package.json +13 -12
package/dist/embed.video.js
CHANGED
|
@@ -932,9 +932,9 @@ class Logger {
|
|
|
932
932
|
* ```
|
|
933
933
|
*/
|
|
934
934
|
removeHandler(handler) {
|
|
935
|
-
const
|
|
936
|
-
if (
|
|
937
|
-
this.handlers.splice(
|
|
935
|
+
const index2 = this.handlers.indexOf(handler);
|
|
936
|
+
if (index2 !== -1) {
|
|
937
|
+
this.handlers.splice(index2, 1);
|
|
938
938
|
}
|
|
939
939
|
}
|
|
940
940
|
/**
|
|
@@ -2148,7 +2148,7 @@ class ScarlettPlayer {
|
|
|
2148
2148
|
* Set quality level (-1 for auto).
|
|
2149
2149
|
* @param index - Quality level index
|
|
2150
2150
|
*/
|
|
2151
|
-
setQuality(
|
|
2151
|
+
setQuality(index2) {
|
|
2152
2152
|
this.checkDestroyed();
|
|
2153
2153
|
if (!this._currentProvider) {
|
|
2154
2154
|
this.logger.warn("No provider available for quality change");
|
|
@@ -2156,17 +2156,17 @@ class ScarlettPlayer {
|
|
|
2156
2156
|
}
|
|
2157
2157
|
const provider = this._currentProvider;
|
|
2158
2158
|
if (typeof provider.setLevel === "function") {
|
|
2159
|
-
if (
|
|
2159
|
+
if (index2 !== -1) {
|
|
2160
2160
|
const levels = this.getQualities();
|
|
2161
|
-
if (levels.length > 0 && (
|
|
2162
|
-
this.logger.warn(`Invalid quality index: ${
|
|
2161
|
+
if (levels.length > 0 && (index2 < 0 || index2 >= levels.length)) {
|
|
2162
|
+
this.logger.warn(`Invalid quality index: ${index2} (available: ${levels.length})`);
|
|
2163
2163
|
return;
|
|
2164
2164
|
}
|
|
2165
2165
|
}
|
|
2166
|
-
provider.setLevel(
|
|
2166
|
+
provider.setLevel(index2);
|
|
2167
2167
|
this.eventBus.emit("quality:change", {
|
|
2168
|
-
quality:
|
|
2169
|
-
auto:
|
|
2168
|
+
quality: index2 === -1 ? "auto" : `level-${index2}`,
|
|
2169
|
+
auto: index2 === -1
|
|
2170
2170
|
});
|
|
2171
2171
|
}
|
|
2172
2172
|
}
|
|
@@ -2557,8 +2557,8 @@ function formatBitrate(bitrate) {
|
|
|
2557
2557
|
return `${bitrate} bps`;
|
|
2558
2558
|
}
|
|
2559
2559
|
function mapLevels(levels, _currentLevel) {
|
|
2560
|
-
return levels.map((level,
|
|
2561
|
-
index,
|
|
2560
|
+
return levels.map((level, index2) => ({
|
|
2561
|
+
index: index2,
|
|
2562
2562
|
width: level.width || 0,
|
|
2563
2563
|
height: level.height || 0,
|
|
2564
2564
|
bitrate: level.bitrate || 0,
|
|
@@ -2613,13 +2613,13 @@ function setupHlsEventHandlers(hls, api, callbacks) {
|
|
|
2613
2613
|
};
|
|
2614
2614
|
addHandler("hlsManifestParsed", (_event, data) => {
|
|
2615
2615
|
api.logger.debug("HLS manifest parsed", { levels: data.levels.length });
|
|
2616
|
-
const levels = data.levels.map((level,
|
|
2617
|
-
id: `level-${
|
|
2616
|
+
const levels = data.levels.map((level, index2) => ({
|
|
2617
|
+
id: `level-${index2}`,
|
|
2618
2618
|
label: formatLevel(level),
|
|
2619
2619
|
width: level.width,
|
|
2620
2620
|
height: level.height,
|
|
2621
2621
|
bitrate: level.bitrate,
|
|
2622
|
-
active:
|
|
2622
|
+
active: index2 === hls.currentLevel
|
|
2623
2623
|
}));
|
|
2624
2624
|
api.setState("qualities", levels);
|
|
2625
2625
|
api.emit("quality:levels", {
|
|
@@ -2895,7 +2895,7 @@ function createValidatingPlaylistLoader(Hls) {
|
|
|
2895
2895
|
}
|
|
2896
2896
|
};
|
|
2897
2897
|
}
|
|
2898
|
-
var PKG_VERSION$
|
|
2898
|
+
var PKG_VERSION$7 = "1.8.1";
|
|
2899
2899
|
var DEFAULT_CONFIG = {
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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$
|
|
3515
|
+
version: PKG_VERSION$7,
|
|
3516
3516
|
type: "provider",
|
|
3517
3517
|
description: variant.description,
|
|
3518
3518
|
canPlay(src) {
|
|
@@ -3669,12 +3669,12 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
3669
3669
|
if (isNative || !hls) return -1;
|
|
3670
3670
|
return hls.currentLevel;
|
|
3671
3671
|
},
|
|
3672
|
-
setLevel(
|
|
3672
|
+
setLevel(index2) {
|
|
3673
3673
|
if (isNative || !hls) {
|
|
3674
3674
|
api?.logger.warn("Quality selection not available in native HLS mode");
|
|
3675
3675
|
return;
|
|
3676
3676
|
}
|
|
3677
|
-
hls.currentLevel =
|
|
3677
|
+
hls.currentLevel = index2;
|
|
3678
3678
|
},
|
|
3679
3679
|
getLevels() {
|
|
3680
3680
|
if (isNative || !hls) return [];
|
|
@@ -3859,7 +3859,7 @@ function createHLSPlugin(config) {
|
|
|
3859
3859
|
config
|
|
3860
3860
|
);
|
|
3861
3861
|
}
|
|
3862
|
-
var PKG_VERSION$
|
|
3862
|
+
var PKG_VERSION$6 = "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$
|
|
4101
|
+
version: PKG_VERSION$6,
|
|
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((
|
|
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
|
-
|
|
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$
|
|
4317
|
+
var styles$2 = `
|
|
4318
4318
|
/* ============================================
|
|
4319
4319
|
Container & Base
|
|
4320
4320
|
============================================ */
|
|
@@ -5403,7 +5403,7 @@ var styles$1 = `
|
|
|
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
|
}
|
|
@@ -5677,9 +5677,9 @@ var ThumbnailPreview = class {
|
|
|
5677
5677
|
return;
|
|
5678
5678
|
}
|
|
5679
5679
|
const { src, width, height, columns, interval } = this.config;
|
|
5680
|
-
const
|
|
5681
|
-
const col =
|
|
5682
|
-
const row = Math.floor(
|
|
5680
|
+
const index2 = Math.floor(time / interval);
|
|
5681
|
+
const col = index2 % columns;
|
|
5682
|
+
const row = Math.floor(index2 / columns);
|
|
5683
5683
|
this.img.style.backgroundImage = `url(${src})`;
|
|
5684
5684
|
this.img.style.backgroundPosition = `-${col * width}px -${row * height}px`;
|
|
5685
5685
|
this.img.style.backgroundSize = `${columns * width}px auto`;
|
|
@@ -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);
|
|
@@ -7425,6 +7425,12 @@ var OverflowTray = class {
|
|
|
7425
7425
|
};
|
|
7426
7426
|
var registry = /* @__PURE__ */ new Map();
|
|
7427
7427
|
var listeners = /* @__PURE__ */ new Set();
|
|
7428
|
+
function registerControl(id, factory) {
|
|
7429
|
+
registry.set(id, factory);
|
|
7430
|
+
for (const listener of listeners) {
|
|
7431
|
+
listener(id);
|
|
7432
|
+
}
|
|
7433
|
+
}
|
|
7428
7434
|
function getControlFactory(id) {
|
|
7429
7435
|
return registry.get(id) ?? null;
|
|
7430
7436
|
}
|
|
@@ -7434,7 +7440,7 @@ function onControlRegistered(listener) {
|
|
|
7434
7440
|
listeners.delete(listener);
|
|
7435
7441
|
};
|
|
7436
7442
|
}
|
|
7437
|
-
var PKG_VERSION$
|
|
7443
|
+
var PKG_VERSION$5 = "1.8.1";
|
|
7438
7444
|
var DEFAULT_LAYOUT = [
|
|
7439
7445
|
"play",
|
|
7440
7446
|
"skip-backward",
|
|
@@ -7868,11 +7874,11 @@ function uiPlugin(config = {}) {
|
|
|
7868
7874
|
id: "ui-controls",
|
|
7869
7875
|
name: "UI Controls",
|
|
7870
7876
|
type: "ui",
|
|
7871
|
-
version: PKG_VERSION$
|
|
7877
|
+
version: PKG_VERSION$5,
|
|
7872
7878
|
async init(pluginApi) {
|
|
7873
7879
|
api = pluginApi;
|
|
7874
7880
|
styleEl = document.createElement("style");
|
|
7875
|
-
styleEl.textContent = styles$
|
|
7881
|
+
styleEl.textContent = styles$2;
|
|
7876
7882
|
document.head.appendChild(styleEl);
|
|
7877
7883
|
if (config.theme) {
|
|
7878
7884
|
this.setTheme(config.theme);
|
|
@@ -7892,7 +7898,7 @@ function uiPlugin(config = {}) {
|
|
|
7892
7898
|
container.appendChild(gradient);
|
|
7893
7899
|
bufferingIndicator = document.createElement("div");
|
|
7894
7900
|
bufferingIndicator.className = "sp-buffering";
|
|
7895
|
-
bufferingIndicator.innerHTML = icons.spinner;
|
|
7901
|
+
bufferingIndicator.innerHTML = icons$1.spinner;
|
|
7896
7902
|
bufferingIndicator.setAttribute("aria-hidden", "true");
|
|
7897
7903
|
container.appendChild(bufferingIndicator);
|
|
7898
7904
|
errorOverlay = new ErrorOverlay(api);
|
|
@@ -8047,7 +8053,21 @@ function uiPlugin(config = {}) {
|
|
|
8047
8053
|
}
|
|
8048
8054
|
};
|
|
8049
8055
|
}
|
|
8050
|
-
|
|
8056
|
+
const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
8057
|
+
__proto__: null,
|
|
8058
|
+
DEFAULT_PRIORITY,
|
|
8059
|
+
assertFitLayout,
|
|
8060
|
+
formatLiveTime,
|
|
8061
|
+
formatTime,
|
|
8062
|
+
getControlFactory,
|
|
8063
|
+
icons: icons$1,
|
|
8064
|
+
planFit,
|
|
8065
|
+
registerControl,
|
|
8066
|
+
resolveFitItems,
|
|
8067
|
+
styles: styles$2,
|
|
8068
|
+
uiPlugin
|
|
8069
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
8070
|
+
var PKG_VERSION$4 = "1.8.1";
|
|
8051
8071
|
var POSITIONS = ["top-left", "top-right", "bottom-left", "bottom-right", "center"];
|
|
8052
8072
|
function getPositionStyles(padding, bottomPadding) {
|
|
8053
8073
|
return {
|
|
@@ -8157,7 +8177,7 @@ function createWatermarkPlugin(config = {}) {
|
|
|
8157
8177
|
return {
|
|
8158
8178
|
id: "watermark",
|
|
8159
8179
|
name: "Watermark",
|
|
8160
|
-
version: PKG_VERSION$
|
|
8180
|
+
version: PKG_VERSION$4,
|
|
8161
8181
|
type: "feature",
|
|
8162
8182
|
description: "Anti-piracy watermark overlay with text/image support and dynamic repositioning",
|
|
8163
8183
|
init(pluginApi) {
|
|
@@ -8242,7 +8262,7 @@ function createWatermarkPlugin(config = {}) {
|
|
|
8242
8262
|
}
|
|
8243
8263
|
};
|
|
8244
8264
|
}
|
|
8245
|
-
var PKG_VERSION$
|
|
8265
|
+
var PKG_VERSION$3 = "1.8.1";
|
|
8246
8266
|
var HLS_SUBTITLE_TRACKS_UPDATED = "hlsSubtitleTracksUpdated";
|
|
8247
8267
|
var HLS_INSTANCE_RETRY_MS = 500;
|
|
8248
8268
|
function createCaptionsPlugin(config = {}) {
|
|
@@ -8413,7 +8433,7 @@ function createCaptionsPlugin(config = {}) {
|
|
|
8413
8433
|
return {
|
|
8414
8434
|
id: "captions",
|
|
8415
8435
|
name: "Captions",
|
|
8416
|
-
version: PKG_VERSION$
|
|
8436
|
+
version: PKG_VERSION$3,
|
|
8417
8437
|
type: "feature",
|
|
8418
8438
|
description: "WebVTT subtitles and closed captions with HLS extraction",
|
|
8419
8439
|
init(pluginApi) {
|
|
@@ -8608,8 +8628,8 @@ function createRecognizer(options = {}) {
|
|
|
8608
8628
|
}
|
|
8609
8629
|
};
|
|
8610
8630
|
}
|
|
8611
|
-
var STYLE_ID = "sp-gestures-styles";
|
|
8612
|
-
var styles = `
|
|
8631
|
+
var STYLE_ID$1 = "sp-gestures-styles";
|
|
8632
|
+
var styles$1 = `
|
|
8613
8633
|
.sp-gestures {
|
|
8614
8634
|
position: absolute;
|
|
8615
8635
|
top: 0;
|
|
@@ -8772,14 +8792,14 @@ var GestureOverlay = class {
|
|
|
8772
8792
|
return { zone, label };
|
|
8773
8793
|
}
|
|
8774
8794
|
injectStyles() {
|
|
8775
|
-
if (document.getElementById(STYLE_ID)) return;
|
|
8795
|
+
if (document.getElementById(STYLE_ID$1)) return;
|
|
8776
8796
|
this.styleEl = document.createElement("style");
|
|
8777
|
-
this.styleEl.id = STYLE_ID;
|
|
8778
|
-
this.styleEl.textContent = styles;
|
|
8797
|
+
this.styleEl.id = STYLE_ID$1;
|
|
8798
|
+
this.styleEl.textContent = styles$1;
|
|
8779
8799
|
document.head.appendChild(this.styleEl);
|
|
8780
8800
|
}
|
|
8781
8801
|
};
|
|
8782
|
-
var PKG_VERSION$
|
|
8802
|
+
var PKG_VERSION$2 = "1.8.1";
|
|
8783
8803
|
function hasCoarsePointer() {
|
|
8784
8804
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
8785
8805
|
return false;
|
|
@@ -8903,7 +8923,7 @@ function createGesturesPlugin(config = {}) {
|
|
|
8903
8923
|
return {
|
|
8904
8924
|
id: "gestures",
|
|
8905
8925
|
name: "Gestures",
|
|
8906
|
-
version: PKG_VERSION$
|
|
8926
|
+
version: PKG_VERSION$2,
|
|
8907
8927
|
type: "feature",
|
|
8908
8928
|
init(pluginApi) {
|
|
8909
8929
|
api = pluginApi;
|
|
@@ -8949,6 +8969,818 @@ function createGesturesPlugin(config = {}) {
|
|
|
8949
8969
|
}
|
|
8950
8970
|
};
|
|
8951
8971
|
}
|
|
8972
|
+
function resolve(value, fallback) {
|
|
8973
|
+
if (typeof value === "function") {
|
|
8974
|
+
return value();
|
|
8975
|
+
}
|
|
8976
|
+
if (typeof value === "string" && value.length > 0) {
|
|
8977
|
+
return value;
|
|
8978
|
+
}
|
|
8979
|
+
return fallback();
|
|
8980
|
+
}
|
|
8981
|
+
function resolveBaseUrl(config) {
|
|
8982
|
+
return resolve(
|
|
8983
|
+
config.url,
|
|
8984
|
+
() => typeof window === "undefined" ? "" : window.location.href
|
|
8985
|
+
);
|
|
8986
|
+
}
|
|
8987
|
+
function resolveTitle(config) {
|
|
8988
|
+
return resolve(config.title, () => typeof document === "undefined" ? "" : document.title);
|
|
8989
|
+
}
|
|
8990
|
+
function applyTimestamp(baseUrl, currentTime, isLive, config) {
|
|
8991
|
+
const enabled = config.withTimestamp !== false;
|
|
8992
|
+
if (!enabled || isLive || !Number.isFinite(currentTime) || currentTime <= 0) {
|
|
8993
|
+
return baseUrl;
|
|
8994
|
+
}
|
|
8995
|
+
const param = config.timestampParam ?? "t";
|
|
8996
|
+
const rounded = config.roundTimestamp === false ? currentTime : Math.floor(currentTime);
|
|
8997
|
+
if (rounded <= 0) {
|
|
8998
|
+
return baseUrl;
|
|
8999
|
+
}
|
|
9000
|
+
try {
|
|
9001
|
+
const base = typeof window === "undefined" ? void 0 : window.location.href;
|
|
9002
|
+
const url = new URL(baseUrl, base);
|
|
9003
|
+
url.searchParams.set(param, String(rounded));
|
|
9004
|
+
return url.toString();
|
|
9005
|
+
} catch {
|
|
9006
|
+
return baseUrl;
|
|
9007
|
+
}
|
|
9008
|
+
}
|
|
9009
|
+
var icons = {
|
|
9010
|
+
/**
|
|
9011
|
+
* Three connected nodes. The one share glyph that reads the same everywhere:
|
|
9012
|
+
* it is the Material/Android share icon, and it carries no platform baggage.
|
|
9013
|
+
* The tray-and-arrow below is native on iOS but reads as "upload" or "export"
|
|
9014
|
+
* to everyone else, which is why it is no longer the default.
|
|
9015
|
+
*/
|
|
9016
|
+
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>',
|
|
9017
|
+
/** iOS-style tray and arrow. Kept for hosts targeting an iOS-heavy audience. */
|
|
9018
|
+
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>',
|
|
9019
|
+
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>',
|
|
9020
|
+
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>',
|
|
9021
|
+
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>',
|
|
9022
|
+
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>',
|
|
9023
|
+
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>',
|
|
9024
|
+
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>',
|
|
9025
|
+
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>',
|
|
9026
|
+
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>',
|
|
9027
|
+
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>',
|
|
9028
|
+
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>'
|
|
9029
|
+
};
|
|
9030
|
+
var encode = encodeURIComponent;
|
|
9031
|
+
var BUILTIN_TARGETS = {
|
|
9032
|
+
native: { id: "native", label: "Share", icon: icons.native },
|
|
9033
|
+
copy: { id: "copy", label: "Copy link", icon: icons.copy },
|
|
9034
|
+
embed: { id: "embed", label: "Embed", icon: icons.embed },
|
|
9035
|
+
x: {
|
|
9036
|
+
id: "x",
|
|
9037
|
+
label: "X",
|
|
9038
|
+
icon: icons.x,
|
|
9039
|
+
href: (c) => `https://x.com/intent/tweet?url=${encode(c.url)}&text=${encode(c.title)}`
|
|
9040
|
+
},
|
|
9041
|
+
facebook: {
|
|
9042
|
+
id: "facebook",
|
|
9043
|
+
label: "Facebook",
|
|
9044
|
+
icon: icons.facebook,
|
|
9045
|
+
href: (c) => `https://www.facebook.com/sharer/sharer.php?u=${encode(c.url)}`
|
|
9046
|
+
},
|
|
9047
|
+
linkedin: {
|
|
9048
|
+
id: "linkedin",
|
|
9049
|
+
label: "LinkedIn",
|
|
9050
|
+
icon: icons.linkedin,
|
|
9051
|
+
href: (c) => `https://www.linkedin.com/sharing/share-offsite/?url=${encode(c.url)}`
|
|
9052
|
+
},
|
|
9053
|
+
whatsapp: {
|
|
9054
|
+
id: "whatsapp",
|
|
9055
|
+
label: "WhatsApp",
|
|
9056
|
+
icon: icons.whatsapp,
|
|
9057
|
+
href: (c) => `https://api.whatsapp.com/send?text=${encode(`${c.title} ${c.url}`)}`
|
|
9058
|
+
},
|
|
9059
|
+
telegram: {
|
|
9060
|
+
id: "telegram",
|
|
9061
|
+
label: "Telegram",
|
|
9062
|
+
icon: icons.telegram,
|
|
9063
|
+
href: (c) => `https://t.me/share/url?url=${encode(c.url)}&text=${encode(c.title)}`
|
|
9064
|
+
},
|
|
9065
|
+
reddit: {
|
|
9066
|
+
id: "reddit",
|
|
9067
|
+
label: "Reddit",
|
|
9068
|
+
icon: icons.reddit,
|
|
9069
|
+
href: (c) => `https://www.reddit.com/submit?url=${encode(c.url)}&title=${encode(c.title)}`
|
|
9070
|
+
},
|
|
9071
|
+
email: {
|
|
9072
|
+
id: "email",
|
|
9073
|
+
label: "Email",
|
|
9074
|
+
icon: icons.email,
|
|
9075
|
+
href: (c) => `mailto:?subject=${encode(c.title)}&body=${encode(c.url)}`
|
|
9076
|
+
}
|
|
9077
|
+
};
|
|
9078
|
+
var DEFAULT_TARGETS = ["native", "copy", "embed"];
|
|
9079
|
+
function resolveTargets(configured, onUnknown) {
|
|
9080
|
+
const resolved = [];
|
|
9081
|
+
for (const entry of configured) {
|
|
9082
|
+
if (typeof entry !== "string") {
|
|
9083
|
+
resolved.push({ ...entry, icon: entry.icon ?? icons[entry.id] });
|
|
9084
|
+
continue;
|
|
9085
|
+
}
|
|
9086
|
+
const builtin = BUILTIN_TARGETS[entry];
|
|
9087
|
+
if (builtin) {
|
|
9088
|
+
resolved.push(builtin);
|
|
9089
|
+
} else {
|
|
9090
|
+
onUnknown?.(entry);
|
|
9091
|
+
}
|
|
9092
|
+
}
|
|
9093
|
+
return resolved;
|
|
9094
|
+
}
|
|
9095
|
+
function canUseNativeShare() {
|
|
9096
|
+
return typeof navigator !== "undefined" && typeof navigator.share === "function";
|
|
9097
|
+
}
|
|
9098
|
+
async function nativeShare(context) {
|
|
9099
|
+
try {
|
|
9100
|
+
await navigator.share({ title: context.title, url: context.url });
|
|
9101
|
+
return true;
|
|
9102
|
+
} catch (error) {
|
|
9103
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
9104
|
+
return false;
|
|
9105
|
+
}
|
|
9106
|
+
throw error;
|
|
9107
|
+
}
|
|
9108
|
+
}
|
|
9109
|
+
async function copyText(text) {
|
|
9110
|
+
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
9111
|
+
try {
|
|
9112
|
+
await navigator.clipboard.writeText(text);
|
|
9113
|
+
return true;
|
|
9114
|
+
} catch {
|
|
9115
|
+
}
|
|
9116
|
+
}
|
|
9117
|
+
if (typeof document === "undefined") {
|
|
9118
|
+
return false;
|
|
9119
|
+
}
|
|
9120
|
+
const textarea = document.createElement("textarea");
|
|
9121
|
+
textarea.value = text;
|
|
9122
|
+
textarea.setAttribute("readonly", "");
|
|
9123
|
+
textarea.style.position = "fixed";
|
|
9124
|
+
textarea.style.top = "-9999px";
|
|
9125
|
+
textarea.style.opacity = "0";
|
|
9126
|
+
document.body.appendChild(textarea);
|
|
9127
|
+
try {
|
|
9128
|
+
textarea.select();
|
|
9129
|
+
textarea.setSelectionRange(0, textarea.value.length);
|
|
9130
|
+
return document.execCommand("copy");
|
|
9131
|
+
} catch {
|
|
9132
|
+
return false;
|
|
9133
|
+
} finally {
|
|
9134
|
+
textarea.remove();
|
|
9135
|
+
}
|
|
9136
|
+
}
|
|
9137
|
+
function buildEmbedSnippet(context, config) {
|
|
9138
|
+
if (config.embedSnippet) {
|
|
9139
|
+
return config.embedSnippet(context);
|
|
9140
|
+
}
|
|
9141
|
+
if (!config.embedBaseUrl) {
|
|
9142
|
+
return null;
|
|
9143
|
+
}
|
|
9144
|
+
try {
|
|
9145
|
+
const base = typeof window === "undefined" ? void 0 : window.location.href;
|
|
9146
|
+
const embedUrl = new URL(config.embedBaseUrl, base);
|
|
9147
|
+
if (context.currentTime > 0 && !context.isLive) {
|
|
9148
|
+
embedUrl.searchParams.set("startTime", String(Math.floor(context.currentTime)));
|
|
9149
|
+
}
|
|
9150
|
+
embedUrl.searchParams.set("shareUrl", context.url);
|
|
9151
|
+
return `<iframe src="${embedUrl.toString()}" width="640" height="360" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen loading="lazy"></iframe>`;
|
|
9152
|
+
} catch {
|
|
9153
|
+
return null;
|
|
9154
|
+
}
|
|
9155
|
+
}
|
|
9156
|
+
var TOAST_MS = 1800;
|
|
9157
|
+
var ShareSheet = class {
|
|
9158
|
+
constructor(api, callbacks) {
|
|
9159
|
+
this.api = api;
|
|
9160
|
+
this.callbacks = callbacks;
|
|
9161
|
+
this.backdrop = null;
|
|
9162
|
+
this.sheet = null;
|
|
9163
|
+
this.toast = null;
|
|
9164
|
+
this.toastTimer = null;
|
|
9165
|
+
this.previouslyFocused = null;
|
|
9166
|
+
this.open = false;
|
|
9167
|
+
this.keydownHandler = (event) => {
|
|
9168
|
+
if (event.key === "Escape") {
|
|
9169
|
+
event.stopPropagation();
|
|
9170
|
+
this.close();
|
|
9171
|
+
return;
|
|
9172
|
+
}
|
|
9173
|
+
if (event.key === "Tab") {
|
|
9174
|
+
this.trapFocus(event);
|
|
9175
|
+
}
|
|
9176
|
+
};
|
|
9177
|
+
}
|
|
9178
|
+
/** Whether the sheet is currently showing. */
|
|
9179
|
+
isOpen() {
|
|
9180
|
+
return this.open;
|
|
9181
|
+
}
|
|
9182
|
+
/**
|
|
9183
|
+
* Show the sheet for a set of targets.
|
|
9184
|
+
*/
|
|
9185
|
+
show(targets) {
|
|
9186
|
+
if (this.open) {
|
|
9187
|
+
return;
|
|
9188
|
+
}
|
|
9189
|
+
this.previouslyFocused = document.activeElement;
|
|
9190
|
+
this.open = true;
|
|
9191
|
+
this.backdrop = document.createElement("div");
|
|
9192
|
+
this.backdrop.className = "sp-share-backdrop";
|
|
9193
|
+
this.backdrop.addEventListener("click", () => this.close());
|
|
9194
|
+
this.sheet = document.createElement("div");
|
|
9195
|
+
this.sheet.className = "sp-share-sheet";
|
|
9196
|
+
this.sheet.setAttribute("role", "dialog");
|
|
9197
|
+
this.sheet.setAttribute("aria-modal", "true");
|
|
9198
|
+
this.sheet.setAttribute("aria-label", "Share");
|
|
9199
|
+
const grip = document.createElement("div");
|
|
9200
|
+
grip.className = "sp-share-grip";
|
|
9201
|
+
this.sheet.appendChild(grip);
|
|
9202
|
+
const heading = document.createElement("p");
|
|
9203
|
+
heading.className = "sp-share-title";
|
|
9204
|
+
heading.textContent = "Share";
|
|
9205
|
+
this.sheet.appendChild(heading);
|
|
9206
|
+
const list = document.createElement("ul");
|
|
9207
|
+
list.className = "sp-share-targets";
|
|
9208
|
+
for (const target of targets) {
|
|
9209
|
+
list.appendChild(this.renderTarget(target));
|
|
9210
|
+
}
|
|
9211
|
+
this.sheet.appendChild(list);
|
|
9212
|
+
this.api.container.appendChild(this.backdrop);
|
|
9213
|
+
this.api.container.appendChild(this.sheet);
|
|
9214
|
+
requestAnimationFrame(() => {
|
|
9215
|
+
this.backdrop?.classList.add("sp-share-backdrop--open");
|
|
9216
|
+
this.sheet?.classList.add("sp-share-sheet--open");
|
|
9217
|
+
});
|
|
9218
|
+
document.addEventListener("keydown", this.keydownHandler, true);
|
|
9219
|
+
const first = this.sheet.querySelector(".sp-share-target");
|
|
9220
|
+
first?.focus();
|
|
9221
|
+
}
|
|
9222
|
+
/** Hide the sheet and restore focus. */
|
|
9223
|
+
close() {
|
|
9224
|
+
if (!this.open) {
|
|
9225
|
+
return;
|
|
9226
|
+
}
|
|
9227
|
+
this.open = false;
|
|
9228
|
+
document.removeEventListener("keydown", this.keydownHandler, true);
|
|
9229
|
+
this.backdrop?.classList.remove("sp-share-backdrop--open");
|
|
9230
|
+
this.sheet?.classList.remove("sp-share-sheet--open");
|
|
9231
|
+
const backdrop = this.backdrop;
|
|
9232
|
+
const sheet = this.sheet;
|
|
9233
|
+
this.backdrop = null;
|
|
9234
|
+
this.sheet = null;
|
|
9235
|
+
setTimeout(() => {
|
|
9236
|
+
backdrop?.remove();
|
|
9237
|
+
sheet?.remove();
|
|
9238
|
+
}, 220);
|
|
9239
|
+
this.previouslyFocused?.focus();
|
|
9240
|
+
this.previouslyFocused = null;
|
|
9241
|
+
this.callbacks.onClose();
|
|
9242
|
+
}
|
|
9243
|
+
/**
|
|
9244
|
+
* Show a transient confirmation, e.g. after copying.
|
|
9245
|
+
*/
|
|
9246
|
+
showToast(message) {
|
|
9247
|
+
if (this.toastTimer) {
|
|
9248
|
+
clearTimeout(this.toastTimer);
|
|
9249
|
+
this.toastTimer = null;
|
|
9250
|
+
}
|
|
9251
|
+
if (!this.toast) {
|
|
9252
|
+
this.toast = document.createElement("div");
|
|
9253
|
+
this.toast.className = "sp-share-toast";
|
|
9254
|
+
this.toast.setAttribute("role", "status");
|
|
9255
|
+
this.toast.setAttribute("aria-live", "polite");
|
|
9256
|
+
this.api.container.appendChild(this.toast);
|
|
9257
|
+
}
|
|
9258
|
+
this.toast.textContent = message;
|
|
9259
|
+
requestAnimationFrame(() => this.toast?.classList.add("sp-share-toast--visible"));
|
|
9260
|
+
this.toastTimer = setTimeout(() => {
|
|
9261
|
+
this.toast?.classList.remove("sp-share-toast--visible");
|
|
9262
|
+
this.toastTimer = null;
|
|
9263
|
+
}, TOAST_MS);
|
|
9264
|
+
}
|
|
9265
|
+
/**
|
|
9266
|
+
* Last-resort clipboard fallback: show the text for manual copying.
|
|
9267
|
+
*/
|
|
9268
|
+
showManualCopy(text) {
|
|
9269
|
+
if (!this.sheet) {
|
|
9270
|
+
return;
|
|
9271
|
+
}
|
|
9272
|
+
const row = document.createElement("div");
|
|
9273
|
+
row.className = "sp-share-fallback";
|
|
9274
|
+
const input = document.createElement("input");
|
|
9275
|
+
input.type = "text";
|
|
9276
|
+
input.readOnly = true;
|
|
9277
|
+
input.value = text;
|
|
9278
|
+
input.setAttribute("aria-label", "Link to copy");
|
|
9279
|
+
row.appendChild(input);
|
|
9280
|
+
this.sheet.appendChild(row);
|
|
9281
|
+
input.focus();
|
|
9282
|
+
input.select();
|
|
9283
|
+
}
|
|
9284
|
+
/** Remove everything and detach listeners. */
|
|
9285
|
+
destroy() {
|
|
9286
|
+
document.removeEventListener("keydown", this.keydownHandler, true);
|
|
9287
|
+
if (this.toastTimer) {
|
|
9288
|
+
clearTimeout(this.toastTimer);
|
|
9289
|
+
this.toastTimer = null;
|
|
9290
|
+
}
|
|
9291
|
+
this.backdrop?.remove();
|
|
9292
|
+
this.sheet?.remove();
|
|
9293
|
+
this.toast?.remove();
|
|
9294
|
+
this.backdrop = null;
|
|
9295
|
+
this.sheet = null;
|
|
9296
|
+
this.toast = null;
|
|
9297
|
+
this.open = false;
|
|
9298
|
+
this.previouslyFocused = null;
|
|
9299
|
+
}
|
|
9300
|
+
/** Build one target button. */
|
|
9301
|
+
renderTarget(target) {
|
|
9302
|
+
const item = document.createElement("li");
|
|
9303
|
+
const button = document.createElement("button");
|
|
9304
|
+
button.type = "button";
|
|
9305
|
+
button.className = `sp-share-target sp-share-target--${target.id}`;
|
|
9306
|
+
button.setAttribute("aria-label", target.label);
|
|
9307
|
+
const icon = document.createElement("span");
|
|
9308
|
+
icon.className = "sp-share-icon";
|
|
9309
|
+
icon.setAttribute("aria-hidden", "true");
|
|
9310
|
+
if (target.icon) {
|
|
9311
|
+
icon.innerHTML = target.icon;
|
|
9312
|
+
}
|
|
9313
|
+
const label = document.createElement("span");
|
|
9314
|
+
label.className = "sp-share-label";
|
|
9315
|
+
label.textContent = target.label;
|
|
9316
|
+
button.appendChild(icon);
|
|
9317
|
+
button.appendChild(label);
|
|
9318
|
+
button.addEventListener("click", () => this.callbacks.onSelect(target));
|
|
9319
|
+
item.appendChild(button);
|
|
9320
|
+
return item;
|
|
9321
|
+
}
|
|
9322
|
+
/** Keep Tab inside the dialog while it is open. */
|
|
9323
|
+
trapFocus(event) {
|
|
9324
|
+
if (!this.sheet) {
|
|
9325
|
+
return;
|
|
9326
|
+
}
|
|
9327
|
+
const focusable = this.sheet.querySelectorAll('button, input, [tabindex]:not([tabindex="-1"])');
|
|
9328
|
+
if (focusable.length === 0) {
|
|
9329
|
+
return;
|
|
9330
|
+
}
|
|
9331
|
+
const first = focusable[0];
|
|
9332
|
+
const last = focusable[focusable.length - 1];
|
|
9333
|
+
const active = document.activeElement;
|
|
9334
|
+
if (event.shiftKey && active === first) {
|
|
9335
|
+
event.preventDefault();
|
|
9336
|
+
last?.focus();
|
|
9337
|
+
} else if (!event.shiftKey && active === last) {
|
|
9338
|
+
event.preventDefault();
|
|
9339
|
+
first?.focus();
|
|
9340
|
+
}
|
|
9341
|
+
}
|
|
9342
|
+
};
|
|
9343
|
+
var ALLOWED_ELEMENTS = /* @__PURE__ */ new Set([
|
|
9344
|
+
"svg",
|
|
9345
|
+
"g",
|
|
9346
|
+
"path",
|
|
9347
|
+
"circle",
|
|
9348
|
+
"ellipse",
|
|
9349
|
+
"line",
|
|
9350
|
+
"polyline",
|
|
9351
|
+
"polygon",
|
|
9352
|
+
"rect",
|
|
9353
|
+
"defs",
|
|
9354
|
+
"lineargradient",
|
|
9355
|
+
"radialgradient",
|
|
9356
|
+
"stop",
|
|
9357
|
+
"clippath",
|
|
9358
|
+
"mask",
|
|
9359
|
+
"use",
|
|
9360
|
+
"symbol",
|
|
9361
|
+
"title",
|
|
9362
|
+
"desc"
|
|
9363
|
+
]);
|
|
9364
|
+
function sanitizeIcon(markup) {
|
|
9365
|
+
if (typeof markup !== "string" || markup.trim() === "") {
|
|
9366
|
+
return null;
|
|
9367
|
+
}
|
|
9368
|
+
if (typeof DOMParser === "undefined") {
|
|
9369
|
+
return null;
|
|
9370
|
+
}
|
|
9371
|
+
const doc = new DOMParser().parseFromString(markup, "text/html");
|
|
9372
|
+
const svg = doc.body.querySelector("svg");
|
|
9373
|
+
if (!svg) {
|
|
9374
|
+
return null;
|
|
9375
|
+
}
|
|
9376
|
+
for (const element of Array.from(svg.querySelectorAll("*"))) {
|
|
9377
|
+
if (!ALLOWED_ELEMENTS.has(element.nodeName.toLowerCase())) {
|
|
9378
|
+
element.remove();
|
|
9379
|
+
continue;
|
|
9380
|
+
}
|
|
9381
|
+
stripDangerousAttributes(element);
|
|
9382
|
+
}
|
|
9383
|
+
stripDangerousAttributes(svg);
|
|
9384
|
+
return svg.outerHTML;
|
|
9385
|
+
}
|
|
9386
|
+
function stripDangerousAttributes(element) {
|
|
9387
|
+
for (const attribute of Array.from(element.attributes)) {
|
|
9388
|
+
const name = attribute.name.toLowerCase();
|
|
9389
|
+
if (name.startsWith("on")) {
|
|
9390
|
+
element.removeAttribute(attribute.name);
|
|
9391
|
+
continue;
|
|
9392
|
+
}
|
|
9393
|
+
if (name === "href" || name === "xlink:href" || name === "src") {
|
|
9394
|
+
const value = attribute.value.replace(/\s+/g, "").toLowerCase();
|
|
9395
|
+
if (value.startsWith("javascript:") || value.startsWith("data:text/html")) {
|
|
9396
|
+
element.removeAttribute(attribute.name);
|
|
9397
|
+
}
|
|
9398
|
+
}
|
|
9399
|
+
}
|
|
9400
|
+
}
|
|
9401
|
+
var ShareButton = class {
|
|
9402
|
+
constructor(_api, onActivate, options = {}) {
|
|
9403
|
+
this.onActivate = onActivate;
|
|
9404
|
+
this.clickHandler = () => {
|
|
9405
|
+
this.onActivate();
|
|
9406
|
+
};
|
|
9407
|
+
const label = options.label ?? "Share";
|
|
9408
|
+
this.el = document.createElement("button");
|
|
9409
|
+
this.el.type = "button";
|
|
9410
|
+
this.el.className = "sp-share sp-control";
|
|
9411
|
+
this.el.setAttribute("aria-label", label);
|
|
9412
|
+
this.el.setAttribute("title", label);
|
|
9413
|
+
this.el.setAttribute("aria-haspopup", "dialog");
|
|
9414
|
+
const icon = options.icon ? sanitizeIcon(options.icon) : null;
|
|
9415
|
+
this.el.innerHTML = icon ?? icons.share ?? "";
|
|
9416
|
+
this.el.addEventListener("click", this.clickHandler);
|
|
9417
|
+
}
|
|
9418
|
+
render() {
|
|
9419
|
+
return this.el;
|
|
9420
|
+
}
|
|
9421
|
+
update() {
|
|
9422
|
+
}
|
|
9423
|
+
destroy() {
|
|
9424
|
+
this.el.removeEventListener("click", this.clickHandler);
|
|
9425
|
+
this.el.remove();
|
|
9426
|
+
}
|
|
9427
|
+
};
|
|
9428
|
+
var styles = `
|
|
9429
|
+
.sp-share-backdrop {
|
|
9430
|
+
position: absolute;
|
|
9431
|
+
inset: 0;
|
|
9432
|
+
background: rgba(0, 0, 0, 0.55);
|
|
9433
|
+
opacity: 0;
|
|
9434
|
+
transition: opacity 180ms ease;
|
|
9435
|
+
z-index: 30;
|
|
9436
|
+
}
|
|
9437
|
+
.sp-share-backdrop--open { opacity: 1; }
|
|
9438
|
+
|
|
9439
|
+
.sp-share-sheet {
|
|
9440
|
+
position: absolute;
|
|
9441
|
+
left: 0;
|
|
9442
|
+
right: 0;
|
|
9443
|
+
bottom: 0;
|
|
9444
|
+
z-index: 31;
|
|
9445
|
+
background: #1c1c1e;
|
|
9446
|
+
color: #fff;
|
|
9447
|
+
border-radius: 14px 14px 0 0;
|
|
9448
|
+
padding: 8px 12px calc(12px + env(safe-area-inset-bottom, 0px));
|
|
9449
|
+
box-shadow: 0 -6px 28px rgba(0, 0, 0, 0.45);
|
|
9450
|
+
transform: translateY(100%);
|
|
9451
|
+
transition: transform 220ms cubic-bezier(0.32, 0.72, 0, 1);
|
|
9452
|
+
max-height: 80%;
|
|
9453
|
+
overflow-y: auto;
|
|
9454
|
+
-webkit-overflow-scrolling: touch;
|
|
9455
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
9456
|
+
}
|
|
9457
|
+
.sp-share-sheet--open { transform: translateY(0); }
|
|
9458
|
+
|
|
9459
|
+
/* Grab handle - the affordance that says "this drags/dismisses" on a phone. */
|
|
9460
|
+
.sp-share-grip {
|
|
9461
|
+
width: 36px;
|
|
9462
|
+
height: 4px;
|
|
9463
|
+
border-radius: 2px;
|
|
9464
|
+
background: rgba(255, 255, 255, 0.3);
|
|
9465
|
+
margin: 4px auto 10px;
|
|
9466
|
+
}
|
|
9467
|
+
|
|
9468
|
+
.sp-share-title {
|
|
9469
|
+
font-size: 13px;
|
|
9470
|
+
font-weight: 600;
|
|
9471
|
+
letter-spacing: 0.02em;
|
|
9472
|
+
text-transform: uppercase;
|
|
9473
|
+
color: rgba(255, 255, 255, 0.55);
|
|
9474
|
+
padding: 0 8px 8px;
|
|
9475
|
+
margin: 0;
|
|
9476
|
+
}
|
|
9477
|
+
|
|
9478
|
+
.sp-share-targets {
|
|
9479
|
+
display: grid;
|
|
9480
|
+
grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
|
|
9481
|
+
gap: 4px;
|
|
9482
|
+
list-style: none;
|
|
9483
|
+
margin: 0;
|
|
9484
|
+
padding: 0;
|
|
9485
|
+
}
|
|
9486
|
+
|
|
9487
|
+
.sp-share-target {
|
|
9488
|
+
display: flex;
|
|
9489
|
+
flex-direction: column;
|
|
9490
|
+
align-items: center;
|
|
9491
|
+
justify-content: center;
|
|
9492
|
+
gap: 8px;
|
|
9493
|
+
/* 44px is the WCAG 2.5.5 floor; 72px is a comfortable thumb target. */
|
|
9494
|
+
min-height: 72px;
|
|
9495
|
+
min-width: 44px;
|
|
9496
|
+
padding: 10px 6px;
|
|
9497
|
+
border: 0;
|
|
9498
|
+
border-radius: 10px;
|
|
9499
|
+
background: transparent;
|
|
9500
|
+
color: inherit;
|
|
9501
|
+
font: inherit;
|
|
9502
|
+
font-size: 12px;
|
|
9503
|
+
cursor: pointer;
|
|
9504
|
+
-webkit-tap-highlight-color: transparent;
|
|
9505
|
+
transition: background-color 120ms ease, transform 120ms ease;
|
|
9506
|
+
}
|
|
9507
|
+
.sp-share-target:active { transform: scale(0.94); background: rgba(255, 255, 255, 0.14); }
|
|
9508
|
+
.sp-share-target:focus-visible { outline: 2px solid #fff; outline-offset: -2px; }
|
|
9509
|
+
@media (hover: hover) {
|
|
9510
|
+
.sp-share-target:hover { background: rgba(255, 255, 255, 0.1); }
|
|
9511
|
+
}
|
|
9512
|
+
|
|
9513
|
+
.sp-share-target svg {
|
|
9514
|
+
width: 24px;
|
|
9515
|
+
height: 24px;
|
|
9516
|
+
display: block;
|
|
9517
|
+
}
|
|
9518
|
+
|
|
9519
|
+
.sp-share-icon {
|
|
9520
|
+
display: flex;
|
|
9521
|
+
align-items: center;
|
|
9522
|
+
justify-content: center;
|
|
9523
|
+
width: 44px;
|
|
9524
|
+
height: 44px;
|
|
9525
|
+
border-radius: 50%;
|
|
9526
|
+
background: rgba(255, 255, 255, 0.12);
|
|
9527
|
+
}
|
|
9528
|
+
|
|
9529
|
+
.sp-share-label {
|
|
9530
|
+
line-height: 1.2;
|
|
9531
|
+
text-align: center;
|
|
9532
|
+
max-width: 100%;
|
|
9533
|
+
overflow: hidden;
|
|
9534
|
+
text-overflow: ellipsis;
|
|
9535
|
+
white-space: nowrap;
|
|
9536
|
+
}
|
|
9537
|
+
|
|
9538
|
+
/* Copy confirmation, announced politely to assistive tech. */
|
|
9539
|
+
.sp-share-toast {
|
|
9540
|
+
position: absolute;
|
|
9541
|
+
left: 50%;
|
|
9542
|
+
bottom: 16px;
|
|
9543
|
+
transform: translateX(-50%) translateY(8px);
|
|
9544
|
+
z-index: 32;
|
|
9545
|
+
background: rgba(28, 28, 30, 0.95);
|
|
9546
|
+
color: #fff;
|
|
9547
|
+
font-size: 13px;
|
|
9548
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
9549
|
+
padding: 10px 16px;
|
|
9550
|
+
border-radius: 20px;
|
|
9551
|
+
opacity: 0;
|
|
9552
|
+
pointer-events: none;
|
|
9553
|
+
transition: opacity 160ms ease, transform 160ms ease;
|
|
9554
|
+
}
|
|
9555
|
+
.sp-share-toast--visible { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
9556
|
+
|
|
9557
|
+
/* Fallback when the clipboard is unavailable: show the URL to copy by hand. */
|
|
9558
|
+
.sp-share-fallback {
|
|
9559
|
+
display: flex;
|
|
9560
|
+
gap: 8px;
|
|
9561
|
+
padding: 8px;
|
|
9562
|
+
}
|
|
9563
|
+
.sp-share-fallback input {
|
|
9564
|
+
flex: 1;
|
|
9565
|
+
min-width: 0;
|
|
9566
|
+
/* 16px keeps iOS Safari from zooming the viewport on focus. */
|
|
9567
|
+
font-size: 16px;
|
|
9568
|
+
padding: 10px 12px;
|
|
9569
|
+
border-radius: 8px;
|
|
9570
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
9571
|
+
background: rgba(255, 255, 255, 0.08);
|
|
9572
|
+
color: #fff;
|
|
9573
|
+
}
|
|
9574
|
+
|
|
9575
|
+
@media (min-width: 640px) {
|
|
9576
|
+
.sp-share-backdrop { background: rgba(0, 0, 0, 0.25); }
|
|
9577
|
+
|
|
9578
|
+
.sp-share-sheet {
|
|
9579
|
+
left: auto;
|
|
9580
|
+
right: 12px;
|
|
9581
|
+
bottom: 64px;
|
|
9582
|
+
width: 320px;
|
|
9583
|
+
border-radius: 12px;
|
|
9584
|
+
padding: 10px;
|
|
9585
|
+
transform: translateY(8px) scale(0.98);
|
|
9586
|
+
opacity: 0;
|
|
9587
|
+
transition: opacity 140ms ease, transform 140ms ease;
|
|
9588
|
+
max-height: 60%;
|
|
9589
|
+
}
|
|
9590
|
+
.sp-share-sheet--open { transform: translateY(0) scale(1); opacity: 1; }
|
|
9591
|
+
|
|
9592
|
+
.sp-share-grip { display: none; }
|
|
9593
|
+
.sp-share-targets { grid-template-columns: repeat(auto-fill, minmax(76px, 1fr)); }
|
|
9594
|
+
}
|
|
9595
|
+
|
|
9596
|
+
@media (prefers-reduced-motion: reduce) {
|
|
9597
|
+
.sp-share-backdrop,
|
|
9598
|
+
.sp-share-sheet,
|
|
9599
|
+
.sp-share-target,
|
|
9600
|
+
.sp-share-toast {
|
|
9601
|
+
transition: none;
|
|
9602
|
+
}
|
|
9603
|
+
}
|
|
9604
|
+
`;
|
|
9605
|
+
var PKG_VERSION$1 = "1.8.1";
|
|
9606
|
+
var STYLE_ID = "sp-share-styles";
|
|
9607
|
+
function createSharePlugin(config = {}) {
|
|
9608
|
+
let api = null;
|
|
9609
|
+
let sheet = null;
|
|
9610
|
+
let styleEl = null;
|
|
9611
|
+
const configuredTargets = config.targets ?? DEFAULT_TARGETS;
|
|
9612
|
+
const buildContext = () => {
|
|
9613
|
+
const currentTime = api?.getState("currentTime") ?? 0;
|
|
9614
|
+
const isLive = Boolean(api?.getState("live"));
|
|
9615
|
+
const baseUrl = resolveBaseUrl(config);
|
|
9616
|
+
return {
|
|
9617
|
+
url: applyTimestamp(baseUrl, currentTime, isLive, config),
|
|
9618
|
+
title: resolveTitle(config),
|
|
9619
|
+
currentTime,
|
|
9620
|
+
isLive
|
|
9621
|
+
};
|
|
9622
|
+
};
|
|
9623
|
+
const reportError = (error) => {
|
|
9624
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
9625
|
+
api?.logger.error("Share failed", { error: err });
|
|
9626
|
+
config.onError?.(err);
|
|
9627
|
+
};
|
|
9628
|
+
const reportShared = (targetId, url) => {
|
|
9629
|
+
config.onShare?.(targetId, url);
|
|
9630
|
+
api?.emit("share:completed", { targetId, url });
|
|
9631
|
+
};
|
|
9632
|
+
const availableTargets = (context) => resolveTargets(configuredTargets, (id) => api?.logger.warn(`Unknown share target: ${id}`)).filter(
|
|
9633
|
+
(target) => {
|
|
9634
|
+
if (target.id === "native") {
|
|
9635
|
+
return canUseNativeShare();
|
|
9636
|
+
}
|
|
9637
|
+
if (target.id === "embed") {
|
|
9638
|
+
return buildEmbedSnippet(context, config) !== null;
|
|
9639
|
+
}
|
|
9640
|
+
return true;
|
|
9641
|
+
}
|
|
9642
|
+
);
|
|
9643
|
+
const runTarget = async (target, context) => {
|
|
9644
|
+
if (target.id === "native") {
|
|
9645
|
+
const shared = await nativeShare(context);
|
|
9646
|
+
if (shared) {
|
|
9647
|
+
reportShared(target.id, context.url);
|
|
9648
|
+
}
|
|
9649
|
+
return;
|
|
9650
|
+
}
|
|
9651
|
+
if (target.id === "copy") {
|
|
9652
|
+
const copied = await copyText(context.url);
|
|
9653
|
+
if (copied) {
|
|
9654
|
+
sheet?.showToast("Link copied");
|
|
9655
|
+
reportShared(target.id, context.url);
|
|
9656
|
+
} else {
|
|
9657
|
+
sheet?.showManualCopy(context.url);
|
|
9658
|
+
}
|
|
9659
|
+
return;
|
|
9660
|
+
}
|
|
9661
|
+
if (target.id === "embed") {
|
|
9662
|
+
const snippet = buildEmbedSnippet(context, config);
|
|
9663
|
+
if (!snippet) {
|
|
9664
|
+
return;
|
|
9665
|
+
}
|
|
9666
|
+
const copied = await copyText(snippet);
|
|
9667
|
+
if (copied) {
|
|
9668
|
+
sheet?.showToast("Embed code copied");
|
|
9669
|
+
reportShared(target.id, context.url);
|
|
9670
|
+
} else {
|
|
9671
|
+
sheet?.showManualCopy(snippet);
|
|
9672
|
+
}
|
|
9673
|
+
return;
|
|
9674
|
+
}
|
|
9675
|
+
if (target.href) {
|
|
9676
|
+
window.open(target.href(context), "_blank", "noopener,noreferrer");
|
|
9677
|
+
reportShared(target.id, context.url);
|
|
9678
|
+
}
|
|
9679
|
+
};
|
|
9680
|
+
const openSheet = () => {
|
|
9681
|
+
if (!sheet) {
|
|
9682
|
+
return;
|
|
9683
|
+
}
|
|
9684
|
+
const context = buildContext();
|
|
9685
|
+
const targets = availableTargets(context);
|
|
9686
|
+
if (targets.length === 0) {
|
|
9687
|
+
api?.logger.warn("No share targets available");
|
|
9688
|
+
return;
|
|
9689
|
+
}
|
|
9690
|
+
sheet.show(targets);
|
|
9691
|
+
api?.emit("share:opened", void 0);
|
|
9692
|
+
};
|
|
9693
|
+
const activate = async () => {
|
|
9694
|
+
const hostChoseTargets = config.targets !== void 0;
|
|
9695
|
+
if (!hostChoseTargets && canUseNativeShare()) {
|
|
9696
|
+
const context = buildContext();
|
|
9697
|
+
try {
|
|
9698
|
+
const shared = await nativeShare(context);
|
|
9699
|
+
if (shared) {
|
|
9700
|
+
reportShared("native", context.url);
|
|
9701
|
+
}
|
|
9702
|
+
} catch (error) {
|
|
9703
|
+
reportError(error);
|
|
9704
|
+
}
|
|
9705
|
+
return;
|
|
9706
|
+
}
|
|
9707
|
+
openSheet();
|
|
9708
|
+
};
|
|
9709
|
+
return {
|
|
9710
|
+
id: "share",
|
|
9711
|
+
name: "Share",
|
|
9712
|
+
version: PKG_VERSION$1,
|
|
9713
|
+
type: "feature",
|
|
9714
|
+
description: "Native share sheet, copy link, timestamps and embed codes",
|
|
9715
|
+
init(pluginApi) {
|
|
9716
|
+
api = pluginApi;
|
|
9717
|
+
api.logger.debug("Share plugin initialized");
|
|
9718
|
+
if (!document.getElementById(STYLE_ID)) {
|
|
9719
|
+
styleEl = document.createElement("style");
|
|
9720
|
+
styleEl.id = STYLE_ID;
|
|
9721
|
+
styleEl.textContent = styles;
|
|
9722
|
+
document.head.appendChild(styleEl);
|
|
9723
|
+
}
|
|
9724
|
+
sheet = new ShareSheet(api, {
|
|
9725
|
+
onSelect: (target) => {
|
|
9726
|
+
const context = buildContext();
|
|
9727
|
+
if (target.id !== "copy" && target.id !== "embed") {
|
|
9728
|
+
sheet?.close();
|
|
9729
|
+
}
|
|
9730
|
+
void runTarget(target, context).catch(reportError);
|
|
9731
|
+
},
|
|
9732
|
+
onClose: () => {
|
|
9733
|
+
api?.emit("share:closed", void 0);
|
|
9734
|
+
}
|
|
9735
|
+
});
|
|
9736
|
+
void Promise.resolve().then(() => index).then(({ registerControl: registerControl2 }) => {
|
|
9737
|
+
registerControl2(
|
|
9738
|
+
"share",
|
|
9739
|
+
(controlApi) => new ShareButton(controlApi, () => void activate(), {
|
|
9740
|
+
icon: config.buttonIcon,
|
|
9741
|
+
label: config.buttonLabel
|
|
9742
|
+
})
|
|
9743
|
+
);
|
|
9744
|
+
}).catch(() => {
|
|
9745
|
+
api?.logger.debug("@scarlett-player/ui not present, share control not registered");
|
|
9746
|
+
});
|
|
9747
|
+
api.onDestroy(() => {
|
|
9748
|
+
sheet?.destroy();
|
|
9749
|
+
sheet = null;
|
|
9750
|
+
});
|
|
9751
|
+
},
|
|
9752
|
+
destroy() {
|
|
9753
|
+
sheet?.destroy();
|
|
9754
|
+
sheet = null;
|
|
9755
|
+
styleEl?.remove();
|
|
9756
|
+
styleEl = null;
|
|
9757
|
+
api = null;
|
|
9758
|
+
},
|
|
9759
|
+
async share(targetId) {
|
|
9760
|
+
if (!targetId) {
|
|
9761
|
+
await activate();
|
|
9762
|
+
return;
|
|
9763
|
+
}
|
|
9764
|
+
const context = buildContext();
|
|
9765
|
+
const target = availableTargets(context).find((t) => t.id === targetId);
|
|
9766
|
+
if (!target) {
|
|
9767
|
+
reportError(new Error(`Unknown or unavailable share target: ${targetId}`));
|
|
9768
|
+
return;
|
|
9769
|
+
}
|
|
9770
|
+
try {
|
|
9771
|
+
await runTarget(target, context);
|
|
9772
|
+
} catch (error) {
|
|
9773
|
+
reportError(error);
|
|
9774
|
+
}
|
|
9775
|
+
},
|
|
9776
|
+
getShareUrl() {
|
|
9777
|
+
return buildContext().url;
|
|
9778
|
+
},
|
|
9779
|
+
close() {
|
|
9780
|
+
sheet?.close();
|
|
9781
|
+
}
|
|
9782
|
+
};
|
|
9783
|
+
}
|
|
8952
9784
|
function getAttr(element, ...names) {
|
|
8953
9785
|
for (const name of names) {
|
|
8954
9786
|
const value = element.getAttribute(name);
|
|
@@ -9014,6 +9846,14 @@ function parseDataAttributes(element) {
|
|
|
9014
9846
|
if (album) {
|
|
9015
9847
|
config.album = album;
|
|
9016
9848
|
}
|
|
9849
|
+
const shareUrl = getAttr(element, "data-share-url", "share-url");
|
|
9850
|
+
if (shareUrl) {
|
|
9851
|
+
config.shareUrl = shareUrl;
|
|
9852
|
+
}
|
|
9853
|
+
const embedBaseUrl = getAttr(element, "data-embed-base-url", "embed-base-url");
|
|
9854
|
+
if (embedBaseUrl) {
|
|
9855
|
+
config.embedBaseUrl = embedBaseUrl;
|
|
9856
|
+
}
|
|
9017
9857
|
const brandColor = getAttr(element, "data-brand-color", "data-color", "color");
|
|
9018
9858
|
if (brandColor) {
|
|
9019
9859
|
config.brandColor = brandColor;
|
|
@@ -9116,6 +9956,23 @@ function applyContainerStyles(container, config) {
|
|
|
9116
9956
|
container.style.width = container.style.width || "100%";
|
|
9117
9957
|
}
|
|
9118
9958
|
}
|
|
9959
|
+
const SHARE_CONTROL_LAYOUT = [
|
|
9960
|
+
"play",
|
|
9961
|
+
"skip-backward",
|
|
9962
|
+
"skip-forward",
|
|
9963
|
+
"volume",
|
|
9964
|
+
"time",
|
|
9965
|
+
"live-indicator",
|
|
9966
|
+
"bandwidth-indicator",
|
|
9967
|
+
"spacer",
|
|
9968
|
+
"share",
|
|
9969
|
+
"settings",
|
|
9970
|
+
"captions",
|
|
9971
|
+
"chromecast",
|
|
9972
|
+
"airplay",
|
|
9973
|
+
"pip",
|
|
9974
|
+
"fullscreen"
|
|
9975
|
+
];
|
|
9119
9976
|
async function createEmbedPlayer(container, config, pluginCreators2, availableTypes) {
|
|
9120
9977
|
const type = config.type || "video";
|
|
9121
9978
|
if (!availableTypes.includes(type)) {
|
|
@@ -9140,8 +9997,8 @@ async function createEmbedPlayer(container, config, pluginCreators2, availableTy
|
|
|
9140
9997
|
}
|
|
9141
9998
|
if (pluginCreators2.playlist && config.playlist?.length) {
|
|
9142
9999
|
plugins.push(pluginCreators2.playlist({
|
|
9143
|
-
items: config.playlist.map((item,
|
|
9144
|
-
id: `item-${
|
|
10000
|
+
items: config.playlist.map((item, index2) => ({
|
|
10001
|
+
id: `item-${index2}`,
|
|
9145
10002
|
src: item.src,
|
|
9146
10003
|
title: item.title,
|
|
9147
10004
|
artist: item.artist,
|
|
@@ -9167,6 +10024,13 @@ async function createEmbedPlayer(container, config, pluginCreators2, availableTy
|
|
|
9167
10024
|
if (type === "video" && pluginCreators2.gestures && config.gestures !== false) {
|
|
9168
10025
|
plugins.push(pluginCreators2.gestures({}));
|
|
9169
10026
|
}
|
|
10027
|
+
const shareEnabled = type === "video" && Boolean(config.shareUrl) && config.controls !== false;
|
|
10028
|
+
if (shareEnabled && pluginCreators2.share) {
|
|
10029
|
+
const shareConfig = { url: config.shareUrl };
|
|
10030
|
+
if (config.title) shareConfig.title = config.title;
|
|
10031
|
+
if (config.embedBaseUrl) shareConfig.embedBaseUrl = config.embedBaseUrl;
|
|
10032
|
+
plugins.push(pluginCreators2.share(shareConfig));
|
|
10033
|
+
}
|
|
9170
10034
|
if (pluginCreators2.analytics && config.analytics?.beaconUrl) {
|
|
9171
10035
|
plugins.push(pluginCreators2.analytics({
|
|
9172
10036
|
beaconUrl: config.analytics.beaconUrl,
|
|
@@ -9180,6 +10044,7 @@ async function createEmbedPlayer(container, config, pluginCreators2, availableTy
|
|
|
9180
10044
|
if (Object.keys(theme).length > 0) uiConfig.theme = theme;
|
|
9181
10045
|
if (config.hideDelay !== void 0) uiConfig.hideDelay = config.hideDelay;
|
|
9182
10046
|
if (config.bigPlayButton !== void 0) uiConfig.bigPlayButton = config.bigPlayButton;
|
|
10047
|
+
if (shareEnabled && pluginCreators2.share) uiConfig.controls = SHARE_CONTROL_LAYOUT;
|
|
9183
10048
|
plugins.push(pluginCreators2.videoUI(uiConfig));
|
|
9184
10049
|
} else if ((type === "audio" || type === "audio-mini") && pluginCreators2.audioUI) {
|
|
9185
10050
|
plugins.push(pluginCreators2.audioUI({
|
|
@@ -9285,7 +10150,7 @@ function setupAutoInit(pluginCreators2, availableTypes) {
|
|
|
9285
10150
|
}
|
|
9286
10151
|
}
|
|
9287
10152
|
}
|
|
9288
|
-
const PKG_VERSION = "1.
|
|
10153
|
+
const PKG_VERSION = "1.8.1";
|
|
9289
10154
|
const VERSION = `${PKG_VERSION}-video`;
|
|
9290
10155
|
const AVAILABLE_TYPES = ["video"];
|
|
9291
10156
|
const pluginCreators = {
|
|
@@ -9294,7 +10159,8 @@ const pluginCreators = {
|
|
|
9294
10159
|
videoUI: uiPlugin,
|
|
9295
10160
|
watermark: createWatermarkPlugin,
|
|
9296
10161
|
captions: createCaptionsPlugin,
|
|
9297
|
-
gestures: createGesturesPlugin
|
|
10162
|
+
gestures: createGesturesPlugin,
|
|
10163
|
+
share: createSharePlugin
|
|
9298
10164
|
// Audio UI not available in this build
|
|
9299
10165
|
// Analytics not available in this build
|
|
9300
10166
|
// Playlist not available in this build
|