@scarlett-player/embed 0.5.2 → 1.0.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 +138 -9
- 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 +511 -10
- 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 +485 -7
- 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 +11 -9
package/dist/embed.audio.js
CHANGED
|
@@ -43,6 +43,18 @@ function mapLevels(levels, _currentLevel) {
|
|
|
43
43
|
codec: level.codecSet
|
|
44
44
|
}));
|
|
45
45
|
}
|
|
46
|
+
function getInitialBandwidthEstimate(overrideBps) {
|
|
47
|
+
const HLS_DEFAULT_ESTIMATE = 5e5;
|
|
48
|
+
if (overrideBps !== void 0 && overrideBps > 0) {
|
|
49
|
+
return overrideBps;
|
|
50
|
+
}
|
|
51
|
+
const connection = navigator.connection;
|
|
52
|
+
if (connection?.downlink && connection.downlink > 0) {
|
|
53
|
+
const bps = connection.downlink * 1e6;
|
|
54
|
+
return Math.round(bps * 0.85);
|
|
55
|
+
}
|
|
56
|
+
return HLS_DEFAULT_ESTIMATE;
|
|
57
|
+
}
|
|
46
58
|
var HLS_ERROR_TYPES = {
|
|
47
59
|
NETWORK_ERROR: "networkError",
|
|
48
60
|
MEDIA_ERROR: "mediaError",
|
|
@@ -113,6 +125,14 @@ function setupHlsEventHandlers(hls, api, callbacks) {
|
|
|
113
125
|
});
|
|
114
126
|
callbacks.onLevelSwitched?.(data.level);
|
|
115
127
|
});
|
|
128
|
+
let lastBandwidthUpdate = 0;
|
|
129
|
+
addHandler("hlsFragLoaded", () => {
|
|
130
|
+
const now = Date.now();
|
|
131
|
+
if (now - lastBandwidthUpdate >= 2e3 && hls.bandwidthEstimate) {
|
|
132
|
+
lastBandwidthUpdate = now;
|
|
133
|
+
api.setState("bandwidth", Math.round(hls.bandwidthEstimate));
|
|
134
|
+
}
|
|
135
|
+
});
|
|
116
136
|
addHandler("hlsFragBuffered", () => {
|
|
117
137
|
api.setState("buffering", false);
|
|
118
138
|
callbacks.onBufferUpdate?.();
|
|
@@ -123,12 +143,44 @@ function setupHlsEventHandlers(hls, api, callbacks) {
|
|
|
123
143
|
addHandler("hlsLevelLoaded", (_event, data) => {
|
|
124
144
|
if (data.details?.live !== void 0) {
|
|
125
145
|
api.setState("live", data.details.live);
|
|
146
|
+
if (data.details.live) {
|
|
147
|
+
const video = hls.media;
|
|
148
|
+
if (video && video.seekable && video.seekable.length > 0) {
|
|
149
|
+
const start = video.seekable.start(0);
|
|
150
|
+
const end = video.seekable.end(video.seekable.length - 1);
|
|
151
|
+
api.setState("seekableRange", { start, end });
|
|
152
|
+
const threshold = (data.details.targetduration ?? 3) * 3;
|
|
153
|
+
const isAtLiveEdge = end - video.currentTime < threshold;
|
|
154
|
+
api.setState("liveEdge", isAtLiveEdge);
|
|
155
|
+
const latency = end - video.currentTime;
|
|
156
|
+
api.setState("liveLatency", Math.max(0, latency));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
126
159
|
callbacks.onLiveUpdate?.();
|
|
127
160
|
}
|
|
128
161
|
});
|
|
129
162
|
addHandler("hlsError", (_event, data) => {
|
|
130
163
|
const error = parseHlsError(data);
|
|
131
|
-
|
|
164
|
+
const isBufferHoleSeek = !error.fatal && (error.details?.includes("bufferStalledError") || data.reason?.includes("buffer holes"));
|
|
165
|
+
if (isBufferHoleSeek) {
|
|
166
|
+
api.logger.debug(`HLS buffer recovery: ${error.reason || error.details}`, {
|
|
167
|
+
details: error.details,
|
|
168
|
+
reason: error.reason
|
|
169
|
+
});
|
|
170
|
+
} else if (error.fatal) {
|
|
171
|
+
api.logger.error(`HLS fatal error: ${error.details} (type=${error.type})`, {
|
|
172
|
+
type: error.type,
|
|
173
|
+
details: error.details,
|
|
174
|
+
url: error.url
|
|
175
|
+
});
|
|
176
|
+
} else {
|
|
177
|
+
api.logger.warn(`HLS error: ${error.details} (type=${error.type}, fatal=${error.fatal})`, {
|
|
178
|
+
type: error.type,
|
|
179
|
+
details: error.details,
|
|
180
|
+
fatal: error.fatal,
|
|
181
|
+
url: error.url
|
|
182
|
+
});
|
|
183
|
+
}
|
|
132
184
|
callbacks.onError?.(error);
|
|
133
185
|
});
|
|
134
186
|
return () => {
|
|
@@ -150,6 +202,8 @@ function setupVideoEventHandlers(video, api) {
|
|
|
150
202
|
addHandler("playing", () => {
|
|
151
203
|
api.setState("playing", true);
|
|
152
204
|
api.setState("paused", false);
|
|
205
|
+
api.setState("waiting", false);
|
|
206
|
+
api.setState("buffering", false);
|
|
153
207
|
api.setState("playbackState", "playing");
|
|
154
208
|
});
|
|
155
209
|
addHandler("pause", () => {
|
|
@@ -166,6 +220,15 @@ function setupVideoEventHandlers(video, api) {
|
|
|
166
220
|
addHandler("timeupdate", () => {
|
|
167
221
|
api.setState("currentTime", video.currentTime);
|
|
168
222
|
api.emit("playback:timeupdate", { currentTime: video.currentTime });
|
|
223
|
+
const isLive = api.getState("live");
|
|
224
|
+
if (isLive && video.seekable && video.seekable.length > 0) {
|
|
225
|
+
const start = video.seekable.start(0);
|
|
226
|
+
const end = video.seekable.end(video.seekable.length - 1);
|
|
227
|
+
api.setState("seekableRange", { start, end });
|
|
228
|
+
const isAtLiveEdge = end - video.currentTime < 10;
|
|
229
|
+
api.setState("liveEdge", isAtLiveEdge);
|
|
230
|
+
api.setState("liveLatency", Math.max(0, end - video.currentTime));
|
|
231
|
+
}
|
|
169
232
|
});
|
|
170
233
|
addHandler("durationchange", () => {
|
|
171
234
|
api.setState("duration", video.duration || 0);
|
|
@@ -312,6 +375,7 @@ var DEFAULT_CONFIG$3 = {
|
|
|
312
375
|
maxMaxBufferLength: 600,
|
|
313
376
|
backBufferLength: 30,
|
|
314
377
|
enableWorker: true,
|
|
378
|
+
capLevelToPlayerSize: true,
|
|
315
379
|
// Error recovery settings
|
|
316
380
|
maxNetworkRetries: 3,
|
|
317
381
|
maxMediaRetries: 2,
|
|
@@ -381,11 +445,13 @@ function createHLSPlugin(config) {
|
|
|
381
445
|
startPosition: mergedConfig.startPosition,
|
|
382
446
|
startLevel: -1,
|
|
383
447
|
// Auto quality selection (ABR)
|
|
448
|
+
abrEwmaDefaultEstimate: getInitialBandwidthEstimate(mergedConfig.initialBandwidthEstimate),
|
|
384
449
|
lowLatencyMode: mergedConfig.lowLatencyMode,
|
|
385
450
|
maxBufferLength: mergedConfig.maxBufferLength,
|
|
386
451
|
maxMaxBufferLength: mergedConfig.maxMaxBufferLength,
|
|
387
452
|
backBufferLength: mergedConfig.backBufferLength,
|
|
388
453
|
enableWorker: mergedConfig.enableWorker,
|
|
454
|
+
capLevelToPlayerSize: mergedConfig.capLevelToPlayerSize,
|
|
389
455
|
// Minimize hls.js internal retries - we handle retries ourselves
|
|
390
456
|
fragLoadingMaxRetry: 1,
|
|
391
457
|
manifestLoadingMaxRetry: 1,
|
|
@@ -656,7 +722,10 @@ function createHLSPlugin(config) {
|
|
|
656
722
|
currentSrc = src;
|
|
657
723
|
api.setState("playbackState", "loading");
|
|
658
724
|
api.setState("buffering", true);
|
|
659
|
-
if (
|
|
725
|
+
if (api.getState("airplayActive") && supportsNativeHLS()) {
|
|
726
|
+
api.logger.info("Using native HLS (AirPlay active)");
|
|
727
|
+
await loadNative(src);
|
|
728
|
+
} else if (isHlsJsSupported()) {
|
|
660
729
|
api.logger.info("Using hls.js for HLS playback");
|
|
661
730
|
await loadWithHlsJs(src);
|
|
662
731
|
} else if (supportsNativeHLS()) {
|
|
@@ -1518,7 +1587,9 @@ var DEFAULT_CONFIG$1 = {
|
|
|
1518
1587
|
persist: false,
|
|
1519
1588
|
persistKey: "scarlett-playlist",
|
|
1520
1589
|
shuffle: false,
|
|
1521
|
-
repeat: "none"
|
|
1590
|
+
repeat: "none",
|
|
1591
|
+
autoLoad: true,
|
|
1592
|
+
advanceDelay: 0
|
|
1522
1593
|
};
|
|
1523
1594
|
function generateId() {
|
|
1524
1595
|
return `track-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
@@ -1662,6 +1733,9 @@ function createPlaylistPlugin(config) {
|
|
|
1662
1733
|
}
|
|
1663
1734
|
api?.setState("mediaType", track.type || "audio");
|
|
1664
1735
|
emitChange();
|
|
1736
|
+
if (mergedConfig.autoLoad !== false && track.src) {
|
|
1737
|
+
api?.emit("media:load-request", { src: track.src, autoplay: true });
|
|
1738
|
+
}
|
|
1665
1739
|
};
|
|
1666
1740
|
const plugin = {
|
|
1667
1741
|
id: "playlist",
|
|
@@ -1676,12 +1750,20 @@ function createPlaylistPlugin(config) {
|
|
|
1676
1750
|
if (shuffle && tracks.length > 0) {
|
|
1677
1751
|
generateShuffleOrder();
|
|
1678
1752
|
}
|
|
1753
|
+
let advanceTimeout = null;
|
|
1679
1754
|
const unsubEnded = api.on("playback:ended", () => {
|
|
1680
1755
|
if (!mergedConfig.autoAdvance) return;
|
|
1681
1756
|
const nextIdx = getNextIndex();
|
|
1682
1757
|
if (nextIdx >= 0) {
|
|
1683
|
-
|
|
1684
|
-
|
|
1758
|
+
const advance = () => {
|
|
1759
|
+
api?.logger.debug("Auto-advancing to next track", { nextIdx });
|
|
1760
|
+
setCurrentTrack(nextIdx);
|
|
1761
|
+
};
|
|
1762
|
+
if (mergedConfig.advanceDelay) {
|
|
1763
|
+
advanceTimeout = setTimeout(advance, mergedConfig.advanceDelay);
|
|
1764
|
+
} else {
|
|
1765
|
+
advance();
|
|
1766
|
+
}
|
|
1685
1767
|
} else {
|
|
1686
1768
|
api?.logger.info("Playlist ended");
|
|
1687
1769
|
api?.emit("playlist:ended", void 0);
|
|
@@ -1689,6 +1771,10 @@ function createPlaylistPlugin(config) {
|
|
|
1689
1771
|
});
|
|
1690
1772
|
api.onDestroy(() => {
|
|
1691
1773
|
unsubEnded();
|
|
1774
|
+
if (advanceTimeout) {
|
|
1775
|
+
clearTimeout(advanceTimeout);
|
|
1776
|
+
advanceTimeout = null;
|
|
1777
|
+
}
|
|
1692
1778
|
persistPlaylist();
|
|
1693
1779
|
});
|
|
1694
1780
|
},
|
|
@@ -3673,6 +3759,13 @@ class ScarlettPlayer {
|
|
|
3673
3759
|
await this.pluginManager.initPlugin(id);
|
|
3674
3760
|
}
|
|
3675
3761
|
}
|
|
3762
|
+
this.eventBus.on("media:load-request", async ({ src, autoplay }) => {
|
|
3763
|
+
if (this.stateManager.getValue("chromecastActive")) return;
|
|
3764
|
+
await this.load(src);
|
|
3765
|
+
if (autoplay !== false) {
|
|
3766
|
+
await this.play();
|
|
3767
|
+
}
|
|
3768
|
+
});
|
|
3676
3769
|
if (this.initialSrc) {
|
|
3677
3770
|
await this.load(this.initialSrc);
|
|
3678
3771
|
}
|
|
@@ -3880,8 +3973,9 @@ class ScarlettPlayer {
|
|
|
3880
3973
|
*/
|
|
3881
3974
|
setPlaybackRate(rate) {
|
|
3882
3975
|
this.checkDestroyed();
|
|
3883
|
-
|
|
3884
|
-
this.
|
|
3976
|
+
const clampedRate = Math.max(0.0625, Math.min(16, rate));
|
|
3977
|
+
this.stateManager.set("playbackRate", clampedRate);
|
|
3978
|
+
this.eventBus.emit("playback:ratechange", { rate: clampedRate });
|
|
3885
3979
|
}
|
|
3886
3980
|
/**
|
|
3887
3981
|
* Set autoplay state.
|
|
@@ -4010,6 +4104,13 @@ class ScarlettPlayer {
|
|
|
4010
4104
|
}
|
|
4011
4105
|
const provider = this._currentProvider;
|
|
4012
4106
|
if (typeof provider.setLevel === "function") {
|
|
4107
|
+
if (index !== -1) {
|
|
4108
|
+
const levels = this.getQualities();
|
|
4109
|
+
if (levels.length > 0 && (index < 0 || index >= levels.length)) {
|
|
4110
|
+
this.logger.warn(`Invalid quality index: ${index} (available: ${levels.length})`);
|
|
4111
|
+
return;
|
|
4112
|
+
}
|
|
4113
|
+
}
|
|
4013
4114
|
provider.setLevel(index);
|
|
4014
4115
|
this.eventBus.emit("quality:change", {
|
|
4015
4116
|
quality: index === -1 ? "auto" : `level-${index}`,
|
|
@@ -4250,18 +4351,40 @@ class ScarlettPlayer {
|
|
|
4250
4351
|
* @private
|
|
4251
4352
|
*/
|
|
4252
4353
|
detectMimeType(source) {
|
|
4253
|
-
|
|
4354
|
+
let path = source;
|
|
4355
|
+
try {
|
|
4356
|
+
path = new URL(source).pathname;
|
|
4357
|
+
} catch {
|
|
4358
|
+
const noQuery = source.split("?")[0] ?? source;
|
|
4359
|
+
path = noQuery.split("#")[0] ?? noQuery;
|
|
4360
|
+
}
|
|
4361
|
+
const ext = path.split(".").pop()?.toLowerCase() ?? "";
|
|
4254
4362
|
switch (ext) {
|
|
4255
4363
|
case "m3u8":
|
|
4256
4364
|
return "application/x-mpegURL";
|
|
4257
4365
|
case "mpd":
|
|
4258
4366
|
return "application/dash+xml";
|
|
4259
4367
|
case "mp4":
|
|
4368
|
+
case "m4v":
|
|
4260
4369
|
return "video/mp4";
|
|
4261
4370
|
case "webm":
|
|
4262
4371
|
return "video/webm";
|
|
4263
4372
|
case "ogg":
|
|
4373
|
+
case "ogv":
|
|
4264
4374
|
return "video/ogg";
|
|
4375
|
+
case "mov":
|
|
4376
|
+
return "video/quicktime";
|
|
4377
|
+
case "mkv":
|
|
4378
|
+
return "video/x-matroska";
|
|
4379
|
+
case "mp3":
|
|
4380
|
+
return "audio/mpeg";
|
|
4381
|
+
case "wav":
|
|
4382
|
+
return "audio/wav";
|
|
4383
|
+
case "flac":
|
|
4384
|
+
return "audio/flac";
|
|
4385
|
+
case "aac":
|
|
4386
|
+
case "m4a":
|
|
4387
|
+
return "audio/mp4";
|
|
4265
4388
|
default:
|
|
4266
4389
|
return "video/mp4";
|
|
4267
4390
|
}
|
|
@@ -4470,6 +4593,12 @@ async function createEmbedPlayer(container, config, pluginCreators2, availableTy
|
|
|
4470
4593
|
artwork: config.artwork || config.poster || config.playlist?.[0]?.artwork
|
|
4471
4594
|
}));
|
|
4472
4595
|
}
|
|
4596
|
+
if (pluginCreators2.watermark && config.watermark) {
|
|
4597
|
+
plugins.push(pluginCreators2.watermark(config.watermark));
|
|
4598
|
+
}
|
|
4599
|
+
if (pluginCreators2.captions) {
|
|
4600
|
+
plugins.push(pluginCreators2.captions(config.captions || {}));
|
|
4601
|
+
}
|
|
4473
4602
|
if (pluginCreators2.analytics && config.analytics?.beaconUrl) {
|
|
4474
4603
|
plugins.push(pluginCreators2.analytics({
|
|
4475
4604
|
beaconUrl: config.analytics.beaconUrl,
|
|
@@ -4587,7 +4716,7 @@ function setupAutoInit(pluginCreators2, availableTypes) {
|
|
|
4587
4716
|
}
|
|
4588
4717
|
}
|
|
4589
4718
|
}
|
|
4590
|
-
const VERSION = "0.3
|
|
4719
|
+
const VERSION = "0.5.3-audio";
|
|
4591
4720
|
const AVAILABLE_TYPES = ["audio", "audio-mini"];
|
|
4592
4721
|
const pluginCreators = {
|
|
4593
4722
|
hls: createHLSPlugin,
|