@scarlett-player/embed 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/embed.audio.js +62 -8
- 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 +230 -40
- 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 +230 -40
- 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/package.json +10 -10
package/dist/embed.js
CHANGED
|
@@ -148,6 +148,7 @@ class StateManager {
|
|
|
148
148
|
constructor(initialState) {
|
|
149
149
|
this.signals = /* @__PURE__ */ new Map();
|
|
150
150
|
this.changeSubscribers = /* @__PURE__ */ new Set();
|
|
151
|
+
this.definedDefaults = /* @__PURE__ */ new Map();
|
|
151
152
|
this.initializeSignals(initialState);
|
|
152
153
|
}
|
|
153
154
|
/**
|
|
@@ -157,13 +158,51 @@ class StateManager {
|
|
|
157
158
|
initializeSignals(overrides) {
|
|
158
159
|
const initialState = { ...DEFAULT_STATE, ...overrides };
|
|
159
160
|
for (const [key, value] of Object.entries(initialState)) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
161
|
+
this.createSignal(key, value);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Create and register a signal, wired to the global change subscribers.
|
|
166
|
+
*
|
|
167
|
+
* Shared by initializeSignals() and define() so a plugin-defined key behaves
|
|
168
|
+
* exactly like a built-in one and the two paths cannot drift apart.
|
|
169
|
+
*
|
|
170
|
+
* @private
|
|
171
|
+
*/
|
|
172
|
+
createSignal(key, value) {
|
|
173
|
+
const stateSignal = signal(value);
|
|
174
|
+
stateSignal.subscribe(() => {
|
|
175
|
+
this.notifyChangeSubscribers(key);
|
|
176
|
+
});
|
|
177
|
+
this.signals.set(key, stateSignal);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Register a state key at runtime, for state a plugin owns.
|
|
181
|
+
*
|
|
182
|
+
* Core cannot know every plugin's keys, and {@link get} deliberately throws
|
|
183
|
+
* for unregistered ones — that throw is a useful typo-catcher and is worth
|
|
184
|
+
* keeping — so a plugin declares its keys before first use.
|
|
185
|
+
*
|
|
186
|
+
* Idempotent by design: re-defining an existing key leaves the current value
|
|
187
|
+
* untouched. Plugins commonly re-run setup after a source change, and that
|
|
188
|
+
* must not reset state that is already live.
|
|
189
|
+
*
|
|
190
|
+
* Namespace plugin keys with the plugin's own name to avoid collisions.
|
|
191
|
+
*
|
|
192
|
+
* @param key - State property key
|
|
193
|
+
* @param initialValue - Value used only when the key is new
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```ts
|
|
197
|
+
* state.define('highlightSelection', null);
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
define(key, initialValue) {
|
|
201
|
+
if (this.signals.has(key)) {
|
|
202
|
+
return;
|
|
166
203
|
}
|
|
204
|
+
this.definedDefaults.set(key, initialValue);
|
|
205
|
+
this.createSignal(key, initialValue);
|
|
167
206
|
}
|
|
168
207
|
/**
|
|
169
208
|
* Get the signal for a state property.
|
|
@@ -307,7 +346,10 @@ class StateManager {
|
|
|
307
346
|
* ```
|
|
308
347
|
*/
|
|
309
348
|
reset() {
|
|
310
|
-
this.update(
|
|
349
|
+
this.update({
|
|
350
|
+
...DEFAULT_STATE,
|
|
351
|
+
...Object.fromEntries(this.definedDefaults)
|
|
352
|
+
});
|
|
311
353
|
}
|
|
312
354
|
/**
|
|
313
355
|
* Reset a specific state property to its default value.
|
|
@@ -320,7 +362,7 @@ class StateManager {
|
|
|
320
362
|
* ```
|
|
321
363
|
*/
|
|
322
364
|
resetKey(key) {
|
|
323
|
-
const defaultValue = DEFAULT_STATE[key];
|
|
365
|
+
const defaultValue = key in DEFAULT_STATE ? DEFAULT_STATE[key] : this.definedDefaults.get(key);
|
|
324
366
|
this.set(key, defaultValue);
|
|
325
367
|
}
|
|
326
368
|
/**
|
|
@@ -1214,6 +1256,18 @@ class PluginAPI {
|
|
|
1214
1256
|
setState(key, value) {
|
|
1215
1257
|
this.stateManager.set(key, value);
|
|
1216
1258
|
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Register a state key this plugin owns, before first use.
|
|
1261
|
+
*
|
|
1262
|
+
* Idempotent — re-defining an existing key keeps its current value.
|
|
1263
|
+
* See {@link IPluginAPI.defineState}.
|
|
1264
|
+
*
|
|
1265
|
+
* @param key - State property key
|
|
1266
|
+
* @param initialValue - Value used only when the key is new
|
|
1267
|
+
*/
|
|
1268
|
+
defineState(key, initialValue) {
|
|
1269
|
+
this.stateManager.define(key, initialValue);
|
|
1270
|
+
}
|
|
1217
1271
|
/**
|
|
1218
1272
|
* Subscribe to an event.
|
|
1219
1273
|
*
|
|
@@ -5968,6 +6022,17 @@ var BandwidthIndicator = class {
|
|
|
5968
6022
|
this.el.remove();
|
|
5969
6023
|
}
|
|
5970
6024
|
};
|
|
6025
|
+
var registry = /* @__PURE__ */ new Map();
|
|
6026
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
6027
|
+
function getControlFactory(id) {
|
|
6028
|
+
return registry.get(id) ?? null;
|
|
6029
|
+
}
|
|
6030
|
+
function onControlRegistered(listener) {
|
|
6031
|
+
listeners.add(listener);
|
|
6032
|
+
return () => {
|
|
6033
|
+
listeners.delete(listener);
|
|
6034
|
+
};
|
|
6035
|
+
}
|
|
5971
6036
|
var DEFAULT_LAYOUT = [
|
|
5972
6037
|
"play",
|
|
5973
6038
|
"skip-backward",
|
|
@@ -5996,6 +6061,7 @@ function uiPlugin(config = {}) {
|
|
|
5996
6061
|
let controls = [];
|
|
5997
6062
|
let hideTimeout = null;
|
|
5998
6063
|
let stateUnsubscribe = null;
|
|
6064
|
+
let controlRegistryUnsubscribe = null;
|
|
5999
6065
|
let errorUnsubscribe = null;
|
|
6000
6066
|
let reconnectingUnsubscribe = null;
|
|
6001
6067
|
let recoveredUnsubscribe = null;
|
|
@@ -6037,10 +6103,43 @@ function uiPlugin(config = {}) {
|
|
|
6037
6103
|
return new FullscreenButton(api);
|
|
6038
6104
|
case "spacer":
|
|
6039
6105
|
return new Spacer();
|
|
6040
|
-
default:
|
|
6106
|
+
default: {
|
|
6107
|
+
const factory = getControlFactory(slot);
|
|
6108
|
+
if (factory) {
|
|
6109
|
+
try {
|
|
6110
|
+
return factory(api);
|
|
6111
|
+
} catch (error) {
|
|
6112
|
+
api.logger.error(`Control factory for "${slot}" threw`, { error });
|
|
6113
|
+
return null;
|
|
6114
|
+
}
|
|
6115
|
+
}
|
|
6116
|
+
api.logger.warn(`Unknown control slot: ${slot}`);
|
|
6041
6117
|
return null;
|
|
6118
|
+
}
|
|
6042
6119
|
}
|
|
6043
6120
|
};
|
|
6121
|
+
const populateControlBar = () => {
|
|
6122
|
+
if (!controlBar) {
|
|
6123
|
+
return;
|
|
6124
|
+
}
|
|
6125
|
+
for (const slot of layout) {
|
|
6126
|
+
const control = createControl(slot);
|
|
6127
|
+
if (control) {
|
|
6128
|
+
controls.push(control);
|
|
6129
|
+
controlBar.appendChild(control.render());
|
|
6130
|
+
}
|
|
6131
|
+
}
|
|
6132
|
+
};
|
|
6133
|
+
const rebuildControlBar = () => {
|
|
6134
|
+
if (!controlBar) {
|
|
6135
|
+
return;
|
|
6136
|
+
}
|
|
6137
|
+
controls.forEach((c) => c.destroy());
|
|
6138
|
+
controls = [];
|
|
6139
|
+
controlBar.replaceChildren();
|
|
6140
|
+
populateControlBar();
|
|
6141
|
+
updateControls();
|
|
6142
|
+
};
|
|
6044
6143
|
const updateControls = () => {
|
|
6045
6144
|
controls.forEach((c) => c.update());
|
|
6046
6145
|
progressBar?.update();
|
|
@@ -6213,14 +6312,15 @@ function uiPlugin(config = {}) {
|
|
|
6213
6312
|
controlBar.className = isPlaying ? "sp-controls sp-controls--hidden" : "sp-controls sp-controls--visible";
|
|
6214
6313
|
controlBar.setAttribute("role", "toolbar");
|
|
6215
6314
|
controlBar.setAttribute("aria-label", "Video controls");
|
|
6216
|
-
|
|
6217
|
-
const control = createControl(slot);
|
|
6218
|
-
if (control) {
|
|
6219
|
-
controls.push(control);
|
|
6220
|
-
controlBar.appendChild(control.render());
|
|
6221
|
-
}
|
|
6222
|
-
}
|
|
6315
|
+
populateControlBar();
|
|
6223
6316
|
container.appendChild(controlBar);
|
|
6317
|
+
controlRegistryUnsubscribe = onControlRegistered((id) => {
|
|
6318
|
+
if (!layout.includes(id)) {
|
|
6319
|
+
return;
|
|
6320
|
+
}
|
|
6321
|
+
api.logger.debug(`Control "${id}" registered after init, rebuilding control bar`);
|
|
6322
|
+
rebuildControlBar();
|
|
6323
|
+
});
|
|
6224
6324
|
container.addEventListener("mousemove", handleInteraction);
|
|
6225
6325
|
container.addEventListener("mouseenter", handleInteraction);
|
|
6226
6326
|
container.addEventListener("mouseleave", handleMouseLeave);
|
|
@@ -6266,6 +6366,8 @@ function uiPlugin(config = {}) {
|
|
|
6266
6366
|
}
|
|
6267
6367
|
document.removeEventListener("keydown", handleKeyDown);
|
|
6268
6368
|
document.removeEventListener("fullscreenchange", scheduleUpdate);
|
|
6369
|
+
controlRegistryUnsubscribe?.();
|
|
6370
|
+
controlRegistryUnsubscribe = null;
|
|
6269
6371
|
controls.forEach((c) => c.destroy());
|
|
6270
6372
|
controls = [];
|
|
6271
6373
|
progressBar?.destroy();
|
|
@@ -8464,10 +8566,18 @@ function createWatermarkPlugin(config = {}) {
|
|
|
8464
8566
|
}
|
|
8465
8567
|
};
|
|
8466
8568
|
}
|
|
8569
|
+
var HLS_SUBTITLE_TRACKS_UPDATED = "hlsSubtitleTracksUpdated";
|
|
8570
|
+
var HLS_INSTANCE_RETRY_MS = 500;
|
|
8467
8571
|
function createCaptionsPlugin(config = {}) {
|
|
8468
8572
|
let api = null;
|
|
8469
8573
|
let video = null;
|
|
8470
8574
|
let addedTrackElements = [];
|
|
8575
|
+
let hlsTrackElements = [];
|
|
8576
|
+
let hlsSubtitleHandler = null;
|
|
8577
|
+
let observedTextTracks = null;
|
|
8578
|
+
let hlsRetryTimer = null;
|
|
8579
|
+
let hlsRetryUsed = false;
|
|
8580
|
+
let hasAutoSelected = false;
|
|
8471
8581
|
const extractFromHLS = config.extractFromHLS !== false;
|
|
8472
8582
|
const autoSelect = config.autoSelect ?? false;
|
|
8473
8583
|
const defaultLanguage = config.defaultLanguage ?? "en";
|
|
@@ -8481,10 +8591,18 @@ function createCaptionsPlugin(config = {}) {
|
|
|
8481
8591
|
trackEl.parentNode?.removeChild(trackEl);
|
|
8482
8592
|
}
|
|
8483
8593
|
addedTrackElements = [];
|
|
8594
|
+
hlsTrackElements = [];
|
|
8484
8595
|
api?.setState("textTracks", []);
|
|
8485
8596
|
api?.setState("currentTextTrack", null);
|
|
8486
8597
|
};
|
|
8487
|
-
const
|
|
8598
|
+
const removeHlsTrackElements = () => {
|
|
8599
|
+
for (const trackEl of hlsTrackElements) {
|
|
8600
|
+
trackEl.parentNode?.removeChild(trackEl);
|
|
8601
|
+
addedTrackElements = addedTrackElements.filter((el) => el !== trackEl);
|
|
8602
|
+
}
|
|
8603
|
+
hlsTrackElements = [];
|
|
8604
|
+
};
|
|
8605
|
+
const addTrackElement = (source, origin = "config") => {
|
|
8488
8606
|
const videoEl = getVideo2();
|
|
8489
8607
|
if (!videoEl) throw new Error("No video element");
|
|
8490
8608
|
const trackEl = document.createElement("track");
|
|
@@ -8495,6 +8613,9 @@ function createCaptionsPlugin(config = {}) {
|
|
|
8495
8613
|
trackEl.default = false;
|
|
8496
8614
|
videoEl.appendChild(trackEl);
|
|
8497
8615
|
addedTrackElements.push(trackEl);
|
|
8616
|
+
if (origin === "hls") {
|
|
8617
|
+
hlsTrackElements.push(trackEl);
|
|
8618
|
+
}
|
|
8498
8619
|
if (trackEl.track) {
|
|
8499
8620
|
trackEl.track.mode = "disabled";
|
|
8500
8621
|
}
|
|
@@ -8526,6 +8647,7 @@ function createCaptionsPlugin(config = {}) {
|
|
|
8526
8647
|
const selectTrack = (trackId) => {
|
|
8527
8648
|
const videoEl = getVideo2();
|
|
8528
8649
|
if (!videoEl) return;
|
|
8650
|
+
hasAutoSelected = true;
|
|
8529
8651
|
for (let i = 0; i < videoEl.textTracks.length; i++) {
|
|
8530
8652
|
const track = videoEl.textTracks[i];
|
|
8531
8653
|
if (track.kind !== "subtitles" && track.kind !== "captions") continue;
|
|
@@ -8538,6 +8660,43 @@ function createCaptionsPlugin(config = {}) {
|
|
|
8538
8660
|
}
|
|
8539
8661
|
syncTracksToState();
|
|
8540
8662
|
};
|
|
8663
|
+
const maybeAutoSelect = () => {
|
|
8664
|
+
if (!autoSelect || hasAutoSelected) return;
|
|
8665
|
+
if (api?.getState("currentTextTrack")) {
|
|
8666
|
+
hasAutoSelected = true;
|
|
8667
|
+
return;
|
|
8668
|
+
}
|
|
8669
|
+
const tracks = api?.getState("textTracks") || [];
|
|
8670
|
+
const match = tracks.find((t) => t.language === defaultLanguage);
|
|
8671
|
+
if (!match) return;
|
|
8672
|
+
selectTrack(match.id);
|
|
8673
|
+
api?.logger.debug("Auto-selected caption track", { language: defaultLanguage, id: match.id });
|
|
8674
|
+
};
|
|
8675
|
+
const handleTextTracksChanged = () => {
|
|
8676
|
+
syncTracksToState();
|
|
8677
|
+
maybeAutoSelect();
|
|
8678
|
+
};
|
|
8679
|
+
const observeTextTracks = () => {
|
|
8680
|
+
const videoEl = getVideo2();
|
|
8681
|
+
if (!videoEl || observedTextTracks === videoEl.textTracks) return;
|
|
8682
|
+
unobserveTextTracks();
|
|
8683
|
+
const list = videoEl.textTracks;
|
|
8684
|
+
if (typeof list?.addEventListener !== "function") return;
|
|
8685
|
+
observedTextTracks = list;
|
|
8686
|
+
observedTextTracks.addEventListener("addtrack", handleTextTracksChanged);
|
|
8687
|
+
observedTextTracks.addEventListener("removetrack", handleTextTracksChanged);
|
|
8688
|
+
observedTextTracks.addEventListener("change", handleTextTracksChanged);
|
|
8689
|
+
};
|
|
8690
|
+
const unobserveTextTracks = () => {
|
|
8691
|
+
if (typeof observedTextTracks?.removeEventListener !== "function") {
|
|
8692
|
+
observedTextTracks = null;
|
|
8693
|
+
return;
|
|
8694
|
+
}
|
|
8695
|
+
observedTextTracks.removeEventListener("addtrack", handleTextTracksChanged);
|
|
8696
|
+
observedTextTracks.removeEventListener("removetrack", handleTextTracksChanged);
|
|
8697
|
+
observedTextTracks.removeEventListener("change", handleTextTracksChanged);
|
|
8698
|
+
observedTextTracks = null;
|
|
8699
|
+
};
|
|
8541
8700
|
const extractHlsSubtitles = () => {
|
|
8542
8701
|
if (!extractFromHLS || !api) return;
|
|
8543
8702
|
const hlsPlugin = api.getPlugin("hls-provider");
|
|
@@ -8547,35 +8706,55 @@ function createCaptionsPlugin(config = {}) {
|
|
|
8547
8706
|
api.logger.debug("Extracting HLS subtitle tracks", {
|
|
8548
8707
|
count: hlsInstance.subtitleTracks.length
|
|
8549
8708
|
});
|
|
8709
|
+
removeHlsTrackElements();
|
|
8550
8710
|
for (const hlsTrack of hlsInstance.subtitleTracks) {
|
|
8551
|
-
addTrackElement(
|
|
8552
|
-
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8711
|
+
addTrackElement(
|
|
8712
|
+
{
|
|
8713
|
+
language: hlsTrack.lang || "unknown",
|
|
8714
|
+
label: hlsTrack.name || `Subtitle ${hlsTrack.id}`,
|
|
8715
|
+
src: hlsTrack.url,
|
|
8716
|
+
kind: "subtitles"
|
|
8717
|
+
},
|
|
8718
|
+
"hls"
|
|
8719
|
+
);
|
|
8557
8720
|
}
|
|
8558
8721
|
syncTracksToState();
|
|
8559
|
-
|
|
8560
|
-
autoSelectTrack();
|
|
8561
|
-
}
|
|
8722
|
+
maybeAutoSelect();
|
|
8562
8723
|
};
|
|
8563
|
-
const
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8724
|
+
const unsubscribeFromHls = () => {
|
|
8725
|
+
if (hlsRetryTimer) {
|
|
8726
|
+
clearTimeout(hlsRetryTimer);
|
|
8727
|
+
hlsRetryTimer = null;
|
|
8728
|
+
}
|
|
8729
|
+
if (!hlsSubtitleHandler) return;
|
|
8730
|
+
const hlsInstance = api?.getPlugin("hls-provider")?.getHlsInstance();
|
|
8731
|
+
hlsInstance?.off(HLS_SUBTITLE_TRACKS_UPDATED, hlsSubtitleHandler);
|
|
8732
|
+
hlsSubtitleHandler = null;
|
|
8733
|
+
};
|
|
8734
|
+
const syncFromHls = () => {
|
|
8735
|
+
if (!extractFromHLS || !api) return;
|
|
8736
|
+
const hlsPlugin = api.getPlugin("hls-provider");
|
|
8737
|
+
if (!hlsPlugin || hlsPlugin.isNativeHLS()) return;
|
|
8738
|
+
const hlsInstance = hlsPlugin.getHlsInstance();
|
|
8739
|
+
if (!hlsInstance) {
|
|
8740
|
+
if (!hlsRetryUsed) {
|
|
8741
|
+
hlsRetryUsed = true;
|
|
8742
|
+
hlsRetryTimer = setTimeout(() => {
|
|
8743
|
+
hlsRetryTimer = null;
|
|
8744
|
+
syncFromHls();
|
|
8745
|
+
}, HLS_INSTANCE_RETRY_MS);
|
|
8746
|
+
}
|
|
8747
|
+
return;
|
|
8569
8748
|
}
|
|
8749
|
+
unsubscribeFromHls();
|
|
8750
|
+
hlsSubtitleHandler = () => extractHlsSubtitles();
|
|
8751
|
+
hlsInstance.on(HLS_SUBTITLE_TRACKS_UPDATED, hlsSubtitleHandler);
|
|
8752
|
+
extractHlsSubtitles();
|
|
8570
8753
|
};
|
|
8571
8754
|
const initSources = () => {
|
|
8572
8755
|
if (!config.sources?.length) return;
|
|
8573
8756
|
for (const source of config.sources) {
|
|
8574
|
-
addTrackElement(source);
|
|
8575
|
-
}
|
|
8576
|
-
syncTracksToState();
|
|
8577
|
-
if (autoSelect) {
|
|
8578
|
-
autoSelectTrack();
|
|
8757
|
+
addTrackElement(source, "config");
|
|
8579
8758
|
}
|
|
8580
8759
|
};
|
|
8581
8760
|
return {
|
|
@@ -8594,25 +8773,36 @@ function createCaptionsPlugin(config = {}) {
|
|
|
8594
8773
|
});
|
|
8595
8774
|
const unsubLoaded = api.on("media:loaded", () => {
|
|
8596
8775
|
video = null;
|
|
8776
|
+
hasAutoSelected = false;
|
|
8777
|
+
hlsRetryUsed = false;
|
|
8597
8778
|
cleanupTracks();
|
|
8598
8779
|
initSources();
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
|
|
8780
|
+
observeTextTracks();
|
|
8781
|
+
syncTracksToState();
|
|
8782
|
+
maybeAutoSelect();
|
|
8783
|
+
syncFromHls();
|
|
8602
8784
|
});
|
|
8603
8785
|
const unsubLoadRequest = api.on("media:load-request", () => {
|
|
8604
8786
|
video = null;
|
|
8787
|
+
hasAutoSelected = false;
|
|
8788
|
+
hlsRetryUsed = false;
|
|
8789
|
+
unsubscribeFromHls();
|
|
8790
|
+
unobserveTextTracks();
|
|
8605
8791
|
cleanupTracks();
|
|
8606
8792
|
});
|
|
8607
8793
|
api.onDestroy(() => {
|
|
8608
8794
|
unsubTrackText();
|
|
8609
8795
|
unsubLoaded();
|
|
8610
8796
|
unsubLoadRequest();
|
|
8797
|
+
unsubscribeFromHls();
|
|
8798
|
+
unobserveTextTracks();
|
|
8611
8799
|
cleanupTracks();
|
|
8612
8800
|
});
|
|
8613
8801
|
},
|
|
8614
8802
|
destroy() {
|
|
8615
8803
|
api?.logger.debug("Captions plugin destroyed");
|
|
8804
|
+
unsubscribeFromHls();
|
|
8805
|
+
unobserveTextTracks();
|
|
8616
8806
|
cleanupTracks();
|
|
8617
8807
|
video = null;
|
|
8618
8808
|
api = null;
|