vidply 1.0.3 → 1.0.5

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.js CHANGED
@@ -422,184 +422,6 @@ var VidPly = (() => {
422
422
  }
423
423
  };
424
424
 
425
- // src/utils/TimeUtils.js
426
- var TimeUtils = {
427
- /**
428
- * Format seconds to time string (HH:MM:SS or MM:SS)
429
- */
430
- formatTime(seconds, alwaysShowHours = false) {
431
- if (!isFinite(seconds) || seconds < 0) {
432
- return alwaysShowHours ? "00:00:00" : "00:00";
433
- }
434
- const hours = Math.floor(seconds / 3600);
435
- const minutes = Math.floor(seconds % 3600 / 60);
436
- const secs = Math.floor(seconds % 60);
437
- const pad = (num) => String(num).padStart(2, "0");
438
- if (hours > 0 || alwaysShowHours) {
439
- return `${pad(hours)}:${pad(minutes)}:${pad(secs)}`;
440
- }
441
- return `${pad(minutes)}:${pad(secs)}`;
442
- },
443
- /**
444
- * Parse time string to seconds
445
- */
446
- parseTime(timeString) {
447
- const parts = timeString.split(":").map((p) => parseInt(p, 10));
448
- if (parts.length === 3) {
449
- return parts[0] * 3600 + parts[1] * 60 + parts[2];
450
- } else if (parts.length === 2) {
451
- return parts[0] * 60 + parts[1];
452
- } else if (parts.length === 1) {
453
- return parts[0];
454
- }
455
- return 0;
456
- },
457
- /**
458
- * Format seconds to readable duration
459
- */
460
- formatDuration(seconds) {
461
- if (!isFinite(seconds) || seconds < 0) {
462
- return "0 seconds";
463
- }
464
- const hours = Math.floor(seconds / 3600);
465
- const minutes = Math.floor(seconds % 3600 / 60);
466
- const secs = Math.floor(seconds % 60);
467
- const parts = [];
468
- if (hours > 0) {
469
- parts.push(`${hours} hour${hours !== 1 ? "s" : ""}`);
470
- }
471
- if (minutes > 0) {
472
- parts.push(`${minutes} minute${minutes !== 1 ? "s" : ""}`);
473
- }
474
- if (secs > 0 || parts.length === 0) {
475
- parts.push(`${secs} second${secs !== 1 ? "s" : ""}`);
476
- }
477
- return parts.join(", ");
478
- },
479
- /**
480
- * Format percentage
481
- */
482
- formatPercentage(value, total) {
483
- if (total === 0) return 0;
484
- return Math.round(value / total * 100);
485
- }
486
- };
487
-
488
- // src/icons/Icons.js
489
- var iconPaths = {
490
- play: `<path d="M8 5v14l11-7z"/>`,
491
- pause: `<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/>`,
492
- stop: `<rect x="6" y="6" width="12" height="12"/>`,
493
- rewind: `<path d="M11 18V6l-8.5 6 8.5 6zm.5-6l8.5 6V6l-8.5 6z"/>`,
494
- forward: `<path d="M4 18l8.5-6L4 6v12zm9-12v12l8.5-6L13 6z"/>`,
495
- skipPrevious: `<path d="M6 6h2v12H6V6zm3 6l8.5 6V6L9 12z"/>`,
496
- skipNext: `<path d="M16 6h2v12h-2V6zM6 6l8.5 6L6 18V6z"/>`,
497
- restart: `<path d="M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/>`,
498
- volumeHigh: `<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>`,
499
- volumeMedium: `<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/>`,
500
- volumeLow: `<path d="M7 9v6h4l5 5V4l-5 5H7z"/>`,
501
- volumeMuted: `<path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/>`,
502
- fullscreen: `<path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>`,
503
- fullscreenExit: `<path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>`,
504
- settings: `<path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94L14.4 2.81c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/>`,
505
- captions: `<path d="M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1c0 .55-.45 1-1 1h-3c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1z"/>`,
506
- captionsOff: `<path d="M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1c0 .55-.45 1-1 1h-3c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1z"/><path d="M0 0h24v24H0z" fill="none"/>`,
507
- pip: `<path d="M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 1.98 2 1.98h18c1.1 0 2-.88 2-1.98V5c0-1.1-.9-2-2-2zm0 16.01H3V4.98h18v14.03z"/>`,
508
- speed: `<path d="M20.38 8.57l-1.23 1.85a8 8 0 0 1-.22 7.58H5.07A8 8 0 0 1 15.58 6.85l1.85-1.23A10 10 0 0 0 3.35 19a2 2 0 0 0 1.72 1h13.85a2 2 0 0 0 1.74-1 10 10 0 0 0-.27-10.44z"/><path d="M10.59 15.41a2 2 0 0 0 2.83 0l5.66-8.49-8.49 5.66a2 2 0 0 0 0 2.83z"/>`,
509
- close: `<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>`,
510
- check: `<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>`,
511
- arrowUp: `<path d="M7 14l5-5 5 5z"/>`,
512
- arrowDown: `<path d="M7 10l5 5 5-5z"/>`,
513
- arrowLeft: `<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>`,
514
- arrowRight: `<path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/>`,
515
- loading: `<path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/>`,
516
- error: `<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>`,
517
- download: `<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>`,
518
- link: `<path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/>`,
519
- playlist: `<path d="M15 6H3v2h12V6zm0 4H3v2h12v-2zM3 16h8v-2H3v2zM17 6v8.18c-.31-.11-.65-.18-1-.18-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3V8h3V6h-5z"/>`,
520
- language: `<path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.91 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56-1.84-.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95c-.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z"/>`,
521
- hd: `<path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-8 12H9.5v-2h-2v2H6V9h1.5v2.5h2V9H11v6zm7-1c0 .55-.45 1-1 1h-.75v1.5h-1.5V15H14c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v4zm-3.5-.5h2v-3h-2v3z"/>`,
522
- transcript: `<path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/>`,
523
- audioDescription: `<path d="M12 3v9.28c-.47-.17-.97-.28-1.5-.28C8.01 12 6 14.01 6 16.5S8.01 21 10.5 21c2.31 0 4.2-1.75 4.45-4H15V6h4V3h-7z"/><path d="M10.5 19c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>`,
524
- audioDescriptionOn: `<path d="M12 3v9.28c-.47-.17-.97-.28-1.5-.28C8.01 12 6 14.01 6 16.5S8.01 21 10.5 21c2.31 0 4.2-1.75 4.45-4H15V6h4V3h-7z"/><path d="M10.5 19c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/><circle cx="19" cy="16" r="3" fill="#3b82f6"/><path d="M18.5 17.5l1-1 1.5 1.5" stroke="white" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>`,
525
- signLanguage: `<g transform="scale(1.5)"><path d="M16 11.3c-.1-.9-4.8 1.3-5.4 1.1-2.6-1 5.8-1.3 5.1-2.9s-5.1 1.5-6 1.4C6.5 9.4 16.5 9.1 13.5 8c-1.9-.6-8.8 2.9-6.8.4.7-.6.7-1.9-.7-1.7-9.7 7.2-.7 12.2 8.8 7 0-1.3-3.5.4-4.1.4-2.6 0 5.6-2 5.4-3ZM3.9 7.8c3.2-4.2 3.7 1.2 6 .1s.2-.2.2-.3c.7-2.7 2.5-7.5-1.5-1.3-1.6 0 1.1-4 1-4.6C8.9-1 7.3 4.4 7.2 4.9c-1.6.7-.9-1.4-.7-1.5 3-6-.6-3.1-.9.4-2.5 1.8 0-2.8 0-3.5C2.8-.9 4 9.4 1.1 4.9S.1 4.6 0 5c-.4 2.7 2.6 7.2 3.9 2.8Z"/></g>`,
526
- signLanguageOn: `<g transform="scale(1.5)"><path d="M16 11.3c-.1-.9-4.8 1.3-5.4 1.1-2.6-1 5.8-1.3 5.1-2.9s-5.1 1.5-6 1.4C6.5 9.4 16.5 9.1 13.5 8c-1.9-.6-8.8 2.9-6.8.4.7-.6.7-1.9-.7-1.7-9.7 7.2-.7 12.2 8.8 7 0-1.3-3.5.4-4.1.4-2.6 0 5.6-2 5.4-3ZM3.9 7.8c3.2-4.2 3.7 1.2 6 .1s.2-.2.2-.3c.7-2.7 2.5-7.5-1.5-1.3-1.6 0 1.1-4 1-4.6C8.9-1 7.3 4.4 7.2 4.9c-1.6.7-.9-1.4-.7-1.5 3-6-.6-3.1-.9.4-2.5 1.8 0-2.8 0-3.5C2.8-.9 4 9.4 1.1 4.9S.1 4.6 0 5c-.4 2.7 2.6 7.2 3.9 2.8Z"/></g>`,
527
- speaker: `<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/>`,
528
- music: `<path d="M12 3v9.28c-.47-.17-.97-.28-1.5-.28C8.01 12 6 14.01 6 16.5S8.01 21 10.5 21c2.31 0 4.2-1.75 4.45-4H15V6h4V3h-7zm-1.5 16c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>`,
529
- moreVertical: `<path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/>`,
530
- moreHorizontal: `<path d="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/>`
531
- };
532
- var svgWrapper = (paths) => `<svg viewBox="0 0 24 24" fill="currentColor">${paths}</svg>`;
533
- var Icons = Object.fromEntries(
534
- Object.entries(iconPaths).map(([key, value]) => [key, svgWrapper(value)])
535
- );
536
- function getIcon(name) {
537
- return Icons[name] || Icons.play;
538
- }
539
- function createIconElement(name, className = "") {
540
- const wrapper = document.createElement("span");
541
- wrapper.className = `vidply-icon ${className}`.trim();
542
- wrapper.innerHTML = getIcon(name);
543
- wrapper.setAttribute("aria-hidden", "true");
544
- return wrapper;
545
- }
546
- function createPlayOverlay() {
547
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
548
- svg.setAttribute("class", "vidply-play-overlay");
549
- svg.setAttribute("viewBox", "0 0 80 80");
550
- svg.setAttribute("width", "80");
551
- svg.setAttribute("height", "80");
552
- svg.setAttribute("aria-hidden", "true");
553
- svg.setAttribute("role", "presentation");
554
- svg.style.cursor = "pointer";
555
- const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
556
- const filterId = `vidply-play-shadow-${Math.random().toString(36).substr(2, 9)}`;
557
- const filter = document.createElementNS("http://www.w3.org/2000/svg", "filter");
558
- filter.setAttribute("id", filterId);
559
- filter.setAttribute("x", "-50%");
560
- filter.setAttribute("y", "-50%");
561
- filter.setAttribute("width", "200%");
562
- filter.setAttribute("height", "200%");
563
- const feGaussianBlur = document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
564
- feGaussianBlur.setAttribute("in", "SourceAlpha");
565
- feGaussianBlur.setAttribute("stdDeviation", "3");
566
- const feOffset = document.createElementNS("http://www.w3.org/2000/svg", "feOffset");
567
- feOffset.setAttribute("dx", "0");
568
- feOffset.setAttribute("dy", "2");
569
- feOffset.setAttribute("result", "offsetblur");
570
- const feComponentTransfer = document.createElementNS("http://www.w3.org/2000/svg", "feComponentTransfer");
571
- const feFuncA = document.createElementNS("http://www.w3.org/2000/svg", "feFuncA");
572
- feFuncA.setAttribute("type", "linear");
573
- feFuncA.setAttribute("slope", "0.3");
574
- feComponentTransfer.appendChild(feFuncA);
575
- const feMerge = document.createElementNS("http://www.w3.org/2000/svg", "feMerge");
576
- const feMergeNode1 = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
577
- const feMergeNode2 = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
578
- feMergeNode2.setAttribute("in", "SourceGraphic");
579
- feMerge.appendChild(feMergeNode1);
580
- feMerge.appendChild(feMergeNode2);
581
- filter.appendChild(feGaussianBlur);
582
- filter.appendChild(feOffset);
583
- filter.appendChild(feComponentTransfer);
584
- filter.appendChild(feMerge);
585
- defs.appendChild(filter);
586
- svg.appendChild(defs);
587
- const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
588
- circle.setAttribute("cx", "40");
589
- circle.setAttribute("cy", "40");
590
- circle.setAttribute("r", "40");
591
- circle.setAttribute("fill", "rgba(255, 255, 255, 0.95)");
592
- circle.setAttribute("filter", `url(#${filterId})`);
593
- circle.setAttribute("class", "vidply-play-overlay-bg");
594
- svg.appendChild(circle);
595
- const playTriangle = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
596
- playTriangle.setAttribute("points", "32,28 32,52 54,40");
597
- playTriangle.setAttribute("fill", "#0a406e");
598
- playTriangle.setAttribute("class", "vidply-play-overlay-icon");
599
- svg.appendChild(playTriangle);
600
- return svg;
601
- }
602
-
603
425
  // src/i18n/translations.js
604
426
  var translations = {
605
427
  en: {
@@ -700,6 +522,16 @@ var VidPly = (() => {
700
522
  },
701
523
  speeds: {
702
524
  normal: "Normal"
525
+ },
526
+ time: {
527
+ display: "Time display",
528
+ durationPrefix: "Duration: ",
529
+ hour: "{count} hour",
530
+ hours: "{count} hours",
531
+ minute: "{count} minute",
532
+ minutes: "{count} minutes",
533
+ second: "{count} second",
534
+ seconds: "{count} seconds"
703
535
  }
704
536
  },
705
537
  de: {
@@ -800,6 +632,16 @@ var VidPly = (() => {
800
632
  },
801
633
  speeds: {
802
634
  normal: "Normal"
635
+ },
636
+ time: {
637
+ display: "Zeitanzeige",
638
+ durationPrefix: "Dauer: ",
639
+ hour: "{count} Stunde",
640
+ hours: "{count} Stunden",
641
+ minute: "{count} Minute",
642
+ minutes: "{count} Minuten",
643
+ second: "{count} Sekunde",
644
+ seconds: "{count} Sekunden"
803
645
  }
804
646
  },
805
647
  es: {
@@ -900,6 +742,16 @@ var VidPly = (() => {
900
742
  },
901
743
  speeds: {
902
744
  normal: "Normal"
745
+ },
746
+ time: {
747
+ display: "Visualizaci\xF3n de tiempo",
748
+ durationPrefix: "Duraci\xF3n: ",
749
+ hour: "{count} hora",
750
+ hours: "{count} horas",
751
+ minute: "{count} minuto",
752
+ minutes: "{count} minutos",
753
+ second: "{count} segundo",
754
+ seconds: "{count} segundos"
903
755
  }
904
756
  },
905
757
  fr: {
@@ -1000,6 +852,16 @@ var VidPly = (() => {
1000
852
  },
1001
853
  speeds: {
1002
854
  normal: "Normal"
855
+ },
856
+ time: {
857
+ display: "Affichage du temps",
858
+ durationPrefix: "Dur\xE9e : ",
859
+ hour: "{count} heure",
860
+ hours: "{count} heures",
861
+ minute: "{count} minute",
862
+ minutes: "{count} minutes",
863
+ second: "{count} seconde",
864
+ seconds: "{count} secondes"
1003
865
  }
1004
866
  },
1005
867
  ja: {
@@ -1100,6 +962,16 @@ var VidPly = (() => {
1100
962
  },
1101
963
  speeds: {
1102
964
  normal: "\u901A\u5E38"
965
+ },
966
+ time: {
967
+ display: "\u6642\u9593\u8868\u793A",
968
+ durationPrefix: "\u518D\u751F\u6642\u9593: ",
969
+ hour: "{count}\u6642\u9593",
970
+ hours: "{count}\u6642\u9593",
971
+ minute: "{count}\u5206",
972
+ minutes: "{count}\u5206",
973
+ second: "{count}\u79D2",
974
+ seconds: "{count}\u79D2"
1103
975
  }
1104
976
  }
1105
977
  };
@@ -1155,6 +1027,187 @@ var VidPly = (() => {
1155
1027
  };
1156
1028
  var i18n = new I18n();
1157
1029
 
1030
+ // src/utils/TimeUtils.js
1031
+ var TimeUtils = {
1032
+ /**
1033
+ * Format seconds to time string (HH:MM:SS or MM:SS)
1034
+ */
1035
+ formatTime(seconds, alwaysShowHours = false) {
1036
+ if (!isFinite(seconds) || seconds < 0) {
1037
+ return alwaysShowHours ? "00:00:00" : "00:00";
1038
+ }
1039
+ const hours = Math.floor(seconds / 3600);
1040
+ const minutes = Math.floor(seconds % 3600 / 60);
1041
+ const secs = Math.floor(seconds % 60);
1042
+ const pad = (num) => String(num).padStart(2, "0");
1043
+ if (hours > 0 || alwaysShowHours) {
1044
+ return `${pad(hours)}:${pad(minutes)}:${pad(secs)}`;
1045
+ }
1046
+ return `${pad(minutes)}:${pad(secs)}`;
1047
+ },
1048
+ /**
1049
+ * Parse time string to seconds
1050
+ */
1051
+ parseTime(timeString) {
1052
+ const parts = timeString.split(":").map((p) => parseInt(p, 10));
1053
+ if (parts.length === 3) {
1054
+ return parts[0] * 3600 + parts[1] * 60 + parts[2];
1055
+ } else if (parts.length === 2) {
1056
+ return parts[0] * 60 + parts[1];
1057
+ } else if (parts.length === 1) {
1058
+ return parts[0];
1059
+ }
1060
+ return 0;
1061
+ },
1062
+ /**
1063
+ * Format seconds to readable duration
1064
+ */
1065
+ formatDuration(seconds) {
1066
+ if (!isFinite(seconds) || seconds < 0) {
1067
+ return i18n.t("time.seconds", { count: 0 });
1068
+ }
1069
+ const hours = Math.floor(seconds / 3600);
1070
+ const minutes = Math.floor(seconds % 3600 / 60);
1071
+ const secs = Math.floor(seconds % 60);
1072
+ const parts = [];
1073
+ if (hours > 0) {
1074
+ const key = hours === 1 ? "time.hour" : "time.hours";
1075
+ parts.push(i18n.t(key, { count: hours }));
1076
+ }
1077
+ if (minutes > 0) {
1078
+ const key = minutes === 1 ? "time.minute" : "time.minutes";
1079
+ parts.push(i18n.t(key, { count: minutes }));
1080
+ }
1081
+ if (secs > 0 || parts.length === 0) {
1082
+ const key = secs === 1 ? "time.second" : "time.seconds";
1083
+ parts.push(i18n.t(key, { count: secs }));
1084
+ }
1085
+ return parts.join(", ");
1086
+ },
1087
+ /**
1088
+ * Format percentage
1089
+ */
1090
+ formatPercentage(value, total) {
1091
+ if (total === 0) return 0;
1092
+ return Math.round(value / total * 100);
1093
+ }
1094
+ };
1095
+
1096
+ // src/icons/Icons.js
1097
+ var iconPaths = {
1098
+ play: `<path d="M8 5v14l11-7z"/>`,
1099
+ pause: `<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/>`,
1100
+ stop: `<rect x="6" y="6" width="12" height="12"/>`,
1101
+ rewind: `<path d="M11 18V6l-8.5 6 8.5 6zm.5-6l8.5 6V6l-8.5 6z"/>`,
1102
+ forward: `<path d="M4 18l8.5-6L4 6v12zm9-12v12l8.5-6L13 6z"/>`,
1103
+ skipPrevious: `<path d="M6 6h2v12H6V6zm3 6l8.5 6V6L9 12z"/>`,
1104
+ skipNext: `<path d="M16 6h2v12h-2V6zM6 6l8.5 6L6 18V6z"/>`,
1105
+ restart: `<path d="M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/>`,
1106
+ volumeHigh: `<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>`,
1107
+ volumeMedium: `<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/>`,
1108
+ volumeLow: `<path d="M7 9v6h4l5 5V4l-5 5H7z"/>`,
1109
+ volumeMuted: `<path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/>`,
1110
+ fullscreen: `<path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>`,
1111
+ fullscreenExit: `<path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>`,
1112
+ settings: `<path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94L14.4 2.81c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/>`,
1113
+ captions: `<path d="M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1c0 .55-.45 1-1 1h-3c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1z"/>`,
1114
+ captionsOff: `<path d="M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1c0 .55-.45 1-1 1h-3c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1z"/><path d="M0 0h24v24H0z" fill="none"/>`,
1115
+ pip: `<path d="M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 1.98 2 1.98h18c1.1 0 2-.88 2-1.98V5c0-1.1-.9-2-2-2zm0 16.01H3V4.98h18v14.03z"/>`,
1116
+ speed: `<path d="M20.38 8.57l-1.23 1.85a8 8 0 0 1-.22 7.58H5.07A8 8 0 0 1 15.58 6.85l1.85-1.23A10 10 0 0 0 3.35 19a2 2 0 0 0 1.72 1h13.85a2 2 0 0 0 1.74-1 10 10 0 0 0-.27-10.44z"/><path d="M10.59 15.41a2 2 0 0 0 2.83 0l5.66-8.49-8.49 5.66a2 2 0 0 0 0 2.83z"/>`,
1117
+ close: `<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>`,
1118
+ check: `<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>`,
1119
+ arrowUp: `<path d="M7 14l5-5 5 5z"/>`,
1120
+ arrowDown: `<path d="M7 10l5 5 5-5z"/>`,
1121
+ arrowLeft: `<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>`,
1122
+ arrowRight: `<path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/>`,
1123
+ loading: `<path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/>`,
1124
+ error: `<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>`,
1125
+ download: `<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>`,
1126
+ link: `<path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/>`,
1127
+ playlist: `<path d="M15 6H3v2h12V6zm0 4H3v2h12v-2zM3 16h8v-2H3v2zM17 6v8.18c-.31-.11-.65-.18-1-.18-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3V8h3V6h-5z"/>`,
1128
+ language: `<path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.91 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56-1.84-.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95c-.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z"/>`,
1129
+ hd: `<path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-8 12H9.5v-2h-2v2H6V9h1.5v2.5h2V9H11v6zm7-1c0 .55-.45 1-1 1h-.75v1.5h-1.5V15H14c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v4zm-3.5-.5h2v-3h-2v3z"/>`,
1130
+ transcript: `<path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/>`,
1131
+ audioDescription: `<path d="M12 3v9.28c-.47-.17-.97-.28-1.5-.28C8.01 12 6 14.01 6 16.5S8.01 21 10.5 21c2.31 0 4.2-1.75 4.45-4H15V6h4V3h-7z"/><path d="M10.5 19c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>`,
1132
+ audioDescriptionOn: `<path d="M12 3v9.28c-.47-.17-.97-.28-1.5-.28C8.01 12 6 14.01 6 16.5S8.01 21 10.5 21c2.31 0 4.2-1.75 4.45-4H15V6h4V3h-7z"/><path d="M10.5 19c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/><circle cx="19" cy="16" r="3" fill="#3b82f6"/><path d="M18.5 17.5l1-1 1.5 1.5" stroke="white" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>`,
1133
+ signLanguage: `<g transform="scale(1.5)"><path d="M16 11.3c-.1-.9-4.8 1.3-5.4 1.1-2.6-1 5.8-1.3 5.1-2.9s-5.1 1.5-6 1.4C6.5 9.4 16.5 9.1 13.5 8c-1.9-.6-8.8 2.9-6.8.4.7-.6.7-1.9-.7-1.7-9.7 7.2-.7 12.2 8.8 7 0-1.3-3.5.4-4.1.4-2.6 0 5.6-2 5.4-3ZM3.9 7.8c3.2-4.2 3.7 1.2 6 .1s.2-.2.2-.3c.7-2.7 2.5-7.5-1.5-1.3-1.6 0 1.1-4 1-4.6C8.9-1 7.3 4.4 7.2 4.9c-1.6.7-.9-1.4-.7-1.5 3-6-.6-3.1-.9.4-2.5 1.8 0-2.8 0-3.5C2.8-.9 4 9.4 1.1 4.9S.1 4.6 0 5c-.4 2.7 2.6 7.2 3.9 2.8Z"/></g>`,
1134
+ signLanguageOn: `<g transform="scale(1.5)"><path d="M16 11.3c-.1-.9-4.8 1.3-5.4 1.1-2.6-1 5.8-1.3 5.1-2.9s-5.1 1.5-6 1.4C6.5 9.4 16.5 9.1 13.5 8c-1.9-.6-8.8 2.9-6.8.4.7-.6.7-1.9-.7-1.7-9.7 7.2-.7 12.2 8.8 7 0-1.3-3.5.4-4.1.4-2.6 0 5.6-2 5.4-3ZM3.9 7.8c3.2-4.2 3.7 1.2 6 .1s.2-.2.2-.3c.7-2.7 2.5-7.5-1.5-1.3-1.6 0 1.1-4 1-4.6C8.9-1 7.3 4.4 7.2 4.9c-1.6.7-.9-1.4-.7-1.5 3-6-.6-3.1-.9.4-2.5 1.8 0-2.8 0-3.5C2.8-.9 4 9.4 1.1 4.9S.1 4.6 0 5c-.4 2.7 2.6 7.2 3.9 2.8Z"/></g>`,
1135
+ speaker: `<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/>`,
1136
+ music: `<path d="M12 3v9.28c-.47-.17-.97-.28-1.5-.28C8.01 12 6 14.01 6 16.5S8.01 21 10.5 21c2.31 0 4.2-1.75 4.45-4H15V6h4V3h-7zm-1.5 16c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>`,
1137
+ moreVertical: `<path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/>`,
1138
+ moreHorizontal: `<path d="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/>`
1139
+ };
1140
+ var svgWrapper = (paths) => `<svg viewBox="0 0 24 24" fill="currentColor">${paths}</svg>`;
1141
+ var Icons = Object.fromEntries(
1142
+ Object.entries(iconPaths).map(([key, value]) => [key, svgWrapper(value)])
1143
+ );
1144
+ function getIcon(name) {
1145
+ return Icons[name] || Icons.play;
1146
+ }
1147
+ function createIconElement(name, className = "") {
1148
+ const wrapper = document.createElement("span");
1149
+ wrapper.className = `vidply-icon ${className}`.trim();
1150
+ wrapper.innerHTML = getIcon(name);
1151
+ wrapper.setAttribute("aria-hidden", "true");
1152
+ return wrapper;
1153
+ }
1154
+ function createPlayOverlay() {
1155
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
1156
+ svg.setAttribute("class", "vidply-play-overlay");
1157
+ svg.setAttribute("viewBox", "0 0 80 80");
1158
+ svg.setAttribute("width", "80");
1159
+ svg.setAttribute("height", "80");
1160
+ svg.setAttribute("aria-hidden", "true");
1161
+ svg.setAttribute("role", "presentation");
1162
+ svg.style.cursor = "pointer";
1163
+ const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
1164
+ const filterId = `vidply-play-shadow-${Math.random().toString(36).substr(2, 9)}`;
1165
+ const filter = document.createElementNS("http://www.w3.org/2000/svg", "filter");
1166
+ filter.setAttribute("id", filterId);
1167
+ filter.setAttribute("x", "-50%");
1168
+ filter.setAttribute("y", "-50%");
1169
+ filter.setAttribute("width", "200%");
1170
+ filter.setAttribute("height", "200%");
1171
+ const feGaussianBlur = document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
1172
+ feGaussianBlur.setAttribute("in", "SourceAlpha");
1173
+ feGaussianBlur.setAttribute("stdDeviation", "3");
1174
+ const feOffset = document.createElementNS("http://www.w3.org/2000/svg", "feOffset");
1175
+ feOffset.setAttribute("dx", "0");
1176
+ feOffset.setAttribute("dy", "2");
1177
+ feOffset.setAttribute("result", "offsetblur");
1178
+ const feComponentTransfer = document.createElementNS("http://www.w3.org/2000/svg", "feComponentTransfer");
1179
+ const feFuncA = document.createElementNS("http://www.w3.org/2000/svg", "feFuncA");
1180
+ feFuncA.setAttribute("type", "linear");
1181
+ feFuncA.setAttribute("slope", "0.3");
1182
+ feComponentTransfer.appendChild(feFuncA);
1183
+ const feMerge = document.createElementNS("http://www.w3.org/2000/svg", "feMerge");
1184
+ const feMergeNode1 = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
1185
+ const feMergeNode2 = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
1186
+ feMergeNode2.setAttribute("in", "SourceGraphic");
1187
+ feMerge.appendChild(feMergeNode1);
1188
+ feMerge.appendChild(feMergeNode2);
1189
+ filter.appendChild(feGaussianBlur);
1190
+ filter.appendChild(feOffset);
1191
+ filter.appendChild(feComponentTransfer);
1192
+ filter.appendChild(feMerge);
1193
+ defs.appendChild(filter);
1194
+ svg.appendChild(defs);
1195
+ const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
1196
+ circle.setAttribute("cx", "40");
1197
+ circle.setAttribute("cy", "40");
1198
+ circle.setAttribute("r", "40");
1199
+ circle.setAttribute("fill", "rgba(255, 255, 255, 0.95)");
1200
+ circle.setAttribute("filter", `url(#${filterId})`);
1201
+ circle.setAttribute("class", "vidply-play-overlay-bg");
1202
+ svg.appendChild(circle);
1203
+ const playTriangle = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
1204
+ playTriangle.setAttribute("points", "32,28 32,52 54,40");
1205
+ playTriangle.setAttribute("fill", "#0a406e");
1206
+ playTriangle.setAttribute("class", "vidply-play-overlay-icon");
1207
+ svg.appendChild(playTriangle);
1208
+ return svg;
1209
+ }
1210
+
1158
1211
  // src/controls/ControlBar.js
1159
1212
  var ControlBar = class {
1160
1213
  constructor(player) {
@@ -1671,12 +1724,26 @@ var VidPly = (() => {
1671
1724
  }
1672
1725
  createTimeDisplay() {
1673
1726
  const container = DOMUtils.createElement("div", {
1674
- className: `${this.player.options.classPrefix}-time`
1727
+ className: `${this.player.options.classPrefix}-time`,
1728
+ attributes: {
1729
+ "role": "group",
1730
+ "aria-label": i18n.t("time.display")
1731
+ }
1675
1732
  });
1676
1733
  this.controls.currentTimeDisplay = DOMUtils.createElement("span", {
1677
1734
  className: `${this.player.options.classPrefix}-current-time`,
1678
- textContent: "00:00"
1735
+ attributes: {
1736
+ "aria-label": i18n.t("time.seconds", { count: 0 })
1737
+ }
1738
+ });
1739
+ const currentTimeVisual = DOMUtils.createElement("span", {
1740
+ textContent: "00:00",
1741
+ attributes: {
1742
+ "aria-hidden": "true"
1743
+ }
1679
1744
  });
1745
+ this.controls.currentTimeDisplay.appendChild(currentTimeVisual);
1746
+ this.controls.currentTimeVisual = currentTimeVisual;
1680
1747
  const separator = DOMUtils.createElement("span", {
1681
1748
  textContent: " / ",
1682
1749
  attributes: {
@@ -1685,8 +1752,18 @@ var VidPly = (() => {
1685
1752
  });
1686
1753
  this.controls.durationDisplay = DOMUtils.createElement("span", {
1687
1754
  className: `${this.player.options.classPrefix}-duration`,
1688
- textContent: "00:00"
1755
+ attributes: {
1756
+ "aria-label": i18n.t("time.durationPrefix") + i18n.t("time.seconds", { count: 0 })
1757
+ }
1689
1758
  });
1759
+ const durationVisual = DOMUtils.createElement("span", {
1760
+ textContent: "00:00",
1761
+ attributes: {
1762
+ "aria-hidden": "true"
1763
+ }
1764
+ });
1765
+ this.controls.durationDisplay.appendChild(durationVisual);
1766
+ this.controls.durationVisual = durationVisual;
1690
1767
  container.appendChild(this.controls.currentTimeDisplay);
1691
1768
  container.appendChild(separator);
1692
1769
  container.appendChild(this.controls.durationDisplay);
@@ -2510,13 +2587,17 @@ var VidPly = (() => {
2510
2587
  const percent = this.player.state.currentTime / this.player.state.duration * 100;
2511
2588
  this.controls.played.style.width = `${percent}%`;
2512
2589
  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);
2590
+ if (this.controls.currentTimeVisual) {
2591
+ const currentTime = this.player.state.currentTime;
2592
+ this.controls.currentTimeVisual.textContent = TimeUtils.formatTime(currentTime);
2593
+ this.controls.currentTimeDisplay.setAttribute("aria-label", TimeUtils.formatDuration(currentTime));
2515
2594
  }
2516
2595
  }
2517
2596
  updateDuration() {
2518
- if (this.controls.durationDisplay) {
2519
- this.controls.durationDisplay.textContent = TimeUtils.formatTime(this.player.state.duration);
2597
+ if (this.controls.durationVisual) {
2598
+ const duration = this.player.state.duration;
2599
+ this.controls.durationVisual.textContent = TimeUtils.formatTime(duration);
2600
+ this.controls.durationDisplay.setAttribute("aria-label", i18n.t("time.durationPrefix") + TimeUtils.formatDuration(duration));
2520
2601
  }
2521
2602
  }
2522
2603
  updateVolumeDisplay() {
@@ -4578,6 +4659,17 @@ var VidPly = (() => {
4578
4659
  this.captionManager.destroy();
4579
4660
  this.captionManager = new CaptionManager(this);
4580
4661
  }
4662
+ if (this.transcriptManager) {
4663
+ const wasVisible = this.transcriptManager.isVisible;
4664
+ this.transcriptManager.destroy();
4665
+ this.transcriptManager = new TranscriptManager(this);
4666
+ if (wasVisible) {
4667
+ this.transcriptManager.showTranscript();
4668
+ }
4669
+ }
4670
+ if (this.controlBar) {
4671
+ this.updateControlBar();
4672
+ }
4581
4673
  this.emit("sourcechange", config);
4582
4674
  this.log("Media loaded successfully");
4583
4675
  } catch (error) {
@@ -4589,6 +4681,17 @@ var VidPly = (() => {
4589
4681
  * @param {string} src - New source URL
4590
4682
  * @returns {boolean}
4591
4683
  */
4684
+ /**
4685
+ * Update control bar to refresh button visibility based on available features
4686
+ */
4687
+ updateControlBar() {
4688
+ if (!this.controlBar) return;
4689
+ const controlBar = this.controlBar;
4690
+ controlBar.element.innerHTML = "";
4691
+ controlBar.createControls();
4692
+ controlBar.attachEvents();
4693
+ controlBar.setupAutoHide();
4694
+ }
4592
4695
  shouldChangeRenderer(src) {
4593
4696
  if (!this.renderer) return true;
4594
4697
  const isYouTube = src.includes("youtube.com") || src.includes("youtu.be");
@@ -5048,7 +5151,9 @@ var VidPly = (() => {
5048
5151
  this.trackInfoElement = null;
5049
5152
  this.handleTrackEnd = this.handleTrackEnd.bind(this);
5050
5153
  this.handleTrackError = this.handleTrackError.bind(this);
5154
+ this.player.playlistManager = this;
5051
5155
  this.init();
5156
+ this.updatePlayerControls();
5052
5157
  }
5053
5158
  init() {
5054
5159
  this.player.on("ended", this.handleTrackEnd);
@@ -5057,6 +5162,17 @@ var VidPly = (() => {
5057
5162
  this.createUI();
5058
5163
  }
5059
5164
  }
5165
+ /**
5166
+ * Update player controls to add playlist navigation buttons
5167
+ */
5168
+ updatePlayerControls() {
5169
+ if (!this.player.controlBar) return;
5170
+ const controlBar = this.player.controlBar;
5171
+ controlBar.element.innerHTML = "";
5172
+ controlBar.createControls();
5173
+ controlBar.attachEvents();
5174
+ controlBar.setupAutoHide();
5175
+ }
5060
5176
  /**
5061
5177
  * Load a playlist
5062
5178
  * @param {Array} tracks - Array of track objects
@@ -5077,8 +5193,9 @@ var VidPly = (() => {
5077
5193
  /**
5078
5194
  * Play a specific track
5079
5195
  * @param {number} index - Track index
5196
+ * @param {boolean} userInitiated - Whether this was triggered by user action (default: false)
5080
5197
  */
5081
- play(index) {
5198
+ play(index, userInitiated = false) {
5082
5199
  if (index < 0 || index >= this.tracks.length) {
5083
5200
  console.warn("VidPly Playlist: Invalid track index", index);
5084
5201
  return;
@@ -5098,6 +5215,9 @@ var VidPly = (() => {
5098
5215
  item: track,
5099
5216
  total: this.tracks.length
5100
5217
  });
5218
+ if (userInitiated && this.player.container) {
5219
+ this.player.container.focus();
5220
+ }
5101
5221
  setTimeout(() => {
5102
5222
  this.player.play();
5103
5223
  }, 100);
@@ -5159,12 +5279,17 @@ var VidPly = (() => {
5159
5279
  return;
5160
5280
  }
5161
5281
  this.trackInfoElement = DOMUtils.createElement("div", {
5162
- className: "vidply-track-info"
5282
+ className: "vidply-track-info",
5283
+ role: "status",
5284
+ "aria-live": "polite",
5285
+ "aria-atomic": "true"
5163
5286
  });
5164
5287
  this.trackInfoElement.style.display = "none";
5165
5288
  this.container.appendChild(this.trackInfoElement);
5166
5289
  this.playlistPanel = DOMUtils.createElement("div", {
5167
- className: "vidply-playlist-panel"
5290
+ className: "vidply-playlist-panel",
5291
+ role: "region",
5292
+ "aria-label": "Media playlist"
5168
5293
  });
5169
5294
  this.playlistPanel.style.display = "none";
5170
5295
  this.container.appendChild(this.playlistPanel);
@@ -5176,10 +5301,14 @@ var VidPly = (() => {
5176
5301
  if (!this.trackInfoElement) return;
5177
5302
  const trackNumber = this.currentIndex + 1;
5178
5303
  const totalTracks = this.tracks.length;
5304
+ const trackTitle = track.title || "Untitled";
5305
+ const trackArtist = track.artist || "";
5306
+ const announcement = `Now playing: Track ${trackNumber} of ${totalTracks}. ${trackTitle}${trackArtist ? " by " + trackArtist : ""}`;
5179
5307
  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>` : ""}
5308
+ <span class="vidply-sr-only">${DOMUtils.escapeHTML(announcement)}</span>
5309
+ <div class="vidply-track-number" aria-hidden="true">Track ${trackNumber} of ${totalTracks}</div>
5310
+ <div class="vidply-track-title" aria-hidden="true">${DOMUtils.escapeHTML(trackTitle)}</div>
5311
+ ${trackArtist ? `<div class="vidply-track-artist" aria-hidden="true">${DOMUtils.escapeHTML(trackArtist)}</div>` : ""}
5183
5312
  `;
5184
5313
  this.trackInfoElement.style.display = "block";
5185
5314
  }
@@ -5189,14 +5318,29 @@ var VidPly = (() => {
5189
5318
  renderPlaylist() {
5190
5319
  if (!this.playlistPanel) return;
5191
5320
  this.playlistPanel.innerHTML = "";
5192
- const header = DOMUtils.createElement("div", {
5193
- className: "vidply-playlist-header"
5321
+ const header = DOMUtils.createElement("h2", {
5322
+ className: "vidply-playlist-header",
5323
+ id: "vidply-playlist-heading"
5194
5324
  });
5195
5325
  header.textContent = `Playlist (${this.tracks.length})`;
5196
5326
  this.playlistPanel.appendChild(header);
5197
- const list = DOMUtils.createElement("div", {
5198
- className: "vidply-playlist-list"
5199
- });
5327
+ const instructions = DOMUtils.createElement("div", {
5328
+ className: "vidply-sr-only",
5329
+ "aria-hidden": "false"
5330
+ });
5331
+ 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.";
5332
+ this.playlistPanel.appendChild(instructions);
5333
+ const list = DOMUtils.createElement("ul", {
5334
+ className: "vidply-playlist-list",
5335
+ "aria-labelledby": "vidply-playlist-heading",
5336
+ "aria-describedby": "vidply-playlist-instructions"
5337
+ });
5338
+ const listDescription = DOMUtils.createElement("div", {
5339
+ className: "vidply-sr-only",
5340
+ id: "vidply-playlist-instructions"
5341
+ });
5342
+ listDescription.textContent = `Playlist with ${this.tracks.length} ${this.tracks.length === 1 ? "track" : "tracks"}`;
5343
+ this.playlistPanel.appendChild(listDescription);
5200
5344
  this.tracks.forEach((track, index) => {
5201
5345
  const item = this.createPlaylistItem(track, index);
5202
5346
  list.appendChild(item);
@@ -5208,20 +5352,39 @@ var VidPly = (() => {
5208
5352
  * Create playlist item element
5209
5353
  */
5210
5354
  createPlaylistItem(track, index) {
5211
- const item = DOMUtils.createElement("div", {
5355
+ const trackPosition = `Track ${index + 1} of ${this.tracks.length}`;
5356
+ const trackTitle = track.title || `Track ${index + 1}`;
5357
+ const trackArtist = track.artist ? ` by ${track.artist}` : "";
5358
+ const isActive = index === this.currentIndex;
5359
+ const statusText = isActive ? "Currently playing" : "Not playing";
5360
+ const actionText = isActive ? "Press Enter to restart" : "Press Enter to play";
5361
+ const item = DOMUtils.createElement("li", {
5212
5362
  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) {
5363
+ tabIndex: index === 0 ? 0 : -1,
5364
+ // Only first item is in tab order initially
5365
+ "aria-label": `${trackPosition}. ${trackTitle}${trackArtist}. ${statusText}. ${actionText}.`,
5366
+ "aria-posinset": index + 1,
5367
+ "aria-setsize": this.tracks.length,
5368
+ "data-playlist-index": index
5369
+ });
5370
+ if (isActive) {
5218
5371
  item.classList.add("vidply-playlist-item-active");
5372
+ item.setAttribute("aria-current", "true");
5373
+ item.setAttribute("tabIndex", "0");
5219
5374
  }
5375
+ const positionInfo = DOMUtils.createElement("span", {
5376
+ className: "vidply-sr-only"
5377
+ });
5378
+ positionInfo.textContent = `${trackPosition}: `;
5379
+ item.appendChild(positionInfo);
5220
5380
  const thumbnail = DOMUtils.createElement("div", {
5221
- className: "vidply-playlist-thumbnail"
5381
+ className: "vidply-playlist-thumbnail",
5382
+ "aria-hidden": "true"
5222
5383
  });
5223
5384
  if (track.poster) {
5224
5385
  thumbnail.style.backgroundImage = `url(${track.poster})`;
5386
+ thumbnail.setAttribute("role", "img");
5387
+ thumbnail.setAttribute("aria-label", `${trackTitle} thumbnail`);
5225
5388
  } else {
5226
5389
  const icon = createIconElement("music");
5227
5390
  icon.classList.add("vidply-playlist-thumbnail-icon");
@@ -5229,12 +5392,13 @@ var VidPly = (() => {
5229
5392
  }
5230
5393
  item.appendChild(thumbnail);
5231
5394
  const info = DOMUtils.createElement("div", {
5232
- className: "vidply-playlist-item-info"
5395
+ className: "vidply-playlist-item-info",
5396
+ "aria-hidden": "true"
5233
5397
  });
5234
5398
  const title = DOMUtils.createElement("div", {
5235
5399
  className: "vidply-playlist-item-title"
5236
5400
  });
5237
- title.textContent = track.title || `Track ${index + 1}`;
5401
+ title.textContent = trackTitle;
5238
5402
  info.appendChild(title);
5239
5403
  if (track.artist) {
5240
5404
  const artist = DOMUtils.createElement("div", {
@@ -5244,20 +5408,64 @@ var VidPly = (() => {
5244
5408
  info.appendChild(artist);
5245
5409
  }
5246
5410
  item.appendChild(info);
5411
+ if (isActive) {
5412
+ const statusIndicator = DOMUtils.createElement("span", {
5413
+ className: "vidply-sr-only"
5414
+ });
5415
+ statusIndicator.textContent = " (Currently playing)";
5416
+ item.appendChild(statusIndicator);
5417
+ }
5247
5418
  const playIcon = createIconElement("play");
5248
5419
  playIcon.classList.add("vidply-playlist-item-icon");
5420
+ playIcon.setAttribute("aria-hidden", "true");
5249
5421
  item.appendChild(playIcon);
5250
5422
  item.addEventListener("click", () => {
5251
- this.play(index);
5423
+ this.play(index, true);
5252
5424
  });
5253
5425
  item.addEventListener("keydown", (e) => {
5254
- if (e.key === "Enter" || e.key === " ") {
5255
- e.preventDefault();
5256
- this.play(index);
5257
- }
5426
+ this.handlePlaylistItemKeydown(e, index);
5258
5427
  });
5259
5428
  return item;
5260
5429
  }
5430
+ /**
5431
+ * Handle keyboard navigation in playlist items
5432
+ */
5433
+ handlePlaylistItemKeydown(e, index) {
5434
+ const items = Array.from(this.playlistPanel.querySelectorAll(".vidply-playlist-item"));
5435
+ let newIndex = -1;
5436
+ switch (e.key) {
5437
+ case "Enter":
5438
+ case " ":
5439
+ e.preventDefault();
5440
+ this.play(index, true);
5441
+ break;
5442
+ case "ArrowDown":
5443
+ e.preventDefault();
5444
+ if (index < items.length - 1) {
5445
+ newIndex = index + 1;
5446
+ }
5447
+ break;
5448
+ case "ArrowUp":
5449
+ e.preventDefault();
5450
+ if (index > 0) {
5451
+ newIndex = index - 1;
5452
+ }
5453
+ break;
5454
+ case "Home":
5455
+ e.preventDefault();
5456
+ newIndex = 0;
5457
+ break;
5458
+ case "End":
5459
+ e.preventDefault();
5460
+ newIndex = items.length - 1;
5461
+ break;
5462
+ }
5463
+ if (newIndex !== -1 && newIndex !== index) {
5464
+ items[index].setAttribute("tabIndex", "-1");
5465
+ items[newIndex].setAttribute("tabIndex", "0");
5466
+ items[newIndex].focus();
5467
+ }
5468
+ }
5261
5469
  /**
5262
5470
  * Update playlist UI (highlight current track)
5263
5471
  */
@@ -5265,11 +5473,25 @@ var VidPly = (() => {
5265
5473
  if (!this.playlistPanel) return;
5266
5474
  const items = this.playlistPanel.querySelectorAll(".vidply-playlist-item");
5267
5475
  items.forEach((item, index) => {
5476
+ const track = this.tracks[index];
5477
+ const trackPosition = `Track ${index + 1} of ${this.tracks.length}`;
5478
+ const trackTitle = track.title || `Track ${index + 1}`;
5479
+ const trackArtist = track.artist ? ` by ${track.artist}` : "";
5268
5480
  if (index === this.currentIndex) {
5269
5481
  item.classList.add("vidply-playlist-item-active");
5482
+ item.setAttribute("aria-current", "true");
5483
+ item.setAttribute("tabIndex", "0");
5484
+ const statusText = "Currently playing";
5485
+ const actionText = "Press Enter to restart";
5486
+ item.setAttribute("aria-label", `${trackPosition}. ${trackTitle}${trackArtist}. ${statusText}. ${actionText}.`);
5270
5487
  item.scrollIntoView({ behavior: "smooth", block: "nearest" });
5271
5488
  } else {
5272
5489
  item.classList.remove("vidply-playlist-item-active");
5490
+ item.removeAttribute("aria-current");
5491
+ item.setAttribute("tabIndex", "-1");
5492
+ const statusText = "Not playing";
5493
+ const actionText = "Press Enter to play";
5494
+ item.setAttribute("aria-label", `${trackPosition}. ${trackTitle}${trackArtist}. ${statusText}. ${actionText}.`);
5273
5495
  }
5274
5496
  });
5275
5497
  }
@@ -5287,10 +5509,24 @@ var VidPly = (() => {
5287
5509
  currentIndex: this.currentIndex,
5288
5510
  totalTracks: this.tracks.length,
5289
5511
  currentTrack: this.getCurrentTrack(),
5290
- hasNext: this.currentIndex < this.tracks.length - 1,
5291
- hasPrevious: this.currentIndex > 0
5512
+ hasNext: this.hasNext(),
5513
+ hasPrevious: this.hasPrevious()
5292
5514
  };
5293
5515
  }
5516
+ /**
5517
+ * Check if there is a next track
5518
+ */
5519
+ hasNext() {
5520
+ if (this.options.loop) return true;
5521
+ return this.currentIndex < this.tracks.length - 1;
5522
+ }
5523
+ /**
5524
+ * Check if there is a previous track
5525
+ */
5526
+ hasPrevious() {
5527
+ if (this.options.loop) return true;
5528
+ return this.currentIndex > 0;
5529
+ }
5294
5530
  /**
5295
5531
  * Add track to playlist
5296
5532
  */