@phystack/hub-client 4.5.19-dev → 4.5.21-dev

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 (119) hide show
  1. package/dist/index.d.ts +22 -28
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +252 -378
  4. package/dist/index.js.map +1 -1
  5. package/dist/peripheral-twin.d.ts +34 -0
  6. package/dist/peripheral-twin.d.ts.map +1 -0
  7. package/dist/peripheral-twin.js +234 -0
  8. package/dist/peripheral-twin.js.map +1 -0
  9. package/dist/services/phyhub-connection.service.d.ts +1 -0
  10. package/dist/services/phyhub-connection.service.d.ts.map +1 -1
  11. package/dist/services/phyhub-connection.service.js +17 -0
  12. package/dist/services/phyhub-connection.service.js.map +1 -1
  13. package/dist/services/phyhub-direct-connection.service.d.ts +21 -0
  14. package/dist/services/phyhub-direct-connection.service.d.ts.map +1 -0
  15. package/dist/services/phyhub-direct-connection.service.js +101 -0
  16. package/dist/services/phyhub-direct-connection.service.js.map +1 -0
  17. package/dist/services/webrtc/data-channel-handler.d.ts +45 -0
  18. package/dist/services/webrtc/data-channel-handler.d.ts.map +1 -0
  19. package/dist/services/webrtc/data-channel-handler.js +260 -0
  20. package/dist/services/webrtc/data-channel-handler.js.map +1 -0
  21. package/dist/services/webrtc/index.d.ts +8 -0
  22. package/dist/services/webrtc/index.d.ts.map +1 -0
  23. package/dist/services/webrtc/index.js +18 -0
  24. package/dist/services/webrtc/index.js.map +1 -0
  25. package/dist/services/webrtc/media-stream-handler.d.ts +57 -0
  26. package/dist/services/webrtc/media-stream-handler.d.ts.map +1 -0
  27. package/dist/services/webrtc/media-stream-handler.js +383 -0
  28. package/dist/services/webrtc/media-stream-handler.js.map +1 -0
  29. package/dist/services/webrtc/peer-connection-manager.d.ts +40 -0
  30. package/dist/services/webrtc/peer-connection-manager.d.ts.map +1 -0
  31. package/dist/services/webrtc/peer-connection-manager.js +336 -0
  32. package/dist/services/webrtc/peer-connection-manager.js.map +1 -0
  33. package/dist/services/webrtc/types.d.ts +134 -0
  34. package/dist/services/webrtc/types.d.ts.map +1 -0
  35. package/dist/services/webrtc/types.js +12 -0
  36. package/dist/services/webrtc/types.js.map +1 -0
  37. package/dist/services/webrtc/webrtc-globals.d.ts +4 -0
  38. package/dist/services/webrtc/webrtc-globals.d.ts.map +1 -0
  39. package/dist/services/webrtc/webrtc-globals.js +72 -0
  40. package/dist/services/webrtc/webrtc-globals.js.map +1 -0
  41. package/dist/services/webrtc/webrtc-manager.d.ts +35 -0
  42. package/dist/services/webrtc/webrtc-manager.d.ts.map +1 -0
  43. package/dist/services/webrtc/webrtc-manager.js +274 -0
  44. package/dist/services/webrtc/webrtc-manager.js.map +1 -0
  45. package/dist/test/communication-comprehensive-test.d.ts +8 -0
  46. package/dist/test/communication-comprehensive-test.d.ts.map +1 -0
  47. package/dist/test/communication-comprehensive-test.js +356 -0
  48. package/dist/test/communication-comprehensive-test.js.map +1 -0
  49. package/dist/test/webrtc-channel-names-test.d.ts +2 -0
  50. package/dist/test/webrtc-channel-names-test.d.ts.map +1 -0
  51. package/dist/test/webrtc-channel-names-test.js +177 -0
  52. package/dist/test/webrtc-channel-names-test.js.map +1 -0
  53. package/dist/test/webrtc-comprehensive-test.d.ts +2 -0
  54. package/dist/test/webrtc-comprehensive-test.d.ts.map +1 -0
  55. package/dist/test/webrtc-comprehensive-test.js +328 -0
  56. package/dist/test/webrtc-comprehensive-test.js.map +1 -0
  57. package/dist/test/webrtc-reconnect-test.d.ts +4 -0
  58. package/dist/test/webrtc-reconnect-test.d.ts.map +1 -0
  59. package/dist/test/webrtc-reconnect-test.js +244 -0
  60. package/dist/test/webrtc-reconnect-test.js.map +1 -0
  61. package/dist/test/webrtc-test-harness.d.ts +4 -0
  62. package/dist/test/webrtc-test-harness.d.ts.map +1 -0
  63. package/dist/test/webrtc-test-harness.js +169 -0
  64. package/dist/test/webrtc-test-harness.js.map +1 -0
  65. package/dist/twin-messaging.d.ts +20 -0
  66. package/dist/twin-messaging.d.ts.map +1 -0
  67. package/dist/twin-messaging.js +94 -0
  68. package/dist/twin-messaging.js.map +1 -0
  69. package/dist/twin-registry.d.ts +9 -0
  70. package/dist/twin-registry.d.ts.map +1 -0
  71. package/dist/twin-registry.js +26 -0
  72. package/dist/twin-registry.js.map +1 -0
  73. package/dist/types/index.d.ts +4 -0
  74. package/dist/types/index.d.ts.map +1 -0
  75. package/dist/types/index.js +20 -0
  76. package/dist/types/index.js.map +1 -0
  77. package/dist/types/twin.types.d.ts +62 -14
  78. package/dist/types/twin.types.d.ts.map +1 -1
  79. package/dist/types/twin.types.js +8 -1
  80. package/dist/types/twin.types.js.map +1 -1
  81. package/docs/webrtc-howto.md +398 -0
  82. package/docs/webrtc-test.md +330 -0
  83. package/package.json +3 -3
  84. package/scripts/webrtc-test.sh +401 -0
  85. package/src/index.ts +378 -568
  86. package/src/peripheral-twin.ts +337 -0
  87. package/src/services/phyhub-connection.service.ts +24 -0
  88. package/src/services/phyhub-direct-connection.service.ts +159 -0
  89. package/src/services/webrtc/data-channel-handler.ts +362 -0
  90. package/src/services/webrtc/index.ts +36 -0
  91. package/src/services/webrtc/media-stream-handler.ts +536 -0
  92. package/src/services/webrtc/peer-connection-manager.ts +467 -0
  93. package/src/services/webrtc/types.ts +273 -0
  94. package/src/services/webrtc/webrtc-globals.ts +108 -0
  95. package/src/services/webrtc/webrtc-manager.ts +490 -0
  96. package/src/test/communication-comprehensive-test.ts +533 -0
  97. package/src/test/webrtc-channel-names-test.ts +266 -0
  98. package/src/test/webrtc-comprehensive-test.ts +494 -0
  99. package/src/test/webrtc-reconnect-test.ts +345 -0
  100. package/src/test/webrtc-test-harness.ts +254 -0
  101. package/src/twin-messaging.ts +184 -0
  102. package/src/twin-registry.ts +39 -0
  103. package/src/types/index.ts +3 -0
  104. package/src/types/twin.types.ts +80 -14
  105. package/dist/services/webrtc/datachannel.d.ts +0 -10
  106. package/dist/services/webrtc/datachannel.d.ts.map +0 -1
  107. package/dist/services/webrtc/datachannel.js +0 -290
  108. package/dist/services/webrtc/datachannel.js.map +0 -1
  109. package/dist/services/webrtc/mediastream.d.ts +0 -10
  110. package/dist/services/webrtc/mediastream.d.ts.map +0 -1
  111. package/dist/services/webrtc/mediastream.js +0 -396
  112. package/dist/services/webrtc/mediastream.js.map +0 -1
  113. package/dist/services/webrtc/peer-connection-ice.d.ts +0 -32
  114. package/dist/services/webrtc/peer-connection-ice.d.ts.map +0 -1
  115. package/dist/services/webrtc/peer-connection-ice.js +0 -483
  116. package/dist/services/webrtc/peer-connection-ice.js.map +0 -1
  117. package/src/services/webrtc/datachannel.ts +0 -421
  118. package/src/services/webrtc/mediastream.ts +0 -602
  119. package/src/services/webrtc/peer-connection-ice.ts +0 -689
@@ -1,602 +0,0 @@
1
- // Initialize WebRTC globals if we're in Node environment
2
- (async function initializeWebRTCGlobals() {
3
- // Only run in Node environment
4
- if (typeof window !== 'undefined') return;
5
-
6
- try {
7
- // Dynamically import wrtc
8
- const wrtc = require('@roamhq/wrtc');
9
-
10
- // Set all WebRTC globals
11
- if (typeof global.MediaStream === 'undefined' && wrtc.MediaStream) {
12
- global.MediaStream = wrtc.MediaStream;
13
- console.log('Global MediaStream initialized');
14
- }
15
-
16
- if (typeof global.RTCDataChannel === 'undefined' && wrtc.RTCDataChannel) {
17
- global.RTCDataChannel = wrtc.RTCDataChannel;
18
- console.log('Global RTCDataChannel initialized');
19
- }
20
-
21
- if (typeof global.RTCPeerConnection === 'undefined' && wrtc.RTCPeerConnection) {
22
- global.RTCPeerConnection = wrtc.RTCPeerConnection;
23
- console.log('Global RTCPeerConnection initialized');
24
- }
25
-
26
- if (typeof global.RTCSessionDescription === 'undefined' && wrtc.RTCSessionDescription) {
27
- global.RTCSessionDescription = wrtc.RTCSessionDescription;
28
- console.log('Global RTCSessionDescription initialized');
29
- }
30
-
31
- if (typeof global.RTCIceCandidate === 'undefined' && wrtc.RTCIceCandidate) {
32
- global.RTCIceCandidate = wrtc.RTCIceCandidate;
33
- console.log('Global RTCIceCandidate initialized');
34
- }
35
-
36
- console.log('WebRTC globals successfully initialized');
37
- } catch (error) {
38
- console.error('Failed to initialize WebRTC globals:', error);
39
- }
40
- })().catch(err => console.error('Error in WebRTC globals initialization:', err));
41
-
42
- import {
43
- PhygridMediaStream,
44
- WebRTCConnectionOptions,
45
- ExtendedMediaStreamTrack,
46
- } from '../../types/webrtc.types';
47
- import {
48
- PeerConnectionState,
49
- PeerConnectionCallbacks,
50
- attemptConnection as attemptConnectionICE,
51
- triggerReconnect as triggerReconnectICE,
52
- } from './peer-connection-ice';
53
-
54
- export interface WebRTCMediaStreamResult {
55
- mediaStream: PhygridMediaStream;
56
- close: () => void;
57
- _rawPeerConnection?: RTCPeerConnection;
58
- _rawMediaStream?: MediaStream;
59
- }
60
-
61
- export type WebRTCMediaStreamConnectionResult = WebRTCMediaStreamResult;
62
-
63
- const CONNECTION_TIMEOUT = 15000; // 15 seconds
64
- const RECONNECT_DELAY = 1000; // 1 second initial reconnect delay
65
- const INITIAL_RETRY_DELAY = 1000; // 1 second
66
- const MAX_RETRY_DELAY = 30000; // 30 seconds maximum
67
- const FRAME_INACTIVITY_TIMEOUT = 5000; // 5 seconds without frames is considered inactive
68
-
69
- export async function createWebRTCMediaStreamConnection(
70
- options: WebRTCConnectionOptions
71
- ): Promise<WebRTCMediaStreamConnectionResult> {
72
- let pc: RTCPeerConnection | null = null;
73
- let isShuttingDown = false;
74
- let isReconnecting = false;
75
- let currentConnectionId = 0;
76
- let pendingCandidates: RTCIceCandidateInit[] = [];
77
- let activeMediaStream: MediaStream | null = null;
78
- let persistentLocalStream: MediaStream | undefined;
79
-
80
- const {
81
- targetTwinId,
82
- sendTwinMessage,
83
- subscribeTwin,
84
- onTwinMessage,
85
- offTwinMessage,
86
- useStun = true,
87
- isInitiator = true,
88
- channelPrefix = 'stream',
89
- mediaOptions = { isMediaConnection: true },
90
- } = options;
91
-
92
- // Create the persistent media stream once - this is the key to maintaining state across reconnections
93
- const { persistentMediaStream, setupMediaStream, setCleanupFn } = createPersistentMediaStream();
94
-
95
- // Set up cleanup function
96
- setCleanupFn(() => {
97
- if (mediaOptions.isMediaConnection && mediaOptions.localStream) {
98
- mediaOptions.localStream.getTracks().forEach(track => track.stop());
99
- }
100
- });
101
-
102
- if (!isInitiator && mediaOptions.isMediaConnection) {
103
- if (!mediaOptions.localStream) {
104
- mediaOptions.localStream = new MediaStream();
105
- console.log('Created persistent local MediaStream for the non-initiator side.');
106
- }
107
- persistentLocalStream = mediaOptions.localStream;
108
- }
109
-
110
- const callbacks: PeerConnectionCallbacks = {
111
- async onCreateConnection(
112
- pcInstance: RTCPeerConnection,
113
- connectionId: number,
114
- localInitiator: boolean
115
- ) {
116
- console.log(
117
- `Setting up media stream for connection ID: ${connectionId} ${JSON.stringify(localInitiator)}`
118
- );
119
-
120
- if (mediaOptions.isMediaConnection) {
121
- if (localInitiator) {
122
- console.log('Setting up recvonly transceiver for initiator');
123
- pcInstance.addTransceiver('video', { direction: 'recvonly' });
124
- } else {
125
- if (persistentLocalStream) {
126
- console.log('Attaching previously added tracks to new PC...');
127
- for (const track of persistentLocalStream.getTracks()) {
128
- console.log('Adding track to new peer connection:', track.kind, track.id);
129
- pcInstance.addTrack(track, persistentLocalStream);
130
- }
131
- } else {
132
- console.log('No localStream found yet. Will wait for app to add tracks...');
133
- }
134
- }
135
-
136
- pcInstance.ontrack = (event: RTCTrackEvent) => {
137
- console.log('Track received in hub-client');
138
- if (event.streams && event.streams[0]) {
139
- setupMediaStream(event.streams[0], isReconnecting, isShuttingDown, triggerReconnect);
140
- activeMediaStream = event.streams[0];
141
- }
142
- };
143
- }
144
-
145
- return null;
146
- },
147
-
148
- onCloseCleanup() {
149
- if (mediaOptions.isMediaConnection && mediaOptions.localStream) {
150
- mediaOptions.localStream.getTracks().forEach(track => track.stop());
151
- }
152
- },
153
-
154
- async onSubscribe() {
155
- await subscribeTwin(targetTwinId);
156
- },
157
-
158
- async sendSignalingMessage(type, data) {
159
- await sendTwinMessage(targetTwinId, {
160
- type: `${channelPrefix}-${targetTwinId}:${type}`,
161
- data,
162
- });
163
- },
164
-
165
- setupMessageHandling(messageHandler) {
166
- onTwinMessage(targetTwinId, messageHandler);
167
- },
168
-
169
- removeMessageHandling(messageHandler) {
170
- console.log(`Removing message listener for twin ${targetTwinId}`);
171
- offTwinMessage(targetTwinId, messageHandler);
172
- },
173
-
174
- reportConnectionState(info: string) {
175
- console.log(info);
176
- },
177
-
178
- reportError(info: string, error?: any) {
179
- console.error(info, error || '');
180
- },
181
-
182
- async retryConnection(retries: number, toggledUseStun?: boolean, nextDelay?: number) {
183
- isReconnecting = false;
184
-
185
- return attemptConnectionICE(
186
- state,
187
- { ...options, useStun: toggledUseStun ?? useStun },
188
- retries,
189
- toggledUseStun ?? useStun,
190
- nextDelay ?? INITIAL_RETRY_DELAY,
191
- INITIAL_RETRY_DELAY,
192
- CONNECTION_TIMEOUT,
193
- MAX_RETRY_DELAY,
194
- callbacks
195
- );
196
- },
197
-
198
- safeReject(error) {
199
- throw error;
200
- },
201
- };
202
-
203
- const state: PeerConnectionState = {
204
- pc,
205
- isShuttingDown,
206
- isReconnecting,
207
- currentConnectionId,
208
- pendingCandidates,
209
- activeDataChannel: null,
210
- activeMediaStream,
211
- useStun,
212
- channelPrefix,
213
- targetTwinId,
214
- isInitiator,
215
- connectionType: 'mediastream',
216
- };
217
-
218
- function triggerReconnect(attempt: number) {
219
- if (isReconnecting || isShuttingDown) {
220
- console.log(
221
- `triggerReconnect() called but already reconnecting or shutting down (attempt ${attempt})`
222
- );
223
- return;
224
- }
225
-
226
- isReconnecting = true;
227
-
228
- triggerReconnectICE(state, attempt, RECONNECT_DELAY, callbacks);
229
- }
230
-
231
- const result = await attemptConnectionICE(
232
- state,
233
- { ...options },
234
- 0,
235
- useStun,
236
- INITIAL_RETRY_DELAY,
237
- INITIAL_RETRY_DELAY,
238
- CONNECTION_TIMEOUT,
239
- MAX_RETRY_DELAY,
240
- callbacks
241
- );
242
-
243
- persistentMediaStream.addTrack = function (track: MediaStreamTrack) {
244
- console.log(`Adding track to persistent media stream: ${track.kind} - ${track.id}`);
245
-
246
- // Find existing track with same ID
247
- const existingTrack = persistentMediaStream._internalTracks?.find(t => t.id === track.id);
248
-
249
- // If exists, remove it first to prevent duplicates
250
- if (existingTrack) {
251
- console.log(`Track ${track.id} already exists, replacing it`);
252
- const trackIndex =
253
- persistentMediaStream._internalTracks?.findIndex(t => t.id === track.id) ?? -1;
254
- if (trackIndex !== -1 && persistentMediaStream._internalTracks) {
255
- persistentMediaStream._internalTracks.splice(trackIndex, 1);
256
- }
257
- }
258
-
259
- // Add the new track
260
- if (persistentMediaStream._internalTracks) {
261
- persistentMediaStream._internalTracks.push(track);
262
- }
263
-
264
- // Propagate to active stream if it exists
265
- if (activeMediaStream) {
266
- try {
267
- // Check if track already exists in the activeMediaStream
268
- const existingActiveTrack = activeMediaStream.getTrackById(track.id);
269
- if (existingActiveTrack) {
270
- // Replace the track if it exists
271
- activeMediaStream.removeTrack(existingActiveTrack);
272
- }
273
- activeMediaStream.addTrack(track);
274
- } catch (error) {
275
- console.warn('Error adding track to active MediaStream:', error);
276
- }
277
- }
278
-
279
- if (!isInitiator && persistentLocalStream) {
280
- console.log(
281
- 'Adding track to persistent local stream for non-initiator side:',
282
- track.kind,
283
- track.id
284
- );
285
- // Check if track already exists in the persistentLocalStream
286
- const existingLocalTrack = persistentLocalStream.getTrackById(track.id);
287
- if (existingLocalTrack) {
288
- // Replace the track if it exists
289
- persistentLocalStream.removeTrack(existingLocalTrack);
290
- }
291
- persistentLocalStream.addTrack(track);
292
-
293
- if (state.pc && state.pc.connectionState === 'connected') {
294
- console.log('PeerConnection is connected, calling pc.addTrack() now...');
295
- const senders = state.pc.getSenders();
296
- const existingSender = senders.find(sender => sender.track && sender.track.id === track.id);
297
-
298
- if (existingSender) {
299
- console.log('Replacing existing track in sender');
300
- existingSender.replaceTrack(track);
301
- } else {
302
- state.pc.addTrack(track, persistentLocalStream);
303
- }
304
- } else {
305
- console.log(
306
- 'PeerConnection is not connected yet, will attach track at next onCreateConnection'
307
- );
308
- }
309
- }
310
-
311
- // Notify track listeners about the new track
312
- persistentMediaStream._notifyTrackListeners?.(track);
313
- };
314
-
315
- const mediaStreamResult: WebRTCMediaStreamResult = {
316
- mediaStream: persistentMediaStream,
317
- _rawPeerConnection: result._rawPeerConnection,
318
- _rawMediaStream: activeMediaStream || undefined,
319
- close: () => {
320
- isShuttingDown = true;
321
- if (result.close) {
322
- result.close();
323
- }
324
- },
325
- };
326
-
327
- return mediaStreamResult;
328
- }
329
-
330
- const createPersistentMediaStream = (): {
331
- persistentMediaStream: PhygridMediaStream;
332
- setupMediaStream: (
333
- ms: MediaStream,
334
- isReconnecting: boolean,
335
- isShuttingDown: boolean,
336
- triggerReconnect: (attempt: number) => void
337
- ) => void;
338
- setCleanupFn: (fn: () => void) => void;
339
- } => {
340
- const trackListeners: ((track: MediaStreamTrack) => void)[] = [];
341
- const closeListeners: (() => void)[] = [];
342
- const frameListeners: ((frameData: any) => void)[] = [];
343
- const tracks: MediaStreamTrack[] = [];
344
-
345
- // Track frame reception timestamps for each track
346
- const trackLastFrameTime: Record<string, number> = {};
347
- // Frame activity check interval
348
- let frameActivityCheckInterval: NodeJS.Timeout | null = null;
349
-
350
- // These variables were keeping references to the media streams but aren't being used.
351
- // Removing them to fix the TS6133 errors.
352
- let reconnectAttempts = 0;
353
- let cleanupFn: (() => void) | null = null;
354
- let isActive = false;
355
-
356
- const setCleanupFn = (fn: () => void) => {
357
- cleanupFn = fn;
358
- };
359
-
360
- const notifyTrackListeners = (track: MediaStreamTrack) => {
361
- trackListeners.forEach(listener => {
362
- try {
363
- listener(track);
364
- } catch (error) {
365
- console.error('Error in track listener:', error);
366
- }
367
- });
368
- };
369
-
370
- const updateTrackLastFrameTime = (trackId: string) => {
371
- trackLastFrameTime[trackId] = Date.now();
372
- isActive = true;
373
- };
374
-
375
- const notifyFrameListeners = (frameData: any) => {
376
- frameListeners.forEach(listener => {
377
- try {
378
- listener(frameData);
379
- } catch (error) {
380
- console.error('Error in frame listener:', error);
381
- }
382
- });
383
- };
384
-
385
- // Check if any track has received frames recently
386
- const checkFrameActivity = (triggerReconnect: (attempt: number) => void) => {
387
- const now = Date.now();
388
- let hasActiveTrack = false;
389
-
390
- Object.entries(trackLastFrameTime).forEach(([_, lastTime]) => {
391
- const timeSinceLastFrame = now - lastTime;
392
- if (timeSinceLastFrame < FRAME_INACTIVITY_TIMEOUT) {
393
- hasActiveTrack = true;
394
- }
395
- });
396
-
397
- if (!hasActiveTrack && isActive) {
398
- console.log('No frames received recently on any track, connection might be stale');
399
- isActive = false;
400
-
401
- // Trigger reconnect only if we previously had an active connection
402
- if (Object.keys(trackLastFrameTime).length > 0) {
403
- console.log('Triggering reconnect due to frame inactivity');
404
- triggerReconnect(reconnectAttempts++);
405
- }
406
- }
407
- };
408
-
409
- const setupTrackEndedEvent = (
410
- track: MediaStreamTrack,
411
- ms: MediaStream,
412
- isReconnecting: boolean,
413
- isShuttingDown: boolean,
414
- triggerReconnect: (attempt: number) => void
415
- ) => {
416
- // Initialize frame timestamp tracking for this track
417
- trackLastFrameTime[track.id] = Date.now();
418
-
419
- // Set up onFrame callback to detect actual frame reception
420
- const extendedTrack = track as ExtendedMediaStreamTrack;
421
- if (typeof extendedTrack.onFrame === 'function') {
422
- // Remove any existing onFrame handler to prevent duplicates
423
- const originalOnFrame = extendedTrack.onFrame;
424
- extendedTrack.onFrame = function (frame: any) {
425
- // Update the last frame time
426
- updateTrackLastFrameTime(track.id);
427
- // Notify frame listeners
428
- notifyFrameListeners(frame);
429
- // Call the original handler
430
- originalOnFrame.call(extendedTrack, frame);
431
- };
432
- } else {
433
- // If onFrame isn't available, fallback to readyState monitoring
434
- track.onended = () => {
435
- console.log(`Track ${track.id} ended`);
436
- delete trackLastFrameTime[track.id];
437
-
438
- const trackIndex = tracks.findIndex(t => t.id === track.id);
439
- if (trackIndex !== -1) {
440
- tracks.splice(trackIndex, 1);
441
- }
442
-
443
- const allTracksEnded = ms.getTracks().every(t => t.readyState === 'ended');
444
-
445
- if (allTracksEnded && !isShuttingDown && !isReconnecting) {
446
- console.log('All tracks ended, triggering reconnect');
447
- triggerReconnect(reconnectAttempts++);
448
- }
449
- };
450
- }
451
- };
452
-
453
- const persistentMediaStream: PhygridMediaStream = {
454
- // Internal storage for tracks - not exposed in the interface
455
- _internalTracks: tracks,
456
-
457
- // Internal helper to notify track listeners
458
- _notifyTrackListeners: notifyTrackListeners,
459
-
460
- getTracks: function (): MediaStreamTrack[] {
461
- return [...tracks];
462
- },
463
-
464
- onTrack: function (callback: (track: MediaStreamTrack) => void): void {
465
- console.log('Adding track listener to persistent media stream');
466
- trackListeners.push(callback);
467
-
468
- // Notify for existing tracks
469
- tracks.forEach(track => {
470
- try {
471
- callback(track);
472
- } catch (error) {
473
- console.error('Error in track listener:', error);
474
- }
475
- });
476
- },
477
-
478
- offTrack: function (callback: (track: MediaStreamTrack) => void): void {
479
- const index = trackListeners.indexOf(callback);
480
- if (index !== -1) {
481
- trackListeners.splice(index, 1);
482
- }
483
- },
484
-
485
- onFrame: function (callback: (frameData: any) => void): void {
486
- console.log('Adding frame listener to persistent media stream');
487
- frameListeners.push(callback);
488
- },
489
-
490
- offFrame: function (callback: (frameData: any) => void): void {
491
- const index = frameListeners.indexOf(callback);
492
- if (index !== -1) {
493
- frameListeners.splice(index, 1);
494
- }
495
- },
496
-
497
- onClose: function (callback: () => void): void {
498
- closeListeners.push(callback);
499
- },
500
-
501
- offClose: function (callback: () => void): void {
502
- const index = closeListeners.indexOf(callback);
503
- if (index !== -1) {
504
- closeListeners.splice(index, 1);
505
- }
506
- },
507
-
508
- close: function (): void {
509
- console.log('Closing persistent media stream');
510
-
511
- // Stop checking for frame activity
512
- if (frameActivityCheckInterval) {
513
- clearInterval(frameActivityCheckInterval);
514
- frameActivityCheckInterval = null;
515
- }
516
-
517
- // Clear track data
518
- tracks.splice(0, tracks.length);
519
-
520
- // Clear frame timestamps
521
- Object.keys(trackLastFrameTime).forEach(key => {
522
- delete trackLastFrameTime[key];
523
- });
524
-
525
- // Notify close listeners
526
- closeListeners.forEach(listener => {
527
- try {
528
- listener();
529
- } catch (error) {
530
- console.error('Error in close listener:', error);
531
- }
532
- });
533
-
534
- // Cleanup function if provided
535
- if (cleanupFn) {
536
- cleanupFn();
537
- }
538
-
539
- isActive = false;
540
- },
541
-
542
- addTrack: function (_track: MediaStreamTrack): void {
543
- // Implementation will be replaced in createWebRTCMediaStreamConnection
544
- console.warn('Default addTrack implementation called - this should be replaced');
545
- },
546
-
547
- // Check if stream is currently receiving frames
548
- isReceivingFrames: function (): boolean {
549
- if (Object.keys(trackLastFrameTime).length === 0) {
550
- return false;
551
- }
552
-
553
- const now = Date.now();
554
- return Object.values(trackLastFrameTime).some(
555
- lastTime => now - lastTime < FRAME_INACTIVITY_TIMEOUT
556
- );
557
- },
558
- };
559
-
560
- const setupMediaStream = (
561
- ms: MediaStream,
562
- isReconnecting: boolean,
563
- isShuttingDown: boolean,
564
- triggerReconnect: (attempt: number) => void
565
- ) => {
566
- console.log('Setting up media stream with tracks:', ms.getTracks().length);
567
-
568
- // Clear existing tracks array to prevent duplicates
569
- tracks.splice(0, tracks.length);
570
-
571
- // Start or restart frame activity check
572
- if (frameActivityCheckInterval) {
573
- clearInterval(frameActivityCheckInterval);
574
- }
575
-
576
- frameActivityCheckInterval = setInterval(() => {
577
- checkFrameActivity(triggerReconnect);
578
- }, 1000);
579
-
580
- // Process each track in the stream
581
- ms.getTracks().forEach(track => {
582
- console.log(
583
- `Processing track: ${track.kind} - ${track.id} with readyState: ${track.readyState}`
584
- );
585
-
586
- // Add to our tracks array
587
- tracks.push(track);
588
-
589
- // Set up track ended and frame monitoring
590
- setupTrackEndedEvent(track, ms, isReconnecting, isShuttingDown, triggerReconnect);
591
-
592
- // Notify track listeners
593
- notifyTrackListeners(track);
594
- });
595
- };
596
-
597
- return {
598
- persistentMediaStream,
599
- setupMediaStream,
600
- setCleanupFn,
601
- };
602
- };