vidply 1.0.3 → 1.0.4

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/vidply.css CHANGED
@@ -1393,6 +1393,8 @@
1393
1393
  }
1394
1394
 
1395
1395
  .vidply-playlist-list {
1396
+ list-style: none;
1397
+ margin: 0;
1396
1398
  padding: 4px 0;
1397
1399
  }
1398
1400
 
@@ -1405,6 +1407,7 @@
1405
1407
  display: flex;
1406
1408
  gap: 12px;
1407
1409
  padding: 12px 16px;
1410
+ position: relative;
1408
1411
  transition: all 0.2s ease;
1409
1412
  }
1410
1413
 
@@ -1416,9 +1419,23 @@
1416
1419
  .vidply-playlist-item:focus {
1417
1420
  background: var(--vidply-white-08);
1418
1421
  border-left-color: var(--vidply-primary);
1422
+ outline: 2px solid var(--vidply-primary-light);
1423
+ outline-offset: -2px;
1424
+ z-index: 1;
1425
+ }
1426
+
1427
+ .vidply-playlist-item:focus:not(:focus-visible) {
1419
1428
  outline: none;
1420
1429
  }
1421
1430
 
1431
+ .vidply-playlist-item:focus-visible {
1432
+ background: var(--vidply-white-08);
1433
+ border-left-color: var(--vidply-primary);
1434
+ outline: 2px solid var(--vidply-primary-light);
1435
+ outline-offset: -2px;
1436
+ z-index: 1;
1437
+ }
1438
+
1422
1439
  .vidply-playlist-item-active {
1423
1440
  background: var(--vidply-primary-15);
1424
1441
  border-left-color: var(--vidply-primary);
@@ -1428,6 +1445,13 @@
1428
1445
  background: var(--vidply-primary-20);
1429
1446
  }
1430
1447
 
1448
+ .vidply-playlist-item-active:focus,
1449
+ .vidply-playlist-item-active:focus-visible {
1450
+ background: var(--vidply-primary-20);
1451
+ outline: 2px solid var(--vidply-primary-light);
1452
+ outline-offset: -2px;
1453
+ }
1454
+
1431
1455
  /* Playlist Thumbnail */
1432
1456
  .vidply-playlist-thumbnail {
1433
1457
  align-items: center;
@@ -1651,12 +1651,26 @@ var ControlBar = class {
1651
1651
  }
1652
1652
  createTimeDisplay() {
1653
1653
  const container = DOMUtils.createElement("div", {
1654
- className: `${this.player.options.classPrefix}-time`
1654
+ className: `${this.player.options.classPrefix}-time`,
1655
+ attributes: {
1656
+ "role": "group",
1657
+ "aria-label": "Time display"
1658
+ }
1655
1659
  });
1656
1660
  this.controls.currentTimeDisplay = DOMUtils.createElement("span", {
1657
1661
  className: `${this.player.options.classPrefix}-current-time`,
1658
- textContent: "00:00"
1662
+ attributes: {
1663
+ "aria-label": "0 seconds"
1664
+ }
1665
+ });
1666
+ const currentTimeVisual = DOMUtils.createElement("span", {
1667
+ textContent: "00:00",
1668
+ attributes: {
1669
+ "aria-hidden": "true"
1670
+ }
1659
1671
  });
1672
+ this.controls.currentTimeDisplay.appendChild(currentTimeVisual);
1673
+ this.controls.currentTimeVisual = currentTimeVisual;
1660
1674
  const separator = DOMUtils.createElement("span", {
1661
1675
  textContent: " / ",
1662
1676
  attributes: {
@@ -1665,8 +1679,18 @@ var ControlBar = class {
1665
1679
  });
1666
1680
  this.controls.durationDisplay = DOMUtils.createElement("span", {
1667
1681
  className: `${this.player.options.classPrefix}-duration`,
1668
- textContent: "00:00"
1682
+ attributes: {
1683
+ "aria-label": "Duration: 0 seconds"
1684
+ }
1685
+ });
1686
+ const durationVisual = DOMUtils.createElement("span", {
1687
+ textContent: "00:00",
1688
+ attributes: {
1689
+ "aria-hidden": "true"
1690
+ }
1669
1691
  });
1692
+ this.controls.durationDisplay.appendChild(durationVisual);
1693
+ this.controls.durationVisual = durationVisual;
1670
1694
  container.appendChild(this.controls.currentTimeDisplay);
1671
1695
  container.appendChild(separator);
1672
1696
  container.appendChild(this.controls.durationDisplay);
@@ -2490,13 +2514,17 @@ var ControlBar = class {
2490
2514
  const percent = this.player.state.currentTime / this.player.state.duration * 100;
2491
2515
  this.controls.played.style.width = `${percent}%`;
2492
2516
  this.controls.progress.setAttribute("aria-valuenow", String(Math.round(percent)));
2493
- if (this.controls.currentTimeDisplay) {
2494
- this.controls.currentTimeDisplay.textContent = TimeUtils.formatTime(this.player.state.currentTime);
2517
+ if (this.controls.currentTimeVisual) {
2518
+ const currentTime = this.player.state.currentTime;
2519
+ this.controls.currentTimeVisual.textContent = TimeUtils.formatTime(currentTime);
2520
+ this.controls.currentTimeDisplay.setAttribute("aria-label", TimeUtils.formatDuration(currentTime));
2495
2521
  }
2496
2522
  }
2497
2523
  updateDuration() {
2498
- if (this.controls.durationDisplay) {
2499
- this.controls.durationDisplay.textContent = TimeUtils.formatTime(this.player.state.duration);
2524
+ if (this.controls.durationVisual) {
2525
+ const duration = this.player.state.duration;
2526
+ this.controls.durationVisual.textContent = TimeUtils.formatTime(duration);
2527
+ this.controls.durationDisplay.setAttribute("aria-label", "Duration: " + TimeUtils.formatDuration(duration));
2500
2528
  }
2501
2529
  }
2502
2530
  updateVolumeDisplay() {
@@ -4558,6 +4586,17 @@ var Player = class extends EventEmitter {
4558
4586
  this.captionManager.destroy();
4559
4587
  this.captionManager = new CaptionManager(this);
4560
4588
  }
4589
+ if (this.transcriptManager) {
4590
+ const wasVisible = this.transcriptManager.isVisible;
4591
+ this.transcriptManager.destroy();
4592
+ this.transcriptManager = new TranscriptManager(this);
4593
+ if (wasVisible) {
4594
+ this.transcriptManager.showTranscript();
4595
+ }
4596
+ }
4597
+ if (this.controlBar) {
4598
+ this.updateControlBar();
4599
+ }
4561
4600
  this.emit("sourcechange", config);
4562
4601
  this.log("Media loaded successfully");
4563
4602
  } catch (error) {
@@ -4569,6 +4608,17 @@ var Player = class extends EventEmitter {
4569
4608
  * @param {string} src - New source URL
4570
4609
  * @returns {boolean}
4571
4610
  */
4611
+ /**
4612
+ * Update control bar to refresh button visibility based on available features
4613
+ */
4614
+ updateControlBar() {
4615
+ if (!this.controlBar) return;
4616
+ const controlBar = this.controlBar;
4617
+ controlBar.element.innerHTML = "";
4618
+ controlBar.createControls();
4619
+ controlBar.attachEvents();
4620
+ controlBar.setupAutoHide();
4621
+ }
4572
4622
  shouldChangeRenderer(src) {
4573
4623
  if (!this.renderer) return true;
4574
4624
  const isYouTube = src.includes("youtube.com") || src.includes("youtu.be");
@@ -5028,7 +5078,9 @@ var PlaylistManager = class {
5028
5078
  this.trackInfoElement = null;
5029
5079
  this.handleTrackEnd = this.handleTrackEnd.bind(this);
5030
5080
  this.handleTrackError = this.handleTrackError.bind(this);
5081
+ this.player.playlistManager = this;
5031
5082
  this.init();
5083
+ this.updatePlayerControls();
5032
5084
  }
5033
5085
  init() {
5034
5086
  this.player.on("ended", this.handleTrackEnd);
@@ -5037,6 +5089,17 @@ var PlaylistManager = class {
5037
5089
  this.createUI();
5038
5090
  }
5039
5091
  }
5092
+ /**
5093
+ * Update player controls to add playlist navigation buttons
5094
+ */
5095
+ updatePlayerControls() {
5096
+ if (!this.player.controlBar) return;
5097
+ const controlBar = this.player.controlBar;
5098
+ controlBar.element.innerHTML = "";
5099
+ controlBar.createControls();
5100
+ controlBar.attachEvents();
5101
+ controlBar.setupAutoHide();
5102
+ }
5040
5103
  /**
5041
5104
  * Load a playlist
5042
5105
  * @param {Array} tracks - Array of track objects
@@ -5057,8 +5120,9 @@ var PlaylistManager = class {
5057
5120
  /**
5058
5121
  * Play a specific track
5059
5122
  * @param {number} index - Track index
5123
+ * @param {boolean} userInitiated - Whether this was triggered by user action (default: false)
5060
5124
  */
5061
- play(index) {
5125
+ play(index, userInitiated = false) {
5062
5126
  if (index < 0 || index >= this.tracks.length) {
5063
5127
  console.warn("VidPly Playlist: Invalid track index", index);
5064
5128
  return;
@@ -5078,6 +5142,9 @@ var PlaylistManager = class {
5078
5142
  item: track,
5079
5143
  total: this.tracks.length
5080
5144
  });
5145
+ if (userInitiated && this.player.container) {
5146
+ this.player.container.focus();
5147
+ }
5081
5148
  setTimeout(() => {
5082
5149
  this.player.play();
5083
5150
  }, 100);
@@ -5139,12 +5206,17 @@ var PlaylistManager = class {
5139
5206
  return;
5140
5207
  }
5141
5208
  this.trackInfoElement = DOMUtils.createElement("div", {
5142
- className: "vidply-track-info"
5209
+ className: "vidply-track-info",
5210
+ role: "status",
5211
+ "aria-live": "polite",
5212
+ "aria-atomic": "true"
5143
5213
  });
5144
5214
  this.trackInfoElement.style.display = "none";
5145
5215
  this.container.appendChild(this.trackInfoElement);
5146
5216
  this.playlistPanel = DOMUtils.createElement("div", {
5147
- className: "vidply-playlist-panel"
5217
+ className: "vidply-playlist-panel",
5218
+ role: "region",
5219
+ "aria-label": "Media playlist"
5148
5220
  });
5149
5221
  this.playlistPanel.style.display = "none";
5150
5222
  this.container.appendChild(this.playlistPanel);
@@ -5156,10 +5228,14 @@ var PlaylistManager = class {
5156
5228
  if (!this.trackInfoElement) return;
5157
5229
  const trackNumber = this.currentIndex + 1;
5158
5230
  const totalTracks = this.tracks.length;
5231
+ const trackTitle = track.title || "Untitled";
5232
+ const trackArtist = track.artist || "";
5233
+ const announcement = `Now playing: Track ${trackNumber} of ${totalTracks}. ${trackTitle}${trackArtist ? " by " + trackArtist : ""}`;
5159
5234
  this.trackInfoElement.innerHTML = `
5160
- <div class="vidply-track-number">Track ${trackNumber} of ${totalTracks}</div>
5161
- <div class="vidply-track-title">${DOMUtils.escapeHTML(track.title || "Untitled")}</div>
5162
- ${track.artist ? `<div class="vidply-track-artist">${DOMUtils.escapeHTML(track.artist)}</div>` : ""}
5235
+ <span class="vidply-sr-only">${DOMUtils.escapeHTML(announcement)}</span>
5236
+ <div class="vidply-track-number" aria-hidden="true">Track ${trackNumber} of ${totalTracks}</div>
5237
+ <div class="vidply-track-title" aria-hidden="true">${DOMUtils.escapeHTML(trackTitle)}</div>
5238
+ ${trackArtist ? `<div class="vidply-track-artist" aria-hidden="true">${DOMUtils.escapeHTML(trackArtist)}</div>` : ""}
5163
5239
  `;
5164
5240
  this.trackInfoElement.style.display = "block";
5165
5241
  }
@@ -5169,14 +5245,29 @@ var PlaylistManager = class {
5169
5245
  renderPlaylist() {
5170
5246
  if (!this.playlistPanel) return;
5171
5247
  this.playlistPanel.innerHTML = "";
5172
- const header = DOMUtils.createElement("div", {
5173
- className: "vidply-playlist-header"
5248
+ const header = DOMUtils.createElement("h2", {
5249
+ className: "vidply-playlist-header",
5250
+ id: "vidply-playlist-heading"
5174
5251
  });
5175
5252
  header.textContent = `Playlist (${this.tracks.length})`;
5176
5253
  this.playlistPanel.appendChild(header);
5177
- const list = DOMUtils.createElement("div", {
5178
- className: "vidply-playlist-list"
5179
- });
5254
+ const instructions = DOMUtils.createElement("div", {
5255
+ className: "vidply-sr-only",
5256
+ "aria-hidden": "false"
5257
+ });
5258
+ instructions.textContent = "Use arrow keys to navigate between tracks. Press Enter or Space to play a track. Press Home or End to jump to first or last track.";
5259
+ this.playlistPanel.appendChild(instructions);
5260
+ const list = DOMUtils.createElement("ul", {
5261
+ className: "vidply-playlist-list",
5262
+ "aria-labelledby": "vidply-playlist-heading",
5263
+ "aria-describedby": "vidply-playlist-instructions"
5264
+ });
5265
+ const listDescription = DOMUtils.createElement("div", {
5266
+ className: "vidply-sr-only",
5267
+ id: "vidply-playlist-instructions"
5268
+ });
5269
+ listDescription.textContent = `Playlist with ${this.tracks.length} ${this.tracks.length === 1 ? "track" : "tracks"}`;
5270
+ this.playlistPanel.appendChild(listDescription);
5180
5271
  this.tracks.forEach((track, index) => {
5181
5272
  const item = this.createPlaylistItem(track, index);
5182
5273
  list.appendChild(item);
@@ -5188,20 +5279,39 @@ var PlaylistManager = class {
5188
5279
  * Create playlist item element
5189
5280
  */
5190
5281
  createPlaylistItem(track, index) {
5191
- const item = DOMUtils.createElement("div", {
5282
+ const trackPosition = `Track ${index + 1} of ${this.tracks.length}`;
5283
+ const trackTitle = track.title || `Track ${index + 1}`;
5284
+ const trackArtist = track.artist ? ` by ${track.artist}` : "";
5285
+ const isActive = index === this.currentIndex;
5286
+ const statusText = isActive ? "Currently playing" : "Not playing";
5287
+ const actionText = isActive ? "Press Enter to restart" : "Press Enter to play";
5288
+ const item = DOMUtils.createElement("li", {
5192
5289
  className: "vidply-playlist-item",
5193
- role: "button",
5194
- tabIndex: 0,
5195
- "aria-label": `Play ${track.title || "Track " + (index + 1)}`
5196
- });
5197
- if (index === this.currentIndex) {
5290
+ tabIndex: index === 0 ? 0 : -1,
5291
+ // Only first item is in tab order initially
5292
+ "aria-label": `${trackPosition}. ${trackTitle}${trackArtist}. ${statusText}. ${actionText}.`,
5293
+ "aria-posinset": index + 1,
5294
+ "aria-setsize": this.tracks.length,
5295
+ "data-playlist-index": index
5296
+ });
5297
+ if (isActive) {
5198
5298
  item.classList.add("vidply-playlist-item-active");
5299
+ item.setAttribute("aria-current", "true");
5300
+ item.setAttribute("tabIndex", "0");
5199
5301
  }
5302
+ const positionInfo = DOMUtils.createElement("span", {
5303
+ className: "vidply-sr-only"
5304
+ });
5305
+ positionInfo.textContent = `${trackPosition}: `;
5306
+ item.appendChild(positionInfo);
5200
5307
  const thumbnail = DOMUtils.createElement("div", {
5201
- className: "vidply-playlist-thumbnail"
5308
+ className: "vidply-playlist-thumbnail",
5309
+ "aria-hidden": "true"
5202
5310
  });
5203
5311
  if (track.poster) {
5204
5312
  thumbnail.style.backgroundImage = `url(${track.poster})`;
5313
+ thumbnail.setAttribute("role", "img");
5314
+ thumbnail.setAttribute("aria-label", `${trackTitle} thumbnail`);
5205
5315
  } else {
5206
5316
  const icon = createIconElement("music");
5207
5317
  icon.classList.add("vidply-playlist-thumbnail-icon");
@@ -5209,12 +5319,13 @@ var PlaylistManager = class {
5209
5319
  }
5210
5320
  item.appendChild(thumbnail);
5211
5321
  const info = DOMUtils.createElement("div", {
5212
- className: "vidply-playlist-item-info"
5322
+ className: "vidply-playlist-item-info",
5323
+ "aria-hidden": "true"
5213
5324
  });
5214
5325
  const title = DOMUtils.createElement("div", {
5215
5326
  className: "vidply-playlist-item-title"
5216
5327
  });
5217
- title.textContent = track.title || `Track ${index + 1}`;
5328
+ title.textContent = trackTitle;
5218
5329
  info.appendChild(title);
5219
5330
  if (track.artist) {
5220
5331
  const artist = DOMUtils.createElement("div", {
@@ -5224,20 +5335,64 @@ var PlaylistManager = class {
5224
5335
  info.appendChild(artist);
5225
5336
  }
5226
5337
  item.appendChild(info);
5338
+ if (isActive) {
5339
+ const statusIndicator = DOMUtils.createElement("span", {
5340
+ className: "vidply-sr-only"
5341
+ });
5342
+ statusIndicator.textContent = " (Currently playing)";
5343
+ item.appendChild(statusIndicator);
5344
+ }
5227
5345
  const playIcon = createIconElement("play");
5228
5346
  playIcon.classList.add("vidply-playlist-item-icon");
5347
+ playIcon.setAttribute("aria-hidden", "true");
5229
5348
  item.appendChild(playIcon);
5230
5349
  item.addEventListener("click", () => {
5231
- this.play(index);
5350
+ this.play(index, true);
5232
5351
  });
5233
5352
  item.addEventListener("keydown", (e) => {
5234
- if (e.key === "Enter" || e.key === " ") {
5235
- e.preventDefault();
5236
- this.play(index);
5237
- }
5353
+ this.handlePlaylistItemKeydown(e, index);
5238
5354
  });
5239
5355
  return item;
5240
5356
  }
5357
+ /**
5358
+ * Handle keyboard navigation in playlist items
5359
+ */
5360
+ handlePlaylistItemKeydown(e, index) {
5361
+ const items = Array.from(this.playlistPanel.querySelectorAll(".vidply-playlist-item"));
5362
+ let newIndex = -1;
5363
+ switch (e.key) {
5364
+ case "Enter":
5365
+ case " ":
5366
+ e.preventDefault();
5367
+ this.play(index, true);
5368
+ break;
5369
+ case "ArrowDown":
5370
+ e.preventDefault();
5371
+ if (index < items.length - 1) {
5372
+ newIndex = index + 1;
5373
+ }
5374
+ break;
5375
+ case "ArrowUp":
5376
+ e.preventDefault();
5377
+ if (index > 0) {
5378
+ newIndex = index - 1;
5379
+ }
5380
+ break;
5381
+ case "Home":
5382
+ e.preventDefault();
5383
+ newIndex = 0;
5384
+ break;
5385
+ case "End":
5386
+ e.preventDefault();
5387
+ newIndex = items.length - 1;
5388
+ break;
5389
+ }
5390
+ if (newIndex !== -1 && newIndex !== index) {
5391
+ items[index].setAttribute("tabIndex", "-1");
5392
+ items[newIndex].setAttribute("tabIndex", "0");
5393
+ items[newIndex].focus();
5394
+ }
5395
+ }
5241
5396
  /**
5242
5397
  * Update playlist UI (highlight current track)
5243
5398
  */
@@ -5245,11 +5400,25 @@ var PlaylistManager = class {
5245
5400
  if (!this.playlistPanel) return;
5246
5401
  const items = this.playlistPanel.querySelectorAll(".vidply-playlist-item");
5247
5402
  items.forEach((item, index) => {
5403
+ const track = this.tracks[index];
5404
+ const trackPosition = `Track ${index + 1} of ${this.tracks.length}`;
5405
+ const trackTitle = track.title || `Track ${index + 1}`;
5406
+ const trackArtist = track.artist ? ` by ${track.artist}` : "";
5248
5407
  if (index === this.currentIndex) {
5249
5408
  item.classList.add("vidply-playlist-item-active");
5409
+ item.setAttribute("aria-current", "true");
5410
+ item.setAttribute("tabIndex", "0");
5411
+ const statusText = "Currently playing";
5412
+ const actionText = "Press Enter to restart";
5413
+ item.setAttribute("aria-label", `${trackPosition}. ${trackTitle}${trackArtist}. ${statusText}. ${actionText}.`);
5250
5414
  item.scrollIntoView({ behavior: "smooth", block: "nearest" });
5251
5415
  } else {
5252
5416
  item.classList.remove("vidply-playlist-item-active");
5417
+ item.removeAttribute("aria-current");
5418
+ item.setAttribute("tabIndex", "-1");
5419
+ const statusText = "Not playing";
5420
+ const actionText = "Press Enter to play";
5421
+ item.setAttribute("aria-label", `${trackPosition}. ${trackTitle}${trackArtist}. ${statusText}. ${actionText}.`);
5253
5422
  }
5254
5423
  });
5255
5424
  }
@@ -5267,10 +5436,24 @@ var PlaylistManager = class {
5267
5436
  currentIndex: this.currentIndex,
5268
5437
  totalTracks: this.tracks.length,
5269
5438
  currentTrack: this.getCurrentTrack(),
5270
- hasNext: this.currentIndex < this.tracks.length - 1,
5271
- hasPrevious: this.currentIndex > 0
5439
+ hasNext: this.hasNext(),
5440
+ hasPrevious: this.hasPrevious()
5272
5441
  };
5273
5442
  }
5443
+ /**
5444
+ * Check if there is a next track
5445
+ */
5446
+ hasNext() {
5447
+ if (this.options.loop) return true;
5448
+ return this.currentIndex < this.tracks.length - 1;
5449
+ }
5450
+ /**
5451
+ * Check if there is a previous track
5452
+ */
5453
+ hasPrevious() {
5454
+ if (this.options.loop) return true;
5455
+ return this.currentIndex > 0;
5456
+ }
5274
5457
  /**
5275
5458
  * Add track to playlist
5276
5459
  */