@scarlett-player/embed 0.5.0 → 0.5.2

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.
@@ -33,7 +33,7 @@ function formatBitrate(bitrate) {
33
33
  }
34
34
  return `${bitrate} bps`;
35
35
  }
36
- function mapLevels(levels, currentLevel) {
36
+ function mapLevels(levels, _currentLevel) {
37
37
  return levels.map((level, index) => ({
38
38
  index,
39
39
  width: level.width || 0,
@@ -1006,6 +1006,8 @@ function createStyles(prefix, theme) {
1006
1006
  align-items: center;
1007
1007
  justify-content: center;
1008
1008
  transition: background 0.2s, transform 0.1s;
1009
+ min-width: 44px;
1010
+ min-height: 44px;
1009
1011
  }
1010
1012
 
1011
1013
  .${prefix}__btn:hover {
@@ -1155,6 +1157,8 @@ function createAudioUIPlugin(config) {
1155
1157
  document.head.appendChild(styleElement);
1156
1158
  container = document.createElement("div");
1157
1159
  container.className = `${prefix} ${prefix}--${layout}`;
1160
+ container.setAttribute("role", "region");
1161
+ container.setAttribute("aria-label", "Audio player");
1158
1162
  if (layout === "full") {
1159
1163
  container.innerHTML = buildFullLayout();
1160
1164
  } else if (layout === "compact") {
@@ -1189,24 +1193,24 @@ function createAudioUIPlugin(config) {
1189
1193
  ${mergedConfig.showArtist ? `<div class="${prefix}__artist">-</div>` : ""}
1190
1194
  </div>
1191
1195
  <div class="${prefix}__progress">
1192
- ${mergedConfig.showTime ? `<span class="${prefix}__time ${prefix}__time--current">0:00</span>` : ""}
1193
- <div class="${prefix}__progress-bar">
1196
+ ${mergedConfig.showTime ? `<span class="${prefix}__time ${prefix}__time--current" aria-label="Current time">0:00</span>` : ""}
1197
+ <div class="${prefix}__progress-bar" role="slider" aria-label="Seek" aria-valuemin="0" aria-valuemax="0" aria-valuenow="0" aria-valuetext="0:00" tabindex="0">
1194
1198
  <div class="${prefix}__progress-fill" style="transform: scaleX(0)"></div>
1195
1199
  </div>
1196
- ${mergedConfig.showTime ? `<span class="${prefix}__time ${prefix}__time--duration">0:00</span>` : ""}
1200
+ ${mergedConfig.showTime ? `<span class="${prefix}__time ${prefix}__time--duration" aria-label="Duration">0:00</span>` : ""}
1197
1201
  </div>
1198
- <div class="${prefix}__controls">
1199
- ${mergedConfig.showShuffle ? `<button class="${prefix}__btn ${prefix}__btn--shuffle" title="Shuffle">${ICONS.shuffle}</button>` : ""}
1200
- ${mergedConfig.showNavigation ? `<button class="${prefix}__btn ${prefix}__btn--prev" title="Previous">${ICONS.previous}</button>` : ""}
1201
- <button class="${prefix}__btn ${prefix}__btn--primary ${prefix}__btn--play" title="Play">${ICONS.play}</button>
1202
- ${mergedConfig.showNavigation ? `<button class="${prefix}__btn ${prefix}__btn--next" title="Next">${ICONS.next}</button>` : ""}
1203
- ${mergedConfig.showRepeat ? `<button class="${prefix}__btn ${prefix}__btn--repeat" title="Repeat">${ICONS.repeatOff}</button>` : ""}
1202
+ <div class="${prefix}__controls" role="group" aria-label="Playback controls">
1203
+ ${mergedConfig.showShuffle ? `<button class="${prefix}__btn ${prefix}__btn--shuffle" title="Shuffle" aria-label="Shuffle" aria-pressed="false">${ICONS.shuffle}</button>` : ""}
1204
+ ${mergedConfig.showNavigation ? `<button class="${prefix}__btn ${prefix}__btn--prev" title="Previous" aria-label="Previous track">${ICONS.previous}</button>` : ""}
1205
+ <button class="${prefix}__btn ${prefix}__btn--primary ${prefix}__btn--play" title="Play" aria-label="Play">${ICONS.play}</button>
1206
+ ${mergedConfig.showNavigation ? `<button class="${prefix}__btn ${prefix}__btn--next" title="Next" aria-label="Next track">${ICONS.next}</button>` : ""}
1207
+ ${mergedConfig.showRepeat ? `<button class="${prefix}__btn ${prefix}__btn--repeat" title="Repeat" aria-label="Repeat" aria-pressed="false">${ICONS.repeatOff}</button>` : ""}
1204
1208
  </div>
1205
1209
  ${mergedConfig.showVolume ? `
1206
1210
  <div class="${prefix}__secondary-controls">
1207
- <div class="${prefix}__volume">
1208
- <button class="${prefix}__btn ${prefix}__btn--volume" title="Volume">${ICONS.volumeHigh}</button>
1209
- <div class="${prefix}__volume-slider">
1211
+ <div class="${prefix}__volume" role="group" aria-label="Volume controls">
1212
+ <button class="${prefix}__btn ${prefix}__btn--volume" title="Volume" aria-label="Mute">${ICONS.volumeHigh}</button>
1213
+ <div class="${prefix}__volume-slider" role="slider" aria-label="Volume" aria-valuemin="0" aria-valuemax="100" aria-valuenow="100" aria-valuetext="100%" tabindex="0">
1210
1214
  <div class="${prefix}__volume-fill" style="width: 100%"></div>
1211
1215
  </div>
1212
1216
  </div>
@@ -1225,21 +1229,21 @@ function createAudioUIPlugin(config) {
1225
1229
  ${mergedConfig.showTitle ? `<div class="${prefix}__title">-</div>` : ""}
1226
1230
  ${mergedConfig.showArtist ? `<div class="${prefix}__artist">-</div>` : ""}
1227
1231
  <div class="${prefix}__progress">
1228
- <div class="${prefix}__progress-bar">
1232
+ <div class="${prefix}__progress-bar" role="slider" aria-label="Seek" aria-valuemin="0" aria-valuemax="0" aria-valuenow="0" aria-valuetext="0:00" tabindex="0">
1229
1233
  <div class="${prefix}__progress-fill" style="transform: scaleX(0)"></div>
1230
1234
  </div>
1231
1235
  </div>
1232
1236
  </div>
1233
- <div class="${prefix}__controls">
1234
- ${mergedConfig.showNavigation ? `<button class="${prefix}__btn ${prefix}__btn--prev" title="Previous">${ICONS.previous}</button>` : ""}
1235
- <button class="${prefix}__btn ${prefix}__btn--primary ${prefix}__btn--play" title="Play">${ICONS.play}</button>
1236
- ${mergedConfig.showNavigation ? `<button class="${prefix}__btn ${prefix}__btn--next" title="Next">${ICONS.next}</button>` : ""}
1237
+ <div class="${prefix}__controls" role="group" aria-label="Playback controls">
1238
+ ${mergedConfig.showNavigation ? `<button class="${prefix}__btn ${prefix}__btn--prev" title="Previous" aria-label="Previous track">${ICONS.previous}</button>` : ""}
1239
+ <button class="${prefix}__btn ${prefix}__btn--primary ${prefix}__btn--play" title="Play" aria-label="Play">${ICONS.play}</button>
1240
+ ${mergedConfig.showNavigation ? `<button class="${prefix}__btn ${prefix}__btn--next" title="Next" aria-label="Next track">${ICONS.next}</button>` : ""}
1237
1241
  </div>
1238
1242
  `;
1239
1243
  };
1240
1244
  const buildMiniLayout = () => {
1241
1245
  return `
1242
- <button class="${prefix}__btn ${prefix}__btn--primary ${prefix}__btn--play" title="Play">${ICONS.play}</button>
1246
+ <button class="${prefix}__btn ${prefix}__btn--primary ${prefix}__btn--play" title="Play" aria-label="Play">${ICONS.play}</button>
1243
1247
  ${mergedConfig.showArtwork ? `
1244
1248
  <div class="${prefix}__artwork">
1245
1249
  <img src="${mergedConfig.defaultArtwork || ""}" alt="Album art" />
@@ -1248,7 +1252,7 @@ function createAudioUIPlugin(config) {
1248
1252
  <div class="${prefix}__info">
1249
1253
  ${mergedConfig.showTitle ? `<div class="${prefix}__title-wrapper"><div class="${prefix}__title">-</div></div>` : ""}
1250
1254
  <div class="${prefix}__progress">
1251
- <div class="${prefix}__progress-bar">
1255
+ <div class="${prefix}__progress-bar" role="slider" aria-label="Seek" aria-valuemin="0" aria-valuemax="0" aria-valuenow="0" aria-valuetext="0:00" tabindex="0">
1252
1256
  <div class="${prefix}__progress-fill" style="transform: scaleX(0)"></div>
1253
1257
  </div>
1254
1258
  </div>
@@ -1314,6 +1318,7 @@ function createAudioUIPlugin(config) {
1314
1318
  if (playPauseBtn) {
1315
1319
  playPauseBtn.innerHTML = playing ? ICONS.pause : ICONS.play;
1316
1320
  playPauseBtn.title = playing ? "Pause" : "Play";
1321
+ playPauseBtn.setAttribute("aria-label", playing ? "Pause" : "Play");
1317
1322
  }
1318
1323
  const currentTime = api.getState("currentTime") || 0;
1319
1324
  const duration = api.getState("duration") || 0;
@@ -1336,6 +1341,12 @@ function createAudioUIPlugin(config) {
1336
1341
  if (durationEl) {
1337
1342
  durationEl.textContent = formatTime(duration);
1338
1343
  }
1344
+ const progressBar = container?.querySelector(`.${prefix}__progress-bar`);
1345
+ if (progressBar) {
1346
+ progressBar.setAttribute("aria-valuemax", String(Math.floor(duration)));
1347
+ progressBar.setAttribute("aria-valuenow", String(Math.floor(currentTime)));
1348
+ progressBar.setAttribute("aria-valuetext", formatTime(currentTime));
1349
+ }
1339
1350
  const title = api.getState("title");
1340
1351
  const poster = api.getState("poster");
1341
1352
  if (titleEl && title) {
@@ -1360,21 +1371,34 @@ function createAudioUIPlugin(config) {
1360
1371
  }
1361
1372
  if (volumeBtn) {
1362
1373
  volumeBtn.innerHTML = muted || volume === 0 ? ICONS.volumeMuted : ICONS.volumeHigh;
1374
+ volumeBtn.setAttribute("aria-label", muted || volume === 0 ? "Unmute" : "Mute");
1375
+ }
1376
+ const volumeSlider = container?.querySelector(`.${prefix}__volume-slider`);
1377
+ if (volumeSlider) {
1378
+ const displayVolume = Math.round((muted ? 0 : volume) * 100);
1379
+ volumeSlider.setAttribute("aria-valuenow", String(displayVolume));
1380
+ volumeSlider.setAttribute("aria-valuetext", `${displayVolume}%`);
1363
1381
  }
1364
1382
  const playlist = api.getPlugin("playlist");
1365
1383
  if (playlist) {
1366
1384
  const state = playlist.getState();
1367
1385
  if (shuffleBtn) {
1368
1386
  shuffleBtn.classList.toggle(`${prefix}__btn--active`, state.shuffle);
1387
+ shuffleBtn.setAttribute("aria-pressed", String(state.shuffle));
1388
+ shuffleBtn.setAttribute("aria-label", state.shuffle ? "Shuffle on" : "Shuffle off");
1369
1389
  }
1370
1390
  if (repeatBtn) {
1371
1391
  repeatBtn.classList.toggle(`${prefix}__btn--active`, state.repeat !== "none");
1392
+ repeatBtn.setAttribute("aria-pressed", String(state.repeat !== "none"));
1372
1393
  if (state.repeat === "one") {
1373
1394
  repeatBtn.innerHTML = ICONS.repeatOne;
1395
+ repeatBtn.setAttribute("aria-label", "Repeat one");
1374
1396
  } else if (state.repeat === "all") {
1375
1397
  repeatBtn.innerHTML = ICONS.repeatAll;
1398
+ repeatBtn.setAttribute("aria-label", "Repeat all");
1376
1399
  } else {
1377
1400
  repeatBtn.innerHTML = ICONS.repeatOff;
1401
+ repeatBtn.setAttribute("aria-label", "Repeat off");
1378
1402
  }
1379
1403
  }
1380
1404
  }
@@ -3595,6 +3619,7 @@ class ScarlettPlayer {
3595
3619
  this.destroyed = false;
3596
3620
  this.seekingWhilePlaying = false;
3597
3621
  this.seekResumeTimeout = null;
3622
+ this.loadGeneration = 0;
3598
3623
  if (typeof options.container === "string") {
3599
3624
  const el = document.querySelector(options.container);
3600
3625
  if (!el || !(el instanceof HTMLElement)) {
@@ -3668,6 +3693,7 @@ class ScarlettPlayer {
3668
3693
  */
3669
3694
  async load(source) {
3670
3695
  this.checkDestroyed();
3696
+ const generation = ++this.loadGeneration;
3671
3697
  try {
3672
3698
  this.logger.info("Loading source", { source });
3673
3699
  this.stateManager.update({
@@ -3686,6 +3712,10 @@ class ScarlettPlayer {
3686
3712
  await this.pluginManager.destroyPlugin(previousProviderId);
3687
3713
  this._currentProvider = null;
3688
3714
  }
3715
+ if (generation !== this.loadGeneration) {
3716
+ this.logger.info("Load superseded by newer load call", { source });
3717
+ return;
3718
+ }
3689
3719
  const provider = this.pluginManager.selectProvider(source);
3690
3720
  if (!provider) {
3691
3721
  this.errorHandler.throw(
@@ -3701,18 +3731,28 @@ class ScarlettPlayer {
3701
3731
  this._currentProvider = provider;
3702
3732
  this.logger.info("Provider selected", { provider: provider.id });
3703
3733
  await this.pluginManager.initPlugin(provider.id);
3734
+ if (generation !== this.loadGeneration) {
3735
+ this.logger.info("Load superseded by newer load call", { source });
3736
+ return;
3737
+ }
3704
3738
  this.stateManager.set("source", { src: source, type: this.detectMimeType(source) });
3705
3739
  if (typeof provider.loadSource === "function") {
3706
3740
  await provider.loadSource(source);
3707
3741
  }
3742
+ if (generation !== this.loadGeneration) {
3743
+ this.logger.info("Load superseded by newer load call", { source });
3744
+ return;
3745
+ }
3708
3746
  if (this.stateManager.getValue("autoplay")) {
3709
3747
  await this.play();
3710
3748
  }
3711
3749
  } catch (error) {
3712
- this.errorHandler.handle(error, {
3713
- operation: "load",
3714
- source
3715
- });
3750
+ if (generation === this.loadGeneration) {
3751
+ this.errorHandler.handle(error, {
3752
+ operation: "load",
3753
+ source
3754
+ });
3755
+ }
3716
3756
  }
3717
3757
  }
3718
3758
  /**