@scarlett-player/embed 1.2.0 → 1.4.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 +1 -0
- 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 +207 -41
- 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 +207 -41
- 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/iframe.html +9 -0
- package/package.json +10 -10
package/dist/embed.video.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,14 +158,52 @@ 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
|
-
const stateSignal = signal(value);
|
|
162
|
-
stateSignal.subscribe(() => {
|
|
163
|
-
this.notifyChangeSubscribers(stateKey);
|
|
164
|
-
});
|
|
165
|
-
this.signals.set(stateKey, stateSignal);
|
|
161
|
+
this.createSignal(key, value);
|
|
166
162
|
}
|
|
167
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;
|
|
203
|
+
}
|
|
204
|
+
this.definedDefaults.set(key, initialValue);
|
|
205
|
+
this.createSignal(key, initialValue);
|
|
206
|
+
}
|
|
168
207
|
/**
|
|
169
208
|
* Get the signal for a state property.
|
|
170
209
|
*
|
|
@@ -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,9 +6103,42 @@ 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
|
+
}
|
|
6119
|
+
}
|
|
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;
|
|
6042
6136
|
}
|
|
6137
|
+
controls.forEach((c) => c.destroy());
|
|
6138
|
+
controls = [];
|
|
6139
|
+
controlBar.replaceChildren();
|
|
6140
|
+
populateControlBar();
|
|
6141
|
+
updateControls();
|
|
6043
6142
|
};
|
|
6044
6143
|
const updateControls = () => {
|
|
6045
6144
|
controls.forEach((c) => c.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();
|
|
@@ -6511,10 +6613,17 @@ function createWatermarkPlugin(config = {}) {
|
|
|
6511
6613
|
}
|
|
6512
6614
|
};
|
|
6513
6615
|
}
|
|
6616
|
+
var HLS_SUBTITLE_TRACKS_UPDATED = "hlsSubtitleTracksUpdated";
|
|
6617
|
+
var HLS_INSTANCE_RETRY_MS = 500;
|
|
6514
6618
|
function createCaptionsPlugin(config = {}) {
|
|
6515
6619
|
let api = null;
|
|
6516
6620
|
let video = null;
|
|
6517
6621
|
let addedTrackElements = [];
|
|
6622
|
+
let hlsSubtitleHandler = null;
|
|
6623
|
+
let observedTextTracks = null;
|
|
6624
|
+
let hlsRetryTimer = null;
|
|
6625
|
+
let hlsRetryUsed = false;
|
|
6626
|
+
let hasAutoSelected = false;
|
|
6518
6627
|
const extractFromHLS = config.extractFromHLS !== false;
|
|
6519
6628
|
const autoSelect = config.autoSelect ?? false;
|
|
6520
6629
|
const defaultLanguage = config.defaultLanguage ?? "en";
|
|
@@ -6573,6 +6682,7 @@ function createCaptionsPlugin(config = {}) {
|
|
|
6573
6682
|
const selectTrack = (trackId) => {
|
|
6574
6683
|
const videoEl = getVideo2();
|
|
6575
6684
|
if (!videoEl) return;
|
|
6685
|
+
hasAutoSelected = true;
|
|
6576
6686
|
for (let i = 0; i < videoEl.textTracks.length; i++) {
|
|
6577
6687
|
const track = videoEl.textTracks[i];
|
|
6578
6688
|
if (track.kind !== "subtitles" && track.kind !== "captions") continue;
|
|
@@ -6585,45 +6695,90 @@ function createCaptionsPlugin(config = {}) {
|
|
|
6585
6695
|
}
|
|
6586
6696
|
syncTracksToState();
|
|
6587
6697
|
};
|
|
6698
|
+
const maybeAutoSelect = () => {
|
|
6699
|
+
if (!autoSelect || hasAutoSelected) return;
|
|
6700
|
+
if (api?.getState("currentTextTrack")) {
|
|
6701
|
+
hasAutoSelected = true;
|
|
6702
|
+
return;
|
|
6703
|
+
}
|
|
6704
|
+
const tracks = api?.getState("textTracks") || [];
|
|
6705
|
+
const match = tracks.find((t) => t.language === defaultLanguage);
|
|
6706
|
+
if (!match) return;
|
|
6707
|
+
selectTrack(match.id);
|
|
6708
|
+
api?.logger.debug("Auto-selected caption track", { language: defaultLanguage, id: match.id });
|
|
6709
|
+
};
|
|
6710
|
+
const handleTextTracksChanged = () => {
|
|
6711
|
+
syncTracksToState();
|
|
6712
|
+
maybeAutoSelect();
|
|
6713
|
+
};
|
|
6714
|
+
const observeTextTracks = () => {
|
|
6715
|
+
const videoEl = getVideo2();
|
|
6716
|
+
if (!videoEl || observedTextTracks === videoEl.textTracks) return;
|
|
6717
|
+
unobserveTextTracks();
|
|
6718
|
+
const list = videoEl.textTracks;
|
|
6719
|
+
if (typeof list?.addEventListener !== "function") return;
|
|
6720
|
+
observedTextTracks = list;
|
|
6721
|
+
observedTextTracks.addEventListener("addtrack", handleTextTracksChanged);
|
|
6722
|
+
observedTextTracks.addEventListener("removetrack", handleTextTracksChanged);
|
|
6723
|
+
observedTextTracks.addEventListener("change", handleTextTracksChanged);
|
|
6724
|
+
};
|
|
6725
|
+
const unobserveTextTracks = () => {
|
|
6726
|
+
if (typeof observedTextTracks?.removeEventListener !== "function") {
|
|
6727
|
+
observedTextTracks = null;
|
|
6728
|
+
return;
|
|
6729
|
+
}
|
|
6730
|
+
observedTextTracks.removeEventListener("addtrack", handleTextTracksChanged);
|
|
6731
|
+
observedTextTracks.removeEventListener("removetrack", handleTextTracksChanged);
|
|
6732
|
+
observedTextTracks.removeEventListener("change", handleTextTracksChanged);
|
|
6733
|
+
observedTextTracks = null;
|
|
6734
|
+
};
|
|
6588
6735
|
const extractHlsSubtitles = () => {
|
|
6589
6736
|
if (!extractFromHLS || !api) return;
|
|
6590
6737
|
const hlsPlugin = api.getPlugin("hls-provider");
|
|
6591
6738
|
if (!hlsPlugin || hlsPlugin.isNativeHLS()) return;
|
|
6592
6739
|
const hlsInstance = hlsPlugin.getHlsInstance();
|
|
6593
6740
|
if (!hlsInstance?.subtitleTracks?.length) return;
|
|
6594
|
-
api.logger.debug("
|
|
6741
|
+
api.logger.debug("Syncing HLS subtitle tracks", {
|
|
6595
6742
|
count: hlsInstance.subtitleTracks.length
|
|
6596
6743
|
});
|
|
6597
|
-
for (const hlsTrack of hlsInstance.subtitleTracks) {
|
|
6598
|
-
addTrackElement({
|
|
6599
|
-
language: hlsTrack.lang || "unknown",
|
|
6600
|
-
label: hlsTrack.name || `Subtitle ${hlsTrack.id}`,
|
|
6601
|
-
src: hlsTrack.url,
|
|
6602
|
-
kind: "subtitles"
|
|
6603
|
-
});
|
|
6604
|
-
}
|
|
6605
6744
|
syncTracksToState();
|
|
6606
|
-
|
|
6607
|
-
autoSelectTrack();
|
|
6608
|
-
}
|
|
6745
|
+
maybeAutoSelect();
|
|
6609
6746
|
};
|
|
6610
|
-
const
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6615
|
-
|
|
6747
|
+
const unsubscribeFromHls = () => {
|
|
6748
|
+
if (hlsRetryTimer) {
|
|
6749
|
+
clearTimeout(hlsRetryTimer);
|
|
6750
|
+
hlsRetryTimer = null;
|
|
6751
|
+
}
|
|
6752
|
+
if (!hlsSubtitleHandler) return;
|
|
6753
|
+
const hlsInstance = api?.getPlugin("hls-provider")?.getHlsInstance();
|
|
6754
|
+
hlsInstance?.off(HLS_SUBTITLE_TRACKS_UPDATED, hlsSubtitleHandler);
|
|
6755
|
+
hlsSubtitleHandler = null;
|
|
6756
|
+
};
|
|
6757
|
+
const syncFromHls = () => {
|
|
6758
|
+
if (!extractFromHLS || !api) return;
|
|
6759
|
+
const hlsPlugin = api.getPlugin("hls-provider");
|
|
6760
|
+
if (!hlsPlugin || hlsPlugin.isNativeHLS()) return;
|
|
6761
|
+
const hlsInstance = hlsPlugin.getHlsInstance();
|
|
6762
|
+
if (!hlsInstance) {
|
|
6763
|
+
if (!hlsRetryUsed) {
|
|
6764
|
+
hlsRetryUsed = true;
|
|
6765
|
+
hlsRetryTimer = setTimeout(() => {
|
|
6766
|
+
hlsRetryTimer = null;
|
|
6767
|
+
syncFromHls();
|
|
6768
|
+
}, HLS_INSTANCE_RETRY_MS);
|
|
6769
|
+
}
|
|
6770
|
+
return;
|
|
6616
6771
|
}
|
|
6772
|
+
unsubscribeFromHls();
|
|
6773
|
+
hlsSubtitleHandler = () => extractHlsSubtitles();
|
|
6774
|
+
hlsInstance.on(HLS_SUBTITLE_TRACKS_UPDATED, hlsSubtitleHandler);
|
|
6775
|
+
extractHlsSubtitles();
|
|
6617
6776
|
};
|
|
6618
6777
|
const initSources = () => {
|
|
6619
6778
|
if (!config.sources?.length) return;
|
|
6620
6779
|
for (const source of config.sources) {
|
|
6621
6780
|
addTrackElement(source);
|
|
6622
6781
|
}
|
|
6623
|
-
syncTracksToState();
|
|
6624
|
-
if (autoSelect) {
|
|
6625
|
-
autoSelectTrack();
|
|
6626
|
-
}
|
|
6627
6782
|
};
|
|
6628
6783
|
return {
|
|
6629
6784
|
id: "captions",
|
|
@@ -6641,25 +6796,36 @@ function createCaptionsPlugin(config = {}) {
|
|
|
6641
6796
|
});
|
|
6642
6797
|
const unsubLoaded = api.on("media:loaded", () => {
|
|
6643
6798
|
video = null;
|
|
6799
|
+
hasAutoSelected = false;
|
|
6800
|
+
hlsRetryUsed = false;
|
|
6644
6801
|
cleanupTracks();
|
|
6645
6802
|
initSources();
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6803
|
+
observeTextTracks();
|
|
6804
|
+
syncTracksToState();
|
|
6805
|
+
maybeAutoSelect();
|
|
6806
|
+
syncFromHls();
|
|
6649
6807
|
});
|
|
6650
6808
|
const unsubLoadRequest = api.on("media:load-request", () => {
|
|
6651
6809
|
video = null;
|
|
6810
|
+
hasAutoSelected = false;
|
|
6811
|
+
hlsRetryUsed = false;
|
|
6812
|
+
unsubscribeFromHls();
|
|
6813
|
+
unobserveTextTracks();
|
|
6652
6814
|
cleanupTracks();
|
|
6653
6815
|
});
|
|
6654
6816
|
api.onDestroy(() => {
|
|
6655
6817
|
unsubTrackText();
|
|
6656
6818
|
unsubLoaded();
|
|
6657
6819
|
unsubLoadRequest();
|
|
6820
|
+
unsubscribeFromHls();
|
|
6821
|
+
unobserveTextTracks();
|
|
6658
6822
|
cleanupTracks();
|
|
6659
6823
|
});
|
|
6660
6824
|
},
|
|
6661
6825
|
destroy() {
|
|
6662
6826
|
api?.logger.debug("Captions plugin destroyed");
|
|
6827
|
+
unsubscribeFromHls();
|
|
6828
|
+
unobserveTextTracks();
|
|
6663
6829
|
cleanupTracks();
|
|
6664
6830
|
video = null;
|
|
6665
6831
|
api = null;
|