@phystack/hub-client 4.5.19-dev → 4.5.20-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.
- package/dist/index.d.ts +22 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +252 -378
- package/dist/index.js.map +1 -1
- package/dist/peripheral-twin.d.ts +34 -0
- package/dist/peripheral-twin.d.ts.map +1 -0
- package/dist/peripheral-twin.js +234 -0
- package/dist/peripheral-twin.js.map +1 -0
- package/dist/services/phyhub-connection.service.d.ts +1 -0
- package/dist/services/phyhub-connection.service.d.ts.map +1 -1
- package/dist/services/phyhub-connection.service.js +17 -0
- package/dist/services/phyhub-connection.service.js.map +1 -1
- package/dist/services/phyhub-direct-connection.service.d.ts +21 -0
- package/dist/services/phyhub-direct-connection.service.d.ts.map +1 -0
- package/dist/services/phyhub-direct-connection.service.js +101 -0
- package/dist/services/phyhub-direct-connection.service.js.map +1 -0
- package/dist/services/webrtc/data-channel-handler.d.ts +45 -0
- package/dist/services/webrtc/data-channel-handler.d.ts.map +1 -0
- package/dist/services/webrtc/data-channel-handler.js +260 -0
- package/dist/services/webrtc/data-channel-handler.js.map +1 -0
- package/dist/services/webrtc/index.d.ts +8 -0
- package/dist/services/webrtc/index.d.ts.map +1 -0
- package/dist/services/webrtc/index.js +18 -0
- package/dist/services/webrtc/index.js.map +1 -0
- package/dist/services/webrtc/media-stream-handler.d.ts +57 -0
- package/dist/services/webrtc/media-stream-handler.d.ts.map +1 -0
- package/dist/services/webrtc/media-stream-handler.js +383 -0
- package/dist/services/webrtc/media-stream-handler.js.map +1 -0
- package/dist/services/webrtc/peer-connection-manager.d.ts +40 -0
- package/dist/services/webrtc/peer-connection-manager.d.ts.map +1 -0
- package/dist/services/webrtc/peer-connection-manager.js +336 -0
- package/dist/services/webrtc/peer-connection-manager.js.map +1 -0
- package/dist/services/webrtc/types.d.ts +134 -0
- package/dist/services/webrtc/types.d.ts.map +1 -0
- package/dist/services/webrtc/types.js +12 -0
- package/dist/services/webrtc/types.js.map +1 -0
- package/dist/services/webrtc/webrtc-globals.d.ts +4 -0
- package/dist/services/webrtc/webrtc-globals.d.ts.map +1 -0
- package/dist/services/webrtc/webrtc-globals.js +72 -0
- package/dist/services/webrtc/webrtc-globals.js.map +1 -0
- package/dist/services/webrtc/webrtc-manager.d.ts +35 -0
- package/dist/services/webrtc/webrtc-manager.d.ts.map +1 -0
- package/dist/services/webrtc/webrtc-manager.js +274 -0
- package/dist/services/webrtc/webrtc-manager.js.map +1 -0
- package/dist/test/communication-comprehensive-test.d.ts +8 -0
- package/dist/test/communication-comprehensive-test.d.ts.map +1 -0
- package/dist/test/communication-comprehensive-test.js +356 -0
- package/dist/test/communication-comprehensive-test.js.map +1 -0
- package/dist/test/webrtc-channel-names-test.d.ts +2 -0
- package/dist/test/webrtc-channel-names-test.d.ts.map +1 -0
- package/dist/test/webrtc-channel-names-test.js +177 -0
- package/dist/test/webrtc-channel-names-test.js.map +1 -0
- package/dist/test/webrtc-comprehensive-test.d.ts +2 -0
- package/dist/test/webrtc-comprehensive-test.d.ts.map +1 -0
- package/dist/test/webrtc-comprehensive-test.js +328 -0
- package/dist/test/webrtc-comprehensive-test.js.map +1 -0
- package/dist/test/webrtc-reconnect-test.d.ts +4 -0
- package/dist/test/webrtc-reconnect-test.d.ts.map +1 -0
- package/dist/test/webrtc-reconnect-test.js +244 -0
- package/dist/test/webrtc-reconnect-test.js.map +1 -0
- package/dist/test/webrtc-test-harness.d.ts +4 -0
- package/dist/test/webrtc-test-harness.d.ts.map +1 -0
- package/dist/test/webrtc-test-harness.js +169 -0
- package/dist/test/webrtc-test-harness.js.map +1 -0
- package/dist/twin-messaging.d.ts +20 -0
- package/dist/twin-messaging.d.ts.map +1 -0
- package/dist/twin-messaging.js +94 -0
- package/dist/twin-messaging.js.map +1 -0
- package/dist/twin-registry.d.ts +9 -0
- package/dist/twin-registry.d.ts.map +1 -0
- package/dist/twin-registry.js +26 -0
- package/dist/twin-registry.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +20 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/twin.types.d.ts +62 -14
- package/dist/types/twin.types.d.ts.map +1 -1
- package/dist/types/twin.types.js +8 -1
- package/dist/types/twin.types.js.map +1 -1
- package/docs/webrtc-howto.md +398 -0
- package/docs/webrtc-test.md +330 -0
- package/package.json +3 -3
- package/scripts/webrtc-test.sh +401 -0
- package/src/index.ts +378 -568
- package/src/peripheral-twin.ts +337 -0
- package/src/services/phyhub-connection.service.ts +24 -0
- package/src/services/phyhub-direct-connection.service.ts +159 -0
- package/src/services/webrtc/data-channel-handler.ts +362 -0
- package/src/services/webrtc/index.ts +36 -0
- package/src/services/webrtc/media-stream-handler.ts +536 -0
- package/src/services/webrtc/peer-connection-manager.ts +467 -0
- package/src/services/webrtc/types.ts +273 -0
- package/src/services/webrtc/webrtc-globals.ts +108 -0
- package/src/services/webrtc/webrtc-manager.ts +490 -0
- package/src/test/communication-comprehensive-test.ts +533 -0
- package/src/test/webrtc-channel-names-test.ts +266 -0
- package/src/test/webrtc-comprehensive-test.ts +494 -0
- package/src/test/webrtc-reconnect-test.ts +345 -0
- package/src/test/webrtc-test-harness.ts +254 -0
- package/src/twin-messaging.ts +184 -0
- package/src/twin-registry.ts +39 -0
- package/src/types/index.ts +3 -0
- package/src/types/twin.types.ts +80 -14
- package/dist/services/webrtc/datachannel.d.ts +0 -10
- package/dist/services/webrtc/datachannel.d.ts.map +0 -1
- package/dist/services/webrtc/datachannel.js +0 -290
- package/dist/services/webrtc/datachannel.js.map +0 -1
- package/dist/services/webrtc/mediastream.d.ts +0 -10
- package/dist/services/webrtc/mediastream.d.ts.map +0 -1
- package/dist/services/webrtc/mediastream.js +0 -396
- package/dist/services/webrtc/mediastream.js.map +0 -1
- package/dist/services/webrtc/peer-connection-ice.d.ts +0 -32
- package/dist/services/webrtc/peer-connection-ice.d.ts.map +0 -1
- package/dist/services/webrtc/peer-connection-ice.js +0 -483
- package/dist/services/webrtc/peer-connection-ice.js.map +0 -1
- package/src/services/webrtc/datachannel.ts +0 -421
- package/src/services/webrtc/mediastream.ts +0 -602
- 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
|
-
};
|