vidply 1.0.6 → 1.0.8

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.
@@ -448,7 +448,11 @@ var translations = {
448
448
  noCaptions: "No captions available",
449
449
  auto: "Auto",
450
450
  autoQuality: "Auto (no quality selection available)",
451
- noQuality: "Quality selection not available"
451
+ noQuality: "Quality selection not available",
452
+ signLanguageDragResize: "Sign Language Video - Press D to drag with keyboard, R to resize",
453
+ signLanguageDragActive: "Sign Language Video - Drag mode active. Use arrow keys to move, Escape to exit.",
454
+ signLanguageResizeActive: "Sign Language Video - Resize mode active. Use left/right arrow keys to resize, Escape to exit.",
455
+ resizeHandle: "Resize {direction} corner"
452
456
  },
453
457
  captions: {
454
458
  off: "Off",
@@ -461,7 +465,7 @@ var translations = {
461
465
  },
462
466
  fontSizes: {
463
467
  small: "Small",
464
- medium: "Medium",
468
+ normal: "Normal",
465
469
  large: "Large",
466
470
  xlarge: "X-Large"
467
471
  },
@@ -489,7 +493,14 @@ var translations = {
489
493
  title: "Transcript",
490
494
  close: "Close transcript",
491
495
  loading: "Loading transcript...",
492
- noTranscript: "No transcript available for this video."
496
+ noTranscript: "No transcript available for this video.",
497
+ settings: "Transcript settings. Press Enter to open menu, or D to enable drag mode",
498
+ keyboardDragMode: "Toggle keyboard drag mode with arrow keys. Shortcut: D key",
499
+ keyboardDragActive: "\u2328\uFE0F Keyboard Drag Mode Active (Arrow keys to move, Shift+Arrows for large steps, D or ESC to exit)",
500
+ resizeWindow: "Resize Window",
501
+ styleTranscript: "Open transcript style settings",
502
+ closeMenu: "Close Menu",
503
+ styleTitle: "Transcript Style"
493
504
  },
494
505
  settings: {
495
506
  title: "Settings",
@@ -558,7 +569,11 @@ var translations = {
558
569
  noCaptions: "Keine Untertitel verf\xFCgbar",
559
570
  auto: "Automatisch",
560
571
  autoQuality: "Automatisch (keine Qualit\xE4tsauswahl verf\xFCgbar)",
561
- noQuality: "Qualit\xE4tsauswahl nicht verf\xFCgbar"
572
+ noQuality: "Qualit\xE4tsauswahl nicht verf\xFCgbar",
573
+ signLanguageDragResize: "Geb\xE4rdensprache-Video - Dr\xFCcken Sie D zum Verschieben per Tastatur, R zum \xC4ndern der Gr\xF6\xDFe",
574
+ signLanguageDragActive: "Geb\xE4rdensprache-Video - Verschiebemodus aktiv. Pfeiltasten zum Bewegen, Escape zum Beenden.",
575
+ signLanguageResizeActive: "Geb\xE4rdensprache-Video - Gr\xF6\xDFen\xE4nderungsmodus aktiv. Links-/Rechts-Pfeiltasten zum \xC4ndern der Gr\xF6\xDFe, Escape zum Beenden.",
576
+ resizeHandle: "Gr\xF6\xDFen\xE4nderung {direction}-Ecke"
562
577
  },
563
578
  captions: {
564
579
  off: "Aus",
@@ -571,7 +586,7 @@ var translations = {
571
586
  },
572
587
  fontSizes: {
573
588
  small: "Klein",
574
- medium: "Mittel",
589
+ normal: "Normal",
575
590
  large: "Gro\xDF",
576
591
  xlarge: "Sehr gro\xDF"
577
592
  },
@@ -599,7 +614,14 @@ var translations = {
599
614
  title: "Transkript",
600
615
  close: "Transkript schlie\xDFen",
601
616
  loading: "Transkript wird geladen...",
602
- noTranscript: "Kein Transkript f\xFCr dieses Video verf\xFCgbar."
617
+ noTranscript: "Kein Transkript f\xFCr dieses Video verf\xFCgbar.",
618
+ settings: "Transkript-Einstellungen. Eingabetaste zum \xD6ffnen des Men\xFCs dr\xFCcken oder D zum Aktivieren des Verschiebemodus",
619
+ keyboardDragMode: "Tastatur-Verschiebemodus mit Pfeiltasten umschalten. Tastenkombination: D-Taste",
620
+ keyboardDragActive: "\u2328\uFE0F Tastatur-Verschiebemodus aktiv (Pfeiltasten zum Bewegen, Umschalt+Pfeiltasten f\xFCr gro\xDFe Schritte, D oder ESC zum Beenden)",
621
+ resizeWindow: "Fenster vergr\xF6\xDFern/verkleinern",
622
+ styleTranscript: "Transkript-Stileinstellungen \xF6ffnen",
623
+ closeMenu: "Men\xFC schlie\xDFen",
624
+ styleTitle: "Transkript-Stil"
603
625
  },
604
626
  settings: {
605
627
  title: "Einstellungen",
@@ -668,7 +690,11 @@ var translations = {
668
690
  noCaptions: "No hay subt\xEDtulos disponibles",
669
691
  auto: "Autom\xE1tico",
670
692
  autoQuality: "Autom\xE1tico (selecci\xF3n de calidad no disponible)",
671
- noQuality: "Selecci\xF3n de calidad no disponible"
693
+ noQuality: "Selecci\xF3n de calidad no disponible",
694
+ signLanguageDragResize: "Video en Lengua de Se\xF1as - Presione D para arrastrar con el teclado, R para cambiar el tama\xF1o",
695
+ signLanguageDragActive: "Video en Lengua de Se\xF1as - Modo de arrastre activo. Use las teclas de flecha para mover, Escape para salir.",
696
+ signLanguageResizeActive: "Video en Lengua de Se\xF1as - Modo de cambio de tama\xF1o activo. Use las teclas de flecha izquierda/derecha para cambiar el tama\xF1o, Escape para salir.",
697
+ resizeHandle: "Cambiar tama\xF1o esquina {direction}"
672
698
  },
673
699
  captions: {
674
700
  off: "Desactivado",
@@ -681,7 +707,7 @@ var translations = {
681
707
  },
682
708
  fontSizes: {
683
709
  small: "Peque\xF1o",
684
- medium: "Mediano",
710
+ normal: "Normal",
685
711
  large: "Grande",
686
712
  xlarge: "Muy grande"
687
713
  },
@@ -709,7 +735,14 @@ var translations = {
709
735
  title: "Transcripci\xF3n",
710
736
  close: "Cerrar transcripci\xF3n",
711
737
  loading: "Cargando transcripci\xF3n...",
712
- noTranscript: "No hay transcripci\xF3n disponible para este video."
738
+ noTranscript: "No hay transcripci\xF3n disponible para este video.",
739
+ settings: "Configuraci\xF3n de transcripci\xF3n. Presione Enter para abrir el men\xFA o D para activar el modo de arrastre",
740
+ keyboardDragMode: "Alternar modo de arrastre con teclado usando teclas de flecha. Atajo: tecla D",
741
+ keyboardDragActive: "\u2328\uFE0F Modo de Arrastre con Teclado Activo (Teclas de flecha para mover, May\xFAs+Flechas para pasos grandes, D o ESC para salir)",
742
+ resizeWindow: "Cambiar tama\xF1o de ventana",
743
+ styleTranscript: "Abrir configuraci\xF3n de estilo de transcripci\xF3n",
744
+ closeMenu: "Cerrar men\xFA",
745
+ styleTitle: "Estilo de Transcripci\xF3n"
713
746
  },
714
747
  settings: {
715
748
  title: "Configuraci\xF3n",
@@ -778,7 +811,11 @@ var translations = {
778
811
  noCaptions: "Aucun sous-titre disponible",
779
812
  auto: "Automatique",
780
813
  autoQuality: "Automatique (s\xE9lection de qualit\xE9 non disponible)",
781
- noQuality: "S\xE9lection de qualit\xE9 non disponible"
814
+ noQuality: "S\xE9lection de qualit\xE9 non disponible",
815
+ signLanguageDragResize: "Vid\xE9o en Langue des Signes - Appuyez sur D pour d\xE9placer avec le clavier, R pour redimensionner",
816
+ signLanguageDragActive: "Vid\xE9o en Langue des Signes - Mode glissement actif. Utilisez les touches fl\xE9ch\xE9es pour d\xE9placer, \xC9chap pour quitter.",
817
+ signLanguageResizeActive: "Vid\xE9o en Langue des Signes - Mode redimensionnement actif. Utilisez les touches fl\xE9ch\xE9es gauche/droite pour redimensionner, \xC9chap pour quitter.",
818
+ resizeHandle: "Redimensionner coin {direction}"
782
819
  },
783
820
  captions: {
784
821
  off: "D\xE9sactiv\xE9",
@@ -791,7 +828,7 @@ var translations = {
791
828
  },
792
829
  fontSizes: {
793
830
  small: "Petit",
794
- medium: "Moyen",
831
+ normal: "Normal",
795
832
  large: "Grand",
796
833
  xlarge: "Tr\xE8s grand"
797
834
  },
@@ -819,7 +856,14 @@ var translations = {
819
856
  title: "Transcription",
820
857
  close: "Fermer la transcription",
821
858
  loading: "Chargement de la transcription...",
822
- noTranscript: "Aucune transcription disponible pour cette vid\xE9o."
859
+ noTranscript: "Aucune transcription disponible pour cette vid\xE9o.",
860
+ settings: "Param\xE8tres de transcription. Appuyez sur Entr\xE9e pour ouvrir le menu ou D pour activer le mode glissement",
861
+ keyboardDragMode: "Basculer le mode glissement avec les touches fl\xE9ch\xE9es. Raccourci: touche D",
862
+ keyboardDragActive: "\u2328\uFE0F Mode Glissement Clavier Actif (Touches fl\xE9ch\xE9es pour d\xE9placer, Maj+Fl\xE9ch\xE9es pour grands pas, D ou \xC9chap pour quitter)",
863
+ resizeWindow: "Redimensionner la fen\xEAtre",
864
+ styleTranscript: "Ouvrir les param\xE8tres de style de transcription",
865
+ closeMenu: "Fermer le menu",
866
+ styleTitle: "Style de Transcription"
823
867
  },
824
868
  settings: {
825
869
  title: "Param\xE8tres",
@@ -888,7 +932,11 @@ var translations = {
888
932
  noCaptions: "\u5B57\u5E55\u304C\u3042\u308A\u307E\u305B\u3093",
889
933
  auto: "\u81EA\u52D5",
890
934
  autoQuality: "\u81EA\u52D5\uFF08\u753B\u8CEA\u9078\u629E\u4E0D\u53EF\uFF09",
891
- noQuality: "\u753B\u8CEA\u9078\u629E\u4E0D\u53EF"
935
+ noQuality: "\u753B\u8CEA\u9078\u629E\u4E0D\u53EF",
936
+ signLanguageDragResize: "\u624B\u8A71\u52D5\u753B - \u30AD\u30FC\u30DC\u30FC\u30C9\u3067\u30C9\u30E9\u30C3\u30B0\u3059\u308B\u306B\u306FD\u30AD\u30FC\u3092\u3001\u30B5\u30A4\u30BA\u5909\u66F4\u3059\u308B\u306B\u306FR\u30AD\u30FC\u3092\u62BC\u3057\u3066\u304F\u3060\u3055\u3044",
937
+ signLanguageDragActive: "\u624B\u8A71\u52D5\u753B - \u30C9\u30E9\u30C3\u30B0\u30E2\u30FC\u30C9\u304C\u6709\u52B9\u3067\u3059\u3002\u77E2\u5370\u30AD\u30FC\u3067\u79FB\u52D5\u3001Escape\u3067\u7D42\u4E86\u3057\u307E\u3059\u3002",
938
+ signLanguageResizeActive: "\u624B\u8A71\u52D5\u753B - \u30B5\u30A4\u30BA\u5909\u66F4\u30E2\u30FC\u30C9\u304C\u6709\u52B9\u3067\u3059\u3002\u5DE6\u53F3\u306E\u77E2\u5370\u30AD\u30FC\u3067\u30B5\u30A4\u30BA\u5909\u66F4\u3001Escape\u3067\u7D42\u4E86\u3057\u307E\u3059\u3002",
939
+ resizeHandle: "{direction}\u30B3\u30FC\u30CA\u30FC\u306E\u30B5\u30A4\u30BA\u5909\u66F4"
892
940
  },
893
941
  captions: {
894
942
  off: "\u30AA\u30D5",
@@ -901,7 +949,7 @@ var translations = {
901
949
  },
902
950
  fontSizes: {
903
951
  small: "\u5C0F",
904
- medium: "\u4E2D",
952
+ normal: "\u6A19\u6E96",
905
953
  large: "\u5927",
906
954
  xlarge: "\u7279\u5927"
907
955
  },
@@ -929,7 +977,14 @@ var translations = {
929
977
  title: "\u6587\u5B57\u8D77\u3053\u3057",
930
978
  close: "\u6587\u5B57\u8D77\u3053\u3057\u3092\u9589\u3058\u308B",
931
979
  loading: "\u6587\u5B57\u8D77\u3053\u3057\u3092\u8AAD\u307F\u8FBC\u307F\u4E2D...",
932
- noTranscript: "\u3053\u306E\u30D3\u30C7\u30AA\u306E\u6587\u5B57\u8D77\u3053\u3057\u306F\u3042\u308A\u307E\u305B\u3093\u3002"
980
+ noTranscript: "\u3053\u306E\u30D3\u30C7\u30AA\u306E\u6587\u5B57\u8D77\u3053\u3057\u306F\u3042\u308A\u307E\u305B\u3093\u3002",
981
+ settings: "\u6587\u5B57\u8D77\u3053\u3057\u8A2D\u5B9A\u3002Enter\u30AD\u30FC\u3067\u30E1\u30CB\u30E5\u30FC\u3092\u958B\u304F\u3001\u307E\u305F\u306FD\u30AD\u30FC\u3067\u30C9\u30E9\u30C3\u30B0\u30E2\u30FC\u30C9\u3092\u6709\u52B9\u306B\u3059\u308B",
982
+ keyboardDragMode: "\u77E2\u5370\u30AD\u30FC\u3067\u30AD\u30FC\u30DC\u30FC\u30C9\u30C9\u30E9\u30C3\u30B0\u30E2\u30FC\u30C9\u3092\u5207\u308A\u66FF\u3048\u3002\u30B7\u30E7\u30FC\u30C8\u30AB\u30C3\u30C8\uFF1AD\u30AD\u30FC",
983
+ keyboardDragActive: "\u2328\uFE0F \u30AD\u30FC\u30DC\u30FC\u30C9\u30C9\u30E9\u30C3\u30B0\u30E2\u30FC\u30C9\u6709\u52B9\uFF08\u77E2\u5370\u30AD\u30FC\u3067\u79FB\u52D5\u3001Shift+\u77E2\u5370\u30AD\u30FC\u3067\u5927\u304D\u304F\u79FB\u52D5\u3001D\u307E\u305F\u306FESC\u3067\u7D42\u4E86\uFF09",
984
+ resizeWindow: "\u30A6\u30A3\u30F3\u30C9\u30A6\u306E\u30B5\u30A4\u30BA\u5909\u66F4",
985
+ styleTranscript: "\u6587\u5B57\u8D77\u3053\u3057\u30B9\u30BF\u30A4\u30EB\u8A2D\u5B9A\u3092\u958B\u304F",
986
+ closeMenu: "\u30E1\u30CB\u30E5\u30FC\u3092\u9589\u3058\u308B",
987
+ styleTitle: "\u6587\u5B57\u8D77\u3053\u3057\u30B9\u30BF\u30A4\u30EB"
933
988
  },
934
989
  settings: {
935
990
  title: "\u8A2D\u5B9A",
@@ -1108,14 +1163,16 @@ var iconPaths = {
1108
1163
  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"/>`,
1109
1164
  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"/>`,
1110
1165
  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"/>`,
1111
- 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"/>`,
1112
- 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"/>`,
1166
+ audioDescription: `<rect x="2" y="5" width="20" height="14" rx="2" fill="none" stroke="currentColor" stroke-width="2"/><text x="12" y="16" font-family="Arial, sans-serif" font-size="10" font-weight="bold" text-anchor="middle" fill="currentColor">AD</text>`,
1167
+ audioDescriptionOn: `<rect x="2" y="5" width="20" height="14" rx="2" fill="#1a1a1a" stroke="#1a1a1a" stroke-width="2"/><text x="12" y="16" font-family="Arial, sans-serif" font-size="10" font-weight="bold" text-anchor="middle" fill="#ffffff">AD</text>`,
1113
1168
  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>`,
1114
1169
  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>`,
1115
1170
  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"/>`,
1116
1171
  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"/>`,
1117
1172
  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"/>`,
1118
- 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"/>`
1173
+ 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"/>`,
1174
+ move: `<path d="M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z"/>`,
1175
+ resize: `<path d="M21.71 11.29l-9-9c-.39-.39-1.02-.39-1.41 0l-9 9c-.39.39-.39 1.02 0 1.41l9 9c.39.39 1.02.39 1.41 0l9-9c.39-.38.39-1.01 0-1.41zM14 14.5V12h-4v2.5L7 11l3-3.5V10h4V7.5l3 3.5-3 3.5z"/>`
1119
1176
  };
1120
1177
  var svgWrapper = (paths) => `<svg viewBox="0 0 24 24" fill="currentColor">${paths}</svg>`;
1121
1178
  var Icons = Object.fromEntries(
@@ -1411,7 +1468,11 @@ var ControlBar = class {
1411
1468
  return false;
1412
1469
  }
1413
1470
  hasAudioDescription() {
1414
- return this.player.audioDescriptionSrc && this.player.audioDescriptionSrc.length > 0;
1471
+ if (this.player.audioDescriptionSrc && this.player.audioDescriptionSrc.length > 0) {
1472
+ return true;
1473
+ }
1474
+ const textTracks = Array.from(this.player.element.textTracks || []);
1475
+ return textTracks.some((track) => track.kind === "descriptions");
1415
1476
  }
1416
1477
  hasSignLanguage() {
1417
1478
  return this.player.signLanguageSrc && this.player.signLanguageSrc.length > 0;
@@ -2022,9 +2083,9 @@ var ControlBar = class {
2022
2083
  i18n.t("styleLabels.fontSize"),
2023
2084
  "captionsFontSize",
2024
2085
  [
2025
- { label: i18n.t("fontSizes.small"), value: "80%" },
2026
- { label: i18n.t("fontSizes.medium"), value: "100%" },
2027
- { label: i18n.t("fontSizes.large"), value: "120%" },
2086
+ { label: i18n.t("fontSizes.small"), value: "87.5%" },
2087
+ { label: i18n.t("fontSizes.normal"), value: "100%" },
2088
+ { label: i18n.t("fontSizes.large"), value: "125%" },
2028
2089
  { label: i18n.t("fontSizes.xlarge"), value: "150%" }
2029
2090
  ]
2030
2091
  );
@@ -2713,6 +2774,141 @@ var ControlBar = class {
2713
2774
  }
2714
2775
  };
2715
2776
 
2777
+ // src/utils/StorageManager.js
2778
+ var StorageManager = class {
2779
+ constructor(namespace = "vidply") {
2780
+ this.namespace = namespace;
2781
+ this.storage = this.isStorageAvailable() ? localStorage : null;
2782
+ }
2783
+ /**
2784
+ * Check if localStorage is available
2785
+ */
2786
+ isStorageAvailable() {
2787
+ try {
2788
+ const test = "__storage_test__";
2789
+ localStorage.setItem(test, test);
2790
+ localStorage.removeItem(test);
2791
+ return true;
2792
+ } catch (e) {
2793
+ return false;
2794
+ }
2795
+ }
2796
+ /**
2797
+ * Get a namespaced key
2798
+ */
2799
+ getKey(key) {
2800
+ return `${this.namespace}_${key}`;
2801
+ }
2802
+ /**
2803
+ * Save a value to storage
2804
+ */
2805
+ set(key, value) {
2806
+ if (!this.storage) return false;
2807
+ try {
2808
+ const namespacedKey = this.getKey(key);
2809
+ this.storage.setItem(namespacedKey, JSON.stringify(value));
2810
+ return true;
2811
+ } catch (e) {
2812
+ console.warn("Failed to save to localStorage:", e);
2813
+ return false;
2814
+ }
2815
+ }
2816
+ /**
2817
+ * Get a value from storage
2818
+ */
2819
+ get(key, defaultValue = null) {
2820
+ if (!this.storage) return defaultValue;
2821
+ try {
2822
+ const namespacedKey = this.getKey(key);
2823
+ const value = this.storage.getItem(namespacedKey);
2824
+ return value ? JSON.parse(value) : defaultValue;
2825
+ } catch (e) {
2826
+ console.warn("Failed to read from localStorage:", e);
2827
+ return defaultValue;
2828
+ }
2829
+ }
2830
+ /**
2831
+ * Remove a value from storage
2832
+ */
2833
+ remove(key) {
2834
+ if (!this.storage) return false;
2835
+ try {
2836
+ const namespacedKey = this.getKey(key);
2837
+ this.storage.removeItem(namespacedKey);
2838
+ return true;
2839
+ } catch (e) {
2840
+ console.warn("Failed to remove from localStorage:", e);
2841
+ return false;
2842
+ }
2843
+ }
2844
+ /**
2845
+ * Clear all namespaced values
2846
+ */
2847
+ clear() {
2848
+ if (!this.storage) return false;
2849
+ try {
2850
+ const keys = Object.keys(this.storage);
2851
+ keys.forEach((key) => {
2852
+ if (key.startsWith(this.namespace)) {
2853
+ this.storage.removeItem(key);
2854
+ }
2855
+ });
2856
+ return true;
2857
+ } catch (e) {
2858
+ console.warn("Failed to clear localStorage:", e);
2859
+ return false;
2860
+ }
2861
+ }
2862
+ /**
2863
+ * Save transcript preferences
2864
+ */
2865
+ saveTranscriptPreferences(preferences) {
2866
+ return this.set("transcript_preferences", preferences);
2867
+ }
2868
+ /**
2869
+ * Get transcript preferences
2870
+ */
2871
+ getTranscriptPreferences() {
2872
+ return this.get("transcript_preferences", null);
2873
+ }
2874
+ /**
2875
+ * Save caption preferences
2876
+ */
2877
+ saveCaptionPreferences(preferences) {
2878
+ return this.set("caption_preferences", preferences);
2879
+ }
2880
+ /**
2881
+ * Get caption preferences
2882
+ */
2883
+ getCaptionPreferences() {
2884
+ return this.get("caption_preferences", null);
2885
+ }
2886
+ /**
2887
+ * Save player preferences (volume, speed, etc.)
2888
+ */
2889
+ savePlayerPreferences(preferences) {
2890
+ return this.set("player_preferences", preferences);
2891
+ }
2892
+ /**
2893
+ * Get player preferences
2894
+ */
2895
+ getPlayerPreferences() {
2896
+ return this.get("player_preferences", null);
2897
+ }
2898
+ /**
2899
+ * Save sign language preferences (position and size)
2900
+ */
2901
+ saveSignLanguagePreferences(preferences) {
2902
+ return this.set("sign_language_preferences", preferences);
2903
+ }
2904
+ /**
2905
+ * Get sign language preferences
2906
+ */
2907
+ getSignLanguagePreferences() {
2908
+ return this.get("sign_language_preferences", null);
2909
+ }
2910
+ };
2911
+
2716
2912
  // src/controls/CaptionManager.js
2717
2913
  var CaptionManager = class {
2718
2914
  constructor(player) {
@@ -2721,8 +2917,29 @@ var CaptionManager = class {
2721
2917
  this.tracks = [];
2722
2918
  this.currentTrack = null;
2723
2919
  this.currentCue = null;
2920
+ this.storage = new StorageManager("vidply");
2921
+ this.loadSavedPreferences();
2724
2922
  this.init();
2725
2923
  }
2924
+ loadSavedPreferences() {
2925
+ const saved = this.storage.getCaptionPreferences();
2926
+ if (saved) {
2927
+ if (saved.fontSize) this.player.options.captionsFontSize = saved.fontSize;
2928
+ if (saved.fontFamily) this.player.options.captionsFontFamily = saved.fontFamily;
2929
+ if (saved.color) this.player.options.captionsColor = saved.color;
2930
+ if (saved.backgroundColor) this.player.options.captionsBackgroundColor = saved.backgroundColor;
2931
+ if (saved.opacity !== void 0) this.player.options.captionsOpacity = saved.opacity;
2932
+ }
2933
+ }
2934
+ saveCaptionPreferences() {
2935
+ this.storage.saveCaptionPreferences({
2936
+ fontSize: this.player.options.captionsFontSize,
2937
+ fontFamily: this.player.options.captionsFontFamily,
2938
+ color: this.player.options.captionsColor,
2939
+ backgroundColor: this.player.options.captionsBackgroundColor,
2940
+ opacity: this.player.options.captionsOpacity
2941
+ });
2942
+ }
2726
2943
  init() {
2727
2944
  this.createElement();
2728
2945
  this.loadTracks();
@@ -2871,6 +3088,7 @@ var CaptionManager = class {
2871
3088
  break;
2872
3089
  }
2873
3090
  this.updateStyles();
3091
+ this.saveCaptionPreferences();
2874
3092
  this.player.emit("captionschange");
2875
3093
  }
2876
3094
  getAvailableTracks() {
@@ -3081,11 +3299,36 @@ var TranscriptManager = class {
3081
3299
  this.player = player;
3082
3300
  this.transcriptWindow = null;
3083
3301
  this.transcriptEntries = [];
3302
+ this.metadataCues = [];
3084
3303
  this.currentActiveEntry = null;
3085
3304
  this.isVisible = false;
3305
+ this.storage = new StorageManager("vidply");
3086
3306
  this.isDragging = false;
3087
3307
  this.dragOffsetX = 0;
3088
3308
  this.dragOffsetY = 0;
3309
+ this.isResizing = false;
3310
+ this.resizeDirection = null;
3311
+ this.resizeStartX = 0;
3312
+ this.resizeStartY = 0;
3313
+ this.resizeStartWidth = 0;
3314
+ this.resizeStartHeight = 0;
3315
+ this.resizeEnabled = false;
3316
+ this.settingsMenuVisible = false;
3317
+ this.settingsMenu = null;
3318
+ this.settingsButton = null;
3319
+ this.settingsMenuJustOpened = false;
3320
+ this.keyboardDragMode = false;
3321
+ this.styleDialog = null;
3322
+ this.styleDialogVisible = false;
3323
+ this.styleDialogJustOpened = false;
3324
+ const savedPreferences = this.storage.getTranscriptPreferences();
3325
+ this.transcriptStyle = {
3326
+ fontSize: (savedPreferences == null ? void 0 : savedPreferences.fontSize) || this.player.options.transcriptFontSize || "100%",
3327
+ fontFamily: (savedPreferences == null ? void 0 : savedPreferences.fontFamily) || this.player.options.transcriptFontFamily || "sans-serif",
3328
+ color: (savedPreferences == null ? void 0 : savedPreferences.color) || this.player.options.transcriptColor || "#ffffff",
3329
+ backgroundColor: (savedPreferences == null ? void 0 : savedPreferences.backgroundColor) || this.player.options.transcriptBackgroundColor || "#1e1e1e",
3330
+ opacity: (savedPreferences == null ? void 0 : savedPreferences.opacity) ?? this.player.options.transcriptOpacity ?? 0.98
3331
+ };
3089
3332
  this.handlers = {
3090
3333
  timeupdate: () => this.updateActiveEntry(),
3091
3334
  resize: null,
@@ -3095,7 +3338,11 @@ var TranscriptManager = class {
3095
3338
  touchend: null,
3096
3339
  mousedown: null,
3097
3340
  touchstart: null,
3098
- keydown: null
3341
+ keydown: null,
3342
+ settingsClick: null,
3343
+ settingsKeydown: null,
3344
+ documentClick: null,
3345
+ styleDialogKeydown: null
3099
3346
  };
3100
3347
  this.init();
3101
3348
  }
@@ -3124,6 +3371,11 @@ var TranscriptManager = class {
3124
3371
  if (this.transcriptWindow) {
3125
3372
  this.transcriptWindow.style.display = "flex";
3126
3373
  this.isVisible = true;
3374
+ setTimeout(() => {
3375
+ if (this.settingsButton) {
3376
+ this.settingsButton.focus();
3377
+ }
3378
+ }, 150);
3127
3379
  return;
3128
3380
  }
3129
3381
  this.createTranscriptWindow();
@@ -3131,6 +3383,11 @@ var TranscriptManager = class {
3131
3383
  if (this.transcriptWindow) {
3132
3384
  this.transcriptWindow.style.display = "flex";
3133
3385
  setTimeout(() => this.positionTranscript(), 0);
3386
+ setTimeout(() => {
3387
+ if (this.settingsButton) {
3388
+ this.settingsButton.focus();
3389
+ }
3390
+ }, 150);
3134
3391
  }
3135
3392
  this.isVisible = true;
3136
3393
  }
@@ -3162,9 +3419,49 @@ var TranscriptManager = class {
3162
3419
  "tabindex": "0"
3163
3420
  }
3164
3421
  });
3422
+ this.headerLeft = DOMUtils.createElement("div", {
3423
+ className: `${this.player.options.classPrefix}-transcript-header-left`
3424
+ });
3425
+ this.settingsButton = DOMUtils.createElement("button", {
3426
+ className: `${this.player.options.classPrefix}-transcript-settings`,
3427
+ attributes: {
3428
+ "type": "button",
3429
+ "aria-label": i18n.t("transcript.settings"),
3430
+ "aria-expanded": "false"
3431
+ }
3432
+ });
3433
+ this.settingsButton.appendChild(createIconElement("settings"));
3434
+ this.handlers.settingsClick = (e) => {
3435
+ e.preventDefault();
3436
+ e.stopPropagation();
3437
+ if (this.settingsMenuVisible) {
3438
+ this.hideSettingsMenu();
3439
+ } else {
3440
+ this.showSettingsMenu();
3441
+ }
3442
+ };
3443
+ this.settingsButton.addEventListener("click", this.handlers.settingsClick);
3444
+ this.handlers.settingsKeydown = (e) => {
3445
+ if (e.key === "d" || e.key === "D") {
3446
+ e.preventDefault();
3447
+ e.stopPropagation();
3448
+ this.toggleKeyboardDragMode();
3449
+ } else if (e.key === "r" || e.key === "R") {
3450
+ e.preventDefault();
3451
+ e.stopPropagation();
3452
+ this.toggleResizeMode();
3453
+ } else if (e.key === "Escape" && this.settingsMenuVisible) {
3454
+ e.preventDefault();
3455
+ e.stopPropagation();
3456
+ this.hideSettingsMenu();
3457
+ }
3458
+ };
3459
+ this.settingsButton.addEventListener("keydown", this.handlers.settingsKeydown);
3165
3460
  const title = DOMUtils.createElement("h3", {
3166
3461
  textContent: i18n.t("transcript.title")
3167
3462
  });
3463
+ this.headerLeft.appendChild(this.settingsButton);
3464
+ this.headerLeft.appendChild(title);
3168
3465
  const closeButton = DOMUtils.createElement("button", {
3169
3466
  className: `${this.player.options.classPrefix}-transcript-close`,
3170
3467
  attributes: {
@@ -3174,7 +3471,7 @@ var TranscriptManager = class {
3174
3471
  });
3175
3472
  closeButton.appendChild(createIconElement("close"));
3176
3473
  closeButton.addEventListener("click", () => this.hideTranscript());
3177
- this.transcriptHeader.appendChild(title);
3474
+ this.transcriptHeader.appendChild(this.headerLeft);
3178
3475
  this.transcriptHeader.appendChild(closeButton);
3179
3476
  this.transcriptContent = DOMUtils.createElement("div", {
3180
3477
  className: `${this.player.options.classPrefix}-transcript-content`
@@ -3184,6 +3481,27 @@ var TranscriptManager = class {
3184
3481
  this.player.container.appendChild(this.transcriptWindow);
3185
3482
  this.positionTranscript();
3186
3483
  this.setupDragAndDrop();
3484
+ this.handlers.documentClick = (e) => {
3485
+ if (this.settingsMenuJustOpened) {
3486
+ return;
3487
+ }
3488
+ if (this.styleDialogJustOpened) {
3489
+ return;
3490
+ }
3491
+ if (this.settingsButton && this.settingsButton.contains(e.target)) {
3492
+ return;
3493
+ }
3494
+ if (this.settingsMenu && this.settingsMenu.contains(e.target)) {
3495
+ return;
3496
+ }
3497
+ if (this.settingsMenuVisible) {
3498
+ this.hideSettingsMenu();
3499
+ }
3500
+ if (this.styleDialogVisible && this.styleDialog && !this.styleDialog.contains(e.target)) {
3501
+ this.hideStyleDialog();
3502
+ }
3503
+ };
3504
+ this.documentClickHandlerAdded = false;
3187
3505
  let resizeTimeout;
3188
3506
  this.handlers.resize = () => {
3189
3507
  clearTimeout(resizeTimeout);
@@ -3264,54 +3582,133 @@ var TranscriptManager = class {
3264
3582
  this.transcriptEntries = [];
3265
3583
  this.transcriptContent.innerHTML = "";
3266
3584
  const textTracks = Array.from(this.player.element.textTracks);
3267
- const transcriptTrack = textTracks.find(
3585
+ const captionTrack = textTracks.find(
3268
3586
  (track) => track.kind === "captions" || track.kind === "subtitles"
3269
3587
  );
3270
- if (!transcriptTrack) {
3588
+ const descriptionTrack = textTracks.find((track) => track.kind === "descriptions");
3589
+ const metadataTrack = textTracks.find((track) => track.kind === "metadata");
3590
+ if (!captionTrack && !descriptionTrack && !metadataTrack) {
3271
3591
  this.showNoTranscriptMessage();
3272
3592
  return;
3273
3593
  }
3274
- if (transcriptTrack.mode === "disabled") {
3275
- transcriptTrack.mode = "hidden";
3276
- }
3277
- if (!transcriptTrack.cues || transcriptTrack.cues.length === 0) {
3594
+ const tracksToLoad = [captionTrack, descriptionTrack, metadataTrack].filter(Boolean);
3595
+ tracksToLoad.forEach((track) => {
3596
+ if (track.mode === "disabled") {
3597
+ track.mode = "hidden";
3598
+ }
3599
+ });
3600
+ const needsLoading = tracksToLoad.some((track) => !track.cues || track.cues.length === 0);
3601
+ if (needsLoading) {
3278
3602
  const loadingMessage = DOMUtils.createElement("div", {
3279
3603
  className: `${this.player.options.classPrefix}-transcript-loading`,
3280
3604
  textContent: i18n.t("transcript.loading")
3281
3605
  });
3282
3606
  this.transcriptContent.appendChild(loadingMessage);
3607
+ let loaded = 0;
3283
3608
  const onLoad = () => {
3284
- this.loadTranscriptData();
3285
- };
3286
- transcriptTrack.addEventListener("load", onLoad, { once: true });
3287
- setTimeout(() => {
3288
- if (transcriptTrack.cues && transcriptTrack.cues.length > 0) {
3609
+ loaded++;
3610
+ if (loaded >= tracksToLoad.length) {
3289
3611
  this.loadTranscriptData();
3290
3612
  }
3613
+ };
3614
+ tracksToLoad.forEach((track) => {
3615
+ track.addEventListener("load", onLoad, { once: true });
3616
+ });
3617
+ setTimeout(() => {
3618
+ this.loadTranscriptData();
3291
3619
  }, 500);
3292
3620
  return;
3293
3621
  }
3294
- const cues = Array.from(transcriptTrack.cues);
3295
- cues.forEach((cue, index) => {
3296
- const entry = this.createTranscriptEntry(cue, index);
3622
+ const allCues = [];
3623
+ if (captionTrack && captionTrack.cues) {
3624
+ Array.from(captionTrack.cues).forEach((cue) => {
3625
+ allCues.push({ cue, type: "caption" });
3626
+ });
3627
+ }
3628
+ if (descriptionTrack && descriptionTrack.cues) {
3629
+ Array.from(descriptionTrack.cues).forEach((cue) => {
3630
+ allCues.push({ cue, type: "description" });
3631
+ });
3632
+ }
3633
+ if (metadataTrack && metadataTrack.cues) {
3634
+ this.metadataCues = Array.from(metadataTrack.cues);
3635
+ this.setupMetadataHandling();
3636
+ }
3637
+ allCues.sort((a, b) => a.cue.startTime - b.cue.startTime);
3638
+ allCues.forEach((item, index) => {
3639
+ const entry = this.createTranscriptEntry(item.cue, index, item.type);
3297
3640
  this.transcriptEntries.push({
3298
3641
  element: entry,
3299
- cue,
3300
- startTime: cue.startTime,
3301
- endTime: cue.endTime
3642
+ cue: item.cue,
3643
+ type: item.type,
3644
+ startTime: item.cue.startTime,
3645
+ endTime: item.cue.endTime
3302
3646
  });
3303
3647
  this.transcriptContent.appendChild(entry);
3304
3648
  });
3649
+ this.applyTranscriptStyles();
3650
+ }
3651
+ /**
3652
+ * Setup metadata handling
3653
+ * Metadata cues are not displayed but can be used programmatically
3654
+ */
3655
+ setupMetadataHandling() {
3656
+ if (!this.metadataCues || this.metadataCues.length === 0) {
3657
+ return;
3658
+ }
3659
+ const textTracks = Array.from(this.player.element.textTracks);
3660
+ const metadataTrack = textTracks.find((track) => track.kind === "metadata");
3661
+ if (metadataTrack) {
3662
+ metadataTrack.addEventListener("cuechange", () => {
3663
+ const activeCues = Array.from(metadataTrack.activeCues || []);
3664
+ activeCues.forEach((cue) => {
3665
+ this.handleMetadataCue(cue);
3666
+ });
3667
+ });
3668
+ }
3669
+ }
3670
+ /**
3671
+ * Handle individual metadata cues
3672
+ * Parses metadata text and emits events or triggers actions
3673
+ */
3674
+ handleMetadataCue(cue) {
3675
+ const text = cue.text.trim();
3676
+ this.player.emit("metadata", {
3677
+ time: cue.startTime,
3678
+ endTime: cue.endTime,
3679
+ text,
3680
+ cue
3681
+ });
3682
+ if (text.includes("PAUSE")) {
3683
+ this.player.emit("metadata:pause", { time: cue.startTime, text });
3684
+ }
3685
+ const focusMatch = text.match(/FOCUS:([\w#-]+)/);
3686
+ if (focusMatch) {
3687
+ this.player.emit("metadata:focus", {
3688
+ time: cue.startTime,
3689
+ target: focusMatch[1],
3690
+ text
3691
+ });
3692
+ }
3693
+ const hashtags = text.match(/#[\w-]+/g);
3694
+ if (hashtags) {
3695
+ this.player.emit("metadata:hashtags", {
3696
+ time: cue.startTime,
3697
+ hashtags,
3698
+ text
3699
+ });
3700
+ }
3305
3701
  }
3306
3702
  /**
3307
3703
  * Create a single transcript entry element
3308
3704
  */
3309
- createTranscriptEntry(cue, index) {
3705
+ createTranscriptEntry(cue, index, type = "caption") {
3310
3706
  const entry = DOMUtils.createElement("div", {
3311
- className: `${this.player.options.classPrefix}-transcript-entry`,
3707
+ className: `${this.player.options.classPrefix}-transcript-entry ${this.player.options.classPrefix}-transcript-${type}`,
3312
3708
  attributes: {
3313
3709
  "data-start": String(cue.startTime),
3314
3710
  "data-end": String(cue.endTime),
3711
+ "data-type": type,
3315
3712
  "role": "button",
3316
3713
  "tabindex": "0"
3317
3714
  }
@@ -3408,6 +3805,15 @@ var TranscriptManager = class {
3408
3805
  if (e.target.closest(`.${this.player.options.classPrefix}-transcript-close`)) {
3409
3806
  return;
3410
3807
  }
3808
+ if (e.target.closest(`.${this.player.options.classPrefix}-transcript-settings`)) {
3809
+ return;
3810
+ }
3811
+ if (e.target.closest(`.${this.player.options.classPrefix}-transcript-settings-menu`)) {
3812
+ return;
3813
+ }
3814
+ if (e.target.closest(`.${this.player.options.classPrefix}-transcript-style-dialog`)) {
3815
+ return;
3816
+ }
3411
3817
  this.startDragging(e.clientX, e.clientY);
3412
3818
  e.preventDefault();
3413
3819
  };
@@ -3425,6 +3831,15 @@ var TranscriptManager = class {
3425
3831
  if (e.target.closest(`.${this.player.options.classPrefix}-transcript-close`)) {
3426
3832
  return;
3427
3833
  }
3834
+ if (e.target.closest(`.${this.player.options.classPrefix}-transcript-settings`)) {
3835
+ return;
3836
+ }
3837
+ if (e.target.closest(`.${this.player.options.classPrefix}-transcript-settings-menu`)) {
3838
+ return;
3839
+ }
3840
+ if (e.target.closest(`.${this.player.options.classPrefix}-transcript-style-dialog`)) {
3841
+ return;
3842
+ }
3428
3843
  const isMobile = window.innerWidth < 640;
3429
3844
  const isFullscreen = this.player.state.fullscreen;
3430
3845
  const touch = e.touches[0];
@@ -3451,49 +3866,64 @@ var TranscriptManager = class {
3451
3866
  }
3452
3867
  };
3453
3868
  this.handlers.keydown = (e) => {
3454
- if (!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "Escape"].includes(e.key)) {
3869
+ if (["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(e.key)) {
3870
+ if (!this.keyboardDragMode) {
3871
+ return;
3872
+ }
3873
+ e.preventDefault();
3874
+ e.stopPropagation();
3875
+ const step = e.shiftKey ? 50 : 10;
3876
+ let currentLeft = parseFloat(this.transcriptWindow.style.left) || 0;
3877
+ let currentTop = parseFloat(this.transcriptWindow.style.top) || 0;
3878
+ const computedStyle = window.getComputedStyle(this.transcriptWindow);
3879
+ if (computedStyle.transform !== "none") {
3880
+ const rect = this.transcriptWindow.getBoundingClientRect();
3881
+ currentLeft = rect.left;
3882
+ currentTop = rect.top;
3883
+ this.transcriptWindow.style.transform = "none";
3884
+ this.transcriptWindow.style.left = `${currentLeft}px`;
3885
+ this.transcriptWindow.style.top = `${currentTop}px`;
3886
+ }
3887
+ let newX = currentLeft;
3888
+ let newY = currentTop;
3889
+ switch (e.key) {
3890
+ case "ArrowLeft":
3891
+ newX -= step;
3892
+ break;
3893
+ case "ArrowRight":
3894
+ newX += step;
3895
+ break;
3896
+ case "ArrowUp":
3897
+ newY -= step;
3898
+ break;
3899
+ case "ArrowDown":
3900
+ newY += step;
3901
+ break;
3902
+ }
3903
+ this.transcriptWindow.style.left = `${newX}px`;
3904
+ this.transcriptWindow.style.top = `${newY}px`;
3455
3905
  return;
3456
3906
  }
3457
- e.preventDefault();
3458
- e.stopPropagation();
3459
3907
  if (e.key === "Home") {
3908
+ e.preventDefault();
3909
+ e.stopPropagation();
3460
3910
  this.resetPosition();
3461
3911
  return;
3462
3912
  }
3463
3913
  if (e.key === "Escape") {
3464
- this.hideTranscript();
3914
+ e.preventDefault();
3915
+ e.stopPropagation();
3916
+ if (this.styleDialogVisible) {
3917
+ this.hideStyleDialog();
3918
+ } else if (this.keyboardDragMode) {
3919
+ this.disableKeyboardDragMode();
3920
+ } else if (this.settingsMenuVisible) {
3921
+ this.hideSettingsMenu();
3922
+ } else {
3923
+ this.hideTranscript();
3924
+ }
3465
3925
  return;
3466
3926
  }
3467
- const step = e.shiftKey ? 50 : 10;
3468
- let currentLeft = parseFloat(this.transcriptWindow.style.left) || 0;
3469
- let currentTop = parseFloat(this.transcriptWindow.style.top) || 0;
3470
- const computedStyle = window.getComputedStyle(this.transcriptWindow);
3471
- if (computedStyle.transform !== "none") {
3472
- const rect = this.transcriptWindow.getBoundingClientRect();
3473
- currentLeft = rect.left;
3474
- currentTop = rect.top;
3475
- this.transcriptWindow.style.transform = "none";
3476
- this.transcriptWindow.style.left = `${currentLeft}px`;
3477
- this.transcriptWindow.style.top = `${currentTop}px`;
3478
- }
3479
- let newX = currentLeft;
3480
- let newY = currentTop;
3481
- switch (e.key) {
3482
- case "ArrowLeft":
3483
- newX -= step;
3484
- break;
3485
- case "ArrowRight":
3486
- newX += step;
3487
- break;
3488
- case "ArrowUp":
3489
- newY -= step;
3490
- break;
3491
- case "ArrowDown":
3492
- newY += step;
3493
- break;
3494
- }
3495
- this.transcriptWindow.style.left = `${newX}px`;
3496
- this.transcriptWindow.style.top = `${newY}px`;
3497
3927
  };
3498
3928
  this.transcriptHeader.addEventListener("mousedown", this.handlers.mousedown);
3499
3929
  document.addEventListener("mousemove", this.handlers.mousemove);
@@ -3573,99 +4003,707 @@ var TranscriptManager = class {
3573
4003
  this.transcriptWindow.style.transform = "translate(-50%, -50%)";
3574
4004
  }
3575
4005
  /**
3576
- * Cleanup
4006
+ * Toggle keyboard drag mode
3577
4007
  */
3578
- destroy() {
3579
- if (this.handlers.timeupdate) {
3580
- this.player.off("timeupdate", this.handlers.timeupdate);
3581
- }
3582
- if (this.transcriptHeader) {
3583
- if (this.handlers.mousedown) {
3584
- this.transcriptHeader.removeEventListener("mousedown", this.handlers.mousedown);
3585
- }
3586
- if (this.handlers.touchstart) {
3587
- this.transcriptHeader.removeEventListener("touchstart", this.handlers.touchstart);
3588
- }
3589
- if (this.handlers.keydown) {
3590
- this.transcriptHeader.removeEventListener("keydown", this.handlers.keydown);
3591
- }
3592
- }
3593
- if (this.handlers.mousemove) {
3594
- document.removeEventListener("mousemove", this.handlers.mousemove);
4008
+ toggleKeyboardDragMode() {
4009
+ if (this.keyboardDragMode) {
4010
+ this.disableKeyboardDragMode();
4011
+ } else {
4012
+ this.enableKeyboardDragMode();
3595
4013
  }
3596
- if (this.handlers.mouseup) {
3597
- document.removeEventListener("mouseup", this.handlers.mouseup);
4014
+ }
4015
+ /**
4016
+ * Enable keyboard drag mode
4017
+ */
4018
+ enableKeyboardDragMode() {
4019
+ this.keyboardDragMode = true;
4020
+ this.transcriptWindow.classList.add(`${this.player.options.classPrefix}-transcript-keyboard-drag`);
4021
+ if (this.settingsButton) {
4022
+ this.settingsButton.setAttribute("aria-label", "Keyboard drag mode active. Use arrow keys to move window. Press D or Escape to exit.");
3598
4023
  }
3599
- if (this.handlers.touchmove) {
3600
- document.removeEventListener("touchmove", this.handlers.touchmove);
4024
+ const indicator = DOMUtils.createElement("div", {
4025
+ className: `${this.player.options.classPrefix}-transcript-drag-indicator`,
4026
+ textContent: i18n.t("transcript.keyboardDragActive")
4027
+ });
4028
+ this.transcriptHeader.appendChild(indicator);
4029
+ if (this.settingsMenuVisible) {
4030
+ this.hideSettingsMenu();
3601
4031
  }
3602
- if (this.handlers.touchend) {
3603
- document.removeEventListener("touchend", this.handlers.touchend);
4032
+ this.transcriptHeader.focus();
4033
+ }
4034
+ /**
4035
+ * Disable keyboard drag mode
4036
+ */
4037
+ disableKeyboardDragMode() {
4038
+ this.keyboardDragMode = false;
4039
+ this.transcriptWindow.classList.remove(`${this.player.options.classPrefix}-transcript-keyboard-drag`);
4040
+ if (this.settingsButton) {
4041
+ this.settingsButton.setAttribute("aria-label", "Transcript settings. Press Enter to open menu, or D to enable drag mode");
3604
4042
  }
3605
- if (this.handlers.resize) {
3606
- window.removeEventListener("resize", this.handlers.resize);
4043
+ const indicator = this.transcriptHeader.querySelector(`.${this.player.options.classPrefix}-transcript-drag-indicator`);
4044
+ if (indicator) {
4045
+ indicator.remove();
3607
4046
  }
3608
- this.handlers = null;
3609
- if (this.transcriptWindow && this.transcriptWindow.parentNode) {
3610
- this.transcriptWindow.parentNode.removeChild(this.transcriptWindow);
4047
+ if (this.settingsButton) {
4048
+ this.settingsButton.focus();
3611
4049
  }
3612
- this.transcriptWindow = null;
3613
- this.transcriptHeader = null;
3614
- this.transcriptContent = null;
3615
- this.transcriptEntries = [];
3616
- }
3617
- };
3618
-
3619
- // src/core/Player.js
3620
- init_HTML5Renderer();
3621
-
3622
- // src/renderers/YouTubeRenderer.js
3623
- var YouTubeRenderer = class {
3624
- constructor(player) {
3625
- this.player = player;
3626
- this.youtube = null;
3627
- this.videoId = null;
3628
- this.isReady = false;
3629
- this.iframe = null;
3630
4050
  }
3631
- async init() {
3632
- this.videoId = this.extractVideoId(this.player.element.src);
3633
- if (!this.videoId) {
3634
- throw new Error("Invalid YouTube URL");
4051
+ /**
4052
+ * Toggle settings menu visibility
4053
+ */
4054
+ toggleSettingsMenu() {
4055
+ if (this.settingsMenuVisible) {
4056
+ this.hideSettingsMenu();
4057
+ } else {
4058
+ this.showSettingsMenu();
3635
4059
  }
3636
- await this.loadYouTubeAPI();
3637
- this.createIframe();
3638
- await this.initializePlayer();
3639
4060
  }
3640
- extractVideoId(url) {
3641
- const patterns = [
3642
- /(?:youtube\.com\/watch\?v=|youtu\.be\/)([^&\s]+)/,
3643
- /youtube\.com\/embed\/([^&\s]+)/
3644
- ];
3645
- for (const pattern of patterns) {
3646
- const match = url.match(pattern);
3647
- if (match && match[1]) {
3648
- return match[1];
3649
- }
4061
+ /**
4062
+ * Show settings menu
4063
+ */
4064
+ showSettingsMenu() {
4065
+ this.settingsMenuJustOpened = true;
4066
+ setTimeout(() => {
4067
+ this.settingsMenuJustOpened = false;
4068
+ }, 350);
4069
+ if (!this.documentClickHandlerAdded) {
4070
+ setTimeout(() => {
4071
+ document.addEventListener("click", this.handlers.documentClick);
4072
+ this.documentClickHandlerAdded = true;
4073
+ }, 300);
3650
4074
  }
3651
- return null;
3652
- }
3653
- async loadYouTubeAPI() {
3654
- if (window.YT && window.YT.Player) {
3655
- return Promise.resolve();
4075
+ if (this.settingsMenu) {
4076
+ this.settingsMenu.style.display = "block";
4077
+ this.settingsMenuVisible = true;
4078
+ return;
3656
4079
  }
3657
- return new Promise((resolve, reject) => {
3658
- if (window.onYouTubeIframeAPIReady) {
3659
- const originalCallback = window.onYouTubeIframeAPIReady;
3660
- window.onYouTubeIframeAPIReady = () => {
3661
- originalCallback();
3662
- resolve();
3663
- };
3664
- return;
4080
+ this.settingsMenu = DOMUtils.createElement("div", {
4081
+ className: `${this.player.options.classPrefix}-transcript-settings-menu`
4082
+ });
4083
+ const keyboardDragOption = DOMUtils.createElement("button", {
4084
+ className: `${this.player.options.classPrefix}-transcript-settings-item`,
4085
+ attributes: {
4086
+ "type": "button",
4087
+ "aria-label": i18n.t("transcript.keyboardDragMode")
3665
4088
  }
3666
- const tag = document.createElement("script");
3667
- tag.src = "https://www.youtube.com/iframe_api";
3668
- window.onYouTubeIframeAPIReady = () => {
4089
+ });
4090
+ const keyboardIcon = createIconElement("move");
4091
+ const keyboardText = DOMUtils.createElement("span", {
4092
+ textContent: i18n.t("transcript.keyboardDragMode")
4093
+ });
4094
+ keyboardDragOption.appendChild(keyboardIcon);
4095
+ keyboardDragOption.appendChild(keyboardText);
4096
+ keyboardDragOption.addEventListener("click", () => {
4097
+ this.toggleKeyboardDragMode();
4098
+ this.hideSettingsMenu();
4099
+ });
4100
+ const styleOption = DOMUtils.createElement("button", {
4101
+ className: `${this.player.options.classPrefix}-transcript-settings-item`,
4102
+ attributes: {
4103
+ "type": "button",
4104
+ "aria-label": i18n.t("transcript.styleTranscript")
4105
+ }
4106
+ });
4107
+ const styleIcon = createIconElement("settings");
4108
+ const styleText = DOMUtils.createElement("span", {
4109
+ textContent: i18n.t("transcript.styleTranscript")
4110
+ });
4111
+ styleOption.appendChild(styleIcon);
4112
+ styleOption.appendChild(styleText);
4113
+ styleOption.addEventListener("click", (e) => {
4114
+ e.preventDefault();
4115
+ e.stopPropagation();
4116
+ this.hideSettingsMenu();
4117
+ setTimeout(() => {
4118
+ this.showStyleDialog();
4119
+ }, 50);
4120
+ });
4121
+ const resizeOption = DOMUtils.createElement("button", {
4122
+ className: `${this.player.options.classPrefix}-transcript-settings-item`,
4123
+ attributes: {
4124
+ "type": "button",
4125
+ "aria-label": i18n.t("transcript.resizeWindow")
4126
+ }
4127
+ });
4128
+ const resizeIcon = createIconElement("resize");
4129
+ const resizeText = DOMUtils.createElement("span", {
4130
+ textContent: i18n.t("transcript.resizeWindow")
4131
+ });
4132
+ resizeOption.appendChild(resizeIcon);
4133
+ resizeOption.appendChild(resizeText);
4134
+ resizeOption.addEventListener("click", () => {
4135
+ this.toggleResizeMode();
4136
+ this.hideSettingsMenu();
4137
+ });
4138
+ const closeOption = DOMUtils.createElement("button", {
4139
+ className: `${this.player.options.classPrefix}-transcript-settings-item`,
4140
+ attributes: {
4141
+ "type": "button",
4142
+ "aria-label": i18n.t("transcript.closeMenu")
4143
+ }
4144
+ });
4145
+ const closeIcon = createIconElement("close");
4146
+ const closeText = DOMUtils.createElement("span", {
4147
+ textContent: i18n.t("transcript.closeMenu")
4148
+ });
4149
+ closeOption.appendChild(closeIcon);
4150
+ closeOption.appendChild(closeText);
4151
+ closeOption.addEventListener("click", () => {
4152
+ this.hideSettingsMenu();
4153
+ });
4154
+ this.settingsMenu.appendChild(keyboardDragOption);
4155
+ this.settingsMenu.appendChild(resizeOption);
4156
+ this.settingsMenu.appendChild(styleOption);
4157
+ this.settingsMenu.appendChild(closeOption);
4158
+ if (this.headerLeft) {
4159
+ this.headerLeft.appendChild(this.settingsMenu);
4160
+ } else {
4161
+ this.transcriptHeader.appendChild(this.settingsMenu);
4162
+ }
4163
+ this.settingsMenuVisible = true;
4164
+ this.settingsMenu.style.display = "block";
4165
+ if (this.settingsButton) {
4166
+ this.settingsButton.setAttribute("aria-expanded", "true");
4167
+ }
4168
+ setTimeout(() => {
4169
+ const firstItem = this.settingsMenu.querySelector(`.${this.player.options.classPrefix}-transcript-settings-item`);
4170
+ if (firstItem) {
4171
+ firstItem.focus();
4172
+ }
4173
+ }, 0);
4174
+ }
4175
+ /**
4176
+ * Hide settings menu
4177
+ */
4178
+ hideSettingsMenu() {
4179
+ if (this.settingsMenu) {
4180
+ this.settingsMenu.style.display = "none";
4181
+ this.settingsMenuVisible = false;
4182
+ this.settingsMenuJustOpened = false;
4183
+ if (this.settingsButton) {
4184
+ this.settingsButton.setAttribute("aria-expanded", "false");
4185
+ this.settingsButton.focus();
4186
+ }
4187
+ }
4188
+ }
4189
+ /**
4190
+ * Enable move mode (gives visual feedback)
4191
+ */
4192
+ enableMoveMode() {
4193
+ this.transcriptWindow.classList.add(`${this.player.options.classPrefix}-transcript-move-mode`);
4194
+ const tooltip = DOMUtils.createElement("div", {
4195
+ className: `${this.player.options.classPrefix}-transcript-move-tooltip`,
4196
+ textContent: "Drag with mouse or press D for keyboard drag mode"
4197
+ });
4198
+ this.transcriptHeader.appendChild(tooltip);
4199
+ setTimeout(() => {
4200
+ this.transcriptWindow.classList.remove(`${this.player.options.classPrefix}-transcript-move-mode`);
4201
+ if (tooltip.parentNode) {
4202
+ tooltip.remove();
4203
+ }
4204
+ }, 2e3);
4205
+ }
4206
+ /**
4207
+ * Toggle resize mode
4208
+ */
4209
+ toggleResizeMode() {
4210
+ this.resizeEnabled = !this.resizeEnabled;
4211
+ if (this.resizeEnabled) {
4212
+ this.enableResizeHandles();
4213
+ } else {
4214
+ this.disableResizeHandles();
4215
+ }
4216
+ }
4217
+ /**
4218
+ * Enable resize handles
4219
+ */
4220
+ enableResizeHandles() {
4221
+ if (!this.transcriptWindow) return;
4222
+ const directions = ["n", "s", "e", "w", "ne", "nw", "se", "sw"];
4223
+ directions.forEach((direction) => {
4224
+ const handle = DOMUtils.createElement("div", {
4225
+ className: `${this.player.options.classPrefix}-transcript-resize-handle ${this.player.options.classPrefix}-transcript-resize-${direction}`,
4226
+ attributes: {
4227
+ "data-direction": direction
4228
+ }
4229
+ });
4230
+ handle.addEventListener("mousedown", (e) => this.startResize(e, direction));
4231
+ handle.addEventListener("touchstart", (e) => this.startResize(e.touches[0], direction));
4232
+ this.transcriptWindow.appendChild(handle);
4233
+ });
4234
+ this.transcriptWindow.classList.add(`${this.player.options.classPrefix}-transcript-resizable`);
4235
+ this.handlers.resizeMove = (e) => {
4236
+ if (this.isResizing) {
4237
+ this.performResize(e.clientX, e.clientY);
4238
+ }
4239
+ };
4240
+ this.handlers.resizeEnd = () => {
4241
+ if (this.isResizing) {
4242
+ this.stopResize();
4243
+ }
4244
+ };
4245
+ this.handlers.resizeTouchMove = (e) => {
4246
+ if (this.isResizing) {
4247
+ this.performResize(e.touches[0].clientX, e.touches[0].clientY);
4248
+ e.preventDefault();
4249
+ }
4250
+ };
4251
+ document.addEventListener("mousemove", this.handlers.resizeMove);
4252
+ document.addEventListener("mouseup", this.handlers.resizeEnd);
4253
+ document.addEventListener("touchmove", this.handlers.resizeTouchMove);
4254
+ document.addEventListener("touchend", this.handlers.resizeEnd);
4255
+ }
4256
+ /**
4257
+ * Disable resize handles
4258
+ */
4259
+ disableResizeHandles() {
4260
+ if (!this.transcriptWindow) return;
4261
+ const handles = this.transcriptWindow.querySelectorAll(`.${this.player.options.classPrefix}-transcript-resize-handle`);
4262
+ handles.forEach((handle) => handle.remove());
4263
+ this.transcriptWindow.classList.remove(`${this.player.options.classPrefix}-transcript-resizable`);
4264
+ if (this.handlers.resizeMove) {
4265
+ document.removeEventListener("mousemove", this.handlers.resizeMove);
4266
+ }
4267
+ if (this.handlers.resizeEnd) {
4268
+ document.removeEventListener("mouseup", this.handlers.resizeEnd);
4269
+ }
4270
+ if (this.handlers.resizeTouchMove) {
4271
+ document.removeEventListener("touchmove", this.handlers.resizeTouchMove);
4272
+ }
4273
+ document.removeEventListener("touchend", this.handlers.resizeEnd);
4274
+ }
4275
+ /**
4276
+ * Start resizing
4277
+ */
4278
+ startResize(e, direction) {
4279
+ e.stopPropagation();
4280
+ e.preventDefault();
4281
+ this.isResizing = true;
4282
+ this.resizeDirection = direction;
4283
+ this.resizeStartX = e.clientX;
4284
+ this.resizeStartY = e.clientY;
4285
+ const rect = this.transcriptWindow.getBoundingClientRect();
4286
+ this.resizeStartWidth = rect.width;
4287
+ this.resizeStartHeight = rect.height;
4288
+ this.transcriptWindow.classList.add(`${this.player.options.classPrefix}-transcript-resizing`);
4289
+ document.body.style.cursor = this.getResizeCursor(direction);
4290
+ document.body.style.userSelect = "none";
4291
+ }
4292
+ /**
4293
+ * Perform resize
4294
+ */
4295
+ performResize(clientX, clientY) {
4296
+ if (!this.isResizing) return;
4297
+ const deltaX = clientX - this.resizeStartX;
4298
+ const deltaY = clientY - this.resizeStartY;
4299
+ let newWidth = this.resizeStartWidth;
4300
+ let newHeight = this.resizeStartHeight;
4301
+ const direction = this.resizeDirection;
4302
+ if (direction.includes("e")) {
4303
+ newWidth = this.resizeStartWidth + deltaX;
4304
+ }
4305
+ if (direction.includes("w")) {
4306
+ newWidth = this.resizeStartWidth - deltaX;
4307
+ }
4308
+ if (direction.includes("s")) {
4309
+ newHeight = this.resizeStartHeight + deltaY;
4310
+ }
4311
+ if (direction.includes("n")) {
4312
+ newHeight = this.resizeStartHeight - deltaY;
4313
+ }
4314
+ const minWidth = 300;
4315
+ const minHeight = 200;
4316
+ const maxWidth = window.innerWidth - 40;
4317
+ const maxHeight = window.innerHeight - 40;
4318
+ newWidth = Math.max(minWidth, Math.min(newWidth, maxWidth));
4319
+ newHeight = Math.max(minHeight, Math.min(newHeight, maxHeight));
4320
+ this.transcriptWindow.style.width = `${newWidth}px`;
4321
+ this.transcriptWindow.style.height = `${newHeight}px`;
4322
+ this.transcriptWindow.style.maxWidth = `${newWidth}px`;
4323
+ this.transcriptWindow.style.maxHeight = `${newHeight}px`;
4324
+ if (direction.includes("w")) {
4325
+ const currentLeft = parseFloat(this.transcriptWindow.style.left) || 0;
4326
+ this.transcriptWindow.style.left = `${currentLeft + (this.resizeStartWidth - newWidth)}px`;
4327
+ }
4328
+ if (direction.includes("n")) {
4329
+ const currentTop = parseFloat(this.transcriptWindow.style.top) || 0;
4330
+ this.transcriptWindow.style.top = `${currentTop + (this.resizeStartHeight - newHeight)}px`;
4331
+ }
4332
+ }
4333
+ /**
4334
+ * Stop resizing
4335
+ */
4336
+ stopResize() {
4337
+ this.isResizing = false;
4338
+ this.resizeDirection = null;
4339
+ this.transcriptWindow.classList.remove(`${this.player.options.classPrefix}-transcript-resizing`);
4340
+ document.body.style.cursor = "";
4341
+ document.body.style.userSelect = "";
4342
+ }
4343
+ /**
4344
+ * Get cursor style for resize direction
4345
+ */
4346
+ getResizeCursor(direction) {
4347
+ const cursors = {
4348
+ "n": "ns-resize",
4349
+ "s": "ns-resize",
4350
+ "e": "ew-resize",
4351
+ "w": "ew-resize",
4352
+ "ne": "nesw-resize",
4353
+ "nw": "nwse-resize",
4354
+ "se": "nwse-resize",
4355
+ "sw": "nesw-resize"
4356
+ };
4357
+ return cursors[direction] || "default";
4358
+ }
4359
+ /**
4360
+ * Show style dialog
4361
+ */
4362
+ showStyleDialog() {
4363
+ if (this.styleDialog) {
4364
+ this.styleDialog.style.display = "block";
4365
+ this.styleDialogVisible = true;
4366
+ this.styleDialogJustOpened = true;
4367
+ setTimeout(() => {
4368
+ this.styleDialogJustOpened = false;
4369
+ }, 350);
4370
+ setTimeout(() => {
4371
+ const firstSelect = this.styleDialog.querySelector("select, input");
4372
+ if (firstSelect) {
4373
+ firstSelect.focus();
4374
+ }
4375
+ }, 0);
4376
+ return;
4377
+ }
4378
+ this.styleDialog = DOMUtils.createElement("div", {
4379
+ className: `${this.player.options.classPrefix}-transcript-style-dialog`
4380
+ });
4381
+ const title = DOMUtils.createElement("h4", {
4382
+ textContent: i18n.t("transcript.styleTitle"),
4383
+ className: `${this.player.options.classPrefix}-transcript-style-title`
4384
+ });
4385
+ this.styleDialog.appendChild(title);
4386
+ const fontSizeControl = this.createStyleSelectControl(
4387
+ i18n.t("captions.fontSize"),
4388
+ "fontSize",
4389
+ [
4390
+ { label: i18n.t("fontSizes.small"), value: "87.5%" },
4391
+ { label: i18n.t("fontSizes.normal"), value: "100%" },
4392
+ { label: i18n.t("fontSizes.large"), value: "125%" },
4393
+ { label: i18n.t("fontSizes.xlarge"), value: "150%" }
4394
+ ]
4395
+ );
4396
+ this.styleDialog.appendChild(fontSizeControl);
4397
+ const fontFamilyControl = this.createStyleSelectControl(
4398
+ i18n.t("captions.fontFamily"),
4399
+ "fontFamily",
4400
+ [
4401
+ { label: i18n.t("fontFamilies.sansSerif"), value: "sans-serif" },
4402
+ { label: i18n.t("fontFamilies.serif"), value: "serif" },
4403
+ { label: i18n.t("fontFamilies.monospace"), value: "monospace" }
4404
+ ]
4405
+ );
4406
+ this.styleDialog.appendChild(fontFamilyControl);
4407
+ const colorControl = this.createStyleColorControl(i18n.t("captions.color"), "color");
4408
+ this.styleDialog.appendChild(colorControl);
4409
+ const bgColorControl = this.createStyleColorControl(i18n.t("captions.backgroundColor"), "backgroundColor");
4410
+ this.styleDialog.appendChild(bgColorControl);
4411
+ const opacityControl = this.createStyleOpacityControl(i18n.t("captions.opacity"), "opacity");
4412
+ this.styleDialog.appendChild(opacityControl);
4413
+ const closeBtn = DOMUtils.createElement("button", {
4414
+ className: `${this.player.options.classPrefix}-transcript-style-close`,
4415
+ textContent: i18n.t("settings.close"),
4416
+ attributes: {
4417
+ "type": "button"
4418
+ }
4419
+ });
4420
+ closeBtn.addEventListener("click", () => this.hideStyleDialog());
4421
+ this.styleDialog.appendChild(closeBtn);
4422
+ this.handlers.styleDialogKeydown = (e) => {
4423
+ if (e.key === "Escape") {
4424
+ e.preventDefault();
4425
+ e.stopPropagation();
4426
+ this.hideStyleDialog();
4427
+ }
4428
+ };
4429
+ this.styleDialog.addEventListener("keydown", this.handlers.styleDialogKeydown);
4430
+ if (this.headerLeft) {
4431
+ this.headerLeft.appendChild(this.styleDialog);
4432
+ } else {
4433
+ this.transcriptHeader.appendChild(this.styleDialog);
4434
+ }
4435
+ this.applyTranscriptStyles();
4436
+ this.styleDialogVisible = true;
4437
+ this.styleDialog.style.display = "block";
4438
+ this.styleDialogJustOpened = true;
4439
+ setTimeout(() => {
4440
+ this.styleDialogJustOpened = false;
4441
+ }, 350);
4442
+ setTimeout(() => {
4443
+ const firstSelect = this.styleDialog.querySelector("select, input");
4444
+ if (firstSelect) {
4445
+ firstSelect.focus();
4446
+ }
4447
+ }, 0);
4448
+ }
4449
+ /**
4450
+ * Hide style dialog
4451
+ */
4452
+ hideStyleDialog() {
4453
+ if (this.styleDialog) {
4454
+ this.styleDialog.style.display = "none";
4455
+ this.styleDialogVisible = false;
4456
+ if (this.settingsButton) {
4457
+ this.settingsButton.focus();
4458
+ }
4459
+ }
4460
+ }
4461
+ /**
4462
+ * Create style select control
4463
+ */
4464
+ createStyleSelectControl(label, property, options) {
4465
+ const group = DOMUtils.createElement("div", {
4466
+ className: `${this.player.options.classPrefix}-transcript-style-group`
4467
+ });
4468
+ const labelEl = DOMUtils.createElement("label", {
4469
+ textContent: label
4470
+ });
4471
+ group.appendChild(labelEl);
4472
+ const select = DOMUtils.createElement("select", {
4473
+ className: `${this.player.options.classPrefix}-transcript-style-select`
4474
+ });
4475
+ options.forEach((opt) => {
4476
+ const option = DOMUtils.createElement("option", {
4477
+ textContent: opt.label,
4478
+ attributes: {
4479
+ "value": opt.value
4480
+ }
4481
+ });
4482
+ if (this.transcriptStyle[property] === opt.value) {
4483
+ option.selected = true;
4484
+ }
4485
+ select.appendChild(option);
4486
+ });
4487
+ select.addEventListener("change", (e) => {
4488
+ this.transcriptStyle[property] = e.target.value;
4489
+ this.applyTranscriptStyles();
4490
+ this.savePreferences();
4491
+ });
4492
+ group.appendChild(select);
4493
+ return group;
4494
+ }
4495
+ /**
4496
+ * Create style color control
4497
+ */
4498
+ createStyleColorControl(label, property) {
4499
+ const group = DOMUtils.createElement("div", {
4500
+ className: `${this.player.options.classPrefix}-transcript-style-group`
4501
+ });
4502
+ const labelEl = DOMUtils.createElement("label", {
4503
+ textContent: label
4504
+ });
4505
+ group.appendChild(labelEl);
4506
+ const input = DOMUtils.createElement("input", {
4507
+ attributes: {
4508
+ "type": "color",
4509
+ "value": this.transcriptStyle[property]
4510
+ },
4511
+ className: `${this.player.options.classPrefix}-transcript-style-color`
4512
+ });
4513
+ input.addEventListener("input", (e) => {
4514
+ this.transcriptStyle[property] = e.target.value;
4515
+ this.applyTranscriptStyles();
4516
+ this.savePreferences();
4517
+ });
4518
+ group.appendChild(input);
4519
+ return group;
4520
+ }
4521
+ /**
4522
+ * Create style opacity control
4523
+ */
4524
+ createStyleOpacityControl(label, property) {
4525
+ const group = DOMUtils.createElement("div", {
4526
+ className: `${this.player.options.classPrefix}-transcript-style-group`
4527
+ });
4528
+ const labelEl = DOMUtils.createElement("label", {
4529
+ textContent: label
4530
+ });
4531
+ group.appendChild(labelEl);
4532
+ const valueDisplay = DOMUtils.createElement("span", {
4533
+ textContent: Math.round(this.transcriptStyle[property] * 100) + "%",
4534
+ className: `${this.player.options.classPrefix}-transcript-style-value`
4535
+ });
4536
+ const input = DOMUtils.createElement("input", {
4537
+ attributes: {
4538
+ "type": "range",
4539
+ "min": "0",
4540
+ "max": "1",
4541
+ "step": "0.1",
4542
+ "value": String(this.transcriptStyle[property])
4543
+ },
4544
+ className: `${this.player.options.classPrefix}-transcript-style-range`
4545
+ });
4546
+ input.addEventListener("input", (e) => {
4547
+ const value = parseFloat(e.target.value);
4548
+ this.transcriptStyle[property] = value;
4549
+ valueDisplay.textContent = Math.round(value * 100) + "%";
4550
+ this.applyTranscriptStyles();
4551
+ this.savePreferences();
4552
+ });
4553
+ const inputContainer = DOMUtils.createElement("div", {
4554
+ className: `${this.player.options.classPrefix}-transcript-style-range-container`
4555
+ });
4556
+ inputContainer.appendChild(input);
4557
+ inputContainer.appendChild(valueDisplay);
4558
+ group.appendChild(labelEl);
4559
+ group.appendChild(inputContainer);
4560
+ return group;
4561
+ }
4562
+ /**
4563
+ * Save transcript preferences to localStorage
4564
+ */
4565
+ savePreferences() {
4566
+ this.storage.saveTranscriptPreferences(this.transcriptStyle);
4567
+ }
4568
+ /**
4569
+ * Apply transcript styles
4570
+ */
4571
+ applyTranscriptStyles() {
4572
+ if (!this.transcriptWindow) return;
4573
+ this.transcriptWindow.style.backgroundColor = this.transcriptStyle.backgroundColor;
4574
+ this.transcriptWindow.style.opacity = String(this.transcriptStyle.opacity);
4575
+ if (this.transcriptContent) {
4576
+ this.transcriptContent.style.fontSize = this.transcriptStyle.fontSize;
4577
+ this.transcriptContent.style.fontFamily = this.transcriptStyle.fontFamily;
4578
+ this.transcriptContent.style.color = this.transcriptStyle.color;
4579
+ }
4580
+ const textEntries = this.transcriptWindow.querySelectorAll(`.${this.player.options.classPrefix}-transcript-text`);
4581
+ textEntries.forEach((entry) => {
4582
+ entry.style.fontSize = this.transcriptStyle.fontSize;
4583
+ entry.style.fontFamily = this.transcriptStyle.fontFamily;
4584
+ entry.style.color = this.transcriptStyle.color;
4585
+ });
4586
+ const timeEntries = this.transcriptWindow.querySelectorAll(`.${this.player.options.classPrefix}-transcript-time`);
4587
+ timeEntries.forEach((entry) => {
4588
+ entry.style.fontFamily = this.transcriptStyle.fontFamily;
4589
+ });
4590
+ }
4591
+ /**
4592
+ * Cleanup
4593
+ */
4594
+ destroy() {
4595
+ if (this.resizeEnabled) {
4596
+ this.disableResizeHandles();
4597
+ }
4598
+ if (this.keyboardDragMode) {
4599
+ this.disableKeyboardDragMode();
4600
+ }
4601
+ if (this.handlers.timeupdate) {
4602
+ this.player.off("timeupdate", this.handlers.timeupdate);
4603
+ }
4604
+ if (this.transcriptHeader) {
4605
+ if (this.handlers.mousedown) {
4606
+ this.transcriptHeader.removeEventListener("mousedown", this.handlers.mousedown);
4607
+ }
4608
+ if (this.handlers.touchstart) {
4609
+ this.transcriptHeader.removeEventListener("touchstart", this.handlers.touchstart);
4610
+ }
4611
+ if (this.handlers.keydown) {
4612
+ this.transcriptHeader.removeEventListener("keydown", this.handlers.keydown);
4613
+ }
4614
+ }
4615
+ if (this.settingsButton) {
4616
+ if (this.handlers.settingsClick) {
4617
+ this.settingsButton.removeEventListener("click", this.handlers.settingsClick);
4618
+ }
4619
+ if (this.handlers.settingsKeydown) {
4620
+ this.settingsButton.removeEventListener("keydown", this.handlers.settingsKeydown);
4621
+ }
4622
+ }
4623
+ if (this.styleDialog && this.handlers.styleDialogKeydown) {
4624
+ this.styleDialog.removeEventListener("keydown", this.handlers.styleDialogKeydown);
4625
+ }
4626
+ if (this.handlers.mousemove) {
4627
+ document.removeEventListener("mousemove", this.handlers.mousemove);
4628
+ }
4629
+ if (this.handlers.mouseup) {
4630
+ document.removeEventListener("mouseup", this.handlers.mouseup);
4631
+ }
4632
+ if (this.handlers.touchmove) {
4633
+ document.removeEventListener("touchmove", this.handlers.touchmove);
4634
+ }
4635
+ if (this.handlers.touchend) {
4636
+ document.removeEventListener("touchend", this.handlers.touchend);
4637
+ }
4638
+ if (this.handlers.documentClick) {
4639
+ document.removeEventListener("click", this.handlers.documentClick);
4640
+ }
4641
+ if (this.handlers.resize) {
4642
+ window.removeEventListener("resize", this.handlers.resize);
4643
+ }
4644
+ this.handlers = null;
4645
+ if (this.transcriptWindow && this.transcriptWindow.parentNode) {
4646
+ this.transcriptWindow.parentNode.removeChild(this.transcriptWindow);
4647
+ }
4648
+ this.transcriptWindow = null;
4649
+ this.transcriptHeader = null;
4650
+ this.transcriptContent = null;
4651
+ this.transcriptEntries = [];
4652
+ this.settingsMenu = null;
4653
+ this.styleDialog = null;
4654
+ }
4655
+ };
4656
+
4657
+ // src/core/Player.js
4658
+ init_HTML5Renderer();
4659
+
4660
+ // src/renderers/YouTubeRenderer.js
4661
+ var YouTubeRenderer = class {
4662
+ constructor(player) {
4663
+ this.player = player;
4664
+ this.youtube = null;
4665
+ this.videoId = null;
4666
+ this.isReady = false;
4667
+ this.iframe = null;
4668
+ }
4669
+ async init() {
4670
+ this.videoId = this.extractVideoId(this.player.element.src);
4671
+ if (!this.videoId) {
4672
+ throw new Error("Invalid YouTube URL");
4673
+ }
4674
+ await this.loadYouTubeAPI();
4675
+ this.createIframe();
4676
+ await this.initializePlayer();
4677
+ }
4678
+ extractVideoId(url) {
4679
+ const patterns = [
4680
+ /(?:youtube\.com\/watch\?v=|youtu\.be\/)([^&\s]+)/,
4681
+ /youtube\.com\/embed\/([^&\s]+)/
4682
+ ];
4683
+ for (const pattern of patterns) {
4684
+ const match = url.match(pattern);
4685
+ if (match && match[1]) {
4686
+ return match[1];
4687
+ }
4688
+ }
4689
+ return null;
4690
+ }
4691
+ async loadYouTubeAPI() {
4692
+ if (window.YT && window.YT.Player) {
4693
+ return Promise.resolve();
4694
+ }
4695
+ return new Promise((resolve, reject) => {
4696
+ if (window.onYouTubeIframeAPIReady) {
4697
+ const originalCallback = window.onYouTubeIframeAPIReady;
4698
+ window.onYouTubeIframeAPIReady = () => {
4699
+ originalCallback();
4700
+ resolve();
4701
+ };
4702
+ return;
4703
+ }
4704
+ const tag = document.createElement("script");
4705
+ tag.src = "https://www.youtube.com/iframe_api";
4706
+ window.onYouTubeIframeAPIReady = () => {
3669
4707
  resolve();
3670
4708
  };
3671
4709
  tag.onerror = () => reject(new Error("Failed to load YouTube API"));
@@ -4330,7 +5368,7 @@ var Player = class extends EventEmitter {
4330
5368
  captionsButton: true,
4331
5369
  transcriptButton: true,
4332
5370
  fullscreenButton: true,
4333
- pipButton: true,
5371
+ pipButton: false,
4334
5372
  // Seeking
4335
5373
  seekInterval: 10,
4336
5374
  seekIntervalLarge: 30,
@@ -4400,6 +5438,13 @@ var Player = class extends EventEmitter {
4400
5438
  onError: null,
4401
5439
  ...options
4402
5440
  };
5441
+ this.storage = new StorageManager("vidply");
5442
+ const savedPrefs = this.storage.getPlayerPreferences();
5443
+ if (savedPrefs) {
5444
+ if (savedPrefs.volume !== void 0) this.options.volume = savedPrefs.volume;
5445
+ if (savedPrefs.playbackSpeed !== void 0) this.options.playbackSpeed = savedPrefs.playbackSpeed;
5446
+ if (savedPrefs.muted !== void 0) this.options.muted = savedPrefs.muted;
5447
+ }
4403
5448
  this.state = {
4404
5449
  ready: false,
4405
5450
  playing: false,
@@ -4424,6 +5469,9 @@ var Player = class extends EventEmitter {
4424
5469
  this.audioDescriptionSrc = this.options.audioDescriptionSrc;
4425
5470
  this.signLanguageSrc = this.options.signLanguageSrc;
4426
5471
  this.signLanguageVideo = null;
5472
+ this.audioDescriptionSourceElement = null;
5473
+ this.originalAudioDescriptionSource = null;
5474
+ this.audioDescriptionCaptionTracks = [];
4427
5475
  this.container = null;
4428
5476
  this.renderer = null;
4429
5477
  this.controlBar = null;
@@ -4578,6 +5626,53 @@ var Player = class extends EventEmitter {
4578
5626
  if (!src) {
4579
5627
  throw new Error("No media source found");
4580
5628
  }
5629
+ const sourceElements = this.element.querySelectorAll("source");
5630
+ for (const sourceEl of sourceElements) {
5631
+ const descSrc = sourceEl.getAttribute("data-desc-src");
5632
+ const origSrc = sourceEl.getAttribute("data-orig-src");
5633
+ if (descSrc || origSrc) {
5634
+ if (!this.audioDescriptionSourceElement) {
5635
+ this.audioDescriptionSourceElement = sourceEl;
5636
+ }
5637
+ if (origSrc) {
5638
+ if (!this.originalAudioDescriptionSource) {
5639
+ this.originalAudioDescriptionSource = origSrc;
5640
+ }
5641
+ if (!this.originalSrc) {
5642
+ this.originalSrc = origSrc;
5643
+ }
5644
+ } else {
5645
+ const currentSrcAttr = sourceEl.getAttribute("src");
5646
+ if (!this.originalAudioDescriptionSource && currentSrcAttr) {
5647
+ this.originalAudioDescriptionSource = currentSrcAttr;
5648
+ }
5649
+ if (!this.originalSrc && currentSrcAttr) {
5650
+ this.originalSrc = currentSrcAttr;
5651
+ }
5652
+ }
5653
+ if (descSrc && !this.audioDescriptionSrc) {
5654
+ this.audioDescriptionSrc = descSrc;
5655
+ }
5656
+ }
5657
+ }
5658
+ const trackElements = this.element.querySelectorAll("track");
5659
+ trackElements.forEach((trackEl) => {
5660
+ const trackKind = trackEl.getAttribute("kind");
5661
+ const trackDescSrc = trackEl.getAttribute("data-desc-src");
5662
+ if (trackKind === "captions" || trackKind === "subtitles" || trackKind === "chapters") {
5663
+ if (trackDescSrc) {
5664
+ this.audioDescriptionCaptionTracks.push({
5665
+ trackElement: trackEl,
5666
+ originalSrc: trackEl.getAttribute("src"),
5667
+ describedSrc: trackDescSrc,
5668
+ originalTrackSrc: trackEl.getAttribute("data-orig-src") || trackEl.getAttribute("src"),
5669
+ explicit: true
5670
+ // Explicitly defined, so we should validate it
5671
+ });
5672
+ this.log(`Found explicit described ${trackKind} track: ${trackEl.getAttribute("src")} -> ${trackDescSrc}`);
5673
+ }
5674
+ }
5675
+ });
4581
5676
  if (!this.originalSrc) {
4582
5677
  this.originalSrc = src;
4583
5678
  }
@@ -4734,6 +5829,7 @@ var Player = class extends EventEmitter {
4734
5829
  if (newVolume > 0 && this.state.muted) {
4735
5830
  this.state.muted = false;
4736
5831
  }
5832
+ this.savePlayerPreferences();
4737
5833
  }
4738
5834
  getVolume() {
4739
5835
  return this.state.volume;
@@ -4743,6 +5839,7 @@ var Player = class extends EventEmitter {
4743
5839
  this.renderer.setMuted(true);
4744
5840
  }
4745
5841
  this.state.muted = true;
5842
+ this.savePlayerPreferences();
4746
5843
  this.emit("volumechange");
4747
5844
  }
4748
5845
  unmute() {
@@ -4750,6 +5847,7 @@ var Player = class extends EventEmitter {
4750
5847
  this.renderer.setMuted(false);
4751
5848
  }
4752
5849
  this.state.muted = false;
5850
+ this.savePlayerPreferences();
4753
5851
  this.emit("volumechange");
4754
5852
  }
4755
5853
  toggleMute() {
@@ -4766,11 +5864,20 @@ var Player = class extends EventEmitter {
4766
5864
  this.renderer.setPlaybackSpeed(newSpeed);
4767
5865
  }
4768
5866
  this.state.playbackSpeed = newSpeed;
5867
+ this.savePlayerPreferences();
4769
5868
  this.emit("playbackspeedchange", newSpeed);
4770
5869
  }
4771
5870
  getPlaybackSpeed() {
4772
5871
  return this.state.playbackSpeed;
4773
5872
  }
5873
+ // Save player preferences to localStorage
5874
+ savePlayerPreferences() {
5875
+ this.storage.savePlayerPreferences({
5876
+ volume: this.state.volume,
5877
+ muted: this.state.muted,
5878
+ playbackSpeed: this.state.playbackSpeed
5879
+ });
5880
+ }
4774
5881
  // Fullscreen
4775
5882
  enterFullscreen() {
4776
5883
  const elem = this.container;
@@ -4850,15 +5957,396 @@ var Player = class extends EventEmitter {
4850
5957
  this.enableCaptions();
4851
5958
  }
4852
5959
  }
5960
+ /**
5961
+ * Check if a track file exists
5962
+ * @param {string} url - Track file URL
5963
+ * @returns {Promise<boolean>} - True if file exists
5964
+ */
5965
+ async validateTrackExists(url) {
5966
+ try {
5967
+ const response = await fetch(url, { method: "HEAD", cache: "no-cache" });
5968
+ return response.ok;
5969
+ } catch (error) {
5970
+ return false;
5971
+ }
5972
+ }
4853
5973
  // Audio Description
4854
5974
  async enableAudioDescription() {
4855
- if (!this.audioDescriptionSrc) {
4856
- console.warn("VidPly: No audio description source provided");
5975
+ const hasSourceElementsWithDesc = Array.from(this.element.querySelectorAll("source")).some((el) => el.getAttribute("data-desc-src"));
5976
+ const hasTracksWithDesc = this.audioDescriptionCaptionTracks.length > 0;
5977
+ if (!this.audioDescriptionSrc && !hasSourceElementsWithDesc && !hasTracksWithDesc) {
5978
+ console.warn("VidPly: No audio description source, source elements, or tracks provided");
4857
5979
  return;
4858
5980
  }
4859
5981
  const currentTime = this.state.currentTime;
4860
5982
  const wasPlaying = this.state.playing;
4861
- this.element.src = this.audioDescriptionSrc;
5983
+ let swappedTracksForTranscript = [];
5984
+ if (this.audioDescriptionSourceElement) {
5985
+ const currentSrc = this.element.currentSrc || this.element.src;
5986
+ const sourceElements = Array.from(this.element.querySelectorAll("source"));
5987
+ let sourceElementToUpdate = null;
5988
+ let descSrc = this.audioDescriptionSrc;
5989
+ for (const sourceEl of sourceElements) {
5990
+ const sourceSrc = sourceEl.getAttribute("src");
5991
+ const descSrcAttr = sourceEl.getAttribute("data-desc-src");
5992
+ const sourceFilename = sourceSrc ? sourceSrc.split("/").pop() : "";
5993
+ const currentFilename = currentSrc ? currentSrc.split("/").pop() : "";
5994
+ if (currentSrc && (currentSrc === sourceSrc || currentSrc.includes(sourceSrc) || currentSrc.includes(sourceFilename) || sourceFilename && currentFilename === sourceFilename)) {
5995
+ sourceElementToUpdate = sourceEl;
5996
+ if (descSrcAttr) {
5997
+ descSrc = descSrcAttr;
5998
+ } else if (sourceSrc) {
5999
+ descSrc = this.audioDescriptionSrc || descSrc;
6000
+ }
6001
+ break;
6002
+ }
6003
+ }
6004
+ if (!sourceElementToUpdate) {
6005
+ sourceElementToUpdate = this.audioDescriptionSourceElement;
6006
+ const storedDescSrc = sourceElementToUpdate.getAttribute("data-desc-src");
6007
+ if (storedDescSrc) {
6008
+ descSrc = storedDescSrc;
6009
+ }
6010
+ }
6011
+ if (this.audioDescriptionCaptionTracks.length > 0) {
6012
+ const validationPromises = this.audioDescriptionCaptionTracks.map(async (trackInfo) => {
6013
+ if (trackInfo.trackElement && trackInfo.describedSrc) {
6014
+ if (trackInfo.explicit === true) {
6015
+ try {
6016
+ const exists = await this.validateTrackExists(trackInfo.describedSrc);
6017
+ return { trackInfo, exists };
6018
+ } catch (error) {
6019
+ return { trackInfo, exists: false };
6020
+ }
6021
+ } else {
6022
+ return { trackInfo, exists: false };
6023
+ }
6024
+ }
6025
+ return { trackInfo, exists: false };
6026
+ });
6027
+ const validationResults = await Promise.all(validationPromises);
6028
+ const tracksToSwap = validationResults.filter((result) => result.exists);
6029
+ if (tracksToSwap.length > 0) {
6030
+ const trackModes = /* @__PURE__ */ new Map();
6031
+ tracksToSwap.forEach(({ trackInfo }) => {
6032
+ const textTrack = trackInfo.trackElement.track;
6033
+ if (textTrack) {
6034
+ trackModes.set(trackInfo, {
6035
+ wasShowing: textTrack.mode === "showing",
6036
+ wasHidden: textTrack.mode === "hidden"
6037
+ });
6038
+ } else {
6039
+ trackModes.set(trackInfo, {
6040
+ wasShowing: false,
6041
+ wasHidden: false
6042
+ });
6043
+ }
6044
+ });
6045
+ const tracksToReadd = tracksToSwap.map(({ trackInfo }) => {
6046
+ const oldSrc = trackInfo.trackElement.getAttribute("src");
6047
+ const parent = trackInfo.trackElement.parentNode;
6048
+ const nextSibling = trackInfo.trackElement.nextSibling;
6049
+ const attributes = {};
6050
+ Array.from(trackInfo.trackElement.attributes).forEach((attr) => {
6051
+ attributes[attr.name] = attr.value;
6052
+ });
6053
+ return {
6054
+ trackInfo,
6055
+ oldSrc,
6056
+ parent,
6057
+ nextSibling,
6058
+ attributes
6059
+ };
6060
+ });
6061
+ tracksToReadd.forEach(({ trackInfo }) => {
6062
+ trackInfo.trackElement.remove();
6063
+ });
6064
+ this.element.load();
6065
+ setTimeout(() => {
6066
+ tracksToReadd.forEach(({ trackInfo, oldSrc, parent, nextSibling, attributes }) => {
6067
+ swappedTracksForTranscript.push(trackInfo);
6068
+ const newTrackElement = document.createElement("track");
6069
+ newTrackElement.setAttribute("src", trackInfo.describedSrc);
6070
+ Object.keys(attributes).forEach((attrName) => {
6071
+ if (attrName !== "src" && attrName !== "data-desc-src") {
6072
+ newTrackElement.setAttribute(attrName, attributes[attrName]);
6073
+ }
6074
+ });
6075
+ if (nextSibling && nextSibling.parentNode) {
6076
+ parent.insertBefore(newTrackElement, nextSibling);
6077
+ } else {
6078
+ parent.appendChild(newTrackElement);
6079
+ }
6080
+ trackInfo.trackElement = newTrackElement;
6081
+ });
6082
+ this.element.load();
6083
+ const setupNewTracks = () => {
6084
+ setTimeout(() => {
6085
+ swappedTracksForTranscript.forEach((trackInfo) => {
6086
+ const trackElement = trackInfo.trackElement;
6087
+ const newTextTrack = trackElement.track;
6088
+ if (newTextTrack) {
6089
+ const modeInfo = trackModes.get(trackInfo) || { wasShowing: false, wasHidden: false };
6090
+ newTextTrack.mode = "hidden";
6091
+ const restoreMode = () => {
6092
+ if (modeInfo.wasShowing) {
6093
+ newTextTrack.mode = "hidden";
6094
+ } else if (modeInfo.wasHidden) {
6095
+ newTextTrack.mode = "hidden";
6096
+ } else {
6097
+ newTextTrack.mode = "disabled";
6098
+ }
6099
+ };
6100
+ if (newTextTrack.readyState >= 2) {
6101
+ restoreMode();
6102
+ } else {
6103
+ newTextTrack.addEventListener("load", restoreMode, { once: true });
6104
+ newTextTrack.addEventListener("error", restoreMode, { once: true });
6105
+ }
6106
+ }
6107
+ });
6108
+ }, 300);
6109
+ };
6110
+ if (this.element.readyState >= 1) {
6111
+ setTimeout(setupNewTracks, 200);
6112
+ } else {
6113
+ this.element.addEventListener("loadedmetadata", setupNewTracks, { once: true });
6114
+ setTimeout(setupNewTracks, 2e3);
6115
+ }
6116
+ }, 100);
6117
+ const skippedCount = validationResults.length - tracksToSwap.length;
6118
+ }
6119
+ }
6120
+ const allSourceElements = Array.from(this.element.querySelectorAll("source"));
6121
+ const sourcesToUpdate = [];
6122
+ allSourceElements.forEach((sourceEl) => {
6123
+ const descSrcAttr = sourceEl.getAttribute("data-desc-src");
6124
+ const currentSrc2 = sourceEl.getAttribute("src");
6125
+ if (descSrcAttr) {
6126
+ const type = sourceEl.getAttribute("type");
6127
+ let origSrc = sourceEl.getAttribute("data-orig-src");
6128
+ if (!origSrc) {
6129
+ origSrc = currentSrc2;
6130
+ }
6131
+ sourcesToUpdate.push({
6132
+ src: descSrcAttr,
6133
+ // Use described version
6134
+ type,
6135
+ origSrc,
6136
+ descSrc: descSrcAttr
6137
+ });
6138
+ } else {
6139
+ const type = sourceEl.getAttribute("type");
6140
+ const src = sourceEl.getAttribute("src");
6141
+ sourcesToUpdate.push({
6142
+ src,
6143
+ type,
6144
+ origSrc: null,
6145
+ descSrc: null
6146
+ });
6147
+ }
6148
+ });
6149
+ allSourceElements.forEach((sourceEl) => {
6150
+ sourceEl.remove();
6151
+ });
6152
+ sourcesToUpdate.forEach((sourceInfo) => {
6153
+ const newSource = document.createElement("source");
6154
+ newSource.setAttribute("src", sourceInfo.src);
6155
+ if (sourceInfo.type) {
6156
+ newSource.setAttribute("type", sourceInfo.type);
6157
+ }
6158
+ if (sourceInfo.origSrc) {
6159
+ newSource.setAttribute("data-orig-src", sourceInfo.origSrc);
6160
+ }
6161
+ if (sourceInfo.descSrc) {
6162
+ newSource.setAttribute("data-desc-src", sourceInfo.descSrc);
6163
+ }
6164
+ this.element.appendChild(newSource);
6165
+ });
6166
+ this.element.load();
6167
+ await new Promise((resolve) => {
6168
+ const onLoadedMetadata = () => {
6169
+ this.element.removeEventListener("loadedmetadata", onLoadedMetadata);
6170
+ resolve();
6171
+ };
6172
+ this.element.addEventListener("loadedmetadata", onLoadedMetadata);
6173
+ });
6174
+ await new Promise((resolve) => setTimeout(resolve, 300));
6175
+ if (this.element.tagName === "VIDEO" && currentTime === 0 && !wasPlaying) {
6176
+ if (this.element.readyState >= 1) {
6177
+ this.element.currentTime = 1e-3;
6178
+ setTimeout(() => {
6179
+ this.element.currentTime = 0;
6180
+ }, 10);
6181
+ }
6182
+ }
6183
+ this.seek(currentTime);
6184
+ if (wasPlaying) {
6185
+ this.play();
6186
+ }
6187
+ this.state.audioDescriptionEnabled = true;
6188
+ this.emit("audiodescriptionenabled");
6189
+ } else {
6190
+ if (this.audioDescriptionCaptionTracks.length > 0) {
6191
+ const validationPromises = this.audioDescriptionCaptionTracks.map(async (trackInfo) => {
6192
+ if (trackInfo.trackElement && trackInfo.describedSrc) {
6193
+ if (trackInfo.explicit === true) {
6194
+ try {
6195
+ const exists = await this.validateTrackExists(trackInfo.describedSrc);
6196
+ return { trackInfo, exists };
6197
+ } catch (error) {
6198
+ return { trackInfo, exists: false };
6199
+ }
6200
+ } else {
6201
+ return { trackInfo, exists: false };
6202
+ }
6203
+ }
6204
+ return { trackInfo, exists: false };
6205
+ });
6206
+ const validationResults = await Promise.all(validationPromises);
6207
+ const tracksToSwap = validationResults.filter((result) => result.exists);
6208
+ if (tracksToSwap.length > 0) {
6209
+ const trackModes = /* @__PURE__ */ new Map();
6210
+ tracksToSwap.forEach(({ trackInfo }) => {
6211
+ const textTrack = trackInfo.trackElement.track;
6212
+ if (textTrack) {
6213
+ trackModes.set(trackInfo, {
6214
+ wasShowing: textTrack.mode === "showing",
6215
+ wasHidden: textTrack.mode === "hidden"
6216
+ });
6217
+ } else {
6218
+ trackModes.set(trackInfo, {
6219
+ wasShowing: false,
6220
+ wasHidden: false
6221
+ });
6222
+ }
6223
+ });
6224
+ const tracksToReadd = tracksToSwap.map(({ trackInfo }) => {
6225
+ const oldSrc = trackInfo.trackElement.getAttribute("src");
6226
+ const parent = trackInfo.trackElement.parentNode;
6227
+ const nextSibling = trackInfo.trackElement.nextSibling;
6228
+ const attributes = {};
6229
+ Array.from(trackInfo.trackElement.attributes).forEach((attr) => {
6230
+ attributes[attr.name] = attr.value;
6231
+ });
6232
+ return {
6233
+ trackInfo,
6234
+ oldSrc,
6235
+ parent,
6236
+ nextSibling,
6237
+ attributes
6238
+ };
6239
+ });
6240
+ tracksToReadd.forEach(({ trackInfo }) => {
6241
+ trackInfo.trackElement.remove();
6242
+ });
6243
+ this.element.load();
6244
+ setTimeout(() => {
6245
+ tracksToReadd.forEach(({ trackInfo, oldSrc, parent, nextSibling, attributes }) => {
6246
+ swappedTracksForTranscript.push(trackInfo);
6247
+ const newTrackElement = document.createElement("track");
6248
+ newTrackElement.setAttribute("src", trackInfo.describedSrc);
6249
+ Object.keys(attributes).forEach((attrName) => {
6250
+ if (attrName !== "src" && attrName !== "data-desc-src") {
6251
+ newTrackElement.setAttribute(attrName, attributes[attrName]);
6252
+ }
6253
+ });
6254
+ if (nextSibling && nextSibling.parentNode) {
6255
+ parent.insertBefore(newTrackElement, nextSibling);
6256
+ } else {
6257
+ parent.appendChild(newTrackElement);
6258
+ }
6259
+ trackInfo.trackElement = newTrackElement;
6260
+ });
6261
+ this.element.load();
6262
+ const setupNewTracks = () => {
6263
+ setTimeout(() => {
6264
+ swappedTracksForTranscript.forEach((trackInfo) => {
6265
+ const trackElement = trackInfo.trackElement;
6266
+ const newTextTrack = trackElement.track;
6267
+ if (newTextTrack) {
6268
+ const modeInfo = trackModes.get(trackInfo) || { wasShowing: false, wasHidden: false };
6269
+ newTextTrack.mode = "hidden";
6270
+ const restoreMode = () => {
6271
+ if (modeInfo.wasShowing) {
6272
+ newTextTrack.mode = "hidden";
6273
+ } else if (modeInfo.wasHidden) {
6274
+ newTextTrack.mode = "hidden";
6275
+ } else {
6276
+ newTextTrack.mode = "disabled";
6277
+ }
6278
+ };
6279
+ if (newTextTrack.readyState >= 2) {
6280
+ restoreMode();
6281
+ } else {
6282
+ newTextTrack.addEventListener("load", restoreMode, { once: true });
6283
+ newTextTrack.addEventListener("error", restoreMode, { once: true });
6284
+ }
6285
+ }
6286
+ });
6287
+ }, 300);
6288
+ };
6289
+ if (this.element.readyState >= 1) {
6290
+ setTimeout(setupNewTracks, 200);
6291
+ } else {
6292
+ this.element.addEventListener("loadedmetadata", setupNewTracks, { once: true });
6293
+ setTimeout(setupNewTracks, 2e3);
6294
+ }
6295
+ }, 100);
6296
+ }
6297
+ }
6298
+ const fallbackSourceElements = Array.from(this.element.querySelectorAll("source"));
6299
+ const hasSourceElementsWithDesc2 = fallbackSourceElements.some((el) => el.getAttribute("data-desc-src"));
6300
+ if (hasSourceElementsWithDesc2) {
6301
+ const fallbackSourcesToUpdate = [];
6302
+ fallbackSourceElements.forEach((sourceEl) => {
6303
+ const descSrcAttr = sourceEl.getAttribute("data-desc-src");
6304
+ const currentSrc = sourceEl.getAttribute("src");
6305
+ if (descSrcAttr) {
6306
+ const type = sourceEl.getAttribute("type");
6307
+ let origSrc = sourceEl.getAttribute("data-orig-src");
6308
+ if (!origSrc) {
6309
+ origSrc = currentSrc;
6310
+ }
6311
+ fallbackSourcesToUpdate.push({
6312
+ src: descSrcAttr,
6313
+ type,
6314
+ origSrc,
6315
+ descSrc: descSrcAttr
6316
+ });
6317
+ } else {
6318
+ const type = sourceEl.getAttribute("type");
6319
+ const src = sourceEl.getAttribute("src");
6320
+ fallbackSourcesToUpdate.push({
6321
+ src,
6322
+ type,
6323
+ origSrc: null,
6324
+ descSrc: null
6325
+ });
6326
+ }
6327
+ });
6328
+ fallbackSourceElements.forEach((sourceEl) => {
6329
+ sourceEl.remove();
6330
+ });
6331
+ fallbackSourcesToUpdate.forEach((sourceInfo) => {
6332
+ const newSource = document.createElement("source");
6333
+ newSource.setAttribute("src", sourceInfo.src);
6334
+ if (sourceInfo.type) {
6335
+ newSource.setAttribute("type", sourceInfo.type);
6336
+ }
6337
+ if (sourceInfo.origSrc) {
6338
+ newSource.setAttribute("data-orig-src", sourceInfo.origSrc);
6339
+ }
6340
+ if (sourceInfo.descSrc) {
6341
+ newSource.setAttribute("data-desc-src", sourceInfo.descSrc);
6342
+ }
6343
+ this.element.appendChild(newSource);
6344
+ });
6345
+ this.element.load();
6346
+ } else {
6347
+ this.element.src = this.audioDescriptionSrc;
6348
+ }
6349
+ }
4862
6350
  await new Promise((resolve) => {
4863
6351
  const onLoadedMetadata = () => {
4864
6352
  this.element.removeEventListener("loadedmetadata", onLoadedMetadata);
@@ -4866,10 +6354,183 @@ var Player = class extends EventEmitter {
4866
6354
  };
4867
6355
  this.element.addEventListener("loadedmetadata", onLoadedMetadata);
4868
6356
  });
6357
+ if (this.element.tagName === "VIDEO" && currentTime === 0 && !wasPlaying) {
6358
+ if (this.element.readyState >= 1) {
6359
+ this.element.currentTime = 1e-3;
6360
+ setTimeout(() => {
6361
+ this.element.currentTime = 0;
6362
+ }, 10);
6363
+ }
6364
+ }
4869
6365
  this.seek(currentTime);
4870
6366
  if (wasPlaying) {
4871
6367
  this.play();
4872
6368
  }
6369
+ if (swappedTracksForTranscript.length > 0 && this.captionManager) {
6370
+ const wasCaptionsEnabled = this.state.captionsEnabled;
6371
+ let currentTrackInfo = null;
6372
+ if (this.captionManager.currentTrack) {
6373
+ const currentTrackIndex = this.captionManager.tracks.findIndex((t) => t.track === this.captionManager.currentTrack.track);
6374
+ if (currentTrackIndex >= 0) {
6375
+ currentTrackInfo = {
6376
+ language: this.captionManager.tracks[currentTrackIndex].language,
6377
+ kind: this.captionManager.tracks[currentTrackIndex].kind
6378
+ };
6379
+ }
6380
+ }
6381
+ setTimeout(() => {
6382
+ this.captionManager.tracks = [];
6383
+ this.captionManager.loadTracks();
6384
+ if (wasCaptionsEnabled && currentTrackInfo && this.captionManager.tracks.length > 0) {
6385
+ const matchingTrackIndex = this.captionManager.tracks.findIndex(
6386
+ (t) => t.language === currentTrackInfo.language && t.kind === currentTrackInfo.kind
6387
+ );
6388
+ if (matchingTrackIndex >= 0) {
6389
+ this.captionManager.enable(matchingTrackIndex);
6390
+ } else if (this.captionManager.tracks.length > 0) {
6391
+ this.captionManager.enable(0);
6392
+ }
6393
+ }
6394
+ }, 600);
6395
+ }
6396
+ if (this.transcriptManager && this.transcriptManager.isVisible) {
6397
+ const swappedTracks = typeof swappedTracksForTranscript !== "undefined" ? swappedTracksForTranscript : [];
6398
+ if (swappedTracks.length > 0) {
6399
+ const onMetadataLoaded = () => {
6400
+ const allTextTracks = Array.from(this.element.textTracks);
6401
+ const freshTracks = swappedTracks.map((trackInfo) => {
6402
+ const trackEl = trackInfo.trackElement;
6403
+ const expectedSrc = trackEl.getAttribute("src");
6404
+ const srclang = trackEl.getAttribute("srclang");
6405
+ const kind = trackEl.getAttribute("kind");
6406
+ let foundTrack = allTextTracks.find((track) => trackEl.track === track);
6407
+ if (!foundTrack) {
6408
+ foundTrack = allTextTracks.find((track) => {
6409
+ if (track.language === srclang && (track.kind === kind || kind === "captions" && track.kind === "subtitles")) {
6410
+ const trackElementForTrack = Array.from(this.element.querySelectorAll("track")).find(
6411
+ (el) => el.track === track
6412
+ );
6413
+ if (trackElementForTrack) {
6414
+ const actualSrc = trackElementForTrack.getAttribute("src");
6415
+ if (actualSrc === expectedSrc) {
6416
+ return true;
6417
+ }
6418
+ }
6419
+ }
6420
+ return false;
6421
+ });
6422
+ }
6423
+ if (foundTrack) {
6424
+ const trackElement = Array.from(this.element.querySelectorAll("track")).find(
6425
+ (el) => el.track === foundTrack
6426
+ );
6427
+ if (trackElement && trackElement.getAttribute("src") !== expectedSrc) {
6428
+ return null;
6429
+ }
6430
+ }
6431
+ return foundTrack;
6432
+ }).filter(Boolean);
6433
+ if (freshTracks.length === 0) {
6434
+ setTimeout(() => {
6435
+ if (this.transcriptManager && this.transcriptManager.loadTranscriptData) {
6436
+ this.transcriptManager.loadTranscriptData();
6437
+ }
6438
+ }, 1e3);
6439
+ return;
6440
+ }
6441
+ freshTracks.forEach((track) => {
6442
+ if (track.mode === "disabled") {
6443
+ track.mode = "hidden";
6444
+ }
6445
+ });
6446
+ let loadedCount = 0;
6447
+ const checkLoaded = () => {
6448
+ loadedCount++;
6449
+ if (loadedCount >= freshTracks.length) {
6450
+ setTimeout(() => {
6451
+ if (this.transcriptManager && this.transcriptManager.loadTranscriptData) {
6452
+ const allTextTracks2 = Array.from(this.element.textTracks);
6453
+ const swappedTrackSrcs = swappedTracks.map((t) => t.describedSrc);
6454
+ const hasCorrectTracks = freshTracks.some((track) => {
6455
+ const trackEl = Array.from(this.element.querySelectorAll("track")).find(
6456
+ (el) => el.track === track
6457
+ );
6458
+ return trackEl && swappedTrackSrcs.includes(trackEl.getAttribute("src"));
6459
+ });
6460
+ if (hasCorrectTracks || freshTracks.length > 0) {
6461
+ this.transcriptManager.loadTranscriptData();
6462
+ }
6463
+ }
6464
+ }, 800);
6465
+ }
6466
+ };
6467
+ freshTracks.forEach((track) => {
6468
+ if (track.mode === "disabled") {
6469
+ track.mode = "hidden";
6470
+ }
6471
+ const trackElementForTrack = Array.from(this.element.querySelectorAll("track")).find(
6472
+ (el) => el.track === track
6473
+ );
6474
+ const actualSrc = trackElementForTrack ? trackElementForTrack.getAttribute("src") : null;
6475
+ const expectedTrackInfo = swappedTracks.find((t) => {
6476
+ const tEl = t.trackElement;
6477
+ return tEl && (tEl.track === track || tEl.getAttribute("srclang") === track.language && tEl.getAttribute("kind") === track.kind);
6478
+ });
6479
+ const expectedSrc = expectedTrackInfo ? expectedTrackInfo.describedSrc : null;
6480
+ if (expectedSrc && actualSrc && actualSrc !== expectedSrc) {
6481
+ checkLoaded();
6482
+ return;
6483
+ }
6484
+ if (track.readyState >= 2 && track.cues && track.cues.length > 0) {
6485
+ checkLoaded();
6486
+ } else {
6487
+ if (track.mode === "disabled") {
6488
+ track.mode = "hidden";
6489
+ }
6490
+ const onTrackLoad = () => {
6491
+ setTimeout(checkLoaded, 300);
6492
+ };
6493
+ if (track.readyState >= 2) {
6494
+ setTimeout(() => {
6495
+ if (track.cues && track.cues.length > 0) {
6496
+ checkLoaded();
6497
+ } else {
6498
+ track.addEventListener("load", onTrackLoad, { once: true });
6499
+ }
6500
+ }, 100);
6501
+ } else {
6502
+ track.addEventListener("load", onTrackLoad, { once: true });
6503
+ track.addEventListener("error", () => {
6504
+ checkLoaded();
6505
+ }, { once: true });
6506
+ }
6507
+ }
6508
+ });
6509
+ };
6510
+ const waitForTracks = () => {
6511
+ setTimeout(() => {
6512
+ if (this.element.readyState >= 1) {
6513
+ onMetadataLoaded();
6514
+ } else {
6515
+ this.element.addEventListener("loadedmetadata", onMetadataLoaded, { once: true });
6516
+ setTimeout(onMetadataLoaded, 2e3);
6517
+ }
6518
+ }, 500);
6519
+ };
6520
+ waitForTracks();
6521
+ setTimeout(() => {
6522
+ if (this.transcriptManager && this.transcriptManager.loadTranscriptData) {
6523
+ this.transcriptManager.loadTranscriptData();
6524
+ }
6525
+ }, 5e3);
6526
+ } else {
6527
+ setTimeout(() => {
6528
+ if (this.transcriptManager && this.transcriptManager.loadTranscriptData) {
6529
+ this.transcriptManager.loadTranscriptData();
6530
+ }
6531
+ }, 800);
6532
+ }
6533
+ }
4873
6534
  this.state.audioDescriptionEnabled = true;
4874
6535
  this.emit("audiodescriptionenabled");
4875
6536
  }
@@ -4879,7 +6540,64 @@ var Player = class extends EventEmitter {
4879
6540
  }
4880
6541
  const currentTime = this.state.currentTime;
4881
6542
  const wasPlaying = this.state.playing;
4882
- this.element.src = this.originalSrc;
6543
+ if (this.audioDescriptionCaptionTracks.length > 0) {
6544
+ this.audioDescriptionCaptionTracks.forEach((trackInfo) => {
6545
+ if (trackInfo.trackElement && trackInfo.originalTrackSrc) {
6546
+ trackInfo.trackElement.setAttribute("src", trackInfo.originalTrackSrc);
6547
+ }
6548
+ });
6549
+ }
6550
+ const allSourceElements = Array.from(this.element.querySelectorAll("source"));
6551
+ const hasSourceElementsToSwap = allSourceElements.some((el) => el.getAttribute("data-orig-src"));
6552
+ if (hasSourceElementsToSwap) {
6553
+ const sourcesToRestore = [];
6554
+ allSourceElements.forEach((sourceEl) => {
6555
+ const origSrcAttr = sourceEl.getAttribute("data-orig-src");
6556
+ const descSrcAttr = sourceEl.getAttribute("data-desc-src");
6557
+ if (origSrcAttr) {
6558
+ const type = sourceEl.getAttribute("type");
6559
+ sourcesToRestore.push({
6560
+ src: origSrcAttr,
6561
+ // Use original version
6562
+ type,
6563
+ origSrc: origSrcAttr,
6564
+ descSrc: descSrcAttr
6565
+ // Keep data-desc-src for future swaps
6566
+ });
6567
+ } else {
6568
+ const type = sourceEl.getAttribute("type");
6569
+ const src = sourceEl.getAttribute("src");
6570
+ sourcesToRestore.push({
6571
+ src,
6572
+ type,
6573
+ origSrc: null,
6574
+ descSrc: descSrcAttr
6575
+ });
6576
+ }
6577
+ });
6578
+ allSourceElements.forEach((sourceEl) => {
6579
+ sourceEl.remove();
6580
+ });
6581
+ sourcesToRestore.forEach((sourceInfo) => {
6582
+ const newSource = document.createElement("source");
6583
+ newSource.setAttribute("src", sourceInfo.src);
6584
+ if (sourceInfo.type) {
6585
+ newSource.setAttribute("type", sourceInfo.type);
6586
+ }
6587
+ if (sourceInfo.origSrc) {
6588
+ newSource.setAttribute("data-orig-src", sourceInfo.origSrc);
6589
+ }
6590
+ if (sourceInfo.descSrc) {
6591
+ newSource.setAttribute("data-desc-src", sourceInfo.descSrc);
6592
+ }
6593
+ this.element.appendChild(newSource);
6594
+ });
6595
+ this.element.load();
6596
+ } else {
6597
+ const originalSrcToUse = this.originalAudioDescriptionSource || this.originalSrc;
6598
+ this.element.src = originalSrcToUse;
6599
+ this.element.load();
6600
+ }
4883
6601
  await new Promise((resolve) => {
4884
6602
  const onLoadedMetadata = () => {
4885
6603
  this.element.removeEventListener("loadedmetadata", onLoadedMetadata);
@@ -4891,14 +6609,44 @@ var Player = class extends EventEmitter {
4891
6609
  if (wasPlaying) {
4892
6610
  this.play();
4893
6611
  }
6612
+ if (this.transcriptManager && this.transcriptManager.isVisible) {
6613
+ setTimeout(() => {
6614
+ if (this.transcriptManager && this.transcriptManager.loadTranscriptData) {
6615
+ this.transcriptManager.loadTranscriptData();
6616
+ }
6617
+ }, 500);
6618
+ }
4894
6619
  this.state.audioDescriptionEnabled = false;
4895
6620
  this.emit("audiodescriptiondisabled");
4896
6621
  }
4897
6622
  async toggleAudioDescription() {
4898
- if (this.state.audioDescriptionEnabled) {
4899
- await this.disableAudioDescription();
4900
- } else {
4901
- await this.enableAudioDescription();
6623
+ const textTracks = Array.from(this.element.textTracks || []);
6624
+ const descriptionTrack = textTracks.find((track) => track.kind === "descriptions");
6625
+ const hasAudioDescriptionSrc = this.audioDescriptionSrc || Array.from(this.element.querySelectorAll("source")).some((el) => el.getAttribute("data-desc-src"));
6626
+ if (descriptionTrack && hasAudioDescriptionSrc) {
6627
+ if (this.state.audioDescriptionEnabled) {
6628
+ descriptionTrack.mode = "hidden";
6629
+ await this.disableAudioDescription();
6630
+ } else {
6631
+ await this.enableAudioDescription();
6632
+ descriptionTrack.mode = "showing";
6633
+ }
6634
+ } else if (descriptionTrack) {
6635
+ if (descriptionTrack.mode === "showing") {
6636
+ descriptionTrack.mode = "hidden";
6637
+ this.state.audioDescriptionEnabled = false;
6638
+ this.emit("audiodescriptiondisabled");
6639
+ } else {
6640
+ descriptionTrack.mode = "showing";
6641
+ this.state.audioDescriptionEnabled = true;
6642
+ this.emit("audiodescriptionenabled");
6643
+ }
6644
+ } else if (hasAudioDescriptionSrc) {
6645
+ if (this.state.audioDescriptionEnabled) {
6646
+ await this.disableAudioDescription();
6647
+ } else {
6648
+ await this.enableAudioDescription();
6649
+ }
4902
6650
  }
4903
6651
  }
4904
6652
  // Sign Language
@@ -4907,24 +6655,47 @@ var Player = class extends EventEmitter {
4907
6655
  console.warn("No sign language video source provided");
4908
6656
  return;
4909
6657
  }
4910
- if (this.signLanguageVideo) {
4911
- this.signLanguageVideo.style.display = "block";
6658
+ if (this.signLanguageWrapper) {
6659
+ this.signLanguageWrapper.style.display = "block";
4912
6660
  this.state.signLanguageEnabled = true;
4913
6661
  this.emit("signlanguageenabled");
4914
6662
  return;
4915
6663
  }
6664
+ this.signLanguageWrapper = document.createElement("div");
6665
+ this.signLanguageWrapper.className = "vidply-sign-language-wrapper";
6666
+ this.signLanguageWrapper.setAttribute("tabindex", "0");
6667
+ this.signLanguageWrapper.setAttribute("aria-label", "Sign Language Video - Press D to drag with keyboard, R to resize");
4916
6668
  this.signLanguageVideo = document.createElement("video");
4917
6669
  this.signLanguageVideo.className = "vidply-sign-language-video";
4918
6670
  this.signLanguageVideo.src = this.signLanguageSrc;
4919
6671
  this.signLanguageVideo.setAttribute("aria-label", i18n.t("player.signLanguage"));
4920
- const position = this.options.signLanguagePosition || "bottom-right";
4921
- this.signLanguageVideo.classList.add(`vidply-sign-position-${position}`);
4922
6672
  this.signLanguageVideo.muted = true;
6673
+ const resizeHandles = ["nw", "ne", "sw", "se"].map((dir) => {
6674
+ const handle = document.createElement("div");
6675
+ handle.className = `vidply-sign-resize-handle vidply-sign-resize-${dir}`;
6676
+ handle.setAttribute("data-direction", dir);
6677
+ handle.setAttribute("aria-label", `Resize ${dir.toUpperCase()}`);
6678
+ return handle;
6679
+ });
6680
+ this.signLanguageWrapper.appendChild(this.signLanguageVideo);
6681
+ resizeHandles.forEach((handle) => this.signLanguageWrapper.appendChild(handle));
6682
+ const saved = this.storage.getSignLanguagePreferences();
6683
+ if (saved && saved.size && saved.size.width) {
6684
+ this.signLanguageWrapper.style.width = saved.size.width;
6685
+ } else {
6686
+ this.signLanguageWrapper.style.width = "280px";
6687
+ }
6688
+ this.signLanguageWrapper.style.height = "auto";
6689
+ this.signLanguageDesiredPosition = this.options.signLanguagePosition || "bottom-right";
6690
+ this.container.appendChild(this.signLanguageWrapper);
6691
+ requestAnimationFrame(() => {
6692
+ this.constrainSignLanguagePosition();
6693
+ });
4923
6694
  this.signLanguageVideo.currentTime = this.state.currentTime;
4924
6695
  if (!this.state.paused) {
4925
6696
  this.signLanguageVideo.play();
4926
6697
  }
4927
- this.videoWrapper.appendChild(this.signLanguageVideo);
6698
+ this.setupSignLanguageInteraction();
4928
6699
  this.signLanguageHandlers = {
4929
6700
  play: () => {
4930
6701
  if (this.signLanguageVideo) {
@@ -4955,8 +6726,8 @@ var Player = class extends EventEmitter {
4955
6726
  this.emit("signlanguageenabled");
4956
6727
  }
4957
6728
  disableSignLanguage() {
4958
- if (this.signLanguageVideo) {
4959
- this.signLanguageVideo.style.display = "none";
6729
+ if (this.signLanguageWrapper) {
6730
+ this.signLanguageWrapper.style.display = "none";
4960
6731
  }
4961
6732
  this.state.signLanguageEnabled = false;
4962
6733
  this.emit("signlanguagedisabled");
@@ -4968,6 +6739,237 @@ var Player = class extends EventEmitter {
4968
6739
  this.enableSignLanguage();
4969
6740
  }
4970
6741
  }
6742
+ setupSignLanguageInteraction() {
6743
+ if (!this.signLanguageWrapper) return;
6744
+ let isDragging = false;
6745
+ let isResizing = false;
6746
+ let resizeDirection = null;
6747
+ let startX = 0;
6748
+ let startY = 0;
6749
+ let startLeft = 0;
6750
+ let startTop = 0;
6751
+ let startWidth = 0;
6752
+ let startHeight = 0;
6753
+ let dragMode = false;
6754
+ let resizeMode = false;
6755
+ const onMouseDownVideo = (e) => {
6756
+ if (e.target !== this.signLanguageVideo) return;
6757
+ e.preventDefault();
6758
+ isDragging = true;
6759
+ startX = e.clientX;
6760
+ startY = e.clientY;
6761
+ const rect = this.signLanguageWrapper.getBoundingClientRect();
6762
+ startLeft = rect.left;
6763
+ startTop = rect.top;
6764
+ this.signLanguageWrapper.classList.add("vidply-sign-dragging");
6765
+ };
6766
+ const onMouseDownHandle = (e) => {
6767
+ if (!e.target.classList.contains("vidply-sign-resize-handle")) return;
6768
+ e.preventDefault();
6769
+ e.stopPropagation();
6770
+ isResizing = true;
6771
+ resizeDirection = e.target.getAttribute("data-direction");
6772
+ startX = e.clientX;
6773
+ startY = e.clientY;
6774
+ const rect = this.signLanguageWrapper.getBoundingClientRect();
6775
+ startLeft = rect.left;
6776
+ startTop = rect.top;
6777
+ startWidth = rect.width;
6778
+ startHeight = rect.height;
6779
+ this.signLanguageWrapper.classList.add("vidply-sign-resizing");
6780
+ };
6781
+ const onMouseMove = (e) => {
6782
+ if (isDragging) {
6783
+ const deltaX = e.clientX - startX;
6784
+ const deltaY = e.clientY - startY;
6785
+ const videoWrapperRect = this.videoWrapper.getBoundingClientRect();
6786
+ const containerRect = this.container.getBoundingClientRect();
6787
+ const wrapperRect = this.signLanguageWrapper.getBoundingClientRect();
6788
+ const videoWrapperLeft = videoWrapperRect.left - containerRect.left;
6789
+ const videoWrapperTop = videoWrapperRect.top - containerRect.top;
6790
+ let newLeft = startLeft + deltaX - containerRect.left;
6791
+ let newTop = startTop + deltaY - containerRect.top;
6792
+ const controlsHeight = 95;
6793
+ newLeft = Math.max(videoWrapperLeft, Math.min(newLeft, videoWrapperLeft + videoWrapperRect.width - wrapperRect.width));
6794
+ newTop = Math.max(videoWrapperTop, Math.min(newTop, videoWrapperTop + videoWrapperRect.height - wrapperRect.height - controlsHeight));
6795
+ this.signLanguageWrapper.style.left = `${newLeft}px`;
6796
+ this.signLanguageWrapper.style.top = `${newTop}px`;
6797
+ this.signLanguageWrapper.style.right = "auto";
6798
+ this.signLanguageWrapper.style.bottom = "auto";
6799
+ this.signLanguageWrapper.classList.remove(...Array.from(this.signLanguageWrapper.classList).filter((c) => c.startsWith("vidply-sign-position-")));
6800
+ } else if (isResizing) {
6801
+ const deltaX = e.clientX - startX;
6802
+ const videoWrapperRect = this.videoWrapper.getBoundingClientRect();
6803
+ const containerRect = this.container.getBoundingClientRect();
6804
+ let newWidth = startWidth;
6805
+ let newLeft = startLeft - containerRect.left;
6806
+ if (resizeDirection.includes("e")) {
6807
+ newWidth = Math.max(150, startWidth + deltaX);
6808
+ const maxWidth = videoWrapperRect.right - startLeft;
6809
+ newWidth = Math.min(newWidth, maxWidth);
6810
+ }
6811
+ if (resizeDirection.includes("w")) {
6812
+ const proposedWidth = Math.max(150, startWidth - deltaX);
6813
+ const proposedLeft = startLeft + (startWidth - proposedWidth) - containerRect.left;
6814
+ const videoWrapperLeft = videoWrapperRect.left - containerRect.left;
6815
+ if (proposedLeft >= videoWrapperLeft) {
6816
+ newWidth = proposedWidth;
6817
+ newLeft = proposedLeft;
6818
+ }
6819
+ }
6820
+ this.signLanguageWrapper.style.width = `${newWidth}px`;
6821
+ this.signLanguageWrapper.style.height = "auto";
6822
+ if (resizeDirection.includes("w")) {
6823
+ this.signLanguageWrapper.style.left = `${newLeft}px`;
6824
+ }
6825
+ this.signLanguageWrapper.style.right = "auto";
6826
+ this.signLanguageWrapper.style.bottom = "auto";
6827
+ this.signLanguageWrapper.classList.remove(...Array.from(this.signLanguageWrapper.classList).filter((c) => c.startsWith("vidply-sign-position-")));
6828
+ }
6829
+ };
6830
+ const onMouseUp = () => {
6831
+ if (isDragging || isResizing) {
6832
+ this.saveSignLanguagePreferences();
6833
+ }
6834
+ isDragging = false;
6835
+ isResizing = false;
6836
+ resizeDirection = null;
6837
+ this.signLanguageWrapper.classList.remove("vidply-sign-dragging", "vidply-sign-resizing");
6838
+ };
6839
+ const onKeyDown = (e) => {
6840
+ if (e.key === "d" || e.key === "D") {
6841
+ dragMode = !dragMode;
6842
+ resizeMode = false;
6843
+ this.signLanguageWrapper.classList.toggle("vidply-sign-keyboard-drag", dragMode);
6844
+ this.signLanguageWrapper.classList.remove("vidply-sign-keyboard-resize");
6845
+ e.preventDefault();
6846
+ return;
6847
+ }
6848
+ if (e.key === "r" || e.key === "R") {
6849
+ resizeMode = !resizeMode;
6850
+ dragMode = false;
6851
+ this.signLanguageWrapper.classList.toggle("vidply-sign-keyboard-resize", resizeMode);
6852
+ this.signLanguageWrapper.classList.remove("vidply-sign-keyboard-drag");
6853
+ e.preventDefault();
6854
+ return;
6855
+ }
6856
+ if (e.key === "Escape") {
6857
+ dragMode = false;
6858
+ resizeMode = false;
6859
+ this.signLanguageWrapper.classList.remove("vidply-sign-keyboard-drag", "vidply-sign-keyboard-resize");
6860
+ e.preventDefault();
6861
+ return;
6862
+ }
6863
+ if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) {
6864
+ const step = e.shiftKey ? 10 : 5;
6865
+ const rect = this.signLanguageWrapper.getBoundingClientRect();
6866
+ const videoWrapperRect = this.videoWrapper.getBoundingClientRect();
6867
+ const containerRect = this.container.getBoundingClientRect();
6868
+ const videoWrapperLeft = videoWrapperRect.left - containerRect.left;
6869
+ const videoWrapperTop = videoWrapperRect.top - containerRect.top;
6870
+ if (dragMode) {
6871
+ let left = rect.left - containerRect.left;
6872
+ let top = rect.top - containerRect.top;
6873
+ if (e.key === "ArrowLeft") left -= step;
6874
+ if (e.key === "ArrowRight") left += step;
6875
+ if (e.key === "ArrowUp") top -= step;
6876
+ if (e.key === "ArrowDown") top += step;
6877
+ const controlsHeight = 95;
6878
+ left = Math.max(videoWrapperLeft, Math.min(left, videoWrapperLeft + videoWrapperRect.width - rect.width));
6879
+ top = Math.max(videoWrapperTop, Math.min(top, videoWrapperTop + videoWrapperRect.height - rect.height - controlsHeight));
6880
+ this.signLanguageWrapper.style.left = `${left}px`;
6881
+ this.signLanguageWrapper.style.top = `${top}px`;
6882
+ this.signLanguageWrapper.style.right = "auto";
6883
+ this.signLanguageWrapper.style.bottom = "auto";
6884
+ this.signLanguageWrapper.classList.remove(...Array.from(this.signLanguageWrapper.classList).filter((c) => c.startsWith("vidply-sign-position-")));
6885
+ this.saveSignLanguagePreferences();
6886
+ e.preventDefault();
6887
+ } else if (resizeMode) {
6888
+ let width = rect.width;
6889
+ if (e.key === "ArrowLeft") width -= step;
6890
+ if (e.key === "ArrowRight") width += step;
6891
+ if (e.key === "ArrowUp") width += step;
6892
+ if (e.key === "ArrowDown") width -= step;
6893
+ width = Math.max(150, width);
6894
+ width = Math.min(width, videoWrapperRect.width);
6895
+ this.signLanguageWrapper.style.width = `${width}px`;
6896
+ this.signLanguageWrapper.style.height = "auto";
6897
+ this.saveSignLanguagePreferences();
6898
+ e.preventDefault();
6899
+ }
6900
+ }
6901
+ };
6902
+ this.signLanguageVideo.addEventListener("mousedown", onMouseDownVideo);
6903
+ const handles = this.signLanguageWrapper.querySelectorAll(".vidply-sign-resize-handle");
6904
+ handles.forEach((handle) => handle.addEventListener("mousedown", onMouseDownHandle));
6905
+ document.addEventListener("mousemove", onMouseMove);
6906
+ document.addEventListener("mouseup", onMouseUp);
6907
+ this.signLanguageWrapper.addEventListener("keydown", onKeyDown);
6908
+ this.signLanguageInteractionHandlers = {
6909
+ mouseDownVideo: onMouseDownVideo,
6910
+ mouseDownHandle: onMouseDownHandle,
6911
+ mouseMove: onMouseMove,
6912
+ mouseUp: onMouseUp,
6913
+ keyDown: onKeyDown,
6914
+ handles
6915
+ };
6916
+ }
6917
+ constrainSignLanguagePosition() {
6918
+ if (!this.signLanguageWrapper || !this.videoWrapper) return;
6919
+ if (!this.signLanguageWrapper.style.width || this.signLanguageWrapper.style.width === "") {
6920
+ this.signLanguageWrapper.style.width = "280px";
6921
+ }
6922
+ const videoWrapperRect = this.videoWrapper.getBoundingClientRect();
6923
+ const containerRect = this.container.getBoundingClientRect();
6924
+ const wrapperRect = this.signLanguageWrapper.getBoundingClientRect();
6925
+ const videoWrapperLeft = videoWrapperRect.left - containerRect.left;
6926
+ const videoWrapperTop = videoWrapperRect.top - containerRect.top;
6927
+ const videoWrapperWidth = videoWrapperRect.width;
6928
+ const videoWrapperHeight = videoWrapperRect.height;
6929
+ let wrapperWidth = wrapperRect.width || 280;
6930
+ let wrapperHeight = wrapperRect.height || 280 * 9 / 16;
6931
+ let left, top;
6932
+ const margin = 16;
6933
+ const controlsHeight = 95;
6934
+ const position = this.signLanguageDesiredPosition || "bottom-right";
6935
+ switch (position) {
6936
+ case "bottom-right":
6937
+ left = videoWrapperLeft + videoWrapperWidth - wrapperWidth - margin;
6938
+ top = videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight;
6939
+ break;
6940
+ case "bottom-left":
6941
+ left = videoWrapperLeft + margin;
6942
+ top = videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight;
6943
+ break;
6944
+ case "top-right":
6945
+ left = videoWrapperLeft + videoWrapperWidth - wrapperWidth - margin;
6946
+ top = videoWrapperTop + margin;
6947
+ break;
6948
+ case "top-left":
6949
+ left = videoWrapperLeft + margin;
6950
+ top = videoWrapperTop + margin;
6951
+ break;
6952
+ default:
6953
+ left = videoWrapperLeft + videoWrapperWidth - wrapperWidth - margin;
6954
+ top = videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight;
6955
+ }
6956
+ left = Math.max(videoWrapperLeft, Math.min(left, videoWrapperLeft + videoWrapperWidth - wrapperWidth));
6957
+ top = Math.max(videoWrapperTop, Math.min(top, videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight));
6958
+ this.signLanguageWrapper.style.left = `${left}px`;
6959
+ this.signLanguageWrapper.style.top = `${top}px`;
6960
+ this.signLanguageWrapper.style.right = "auto";
6961
+ this.signLanguageWrapper.style.bottom = "auto";
6962
+ this.signLanguageWrapper.classList.remove(...Array.from(this.signLanguageWrapper.classList).filter((c) => c.startsWith("vidply-sign-position-")));
6963
+ }
6964
+ saveSignLanguagePreferences() {
6965
+ if (!this.signLanguageWrapper) return;
6966
+ this.storage.saveSignLanguagePreferences({
6967
+ size: {
6968
+ width: this.signLanguageWrapper.style.width
6969
+ // Height is auto - maintained by aspect ratio
6970
+ }
6971
+ });
6972
+ }
4971
6973
  cleanupSignLanguage() {
4972
6974
  if (this.signLanguageHandlers) {
4973
6975
  this.off("play", this.signLanguageHandlers.play);
@@ -4976,10 +6978,29 @@ var Player = class extends EventEmitter {
4976
6978
  this.off("ratechange", this.signLanguageHandlers.ratechange);
4977
6979
  this.signLanguageHandlers = null;
4978
6980
  }
4979
- if (this.signLanguageVideo && this.signLanguageVideo.parentNode) {
4980
- this.signLanguageVideo.pause();
4981
- this.signLanguageVideo.src = "";
4982
- this.signLanguageVideo.parentNode.removeChild(this.signLanguageVideo);
6981
+ if (this.signLanguageInteractionHandlers) {
6982
+ if (this.signLanguageVideo) {
6983
+ this.signLanguageVideo.removeEventListener("mousedown", this.signLanguageInteractionHandlers.mouseDownVideo);
6984
+ }
6985
+ if (this.signLanguageInteractionHandlers.handles) {
6986
+ this.signLanguageInteractionHandlers.handles.forEach((handle) => {
6987
+ handle.removeEventListener("mousedown", this.signLanguageInteractionHandlers.mouseDownHandle);
6988
+ });
6989
+ }
6990
+ document.removeEventListener("mousemove", this.signLanguageInteractionHandlers.mouseMove);
6991
+ document.removeEventListener("mouseup", this.signLanguageInteractionHandlers.mouseUp);
6992
+ if (this.signLanguageWrapper) {
6993
+ this.signLanguageWrapper.removeEventListener("keydown", this.signLanguageInteractionHandlers.keyDown);
6994
+ }
6995
+ this.signLanguageInteractionHandlers = null;
6996
+ }
6997
+ if (this.signLanguageWrapper && this.signLanguageWrapper.parentNode) {
6998
+ if (this.signLanguageVideo) {
6999
+ this.signLanguageVideo.pause();
7000
+ this.signLanguageVideo.src = "";
7001
+ }
7002
+ this.signLanguageWrapper.parentNode.removeChild(this.signLanguageWrapper);
7003
+ this.signLanguageWrapper = null;
4983
7004
  this.signLanguageVideo = null;
4984
7005
  }
4985
7006
  }
@@ -5082,6 +7103,16 @@ var Player = class extends EventEmitter {
5082
7103
  if (this.controlBar) {
5083
7104
  this.controlBar.updateFullscreenButton();
5084
7105
  }
7106
+ if (this.signLanguageWrapper && this.signLanguageWrapper.style.display !== "none") {
7107
+ setTimeout(() => {
7108
+ requestAnimationFrame(() => {
7109
+ this.storage.saveSignLanguagePreferences({ size: null });
7110
+ this.signLanguageDesiredPosition = "bottom-right";
7111
+ this.signLanguageWrapper.style.width = isFullscreen ? "400px" : "280px";
7112
+ this.constrainSignLanguagePosition();
7113
+ });
7114
+ }, 500);
7115
+ }
5085
7116
  }
5086
7117
  };
5087
7118
  document.addEventListener("fullscreenchange", this.fullscreenChangeHandler);