@livepeer-frameworks/player-core 0.1.0 → 0.1.2

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 (294) hide show
  1. package/README.md +11 -9
  2. package/dist/cjs/core/ABRController.js +456 -0
  3. package/dist/cjs/core/ABRController.js.map +1 -0
  4. package/dist/cjs/core/CodecUtils.js +195 -0
  5. package/dist/cjs/core/CodecUtils.js.map +1 -0
  6. package/dist/cjs/core/ErrorClassifier.js +410 -0
  7. package/dist/cjs/core/ErrorClassifier.js.map +1 -0
  8. package/dist/cjs/core/EventEmitter.js +108 -0
  9. package/dist/cjs/core/EventEmitter.js.map +1 -0
  10. package/dist/cjs/core/GatewayClient.js +342 -0
  11. package/dist/cjs/core/GatewayClient.js.map +1 -0
  12. package/dist/cjs/core/InteractionController.js +606 -0
  13. package/dist/cjs/core/InteractionController.js.map +1 -0
  14. package/dist/cjs/core/LiveDurationProxy.js +186 -0
  15. package/dist/cjs/core/LiveDurationProxy.js.map +1 -0
  16. package/dist/cjs/core/MetaTrackManager.js +624 -0
  17. package/dist/cjs/core/MetaTrackManager.js.map +1 -0
  18. package/dist/cjs/core/MistReporter.js +449 -0
  19. package/dist/cjs/core/MistReporter.js.map +1 -0
  20. package/dist/cjs/core/MistSignaling.js +264 -0
  21. package/dist/cjs/core/MistSignaling.js.map +1 -0
  22. package/dist/cjs/core/PlayerController.js +2658 -0
  23. package/dist/cjs/core/PlayerController.js.map +1 -0
  24. package/dist/cjs/core/PlayerInterface.js +269 -0
  25. package/dist/cjs/core/PlayerInterface.js.map +1 -0
  26. package/dist/cjs/core/PlayerManager.js +806 -0
  27. package/dist/cjs/core/PlayerManager.js.map +1 -0
  28. package/dist/cjs/core/PlayerRegistry.js +270 -0
  29. package/dist/cjs/core/PlayerRegistry.js.map +1 -0
  30. package/dist/cjs/core/QualityMonitor.js +474 -0
  31. package/dist/cjs/core/QualityMonitor.js.map +1 -0
  32. package/dist/cjs/core/SeekingUtils.js +292 -0
  33. package/dist/cjs/core/SeekingUtils.js.map +1 -0
  34. package/dist/cjs/core/StreamStateClient.js +381 -0
  35. package/dist/cjs/core/StreamStateClient.js.map +1 -0
  36. package/dist/cjs/core/SubtitleManager.js +227 -0
  37. package/dist/cjs/core/SubtitleManager.js.map +1 -0
  38. package/dist/cjs/core/TelemetryReporter.js +258 -0
  39. package/dist/cjs/core/TelemetryReporter.js.map +1 -0
  40. package/dist/cjs/core/TimeFormat.js +176 -0
  41. package/dist/cjs/core/TimeFormat.js.map +1 -0
  42. package/dist/cjs/core/TimerManager.js +176 -0
  43. package/dist/cjs/core/TimerManager.js.map +1 -0
  44. package/dist/cjs/core/UrlUtils.js +160 -0
  45. package/dist/cjs/core/UrlUtils.js.map +1 -0
  46. package/dist/cjs/core/detector.js +293 -0
  47. package/dist/cjs/core/detector.js.map +1 -0
  48. package/dist/cjs/core/scorer.js +443 -0
  49. package/dist/cjs/core/scorer.js.map +1 -0
  50. package/dist/cjs/index.js +121 -20134
  51. package/dist/cjs/index.js.map +1 -1
  52. package/dist/cjs/lib/utils.js +11 -0
  53. package/dist/cjs/lib/utils.js.map +1 -0
  54. package/dist/cjs/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.js +6 -0
  55. package/dist/cjs/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.js.map +1 -0
  56. package/dist/cjs/node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.js +3042 -0
  57. package/dist/cjs/node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
  58. package/dist/cjs/players/DashJsPlayer.js +638 -0
  59. package/dist/cjs/players/DashJsPlayer.js.map +1 -0
  60. package/dist/cjs/players/HlsJsPlayer.js +482 -0
  61. package/dist/cjs/players/HlsJsPlayer.js.map +1 -0
  62. package/dist/cjs/players/MewsWsPlayer/SourceBufferManager.js +522 -0
  63. package/dist/cjs/players/MewsWsPlayer/SourceBufferManager.js.map +1 -0
  64. package/dist/cjs/players/MewsWsPlayer/WebSocketManager.js +215 -0
  65. package/dist/cjs/players/MewsWsPlayer/WebSocketManager.js.map +1 -0
  66. package/dist/cjs/players/MewsWsPlayer/index.js +987 -0
  67. package/dist/cjs/players/MewsWsPlayer/index.js.map +1 -0
  68. package/dist/cjs/players/MistPlayer.js +185 -0
  69. package/dist/cjs/players/MistPlayer.js.map +1 -0
  70. package/dist/cjs/players/MistWebRTCPlayer/index.js +635 -0
  71. package/dist/cjs/players/MistWebRTCPlayer/index.js.map +1 -0
  72. package/dist/cjs/players/NativePlayer.js +762 -0
  73. package/dist/cjs/players/NativePlayer.js.map +1 -0
  74. package/dist/cjs/players/VideoJsPlayer.js +585 -0
  75. package/dist/cjs/players/VideoJsPlayer.js.map +1 -0
  76. package/dist/cjs/players/WebCodecsPlayer/JitterBuffer.js +236 -0
  77. package/dist/cjs/players/WebCodecsPlayer/JitterBuffer.js.map +1 -0
  78. package/dist/cjs/players/WebCodecsPlayer/LatencyProfiles.js +143 -0
  79. package/dist/cjs/players/WebCodecsPlayer/LatencyProfiles.js.map +1 -0
  80. package/dist/cjs/players/WebCodecsPlayer/RawChunkParser.js +96 -0
  81. package/dist/cjs/players/WebCodecsPlayer/RawChunkParser.js.map +1 -0
  82. package/dist/cjs/players/WebCodecsPlayer/SyncController.js +359 -0
  83. package/dist/cjs/players/WebCodecsPlayer/SyncController.js.map +1 -0
  84. package/dist/cjs/players/WebCodecsPlayer/WebSocketController.js +460 -0
  85. package/dist/cjs/players/WebCodecsPlayer/WebSocketController.js.map +1 -0
  86. package/dist/cjs/players/WebCodecsPlayer/index.js +1467 -0
  87. package/dist/cjs/players/WebCodecsPlayer/index.js.map +1 -0
  88. package/dist/cjs/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.js +320 -0
  89. package/dist/cjs/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.js.map +1 -0
  90. package/dist/cjs/styles/index.js +57 -0
  91. package/dist/cjs/styles/index.js.map +1 -0
  92. package/dist/cjs/vanilla/FrameWorksPlayer.js +269 -0
  93. package/dist/cjs/vanilla/FrameWorksPlayer.js.map +1 -0
  94. package/dist/cjs/vanilla.js +11 -0
  95. package/dist/cjs/vanilla.js.map +1 -0
  96. package/dist/esm/core/ABRController.js +454 -0
  97. package/dist/esm/core/ABRController.js.map +1 -0
  98. package/dist/esm/core/CodecUtils.js +193 -0
  99. package/dist/esm/core/CodecUtils.js.map +1 -0
  100. package/dist/esm/core/ErrorClassifier.js +408 -0
  101. package/dist/esm/core/ErrorClassifier.js.map +1 -0
  102. package/dist/esm/core/EventEmitter.js +106 -0
  103. package/dist/esm/core/EventEmitter.js.map +1 -0
  104. package/dist/esm/core/GatewayClient.js +340 -0
  105. package/dist/esm/core/GatewayClient.js.map +1 -0
  106. package/dist/esm/core/InteractionController.js +604 -0
  107. package/dist/esm/core/InteractionController.js.map +1 -0
  108. package/dist/esm/core/LiveDurationProxy.js +184 -0
  109. package/dist/esm/core/LiveDurationProxy.js.map +1 -0
  110. package/dist/esm/core/MetaTrackManager.js +622 -0
  111. package/dist/esm/core/MetaTrackManager.js.map +1 -0
  112. package/dist/esm/core/MistReporter.js +447 -0
  113. package/dist/esm/core/MistReporter.js.map +1 -0
  114. package/dist/esm/core/MistSignaling.js +262 -0
  115. package/dist/esm/core/MistSignaling.js.map +1 -0
  116. package/dist/esm/core/PlayerController.js +2651 -0
  117. package/dist/esm/core/PlayerController.js.map +1 -0
  118. package/dist/esm/core/PlayerInterface.js +267 -0
  119. package/dist/esm/core/PlayerInterface.js.map +1 -0
  120. package/dist/esm/core/PlayerManager.js +804 -0
  121. package/dist/esm/core/PlayerManager.js.map +1 -0
  122. package/dist/esm/core/PlayerRegistry.js +264 -0
  123. package/dist/esm/core/PlayerRegistry.js.map +1 -0
  124. package/dist/esm/core/QualityMonitor.js +471 -0
  125. package/dist/esm/core/QualityMonitor.js.map +1 -0
  126. package/dist/esm/core/SeekingUtils.js +280 -0
  127. package/dist/esm/core/SeekingUtils.js.map +1 -0
  128. package/dist/esm/core/StreamStateClient.js +379 -0
  129. package/dist/esm/core/StreamStateClient.js.map +1 -0
  130. package/dist/esm/core/SubtitleManager.js +225 -0
  131. package/dist/esm/core/SubtitleManager.js.map +1 -0
  132. package/dist/esm/core/TelemetryReporter.js +256 -0
  133. package/dist/esm/core/TelemetryReporter.js.map +1 -0
  134. package/dist/esm/core/TimeFormat.js +169 -0
  135. package/dist/esm/core/TimeFormat.js.map +1 -0
  136. package/dist/esm/core/TimerManager.js +174 -0
  137. package/dist/esm/core/TimerManager.js.map +1 -0
  138. package/dist/esm/core/UrlUtils.js +151 -0
  139. package/dist/esm/core/UrlUtils.js.map +1 -0
  140. package/dist/esm/core/detector.js +279 -0
  141. package/dist/esm/core/detector.js.map +1 -0
  142. package/dist/esm/core/scorer.js +422 -0
  143. package/dist/esm/core/scorer.js.map +1 -0
  144. package/dist/esm/index.js +26 -20043
  145. package/dist/esm/index.js.map +1 -1
  146. package/dist/esm/lib/utils.js +9 -0
  147. package/dist/esm/lib/utils.js.map +1 -0
  148. package/dist/esm/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.js +4 -0
  149. package/dist/esm/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.js.map +1 -0
  150. package/dist/esm/node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.js +3036 -0
  151. package/dist/esm/node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
  152. package/dist/esm/players/DashJsPlayer.js +636 -0
  153. package/dist/esm/players/DashJsPlayer.js.map +1 -0
  154. package/dist/esm/players/HlsJsPlayer.js +480 -0
  155. package/dist/esm/players/HlsJsPlayer.js.map +1 -0
  156. package/dist/esm/players/MewsWsPlayer/SourceBufferManager.js +520 -0
  157. package/dist/esm/players/MewsWsPlayer/SourceBufferManager.js.map +1 -0
  158. package/dist/esm/players/MewsWsPlayer/WebSocketManager.js +213 -0
  159. package/dist/esm/players/MewsWsPlayer/WebSocketManager.js.map +1 -0
  160. package/dist/esm/players/MewsWsPlayer/index.js +985 -0
  161. package/dist/esm/players/MewsWsPlayer/index.js.map +1 -0
  162. package/dist/esm/players/MistPlayer.js +183 -0
  163. package/dist/esm/players/MistPlayer.js.map +1 -0
  164. package/dist/esm/players/MistWebRTCPlayer/index.js +633 -0
  165. package/dist/esm/players/MistWebRTCPlayer/index.js.map +1 -0
  166. package/dist/esm/players/NativePlayer.js +759 -0
  167. package/dist/esm/players/NativePlayer.js.map +1 -0
  168. package/dist/esm/players/VideoJsPlayer.js +583 -0
  169. package/dist/esm/players/VideoJsPlayer.js.map +1 -0
  170. package/dist/esm/players/WebCodecsPlayer/JitterBuffer.js +233 -0
  171. package/dist/esm/players/WebCodecsPlayer/JitterBuffer.js.map +1 -0
  172. package/dist/esm/players/WebCodecsPlayer/LatencyProfiles.js +134 -0
  173. package/dist/esm/players/WebCodecsPlayer/LatencyProfiles.js.map +1 -0
  174. package/dist/esm/players/WebCodecsPlayer/RawChunkParser.js +91 -0
  175. package/dist/esm/players/WebCodecsPlayer/RawChunkParser.js.map +1 -0
  176. package/dist/esm/players/WebCodecsPlayer/SyncController.js +357 -0
  177. package/dist/esm/players/WebCodecsPlayer/SyncController.js.map +1 -0
  178. package/dist/esm/players/WebCodecsPlayer/WebSocketController.js +458 -0
  179. package/dist/esm/players/WebCodecsPlayer/WebSocketController.js.map +1 -0
  180. package/dist/esm/players/WebCodecsPlayer/index.js +1458 -0
  181. package/dist/esm/players/WebCodecsPlayer/index.js.map +1 -0
  182. package/dist/esm/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.js +315 -0
  183. package/dist/esm/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.js.map +1 -0
  184. package/dist/esm/styles/index.js +54 -0
  185. package/dist/esm/styles/index.js.map +1 -0
  186. package/dist/esm/vanilla/FrameWorksPlayer.js +264 -0
  187. package/dist/esm/vanilla/FrameWorksPlayer.js.map +1 -0
  188. package/dist/esm/vanilla.js +2 -0
  189. package/dist/esm/vanilla.js.map +1 -0
  190. package/dist/player.css +185 -42
  191. package/dist/types/core/ABRController.d.ts +4 -4
  192. package/dist/types/core/CodecUtils.d.ts +1 -1
  193. package/dist/types/core/ErrorClassifier.d.ts +77 -0
  194. package/dist/types/core/GatewayClient.d.ts +4 -4
  195. package/dist/types/core/MetaTrackManager.d.ts +2 -2
  196. package/dist/types/core/MistReporter.d.ts +3 -3
  197. package/dist/types/core/MistSignaling.d.ts +12 -12
  198. package/dist/types/core/PlayerController.d.ts +19 -14
  199. package/dist/types/core/PlayerInterface.d.ts +100 -2
  200. package/dist/types/core/PlayerManager.d.ts +36 -9
  201. package/dist/types/core/PlayerRegistry.d.ts +11 -11
  202. package/dist/types/core/QualityMonitor.d.ts +2 -2
  203. package/dist/types/core/SeekingUtils.d.ts +2 -2
  204. package/dist/types/core/StreamStateClient.d.ts +2 -2
  205. package/dist/types/core/TelemetryReporter.d.ts +1 -1
  206. package/dist/types/core/TimerManager.d.ts +1 -1
  207. package/dist/types/core/detector.d.ts +1 -1
  208. package/dist/types/core/index.d.ts +44 -44
  209. package/dist/types/core/scorer.d.ts +1 -1
  210. package/dist/types/core/selector.d.ts +2 -2
  211. package/dist/types/index.d.ts +35 -34
  212. package/dist/types/players/DashJsPlayer.d.ts +3 -3
  213. package/dist/types/players/HlsJsPlayer.d.ts +3 -3
  214. package/dist/types/players/MewsWsPlayer/SourceBufferManager.d.ts +1 -1
  215. package/dist/types/players/MewsWsPlayer/WebSocketManager.d.ts +1 -1
  216. package/dist/types/players/MewsWsPlayer/index.d.ts +2 -2
  217. package/dist/types/players/MewsWsPlayer/types.d.ts +15 -15
  218. package/dist/types/players/MistPlayer.d.ts +2 -2
  219. package/dist/types/players/MistWebRTCPlayer/index.d.ts +3 -3
  220. package/dist/types/players/NativePlayer.d.ts +3 -3
  221. package/dist/types/players/VideoJsPlayer.d.ts +3 -3
  222. package/dist/types/players/WebCodecsPlayer/JitterBuffer.d.ts +3 -3
  223. package/dist/types/players/WebCodecsPlayer/LatencyProfiles.d.ts +1 -1
  224. package/dist/types/players/WebCodecsPlayer/RawChunkParser.d.ts +2 -2
  225. package/dist/types/players/WebCodecsPlayer/SyncController.d.ts +2 -2
  226. package/dist/types/players/WebCodecsPlayer/WebSocketController.d.ts +3 -3
  227. package/dist/types/players/WebCodecsPlayer/index.d.ts +9 -9
  228. package/dist/types/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.d.ts +1 -1
  229. package/dist/types/players/WebCodecsPlayer/types.d.ts +49 -49
  230. package/dist/types/players/WebCodecsPlayer/worker/types.d.ts +31 -31
  231. package/dist/types/players/index.d.ts +5 -8
  232. package/dist/types/types.d.ts +15 -15
  233. package/dist/types/vanilla/FrameWorksPlayer.d.ts +2 -2
  234. package/dist/types/vanilla/index.d.ts +4 -4
  235. package/dist/workers/decoder.worker.js +129 -122
  236. package/dist/workers/decoder.worker.js.map +1 -1
  237. package/package.json +31 -15
  238. package/src/core/ABRController.ts +38 -36
  239. package/src/core/CodecUtils.ts +49 -46
  240. package/src/core/Disposable.ts +4 -4
  241. package/src/core/ErrorClassifier.ts +499 -0
  242. package/src/core/EventEmitter.ts +1 -1
  243. package/src/core/GatewayClient.ts +41 -39
  244. package/src/core/InteractionController.ts +89 -82
  245. package/src/core/LiveDurationProxy.ts +14 -15
  246. package/src/core/MetaTrackManager.ts +73 -65
  247. package/src/core/MistReporter.ts +72 -45
  248. package/src/core/MistSignaling.ts +59 -56
  249. package/src/core/PlayerController.ts +542 -384
  250. package/src/core/PlayerInterface.ts +192 -59
  251. package/src/core/PlayerManager.ts +354 -164
  252. package/src/core/PlayerRegistry.ts +238 -87
  253. package/src/core/QualityMonitor.ts +38 -31
  254. package/src/core/ScreenWakeLockManager.ts +8 -9
  255. package/src/core/SeekingUtils.ts +31 -22
  256. package/src/core/StreamStateClient.ts +74 -68
  257. package/src/core/SubtitleManager.ts +24 -22
  258. package/src/core/TelemetryReporter.ts +38 -32
  259. package/src/core/TimeFormat.ts +13 -17
  260. package/src/core/TimerManager.ts +24 -8
  261. package/src/core/UrlUtils.ts +20 -17
  262. package/src/core/detector.ts +44 -44
  263. package/src/core/index.ts +57 -48
  264. package/src/core/scorer.ts +136 -141
  265. package/src/core/selector.ts +2 -6
  266. package/src/global.d.ts +1 -1
  267. package/src/index.ts +56 -36
  268. package/src/players/DashJsPlayer.ts +164 -115
  269. package/src/players/HlsJsPlayer.ts +132 -78
  270. package/src/players/MewsWsPlayer/SourceBufferManager.ts +41 -36
  271. package/src/players/MewsWsPlayer/WebSocketManager.ts +9 -9
  272. package/src/players/MewsWsPlayer/index.ts +192 -152
  273. package/src/players/MewsWsPlayer/types.ts +21 -21
  274. package/src/players/MistPlayer.ts +45 -26
  275. package/src/players/MistWebRTCPlayer/index.ts +175 -129
  276. package/src/players/NativePlayer.ts +203 -143
  277. package/src/players/VideoJsPlayer.ts +170 -118
  278. package/src/players/WebCodecsPlayer/JitterBuffer.ts +6 -7
  279. package/src/players/WebCodecsPlayer/LatencyProfiles.ts +43 -43
  280. package/src/players/WebCodecsPlayer/RawChunkParser.ts +10 -10
  281. package/src/players/WebCodecsPlayer/SyncController.ts +45 -53
  282. package/src/players/WebCodecsPlayer/WebSocketController.ts +66 -68
  283. package/src/players/WebCodecsPlayer/index.ts +265 -223
  284. package/src/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.ts +12 -17
  285. package/src/players/WebCodecsPlayer/types.ts +56 -56
  286. package/src/players/WebCodecsPlayer/worker/decoder.worker.ts +238 -182
  287. package/src/players/WebCodecsPlayer/worker/types.ts +31 -31
  288. package/src/players/index.ts +5 -16
  289. package/src/styles/animations.css +2 -1
  290. package/src/styles/player.css +185 -42
  291. package/src/styles/tailwind.css +473 -159
  292. package/src/types.ts +43 -43
  293. package/src/vanilla/FrameWorksPlayer.ts +26 -14
  294. package/src/vanilla/index.ts +4 -4
@@ -0,0 +1,606 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * InteractionController - Unified keyboard and gesture handling for video players
5
+ *
6
+ * Features:
7
+ * - Hold space for 2x speed (VOD/clips only, tap = play/pause)
8
+ * - Click/touch and hold for 2x speed
9
+ * - Comprehensive keyboard shortcuts
10
+ * - Double-tap to skip on mobile
11
+ * - All interactions disabled for live streams (where applicable)
12
+ */
13
+ // Timing constants
14
+ const HOLD_THRESHOLD_MS = 200; // Time before keydown becomes "hold" vs "tap"
15
+ const LONG_PRESS_THRESHOLD_MS = 300; // Time for touch/click to become "hold"
16
+ const DOUBLE_TAP_WINDOW_MS = 300; // Window for detecting double-tap
17
+ const SKIP_AMOUNT_SECONDS = 10; // Skip forward/backward amount
18
+ const VOLUME_STEP = 0.1; // Volume change per arrow press (10%)
19
+ const DEFAULT_IDLE_TIMEOUT_MS = 5000; // Default idle timeout (5 seconds)
20
+ class InteractionController {
21
+ constructor(config) {
22
+ this.isAttached = false;
23
+ // Keyboard tracking
24
+ this.spaceKeyDownTime = 0;
25
+ this.spaceIsHeld = false;
26
+ this.holdCheckTimeout = null;
27
+ // Touch/click tracking
28
+ this.pointerDownTime = 0;
29
+ this.pointerIsHeld = false;
30
+ this.pointerHoldTimeout = null;
31
+ this.lastTapTime = 0;
32
+ this.lastTapX = 0;
33
+ this.pendingTapTimeout = null;
34
+ // Idle tracking
35
+ this.idleTimeout = null;
36
+ this.lastInteractionTime = 0;
37
+ this.config = config;
38
+ this.state = {
39
+ isHoldingSpeed: false,
40
+ previousSpeed: 1,
41
+ holdSpeed: config.speedHoldValue ?? 2,
42
+ isIdle: false,
43
+ };
44
+ // Bind handlers
45
+ this.boundKeyDown = this.handleKeyDown.bind(this);
46
+ this.boundKeyUp = this.handleKeyUp.bind(this);
47
+ this.boundPointerDown = this.handlePointerDown.bind(this);
48
+ this.boundPointerUp = this.handlePointerUp.bind(this);
49
+ this.boundPointerCancel = this.handlePointerCancel.bind(this);
50
+ this.boundContextMenu = this.handleContextMenu.bind(this);
51
+ this.boundMouseMove = this.handleMouseMove.bind(this);
52
+ this.boundDoubleClick = this.handleDoubleClick.bind(this);
53
+ this.boundDocumentKeyDown = this.handleKeyDown.bind(this);
54
+ this.boundDocumentKeyUp = this.handleKeyUp.bind(this);
55
+ }
56
+ /**
57
+ * Attach event listeners to container
58
+ */
59
+ attach() {
60
+ if (this.isAttached)
61
+ return;
62
+ const { container } = this.config;
63
+ // Make container focusable for keyboard events
64
+ if (!container.hasAttribute("tabindex")) {
65
+ container.setAttribute("tabindex", "0");
66
+ }
67
+ // Keyboard events
68
+ container.addEventListener("keydown", this.boundKeyDown);
69
+ container.addEventListener("keyup", this.boundKeyUp);
70
+ document.addEventListener("keydown", this.boundDocumentKeyDown);
71
+ document.addEventListener("keyup", this.boundDocumentKeyUp);
72
+ // Pointer events (unified mouse + touch)
73
+ container.addEventListener("pointerdown", this.boundPointerDown);
74
+ container.addEventListener("pointerup", this.boundPointerUp);
75
+ container.addEventListener("pointercancel", this.boundPointerCancel);
76
+ container.addEventListener("pointerleave", this.boundPointerCancel);
77
+ // Mouse move for idle detection
78
+ container.addEventListener("mousemove", this.boundMouseMove);
79
+ // Double click for fullscreen (desktop)
80
+ container.addEventListener("dblclick", this.boundDoubleClick);
81
+ // Prevent context menu on long press
82
+ container.addEventListener("contextmenu", this.boundContextMenu);
83
+ // Start idle tracking
84
+ this.resetIdleTimer();
85
+ this.isAttached = true;
86
+ }
87
+ /**
88
+ * Detach event listeners and cleanup
89
+ */
90
+ detach() {
91
+ if (!this.isAttached)
92
+ return;
93
+ const { container } = this.config;
94
+ container.removeEventListener("keydown", this.boundKeyDown);
95
+ container.removeEventListener("keyup", this.boundKeyUp);
96
+ document.removeEventListener("keydown", this.boundDocumentKeyDown);
97
+ document.removeEventListener("keyup", this.boundDocumentKeyUp);
98
+ container.removeEventListener("pointerdown", this.boundPointerDown);
99
+ container.removeEventListener("pointerup", this.boundPointerUp);
100
+ container.removeEventListener("pointercancel", this.boundPointerCancel);
101
+ container.removeEventListener("pointerleave", this.boundPointerCancel);
102
+ container.removeEventListener("mousemove", this.boundMouseMove);
103
+ container.removeEventListener("dblclick", this.boundDoubleClick);
104
+ container.removeEventListener("contextmenu", this.boundContextMenu);
105
+ // Clear any pending timeouts
106
+ if (this.holdCheckTimeout) {
107
+ clearTimeout(this.holdCheckTimeout);
108
+ this.holdCheckTimeout = null;
109
+ }
110
+ if (this.pointerHoldTimeout) {
111
+ clearTimeout(this.pointerHoldTimeout);
112
+ this.pointerHoldTimeout = null;
113
+ }
114
+ if (this.pendingTapTimeout) {
115
+ clearTimeout(this.pendingTapTimeout);
116
+ this.pendingTapTimeout = null;
117
+ }
118
+ if (this.idleTimeout) {
119
+ clearTimeout(this.idleTimeout);
120
+ this.idleTimeout = null;
121
+ }
122
+ // Restore speed if holding
123
+ if (this.state.isHoldingSpeed) {
124
+ this.releaseSpeedHold();
125
+ }
126
+ this.isAttached = false;
127
+ }
128
+ /**
129
+ * Check if currently holding for speed boost
130
+ */
131
+ isHoldingSpeed() {
132
+ return this.state.isHoldingSpeed;
133
+ }
134
+ /**
135
+ * Check if user is currently idle (no interaction for idleTimeout)
136
+ */
137
+ isIdle() {
138
+ return this.state.isIdle;
139
+ }
140
+ /**
141
+ * Get current interaction state
142
+ */
143
+ getState() {
144
+ return { ...this.state };
145
+ }
146
+ /**
147
+ * Update config (e.g., when isLive changes)
148
+ */
149
+ updateConfig(updates) {
150
+ this.config = { ...this.config, ...updates };
151
+ // If we switched to live mode while holding, release
152
+ if (updates.isLive && this.state.isHoldingSpeed) {
153
+ this.releaseSpeedHold();
154
+ }
155
+ }
156
+ // ─────────────────────────────────────────────────────────────────
157
+ // Keyboard Handling
158
+ // ─────────────────────────────────────────────────────────────────
159
+ handleKeyDown(e) {
160
+ // Ignore if focus is on an input element
161
+ if (this.isInputElement(e.target))
162
+ return;
163
+ if (e.defaultPrevented)
164
+ return;
165
+ if (!this.shouldHandleKeyboard(e))
166
+ return;
167
+ // Record interaction for idle detection
168
+ this.recordInteraction();
169
+ const { isLive } = this.config;
170
+ const isPaused = this.config.isPaused?.() ?? this.config.videoElement?.paused ?? false;
171
+ switch (e.key) {
172
+ case " ":
173
+ case "Spacebar":
174
+ e.preventDefault();
175
+ this.handleSpaceDown();
176
+ break;
177
+ case "ArrowLeft":
178
+ case "j":
179
+ case "J":
180
+ e.preventDefault();
181
+ if (!isLive) {
182
+ this.config.onSeek(-SKIP_AMOUNT_SECONDS);
183
+ }
184
+ break;
185
+ case "ArrowRight":
186
+ case "l":
187
+ case "L":
188
+ e.preventDefault();
189
+ if (!isLive) {
190
+ this.config.onSeek(SKIP_AMOUNT_SECONDS);
191
+ }
192
+ break;
193
+ case "ArrowUp":
194
+ e.preventDefault();
195
+ this.config.onVolumeChange(VOLUME_STEP);
196
+ break;
197
+ case "ArrowDown":
198
+ e.preventDefault();
199
+ this.config.onVolumeChange(-VOLUME_STEP);
200
+ break;
201
+ case "m":
202
+ case "M":
203
+ e.preventDefault();
204
+ this.config.onMuteToggle();
205
+ break;
206
+ case "f":
207
+ case "F":
208
+ e.preventDefault();
209
+ this.config.onFullscreenToggle();
210
+ break;
211
+ case "c":
212
+ case "C":
213
+ e.preventDefault();
214
+ this.config.onCaptionsToggle?.();
215
+ break;
216
+ case "k":
217
+ case "K":
218
+ // YouTube-style: K = play/pause (no hold behavior)
219
+ e.preventDefault();
220
+ this.config.onPlayPause();
221
+ break;
222
+ case "<":
223
+ // Decrease speed (shift+, = <)
224
+ e.preventDefault();
225
+ if (!isLive) {
226
+ this.adjustPlaybackSpeed(-0.25);
227
+ }
228
+ break;
229
+ case ">":
230
+ // Increase speed (shift+. = >)
231
+ e.preventDefault();
232
+ if (!isLive) {
233
+ this.adjustPlaybackSpeed(0.25);
234
+ }
235
+ break;
236
+ case ",":
237
+ // Previous frame when paused
238
+ if (this.config.onFrameStep || (!isLive && isPaused)) {
239
+ e.preventDefault();
240
+ this.stepFrame(-1);
241
+ }
242
+ break;
243
+ case ".":
244
+ // Next frame when paused
245
+ if (this.config.onFrameStep || (!isLive && isPaused)) {
246
+ e.preventDefault();
247
+ this.stepFrame(1);
248
+ }
249
+ break;
250
+ // Number keys for seeking to percentage
251
+ case "0":
252
+ case "1":
253
+ case "2":
254
+ case "3":
255
+ case "4":
256
+ case "5":
257
+ case "6":
258
+ case "7":
259
+ case "8":
260
+ case "9":
261
+ e.preventDefault();
262
+ if (!isLive && this.config.onSeekPercent) {
263
+ const percent = parseInt(e.key, 10) / 10;
264
+ this.config.onSeekPercent(percent);
265
+ }
266
+ break;
267
+ }
268
+ }
269
+ handleKeyUp(e) {
270
+ if (this.isInputElement(e.target))
271
+ return;
272
+ if (e.defaultPrevented)
273
+ return;
274
+ if (!this.shouldHandleKeyboard(e))
275
+ return;
276
+ if (e.key === " " || e.key === "Spacebar") {
277
+ e.preventDefault();
278
+ this.handleSpaceUp();
279
+ }
280
+ }
281
+ shouldHandleKeyboard(e) {
282
+ if (this.spaceKeyDownTime > 0)
283
+ return true;
284
+ const target = e.target;
285
+ if (target && this.config.container.contains(target))
286
+ return true;
287
+ const active = document.activeElement;
288
+ if (active && this.config.container.contains(active))
289
+ return true;
290
+ try {
291
+ if (this.config.container.matches(":focus-within"))
292
+ return true;
293
+ if (this.config.container.matches(":hover"))
294
+ return true;
295
+ }
296
+ catch { }
297
+ const now = Date.now();
298
+ if (now - this.lastInteractionTime < DEFAULT_IDLE_TIMEOUT_MS)
299
+ return true;
300
+ return false;
301
+ }
302
+ handleSpaceDown() {
303
+ if (this.spaceKeyDownTime > 0)
304
+ return; // Already tracking
305
+ this.spaceKeyDownTime = Date.now();
306
+ this.spaceIsHeld = false;
307
+ // Only enable hold-for-speed on VOD/clips
308
+ if (!this.config.isLive) {
309
+ this.holdCheckTimeout = setTimeout(() => {
310
+ this.spaceIsHeld = true;
311
+ this.engageSpeedHold();
312
+ }, HOLD_THRESHOLD_MS);
313
+ }
314
+ }
315
+ handleSpaceUp() {
316
+ const downTime = this.spaceKeyDownTime;
317
+ this.spaceKeyDownTime = 0;
318
+ if (this.holdCheckTimeout) {
319
+ clearTimeout(this.holdCheckTimeout);
320
+ this.holdCheckTimeout = null;
321
+ }
322
+ if (this.spaceIsHeld) {
323
+ // Was holding - release speed boost
324
+ this.releaseSpeedHold();
325
+ this.spaceIsHeld = false;
326
+ }
327
+ else {
328
+ // Was a quick tap - toggle play/pause
329
+ const elapsed = Date.now() - downTime;
330
+ if (elapsed < HOLD_THRESHOLD_MS || this.config.isLive) {
331
+ this.config.onPlayPause();
332
+ }
333
+ }
334
+ }
335
+ handleDoubleClick(e) {
336
+ if (this.isControlElement(e.target))
337
+ return;
338
+ this.recordInteraction();
339
+ e.preventDefault();
340
+ this.config.onFullscreenToggle();
341
+ }
342
+ stepFrame(direction) {
343
+ const step = this.getFrameStepSeconds();
344
+ if (!Number.isFinite(step) || step <= 0)
345
+ return;
346
+ if (this.config.onFrameStep?.(direction, step))
347
+ return;
348
+ const video = this.config.videoElement;
349
+ if (!video)
350
+ return;
351
+ const target = video.currentTime + direction * step;
352
+ if (!Number.isFinite(target))
353
+ return;
354
+ // Only step within already-buffered ranges to avoid network seeks
355
+ const buffered = video.buffered;
356
+ if (buffered && buffered.length > 0) {
357
+ for (let i = 0; i < buffered.length; i++) {
358
+ const start = buffered.start(i);
359
+ const end = buffered.end(i);
360
+ if (target >= start && target <= end) {
361
+ try {
362
+ video.currentTime = target;
363
+ }
364
+ catch { }
365
+ return;
366
+ }
367
+ }
368
+ }
369
+ }
370
+ // ─────────────────────────────────────────────────────────────────
371
+ // Pointer (Mouse/Touch) Handling
372
+ // ─────────────────────────────────────────────────────────────────
373
+ handlePointerDown(e) {
374
+ // Only handle primary button / single touch on the video area
375
+ if (e.button !== 0)
376
+ return;
377
+ if (this.isControlElement(e.target))
378
+ return;
379
+ // Record interaction for idle detection
380
+ this.recordInteraction();
381
+ // Ensure container has focus for keyboard events
382
+ this.config.container.focus();
383
+ const now = Date.now();
384
+ const rect = this.config.container.getBoundingClientRect();
385
+ const relativeX = (e.clientX - rect.left) / rect.width;
386
+ const isMouse = e.pointerType === "mouse";
387
+ // Check for double-tap
388
+ if (now - this.lastTapTime < DOUBLE_TAP_WINDOW_MS) {
389
+ // Clear pending single-tap
390
+ if (this.pendingTapTimeout) {
391
+ clearTimeout(this.pendingTapTimeout);
392
+ this.pendingTapTimeout = null;
393
+ }
394
+ // Mouse double-click handled via dblclick event (fullscreen)
395
+ if (!isMouse) {
396
+ // Handle double-tap to skip (mobile-style)
397
+ if (!this.config.isLive) {
398
+ if (relativeX < 0.33) {
399
+ // Left third - skip back
400
+ this.config.onSeek(-SKIP_AMOUNT_SECONDS);
401
+ }
402
+ else if (relativeX > 0.67) {
403
+ // Right third - skip forward
404
+ this.config.onSeek(SKIP_AMOUNT_SECONDS);
405
+ }
406
+ else {
407
+ // Center - treat as play/pause
408
+ this.config.onPlayPause();
409
+ }
410
+ }
411
+ }
412
+ this.lastTapTime = 0;
413
+ return;
414
+ }
415
+ this.lastTapTime = now;
416
+ this.lastTapX = relativeX;
417
+ this.pointerDownTime = now;
418
+ this.pointerIsHeld = false;
419
+ // Start long-press detection for 2x speed (VOD only)
420
+ if (!this.config.isLive) {
421
+ this.pointerHoldTimeout = setTimeout(() => {
422
+ this.pointerIsHeld = true;
423
+ this.engageSpeedHold();
424
+ }, LONG_PRESS_THRESHOLD_MS);
425
+ }
426
+ }
427
+ handlePointerUp(e) {
428
+ if (e.button !== 0)
429
+ return;
430
+ const wasHeld = this.pointerIsHeld;
431
+ this.cancelPointerHold();
432
+ if (wasHeld) {
433
+ // Was long-pressing - just release speed
434
+ this.releaseSpeedHold();
435
+ }
436
+ else if (this.pointerDownTime > 0) {
437
+ // Was a quick tap - delay to check for double-tap
438
+ this.pendingTapTimeout = setTimeout(() => {
439
+ this.pendingTapTimeout = null;
440
+ this.config.onPlayPause();
441
+ }, DOUBLE_TAP_WINDOW_MS);
442
+ }
443
+ this.pointerDownTime = 0;
444
+ }
445
+ handlePointerCancel(_e) {
446
+ if (this.pointerIsHeld) {
447
+ this.releaseSpeedHold();
448
+ }
449
+ this.cancelPointerHold();
450
+ this.pointerDownTime = 0;
451
+ }
452
+ cancelPointerHold() {
453
+ if (this.pointerHoldTimeout) {
454
+ clearTimeout(this.pointerHoldTimeout);
455
+ this.pointerHoldTimeout = null;
456
+ }
457
+ this.pointerIsHeld = false;
458
+ }
459
+ handleContextMenu(e) {
460
+ // Prevent context menu during long-press
461
+ if (this.pointerIsHeld || this.pointerDownTime > 0) {
462
+ e.preventDefault();
463
+ }
464
+ }
465
+ // ─────────────────────────────────────────────────────────────────
466
+ // Speed Hold Logic
467
+ // ─────────────────────────────────────────────────────────────────
468
+ engageSpeedHold() {
469
+ if (this.state.isHoldingSpeed)
470
+ return;
471
+ if (this.config.isLive)
472
+ return;
473
+ // Save current speed
474
+ this.state.previousSpeed = this.config.videoElement.playbackRate;
475
+ this.state.isHoldingSpeed = true;
476
+ // Apply hold speed
477
+ this.config.onSpeedChange(this.state.holdSpeed, true);
478
+ }
479
+ releaseSpeedHold() {
480
+ if (!this.state.isHoldingSpeed)
481
+ return;
482
+ this.state.isHoldingSpeed = false;
483
+ // Restore previous speed
484
+ this.config.onSpeedChange(this.state.previousSpeed, false);
485
+ }
486
+ adjustPlaybackSpeed(delta) {
487
+ if (this.state.isHoldingSpeed)
488
+ return;
489
+ const currentSpeed = this.config.videoElement.playbackRate;
490
+ const newSpeed = Math.max(0.25, Math.min(4, currentSpeed + delta));
491
+ // Round to avoid floating point issues
492
+ const roundedSpeed = Math.round(newSpeed * 100) / 100;
493
+ this.config.onSpeedChange(roundedSpeed, false);
494
+ }
495
+ // ─────────────────────────────────────────────────────────────────
496
+ // Idle Detection
497
+ // ─────────────────────────────────────────────────────────────────
498
+ handleMouseMove(_e) {
499
+ this.recordInteraction();
500
+ }
501
+ /**
502
+ * Record that an interaction occurred and reset idle timer
503
+ */
504
+ recordInteraction() {
505
+ this.lastInteractionTime = Date.now();
506
+ // If was idle, become active
507
+ if (this.state.isIdle) {
508
+ this.state.isIdle = false;
509
+ this.config.onActive?.();
510
+ }
511
+ // Reset idle timer
512
+ this.resetIdleTimer();
513
+ }
514
+ /**
515
+ * Reset the idle timer
516
+ */
517
+ resetIdleTimer() {
518
+ // Clear existing timer
519
+ if (this.idleTimeout) {
520
+ clearTimeout(this.idleTimeout);
521
+ this.idleTimeout = null;
522
+ }
523
+ // Get timeout value (0 means disabled)
524
+ const timeout = this.config.idleTimeout ?? DEFAULT_IDLE_TIMEOUT_MS;
525
+ if (timeout <= 0)
526
+ return;
527
+ // Set new timer
528
+ this.idleTimeout = setTimeout(() => {
529
+ this.idleTimeout = null;
530
+ if (!this.state.isIdle) {
531
+ this.state.isIdle = true;
532
+ this.config.onIdle?.();
533
+ }
534
+ }, timeout);
535
+ }
536
+ /**
537
+ * Manually mark as active (e.g., when controls become visible)
538
+ */
539
+ markActive() {
540
+ this.recordInteraction();
541
+ }
542
+ /**
543
+ * Pause idle tracking (e.g., when controls are visible)
544
+ */
545
+ pauseIdleTracking() {
546
+ if (this.idleTimeout) {
547
+ clearTimeout(this.idleTimeout);
548
+ this.idleTimeout = null;
549
+ }
550
+ }
551
+ /**
552
+ * Resume idle tracking
553
+ */
554
+ resumeIdleTracking() {
555
+ if (this.isAttached) {
556
+ this.resetIdleTimer();
557
+ }
558
+ }
559
+ // ─────────────────────────────────────────────────────────────────
560
+ // Utilities
561
+ // ─────────────────────────────────────────────────────────────────
562
+ isInputElement(target) {
563
+ if (!target || !(target instanceof HTMLElement))
564
+ return false;
565
+ const tagName = target.tagName.toLowerCase();
566
+ return (tagName === "input" ||
567
+ tagName === "textarea" ||
568
+ tagName === "select" ||
569
+ target.isContentEditable);
570
+ }
571
+ isControlElement(target) {
572
+ if (!target || !(target instanceof HTMLElement))
573
+ return false;
574
+ // Check if clicking on player controls (buttons, sliders, etc.)
575
+ const controlSelectors = [
576
+ "button",
577
+ '[role="button"]',
578
+ '[role="slider"]',
579
+ "input",
580
+ "select",
581
+ ".fw-player-controls",
582
+ "[data-player-controls]",
583
+ ".fw-controls-wrapper",
584
+ ".fw-control-bar",
585
+ ".fw-settings-menu",
586
+ ".fw-context-menu",
587
+ ".fw-stats-panel",
588
+ ".fw-dev-panel",
589
+ ".fw-error-overlay",
590
+ ".fw-error-popup",
591
+ ".fw-player-error",
592
+ ];
593
+ return controlSelectors.some((selector) => {
594
+ return target.matches(selector) || target.closest(selector) !== null;
595
+ });
596
+ }
597
+ getFrameStepSeconds() {
598
+ const step = this.config.frameStepSeconds;
599
+ if (Number.isFinite(step) && step > 0)
600
+ return step;
601
+ return 1 / 30;
602
+ }
603
+ }
604
+
605
+ exports.InteractionController = InteractionController;
606
+ //# sourceMappingURL=InteractionController.js.map