@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.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,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();
|
|
@@ -6511,10 +6613,18 @@ 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 hlsTrackElements = [];
|
|
6623
|
+
let hlsSubtitleHandler = null;
|
|
6624
|
+
let observedTextTracks = null;
|
|
6625
|
+
let hlsRetryTimer = null;
|
|
6626
|
+
let hlsRetryUsed = false;
|
|
6627
|
+
let hasAutoSelected = false;
|
|
6518
6628
|
const extractFromHLS = config.extractFromHLS !== false;
|
|
6519
6629
|
const autoSelect = config.autoSelect ?? false;
|
|
6520
6630
|
const defaultLanguage = config.defaultLanguage ?? "en";
|
|
@@ -6528,10 +6638,18 @@ function createCaptionsPlugin(config = {}) {
|
|
|
6528
6638
|
trackEl.parentNode?.removeChild(trackEl);
|
|
6529
6639
|
}
|
|
6530
6640
|
addedTrackElements = [];
|
|
6641
|
+
hlsTrackElements = [];
|
|
6531
6642
|
api?.setState("textTracks", []);
|
|
6532
6643
|
api?.setState("currentTextTrack", null);
|
|
6533
6644
|
};
|
|
6534
|
-
const
|
|
6645
|
+
const removeHlsTrackElements = () => {
|
|
6646
|
+
for (const trackEl of hlsTrackElements) {
|
|
6647
|
+
trackEl.parentNode?.removeChild(trackEl);
|
|
6648
|
+
addedTrackElements = addedTrackElements.filter((el) => el !== trackEl);
|
|
6649
|
+
}
|
|
6650
|
+
hlsTrackElements = [];
|
|
6651
|
+
};
|
|
6652
|
+
const addTrackElement = (source, origin = "config") => {
|
|
6535
6653
|
const videoEl = getVideo2();
|
|
6536
6654
|
if (!videoEl) throw new Error("No video element");
|
|
6537
6655
|
const trackEl = document.createElement("track");
|
|
@@ -6542,6 +6660,9 @@ function createCaptionsPlugin(config = {}) {
|
|
|
6542
6660
|
trackEl.default = false;
|
|
6543
6661
|
videoEl.appendChild(trackEl);
|
|
6544
6662
|
addedTrackElements.push(trackEl);
|
|
6663
|
+
if (origin === "hls") {
|
|
6664
|
+
hlsTrackElements.push(trackEl);
|
|
6665
|
+
}
|
|
6545
6666
|
if (trackEl.track) {
|
|
6546
6667
|
trackEl.track.mode = "disabled";
|
|
6547
6668
|
}
|
|
@@ -6573,6 +6694,7 @@ function createCaptionsPlugin(config = {}) {
|
|
|
6573
6694
|
const selectTrack = (trackId) => {
|
|
6574
6695
|
const videoEl = getVideo2();
|
|
6575
6696
|
if (!videoEl) return;
|
|
6697
|
+
hasAutoSelected = true;
|
|
6576
6698
|
for (let i = 0; i < videoEl.textTracks.length; i++) {
|
|
6577
6699
|
const track = videoEl.textTracks[i];
|
|
6578
6700
|
if (track.kind !== "subtitles" && track.kind !== "captions") continue;
|
|
@@ -6585,6 +6707,43 @@ function createCaptionsPlugin(config = {}) {
|
|
|
6585
6707
|
}
|
|
6586
6708
|
syncTracksToState();
|
|
6587
6709
|
};
|
|
6710
|
+
const maybeAutoSelect = () => {
|
|
6711
|
+
if (!autoSelect || hasAutoSelected) return;
|
|
6712
|
+
if (api?.getState("currentTextTrack")) {
|
|
6713
|
+
hasAutoSelected = true;
|
|
6714
|
+
return;
|
|
6715
|
+
}
|
|
6716
|
+
const tracks = api?.getState("textTracks") || [];
|
|
6717
|
+
const match = tracks.find((t) => t.language === defaultLanguage);
|
|
6718
|
+
if (!match) return;
|
|
6719
|
+
selectTrack(match.id);
|
|
6720
|
+
api?.logger.debug("Auto-selected caption track", { language: defaultLanguage, id: match.id });
|
|
6721
|
+
};
|
|
6722
|
+
const handleTextTracksChanged = () => {
|
|
6723
|
+
syncTracksToState();
|
|
6724
|
+
maybeAutoSelect();
|
|
6725
|
+
};
|
|
6726
|
+
const observeTextTracks = () => {
|
|
6727
|
+
const videoEl = getVideo2();
|
|
6728
|
+
if (!videoEl || observedTextTracks === videoEl.textTracks) return;
|
|
6729
|
+
unobserveTextTracks();
|
|
6730
|
+
const list = videoEl.textTracks;
|
|
6731
|
+
if (typeof list?.addEventListener !== "function") return;
|
|
6732
|
+
observedTextTracks = list;
|
|
6733
|
+
observedTextTracks.addEventListener("addtrack", handleTextTracksChanged);
|
|
6734
|
+
observedTextTracks.addEventListener("removetrack", handleTextTracksChanged);
|
|
6735
|
+
observedTextTracks.addEventListener("change", handleTextTracksChanged);
|
|
6736
|
+
};
|
|
6737
|
+
const unobserveTextTracks = () => {
|
|
6738
|
+
if (typeof observedTextTracks?.removeEventListener !== "function") {
|
|
6739
|
+
observedTextTracks = null;
|
|
6740
|
+
return;
|
|
6741
|
+
}
|
|
6742
|
+
observedTextTracks.removeEventListener("addtrack", handleTextTracksChanged);
|
|
6743
|
+
observedTextTracks.removeEventListener("removetrack", handleTextTracksChanged);
|
|
6744
|
+
observedTextTracks.removeEventListener("change", handleTextTracksChanged);
|
|
6745
|
+
observedTextTracks = null;
|
|
6746
|
+
};
|
|
6588
6747
|
const extractHlsSubtitles = () => {
|
|
6589
6748
|
if (!extractFromHLS || !api) return;
|
|
6590
6749
|
const hlsPlugin = api.getPlugin("hls-provider");
|
|
@@ -6594,35 +6753,55 @@ function createCaptionsPlugin(config = {}) {
|
|
|
6594
6753
|
api.logger.debug("Extracting HLS subtitle tracks", {
|
|
6595
6754
|
count: hlsInstance.subtitleTracks.length
|
|
6596
6755
|
});
|
|
6756
|
+
removeHlsTrackElements();
|
|
6597
6757
|
for (const hlsTrack of hlsInstance.subtitleTracks) {
|
|
6598
|
-
addTrackElement(
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6758
|
+
addTrackElement(
|
|
6759
|
+
{
|
|
6760
|
+
language: hlsTrack.lang || "unknown",
|
|
6761
|
+
label: hlsTrack.name || `Subtitle ${hlsTrack.id}`,
|
|
6762
|
+
src: hlsTrack.url,
|
|
6763
|
+
kind: "subtitles"
|
|
6764
|
+
},
|
|
6765
|
+
"hls"
|
|
6766
|
+
);
|
|
6604
6767
|
}
|
|
6605
6768
|
syncTracksToState();
|
|
6606
|
-
|
|
6607
|
-
autoSelectTrack();
|
|
6608
|
-
}
|
|
6769
|
+
maybeAutoSelect();
|
|
6609
6770
|
};
|
|
6610
|
-
const
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6615
|
-
|
|
6771
|
+
const unsubscribeFromHls = () => {
|
|
6772
|
+
if (hlsRetryTimer) {
|
|
6773
|
+
clearTimeout(hlsRetryTimer);
|
|
6774
|
+
hlsRetryTimer = null;
|
|
6775
|
+
}
|
|
6776
|
+
if (!hlsSubtitleHandler) return;
|
|
6777
|
+
const hlsInstance = api?.getPlugin("hls-provider")?.getHlsInstance();
|
|
6778
|
+
hlsInstance?.off(HLS_SUBTITLE_TRACKS_UPDATED, hlsSubtitleHandler);
|
|
6779
|
+
hlsSubtitleHandler = null;
|
|
6780
|
+
};
|
|
6781
|
+
const syncFromHls = () => {
|
|
6782
|
+
if (!extractFromHLS || !api) return;
|
|
6783
|
+
const hlsPlugin = api.getPlugin("hls-provider");
|
|
6784
|
+
if (!hlsPlugin || hlsPlugin.isNativeHLS()) return;
|
|
6785
|
+
const hlsInstance = hlsPlugin.getHlsInstance();
|
|
6786
|
+
if (!hlsInstance) {
|
|
6787
|
+
if (!hlsRetryUsed) {
|
|
6788
|
+
hlsRetryUsed = true;
|
|
6789
|
+
hlsRetryTimer = setTimeout(() => {
|
|
6790
|
+
hlsRetryTimer = null;
|
|
6791
|
+
syncFromHls();
|
|
6792
|
+
}, HLS_INSTANCE_RETRY_MS);
|
|
6793
|
+
}
|
|
6794
|
+
return;
|
|
6616
6795
|
}
|
|
6796
|
+
unsubscribeFromHls();
|
|
6797
|
+
hlsSubtitleHandler = () => extractHlsSubtitles();
|
|
6798
|
+
hlsInstance.on(HLS_SUBTITLE_TRACKS_UPDATED, hlsSubtitleHandler);
|
|
6799
|
+
extractHlsSubtitles();
|
|
6617
6800
|
};
|
|
6618
6801
|
const initSources = () => {
|
|
6619
6802
|
if (!config.sources?.length) return;
|
|
6620
6803
|
for (const source of config.sources) {
|
|
6621
|
-
addTrackElement(source);
|
|
6622
|
-
}
|
|
6623
|
-
syncTracksToState();
|
|
6624
|
-
if (autoSelect) {
|
|
6625
|
-
autoSelectTrack();
|
|
6804
|
+
addTrackElement(source, "config");
|
|
6626
6805
|
}
|
|
6627
6806
|
};
|
|
6628
6807
|
return {
|
|
@@ -6641,25 +6820,36 @@ function createCaptionsPlugin(config = {}) {
|
|
|
6641
6820
|
});
|
|
6642
6821
|
const unsubLoaded = api.on("media:loaded", () => {
|
|
6643
6822
|
video = null;
|
|
6823
|
+
hasAutoSelected = false;
|
|
6824
|
+
hlsRetryUsed = false;
|
|
6644
6825
|
cleanupTracks();
|
|
6645
6826
|
initSources();
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6827
|
+
observeTextTracks();
|
|
6828
|
+
syncTracksToState();
|
|
6829
|
+
maybeAutoSelect();
|
|
6830
|
+
syncFromHls();
|
|
6649
6831
|
});
|
|
6650
6832
|
const unsubLoadRequest = api.on("media:load-request", () => {
|
|
6651
6833
|
video = null;
|
|
6834
|
+
hasAutoSelected = false;
|
|
6835
|
+
hlsRetryUsed = false;
|
|
6836
|
+
unsubscribeFromHls();
|
|
6837
|
+
unobserveTextTracks();
|
|
6652
6838
|
cleanupTracks();
|
|
6653
6839
|
});
|
|
6654
6840
|
api.onDestroy(() => {
|
|
6655
6841
|
unsubTrackText();
|
|
6656
6842
|
unsubLoaded();
|
|
6657
6843
|
unsubLoadRequest();
|
|
6844
|
+
unsubscribeFromHls();
|
|
6845
|
+
unobserveTextTracks();
|
|
6658
6846
|
cleanupTracks();
|
|
6659
6847
|
});
|
|
6660
6848
|
},
|
|
6661
6849
|
destroy() {
|
|
6662
6850
|
api?.logger.debug("Captions plugin destroyed");
|
|
6851
|
+
unsubscribeFromHls();
|
|
6852
|
+
unobserveTextTracks();
|
|
6663
6853
|
cleanupTracks();
|
|
6664
6854
|
video = null;
|
|
6665
6855
|
api = null;
|