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