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 +24 -0
- package/dist/vidply.esm.js +217 -34
- package/dist/vidply.esm.js.map +2 -2
- package/dist/vidply.esm.min.js +7 -6
- package/dist/vidply.esm.min.meta.json +7 -7
- package/dist/vidply.js +217 -34
- package/dist/vidply.js.map +2 -2
- package/dist/vidply.min.css +1 -1
- package/dist/vidply.min.js +7 -6
- package/dist/vidply.min.meta.json +7 -7
- package/package.json +57 -54
- package/src/controls/ControlBar.js +2026 -1988
- package/src/core/Player.js +36 -0
- package/src/features/PlaylistManager.js +611 -437
- package/src/styles/vidply.css +24 -0
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"format": "esm"
|
|
38
38
|
},
|
|
39
39
|
"src/controls/ControlBar.js": {
|
|
40
|
-
"bytes":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
293
|
+
"bytesInOutput": 8100
|
|
294
294
|
},
|
|
295
295
|
"src/index.js": {
|
|
296
296
|
"bytesInOutput": 1000
|
|
297
297
|
}
|
|
298
298
|
},
|
|
299
|
-
"bytes":
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
2514
|
-
|
|
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.
|
|
2519
|
-
|
|
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
|
-
<
|
|
5181
|
-
<div class="vidply-track-
|
|
5182
|
-
|
|
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("
|
|
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
|
|
5198
|
-
className: "vidply-
|
|
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
|
|
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
|
-
|
|
5214
|
-
|
|
5215
|
-
"aria-label":
|
|
5216
|
-
|
|
5217
|
-
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
5291
|
-
hasPrevious: this.
|
|
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
|
*/
|