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.
@@ -37,7 +37,7 @@
37
37
  "format": "esm"
38
38
  },
39
39
  "src/controls/ControlBar.js": {
40
- "bytes": 70888,
40
+ "bytes": 74623,
41
41
  "imports": [
42
42
  {
43
43
  "path": "src/utils/DOMUtils.js",
@@ -136,7 +136,7 @@
136
136
  "format": "esm"
137
137
  },
138
138
  "src/core/Player.js": {
139
- "bytes": 34580,
139
+ "bytes": 35845,
140
140
  "imports": [
141
141
  {
142
142
  "path": "src/utils/EventEmitter.js",
@@ -202,7 +202,7 @@
202
202
  "format": "esm"
203
203
  },
204
204
  "src/features/PlaylistManager.js": {
205
- "bytes": 10068,
205
+ "bytes": 17043,
206
206
  "imports": [
207
207
  {
208
208
  "path": "src/utils/DOMUtils.js",
@@ -266,7 +266,7 @@
266
266
  "bytesInOutput": 720
267
267
  },
268
268
  "src/controls/ControlBar.js": {
269
- "bytesInOutput": 35686
269
+ "bytesInOutput": 36302
270
270
  },
271
271
  "src/controls/CaptionManager.js": {
272
272
  "bytesInOutput": 3599
@@ -278,7 +278,7 @@
278
278
  "bytesInOutput": 12461
279
279
  },
280
280
  "src/core/Player.js": {
281
- "bytesInOutput": 16383
281
+ "bytesInOutput": 16746
282
282
  },
283
283
  "src/renderers/YouTubeRenderer.js": {
284
284
  "bytesInOutput": 4140
@@ -290,13 +290,13 @@
290
290
  "bytesInOutput": 5386
291
291
  },
292
292
  "src/features/PlaylistManager.js": {
293
- "bytesInOutput": 5245
293
+ "bytesInOutput": 8100
294
294
  },
295
295
  "src/index.js": {
296
296
  "bytesInOutput": 1000
297
297
  }
298
298
  },
299
- "bytes": 123346
299
+ "bytes": 127180
300
300
  }
301
301
  }
302
302
  }
package/dist/vidply.js CHANGED
@@ -1671,12 +1671,26 @@ var VidPly = (() => {
1671
1671
  }
1672
1672
  createTimeDisplay() {
1673
1673
  const container = DOMUtils.createElement("div", {
1674
- className: `${this.player.options.classPrefix}-time`
1674
+ className: `${this.player.options.classPrefix}-time`,
1675
+ attributes: {
1676
+ "role": "group",
1677
+ "aria-label": "Time display"
1678
+ }
1675
1679
  });
1676
1680
  this.controls.currentTimeDisplay = DOMUtils.createElement("span", {
1677
1681
  className: `${this.player.options.classPrefix}-current-time`,
1678
- textContent: "00:00"
1682
+ attributes: {
1683
+ "aria-label": "0 seconds"
1684
+ }
1685
+ });
1686
+ const currentTimeVisual = DOMUtils.createElement("span", {
1687
+ textContent: "00:00",
1688
+ attributes: {
1689
+ "aria-hidden": "true"
1690
+ }
1679
1691
  });
1692
+ this.controls.currentTimeDisplay.appendChild(currentTimeVisual);
1693
+ this.controls.currentTimeVisual = currentTimeVisual;
1680
1694
  const separator = DOMUtils.createElement("span", {
1681
1695
  textContent: " / ",
1682
1696
  attributes: {
@@ -1685,8 +1699,18 @@ var VidPly = (() => {
1685
1699
  });
1686
1700
  this.controls.durationDisplay = DOMUtils.createElement("span", {
1687
1701
  className: `${this.player.options.classPrefix}-duration`,
1688
- textContent: "00:00"
1702
+ attributes: {
1703
+ "aria-label": "Duration: 0 seconds"
1704
+ }
1705
+ });
1706
+ const durationVisual = DOMUtils.createElement("span", {
1707
+ textContent: "00:00",
1708
+ attributes: {
1709
+ "aria-hidden": "true"
1710
+ }
1689
1711
  });
1712
+ this.controls.durationDisplay.appendChild(durationVisual);
1713
+ this.controls.durationVisual = durationVisual;
1690
1714
  container.appendChild(this.controls.currentTimeDisplay);
1691
1715
  container.appendChild(separator);
1692
1716
  container.appendChild(this.controls.durationDisplay);
@@ -2510,13 +2534,17 @@ var VidPly = (() => {
2510
2534
  const percent = this.player.state.currentTime / this.player.state.duration * 100;
2511
2535
  this.controls.played.style.width = `${percent}%`;
2512
2536
  this.controls.progress.setAttribute("aria-valuenow", String(Math.round(percent)));
2513
- if (this.controls.currentTimeDisplay) {
2514
- this.controls.currentTimeDisplay.textContent = TimeUtils.formatTime(this.player.state.currentTime);
2537
+ if (this.controls.currentTimeVisual) {
2538
+ const currentTime = this.player.state.currentTime;
2539
+ this.controls.currentTimeVisual.textContent = TimeUtils.formatTime(currentTime);
2540
+ this.controls.currentTimeDisplay.setAttribute("aria-label", TimeUtils.formatDuration(currentTime));
2515
2541
  }
2516
2542
  }
2517
2543
  updateDuration() {
2518
- if (this.controls.durationDisplay) {
2519
- this.controls.durationDisplay.textContent = TimeUtils.formatTime(this.player.state.duration);
2544
+ if (this.controls.durationVisual) {
2545
+ const duration = this.player.state.duration;
2546
+ this.controls.durationVisual.textContent = TimeUtils.formatTime(duration);
2547
+ this.controls.durationDisplay.setAttribute("aria-label", "Duration: " + TimeUtils.formatDuration(duration));
2520
2548
  }
2521
2549
  }
2522
2550
  updateVolumeDisplay() {
@@ -4578,6 +4606,17 @@ var VidPly = (() => {
4578
4606
  this.captionManager.destroy();
4579
4607
  this.captionManager = new CaptionManager(this);
4580
4608
  }
4609
+ if (this.transcriptManager) {
4610
+ const wasVisible = this.transcriptManager.isVisible;
4611
+ this.transcriptManager.destroy();
4612
+ this.transcriptManager = new TranscriptManager(this);
4613
+ if (wasVisible) {
4614
+ this.transcriptManager.showTranscript();
4615
+ }
4616
+ }
4617
+ if (this.controlBar) {
4618
+ this.updateControlBar();
4619
+ }
4581
4620
  this.emit("sourcechange", config);
4582
4621
  this.log("Media loaded successfully");
4583
4622
  } catch (error) {
@@ -4589,6 +4628,17 @@ var VidPly = (() => {
4589
4628
  * @param {string} src - New source URL
4590
4629
  * @returns {boolean}
4591
4630
  */
4631
+ /**
4632
+ * Update control bar to refresh button visibility based on available features
4633
+ */
4634
+ updateControlBar() {
4635
+ if (!this.controlBar) return;
4636
+ const controlBar = this.controlBar;
4637
+ controlBar.element.innerHTML = "";
4638
+ controlBar.createControls();
4639
+ controlBar.attachEvents();
4640
+ controlBar.setupAutoHide();
4641
+ }
4592
4642
  shouldChangeRenderer(src) {
4593
4643
  if (!this.renderer) return true;
4594
4644
  const isYouTube = src.includes("youtube.com") || src.includes("youtu.be");
@@ -5048,7 +5098,9 @@ var VidPly = (() => {
5048
5098
  this.trackInfoElement = null;
5049
5099
  this.handleTrackEnd = this.handleTrackEnd.bind(this);
5050
5100
  this.handleTrackError = this.handleTrackError.bind(this);
5101
+ this.player.playlistManager = this;
5051
5102
  this.init();
5103
+ this.updatePlayerControls();
5052
5104
  }
5053
5105
  init() {
5054
5106
  this.player.on("ended", this.handleTrackEnd);
@@ -5057,6 +5109,17 @@ var VidPly = (() => {
5057
5109
  this.createUI();
5058
5110
  }
5059
5111
  }
5112
+ /**
5113
+ * Update player controls to add playlist navigation buttons
5114
+ */
5115
+ updatePlayerControls() {
5116
+ if (!this.player.controlBar) return;
5117
+ const controlBar = this.player.controlBar;
5118
+ controlBar.element.innerHTML = "";
5119
+ controlBar.createControls();
5120
+ controlBar.attachEvents();
5121
+ controlBar.setupAutoHide();
5122
+ }
5060
5123
  /**
5061
5124
  * Load a playlist
5062
5125
  * @param {Array} tracks - Array of track objects
@@ -5077,8 +5140,9 @@ var VidPly = (() => {
5077
5140
  /**
5078
5141
  * Play a specific track
5079
5142
  * @param {number} index - Track index
5143
+ * @param {boolean} userInitiated - Whether this was triggered by user action (default: false)
5080
5144
  */
5081
- play(index) {
5145
+ play(index, userInitiated = false) {
5082
5146
  if (index < 0 || index >= this.tracks.length) {
5083
5147
  console.warn("VidPly Playlist: Invalid track index", index);
5084
5148
  return;
@@ -5098,6 +5162,9 @@ var VidPly = (() => {
5098
5162
  item: track,
5099
5163
  total: this.tracks.length
5100
5164
  });
5165
+ if (userInitiated && this.player.container) {
5166
+ this.player.container.focus();
5167
+ }
5101
5168
  setTimeout(() => {
5102
5169
  this.player.play();
5103
5170
  }, 100);
@@ -5159,12 +5226,17 @@ var VidPly = (() => {
5159
5226
  return;
5160
5227
  }
5161
5228
  this.trackInfoElement = DOMUtils.createElement("div", {
5162
- className: "vidply-track-info"
5229
+ className: "vidply-track-info",
5230
+ role: "status",
5231
+ "aria-live": "polite",
5232
+ "aria-atomic": "true"
5163
5233
  });
5164
5234
  this.trackInfoElement.style.display = "none";
5165
5235
  this.container.appendChild(this.trackInfoElement);
5166
5236
  this.playlistPanel = DOMUtils.createElement("div", {
5167
- className: "vidply-playlist-panel"
5237
+ className: "vidply-playlist-panel",
5238
+ role: "region",
5239
+ "aria-label": "Media playlist"
5168
5240
  });
5169
5241
  this.playlistPanel.style.display = "none";
5170
5242
  this.container.appendChild(this.playlistPanel);
@@ -5176,10 +5248,14 @@ var VidPly = (() => {
5176
5248
  if (!this.trackInfoElement) return;
5177
5249
  const trackNumber = this.currentIndex + 1;
5178
5250
  const totalTracks = this.tracks.length;
5251
+ const trackTitle = track.title || "Untitled";
5252
+ const trackArtist = track.artist || "";
5253
+ const announcement = `Now playing: Track ${trackNumber} of ${totalTracks}. ${trackTitle}${trackArtist ? " by " + trackArtist : ""}`;
5179
5254
  this.trackInfoElement.innerHTML = `
5180
- <div class="vidply-track-number">Track ${trackNumber} of ${totalTracks}</div>
5181
- <div class="vidply-track-title">${DOMUtils.escapeHTML(track.title || "Untitled")}</div>
5182
- ${track.artist ? `<div class="vidply-track-artist">${DOMUtils.escapeHTML(track.artist)}</div>` : ""}
5255
+ <span class="vidply-sr-only">${DOMUtils.escapeHTML(announcement)}</span>
5256
+ <div class="vidply-track-number" aria-hidden="true">Track ${trackNumber} of ${totalTracks}</div>
5257
+ <div class="vidply-track-title" aria-hidden="true">${DOMUtils.escapeHTML(trackTitle)}</div>
5258
+ ${trackArtist ? `<div class="vidply-track-artist" aria-hidden="true">${DOMUtils.escapeHTML(trackArtist)}</div>` : ""}
5183
5259
  `;
5184
5260
  this.trackInfoElement.style.display = "block";
5185
5261
  }
@@ -5189,14 +5265,29 @@ var VidPly = (() => {
5189
5265
  renderPlaylist() {
5190
5266
  if (!this.playlistPanel) return;
5191
5267
  this.playlistPanel.innerHTML = "";
5192
- const header = DOMUtils.createElement("div", {
5193
- className: "vidply-playlist-header"
5268
+ const header = DOMUtils.createElement("h2", {
5269
+ className: "vidply-playlist-header",
5270
+ id: "vidply-playlist-heading"
5194
5271
  });
5195
5272
  header.textContent = `Playlist (${this.tracks.length})`;
5196
5273
  this.playlistPanel.appendChild(header);
5197
- const list = DOMUtils.createElement("div", {
5198
- className: "vidply-playlist-list"
5199
- });
5274
+ const instructions = DOMUtils.createElement("div", {
5275
+ className: "vidply-sr-only",
5276
+ "aria-hidden": "false"
5277
+ });
5278
+ 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.";
5279
+ this.playlistPanel.appendChild(instructions);
5280
+ const list = DOMUtils.createElement("ul", {
5281
+ className: "vidply-playlist-list",
5282
+ "aria-labelledby": "vidply-playlist-heading",
5283
+ "aria-describedby": "vidply-playlist-instructions"
5284
+ });
5285
+ const listDescription = DOMUtils.createElement("div", {
5286
+ className: "vidply-sr-only",
5287
+ id: "vidply-playlist-instructions"
5288
+ });
5289
+ listDescription.textContent = `Playlist with ${this.tracks.length} ${this.tracks.length === 1 ? "track" : "tracks"}`;
5290
+ this.playlistPanel.appendChild(listDescription);
5200
5291
  this.tracks.forEach((track, index) => {
5201
5292
  const item = this.createPlaylistItem(track, index);
5202
5293
  list.appendChild(item);
@@ -5208,20 +5299,39 @@ var VidPly = (() => {
5208
5299
  * Create playlist item element
5209
5300
  */
5210
5301
  createPlaylistItem(track, index) {
5211
- const item = DOMUtils.createElement("div", {
5302
+ const trackPosition = `Track ${index + 1} of ${this.tracks.length}`;
5303
+ const trackTitle = track.title || `Track ${index + 1}`;
5304
+ const trackArtist = track.artist ? ` by ${track.artist}` : "";
5305
+ const isActive = index === this.currentIndex;
5306
+ const statusText = isActive ? "Currently playing" : "Not playing";
5307
+ const actionText = isActive ? "Press Enter to restart" : "Press Enter to play";
5308
+ const item = DOMUtils.createElement("li", {
5212
5309
  className: "vidply-playlist-item",
5213
- role: "button",
5214
- tabIndex: 0,
5215
- "aria-label": `Play ${track.title || "Track " + (index + 1)}`
5216
- });
5217
- if (index === this.currentIndex) {
5310
+ tabIndex: index === 0 ? 0 : -1,
5311
+ // Only first item is in tab order initially
5312
+ "aria-label": `${trackPosition}. ${trackTitle}${trackArtist}. ${statusText}. ${actionText}.`,
5313
+ "aria-posinset": index + 1,
5314
+ "aria-setsize": this.tracks.length,
5315
+ "data-playlist-index": index
5316
+ });
5317
+ if (isActive) {
5218
5318
  item.classList.add("vidply-playlist-item-active");
5319
+ item.setAttribute("aria-current", "true");
5320
+ item.setAttribute("tabIndex", "0");
5219
5321
  }
5322
+ const positionInfo = DOMUtils.createElement("span", {
5323
+ className: "vidply-sr-only"
5324
+ });
5325
+ positionInfo.textContent = `${trackPosition}: `;
5326
+ item.appendChild(positionInfo);
5220
5327
  const thumbnail = DOMUtils.createElement("div", {
5221
- className: "vidply-playlist-thumbnail"
5328
+ className: "vidply-playlist-thumbnail",
5329
+ "aria-hidden": "true"
5222
5330
  });
5223
5331
  if (track.poster) {
5224
5332
  thumbnail.style.backgroundImage = `url(${track.poster})`;
5333
+ thumbnail.setAttribute("role", "img");
5334
+ thumbnail.setAttribute("aria-label", `${trackTitle} thumbnail`);
5225
5335
  } else {
5226
5336
  const icon = createIconElement("music");
5227
5337
  icon.classList.add("vidply-playlist-thumbnail-icon");
@@ -5229,12 +5339,13 @@ var VidPly = (() => {
5229
5339
  }
5230
5340
  item.appendChild(thumbnail);
5231
5341
  const info = DOMUtils.createElement("div", {
5232
- className: "vidply-playlist-item-info"
5342
+ className: "vidply-playlist-item-info",
5343
+ "aria-hidden": "true"
5233
5344
  });
5234
5345
  const title = DOMUtils.createElement("div", {
5235
5346
  className: "vidply-playlist-item-title"
5236
5347
  });
5237
- title.textContent = track.title || `Track ${index + 1}`;
5348
+ title.textContent = trackTitle;
5238
5349
  info.appendChild(title);
5239
5350
  if (track.artist) {
5240
5351
  const artist = DOMUtils.createElement("div", {
@@ -5244,20 +5355,64 @@ var VidPly = (() => {
5244
5355
  info.appendChild(artist);
5245
5356
  }
5246
5357
  item.appendChild(info);
5358
+ if (isActive) {
5359
+ const statusIndicator = DOMUtils.createElement("span", {
5360
+ className: "vidply-sr-only"
5361
+ });
5362
+ statusIndicator.textContent = " (Currently playing)";
5363
+ item.appendChild(statusIndicator);
5364
+ }
5247
5365
  const playIcon = createIconElement("play");
5248
5366
  playIcon.classList.add("vidply-playlist-item-icon");
5367
+ playIcon.setAttribute("aria-hidden", "true");
5249
5368
  item.appendChild(playIcon);
5250
5369
  item.addEventListener("click", () => {
5251
- this.play(index);
5370
+ this.play(index, true);
5252
5371
  });
5253
5372
  item.addEventListener("keydown", (e) => {
5254
- if (e.key === "Enter" || e.key === " ") {
5255
- e.preventDefault();
5256
- this.play(index);
5257
- }
5373
+ this.handlePlaylistItemKeydown(e, index);
5258
5374
  });
5259
5375
  return item;
5260
5376
  }
5377
+ /**
5378
+ * Handle keyboard navigation in playlist items
5379
+ */
5380
+ handlePlaylistItemKeydown(e, index) {
5381
+ const items = Array.from(this.playlistPanel.querySelectorAll(".vidply-playlist-item"));
5382
+ let newIndex = -1;
5383
+ switch (e.key) {
5384
+ case "Enter":
5385
+ case " ":
5386
+ e.preventDefault();
5387
+ this.play(index, true);
5388
+ break;
5389
+ case "ArrowDown":
5390
+ e.preventDefault();
5391
+ if (index < items.length - 1) {
5392
+ newIndex = index + 1;
5393
+ }
5394
+ break;
5395
+ case "ArrowUp":
5396
+ e.preventDefault();
5397
+ if (index > 0) {
5398
+ newIndex = index - 1;
5399
+ }
5400
+ break;
5401
+ case "Home":
5402
+ e.preventDefault();
5403
+ newIndex = 0;
5404
+ break;
5405
+ case "End":
5406
+ e.preventDefault();
5407
+ newIndex = items.length - 1;
5408
+ break;
5409
+ }
5410
+ if (newIndex !== -1 && newIndex !== index) {
5411
+ items[index].setAttribute("tabIndex", "-1");
5412
+ items[newIndex].setAttribute("tabIndex", "0");
5413
+ items[newIndex].focus();
5414
+ }
5415
+ }
5261
5416
  /**
5262
5417
  * Update playlist UI (highlight current track)
5263
5418
  */
@@ -5265,11 +5420,25 @@ var VidPly = (() => {
5265
5420
  if (!this.playlistPanel) return;
5266
5421
  const items = this.playlistPanel.querySelectorAll(".vidply-playlist-item");
5267
5422
  items.forEach((item, index) => {
5423
+ const track = this.tracks[index];
5424
+ const trackPosition = `Track ${index + 1} of ${this.tracks.length}`;
5425
+ const trackTitle = track.title || `Track ${index + 1}`;
5426
+ const trackArtist = track.artist ? ` by ${track.artist}` : "";
5268
5427
  if (index === this.currentIndex) {
5269
5428
  item.classList.add("vidply-playlist-item-active");
5429
+ item.setAttribute("aria-current", "true");
5430
+ item.setAttribute("tabIndex", "0");
5431
+ const statusText = "Currently playing";
5432
+ const actionText = "Press Enter to restart";
5433
+ item.setAttribute("aria-label", `${trackPosition}. ${trackTitle}${trackArtist}. ${statusText}. ${actionText}.`);
5270
5434
  item.scrollIntoView({ behavior: "smooth", block: "nearest" });
5271
5435
  } else {
5272
5436
  item.classList.remove("vidply-playlist-item-active");
5437
+ item.removeAttribute("aria-current");
5438
+ item.setAttribute("tabIndex", "-1");
5439
+ const statusText = "Not playing";
5440
+ const actionText = "Press Enter to play";
5441
+ item.setAttribute("aria-label", `${trackPosition}. ${trackTitle}${trackArtist}. ${statusText}. ${actionText}.`);
5273
5442
  }
5274
5443
  });
5275
5444
  }
@@ -5287,10 +5456,24 @@ var VidPly = (() => {
5287
5456
  currentIndex: this.currentIndex,
5288
5457
  totalTracks: this.tracks.length,
5289
5458
  currentTrack: this.getCurrentTrack(),
5290
- hasNext: this.currentIndex < this.tracks.length - 1,
5291
- hasPrevious: this.currentIndex > 0
5459
+ hasNext: this.hasNext(),
5460
+ hasPrevious: this.hasPrevious()
5292
5461
  };
5293
5462
  }
5463
+ /**
5464
+ * Check if there is a next track
5465
+ */
5466
+ hasNext() {
5467
+ if (this.options.loop) return true;
5468
+ return this.currentIndex < this.tracks.length - 1;
5469
+ }
5470
+ /**
5471
+ * Check if there is a previous track
5472
+ */
5473
+ hasPrevious() {
5474
+ if (this.options.loop) return true;
5475
+ return this.currentIndex > 0;
5476
+ }
5294
5477
  /**
5295
5478
  * Add track to playlist
5296
5479
  */