@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,379 @@
1
+ import { TypedEventEmitter } from './EventEmitter.js';
2
+ import { TimerManager } from './TimerManager.js';
3
+
4
+ /**
5
+ * StreamStateClient.ts
6
+ *
7
+ * Framework-agnostic client for polling MistServer stream status via WebSocket or HTTP.
8
+ * Extracted from useStreamState.ts for use in headless core.
9
+ */
10
+ // ============================================================================
11
+ // Constants
12
+ // ============================================================================
13
+ const DEFAULT_POLL_INTERVAL = 3000;
14
+ const initialState = {
15
+ status: "OFFLINE",
16
+ isOnline: false,
17
+ message: "Connecting...",
18
+ lastUpdate: 0,
19
+ };
20
+ // ============================================================================
21
+ // Helper Functions
22
+ // ============================================================================
23
+ /**
24
+ * Parse MistServer error string into StreamStatus enum.
25
+ */
26
+ function parseErrorToStatus(error) {
27
+ const lowerError = error.toLowerCase();
28
+ if (lowerError.includes("offline"))
29
+ return "OFFLINE";
30
+ if (lowerError.includes("initializing"))
31
+ return "INITIALIZING";
32
+ if (lowerError.includes("booting"))
33
+ return "BOOTING";
34
+ if (lowerError.includes("waiting for data"))
35
+ return "WAITING_FOR_DATA";
36
+ if (lowerError.includes("shutting down"))
37
+ return "SHUTTING_DOWN";
38
+ if (lowerError.includes("invalid"))
39
+ return "INVALID";
40
+ return "ERROR";
41
+ }
42
+ /**
43
+ * Get human-readable message for stream status.
44
+ */
45
+ function getStatusMessage(status, percentage) {
46
+ switch (status) {
47
+ case "ONLINE":
48
+ return "Stream is online";
49
+ case "OFFLINE":
50
+ return "Stream is offline";
51
+ case "INITIALIZING":
52
+ return percentage !== undefined
53
+ ? `Initializing... ${Math.round(percentage * 10) / 10}%`
54
+ : "Stream is initializing";
55
+ case "BOOTING":
56
+ return "Stream is starting up";
57
+ case "WAITING_FOR_DATA":
58
+ return "Waiting for stream data";
59
+ case "SHUTTING_DOWN":
60
+ return "Stream is shutting down";
61
+ case "INVALID":
62
+ return "Stream status is invalid";
63
+ case "ERROR":
64
+ default:
65
+ return "Stream error";
66
+ }
67
+ }
68
+ // ============================================================================
69
+ // StreamStateClient Class
70
+ // ============================================================================
71
+ /**
72
+ * Client for polling MistServer stream status via WebSocket or HTTP.
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * const client = new StreamStateClient({
77
+ * mistBaseUrl: 'https://mist.example.com',
78
+ * streamName: 'pk_...', // playbackId (view key)
79
+ * });
80
+ *
81
+ * client.on('stateChange', ({ state }) => console.log('State:', state));
82
+ * client.on('online', () => console.log('Stream is online!'));
83
+ * client.on('offline', () => console.log('Stream is offline'));
84
+ *
85
+ * client.start();
86
+ * // ...later
87
+ * client.stop();
88
+ * ```
89
+ */
90
+ class StreamStateClient extends TypedEventEmitter {
91
+ constructor(config) {
92
+ super();
93
+ this.state = { ...initialState };
94
+ this.ws = null;
95
+ this.timers = new TimerManager();
96
+ this.isRunning = false;
97
+ this.wasOnline = false;
98
+ this.connectionId = 0; // Track connection attempts to prevent stale callbacks
99
+ this.config = {
100
+ pollInterval: DEFAULT_POLL_INTERVAL,
101
+ useWebSocket: true,
102
+ ...config,
103
+ };
104
+ }
105
+ /**
106
+ * Start polling/WebSocket connection.
107
+ * Always does initial HTTP poll to get full stream info (including sources),
108
+ * then connects WebSocket for real-time status updates.
109
+ *
110
+ * Debounced to prevent orphaned connections during rapid mount/unmount cycles.
111
+ */
112
+ start() {
113
+ if (this.isRunning)
114
+ return;
115
+ this.isRunning = true;
116
+ const { mistBaseUrl, streamName, useWebSocket } = this.config;
117
+ if (!mistBaseUrl || !streamName) {
118
+ console.warn("[StreamStateClient] Missing mistBaseUrl or streamName");
119
+ return;
120
+ }
121
+ // Reset state
122
+ this.setState({
123
+ ...initialState,
124
+ message: "Connecting...",
125
+ lastUpdate: Date.now(),
126
+ });
127
+ // Increment connection ID to invalidate any pending callbacks from previous attempts
128
+ const currentConnectionId = ++this.connectionId;
129
+ // Debounce connection to prevent rapid reconnects during mount/unmount cycles
130
+ this.timers.start(() => {
131
+ // Check if this connection attempt is still valid
132
+ if (!this.isRunning || this.connectionId !== currentConnectionId) {
133
+ return;
134
+ }
135
+ // Always do initial HTTP poll to get full data (including sources)
136
+ // Then connect WebSocket for real-time updates
137
+ this.pollHttp().then(() => {
138
+ // Verify still valid before WebSocket connection
139
+ if (useWebSocket && this.isRunning && this.connectionId === currentConnectionId) {
140
+ this.connectWebSocket();
141
+ }
142
+ });
143
+ }, StreamStateClient.CONNECTION_DEBOUNCE_MS, "connect");
144
+ }
145
+ /**
146
+ * Stop polling and close connections.
147
+ */
148
+ stop() {
149
+ this.isRunning = false;
150
+ // Close WebSocket
151
+ if (this.ws) {
152
+ this.ws.close();
153
+ this.ws = null;
154
+ }
155
+ // Clear all timers
156
+ this.timers.destroy();
157
+ }
158
+ /**
159
+ * Manual refresh - trigger an immediate poll.
160
+ */
161
+ refresh() {
162
+ if (this.config.useWebSocket && this.ws?.readyState === WebSocket.OPEN) {
163
+ // WebSocket will receive updates automatically
164
+ return;
165
+ }
166
+ this.pollHttp();
167
+ }
168
+ /**
169
+ * Get the underlying WebSocket connection (for MistReporter integration).
170
+ * Returns null if WebSocket is not connected.
171
+ */
172
+ getSocket() {
173
+ return this.ws;
174
+ }
175
+ /**
176
+ * Check if the WebSocket is connected and ready.
177
+ */
178
+ isSocketReady() {
179
+ return this.ws?.readyState === WebSocket.OPEN;
180
+ }
181
+ /**
182
+ * Get current stream state.
183
+ */
184
+ getState() {
185
+ return { ...this.state };
186
+ }
187
+ /**
188
+ * Check if stream is online.
189
+ */
190
+ isOnline() {
191
+ return this.state.isOnline;
192
+ }
193
+ /**
194
+ * Update configuration (stops and restarts if running).
195
+ */
196
+ updateConfig(config) {
197
+ const wasRunning = this.isRunning;
198
+ this.stop();
199
+ this.config = { ...this.config, ...config };
200
+ if (wasRunning) {
201
+ this.start();
202
+ }
203
+ }
204
+ /**
205
+ * Clean up resources.
206
+ */
207
+ destroy() {
208
+ this.stop();
209
+ this.removeAllListeners();
210
+ }
211
+ // ============================================================================
212
+ // Private Methods
213
+ // ============================================================================
214
+ connectWebSocket() {
215
+ if (!this.isRunning)
216
+ return;
217
+ const { mistBaseUrl, streamName } = this.config;
218
+ // Clean up existing connection
219
+ if (this.ws) {
220
+ this.ws.close();
221
+ this.ws = null;
222
+ }
223
+ try {
224
+ // Convert http(s) to ws(s)
225
+ const wsUrl = mistBaseUrl
226
+ .replace(/^http:/, "ws:")
227
+ .replace(/^https:/, "wss:")
228
+ .replace(/\/$/, "");
229
+ const ws = new WebSocket(`${wsUrl}/json_${encodeURIComponent(streamName)}.js?metaeverywhere=1&inclzero=1`);
230
+ this.ws = ws;
231
+ ws.onopen = () => {
232
+ console.debug("[StreamStateClient] WebSocket connected");
233
+ };
234
+ ws.onmessage = (event) => {
235
+ try {
236
+ const data = JSON.parse(event.data);
237
+ this.processStreamInfo(data);
238
+ }
239
+ catch (e) {
240
+ console.warn("[StreamStateClient] Failed to parse WebSocket message:", e);
241
+ }
242
+ };
243
+ ws.onerror = () => {
244
+ console.warn("[StreamStateClient] WebSocket error, falling back to HTTP polling");
245
+ ws.close();
246
+ };
247
+ ws.onclose = () => {
248
+ this.ws = null;
249
+ if (!this.isRunning)
250
+ return;
251
+ // Disable WebSocket and switch to HTTP polling
252
+ // This ensures pollHttp() schedules repeat polls (see line 365 condition)
253
+ this.config.useWebSocket = false;
254
+ console.debug("[StreamStateClient] WebSocket closed, switching to HTTP polling");
255
+ this.pollHttp();
256
+ };
257
+ }
258
+ catch (error) {
259
+ console.warn("[StreamStateClient] WebSocket connection failed:", error);
260
+ // Disable WebSocket and switch to HTTP polling
261
+ this.config.useWebSocket = false;
262
+ this.pollHttp();
263
+ }
264
+ }
265
+ async pollHttp() {
266
+ if (!this.isRunning)
267
+ return;
268
+ const { mistBaseUrl, streamName, pollInterval } = this.config;
269
+ try {
270
+ const url = `${mistBaseUrl.replace(/\/$/, "")}/json_${encodeURIComponent(streamName)}.js?metaeverywhere=1&inclzero=1`;
271
+ const response = await fetch(url, {
272
+ method: "GET",
273
+ headers: { Accept: "application/json" },
274
+ });
275
+ if (!response.ok) {
276
+ throw new Error(`HTTP ${response.status}`);
277
+ }
278
+ // MistServer returns JSON with potential JSONP wrapper
279
+ let text = await response.text();
280
+ // Strip JSONP callback if present
281
+ const jsonpMatch = text.match(/^[^(]+\(([\s\S]*)\);?$/);
282
+ if (jsonpMatch) {
283
+ text = jsonpMatch[1];
284
+ }
285
+ const data = JSON.parse(text);
286
+ this.processStreamInfo(data);
287
+ }
288
+ catch (error) {
289
+ if (!this.isRunning)
290
+ return;
291
+ const errorMessage = error instanceof Error ? error.message : "Connection failed";
292
+ this.setState({
293
+ ...this.state,
294
+ status: "ERROR",
295
+ isOnline: false,
296
+ message: errorMessage,
297
+ lastUpdate: Date.now(),
298
+ error: errorMessage,
299
+ });
300
+ this.emit("error", { error: errorMessage });
301
+ }
302
+ // Schedule next poll
303
+ if (this.isRunning && !this.config.useWebSocket) {
304
+ this.timers.start(() => this.pollHttp(), pollInterval, "poll");
305
+ }
306
+ }
307
+ processStreamInfo(data) {
308
+ if (!this.isRunning)
309
+ return;
310
+ let newState;
311
+ if (data.error) {
312
+ // Stream has an error state - preserve existing streamInfo
313
+ const status = parseErrorToStatus(data.error);
314
+ const message = data.on_error || getStatusMessage(status, data.perc);
315
+ newState = {
316
+ status,
317
+ isOnline: false,
318
+ message,
319
+ percentage: data.perc,
320
+ lastUpdate: Date.now(),
321
+ error: data.error,
322
+ streamInfo: this.state.streamInfo, // Preserve existing source/tracks
323
+ };
324
+ }
325
+ else {
326
+ // Stream is online with valid metadata
327
+ // Merge new data with existing streamInfo to preserve source/tracks from initial fetch
328
+ // WebSocket updates may not include source array
329
+ const mergedStreamInfo = {
330
+ ...this.state.streamInfo, // Keep existing source/meta if present
331
+ ...data, // Override with new data
332
+ // Explicitly preserve source if not in new data
333
+ source: data.source || this.state.streamInfo?.source,
334
+ // Merge meta to preserve tracks
335
+ meta: {
336
+ ...this.state.streamInfo?.meta,
337
+ ...data.meta,
338
+ // Preserve tracks if not in new data
339
+ tracks: data.meta?.tracks || this.state.streamInfo?.meta?.tracks,
340
+ },
341
+ };
342
+ newState = {
343
+ status: "ONLINE",
344
+ isOnline: true,
345
+ message: "Stream is online",
346
+ lastUpdate: Date.now(),
347
+ streamInfo: mergedStreamInfo,
348
+ };
349
+ }
350
+ this.setState(newState);
351
+ // Emit online/offline events on state transitions
352
+ if (newState.isOnline && !this.wasOnline) {
353
+ this.emit("online", undefined);
354
+ }
355
+ else if (!newState.isOnline && this.wasOnline) {
356
+ this.emit("offline", undefined);
357
+ }
358
+ this.wasOnline = newState.isOnline;
359
+ }
360
+ setState(state) {
361
+ const prevState = this.state;
362
+ this.state = state;
363
+ // Emit if ANY state field changed - including streamInfo (track data)
364
+ // Previously only checked status/isOnline/message, causing track updates to be lost
365
+ const hasChanged = prevState.status !== state.status ||
366
+ prevState.isOnline !== state.isOnline ||
367
+ prevState.message !== state.message ||
368
+ prevState.streamInfo !== state.streamInfo ||
369
+ prevState.lastUpdate !== state.lastUpdate;
370
+ if (hasChanged) {
371
+ this.emit("stateChange", { state });
372
+ }
373
+ }
374
+ }
375
+ // Debounce time for rapid mount/unmount cycles (ms)
376
+ StreamStateClient.CONNECTION_DEBOUNCE_MS = 100;
377
+
378
+ export { StreamStateClient };
379
+ //# sourceMappingURL=StreamStateClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StreamStateClient.js","sources":["../../../../src/core/StreamStateClient.ts"],"sourcesContent":["/**\n * StreamStateClient.ts\n *\n * Framework-agnostic client for polling MistServer stream status via WebSocket or HTTP.\n * Extracted from useStreamState.ts for use in headless core.\n */\n\nimport { TypedEventEmitter } from \"./EventEmitter\";\nimport { TimerManager } from \"./TimerManager\";\nimport type { StreamState, StreamStatus, MistStreamInfo } from \"../types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface StreamStateClientConfig {\n /** MistServer base URL (e.g., https://mist.example.com) */\n mistBaseUrl: string;\n /** Stream name to poll */\n streamName: string;\n /** Poll interval in ms for HTTP fallback (default: 3000) */\n pollInterval?: number;\n /** Use WebSocket if available (default: true) */\n useWebSocket?: boolean;\n}\n\ntype StreamStateClientResolvedConfig = Omit<\n StreamStateClientConfig,\n \"pollInterval\" | \"useWebSocket\"\n> & {\n pollInterval: number;\n useWebSocket: boolean;\n};\n\nexport interface StreamStateClientEvents {\n /** Emitted when stream state changes */\n stateChange: { state: StreamState };\n /** Emitted when stream comes online */\n online: void;\n /** Emitted when stream goes offline */\n offline: void;\n /** Emitted on connection error */\n error: { error: string };\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DEFAULT_POLL_INTERVAL = 3000;\n\nconst initialState: StreamState = {\n status: \"OFFLINE\",\n isOnline: false,\n message: \"Connecting...\",\n lastUpdate: 0,\n};\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Parse MistServer error string into StreamStatus enum.\n */\nfunction parseErrorToStatus(error: string): StreamStatus {\n const lowerError = error.toLowerCase();\n\n if (lowerError.includes(\"offline\")) return \"OFFLINE\";\n if (lowerError.includes(\"initializing\")) return \"INITIALIZING\";\n if (lowerError.includes(\"booting\")) return \"BOOTING\";\n if (lowerError.includes(\"waiting for data\")) return \"WAITING_FOR_DATA\";\n if (lowerError.includes(\"shutting down\")) return \"SHUTTING_DOWN\";\n if (lowerError.includes(\"invalid\")) return \"INVALID\";\n\n return \"ERROR\";\n}\n\n/**\n * Get human-readable message for stream status.\n */\nfunction getStatusMessage(status: StreamStatus, percentage?: number): string {\n switch (status) {\n case \"ONLINE\":\n return \"Stream is online\";\n case \"OFFLINE\":\n return \"Stream is offline\";\n case \"INITIALIZING\":\n return percentage !== undefined\n ? `Initializing... ${Math.round(percentage * 10) / 10}%`\n : \"Stream is initializing\";\n case \"BOOTING\":\n return \"Stream is starting up\";\n case \"WAITING_FOR_DATA\":\n return \"Waiting for stream data\";\n case \"SHUTTING_DOWN\":\n return \"Stream is shutting down\";\n case \"INVALID\":\n return \"Stream status is invalid\";\n case \"ERROR\":\n default:\n return \"Stream error\";\n }\n}\n\n// ============================================================================\n// StreamStateClient Class\n// ============================================================================\n\n/**\n * Client for polling MistServer stream status via WebSocket or HTTP.\n *\n * @example\n * ```typescript\n * const client = new StreamStateClient({\n * mistBaseUrl: 'https://mist.example.com',\n * streamName: 'pk_...', // playbackId (view key)\n * });\n *\n * client.on('stateChange', ({ state }) => console.log('State:', state));\n * client.on('online', () => console.log('Stream is online!'));\n * client.on('offline', () => console.log('Stream is offline'));\n *\n * client.start();\n * // ...later\n * client.stop();\n * ```\n */\nexport class StreamStateClient extends TypedEventEmitter<StreamStateClientEvents> {\n private config: StreamStateClientResolvedConfig;\n private state: StreamState = { ...initialState };\n private ws: WebSocket | null = null;\n private timers = new TimerManager();\n private isRunning: boolean = false;\n private wasOnline: boolean = false;\n private connectionId: number = 0; // Track connection attempts to prevent stale callbacks\n\n // Debounce time for rapid mount/unmount cycles (ms)\n private static readonly CONNECTION_DEBOUNCE_MS = 100;\n\n constructor(config: StreamStateClientConfig) {\n super();\n this.config = {\n pollInterval: DEFAULT_POLL_INTERVAL,\n useWebSocket: true,\n ...config,\n };\n }\n\n /**\n * Start polling/WebSocket connection.\n * Always does initial HTTP poll to get full stream info (including sources),\n * then connects WebSocket for real-time status updates.\n *\n * Debounced to prevent orphaned connections during rapid mount/unmount cycles.\n */\n start(): void {\n if (this.isRunning) return;\n this.isRunning = true;\n\n const { mistBaseUrl, streamName, useWebSocket } = this.config;\n\n if (!mistBaseUrl || !streamName) {\n console.warn(\"[StreamStateClient] Missing mistBaseUrl or streamName\");\n return;\n }\n\n // Reset state\n this.setState({\n ...initialState,\n message: \"Connecting...\",\n lastUpdate: Date.now(),\n });\n\n // Increment connection ID to invalidate any pending callbacks from previous attempts\n const currentConnectionId = ++this.connectionId;\n\n // Debounce connection to prevent rapid reconnects during mount/unmount cycles\n this.timers.start(\n () => {\n // Check if this connection attempt is still valid\n if (!this.isRunning || this.connectionId !== currentConnectionId) {\n return;\n }\n\n // Always do initial HTTP poll to get full data (including sources)\n // Then connect WebSocket for real-time updates\n this.pollHttp().then(() => {\n // Verify still valid before WebSocket connection\n if (useWebSocket && this.isRunning && this.connectionId === currentConnectionId) {\n this.connectWebSocket();\n }\n });\n },\n StreamStateClient.CONNECTION_DEBOUNCE_MS,\n \"connect\"\n );\n }\n\n /**\n * Stop polling and close connections.\n */\n stop(): void {\n this.isRunning = false;\n\n // Close WebSocket\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n\n // Clear all timers\n this.timers.destroy();\n }\n\n /**\n * Manual refresh - trigger an immediate poll.\n */\n refresh(): void {\n if (this.config.useWebSocket && this.ws?.readyState === WebSocket.OPEN) {\n // WebSocket will receive updates automatically\n return;\n }\n this.pollHttp();\n }\n\n /**\n * Get the underlying WebSocket connection (for MistReporter integration).\n * Returns null if WebSocket is not connected.\n */\n getSocket(): WebSocket | null {\n return this.ws;\n }\n\n /**\n * Check if the WebSocket is connected and ready.\n */\n isSocketReady(): boolean {\n return this.ws?.readyState === WebSocket.OPEN;\n }\n\n /**\n * Get current stream state.\n */\n getState(): StreamState {\n return { ...this.state };\n }\n\n /**\n * Check if stream is online.\n */\n isOnline(): boolean {\n return this.state.isOnline;\n }\n\n /**\n * Update configuration (stops and restarts if running).\n */\n updateConfig(config: Partial<StreamStateClientConfig>): void {\n const wasRunning = this.isRunning;\n this.stop();\n this.config = { ...this.config, ...config };\n if (wasRunning) {\n this.start();\n }\n }\n\n /**\n * Clean up resources.\n */\n destroy(): void {\n this.stop();\n this.removeAllListeners();\n }\n\n // ============================================================================\n // Private Methods\n // ============================================================================\n\n private connectWebSocket(): void {\n if (!this.isRunning) return;\n\n const { mistBaseUrl, streamName } = this.config;\n\n // Clean up existing connection\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n\n try {\n // Convert http(s) to ws(s)\n const wsUrl = mistBaseUrl\n .replace(/^http:/, \"ws:\")\n .replace(/^https:/, \"wss:\")\n .replace(/\\/$/, \"\");\n\n const ws = new WebSocket(\n `${wsUrl}/json_${encodeURIComponent(streamName)}.js?metaeverywhere=1&inclzero=1`\n );\n this.ws = ws;\n\n ws.onopen = () => {\n console.debug(\"[StreamStateClient] WebSocket connected\");\n };\n\n ws.onmessage = (event) => {\n try {\n const data = JSON.parse(event.data) as MistStreamInfo;\n this.processStreamInfo(data);\n } catch (e) {\n console.warn(\"[StreamStateClient] Failed to parse WebSocket message:\", e);\n }\n };\n\n ws.onerror = () => {\n console.warn(\"[StreamStateClient] WebSocket error, falling back to HTTP polling\");\n ws.close();\n };\n\n ws.onclose = () => {\n this.ws = null;\n\n if (!this.isRunning) return;\n\n // Disable WebSocket and switch to HTTP polling\n // This ensures pollHttp() schedules repeat polls (see line 365 condition)\n this.config.useWebSocket = false;\n console.debug(\"[StreamStateClient] WebSocket closed, switching to HTTP polling\");\n this.pollHttp();\n };\n } catch (error) {\n console.warn(\"[StreamStateClient] WebSocket connection failed:\", error);\n // Disable WebSocket and switch to HTTP polling\n this.config.useWebSocket = false;\n this.pollHttp();\n }\n }\n\n private async pollHttp(): Promise<void> {\n if (!this.isRunning) return;\n\n const { mistBaseUrl, streamName, pollInterval } = this.config;\n\n try {\n const url = `${mistBaseUrl.replace(/\\/$/, \"\")}/json_${encodeURIComponent(streamName)}.js?metaeverywhere=1&inclzero=1`;\n const response = await fetch(url, {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n // MistServer returns JSON with potential JSONP wrapper\n let text = await response.text();\n // Strip JSONP callback if present\n const jsonpMatch = text.match(/^[^(]+\\(([\\s\\S]*)\\);?$/);\n if (jsonpMatch) {\n text = jsonpMatch[1];\n }\n\n const data = JSON.parse(text) as MistStreamInfo;\n this.processStreamInfo(data);\n } catch (error) {\n if (!this.isRunning) return;\n\n const errorMessage = error instanceof Error ? error.message : \"Connection failed\";\n this.setState({\n ...this.state,\n status: \"ERROR\",\n isOnline: false,\n message: errorMessage,\n lastUpdate: Date.now(),\n error: errorMessage,\n });\n this.emit(\"error\", { error: errorMessage });\n }\n\n // Schedule next poll\n if (this.isRunning && !this.config.useWebSocket) {\n this.timers.start(() => this.pollHttp(), pollInterval, \"poll\");\n }\n }\n\n private processStreamInfo(data: MistStreamInfo): void {\n if (!this.isRunning) return;\n\n let newState: StreamState;\n\n if (data.error) {\n // Stream has an error state - preserve existing streamInfo\n const status = parseErrorToStatus(data.error);\n const message = data.on_error || getStatusMessage(status, data.perc);\n\n newState = {\n status,\n isOnline: false,\n message,\n percentage: data.perc,\n lastUpdate: Date.now(),\n error: data.error,\n streamInfo: this.state.streamInfo, // Preserve existing source/tracks\n };\n } else {\n // Stream is online with valid metadata\n // Merge new data with existing streamInfo to preserve source/tracks from initial fetch\n // WebSocket updates may not include source array\n const mergedStreamInfo: MistStreamInfo = {\n ...this.state.streamInfo, // Keep existing source/meta if present\n ...data, // Override with new data\n // Explicitly preserve source if not in new data\n source: data.source || this.state.streamInfo?.source,\n // Merge meta to preserve tracks\n meta: {\n ...this.state.streamInfo?.meta,\n ...data.meta,\n // Preserve tracks if not in new data\n tracks: data.meta?.tracks || this.state.streamInfo?.meta?.tracks,\n },\n };\n\n newState = {\n status: \"ONLINE\",\n isOnline: true,\n message: \"Stream is online\",\n lastUpdate: Date.now(),\n streamInfo: mergedStreamInfo,\n };\n }\n\n this.setState(newState);\n\n // Emit online/offline events on state transitions\n if (newState.isOnline && !this.wasOnline) {\n this.emit(\"online\", undefined as never);\n } else if (!newState.isOnline && this.wasOnline) {\n this.emit(\"offline\", undefined as never);\n }\n this.wasOnline = newState.isOnline;\n }\n\n private setState(state: StreamState): void {\n const prevState = this.state;\n this.state = state;\n\n // Emit if ANY state field changed - including streamInfo (track data)\n // Previously only checked status/isOnline/message, causing track updates to be lost\n const hasChanged =\n prevState.status !== state.status ||\n prevState.isOnline !== state.isOnline ||\n prevState.message !== state.message ||\n prevState.streamInfo !== state.streamInfo ||\n prevState.lastUpdate !== state.lastUpdate;\n\n if (hasChanged) {\n this.emit(\"stateChange\", { state });\n }\n }\n}\n\nexport default StreamStateClient;\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,225 @@
1
+ /**
2
+ * SubtitleManager - WebVTT subtitle track management
3
+ *
4
+ * Based on MistMetaPlayer's subtitle handling (wrappers/html5.js, webrtc.js).
5
+ * Manages text tracks on video elements with support for:
6
+ * - Loading WebVTT from MistServer URLs
7
+ * - Multiple subtitle track selection
8
+ * - Sync correction for WebRTC seek offsets
9
+ */
10
+ /**
11
+ * SubtitleManager handles text track lifecycle on a video element
12
+ */
13
+ class SubtitleManager {
14
+ constructor(config = {}) {
15
+ this.video = null;
16
+ this.currentTrackId = null;
17
+ this.seekOffset = 0;
18
+ this.listeners = [];
19
+ this.config = config;
20
+ this.debug = config.debug ?? false;
21
+ }
22
+ /**
23
+ * Attach to a video element
24
+ */
25
+ attach(video) {
26
+ this.detach();
27
+ this.video = video;
28
+ // Listen for events that may require sync correction
29
+ const onLoadedData = () => this.correctSubtitleSync();
30
+ const onSeeked = () => this.correctSubtitleSync();
31
+ video.addEventListener("loadeddata", onLoadedData);
32
+ video.addEventListener("seeked", onSeeked);
33
+ this.listeners = [
34
+ () => video.removeEventListener("loadeddata", onLoadedData),
35
+ () => video.removeEventListener("seeked", onSeeked),
36
+ ];
37
+ }
38
+ /**
39
+ * Detach from video element
40
+ */
41
+ detach() {
42
+ this.listeners.forEach((fn) => fn());
43
+ this.listeners = [];
44
+ this.removeAllTracks();
45
+ this.video = null;
46
+ this.currentTrackId = null;
47
+ }
48
+ /**
49
+ * Get available text tracks from the video element
50
+ */
51
+ getTextTracks() {
52
+ if (!this.video)
53
+ return [];
54
+ return Array.from(this.video.textTracks);
55
+ }
56
+ /**
57
+ * Get all track elements from the video
58
+ */
59
+ getTrackElements() {
60
+ if (!this.video)
61
+ return [];
62
+ return Array.from(this.video.querySelectorAll("track"));
63
+ }
64
+ /**
65
+ * Set the active subtitle track
66
+ * Pass null to disable subtitles
67
+ */
68
+ setSubtitle(track) {
69
+ if (!this.video) {
70
+ this.log("Cannot set subtitle: no video element attached");
71
+ return;
72
+ }
73
+ // Remove existing subtitle tracks
74
+ this.removeAllTracks();
75
+ if (!track) {
76
+ this.currentTrackId = null;
77
+ this.log("Subtitles disabled");
78
+ return;
79
+ }
80
+ // Create new track element
81
+ const trackElement = document.createElement("track");
82
+ trackElement.kind = "subtitles";
83
+ trackElement.label = track.label;
84
+ trackElement.srclang = track.lang;
85
+ trackElement.src = this.buildTrackUrl(track.src);
86
+ trackElement.default = true;
87
+ // Set up load handler for sync correction
88
+ trackElement.addEventListener("load", () => {
89
+ this.correctSubtitleSync();
90
+ });
91
+ this.video.appendChild(trackElement);
92
+ this.currentTrackId = track.id;
93
+ // Enable the track
94
+ const textTrack = this.video.textTracks[this.video.textTracks.length - 1];
95
+ if (textTrack) {
96
+ textTrack.mode = "showing";
97
+ }
98
+ this.log(`Subtitle track set: ${track.label} (${track.lang})`);
99
+ }
100
+ /**
101
+ * Build track URL with base URL and append params
102
+ */
103
+ buildTrackUrl(src) {
104
+ let url = src;
105
+ // If relative URL and base URL provided, construct full URL
106
+ if (!url.startsWith("http") && this.config.mistBaseUrl) {
107
+ const base = this.config.mistBaseUrl.replace(/\/$/, "");
108
+ url = url.startsWith("/") ? `${base}${url}` : `${base}/${url}`;
109
+ }
110
+ // Append URL params if configured
111
+ if (this.config.urlAppend) {
112
+ const separator = url.includes("?") ? "&" : "?";
113
+ url = `${url}${separator}${this.config.urlAppend}`;
114
+ }
115
+ return url;
116
+ }
117
+ /**
118
+ * Create subtitle track info from MistServer track metadata
119
+ */
120
+ static createTrackInfo(trackId, label, lang, baseUrl, streamName) {
121
+ // MistServer WebVTT URL format
122
+ const src = `${baseUrl}/${streamName}.vtt?track=${trackId}`;
123
+ return { id: trackId, label, lang, src };
124
+ }
125
+ /**
126
+ * Remove all track elements from video
127
+ */
128
+ removeAllTracks() {
129
+ if (!this.video)
130
+ return;
131
+ const tracks = this.video.querySelectorAll("track");
132
+ tracks.forEach((track) => track.remove());
133
+ }
134
+ /**
135
+ * Get currently active track ID
136
+ */
137
+ getCurrentTrackId() {
138
+ return this.currentTrackId;
139
+ }
140
+ /**
141
+ * Set seek offset for WebRTC sync correction
142
+ * WebRTC playback has a seek offset that needs to be applied to subtitle timing
143
+ */
144
+ setSeekOffset(offset) {
145
+ const oldOffset = this.seekOffset;
146
+ this.seekOffset = offset;
147
+ // Re-sync if offset changed significantly
148
+ if (Math.abs(oldOffset - offset) > 1) {
149
+ this.correctSubtitleSync();
150
+ }
151
+ }
152
+ /**
153
+ * Correct subtitle timing based on seek offset
154
+ * This is needed for WebRTC where video.currentTime doesn't match actual playback position
155
+ */
156
+ correctSubtitleSync() {
157
+ if (!this.video || this.video.textTracks.length === 0)
158
+ return;
159
+ const textTrack = this.video.textTracks[0];
160
+ if (!textTrack || !textTrack.cues)
161
+ return;
162
+ const currentOffset = textTrack.currentOffset || 0;
163
+ // Don't bother if change is small
164
+ if (Math.abs(this.seekOffset - currentOffset) < 1)
165
+ return;
166
+ this.log(`Correcting subtitle sync: offset ${currentOffset} -> ${this.seekOffset}`);
167
+ // Collect and re-add cues with corrected timing
168
+ const newCues = [];
169
+ for (let i = textTrack.cues.length - 1; i >= 0; i--) {
170
+ const cue = textTrack.cues[i];
171
+ textTrack.removeCue(cue);
172
+ // Store original timing if not already stored
173
+ if (!cue.orig) {
174
+ cue.orig = { start: cue.startTime, end: cue.endTime };
175
+ }
176
+ // Apply offset correction
177
+ cue.startTime = cue.orig.start - this.seekOffset;
178
+ cue.endTime = cue.orig.end - this.seekOffset;
179
+ newCues.push(cue);
180
+ }
181
+ // Re-add cues
182
+ for (const cue of newCues) {
183
+ try {
184
+ textTrack.addCue(cue);
185
+ }
186
+ catch {
187
+ // Ignore errors from invalid cue timing
188
+ }
189
+ }
190
+ textTrack.currentOffset = this.seekOffset;
191
+ }
192
+ /**
193
+ * Parse subtitle tracks from MistServer stream info
194
+ */
195
+ static parseTracksFromStreamInfo(streamInfo, baseUrl, streamName) {
196
+ const tracks = [];
197
+ if (!streamInfo.meta?.tracks)
198
+ return tracks;
199
+ for (const [trackId, trackData] of Object.entries(streamInfo.meta.tracks)) {
200
+ if (trackData.type === "meta" && trackData.codec === "subtitle") {
201
+ const lang = trackData.lang || "und";
202
+ const label = lang === "und" ? `Subtitles ${trackId}` : lang.toUpperCase();
203
+ tracks.push(SubtitleManager.createTrackInfo(trackId, label, lang, baseUrl, streamName));
204
+ }
205
+ }
206
+ return tracks;
207
+ }
208
+ /**
209
+ * Debug logging
210
+ */
211
+ log(message) {
212
+ if (this.debug) {
213
+ console.debug(`[SubtitleManager] ${message}`);
214
+ }
215
+ }
216
+ /**
217
+ * Cleanup
218
+ */
219
+ destroy() {
220
+ this.detach();
221
+ }
222
+ }
223
+
224
+ export { SubtitleManager };
225
+ //# sourceMappingURL=SubtitleManager.js.map