vidply 1.2.0 → 1.2.1

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.
Files changed (164) hide show
  1. package/README.md +946 -946
  2. package/dist/dev/{vidply.AudioDescriptionManager-R47Q4YR4.js → vidply.AudioDescriptionManager-6RFVF27Z.js} +275 -19
  3. package/dist/dev/vidply.AudioDescriptionManager-6RFVF27Z.js.map +7 -0
  4. package/dist/dev/{vidply.CaptionStyleMenu-7XHRBWUM.js → vidply.CaptionStyleMenu-R2SCQ6VP.js} +4 -4
  5. package/dist/dev/{vidply.DASHRenderer-PWEMC3BT.js → vidply.DASHRenderer-EOVEW73H.js} +3 -3
  6. package/dist/dev/vidply.DASHRenderer-EOVEW73H.js.map +7 -0
  7. package/dist/dev/{vidply.FloatingPlayerManager-PKNNNAS2.js → vidply.FloatingPlayerManager-V5XQWS75.js} +5 -5
  8. package/dist/dev/vidply.FloatingPlayerManager-V5XQWS75.js.map +7 -0
  9. package/dist/dev/{vidply.HLSRenderer-E7RQ74HI.js → vidply.HLSRenderer-ZBEFVH5W.js} +4 -4
  10. package/dist/dev/vidply.HLSRenderer-ZBEFVH5W.js.map +7 -0
  11. package/dist/dev/vidply.HTML5Renderer-P7ICKYU4.js +12 -0
  12. package/dist/dev/{vidply.SignLanguageManager-FET5FQZ7.js → vidply.SignLanguageManager-7IAJOVF6.js} +8 -8
  13. package/dist/dev/vidply.SignLanguageManager-7IAJOVF6.js.map +7 -0
  14. package/dist/dev/{vidply.SoundCloudRenderer-RVSJZODO.js → vidply.SoundCloudRenderer-OA7QQYGN.js} +3 -3
  15. package/dist/dev/vidply.SoundCloudRenderer-OA7QQYGN.js.map +7 -0
  16. package/dist/dev/{vidply.TranscriptManager-5GXOQCLX.js → vidply.TranscriptManager-BPM3T23L.js} +10 -10
  17. package/dist/dev/vidply.TranscriptManager-BPM3T23L.js.map +7 -0
  18. package/dist/dev/{vidply.VimeoRenderer-ER2LSBBW.js → vidply.VimeoRenderer-BRHFYPNJ.js} +3 -3
  19. package/dist/dev/vidply.VimeoRenderer-BRHFYPNJ.js.map +7 -0
  20. package/dist/dev/{vidply.YouTubeRenderer-NFTOWJAB.js → vidply.YouTubeRenderer-HRTN4EJ5.js} +3 -3
  21. package/dist/dev/vidply.YouTubeRenderer-HRTN4EJ5.js.map +7 -0
  22. package/dist/dev/{vidply.chunk-6RQA4ROD.js → vidply.chunk-5IK3IC5V.js} +2 -2
  23. package/dist/dev/{vidply.chunk-JJ47NO24.js → vidply.chunk-C55OBT5H.js} +2 -2
  24. package/dist/dev/vidply.chunk-C55OBT5H.js.map +7 -0
  25. package/dist/dev/{vidply.chunk-DZSDO3B7.js → vidply.chunk-HOYEMJSD.js} +2 -2
  26. package/dist/dev/{vidply.chunk-VMSQAZM7.js → vidply.chunk-JAKZNGOR.js} +2 -2
  27. package/dist/dev/{vidply.chunk-64WXMTGL.js → vidply.chunk-LDVFAFGC.js} +5 -5
  28. package/dist/dev/vidply.chunk-LDVFAFGC.js.map +7 -0
  29. package/dist/dev/{vidply.chunk-RZWZGDV7.js → vidply.chunk-NSMAZCMB.js} +6 -6
  30. package/dist/dev/vidply.chunk-NSMAZCMB.js.map +7 -0
  31. package/dist/dev/{vidply.chunk-KI7C3QVL.js → vidply.chunk-RQG3MA7W.js} +2 -2
  32. package/dist/dev/vidply.chunk-RQG3MA7W.js.map +7 -0
  33. package/dist/dev/{vidply.chunk-BNFIUL2Z.js → vidply.chunk-UHMVDBDB.js} +3 -3
  34. package/dist/dev/vidply.chunk-UHMVDBDB.js.map +7 -0
  35. package/dist/dev/{vidply.chunk-NQPURMQY.js → vidply.chunk-W5BR65JY.js} +6 -6
  36. package/dist/dev/vidply.chunk-W5BR65JY.js.map +7 -0
  37. package/dist/dev/{vidply.chunk-W25HUCHS.js → vidply.chunk-XAK3DPTO.js} +2 -2
  38. package/dist/dev/vidply.chunk-XAK3DPTO.js.map +7 -0
  39. package/dist/dev/{vidply.chunk-SK4V2DAU.js → vidply.chunk-YVCYQZJI.js} +3 -3
  40. package/dist/dev/{vidply.de-4YAT7FFM.js → vidply.de-5W5LBFSJ.js} +2 -2
  41. package/dist/dev/vidply.de-5W5LBFSJ.js.map +7 -0
  42. package/dist/dev/{vidply.es-LEWIRUBH.js → vidply.es-SHDAT6L6.js} +2 -2
  43. package/dist/dev/vidply.es-SHDAT6L6.js.map +7 -0
  44. package/dist/dev/vidply.esm.js +73 -34
  45. package/dist/dev/vidply.esm.js.map +2 -2
  46. package/dist/dev/{vidply.fr-Q4GV2NOE.js → vidply.fr-2JANOMNB.js} +2 -2
  47. package/dist/dev/vidply.fr-2JANOMNB.js.map +7 -0
  48. package/dist/dev/{vidply.ja-S6ITA6WU.js → vidply.ja-WWMSTEJH.js} +2 -2
  49. package/dist/dev/vidply.ja-WWMSTEJH.js.map +7 -0
  50. package/dist/legacy/vidply.js +345 -32
  51. package/dist/legacy/vidply.js.map +3 -3
  52. package/dist/legacy/vidply.min.js +2 -2
  53. package/dist/legacy/vidply.min.meta.json +66 -47
  54. package/dist/prod/vidply.AudioDescriptionManager-YTDVDQBO.min.js +6 -0
  55. package/dist/prod/{vidply.CaptionStyleMenu-M3KVQWB5.min.js → vidply.CaptionStyleMenu-2AFF3HF2.min.js} +2 -2
  56. package/dist/prod/{vidply.DASHRenderer-DN3S27AV.min.js → vidply.DASHRenderer-URDOTOKS.min.js} +2 -2
  57. package/dist/prod/{vidply.FloatingPlayerManager-SKAZ4J43.min.js → vidply.FloatingPlayerManager-QUBFK7CQ.min.js} +2 -2
  58. package/dist/prod/{vidply.HLSRenderer-62HH4YTK.min.js → vidply.HLSRenderer-46KNUVAB.min.js} +2 -2
  59. package/dist/prod/vidply.HTML5Renderer-7TQDMIGY.min.js +6 -0
  60. package/dist/prod/{vidply.SignLanguageManager-V3OBTSSF.min.js → vidply.SignLanguageManager-H4C36EVL.min.js} +2 -2
  61. package/dist/prod/{vidply.SoundCloudRenderer-CXRVZIAJ.min.js → vidply.SoundCloudRenderer-OYSWMW7D.min.js} +2 -2
  62. package/dist/prod/{vidply.TranscriptManager-SYM3DFUY.min.js → vidply.TranscriptManager-BA3DAS63.min.js} +2 -2
  63. package/dist/prod/{vidply.VimeoRenderer-72TWRYFQ.min.js → vidply.VimeoRenderer-NF3COTHU.min.js} +2 -2
  64. package/dist/prod/{vidply.YouTubeRenderer-UMHEV3KD.min.js → vidply.YouTubeRenderer-UCQKYIJ4.min.js} +2 -2
  65. package/dist/prod/{vidply.chunk-Z3YDSA7O.min.js → vidply.chunk-2HNCP64N.min.js} +1 -1
  66. package/dist/prod/{vidply.chunk-EKEYJWMG.min.js → vidply.chunk-6ECBIYVI.min.js} +2 -2
  67. package/dist/prod/{vidply.chunk-YEENJS3Q.min.js → vidply.chunk-7S5AFTST.min.js} +2 -2
  68. package/dist/prod/{vidply.chunk-P4UXOHWA.min.js → vidply.chunk-EUOA2DJK.min.js} +1 -1
  69. package/dist/prod/{vidply.chunk-2TB6MUHO.min.js → vidply.chunk-G6KGXNEW.min.js} +1 -1
  70. package/dist/prod/{vidply.chunk-ABPXVDLZ.min.js → vidply.chunk-JPMAK3NS.min.js} +1 -1
  71. package/dist/prod/{vidply.chunk-P3PQBY7N.min.js → vidply.chunk-L4O44K63.min.js} +1 -1
  72. package/dist/prod/{vidply.chunk-WX3MVEAL.min.js → vidply.chunk-M76ABMKB.min.js} +1 -1
  73. package/dist/prod/{vidply.chunk-TM6AXJVD.min.js → vidply.chunk-WRVDVD33.min.js} +2 -2
  74. package/dist/prod/{vidply.chunk-RI3X7WHK.min.js → vidply.chunk-X2FQEQYR.min.js} +2 -2
  75. package/dist/prod/{vidply.chunk-UK7YGOB5.min.js → vidply.chunk-XXUH7ADU.min.js} +2 -2
  76. package/dist/prod/{vidply.de-BCHXRQ53.min.js → vidply.de-JMCATTKZ.min.js} +1 -1
  77. package/dist/prod/{vidply.es-PICAKMZD.min.js → vidply.es-YVG37V7Z.min.js} +1 -1
  78. package/dist/prod/vidply.esm.min.js +2 -2
  79. package/dist/prod/{vidply.fr-ZILI7CDR.min.js → vidply.fr-OGKGN2R4.min.js} +1 -1
  80. package/dist/prod/{vidply.ja-5WQ4OK4J.min.js → vidply.ja-Z6DS7QLS.min.js} +1 -1
  81. package/dist/vidply.css +5796 -5796
  82. package/dist/vidply.esm.min.meta.json +148 -135
  83. package/package.json +7 -7
  84. package/src/controls/CaptionManager.ts +598 -598
  85. package/src/controls/ControlBar.ts +4159 -4159
  86. package/src/controls/KeyboardManager.ts +383 -383
  87. package/src/controls/SettingsDialog.ts +488 -488
  88. package/src/controls/TranscriptManager.ts +2426 -2426
  89. package/src/core/AudioDescriptionManager.ts +863 -774
  90. package/src/core/DescriptionSpeechManager.ts +232 -0
  91. package/src/core/FloatingPlayerManager.ts +674 -674
  92. package/src/core/LazyInit.ts +80 -80
  93. package/src/core/MetadataAlertsManager.ts +514 -514
  94. package/src/core/Player.ts +2915 -2872
  95. package/src/core/PosterManager.ts +169 -169
  96. package/src/core/PseudoFullscreen.ts +179 -179
  97. package/src/core/ResponsiveManager.ts +231 -231
  98. package/src/core/ResumeManager.ts +236 -236
  99. package/src/core/SignLanguageManager.ts +1308 -1308
  100. package/src/core/ThemeManager.ts +157 -157
  101. package/src/features/PlaylistManager.ts +1838 -1827
  102. package/src/i18n/i18n.ts +223 -223
  103. package/src/i18n/languages/de.ts +217 -217
  104. package/src/i18n/languages/en.ts +218 -218
  105. package/src/i18n/languages/es.ts +214 -214
  106. package/src/i18n/languages/fr.ts +214 -214
  107. package/src/i18n/languages/ja.ts +214 -214
  108. package/src/icons/Icons.ts +167 -167
  109. package/src/index.ts +196 -193
  110. package/src/renderers/DASHRenderer.ts +1077 -1077
  111. package/src/renderers/HLSRenderer.ts +944 -944
  112. package/src/renderers/HTML5Renderer.ts +389 -389
  113. package/src/renderers/SoundCloudRenderer.ts +437 -437
  114. package/src/renderers/VimeoRenderer.ts +298 -298
  115. package/src/renderers/YouTubeRenderer.ts +290 -290
  116. package/src/styles/vidply.css +5796 -5796
  117. package/src/types/events.ts +124 -122
  118. package/src/types/globals.d.ts +313 -313
  119. package/src/types/options.ts +185 -182
  120. package/src/types/renderer.ts +58 -58
  121. package/src/utils/DOMUtils.ts +281 -281
  122. package/src/utils/DownloadInfo.ts +171 -171
  123. package/src/utils/DraggablePanel.ts +539 -539
  124. package/src/utils/DraggablePanelMenu.ts +195 -195
  125. package/src/utils/DraggableResizable.ts +997 -997
  126. package/src/utils/EventEmitter.ts +59 -59
  127. package/src/utils/FormUtils.ts +95 -95
  128. package/src/utils/MenuFactory.ts +483 -483
  129. package/src/utils/Sanitize.ts +70 -70
  130. package/src/utils/SettingsMenuFactory.ts +289 -289
  131. package/src/utils/StorageManager.ts +287 -287
  132. package/src/utils/TrackLabelUtils.ts +52 -52
  133. package/src/utils/UrlSafe.ts +69 -69
  134. package/src/utils/VideoFrameCapture.ts +114 -114
  135. package/src/utils/WindowComponents.ts +250 -250
  136. package/dist/dev/vidply.AudioDescriptionManager-R47Q4YR4.js.map +0 -7
  137. package/dist/dev/vidply.DASHRenderer-PWEMC3BT.js.map +0 -7
  138. package/dist/dev/vidply.FloatingPlayerManager-PKNNNAS2.js.map +0 -7
  139. package/dist/dev/vidply.HLSRenderer-E7RQ74HI.js.map +0 -7
  140. package/dist/dev/vidply.HTML5Renderer-BU4QJJJY.js +0 -12
  141. package/dist/dev/vidply.SignLanguageManager-FET5FQZ7.js.map +0 -7
  142. package/dist/dev/vidply.SoundCloudRenderer-RVSJZODO.js.map +0 -7
  143. package/dist/dev/vidply.TranscriptManager-5GXOQCLX.js.map +0 -7
  144. package/dist/dev/vidply.VimeoRenderer-ER2LSBBW.js.map +0 -7
  145. package/dist/dev/vidply.YouTubeRenderer-NFTOWJAB.js.map +0 -7
  146. package/dist/dev/vidply.chunk-64WXMTGL.js.map +0 -7
  147. package/dist/dev/vidply.chunk-BNFIUL2Z.js.map +0 -7
  148. package/dist/dev/vidply.chunk-JJ47NO24.js.map +0 -7
  149. package/dist/dev/vidply.chunk-KI7C3QVL.js.map +0 -7
  150. package/dist/dev/vidply.chunk-NQPURMQY.js.map +0 -7
  151. package/dist/dev/vidply.chunk-RZWZGDV7.js.map +0 -7
  152. package/dist/dev/vidply.chunk-W25HUCHS.js.map +0 -7
  153. package/dist/dev/vidply.de-4YAT7FFM.js.map +0 -7
  154. package/dist/dev/vidply.es-LEWIRUBH.js.map +0 -7
  155. package/dist/dev/vidply.fr-Q4GV2NOE.js.map +0 -7
  156. package/dist/dev/vidply.ja-S6ITA6WU.js.map +0 -7
  157. package/dist/prod/vidply.AudioDescriptionManager-RHLIJW5C.min.js +0 -6
  158. package/dist/prod/vidply.HTML5Renderer-IU5T3WEW.min.js +0 -6
  159. /package/dist/dev/{vidply.CaptionStyleMenu-7XHRBWUM.js.map → vidply.CaptionStyleMenu-R2SCQ6VP.js.map} +0 -0
  160. /package/dist/dev/{vidply.HTML5Renderer-BU4QJJJY.js.map → vidply.HTML5Renderer-P7ICKYU4.js.map} +0 -0
  161. /package/dist/dev/{vidply.chunk-6RQA4ROD.js.map → vidply.chunk-5IK3IC5V.js.map} +0 -0
  162. /package/dist/dev/{vidply.chunk-DZSDO3B7.js.map → vidply.chunk-HOYEMJSD.js.map} +0 -0
  163. /package/dist/dev/{vidply.chunk-VMSQAZM7.js.map → vidply.chunk-JAKZNGOR.js.map} +0 -0
  164. /package/dist/dev/{vidply.chunk-SK4V2DAU.js.map → vidply.chunk-YVCYQZJI.js.map} +0 -0
@@ -1,157 +1,157 @@
1
- /**
2
- * Theme + CSS custom-property support extracted from Player.
3
- *
4
- * Every path that used to live on `Player.applyTheme` /
5
- * `Player.setTheme` / `Player.setThemeVariable` / `Player.resetTheme`
6
- * now routes through this module. Player keeps thin public delegates
7
- * so existing consumers (`player.setTheme('light')`, etc.) keep
8
- * working unchanged.
9
- *
10
- * The validation rules are deliberately strict: names must start with
11
- * `--vidply-` followed by `[A-Za-z0-9_-]+`, values must be short and
12
- * must not contain characters that would let a malicious override
13
- * escape the declaration or rule. That way a consumer that passes
14
- * user-supplied text through `setThemeVariable` cannot accidentally
15
- * turn the player into a CSS-injection sink.
16
- */
17
-
18
- import { isForbiddenKey } from '../utils/Sanitize.js';
19
- import type { Player } from './Player.js';
20
-
21
- export type ThemeName = 'dark' | 'light' | 'minimal' | 'high-contrast';
22
-
23
- export const PLAYER_THEMES: readonly ThemeName[] = ['dark', 'light', 'minimal', 'high-contrast'];
24
-
25
- /**
26
- * Validate a CSS variable name before it reaches `setProperty`. The
27
- * browser would silently drop unparseable names; the bigger risk is an
28
- * attacker smuggling `;` / `:` to escape into another declaration, so
29
- * the allow-list is intentionally narrow.
30
- */
31
- export function isValidThemeVariableName(name: string): boolean {
32
- return /^--vidply-[A-Za-z0-9_-]{1,64}$/.test(name);
33
- }
34
-
35
- export function isValidThemeVariableValue(value: unknown): value is string {
36
- if (typeof value !== 'string') return false;
37
- if (value.length > 200) return false;
38
- return !/[<>{};@\\]/.test(value);
39
- }
40
-
41
- export class ThemeManager {
42
- private readonly player: Player;
43
-
44
- constructor(player: Player) {
45
- this.player = player;
46
- }
47
-
48
- /**
49
- * Apply `options.theme` and validate-and-apply every entry in
50
- * `options.themeVariables` to the container. Bad entries are logged
51
- * and skipped so a single malformed override cannot poison siblings.
52
- */
53
- apply(): void {
54
- const player = this.player;
55
- if (!player.container) return;
56
-
57
- // Clear any previous theme class so switching themes at runtime
58
- // does not leave two active theme classes on the element.
59
- const themeClasses = PLAYER_THEMES.map(t => `${player.options.classPrefix}-theme-${t}`);
60
- player.container.classList.remove(...themeClasses);
61
-
62
- const theme = player.options.theme as ThemeName | undefined;
63
- if (theme && (PLAYER_THEMES as readonly string[]).includes(theme)) {
64
- player.container.classList.add(`${player.options.classPrefix}-theme-${theme}`);
65
- }
66
-
67
- if (player.options.themeVariables && typeof player.options.themeVariables === 'object') {
68
- for (const [rawKey, rawValue] of Object.entries(player.options.themeVariables)) {
69
- if (isForbiddenKey(rawKey)) continue;
70
- const cssVar = rawKey.startsWith('--vidply-') ? rawKey : `--vidply-${rawKey}`;
71
- if (!isValidThemeVariableName(cssVar)) {
72
- player.log(`[VidPly] Ignoring invalid theme variable name: ${rawKey}`, 'warn');
73
- continue;
74
- }
75
- if (!isValidThemeVariableValue(rawValue)) {
76
- player.log(`[VidPly] Ignoring invalid theme variable value for ${cssVar}`, 'warn');
77
- continue;
78
- }
79
- player.container.style.setProperty(cssVar, rawValue);
80
- }
81
- }
82
- }
83
-
84
- /**
85
- * Swap the active theme at runtime. Emits `themechange` with the old
86
- * and new names so external consumers (e.g. telemetry) can react.
87
- */
88
- set(themeName: ThemeName, customVariables: Record<string, string> = {}): void {
89
- const player = this.player;
90
- const previousTheme = player.options.theme;
91
-
92
- player.options.theme = themeName;
93
-
94
- if (customVariables && Object.keys(customVariables).length > 0) {
95
- player.options.themeVariables = {
96
- ...player.options.themeVariables,
97
- ...customVariables
98
- };
99
- }
100
-
101
- this.apply();
102
-
103
- player.emit('themechange', {
104
- theme: themeName,
105
- previousTheme,
106
- customVariables: player.options.themeVariables
107
- });
108
- }
109
-
110
- get(): ThemeName | undefined {
111
- return this.player.options.theme as ThemeName | undefined;
112
- }
113
-
114
- /** Set a single CSS variable override, validating the (name, value)
115
- * pair before it reaches the DOM. Callers must pass a string value. */
116
- setVariable(variableName: string, value: string): void {
117
- const player = this.player;
118
- if (!player.container) return;
119
-
120
- const cssVar = variableName.startsWith('--vidply-')
121
- ? variableName
122
- : `--vidply-${variableName}`;
123
-
124
- if (!isValidThemeVariableName(cssVar) || !isValidThemeVariableValue(value)) {
125
- player.log(`[VidPly] Ignoring unsafe setThemeVariable(${variableName})`, 'warn');
126
- return;
127
- }
128
-
129
- player.container.style.setProperty(cssVar, value);
130
-
131
- if (!player.options.themeVariables) {
132
- player.options.themeVariables = {};
133
- }
134
- player.options.themeVariables[variableName] = value;
135
- }
136
-
137
- /**
138
- * Reset to the default theme (dark) and clear every override that was
139
- * applied through `options.themeVariables`.
140
- */
141
- reset(): void {
142
- const player = this.player;
143
- if (player.container && player.options.themeVariables) {
144
- Object.keys(player.options.themeVariables).forEach(key => {
145
- const cssVar = key.startsWith('--vidply-') ? key : `--vidply-${key}`;
146
- player.container.style.removeProperty(cssVar);
147
- });
148
- }
149
-
150
- const previousTheme = player.options.theme;
151
- player.options.theme = 'dark';
152
- player.options.themeVariables = {};
153
-
154
- this.apply();
155
- player.emit('themechange', { theme: 'dark', previousTheme });
156
- }
157
- }
1
+ /**
2
+ * Theme + CSS custom-property support extracted from Player.
3
+ *
4
+ * Every path that used to live on `Player.applyTheme` /
5
+ * `Player.setTheme` / `Player.setThemeVariable` / `Player.resetTheme`
6
+ * now routes through this module. Player keeps thin public delegates
7
+ * so existing consumers (`player.setTheme('light')`, etc.) keep
8
+ * working unchanged.
9
+ *
10
+ * The validation rules are deliberately strict: names must start with
11
+ * `--vidply-` followed by `[A-Za-z0-9_-]+`, values must be short and
12
+ * must not contain characters that would let a malicious override
13
+ * escape the declaration or rule. That way a consumer that passes
14
+ * user-supplied text through `setThemeVariable` cannot accidentally
15
+ * turn the player into a CSS-injection sink.
16
+ */
17
+
18
+ import { isForbiddenKey } from '../utils/Sanitize.js';
19
+ import type { Player } from './Player.js';
20
+
21
+ export type ThemeName = 'dark' | 'light' | 'minimal' | 'high-contrast';
22
+
23
+ export const PLAYER_THEMES: readonly ThemeName[] = ['dark', 'light', 'minimal', 'high-contrast'];
24
+
25
+ /**
26
+ * Validate a CSS variable name before it reaches `setProperty`. The
27
+ * browser would silently drop unparseable names; the bigger risk is an
28
+ * attacker smuggling `;` / `:` to escape into another declaration, so
29
+ * the allow-list is intentionally narrow.
30
+ */
31
+ export function isValidThemeVariableName(name: string): boolean {
32
+ return /^--vidply-[A-Za-z0-9_-]{1,64}$/.test(name);
33
+ }
34
+
35
+ export function isValidThemeVariableValue(value: unknown): value is string {
36
+ if (typeof value !== 'string') return false;
37
+ if (value.length > 200) return false;
38
+ return !/[<>{};@\\]/.test(value);
39
+ }
40
+
41
+ export class ThemeManager {
42
+ private readonly player: Player;
43
+
44
+ constructor(player: Player) {
45
+ this.player = player;
46
+ }
47
+
48
+ /**
49
+ * Apply `options.theme` and validate-and-apply every entry in
50
+ * `options.themeVariables` to the container. Bad entries are logged
51
+ * and skipped so a single malformed override cannot poison siblings.
52
+ */
53
+ apply(): void {
54
+ const player = this.player;
55
+ if (!player.container) return;
56
+
57
+ // Clear any previous theme class so switching themes at runtime
58
+ // does not leave two active theme classes on the element.
59
+ const themeClasses = PLAYER_THEMES.map(t => `${player.options.classPrefix}-theme-${t}`);
60
+ player.container.classList.remove(...themeClasses);
61
+
62
+ const theme = player.options.theme as ThemeName | undefined;
63
+ if (theme && (PLAYER_THEMES as readonly string[]).includes(theme)) {
64
+ player.container.classList.add(`${player.options.classPrefix}-theme-${theme}`);
65
+ }
66
+
67
+ if (player.options.themeVariables && typeof player.options.themeVariables === 'object') {
68
+ for (const [rawKey, rawValue] of Object.entries(player.options.themeVariables)) {
69
+ if (isForbiddenKey(rawKey)) continue;
70
+ const cssVar = rawKey.startsWith('--vidply-') ? rawKey : `--vidply-${rawKey}`;
71
+ if (!isValidThemeVariableName(cssVar)) {
72
+ player.log(`[VidPly] Ignoring invalid theme variable name: ${rawKey}`, 'warn');
73
+ continue;
74
+ }
75
+ if (!isValidThemeVariableValue(rawValue)) {
76
+ player.log(`[VidPly] Ignoring invalid theme variable value for ${cssVar}`, 'warn');
77
+ continue;
78
+ }
79
+ player.container.style.setProperty(cssVar, rawValue);
80
+ }
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Swap the active theme at runtime. Emits `themechange` with the old
86
+ * and new names so external consumers (e.g. telemetry) can react.
87
+ */
88
+ set(themeName: ThemeName, customVariables: Record<string, string> = {}): void {
89
+ const player = this.player;
90
+ const previousTheme = player.options.theme;
91
+
92
+ player.options.theme = themeName;
93
+
94
+ if (customVariables && Object.keys(customVariables).length > 0) {
95
+ player.options.themeVariables = {
96
+ ...player.options.themeVariables,
97
+ ...customVariables
98
+ };
99
+ }
100
+
101
+ this.apply();
102
+
103
+ player.emit('themechange', {
104
+ theme: themeName,
105
+ previousTheme,
106
+ customVariables: player.options.themeVariables
107
+ });
108
+ }
109
+
110
+ get(): ThemeName | undefined {
111
+ return this.player.options.theme as ThemeName | undefined;
112
+ }
113
+
114
+ /** Set a single CSS variable override, validating the (name, value)
115
+ * pair before it reaches the DOM. Callers must pass a string value. */
116
+ setVariable(variableName: string, value: string): void {
117
+ const player = this.player;
118
+ if (!player.container) return;
119
+
120
+ const cssVar = variableName.startsWith('--vidply-')
121
+ ? variableName
122
+ : `--vidply-${variableName}`;
123
+
124
+ if (!isValidThemeVariableName(cssVar) || !isValidThemeVariableValue(value)) {
125
+ player.log(`[VidPly] Ignoring unsafe setThemeVariable(${variableName})`, 'warn');
126
+ return;
127
+ }
128
+
129
+ player.container.style.setProperty(cssVar, value);
130
+
131
+ if (!player.options.themeVariables) {
132
+ player.options.themeVariables = {};
133
+ }
134
+ player.options.themeVariables[variableName] = value;
135
+ }
136
+
137
+ /**
138
+ * Reset to the default theme (dark) and clear every override that was
139
+ * applied through `options.themeVariables`.
140
+ */
141
+ reset(): void {
142
+ const player = this.player;
143
+ if (player.container && player.options.themeVariables) {
144
+ Object.keys(player.options.themeVariables).forEach(key => {
145
+ const cssVar = key.startsWith('--vidply-') ? key : `--vidply-${key}`;
146
+ player.container.style.removeProperty(cssVar);
147
+ });
148
+ }
149
+
150
+ const previousTheme = player.options.theme;
151
+ player.options.theme = 'dark';
152
+ player.options.themeVariables = {};
153
+
154
+ this.apply();
155
+ player.emit('themechange', { theme: 'dark', previousTheme });
156
+ }
157
+ }