@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,987 @@
1
+ 'use strict';
2
+
3
+ var PlayerInterface = require('../../core/PlayerInterface.js');
4
+ var WebSocketManager = require('./WebSocketManager.js');
5
+ var SourceBufferManager = require('./SourceBufferManager.js');
6
+ var CodecUtils = require('../../core/CodecUtils.js');
7
+
8
+ /**
9
+ * MEWS WebSocket Player Implementation
10
+ *
11
+ * Low-latency WebSocket MP4 streaming using MediaSource Extensions.
12
+ * Protocol: Custom MEWS (MistServer Extended WebSocket)
13
+ *
14
+ * Ported from reference: mews.js (MistMetaPlayer)
15
+ */
16
+ class MewsWsPlayerImpl extends PlayerInterface.BasePlayer {
17
+ constructor() {
18
+ super(...arguments);
19
+ this.capability = {
20
+ name: "MEWS WebSocket Player",
21
+ shortname: "mews",
22
+ priority: 2, // High priority - low latency protocol
23
+ mimes: ["ws/video/mp4", "wss/video/mp4", "ws/video/webm", "wss/video/webm"],
24
+ };
25
+ this.wsManager = null;
26
+ this.sbManager = null;
27
+ this.mediaSource = null;
28
+ this.objectUrl = null;
29
+ this.container = null;
30
+ this.isDestroyed = false;
31
+ this.debugging = false;
32
+ // Server delay estimation (ported from mews.js:833-882)
33
+ this.serverDelays = [];
34
+ this.pendingDelayTypes = {};
35
+ // Supported codecs (short names for MistServer protocol)
36
+ this.supportedCodecs = [];
37
+ // Ready state - true after codec_data received and SourceBuffer initialized
38
+ this.isReady = false;
39
+ this.readyResolvers = [];
40
+ // Duration tracking (ported from mews.js:1113)
41
+ this.lastDuration = Infinity;
42
+ // Live vs VoD detection (ported from mews.js:105-107, 508)
43
+ this.streamType = "unknown";
44
+ // Current tracks for change detection (ported from mews.js:455, 593-619)
45
+ this.currentTracks = [];
46
+ // Last codecs for track switch comparison (ported from mews.js:687)
47
+ this.lastCodecs = null;
48
+ // Playback rate tuning (ported from mews.js:453, 509-545)
49
+ this.requestedRate = 1;
50
+ // ABR state (ported from mews.js:1266-1314)
51
+ this.bitCounter = [];
52
+ this.bitsSince = [];
53
+ this.currentBps = null;
54
+ this.nWaiting = 0;
55
+ this.nWaitingThreshold = 3;
56
+ // Seeking state (ported from mews.js:1169-1175)
57
+ this.seeking = false;
58
+ // Analytics
59
+ this.analyticsConfig = { enabled: false, endpoint: null };
60
+ this.analyticsTimer = null;
61
+ }
62
+ isMimeSupported(mimetype) {
63
+ return this.capability.mimes.includes(mimetype);
64
+ }
65
+ isBrowserSupported(mimetype, source, streamInfo) {
66
+ // Basic requirements check (mews.js:10)
67
+ if (!("WebSocket" in window) || !("MediaSource" in window) || !("Promise" in window)) {
68
+ return false;
69
+ }
70
+ // MacOS exemption (reference mews.js behavior)
71
+ // MediaSource has bugs on Safari/MacOS - prefer HLS
72
+ const isMac = /Mac OS X/.test(navigator.userAgent);
73
+ if (isMac) {
74
+ return false;
75
+ }
76
+ // Check codec compatibility using ACTUAL stream codecs (mews.js:45-83)
77
+ const container = mimetype.split("/")[2] || "mp4";
78
+ const playableTracks = {};
79
+ let hasSubtitles = false;
80
+ // Test actual stream codecs against MediaSource
81
+ this.supportedCodecs = [];
82
+ for (const track of streamInfo.meta.tracks) {
83
+ if (track.type === "meta") {
84
+ if (track.codec === "subtitle")
85
+ hasSubtitles = true;
86
+ continue;
87
+ }
88
+ const codecString = CodecUtils.translateCodec(track);
89
+ const testMime = `video/${container};codecs="${codecString}"`;
90
+ if (MediaSource.isTypeSupported(testMime)) {
91
+ this.supportedCodecs.push(track.codec);
92
+ playableTracks[track.type] = 1;
93
+ }
94
+ }
95
+ // Check for subtitle source (mews.js:73-80)
96
+ if (hasSubtitles) {
97
+ const hasVttSource = streamInfo.source?.some((s) => s.type === "html5/text/vtt");
98
+ if (hasVttSource) {
99
+ playableTracks["subtitle"] = 1;
100
+ }
101
+ }
102
+ if (Object.keys(playableTracks).length === 0)
103
+ return false;
104
+ return Object.keys(playableTracks);
105
+ }
106
+ async initialize(container, source, options, streamInfo) {
107
+ this.container = container;
108
+ container.classList.add("fw-player-container");
109
+ const video = document.createElement("video");
110
+ video.classList.add("fw-player-video");
111
+ video.setAttribute("playsinline", ""); // iphones (mews.js:92)
112
+ video.setAttribute("crossorigin", "anonymous"); // mews.js:111
113
+ // Apply options (mews.js:95-110)
114
+ if (options.autoplay)
115
+ video.autoplay = true;
116
+ if (options.muted)
117
+ video.muted = true;
118
+ video.controls = options.controls === true;
119
+ if (options.loop)
120
+ video.loop = true;
121
+ if (options.poster)
122
+ video.poster = options.poster;
123
+ // Live streams don't loop (mews.js:105-107)
124
+ if (this.streamType === "live") {
125
+ video.loop = false;
126
+ }
127
+ this.videoElement = video;
128
+ container.appendChild(video);
129
+ this.setupVideoEventListeners(video, options);
130
+ // Analytics configuration
131
+ const anyOpts = options;
132
+ this.analyticsConfig = {
133
+ enabled: !!anyOpts.analytics?.enabled,
134
+ endpoint: anyOpts.analytics?.endpoint || null,
135
+ };
136
+ // Get stream type from streamInfo if available
137
+ // Note: source.type is a MIME string (e.g., 'ws/video/mp4'), not 'live'/'vod'
138
+ if (streamInfo?.type === "live") {
139
+ this.streamType = "live";
140
+ }
141
+ else if (streamInfo?.type === "vod") {
142
+ this.streamType = "vod";
143
+ }
144
+ // Fallback: will be determined by server on_time messages (end === 0 means live)
145
+ try {
146
+ // Initialize MediaSource (mews.js:138-196)
147
+ this.mediaSource = new MediaSource();
148
+ // Set up MediaSource event handlers (mews.js:143-195)
149
+ this.mediaSource.addEventListener("sourceopen", () => this.handleSourceOpen(source));
150
+ this.mediaSource.addEventListener("sourceclose", () => this.handleSourceClose());
151
+ this.mediaSource.addEventListener("sourceended", () => this.handleSourceEnded());
152
+ this.objectUrl = URL.createObjectURL(this.mediaSource);
153
+ video.src = this.objectUrl;
154
+ this.isDestroyed = false;
155
+ this.startTelemetry();
156
+ return video;
157
+ }
158
+ catch (error) {
159
+ this.emit("error", error.message || String(error));
160
+ throw error;
161
+ }
162
+ }
163
+ /**
164
+ * Handle MediaSource sourceopen event.
165
+ * Ported from mews.js:143-148, 198-204, 885-902
166
+ */
167
+ handleSourceOpen(source) {
168
+ if (!this.mediaSource || !this.videoElement)
169
+ return;
170
+ // Create SourceBufferManager
171
+ this.sbManager = new SourceBufferManager.SourceBufferManager({
172
+ mediaSource: this.mediaSource,
173
+ videoElement: this.videoElement,
174
+ onError: (msg) => this.emit("error", msg),
175
+ });
176
+ // Install browser event handlers
177
+ this.installWaitingHandler();
178
+ this.installSeekingHandler();
179
+ this.installPauseHandler();
180
+ this.installLoopHandler();
181
+ // Create WebSocketManager with listener support
182
+ this.wsManager = new WebSocketManager.WebSocketManager({
183
+ url: source.url,
184
+ maxReconnectAttempts: 5,
185
+ onMessage: (data) => this.handleMessage(data),
186
+ onOpen: () => this.handleWsOpen(),
187
+ onClose: () => this.handleWsClose(),
188
+ onError: (msg) => this.emit("error", msg),
189
+ });
190
+ this.wsManager.connect();
191
+ }
192
+ /**
193
+ * Handle MediaSource sourceclose event.
194
+ * Ported from mews.js:150-153
195
+ */
196
+ handleSourceClose() {
197
+ if (this.debugging)
198
+ console.log("MEWS: MediaSource closed");
199
+ this.send({ type: "stop" });
200
+ }
201
+ /**
202
+ * Handle MediaSource sourceended event.
203
+ * Ported from mews.js:154-194
204
+ */
205
+ handleSourceEnded() {
206
+ if (this.debugging)
207
+ console.log("MEWS: MediaSource ended");
208
+ this.send({ type: "stop" });
209
+ }
210
+ /**
211
+ * Handle WebSocket open event.
212
+ * Ported from mews.js:401-403, 885-902
213
+ */
214
+ handleWsOpen() {
215
+ // Request codec data (mews.js:885-902)
216
+ const listener = (msg) => {
217
+ // Got codec data, set up source buffer
218
+ if (this.mediaSource?.readyState === "open") {
219
+ const codecs = msg.data?.codecs || [];
220
+ const initialized = this.sbManager?.initWithCodecs(codecs);
221
+ if (initialized && !this.isReady) {
222
+ this.isReady = true;
223
+ // Resolve any waiting play() calls
224
+ for (const resolve of this.readyResolvers) {
225
+ resolve();
226
+ }
227
+ this.readyResolvers = [];
228
+ }
229
+ }
230
+ this.wsManager?.removeListener("codec_data", listener);
231
+ };
232
+ this.wsManager?.addListener("codec_data", listener);
233
+ this.logDelay("codec_data");
234
+ // Send request with SHORT codec names (mews.js:901)
235
+ // CRITICAL: MistServer expects short names like "H264", not browser codec strings
236
+ this.send({ type: "request_codec_data", supported_codecs: this.supportedCodecs });
237
+ }
238
+ /**
239
+ * Handle WebSocket close event with reconnection logic.
240
+ * Ported from mews.js:408-431
241
+ */
242
+ handleWsClose() {
243
+ if (this.debugging)
244
+ console.log("MEWS: WebSocket closed");
245
+ // Reconnection is handled by WebSocketManager
246
+ }
247
+ /**
248
+ * Handle incoming WebSocket message.
249
+ * Routes to binary append or JSON control message handler.
250
+ * Ported from mews.js:456-830
251
+ */
252
+ handleMessage(data) {
253
+ if (typeof data === "string") {
254
+ try {
255
+ const msg = JSON.parse(data);
256
+ this.handleControlMessage(msg);
257
+ // Notify listeners (mews.js:795-799)
258
+ this.wsManager?.notifyListeners(msg);
259
+ }
260
+ catch (e) {
261
+ if (this.debugging)
262
+ console.error("MEWS: Failed to parse message", e);
263
+ }
264
+ return;
265
+ }
266
+ // Binary data - MP4 segment (mews.js:802-829)
267
+ const bytes = new Uint8Array(data);
268
+ this.sbManager?.append(bytes);
269
+ this.trackBits(data);
270
+ }
271
+ /**
272
+ * Handle JSON control messages.
273
+ * Ported from mews.js:461-799
274
+ */
275
+ handleControlMessage(msg) {
276
+ if (this.debugging && msg.type !== "on_time") {
277
+ console.log("MEWS: message", msg);
278
+ }
279
+ switch (msg.type) {
280
+ case "on_stop":
281
+ this.handleOnStop();
282
+ break;
283
+ case "on_time":
284
+ this.handleOnTime(msg);
285
+ break;
286
+ case "tracks":
287
+ this.handleTracks(msg);
288
+ break;
289
+ case "pause":
290
+ this.handlePause();
291
+ break;
292
+ case "codec_data":
293
+ this.resolveDelay("codec_data");
294
+ break;
295
+ case "seek":
296
+ this.resolveDelay("seek");
297
+ break;
298
+ case "set_speed":
299
+ this.resolveDelay("set_speed");
300
+ break;
301
+ }
302
+ }
303
+ /**
304
+ * Handle on_stop message - stream ended (VoD).
305
+ * Ported from mews.js:462-471
306
+ */
307
+ handleOnStop() {
308
+ // Mark as VoD (stream ended)
309
+ this.streamType = "vod";
310
+ // Wait for buffer to finish playing (mews.js:465-469)
311
+ const onWaiting = () => {
312
+ if (this.sbManager) {
313
+ this.sbManager.paused = true;
314
+ }
315
+ this.emit("ended", undefined);
316
+ this.videoElement?.removeEventListener("waiting", onWaiting);
317
+ };
318
+ this.videoElement?.addEventListener("waiting", onWaiting);
319
+ }
320
+ /**
321
+ * Handle on_time message - playback time sync.
322
+ * Ported from mews.js:473-621
323
+ */
324
+ handleOnTime(msg) {
325
+ const data = msg.data;
326
+ if (!data || !this.videoElement)
327
+ return;
328
+ const currentMs = data.current;
329
+ const endMs = data.end;
330
+ const jitter = data.jitter || 0;
331
+ // Buffer calculation (mews.js:474)
332
+ const buffer = currentMs - this.videoElement.currentTime * 1000;
333
+ const serverDelay = this.getServerDelay();
334
+ // Chrome needs larger base buffer (mews.js:482)
335
+ const isChrome = /Chrome/.test(navigator.userAgent) && !/Edge|Edg/.test(navigator.userAgent);
336
+ const baseBuffer = isChrome ? 1000 : 100;
337
+ const desiredBuffer = Math.max(baseBuffer + serverDelay, serverDelay * 2);
338
+ const desiredBufferWithJitter = desiredBuffer + jitter;
339
+ // VoD gets extra buffer (mews.js:480)
340
+ const actualDesiredBuffer = this.streamType !== "live" ? desiredBuffer + 2000 : desiredBuffer;
341
+ if (this.debugging) {
342
+ console.log("MEWS: on_time", "current:", currentMs / 1000, "video:", this.videoElement.currentTime, "rate:", this.requestedRate + "x", "buffer:", Math.round(buffer), "/", Math.round(desiredBuffer), this.streamType === "live"
343
+ ? "latency:" + Math.round((endMs || 0) - this.videoElement.currentTime * 1000) + "ms"
344
+ : "");
345
+ }
346
+ if (!this.sbManager) {
347
+ if (this.debugging)
348
+ console.log("MEWS: on_time but no sourceBuffer");
349
+ return;
350
+ }
351
+ // Update duration (mews.js:501-504)
352
+ if (endMs !== undefined && this.lastDuration !== endMs / 1000) {
353
+ this.lastDuration = endMs / 1000;
354
+ // Duration is updated via native video element durationchange event
355
+ }
356
+ // Mark source buffer as not paused
357
+ this.sbManager.paused = false;
358
+ // Playback rate tuning for LIVE streams (mews.js:508-545)
359
+ if (this.streamType === "live") {
360
+ this.tuneLivePlaybackRate(buffer, desiredBufferWithJitter, data.play_rate_curr);
361
+ }
362
+ else {
363
+ // VoD - adjust server delivery speed (mews.js:547-586)
364
+ this.tuneVodDeliverySpeed(buffer, actualDesiredBuffer, data.play_rate_curr);
365
+ }
366
+ // Track change detection (mews.js:593-619)
367
+ if (data.tracks && this.currentTracks.join(",") !== data.tracks.join(",")) {
368
+ if (this.debugging) {
369
+ for (const trackId of data.tracks) {
370
+ if (!this.currentTracks.includes(trackId)) {
371
+ console.log("MEWS: track changed", trackId);
372
+ }
373
+ }
374
+ }
375
+ this.currentTracks = data.tracks;
376
+ }
377
+ }
378
+ /**
379
+ * Tune playback rate for live streams.
380
+ * Ported from mews.js:508-545
381
+ *
382
+ * Fixed: Use direct assignment instead of multiplication to prevent
383
+ * compounding rate adjustments on each on_time message.
384
+ */
385
+ tuneLivePlaybackRate(buffer, desiredBuffer, playRateCurr) {
386
+ if (!this.videoElement)
387
+ return;
388
+ if (this.requestedRate === 1) {
389
+ if (playRateCurr === "auto" && this.videoElement.currentTime > 0) {
390
+ // Assume we want to be as live as possible
391
+ if (buffer > desiredBuffer * 2) {
392
+ // Buffer too big, speed up (mews.js:513-516)
393
+ this.requestedRate = 1 + Math.min(1, (buffer - desiredBuffer) / desiredBuffer) * 0.08;
394
+ this.videoElement.playbackRate = this.requestedRate;
395
+ if (this.debugging)
396
+ console.log("MEWS: speeding up to", this.requestedRate);
397
+ }
398
+ else if (buffer < 0) {
399
+ // Negative buffer, slow down (mews.js:518-521)
400
+ this.requestedRate = 0.8;
401
+ this.videoElement.playbackRate = this.requestedRate;
402
+ if (this.debugging)
403
+ console.log("MEWS: slowing down to", this.requestedRate);
404
+ }
405
+ else if (buffer < desiredBuffer / 2) {
406
+ // Buffer too small, slow down (mews.js:523-526)
407
+ this.requestedRate = 1 + Math.min(1, (buffer - desiredBuffer) / desiredBuffer) * 0.08;
408
+ this.videoElement.playbackRate = this.requestedRate;
409
+ if (this.debugging)
410
+ console.log("MEWS: adjusting to", this.requestedRate);
411
+ }
412
+ }
413
+ }
414
+ else if (this.requestedRate > 1) {
415
+ // Return to normal when buffer is small enough (mews.js:531-536)
416
+ if (buffer < desiredBuffer) {
417
+ this.videoElement.playbackRate = 1;
418
+ this.requestedRate = 1;
419
+ if (this.debugging)
420
+ console.log("MEWS: returning to normal rate");
421
+ }
422
+ }
423
+ else {
424
+ // requestedRate < 1, return to normal when buffer is big enough (mews.js:538-544)
425
+ if (buffer > desiredBuffer) {
426
+ this.videoElement.playbackRate = 1;
427
+ this.requestedRate = 1;
428
+ if (this.debugging)
429
+ console.log("MEWS: returning to normal rate");
430
+ }
431
+ }
432
+ }
433
+ /**
434
+ * Tune server delivery speed for VoD.
435
+ * Ported from mews.js:547-586
436
+ */
437
+ tuneVodDeliverySpeed(buffer, desiredBuffer, playRateCurr) {
438
+ if (this.requestedRate === 1) {
439
+ if (playRateCurr === "auto") {
440
+ if (buffer < desiredBuffer / 2) {
441
+ if (buffer < -1e4) {
442
+ // Way behind, seek to current position (mews.js:553-554)
443
+ this.send({
444
+ type: "seek",
445
+ seek_time: Math.round((this.videoElement?.currentTime || 0) * 1000),
446
+ });
447
+ }
448
+ else {
449
+ // Request faster delivery (mews.js:557-560)
450
+ this.requestedRate = 2;
451
+ this.send({ type: "set_speed", play_rate: this.requestedRate });
452
+ if (this.debugging)
453
+ console.log("MEWS: requesting faster delivery");
454
+ }
455
+ }
456
+ else if (buffer - desiredBuffer > desiredBuffer) {
457
+ // Too much buffer, slow down (mews.js:563-566)
458
+ this.requestedRate = 0.5;
459
+ this.send({ type: "set_speed", play_rate: this.requestedRate });
460
+ if (this.debugging)
461
+ console.log("MEWS: requesting slower delivery");
462
+ }
463
+ }
464
+ }
465
+ else if (this.requestedRate > 1) {
466
+ if (buffer > desiredBuffer) {
467
+ // Enough buffer, return to realtime (mews.js:571-575)
468
+ this.send({ type: "set_speed", play_rate: "auto" });
469
+ this.requestedRate = 1;
470
+ if (this.debugging)
471
+ console.log("MEWS: returning to realtime delivery");
472
+ }
473
+ }
474
+ else {
475
+ if (buffer < desiredBuffer) {
476
+ // Buffer small enough, return to realtime (mews.js:579-583)
477
+ this.send({ type: "set_speed", play_rate: "auto" });
478
+ this.requestedRate = 1;
479
+ if (this.debugging)
480
+ console.log("MEWS: returning to realtime delivery");
481
+ }
482
+ }
483
+ }
484
+ /**
485
+ * Handle tracks message - codec switch.
486
+ * Ported from mews.js:623-788
487
+ */
488
+ handleTracks(msg) {
489
+ const codecs = msg.data?.codecs || [];
490
+ const switchPointMs = msg.data?.current;
491
+ if (!codecs.length) {
492
+ this.emit("error", "Track switch contains no codecs");
493
+ return;
494
+ }
495
+ // Check if codecs are same as before (mews.js:676)
496
+ const prevCodecs = this.lastCodecs || this.sbManager?.getCodecs() || [];
497
+ if (this.codecsEqual(prevCodecs, codecs)) {
498
+ if (this.debugging)
499
+ console.log("MEWS: keeping buffer, codecs same");
500
+ // If at position 0 and switch point is not 0, seek to switch point (mews.js:678-679)
501
+ if (this.videoElement?.currentTime === 0 && switchPointMs && switchPointMs !== 0) {
502
+ this.setSeekingPosition(switchPointMs / 1000);
503
+ }
504
+ return;
505
+ }
506
+ // Different codecs, save for next comparison (mews.js:687)
507
+ this.lastCodecs = codecs;
508
+ // Change codecs (will handle msgqueue internally)
509
+ this.sbManager?.changeCodecs(codecs, switchPointMs);
510
+ }
511
+ /**
512
+ * Handle pause message.
513
+ * Ported from mews.js:790-792
514
+ */
515
+ handlePause() {
516
+ if (this.sbManager) {
517
+ this.sbManager.paused = true;
518
+ }
519
+ }
520
+ /**
521
+ * Set video currentTime with retry logic.
522
+ * Ported from mews.js:635-672
523
+ */
524
+ setSeekingPosition(tSec) {
525
+ if (!this.videoElement || !this.sbManager)
526
+ return;
527
+ const currPos = this.videoElement.currentTime;
528
+ if (currPos > tSec) {
529
+ // Don't seek backwards (mews.js:637-639)
530
+ tSec = currPos;
531
+ }
532
+ const buffered = this.videoElement.buffered;
533
+ if (!buffered.length || buffered.end(buffered.length - 1) < tSec) {
534
+ // Desired position not in buffer yet, wait for more data (mews.js:641-644)
535
+ this.sbManager.scheduleAfterUpdate(() => this.setSeekingPosition(tSec));
536
+ return;
537
+ }
538
+ this.videoElement.currentTime = tSec;
539
+ if (this.videoElement.currentTime < tSec - 0.001) {
540
+ // Didn't reach target, retry (mews.js:648-651)
541
+ this.sbManager.scheduleAfterUpdate(() => this.setSeekingPosition(tSec));
542
+ }
543
+ }
544
+ /**
545
+ * Check if two codec arrays are equivalent (order-independent)
546
+ */
547
+ codecsEqual(arr1, arr2) {
548
+ if (arr1.length !== arr2.length)
549
+ return false;
550
+ for (const codec of arr1) {
551
+ if (!arr2.includes(codec))
552
+ return false;
553
+ }
554
+ return true;
555
+ }
556
+ // ========== PUBLIC API ==========
557
+ /**
558
+ * Play with optional skip to live edge.
559
+ * Ported from mews.js:959-1023
560
+ */
561
+ async play() {
562
+ const v = this.videoElement;
563
+ if (!v)
564
+ return;
565
+ // If already playing, nothing to do (mews.js:961-964)
566
+ if (!v.paused)
567
+ return;
568
+ // Wait for ready state (codec_data received) with timeout
569
+ if (!this.isReady) {
570
+ await new Promise((resolve, reject) => {
571
+ const timeout = setTimeout(() => {
572
+ reject(new Error("MEWS: Timeout waiting for codec data"));
573
+ }, 5000);
574
+ this.readyResolvers.push(() => {
575
+ clearTimeout(timeout);
576
+ resolve();
577
+ });
578
+ });
579
+ }
580
+ // Use listener to wait for on_time before playing (mews.js:973-1017)
581
+ return new Promise((resolve, reject) => {
582
+ // Flag to prevent race condition where multiple on_time messages
583
+ // could trigger seek before the first completes
584
+ let handled = false;
585
+ const onTime = (msg) => {
586
+ // Remove listener immediately to prevent race condition (single-use pattern)
587
+ if (handled)
588
+ return;
589
+ handled = true;
590
+ this.wsManager?.removeListener("on_time", onTime);
591
+ if (!this.sbManager) {
592
+ if (this.debugging)
593
+ console.log("MEWS: play waiting for sourceBuffer");
594
+ handled = false; // Allow retry
595
+ this.wsManager?.addListener("on_time", onTime);
596
+ return;
597
+ }
598
+ const data = msg.data;
599
+ if (this.streamType === "live") {
600
+ // Live stream - wait for buffer then seek to live edge (mews.js:978-998)
601
+ const waitForBuffer = () => {
602
+ if (!v.buffered.length)
603
+ return;
604
+ const bufferIdx = this.sbManager?.findBufferIndex(data.current / 1000);
605
+ if (typeof bufferIdx === "number") {
606
+ // Check if current position is in buffer
607
+ if (v.buffered.start(bufferIdx) > v.currentTime ||
608
+ v.buffered.end(bufferIdx) < v.currentTime) {
609
+ v.currentTime = data.current / 1000;
610
+ if (this.debugging)
611
+ console.log("MEWS: seeking to live position", v.currentTime);
612
+ }
613
+ v.play()
614
+ .then(resolve)
615
+ .catch((err) => {
616
+ this.pause();
617
+ reject(err);
618
+ });
619
+ this.sbManager.paused = false;
620
+ }
621
+ };
622
+ // Wait for buffer via updateend
623
+ this.sbManager?.scheduleAfterUpdate(waitForBuffer);
624
+ }
625
+ else {
626
+ // VoD - just play when we have data (mews.js:1010-1016)
627
+ this.sbManager.paused = false;
628
+ if (v.buffered.length && v.buffered.start(0) > v.currentTime) {
629
+ v.currentTime = v.buffered.start(0);
630
+ }
631
+ v.play().then(resolve).catch(reject);
632
+ }
633
+ };
634
+ this.wsManager?.addListener("on_time", onTime);
635
+ // Send play command (mews.js:1020-1022)
636
+ const skipToLive = this.streamType === "live" && v.currentTime === 0;
637
+ if (skipToLive) {
638
+ this.send({ type: "play", seek_time: "live" });
639
+ }
640
+ else {
641
+ this.send({ type: "play" });
642
+ }
643
+ });
644
+ }
645
+ /**
646
+ * Pause playback and server delivery.
647
+ * Ported from mews.js:1025-1029
648
+ */
649
+ pause() {
650
+ this.videoElement?.pause();
651
+ this.send({ type: "hold" });
652
+ if (this.sbManager) {
653
+ this.sbManager.paused = true;
654
+ }
655
+ }
656
+ /**
657
+ * Seek to position with server sync.
658
+ * Ported from mews.js:1071-1111
659
+ */
660
+ seek(time) {
661
+ if (!this.videoElement || isNaN(time) || time < 0)
662
+ return;
663
+ // Calculate seek time with server delay compensation (mews.js:1082)
664
+ const seekMs = Math.round(Math.max(0, time * 1000 - (250 + this.getServerDelay())));
665
+ this.logDelay("seek");
666
+ this.send({ type: "seek", seek_time: seekMs });
667
+ // Wait for seek acknowledgment then on_time (mews.js:1084-1108)
668
+ const onSeek = () => {
669
+ this.wsManager?.removeListener("seek", onSeek);
670
+ const onTime = (msg) => {
671
+ this.wsManager?.removeListener("on_time", onTime);
672
+ // Use server's actual position (mews.js:1089)
673
+ const actualTime = msg.data.current / 1000;
674
+ this.trySetCurrentTime(actualTime);
675
+ };
676
+ this.wsManager?.addListener("on_time", onTime);
677
+ };
678
+ this.wsManager?.addListener("seek", onSeek);
679
+ // Also set directly as fallback
680
+ this.videoElement.currentTime = time;
681
+ if (this.debugging)
682
+ console.log("MEWS: seeking to", time);
683
+ }
684
+ /**
685
+ * Try to set currentTime with retry logic.
686
+ * Ported from mews.js:1092-1103
687
+ */
688
+ trySetCurrentTime(tSec, retries = 10) {
689
+ const v = this.videoElement;
690
+ if (!v)
691
+ return;
692
+ v.currentTime = tSec;
693
+ if (v.currentTime < tSec - 0.001 && retries > 0) {
694
+ // Failed to seek, retry (mews.js:1095-1100)
695
+ this.sbManager?.scheduleAfterUpdate(() => this.trySetCurrentTime(tSec, retries - 1));
696
+ }
697
+ }
698
+ getCurrentTime() {
699
+ return this.videoElement?.currentTime ?? 0;
700
+ }
701
+ getDuration() {
702
+ return isFinite(this.lastDuration) ? this.lastDuration : super.getDuration();
703
+ }
704
+ /**
705
+ * Set playback rate.
706
+ * Ported from mews.js:1119-1129
707
+ */
708
+ setPlaybackRate(rate) {
709
+ super.setPlaybackRate(rate);
710
+ const playRate = rate === 1 ? "auto" : rate;
711
+ this.logDelay("set_speed");
712
+ this.send({ type: "set_speed", play_rate: playRate });
713
+ }
714
+ getQualities() {
715
+ return [{ id: "auto", label: "Auto", isAuto: true, active: true }];
716
+ }
717
+ selectQuality(id) {
718
+ if (id === "auto") {
719
+ this.send({ type: "set_speed", play_rate: "auto" });
720
+ }
721
+ }
722
+ /**
723
+ * Set tracks for ABR or quality selection.
724
+ * Ported from mews.js:1030-1037
725
+ */
726
+ setTracks(obj) {
727
+ if (!Object.keys(obj).length)
728
+ return;
729
+ this.send({ type: "tracks", ...obj });
730
+ }
731
+ /**
732
+ * Select a subtitle track.
733
+ */
734
+ selectTextTrack(id) {
735
+ if (id === null) {
736
+ this.send({ type: "tracks", subtitle: "none" });
737
+ }
738
+ else {
739
+ this.send({ type: "tracks", subtitle: id });
740
+ }
741
+ }
742
+ isLive() {
743
+ return this.streamType === "live";
744
+ }
745
+ /**
746
+ * Jump to live edge.
747
+ */
748
+ jumpToLive() {
749
+ if (this.streamType !== "live" || !this.wsManager)
750
+ return;
751
+ this.send({ type: "play", seek_time: "live" });
752
+ this.videoElement?.play().catch(() => { });
753
+ }
754
+ async getStats() {
755
+ return {
756
+ currentBps: this.currentBps,
757
+ waitingEvents: this.nWaiting,
758
+ isLive: this.streamType === "live",
759
+ serverDelay: this.getServerDelay(),
760
+ };
761
+ }
762
+ // ========== EVENT HANDLERS ==========
763
+ /**
764
+ * Install waiting event handler.
765
+ * Handles buffer gaps and ABR.
766
+ * Ported from mews.js:1177-1186, 1272-1278
767
+ */
768
+ installWaitingHandler() {
769
+ if (!this.videoElement)
770
+ return;
771
+ this.videoElement.addEventListener("waiting", () => {
772
+ if (this.seeking)
773
+ return;
774
+ const v = this.videoElement;
775
+ if (!v.buffered || !v.buffered.length)
776
+ return;
777
+ // Check for buffer gap and jump it (mews.js:1180-1186)
778
+ const bufferIdx = this.sbManager?.findBufferIndex(v.currentTime);
779
+ if (bufferIdx !== false && typeof bufferIdx === "number") {
780
+ if (bufferIdx + 1 < v.buffered.length) {
781
+ const nextStart = v.buffered.start(bufferIdx + 1);
782
+ if (nextStart - v.currentTime < 10) {
783
+ if (this.debugging)
784
+ console.log("MEWS: skipping buffer gap to", nextStart);
785
+ v.currentTime = nextStart;
786
+ }
787
+ }
788
+ }
789
+ // ABR trigger (mews.js:1272-1278)
790
+ this.nWaiting++;
791
+ if (this.nWaiting >= this.nWaitingThreshold && this.currentBps) {
792
+ this.nWaiting = 0;
793
+ if (this.debugging)
794
+ console.log("MEWS: ABR triggered, requesting lower bitrate");
795
+ this.setTracks({ video: `<${Math.round(this.currentBps)}bps,minbps` });
796
+ }
797
+ });
798
+ }
799
+ /**
800
+ * Install seeking event handlers.
801
+ * Ported from mews.js:1169-1175
802
+ */
803
+ installSeekingHandler() {
804
+ if (!this.videoElement)
805
+ return;
806
+ this.videoElement.addEventListener("seeking", () => {
807
+ this.seeking = true;
808
+ });
809
+ this.videoElement.addEventListener("seeked", () => {
810
+ this.seeking = false;
811
+ });
812
+ }
813
+ /**
814
+ * Install pause event handler for browser pause detection.
815
+ * Ported from mews.js:1188-1200
816
+ */
817
+ installPauseHandler() {
818
+ if (!this.videoElement)
819
+ return;
820
+ this.videoElement.addEventListener("pause", () => {
821
+ if (this.sbManager && !this.sbManager.paused) {
822
+ // Browser paused (probably tab hidden) - pause download (mews.js:1189-1192)
823
+ if (this.debugging)
824
+ console.log("MEWS: browser paused, pausing download");
825
+ this.send({ type: "hold" });
826
+ this.sbManager.paused = true;
827
+ // Resume on play (mews.js:1193-1197)
828
+ const onPlay = () => {
829
+ if (this.sbManager?.paused) {
830
+ this.send({ type: "play" });
831
+ }
832
+ this.videoElement?.removeEventListener("play", onPlay);
833
+ };
834
+ this.videoElement?.addEventListener("play", onPlay);
835
+ }
836
+ });
837
+ }
838
+ /**
839
+ * Install loop handler for VoD content.
840
+ * Ported from mews.js:1157-1167
841
+ */
842
+ installLoopHandler() {
843
+ if (!this.videoElement)
844
+ return;
845
+ this.videoElement.addEventListener("ended", () => {
846
+ const v = this.videoElement;
847
+ if (!v)
848
+ return;
849
+ if (v.loop && this.streamType !== "live") {
850
+ // Loop VoD content (mews.js:1159-1166)
851
+ this.seek(0);
852
+ this.sbManager?._do(() => {
853
+ });
854
+ }
855
+ });
856
+ }
857
+ // ========== UTILITIES ==========
858
+ /**
859
+ * Send command to server with retry.
860
+ * Ported from mews.js:904-944
861
+ */
862
+ send(cmd) {
863
+ if (this.wsManager) {
864
+ this.wsManager.send(cmd);
865
+ }
866
+ }
867
+ /**
868
+ * Log delay for server RTT estimation.
869
+ * Ported from mews.js:835-862
870
+ */
871
+ logDelay(type) {
872
+ this.pendingDelayTypes[type] = Date.now();
873
+ }
874
+ /**
875
+ * Resolve delay measurement.
876
+ * Ported from mews.js:855-861, 863-867
877
+ */
878
+ resolveDelay(type) {
879
+ const start = this.pendingDelayTypes[type];
880
+ if (start) {
881
+ const delay = Date.now() - start;
882
+ this.serverDelays.unshift(delay);
883
+ if (this.serverDelays.length > 5) {
884
+ this.serverDelays.pop();
885
+ }
886
+ delete this.pendingDelayTypes[type];
887
+ }
888
+ }
889
+ /**
890
+ * Get average server delay.
891
+ * Ported from mews.js:869-881
892
+ */
893
+ getServerDelay() {
894
+ if (!this.serverDelays.length)
895
+ return 500;
896
+ const n = Math.min(3, this.serverDelays.length);
897
+ let sum = 0;
898
+ for (let i = 0; i < n; i++) {
899
+ sum += this.serverDelays[i];
900
+ }
901
+ return sum / n;
902
+ }
903
+ /**
904
+ * Track bandwidth for ABR.
905
+ * Ported from mews.js:1280-1303
906
+ */
907
+ trackBits(buf) {
908
+ this.bitCounter.push(buf.byteLength * 8);
909
+ this.bitsSince.push(Date.now());
910
+ // Keep window size of 5 samples
911
+ if (this.bitCounter.length > 5) {
912
+ this.bitCounter.shift();
913
+ this.bitsSince.shift();
914
+ }
915
+ // Calculate current bitrate
916
+ if (this.bitCounter.length >= 2) {
917
+ const bits = this.bitCounter[this.bitCounter.length - 1];
918
+ const dt = (this.bitsSince[this.bitsSince.length - 1] - this.bitsSince[0]) / 1000;
919
+ if (dt > 0) {
920
+ this.currentBps = Math.round(bits / dt);
921
+ }
922
+ }
923
+ }
924
+ startTelemetry() {
925
+ if (!this.analyticsConfig.enabled || !this.analyticsConfig.endpoint)
926
+ return;
927
+ const endpoint = this.analyticsConfig.endpoint;
928
+ this.analyticsTimer = setInterval(async () => {
929
+ if (!this.videoElement)
930
+ return;
931
+ const stats = await this.getStats();
932
+ const payload = {
933
+ t: Date.now(),
934
+ bps: stats.currentBps || 0,
935
+ waiting: stats.waitingEvents || 0,
936
+ };
937
+ try {
938
+ await fetch(endpoint, {
939
+ method: "POST",
940
+ headers: { "Content-Type": "application/json" },
941
+ body: JSON.stringify(payload),
942
+ });
943
+ }
944
+ catch { }
945
+ }, 5000);
946
+ }
947
+ async destroy() {
948
+ console.debug("[MEWS] destroy() called");
949
+ this.isDestroyed = true;
950
+ this.isReady = false;
951
+ this.readyResolvers = [];
952
+ if (this.analyticsTimer) {
953
+ clearInterval(this.analyticsTimer);
954
+ this.analyticsTimer = null;
955
+ }
956
+ // CRITICAL: Close WebSocket FIRST to stop all network activity immediately
957
+ // Don't send 'stop' message - it can trigger retry logic and delay cleanup
958
+ this.wsManager?.destroy();
959
+ this.wsManager = null;
960
+ this.sbManager?.destroy();
961
+ this.sbManager = null;
962
+ if (this.mediaSource?.readyState === "open") {
963
+ try {
964
+ this.mediaSource.endOfStream();
965
+ }
966
+ catch { }
967
+ }
968
+ if (this.objectUrl) {
969
+ URL.revokeObjectURL(this.objectUrl);
970
+ this.objectUrl = null;
971
+ }
972
+ if (this.videoElement && this.container) {
973
+ try {
974
+ this.container.removeChild(this.videoElement);
975
+ }
976
+ catch { }
977
+ }
978
+ this.videoElement = null;
979
+ this.container = null;
980
+ this.mediaSource = null;
981
+ this.listeners.clear();
982
+ console.debug("[MEWS] destroy() completed");
983
+ }
984
+ }
985
+
986
+ exports.MewsWsPlayerImpl = MewsWsPlayerImpl;
987
+ //# sourceMappingURL=index.js.map