vidply 1.0.20 → 1.0.21
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 +31 -0
- package/dist/vidply.esm.js +186 -17
- package/dist/vidply.esm.js.map +2 -2
- package/dist/vidply.esm.min.js +5 -5
- package/dist/vidply.esm.min.meta.json +30 -25
- package/dist/vidply.js +186 -17
- package/dist/vidply.js.map +2 -2
- package/dist/vidply.min.css +1 -1
- package/dist/vidply.min.js +5 -5
- package/dist/vidply.min.meta.json +30 -25
- package/package.json +1 -1
- package/src/controls/ControlBar.js +2 -1
- package/src/core/Player.js +68 -3
- package/src/features/PlaylistManager.js +109 -15
- package/src/i18n/languages/de.js +8 -0
- package/src/i18n/languages/en.js +8 -0
- package/src/i18n/languages/es.js +8 -0
- package/src/i18n/languages/fr.js +8 -0
- package/src/i18n/languages/ja.js +8 -0
- package/src/renderers/HLSRenderer.js +17 -0
- package/src/renderers/HTML5Renderer.js +14 -1
- package/src/renderers/VimeoRenderer.js +7 -0
- package/src/renderers/YouTubeRenderer.js +7 -0
- package/src/styles/vidply.css +31 -0
|
@@ -11,27 +11,27 @@
|
|
|
11
11
|
"format": "esm"
|
|
12
12
|
},
|
|
13
13
|
"src/i18n/languages/en.js": {
|
|
14
|
-
"bytes":
|
|
14
|
+
"bytes": 6566,
|
|
15
15
|
"imports": [],
|
|
16
16
|
"format": "esm"
|
|
17
17
|
},
|
|
18
18
|
"src/i18n/languages/de.js": {
|
|
19
|
-
"bytes":
|
|
19
|
+
"bytes": 8063,
|
|
20
20
|
"imports": [],
|
|
21
21
|
"format": "esm"
|
|
22
22
|
},
|
|
23
23
|
"src/i18n/languages/es.js": {
|
|
24
|
-
"bytes":
|
|
24
|
+
"bytes": 7619,
|
|
25
25
|
"imports": [],
|
|
26
26
|
"format": "esm"
|
|
27
27
|
},
|
|
28
28
|
"src/i18n/languages/fr.js": {
|
|
29
|
-
"bytes":
|
|
29
|
+
"bytes": 7826,
|
|
30
30
|
"imports": [],
|
|
31
31
|
"format": "esm"
|
|
32
32
|
},
|
|
33
33
|
"src/i18n/languages/ja.js": {
|
|
34
|
-
"bytes":
|
|
34
|
+
"bytes": 8251,
|
|
35
35
|
"imports": [],
|
|
36
36
|
"format": "esm"
|
|
37
37
|
},
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
"format": "esm"
|
|
100
100
|
},
|
|
101
101
|
"src/controls/ControlBar.js": {
|
|
102
|
-
"bytes":
|
|
102
|
+
"bytes": 129940,
|
|
103
103
|
"imports": [
|
|
104
104
|
{
|
|
105
105
|
"path": "src/utils/DOMUtils.js",
|
|
@@ -269,22 +269,22 @@
|
|
|
269
269
|
"format": "esm"
|
|
270
270
|
},
|
|
271
271
|
"src/renderers/HTML5Renderer.js": {
|
|
272
|
-
"bytes":
|
|
272
|
+
"bytes": 9169,
|
|
273
273
|
"imports": [],
|
|
274
274
|
"format": "esm"
|
|
275
275
|
},
|
|
276
276
|
"src/renderers/YouTubeRenderer.js": {
|
|
277
|
-
"bytes":
|
|
277
|
+
"bytes": 7465,
|
|
278
278
|
"imports": [],
|
|
279
279
|
"format": "esm"
|
|
280
280
|
},
|
|
281
281
|
"src/renderers/VimeoRenderer.js": {
|
|
282
|
-
"bytes":
|
|
282
|
+
"bytes": 6978,
|
|
283
283
|
"imports": [],
|
|
284
284
|
"format": "esm"
|
|
285
285
|
},
|
|
286
286
|
"src/renderers/HLSRenderer.js": {
|
|
287
|
-
"bytes":
|
|
287
|
+
"bytes": 9492,
|
|
288
288
|
"imports": [
|
|
289
289
|
{
|
|
290
290
|
"path": "src/renderers/HTML5Renderer.js",
|
|
@@ -295,7 +295,7 @@
|
|
|
295
295
|
"format": "esm"
|
|
296
296
|
},
|
|
297
297
|
"src/core/Player.js": {
|
|
298
|
-
"bytes":
|
|
298
|
+
"bytes": 208826,
|
|
299
299
|
"imports": [
|
|
300
300
|
{
|
|
301
301
|
"path": "src/utils/EventEmitter.js",
|
|
@@ -386,7 +386,7 @@
|
|
|
386
386
|
"format": "esm"
|
|
387
387
|
},
|
|
388
388
|
"src/features/PlaylistManager.js": {
|
|
389
|
-
"bytes":
|
|
389
|
+
"bytes": 32072,
|
|
390
390
|
"imports": [
|
|
391
391
|
{
|
|
392
392
|
"path": "src/utils/DOMUtils.js",
|
|
@@ -397,6 +397,11 @@
|
|
|
397
397
|
"path": "src/icons/Icons.js",
|
|
398
398
|
"kind": "import-statement",
|
|
399
399
|
"original": "../icons/Icons.js"
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
"path": "src/i18n/i18n.js",
|
|
403
|
+
"kind": "import-statement",
|
|
404
|
+
"original": "../i18n/i18n.js"
|
|
400
405
|
}
|
|
401
406
|
],
|
|
402
407
|
"format": "esm"
|
|
@@ -425,7 +430,7 @@
|
|
|
425
430
|
"entryPoint": "src/index.js",
|
|
426
431
|
"inputs": {
|
|
427
432
|
"src/renderers/HTML5Renderer.js": {
|
|
428
|
-
"bytesInOutput":
|
|
433
|
+
"bytesInOutput": 4803
|
|
429
434
|
},
|
|
430
435
|
"src/index.js": {
|
|
431
436
|
"bytesInOutput": 1939
|
|
@@ -437,19 +442,19 @@
|
|
|
437
442
|
"bytesInOutput": 1581
|
|
438
443
|
},
|
|
439
444
|
"src/i18n/languages/en.js": {
|
|
440
|
-
"bytesInOutput":
|
|
445
|
+
"bytesInOutput": 5426
|
|
441
446
|
},
|
|
442
447
|
"src/i18n/languages/de.js": {
|
|
443
|
-
"bytesInOutput":
|
|
448
|
+
"bytesInOutput": 6510
|
|
444
449
|
},
|
|
445
450
|
"src/i18n/languages/es.js": {
|
|
446
|
-
"bytesInOutput":
|
|
451
|
+
"bytesInOutput": 6637
|
|
447
452
|
},
|
|
448
453
|
"src/i18n/languages/fr.js": {
|
|
449
|
-
"bytesInOutput":
|
|
454
|
+
"bytesInOutput": 6895
|
|
450
455
|
},
|
|
451
456
|
"src/i18n/languages/ja.js": {
|
|
452
|
-
"bytesInOutput":
|
|
457
|
+
"bytesInOutput": 11757
|
|
453
458
|
},
|
|
454
459
|
"src/i18n/translations.js": {
|
|
455
460
|
"bytesInOutput": 61
|
|
@@ -467,7 +472,7 @@
|
|
|
467
472
|
"bytesInOutput": 222
|
|
468
473
|
},
|
|
469
474
|
"src/controls/ControlBar.js": {
|
|
470
|
-
"bytesInOutput":
|
|
475
|
+
"bytesInOutput": 54457
|
|
471
476
|
},
|
|
472
477
|
"src/utils/StorageManager.js": {
|
|
473
478
|
"bytesInOutput": 1606
|
|
@@ -494,22 +499,22 @@
|
|
|
494
499
|
"bytesInOutput": 41981
|
|
495
500
|
},
|
|
496
501
|
"src/core/Player.js": {
|
|
497
|
-
"bytesInOutput":
|
|
502
|
+
"bytesInOutput": 74975
|
|
498
503
|
},
|
|
499
504
|
"src/renderers/YouTubeRenderer.js": {
|
|
500
|
-
"bytesInOutput":
|
|
505
|
+
"bytesInOutput": 4203
|
|
501
506
|
},
|
|
502
507
|
"src/renderers/VimeoRenderer.js": {
|
|
503
|
-
"bytesInOutput":
|
|
508
|
+
"bytesInOutput": 4268
|
|
504
509
|
},
|
|
505
510
|
"src/renderers/HLSRenderer.js": {
|
|
506
|
-
"bytesInOutput":
|
|
511
|
+
"bytesInOutput": 5583
|
|
507
512
|
},
|
|
508
513
|
"src/features/PlaylistManager.js": {
|
|
509
|
-
"bytesInOutput":
|
|
514
|
+
"bytesInOutput": 14667
|
|
510
515
|
}
|
|
511
516
|
},
|
|
512
|
-
"bytes":
|
|
517
|
+
"bytes": 290360
|
|
513
518
|
}
|
|
514
519
|
}
|
|
515
520
|
}
|
package/package.json
CHANGED
package/src/core/Player.js
CHANGED
|
@@ -432,6 +432,20 @@ export class Player extends EventEmitter {
|
|
|
432
432
|
|
|
433
433
|
// Wrap original element
|
|
434
434
|
this.element.parentNode.insertBefore(this.container, this.element);
|
|
435
|
+
|
|
436
|
+
// Create track artwork element for single audio files (before video wrapper)
|
|
437
|
+
// This shows the poster/artwork above the audio player (similar to playlists)
|
|
438
|
+
if (this.element.tagName === 'AUDIO' && this.options.poster) {
|
|
439
|
+
this.trackArtworkElement = DOMUtils.createElement('div', {
|
|
440
|
+
className: `${this.options.classPrefix}-track-artwork`,
|
|
441
|
+
attributes: {
|
|
442
|
+
'aria-hidden': 'true'
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
this.trackArtworkElement.style.backgroundImage = `url(${this.options.poster})`;
|
|
446
|
+
this.container.appendChild(this.trackArtworkElement);
|
|
447
|
+
}
|
|
448
|
+
|
|
435
449
|
this.container.appendChild(this.videoWrapper);
|
|
436
450
|
this.videoWrapper.appendChild(this.element);
|
|
437
451
|
|
|
@@ -850,6 +864,8 @@ export class Player extends EventEmitter {
|
|
|
850
864
|
* @param {string} config.type - Media MIME type
|
|
851
865
|
* @param {string} [config.poster] - Poster image URL
|
|
852
866
|
* @param {Array} [config.tracks] - Text tracks (captions, chapters, etc.)
|
|
867
|
+
* @param {string} [config.audioDescriptionSrc] - Audio description video URL
|
|
868
|
+
* @param {string} [config.signLanguageSrc] - Sign language video URL
|
|
853
869
|
*/
|
|
854
870
|
async load(config) {
|
|
855
871
|
try {
|
|
@@ -860,6 +876,10 @@ export class Player extends EventEmitter {
|
|
|
860
876
|
this.pause();
|
|
861
877
|
}
|
|
862
878
|
|
|
879
|
+
// Save scroll position to prevent browser from auto-scrolling when loading new media
|
|
880
|
+
const scrollX = window.scrollX || window.pageXOffset;
|
|
881
|
+
const scrollY = window.scrollY || window.pageYOffset;
|
|
882
|
+
|
|
863
883
|
// Clear existing text tracks
|
|
864
884
|
const existingTracks = this.trackElements;
|
|
865
885
|
existingTracks.forEach(track => track.remove());
|
|
@@ -893,6 +913,25 @@ export class Player extends EventEmitter {
|
|
|
893
913
|
});
|
|
894
914
|
this.invalidateTrackCache();
|
|
895
915
|
}
|
|
916
|
+
|
|
917
|
+
// Remember accessibility feature states before switching tracks
|
|
918
|
+
const wasSignLanguageEnabled = this.state.signLanguageEnabled;
|
|
919
|
+
const wasAudioDescriptionEnabled = this.state.audioDescriptionEnabled;
|
|
920
|
+
|
|
921
|
+
// Update sources from config FIRST (before hiding features)
|
|
922
|
+
this.audioDescriptionSrc = config.audioDescriptionSrc || null;
|
|
923
|
+
this.signLanguageSrc = config.signLanguageSrc || null;
|
|
924
|
+
|
|
925
|
+
// Update original source for toggling
|
|
926
|
+
this.originalSrc = config.src;
|
|
927
|
+
|
|
928
|
+
// Hide accessibility features that were enabled (must happen AFTER updating sources)
|
|
929
|
+
if (wasAudioDescriptionEnabled) {
|
|
930
|
+
this.disableAudioDescription();
|
|
931
|
+
}
|
|
932
|
+
if (wasSignLanguageEnabled) {
|
|
933
|
+
this.disableSignLanguage();
|
|
934
|
+
}
|
|
896
935
|
|
|
897
936
|
// Check if we need to change renderer type
|
|
898
937
|
const shouldChangeRenderer = this.shouldChangeRenderer(config.src);
|
|
@@ -911,6 +950,9 @@ export class Player extends EventEmitter {
|
|
|
911
950
|
this.renderer.media = this.element; // Update media reference
|
|
912
951
|
this.element.load();
|
|
913
952
|
}
|
|
953
|
+
|
|
954
|
+
// Restore scroll position immediately after loading to prevent auto-scroll
|
|
955
|
+
window.scrollTo(scrollX, scrollY);
|
|
914
956
|
|
|
915
957
|
// Reinitialize caption manager to pick up new tracks
|
|
916
958
|
if (this.captionManager) {
|
|
@@ -920,12 +962,12 @@ export class Player extends EventEmitter {
|
|
|
920
962
|
|
|
921
963
|
// Reinitialize transcript manager to pick up new tracks
|
|
922
964
|
if (this.transcriptManager) {
|
|
923
|
-
const
|
|
965
|
+
const wasTranscriptVisible = this.transcriptManager.isVisible;
|
|
924
966
|
this.transcriptManager.destroy();
|
|
925
967
|
this.transcriptManager = new TranscriptManager(this);
|
|
926
968
|
|
|
927
|
-
//
|
|
928
|
-
if (
|
|
969
|
+
// Only restore transcript visibility if new track has captions
|
|
970
|
+
if (wasTranscriptVisible && this.controlBar && this.controlBar.hasCaptionTracks()) {
|
|
929
971
|
this.transcriptManager.showTranscript();
|
|
930
972
|
}
|
|
931
973
|
}
|
|
@@ -934,6 +976,28 @@ export class Player extends EventEmitter {
|
|
|
934
976
|
if (this.controlBar) {
|
|
935
977
|
this.updateControlBar();
|
|
936
978
|
}
|
|
979
|
+
|
|
980
|
+
// Restore scroll position after control bar update (may have caused micro-scrolls)
|
|
981
|
+
window.scrollTo(scrollX, scrollY);
|
|
982
|
+
|
|
983
|
+
// Restore accessibility features if they were enabled and available in new track
|
|
984
|
+
if (wasSignLanguageEnabled && this.signLanguageSrc) {
|
|
985
|
+
// Small delay to ensure player and control bar are ready
|
|
986
|
+
setTimeout(() => {
|
|
987
|
+
this.enableSignLanguage();
|
|
988
|
+
// Restore scroll after sign language is shown
|
|
989
|
+
window.scrollTo(scrollX, scrollY);
|
|
990
|
+
}, 150);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
if (wasAudioDescriptionEnabled && this.audioDescriptionSrc) {
|
|
994
|
+
// Small delay to ensure player is ready
|
|
995
|
+
setTimeout(() => {
|
|
996
|
+
this.enableAudioDescription();
|
|
997
|
+
// Restore scroll after audio description is enabled
|
|
998
|
+
window.scrollTo(scrollX, scrollY);
|
|
999
|
+
}, 150);
|
|
1000
|
+
}
|
|
937
1001
|
|
|
938
1002
|
this.emit('sourcechange', config);
|
|
939
1003
|
this.log('Media loaded successfully');
|
|
@@ -965,6 +1029,7 @@ export class Player extends EventEmitter {
|
|
|
965
1029
|
// Reattach events for the new controls
|
|
966
1030
|
controlBar.attachEvents();
|
|
967
1031
|
controlBar.setupAutoHide();
|
|
1032
|
+
controlBar.setupOverflowDetection();
|
|
968
1033
|
}
|
|
969
1034
|
|
|
970
1035
|
shouldChangeRenderer(src) {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { DOMUtils } from '../utils/DOMUtils.js';
|
|
7
7
|
import { createIconElement } from '../icons/Icons.js';
|
|
8
|
+
import { i18n } from '../i18n/i18n.js';
|
|
8
9
|
|
|
9
10
|
// Static counter for unique IDs
|
|
10
11
|
let playlistInstanceCounter = 0;
|
|
@@ -35,6 +36,9 @@ export class PlaylistManager {
|
|
|
35
36
|
this.navigationFeedback = null; // Live region for keyboard navigation feedback
|
|
36
37
|
this.isPanelVisible = this.options.showPanel !== false;
|
|
37
38
|
|
|
39
|
+
// Track change guard to prevent cascade of next() calls
|
|
40
|
+
this.isChangingTrack = false;
|
|
41
|
+
|
|
38
42
|
// Bind methods
|
|
39
43
|
this.handleTrackEnd = this.handleTrackEnd.bind(this);
|
|
40
44
|
this.handleTrackError = this.handleTrackError.bind(this);
|
|
@@ -216,6 +220,9 @@ export class PlaylistManager {
|
|
|
216
220
|
|
|
217
221
|
const track = this.tracks[index];
|
|
218
222
|
|
|
223
|
+
// Set guard flag to prevent cascade of next() calls during track change
|
|
224
|
+
this.isChangingTrack = true;
|
|
225
|
+
|
|
219
226
|
// Update current index
|
|
220
227
|
this.currentIndex = index;
|
|
221
228
|
|
|
@@ -224,7 +231,9 @@ export class PlaylistManager {
|
|
|
224
231
|
src: track.src,
|
|
225
232
|
type: track.type,
|
|
226
233
|
poster: track.poster,
|
|
227
|
-
tracks: track.tracks || []
|
|
234
|
+
tracks: track.tracks || [],
|
|
235
|
+
audioDescriptionSrc: track.audioDescriptionSrc || null,
|
|
236
|
+
signLanguageSrc: track.signLanguageSrc || null
|
|
228
237
|
});
|
|
229
238
|
|
|
230
239
|
// Update UI
|
|
@@ -237,6 +246,11 @@ export class PlaylistManager {
|
|
|
237
246
|
item: track,
|
|
238
247
|
total: this.tracks.length
|
|
239
248
|
});
|
|
249
|
+
|
|
250
|
+
// Clear guard flag after a short delay to ensure track is loaded
|
|
251
|
+
setTimeout(() => {
|
|
252
|
+
this.isChangingTrack = false;
|
|
253
|
+
}, 150);
|
|
240
254
|
}
|
|
241
255
|
|
|
242
256
|
/**
|
|
@@ -252,6 +266,9 @@ export class PlaylistManager {
|
|
|
252
266
|
|
|
253
267
|
const track = this.tracks[index];
|
|
254
268
|
|
|
269
|
+
// Set guard flag to prevent cascade of next() calls during track change
|
|
270
|
+
this.isChangingTrack = true;
|
|
271
|
+
|
|
255
272
|
// Update current index
|
|
256
273
|
this.currentIndex = index;
|
|
257
274
|
|
|
@@ -260,7 +277,9 @@ export class PlaylistManager {
|
|
|
260
277
|
src: track.src,
|
|
261
278
|
type: track.type,
|
|
262
279
|
poster: track.poster,
|
|
263
|
-
tracks: track.tracks || []
|
|
280
|
+
tracks: track.tracks || [],
|
|
281
|
+
audioDescriptionSrc: track.audioDescriptionSrc || null,
|
|
282
|
+
signLanguageSrc: track.signLanguageSrc || null
|
|
264
283
|
});
|
|
265
284
|
|
|
266
285
|
// Update UI
|
|
@@ -274,9 +293,13 @@ export class PlaylistManager {
|
|
|
274
293
|
total: this.tracks.length
|
|
275
294
|
});
|
|
276
295
|
|
|
277
|
-
// Auto-play
|
|
296
|
+
// Auto-play and clear guard flag after playback starts
|
|
278
297
|
setTimeout(() => {
|
|
279
298
|
this.player.play();
|
|
299
|
+
// Clear guard flag after a short delay to ensure track has started
|
|
300
|
+
setTimeout(() => {
|
|
301
|
+
this.isChangingTrack = false;
|
|
302
|
+
}, 50);
|
|
280
303
|
}, 100);
|
|
281
304
|
}
|
|
282
305
|
|
|
@@ -318,6 +341,12 @@ export class PlaylistManager {
|
|
|
318
341
|
* Handle track end
|
|
319
342
|
*/
|
|
320
343
|
handleTrackEnd() {
|
|
344
|
+
// Don't auto-advance if we're already in the process of changing tracks
|
|
345
|
+
// This prevents a cascade of next() calls when loading a new track triggers an 'ended' event
|
|
346
|
+
if (this.isChangingTrack) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
321
350
|
if (this.options.autoAdvance) {
|
|
322
351
|
this.next();
|
|
323
352
|
}
|
|
@@ -404,6 +433,26 @@ export class PlaylistManager {
|
|
|
404
433
|
return;
|
|
405
434
|
}
|
|
406
435
|
|
|
436
|
+
// Create track artwork element (shows album art/poster for audio playlists)
|
|
437
|
+
// Only create for audio players
|
|
438
|
+
if (this.player.element.tagName === 'AUDIO') {
|
|
439
|
+
this.trackArtworkElement = DOMUtils.createElement('div', {
|
|
440
|
+
className: 'vidply-track-artwork',
|
|
441
|
+
attributes: {
|
|
442
|
+
'aria-hidden': 'true'
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
this.trackArtworkElement.style.display = 'none';
|
|
446
|
+
|
|
447
|
+
// Insert before video wrapper
|
|
448
|
+
const videoWrapper = this.container.querySelector('.vidply-video-wrapper');
|
|
449
|
+
if (videoWrapper) {
|
|
450
|
+
this.container.insertBefore(this.trackArtworkElement, videoWrapper);
|
|
451
|
+
} else {
|
|
452
|
+
this.container.appendChild(this.trackArtworkElement);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
407
456
|
// Create track info element (shows current track)
|
|
408
457
|
this.trackInfoElement = DOMUtils.createElement('div', {
|
|
409
458
|
className: 'vidply-track-info',
|
|
@@ -434,7 +483,7 @@ export class PlaylistManager {
|
|
|
434
483
|
attributes: {
|
|
435
484
|
id: `${this.uniqueId}-panel`,
|
|
436
485
|
role: 'region',
|
|
437
|
-
'aria-label': '
|
|
486
|
+
'aria-label': i18n.t('playlist.title'),
|
|
438
487
|
'aria-labelledby': `${this.uniqueId}-heading`
|
|
439
488
|
}
|
|
440
489
|
});
|
|
@@ -451,20 +500,50 @@ export class PlaylistManager {
|
|
|
451
500
|
|
|
452
501
|
const trackNumber = this.currentIndex + 1;
|
|
453
502
|
const totalTracks = this.tracks.length;
|
|
454
|
-
const trackTitle = track.title || '
|
|
503
|
+
const trackTitle = track.title || i18n.t('playlist.untitled');
|
|
455
504
|
const trackArtist = track.artist || '';
|
|
456
505
|
|
|
457
506
|
// Screen reader announcement
|
|
458
|
-
const
|
|
507
|
+
const artistPart = trackArtist ? i18n.t('playlist.by') + trackArtist : '';
|
|
508
|
+
const announcement = i18n.t('playlist.nowPlaying', {
|
|
509
|
+
current: trackNumber,
|
|
510
|
+
total: totalTracks,
|
|
511
|
+
title: trackTitle,
|
|
512
|
+
artist: artistPart
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
const trackOfText = i18n.t('playlist.trackOf', {
|
|
516
|
+
current: trackNumber,
|
|
517
|
+
total: totalTracks
|
|
518
|
+
});
|
|
459
519
|
|
|
460
520
|
this.trackInfoElement.innerHTML = `
|
|
461
521
|
<span class="vidply-sr-only">${DOMUtils.escapeHTML(announcement)}</span>
|
|
462
|
-
<div class="vidply-track-number" aria-hidden="true"
|
|
522
|
+
<div class="vidply-track-number" aria-hidden="true">${DOMUtils.escapeHTML(trackOfText)}</div>
|
|
463
523
|
<div class="vidply-track-title" aria-hidden="true">${DOMUtils.escapeHTML(trackTitle)}</div>
|
|
464
524
|
${trackArtist ? `<div class="vidply-track-artist" aria-hidden="true">${DOMUtils.escapeHTML(trackArtist)}</div>` : ''}
|
|
465
525
|
`;
|
|
466
526
|
|
|
467
527
|
this.trackInfoElement.style.display = 'block';
|
|
528
|
+
|
|
529
|
+
// Update track artwork if available (for audio playlists)
|
|
530
|
+
this.updateTrackArtwork(track);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Update track artwork display (for audio playlists)
|
|
535
|
+
*/
|
|
536
|
+
updateTrackArtwork(track) {
|
|
537
|
+
if (!this.trackArtworkElement) return;
|
|
538
|
+
|
|
539
|
+
// If track has a poster/artwork, show it
|
|
540
|
+
if (track.poster) {
|
|
541
|
+
this.trackArtworkElement.style.backgroundImage = `url(${track.poster})`;
|
|
542
|
+
this.trackArtworkElement.style.display = 'block';
|
|
543
|
+
} else {
|
|
544
|
+
// No artwork available, hide the element
|
|
545
|
+
this.trackArtworkElement.style.display = 'none';
|
|
546
|
+
}
|
|
468
547
|
}
|
|
469
548
|
|
|
470
549
|
/**
|
|
@@ -483,7 +562,7 @@ export class PlaylistManager {
|
|
|
483
562
|
id: `${this.uniqueId}-heading`
|
|
484
563
|
}
|
|
485
564
|
});
|
|
486
|
-
header.textContent =
|
|
565
|
+
header.textContent = `${i18n.t('playlist.title')} (${this.tracks.length})`;
|
|
487
566
|
this.playlistPanel.appendChild(header);
|
|
488
567
|
|
|
489
568
|
// Add keyboard instructions (visually hidden)
|
|
@@ -522,9 +601,12 @@ export class PlaylistManager {
|
|
|
522
601
|
* Create playlist item element
|
|
523
602
|
*/
|
|
524
603
|
createPlaylistItem(track, index) {
|
|
525
|
-
const trackPosition =
|
|
526
|
-
|
|
527
|
-
|
|
604
|
+
const trackPosition = i18n.t('playlist.trackOf', {
|
|
605
|
+
current: index + 1,
|
|
606
|
+
total: this.tracks.length
|
|
607
|
+
});
|
|
608
|
+
const trackTitle = track.title || i18n.t('playlist.trackUntitled', { number: index + 1 });
|
|
609
|
+
const trackArtist = track.artist ? i18n.t('playlist.by') + track.artist : '';
|
|
528
610
|
const isActive = index === this.currentIndex;
|
|
529
611
|
const statusText = isActive ? 'Currently playing' : 'Not playing';
|
|
530
612
|
const actionText = isActive ? 'Press Enter to restart' : 'Press Enter to play';
|
|
@@ -734,9 +816,12 @@ export class PlaylistManager {
|
|
|
734
816
|
if (!button) return;
|
|
735
817
|
|
|
736
818
|
const track = this.tracks[index];
|
|
737
|
-
const trackPosition =
|
|
738
|
-
|
|
739
|
-
|
|
819
|
+
const trackPosition = i18n.t('playlist.trackOf', {
|
|
820
|
+
current: index + 1,
|
|
821
|
+
total: this.tracks.length
|
|
822
|
+
});
|
|
823
|
+
const trackTitle = track.title || i18n.t('playlist.trackUntitled', { number: index + 1 });
|
|
824
|
+
const trackArtist = track.artist ? i18n.t('playlist.by') + track.artist : '';
|
|
740
825
|
|
|
741
826
|
if (index === this.currentIndex) {
|
|
742
827
|
// Update list item styling
|
|
@@ -750,7 +835,7 @@ export class PlaylistManager {
|
|
|
750
835
|
const actionText = 'Press Enter to restart';
|
|
751
836
|
button.setAttribute('aria-label', `${trackPosition}. ${trackTitle}${trackArtist}. ${statusText}. ${actionText}.`);
|
|
752
837
|
|
|
753
|
-
// Scroll into view
|
|
838
|
+
// Scroll into view within playlist panel (uses 'nearest' to minimize page scroll)
|
|
754
839
|
item.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
755
840
|
} else {
|
|
756
841
|
// Update list item styling
|
|
@@ -857,6 +942,11 @@ export class PlaylistManager {
|
|
|
857
942
|
this.trackInfoElement.innerHTML = '';
|
|
858
943
|
this.trackInfoElement.style.display = 'none';
|
|
859
944
|
}
|
|
945
|
+
|
|
946
|
+
if (this.trackArtworkElement) {
|
|
947
|
+
this.trackArtworkElement.style.backgroundImage = '';
|
|
948
|
+
this.trackArtworkElement.style.display = 'none';
|
|
949
|
+
}
|
|
860
950
|
}
|
|
861
951
|
|
|
862
952
|
/**
|
|
@@ -929,6 +1019,10 @@ export class PlaylistManager {
|
|
|
929
1019
|
this.player.off('error', this.handleTrackError);
|
|
930
1020
|
|
|
931
1021
|
// Remove UI
|
|
1022
|
+
if (this.trackArtworkElement) {
|
|
1023
|
+
this.trackArtworkElement.remove();
|
|
1024
|
+
}
|
|
1025
|
+
|
|
932
1026
|
if (this.trackInfoElement) {
|
|
933
1027
|
this.trackInfoElement.remove();
|
|
934
1028
|
}
|
package/src/i18n/languages/de.js
CHANGED
|
@@ -157,6 +157,14 @@ export const de = {
|
|
|
157
157
|
minutes: '{count} Minuten',
|
|
158
158
|
second: '{count} Sekunde',
|
|
159
159
|
seconds: '{count} Sekunden'
|
|
160
|
+
},
|
|
161
|
+
playlist: {
|
|
162
|
+
title: 'Wiedergabeliste',
|
|
163
|
+
trackOf: 'Titel {current} von {total}',
|
|
164
|
+
nowPlaying: 'Läuft gerade: Titel {current} von {total}. {title}{artist}',
|
|
165
|
+
by: ' von ',
|
|
166
|
+
untitled: 'Ohne Titel',
|
|
167
|
+
trackUntitled: 'Titel {number}'
|
|
160
168
|
}
|
|
161
169
|
};
|
|
162
170
|
|
package/src/i18n/languages/en.js
CHANGED
|
@@ -157,6 +157,14 @@ export const en = {
|
|
|
157
157
|
minutes: '{count} minutes',
|
|
158
158
|
second: '{count} second',
|
|
159
159
|
seconds: '{count} seconds'
|
|
160
|
+
},
|
|
161
|
+
playlist: {
|
|
162
|
+
title: 'Playlist',
|
|
163
|
+
trackOf: 'Track {current} of {total}',
|
|
164
|
+
nowPlaying: 'Now playing: Track {current} of {total}. {title}{artist}',
|
|
165
|
+
by: ' by ',
|
|
166
|
+
untitled: 'Untitled',
|
|
167
|
+
trackUntitled: 'Track {number}'
|
|
160
168
|
}
|
|
161
169
|
};
|
|
162
170
|
|
package/src/i18n/languages/es.js
CHANGED
|
@@ -157,6 +157,14 @@ export const es = {
|
|
|
157
157
|
minutes: '{count} minutos',
|
|
158
158
|
second: '{count} segundo',
|
|
159
159
|
seconds: '{count} segundos'
|
|
160
|
+
},
|
|
161
|
+
playlist: {
|
|
162
|
+
title: 'Lista de reproducción',
|
|
163
|
+
trackOf: 'Pista {current} de {total}',
|
|
164
|
+
nowPlaying: 'Reproduciendo ahora: Pista {current} de {total}. {title}{artist}',
|
|
165
|
+
by: ' por ',
|
|
166
|
+
untitled: 'Sin título',
|
|
167
|
+
trackUntitled: 'Pista {number}'
|
|
160
168
|
}
|
|
161
169
|
};
|
|
162
170
|
|
package/src/i18n/languages/fr.js
CHANGED
|
@@ -157,6 +157,14 @@ export const fr = {
|
|
|
157
157
|
minutes: '{count} minutes',
|
|
158
158
|
second: '{count} seconde',
|
|
159
159
|
seconds: '{count} secondes'
|
|
160
|
+
},
|
|
161
|
+
playlist: {
|
|
162
|
+
title: 'Liste de lecture',
|
|
163
|
+
trackOf: 'Piste {current} sur {total}',
|
|
164
|
+
nowPlaying: 'Lecture en cours : Piste {current} sur {total}. {title}{artist}',
|
|
165
|
+
by: ' par ',
|
|
166
|
+
untitled: 'Sans titre',
|
|
167
|
+
trackUntitled: 'Piste {number}'
|
|
160
168
|
}
|
|
161
169
|
};
|
|
162
170
|
|
package/src/i18n/languages/ja.js
CHANGED
|
@@ -157,6 +157,14 @@ export const ja = {
|
|
|
157
157
|
minutes: '{count}分',
|
|
158
158
|
second: '{count}秒',
|
|
159
159
|
seconds: '{count}秒'
|
|
160
|
+
},
|
|
161
|
+
playlist: {
|
|
162
|
+
title: 'プレイリスト',
|
|
163
|
+
trackOf: 'トラック {current}/{total}',
|
|
164
|
+
nowPlaying: '再生中: トラック {current}/{total}. {title}{artist}',
|
|
165
|
+
by: ' - ',
|
|
166
|
+
untitled: 'タイトルなし',
|
|
167
|
+
trackUntitled: 'トラック {number}'
|
|
160
168
|
}
|
|
161
169
|
};
|
|
162
170
|
|