ts-video-player 0.1.0

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 (251) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +438 -0
  3. package/dist/cdn.d.ts +10 -0
  4. package/dist/cdn.d.ts.map +1 -0
  5. package/dist/cdn.min.js +1046 -0
  6. package/dist/cdn.min.js.map +54 -0
  7. package/dist/cdn.test.d.ts +2 -0
  8. package/dist/cdn.test.d.ts.map +1 -0
  9. package/dist/chunk-36x4yhjn.js +765 -0
  10. package/dist/chunk-36x4yhjn.js.map +12 -0
  11. package/dist/chunk-eajts2jv.js +4243 -0
  12. package/dist/chunk-eajts2jv.js.map +42 -0
  13. package/dist/chunk-z1ehbz9f.js +380 -0
  14. package/dist/chunk-z1ehbz9f.js.map +19 -0
  15. package/dist/config.d.ts +9 -0
  16. package/dist/config.d.ts.map +1 -0
  17. package/dist/core/audio-gain.d.ts +67 -0
  18. package/dist/core/audio-gain.d.ts.map +1 -0
  19. package/dist/core/caption-styles.d.ts +121 -0
  20. package/dist/core/caption-styles.d.ts.map +1 -0
  21. package/dist/core/chapters.d.ts +150 -0
  22. package/dist/core/chapters.d.ts.map +1 -0
  23. package/dist/core/error-overlay.d.ts +71 -0
  24. package/dist/core/error-overlay.d.ts.map +1 -0
  25. package/dist/core/events.d.ts +119 -0
  26. package/dist/core/events.d.ts.map +1 -0
  27. package/dist/core/features.d.ts +64 -0
  28. package/dist/core/features.d.ts.map +1 -0
  29. package/dist/core/i18n.d.ts +132 -0
  30. package/dist/core/i18n.d.ts.map +1 -0
  31. package/dist/core/index.d.ts +37 -0
  32. package/dist/core/index.d.ts.map +1 -0
  33. package/dist/core/live.d.ts +130 -0
  34. package/dist/core/live.d.ts.map +1 -0
  35. package/dist/core/media-session.d.ts +38 -0
  36. package/dist/core/media-session.d.ts.map +1 -0
  37. package/dist/core/orientation.d.ts +22 -0
  38. package/dist/core/orientation.d.ts.map +1 -0
  39. package/dist/core/popover.d.ts +42 -0
  40. package/dist/core/popover.d.ts.map +1 -0
  41. package/dist/core/remote-playback.d.ts +195 -0
  42. package/dist/core/remote-playback.d.ts.map +1 -0
  43. package/dist/core/settings-menu.d.ts +132 -0
  44. package/dist/core/settings-menu.d.ts.map +1 -0
  45. package/dist/core/spinner.d.ts +67 -0
  46. package/dist/core/spinner.d.ts.map +1 -0
  47. package/dist/core/state.d.ts +142 -0
  48. package/dist/core/state.d.ts.map +1 -0
  49. package/dist/core/text-tracks.d.ts +162 -0
  50. package/dist/core/text-tracks.d.ts.map +1 -0
  51. package/dist/core/themes.d.ts +213 -0
  52. package/dist/core/themes.d.ts.map +1 -0
  53. package/dist/core/thumbnails.d.ts +124 -0
  54. package/dist/core/thumbnails.d.ts.map +1 -0
  55. package/dist/core/tooltips.d.ts +100 -0
  56. package/dist/core/tooltips.d.ts.map +1 -0
  57. package/dist/elements/base.d.ts +3 -0
  58. package/dist/elements/base.d.ts.map +1 -0
  59. package/dist/elements/index.d.ts +33 -0
  60. package/dist/elements/index.d.ts.map +1 -0
  61. package/dist/elements/index.js +55 -0
  62. package/dist/elements/index.js.map +9 -0
  63. package/dist/elements/media-airplay-button.d.ts +13 -0
  64. package/dist/elements/media-airplay-button.d.ts.map +1 -0
  65. package/dist/elements/media-error-display.d.ts +13 -0
  66. package/dist/elements/media-error-display.d.ts.map +1 -0
  67. package/dist/elements/media-fullscreen-button.d.ts +13 -0
  68. package/dist/elements/media-fullscreen-button.d.ts.map +1 -0
  69. package/dist/elements/media-live-button.d.ts +13 -0
  70. package/dist/elements/media-live-button.d.ts.map +1 -0
  71. package/dist/elements/media-mute-button.d.ts +13 -0
  72. package/dist/elements/media-mute-button.d.ts.map +1 -0
  73. package/dist/elements/media-pip-button.d.ts +13 -0
  74. package/dist/elements/media-pip-button.d.ts.map +1 -0
  75. package/dist/elements/media-play-button.d.ts +14 -0
  76. package/dist/elements/media-play-button.d.ts.map +1 -0
  77. package/dist/elements/media-playback-rate-button.d.ts +10 -0
  78. package/dist/elements/media-playback-rate-button.d.ts.map +1 -0
  79. package/dist/elements/media-preview-time.d.ts +18 -0
  80. package/dist/elements/media-preview-time.d.ts.map +1 -0
  81. package/dist/elements/media-progress-bar.d.ts +16 -0
  82. package/dist/elements/media-progress-bar.d.ts.map +1 -0
  83. package/dist/elements/media-remote-playback-button.d.ts +16 -0
  84. package/dist/elements/media-remote-playback-button.d.ts.map +1 -0
  85. package/dist/elements/media-seek-button.d.ts +9 -0
  86. package/dist/elements/media-seek-button.d.ts.map +1 -0
  87. package/dist/elements/media-settings-menu.d.ts +32 -0
  88. package/dist/elements/media-settings-menu.d.ts.map +1 -0
  89. package/dist/elements/media-time-display.d.ts +21 -0
  90. package/dist/elements/media-time-display.d.ts.map +1 -0
  91. package/dist/elements/media-time-group.d.ts +22 -0
  92. package/dist/elements/media-time-group.d.ts.map +1 -0
  93. package/dist/elements/media-time-separator.d.ts +13 -0
  94. package/dist/elements/media-time-separator.d.ts.map +1 -0
  95. package/dist/elements/media-volume-slider.d.ts +16 -0
  96. package/dist/elements/media-volume-slider.d.ts.map +1 -0
  97. package/dist/elements/utils.d.ts +27 -0
  98. package/dist/elements/utils.d.ts.map +1 -0
  99. package/dist/elements/video-player.d.ts +23 -0
  100. package/dist/elements/video-player.d.ts.map +1 -0
  101. package/dist/elements/video-skin.d.ts +23 -0
  102. package/dist/elements/video-skin.d.ts.map +1 -0
  103. package/dist/features/captions.d.ts +8 -0
  104. package/dist/features/captions.d.ts.map +1 -0
  105. package/dist/features/create-player.d.ts +45 -0
  106. package/dist/features/create-player.d.ts.map +1 -0
  107. package/dist/features/fullscreen.d.ts +8 -0
  108. package/dist/features/fullscreen.d.ts.map +1 -0
  109. package/dist/features/gestures.d.ts +8 -0
  110. package/dist/features/gestures.d.ts.map +1 -0
  111. package/dist/features/index.d.ts +26 -0
  112. package/dist/features/index.d.ts.map +1 -0
  113. package/dist/features/index.js +31 -0
  114. package/dist/features/index.js.map +9 -0
  115. package/dist/features/keyboard.d.ts +8 -0
  116. package/dist/features/keyboard.d.ts.map +1 -0
  117. package/dist/features/pip.d.ts +8 -0
  118. package/dist/features/pip.d.ts.map +1 -0
  119. package/dist/features/playback.d.ts +8 -0
  120. package/dist/features/playback.d.ts.map +1 -0
  121. package/dist/features/quality.d.ts +8 -0
  122. package/dist/features/quality.d.ts.map +1 -0
  123. package/dist/features/types.d.ts +26 -0
  124. package/dist/features/types.d.ts.map +1 -0
  125. package/dist/features/volume.d.ts +8 -0
  126. package/dist/features/volume.d.ts.map +1 -0
  127. package/dist/index.d.ts +84 -0
  128. package/dist/index.d.ts.map +1 -0
  129. package/dist/index.js +8149 -0
  130. package/dist/index.js.map +31 -0
  131. package/dist/layouts/index.d.ts +58 -0
  132. package/dist/layouts/index.d.ts.map +1 -0
  133. package/dist/player.d.ts +90 -0
  134. package/dist/player.d.ts.map +1 -0
  135. package/dist/plugins/ads.d.ts +311 -0
  136. package/dist/plugins/ads.d.ts.map +1 -0
  137. package/dist/plugins/analytics.d.ts +203 -0
  138. package/dist/plugins/analytics.d.ts.map +1 -0
  139. package/dist/plugins/end-screen.d.ts +184 -0
  140. package/dist/plugins/end-screen.d.ts.map +1 -0
  141. package/dist/plugins/index.d.ts +13 -0
  142. package/dist/plugins/index.d.ts.map +1 -0
  143. package/dist/plugins/index.js +20 -0
  144. package/dist/plugins/index.js.map +9 -0
  145. package/dist/plugins/skip-segments.d.ts +159 -0
  146. package/dist/plugins/skip-segments.d.ts.map +1 -0
  147. package/dist/plugins/watermark.d.ts +157 -0
  148. package/dist/plugins/watermark.d.ts.map +1 -0
  149. package/dist/providers/base.d.ts +56 -0
  150. package/dist/providers/base.d.ts.map +1 -0
  151. package/dist/providers/dash.d.ts +94 -0
  152. package/dist/providers/dash.d.ts.map +1 -0
  153. package/dist/providers/hls.d.ts +182 -0
  154. package/dist/providers/hls.d.ts.map +1 -0
  155. package/dist/providers/html5.d.ts +64 -0
  156. package/dist/providers/html5.d.ts.map +1 -0
  157. package/dist/providers/index.d.ts +41 -0
  158. package/dist/providers/index.d.ts.map +1 -0
  159. package/dist/providers/vimeo.d.ts +157 -0
  160. package/dist/providers/vimeo.d.ts.map +1 -0
  161. package/dist/providers/youtube.d.ts +134 -0
  162. package/dist/providers/youtube.d.ts.map +1 -0
  163. package/dist/stx/index.d.ts +59 -0
  164. package/dist/stx/index.d.ts.map +1 -0
  165. package/dist/stx/index.js +480 -0
  166. package/dist/stx/index.js.map +11 -0
  167. package/dist/types.d.ts +724 -0
  168. package/dist/types.d.ts.map +1 -0
  169. package/dist/ui/components.d.ts +121 -0
  170. package/dist/ui/components.d.ts.map +1 -0
  171. package/dist/ui/index.d.ts +9 -0
  172. package/dist/ui/index.d.ts.map +1 -0
  173. package/dist/ui/index.js +16 -0
  174. package/dist/ui/index.js.map +9 -0
  175. package/package.json +109 -0
  176. package/src/cdn.test.ts +10 -0
  177. package/src/cdn.ts +60 -0
  178. package/src/config.ts +25 -0
  179. package/src/core/audio-gain.ts +171 -0
  180. package/src/core/caption-styles.ts +568 -0
  181. package/src/core/chapters.ts +551 -0
  182. package/src/core/error-overlay.ts +354 -0
  183. package/src/core/events.ts +552 -0
  184. package/src/core/features.ts +287 -0
  185. package/src/core/i18n.ts +590 -0
  186. package/src/core/index.ts +249 -0
  187. package/src/core/live.ts +472 -0
  188. package/src/core/media-session.ts +137 -0
  189. package/src/core/orientation.ts +57 -0
  190. package/src/core/popover.ts +143 -0
  191. package/src/core/remote-playback.ts +661 -0
  192. package/src/core/settings-menu.ts +630 -0
  193. package/src/core/spinner.ts +310 -0
  194. package/src/core/state.ts +476 -0
  195. package/src/core/text-tracks.ts +599 -0
  196. package/src/core/themes.ts +607 -0
  197. package/src/core/thumbnails.ts +457 -0
  198. package/src/core/tooltips.ts +370 -0
  199. package/src/elements/base.ts +2 -0
  200. package/src/elements/index.ts +82 -0
  201. package/src/elements/media-airplay-button.ts +78 -0
  202. package/src/elements/media-error-display.ts +47 -0
  203. package/src/elements/media-fullscreen-button.ts +72 -0
  204. package/src/elements/media-live-button.ts +65 -0
  205. package/src/elements/media-mute-button.ts +69 -0
  206. package/src/elements/media-pip-button.ts +66 -0
  207. package/src/elements/media-play-button.ts +69 -0
  208. package/src/elements/media-playback-rate-button.ts +59 -0
  209. package/src/elements/media-preview-time.ts +75 -0
  210. package/src/elements/media-progress-bar.ts +200 -0
  211. package/src/elements/media-remote-playback-button.ts +108 -0
  212. package/src/elements/media-seek-button.ts +50 -0
  213. package/src/elements/media-settings-menu.ts +302 -0
  214. package/src/elements/media-time-display.ts +99 -0
  215. package/src/elements/media-time-group.ts +41 -0
  216. package/src/elements/media-time-separator.ts +35 -0
  217. package/src/elements/media-volume-slider.ts +140 -0
  218. package/src/elements/utils.ts +82 -0
  219. package/src/elements/video-player.ts +175 -0
  220. package/src/elements/video-skin.ts +127 -0
  221. package/src/features/captions.ts +56 -0
  222. package/src/features/create-player.ts +91 -0
  223. package/src/features/fullscreen.ts +44 -0
  224. package/src/features/gestures.ts +44 -0
  225. package/src/features/index.ts +44 -0
  226. package/src/features/keyboard.ts +89 -0
  227. package/src/features/pip.ts +47 -0
  228. package/src/features/playback.ts +68 -0
  229. package/src/features/quality.ts +19 -0
  230. package/src/features/types.ts +28 -0
  231. package/src/features/volume.ts +46 -0
  232. package/src/index.ts +635 -0
  233. package/src/layouts/index.ts +672 -0
  234. package/src/player.ts +821 -0
  235. package/src/plugins/ads.ts +1045 -0
  236. package/src/plugins/analytics.ts +673 -0
  237. package/src/plugins/end-screen.ts +793 -0
  238. package/src/plugins/index.ts +61 -0
  239. package/src/plugins/skip-segments.ts +537 -0
  240. package/src/plugins/watermark.ts +537 -0
  241. package/src/providers/base.ts +197 -0
  242. package/src/providers/dash.ts +588 -0
  243. package/src/providers/hls.ts +624 -0
  244. package/src/providers/html5.ts +473 -0
  245. package/src/providers/index.ts +97 -0
  246. package/src/providers/vimeo.ts +533 -0
  247. package/src/providers/youtube.ts +542 -0
  248. package/src/stx/index.ts +606 -0
  249. package/src/types.ts +874 -0
  250. package/src/ui/components.ts +910 -0
  251. package/src/ui/index.ts +29 -0
@@ -0,0 +1,672 @@
1
+ /**
2
+ * Player Layouts
3
+ *
4
+ * Pre-built player layouts for quick setup.
5
+ *
6
+ * @module layouts
7
+ */
8
+
9
+ import type { PlayerOptions } from '../types'
10
+
11
+ // =============================================================================
12
+ // Types
13
+ // =============================================================================
14
+
15
+ export interface LayoutConfig {
16
+ /** Layout name */
17
+ name: string
18
+ /** Layout description */
19
+ description: string
20
+ /** CSS styles to inject */
21
+ styles: string
22
+ /** HTML structure template */
23
+ template: string
24
+ /** Default player options for this layout */
25
+ defaultOptions?: Partial<PlayerOptions>
26
+ /** Initialize layout-specific behavior */
27
+ init?: (container: HTMLElement) => void
28
+ /** Cleanup layout */
29
+ destroy?: (container: HTMLElement) => void
30
+ }
31
+
32
+ export type LayoutName = 'default' | 'minimal' | 'cinema' | 'youtube' | 'vimeo' | 'audio'
33
+
34
+ // =============================================================================
35
+ // Default Layout
36
+ // =============================================================================
37
+
38
+ export const defaultLayout: LayoutConfig = {
39
+ name: 'default',
40
+ description: 'Full-featured default layout with all controls',
41
+ styles: `
42
+ .tsvp-layout-default {
43
+ --tsvp-controls-height: 48px;
44
+ }
45
+
46
+ .tsvp-layout-default .tsvp-controls {
47
+ display: flex;
48
+ flex-direction: column;
49
+ position: absolute;
50
+ bottom: 0;
51
+ left: 0;
52
+ right: 0;
53
+ background: var(--tsvp-controls-bg);
54
+ padding: var(--tsvp-controls-padding);
55
+ opacity: 0;
56
+ transition: opacity var(--tsvp-transition-normal) var(--tsvp-transition-easing);
57
+ }
58
+
59
+ .tsvp-layout-default:hover .tsvp-controls,
60
+ .tsvp-layout-default.tsvp-controls-visible .tsvp-controls {
61
+ opacity: 1;
62
+ }
63
+
64
+ .tsvp-layout-default .tsvp-controls-row {
65
+ display: flex;
66
+ align-items: center;
67
+ gap: var(--tsvp-spacing-sm);
68
+ }
69
+
70
+ .tsvp-layout-default .tsvp-controls-row-progress {
71
+ padding: 0 var(--tsvp-spacing-sm);
72
+ }
73
+
74
+ .tsvp-layout-default .tsvp-controls-row-buttons {
75
+ height: var(--tsvp-controls-height);
76
+ }
77
+
78
+ .tsvp-layout-default .tsvp-controls-left,
79
+ .tsvp-layout-default .tsvp-controls-right {
80
+ display: flex;
81
+ align-items: center;
82
+ gap: var(--tsvp-spacing-xs);
83
+ }
84
+
85
+ .tsvp-layout-default .tsvp-controls-center {
86
+ flex: 1;
87
+ display: flex;
88
+ align-items: center;
89
+ justify-content: center;
90
+ }
91
+
92
+ .tsvp-layout-default .tsvp-controls-spacer {
93
+ flex: 1;
94
+ }
95
+ `,
96
+ template: `
97
+ <div class="tsvp-video-container">
98
+ <slot name="video"></slot>
99
+ </div>
100
+ <div class="tsvp-poster"></div>
101
+ <div class="tsvp-captions"></div>
102
+ <div class="tsvp-gestures"></div>
103
+ <div class="tsvp-buffering-indicator"></div>
104
+ <div class="tsvp-controls">
105
+ <div class="tsvp-controls-row tsvp-controls-row-progress">
106
+ <slot name="progress"></slot>
107
+ </div>
108
+ <div class="tsvp-controls-row tsvp-controls-row-buttons">
109
+ <div class="tsvp-controls-left">
110
+ <slot name="play-button"></slot>
111
+ <slot name="volume"></slot>
112
+ <slot name="time"></slot>
113
+ <slot name="live-indicator"></slot>
114
+ </div>
115
+ <div class="tsvp-controls-spacer"></div>
116
+ <div class="tsvp-controls-right">
117
+ <slot name="captions-button"></slot>
118
+ <slot name="settings-button"></slot>
119
+ <slot name="pip-button"></slot>
120
+ <slot name="airplay-button"></slot>
121
+ <slot name="cast-button"></slot>
122
+ <slot name="fullscreen-button"></slot>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ `,
127
+ }
128
+
129
+ // =============================================================================
130
+ // Minimal Layout
131
+ // =============================================================================
132
+
133
+ export const minimalLayout: LayoutConfig = {
134
+ name: 'minimal',
135
+ description: 'Clean, minimal layout with essential controls only',
136
+ styles: `
137
+ .tsvp-layout-minimal {
138
+ --tsvp-controls-height: 36px;
139
+ --tsvp-slider-track-height: 2px;
140
+ --tsvp-slider-thumb-size: 10px;
141
+ }
142
+
143
+ .tsvp-layout-minimal .tsvp-controls {
144
+ display: flex;
145
+ align-items: center;
146
+ position: absolute;
147
+ bottom: 0;
148
+ left: 0;
149
+ right: 0;
150
+ height: var(--tsvp-controls-height);
151
+ background: linear-gradient(transparent, rgba(0,0,0,0.5));
152
+ padding: 0 var(--tsvp-spacing-sm);
153
+ opacity: 0;
154
+ transition: opacity var(--tsvp-transition-normal);
155
+ }
156
+
157
+ .tsvp-layout-minimal:hover .tsvp-controls {
158
+ opacity: 1;
159
+ }
160
+
161
+ .tsvp-layout-minimal .tsvp-progress {
162
+ flex: 1;
163
+ margin: 0 var(--tsvp-spacing-sm);
164
+ }
165
+ `,
166
+ template: `
167
+ <div class="tsvp-video-container">
168
+ <slot name="video"></slot>
169
+ </div>
170
+ <div class="tsvp-controls">
171
+ <slot name="play-button"></slot>
172
+ <slot name="progress"></slot>
173
+ <slot name="time"></slot>
174
+ <slot name="fullscreen-button"></slot>
175
+ </div>
176
+ `,
177
+ }
178
+
179
+ // =============================================================================
180
+ // Cinema Layout
181
+ // =============================================================================
182
+
183
+ export const cinemaLayout: LayoutConfig = {
184
+ name: 'cinema',
185
+ description: 'Immersive cinema-style layout with center play button',
186
+ styles: `
187
+ .tsvp-layout-cinema {
188
+ --tsvp-controls-height: 60px;
189
+ }
190
+
191
+ .tsvp-layout-cinema .tsvp-center-play {
192
+ position: absolute;
193
+ top: 50%;
194
+ left: 50%;
195
+ transform: translate(-50%, -50%);
196
+ width: 80px;
197
+ height: 80px;
198
+ border-radius: 50%;
199
+ background: rgba(0, 0, 0, 0.6);
200
+ display: flex;
201
+ align-items: center;
202
+ justify-content: center;
203
+ cursor: pointer;
204
+ opacity: 0;
205
+ transition: opacity var(--tsvp-transition-normal), transform var(--tsvp-transition-normal);
206
+ }
207
+
208
+ .tsvp-layout-cinema:hover .tsvp-center-play,
209
+ .tsvp-layout-cinema.tsvp-paused .tsvp-center-play {
210
+ opacity: 1;
211
+ }
212
+
213
+ .tsvp-layout-cinema .tsvp-center-play:hover {
214
+ transform: translate(-50%, -50%) scale(1.1);
215
+ background: rgba(0, 0, 0, 0.8);
216
+ }
217
+
218
+ .tsvp-layout-cinema .tsvp-center-play svg {
219
+ width: 40px;
220
+ height: 40px;
221
+ fill: white;
222
+ }
223
+
224
+ .tsvp-layout-cinema .tsvp-controls {
225
+ position: absolute;
226
+ bottom: 0;
227
+ left: 0;
228
+ right: 0;
229
+ background: linear-gradient(transparent, rgba(0,0,0,0.8));
230
+ padding: var(--tsvp-spacing-lg);
231
+ opacity: 0;
232
+ transform: translateY(10px);
233
+ transition: all var(--tsvp-transition-slow);
234
+ }
235
+
236
+ .tsvp-layout-cinema:hover .tsvp-controls {
237
+ opacity: 1;
238
+ transform: translateY(0);
239
+ }
240
+
241
+ .tsvp-layout-cinema .tsvp-title {
242
+ color: white;
243
+ font-size: var(--tsvp-font-size-lg);
244
+ font-weight: var(--tsvp-font-weight-bold);
245
+ margin-bottom: var(--tsvp-spacing-md);
246
+ }
247
+ `,
248
+ template: `
249
+ <div class="tsvp-video-container">
250
+ <slot name="video"></slot>
251
+ </div>
252
+ <div class="tsvp-poster"></div>
253
+ <div class="tsvp-captions"></div>
254
+ <div class="tsvp-center-play">
255
+ <svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
256
+ </div>
257
+ <div class="tsvp-controls">
258
+ <div class="tsvp-title"></div>
259
+ <div class="tsvp-controls-row">
260
+ <slot name="progress"></slot>
261
+ </div>
262
+ <div class="tsvp-controls-row">
263
+ <slot name="play-button"></slot>
264
+ <slot name="volume"></slot>
265
+ <slot name="time"></slot>
266
+ <div style="flex:1"></div>
267
+ <slot name="captions-button"></slot>
268
+ <slot name="settings-button"></slot>
269
+ <slot name="fullscreen-button"></slot>
270
+ </div>
271
+ </div>
272
+ `,
273
+ init: (container) => {
274
+ const centerPlay = container.querySelector('.tsvp-center-play')
275
+ if (centerPlay) {
276
+ centerPlay.addEventListener('click', () => {
277
+ container.dispatchEvent(new CustomEvent('tsvp:toggleplay'))
278
+ })
279
+ }
280
+ },
281
+ }
282
+
283
+ // =============================================================================
284
+ // YouTube-style Layout
285
+ // =============================================================================
286
+
287
+ export const youtubeLayout: LayoutConfig = {
288
+ name: 'youtube',
289
+ description: 'YouTube-inspired layout with familiar controls',
290
+ styles: `
291
+ .tsvp-layout-youtube {
292
+ --tsvp-color-primary: #ff0000;
293
+ --tsvp-slider-track-height: 3px;
294
+ --tsvp-slider-thumb-size: 13px;
295
+ --tsvp-controls-height: 48px;
296
+ }
297
+
298
+ .tsvp-layout-youtube .tsvp-controls {
299
+ position: absolute;
300
+ bottom: 0;
301
+ left: 0;
302
+ right: 0;
303
+ background: linear-gradient(transparent, rgba(0,0,0,0.7));
304
+ opacity: 0;
305
+ transition: opacity var(--tsvp-transition-normal);
306
+ }
307
+
308
+ .tsvp-layout-youtube:hover .tsvp-controls,
309
+ .tsvp-layout-youtube.tsvp-paused .tsvp-controls {
310
+ opacity: 1;
311
+ }
312
+
313
+ .tsvp-layout-youtube .tsvp-progress-container {
314
+ position: relative;
315
+ padding: 0 12px;
316
+ margin-bottom: -4px;
317
+ }
318
+
319
+ .tsvp-layout-youtube .tsvp-controls-row {
320
+ display: flex;
321
+ align-items: center;
322
+ height: var(--tsvp-controls-height);
323
+ padding: 0 4px;
324
+ }
325
+
326
+ .tsvp-layout-youtube .tsvp-chapter-markers {
327
+ position: absolute;
328
+ left: 12px;
329
+ right: 12px;
330
+ top: 0;
331
+ height: 100%;
332
+ pointer-events: none;
333
+ }
334
+
335
+ .tsvp-layout-youtube .tsvp-end-screen {
336
+ position: absolute;
337
+ inset: 0;
338
+ background: rgba(0, 0, 0, 0.85);
339
+ display: none;
340
+ align-items: center;
341
+ justify-content: center;
342
+ }
343
+
344
+ .tsvp-layout-youtube.tsvp-ended .tsvp-end-screen {
345
+ display: flex;
346
+ }
347
+ `,
348
+ template: `
349
+ <div class="tsvp-video-container">
350
+ <slot name="video"></slot>
351
+ </div>
352
+ <div class="tsvp-poster"></div>
353
+ <div class="tsvp-captions"></div>
354
+ <div class="tsvp-controls">
355
+ <div class="tsvp-progress-container">
356
+ <slot name="progress"></slot>
357
+ <div class="tsvp-chapter-markers"></div>
358
+ <slot name="thumbnail-preview"></slot>
359
+ </div>
360
+ <div class="tsvp-controls-row">
361
+ <slot name="play-button"></slot>
362
+ <slot name="next-button"></slot>
363
+ <slot name="volume"></slot>
364
+ <slot name="time"></slot>
365
+ <div style="flex:1"></div>
366
+ <slot name="autoplay-toggle"></slot>
367
+ <slot name="captions-button"></slot>
368
+ <slot name="settings-button"></slot>
369
+ <slot name="miniplayer-button"></slot>
370
+ <slot name="theater-button"></slot>
371
+ <slot name="fullscreen-button"></slot>
372
+ </div>
373
+ </div>
374
+ <div class="tsvp-end-screen">
375
+ <slot name="end-screen"></slot>
376
+ </div>
377
+ `,
378
+ }
379
+
380
+ // =============================================================================
381
+ // Vimeo-style Layout
382
+ // =============================================================================
383
+
384
+ export const vimeoLayout: LayoutConfig = {
385
+ name: 'vimeo',
386
+ description: 'Vimeo-inspired clean layout',
387
+ styles: `
388
+ .tsvp-layout-vimeo {
389
+ --tsvp-color-primary: #00adef;
390
+ --tsvp-controls-height: 44px;
391
+ --tsvp-slider-track-height: 4px;
392
+ }
393
+
394
+ .tsvp-layout-vimeo .tsvp-controls {
395
+ position: absolute;
396
+ bottom: 0;
397
+ left: 0;
398
+ right: 0;
399
+ display: flex;
400
+ flex-direction: column;
401
+ background: linear-gradient(transparent, rgba(0,0,0,0.6));
402
+ padding: var(--tsvp-spacing-sm);
403
+ opacity: 0;
404
+ transition: opacity var(--tsvp-transition-normal);
405
+ }
406
+
407
+ .tsvp-layout-vimeo:hover .tsvp-controls {
408
+ opacity: 1;
409
+ }
410
+
411
+ .tsvp-layout-vimeo .tsvp-controls-row {
412
+ display: flex;
413
+ align-items: center;
414
+ gap: var(--tsvp-spacing-sm);
415
+ }
416
+
417
+ .tsvp-layout-vimeo .tsvp-title-bar {
418
+ position: absolute;
419
+ top: 0;
420
+ left: 0;
421
+ right: 0;
422
+ padding: var(--tsvp-spacing-md);
423
+ background: linear-gradient(rgba(0,0,0,0.6), transparent);
424
+ color: white;
425
+ opacity: 0;
426
+ transition: opacity var(--tsvp-transition-normal);
427
+ }
428
+
429
+ .tsvp-layout-vimeo:hover .tsvp-title-bar {
430
+ opacity: 1;
431
+ }
432
+
433
+ .tsvp-layout-vimeo .tsvp-like-button,
434
+ .tsvp-layout-vimeo .tsvp-share-button {
435
+ position: absolute;
436
+ right: var(--tsvp-spacing-md);
437
+ background: rgba(0,0,0,0.5);
438
+ border-radius: var(--tsvp-radius-full);
439
+ padding: var(--tsvp-spacing-sm);
440
+ }
441
+
442
+ .tsvp-layout-vimeo .tsvp-like-button { top: var(--tsvp-spacing-md); }
443
+ .tsvp-layout-vimeo .tsvp-share-button { top: calc(var(--tsvp-spacing-md) + 48px); }
444
+ `,
445
+ template: `
446
+ <div class="tsvp-video-container">
447
+ <slot name="video"></slot>
448
+ </div>
449
+ <div class="tsvp-poster"></div>
450
+ <div class="tsvp-captions"></div>
451
+ <div class="tsvp-title-bar">
452
+ <slot name="title"></slot>
453
+ </div>
454
+ <div class="tsvp-controls">
455
+ <div class="tsvp-controls-row">
456
+ <slot name="progress"></slot>
457
+ </div>
458
+ <div class="tsvp-controls-row">
459
+ <slot name="play-button"></slot>
460
+ <slot name="volume"></slot>
461
+ <slot name="time"></slot>
462
+ <div style="flex:1"></div>
463
+ <slot name="captions-button"></slot>
464
+ <slot name="settings-button"></slot>
465
+ <slot name="pip-button"></slot>
466
+ <slot name="fullscreen-button"></slot>
467
+ </div>
468
+ </div>
469
+ `,
470
+ }
471
+
472
+ // =============================================================================
473
+ // Audio Layout
474
+ // =============================================================================
475
+
476
+ export const audioLayout: LayoutConfig = {
477
+ name: 'audio',
478
+ description: 'Compact layout for audio-only playback',
479
+ styles: `
480
+ .tsvp-layout-audio {
481
+ --tsvp-controls-height: 64px;
482
+ height: var(--tsvp-controls-height);
483
+ background: var(--tsvp-color-background);
484
+ border-radius: var(--tsvp-radius-lg);
485
+ }
486
+
487
+ .tsvp-layout-audio .tsvp-controls {
488
+ display: flex;
489
+ align-items: center;
490
+ height: 100%;
491
+ padding: 0 var(--tsvp-spacing-md);
492
+ gap: var(--tsvp-spacing-md);
493
+ }
494
+
495
+ .tsvp-layout-audio .tsvp-artwork {
496
+ width: 48px;
497
+ height: 48px;
498
+ border-radius: var(--tsvp-radius-sm);
499
+ background: rgba(255,255,255,0.1);
500
+ overflow: hidden;
501
+ }
502
+
503
+ .tsvp-layout-audio .tsvp-artwork img {
504
+ width: 100%;
505
+ height: 100%;
506
+ object-fit: cover;
507
+ }
508
+
509
+ .tsvp-layout-audio .tsvp-info {
510
+ flex: 0 0 auto;
511
+ max-width: 200px;
512
+ }
513
+
514
+ .tsvp-layout-audio .tsvp-track-title {
515
+ color: var(--tsvp-color-text);
516
+ font-weight: var(--tsvp-font-weight-bold);
517
+ font-size: var(--tsvp-font-size);
518
+ white-space: nowrap;
519
+ overflow: hidden;
520
+ text-overflow: ellipsis;
521
+ }
522
+
523
+ .tsvp-layout-audio .tsvp-track-artist {
524
+ color: var(--tsvp-color-text);
525
+ opacity: 0.7;
526
+ font-size: var(--tsvp-font-size-sm);
527
+ white-space: nowrap;
528
+ overflow: hidden;
529
+ text-overflow: ellipsis;
530
+ }
531
+
532
+ .tsvp-layout-audio .tsvp-progress-section {
533
+ flex: 1;
534
+ display: flex;
535
+ align-items: center;
536
+ gap: var(--tsvp-spacing-sm);
537
+ }
538
+
539
+ .tsvp-layout-audio .tsvp-progress-bar {
540
+ flex: 1;
541
+ }
542
+ `,
543
+ template: `
544
+ <div class="tsvp-controls">
545
+ <div class="tsvp-artwork">
546
+ <slot name="artwork"></slot>
547
+ </div>
548
+ <slot name="play-button"></slot>
549
+ <div class="tsvp-info">
550
+ <div class="tsvp-track-title"><slot name="title"></slot></div>
551
+ <div class="tsvp-track-artist"><slot name="artist"></slot></div>
552
+ </div>
553
+ <div class="tsvp-progress-section">
554
+ <slot name="current-time"></slot>
555
+ <div class="tsvp-progress-bar">
556
+ <slot name="progress"></slot>
557
+ </div>
558
+ <slot name="duration"></slot>
559
+ </div>
560
+ <slot name="volume"></slot>
561
+ </div>
562
+ `,
563
+ }
564
+
565
+ // =============================================================================
566
+ // Layout Registry
567
+ // =============================================================================
568
+
569
+ export const layouts: Record<LayoutName, LayoutConfig> = {
570
+ default: defaultLayout,
571
+ minimal: minimalLayout,
572
+ cinema: cinemaLayout,
573
+ youtube: youtubeLayout,
574
+ vimeo: vimeoLayout,
575
+ audio: audioLayout,
576
+ }
577
+
578
+ // =============================================================================
579
+ // Layout Manager
580
+ // =============================================================================
581
+
582
+ export class LayoutManager {
583
+ private container: HTMLElement
584
+ private currentLayout: LayoutConfig | null = null
585
+ private styleElement: HTMLStyleElement | null = null
586
+
587
+ constructor(container: HTMLElement) {
588
+ this.container = container
589
+ }
590
+
591
+ /**
592
+ * Apply a layout to the container
593
+ */
594
+ apply(layout: LayoutName | LayoutConfig): void {
595
+ // Get layout config
596
+ const config = typeof layout === 'string' ? layouts[layout] : layout
597
+
598
+ // Cleanup previous layout
599
+ this.cleanup()
600
+
601
+ // Apply new layout class
602
+ this.container.classList.add(`tsvp-layout-${config.name}`)
603
+
604
+ // Inject styles
605
+ this.styleElement = document.createElement('style')
606
+ this.styleElement.textContent = config.styles
607
+ document.head.appendChild(this.styleElement)
608
+
609
+ // Set template
610
+ this.container.innerHTML = config.template
611
+
612
+ // Initialize layout
613
+ if (config.init) {
614
+ config.init(this.container)
615
+ }
616
+
617
+ this.currentLayout = config
618
+ }
619
+
620
+ /**
621
+ * Get current layout
622
+ */
623
+ getLayout(): LayoutConfig | null {
624
+ return this.currentLayout
625
+ }
626
+
627
+ /**
628
+ * Cleanup current layout
629
+ */
630
+ cleanup(): void {
631
+ if (this.currentLayout) {
632
+ // Remove layout class
633
+ this.container.classList.remove(`tsvp-layout-${this.currentLayout.name}`)
634
+
635
+ // Call destroy
636
+ if (this.currentLayout.destroy) {
637
+ this.currentLayout.destroy(this.container)
638
+ }
639
+
640
+ // Remove styles
641
+ if (this.styleElement) {
642
+ this.styleElement.remove()
643
+ this.styleElement = null
644
+ }
645
+
646
+ this.currentLayout = null
647
+ }
648
+ }
649
+
650
+ /**
651
+ * Destroy layout manager
652
+ */
653
+ destroy(): void {
654
+ this.cleanup()
655
+ }
656
+ }
657
+
658
+ // =============================================================================
659
+ // Factory
660
+ // =============================================================================
661
+
662
+ export function createLayoutManager(container: HTMLElement): LayoutManager {
663
+ return new LayoutManager(container)
664
+ }
665
+
666
+ export function getLayout(name: LayoutName): LayoutConfig {
667
+ return layouts[name]
668
+ }
669
+
670
+ export function registerLayout(config: LayoutConfig): void {
671
+ (layouts as any)[config.name] = config
672
+ }