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.
package/dist/vidply.js CHANGED
@@ -468,7 +468,11 @@ var VidPly = (() => {
468
468
  noCaptions: "No captions available",
469
469
  auto: "Auto",
470
470
  autoQuality: "Auto (no quality selection available)",
471
- noQuality: "Quality selection not available"
471
+ noQuality: "Quality selection not available",
472
+ signLanguageDragResize: "Sign Language Video - Press D to drag with keyboard, R to resize",
473
+ signLanguageDragActive: "Sign Language Video - Drag mode active. Use arrow keys to move, Escape to exit.",
474
+ signLanguageResizeActive: "Sign Language Video - Resize mode active. Use left/right arrow keys to resize, Escape to exit.",
475
+ resizeHandle: "Resize {direction} corner"
472
476
  },
473
477
  captions: {
474
478
  off: "Off",
@@ -481,7 +485,7 @@ var VidPly = (() => {
481
485
  },
482
486
  fontSizes: {
483
487
  small: "Small",
484
- medium: "Medium",
488
+ normal: "Normal",
485
489
  large: "Large",
486
490
  xlarge: "X-Large"
487
491
  },
@@ -509,7 +513,14 @@ var VidPly = (() => {
509
513
  title: "Transcript",
510
514
  close: "Close transcript",
511
515
  loading: "Loading transcript...",
512
- noTranscript: "No transcript available for this video."
516
+ noTranscript: "No transcript available for this video.",
517
+ settings: "Transcript settings. Press Enter to open menu, or D to enable drag mode",
518
+ keyboardDragMode: "Toggle keyboard drag mode with arrow keys. Shortcut: D key",
519
+ keyboardDragActive: "\u2328\uFE0F Keyboard Drag Mode Active (Arrow keys to move, Shift+Arrows for large steps, D or ESC to exit)",
520
+ resizeWindow: "Resize Window",
521
+ styleTranscript: "Open transcript style settings",
522
+ closeMenu: "Close Menu",
523
+ styleTitle: "Transcript Style"
513
524
  },
514
525
  settings: {
515
526
  title: "Settings",
@@ -578,7 +589,11 @@ var VidPly = (() => {
578
589
  noCaptions: "Keine Untertitel verf\xFCgbar",
579
590
  auto: "Automatisch",
580
591
  autoQuality: "Automatisch (keine Qualit\xE4tsauswahl verf\xFCgbar)",
581
- noQuality: "Qualit\xE4tsauswahl nicht verf\xFCgbar"
592
+ noQuality: "Qualit\xE4tsauswahl nicht verf\xFCgbar",
593
+ signLanguageDragResize: "Geb\xE4rdensprache-Video - Dr\xFCcken Sie D zum Verschieben per Tastatur, R zum \xC4ndern der Gr\xF6\xDFe",
594
+ signLanguageDragActive: "Geb\xE4rdensprache-Video - Verschiebemodus aktiv. Pfeiltasten zum Bewegen, Escape zum Beenden.",
595
+ signLanguageResizeActive: "Geb\xE4rdensprache-Video - Gr\xF6\xDFen\xE4nderungsmodus aktiv. Links-/Rechts-Pfeiltasten zum \xC4ndern der Gr\xF6\xDFe, Escape zum Beenden.",
596
+ resizeHandle: "Gr\xF6\xDFen\xE4nderung {direction}-Ecke"
582
597
  },
583
598
  captions: {
584
599
  off: "Aus",
@@ -591,7 +606,7 @@ var VidPly = (() => {
591
606
  },
592
607
  fontSizes: {
593
608
  small: "Klein",
594
- medium: "Mittel",
609
+ normal: "Normal",
595
610
  large: "Gro\xDF",
596
611
  xlarge: "Sehr gro\xDF"
597
612
  },
@@ -619,7 +634,14 @@ var VidPly = (() => {
619
634
  title: "Transkript",
620
635
  close: "Transkript schlie\xDFen",
621
636
  loading: "Transkript wird geladen...",
622
- noTranscript: "Kein Transkript f\xFCr dieses Video verf\xFCgbar."
637
+ noTranscript: "Kein Transkript f\xFCr dieses Video verf\xFCgbar.",
638
+ settings: "Transkript-Einstellungen. Eingabetaste zum \xD6ffnen des Men\xFCs dr\xFCcken oder D zum Aktivieren des Verschiebemodus",
639
+ keyboardDragMode: "Tastatur-Verschiebemodus mit Pfeiltasten umschalten. Tastenkombination: D-Taste",
640
+ keyboardDragActive: "\u2328\uFE0F Tastatur-Verschiebemodus aktiv (Pfeiltasten zum Bewegen, Umschalt+Pfeiltasten f\xFCr gro\xDFe Schritte, D oder ESC zum Beenden)",
641
+ resizeWindow: "Fenster vergr\xF6\xDFern/verkleinern",
642
+ styleTranscript: "Transkript-Stileinstellungen \xF6ffnen",
643
+ closeMenu: "Men\xFC schlie\xDFen",
644
+ styleTitle: "Transkript-Stil"
623
645
  },
624
646
  settings: {
625
647
  title: "Einstellungen",
@@ -688,7 +710,11 @@ var VidPly = (() => {
688
710
  noCaptions: "No hay subt\xEDtulos disponibles",
689
711
  auto: "Autom\xE1tico",
690
712
  autoQuality: "Autom\xE1tico (selecci\xF3n de calidad no disponible)",
691
- noQuality: "Selecci\xF3n de calidad no disponible"
713
+ noQuality: "Selecci\xF3n de calidad no disponible",
714
+ signLanguageDragResize: "Video en Lengua de Se\xF1as - Presione D para arrastrar con el teclado, R para cambiar el tama\xF1o",
715
+ signLanguageDragActive: "Video en Lengua de Se\xF1as - Modo de arrastre activo. Use las teclas de flecha para mover, Escape para salir.",
716
+ 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.",
717
+ resizeHandle: "Cambiar tama\xF1o esquina {direction}"
692
718
  },
693
719
  captions: {
694
720
  off: "Desactivado",
@@ -701,7 +727,7 @@ var VidPly = (() => {
701
727
  },
702
728
  fontSizes: {
703
729
  small: "Peque\xF1o",
704
- medium: "Mediano",
730
+ normal: "Normal",
705
731
  large: "Grande",
706
732
  xlarge: "Muy grande"
707
733
  },
@@ -729,7 +755,14 @@ var VidPly = (() => {
729
755
  title: "Transcripci\xF3n",
730
756
  close: "Cerrar transcripci\xF3n",
731
757
  loading: "Cargando transcripci\xF3n...",
732
- noTranscript: "No hay transcripci\xF3n disponible para este video."
758
+ noTranscript: "No hay transcripci\xF3n disponible para este video.",
759
+ settings: "Configuraci\xF3n de transcripci\xF3n. Presione Enter para abrir el men\xFA o D para activar el modo de arrastre",
760
+ keyboardDragMode: "Alternar modo de arrastre con teclado usando teclas de flecha. Atajo: tecla D",
761
+ 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)",
762
+ resizeWindow: "Cambiar tama\xF1o de ventana",
763
+ styleTranscript: "Abrir configuraci\xF3n de estilo de transcripci\xF3n",
764
+ closeMenu: "Cerrar men\xFA",
765
+ styleTitle: "Estilo de Transcripci\xF3n"
733
766
  },
734
767
  settings: {
735
768
  title: "Configuraci\xF3n",
@@ -798,7 +831,11 @@ var VidPly = (() => {
798
831
  noCaptions: "Aucun sous-titre disponible",
799
832
  auto: "Automatique",
800
833
  autoQuality: "Automatique (s\xE9lection de qualit\xE9 non disponible)",
801
- noQuality: "S\xE9lection de qualit\xE9 non disponible"
834
+ noQuality: "S\xE9lection de qualit\xE9 non disponible",
835
+ signLanguageDragResize: "Vid\xE9o en Langue des Signes - Appuyez sur D pour d\xE9placer avec le clavier, R pour redimensionner",
836
+ signLanguageDragActive: "Vid\xE9o en Langue des Signes - Mode glissement actif. Utilisez les touches fl\xE9ch\xE9es pour d\xE9placer, \xC9chap pour quitter.",
837
+ signLanguageResizeActive: "Vid\xE9o en Langue des Signes - Mode redimensionnement actif. Utilisez les touches fl\xE9ch\xE9es gauche/droite pour redimensionner, \xC9chap pour quitter.",
838
+ resizeHandle: "Redimensionner coin {direction}"
802
839
  },
803
840
  captions: {
804
841
  off: "D\xE9sactiv\xE9",
@@ -811,7 +848,7 @@ var VidPly = (() => {
811
848
  },
812
849
  fontSizes: {
813
850
  small: "Petit",
814
- medium: "Moyen",
851
+ normal: "Normal",
815
852
  large: "Grand",
816
853
  xlarge: "Tr\xE8s grand"
817
854
  },
@@ -839,7 +876,14 @@ var VidPly = (() => {
839
876
  title: "Transcription",
840
877
  close: "Fermer la transcription",
841
878
  loading: "Chargement de la transcription...",
842
- noTranscript: "Aucune transcription disponible pour cette vid\xE9o."
879
+ noTranscript: "Aucune transcription disponible pour cette vid\xE9o.",
880
+ settings: "Param\xE8tres de transcription. Appuyez sur Entr\xE9e pour ouvrir le menu ou D pour activer le mode glissement",
881
+ keyboardDragMode: "Basculer le mode glissement avec les touches fl\xE9ch\xE9es. Raccourci: touche D",
882
+ 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)",
883
+ resizeWindow: "Redimensionner la fen\xEAtre",
884
+ styleTranscript: "Ouvrir les param\xE8tres de style de transcription",
885
+ closeMenu: "Fermer le menu",
886
+ styleTitle: "Style de Transcription"
843
887
  },
844
888
  settings: {
845
889
  title: "Param\xE8tres",
@@ -908,7 +952,11 @@ var VidPly = (() => {
908
952
  noCaptions: "\u5B57\u5E55\u304C\u3042\u308A\u307E\u305B\u3093",
909
953
  auto: "\u81EA\u52D5",
910
954
  autoQuality: "\u81EA\u52D5\uFF08\u753B\u8CEA\u9078\u629E\u4E0D\u53EF\uFF09",
911
- noQuality: "\u753B\u8CEA\u9078\u629E\u4E0D\u53EF"
955
+ noQuality: "\u753B\u8CEA\u9078\u629E\u4E0D\u53EF",
956
+ 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",
957
+ 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",
958
+ 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",
959
+ resizeHandle: "{direction}\u30B3\u30FC\u30CA\u30FC\u306E\u30B5\u30A4\u30BA\u5909\u66F4"
912
960
  },
913
961
  captions: {
914
962
  off: "\u30AA\u30D5",
@@ -921,7 +969,7 @@ var VidPly = (() => {
921
969
  },
922
970
  fontSizes: {
923
971
  small: "\u5C0F",
924
- medium: "\u4E2D",
972
+ normal: "\u6A19\u6E96",
925
973
  large: "\u5927",
926
974
  xlarge: "\u7279\u5927"
927
975
  },
@@ -949,7 +997,14 @@ var VidPly = (() => {
949
997
  title: "\u6587\u5B57\u8D77\u3053\u3057",
950
998
  close: "\u6587\u5B57\u8D77\u3053\u3057\u3092\u9589\u3058\u308B",
951
999
  loading: "\u6587\u5B57\u8D77\u3053\u3057\u3092\u8AAD\u307F\u8FBC\u307F\u4E2D...",
952
- noTranscript: "\u3053\u306E\u30D3\u30C7\u30AA\u306E\u6587\u5B57\u8D77\u3053\u3057\u306F\u3042\u308A\u307E\u305B\u3093\u3002"
1000
+ noTranscript: "\u3053\u306E\u30D3\u30C7\u30AA\u306E\u6587\u5B57\u8D77\u3053\u3057\u306F\u3042\u308A\u307E\u305B\u3093\u3002",
1001
+ 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",
1002
+ 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",
1003
+ 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",
1004
+ resizeWindow: "\u30A6\u30A3\u30F3\u30C9\u30A6\u306E\u30B5\u30A4\u30BA\u5909\u66F4",
1005
+ styleTranscript: "\u6587\u5B57\u8D77\u3053\u3057\u30B9\u30BF\u30A4\u30EB\u8A2D\u5B9A\u3092\u958B\u304F",
1006
+ closeMenu: "\u30E1\u30CB\u30E5\u30FC\u3092\u9589\u3058\u308B",
1007
+ styleTitle: "\u6587\u5B57\u8D77\u3053\u3057\u30B9\u30BF\u30A4\u30EB"
953
1008
  },
954
1009
  settings: {
955
1010
  title: "\u8A2D\u5B9A",
@@ -1128,14 +1183,16 @@ var VidPly = (() => {
1128
1183
  language: `<path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.91 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56-1.84-.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95c-.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z"/>`,
1129
1184
  hd: `<path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-8 12H9.5v-2h-2v2H6V9h1.5v2.5h2V9H11v6zm7-1c0 .55-.45 1-1 1h-.75v1.5h-1.5V15H14c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v4zm-3.5-.5h2v-3h-2v3z"/>`,
1130
1185
  transcript: `<path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/>`,
1131
- audioDescription: `<path d="M12 3v9.28c-.47-.17-.97-.28-1.5-.28C8.01 12 6 14.01 6 16.5S8.01 21 10.5 21c2.31 0 4.2-1.75 4.45-4H15V6h4V3h-7z"/><path d="M10.5 19c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>`,
1132
- audioDescriptionOn: `<path d="M12 3v9.28c-.47-.17-.97-.28-1.5-.28C8.01 12 6 14.01 6 16.5S8.01 21 10.5 21c2.31 0 4.2-1.75 4.45-4H15V6h4V3h-7z"/><path d="M10.5 19c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/><circle cx="19" cy="16" r="3" fill="#3b82f6"/><path d="M18.5 17.5l1-1 1.5 1.5" stroke="white" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>`,
1186
+ 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>`,
1187
+ 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>`,
1133
1188
  signLanguage: `<g transform="scale(1.5)"><path d="M16 11.3c-.1-.9-4.8 1.3-5.4 1.1-2.6-1 5.8-1.3 5.1-2.9s-5.1 1.5-6 1.4C6.5 9.4 16.5 9.1 13.5 8c-1.9-.6-8.8 2.9-6.8.4.7-.6.7-1.9-.7-1.7-9.7 7.2-.7 12.2 8.8 7 0-1.3-3.5.4-4.1.4-2.6 0 5.6-2 5.4-3ZM3.9 7.8c3.2-4.2 3.7 1.2 6 .1s.2-.2.2-.3c.7-2.7 2.5-7.5-1.5-1.3-1.6 0 1.1-4 1-4.6C8.9-1 7.3 4.4 7.2 4.9c-1.6.7-.9-1.4-.7-1.5 3-6-.6-3.1-.9.4-2.5 1.8 0-2.8 0-3.5C2.8-.9 4 9.4 1.1 4.9S.1 4.6 0 5c-.4 2.7 2.6 7.2 3.9 2.8Z"/></g>`,
1134
1189
  signLanguageOn: `<g transform="scale(1.5)"><path d="M16 11.3c-.1-.9-4.8 1.3-5.4 1.1-2.6-1 5.8-1.3 5.1-2.9s-5.1 1.5-6 1.4C6.5 9.4 16.5 9.1 13.5 8c-1.9-.6-8.8 2.9-6.8.4.7-.6.7-1.9-.7-1.7-9.7 7.2-.7 12.2 8.8 7 0-1.3-3.5.4-4.1.4-2.6 0 5.6-2 5.4-3ZM3.9 7.8c3.2-4.2 3.7 1.2 6 .1s.2-.2.2-.3c.7-2.7 2.5-7.5-1.5-1.3-1.6 0 1.1-4 1-4.6C8.9-1 7.3 4.4 7.2 4.9c-1.6.7-.9-1.4-.7-1.5 3-6-.6-3.1-.9.4-2.5 1.8 0-2.8 0-3.5C2.8-.9 4 9.4 1.1 4.9S.1 4.6 0 5c-.4 2.7 2.6 7.2 3.9 2.8Z"/></g>`,
1135
1190
  speaker: `<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/>`,
1136
1191
  music: `<path d="M12 3v9.28c-.47-.17-.97-.28-1.5-.28C8.01 12 6 14.01 6 16.5S8.01 21 10.5 21c2.31 0 4.2-1.75 4.45-4H15V6h4V3h-7zm-1.5 16c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>`,
1137
1192
  moreVertical: `<path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/>`,
1138
- moreHorizontal: `<path d="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/>`
1193
+ 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"/>`,
1194
+ 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"/>`,
1195
+ 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"/>`
1139
1196
  };
1140
1197
  var svgWrapper = (paths) => `<svg viewBox="0 0 24 24" fill="currentColor">${paths}</svg>`;
1141
1198
  var Icons = Object.fromEntries(
@@ -1431,7 +1488,11 @@ var VidPly = (() => {
1431
1488
  return false;
1432
1489
  }
1433
1490
  hasAudioDescription() {
1434
- return this.player.audioDescriptionSrc && this.player.audioDescriptionSrc.length > 0;
1491
+ if (this.player.audioDescriptionSrc && this.player.audioDescriptionSrc.length > 0) {
1492
+ return true;
1493
+ }
1494
+ const textTracks = Array.from(this.player.element.textTracks || []);
1495
+ return textTracks.some((track) => track.kind === "descriptions");
1435
1496
  }
1436
1497
  hasSignLanguage() {
1437
1498
  return this.player.signLanguageSrc && this.player.signLanguageSrc.length > 0;
@@ -2042,9 +2103,9 @@ var VidPly = (() => {
2042
2103
  i18n.t("styleLabels.fontSize"),
2043
2104
  "captionsFontSize",
2044
2105
  [
2045
- { label: i18n.t("fontSizes.small"), value: "80%" },
2046
- { label: i18n.t("fontSizes.medium"), value: "100%" },
2047
- { label: i18n.t("fontSizes.large"), value: "120%" },
2106
+ { label: i18n.t("fontSizes.small"), value: "87.5%" },
2107
+ { label: i18n.t("fontSizes.normal"), value: "100%" },
2108
+ { label: i18n.t("fontSizes.large"), value: "125%" },
2048
2109
  { label: i18n.t("fontSizes.xlarge"), value: "150%" }
2049
2110
  ]
2050
2111
  );
@@ -2733,6 +2794,141 @@ var VidPly = (() => {
2733
2794
  }
2734
2795
  };
2735
2796
 
2797
+ // src/utils/StorageManager.js
2798
+ var StorageManager = class {
2799
+ constructor(namespace = "vidply") {
2800
+ this.namespace = namespace;
2801
+ this.storage = this.isStorageAvailable() ? localStorage : null;
2802
+ }
2803
+ /**
2804
+ * Check if localStorage is available
2805
+ */
2806
+ isStorageAvailable() {
2807
+ try {
2808
+ const test = "__storage_test__";
2809
+ localStorage.setItem(test, test);
2810
+ localStorage.removeItem(test);
2811
+ return true;
2812
+ } catch (e) {
2813
+ return false;
2814
+ }
2815
+ }
2816
+ /**
2817
+ * Get a namespaced key
2818
+ */
2819
+ getKey(key) {
2820
+ return `${this.namespace}_${key}`;
2821
+ }
2822
+ /**
2823
+ * Save a value to storage
2824
+ */
2825
+ set(key, value) {
2826
+ if (!this.storage) return false;
2827
+ try {
2828
+ const namespacedKey = this.getKey(key);
2829
+ this.storage.setItem(namespacedKey, JSON.stringify(value));
2830
+ return true;
2831
+ } catch (e) {
2832
+ console.warn("Failed to save to localStorage:", e);
2833
+ return false;
2834
+ }
2835
+ }
2836
+ /**
2837
+ * Get a value from storage
2838
+ */
2839
+ get(key, defaultValue = null) {
2840
+ if (!this.storage) return defaultValue;
2841
+ try {
2842
+ const namespacedKey = this.getKey(key);
2843
+ const value = this.storage.getItem(namespacedKey);
2844
+ return value ? JSON.parse(value) : defaultValue;
2845
+ } catch (e) {
2846
+ console.warn("Failed to read from localStorage:", e);
2847
+ return defaultValue;
2848
+ }
2849
+ }
2850
+ /**
2851
+ * Remove a value from storage
2852
+ */
2853
+ remove(key) {
2854
+ if (!this.storage) return false;
2855
+ try {
2856
+ const namespacedKey = this.getKey(key);
2857
+ this.storage.removeItem(namespacedKey);
2858
+ return true;
2859
+ } catch (e) {
2860
+ console.warn("Failed to remove from localStorage:", e);
2861
+ return false;
2862
+ }
2863
+ }
2864
+ /**
2865
+ * Clear all namespaced values
2866
+ */
2867
+ clear() {
2868
+ if (!this.storage) return false;
2869
+ try {
2870
+ const keys = Object.keys(this.storage);
2871
+ keys.forEach((key) => {
2872
+ if (key.startsWith(this.namespace)) {
2873
+ this.storage.removeItem(key);
2874
+ }
2875
+ });
2876
+ return true;
2877
+ } catch (e) {
2878
+ console.warn("Failed to clear localStorage:", e);
2879
+ return false;
2880
+ }
2881
+ }
2882
+ /**
2883
+ * Save transcript preferences
2884
+ */
2885
+ saveTranscriptPreferences(preferences) {
2886
+ return this.set("transcript_preferences", preferences);
2887
+ }
2888
+ /**
2889
+ * Get transcript preferences
2890
+ */
2891
+ getTranscriptPreferences() {
2892
+ return this.get("transcript_preferences", null);
2893
+ }
2894
+ /**
2895
+ * Save caption preferences
2896
+ */
2897
+ saveCaptionPreferences(preferences) {
2898
+ return this.set("caption_preferences", preferences);
2899
+ }
2900
+ /**
2901
+ * Get caption preferences
2902
+ */
2903
+ getCaptionPreferences() {
2904
+ return this.get("caption_preferences", null);
2905
+ }
2906
+ /**
2907
+ * Save player preferences (volume, speed, etc.)
2908
+ */
2909
+ savePlayerPreferences(preferences) {
2910
+ return this.set("player_preferences", preferences);
2911
+ }
2912
+ /**
2913
+ * Get player preferences
2914
+ */
2915
+ getPlayerPreferences() {
2916
+ return this.get("player_preferences", null);
2917
+ }
2918
+ /**
2919
+ * Save sign language preferences (position and size)
2920
+ */
2921
+ saveSignLanguagePreferences(preferences) {
2922
+ return this.set("sign_language_preferences", preferences);
2923
+ }
2924
+ /**
2925
+ * Get sign language preferences
2926
+ */
2927
+ getSignLanguagePreferences() {
2928
+ return this.get("sign_language_preferences", null);
2929
+ }
2930
+ };
2931
+
2736
2932
  // src/controls/CaptionManager.js
2737
2933
  var CaptionManager = class {
2738
2934
  constructor(player) {
@@ -2741,8 +2937,29 @@ var VidPly = (() => {
2741
2937
  this.tracks = [];
2742
2938
  this.currentTrack = null;
2743
2939
  this.currentCue = null;
2940
+ this.storage = new StorageManager("vidply");
2941
+ this.loadSavedPreferences();
2744
2942
  this.init();
2745
2943
  }
2944
+ loadSavedPreferences() {
2945
+ const saved = this.storage.getCaptionPreferences();
2946
+ if (saved) {
2947
+ if (saved.fontSize) this.player.options.captionsFontSize = saved.fontSize;
2948
+ if (saved.fontFamily) this.player.options.captionsFontFamily = saved.fontFamily;
2949
+ if (saved.color) this.player.options.captionsColor = saved.color;
2950
+ if (saved.backgroundColor) this.player.options.captionsBackgroundColor = saved.backgroundColor;
2951
+ if (saved.opacity !== void 0) this.player.options.captionsOpacity = saved.opacity;
2952
+ }
2953
+ }
2954
+ saveCaptionPreferences() {
2955
+ this.storage.saveCaptionPreferences({
2956
+ fontSize: this.player.options.captionsFontSize,
2957
+ fontFamily: this.player.options.captionsFontFamily,
2958
+ color: this.player.options.captionsColor,
2959
+ backgroundColor: this.player.options.captionsBackgroundColor,
2960
+ opacity: this.player.options.captionsOpacity
2961
+ });
2962
+ }
2746
2963
  init() {
2747
2964
  this.createElement();
2748
2965
  this.loadTracks();
@@ -2891,6 +3108,7 @@ var VidPly = (() => {
2891
3108
  break;
2892
3109
  }
2893
3110
  this.updateStyles();
3111
+ this.saveCaptionPreferences();
2894
3112
  this.player.emit("captionschange");
2895
3113
  }
2896
3114
  getAvailableTracks() {
@@ -3101,11 +3319,36 @@ var VidPly = (() => {
3101
3319
  this.player = player;
3102
3320
  this.transcriptWindow = null;
3103
3321
  this.transcriptEntries = [];
3322
+ this.metadataCues = [];
3104
3323
  this.currentActiveEntry = null;
3105
3324
  this.isVisible = false;
3325
+ this.storage = new StorageManager("vidply");
3106
3326
  this.isDragging = false;
3107
3327
  this.dragOffsetX = 0;
3108
3328
  this.dragOffsetY = 0;
3329
+ this.isResizing = false;
3330
+ this.resizeDirection = null;
3331
+ this.resizeStartX = 0;
3332
+ this.resizeStartY = 0;
3333
+ this.resizeStartWidth = 0;
3334
+ this.resizeStartHeight = 0;
3335
+ this.resizeEnabled = false;
3336
+ this.settingsMenuVisible = false;
3337
+ this.settingsMenu = null;
3338
+ this.settingsButton = null;
3339
+ this.settingsMenuJustOpened = false;
3340
+ this.keyboardDragMode = false;
3341
+ this.styleDialog = null;
3342
+ this.styleDialogVisible = false;
3343
+ this.styleDialogJustOpened = false;
3344
+ const savedPreferences = this.storage.getTranscriptPreferences();
3345
+ this.transcriptStyle = {
3346
+ fontSize: (savedPreferences == null ? void 0 : savedPreferences.fontSize) || this.player.options.transcriptFontSize || "100%",
3347
+ fontFamily: (savedPreferences == null ? void 0 : savedPreferences.fontFamily) || this.player.options.transcriptFontFamily || "sans-serif",
3348
+ color: (savedPreferences == null ? void 0 : savedPreferences.color) || this.player.options.transcriptColor || "#ffffff",
3349
+ backgroundColor: (savedPreferences == null ? void 0 : savedPreferences.backgroundColor) || this.player.options.transcriptBackgroundColor || "#1e1e1e",
3350
+ opacity: (savedPreferences == null ? void 0 : savedPreferences.opacity) ?? this.player.options.transcriptOpacity ?? 0.98
3351
+ };
3109
3352
  this.handlers = {
3110
3353
  timeupdate: () => this.updateActiveEntry(),
3111
3354
  resize: null,
@@ -3115,7 +3358,11 @@ var VidPly = (() => {
3115
3358
  touchend: null,
3116
3359
  mousedown: null,
3117
3360
  touchstart: null,
3118
- keydown: null
3361
+ keydown: null,
3362
+ settingsClick: null,
3363
+ settingsKeydown: null,
3364
+ documentClick: null,
3365
+ styleDialogKeydown: null
3119
3366
  };
3120
3367
  this.init();
3121
3368
  }
@@ -3144,6 +3391,11 @@ var VidPly = (() => {
3144
3391
  if (this.transcriptWindow) {
3145
3392
  this.transcriptWindow.style.display = "flex";
3146
3393
  this.isVisible = true;
3394
+ setTimeout(() => {
3395
+ if (this.settingsButton) {
3396
+ this.settingsButton.focus();
3397
+ }
3398
+ }, 150);
3147
3399
  return;
3148
3400
  }
3149
3401
  this.createTranscriptWindow();
@@ -3151,6 +3403,11 @@ var VidPly = (() => {
3151
3403
  if (this.transcriptWindow) {
3152
3404
  this.transcriptWindow.style.display = "flex";
3153
3405
  setTimeout(() => this.positionTranscript(), 0);
3406
+ setTimeout(() => {
3407
+ if (this.settingsButton) {
3408
+ this.settingsButton.focus();
3409
+ }
3410
+ }, 150);
3154
3411
  }
3155
3412
  this.isVisible = true;
3156
3413
  }
@@ -3182,9 +3439,49 @@ var VidPly = (() => {
3182
3439
  "tabindex": "0"
3183
3440
  }
3184
3441
  });
3442
+ this.headerLeft = DOMUtils.createElement("div", {
3443
+ className: `${this.player.options.classPrefix}-transcript-header-left`
3444
+ });
3445
+ this.settingsButton = DOMUtils.createElement("button", {
3446
+ className: `${this.player.options.classPrefix}-transcript-settings`,
3447
+ attributes: {
3448
+ "type": "button",
3449
+ "aria-label": i18n.t("transcript.settings"),
3450
+ "aria-expanded": "false"
3451
+ }
3452
+ });
3453
+ this.settingsButton.appendChild(createIconElement("settings"));
3454
+ this.handlers.settingsClick = (e) => {
3455
+ e.preventDefault();
3456
+ e.stopPropagation();
3457
+ if (this.settingsMenuVisible) {
3458
+ this.hideSettingsMenu();
3459
+ } else {
3460
+ this.showSettingsMenu();
3461
+ }
3462
+ };
3463
+ this.settingsButton.addEventListener("click", this.handlers.settingsClick);
3464
+ this.handlers.settingsKeydown = (e) => {
3465
+ if (e.key === "d" || e.key === "D") {
3466
+ e.preventDefault();
3467
+ e.stopPropagation();
3468
+ this.toggleKeyboardDragMode();
3469
+ } else if (e.key === "r" || e.key === "R") {
3470
+ e.preventDefault();
3471
+ e.stopPropagation();
3472
+ this.toggleResizeMode();
3473
+ } else if (e.key === "Escape" && this.settingsMenuVisible) {
3474
+ e.preventDefault();
3475
+ e.stopPropagation();
3476
+ this.hideSettingsMenu();
3477
+ }
3478
+ };
3479
+ this.settingsButton.addEventListener("keydown", this.handlers.settingsKeydown);
3185
3480
  const title = DOMUtils.createElement("h3", {
3186
3481
  textContent: i18n.t("transcript.title")
3187
3482
  });
3483
+ this.headerLeft.appendChild(this.settingsButton);
3484
+ this.headerLeft.appendChild(title);
3188
3485
  const closeButton = DOMUtils.createElement("button", {
3189
3486
  className: `${this.player.options.classPrefix}-transcript-close`,
3190
3487
  attributes: {
@@ -3194,7 +3491,7 @@ var VidPly = (() => {
3194
3491
  });
3195
3492
  closeButton.appendChild(createIconElement("close"));
3196
3493
  closeButton.addEventListener("click", () => this.hideTranscript());
3197
- this.transcriptHeader.appendChild(title);
3494
+ this.transcriptHeader.appendChild(this.headerLeft);
3198
3495
  this.transcriptHeader.appendChild(closeButton);
3199
3496
  this.transcriptContent = DOMUtils.createElement("div", {
3200
3497
  className: `${this.player.options.classPrefix}-transcript-content`
@@ -3204,6 +3501,27 @@ var VidPly = (() => {
3204
3501
  this.player.container.appendChild(this.transcriptWindow);
3205
3502
  this.positionTranscript();
3206
3503
  this.setupDragAndDrop();
3504
+ this.handlers.documentClick = (e) => {
3505
+ if (this.settingsMenuJustOpened) {
3506
+ return;
3507
+ }
3508
+ if (this.styleDialogJustOpened) {
3509
+ return;
3510
+ }
3511
+ if (this.settingsButton && this.settingsButton.contains(e.target)) {
3512
+ return;
3513
+ }
3514
+ if (this.settingsMenu && this.settingsMenu.contains(e.target)) {
3515
+ return;
3516
+ }
3517
+ if (this.settingsMenuVisible) {
3518
+ this.hideSettingsMenu();
3519
+ }
3520
+ if (this.styleDialogVisible && this.styleDialog && !this.styleDialog.contains(e.target)) {
3521
+ this.hideStyleDialog();
3522
+ }
3523
+ };
3524
+ this.documentClickHandlerAdded = false;
3207
3525
  let resizeTimeout;
3208
3526
  this.handlers.resize = () => {
3209
3527
  clearTimeout(resizeTimeout);
@@ -3284,54 +3602,133 @@ var VidPly = (() => {
3284
3602
  this.transcriptEntries = [];
3285
3603
  this.transcriptContent.innerHTML = "";
3286
3604
  const textTracks = Array.from(this.player.element.textTracks);
3287
- const transcriptTrack = textTracks.find(
3605
+ const captionTrack = textTracks.find(
3288
3606
  (track) => track.kind === "captions" || track.kind === "subtitles"
3289
3607
  );
3290
- if (!transcriptTrack) {
3608
+ const descriptionTrack = textTracks.find((track) => track.kind === "descriptions");
3609
+ const metadataTrack = textTracks.find((track) => track.kind === "metadata");
3610
+ if (!captionTrack && !descriptionTrack && !metadataTrack) {
3291
3611
  this.showNoTranscriptMessage();
3292
3612
  return;
3293
3613
  }
3294
- if (transcriptTrack.mode === "disabled") {
3295
- transcriptTrack.mode = "hidden";
3296
- }
3297
- if (!transcriptTrack.cues || transcriptTrack.cues.length === 0) {
3614
+ const tracksToLoad = [captionTrack, descriptionTrack, metadataTrack].filter(Boolean);
3615
+ tracksToLoad.forEach((track) => {
3616
+ if (track.mode === "disabled") {
3617
+ track.mode = "hidden";
3618
+ }
3619
+ });
3620
+ const needsLoading = tracksToLoad.some((track) => !track.cues || track.cues.length === 0);
3621
+ if (needsLoading) {
3298
3622
  const loadingMessage = DOMUtils.createElement("div", {
3299
3623
  className: `${this.player.options.classPrefix}-transcript-loading`,
3300
3624
  textContent: i18n.t("transcript.loading")
3301
3625
  });
3302
3626
  this.transcriptContent.appendChild(loadingMessage);
3627
+ let loaded = 0;
3303
3628
  const onLoad = () => {
3304
- this.loadTranscriptData();
3305
- };
3306
- transcriptTrack.addEventListener("load", onLoad, { once: true });
3307
- setTimeout(() => {
3308
- if (transcriptTrack.cues && transcriptTrack.cues.length > 0) {
3629
+ loaded++;
3630
+ if (loaded >= tracksToLoad.length) {
3309
3631
  this.loadTranscriptData();
3310
3632
  }
3633
+ };
3634
+ tracksToLoad.forEach((track) => {
3635
+ track.addEventListener("load", onLoad, { once: true });
3636
+ });
3637
+ setTimeout(() => {
3638
+ this.loadTranscriptData();
3311
3639
  }, 500);
3312
3640
  return;
3313
3641
  }
3314
- const cues = Array.from(transcriptTrack.cues);
3315
- cues.forEach((cue, index) => {
3316
- const entry = this.createTranscriptEntry(cue, index);
3642
+ const allCues = [];
3643
+ if (captionTrack && captionTrack.cues) {
3644
+ Array.from(captionTrack.cues).forEach((cue) => {
3645
+ allCues.push({ cue, type: "caption" });
3646
+ });
3647
+ }
3648
+ if (descriptionTrack && descriptionTrack.cues) {
3649
+ Array.from(descriptionTrack.cues).forEach((cue) => {
3650
+ allCues.push({ cue, type: "description" });
3651
+ });
3652
+ }
3653
+ if (metadataTrack && metadataTrack.cues) {
3654
+ this.metadataCues = Array.from(metadataTrack.cues);
3655
+ this.setupMetadataHandling();
3656
+ }
3657
+ allCues.sort((a, b) => a.cue.startTime - b.cue.startTime);
3658
+ allCues.forEach((item, index) => {
3659
+ const entry = this.createTranscriptEntry(item.cue, index, item.type);
3317
3660
  this.transcriptEntries.push({
3318
3661
  element: entry,
3319
- cue,
3320
- startTime: cue.startTime,
3321
- endTime: cue.endTime
3662
+ cue: item.cue,
3663
+ type: item.type,
3664
+ startTime: item.cue.startTime,
3665
+ endTime: item.cue.endTime
3322
3666
  });
3323
3667
  this.transcriptContent.appendChild(entry);
3324
3668
  });
3669
+ this.applyTranscriptStyles();
3670
+ }
3671
+ /**
3672
+ * Setup metadata handling
3673
+ * Metadata cues are not displayed but can be used programmatically
3674
+ */
3675
+ setupMetadataHandling() {
3676
+ if (!this.metadataCues || this.metadataCues.length === 0) {
3677
+ return;
3678
+ }
3679
+ const textTracks = Array.from(this.player.element.textTracks);
3680
+ const metadataTrack = textTracks.find((track) => track.kind === "metadata");
3681
+ if (metadataTrack) {
3682
+ metadataTrack.addEventListener("cuechange", () => {
3683
+ const activeCues = Array.from(metadataTrack.activeCues || []);
3684
+ activeCues.forEach((cue) => {
3685
+ this.handleMetadataCue(cue);
3686
+ });
3687
+ });
3688
+ }
3689
+ }
3690
+ /**
3691
+ * Handle individual metadata cues
3692
+ * Parses metadata text and emits events or triggers actions
3693
+ */
3694
+ handleMetadataCue(cue) {
3695
+ const text = cue.text.trim();
3696
+ this.player.emit("metadata", {
3697
+ time: cue.startTime,
3698
+ endTime: cue.endTime,
3699
+ text,
3700
+ cue
3701
+ });
3702
+ if (text.includes("PAUSE")) {
3703
+ this.player.emit("metadata:pause", { time: cue.startTime, text });
3704
+ }
3705
+ const focusMatch = text.match(/FOCUS:([\w#-]+)/);
3706
+ if (focusMatch) {
3707
+ this.player.emit("metadata:focus", {
3708
+ time: cue.startTime,
3709
+ target: focusMatch[1],
3710
+ text
3711
+ });
3712
+ }
3713
+ const hashtags = text.match(/#[\w-]+/g);
3714
+ if (hashtags) {
3715
+ this.player.emit("metadata:hashtags", {
3716
+ time: cue.startTime,
3717
+ hashtags,
3718
+ text
3719
+ });
3720
+ }
3325
3721
  }
3326
3722
  /**
3327
3723
  * Create a single transcript entry element
3328
3724
  */
3329
- createTranscriptEntry(cue, index) {
3725
+ createTranscriptEntry(cue, index, type = "caption") {
3330
3726
  const entry = DOMUtils.createElement("div", {
3331
- className: `${this.player.options.classPrefix}-transcript-entry`,
3727
+ className: `${this.player.options.classPrefix}-transcript-entry ${this.player.options.classPrefix}-transcript-${type}`,
3332
3728
  attributes: {
3333
3729
  "data-start": String(cue.startTime),
3334
3730
  "data-end": String(cue.endTime),
3731
+ "data-type": type,
3335
3732
  "role": "button",
3336
3733
  "tabindex": "0"
3337
3734
  }
@@ -3428,6 +3825,15 @@ var VidPly = (() => {
3428
3825
  if (e.target.closest(`.${this.player.options.classPrefix}-transcript-close`)) {
3429
3826
  return;
3430
3827
  }
3828
+ if (e.target.closest(`.${this.player.options.classPrefix}-transcript-settings`)) {
3829
+ return;
3830
+ }
3831
+ if (e.target.closest(`.${this.player.options.classPrefix}-transcript-settings-menu`)) {
3832
+ return;
3833
+ }
3834
+ if (e.target.closest(`.${this.player.options.classPrefix}-transcript-style-dialog`)) {
3835
+ return;
3836
+ }
3431
3837
  this.startDragging(e.clientX, e.clientY);
3432
3838
  e.preventDefault();
3433
3839
  };
@@ -3445,6 +3851,15 @@ var VidPly = (() => {
3445
3851
  if (e.target.closest(`.${this.player.options.classPrefix}-transcript-close`)) {
3446
3852
  return;
3447
3853
  }
3854
+ if (e.target.closest(`.${this.player.options.classPrefix}-transcript-settings`)) {
3855
+ return;
3856
+ }
3857
+ if (e.target.closest(`.${this.player.options.classPrefix}-transcript-settings-menu`)) {
3858
+ return;
3859
+ }
3860
+ if (e.target.closest(`.${this.player.options.classPrefix}-transcript-style-dialog`)) {
3861
+ return;
3862
+ }
3448
3863
  const isMobile = window.innerWidth < 640;
3449
3864
  const isFullscreen = this.player.state.fullscreen;
3450
3865
  const touch = e.touches[0];
@@ -3471,49 +3886,64 @@ var VidPly = (() => {
3471
3886
  }
3472
3887
  };
3473
3888
  this.handlers.keydown = (e) => {
3474
- if (!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "Escape"].includes(e.key)) {
3889
+ if (["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(e.key)) {
3890
+ if (!this.keyboardDragMode) {
3891
+ return;
3892
+ }
3893
+ e.preventDefault();
3894
+ e.stopPropagation();
3895
+ const step = e.shiftKey ? 50 : 10;
3896
+ let currentLeft = parseFloat(this.transcriptWindow.style.left) || 0;
3897
+ let currentTop = parseFloat(this.transcriptWindow.style.top) || 0;
3898
+ const computedStyle = window.getComputedStyle(this.transcriptWindow);
3899
+ if (computedStyle.transform !== "none") {
3900
+ const rect = this.transcriptWindow.getBoundingClientRect();
3901
+ currentLeft = rect.left;
3902
+ currentTop = rect.top;
3903
+ this.transcriptWindow.style.transform = "none";
3904
+ this.transcriptWindow.style.left = `${currentLeft}px`;
3905
+ this.transcriptWindow.style.top = `${currentTop}px`;
3906
+ }
3907
+ let newX = currentLeft;
3908
+ let newY = currentTop;
3909
+ switch (e.key) {
3910
+ case "ArrowLeft":
3911
+ newX -= step;
3912
+ break;
3913
+ case "ArrowRight":
3914
+ newX += step;
3915
+ break;
3916
+ case "ArrowUp":
3917
+ newY -= step;
3918
+ break;
3919
+ case "ArrowDown":
3920
+ newY += step;
3921
+ break;
3922
+ }
3923
+ this.transcriptWindow.style.left = `${newX}px`;
3924
+ this.transcriptWindow.style.top = `${newY}px`;
3475
3925
  return;
3476
3926
  }
3477
- e.preventDefault();
3478
- e.stopPropagation();
3479
3927
  if (e.key === "Home") {
3928
+ e.preventDefault();
3929
+ e.stopPropagation();
3480
3930
  this.resetPosition();
3481
3931
  return;
3482
3932
  }
3483
3933
  if (e.key === "Escape") {
3484
- this.hideTranscript();
3934
+ e.preventDefault();
3935
+ e.stopPropagation();
3936
+ if (this.styleDialogVisible) {
3937
+ this.hideStyleDialog();
3938
+ } else if (this.keyboardDragMode) {
3939
+ this.disableKeyboardDragMode();
3940
+ } else if (this.settingsMenuVisible) {
3941
+ this.hideSettingsMenu();
3942
+ } else {
3943
+ this.hideTranscript();
3944
+ }
3485
3945
  return;
3486
3946
  }
3487
- const step = e.shiftKey ? 50 : 10;
3488
- let currentLeft = parseFloat(this.transcriptWindow.style.left) || 0;
3489
- let currentTop = parseFloat(this.transcriptWindow.style.top) || 0;
3490
- const computedStyle = window.getComputedStyle(this.transcriptWindow);
3491
- if (computedStyle.transform !== "none") {
3492
- const rect = this.transcriptWindow.getBoundingClientRect();
3493
- currentLeft = rect.left;
3494
- currentTop = rect.top;
3495
- this.transcriptWindow.style.transform = "none";
3496
- this.transcriptWindow.style.left = `${currentLeft}px`;
3497
- this.transcriptWindow.style.top = `${currentTop}px`;
3498
- }
3499
- let newX = currentLeft;
3500
- let newY = currentTop;
3501
- switch (e.key) {
3502
- case "ArrowLeft":
3503
- newX -= step;
3504
- break;
3505
- case "ArrowRight":
3506
- newX += step;
3507
- break;
3508
- case "ArrowUp":
3509
- newY -= step;
3510
- break;
3511
- case "ArrowDown":
3512
- newY += step;
3513
- break;
3514
- }
3515
- this.transcriptWindow.style.left = `${newX}px`;
3516
- this.transcriptWindow.style.top = `${newY}px`;
3517
3947
  };
3518
3948
  this.transcriptHeader.addEventListener("mousedown", this.handlers.mousedown);
3519
3949
  document.addEventListener("mousemove", this.handlers.mousemove);
@@ -3593,99 +4023,707 @@ var VidPly = (() => {
3593
4023
  this.transcriptWindow.style.transform = "translate(-50%, -50%)";
3594
4024
  }
3595
4025
  /**
3596
- * Cleanup
4026
+ * Toggle keyboard drag mode
3597
4027
  */
3598
- destroy() {
3599
- if (this.handlers.timeupdate) {
3600
- this.player.off("timeupdate", this.handlers.timeupdate);
3601
- }
3602
- if (this.transcriptHeader) {
3603
- if (this.handlers.mousedown) {
3604
- this.transcriptHeader.removeEventListener("mousedown", this.handlers.mousedown);
3605
- }
3606
- if (this.handlers.touchstart) {
3607
- this.transcriptHeader.removeEventListener("touchstart", this.handlers.touchstart);
3608
- }
3609
- if (this.handlers.keydown) {
3610
- this.transcriptHeader.removeEventListener("keydown", this.handlers.keydown);
3611
- }
3612
- }
3613
- if (this.handlers.mousemove) {
3614
- document.removeEventListener("mousemove", this.handlers.mousemove);
4028
+ toggleKeyboardDragMode() {
4029
+ if (this.keyboardDragMode) {
4030
+ this.disableKeyboardDragMode();
4031
+ } else {
4032
+ this.enableKeyboardDragMode();
3615
4033
  }
3616
- if (this.handlers.mouseup) {
3617
- document.removeEventListener("mouseup", this.handlers.mouseup);
4034
+ }
4035
+ /**
4036
+ * Enable keyboard drag mode
4037
+ */
4038
+ enableKeyboardDragMode() {
4039
+ this.keyboardDragMode = true;
4040
+ this.transcriptWindow.classList.add(`${this.player.options.classPrefix}-transcript-keyboard-drag`);
4041
+ if (this.settingsButton) {
4042
+ this.settingsButton.setAttribute("aria-label", "Keyboard drag mode active. Use arrow keys to move window. Press D or Escape to exit.");
3618
4043
  }
3619
- if (this.handlers.touchmove) {
3620
- document.removeEventListener("touchmove", this.handlers.touchmove);
4044
+ const indicator = DOMUtils.createElement("div", {
4045
+ className: `${this.player.options.classPrefix}-transcript-drag-indicator`,
4046
+ textContent: i18n.t("transcript.keyboardDragActive")
4047
+ });
4048
+ this.transcriptHeader.appendChild(indicator);
4049
+ if (this.settingsMenuVisible) {
4050
+ this.hideSettingsMenu();
3621
4051
  }
3622
- if (this.handlers.touchend) {
3623
- document.removeEventListener("touchend", this.handlers.touchend);
4052
+ this.transcriptHeader.focus();
4053
+ }
4054
+ /**
4055
+ * Disable keyboard drag mode
4056
+ */
4057
+ disableKeyboardDragMode() {
4058
+ this.keyboardDragMode = false;
4059
+ this.transcriptWindow.classList.remove(`${this.player.options.classPrefix}-transcript-keyboard-drag`);
4060
+ if (this.settingsButton) {
4061
+ this.settingsButton.setAttribute("aria-label", "Transcript settings. Press Enter to open menu, or D to enable drag mode");
3624
4062
  }
3625
- if (this.handlers.resize) {
3626
- window.removeEventListener("resize", this.handlers.resize);
4063
+ const indicator = this.transcriptHeader.querySelector(`.${this.player.options.classPrefix}-transcript-drag-indicator`);
4064
+ if (indicator) {
4065
+ indicator.remove();
3627
4066
  }
3628
- this.handlers = null;
3629
- if (this.transcriptWindow && this.transcriptWindow.parentNode) {
3630
- this.transcriptWindow.parentNode.removeChild(this.transcriptWindow);
4067
+ if (this.settingsButton) {
4068
+ this.settingsButton.focus();
3631
4069
  }
3632
- this.transcriptWindow = null;
3633
- this.transcriptHeader = null;
3634
- this.transcriptContent = null;
3635
- this.transcriptEntries = [];
3636
- }
3637
- };
3638
-
3639
- // src/core/Player.js
3640
- init_HTML5Renderer();
3641
-
3642
- // src/renderers/YouTubeRenderer.js
3643
- var YouTubeRenderer = class {
3644
- constructor(player) {
3645
- this.player = player;
3646
- this.youtube = null;
3647
- this.videoId = null;
3648
- this.isReady = false;
3649
- this.iframe = null;
3650
4070
  }
3651
- async init() {
3652
- this.videoId = this.extractVideoId(this.player.element.src);
3653
- if (!this.videoId) {
3654
- throw new Error("Invalid YouTube URL");
4071
+ /**
4072
+ * Toggle settings menu visibility
4073
+ */
4074
+ toggleSettingsMenu() {
4075
+ if (this.settingsMenuVisible) {
4076
+ this.hideSettingsMenu();
4077
+ } else {
4078
+ this.showSettingsMenu();
3655
4079
  }
3656
- await this.loadYouTubeAPI();
3657
- this.createIframe();
3658
- await this.initializePlayer();
3659
4080
  }
3660
- extractVideoId(url) {
3661
- const patterns = [
3662
- /(?:youtube\.com\/watch\?v=|youtu\.be\/)([^&\s]+)/,
3663
- /youtube\.com\/embed\/([^&\s]+)/
3664
- ];
3665
- for (const pattern of patterns) {
3666
- const match = url.match(pattern);
3667
- if (match && match[1]) {
3668
- return match[1];
3669
- }
4081
+ /**
4082
+ * Show settings menu
4083
+ */
4084
+ showSettingsMenu() {
4085
+ this.settingsMenuJustOpened = true;
4086
+ setTimeout(() => {
4087
+ this.settingsMenuJustOpened = false;
4088
+ }, 350);
4089
+ if (!this.documentClickHandlerAdded) {
4090
+ setTimeout(() => {
4091
+ document.addEventListener("click", this.handlers.documentClick);
4092
+ this.documentClickHandlerAdded = true;
4093
+ }, 300);
3670
4094
  }
3671
- return null;
3672
- }
3673
- async loadYouTubeAPI() {
3674
- if (window.YT && window.YT.Player) {
3675
- return Promise.resolve();
4095
+ if (this.settingsMenu) {
4096
+ this.settingsMenu.style.display = "block";
4097
+ this.settingsMenuVisible = true;
4098
+ return;
3676
4099
  }
3677
- return new Promise((resolve, reject) => {
3678
- if (window.onYouTubeIframeAPIReady) {
3679
- const originalCallback = window.onYouTubeIframeAPIReady;
3680
- window.onYouTubeIframeAPIReady = () => {
3681
- originalCallback();
3682
- resolve();
3683
- };
3684
- return;
4100
+ this.settingsMenu = DOMUtils.createElement("div", {
4101
+ className: `${this.player.options.classPrefix}-transcript-settings-menu`
4102
+ });
4103
+ const keyboardDragOption = DOMUtils.createElement("button", {
4104
+ className: `${this.player.options.classPrefix}-transcript-settings-item`,
4105
+ attributes: {
4106
+ "type": "button",
4107
+ "aria-label": i18n.t("transcript.keyboardDragMode")
3685
4108
  }
3686
- const tag = document.createElement("script");
3687
- tag.src = "https://www.youtube.com/iframe_api";
3688
- window.onYouTubeIframeAPIReady = () => {
4109
+ });
4110
+ const keyboardIcon = createIconElement("move");
4111
+ const keyboardText = DOMUtils.createElement("span", {
4112
+ textContent: i18n.t("transcript.keyboardDragMode")
4113
+ });
4114
+ keyboardDragOption.appendChild(keyboardIcon);
4115
+ keyboardDragOption.appendChild(keyboardText);
4116
+ keyboardDragOption.addEventListener("click", () => {
4117
+ this.toggleKeyboardDragMode();
4118
+ this.hideSettingsMenu();
4119
+ });
4120
+ const styleOption = DOMUtils.createElement("button", {
4121
+ className: `${this.player.options.classPrefix}-transcript-settings-item`,
4122
+ attributes: {
4123
+ "type": "button",
4124
+ "aria-label": i18n.t("transcript.styleTranscript")
4125
+ }
4126
+ });
4127
+ const styleIcon = createIconElement("settings");
4128
+ const styleText = DOMUtils.createElement("span", {
4129
+ textContent: i18n.t("transcript.styleTranscript")
4130
+ });
4131
+ styleOption.appendChild(styleIcon);
4132
+ styleOption.appendChild(styleText);
4133
+ styleOption.addEventListener("click", (e) => {
4134
+ e.preventDefault();
4135
+ e.stopPropagation();
4136
+ this.hideSettingsMenu();
4137
+ setTimeout(() => {
4138
+ this.showStyleDialog();
4139
+ }, 50);
4140
+ });
4141
+ const resizeOption = DOMUtils.createElement("button", {
4142
+ className: `${this.player.options.classPrefix}-transcript-settings-item`,
4143
+ attributes: {
4144
+ "type": "button",
4145
+ "aria-label": i18n.t("transcript.resizeWindow")
4146
+ }
4147
+ });
4148
+ const resizeIcon = createIconElement("resize");
4149
+ const resizeText = DOMUtils.createElement("span", {
4150
+ textContent: i18n.t("transcript.resizeWindow")
4151
+ });
4152
+ resizeOption.appendChild(resizeIcon);
4153
+ resizeOption.appendChild(resizeText);
4154
+ resizeOption.addEventListener("click", () => {
4155
+ this.toggleResizeMode();
4156
+ this.hideSettingsMenu();
4157
+ });
4158
+ const closeOption = DOMUtils.createElement("button", {
4159
+ className: `${this.player.options.classPrefix}-transcript-settings-item`,
4160
+ attributes: {
4161
+ "type": "button",
4162
+ "aria-label": i18n.t("transcript.closeMenu")
4163
+ }
4164
+ });
4165
+ const closeIcon = createIconElement("close");
4166
+ const closeText = DOMUtils.createElement("span", {
4167
+ textContent: i18n.t("transcript.closeMenu")
4168
+ });
4169
+ closeOption.appendChild(closeIcon);
4170
+ closeOption.appendChild(closeText);
4171
+ closeOption.addEventListener("click", () => {
4172
+ this.hideSettingsMenu();
4173
+ });
4174
+ this.settingsMenu.appendChild(keyboardDragOption);
4175
+ this.settingsMenu.appendChild(resizeOption);
4176
+ this.settingsMenu.appendChild(styleOption);
4177
+ this.settingsMenu.appendChild(closeOption);
4178
+ if (this.headerLeft) {
4179
+ this.headerLeft.appendChild(this.settingsMenu);
4180
+ } else {
4181
+ this.transcriptHeader.appendChild(this.settingsMenu);
4182
+ }
4183
+ this.settingsMenuVisible = true;
4184
+ this.settingsMenu.style.display = "block";
4185
+ if (this.settingsButton) {
4186
+ this.settingsButton.setAttribute("aria-expanded", "true");
4187
+ }
4188
+ setTimeout(() => {
4189
+ const firstItem = this.settingsMenu.querySelector(`.${this.player.options.classPrefix}-transcript-settings-item`);
4190
+ if (firstItem) {
4191
+ firstItem.focus();
4192
+ }
4193
+ }, 0);
4194
+ }
4195
+ /**
4196
+ * Hide settings menu
4197
+ */
4198
+ hideSettingsMenu() {
4199
+ if (this.settingsMenu) {
4200
+ this.settingsMenu.style.display = "none";
4201
+ this.settingsMenuVisible = false;
4202
+ this.settingsMenuJustOpened = false;
4203
+ if (this.settingsButton) {
4204
+ this.settingsButton.setAttribute("aria-expanded", "false");
4205
+ this.settingsButton.focus();
4206
+ }
4207
+ }
4208
+ }
4209
+ /**
4210
+ * Enable move mode (gives visual feedback)
4211
+ */
4212
+ enableMoveMode() {
4213
+ this.transcriptWindow.classList.add(`${this.player.options.classPrefix}-transcript-move-mode`);
4214
+ const tooltip = DOMUtils.createElement("div", {
4215
+ className: `${this.player.options.classPrefix}-transcript-move-tooltip`,
4216
+ textContent: "Drag with mouse or press D for keyboard drag mode"
4217
+ });
4218
+ this.transcriptHeader.appendChild(tooltip);
4219
+ setTimeout(() => {
4220
+ this.transcriptWindow.classList.remove(`${this.player.options.classPrefix}-transcript-move-mode`);
4221
+ if (tooltip.parentNode) {
4222
+ tooltip.remove();
4223
+ }
4224
+ }, 2e3);
4225
+ }
4226
+ /**
4227
+ * Toggle resize mode
4228
+ */
4229
+ toggleResizeMode() {
4230
+ this.resizeEnabled = !this.resizeEnabled;
4231
+ if (this.resizeEnabled) {
4232
+ this.enableResizeHandles();
4233
+ } else {
4234
+ this.disableResizeHandles();
4235
+ }
4236
+ }
4237
+ /**
4238
+ * Enable resize handles
4239
+ */
4240
+ enableResizeHandles() {
4241
+ if (!this.transcriptWindow) return;
4242
+ const directions = ["n", "s", "e", "w", "ne", "nw", "se", "sw"];
4243
+ directions.forEach((direction) => {
4244
+ const handle = DOMUtils.createElement("div", {
4245
+ className: `${this.player.options.classPrefix}-transcript-resize-handle ${this.player.options.classPrefix}-transcript-resize-${direction}`,
4246
+ attributes: {
4247
+ "data-direction": direction
4248
+ }
4249
+ });
4250
+ handle.addEventListener("mousedown", (e) => this.startResize(e, direction));
4251
+ handle.addEventListener("touchstart", (e) => this.startResize(e.touches[0], direction));
4252
+ this.transcriptWindow.appendChild(handle);
4253
+ });
4254
+ this.transcriptWindow.classList.add(`${this.player.options.classPrefix}-transcript-resizable`);
4255
+ this.handlers.resizeMove = (e) => {
4256
+ if (this.isResizing) {
4257
+ this.performResize(e.clientX, e.clientY);
4258
+ }
4259
+ };
4260
+ this.handlers.resizeEnd = () => {
4261
+ if (this.isResizing) {
4262
+ this.stopResize();
4263
+ }
4264
+ };
4265
+ this.handlers.resizeTouchMove = (e) => {
4266
+ if (this.isResizing) {
4267
+ this.performResize(e.touches[0].clientX, e.touches[0].clientY);
4268
+ e.preventDefault();
4269
+ }
4270
+ };
4271
+ document.addEventListener("mousemove", this.handlers.resizeMove);
4272
+ document.addEventListener("mouseup", this.handlers.resizeEnd);
4273
+ document.addEventListener("touchmove", this.handlers.resizeTouchMove);
4274
+ document.addEventListener("touchend", this.handlers.resizeEnd);
4275
+ }
4276
+ /**
4277
+ * Disable resize handles
4278
+ */
4279
+ disableResizeHandles() {
4280
+ if (!this.transcriptWindow) return;
4281
+ const handles = this.transcriptWindow.querySelectorAll(`.${this.player.options.classPrefix}-transcript-resize-handle`);
4282
+ handles.forEach((handle) => handle.remove());
4283
+ this.transcriptWindow.classList.remove(`${this.player.options.classPrefix}-transcript-resizable`);
4284
+ if (this.handlers.resizeMove) {
4285
+ document.removeEventListener("mousemove", this.handlers.resizeMove);
4286
+ }
4287
+ if (this.handlers.resizeEnd) {
4288
+ document.removeEventListener("mouseup", this.handlers.resizeEnd);
4289
+ }
4290
+ if (this.handlers.resizeTouchMove) {
4291
+ document.removeEventListener("touchmove", this.handlers.resizeTouchMove);
4292
+ }
4293
+ document.removeEventListener("touchend", this.handlers.resizeEnd);
4294
+ }
4295
+ /**
4296
+ * Start resizing
4297
+ */
4298
+ startResize(e, direction) {
4299
+ e.stopPropagation();
4300
+ e.preventDefault();
4301
+ this.isResizing = true;
4302
+ this.resizeDirection = direction;
4303
+ this.resizeStartX = e.clientX;
4304
+ this.resizeStartY = e.clientY;
4305
+ const rect = this.transcriptWindow.getBoundingClientRect();
4306
+ this.resizeStartWidth = rect.width;
4307
+ this.resizeStartHeight = rect.height;
4308
+ this.transcriptWindow.classList.add(`${this.player.options.classPrefix}-transcript-resizing`);
4309
+ document.body.style.cursor = this.getResizeCursor(direction);
4310
+ document.body.style.userSelect = "none";
4311
+ }
4312
+ /**
4313
+ * Perform resize
4314
+ */
4315
+ performResize(clientX, clientY) {
4316
+ if (!this.isResizing) return;
4317
+ const deltaX = clientX - this.resizeStartX;
4318
+ const deltaY = clientY - this.resizeStartY;
4319
+ let newWidth = this.resizeStartWidth;
4320
+ let newHeight = this.resizeStartHeight;
4321
+ const direction = this.resizeDirection;
4322
+ if (direction.includes("e")) {
4323
+ newWidth = this.resizeStartWidth + deltaX;
4324
+ }
4325
+ if (direction.includes("w")) {
4326
+ newWidth = this.resizeStartWidth - deltaX;
4327
+ }
4328
+ if (direction.includes("s")) {
4329
+ newHeight = this.resizeStartHeight + deltaY;
4330
+ }
4331
+ if (direction.includes("n")) {
4332
+ newHeight = this.resizeStartHeight - deltaY;
4333
+ }
4334
+ const minWidth = 300;
4335
+ const minHeight = 200;
4336
+ const maxWidth = window.innerWidth - 40;
4337
+ const maxHeight = window.innerHeight - 40;
4338
+ newWidth = Math.max(minWidth, Math.min(newWidth, maxWidth));
4339
+ newHeight = Math.max(minHeight, Math.min(newHeight, maxHeight));
4340
+ this.transcriptWindow.style.width = `${newWidth}px`;
4341
+ this.transcriptWindow.style.height = `${newHeight}px`;
4342
+ this.transcriptWindow.style.maxWidth = `${newWidth}px`;
4343
+ this.transcriptWindow.style.maxHeight = `${newHeight}px`;
4344
+ if (direction.includes("w")) {
4345
+ const currentLeft = parseFloat(this.transcriptWindow.style.left) || 0;
4346
+ this.transcriptWindow.style.left = `${currentLeft + (this.resizeStartWidth - newWidth)}px`;
4347
+ }
4348
+ if (direction.includes("n")) {
4349
+ const currentTop = parseFloat(this.transcriptWindow.style.top) || 0;
4350
+ this.transcriptWindow.style.top = `${currentTop + (this.resizeStartHeight - newHeight)}px`;
4351
+ }
4352
+ }
4353
+ /**
4354
+ * Stop resizing
4355
+ */
4356
+ stopResize() {
4357
+ this.isResizing = false;
4358
+ this.resizeDirection = null;
4359
+ this.transcriptWindow.classList.remove(`${this.player.options.classPrefix}-transcript-resizing`);
4360
+ document.body.style.cursor = "";
4361
+ document.body.style.userSelect = "";
4362
+ }
4363
+ /**
4364
+ * Get cursor style for resize direction
4365
+ */
4366
+ getResizeCursor(direction) {
4367
+ const cursors = {
4368
+ "n": "ns-resize",
4369
+ "s": "ns-resize",
4370
+ "e": "ew-resize",
4371
+ "w": "ew-resize",
4372
+ "ne": "nesw-resize",
4373
+ "nw": "nwse-resize",
4374
+ "se": "nwse-resize",
4375
+ "sw": "nesw-resize"
4376
+ };
4377
+ return cursors[direction] || "default";
4378
+ }
4379
+ /**
4380
+ * Show style dialog
4381
+ */
4382
+ showStyleDialog() {
4383
+ if (this.styleDialog) {
4384
+ this.styleDialog.style.display = "block";
4385
+ this.styleDialogVisible = true;
4386
+ this.styleDialogJustOpened = true;
4387
+ setTimeout(() => {
4388
+ this.styleDialogJustOpened = false;
4389
+ }, 350);
4390
+ setTimeout(() => {
4391
+ const firstSelect = this.styleDialog.querySelector("select, input");
4392
+ if (firstSelect) {
4393
+ firstSelect.focus();
4394
+ }
4395
+ }, 0);
4396
+ return;
4397
+ }
4398
+ this.styleDialog = DOMUtils.createElement("div", {
4399
+ className: `${this.player.options.classPrefix}-transcript-style-dialog`
4400
+ });
4401
+ const title = DOMUtils.createElement("h4", {
4402
+ textContent: i18n.t("transcript.styleTitle"),
4403
+ className: `${this.player.options.classPrefix}-transcript-style-title`
4404
+ });
4405
+ this.styleDialog.appendChild(title);
4406
+ const fontSizeControl = this.createStyleSelectControl(
4407
+ i18n.t("captions.fontSize"),
4408
+ "fontSize",
4409
+ [
4410
+ { label: i18n.t("fontSizes.small"), value: "87.5%" },
4411
+ { label: i18n.t("fontSizes.normal"), value: "100%" },
4412
+ { label: i18n.t("fontSizes.large"), value: "125%" },
4413
+ { label: i18n.t("fontSizes.xlarge"), value: "150%" }
4414
+ ]
4415
+ );
4416
+ this.styleDialog.appendChild(fontSizeControl);
4417
+ const fontFamilyControl = this.createStyleSelectControl(
4418
+ i18n.t("captions.fontFamily"),
4419
+ "fontFamily",
4420
+ [
4421
+ { label: i18n.t("fontFamilies.sansSerif"), value: "sans-serif" },
4422
+ { label: i18n.t("fontFamilies.serif"), value: "serif" },
4423
+ { label: i18n.t("fontFamilies.monospace"), value: "monospace" }
4424
+ ]
4425
+ );
4426
+ this.styleDialog.appendChild(fontFamilyControl);
4427
+ const colorControl = this.createStyleColorControl(i18n.t("captions.color"), "color");
4428
+ this.styleDialog.appendChild(colorControl);
4429
+ const bgColorControl = this.createStyleColorControl(i18n.t("captions.backgroundColor"), "backgroundColor");
4430
+ this.styleDialog.appendChild(bgColorControl);
4431
+ const opacityControl = this.createStyleOpacityControl(i18n.t("captions.opacity"), "opacity");
4432
+ this.styleDialog.appendChild(opacityControl);
4433
+ const closeBtn = DOMUtils.createElement("button", {
4434
+ className: `${this.player.options.classPrefix}-transcript-style-close`,
4435
+ textContent: i18n.t("settings.close"),
4436
+ attributes: {
4437
+ "type": "button"
4438
+ }
4439
+ });
4440
+ closeBtn.addEventListener("click", () => this.hideStyleDialog());
4441
+ this.styleDialog.appendChild(closeBtn);
4442
+ this.handlers.styleDialogKeydown = (e) => {
4443
+ if (e.key === "Escape") {
4444
+ e.preventDefault();
4445
+ e.stopPropagation();
4446
+ this.hideStyleDialog();
4447
+ }
4448
+ };
4449
+ this.styleDialog.addEventListener("keydown", this.handlers.styleDialogKeydown);
4450
+ if (this.headerLeft) {
4451
+ this.headerLeft.appendChild(this.styleDialog);
4452
+ } else {
4453
+ this.transcriptHeader.appendChild(this.styleDialog);
4454
+ }
4455
+ this.applyTranscriptStyles();
4456
+ this.styleDialogVisible = true;
4457
+ this.styleDialog.style.display = "block";
4458
+ this.styleDialogJustOpened = true;
4459
+ setTimeout(() => {
4460
+ this.styleDialogJustOpened = false;
4461
+ }, 350);
4462
+ setTimeout(() => {
4463
+ const firstSelect = this.styleDialog.querySelector("select, input");
4464
+ if (firstSelect) {
4465
+ firstSelect.focus();
4466
+ }
4467
+ }, 0);
4468
+ }
4469
+ /**
4470
+ * Hide style dialog
4471
+ */
4472
+ hideStyleDialog() {
4473
+ if (this.styleDialog) {
4474
+ this.styleDialog.style.display = "none";
4475
+ this.styleDialogVisible = false;
4476
+ if (this.settingsButton) {
4477
+ this.settingsButton.focus();
4478
+ }
4479
+ }
4480
+ }
4481
+ /**
4482
+ * Create style select control
4483
+ */
4484
+ createStyleSelectControl(label, property, options) {
4485
+ const group = DOMUtils.createElement("div", {
4486
+ className: `${this.player.options.classPrefix}-transcript-style-group`
4487
+ });
4488
+ const labelEl = DOMUtils.createElement("label", {
4489
+ textContent: label
4490
+ });
4491
+ group.appendChild(labelEl);
4492
+ const select = DOMUtils.createElement("select", {
4493
+ className: `${this.player.options.classPrefix}-transcript-style-select`
4494
+ });
4495
+ options.forEach((opt) => {
4496
+ const option = DOMUtils.createElement("option", {
4497
+ textContent: opt.label,
4498
+ attributes: {
4499
+ "value": opt.value
4500
+ }
4501
+ });
4502
+ if (this.transcriptStyle[property] === opt.value) {
4503
+ option.selected = true;
4504
+ }
4505
+ select.appendChild(option);
4506
+ });
4507
+ select.addEventListener("change", (e) => {
4508
+ this.transcriptStyle[property] = e.target.value;
4509
+ this.applyTranscriptStyles();
4510
+ this.savePreferences();
4511
+ });
4512
+ group.appendChild(select);
4513
+ return group;
4514
+ }
4515
+ /**
4516
+ * Create style color control
4517
+ */
4518
+ createStyleColorControl(label, property) {
4519
+ const group = DOMUtils.createElement("div", {
4520
+ className: `${this.player.options.classPrefix}-transcript-style-group`
4521
+ });
4522
+ const labelEl = DOMUtils.createElement("label", {
4523
+ textContent: label
4524
+ });
4525
+ group.appendChild(labelEl);
4526
+ const input = DOMUtils.createElement("input", {
4527
+ attributes: {
4528
+ "type": "color",
4529
+ "value": this.transcriptStyle[property]
4530
+ },
4531
+ className: `${this.player.options.classPrefix}-transcript-style-color`
4532
+ });
4533
+ input.addEventListener("input", (e) => {
4534
+ this.transcriptStyle[property] = e.target.value;
4535
+ this.applyTranscriptStyles();
4536
+ this.savePreferences();
4537
+ });
4538
+ group.appendChild(input);
4539
+ return group;
4540
+ }
4541
+ /**
4542
+ * Create style opacity control
4543
+ */
4544
+ createStyleOpacityControl(label, property) {
4545
+ const group = DOMUtils.createElement("div", {
4546
+ className: `${this.player.options.classPrefix}-transcript-style-group`
4547
+ });
4548
+ const labelEl = DOMUtils.createElement("label", {
4549
+ textContent: label
4550
+ });
4551
+ group.appendChild(labelEl);
4552
+ const valueDisplay = DOMUtils.createElement("span", {
4553
+ textContent: Math.round(this.transcriptStyle[property] * 100) + "%",
4554
+ className: `${this.player.options.classPrefix}-transcript-style-value`
4555
+ });
4556
+ const input = DOMUtils.createElement("input", {
4557
+ attributes: {
4558
+ "type": "range",
4559
+ "min": "0",
4560
+ "max": "1",
4561
+ "step": "0.1",
4562
+ "value": String(this.transcriptStyle[property])
4563
+ },
4564
+ className: `${this.player.options.classPrefix}-transcript-style-range`
4565
+ });
4566
+ input.addEventListener("input", (e) => {
4567
+ const value = parseFloat(e.target.value);
4568
+ this.transcriptStyle[property] = value;
4569
+ valueDisplay.textContent = Math.round(value * 100) + "%";
4570
+ this.applyTranscriptStyles();
4571
+ this.savePreferences();
4572
+ });
4573
+ const inputContainer = DOMUtils.createElement("div", {
4574
+ className: `${this.player.options.classPrefix}-transcript-style-range-container`
4575
+ });
4576
+ inputContainer.appendChild(input);
4577
+ inputContainer.appendChild(valueDisplay);
4578
+ group.appendChild(labelEl);
4579
+ group.appendChild(inputContainer);
4580
+ return group;
4581
+ }
4582
+ /**
4583
+ * Save transcript preferences to localStorage
4584
+ */
4585
+ savePreferences() {
4586
+ this.storage.saveTranscriptPreferences(this.transcriptStyle);
4587
+ }
4588
+ /**
4589
+ * Apply transcript styles
4590
+ */
4591
+ applyTranscriptStyles() {
4592
+ if (!this.transcriptWindow) return;
4593
+ this.transcriptWindow.style.backgroundColor = this.transcriptStyle.backgroundColor;
4594
+ this.transcriptWindow.style.opacity = String(this.transcriptStyle.opacity);
4595
+ if (this.transcriptContent) {
4596
+ this.transcriptContent.style.fontSize = this.transcriptStyle.fontSize;
4597
+ this.transcriptContent.style.fontFamily = this.transcriptStyle.fontFamily;
4598
+ this.transcriptContent.style.color = this.transcriptStyle.color;
4599
+ }
4600
+ const textEntries = this.transcriptWindow.querySelectorAll(`.${this.player.options.classPrefix}-transcript-text`);
4601
+ textEntries.forEach((entry) => {
4602
+ entry.style.fontSize = this.transcriptStyle.fontSize;
4603
+ entry.style.fontFamily = this.transcriptStyle.fontFamily;
4604
+ entry.style.color = this.transcriptStyle.color;
4605
+ });
4606
+ const timeEntries = this.transcriptWindow.querySelectorAll(`.${this.player.options.classPrefix}-transcript-time`);
4607
+ timeEntries.forEach((entry) => {
4608
+ entry.style.fontFamily = this.transcriptStyle.fontFamily;
4609
+ });
4610
+ }
4611
+ /**
4612
+ * Cleanup
4613
+ */
4614
+ destroy() {
4615
+ if (this.resizeEnabled) {
4616
+ this.disableResizeHandles();
4617
+ }
4618
+ if (this.keyboardDragMode) {
4619
+ this.disableKeyboardDragMode();
4620
+ }
4621
+ if (this.handlers.timeupdate) {
4622
+ this.player.off("timeupdate", this.handlers.timeupdate);
4623
+ }
4624
+ if (this.transcriptHeader) {
4625
+ if (this.handlers.mousedown) {
4626
+ this.transcriptHeader.removeEventListener("mousedown", this.handlers.mousedown);
4627
+ }
4628
+ if (this.handlers.touchstart) {
4629
+ this.transcriptHeader.removeEventListener("touchstart", this.handlers.touchstart);
4630
+ }
4631
+ if (this.handlers.keydown) {
4632
+ this.transcriptHeader.removeEventListener("keydown", this.handlers.keydown);
4633
+ }
4634
+ }
4635
+ if (this.settingsButton) {
4636
+ if (this.handlers.settingsClick) {
4637
+ this.settingsButton.removeEventListener("click", this.handlers.settingsClick);
4638
+ }
4639
+ if (this.handlers.settingsKeydown) {
4640
+ this.settingsButton.removeEventListener("keydown", this.handlers.settingsKeydown);
4641
+ }
4642
+ }
4643
+ if (this.styleDialog && this.handlers.styleDialogKeydown) {
4644
+ this.styleDialog.removeEventListener("keydown", this.handlers.styleDialogKeydown);
4645
+ }
4646
+ if (this.handlers.mousemove) {
4647
+ document.removeEventListener("mousemove", this.handlers.mousemove);
4648
+ }
4649
+ if (this.handlers.mouseup) {
4650
+ document.removeEventListener("mouseup", this.handlers.mouseup);
4651
+ }
4652
+ if (this.handlers.touchmove) {
4653
+ document.removeEventListener("touchmove", this.handlers.touchmove);
4654
+ }
4655
+ if (this.handlers.touchend) {
4656
+ document.removeEventListener("touchend", this.handlers.touchend);
4657
+ }
4658
+ if (this.handlers.documentClick) {
4659
+ document.removeEventListener("click", this.handlers.documentClick);
4660
+ }
4661
+ if (this.handlers.resize) {
4662
+ window.removeEventListener("resize", this.handlers.resize);
4663
+ }
4664
+ this.handlers = null;
4665
+ if (this.transcriptWindow && this.transcriptWindow.parentNode) {
4666
+ this.transcriptWindow.parentNode.removeChild(this.transcriptWindow);
4667
+ }
4668
+ this.transcriptWindow = null;
4669
+ this.transcriptHeader = null;
4670
+ this.transcriptContent = null;
4671
+ this.transcriptEntries = [];
4672
+ this.settingsMenu = null;
4673
+ this.styleDialog = null;
4674
+ }
4675
+ };
4676
+
4677
+ // src/core/Player.js
4678
+ init_HTML5Renderer();
4679
+
4680
+ // src/renderers/YouTubeRenderer.js
4681
+ var YouTubeRenderer = class {
4682
+ constructor(player) {
4683
+ this.player = player;
4684
+ this.youtube = null;
4685
+ this.videoId = null;
4686
+ this.isReady = false;
4687
+ this.iframe = null;
4688
+ }
4689
+ async init() {
4690
+ this.videoId = this.extractVideoId(this.player.element.src);
4691
+ if (!this.videoId) {
4692
+ throw new Error("Invalid YouTube URL");
4693
+ }
4694
+ await this.loadYouTubeAPI();
4695
+ this.createIframe();
4696
+ await this.initializePlayer();
4697
+ }
4698
+ extractVideoId(url) {
4699
+ const patterns = [
4700
+ /(?:youtube\.com\/watch\?v=|youtu\.be\/)([^&\s]+)/,
4701
+ /youtube\.com\/embed\/([^&\s]+)/
4702
+ ];
4703
+ for (const pattern of patterns) {
4704
+ const match = url.match(pattern);
4705
+ if (match && match[1]) {
4706
+ return match[1];
4707
+ }
4708
+ }
4709
+ return null;
4710
+ }
4711
+ async loadYouTubeAPI() {
4712
+ if (window.YT && window.YT.Player) {
4713
+ return Promise.resolve();
4714
+ }
4715
+ return new Promise((resolve, reject) => {
4716
+ if (window.onYouTubeIframeAPIReady) {
4717
+ const originalCallback = window.onYouTubeIframeAPIReady;
4718
+ window.onYouTubeIframeAPIReady = () => {
4719
+ originalCallback();
4720
+ resolve();
4721
+ };
4722
+ return;
4723
+ }
4724
+ const tag = document.createElement("script");
4725
+ tag.src = "https://www.youtube.com/iframe_api";
4726
+ window.onYouTubeIframeAPIReady = () => {
3689
4727
  resolve();
3690
4728
  };
3691
4729
  tag.onerror = () => reject(new Error("Failed to load YouTube API"));
@@ -4350,7 +5388,7 @@ var VidPly = (() => {
4350
5388
  captionsButton: true,
4351
5389
  transcriptButton: true,
4352
5390
  fullscreenButton: true,
4353
- pipButton: true,
5391
+ pipButton: false,
4354
5392
  // Seeking
4355
5393
  seekInterval: 10,
4356
5394
  seekIntervalLarge: 30,
@@ -4420,6 +5458,13 @@ var VidPly = (() => {
4420
5458
  onError: null,
4421
5459
  ...options
4422
5460
  };
5461
+ this.storage = new StorageManager("vidply");
5462
+ const savedPrefs = this.storage.getPlayerPreferences();
5463
+ if (savedPrefs) {
5464
+ if (savedPrefs.volume !== void 0) this.options.volume = savedPrefs.volume;
5465
+ if (savedPrefs.playbackSpeed !== void 0) this.options.playbackSpeed = savedPrefs.playbackSpeed;
5466
+ if (savedPrefs.muted !== void 0) this.options.muted = savedPrefs.muted;
5467
+ }
4423
5468
  this.state = {
4424
5469
  ready: false,
4425
5470
  playing: false,
@@ -4444,6 +5489,9 @@ var VidPly = (() => {
4444
5489
  this.audioDescriptionSrc = this.options.audioDescriptionSrc;
4445
5490
  this.signLanguageSrc = this.options.signLanguageSrc;
4446
5491
  this.signLanguageVideo = null;
5492
+ this.audioDescriptionSourceElement = null;
5493
+ this.originalAudioDescriptionSource = null;
5494
+ this.audioDescriptionCaptionTracks = [];
4447
5495
  this.container = null;
4448
5496
  this.renderer = null;
4449
5497
  this.controlBar = null;
@@ -4598,6 +5646,53 @@ var VidPly = (() => {
4598
5646
  if (!src) {
4599
5647
  throw new Error("No media source found");
4600
5648
  }
5649
+ const sourceElements = this.element.querySelectorAll("source");
5650
+ for (const sourceEl of sourceElements) {
5651
+ const descSrc = sourceEl.getAttribute("data-desc-src");
5652
+ const origSrc = sourceEl.getAttribute("data-orig-src");
5653
+ if (descSrc || origSrc) {
5654
+ if (!this.audioDescriptionSourceElement) {
5655
+ this.audioDescriptionSourceElement = sourceEl;
5656
+ }
5657
+ if (origSrc) {
5658
+ if (!this.originalAudioDescriptionSource) {
5659
+ this.originalAudioDescriptionSource = origSrc;
5660
+ }
5661
+ if (!this.originalSrc) {
5662
+ this.originalSrc = origSrc;
5663
+ }
5664
+ } else {
5665
+ const currentSrcAttr = sourceEl.getAttribute("src");
5666
+ if (!this.originalAudioDescriptionSource && currentSrcAttr) {
5667
+ this.originalAudioDescriptionSource = currentSrcAttr;
5668
+ }
5669
+ if (!this.originalSrc && currentSrcAttr) {
5670
+ this.originalSrc = currentSrcAttr;
5671
+ }
5672
+ }
5673
+ if (descSrc && !this.audioDescriptionSrc) {
5674
+ this.audioDescriptionSrc = descSrc;
5675
+ }
5676
+ }
5677
+ }
5678
+ const trackElements = this.element.querySelectorAll("track");
5679
+ trackElements.forEach((trackEl) => {
5680
+ const trackKind = trackEl.getAttribute("kind");
5681
+ const trackDescSrc = trackEl.getAttribute("data-desc-src");
5682
+ if (trackKind === "captions" || trackKind === "subtitles" || trackKind === "chapters") {
5683
+ if (trackDescSrc) {
5684
+ this.audioDescriptionCaptionTracks.push({
5685
+ trackElement: trackEl,
5686
+ originalSrc: trackEl.getAttribute("src"),
5687
+ describedSrc: trackDescSrc,
5688
+ originalTrackSrc: trackEl.getAttribute("data-orig-src") || trackEl.getAttribute("src"),
5689
+ explicit: true
5690
+ // Explicitly defined, so we should validate it
5691
+ });
5692
+ this.log(`Found explicit described ${trackKind} track: ${trackEl.getAttribute("src")} -> ${trackDescSrc}`);
5693
+ }
5694
+ }
5695
+ });
4601
5696
  if (!this.originalSrc) {
4602
5697
  this.originalSrc = src;
4603
5698
  }
@@ -4754,6 +5849,7 @@ var VidPly = (() => {
4754
5849
  if (newVolume > 0 && this.state.muted) {
4755
5850
  this.state.muted = false;
4756
5851
  }
5852
+ this.savePlayerPreferences();
4757
5853
  }
4758
5854
  getVolume() {
4759
5855
  return this.state.volume;
@@ -4763,6 +5859,7 @@ var VidPly = (() => {
4763
5859
  this.renderer.setMuted(true);
4764
5860
  }
4765
5861
  this.state.muted = true;
5862
+ this.savePlayerPreferences();
4766
5863
  this.emit("volumechange");
4767
5864
  }
4768
5865
  unmute() {
@@ -4770,6 +5867,7 @@ var VidPly = (() => {
4770
5867
  this.renderer.setMuted(false);
4771
5868
  }
4772
5869
  this.state.muted = false;
5870
+ this.savePlayerPreferences();
4773
5871
  this.emit("volumechange");
4774
5872
  }
4775
5873
  toggleMute() {
@@ -4786,11 +5884,20 @@ var VidPly = (() => {
4786
5884
  this.renderer.setPlaybackSpeed(newSpeed);
4787
5885
  }
4788
5886
  this.state.playbackSpeed = newSpeed;
5887
+ this.savePlayerPreferences();
4789
5888
  this.emit("playbackspeedchange", newSpeed);
4790
5889
  }
4791
5890
  getPlaybackSpeed() {
4792
5891
  return this.state.playbackSpeed;
4793
5892
  }
5893
+ // Save player preferences to localStorage
5894
+ savePlayerPreferences() {
5895
+ this.storage.savePlayerPreferences({
5896
+ volume: this.state.volume,
5897
+ muted: this.state.muted,
5898
+ playbackSpeed: this.state.playbackSpeed
5899
+ });
5900
+ }
4794
5901
  // Fullscreen
4795
5902
  enterFullscreen() {
4796
5903
  const elem = this.container;
@@ -4870,15 +5977,396 @@ var VidPly = (() => {
4870
5977
  this.enableCaptions();
4871
5978
  }
4872
5979
  }
5980
+ /**
5981
+ * Check if a track file exists
5982
+ * @param {string} url - Track file URL
5983
+ * @returns {Promise<boolean>} - True if file exists
5984
+ */
5985
+ async validateTrackExists(url) {
5986
+ try {
5987
+ const response = await fetch(url, { method: "HEAD", cache: "no-cache" });
5988
+ return response.ok;
5989
+ } catch (error) {
5990
+ return false;
5991
+ }
5992
+ }
4873
5993
  // Audio Description
4874
5994
  async enableAudioDescription() {
4875
- if (!this.audioDescriptionSrc) {
4876
- console.warn("VidPly: No audio description source provided");
5995
+ const hasSourceElementsWithDesc = Array.from(this.element.querySelectorAll("source")).some((el) => el.getAttribute("data-desc-src"));
5996
+ const hasTracksWithDesc = this.audioDescriptionCaptionTracks.length > 0;
5997
+ if (!this.audioDescriptionSrc && !hasSourceElementsWithDesc && !hasTracksWithDesc) {
5998
+ console.warn("VidPly: No audio description source, source elements, or tracks provided");
4877
5999
  return;
4878
6000
  }
4879
6001
  const currentTime = this.state.currentTime;
4880
6002
  const wasPlaying = this.state.playing;
4881
- this.element.src = this.audioDescriptionSrc;
6003
+ let swappedTracksForTranscript = [];
6004
+ if (this.audioDescriptionSourceElement) {
6005
+ const currentSrc = this.element.currentSrc || this.element.src;
6006
+ const sourceElements = Array.from(this.element.querySelectorAll("source"));
6007
+ let sourceElementToUpdate = null;
6008
+ let descSrc = this.audioDescriptionSrc;
6009
+ for (const sourceEl of sourceElements) {
6010
+ const sourceSrc = sourceEl.getAttribute("src");
6011
+ const descSrcAttr = sourceEl.getAttribute("data-desc-src");
6012
+ const sourceFilename = sourceSrc ? sourceSrc.split("/").pop() : "";
6013
+ const currentFilename = currentSrc ? currentSrc.split("/").pop() : "";
6014
+ if (currentSrc && (currentSrc === sourceSrc || currentSrc.includes(sourceSrc) || currentSrc.includes(sourceFilename) || sourceFilename && currentFilename === sourceFilename)) {
6015
+ sourceElementToUpdate = sourceEl;
6016
+ if (descSrcAttr) {
6017
+ descSrc = descSrcAttr;
6018
+ } else if (sourceSrc) {
6019
+ descSrc = this.audioDescriptionSrc || descSrc;
6020
+ }
6021
+ break;
6022
+ }
6023
+ }
6024
+ if (!sourceElementToUpdate) {
6025
+ sourceElementToUpdate = this.audioDescriptionSourceElement;
6026
+ const storedDescSrc = sourceElementToUpdate.getAttribute("data-desc-src");
6027
+ if (storedDescSrc) {
6028
+ descSrc = storedDescSrc;
6029
+ }
6030
+ }
6031
+ if (this.audioDescriptionCaptionTracks.length > 0) {
6032
+ const validationPromises = this.audioDescriptionCaptionTracks.map(async (trackInfo) => {
6033
+ if (trackInfo.trackElement && trackInfo.describedSrc) {
6034
+ if (trackInfo.explicit === true) {
6035
+ try {
6036
+ const exists = await this.validateTrackExists(trackInfo.describedSrc);
6037
+ return { trackInfo, exists };
6038
+ } catch (error) {
6039
+ return { trackInfo, exists: false };
6040
+ }
6041
+ } else {
6042
+ return { trackInfo, exists: false };
6043
+ }
6044
+ }
6045
+ return { trackInfo, exists: false };
6046
+ });
6047
+ const validationResults = await Promise.all(validationPromises);
6048
+ const tracksToSwap = validationResults.filter((result) => result.exists);
6049
+ if (tracksToSwap.length > 0) {
6050
+ const trackModes = /* @__PURE__ */ new Map();
6051
+ tracksToSwap.forEach(({ trackInfo }) => {
6052
+ const textTrack = trackInfo.trackElement.track;
6053
+ if (textTrack) {
6054
+ trackModes.set(trackInfo, {
6055
+ wasShowing: textTrack.mode === "showing",
6056
+ wasHidden: textTrack.mode === "hidden"
6057
+ });
6058
+ } else {
6059
+ trackModes.set(trackInfo, {
6060
+ wasShowing: false,
6061
+ wasHidden: false
6062
+ });
6063
+ }
6064
+ });
6065
+ const tracksToReadd = tracksToSwap.map(({ trackInfo }) => {
6066
+ const oldSrc = trackInfo.trackElement.getAttribute("src");
6067
+ const parent = trackInfo.trackElement.parentNode;
6068
+ const nextSibling = trackInfo.trackElement.nextSibling;
6069
+ const attributes = {};
6070
+ Array.from(trackInfo.trackElement.attributes).forEach((attr) => {
6071
+ attributes[attr.name] = attr.value;
6072
+ });
6073
+ return {
6074
+ trackInfo,
6075
+ oldSrc,
6076
+ parent,
6077
+ nextSibling,
6078
+ attributes
6079
+ };
6080
+ });
6081
+ tracksToReadd.forEach(({ trackInfo }) => {
6082
+ trackInfo.trackElement.remove();
6083
+ });
6084
+ this.element.load();
6085
+ setTimeout(() => {
6086
+ tracksToReadd.forEach(({ trackInfo, oldSrc, parent, nextSibling, attributes }) => {
6087
+ swappedTracksForTranscript.push(trackInfo);
6088
+ const newTrackElement = document.createElement("track");
6089
+ newTrackElement.setAttribute("src", trackInfo.describedSrc);
6090
+ Object.keys(attributes).forEach((attrName) => {
6091
+ if (attrName !== "src" && attrName !== "data-desc-src") {
6092
+ newTrackElement.setAttribute(attrName, attributes[attrName]);
6093
+ }
6094
+ });
6095
+ if (nextSibling && nextSibling.parentNode) {
6096
+ parent.insertBefore(newTrackElement, nextSibling);
6097
+ } else {
6098
+ parent.appendChild(newTrackElement);
6099
+ }
6100
+ trackInfo.trackElement = newTrackElement;
6101
+ });
6102
+ this.element.load();
6103
+ const setupNewTracks = () => {
6104
+ setTimeout(() => {
6105
+ swappedTracksForTranscript.forEach((trackInfo) => {
6106
+ const trackElement = trackInfo.trackElement;
6107
+ const newTextTrack = trackElement.track;
6108
+ if (newTextTrack) {
6109
+ const modeInfo = trackModes.get(trackInfo) || { wasShowing: false, wasHidden: false };
6110
+ newTextTrack.mode = "hidden";
6111
+ const restoreMode = () => {
6112
+ if (modeInfo.wasShowing) {
6113
+ newTextTrack.mode = "hidden";
6114
+ } else if (modeInfo.wasHidden) {
6115
+ newTextTrack.mode = "hidden";
6116
+ } else {
6117
+ newTextTrack.mode = "disabled";
6118
+ }
6119
+ };
6120
+ if (newTextTrack.readyState >= 2) {
6121
+ restoreMode();
6122
+ } else {
6123
+ newTextTrack.addEventListener("load", restoreMode, { once: true });
6124
+ newTextTrack.addEventListener("error", restoreMode, { once: true });
6125
+ }
6126
+ }
6127
+ });
6128
+ }, 300);
6129
+ };
6130
+ if (this.element.readyState >= 1) {
6131
+ setTimeout(setupNewTracks, 200);
6132
+ } else {
6133
+ this.element.addEventListener("loadedmetadata", setupNewTracks, { once: true });
6134
+ setTimeout(setupNewTracks, 2e3);
6135
+ }
6136
+ }, 100);
6137
+ const skippedCount = validationResults.length - tracksToSwap.length;
6138
+ }
6139
+ }
6140
+ const allSourceElements = Array.from(this.element.querySelectorAll("source"));
6141
+ const sourcesToUpdate = [];
6142
+ allSourceElements.forEach((sourceEl) => {
6143
+ const descSrcAttr = sourceEl.getAttribute("data-desc-src");
6144
+ const currentSrc2 = sourceEl.getAttribute("src");
6145
+ if (descSrcAttr) {
6146
+ const type = sourceEl.getAttribute("type");
6147
+ let origSrc = sourceEl.getAttribute("data-orig-src");
6148
+ if (!origSrc) {
6149
+ origSrc = currentSrc2;
6150
+ }
6151
+ sourcesToUpdate.push({
6152
+ src: descSrcAttr,
6153
+ // Use described version
6154
+ type,
6155
+ origSrc,
6156
+ descSrc: descSrcAttr
6157
+ });
6158
+ } else {
6159
+ const type = sourceEl.getAttribute("type");
6160
+ const src = sourceEl.getAttribute("src");
6161
+ sourcesToUpdate.push({
6162
+ src,
6163
+ type,
6164
+ origSrc: null,
6165
+ descSrc: null
6166
+ });
6167
+ }
6168
+ });
6169
+ allSourceElements.forEach((sourceEl) => {
6170
+ sourceEl.remove();
6171
+ });
6172
+ sourcesToUpdate.forEach((sourceInfo) => {
6173
+ const newSource = document.createElement("source");
6174
+ newSource.setAttribute("src", sourceInfo.src);
6175
+ if (sourceInfo.type) {
6176
+ newSource.setAttribute("type", sourceInfo.type);
6177
+ }
6178
+ if (sourceInfo.origSrc) {
6179
+ newSource.setAttribute("data-orig-src", sourceInfo.origSrc);
6180
+ }
6181
+ if (sourceInfo.descSrc) {
6182
+ newSource.setAttribute("data-desc-src", sourceInfo.descSrc);
6183
+ }
6184
+ this.element.appendChild(newSource);
6185
+ });
6186
+ this.element.load();
6187
+ await new Promise((resolve) => {
6188
+ const onLoadedMetadata = () => {
6189
+ this.element.removeEventListener("loadedmetadata", onLoadedMetadata);
6190
+ resolve();
6191
+ };
6192
+ this.element.addEventListener("loadedmetadata", onLoadedMetadata);
6193
+ });
6194
+ await new Promise((resolve) => setTimeout(resolve, 300));
6195
+ if (this.element.tagName === "VIDEO" && currentTime === 0 && !wasPlaying) {
6196
+ if (this.element.readyState >= 1) {
6197
+ this.element.currentTime = 1e-3;
6198
+ setTimeout(() => {
6199
+ this.element.currentTime = 0;
6200
+ }, 10);
6201
+ }
6202
+ }
6203
+ this.seek(currentTime);
6204
+ if (wasPlaying) {
6205
+ this.play();
6206
+ }
6207
+ this.state.audioDescriptionEnabled = true;
6208
+ this.emit("audiodescriptionenabled");
6209
+ } else {
6210
+ if (this.audioDescriptionCaptionTracks.length > 0) {
6211
+ const validationPromises = this.audioDescriptionCaptionTracks.map(async (trackInfo) => {
6212
+ if (trackInfo.trackElement && trackInfo.describedSrc) {
6213
+ if (trackInfo.explicit === true) {
6214
+ try {
6215
+ const exists = await this.validateTrackExists(trackInfo.describedSrc);
6216
+ return { trackInfo, exists };
6217
+ } catch (error) {
6218
+ return { trackInfo, exists: false };
6219
+ }
6220
+ } else {
6221
+ return { trackInfo, exists: false };
6222
+ }
6223
+ }
6224
+ return { trackInfo, exists: false };
6225
+ });
6226
+ const validationResults = await Promise.all(validationPromises);
6227
+ const tracksToSwap = validationResults.filter((result) => result.exists);
6228
+ if (tracksToSwap.length > 0) {
6229
+ const trackModes = /* @__PURE__ */ new Map();
6230
+ tracksToSwap.forEach(({ trackInfo }) => {
6231
+ const textTrack = trackInfo.trackElement.track;
6232
+ if (textTrack) {
6233
+ trackModes.set(trackInfo, {
6234
+ wasShowing: textTrack.mode === "showing",
6235
+ wasHidden: textTrack.mode === "hidden"
6236
+ });
6237
+ } else {
6238
+ trackModes.set(trackInfo, {
6239
+ wasShowing: false,
6240
+ wasHidden: false
6241
+ });
6242
+ }
6243
+ });
6244
+ const tracksToReadd = tracksToSwap.map(({ trackInfo }) => {
6245
+ const oldSrc = trackInfo.trackElement.getAttribute("src");
6246
+ const parent = trackInfo.trackElement.parentNode;
6247
+ const nextSibling = trackInfo.trackElement.nextSibling;
6248
+ const attributes = {};
6249
+ Array.from(trackInfo.trackElement.attributes).forEach((attr) => {
6250
+ attributes[attr.name] = attr.value;
6251
+ });
6252
+ return {
6253
+ trackInfo,
6254
+ oldSrc,
6255
+ parent,
6256
+ nextSibling,
6257
+ attributes
6258
+ };
6259
+ });
6260
+ tracksToReadd.forEach(({ trackInfo }) => {
6261
+ trackInfo.trackElement.remove();
6262
+ });
6263
+ this.element.load();
6264
+ setTimeout(() => {
6265
+ tracksToReadd.forEach(({ trackInfo, oldSrc, parent, nextSibling, attributes }) => {
6266
+ swappedTracksForTranscript.push(trackInfo);
6267
+ const newTrackElement = document.createElement("track");
6268
+ newTrackElement.setAttribute("src", trackInfo.describedSrc);
6269
+ Object.keys(attributes).forEach((attrName) => {
6270
+ if (attrName !== "src" && attrName !== "data-desc-src") {
6271
+ newTrackElement.setAttribute(attrName, attributes[attrName]);
6272
+ }
6273
+ });
6274
+ if (nextSibling && nextSibling.parentNode) {
6275
+ parent.insertBefore(newTrackElement, nextSibling);
6276
+ } else {
6277
+ parent.appendChild(newTrackElement);
6278
+ }
6279
+ trackInfo.trackElement = newTrackElement;
6280
+ });
6281
+ this.element.load();
6282
+ const setupNewTracks = () => {
6283
+ setTimeout(() => {
6284
+ swappedTracksForTranscript.forEach((trackInfo) => {
6285
+ const trackElement = trackInfo.trackElement;
6286
+ const newTextTrack = trackElement.track;
6287
+ if (newTextTrack) {
6288
+ const modeInfo = trackModes.get(trackInfo) || { wasShowing: false, wasHidden: false };
6289
+ newTextTrack.mode = "hidden";
6290
+ const restoreMode = () => {
6291
+ if (modeInfo.wasShowing) {
6292
+ newTextTrack.mode = "hidden";
6293
+ } else if (modeInfo.wasHidden) {
6294
+ newTextTrack.mode = "hidden";
6295
+ } else {
6296
+ newTextTrack.mode = "disabled";
6297
+ }
6298
+ };
6299
+ if (newTextTrack.readyState >= 2) {
6300
+ restoreMode();
6301
+ } else {
6302
+ newTextTrack.addEventListener("load", restoreMode, { once: true });
6303
+ newTextTrack.addEventListener("error", restoreMode, { once: true });
6304
+ }
6305
+ }
6306
+ });
6307
+ }, 300);
6308
+ };
6309
+ if (this.element.readyState >= 1) {
6310
+ setTimeout(setupNewTracks, 200);
6311
+ } else {
6312
+ this.element.addEventListener("loadedmetadata", setupNewTracks, { once: true });
6313
+ setTimeout(setupNewTracks, 2e3);
6314
+ }
6315
+ }, 100);
6316
+ }
6317
+ }
6318
+ const fallbackSourceElements = Array.from(this.element.querySelectorAll("source"));
6319
+ const hasSourceElementsWithDesc2 = fallbackSourceElements.some((el) => el.getAttribute("data-desc-src"));
6320
+ if (hasSourceElementsWithDesc2) {
6321
+ const fallbackSourcesToUpdate = [];
6322
+ fallbackSourceElements.forEach((sourceEl) => {
6323
+ const descSrcAttr = sourceEl.getAttribute("data-desc-src");
6324
+ const currentSrc = sourceEl.getAttribute("src");
6325
+ if (descSrcAttr) {
6326
+ const type = sourceEl.getAttribute("type");
6327
+ let origSrc = sourceEl.getAttribute("data-orig-src");
6328
+ if (!origSrc) {
6329
+ origSrc = currentSrc;
6330
+ }
6331
+ fallbackSourcesToUpdate.push({
6332
+ src: descSrcAttr,
6333
+ type,
6334
+ origSrc,
6335
+ descSrc: descSrcAttr
6336
+ });
6337
+ } else {
6338
+ const type = sourceEl.getAttribute("type");
6339
+ const src = sourceEl.getAttribute("src");
6340
+ fallbackSourcesToUpdate.push({
6341
+ src,
6342
+ type,
6343
+ origSrc: null,
6344
+ descSrc: null
6345
+ });
6346
+ }
6347
+ });
6348
+ fallbackSourceElements.forEach((sourceEl) => {
6349
+ sourceEl.remove();
6350
+ });
6351
+ fallbackSourcesToUpdate.forEach((sourceInfo) => {
6352
+ const newSource = document.createElement("source");
6353
+ newSource.setAttribute("src", sourceInfo.src);
6354
+ if (sourceInfo.type) {
6355
+ newSource.setAttribute("type", sourceInfo.type);
6356
+ }
6357
+ if (sourceInfo.origSrc) {
6358
+ newSource.setAttribute("data-orig-src", sourceInfo.origSrc);
6359
+ }
6360
+ if (sourceInfo.descSrc) {
6361
+ newSource.setAttribute("data-desc-src", sourceInfo.descSrc);
6362
+ }
6363
+ this.element.appendChild(newSource);
6364
+ });
6365
+ this.element.load();
6366
+ } else {
6367
+ this.element.src = this.audioDescriptionSrc;
6368
+ }
6369
+ }
4882
6370
  await new Promise((resolve) => {
4883
6371
  const onLoadedMetadata = () => {
4884
6372
  this.element.removeEventListener("loadedmetadata", onLoadedMetadata);
@@ -4886,10 +6374,183 @@ var VidPly = (() => {
4886
6374
  };
4887
6375
  this.element.addEventListener("loadedmetadata", onLoadedMetadata);
4888
6376
  });
6377
+ if (this.element.tagName === "VIDEO" && currentTime === 0 && !wasPlaying) {
6378
+ if (this.element.readyState >= 1) {
6379
+ this.element.currentTime = 1e-3;
6380
+ setTimeout(() => {
6381
+ this.element.currentTime = 0;
6382
+ }, 10);
6383
+ }
6384
+ }
4889
6385
  this.seek(currentTime);
4890
6386
  if (wasPlaying) {
4891
6387
  this.play();
4892
6388
  }
6389
+ if (swappedTracksForTranscript.length > 0 && this.captionManager) {
6390
+ const wasCaptionsEnabled = this.state.captionsEnabled;
6391
+ let currentTrackInfo = null;
6392
+ if (this.captionManager.currentTrack) {
6393
+ const currentTrackIndex = this.captionManager.tracks.findIndex((t) => t.track === this.captionManager.currentTrack.track);
6394
+ if (currentTrackIndex >= 0) {
6395
+ currentTrackInfo = {
6396
+ language: this.captionManager.tracks[currentTrackIndex].language,
6397
+ kind: this.captionManager.tracks[currentTrackIndex].kind
6398
+ };
6399
+ }
6400
+ }
6401
+ setTimeout(() => {
6402
+ this.captionManager.tracks = [];
6403
+ this.captionManager.loadTracks();
6404
+ if (wasCaptionsEnabled && currentTrackInfo && this.captionManager.tracks.length > 0) {
6405
+ const matchingTrackIndex = this.captionManager.tracks.findIndex(
6406
+ (t) => t.language === currentTrackInfo.language && t.kind === currentTrackInfo.kind
6407
+ );
6408
+ if (matchingTrackIndex >= 0) {
6409
+ this.captionManager.enable(matchingTrackIndex);
6410
+ } else if (this.captionManager.tracks.length > 0) {
6411
+ this.captionManager.enable(0);
6412
+ }
6413
+ }
6414
+ }, 600);
6415
+ }
6416
+ if (this.transcriptManager && this.transcriptManager.isVisible) {
6417
+ const swappedTracks = typeof swappedTracksForTranscript !== "undefined" ? swappedTracksForTranscript : [];
6418
+ if (swappedTracks.length > 0) {
6419
+ const onMetadataLoaded = () => {
6420
+ const allTextTracks = Array.from(this.element.textTracks);
6421
+ const freshTracks = swappedTracks.map((trackInfo) => {
6422
+ const trackEl = trackInfo.trackElement;
6423
+ const expectedSrc = trackEl.getAttribute("src");
6424
+ const srclang = trackEl.getAttribute("srclang");
6425
+ const kind = trackEl.getAttribute("kind");
6426
+ let foundTrack = allTextTracks.find((track) => trackEl.track === track);
6427
+ if (!foundTrack) {
6428
+ foundTrack = allTextTracks.find((track) => {
6429
+ if (track.language === srclang && (track.kind === kind || kind === "captions" && track.kind === "subtitles")) {
6430
+ const trackElementForTrack = Array.from(this.element.querySelectorAll("track")).find(
6431
+ (el) => el.track === track
6432
+ );
6433
+ if (trackElementForTrack) {
6434
+ const actualSrc = trackElementForTrack.getAttribute("src");
6435
+ if (actualSrc === expectedSrc) {
6436
+ return true;
6437
+ }
6438
+ }
6439
+ }
6440
+ return false;
6441
+ });
6442
+ }
6443
+ if (foundTrack) {
6444
+ const trackElement = Array.from(this.element.querySelectorAll("track")).find(
6445
+ (el) => el.track === foundTrack
6446
+ );
6447
+ if (trackElement && trackElement.getAttribute("src") !== expectedSrc) {
6448
+ return null;
6449
+ }
6450
+ }
6451
+ return foundTrack;
6452
+ }).filter(Boolean);
6453
+ if (freshTracks.length === 0) {
6454
+ setTimeout(() => {
6455
+ if (this.transcriptManager && this.transcriptManager.loadTranscriptData) {
6456
+ this.transcriptManager.loadTranscriptData();
6457
+ }
6458
+ }, 1e3);
6459
+ return;
6460
+ }
6461
+ freshTracks.forEach((track) => {
6462
+ if (track.mode === "disabled") {
6463
+ track.mode = "hidden";
6464
+ }
6465
+ });
6466
+ let loadedCount = 0;
6467
+ const checkLoaded = () => {
6468
+ loadedCount++;
6469
+ if (loadedCount >= freshTracks.length) {
6470
+ setTimeout(() => {
6471
+ if (this.transcriptManager && this.transcriptManager.loadTranscriptData) {
6472
+ const allTextTracks2 = Array.from(this.element.textTracks);
6473
+ const swappedTrackSrcs = swappedTracks.map((t) => t.describedSrc);
6474
+ const hasCorrectTracks = freshTracks.some((track) => {
6475
+ const trackEl = Array.from(this.element.querySelectorAll("track")).find(
6476
+ (el) => el.track === track
6477
+ );
6478
+ return trackEl && swappedTrackSrcs.includes(trackEl.getAttribute("src"));
6479
+ });
6480
+ if (hasCorrectTracks || freshTracks.length > 0) {
6481
+ this.transcriptManager.loadTranscriptData();
6482
+ }
6483
+ }
6484
+ }, 800);
6485
+ }
6486
+ };
6487
+ freshTracks.forEach((track) => {
6488
+ if (track.mode === "disabled") {
6489
+ track.mode = "hidden";
6490
+ }
6491
+ const trackElementForTrack = Array.from(this.element.querySelectorAll("track")).find(
6492
+ (el) => el.track === track
6493
+ );
6494
+ const actualSrc = trackElementForTrack ? trackElementForTrack.getAttribute("src") : null;
6495
+ const expectedTrackInfo = swappedTracks.find((t) => {
6496
+ const tEl = t.trackElement;
6497
+ return tEl && (tEl.track === track || tEl.getAttribute("srclang") === track.language && tEl.getAttribute("kind") === track.kind);
6498
+ });
6499
+ const expectedSrc = expectedTrackInfo ? expectedTrackInfo.describedSrc : null;
6500
+ if (expectedSrc && actualSrc && actualSrc !== expectedSrc) {
6501
+ checkLoaded();
6502
+ return;
6503
+ }
6504
+ if (track.readyState >= 2 && track.cues && track.cues.length > 0) {
6505
+ checkLoaded();
6506
+ } else {
6507
+ if (track.mode === "disabled") {
6508
+ track.mode = "hidden";
6509
+ }
6510
+ const onTrackLoad = () => {
6511
+ setTimeout(checkLoaded, 300);
6512
+ };
6513
+ if (track.readyState >= 2) {
6514
+ setTimeout(() => {
6515
+ if (track.cues && track.cues.length > 0) {
6516
+ checkLoaded();
6517
+ } else {
6518
+ track.addEventListener("load", onTrackLoad, { once: true });
6519
+ }
6520
+ }, 100);
6521
+ } else {
6522
+ track.addEventListener("load", onTrackLoad, { once: true });
6523
+ track.addEventListener("error", () => {
6524
+ checkLoaded();
6525
+ }, { once: true });
6526
+ }
6527
+ }
6528
+ });
6529
+ };
6530
+ const waitForTracks = () => {
6531
+ setTimeout(() => {
6532
+ if (this.element.readyState >= 1) {
6533
+ onMetadataLoaded();
6534
+ } else {
6535
+ this.element.addEventListener("loadedmetadata", onMetadataLoaded, { once: true });
6536
+ setTimeout(onMetadataLoaded, 2e3);
6537
+ }
6538
+ }, 500);
6539
+ };
6540
+ waitForTracks();
6541
+ setTimeout(() => {
6542
+ if (this.transcriptManager && this.transcriptManager.loadTranscriptData) {
6543
+ this.transcriptManager.loadTranscriptData();
6544
+ }
6545
+ }, 5e3);
6546
+ } else {
6547
+ setTimeout(() => {
6548
+ if (this.transcriptManager && this.transcriptManager.loadTranscriptData) {
6549
+ this.transcriptManager.loadTranscriptData();
6550
+ }
6551
+ }, 800);
6552
+ }
6553
+ }
4893
6554
  this.state.audioDescriptionEnabled = true;
4894
6555
  this.emit("audiodescriptionenabled");
4895
6556
  }
@@ -4899,7 +6560,64 @@ var VidPly = (() => {
4899
6560
  }
4900
6561
  const currentTime = this.state.currentTime;
4901
6562
  const wasPlaying = this.state.playing;
4902
- this.element.src = this.originalSrc;
6563
+ if (this.audioDescriptionCaptionTracks.length > 0) {
6564
+ this.audioDescriptionCaptionTracks.forEach((trackInfo) => {
6565
+ if (trackInfo.trackElement && trackInfo.originalTrackSrc) {
6566
+ trackInfo.trackElement.setAttribute("src", trackInfo.originalTrackSrc);
6567
+ }
6568
+ });
6569
+ }
6570
+ const allSourceElements = Array.from(this.element.querySelectorAll("source"));
6571
+ const hasSourceElementsToSwap = allSourceElements.some((el) => el.getAttribute("data-orig-src"));
6572
+ if (hasSourceElementsToSwap) {
6573
+ const sourcesToRestore = [];
6574
+ allSourceElements.forEach((sourceEl) => {
6575
+ const origSrcAttr = sourceEl.getAttribute("data-orig-src");
6576
+ const descSrcAttr = sourceEl.getAttribute("data-desc-src");
6577
+ if (origSrcAttr) {
6578
+ const type = sourceEl.getAttribute("type");
6579
+ sourcesToRestore.push({
6580
+ src: origSrcAttr,
6581
+ // Use original version
6582
+ type,
6583
+ origSrc: origSrcAttr,
6584
+ descSrc: descSrcAttr
6585
+ // Keep data-desc-src for future swaps
6586
+ });
6587
+ } else {
6588
+ const type = sourceEl.getAttribute("type");
6589
+ const src = sourceEl.getAttribute("src");
6590
+ sourcesToRestore.push({
6591
+ src,
6592
+ type,
6593
+ origSrc: null,
6594
+ descSrc: descSrcAttr
6595
+ });
6596
+ }
6597
+ });
6598
+ allSourceElements.forEach((sourceEl) => {
6599
+ sourceEl.remove();
6600
+ });
6601
+ sourcesToRestore.forEach((sourceInfo) => {
6602
+ const newSource = document.createElement("source");
6603
+ newSource.setAttribute("src", sourceInfo.src);
6604
+ if (sourceInfo.type) {
6605
+ newSource.setAttribute("type", sourceInfo.type);
6606
+ }
6607
+ if (sourceInfo.origSrc) {
6608
+ newSource.setAttribute("data-orig-src", sourceInfo.origSrc);
6609
+ }
6610
+ if (sourceInfo.descSrc) {
6611
+ newSource.setAttribute("data-desc-src", sourceInfo.descSrc);
6612
+ }
6613
+ this.element.appendChild(newSource);
6614
+ });
6615
+ this.element.load();
6616
+ } else {
6617
+ const originalSrcToUse = this.originalAudioDescriptionSource || this.originalSrc;
6618
+ this.element.src = originalSrcToUse;
6619
+ this.element.load();
6620
+ }
4903
6621
  await new Promise((resolve) => {
4904
6622
  const onLoadedMetadata = () => {
4905
6623
  this.element.removeEventListener("loadedmetadata", onLoadedMetadata);
@@ -4911,14 +6629,44 @@ var VidPly = (() => {
4911
6629
  if (wasPlaying) {
4912
6630
  this.play();
4913
6631
  }
6632
+ if (this.transcriptManager && this.transcriptManager.isVisible) {
6633
+ setTimeout(() => {
6634
+ if (this.transcriptManager && this.transcriptManager.loadTranscriptData) {
6635
+ this.transcriptManager.loadTranscriptData();
6636
+ }
6637
+ }, 500);
6638
+ }
4914
6639
  this.state.audioDescriptionEnabled = false;
4915
6640
  this.emit("audiodescriptiondisabled");
4916
6641
  }
4917
6642
  async toggleAudioDescription() {
4918
- if (this.state.audioDescriptionEnabled) {
4919
- await this.disableAudioDescription();
4920
- } else {
4921
- await this.enableAudioDescription();
6643
+ const textTracks = Array.from(this.element.textTracks || []);
6644
+ const descriptionTrack = textTracks.find((track) => track.kind === "descriptions");
6645
+ const hasAudioDescriptionSrc = this.audioDescriptionSrc || Array.from(this.element.querySelectorAll("source")).some((el) => el.getAttribute("data-desc-src"));
6646
+ if (descriptionTrack && hasAudioDescriptionSrc) {
6647
+ if (this.state.audioDescriptionEnabled) {
6648
+ descriptionTrack.mode = "hidden";
6649
+ await this.disableAudioDescription();
6650
+ } else {
6651
+ await this.enableAudioDescription();
6652
+ descriptionTrack.mode = "showing";
6653
+ }
6654
+ } else if (descriptionTrack) {
6655
+ if (descriptionTrack.mode === "showing") {
6656
+ descriptionTrack.mode = "hidden";
6657
+ this.state.audioDescriptionEnabled = false;
6658
+ this.emit("audiodescriptiondisabled");
6659
+ } else {
6660
+ descriptionTrack.mode = "showing";
6661
+ this.state.audioDescriptionEnabled = true;
6662
+ this.emit("audiodescriptionenabled");
6663
+ }
6664
+ } else if (hasAudioDescriptionSrc) {
6665
+ if (this.state.audioDescriptionEnabled) {
6666
+ await this.disableAudioDescription();
6667
+ } else {
6668
+ await this.enableAudioDescription();
6669
+ }
4922
6670
  }
4923
6671
  }
4924
6672
  // Sign Language
@@ -4927,24 +6675,47 @@ var VidPly = (() => {
4927
6675
  console.warn("No sign language video source provided");
4928
6676
  return;
4929
6677
  }
4930
- if (this.signLanguageVideo) {
4931
- this.signLanguageVideo.style.display = "block";
6678
+ if (this.signLanguageWrapper) {
6679
+ this.signLanguageWrapper.style.display = "block";
4932
6680
  this.state.signLanguageEnabled = true;
4933
6681
  this.emit("signlanguageenabled");
4934
6682
  return;
4935
6683
  }
6684
+ this.signLanguageWrapper = document.createElement("div");
6685
+ this.signLanguageWrapper.className = "vidply-sign-language-wrapper";
6686
+ this.signLanguageWrapper.setAttribute("tabindex", "0");
6687
+ this.signLanguageWrapper.setAttribute("aria-label", "Sign Language Video - Press D to drag with keyboard, R to resize");
4936
6688
  this.signLanguageVideo = document.createElement("video");
4937
6689
  this.signLanguageVideo.className = "vidply-sign-language-video";
4938
6690
  this.signLanguageVideo.src = this.signLanguageSrc;
4939
6691
  this.signLanguageVideo.setAttribute("aria-label", i18n.t("player.signLanguage"));
4940
- const position = this.options.signLanguagePosition || "bottom-right";
4941
- this.signLanguageVideo.classList.add(`vidply-sign-position-${position}`);
4942
6692
  this.signLanguageVideo.muted = true;
6693
+ const resizeHandles = ["nw", "ne", "sw", "se"].map((dir) => {
6694
+ const handle = document.createElement("div");
6695
+ handle.className = `vidply-sign-resize-handle vidply-sign-resize-${dir}`;
6696
+ handle.setAttribute("data-direction", dir);
6697
+ handle.setAttribute("aria-label", `Resize ${dir.toUpperCase()}`);
6698
+ return handle;
6699
+ });
6700
+ this.signLanguageWrapper.appendChild(this.signLanguageVideo);
6701
+ resizeHandles.forEach((handle) => this.signLanguageWrapper.appendChild(handle));
6702
+ const saved = this.storage.getSignLanguagePreferences();
6703
+ if (saved && saved.size && saved.size.width) {
6704
+ this.signLanguageWrapper.style.width = saved.size.width;
6705
+ } else {
6706
+ this.signLanguageWrapper.style.width = "280px";
6707
+ }
6708
+ this.signLanguageWrapper.style.height = "auto";
6709
+ this.signLanguageDesiredPosition = this.options.signLanguagePosition || "bottom-right";
6710
+ this.container.appendChild(this.signLanguageWrapper);
6711
+ requestAnimationFrame(() => {
6712
+ this.constrainSignLanguagePosition();
6713
+ });
4943
6714
  this.signLanguageVideo.currentTime = this.state.currentTime;
4944
6715
  if (!this.state.paused) {
4945
6716
  this.signLanguageVideo.play();
4946
6717
  }
4947
- this.videoWrapper.appendChild(this.signLanguageVideo);
6718
+ this.setupSignLanguageInteraction();
4948
6719
  this.signLanguageHandlers = {
4949
6720
  play: () => {
4950
6721
  if (this.signLanguageVideo) {
@@ -4975,8 +6746,8 @@ var VidPly = (() => {
4975
6746
  this.emit("signlanguageenabled");
4976
6747
  }
4977
6748
  disableSignLanguage() {
4978
- if (this.signLanguageVideo) {
4979
- this.signLanguageVideo.style.display = "none";
6749
+ if (this.signLanguageWrapper) {
6750
+ this.signLanguageWrapper.style.display = "none";
4980
6751
  }
4981
6752
  this.state.signLanguageEnabled = false;
4982
6753
  this.emit("signlanguagedisabled");
@@ -4988,6 +6759,237 @@ var VidPly = (() => {
4988
6759
  this.enableSignLanguage();
4989
6760
  }
4990
6761
  }
6762
+ setupSignLanguageInteraction() {
6763
+ if (!this.signLanguageWrapper) return;
6764
+ let isDragging = false;
6765
+ let isResizing = false;
6766
+ let resizeDirection = null;
6767
+ let startX = 0;
6768
+ let startY = 0;
6769
+ let startLeft = 0;
6770
+ let startTop = 0;
6771
+ let startWidth = 0;
6772
+ let startHeight = 0;
6773
+ let dragMode = false;
6774
+ let resizeMode = false;
6775
+ const onMouseDownVideo = (e) => {
6776
+ if (e.target !== this.signLanguageVideo) return;
6777
+ e.preventDefault();
6778
+ isDragging = true;
6779
+ startX = e.clientX;
6780
+ startY = e.clientY;
6781
+ const rect = this.signLanguageWrapper.getBoundingClientRect();
6782
+ startLeft = rect.left;
6783
+ startTop = rect.top;
6784
+ this.signLanguageWrapper.classList.add("vidply-sign-dragging");
6785
+ };
6786
+ const onMouseDownHandle = (e) => {
6787
+ if (!e.target.classList.contains("vidply-sign-resize-handle")) return;
6788
+ e.preventDefault();
6789
+ e.stopPropagation();
6790
+ isResizing = true;
6791
+ resizeDirection = e.target.getAttribute("data-direction");
6792
+ startX = e.clientX;
6793
+ startY = e.clientY;
6794
+ const rect = this.signLanguageWrapper.getBoundingClientRect();
6795
+ startLeft = rect.left;
6796
+ startTop = rect.top;
6797
+ startWidth = rect.width;
6798
+ startHeight = rect.height;
6799
+ this.signLanguageWrapper.classList.add("vidply-sign-resizing");
6800
+ };
6801
+ const onMouseMove = (e) => {
6802
+ if (isDragging) {
6803
+ const deltaX = e.clientX - startX;
6804
+ const deltaY = e.clientY - startY;
6805
+ const videoWrapperRect = this.videoWrapper.getBoundingClientRect();
6806
+ const containerRect = this.container.getBoundingClientRect();
6807
+ const wrapperRect = this.signLanguageWrapper.getBoundingClientRect();
6808
+ const videoWrapperLeft = videoWrapperRect.left - containerRect.left;
6809
+ const videoWrapperTop = videoWrapperRect.top - containerRect.top;
6810
+ let newLeft = startLeft + deltaX - containerRect.left;
6811
+ let newTop = startTop + deltaY - containerRect.top;
6812
+ const controlsHeight = 95;
6813
+ newLeft = Math.max(videoWrapperLeft, Math.min(newLeft, videoWrapperLeft + videoWrapperRect.width - wrapperRect.width));
6814
+ newTop = Math.max(videoWrapperTop, Math.min(newTop, videoWrapperTop + videoWrapperRect.height - wrapperRect.height - controlsHeight));
6815
+ this.signLanguageWrapper.style.left = `${newLeft}px`;
6816
+ this.signLanguageWrapper.style.top = `${newTop}px`;
6817
+ this.signLanguageWrapper.style.right = "auto";
6818
+ this.signLanguageWrapper.style.bottom = "auto";
6819
+ this.signLanguageWrapper.classList.remove(...Array.from(this.signLanguageWrapper.classList).filter((c) => c.startsWith("vidply-sign-position-")));
6820
+ } else if (isResizing) {
6821
+ const deltaX = e.clientX - startX;
6822
+ const videoWrapperRect = this.videoWrapper.getBoundingClientRect();
6823
+ const containerRect = this.container.getBoundingClientRect();
6824
+ let newWidth = startWidth;
6825
+ let newLeft = startLeft - containerRect.left;
6826
+ if (resizeDirection.includes("e")) {
6827
+ newWidth = Math.max(150, startWidth + deltaX);
6828
+ const maxWidth = videoWrapperRect.right - startLeft;
6829
+ newWidth = Math.min(newWidth, maxWidth);
6830
+ }
6831
+ if (resizeDirection.includes("w")) {
6832
+ const proposedWidth = Math.max(150, startWidth - deltaX);
6833
+ const proposedLeft = startLeft + (startWidth - proposedWidth) - containerRect.left;
6834
+ const videoWrapperLeft = videoWrapperRect.left - containerRect.left;
6835
+ if (proposedLeft >= videoWrapperLeft) {
6836
+ newWidth = proposedWidth;
6837
+ newLeft = proposedLeft;
6838
+ }
6839
+ }
6840
+ this.signLanguageWrapper.style.width = `${newWidth}px`;
6841
+ this.signLanguageWrapper.style.height = "auto";
6842
+ if (resizeDirection.includes("w")) {
6843
+ this.signLanguageWrapper.style.left = `${newLeft}px`;
6844
+ }
6845
+ this.signLanguageWrapper.style.right = "auto";
6846
+ this.signLanguageWrapper.style.bottom = "auto";
6847
+ this.signLanguageWrapper.classList.remove(...Array.from(this.signLanguageWrapper.classList).filter((c) => c.startsWith("vidply-sign-position-")));
6848
+ }
6849
+ };
6850
+ const onMouseUp = () => {
6851
+ if (isDragging || isResizing) {
6852
+ this.saveSignLanguagePreferences();
6853
+ }
6854
+ isDragging = false;
6855
+ isResizing = false;
6856
+ resizeDirection = null;
6857
+ this.signLanguageWrapper.classList.remove("vidply-sign-dragging", "vidply-sign-resizing");
6858
+ };
6859
+ const onKeyDown = (e) => {
6860
+ if (e.key === "d" || e.key === "D") {
6861
+ dragMode = !dragMode;
6862
+ resizeMode = false;
6863
+ this.signLanguageWrapper.classList.toggle("vidply-sign-keyboard-drag", dragMode);
6864
+ this.signLanguageWrapper.classList.remove("vidply-sign-keyboard-resize");
6865
+ e.preventDefault();
6866
+ return;
6867
+ }
6868
+ if (e.key === "r" || e.key === "R") {
6869
+ resizeMode = !resizeMode;
6870
+ dragMode = false;
6871
+ this.signLanguageWrapper.classList.toggle("vidply-sign-keyboard-resize", resizeMode);
6872
+ this.signLanguageWrapper.classList.remove("vidply-sign-keyboard-drag");
6873
+ e.preventDefault();
6874
+ return;
6875
+ }
6876
+ if (e.key === "Escape") {
6877
+ dragMode = false;
6878
+ resizeMode = false;
6879
+ this.signLanguageWrapper.classList.remove("vidply-sign-keyboard-drag", "vidply-sign-keyboard-resize");
6880
+ e.preventDefault();
6881
+ return;
6882
+ }
6883
+ if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) {
6884
+ const step = e.shiftKey ? 10 : 5;
6885
+ const rect = this.signLanguageWrapper.getBoundingClientRect();
6886
+ const videoWrapperRect = this.videoWrapper.getBoundingClientRect();
6887
+ const containerRect = this.container.getBoundingClientRect();
6888
+ const videoWrapperLeft = videoWrapperRect.left - containerRect.left;
6889
+ const videoWrapperTop = videoWrapperRect.top - containerRect.top;
6890
+ if (dragMode) {
6891
+ let left = rect.left - containerRect.left;
6892
+ let top = rect.top - containerRect.top;
6893
+ if (e.key === "ArrowLeft") left -= step;
6894
+ if (e.key === "ArrowRight") left += step;
6895
+ if (e.key === "ArrowUp") top -= step;
6896
+ if (e.key === "ArrowDown") top += step;
6897
+ const controlsHeight = 95;
6898
+ left = Math.max(videoWrapperLeft, Math.min(left, videoWrapperLeft + videoWrapperRect.width - rect.width));
6899
+ top = Math.max(videoWrapperTop, Math.min(top, videoWrapperTop + videoWrapperRect.height - rect.height - controlsHeight));
6900
+ this.signLanguageWrapper.style.left = `${left}px`;
6901
+ this.signLanguageWrapper.style.top = `${top}px`;
6902
+ this.signLanguageWrapper.style.right = "auto";
6903
+ this.signLanguageWrapper.style.bottom = "auto";
6904
+ this.signLanguageWrapper.classList.remove(...Array.from(this.signLanguageWrapper.classList).filter((c) => c.startsWith("vidply-sign-position-")));
6905
+ this.saveSignLanguagePreferences();
6906
+ e.preventDefault();
6907
+ } else if (resizeMode) {
6908
+ let width = rect.width;
6909
+ if (e.key === "ArrowLeft") width -= step;
6910
+ if (e.key === "ArrowRight") width += step;
6911
+ if (e.key === "ArrowUp") width += step;
6912
+ if (e.key === "ArrowDown") width -= step;
6913
+ width = Math.max(150, width);
6914
+ width = Math.min(width, videoWrapperRect.width);
6915
+ this.signLanguageWrapper.style.width = `${width}px`;
6916
+ this.signLanguageWrapper.style.height = "auto";
6917
+ this.saveSignLanguagePreferences();
6918
+ e.preventDefault();
6919
+ }
6920
+ }
6921
+ };
6922
+ this.signLanguageVideo.addEventListener("mousedown", onMouseDownVideo);
6923
+ const handles = this.signLanguageWrapper.querySelectorAll(".vidply-sign-resize-handle");
6924
+ handles.forEach((handle) => handle.addEventListener("mousedown", onMouseDownHandle));
6925
+ document.addEventListener("mousemove", onMouseMove);
6926
+ document.addEventListener("mouseup", onMouseUp);
6927
+ this.signLanguageWrapper.addEventListener("keydown", onKeyDown);
6928
+ this.signLanguageInteractionHandlers = {
6929
+ mouseDownVideo: onMouseDownVideo,
6930
+ mouseDownHandle: onMouseDownHandle,
6931
+ mouseMove: onMouseMove,
6932
+ mouseUp: onMouseUp,
6933
+ keyDown: onKeyDown,
6934
+ handles
6935
+ };
6936
+ }
6937
+ constrainSignLanguagePosition() {
6938
+ if (!this.signLanguageWrapper || !this.videoWrapper) return;
6939
+ if (!this.signLanguageWrapper.style.width || this.signLanguageWrapper.style.width === "") {
6940
+ this.signLanguageWrapper.style.width = "280px";
6941
+ }
6942
+ const videoWrapperRect = this.videoWrapper.getBoundingClientRect();
6943
+ const containerRect = this.container.getBoundingClientRect();
6944
+ const wrapperRect = this.signLanguageWrapper.getBoundingClientRect();
6945
+ const videoWrapperLeft = videoWrapperRect.left - containerRect.left;
6946
+ const videoWrapperTop = videoWrapperRect.top - containerRect.top;
6947
+ const videoWrapperWidth = videoWrapperRect.width;
6948
+ const videoWrapperHeight = videoWrapperRect.height;
6949
+ let wrapperWidth = wrapperRect.width || 280;
6950
+ let wrapperHeight = wrapperRect.height || 280 * 9 / 16;
6951
+ let left, top;
6952
+ const margin = 16;
6953
+ const controlsHeight = 95;
6954
+ const position = this.signLanguageDesiredPosition || "bottom-right";
6955
+ switch (position) {
6956
+ case "bottom-right":
6957
+ left = videoWrapperLeft + videoWrapperWidth - wrapperWidth - margin;
6958
+ top = videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight;
6959
+ break;
6960
+ case "bottom-left":
6961
+ left = videoWrapperLeft + margin;
6962
+ top = videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight;
6963
+ break;
6964
+ case "top-right":
6965
+ left = videoWrapperLeft + videoWrapperWidth - wrapperWidth - margin;
6966
+ top = videoWrapperTop + margin;
6967
+ break;
6968
+ case "top-left":
6969
+ left = videoWrapperLeft + margin;
6970
+ top = videoWrapperTop + margin;
6971
+ break;
6972
+ default:
6973
+ left = videoWrapperLeft + videoWrapperWidth - wrapperWidth - margin;
6974
+ top = videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight;
6975
+ }
6976
+ left = Math.max(videoWrapperLeft, Math.min(left, videoWrapperLeft + videoWrapperWidth - wrapperWidth));
6977
+ top = Math.max(videoWrapperTop, Math.min(top, videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight));
6978
+ this.signLanguageWrapper.style.left = `${left}px`;
6979
+ this.signLanguageWrapper.style.top = `${top}px`;
6980
+ this.signLanguageWrapper.style.right = "auto";
6981
+ this.signLanguageWrapper.style.bottom = "auto";
6982
+ this.signLanguageWrapper.classList.remove(...Array.from(this.signLanguageWrapper.classList).filter((c) => c.startsWith("vidply-sign-position-")));
6983
+ }
6984
+ saveSignLanguagePreferences() {
6985
+ if (!this.signLanguageWrapper) return;
6986
+ this.storage.saveSignLanguagePreferences({
6987
+ size: {
6988
+ width: this.signLanguageWrapper.style.width
6989
+ // Height is auto - maintained by aspect ratio
6990
+ }
6991
+ });
6992
+ }
4991
6993
  cleanupSignLanguage() {
4992
6994
  if (this.signLanguageHandlers) {
4993
6995
  this.off("play", this.signLanguageHandlers.play);
@@ -4996,10 +6998,29 @@ var VidPly = (() => {
4996
6998
  this.off("ratechange", this.signLanguageHandlers.ratechange);
4997
6999
  this.signLanguageHandlers = null;
4998
7000
  }
4999
- if (this.signLanguageVideo && this.signLanguageVideo.parentNode) {
5000
- this.signLanguageVideo.pause();
5001
- this.signLanguageVideo.src = "";
5002
- this.signLanguageVideo.parentNode.removeChild(this.signLanguageVideo);
7001
+ if (this.signLanguageInteractionHandlers) {
7002
+ if (this.signLanguageVideo) {
7003
+ this.signLanguageVideo.removeEventListener("mousedown", this.signLanguageInteractionHandlers.mouseDownVideo);
7004
+ }
7005
+ if (this.signLanguageInteractionHandlers.handles) {
7006
+ this.signLanguageInteractionHandlers.handles.forEach((handle) => {
7007
+ handle.removeEventListener("mousedown", this.signLanguageInteractionHandlers.mouseDownHandle);
7008
+ });
7009
+ }
7010
+ document.removeEventListener("mousemove", this.signLanguageInteractionHandlers.mouseMove);
7011
+ document.removeEventListener("mouseup", this.signLanguageInteractionHandlers.mouseUp);
7012
+ if (this.signLanguageWrapper) {
7013
+ this.signLanguageWrapper.removeEventListener("keydown", this.signLanguageInteractionHandlers.keyDown);
7014
+ }
7015
+ this.signLanguageInteractionHandlers = null;
7016
+ }
7017
+ if (this.signLanguageWrapper && this.signLanguageWrapper.parentNode) {
7018
+ if (this.signLanguageVideo) {
7019
+ this.signLanguageVideo.pause();
7020
+ this.signLanguageVideo.src = "";
7021
+ }
7022
+ this.signLanguageWrapper.parentNode.removeChild(this.signLanguageWrapper);
7023
+ this.signLanguageWrapper = null;
5003
7024
  this.signLanguageVideo = null;
5004
7025
  }
5005
7026
  }
@@ -5102,6 +7123,16 @@ var VidPly = (() => {
5102
7123
  if (this.controlBar) {
5103
7124
  this.controlBar.updateFullscreenButton();
5104
7125
  }
7126
+ if (this.signLanguageWrapper && this.signLanguageWrapper.style.display !== "none") {
7127
+ setTimeout(() => {
7128
+ requestAnimationFrame(() => {
7129
+ this.storage.saveSignLanguagePreferences({ size: null });
7130
+ this.signLanguageDesiredPosition = "bottom-right";
7131
+ this.signLanguageWrapper.style.width = isFullscreen ? "400px" : "280px";
7132
+ this.constrainSignLanguagePosition();
7133
+ });
7134
+ }, 500);
7135
+ }
5105
7136
  }
5106
7137
  };
5107
7138
  document.addEventListener("fullscreenchange", this.fullscreenChangeHandler);