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,765 @@
1
+ // src/core/state.ts
2
+ function shallowEqual(a, b) {
3
+ if (a === b)
4
+ return true;
5
+ if (!a || !b)
6
+ return false;
7
+ const keysA = Object.keys(a);
8
+ const keysB = Object.keys(b);
9
+ if (keysA.length !== keysB.length)
10
+ return false;
11
+ for (const key of keysA) {
12
+ if (a[key] !== b[key])
13
+ return false;
14
+ }
15
+ return true;
16
+ }
17
+ function createSignal(initial) {
18
+ let value = initial;
19
+ const listeners = new Set;
20
+ const get = () => value;
21
+ const set = (newValue) => {
22
+ const prev = value;
23
+ value = typeof newValue === "function" ? newValue(prev) : newValue;
24
+ if (value !== prev) {
25
+ listeners.forEach((listener) => listener(value, prev));
26
+ }
27
+ };
28
+ get.subscribe = (listener) => {
29
+ listeners.add(listener);
30
+ return () => listeners.delete(listener);
31
+ };
32
+ return [get, set];
33
+ }
34
+ function computed(fn, deps) {
35
+ let value;
36
+ let dirty = true;
37
+ deps.forEach((dep) => {
38
+ if (dep.subscribe) {
39
+ dep.subscribe(() => {
40
+ dirty = true;
41
+ });
42
+ }
43
+ });
44
+ return () => {
45
+ if (dirty) {
46
+ value = fn();
47
+ dirty = false;
48
+ }
49
+ return value;
50
+ };
51
+ }
52
+ function createDefaultState() {
53
+ return {
54
+ src: null,
55
+ sources: [],
56
+ currentSourceIndex: 0,
57
+ mediaType: "unknown",
58
+ streamType: "unknown",
59
+ providerType: "unknown",
60
+ loadingState: "idle",
61
+ preload: "metadata",
62
+ canPlay: false,
63
+ canPlayThrough: false,
64
+ error: null,
65
+ playbackState: "idle",
66
+ paused: true,
67
+ playing: false,
68
+ started: false,
69
+ ended: false,
70
+ seeking: false,
71
+ waiting: false,
72
+ currentTime: 0,
73
+ duration: 0,
74
+ playbackRate: 1,
75
+ loop: false,
76
+ autoplay: false,
77
+ playsinline: true,
78
+ muted: false,
79
+ volume: 1,
80
+ buffered: [],
81
+ bufferedAmount: 0,
82
+ seekable: [],
83
+ videoWidth: 0,
84
+ videoHeight: 0,
85
+ aspectRatio: 16 / 9,
86
+ qualities: [],
87
+ autoQuality: true,
88
+ audioTracks: [],
89
+ textTracks: [],
90
+ fullscreen: false,
91
+ pictureInPicture: false,
92
+ canFullscreen: false,
93
+ canPictureInPicture: false,
94
+ volumeAvailability: "unavailable",
95
+ fullscreenAvailability: "unavailable",
96
+ pipAvailability: "unavailable",
97
+ controlsVisible: true,
98
+ userActive: true,
99
+ pointerOver: false,
100
+ title: "",
101
+ poster: ""
102
+ };
103
+ }
104
+
105
+ class StateStore {
106
+ state;
107
+ listeners = new Map;
108
+ _pendingKeys = null;
109
+ _flushScheduled = false;
110
+ constructor(initial) {
111
+ this.state = { ...createDefaultState(), ...initial };
112
+ }
113
+ getState() {
114
+ return this.state;
115
+ }
116
+ get(key) {
117
+ return this.state[key];
118
+ }
119
+ set(key, value) {
120
+ if (this.state[key] === value)
121
+ return;
122
+ this.state = { ...this.state, [key]: value };
123
+ this.scheduleNotify(key);
124
+ }
125
+ batch(updates) {
126
+ for (const [key, value] of Object.entries(updates)) {
127
+ if (this.state[key] !== value) {
128
+ this.state[key] = value;
129
+ this.scheduleNotify(key);
130
+ }
131
+ }
132
+ }
133
+ subscribe(keyOrListener, listener) {
134
+ if (typeof keyOrListener === "function") {
135
+ const key2 = "*";
136
+ if (!this.listeners.has(key2)) {
137
+ this.listeners.set(key2, new Set);
138
+ }
139
+ this.listeners.get(key2).add(keyOrListener);
140
+ return () => this.listeners.get(key2)?.delete(keyOrListener);
141
+ }
142
+ const key = keyOrListener;
143
+ if (!listener)
144
+ return () => {};
145
+ if (!this.listeners.has(key)) {
146
+ this.listeners.set(key, new Set);
147
+ }
148
+ this.listeners.get(key).add(listener);
149
+ return () => this.listeners.get(key)?.delete(listener);
150
+ }
151
+ reset() {
152
+ this.state = createDefaultState();
153
+ this._pendingKeys = null;
154
+ this._flushScheduled = false;
155
+ this.notifyAll();
156
+ }
157
+ flush() {
158
+ if (this._pendingKeys) {
159
+ const keys = this._pendingKeys;
160
+ this._pendingKeys = null;
161
+ this._flushScheduled = false;
162
+ for (const key of keys) {
163
+ this.notifyKey(key);
164
+ }
165
+ this.notifyAll();
166
+ }
167
+ }
168
+ scheduleNotify(key) {
169
+ if (!this._pendingKeys) {
170
+ this._pendingKeys = new Set;
171
+ }
172
+ this._pendingKeys.add(key);
173
+ if (!this._flushScheduled) {
174
+ this._flushScheduled = true;
175
+ queueMicrotask(() => this.flush());
176
+ }
177
+ }
178
+ notifyKey(key) {
179
+ this.listeners.get(key)?.forEach((listener) => listener(this.state, key));
180
+ }
181
+ notifyAll() {
182
+ this.listeners.get("*")?.forEach((listener) => listener(this.state));
183
+ }
184
+ }
185
+ function selectBufferedAmount(state) {
186
+ if (state.duration === 0 || state.buffered.length === 0)
187
+ return 0;
188
+ let buffered = 0;
189
+ for (const range of state.buffered) {
190
+ buffered += range.end - range.start;
191
+ }
192
+ return Math.min(buffered / state.duration, 1);
193
+ }
194
+ function selectProgress(state) {
195
+ if (state.duration === 0)
196
+ return 0;
197
+ return Math.min(state.currentTime / state.duration, 1);
198
+ }
199
+ function selectRemainingTime(state) {
200
+ return Math.max(state.duration - state.currentTime, 0);
201
+ }
202
+ function selectIsIdle(state) {
203
+ return state.loadingState === "idle" && state.playbackState === "idle";
204
+ }
205
+ function selectIsLoading(state) {
206
+ return state.loadingState === "loading" || state.waiting;
207
+ }
208
+ function selectIsLive(state) {
209
+ return state.streamType.includes("live");
210
+ }
211
+ function selectIsDVR(state) {
212
+ return state.streamType === "live:dvr";
213
+ }
214
+ function selectCurrentQuality(state) {
215
+ const selected = state.qualities.find((q) => q.selected);
216
+ if (!selected)
217
+ return state.autoQuality ? "auto" : null;
218
+ return `${selected.height}p`;
219
+ }
220
+ function selectCurrentTextTrack(state) {
221
+ const showing = state.textTracks.find((t) => t.mode === "showing");
222
+ return showing?.label || null;
223
+ }
224
+ function selectCurrentAudioTrack(state) {
225
+ const selected = state.audioTracks.find((t) => t.selected);
226
+ return selected?.label || null;
227
+ }
228
+ var STORAGE_KEY = "ts-video-player";
229
+ function loadPersistedState(storage = localStorage) {
230
+ try {
231
+ const data = storage.getItem(STORAGE_KEY);
232
+ if (data) {
233
+ return JSON.parse(data);
234
+ }
235
+ } catch {}
236
+ return {};
237
+ }
238
+ function savePersistedState(state, storage = localStorage) {
239
+ try {
240
+ const existing = loadPersistedState(storage);
241
+ storage.setItem(STORAGE_KEY, JSON.stringify({ ...existing, ...state }));
242
+ } catch {}
243
+ }
244
+ function createStorageSync(store, keys = ["volume", "muted", "playbackRate"], storage = localStorage) {
245
+ const persisted = loadPersistedState(storage);
246
+ if (persisted.volume !== undefined)
247
+ store.set("volume", persisted.volume);
248
+ if (persisted.muted !== undefined)
249
+ store.set("muted", persisted.muted);
250
+ if (persisted.playbackRate !== undefined)
251
+ store.set("playbackRate", persisted.playbackRate);
252
+ const unsubscribes = keys.map((key) => store.subscribe(key, (state) => {
253
+ const value = state[key];
254
+ savePersistedState({ [key]: value }, storage);
255
+ }));
256
+ return () => unsubscribes.forEach((unsub) => unsub());
257
+ }
258
+
259
+ // src/core/events.ts
260
+ class EventEmitter {
261
+ listeners = new Map;
262
+ onceListeners = new Map;
263
+ on(event, handler) {
264
+ if (!this.listeners.has(event)) {
265
+ this.listeners.set(event, new Set);
266
+ }
267
+ this.listeners.get(event).add(handler);
268
+ return () => this.off(event, handler);
269
+ }
270
+ once(event, handler) {
271
+ if (!this.onceListeners.has(event)) {
272
+ this.onceListeners.set(event, new Set);
273
+ }
274
+ this.onceListeners.get(event).add(handler);
275
+ return () => this.onceListeners.get(event)?.delete(handler);
276
+ }
277
+ off(event, handler) {
278
+ this.listeners.get(event)?.delete(handler);
279
+ this.onceListeners.get(event)?.delete(handler);
280
+ }
281
+ emit(event, ...args) {
282
+ this.listeners.get(event)?.forEach((handler) => {
283
+ try {
284
+ handler(...args);
285
+ } catch (error) {
286
+ console.error(`[ts-video-player] Error in event handler for "${String(event)}":`, error);
287
+ }
288
+ });
289
+ const onceHandlers = this.onceListeners.get(event);
290
+ if (onceHandlers) {
291
+ onceHandlers.forEach((handler) => {
292
+ try {
293
+ handler(...args);
294
+ } catch (error) {
295
+ console.error(`[ts-video-player] Error in once handler for "${String(event)}":`, error);
296
+ }
297
+ });
298
+ onceHandlers.clear();
299
+ }
300
+ }
301
+ removeAllListeners(event) {
302
+ if (event) {
303
+ this.listeners.delete(event);
304
+ this.onceListeners.delete(event);
305
+ } else {
306
+ this.listeners.clear();
307
+ this.onceListeners.clear();
308
+ }
309
+ }
310
+ listenerCount(event) {
311
+ return (this.listeners.get(event)?.size || 0) + (this.onceListeners.get(event)?.size || 0);
312
+ }
313
+ }
314
+
315
+ class MediaEventsNormalizer {
316
+ media;
317
+ emitter;
318
+ listeners = [];
319
+ constructor(media, emitter) {
320
+ this.media = media;
321
+ this.emitter = emitter;
322
+ this.attachListeners();
323
+ }
324
+ attachListeners() {
325
+ const on = (event, handler) => {
326
+ this.media.addEventListener(event, handler);
327
+ this.listeners.push(() => this.media.removeEventListener(event, handler));
328
+ };
329
+ on("loadstart", () => this.emitter.emit("loadstart"));
330
+ on("loadedmetadata", () => this.emitter.emit("loadedmetadata"));
331
+ on("loadeddata", () => this.emitter.emit("loadeddata"));
332
+ on("canplay", () => this.emitter.emit("canplay"));
333
+ on("canplaythrough", () => this.emitter.emit("canplaythrough"));
334
+ on("play", () => this.emitter.emit("play"));
335
+ on("pause", () => this.emitter.emit("pause"));
336
+ on("playing", () => this.emitter.emit("playing"));
337
+ on("waiting", () => this.emitter.emit("waiting"));
338
+ on("ended", () => this.emitter.emit("ended"));
339
+ on("seeking", () => this.emitter.emit("seeking"));
340
+ on("seeked", () => this.emitter.emit("seeked"));
341
+ on("timeupdate", () => this.emitter.emit("timeupdate", this.media.currentTime));
342
+ on("durationchange", () => this.emitter.emit("durationchange", this.media.duration));
343
+ on("volumechange", () => this.emitter.emit("volumechange", this.media.volume, this.media.muted));
344
+ on("ratechange", () => this.emitter.emit("ratechange", this.media.playbackRate));
345
+ on("progress", () => {
346
+ const buffered = this.getTimeRanges(this.media.buffered);
347
+ this.emitter.emit("progress", buffered);
348
+ });
349
+ on("error", () => {
350
+ const error = this.normalizeError(this.media.error);
351
+ this.emitter.emit("error", error);
352
+ });
353
+ }
354
+ getTimeRanges(ranges) {
355
+ const result = [];
356
+ for (let i = 0;i < ranges.length; i++) {
357
+ result.push({
358
+ start: ranges.start(i),
359
+ end: ranges.end(i)
360
+ });
361
+ }
362
+ return result;
363
+ }
364
+ normalizeError(error) {
365
+ if (!error) {
366
+ return { code: 0, message: "Unknown error" };
367
+ }
368
+ const messages = {
369
+ 1: "The fetching of the media resource was aborted by the user agent.",
370
+ 2: "A network error occurred while fetching the media resource.",
371
+ 3: "An error occurred while decoding the media resource.",
372
+ 4: "The media resource is not supported."
373
+ };
374
+ return {
375
+ code: error.code,
376
+ message: messages[error.code] || error.message || "Unknown error"
377
+ };
378
+ }
379
+ destroy() {
380
+ this.listeners.forEach((remove) => remove());
381
+ this.listeners = [];
382
+ }
383
+ }
384
+ function onFullscreenChange(handler) {
385
+ const events = ["fullscreenchange", "webkitfullscreenchange", "mozfullscreenchange", "MSFullscreenChange"];
386
+ const listener = () => {
387
+ const fullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement);
388
+ handler(fullscreen);
389
+ };
390
+ events.forEach((event) => document.addEventListener(event, listener));
391
+ return () => {
392
+ events.forEach((event) => document.removeEventListener(event, listener));
393
+ };
394
+ }
395
+ function onPiPChange(video, handler) {
396
+ const enterHandler = () => handler(true);
397
+ const leaveHandler = () => handler(false);
398
+ video.addEventListener("enterpictureinpicture", enterHandler);
399
+ video.addEventListener("leavepictureinpicture", leaveHandler);
400
+ return () => {
401
+ video.removeEventListener("enterpictureinpicture", enterHandler);
402
+ video.removeEventListener("leavepictureinpicture", leaveHandler);
403
+ };
404
+ }
405
+ var DEFAULT_KEYBOARD_SHORTCUTS = {
406
+ " ": "togglePlay",
407
+ k: "togglePlay",
408
+ m: "toggleMute",
409
+ f: "toggleFullscreen",
410
+ i: "togglePiP",
411
+ c: "toggleCaptions",
412
+ ArrowLeft: "seekBackward",
413
+ ArrowRight: "seekForward",
414
+ j: "seekBackward",
415
+ l: "seekForward",
416
+ ArrowUp: "volumeUp",
417
+ ArrowDown: "volumeDown",
418
+ ">": "speedUp",
419
+ "<": "speedDown",
420
+ Home: "seekStart",
421
+ End: "seekEnd",
422
+ "0": "seek0",
423
+ "1": "seek10",
424
+ "2": "seek20",
425
+ "3": "seek30",
426
+ "4": "seek40",
427
+ "5": "seek50",
428
+ "6": "seek60",
429
+ "7": "seek70",
430
+ "8": "seek80",
431
+ "9": "seek90"
432
+ };
433
+ function createKeyboardHandler(actions, options = {}) {
434
+ const { seekStep: _seekStep = 5, volumeStep: _volumeStep = 0.1, speedStep: _speedStep = 0.25 } = options;
435
+ return (event) => {
436
+ if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement || event.target instanceof HTMLSelectElement || event.target?.isContentEditable) {
437
+ return;
438
+ }
439
+ const key = event.key;
440
+ const action = DEFAULT_KEYBOARD_SHORTCUTS[key];
441
+ if (!action)
442
+ return;
443
+ event.preventDefault();
444
+ switch (action) {
445
+ case "togglePlay":
446
+ actions.togglePlay();
447
+ break;
448
+ case "toggleMute":
449
+ actions.toggleMute();
450
+ break;
451
+ case "toggleFullscreen":
452
+ actions.toggleFullscreen();
453
+ break;
454
+ case "togglePiP":
455
+ actions.togglePiP();
456
+ break;
457
+ case "toggleCaptions":
458
+ actions.toggleCaptions();
459
+ break;
460
+ case "seekBackward":
461
+ actions.seekBackward();
462
+ break;
463
+ case "seekForward":
464
+ actions.seekForward();
465
+ break;
466
+ case "volumeUp":
467
+ actions.volumeUp();
468
+ break;
469
+ case "volumeDown":
470
+ actions.volumeDown();
471
+ break;
472
+ case "speedUp":
473
+ actions.speedUp();
474
+ break;
475
+ case "speedDown":
476
+ actions.speedDown();
477
+ break;
478
+ case "seekStart":
479
+ actions.seekStart();
480
+ break;
481
+ case "seekEnd":
482
+ actions.seekEnd();
483
+ break;
484
+ default:
485
+ if (action.startsWith("seek")) {
486
+ const percent = parseInt(action.replace("seek", ""), 10);
487
+ if (!isNaN(percent)) {
488
+ actions.seekPercent(percent);
489
+ }
490
+ }
491
+ }
492
+ };
493
+ }
494
+ function createActivityDetector(element, options = {}) {
495
+ const { timeout = 3000, onActive, onInactive } = options;
496
+ let timer = null;
497
+ let active = true;
498
+ const setActive = () => {
499
+ if (!active) {
500
+ active = true;
501
+ onActive?.();
502
+ }
503
+ resetTimer();
504
+ };
505
+ const setInactive = () => {
506
+ if (active) {
507
+ active = false;
508
+ onInactive?.();
509
+ }
510
+ };
511
+ const resetTimer = () => {
512
+ if (timer)
513
+ clearTimeout(timer);
514
+ timer = setTimeout(setInactive, timeout);
515
+ };
516
+ const events = ["mousemove", "mouseenter", "mouseleave", "mousedown", "keydown", "touchstart", "touchmove"];
517
+ events.forEach((event) => element.addEventListener(event, setActive));
518
+ resetTimer();
519
+ return () => {
520
+ if (timer)
521
+ clearTimeout(timer);
522
+ events.forEach((event) => element.removeEventListener(event, setActive));
523
+ };
524
+ }
525
+ function createGestureDetector(element, handlers) {
526
+ let startX = 0;
527
+ let startY = 0;
528
+ let startTime = 0;
529
+ let lastTap = 0;
530
+ let isScrubbing = false;
531
+ let cachedRect = null;
532
+ const SWIPE_THRESHOLD = 50;
533
+ const TAP_THRESHOLD = 200;
534
+ const DOUBLE_TAP_THRESHOLD = 300;
535
+ const onTouchStart = (e) => {
536
+ const touch = e.touches[0];
537
+ startX = touch.clientX;
538
+ startY = touch.clientY;
539
+ startTime = Date.now();
540
+ isScrubbing = false;
541
+ cachedRect = element.getBoundingClientRect();
542
+ };
543
+ const onTouchMove = (e) => {
544
+ if (!startTime)
545
+ return;
546
+ const touch = e.touches[0];
547
+ const deltaX = touch.clientX - startX;
548
+ const deltaY = touch.clientY - startY;
549
+ if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 20) {
550
+ isScrubbing = true;
551
+ const rect = cachedRect || element.getBoundingClientRect();
552
+ const progress = Math.max(0, Math.min(1, (touch.clientX - rect.left) / rect.width));
553
+ handlers.onScrub?.(progress);
554
+ }
555
+ };
556
+ const onTouchEnd = (e) => {
557
+ const endTime = Date.now();
558
+ const duration = endTime - startTime;
559
+ if (isScrubbing) {
560
+ handlers.onScrubEnd?.();
561
+ isScrubbing = false;
562
+ startTime = 0;
563
+ return;
564
+ }
565
+ const touch = e.changedTouches[0];
566
+ const deltaX = touch.clientX - startX;
567
+ const deltaY = touch.clientY - startY;
568
+ if (duration < 500) {
569
+ if (Math.abs(deltaX) > SWIPE_THRESHOLD && Math.abs(deltaX) > Math.abs(deltaY)) {
570
+ if (deltaX > 0) {
571
+ handlers.onSwipeRight?.();
572
+ } else {
573
+ handlers.onSwipeLeft?.();
574
+ }
575
+ startTime = 0;
576
+ return;
577
+ }
578
+ if (Math.abs(deltaY) > SWIPE_THRESHOLD && Math.abs(deltaY) > Math.abs(deltaX)) {
579
+ if (deltaY > 0) {
580
+ handlers.onSwipeDown?.();
581
+ } else {
582
+ handlers.onSwipeUp?.();
583
+ }
584
+ startTime = 0;
585
+ return;
586
+ }
587
+ }
588
+ if (duration < TAP_THRESHOLD && Math.abs(deltaX) < 10 && Math.abs(deltaY) < 10) {
589
+ const now = Date.now();
590
+ if (now - lastTap < DOUBLE_TAP_THRESHOLD) {
591
+ handlers.onDoubleTap?.(touch.clientX, touch.clientY);
592
+ lastTap = 0;
593
+ } else {
594
+ handlers.onTap?.(touch.clientX, touch.clientY);
595
+ lastTap = now;
596
+ }
597
+ }
598
+ startTime = 0;
599
+ };
600
+ element.addEventListener("touchstart", onTouchStart, { passive: true });
601
+ element.addEventListener("touchmove", onTouchMove, { passive: true });
602
+ element.addEventListener("touchend", onTouchEnd, { passive: true });
603
+ return () => {
604
+ element.removeEventListener("touchstart", onTouchStart);
605
+ element.removeEventListener("touchmove", onTouchMove);
606
+ element.removeEventListener("touchend", onTouchEnd);
607
+ };
608
+ }
609
+
610
+ // src/core/features.ts
611
+ function isIOSSafari() {
612
+ if (typeof navigator === "undefined")
613
+ return false;
614
+ const ua = navigator.userAgent;
615
+ if (/iPad|iPhone|iPod/.test(ua) && !window.MSStream)
616
+ return true;
617
+ if (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1)
618
+ return true;
619
+ return false;
620
+ }
621
+ function isSafari() {
622
+ if (typeof navigator === "undefined")
623
+ return false;
624
+ return /^(?:(?!chrome|android).)*safari/i.test(navigator.userAgent);
625
+ }
626
+ function isStandalonePWA() {
627
+ if (typeof window === "undefined")
628
+ return false;
629
+ return navigator.standalone === true || window.matchMedia("(display-mode: standalone)").matches;
630
+ }
631
+ function detectVolumeAvailability(media) {
632
+ if (isIOSSafari())
633
+ return "unsupported";
634
+ if (!media)
635
+ return "unavailable";
636
+ return "available";
637
+ }
638
+ async function probeVolumeAvailability(media) {
639
+ const original = media.volume;
640
+ try {
641
+ const testValue = original === 1 ? 0.5 : 1;
642
+ media.volume = testValue;
643
+ await new Promise((r) => setTimeout(r, 0));
644
+ const changed = media.volume !== original;
645
+ media.volume = original;
646
+ return changed ? "available" : "unsupported";
647
+ } catch {
648
+ try {
649
+ media.volume = original;
650
+ } catch {}
651
+ return "unsupported";
652
+ }
653
+ }
654
+ function detectFullscreenAvailability(container, videoElement) {
655
+ const doc = typeof document !== "undefined" ? document : null;
656
+ if (!doc)
657
+ return "unsupported";
658
+ if (doc.fullscreenEnabled || doc.webkitFullscreenEnabled)
659
+ return "available";
660
+ if (doc.mozFullScreenEnabled || doc.msFullscreenEnabled)
661
+ return "available";
662
+ if (videoElement) {
663
+ const video = videoElement;
664
+ if (video.webkitSupportsFullscreen === true)
665
+ return "available";
666
+ if (typeof video.webkitEnterFullscreen === "function")
667
+ return "available";
668
+ }
669
+ if (!container && !videoElement)
670
+ return "unavailable";
671
+ return "unsupported";
672
+ }
673
+ function isVideoOnlyFullscreen() {
674
+ if (typeof document === "undefined")
675
+ return false;
676
+ const doc = document;
677
+ return isIOSSafari() && !doc.fullscreenEnabled && !doc.webkitFullscreenEnabled;
678
+ }
679
+ async function enterFullscreen(container, media) {
680
+ const video = media;
681
+ if (container) {
682
+ const el = container;
683
+ if (typeof el.requestFullscreen === "function")
684
+ return el.requestFullscreen();
685
+ if (typeof el.webkitRequestFullscreen === "function")
686
+ return el.webkitRequestFullscreen();
687
+ if (typeof el.webkitRequestFullScreen === "function")
688
+ return el.webkitRequestFullScreen();
689
+ }
690
+ if (video && typeof video.webkitEnterFullscreen === "function") {
691
+ video.webkitEnterFullscreen();
692
+ return;
693
+ }
694
+ if (media && typeof media.requestFullscreen === "function") {
695
+ return media.requestFullscreen();
696
+ }
697
+ }
698
+ async function exitFullscreen(media) {
699
+ const doc = document;
700
+ const video = media;
701
+ if (typeof doc.exitFullscreen === "function")
702
+ return doc.exitFullscreen();
703
+ if (typeof doc.webkitExitFullscreen === "function")
704
+ return doc.webkitExitFullscreen();
705
+ if (typeof doc.webkitCancelFullScreen === "function")
706
+ return doc.webkitCancelFullScreen();
707
+ if (video && typeof video.webkitExitFullscreen === "function") {
708
+ video.webkitExitFullscreen();
709
+ }
710
+ }
711
+ function detectPipAvailability(videoElement) {
712
+ if (!videoElement)
713
+ return "unavailable";
714
+ if ("requestPictureInPicture" in videoElement) {
715
+ if (typeof document !== "undefined" && document.pictureInPictureEnabled === false) {
716
+ return "unavailable";
717
+ }
718
+ if (isSafari() && isStandalonePWA())
719
+ return "unavailable";
720
+ return "available";
721
+ }
722
+ const video = videoElement;
723
+ if (typeof video.webkitSetPresentationMode === "function") {
724
+ if (isStandalonePWA())
725
+ return "unavailable";
726
+ if (typeof video.webkitSupportsPresentationMode === "function") {
727
+ return video.webkitSupportsPresentationMode("picture-in-picture") ? "available" : "unsupported";
728
+ }
729
+ return "available";
730
+ }
731
+ return "unsupported";
732
+ }
733
+ async function enterPiP(media) {
734
+ const video = media;
735
+ if (typeof video.requestPictureInPicture === "function") {
736
+ await video.requestPictureInPicture();
737
+ return;
738
+ }
739
+ if (typeof video.webkitSetPresentationMode === "function") {
740
+ video.webkitSetPresentationMode("picture-in-picture");
741
+ }
742
+ }
743
+ async function exitPiP(media) {
744
+ if (document.pictureInPictureElement && typeof document.exitPictureInPicture === "function") {
745
+ await document.exitPictureInPicture();
746
+ return;
747
+ }
748
+ if (media) {
749
+ const video = media;
750
+ if (video.webkitPresentationMode === "picture-in-picture" && typeof video.webkitSetPresentationMode === "function") {
751
+ video.webkitSetPresentationMode("inline");
752
+ }
753
+ }
754
+ }
755
+ function detectAllFeatures(container, videoElement, mediaElement) {
756
+ return {
757
+ volume: detectVolumeAvailability(mediaElement),
758
+ fullscreen: detectFullscreenAvailability(container, videoElement),
759
+ pip: detectPipAvailability(videoElement)
760
+ };
761
+ }
762
+
763
+ export { shallowEqual, createSignal, computed, createDefaultState, StateStore, selectBufferedAmount, selectProgress, selectRemainingTime, selectIsIdle, selectIsLoading, selectIsLive, selectIsDVR, selectCurrentQuality, selectCurrentTextTrack, selectCurrentAudioTrack, loadPersistedState, savePersistedState, createStorageSync, EventEmitter, MediaEventsNormalizer, onFullscreenChange, onPiPChange, DEFAULT_KEYBOARD_SHORTCUTS, createKeyboardHandler, createActivityDetector, createGestureDetector, isIOSSafari, isSafari, isStandalonePWA, detectVolumeAvailability, probeVolumeAvailability, detectFullscreenAvailability, isVideoOnlyFullscreen, enterFullscreen, exitFullscreen, detectPipAvailability, enterPiP, exitPiP, detectAllFeatures };
764
+
765
+ //# debugId=24A95575AA67786364756E2164756E21