@afosecure/meetingsdk 1.1.1 → 1.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.
- package/dist/index.d.mts +7 -4
- package/dist/index.d.ts +7 -4
- package/dist/index.js +62 -85
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +63 -86
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import react__default from 'react';
|
|
3
4
|
|
|
4
5
|
type Events = {
|
|
5
6
|
onMicToggled?: (peerId: string, enabled: boolean) => void;
|
|
@@ -107,6 +108,8 @@ declare class VideoSDKCore {
|
|
|
107
108
|
private ws;
|
|
108
109
|
private peers;
|
|
109
110
|
private initiators;
|
|
111
|
+
private lastPong;
|
|
112
|
+
private intentionalDisconnect;
|
|
110
113
|
private myId;
|
|
111
114
|
private roomId;
|
|
112
115
|
private localStream;
|
|
@@ -170,7 +173,7 @@ type MeetingContextValue = {
|
|
|
170
173
|
};
|
|
171
174
|
declare const MeetingProvider: ({ config, children, }: {
|
|
172
175
|
config: MeetingConfig;
|
|
173
|
-
children:
|
|
176
|
+
children: react__default.ReactNode;
|
|
174
177
|
}) => react_jsx_runtime.JSX.Element;
|
|
175
178
|
declare const useMeetingContext: () => MeetingContextValue;
|
|
176
179
|
|
|
@@ -200,8 +203,8 @@ declare const useMeeting: (handlers?: {
|
|
|
200
203
|
declare const useParticipants: () => Participant[];
|
|
201
204
|
|
|
202
205
|
declare const useRemoteMedia: (participantId: string) => {
|
|
203
|
-
videoRef:
|
|
204
|
-
audioRef:
|
|
206
|
+
videoRef: react.RefObject<HTMLVideoElement | null>;
|
|
207
|
+
audioRef: react.RefObject<HTMLAudioElement | null>;
|
|
205
208
|
isCamActive: boolean;
|
|
206
209
|
isMicEnabled: boolean;
|
|
207
210
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import react__default from 'react';
|
|
3
4
|
|
|
4
5
|
type Events = {
|
|
5
6
|
onMicToggled?: (peerId: string, enabled: boolean) => void;
|
|
@@ -107,6 +108,8 @@ declare class VideoSDKCore {
|
|
|
107
108
|
private ws;
|
|
108
109
|
private peers;
|
|
109
110
|
private initiators;
|
|
111
|
+
private lastPong;
|
|
112
|
+
private intentionalDisconnect;
|
|
110
113
|
private myId;
|
|
111
114
|
private roomId;
|
|
112
115
|
private localStream;
|
|
@@ -170,7 +173,7 @@ type MeetingContextValue = {
|
|
|
170
173
|
};
|
|
171
174
|
declare const MeetingProvider: ({ config, children, }: {
|
|
172
175
|
config: MeetingConfig;
|
|
173
|
-
children:
|
|
176
|
+
children: react__default.ReactNode;
|
|
174
177
|
}) => react_jsx_runtime.JSX.Element;
|
|
175
178
|
declare const useMeetingContext: () => MeetingContextValue;
|
|
176
179
|
|
|
@@ -200,8 +203,8 @@ declare const useMeeting: (handlers?: {
|
|
|
200
203
|
declare const useParticipants: () => Participant[];
|
|
201
204
|
|
|
202
205
|
declare const useRemoteMedia: (participantId: string) => {
|
|
203
|
-
videoRef:
|
|
204
|
-
audioRef:
|
|
206
|
+
videoRef: react.RefObject<HTMLVideoElement | null>;
|
|
207
|
+
audioRef: react.RefObject<HTMLAudioElement | null>;
|
|
205
208
|
isCamActive: boolean;
|
|
206
209
|
isMicEnabled: boolean;
|
|
207
210
|
};
|
package/dist/index.js
CHANGED
|
@@ -200,6 +200,8 @@ var VideoSDKCore = class {
|
|
|
200
200
|
this.ws = null;
|
|
201
201
|
this.peers = {};
|
|
202
202
|
this.initiators = /* @__PURE__ */ new Set();
|
|
203
|
+
this.lastPong = Date.now();
|
|
204
|
+
this.intentionalDisconnect = false;
|
|
203
205
|
this.roomId = null;
|
|
204
206
|
this.localStream = null;
|
|
205
207
|
this.screenStream = null;
|
|
@@ -271,18 +273,18 @@ var VideoSDKCore = class {
|
|
|
271
273
|
this.emitError("WS_ERROR", "WebSocket encountered an error", err, true);
|
|
272
274
|
};
|
|
273
275
|
this.ws.onclose = (e) => {
|
|
274
|
-
this.emitError(
|
|
275
|
-
"WS_CLOSED",
|
|
276
|
-
`Connection closed (${e.code}) ${e.reason || ""}`,
|
|
277
|
-
e,
|
|
278
|
-
true
|
|
279
|
-
);
|
|
280
276
|
this.joinRejecter?.({
|
|
281
277
|
code: "WS_CLOSED",
|
|
282
278
|
message: "Connection closed before join completed",
|
|
283
279
|
raw: e
|
|
284
280
|
});
|
|
285
281
|
this.joinRejecter = void 0;
|
|
282
|
+
if (this.intentionalDisconnect) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (e.code === 1e3 || e.code === 1001) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
286
288
|
this.scheduleReconnect();
|
|
287
289
|
};
|
|
288
290
|
this.ws.onmessage = async (e) => {
|
|
@@ -406,33 +408,9 @@ var VideoSDKCore = class {
|
|
|
406
408
|
var _a, _b, _c, _d;
|
|
407
409
|
if (msg.sender === this.myId) return;
|
|
408
410
|
switch (msg.type) {
|
|
409
|
-
case "
|
|
410
|
-
|
|
411
|
-
this.state.setPresenterId(msg.presenterId);
|
|
412
|
-
this.events.onScreenShareStarted?.(msg.presenterId, null);
|
|
413
|
-
}
|
|
414
|
-
for (const p of msg.participants || []) {
|
|
415
|
-
if (!p?.id || p.id === this.myId) continue;
|
|
416
|
-
this.state.addParticipant(p);
|
|
417
|
-
this.events.onUserJoined?.(p);
|
|
418
|
-
await this.createOffer(p.id);
|
|
419
|
-
}
|
|
420
|
-
break;
|
|
421
|
-
case "JOINED": {
|
|
422
|
-
this.startHeartbeat();
|
|
423
|
-
this.joinResolver?.();
|
|
424
|
-
this.joinResolver = void 0;
|
|
425
|
-
this.joinRejecter = void 0;
|
|
411
|
+
case "PONG":
|
|
412
|
+
this.lastPong = Date.now();
|
|
426
413
|
break;
|
|
427
|
-
}
|
|
428
|
-
case "USER_JOINED": {
|
|
429
|
-
const p = msg.participant;
|
|
430
|
-
if (!p?.id || p.id === this.myId) return;
|
|
431
|
-
this.state.addParticipant(p);
|
|
432
|
-
this.events.onUserJoined?.(p);
|
|
433
|
-
await this.createOffer(p.id);
|
|
434
|
-
break;
|
|
435
|
-
}
|
|
436
414
|
case "OFFER":
|
|
437
415
|
await this.handleOffer(msg.payload, msg.sender);
|
|
438
416
|
break;
|
|
@@ -482,6 +460,35 @@ var VideoSDKCore = class {
|
|
|
482
460
|
}
|
|
483
461
|
break;
|
|
484
462
|
}
|
|
463
|
+
case "EXISTING_USERS":
|
|
464
|
+
if (msg.presenterId) {
|
|
465
|
+
this.state.setPresenterId(msg.presenterId);
|
|
466
|
+
this.events.onScreenShareStarted?.(msg.presenterId, null);
|
|
467
|
+
}
|
|
468
|
+
for (const p of msg.participants || []) {
|
|
469
|
+
if (!p?.id || p.id === this.myId) continue;
|
|
470
|
+
this.state.addParticipant(p);
|
|
471
|
+
this.events.onUserJoined?.(p);
|
|
472
|
+
await this.createOffer(p.id);
|
|
473
|
+
}
|
|
474
|
+
break;
|
|
475
|
+
case "JOINED": {
|
|
476
|
+
this.intentionalDisconnect = false;
|
|
477
|
+
this.reconnectAttempts = 0;
|
|
478
|
+
this.startHeartbeat();
|
|
479
|
+
this.joinResolver?.();
|
|
480
|
+
this.joinResolver = void 0;
|
|
481
|
+
this.joinRejecter = void 0;
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
case "USER_JOINED": {
|
|
485
|
+
const p = msg.participant;
|
|
486
|
+
if (!p?.id || p.id === this.myId) return;
|
|
487
|
+
this.state.addParticipant(p);
|
|
488
|
+
this.events.onUserJoined?.(p);
|
|
489
|
+
await this.createOffer(p.id);
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
485
492
|
case "USER_LEFT":
|
|
486
493
|
const peerId = msg.participant.id;
|
|
487
494
|
this.closePeer(peerId);
|
|
@@ -595,9 +602,14 @@ var VideoSDKCore = class {
|
|
|
595
602
|
streamCount: event.streams.length,
|
|
596
603
|
streamTrackCount: event.streams[0]?.getTracks().length
|
|
597
604
|
});
|
|
598
|
-
const incomingStream = event.streams[0];
|
|
605
|
+
const incomingStream = event.streams?.[0] || new MediaStream([event.track]);
|
|
599
606
|
const participant = this.state.getParticipant(id);
|
|
600
607
|
const isScreenStream = incomingStream.id === participant?.media?.remoteScreenStreamId;
|
|
608
|
+
if (event.track.kind === "video") {
|
|
609
|
+
event.track.onunmute = () => {
|
|
610
|
+
console.log("Video track unmuted for", id);
|
|
611
|
+
};
|
|
612
|
+
}
|
|
601
613
|
if (isScreenStream) {
|
|
602
614
|
const videoTrack = event.track.kind === "video" ? event.track : incomingStream.getVideoTracks()[0] || participant?.media?.screenTrack;
|
|
603
615
|
this.state.updateParticipantMedia(id, {
|
|
@@ -629,12 +641,6 @@ var VideoSDKCore = class {
|
|
|
629
641
|
};
|
|
630
642
|
pc.oniceconnectionstatechange = () => {
|
|
631
643
|
console.log(`ICE Connection State: ${pc.iceConnectionState}`);
|
|
632
|
-
if (pc.iceConnectionState === "failed" || pc.iceConnectionState === "disconnected") {
|
|
633
|
-
this.emitError(
|
|
634
|
-
"ICE_FAILED",
|
|
635
|
-
"Connection failed. Please check your network or refresh."
|
|
636
|
-
);
|
|
637
|
-
}
|
|
638
644
|
};
|
|
639
645
|
pc.onconnectionstatechange = () => {
|
|
640
646
|
if (pc.connectionState === "failed") {
|
|
@@ -662,7 +668,6 @@ var VideoSDKCore = class {
|
|
|
662
668
|
console.debug(
|
|
663
669
|
`[Offer] Already initiating with ${id}, skipping duplicate`
|
|
664
670
|
);
|
|
665
|
-
return;
|
|
666
671
|
}
|
|
667
672
|
if (isRenegotiation && this.peers[id]) {
|
|
668
673
|
const pc2 = this.peers[id];
|
|
@@ -670,7 +675,6 @@ var VideoSDKCore = class {
|
|
|
670
675
|
console.warn(
|
|
671
676
|
`[Offer] Cannot renegotiate: peer in state "${pc2.signalingState}"`
|
|
672
677
|
);
|
|
673
|
-
return;
|
|
674
678
|
}
|
|
675
679
|
}
|
|
676
680
|
if (!isRenegotiation) {
|
|
@@ -702,15 +706,6 @@ var VideoSDKCore = class {
|
|
|
702
706
|
}
|
|
703
707
|
// ---------------- ANSWER ----------------
|
|
704
708
|
async handleOffer(sdp, id) {
|
|
705
|
-
if (this.initiators.has(id) && this.peers[id]) {
|
|
706
|
-
const pc2 = this.peers[id];
|
|
707
|
-
if (pc2.signalingState !== "stable") {
|
|
708
|
-
console.warn(
|
|
709
|
-
`[Signaling] Offer collision with ${id}. We initiated, ignoring their offer.`
|
|
710
|
-
);
|
|
711
|
-
return;
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
709
|
if (!this.peers[id]) {
|
|
715
710
|
this.peers[id] = this.createPeer(id);
|
|
716
711
|
}
|
|
@@ -884,6 +879,7 @@ var VideoSDKCore = class {
|
|
|
884
879
|
});
|
|
885
880
|
}
|
|
886
881
|
disconnect() {
|
|
882
|
+
this.intentionalDisconnect = true;
|
|
887
883
|
this.stopScreenShare();
|
|
888
884
|
Object.values(this.peers).forEach((pc) => pc.close());
|
|
889
885
|
this.peers = {};
|
|
@@ -1100,18 +1096,11 @@ var useParticipants = () => {
|
|
|
1100
1096
|
var import_react6 = require("react");
|
|
1101
1097
|
var useRemoteMedia = (participantId) => {
|
|
1102
1098
|
const { sdk } = useMeetingContext();
|
|
1099
|
+
const videoRef = (0, import_react6.useRef)(null);
|
|
1100
|
+
const audioRef = (0, import_react6.useRef)(null);
|
|
1103
1101
|
const [participant, setParticipant] = (0, import_react6.useState)(
|
|
1104
1102
|
() => sdk.state.getParticipant(participantId) || null
|
|
1105
1103
|
);
|
|
1106
|
-
const buildMediaStream = (participant2) => {
|
|
1107
|
-
if (!participant2?.media) return null;
|
|
1108
|
-
const stream = new MediaStream();
|
|
1109
|
-
const videoTrack = participant2.media.stream?.getVideoTracks?.()?.[0];
|
|
1110
|
-
const audioTrack = participant2.media.stream?.getAudioTracks?.()?.[0];
|
|
1111
|
-
if (videoTrack) stream.addTrack(videoTrack);
|
|
1112
|
-
if (audioTrack) stream.addTrack(audioTrack);
|
|
1113
|
-
return stream;
|
|
1114
|
-
};
|
|
1115
1104
|
(0, import_react6.useEffect)(() => {
|
|
1116
1105
|
const unsub = sdk.state.subscribe(`participant:${participantId}`, () => {
|
|
1117
1106
|
const p = sdk.state.getParticipant(participantId);
|
|
@@ -1119,34 +1108,22 @@ var useRemoteMedia = (participantId) => {
|
|
|
1119
1108
|
});
|
|
1120
1109
|
return unsub;
|
|
1121
1110
|
}, [participantId, sdk]);
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
node.autoplay = true;
|
|
1131
|
-
node.play().catch((err) => {
|
|
1132
|
-
console.log("can't play video:", err);
|
|
1111
|
+
(0, import_react6.useEffect)(() => {
|
|
1112
|
+
const stream = participant?.media?.stream;
|
|
1113
|
+
if (!stream) return;
|
|
1114
|
+
if (videoRef.current) {
|
|
1115
|
+
videoRef.current.srcObject = stream;
|
|
1116
|
+
videoRef.current.muted = true;
|
|
1117
|
+
videoRef.current.playsInline = true;
|
|
1118
|
+
videoRef.current.play().catch(() => {
|
|
1133
1119
|
});
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
(node) => {
|
|
1139
|
-
if (!node) return;
|
|
1140
|
-
const stream = participant?.media?.stream;
|
|
1141
|
-
if (!stream) return;
|
|
1142
|
-
node.srcObject = stream;
|
|
1143
|
-
node.muted = false;
|
|
1144
|
-
node.play().catch((err) => {
|
|
1145
|
-
console.log("can't play Audio:", err);
|
|
1120
|
+
}
|
|
1121
|
+
if (audioRef.current) {
|
|
1122
|
+
audioRef.current.srcObject = stream;
|
|
1123
|
+
audioRef.current.play().catch(() => {
|
|
1146
1124
|
});
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
);
|
|
1125
|
+
}
|
|
1126
|
+
}, [participant?.media?.stream]);
|
|
1150
1127
|
return {
|
|
1151
1128
|
videoRef,
|
|
1152
1129
|
audioRef,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/react/useLocalParticipant.tsx","../src/react/MeetingProvider.tsx","../src/config/ws.ts","../src/core/MeetingState.ts","../src/core/VideoCore.ts","../src/react/useMeetingStore.ts","../src/react/useMeeting.ts","../src/react/useParticipants.ts","../src/react/useRemoteMedia.ts"],"sourcesContent":["export { useLocalParticipant } from \"./react/useLocalParticipant\";\r\n\r\n// Core exports\r\nexport { VideoSDKCore } from \"./core/VideoCore\";\r\nexport { MeetingState } from \"./core/MeetingState\";\r\nexport type { Participant, ChatInput } from \"./types/meeting\";\r\n\r\n// React hooks and components\r\nexport { MeetingProvider, useMeetingContext } from \"./react/MeetingProvider\";\r\nexport { useMeeting } from \"./react/useMeeting\";\r\nexport { useParticipants } from \"./react/useParticipants\";\r\nexport { useRemoteMedia } from \"./react/useRemoteMedia\";\r\n","import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport { useMeetingContext } from \"./MeetingProvider\";\r\nimport { Participant } from \"../types/meeting\";\r\n\r\nexport const useLocalParticipant = () => {\r\n const { sdk } = useMeetingContext();\r\n\r\n // Guard the initial state to ensure it matches Participant | null\r\n const [localParticipant, setLocalParticipant] = useState<Participant | null>(\r\n () => {\r\n const current = sdk.state.localParticipant;\r\n return current && current.id ? (current as Participant) : null;\r\n },\r\n );\r\n\r\n useEffect(() => {\r\n const unsubscribe = sdk.state.subscribe(\"localParticipant\", () => {\r\n const current = sdk.state.localParticipant;\r\n\r\n // Safe Type-Guard: Only update if the object has a finalized id\r\n if (current && current.id) {\r\n setLocalParticipant({ ...current } as Participant);\r\n } else {\r\n setLocalParticipant(null);\r\n }\r\n });\r\n\r\n return unsubscribe;\r\n }, [sdk]);\r\n\r\n const lastStreamRef = useRef<MediaStream | null>(null);\r\n\r\n const videoRef = useCallback(\r\n (video: HTMLVideoElement | null) => {\r\n if (!video) return;\r\n\r\n const stream = localParticipant?.media?.stream;\r\n if (!stream) return;\r\n\r\n if (lastStreamRef.current === stream) return;\r\n lastStreamRef.current = stream;\r\n\r\n video.srcObject = stream;\r\n video.autoplay = true;\r\n video.playsInline = true;\r\n\r\n video.play().catch((err) => {\r\n console.warn(`Autoplay failed for local view:`, err);\r\n });\r\n },\r\n [localParticipant?.media?.stream],\r\n );\r\n\r\n return {\r\n participant: localParticipant,\r\n videoRef,\r\n };\r\n};\r\n","import React, { createContext, useContext, useMemo, useRef } from \"react\";\r\nimport { VideoSDKCore } from \"../core/VideoCore\";\r\nimport {\r\n ChatInput,\r\n ChatMessage,\r\n MeetingConfig,\r\n Participant,\r\n PubSubTopic,\r\n} from \"../types/meeting\";\r\nimport { useMeetingStore } from \"./useMeetingStore\";\r\n\r\ntype PubSubHandle = {\r\n messages: ChatMessage[];\r\n publish: (input: ChatInput) => void;\r\n};\r\n\r\ntype MeetingContextValue = {\r\n sdk: VideoSDKCore;\r\n\r\n join: (config: MeetingConfig) => Promise<void>;\r\n leave: () => void;\r\n\r\n toggleMic: () => void;\r\n toggleCam: () => void;\r\n\r\n startScreenShare: () => Promise<MediaStream>;\r\n stopScreenShare: () => void;\r\n\r\n sendMessage: (input: ChatInput) => void;\r\n\r\n meetingId: string | null;\r\n localParticipant: Participant | null;\r\n participants: Map<string, Participant>;\r\n messages: ChatMessage[];\r\n presenterId: string | null;\r\n usePubSub: (topic: PubSubTopic) => PubSubHandle;\r\n onError: (cb: (err: any) => void) => () => void;\r\n};\r\n\r\nconst MeetingContext = createContext<MeetingContextValue | null>(null);\r\n\r\nexport const MeetingProvider = ({\r\n config,\r\n children,\r\n}: {\r\n config: MeetingConfig;\r\n children: React.ReactNode;\r\n}) => {\r\n const sdkRef = useRef<VideoSDKCore | null>(null);\r\n const errorListeners = useRef(new Set<(err: any) => void>());\r\n if (!sdkRef.current) {\r\n sdkRef.current = new VideoSDKCore({\r\n onError: (err) => {\r\n errorListeners.current.forEach((fn) => fn(err));\r\n },\r\n });\r\n }\r\n\r\n const sdk = sdkRef.current;\r\n const presenterId = useMeetingStore(\r\n sdk.state,\r\n \"presenter\",\r\n (s) => s.presenterId,\r\n );\r\n const participants = useMeetingStore(\r\n sdk.state,\r\n \"participants\",\r\n (s) => s.participants,\r\n );\r\n const localParticipant = useMeetingStore(\r\n sdk.state,\r\n \"localParticipant\",\r\n (s) => s.localParticipant,\r\n );\r\n const messages = useMeetingStore(sdk.state, \"chat\", (s) =>\r\n s.getChatMessages(),\r\n );\r\n\r\n const value = useMemo<MeetingContextValue>(() => {\r\n if (!sdkRef.current) {\r\n sdkRef.current = new VideoSDKCore({\r\n onError: (err) => {\r\n errorListeners.current.forEach((fn) => fn(err));\r\n },\r\n });\r\n }\r\n\r\n return {\r\n sdk,\r\n\r\n join: (joinConfig: MeetingConfig) =>\r\n sdk.joinMeeting({\r\n ...config,\r\n ...joinConfig,\r\n }),\r\n leave: () => sdk.disconnect(),\r\n toggleMic: sdk.toggleMic.bind(sdk),\r\n toggleCam: sdk.toggleCam.bind(sdk),\r\n startScreenShare: sdk.startScreenShare.bind(sdk),\r\n stopScreenShare: sdk.stopScreenShare.bind(sdk),\r\n sendMessage: sdk.sendChatMessage.bind(sdk),\r\n\r\n meetingId: sdk.getMeetingId(),\r\n localParticipant,\r\n participants,\r\n messages,\r\n presenterId,\r\n usePubSub: (topic: PubSubTopic) => {\r\n if (topic !== \"SECURE_CHAT\") {\r\n throw new Error(`Unsupported PubSub argument: \"${topic}\"`);\r\n }\r\n return {\r\n messages: sdk.state.getChatMessages(),\r\n publish: sdk.sendChatMessage.bind(sdk),\r\n };\r\n },\r\n onError: (cb: (err: any) => void) => {\r\n errorListeners.current.add(cb);\r\n\r\n return () => {\r\n errorListeners.current.delete(cb);\r\n };\r\n },\r\n };\r\n }, [config, sdk, localParticipant, participants, messages, presenterId]);\r\n\r\n return (\r\n <MeetingContext.Provider value={value}>{children}</MeetingContext.Provider>\r\n );\r\n};\r\n\r\nexport const useMeetingContext = () => {\r\n const ctx = useContext(MeetingContext);\r\n if (!ctx)\r\n throw new Error(\"useMeetingContext must be used inside <MeetingProvider>\");\r\n return ctx;\r\n};\r\n","export const SDK_CONFIG = {\r\n wsUrl: \"wss://rust-video-server-sfyf.onrender.com/ws\",\r\n};\r\n","import {\r\n ChatMessage,\r\n Listener,\r\n Participant,\r\n ParticipantMedia,\r\n StateScope,\r\n} from \"../types/meeting\";\r\n\r\ntype LocalParticipantPatch = {\r\n id?: string;\r\n name?: string;\r\n media?: Partial<ParticipantMedia>;\r\n};\r\n\r\nexport class MeetingState {\r\n participants = new Map<string, Participant>();\r\n localParticipant: Participant | null = null;\r\n localStream: MediaStream | null = null;\r\n chatMessages = new Map<string, ChatMessage>();\r\n presenterId: string | null = null;\r\n\r\n private listeners = new Map<StateScope, Set<Listener>>();\r\n\r\n // ---- reactive system ----\r\n\r\n subscribe(scope: StateScope, fn: Listener): () => void {\r\n if (!this.listeners.has(scope)) {\r\n this.listeners.set(scope, new Set());\r\n }\r\n this.listeners.get(scope)!.add(fn);\r\n\r\n return () => {\r\n this.listeners.get(scope)?.delete(fn);\r\n };\r\n }\r\n\r\n notify(scope: StateScope) {\r\n this.listeners.get(scope)?.forEach((fn) => fn());\r\n }\r\n\r\n setPresenterId(id: string | null) {\r\n if (this.presenterId === id) return;\r\n this.presenterId = id;\r\n this.notify(\"presenter\");\r\n this.notify(\"participants\");\r\n }\r\n\r\n // ---- participants ----\r\n\r\n addParticipant(p: Participant) {\r\n if (this.participants.has(p.id)) return false;\r\n // Fix: Immutable Map update\r\n const next = new Map(this.participants);\r\n next.set(p.id, p);\r\n this.participants = next;\r\n\r\n this.notify(\"participants\");\r\n return true;\r\n }\r\n\r\n removeParticipant(id: string) {\r\n // Fix: Immutable Map update\r\n const next = new Map(this.participants);\r\n next.delete(id);\r\n this.participants = next;\r\n\r\n this.notify(\"participants\");\r\n }\r\n\r\n updateParticipantMedia(\r\n id: string,\r\n patch: Partial<NonNullable<Participant[\"media\"]>>,\r\n ) {\r\n const p = this.participants.get(id);\r\n if (!p) return;\r\n\r\n const updated: Participant = {\r\n ...p,\r\n media: {\r\n stream: null,\r\n screenStream: undefined,\r\n cameraTrack: undefined,\r\n screenTrack: undefined,\r\n audioTrack: undefined,\r\n micEnabled: true,\r\n camEnabled: true,\r\n isScreenSharing: false,\r\n ...p.media, // preserve existing media items if they happen to exist\r\n ...patch, // apply the incoming stream updates\r\n },\r\n };\r\n\r\n // Keep your clean immutable map update\r\n const next = new Map(this.participants);\r\n next.set(id, updated);\r\n this.participants = next;\r\n\r\n this.notify(`participant:${id}`);\r\n this.notify(\"participants\");\r\n }\r\n\r\n updateLocalParticipant(patch: LocalParticipantPatch) {\r\n const prev = this.localParticipant;\r\n\r\n if (!prev) {\r\n this.localParticipant = {\r\n id: patch.id ?? \"\",\r\n name: patch.name ?? \"\",\r\n media: {\r\n stream: patch.media?.stream ?? null, // ◄ FIX: Capture the stream from the patch here\r\n screenStream: patch.media?.screenStream,\r\n cameraTrack: patch.media?.cameraTrack,\r\n screenTrack: patch.media?.screenTrack,\r\n audioTrack: patch.media?.audioTrack,\r\n micEnabled: patch.media?.micEnabled ?? true,\r\n camEnabled: patch.media?.camEnabled ?? true,\r\n isScreenSharing: patch.media?.isScreenSharing ?? false,\r\n },\r\n };\r\n\r\n this.notify(\"localParticipant\");\r\n return;\r\n }\r\n\r\n const prevMedia = prev.media ?? {\r\n stream: null,\r\n screenStream: undefined,\r\n cameraTrack: undefined,\r\n screenTrack: undefined,\r\n audioTrack: undefined,\r\n micEnabled: true,\r\n camEnabled: true,\r\n isScreenSharing: false,\r\n };\r\n\r\n const nextMedia: ParticipantMedia = {\r\n stream: patch.media?.stream ?? prevMedia.stream,\r\n screenStream: patch.media?.screenStream ?? prevMedia.screenStream,\r\n cameraTrack: patch.media?.cameraTrack ?? prevMedia.cameraTrack,\r\n screenTrack: patch.media?.screenTrack ?? prevMedia.screenTrack,\r\n audioTrack: patch.media?.audioTrack ?? prevMedia.audioTrack,\r\n micEnabled: patch.media?.micEnabled ?? prevMedia.micEnabled,\r\n camEnabled: patch.media?.camEnabled ?? prevMedia.camEnabled,\r\n isScreenSharing:\r\n patch.media?.isScreenSharing ?? prevMedia.isScreenSharing,\r\n };\r\n\r\n this.localParticipant = {\r\n ...prev,\r\n id: patch.id ?? prev.id,\r\n name: patch.name ?? prev.name,\r\n media: nextMedia,\r\n };\r\n\r\n this.notify(\"localParticipant\");\r\n }\r\n\r\n // ---- chat ----\r\n\r\n addChatMessage(msg: ChatMessage) {\r\n this.chatMessages.set(msg.id, msg);\r\n this.notify(\"chat\");\r\n }\r\n\r\n getChatMessages() {\r\n return Array.from(this.chatMessages.values()).sort(\r\n (a, b) => a.timestamp - b.timestamp,\r\n );\r\n }\r\n\r\n clearChat() {\r\n this.chatMessages.clear();\r\n this.notify(\"chat\");\r\n }\r\n\r\n // ---- helpers ----\r\n\r\n getParticipants() {\r\n return Array.from(this.participants.values());\r\n }\r\n\r\n getParticipant(id: string) {\r\n return this.participants.get(id) ?? null;\r\n }\r\n\r\n resetRemoteState() {\r\n this.participants.clear();\r\n this.chatMessages.clear();\r\n this.presenterId = null;\r\n this.notify(\"participants\");\r\n this.notify(\"chat\");\r\n this.notify(\"presenter\");\r\n }\r\n}\r\n","import { SDK_CONFIG } from \"../config/ws\";\r\nimport {\r\n ChatInput,\r\n ChatMessage,\r\n Events,\r\n MeetingConfig,\r\n SDKError,\r\n} from \"../types/meeting\";\r\nimport { MeetingState } from \"./MeetingState\";\r\n\r\nexport class VideoSDKCore {\r\n private ws: WebSocket | null = null;\r\n private peers: Record<string, RTCPeerConnection> = {};\r\n private initiators = new Set<string>();\r\n\r\n private myId: string;\r\n private roomId: string | null = null;\r\n private localStream: MediaStream | null = null;\r\n private screenStream: MediaStream | null = null;\r\n private isScreenSharing = false;\r\n private screenSenders: Record<string, RTCRtpSender[]> = {};\r\n\r\n private pingInterval: any = null;\r\n private pendingIceCandidates: Record<string, RTCIceCandidateInit[]> = {};\r\n private reconnectAttempts = 0;\r\n private reconnectTimer?: number;\r\n private participantName = \"\";\r\n public readonly state: MeetingState;\r\n private joinResolver?: () => void;\r\n private joinRejecter?: (e: any) => void;\r\n private emitError(\r\n code: string,\r\n message: string,\r\n raw?: any,\r\n recoverable = true,\r\n ) {\r\n const err: SDKError = {\r\n code,\r\n message,\r\n raw,\r\n roomId: this.roomId,\r\n userId: this.myId,\r\n recoverable,\r\n };\r\n\r\n this.events.onError?.(err);\r\n\r\n this.joinRejecter?.(err);\r\n this.joinRejecter = undefined;\r\n\r\n console.error(\"[MeetingSDK Error]\", err);\r\n }\r\n\r\n constructor(\r\n private events: Events = {},\r\n private url: string = SDK_CONFIG.wsUrl,\r\n ) {\r\n this.state = new MeetingState();\r\n this.events = events;\r\n this.url = url;\r\n\r\n this.myId = localStorage.getItem(\"vsdk_id\") || crypto.randomUUID();\r\n\r\n localStorage.setItem(\"vsdk_id\", this.myId);\r\n }\r\n\r\n // ---------------- STREAM ----------------\r\n async initLocal(video: HTMLVideoElement, name: string) {\r\n this.participantName = name;\r\n\r\n if (!this.localStream) {\r\n this.localStream = await navigator.mediaDevices.getUserMedia({\r\n video: true,\r\n audio: true,\r\n });\r\n }\r\n\r\n video.srcObject = this.localStream;\r\n\r\n // Fix: Supply mandatory fields to satisfy the Participant type constraint\r\n this.state.updateLocalParticipant({\r\n id: this.myId,\r\n name: this.participantName,\r\n media: {\r\n stream: this.localStream,\r\n micEnabled: true,\r\n camEnabled: true,\r\n isScreenSharing: false,\r\n },\r\n });\r\n\r\n this.state.localStream = this.localStream;\r\n }\r\n\r\n // ---------------- CONNECT ----------------\r\n async connect(roomId: string, name: string) {\r\n this.roomId = roomId;\r\n\r\n this.reset();\r\n\r\n return new Promise<void>((resolve, reject) => {\r\n this.joinResolver = resolve;\r\n this.joinRejecter = reject;\r\n this.ws = new WebSocket(this.url);\r\n\r\n this.ws.onopen = () => {\r\n this.send({\r\n type: \"JOIN\",\r\n room_id: roomId,\r\n user_id: this.myId,\r\n sender_name: name,\r\n });\r\n };\r\n\r\n this.ws.onerror = (err) => {\r\n this.emitError(\"WS_ERROR\", \"WebSocket encountered an error\", err, true);\r\n };\r\n\r\n this.ws.onclose = (e) => {\r\n this.emitError(\r\n \"WS_CLOSED\",\r\n `Connection closed (${e.code}) ${e.reason || \"\"}`,\r\n e,\r\n true,\r\n );\r\n\r\n // If join never resolved, fail the promise\r\n this.joinRejecter?.({\r\n code: \"WS_CLOSED\",\r\n message: \"Connection closed before join completed\",\r\n raw: e,\r\n });\r\n\r\n this.joinRejecter = undefined;\r\n\r\n this.scheduleReconnect();\r\n };\r\n\r\n this.ws.onmessage = async (e) => {\r\n await this.handle(JSON.parse(e.data));\r\n };\r\n });\r\n }\r\n\r\n async joinMeeting(config: MeetingConfig) {\r\n const { roomId, name, audioMuted = false, videoMuted = false } = config;\r\n\r\n if (!roomId || !name) {\r\n throw new Error(\"roomId and name are required to join meeting\");\r\n }\r\n\r\n this.participantName = name;\r\n\r\n // Reuse existing stream if initLocal already configured it\r\n if (!this.localStream) {\r\n this.localStream = await navigator.mediaDevices.getUserMedia({\r\n video: true,\r\n audio: true,\r\n });\r\n }\r\n\r\n this.localStream.getAudioTracks().forEach((t) => {\r\n t.enabled = !audioMuted;\r\n });\r\n this.localStream.getVideoTracks().forEach((t) => {\r\n t.enabled = !videoMuted;\r\n });\r\n\r\n this.state.updateLocalParticipant({\r\n id: this.myId,\r\n name: this.participantName,\r\n media: {\r\n stream: this.localStream,\r\n micEnabled: !audioMuted,\r\n camEnabled: !videoMuted,\r\n isScreenSharing: false,\r\n },\r\n });\r\n\r\n this.state.localStream = this.localStream;\r\n\r\n await this.connect(roomId, name);\r\n }\r\n\r\n /** Expose the roomId without making it fully public */\r\n getMeetingId(): string | null {\r\n return this.roomId;\r\n }\r\n\r\n toggleMic() {\r\n const mediaState = this.state.localParticipant?.media;\r\n if (!mediaState) return;\r\n\r\n // Flip the state tracked in localParticipant.media\r\n const nextEnabled = !mediaState.micEnabled;\r\n\r\n // Mirror the state change down to the actual hardware tracks\r\n this.localStream\r\n ?.getAudioTracks()\r\n .forEach((t) => (t.enabled = nextEnabled));\r\n\r\n // Update state layer\r\n this.state.updateLocalParticipant({\r\n id: this.myId,\r\n name: this.participantName,\r\n media: {\r\n ...mediaState,\r\n micEnabled: nextEnabled,\r\n },\r\n });\r\n\r\n // Notify peers\r\n this.send({\r\n type: \"MEDIA_STATE\",\r\n kind: \"audio\",\r\n enabled: nextEnabled,\r\n });\r\n }\r\n\r\n toggleCam() {\r\n const mediaState = this.state.localParticipant?.media;\r\n if (!mediaState) return;\r\n\r\n // Flip the state tracked in localParticipant.media\r\n const nextEnabled = !mediaState.camEnabled;\r\n\r\n // Mirror the state change down to the actual hardware tracks\r\n this.localStream\r\n ?.getVideoTracks()\r\n .forEach((t) => (t.enabled = nextEnabled));\r\n\r\n // Update state layer\r\n this.state.updateLocalParticipant({\r\n id: this.myId,\r\n name: this.participantName,\r\n media: {\r\n ...mediaState,\r\n camEnabled: nextEnabled,\r\n },\r\n });\r\n\r\n // Notify peers\r\n this.send({\r\n type: \"MEDIA_STATE\",\r\n kind: \"video\",\r\n enabled: nextEnabled,\r\n });\r\n }\r\n\r\n private scheduleReconnect() {\r\n if (!this.roomId) return;\r\n\r\n const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);\r\n\r\n clearTimeout(this.reconnectTimer);\r\n\r\n this.reconnectTimer = window.setTimeout(async () => {\r\n try {\r\n await this.connect(this.roomId!, this.participantName);\r\n\r\n this.reconnectAttempts = 0;\r\n } catch {\r\n this.reconnectAttempts++;\r\n this.scheduleReconnect();\r\n }\r\n }, delay);\r\n }\r\n\r\n private startHeartbeat() {\r\n this.stopHeartbeat();\r\n\r\n this.pingInterval = setInterval(() => {\r\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\r\n\r\n this.send({\r\n type: \"PING\",\r\n client_ts: Date.now(),\r\n });\r\n }, 20000); // every 20s\r\n }\r\n private stopHeartbeat() {\r\n if (this.pingInterval) {\r\n clearInterval(this.pingInterval);\r\n this.pingInterval = null;\r\n }\r\n }\r\n\r\n // ---------------- RESET ----------------\r\n private reset() {\r\n Object.values(this.peers).forEach((pc) => pc.close());\r\n\r\n this.peers = {};\r\n this.initiators.clear();\r\n this.pendingIceCandidates = {};\r\n\r\n this.state.resetRemoteState();\r\n }\r\n\r\n // ---------------- HANDLE SIGNALS ----------------\r\n private async handle(msg: any) {\r\n if (msg.sender === this.myId) return;\r\n\r\n switch (msg.type) {\r\n case \"EXISTING_USERS\":\r\n if (msg.presenterId) {\r\n this.state.setPresenterId(msg.presenterId);\r\n\r\n // Trigger your event so the UI knows to render the stage\r\n this.events.onScreenShareStarted?.(msg.presenterId, null!);\r\n }\r\n\r\n for (const p of msg.participants || []) {\r\n if (!p?.id || p.id === this.myId) continue;\r\n this.state.addParticipant(p);\r\n this.events.onUserJoined?.(p);\r\n await this.createOffer(p.id);\r\n }\r\n break;\r\n\r\n case \"JOINED\": {\r\n this.startHeartbeat();\r\n this.joinResolver?.();\r\n this.joinResolver = undefined;\r\n this.joinRejecter = undefined;\r\n break;\r\n }\r\n case \"USER_JOINED\": {\r\n const p = msg.participant;\r\n\r\n if (!p?.id || p.id === this.myId) return;\r\n\r\n this.state.addParticipant(p);\r\n\r\n this.events.onUserJoined?.(p);\r\n await this.createOffer(p.id);\r\n\r\n break;\r\n }\r\n\r\n case \"OFFER\":\r\n await this.handleOffer(msg.payload, msg.sender);\r\n break;\r\n\r\n case \"ANSWER\": {\r\n const pc = this.peers[msg.sender];\r\n\r\n if (!pc) return;\r\n\r\n if (pc.signalingState !== \"have-local-offer\") {\r\n console.warn(\r\n `[Signaling] Unexpected ANSWER in state \"${pc.signalingState}\", ignoring`,\r\n );\r\n return;\r\n }\r\n\r\n try {\r\n await pc.setRemoteDescription({\r\n type: \"answer\",\r\n sdp: msg.payload,\r\n });\r\n\r\n await this.flushIce(msg.sender, pc);\r\n } catch (err) {\r\n console.error(\"[Signaling] Failed to apply answer:\", err);\r\n this.emitError(\r\n \"ANSWER_FAILED\",\r\n `Failed to apply answer from ${msg.sender}`,\r\n err,\r\n true,\r\n );\r\n }\r\n break;\r\n }\r\n\r\n case \"ICE\": {\r\n const candidate = JSON.parse(msg.payload);\r\n\r\n let pc = this.peers[msg.sender];\r\n\r\n if (!pc) {\r\n this.pendingIceCandidates[msg.sender] ??= [];\r\n this.pendingIceCandidates[msg.sender].push(candidate);\r\n break;\r\n }\r\n\r\n if (!pc.remoteDescription) {\r\n this.pendingIceCandidates[msg.sender] ??= [];\r\n this.pendingIceCandidates[msg.sender].push(candidate);\r\n break;\r\n }\r\n\r\n try {\r\n await pc.addIceCandidate(candidate);\r\n } catch (err) {\r\n console.warn(\"ICE error:\", err);\r\n }\r\n\r\n break;\r\n }\r\n\r\n case \"USER_LEFT\":\r\n const peerId = msg.participant.id;\r\n this.closePeer(peerId);\r\n\r\n this.state.removeParticipant(peerId);\r\n\r\n this.events.onUserLeft?.(peerId);\r\n\r\n break;\r\n\r\n case \"MEDIA_STATE_CHANGE\": {\r\n const peerId = msg.peerId;\r\n const { kind, enabled } = msg;\r\n\r\n // 1. Sync the app state layer for UI rendering components\r\n if (kind === \"audio\") {\r\n this.state.updateParticipantMedia(peerId, { micEnabled: enabled });\r\n this.events.onMicToggled?.(peerId, enabled);\r\n } else if (kind === \"video\") {\r\n this.state.updateParticipantMedia(peerId, { camEnabled: enabled });\r\n this.events.onCamToggled?.(peerId, enabled);\r\n }\r\n\r\n break;\r\n }\r\n\r\n case \"CHAT_MESSAGE\": {\r\n const newMsg = msg.data;\r\n\r\n if (newMsg.sender_id === this.myId) break; // already added optimistically\r\n this.state.addChatMessage({\r\n id: newMsg.id,\r\n text: newMsg.message,\r\n sender_id: newMsg.sender_id,\r\n sender_name: newMsg.sender_name,\r\n timestamp: new Date(newMsg.timestamp).getTime(),\r\n target: newMsg.target,\r\n });\r\n\r\n this.events.onChatMessage?.(msg);\r\n break;\r\n }\r\n case \"SCREEN_SHARE_START\": {\r\n const peerId = msg.peerId;\r\n\r\n this.state.updateParticipantMedia(peerId, {\r\n isScreenSharing: true,\r\n remoteScreenStreamId: msg.stream_id,\r\n });\r\n\r\n if (!this.state.presenterId) {\r\n this.state.setPresenterId(peerId);\r\n }\r\n\r\n // Fix: Use screenStream instead of the regular camera stream\r\n const screenStream =\r\n this.state.getParticipant(peerId)?.media?.screenStream;\r\n\r\n this.events.onScreenShareStarted?.(peerId, screenStream || null!);\r\n break;\r\n }\r\n\r\n case \"SCREEN_SHARE_STOP\": {\r\n const peerId = msg.peerId;\r\n this.state.updateParticipantMedia(peerId, { isScreenSharing: false });\r\n if (this.state.presenterId === peerId) {\r\n this.state.setPresenterId(null);\r\n }\r\n this.events.onScreenShareStopped?.(peerId);\r\n break;\r\n }\r\n case \"ERROR\": {\r\n const fatal = msg?.fatal === true;\r\n\r\n this.emitError(\r\n \"WS_ERROR\",\r\n msg?.message || \"Unknown error\",\r\n msg,\r\n !fatal,\r\n );\r\n\r\n if (fatal) {\r\n this.disconnect();\r\n }\r\n\r\n return;\r\n }\r\n }\r\n }\r\n\r\n // ---------------- PEER ----------------\r\n private createPeer(id: string) {\r\n if (!this.localStream) throw new Error(\"No local stream\");\r\n console.log(\r\n \"Adding tracks\",\r\n this.localStream.getTracks().map((t) => ({\r\n kind: t.kind,\r\n enabled: t.enabled,\r\n state: t.readyState,\r\n })),\r\n );\r\n\r\n const pc = new RTCPeerConnection({\r\n iceServers: [\r\n {\r\n urls: \"stun:stun.relay.metered.ca:80\",\r\n },\r\n {\r\n urls: \"turn:global.relay.metered.ca:80\",\r\n username: \"25aed888d2d360e9fae0e812\",\r\n credential: \"WPYstojO9Wf3+HsQ\",\r\n },\r\n {\r\n urls: \"turn:global.relay.metered.ca:80?transport=tcp\",\r\n username: \"25aed888d2d360e9fae0e812\",\r\n credential: \"WPYstojO9Wf3+HsQ\",\r\n },\r\n {\r\n urls: \"turn:global.relay.metered.ca:443\",\r\n username: \"25aed888d2d360e9fae0e812\",\r\n credential: \"WPYstojO9Wf3+HsQ\",\r\n },\r\n {\r\n urls: \"turns:global.relay.metered.ca:443?transport=tcp\",\r\n username: \"25aed888d2d360e9fae0e812\",\r\n credential: \"WPYstojO9Wf3+HsQ\",\r\n },\r\n ],\r\n });\r\n\r\n pc.ontrack = (event) => {\r\n console.log(`Track received: ${event.track.kind}`, {\r\n trackId: event.track.id,\r\n streamCount: event.streams.length,\r\n streamTrackCount: event.streams[0]?.getTracks().length,\r\n });\r\n const incomingStream = event.streams[0];\r\n const participant = this.state.getParticipant(id);\r\n\r\n const isScreenStream =\r\n incomingStream.id === participant?.media?.remoteScreenStreamId;\r\n\r\n if (isScreenStream) {\r\n const videoTrack =\r\n event.track.kind === \"video\"\r\n ? event.track\r\n : incomingStream.getVideoTracks()[0] ||\r\n participant?.media?.screenTrack;\r\n\r\n this.state.updateParticipantMedia(id, {\r\n screenStream: incomingStream,\r\n screenTrack: videoTrack,\r\n\r\n isScreenSharing: true,\r\n });\r\n\r\n if (!this.state.presenterId) {\r\n this.state.setPresenterId(id);\r\n }\r\n\r\n this.events.onScreenShareStarted?.(id, incomingStream);\r\n } else {\r\n this.state.updateParticipantMedia(id, {\r\n stream: incomingStream,\r\n cameraTrack: incomingStream.getVideoTracks()[0],\r\n audioTrack: incomingStream.getAudioTracks()[0],\r\n });\r\n this.events.onTrack?.(incomingStream, id);\r\n }\r\n };\r\n\r\n pc.onicecandidate = (e) => {\r\n if (!e.candidate) return;\r\n this.send({\r\n type: \"ICE\",\r\n payload: JSON.stringify(e.candidate),\r\n sender: this.myId,\r\n target: id,\r\n });\r\n };\r\n\r\n pc.oniceconnectionstatechange = () => {\r\n console.log(`ICE Connection State: ${pc.iceConnectionState}`);\r\n if (\r\n pc.iceConnectionState === \"failed\" ||\r\n pc.iceConnectionState === \"disconnected\"\r\n ) {\r\n // Trigger a UI notification to the user that the connection is unstable\r\n this.emitError(\r\n \"ICE_FAILED\",\r\n \"Connection failed. Please check your network or refresh.\",\r\n );\r\n }\r\n };\r\n\r\n pc.onconnectionstatechange = () => {\r\n if (pc.connectionState === \"failed\") {\r\n try {\r\n pc.restartIce();\r\n } catch {}\r\n }\r\n };\r\n\r\n this.localStream.getTracks().forEach((track) => {\r\n pc.addTrack(track, this.localStream!);\r\n });\r\n\r\n if (this.isScreenSharing && this.screenStream) {\r\n this.screenSenders[id] = [];\r\n this.screenStream.getTracks().forEach((track) => {\r\n const sender = pc.addTrack(track, this.screenStream!);\r\n this.screenSenders[id].push(sender);\r\n });\r\n }\r\n\r\n return pc;\r\n }\r\n\r\n // ---------------- OFFER ----------------\r\n private async createOffer(id: string, isRenegotiation = false) {\r\n if (!isRenegotiation && this.initiators.has(id)) {\r\n console.debug(\r\n `[Offer] Already initiating with ${id}, skipping duplicate`,\r\n );\r\n return;\r\n }\r\n\r\n if (isRenegotiation && this.peers[id]) {\r\n const pc = this.peers[id];\r\n if (pc.signalingState !== \"stable\") {\r\n console.warn(\r\n `[Offer] Cannot renegotiate: peer in state \"${pc.signalingState}\"`,\r\n );\r\n return;\r\n }\r\n }\r\n\r\n if (!isRenegotiation) {\r\n this.initiators.add(id);\r\n }\r\n if (!this.peers[id]) {\r\n this.peers[id] = this.createPeer(id);\r\n }\r\n\r\n const pc = this.peers[id];\r\n\r\n try {\r\n const offer = await pc.createOffer();\r\n await pc.setLocalDescription(offer);\r\n\r\n this.send({\r\n type: \"OFFER\",\r\n payload: offer.sdp,\r\n sender: this.myId,\r\n target: id,\r\n });\r\n\r\n console.debug(`[Offer] Sent to ${id}`);\r\n } catch (err) {\r\n console.error(`[Offer] Failed for ${id}:`, err);\r\n this.emitError(\r\n \"OFFER_FAILED\",\r\n `Failed to create offer for ${id}`,\r\n err,\r\n true,\r\n );\r\n }\r\n }\r\n\r\n // ---------------- ANSWER ----------------\r\n private async handleOffer(sdp: string, id: string) {\r\n if (this.initiators.has(id) && this.peers[id]) {\r\n const pc = this.peers[id];\r\n if (pc.signalingState !== \"stable\") {\r\n console.warn(\r\n `[Signaling] Offer collision with ${id}. We initiated, ignoring their offer.`,\r\n );\r\n return; // We initiated, so ignore their offer\r\n }\r\n }\r\n\r\n if (!this.peers[id]) {\r\n this.peers[id] = this.createPeer(id);\r\n }\r\n\r\n const pc = this.peers[id];\r\n\r\n try {\r\n // ✅ Only set remote description if we're not already in negotiation\r\n if (\r\n pc.signalingState !== \"stable\" &&\r\n pc.signalingState !== \"have-local-offer\"\r\n ) {\r\n console.warn(\r\n `[Signaling] Cannot accept OFFER in state \"${pc.signalingState}\"`,\r\n );\r\n return;\r\n }\r\n\r\n await pc.setRemoteDescription({\r\n type: \"offer\",\r\n sdp,\r\n });\r\n\r\n const pending = this.pendingIceCandidates[id] || [];\r\n\r\n for (const candidate of pending) {\r\n try {\r\n await pc.addIceCandidate(candidate);\r\n } catch (err) {\r\n console.warn(\"[ICE] Failed to add candidate:\", err);\r\n }\r\n }\r\n\r\n delete this.pendingIceCandidates[id];\r\n\r\n const answer = await pc.createAnswer();\r\n\r\n await pc.setLocalDescription(answer);\r\n await this.flushIce(id, pc);\r\n\r\n this.send({\r\n type: \"ANSWER\",\r\n payload: answer.sdp,\r\n sender: this.myId,\r\n target: id,\r\n });\r\n\r\n console.debug(`[Answer] Sent to ${id}`);\r\n } catch (err) {\r\n console.error(`[Signaling] Failed to handle OFFER from ${id}:`, err);\r\n this.emitError(\r\n \"OFFER_HANDLING_FAILED\",\r\n `Failed to handle offer from ${id}`,\r\n err,\r\n true,\r\n );\r\n }\r\n }\r\n\r\n // ---------------- CLEANUP ----------------\r\n private closePeer(id: string) {\r\n const pc = this.peers[id];\r\n\r\n if (!pc) return;\r\n\r\n pc.ontrack = null;\r\n pc.onicecandidate = null;\r\n pc.onconnectionstatechange = null;\r\n\r\n pc.close();\r\n\r\n delete this.peers[id];\r\n\r\n this.initiators.delete(id);\r\n\r\n this.state.removeParticipant(id);\r\n }\r\n\r\n async startScreenShare() {\r\n try {\r\n if (this.state.presenterId && this.state.presenterId !== this.myId) {\r\n throw new Error(\"Another user is already sharing their screen.\");\r\n }\r\n if (!navigator.mediaDevices?.getDisplayMedia) {\r\n throw new Error(\"Screen sharing not supported on this device\");\r\n }\r\n\r\n this.screenStream = await navigator.mediaDevices.getDisplayMedia({\r\n video: true,\r\n // audio: true,\r\n });\r\n\r\n this.isScreenSharing = true;\r\n\r\n this.state.updateLocalParticipant({\r\n media: {\r\n isScreenSharing: true,\r\n screenStream: this.screenStream,\r\n screenTrack: this.screenStream.getVideoTracks()[0],\r\n },\r\n });\r\n\r\n this.state.setPresenterId(this.myId);\r\n // Handle the user clicking browser's built-in \"Stop Sharing\" button\r\n this.screenStream.getVideoTracks()[0].onended = () => {\r\n this.stopScreenShare();\r\n };\r\n\r\n Object.entries(this.peers).forEach(([peerId, pc]) => {\r\n this.screenSenders[peerId] = [];\r\n this.screenStream!.getTracks().forEach((track) => {\r\n const sender = pc.addTrack(track, this.screenStream!);\r\n this.screenSenders[peerId].push(sender);\r\n });\r\n\r\n // Renegotiate peer connection descriptors to notify remote side of new track footprint\r\n this.createOffer(peerId, true);\r\n });\r\n\r\n this.send({\r\n type: \"SCREEN_SHARE_START\",\r\n sender: this.myId,\r\n room_id: this.roomId,\r\n stream_id: this.screenStream.id.replace(/[{}]/g, \"\"),\r\n });\r\n\r\n return this.screenStream;\r\n } catch (err: any) {\r\n this.emitError(\r\n \"SCREEN_SHARE_FAILED\",\r\n err?.message || \"Failed to start screen sharing\",\r\n err,\r\n true,\r\n );\r\n\r\n this.isScreenSharing = false;\r\n this.screenStream = null;\r\n throw err;\r\n }\r\n }\r\n\r\n stopScreenShare() {\r\n if (!this.screenStream) return;\r\n\r\n this.screenStream.getTracks().forEach((t) => t.stop());\r\n\r\n // Remove tracks cleanly from WebRTC channel pathways across your peers\r\n Object.entries(this.peers).forEach(([peerId, pc]) => {\r\n const senders = this.screenSenders[peerId] || [];\r\n senders.forEach((sender) => {\r\n try {\r\n pc.removeTrack(sender);\r\n } catch (err) {\r\n console.warn(err);\r\n }\r\n });\r\n delete this.screenSenders[peerId];\r\n\r\n // Renegotiate layout expectations to scale down stream bounds\r\n this.createOffer(peerId, true);\r\n });\r\n\r\n this.screenStream = null;\r\n this.isScreenSharing = false;\r\n\r\n this.state.updateLocalParticipant({\r\n media: {\r\n isScreenSharing: false,\r\n screenStream: null,\r\n screenTrack: undefined,\r\n },\r\n });\r\n\r\n if (this.state.presenterId === this.myId) {\r\n this.state.setPresenterId(null);\r\n }\r\n\r\n this.send({\r\n type: \"SCREEN_SHARE_STOP\",\r\n sender: this.myId,\r\n room_id: this.roomId,\r\n });\r\n }\r\n\r\n sendChatMessage(payload: ChatInput) {\r\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\r\n console.warn(\"WS not connected\");\r\n return;\r\n }\r\n\r\n if (!this.roomId) {\r\n console.warn(\"No roomId set\");\r\n return;\r\n }\r\n\r\n const isPrivate = !!payload?.target;\r\n\r\n const senderName = this.state.localParticipant?.name || \"Anonymous\";\r\n\r\n const msg: ChatMessage = {\r\n id: crypto.randomUUID(),\r\n sender_id: this.myId,\r\n sender_name: senderName,\r\n text: payload.message.trim(),\r\n timestamp: Date.now(),\r\n reply_to: payload.reply_to ?? null,\r\n target: payload.target ?? null,\r\n };\r\n\r\n // optimistic UI update\r\n this.state.addChatMessage(msg);\r\n\r\n // send protocol payload (clean + consistent)\r\n this.send({\r\n type: \"CHAT_MESSAGE\",\r\n message: payload.message.trim(),\r\n user_id: this.myId,\r\n sender_name: senderName,\r\n room_id: this.roomId,\r\n target: isPrivate ? (payload.target ?? null) : null,\r\n reply_to: payload.reply_to ?? null,\r\n\r\n client_ts: Date.now(),\r\n });\r\n }\r\n\r\n disconnect() {\r\n this.stopScreenShare();\r\n\r\n Object.values(this.peers).forEach((pc) => pc.close());\r\n this.peers = {};\r\n this.initiators.clear();\r\n\r\n this.stopHeartbeat();\r\n\r\n if (this.ws) {\r\n this.ws.close();\r\n this.ws = null;\r\n }\r\n\r\n if (this.localStream) {\r\n this.localStream.getTracks().forEach((track) => track.stop());\r\n this.localStream = null;\r\n }\r\n\r\n this.roomId = null;\r\n\r\n // Clear and notify\r\n this.state.localParticipant = null;\r\n this.state.notify(\"localParticipant\");\r\n\r\n this.state.participants.clear();\r\n this.state.notify(\"participants\");\r\n\r\n this.state.clearChat();\r\n this.state.setPresenterId(null);\r\n }\r\n\r\n private async flushIce(id: string, pc: RTCPeerConnection) {\r\n const pending = this.pendingIceCandidates[id];\r\n if (!pending?.length) return;\r\n\r\n for (const candidate of pending) {\r\n try {\r\n await pc.addIceCandidate(candidate);\r\n } catch (e) {\r\n console.warn(\"ICE flush error\", e);\r\n }\r\n }\r\n\r\n delete this.pendingIceCandidates[id];\r\n }\r\n\r\n private send(msg: any) {\r\n this.ws?.send(JSON.stringify(msg));\r\n }\r\n}\r\n","import { useEffect, useState } from \"react\";\r\nimport { StateScope } from \"../types/meeting\";\r\nimport { MeetingState } from \"../core/MeetingState\";\r\n\r\nexport function useMeetingStore<T>(\r\n stateManager: MeetingState,\r\n scope: StateScope,\r\n selector: (state: MeetingState) => T,\r\n): T {\r\n const [state, setState] = useState(() => selector(stateManager));\r\n\r\n useEffect(() => {\r\n // Update local react state whenever the SDK notifies this scope\r\n const unsubscribe = stateManager.subscribe(scope, () => {\r\n setState(selector(stateManager));\r\n });\r\n\r\n return unsubscribe;\r\n }, [stateManager, scope, selector]);\r\n\r\n return state;\r\n}\r\n","import { useEffect } from \"react\";\r\nimport { useMeetingContext } from \"./MeetingProvider\";\r\n\r\nexport const useMeeting = (handlers?: { onError?: (err: any) => void }) => {\r\n const ctx = useMeetingContext();\r\n\r\n useEffect(() => {\r\n if (!handlers?.onError) return;\r\n\r\n const unsubscribe = ctx.onError(handlers.onError);\r\n return unsubscribe;\r\n }, [handlers?.onError]);\r\n\r\n return ctx;\r\n};\r\n","import { useEffect, useState } from \"react\";\r\nimport { useMeetingContext } from \"./MeetingProvider\";\r\nimport { Participant } from \"../types/meeting\";\r\n\r\nexport const useParticipants = () => {\r\n const { sdk } = useMeetingContext();\r\n\r\n const [participants, setParticipants] = useState<Participant[]>(() =>\r\n sdk.state.getParticipants(),\r\n );\r\n\r\n useEffect(() => {\r\n const update = () => {\r\n setParticipants(sdk.state.getParticipants());\r\n };\r\n\r\n update();\r\n\r\n const unsub = sdk.state.subscribe(\"participants\", update);\r\n\r\n return unsub;\r\n }, [sdk]);\r\n\r\n return participants;\r\n};\r\n","import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport { useMeetingContext } from \"./MeetingProvider\";\r\nimport { Participant } from \"../types/meeting\";\r\n\r\nexport const useRemoteMedia = (participantId: string) => {\r\n const { sdk } = useMeetingContext();\r\n const [participant, setParticipant] = useState<Participant | null>(\r\n () => sdk.state.getParticipant(participantId) || null,\r\n );\r\n\r\n const buildMediaStream = (participant: Participant | null) => {\r\n if (!participant?.media) return null;\r\n\r\n const stream = new MediaStream();\r\n\r\n const videoTrack = participant.media.stream?.getVideoTracks?.()?.[0];\r\n const audioTrack = participant.media.stream?.getAudioTracks?.()?.[0];\r\n\r\n if (videoTrack) stream.addTrack(videoTrack);\r\n if (audioTrack) stream.addTrack(audioTrack);\r\n\r\n return stream;\r\n };\r\n\r\n useEffect(() => {\r\n const unsub = sdk.state.subscribe(`participant:${participantId}`, () => {\r\n const p = sdk.state.getParticipant(participantId);\r\n setParticipant(p ? { ...p } : null);\r\n });\r\n return unsub;\r\n }, [participantId, sdk]);\r\n\r\n // Video Callback Ref\r\n const videoRef = useCallback(\r\n (node: HTMLVideoElement | null) => {\r\n if (!node) return;\r\n\r\n const stream = participant?.media?.stream;\r\n if (!stream) return;\r\n\r\n // IMPORTANT: always force rebind (no conditions)\r\n node.srcObject = stream;\r\n\r\n node.muted = true;\r\n node.playsInline = true;\r\n node.autoplay = true;\r\n\r\n node.play().catch((err) => {\r\n // ignore autoplay restrictions\r\n console.log(\"can't play video:\", err);\r\n });\r\n },\r\n [participant?.media?.stream],\r\n );\r\n\r\n // Audio Callback Ref\r\n const audioRef = useCallback(\r\n (node: HTMLAudioElement | null) => {\r\n if (!node) return;\r\n\r\n const stream = participant?.media?.stream;\r\n if (!stream) return;\r\n\r\n node.srcObject = stream;\r\n\r\n node.muted = false;\r\n\r\n node.play().catch((err) => {\r\n console.log(\"can't play Audio:\", err);\r\n });\r\n },\r\n [participant?.media?.stream],\r\n );\r\n\r\n return {\r\n videoRef,\r\n audioRef,\r\n isCamActive: !!participant?.media?.camEnabled,\r\n isMicEnabled: !!participant?.media?.micEnabled,\r\n };\r\n};\r\n"],"mappings":"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","names":["import_react","import_react","peerId","pc","import_react","import_react","import_react","participant"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/react/useLocalParticipant.tsx","../src/react/MeetingProvider.tsx","../src/config/ws.ts","../src/core/MeetingState.ts","../src/core/VideoCore.ts","../src/react/useMeetingStore.ts","../src/react/useMeeting.ts","../src/react/useParticipants.ts","../src/react/useRemoteMedia.ts"],"sourcesContent":["export { useLocalParticipant } from \"./react/useLocalParticipant\";\r\n\r\n// Core exports\r\nexport { VideoSDKCore } from \"./core/VideoCore\";\r\nexport { MeetingState } from \"./core/MeetingState\";\r\nexport type { Participant, ChatInput } from \"./types/meeting\";\r\n\r\n// React hooks and components\r\nexport { MeetingProvider, useMeetingContext } from \"./react/MeetingProvider\";\r\nexport { useMeeting } from \"./react/useMeeting\";\r\nexport { useParticipants } from \"./react/useParticipants\";\r\nexport { useRemoteMedia } from \"./react/useRemoteMedia\";\r\n","import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport { useMeetingContext } from \"./MeetingProvider\";\r\nimport { Participant } from \"../types/meeting\";\r\n\r\nexport const useLocalParticipant = () => {\r\n const { sdk } = useMeetingContext();\r\n\r\n // Guard the initial state to ensure it matches Participant | null\r\n const [localParticipant, setLocalParticipant] = useState<Participant | null>(\r\n () => {\r\n const current = sdk.state.localParticipant;\r\n return current && current.id ? (current as Participant) : null;\r\n },\r\n );\r\n\r\n useEffect(() => {\r\n const unsubscribe = sdk.state.subscribe(\"localParticipant\", () => {\r\n const current = sdk.state.localParticipant;\r\n\r\n // Safe Type-Guard: Only update if the object has a finalized id\r\n if (current && current.id) {\r\n setLocalParticipant({ ...current } as Participant);\r\n } else {\r\n setLocalParticipant(null);\r\n }\r\n });\r\n\r\n return unsubscribe;\r\n }, [sdk]);\r\n\r\n const lastStreamRef = useRef<MediaStream | null>(null);\r\n\r\n const videoRef = useCallback(\r\n (video: HTMLVideoElement | null) => {\r\n if (!video) return;\r\n\r\n const stream = localParticipant?.media?.stream;\r\n if (!stream) return;\r\n\r\n if (lastStreamRef.current === stream) return;\r\n lastStreamRef.current = stream;\r\n\r\n video.srcObject = stream;\r\n video.autoplay = true;\r\n video.playsInline = true;\r\n\r\n video.play().catch((err) => {\r\n console.warn(`Autoplay failed for local view:`, err);\r\n });\r\n },\r\n [localParticipant?.media?.stream],\r\n );\r\n\r\n return {\r\n participant: localParticipant,\r\n videoRef,\r\n };\r\n};\r\n","import React, { createContext, useContext, useMemo, useRef } from \"react\";\r\nimport { VideoSDKCore } from \"../core/VideoCore\";\r\nimport {\r\n ChatInput,\r\n ChatMessage,\r\n MeetingConfig,\r\n Participant,\r\n PubSubTopic,\r\n} from \"../types/meeting\";\r\nimport { useMeetingStore } from \"./useMeetingStore\";\r\n\r\ntype PubSubHandle = {\r\n messages: ChatMessage[];\r\n publish: (input: ChatInput) => void;\r\n};\r\n\r\ntype MeetingContextValue = {\r\n sdk: VideoSDKCore;\r\n\r\n join: (config: MeetingConfig) => Promise<void>;\r\n leave: () => void;\r\n\r\n toggleMic: () => void;\r\n toggleCam: () => void;\r\n\r\n startScreenShare: () => Promise<MediaStream>;\r\n stopScreenShare: () => void;\r\n\r\n sendMessage: (input: ChatInput) => void;\r\n\r\n meetingId: string | null;\r\n localParticipant: Participant | null;\r\n participants: Map<string, Participant>;\r\n messages: ChatMessage[];\r\n presenterId: string | null;\r\n usePubSub: (topic: PubSubTopic) => PubSubHandle;\r\n onError: (cb: (err: any) => void) => () => void;\r\n};\r\n\r\nconst MeetingContext = createContext<MeetingContextValue | null>(null);\r\n\r\nexport const MeetingProvider = ({\r\n config,\r\n children,\r\n}: {\r\n config: MeetingConfig;\r\n children: React.ReactNode;\r\n}) => {\r\n const sdkRef = useRef<VideoSDKCore | null>(null);\r\n const errorListeners = useRef(new Set<(err: any) => void>());\r\n if (!sdkRef.current) {\r\n sdkRef.current = new VideoSDKCore({\r\n onError: (err) => {\r\n errorListeners.current.forEach((fn) => fn(err));\r\n },\r\n });\r\n }\r\n\r\n const sdk = sdkRef.current;\r\n const presenterId = useMeetingStore(\r\n sdk.state,\r\n \"presenter\",\r\n (s) => s.presenterId,\r\n );\r\n const participants = useMeetingStore(\r\n sdk.state,\r\n \"participants\",\r\n (s) => s.participants,\r\n );\r\n const localParticipant = useMeetingStore(\r\n sdk.state,\r\n \"localParticipant\",\r\n (s) => s.localParticipant,\r\n );\r\n const messages = useMeetingStore(sdk.state, \"chat\", (s) =>\r\n s.getChatMessages(),\r\n );\r\n\r\n const value = useMemo<MeetingContextValue>(() => {\r\n if (!sdkRef.current) {\r\n sdkRef.current = new VideoSDKCore({\r\n onError: (err) => {\r\n errorListeners.current.forEach((fn) => fn(err));\r\n },\r\n });\r\n }\r\n\r\n return {\r\n sdk,\r\n\r\n join: (joinConfig: MeetingConfig) =>\r\n sdk.joinMeeting({\r\n ...config,\r\n ...joinConfig,\r\n }),\r\n leave: () => sdk.disconnect(),\r\n toggleMic: sdk.toggleMic.bind(sdk),\r\n toggleCam: sdk.toggleCam.bind(sdk),\r\n startScreenShare: sdk.startScreenShare.bind(sdk),\r\n stopScreenShare: sdk.stopScreenShare.bind(sdk),\r\n sendMessage: sdk.sendChatMessage.bind(sdk),\r\n\r\n meetingId: sdk.getMeetingId(),\r\n localParticipant,\r\n participants,\r\n messages,\r\n presenterId,\r\n usePubSub: (topic: PubSubTopic) => {\r\n if (topic !== \"SECURE_CHAT\") {\r\n throw new Error(`Unsupported PubSub argument: \"${topic}\"`);\r\n }\r\n return {\r\n messages: sdk.state.getChatMessages(),\r\n publish: sdk.sendChatMessage.bind(sdk),\r\n };\r\n },\r\n onError: (cb: (err: any) => void) => {\r\n errorListeners.current.add(cb);\r\n\r\n return () => {\r\n errorListeners.current.delete(cb);\r\n };\r\n },\r\n };\r\n }, [config, sdk, localParticipant, participants, messages, presenterId]);\r\n\r\n return (\r\n <MeetingContext.Provider value={value}>{children}</MeetingContext.Provider>\r\n );\r\n};\r\n\r\nexport const useMeetingContext = () => {\r\n const ctx = useContext(MeetingContext);\r\n if (!ctx)\r\n throw new Error(\"useMeetingContext must be used inside <MeetingProvider>\");\r\n return ctx;\r\n};\r\n","export const SDK_CONFIG = {\r\n wsUrl: \"wss://rust-video-server-sfyf.onrender.com/ws\",\r\n};\r\n","import {\r\n ChatMessage,\r\n Listener,\r\n Participant,\r\n ParticipantMedia,\r\n StateScope,\r\n} from \"../types/meeting\";\r\n\r\ntype LocalParticipantPatch = {\r\n id?: string;\r\n name?: string;\r\n media?: Partial<ParticipantMedia>;\r\n};\r\n\r\nexport class MeetingState {\r\n participants = new Map<string, Participant>();\r\n localParticipant: Participant | null = null;\r\n localStream: MediaStream | null = null;\r\n chatMessages = new Map<string, ChatMessage>();\r\n presenterId: string | null = null;\r\n\r\n private listeners = new Map<StateScope, Set<Listener>>();\r\n\r\n // ---- reactive system ----\r\n\r\n subscribe(scope: StateScope, fn: Listener): () => void {\r\n if (!this.listeners.has(scope)) {\r\n this.listeners.set(scope, new Set());\r\n }\r\n this.listeners.get(scope)!.add(fn);\r\n\r\n return () => {\r\n this.listeners.get(scope)?.delete(fn);\r\n };\r\n }\r\n\r\n notify(scope: StateScope) {\r\n this.listeners.get(scope)?.forEach((fn) => fn());\r\n }\r\n\r\n setPresenterId(id: string | null) {\r\n if (this.presenterId === id) return;\r\n this.presenterId = id;\r\n this.notify(\"presenter\");\r\n this.notify(\"participants\");\r\n }\r\n\r\n // ---- participants ----\r\n\r\n addParticipant(p: Participant) {\r\n if (this.participants.has(p.id)) return false;\r\n // Fix: Immutable Map update\r\n const next = new Map(this.participants);\r\n next.set(p.id, p);\r\n this.participants = next;\r\n\r\n this.notify(\"participants\");\r\n return true;\r\n }\r\n\r\n removeParticipant(id: string) {\r\n // Fix: Immutable Map update\r\n const next = new Map(this.participants);\r\n next.delete(id);\r\n this.participants = next;\r\n\r\n this.notify(\"participants\");\r\n }\r\n\r\n updateParticipantMedia(\r\n id: string,\r\n patch: Partial<NonNullable<Participant[\"media\"]>>,\r\n ) {\r\n const p = this.participants.get(id);\r\n if (!p) return;\r\n\r\n const updated: Participant = {\r\n ...p,\r\n media: {\r\n stream: null,\r\n screenStream: undefined,\r\n cameraTrack: undefined,\r\n screenTrack: undefined,\r\n audioTrack: undefined,\r\n micEnabled: true,\r\n camEnabled: true,\r\n isScreenSharing: false,\r\n ...p.media, // preserve existing media items if they happen to exist\r\n ...patch, // apply the incoming stream updates\r\n },\r\n };\r\n\r\n // Keep your clean immutable map update\r\n const next = new Map(this.participants);\r\n next.set(id, updated);\r\n this.participants = next;\r\n\r\n this.notify(`participant:${id}`);\r\n this.notify(\"participants\");\r\n }\r\n\r\n updateLocalParticipant(patch: LocalParticipantPatch) {\r\n const prev = this.localParticipant;\r\n\r\n if (!prev) {\r\n this.localParticipant = {\r\n id: patch.id ?? \"\",\r\n name: patch.name ?? \"\",\r\n media: {\r\n stream: patch.media?.stream ?? null, // ◄ FIX: Capture the stream from the patch here\r\n screenStream: patch.media?.screenStream,\r\n cameraTrack: patch.media?.cameraTrack,\r\n screenTrack: patch.media?.screenTrack,\r\n audioTrack: patch.media?.audioTrack,\r\n micEnabled: patch.media?.micEnabled ?? true,\r\n camEnabled: patch.media?.camEnabled ?? true,\r\n isScreenSharing: patch.media?.isScreenSharing ?? false,\r\n },\r\n };\r\n\r\n this.notify(\"localParticipant\");\r\n return;\r\n }\r\n\r\n const prevMedia = prev.media ?? {\r\n stream: null,\r\n screenStream: undefined,\r\n cameraTrack: undefined,\r\n screenTrack: undefined,\r\n audioTrack: undefined,\r\n micEnabled: true,\r\n camEnabled: true,\r\n isScreenSharing: false,\r\n };\r\n\r\n const nextMedia: ParticipantMedia = {\r\n stream: patch.media?.stream ?? prevMedia.stream,\r\n screenStream: patch.media?.screenStream ?? prevMedia.screenStream,\r\n cameraTrack: patch.media?.cameraTrack ?? prevMedia.cameraTrack,\r\n screenTrack: patch.media?.screenTrack ?? prevMedia.screenTrack,\r\n audioTrack: patch.media?.audioTrack ?? prevMedia.audioTrack,\r\n micEnabled: patch.media?.micEnabled ?? prevMedia.micEnabled,\r\n camEnabled: patch.media?.camEnabled ?? prevMedia.camEnabled,\r\n isScreenSharing:\r\n patch.media?.isScreenSharing ?? prevMedia.isScreenSharing,\r\n };\r\n\r\n this.localParticipant = {\r\n ...prev,\r\n id: patch.id ?? prev.id,\r\n name: patch.name ?? prev.name,\r\n media: nextMedia,\r\n };\r\n\r\n this.notify(\"localParticipant\");\r\n }\r\n\r\n // ---- chat ----\r\n\r\n addChatMessage(msg: ChatMessage) {\r\n this.chatMessages.set(msg.id, msg);\r\n this.notify(\"chat\");\r\n }\r\n\r\n getChatMessages() {\r\n return Array.from(this.chatMessages.values()).sort(\r\n (a, b) => a.timestamp - b.timestamp,\r\n );\r\n }\r\n\r\n clearChat() {\r\n this.chatMessages.clear();\r\n this.notify(\"chat\");\r\n }\r\n\r\n // ---- helpers ----\r\n\r\n getParticipants() {\r\n return Array.from(this.participants.values());\r\n }\r\n\r\n getParticipant(id: string) {\r\n return this.participants.get(id) ?? null;\r\n }\r\n\r\n resetRemoteState() {\r\n this.participants.clear();\r\n this.chatMessages.clear();\r\n this.presenterId = null;\r\n this.notify(\"participants\");\r\n this.notify(\"chat\");\r\n this.notify(\"presenter\");\r\n }\r\n}\r\n","import { SDK_CONFIG } from \"../config/ws\";\r\nimport {\r\n ChatInput,\r\n ChatMessage,\r\n Events,\r\n MeetingConfig,\r\n SDKError,\r\n} from \"../types/meeting\";\r\nimport { MeetingState } from \"./MeetingState\";\r\n\r\nexport class VideoSDKCore {\r\n private ws: WebSocket | null = null;\r\n private peers: Record<string, RTCPeerConnection> = {};\r\n private initiators = new Set<string>();\r\n private lastPong = Date.now();\r\n private intentionalDisconnect = false;\r\n\r\n private myId: string;\r\n private roomId: string | null = null;\r\n private localStream: MediaStream | null = null;\r\n private screenStream: MediaStream | null = null;\r\n private isScreenSharing = false;\r\n private screenSenders: Record<string, RTCRtpSender[]> = {};\r\n\r\n private pingInterval: any = null;\r\n private pendingIceCandidates: Record<string, RTCIceCandidateInit[]> = {};\r\n private reconnectAttempts = 0;\r\n private reconnectTimer?: number;\r\n private participantName = \"\";\r\n public readonly state: MeetingState;\r\n private joinResolver?: () => void;\r\n private joinRejecter?: (e: any) => void;\r\n private emitError(\r\n code: string,\r\n message: string,\r\n raw?: any,\r\n recoverable = true,\r\n ) {\r\n const err: SDKError = {\r\n code,\r\n message,\r\n raw,\r\n roomId: this.roomId,\r\n userId: this.myId,\r\n recoverable,\r\n };\r\n\r\n this.events.onError?.(err);\r\n\r\n this.joinRejecter?.(err);\r\n this.joinRejecter = undefined;\r\n\r\n console.error(\"[MeetingSDK Error]\", err);\r\n }\r\n\r\n constructor(\r\n private events: Events = {},\r\n private url: string = SDK_CONFIG.wsUrl,\r\n ) {\r\n this.state = new MeetingState();\r\n this.events = events;\r\n this.url = url;\r\n\r\n this.myId = localStorage.getItem(\"vsdk_id\") || crypto.randomUUID();\r\n\r\n localStorage.setItem(\"vsdk_id\", this.myId);\r\n }\r\n\r\n // ---------------- STREAM ----------------\r\n async initLocal(video: HTMLVideoElement, name: string) {\r\n this.participantName = name;\r\n\r\n if (!this.localStream) {\r\n this.localStream = await navigator.mediaDevices.getUserMedia({\r\n video: true,\r\n audio: true,\r\n });\r\n }\r\n\r\n video.srcObject = this.localStream;\r\n\r\n // Fix: Supply mandatory fields to satisfy the Participant type constraint\r\n this.state.updateLocalParticipant({\r\n id: this.myId,\r\n name: this.participantName,\r\n media: {\r\n stream: this.localStream,\r\n micEnabled: true,\r\n camEnabled: true,\r\n isScreenSharing: false,\r\n },\r\n });\r\n\r\n this.state.localStream = this.localStream;\r\n }\r\n\r\n // ---------------- CONNECT ----------------\r\n async connect(roomId: string, name: string) {\r\n this.roomId = roomId;\r\n\r\n this.reset();\r\n\r\n return new Promise<void>((resolve, reject) => {\r\n this.joinResolver = resolve;\r\n this.joinRejecter = reject;\r\n this.ws = new WebSocket(this.url);\r\n\r\n this.ws.onopen = () => {\r\n this.send({\r\n type: \"JOIN\",\r\n room_id: roomId,\r\n user_id: this.myId,\r\n sender_name: name,\r\n });\r\n };\r\n\r\n this.ws.onerror = (err) => {\r\n this.emitError(\"WS_ERROR\", \"WebSocket encountered an error\", err, true);\r\n };\r\n\r\n this.ws.onclose = (e) => {\r\n this.joinRejecter?.({\r\n code: \"WS_CLOSED\",\r\n message: \"Connection closed before join completed\",\r\n raw: e,\r\n });\r\n\r\n this.joinRejecter = undefined;\r\n\r\n if (this.intentionalDisconnect) {\r\n return; // do NOT reconnect\r\n }\r\n\r\n if (e.code === 1000 || e.code === 1001) {\r\n return;\r\n }\r\n\r\n this.scheduleReconnect();\r\n };\r\n\r\n this.ws.onmessage = async (e) => {\r\n await this.handle(JSON.parse(e.data));\r\n };\r\n });\r\n }\r\n\r\n async joinMeeting(config: MeetingConfig) {\r\n const { roomId, name, audioMuted = false, videoMuted = false } = config;\r\n\r\n if (!roomId || !name) {\r\n throw new Error(\"roomId and name are required to join meeting\");\r\n }\r\n\r\n this.participantName = name;\r\n\r\n // Reuse existing stream if initLocal already configured it\r\n if (!this.localStream) {\r\n this.localStream = await navigator.mediaDevices.getUserMedia({\r\n video: true,\r\n audio: true,\r\n });\r\n }\r\n\r\n this.localStream.getAudioTracks().forEach((t) => {\r\n t.enabled = !audioMuted;\r\n });\r\n this.localStream.getVideoTracks().forEach((t) => {\r\n t.enabled = !videoMuted;\r\n });\r\n\r\n this.state.updateLocalParticipant({\r\n id: this.myId,\r\n name: this.participantName,\r\n media: {\r\n stream: this.localStream,\r\n micEnabled: !audioMuted,\r\n camEnabled: !videoMuted,\r\n isScreenSharing: false,\r\n },\r\n });\r\n\r\n this.state.localStream = this.localStream;\r\n\r\n await this.connect(roomId, name);\r\n }\r\n\r\n /** Expose the roomId without making it fully public */\r\n getMeetingId(): string | null {\r\n return this.roomId;\r\n }\r\n\r\n toggleMic() {\r\n const mediaState = this.state.localParticipant?.media;\r\n if (!mediaState) return;\r\n\r\n const nextEnabled = !mediaState.micEnabled;\r\n\r\n this.localStream\r\n ?.getAudioTracks()\r\n .forEach((t) => (t.enabled = nextEnabled));\r\n\r\n // Update state layer\r\n this.state.updateLocalParticipant({\r\n id: this.myId,\r\n name: this.participantName,\r\n media: {\r\n ...mediaState,\r\n micEnabled: nextEnabled,\r\n },\r\n });\r\n\r\n // Notify peers\r\n this.send({\r\n type: \"MEDIA_STATE\",\r\n kind: \"audio\",\r\n enabled: nextEnabled,\r\n });\r\n }\r\n\r\n toggleCam() {\r\n const mediaState = this.state.localParticipant?.media;\r\n if (!mediaState) return;\r\n\r\n const nextEnabled = !mediaState.camEnabled;\r\n\r\n this.localStream\r\n ?.getVideoTracks()\r\n .forEach((t) => (t.enabled = nextEnabled));\r\n\r\n // Update state layer\r\n this.state.updateLocalParticipant({\r\n id: this.myId,\r\n name: this.participantName,\r\n media: {\r\n ...mediaState,\r\n camEnabled: nextEnabled,\r\n },\r\n });\r\n\r\n // Notify peers\r\n this.send({\r\n type: \"MEDIA_STATE\",\r\n kind: \"video\",\r\n enabled: nextEnabled,\r\n });\r\n }\r\n\r\n private scheduleReconnect() {\r\n if (!this.roomId) return;\r\n\r\n const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);\r\n\r\n clearTimeout(this.reconnectTimer);\r\n\r\n this.reconnectTimer = window.setTimeout(async () => {\r\n try {\r\n await this.connect(this.roomId!, this.participantName);\r\n\r\n this.reconnectAttempts = 0;\r\n } catch {\r\n this.reconnectAttempts++;\r\n this.scheduleReconnect();\r\n }\r\n }, delay);\r\n }\r\n\r\n private startHeartbeat() {\r\n this.stopHeartbeat();\r\n\r\n this.pingInterval = setInterval(() => {\r\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\r\n\r\n this.send({\r\n type: \"PING\",\r\n client_ts: Date.now(),\r\n });\r\n }, 20000); // every 20s\r\n }\r\n private stopHeartbeat() {\r\n if (this.pingInterval) {\r\n clearInterval(this.pingInterval);\r\n this.pingInterval = null;\r\n }\r\n }\r\n\r\n // ---------------- RESET ----------------\r\n private reset() {\r\n Object.values(this.peers).forEach((pc) => pc.close());\r\n\r\n this.peers = {};\r\n this.initiators.clear();\r\n this.pendingIceCandidates = {};\r\n\r\n this.state.resetRemoteState();\r\n }\r\n\r\n // ---------------- HANDLE SIGNALS ----------------\r\n private async handle(msg: any) {\r\n if (msg.sender === this.myId) return;\r\n\r\n switch (msg.type) {\r\n case \"PONG\":\r\n this.lastPong = Date.now();\r\n break;\r\n case \"OFFER\":\r\n await this.handleOffer(msg.payload, msg.sender);\r\n break;\r\n\r\n case \"ANSWER\": {\r\n const pc = this.peers[msg.sender];\r\n\r\n if (!pc) return;\r\n\r\n if (pc.signalingState !== \"have-local-offer\") {\r\n console.warn(\r\n `[Signaling] Unexpected ANSWER in state \"${pc.signalingState}\", ignoring`,\r\n );\r\n return;\r\n }\r\n\r\n try {\r\n await pc.setRemoteDescription({\r\n type: \"answer\",\r\n sdp: msg.payload,\r\n });\r\n\r\n await this.flushIce(msg.sender, pc);\r\n } catch (err) {\r\n console.error(\"[Signaling] Failed to apply answer:\", err);\r\n this.emitError(\r\n \"ANSWER_FAILED\",\r\n `Failed to apply answer from ${msg.sender}`,\r\n err,\r\n true,\r\n );\r\n }\r\n break;\r\n }\r\n case \"ICE\": {\r\n const candidate = JSON.parse(msg.payload);\r\n\r\n let pc = this.peers[msg.sender];\r\n\r\n if (!pc) {\r\n this.pendingIceCandidates[msg.sender] ??= [];\r\n this.pendingIceCandidates[msg.sender].push(candidate);\r\n break;\r\n }\r\n\r\n if (!pc.remoteDescription) {\r\n this.pendingIceCandidates[msg.sender] ??= [];\r\n this.pendingIceCandidates[msg.sender].push(candidate);\r\n break;\r\n }\r\n\r\n try {\r\n await pc.addIceCandidate(candidate);\r\n } catch (err) {\r\n console.warn(\"ICE error:\", err);\r\n }\r\n\r\n break;\r\n }\r\n case \"EXISTING_USERS\":\r\n if (msg.presenterId) {\r\n this.state.setPresenterId(msg.presenterId);\r\n\r\n // Trigger your event so the UI knows to render the stage\r\n this.events.onScreenShareStarted?.(msg.presenterId, null!);\r\n }\r\n\r\n for (const p of msg.participants || []) {\r\n if (!p?.id || p.id === this.myId) continue;\r\n this.state.addParticipant(p);\r\n this.events.onUserJoined?.(p);\r\n await this.createOffer(p.id);\r\n }\r\n break;\r\n\r\n case \"JOINED\": {\r\n this.intentionalDisconnect = false;\r\n this.reconnectAttempts = 0;\r\n this.startHeartbeat();\r\n this.joinResolver?.();\r\n this.joinResolver = undefined;\r\n this.joinRejecter = undefined;\r\n break;\r\n }\r\n case \"USER_JOINED\": {\r\n const p = msg.participant;\r\n\r\n if (!p?.id || p.id === this.myId) return;\r\n\r\n this.state.addParticipant(p);\r\n\r\n this.events.onUserJoined?.(p);\r\n await this.createOffer(p.id);\r\n\r\n break;\r\n }\r\n\r\n case \"USER_LEFT\":\r\n const peerId = msg.participant.id;\r\n this.closePeer(peerId);\r\n\r\n this.state.removeParticipant(peerId);\r\n\r\n this.events.onUserLeft?.(peerId);\r\n\r\n break;\r\n\r\n case \"MEDIA_STATE_CHANGE\": {\r\n const peerId = msg.peerId;\r\n const { kind, enabled } = msg;\r\n\r\n // 1. Sync the app state layer for UI rendering components\r\n if (kind === \"audio\") {\r\n this.state.updateParticipantMedia(peerId, { micEnabled: enabled });\r\n this.events.onMicToggled?.(peerId, enabled);\r\n } else if (kind === \"video\") {\r\n this.state.updateParticipantMedia(peerId, { camEnabled: enabled });\r\n this.events.onCamToggled?.(peerId, enabled);\r\n }\r\n\r\n break;\r\n }\r\n\r\n case \"CHAT_MESSAGE\": {\r\n const newMsg = msg.data;\r\n\r\n if (newMsg.sender_id === this.myId) break; // already added optimistically\r\n this.state.addChatMessage({\r\n id: newMsg.id,\r\n text: newMsg.message,\r\n sender_id: newMsg.sender_id,\r\n sender_name: newMsg.sender_name,\r\n timestamp: new Date(newMsg.timestamp).getTime(),\r\n target: newMsg.target,\r\n });\r\n\r\n this.events.onChatMessage?.(msg);\r\n break;\r\n }\r\n case \"SCREEN_SHARE_START\": {\r\n const peerId = msg.peerId;\r\n\r\n this.state.updateParticipantMedia(peerId, {\r\n isScreenSharing: true,\r\n remoteScreenStreamId: msg.stream_id,\r\n });\r\n\r\n if (!this.state.presenterId) {\r\n this.state.setPresenterId(peerId);\r\n }\r\n\r\n // Fix: Use screenStream instead of the regular camera stream\r\n const screenStream =\r\n this.state.getParticipant(peerId)?.media?.screenStream;\r\n\r\n this.events.onScreenShareStarted?.(peerId, screenStream || null!);\r\n break;\r\n }\r\n\r\n case \"SCREEN_SHARE_STOP\": {\r\n const peerId = msg.peerId;\r\n this.state.updateParticipantMedia(peerId, { isScreenSharing: false });\r\n if (this.state.presenterId === peerId) {\r\n this.state.setPresenterId(null);\r\n }\r\n this.events.onScreenShareStopped?.(peerId);\r\n break;\r\n }\r\n case \"ERROR\": {\r\n const fatal = msg?.fatal === true;\r\n\r\n this.emitError(\r\n \"WS_ERROR\",\r\n msg?.message || \"Unknown error\",\r\n msg,\r\n !fatal,\r\n );\r\n\r\n if (fatal) {\r\n this.disconnect();\r\n }\r\n\r\n return;\r\n }\r\n }\r\n }\r\n\r\n // ---------------- PEER ----------------\r\n private createPeer(id: string) {\r\n if (!this.localStream) throw new Error(\"No local stream\");\r\n console.log(\r\n \"Adding tracks\",\r\n this.localStream.getTracks().map((t) => ({\r\n kind: t.kind,\r\n enabled: t.enabled,\r\n state: t.readyState,\r\n })),\r\n );\r\n\r\n const pc = new RTCPeerConnection({\r\n iceServers: [\r\n {\r\n urls: \"stun:stun.relay.metered.ca:80\",\r\n },\r\n {\r\n urls: \"turn:global.relay.metered.ca:80\",\r\n username: \"25aed888d2d360e9fae0e812\",\r\n credential: \"WPYstojO9Wf3+HsQ\",\r\n },\r\n {\r\n urls: \"turn:global.relay.metered.ca:80?transport=tcp\",\r\n username: \"25aed888d2d360e9fae0e812\",\r\n credential: \"WPYstojO9Wf3+HsQ\",\r\n },\r\n {\r\n urls: \"turn:global.relay.metered.ca:443\",\r\n username: \"25aed888d2d360e9fae0e812\",\r\n credential: \"WPYstojO9Wf3+HsQ\",\r\n },\r\n {\r\n urls: \"turns:global.relay.metered.ca:443?transport=tcp\",\r\n username: \"25aed888d2d360e9fae0e812\",\r\n credential: \"WPYstojO9Wf3+HsQ\",\r\n },\r\n ],\r\n });\r\n\r\n pc.ontrack = (event) => {\r\n console.log(`Track received: ${event.track.kind}`, {\r\n trackId: event.track.id,\r\n streamCount: event.streams.length,\r\n streamTrackCount: event.streams[0]?.getTracks().length,\r\n });\r\n const incomingStream =\r\n event.streams?.[0] || new MediaStream([event.track]);\r\n const participant = this.state.getParticipant(id);\r\n\r\n const isScreenStream =\r\n incomingStream.id === participant?.media?.remoteScreenStreamId;\r\n\r\n if (event.track.kind === \"video\") {\r\n event.track.onunmute = () => {\r\n console.log(\"Video track unmuted for\", id);\r\n };\r\n }\r\n\r\n if (isScreenStream) {\r\n const videoTrack =\r\n event.track.kind === \"video\"\r\n ? event.track\r\n : incomingStream.getVideoTracks()[0] ||\r\n participant?.media?.screenTrack;\r\n\r\n this.state.updateParticipantMedia(id, {\r\n screenStream: incomingStream,\r\n screenTrack: videoTrack,\r\n\r\n isScreenSharing: true,\r\n });\r\n\r\n if (!this.state.presenterId) {\r\n this.state.setPresenterId(id);\r\n }\r\n\r\n this.events.onScreenShareStarted?.(id, incomingStream);\r\n } else {\r\n this.state.updateParticipantMedia(id, {\r\n stream: incomingStream,\r\n cameraTrack: incomingStream.getVideoTracks()[0],\r\n audioTrack: incomingStream.getAudioTracks()[0],\r\n });\r\n this.events.onTrack?.(incomingStream, id);\r\n }\r\n };\r\n\r\n pc.onicecandidate = (e) => {\r\n if (!e.candidate) return;\r\n this.send({\r\n type: \"ICE\",\r\n payload: JSON.stringify(e.candidate),\r\n sender: this.myId,\r\n target: id,\r\n });\r\n };\r\n\r\n pc.oniceconnectionstatechange = () => {\r\n console.log(`ICE Connection State: ${pc.iceConnectionState}`);\r\n };\r\n\r\n pc.onconnectionstatechange = () => {\r\n if (pc.connectionState === \"failed\") {\r\n try {\r\n pc.restartIce();\r\n } catch {}\r\n }\r\n };\r\n\r\n this.localStream.getTracks().forEach((track) => {\r\n pc.addTrack(track, this.localStream!);\r\n });\r\n\r\n if (this.isScreenSharing && this.screenStream) {\r\n this.screenSenders[id] = [];\r\n this.screenStream.getTracks().forEach((track) => {\r\n const sender = pc.addTrack(track, this.screenStream!);\r\n this.screenSenders[id].push(sender);\r\n });\r\n }\r\n\r\n return pc;\r\n }\r\n\r\n // ---------------- OFFER ----------------\r\n private async createOffer(id: string, isRenegotiation = false) {\r\n if (!isRenegotiation && this.initiators.has(id)) {\r\n console.debug(\r\n `[Offer] Already initiating with ${id}, skipping duplicate`,\r\n );\r\n }\r\n\r\n if (isRenegotiation && this.peers[id]) {\r\n const pc = this.peers[id];\r\n if (pc.signalingState !== \"stable\") {\r\n console.warn(\r\n `[Offer] Cannot renegotiate: peer in state \"${pc.signalingState}\"`,\r\n );\r\n }\r\n }\r\n\r\n if (!isRenegotiation) {\r\n this.initiators.add(id);\r\n }\r\n if (!this.peers[id]) {\r\n this.peers[id] = this.createPeer(id);\r\n }\r\n\r\n const pc = this.peers[id];\r\n\r\n try {\r\n const offer = await pc.createOffer();\r\n await pc.setLocalDescription(offer);\r\n\r\n this.send({\r\n type: \"OFFER\",\r\n payload: offer.sdp,\r\n sender: this.myId,\r\n target: id,\r\n });\r\n\r\n console.debug(`[Offer] Sent to ${id}`);\r\n } catch (err) {\r\n console.error(`[Offer] Failed for ${id}:`, err);\r\n this.emitError(\r\n \"OFFER_FAILED\",\r\n `Failed to create offer for ${id}`,\r\n err,\r\n true,\r\n );\r\n }\r\n }\r\n\r\n // ---------------- ANSWER ----------------\r\n private async handleOffer(sdp: string, id: string) {\r\n if (!this.peers[id]) {\r\n this.peers[id] = this.createPeer(id);\r\n }\r\n\r\n const pc = this.peers[id];\r\n\r\n try {\r\n // ✅ Only set remote description if we're not already in negotiation\r\n if (\r\n pc.signalingState !== \"stable\" &&\r\n pc.signalingState !== \"have-local-offer\"\r\n ) {\r\n console.warn(\r\n `[Signaling] Cannot accept OFFER in state \"${pc.signalingState}\"`,\r\n );\r\n return;\r\n }\r\n\r\n await pc.setRemoteDescription({\r\n type: \"offer\",\r\n sdp,\r\n });\r\n\r\n const pending = this.pendingIceCandidates[id] || [];\r\n\r\n for (const candidate of pending) {\r\n try {\r\n await pc.addIceCandidate(candidate);\r\n } catch (err) {\r\n console.warn(\"[ICE] Failed to add candidate:\", err);\r\n }\r\n }\r\n\r\n delete this.pendingIceCandidates[id];\r\n\r\n const answer = await pc.createAnswer();\r\n\r\n await pc.setLocalDescription(answer);\r\n await this.flushIce(id, pc);\r\n\r\n this.send({\r\n type: \"ANSWER\",\r\n payload: answer.sdp,\r\n sender: this.myId,\r\n target: id,\r\n });\r\n\r\n console.debug(`[Answer] Sent to ${id}`);\r\n } catch (err) {\r\n console.error(`[Signaling] Failed to handle OFFER from ${id}:`, err);\r\n this.emitError(\r\n \"OFFER_HANDLING_FAILED\",\r\n `Failed to handle offer from ${id}`,\r\n err,\r\n true,\r\n );\r\n }\r\n }\r\n\r\n // ---------------- CLEANUP ----------------\r\n private closePeer(id: string) {\r\n const pc = this.peers[id];\r\n\r\n if (!pc) return;\r\n\r\n pc.ontrack = null;\r\n pc.onicecandidate = null;\r\n pc.onconnectionstatechange = null;\r\n\r\n pc.close();\r\n\r\n delete this.peers[id];\r\n\r\n this.initiators.delete(id);\r\n\r\n this.state.removeParticipant(id);\r\n }\r\n\r\n async startScreenShare() {\r\n try {\r\n if (this.state.presenterId && this.state.presenterId !== this.myId) {\r\n throw new Error(\"Another user is already sharing their screen.\");\r\n }\r\n if (!navigator.mediaDevices?.getDisplayMedia) {\r\n throw new Error(\"Screen sharing not supported on this device\");\r\n }\r\n\r\n this.screenStream = await navigator.mediaDevices.getDisplayMedia({\r\n video: true,\r\n // audio: true,\r\n });\r\n\r\n this.isScreenSharing = true;\r\n\r\n this.state.updateLocalParticipant({\r\n media: {\r\n isScreenSharing: true,\r\n screenStream: this.screenStream,\r\n screenTrack: this.screenStream.getVideoTracks()[0],\r\n },\r\n });\r\n\r\n this.state.setPresenterId(this.myId);\r\n // Handle the user clicking browser's built-in \"Stop Sharing\" button\r\n this.screenStream.getVideoTracks()[0].onended = () => {\r\n this.stopScreenShare();\r\n };\r\n\r\n Object.entries(this.peers).forEach(([peerId, pc]) => {\r\n this.screenSenders[peerId] = [];\r\n this.screenStream!.getTracks().forEach((track) => {\r\n const sender = pc.addTrack(track, this.screenStream!);\r\n this.screenSenders[peerId].push(sender);\r\n });\r\n\r\n // Renegotiate peer connection descriptors to notify remote side of new track footprint\r\n this.createOffer(peerId, true);\r\n });\r\n\r\n this.send({\r\n type: \"SCREEN_SHARE_START\",\r\n sender: this.myId,\r\n room_id: this.roomId,\r\n stream_id: this.screenStream.id.replace(/[{}]/g, \"\"),\r\n });\r\n\r\n return this.screenStream;\r\n } catch (err: any) {\r\n this.emitError(\r\n \"SCREEN_SHARE_FAILED\",\r\n err?.message || \"Failed to start screen sharing\",\r\n err,\r\n true,\r\n );\r\n\r\n this.isScreenSharing = false;\r\n this.screenStream = null;\r\n throw err;\r\n }\r\n }\r\n\r\n stopScreenShare() {\r\n if (!this.screenStream) return;\r\n\r\n this.screenStream.getTracks().forEach((t) => t.stop());\r\n\r\n // Remove tracks cleanly from WebRTC channel pathways across your peers\r\n Object.entries(this.peers).forEach(([peerId, pc]) => {\r\n const senders = this.screenSenders[peerId] || [];\r\n senders.forEach((sender) => {\r\n try {\r\n pc.removeTrack(sender);\r\n } catch (err) {\r\n console.warn(err);\r\n }\r\n });\r\n delete this.screenSenders[peerId];\r\n\r\n // Renegotiate layout expectations to scale down stream bounds\r\n this.createOffer(peerId, true);\r\n });\r\n\r\n this.screenStream = null;\r\n this.isScreenSharing = false;\r\n\r\n this.state.updateLocalParticipant({\r\n media: {\r\n isScreenSharing: false,\r\n screenStream: null,\r\n screenTrack: undefined,\r\n },\r\n });\r\n\r\n if (this.state.presenterId === this.myId) {\r\n this.state.setPresenterId(null);\r\n }\r\n\r\n this.send({\r\n type: \"SCREEN_SHARE_STOP\",\r\n sender: this.myId,\r\n room_id: this.roomId,\r\n });\r\n }\r\n\r\n sendChatMessage(payload: ChatInput) {\r\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\r\n console.warn(\"WS not connected\");\r\n return;\r\n }\r\n\r\n if (!this.roomId) {\r\n console.warn(\"No roomId set\");\r\n return;\r\n }\r\n\r\n const isPrivate = !!payload?.target;\r\n\r\n const senderName = this.state.localParticipant?.name || \"Anonymous\";\r\n\r\n const msg: ChatMessage = {\r\n id: crypto.randomUUID(),\r\n sender_id: this.myId,\r\n sender_name: senderName,\r\n text: payload.message.trim(),\r\n timestamp: Date.now(),\r\n reply_to: payload.reply_to ?? null,\r\n target: payload.target ?? null,\r\n };\r\n\r\n // optimistic UI update\r\n this.state.addChatMessage(msg);\r\n\r\n // send protocol payload (clean + consistent)\r\n this.send({\r\n type: \"CHAT_MESSAGE\",\r\n message: payload.message.trim(),\r\n user_id: this.myId,\r\n sender_name: senderName,\r\n room_id: this.roomId,\r\n target: isPrivate ? (payload.target ?? null) : null,\r\n reply_to: payload.reply_to ?? null,\r\n\r\n client_ts: Date.now(),\r\n });\r\n }\r\n\r\n disconnect() {\r\n this.intentionalDisconnect = true;\r\n this.stopScreenShare();\r\n\r\n Object.values(this.peers).forEach((pc) => pc.close());\r\n this.peers = {};\r\n this.initiators.clear();\r\n\r\n this.stopHeartbeat();\r\n\r\n if (this.ws) {\r\n this.ws.close();\r\n this.ws = null;\r\n }\r\n\r\n if (this.localStream) {\r\n this.localStream.getTracks().forEach((track) => track.stop());\r\n this.localStream = null;\r\n }\r\n\r\n this.roomId = null;\r\n\r\n // Clear and notify\r\n this.state.localParticipant = null;\r\n this.state.notify(\"localParticipant\");\r\n\r\n this.state.participants.clear();\r\n this.state.notify(\"participants\");\r\n\r\n this.state.clearChat();\r\n this.state.setPresenterId(null);\r\n }\r\n\r\n private async flushIce(id: string, pc: RTCPeerConnection) {\r\n const pending = this.pendingIceCandidates[id];\r\n if (!pending?.length) return;\r\n\r\n for (const candidate of pending) {\r\n try {\r\n await pc.addIceCandidate(candidate);\r\n } catch (e) {\r\n console.warn(\"ICE flush error\", e);\r\n }\r\n }\r\n\r\n delete this.pendingIceCandidates[id];\r\n }\r\n\r\n private send(msg: any) {\r\n this.ws?.send(JSON.stringify(msg));\r\n }\r\n}\r\n","import { useEffect, useState } from \"react\";\r\nimport { StateScope } from \"../types/meeting\";\r\nimport { MeetingState } from \"../core/MeetingState\";\r\n\r\nexport function useMeetingStore<T>(\r\n stateManager: MeetingState,\r\n scope: StateScope,\r\n selector: (state: MeetingState) => T,\r\n): T {\r\n const [state, setState] = useState(() => selector(stateManager));\r\n\r\n useEffect(() => {\r\n // Update local react state whenever the SDK notifies this scope\r\n const unsubscribe = stateManager.subscribe(scope, () => {\r\n setState(selector(stateManager));\r\n });\r\n\r\n return unsubscribe;\r\n }, [stateManager, scope, selector]);\r\n\r\n return state;\r\n}\r\n","import { useEffect } from \"react\";\r\nimport { useMeetingContext } from \"./MeetingProvider\";\r\n\r\nexport const useMeeting = (handlers?: { onError?: (err: any) => void }) => {\r\n const ctx = useMeetingContext();\r\n\r\n useEffect(() => {\r\n if (!handlers?.onError) return;\r\n\r\n const unsubscribe = ctx.onError(handlers.onError);\r\n return unsubscribe;\r\n }, [handlers?.onError]);\r\n\r\n return ctx;\r\n};\r\n","import { useEffect, useState } from \"react\";\r\nimport { useMeetingContext } from \"./MeetingProvider\";\r\nimport { Participant } from \"../types/meeting\";\r\n\r\nexport const useParticipants = () => {\r\n const { sdk } = useMeetingContext();\r\n\r\n const [participants, setParticipants] = useState<Participant[]>(() =>\r\n sdk.state.getParticipants(),\r\n );\r\n\r\n useEffect(() => {\r\n const update = () => {\r\n setParticipants(sdk.state.getParticipants());\r\n };\r\n\r\n update();\r\n\r\n const unsub = sdk.state.subscribe(\"participants\", update);\r\n\r\n return unsub;\r\n }, [sdk]);\r\n\r\n return participants;\r\n};\r\n","import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport { useMeetingContext } from \"./MeetingProvider\";\r\nimport { Participant } from \"../types/meeting\";\r\n\r\nexport const useRemoteMedia = (participantId: string) => {\r\n const { sdk } = useMeetingContext();\r\n const videoRef = useRef<HTMLVideoElement | null>(null);\r\n const audioRef = useRef<HTMLAudioElement | null>(null);\r\n\r\n const [participant, setParticipant] = useState<Participant | null>(\r\n () => sdk.state.getParticipant(participantId) || null,\r\n );\r\n\r\n useEffect(() => {\r\n const unsub = sdk.state.subscribe(`participant:${participantId}`, () => {\r\n const p = sdk.state.getParticipant(participantId);\r\n setParticipant(p ? { ...p } : null);\r\n });\r\n\r\n return unsub;\r\n }, [participantId, sdk]);\r\n\r\n useEffect(() => {\r\n const stream = participant?.media?.stream;\r\n if (!stream) return;\r\n\r\n // VIDEO\r\n if (videoRef.current) {\r\n videoRef.current.srcObject = stream;\r\n videoRef.current.muted = true;\r\n videoRef.current.playsInline = true;\r\n videoRef.current.play().catch(() => {});\r\n }\r\n\r\n // AUDIO\r\n if (audioRef.current) {\r\n audioRef.current.srcObject = stream;\r\n audioRef.current.play().catch(() => {});\r\n }\r\n }, [participant?.media?.stream]);\r\n\r\n return {\r\n videoRef,\r\n audioRef,\r\n isCamActive: !!participant?.media?.camEnabled,\r\n isMicEnabled: !!participant?.media?.micEnabled,\r\n };\r\n};\r\n"],"mappings":"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","names":["import_react","import_react","peerId","pc","import_react","import_react","import_react"]}
|
package/dist/index.mjs
CHANGED
|
@@ -167,6 +167,8 @@ var VideoSDKCore = class {
|
|
|
167
167
|
this.ws = null;
|
|
168
168
|
this.peers = {};
|
|
169
169
|
this.initiators = /* @__PURE__ */ new Set();
|
|
170
|
+
this.lastPong = Date.now();
|
|
171
|
+
this.intentionalDisconnect = false;
|
|
170
172
|
this.roomId = null;
|
|
171
173
|
this.localStream = null;
|
|
172
174
|
this.screenStream = null;
|
|
@@ -238,18 +240,18 @@ var VideoSDKCore = class {
|
|
|
238
240
|
this.emitError("WS_ERROR", "WebSocket encountered an error", err, true);
|
|
239
241
|
};
|
|
240
242
|
this.ws.onclose = (e) => {
|
|
241
|
-
this.emitError(
|
|
242
|
-
"WS_CLOSED",
|
|
243
|
-
`Connection closed (${e.code}) ${e.reason || ""}`,
|
|
244
|
-
e,
|
|
245
|
-
true
|
|
246
|
-
);
|
|
247
243
|
this.joinRejecter?.({
|
|
248
244
|
code: "WS_CLOSED",
|
|
249
245
|
message: "Connection closed before join completed",
|
|
250
246
|
raw: e
|
|
251
247
|
});
|
|
252
248
|
this.joinRejecter = void 0;
|
|
249
|
+
if (this.intentionalDisconnect) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (e.code === 1e3 || e.code === 1001) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
253
255
|
this.scheduleReconnect();
|
|
254
256
|
};
|
|
255
257
|
this.ws.onmessage = async (e) => {
|
|
@@ -373,33 +375,9 @@ var VideoSDKCore = class {
|
|
|
373
375
|
var _a, _b, _c, _d;
|
|
374
376
|
if (msg.sender === this.myId) return;
|
|
375
377
|
switch (msg.type) {
|
|
376
|
-
case "
|
|
377
|
-
|
|
378
|
-
this.state.setPresenterId(msg.presenterId);
|
|
379
|
-
this.events.onScreenShareStarted?.(msg.presenterId, null);
|
|
380
|
-
}
|
|
381
|
-
for (const p of msg.participants || []) {
|
|
382
|
-
if (!p?.id || p.id === this.myId) continue;
|
|
383
|
-
this.state.addParticipant(p);
|
|
384
|
-
this.events.onUserJoined?.(p);
|
|
385
|
-
await this.createOffer(p.id);
|
|
386
|
-
}
|
|
387
|
-
break;
|
|
388
|
-
case "JOINED": {
|
|
389
|
-
this.startHeartbeat();
|
|
390
|
-
this.joinResolver?.();
|
|
391
|
-
this.joinResolver = void 0;
|
|
392
|
-
this.joinRejecter = void 0;
|
|
378
|
+
case "PONG":
|
|
379
|
+
this.lastPong = Date.now();
|
|
393
380
|
break;
|
|
394
|
-
}
|
|
395
|
-
case "USER_JOINED": {
|
|
396
|
-
const p = msg.participant;
|
|
397
|
-
if (!p?.id || p.id === this.myId) return;
|
|
398
|
-
this.state.addParticipant(p);
|
|
399
|
-
this.events.onUserJoined?.(p);
|
|
400
|
-
await this.createOffer(p.id);
|
|
401
|
-
break;
|
|
402
|
-
}
|
|
403
381
|
case "OFFER":
|
|
404
382
|
await this.handleOffer(msg.payload, msg.sender);
|
|
405
383
|
break;
|
|
@@ -449,6 +427,35 @@ var VideoSDKCore = class {
|
|
|
449
427
|
}
|
|
450
428
|
break;
|
|
451
429
|
}
|
|
430
|
+
case "EXISTING_USERS":
|
|
431
|
+
if (msg.presenterId) {
|
|
432
|
+
this.state.setPresenterId(msg.presenterId);
|
|
433
|
+
this.events.onScreenShareStarted?.(msg.presenterId, null);
|
|
434
|
+
}
|
|
435
|
+
for (const p of msg.participants || []) {
|
|
436
|
+
if (!p?.id || p.id === this.myId) continue;
|
|
437
|
+
this.state.addParticipant(p);
|
|
438
|
+
this.events.onUserJoined?.(p);
|
|
439
|
+
await this.createOffer(p.id);
|
|
440
|
+
}
|
|
441
|
+
break;
|
|
442
|
+
case "JOINED": {
|
|
443
|
+
this.intentionalDisconnect = false;
|
|
444
|
+
this.reconnectAttempts = 0;
|
|
445
|
+
this.startHeartbeat();
|
|
446
|
+
this.joinResolver?.();
|
|
447
|
+
this.joinResolver = void 0;
|
|
448
|
+
this.joinRejecter = void 0;
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
case "USER_JOINED": {
|
|
452
|
+
const p = msg.participant;
|
|
453
|
+
if (!p?.id || p.id === this.myId) return;
|
|
454
|
+
this.state.addParticipant(p);
|
|
455
|
+
this.events.onUserJoined?.(p);
|
|
456
|
+
await this.createOffer(p.id);
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
452
459
|
case "USER_LEFT":
|
|
453
460
|
const peerId = msg.participant.id;
|
|
454
461
|
this.closePeer(peerId);
|
|
@@ -562,9 +569,14 @@ var VideoSDKCore = class {
|
|
|
562
569
|
streamCount: event.streams.length,
|
|
563
570
|
streamTrackCount: event.streams[0]?.getTracks().length
|
|
564
571
|
});
|
|
565
|
-
const incomingStream = event.streams[0];
|
|
572
|
+
const incomingStream = event.streams?.[0] || new MediaStream([event.track]);
|
|
566
573
|
const participant = this.state.getParticipant(id);
|
|
567
574
|
const isScreenStream = incomingStream.id === participant?.media?.remoteScreenStreamId;
|
|
575
|
+
if (event.track.kind === "video") {
|
|
576
|
+
event.track.onunmute = () => {
|
|
577
|
+
console.log("Video track unmuted for", id);
|
|
578
|
+
};
|
|
579
|
+
}
|
|
568
580
|
if (isScreenStream) {
|
|
569
581
|
const videoTrack = event.track.kind === "video" ? event.track : incomingStream.getVideoTracks()[0] || participant?.media?.screenTrack;
|
|
570
582
|
this.state.updateParticipantMedia(id, {
|
|
@@ -596,12 +608,6 @@ var VideoSDKCore = class {
|
|
|
596
608
|
};
|
|
597
609
|
pc.oniceconnectionstatechange = () => {
|
|
598
610
|
console.log(`ICE Connection State: ${pc.iceConnectionState}`);
|
|
599
|
-
if (pc.iceConnectionState === "failed" || pc.iceConnectionState === "disconnected") {
|
|
600
|
-
this.emitError(
|
|
601
|
-
"ICE_FAILED",
|
|
602
|
-
"Connection failed. Please check your network or refresh."
|
|
603
|
-
);
|
|
604
|
-
}
|
|
605
611
|
};
|
|
606
612
|
pc.onconnectionstatechange = () => {
|
|
607
613
|
if (pc.connectionState === "failed") {
|
|
@@ -629,7 +635,6 @@ var VideoSDKCore = class {
|
|
|
629
635
|
console.debug(
|
|
630
636
|
`[Offer] Already initiating with ${id}, skipping duplicate`
|
|
631
637
|
);
|
|
632
|
-
return;
|
|
633
638
|
}
|
|
634
639
|
if (isRenegotiation && this.peers[id]) {
|
|
635
640
|
const pc2 = this.peers[id];
|
|
@@ -637,7 +642,6 @@ var VideoSDKCore = class {
|
|
|
637
642
|
console.warn(
|
|
638
643
|
`[Offer] Cannot renegotiate: peer in state "${pc2.signalingState}"`
|
|
639
644
|
);
|
|
640
|
-
return;
|
|
641
645
|
}
|
|
642
646
|
}
|
|
643
647
|
if (!isRenegotiation) {
|
|
@@ -669,15 +673,6 @@ var VideoSDKCore = class {
|
|
|
669
673
|
}
|
|
670
674
|
// ---------------- ANSWER ----------------
|
|
671
675
|
async handleOffer(sdp, id) {
|
|
672
|
-
if (this.initiators.has(id) && this.peers[id]) {
|
|
673
|
-
const pc2 = this.peers[id];
|
|
674
|
-
if (pc2.signalingState !== "stable") {
|
|
675
|
-
console.warn(
|
|
676
|
-
`[Signaling] Offer collision with ${id}. We initiated, ignoring their offer.`
|
|
677
|
-
);
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
676
|
if (!this.peers[id]) {
|
|
682
677
|
this.peers[id] = this.createPeer(id);
|
|
683
678
|
}
|
|
@@ -851,6 +846,7 @@ var VideoSDKCore = class {
|
|
|
851
846
|
});
|
|
852
847
|
}
|
|
853
848
|
disconnect() {
|
|
849
|
+
this.intentionalDisconnect = true;
|
|
854
850
|
this.stopScreenShare();
|
|
855
851
|
Object.values(this.peers).forEach((pc) => pc.close());
|
|
856
852
|
this.peers = {};
|
|
@@ -1064,21 +1060,14 @@ var useParticipants = () => {
|
|
|
1064
1060
|
};
|
|
1065
1061
|
|
|
1066
1062
|
// src/react/useRemoteMedia.ts
|
|
1067
|
-
import {
|
|
1063
|
+
import { useEffect as useEffect5, useRef as useRef3, useState as useState4 } from "react";
|
|
1068
1064
|
var useRemoteMedia = (participantId) => {
|
|
1069
1065
|
const { sdk } = useMeetingContext();
|
|
1066
|
+
const videoRef = useRef3(null);
|
|
1067
|
+
const audioRef = useRef3(null);
|
|
1070
1068
|
const [participant, setParticipant] = useState4(
|
|
1071
1069
|
() => sdk.state.getParticipant(participantId) || null
|
|
1072
1070
|
);
|
|
1073
|
-
const buildMediaStream = (participant2) => {
|
|
1074
|
-
if (!participant2?.media) return null;
|
|
1075
|
-
const stream = new MediaStream();
|
|
1076
|
-
const videoTrack = participant2.media.stream?.getVideoTracks?.()?.[0];
|
|
1077
|
-
const audioTrack = participant2.media.stream?.getAudioTracks?.()?.[0];
|
|
1078
|
-
if (videoTrack) stream.addTrack(videoTrack);
|
|
1079
|
-
if (audioTrack) stream.addTrack(audioTrack);
|
|
1080
|
-
return stream;
|
|
1081
|
-
};
|
|
1082
1071
|
useEffect5(() => {
|
|
1083
1072
|
const unsub = sdk.state.subscribe(`participant:${participantId}`, () => {
|
|
1084
1073
|
const p = sdk.state.getParticipant(participantId);
|
|
@@ -1086,34 +1075,22 @@ var useRemoteMedia = (participantId) => {
|
|
|
1086
1075
|
});
|
|
1087
1076
|
return unsub;
|
|
1088
1077
|
}, [participantId, sdk]);
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
node.autoplay = true;
|
|
1098
|
-
node.play().catch((err) => {
|
|
1099
|
-
console.log("can't play video:", err);
|
|
1078
|
+
useEffect5(() => {
|
|
1079
|
+
const stream = participant?.media?.stream;
|
|
1080
|
+
if (!stream) return;
|
|
1081
|
+
if (videoRef.current) {
|
|
1082
|
+
videoRef.current.srcObject = stream;
|
|
1083
|
+
videoRef.current.muted = true;
|
|
1084
|
+
videoRef.current.playsInline = true;
|
|
1085
|
+
videoRef.current.play().catch(() => {
|
|
1100
1086
|
});
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
(node) => {
|
|
1106
|
-
if (!node) return;
|
|
1107
|
-
const stream = participant?.media?.stream;
|
|
1108
|
-
if (!stream) return;
|
|
1109
|
-
node.srcObject = stream;
|
|
1110
|
-
node.muted = false;
|
|
1111
|
-
node.play().catch((err) => {
|
|
1112
|
-
console.log("can't play Audio:", err);
|
|
1087
|
+
}
|
|
1088
|
+
if (audioRef.current) {
|
|
1089
|
+
audioRef.current.srcObject = stream;
|
|
1090
|
+
audioRef.current.play().catch(() => {
|
|
1113
1091
|
});
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
);
|
|
1092
|
+
}
|
|
1093
|
+
}, [participant?.media?.stream]);
|
|
1117
1094
|
return {
|
|
1118
1095
|
videoRef,
|
|
1119
1096
|
audioRef,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react/useLocalParticipant.tsx","../src/react/MeetingProvider.tsx","../src/config/ws.ts","../src/core/MeetingState.ts","../src/core/VideoCore.ts","../src/react/useMeetingStore.ts","../src/react/useMeeting.ts","../src/react/useParticipants.ts","../src/react/useRemoteMedia.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport { useMeetingContext } from \"./MeetingProvider\";\r\nimport { Participant } from \"../types/meeting\";\r\n\r\nexport const useLocalParticipant = () => {\r\n const { sdk } = useMeetingContext();\r\n\r\n // Guard the initial state to ensure it matches Participant | null\r\n const [localParticipant, setLocalParticipant] = useState<Participant | null>(\r\n () => {\r\n const current = sdk.state.localParticipant;\r\n return current && current.id ? (current as Participant) : null;\r\n },\r\n );\r\n\r\n useEffect(() => {\r\n const unsubscribe = sdk.state.subscribe(\"localParticipant\", () => {\r\n const current = sdk.state.localParticipant;\r\n\r\n // Safe Type-Guard: Only update if the object has a finalized id\r\n if (current && current.id) {\r\n setLocalParticipant({ ...current } as Participant);\r\n } else {\r\n setLocalParticipant(null);\r\n }\r\n });\r\n\r\n return unsubscribe;\r\n }, [sdk]);\r\n\r\n const lastStreamRef = useRef<MediaStream | null>(null);\r\n\r\n const videoRef = useCallback(\r\n (video: HTMLVideoElement | null) => {\r\n if (!video) return;\r\n\r\n const stream = localParticipant?.media?.stream;\r\n if (!stream) return;\r\n\r\n if (lastStreamRef.current === stream) return;\r\n lastStreamRef.current = stream;\r\n\r\n video.srcObject = stream;\r\n video.autoplay = true;\r\n video.playsInline = true;\r\n\r\n video.play().catch((err) => {\r\n console.warn(`Autoplay failed for local view:`, err);\r\n });\r\n },\r\n [localParticipant?.media?.stream],\r\n );\r\n\r\n return {\r\n participant: localParticipant,\r\n videoRef,\r\n };\r\n};\r\n","import React, { createContext, useContext, useMemo, useRef } from \"react\";\r\nimport { VideoSDKCore } from \"../core/VideoCore\";\r\nimport {\r\n ChatInput,\r\n ChatMessage,\r\n MeetingConfig,\r\n Participant,\r\n PubSubTopic,\r\n} from \"../types/meeting\";\r\nimport { useMeetingStore } from \"./useMeetingStore\";\r\n\r\ntype PubSubHandle = {\r\n messages: ChatMessage[];\r\n publish: (input: ChatInput) => void;\r\n};\r\n\r\ntype MeetingContextValue = {\r\n sdk: VideoSDKCore;\r\n\r\n join: (config: MeetingConfig) => Promise<void>;\r\n leave: () => void;\r\n\r\n toggleMic: () => void;\r\n toggleCam: () => void;\r\n\r\n startScreenShare: () => Promise<MediaStream>;\r\n stopScreenShare: () => void;\r\n\r\n sendMessage: (input: ChatInput) => void;\r\n\r\n meetingId: string | null;\r\n localParticipant: Participant | null;\r\n participants: Map<string, Participant>;\r\n messages: ChatMessage[];\r\n presenterId: string | null;\r\n usePubSub: (topic: PubSubTopic) => PubSubHandle;\r\n onError: (cb: (err: any) => void) => () => void;\r\n};\r\n\r\nconst MeetingContext = createContext<MeetingContextValue | null>(null);\r\n\r\nexport const MeetingProvider = ({\r\n config,\r\n children,\r\n}: {\r\n config: MeetingConfig;\r\n children: React.ReactNode;\r\n}) => {\r\n const sdkRef = useRef<VideoSDKCore | null>(null);\r\n const errorListeners = useRef(new Set<(err: any) => void>());\r\n if (!sdkRef.current) {\r\n sdkRef.current = new VideoSDKCore({\r\n onError: (err) => {\r\n errorListeners.current.forEach((fn) => fn(err));\r\n },\r\n });\r\n }\r\n\r\n const sdk = sdkRef.current;\r\n const presenterId = useMeetingStore(\r\n sdk.state,\r\n \"presenter\",\r\n (s) => s.presenterId,\r\n );\r\n const participants = useMeetingStore(\r\n sdk.state,\r\n \"participants\",\r\n (s) => s.participants,\r\n );\r\n const localParticipant = useMeetingStore(\r\n sdk.state,\r\n \"localParticipant\",\r\n (s) => s.localParticipant,\r\n );\r\n const messages = useMeetingStore(sdk.state, \"chat\", (s) =>\r\n s.getChatMessages(),\r\n );\r\n\r\n const value = useMemo<MeetingContextValue>(() => {\r\n if (!sdkRef.current) {\r\n sdkRef.current = new VideoSDKCore({\r\n onError: (err) => {\r\n errorListeners.current.forEach((fn) => fn(err));\r\n },\r\n });\r\n }\r\n\r\n return {\r\n sdk,\r\n\r\n join: (joinConfig: MeetingConfig) =>\r\n sdk.joinMeeting({\r\n ...config,\r\n ...joinConfig,\r\n }),\r\n leave: () => sdk.disconnect(),\r\n toggleMic: sdk.toggleMic.bind(sdk),\r\n toggleCam: sdk.toggleCam.bind(sdk),\r\n startScreenShare: sdk.startScreenShare.bind(sdk),\r\n stopScreenShare: sdk.stopScreenShare.bind(sdk),\r\n sendMessage: sdk.sendChatMessage.bind(sdk),\r\n\r\n meetingId: sdk.getMeetingId(),\r\n localParticipant,\r\n participants,\r\n messages,\r\n presenterId,\r\n usePubSub: (topic: PubSubTopic) => {\r\n if (topic !== \"SECURE_CHAT\") {\r\n throw new Error(`Unsupported PubSub argument: \"${topic}\"`);\r\n }\r\n return {\r\n messages: sdk.state.getChatMessages(),\r\n publish: sdk.sendChatMessage.bind(sdk),\r\n };\r\n },\r\n onError: (cb: (err: any) => void) => {\r\n errorListeners.current.add(cb);\r\n\r\n return () => {\r\n errorListeners.current.delete(cb);\r\n };\r\n },\r\n };\r\n }, [config, sdk, localParticipant, participants, messages, presenterId]);\r\n\r\n return (\r\n <MeetingContext.Provider value={value}>{children}</MeetingContext.Provider>\r\n );\r\n};\r\n\r\nexport const useMeetingContext = () => {\r\n const ctx = useContext(MeetingContext);\r\n if (!ctx)\r\n throw new Error(\"useMeetingContext must be used inside <MeetingProvider>\");\r\n return ctx;\r\n};\r\n","export const SDK_CONFIG = {\r\n wsUrl: \"wss://rust-video-server-sfyf.onrender.com/ws\",\r\n};\r\n","import {\r\n ChatMessage,\r\n Listener,\r\n Participant,\r\n ParticipantMedia,\r\n StateScope,\r\n} from \"../types/meeting\";\r\n\r\ntype LocalParticipantPatch = {\r\n id?: string;\r\n name?: string;\r\n media?: Partial<ParticipantMedia>;\r\n};\r\n\r\nexport class MeetingState {\r\n participants = new Map<string, Participant>();\r\n localParticipant: Participant | null = null;\r\n localStream: MediaStream | null = null;\r\n chatMessages = new Map<string, ChatMessage>();\r\n presenterId: string | null = null;\r\n\r\n private listeners = new Map<StateScope, Set<Listener>>();\r\n\r\n // ---- reactive system ----\r\n\r\n subscribe(scope: StateScope, fn: Listener): () => void {\r\n if (!this.listeners.has(scope)) {\r\n this.listeners.set(scope, new Set());\r\n }\r\n this.listeners.get(scope)!.add(fn);\r\n\r\n return () => {\r\n this.listeners.get(scope)?.delete(fn);\r\n };\r\n }\r\n\r\n notify(scope: StateScope) {\r\n this.listeners.get(scope)?.forEach((fn) => fn());\r\n }\r\n\r\n setPresenterId(id: string | null) {\r\n if (this.presenterId === id) return;\r\n this.presenterId = id;\r\n this.notify(\"presenter\");\r\n this.notify(\"participants\");\r\n }\r\n\r\n // ---- participants ----\r\n\r\n addParticipant(p: Participant) {\r\n if (this.participants.has(p.id)) return false;\r\n // Fix: Immutable Map update\r\n const next = new Map(this.participants);\r\n next.set(p.id, p);\r\n this.participants = next;\r\n\r\n this.notify(\"participants\");\r\n return true;\r\n }\r\n\r\n removeParticipant(id: string) {\r\n // Fix: Immutable Map update\r\n const next = new Map(this.participants);\r\n next.delete(id);\r\n this.participants = next;\r\n\r\n this.notify(\"participants\");\r\n }\r\n\r\n updateParticipantMedia(\r\n id: string,\r\n patch: Partial<NonNullable<Participant[\"media\"]>>,\r\n ) {\r\n const p = this.participants.get(id);\r\n if (!p) return;\r\n\r\n const updated: Participant = {\r\n ...p,\r\n media: {\r\n stream: null,\r\n screenStream: undefined,\r\n cameraTrack: undefined,\r\n screenTrack: undefined,\r\n audioTrack: undefined,\r\n micEnabled: true,\r\n camEnabled: true,\r\n isScreenSharing: false,\r\n ...p.media, // preserve existing media items if they happen to exist\r\n ...patch, // apply the incoming stream updates\r\n },\r\n };\r\n\r\n // Keep your clean immutable map update\r\n const next = new Map(this.participants);\r\n next.set(id, updated);\r\n this.participants = next;\r\n\r\n this.notify(`participant:${id}`);\r\n this.notify(\"participants\");\r\n }\r\n\r\n updateLocalParticipant(patch: LocalParticipantPatch) {\r\n const prev = this.localParticipant;\r\n\r\n if (!prev) {\r\n this.localParticipant = {\r\n id: patch.id ?? \"\",\r\n name: patch.name ?? \"\",\r\n media: {\r\n stream: patch.media?.stream ?? null, // ◄ FIX: Capture the stream from the patch here\r\n screenStream: patch.media?.screenStream,\r\n cameraTrack: patch.media?.cameraTrack,\r\n screenTrack: patch.media?.screenTrack,\r\n audioTrack: patch.media?.audioTrack,\r\n micEnabled: patch.media?.micEnabled ?? true,\r\n camEnabled: patch.media?.camEnabled ?? true,\r\n isScreenSharing: patch.media?.isScreenSharing ?? false,\r\n },\r\n };\r\n\r\n this.notify(\"localParticipant\");\r\n return;\r\n }\r\n\r\n const prevMedia = prev.media ?? {\r\n stream: null,\r\n screenStream: undefined,\r\n cameraTrack: undefined,\r\n screenTrack: undefined,\r\n audioTrack: undefined,\r\n micEnabled: true,\r\n camEnabled: true,\r\n isScreenSharing: false,\r\n };\r\n\r\n const nextMedia: ParticipantMedia = {\r\n stream: patch.media?.stream ?? prevMedia.stream,\r\n screenStream: patch.media?.screenStream ?? prevMedia.screenStream,\r\n cameraTrack: patch.media?.cameraTrack ?? prevMedia.cameraTrack,\r\n screenTrack: patch.media?.screenTrack ?? prevMedia.screenTrack,\r\n audioTrack: patch.media?.audioTrack ?? prevMedia.audioTrack,\r\n micEnabled: patch.media?.micEnabled ?? prevMedia.micEnabled,\r\n camEnabled: patch.media?.camEnabled ?? prevMedia.camEnabled,\r\n isScreenSharing:\r\n patch.media?.isScreenSharing ?? prevMedia.isScreenSharing,\r\n };\r\n\r\n this.localParticipant = {\r\n ...prev,\r\n id: patch.id ?? prev.id,\r\n name: patch.name ?? prev.name,\r\n media: nextMedia,\r\n };\r\n\r\n this.notify(\"localParticipant\");\r\n }\r\n\r\n // ---- chat ----\r\n\r\n addChatMessage(msg: ChatMessage) {\r\n this.chatMessages.set(msg.id, msg);\r\n this.notify(\"chat\");\r\n }\r\n\r\n getChatMessages() {\r\n return Array.from(this.chatMessages.values()).sort(\r\n (a, b) => a.timestamp - b.timestamp,\r\n );\r\n }\r\n\r\n clearChat() {\r\n this.chatMessages.clear();\r\n this.notify(\"chat\");\r\n }\r\n\r\n // ---- helpers ----\r\n\r\n getParticipants() {\r\n return Array.from(this.participants.values());\r\n }\r\n\r\n getParticipant(id: string) {\r\n return this.participants.get(id) ?? null;\r\n }\r\n\r\n resetRemoteState() {\r\n this.participants.clear();\r\n this.chatMessages.clear();\r\n this.presenterId = null;\r\n this.notify(\"participants\");\r\n this.notify(\"chat\");\r\n this.notify(\"presenter\");\r\n }\r\n}\r\n","import { SDK_CONFIG } from \"../config/ws\";\r\nimport {\r\n ChatInput,\r\n ChatMessage,\r\n Events,\r\n MeetingConfig,\r\n SDKError,\r\n} from \"../types/meeting\";\r\nimport { MeetingState } from \"./MeetingState\";\r\n\r\nexport class VideoSDKCore {\r\n private ws: WebSocket | null = null;\r\n private peers: Record<string, RTCPeerConnection> = {};\r\n private initiators = new Set<string>();\r\n\r\n private myId: string;\r\n private roomId: string | null = null;\r\n private localStream: MediaStream | null = null;\r\n private screenStream: MediaStream | null = null;\r\n private isScreenSharing = false;\r\n private screenSenders: Record<string, RTCRtpSender[]> = {};\r\n\r\n private pingInterval: any = null;\r\n private pendingIceCandidates: Record<string, RTCIceCandidateInit[]> = {};\r\n private reconnectAttempts = 0;\r\n private reconnectTimer?: number;\r\n private participantName = \"\";\r\n public readonly state: MeetingState;\r\n private joinResolver?: () => void;\r\n private joinRejecter?: (e: any) => void;\r\n private emitError(\r\n code: string,\r\n message: string,\r\n raw?: any,\r\n recoverable = true,\r\n ) {\r\n const err: SDKError = {\r\n code,\r\n message,\r\n raw,\r\n roomId: this.roomId,\r\n userId: this.myId,\r\n recoverable,\r\n };\r\n\r\n this.events.onError?.(err);\r\n\r\n this.joinRejecter?.(err);\r\n this.joinRejecter = undefined;\r\n\r\n console.error(\"[MeetingSDK Error]\", err);\r\n }\r\n\r\n constructor(\r\n private events: Events = {},\r\n private url: string = SDK_CONFIG.wsUrl,\r\n ) {\r\n this.state = new MeetingState();\r\n this.events = events;\r\n this.url = url;\r\n\r\n this.myId = localStorage.getItem(\"vsdk_id\") || crypto.randomUUID();\r\n\r\n localStorage.setItem(\"vsdk_id\", this.myId);\r\n }\r\n\r\n // ---------------- STREAM ----------------\r\n async initLocal(video: HTMLVideoElement, name: string) {\r\n this.participantName = name;\r\n\r\n if (!this.localStream) {\r\n this.localStream = await navigator.mediaDevices.getUserMedia({\r\n video: true,\r\n audio: true,\r\n });\r\n }\r\n\r\n video.srcObject = this.localStream;\r\n\r\n // Fix: Supply mandatory fields to satisfy the Participant type constraint\r\n this.state.updateLocalParticipant({\r\n id: this.myId,\r\n name: this.participantName,\r\n media: {\r\n stream: this.localStream,\r\n micEnabled: true,\r\n camEnabled: true,\r\n isScreenSharing: false,\r\n },\r\n });\r\n\r\n this.state.localStream = this.localStream;\r\n }\r\n\r\n // ---------------- CONNECT ----------------\r\n async connect(roomId: string, name: string) {\r\n this.roomId = roomId;\r\n\r\n this.reset();\r\n\r\n return new Promise<void>((resolve, reject) => {\r\n this.joinResolver = resolve;\r\n this.joinRejecter = reject;\r\n this.ws = new WebSocket(this.url);\r\n\r\n this.ws.onopen = () => {\r\n this.send({\r\n type: \"JOIN\",\r\n room_id: roomId,\r\n user_id: this.myId,\r\n sender_name: name,\r\n });\r\n };\r\n\r\n this.ws.onerror = (err) => {\r\n this.emitError(\"WS_ERROR\", \"WebSocket encountered an error\", err, true);\r\n };\r\n\r\n this.ws.onclose = (e) => {\r\n this.emitError(\r\n \"WS_CLOSED\",\r\n `Connection closed (${e.code}) ${e.reason || \"\"}`,\r\n e,\r\n true,\r\n );\r\n\r\n // If join never resolved, fail the promise\r\n this.joinRejecter?.({\r\n code: \"WS_CLOSED\",\r\n message: \"Connection closed before join completed\",\r\n raw: e,\r\n });\r\n\r\n this.joinRejecter = undefined;\r\n\r\n this.scheduleReconnect();\r\n };\r\n\r\n this.ws.onmessage = async (e) => {\r\n await this.handle(JSON.parse(e.data));\r\n };\r\n });\r\n }\r\n\r\n async joinMeeting(config: MeetingConfig) {\r\n const { roomId, name, audioMuted = false, videoMuted = false } = config;\r\n\r\n if (!roomId || !name) {\r\n throw new Error(\"roomId and name are required to join meeting\");\r\n }\r\n\r\n this.participantName = name;\r\n\r\n // Reuse existing stream if initLocal already configured it\r\n if (!this.localStream) {\r\n this.localStream = await navigator.mediaDevices.getUserMedia({\r\n video: true,\r\n audio: true,\r\n });\r\n }\r\n\r\n this.localStream.getAudioTracks().forEach((t) => {\r\n t.enabled = !audioMuted;\r\n });\r\n this.localStream.getVideoTracks().forEach((t) => {\r\n t.enabled = !videoMuted;\r\n });\r\n\r\n this.state.updateLocalParticipant({\r\n id: this.myId,\r\n name: this.participantName,\r\n media: {\r\n stream: this.localStream,\r\n micEnabled: !audioMuted,\r\n camEnabled: !videoMuted,\r\n isScreenSharing: false,\r\n },\r\n });\r\n\r\n this.state.localStream = this.localStream;\r\n\r\n await this.connect(roomId, name);\r\n }\r\n\r\n /** Expose the roomId without making it fully public */\r\n getMeetingId(): string | null {\r\n return this.roomId;\r\n }\r\n\r\n toggleMic() {\r\n const mediaState = this.state.localParticipant?.media;\r\n if (!mediaState) return;\r\n\r\n // Flip the state tracked in localParticipant.media\r\n const nextEnabled = !mediaState.micEnabled;\r\n\r\n // Mirror the state change down to the actual hardware tracks\r\n this.localStream\r\n ?.getAudioTracks()\r\n .forEach((t) => (t.enabled = nextEnabled));\r\n\r\n // Update state layer\r\n this.state.updateLocalParticipant({\r\n id: this.myId,\r\n name: this.participantName,\r\n media: {\r\n ...mediaState,\r\n micEnabled: nextEnabled,\r\n },\r\n });\r\n\r\n // Notify peers\r\n this.send({\r\n type: \"MEDIA_STATE\",\r\n kind: \"audio\",\r\n enabled: nextEnabled,\r\n });\r\n }\r\n\r\n toggleCam() {\r\n const mediaState = this.state.localParticipant?.media;\r\n if (!mediaState) return;\r\n\r\n // Flip the state tracked in localParticipant.media\r\n const nextEnabled = !mediaState.camEnabled;\r\n\r\n // Mirror the state change down to the actual hardware tracks\r\n this.localStream\r\n ?.getVideoTracks()\r\n .forEach((t) => (t.enabled = nextEnabled));\r\n\r\n // Update state layer\r\n this.state.updateLocalParticipant({\r\n id: this.myId,\r\n name: this.participantName,\r\n media: {\r\n ...mediaState,\r\n camEnabled: nextEnabled,\r\n },\r\n });\r\n\r\n // Notify peers\r\n this.send({\r\n type: \"MEDIA_STATE\",\r\n kind: \"video\",\r\n enabled: nextEnabled,\r\n });\r\n }\r\n\r\n private scheduleReconnect() {\r\n if (!this.roomId) return;\r\n\r\n const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);\r\n\r\n clearTimeout(this.reconnectTimer);\r\n\r\n this.reconnectTimer = window.setTimeout(async () => {\r\n try {\r\n await this.connect(this.roomId!, this.participantName);\r\n\r\n this.reconnectAttempts = 0;\r\n } catch {\r\n this.reconnectAttempts++;\r\n this.scheduleReconnect();\r\n }\r\n }, delay);\r\n }\r\n\r\n private startHeartbeat() {\r\n this.stopHeartbeat();\r\n\r\n this.pingInterval = setInterval(() => {\r\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\r\n\r\n this.send({\r\n type: \"PING\",\r\n client_ts: Date.now(),\r\n });\r\n }, 20000); // every 20s\r\n }\r\n private stopHeartbeat() {\r\n if (this.pingInterval) {\r\n clearInterval(this.pingInterval);\r\n this.pingInterval = null;\r\n }\r\n }\r\n\r\n // ---------------- RESET ----------------\r\n private reset() {\r\n Object.values(this.peers).forEach((pc) => pc.close());\r\n\r\n this.peers = {};\r\n this.initiators.clear();\r\n this.pendingIceCandidates = {};\r\n\r\n this.state.resetRemoteState();\r\n }\r\n\r\n // ---------------- HANDLE SIGNALS ----------------\r\n private async handle(msg: any) {\r\n if (msg.sender === this.myId) return;\r\n\r\n switch (msg.type) {\r\n case \"EXISTING_USERS\":\r\n if (msg.presenterId) {\r\n this.state.setPresenterId(msg.presenterId);\r\n\r\n // Trigger your event so the UI knows to render the stage\r\n this.events.onScreenShareStarted?.(msg.presenterId, null!);\r\n }\r\n\r\n for (const p of msg.participants || []) {\r\n if (!p?.id || p.id === this.myId) continue;\r\n this.state.addParticipant(p);\r\n this.events.onUserJoined?.(p);\r\n await this.createOffer(p.id);\r\n }\r\n break;\r\n\r\n case \"JOINED\": {\r\n this.startHeartbeat();\r\n this.joinResolver?.();\r\n this.joinResolver = undefined;\r\n this.joinRejecter = undefined;\r\n break;\r\n }\r\n case \"USER_JOINED\": {\r\n const p = msg.participant;\r\n\r\n if (!p?.id || p.id === this.myId) return;\r\n\r\n this.state.addParticipant(p);\r\n\r\n this.events.onUserJoined?.(p);\r\n await this.createOffer(p.id);\r\n\r\n break;\r\n }\r\n\r\n case \"OFFER\":\r\n await this.handleOffer(msg.payload, msg.sender);\r\n break;\r\n\r\n case \"ANSWER\": {\r\n const pc = this.peers[msg.sender];\r\n\r\n if (!pc) return;\r\n\r\n if (pc.signalingState !== \"have-local-offer\") {\r\n console.warn(\r\n `[Signaling] Unexpected ANSWER in state \"${pc.signalingState}\", ignoring`,\r\n );\r\n return;\r\n }\r\n\r\n try {\r\n await pc.setRemoteDescription({\r\n type: \"answer\",\r\n sdp: msg.payload,\r\n });\r\n\r\n await this.flushIce(msg.sender, pc);\r\n } catch (err) {\r\n console.error(\"[Signaling] Failed to apply answer:\", err);\r\n this.emitError(\r\n \"ANSWER_FAILED\",\r\n `Failed to apply answer from ${msg.sender}`,\r\n err,\r\n true,\r\n );\r\n }\r\n break;\r\n }\r\n\r\n case \"ICE\": {\r\n const candidate = JSON.parse(msg.payload);\r\n\r\n let pc = this.peers[msg.sender];\r\n\r\n if (!pc) {\r\n this.pendingIceCandidates[msg.sender] ??= [];\r\n this.pendingIceCandidates[msg.sender].push(candidate);\r\n break;\r\n }\r\n\r\n if (!pc.remoteDescription) {\r\n this.pendingIceCandidates[msg.sender] ??= [];\r\n this.pendingIceCandidates[msg.sender].push(candidate);\r\n break;\r\n }\r\n\r\n try {\r\n await pc.addIceCandidate(candidate);\r\n } catch (err) {\r\n console.warn(\"ICE error:\", err);\r\n }\r\n\r\n break;\r\n }\r\n\r\n case \"USER_LEFT\":\r\n const peerId = msg.participant.id;\r\n this.closePeer(peerId);\r\n\r\n this.state.removeParticipant(peerId);\r\n\r\n this.events.onUserLeft?.(peerId);\r\n\r\n break;\r\n\r\n case \"MEDIA_STATE_CHANGE\": {\r\n const peerId = msg.peerId;\r\n const { kind, enabled } = msg;\r\n\r\n // 1. Sync the app state layer for UI rendering components\r\n if (kind === \"audio\") {\r\n this.state.updateParticipantMedia(peerId, { micEnabled: enabled });\r\n this.events.onMicToggled?.(peerId, enabled);\r\n } else if (kind === \"video\") {\r\n this.state.updateParticipantMedia(peerId, { camEnabled: enabled });\r\n this.events.onCamToggled?.(peerId, enabled);\r\n }\r\n\r\n break;\r\n }\r\n\r\n case \"CHAT_MESSAGE\": {\r\n const newMsg = msg.data;\r\n\r\n if (newMsg.sender_id === this.myId) break; // already added optimistically\r\n this.state.addChatMessage({\r\n id: newMsg.id,\r\n text: newMsg.message,\r\n sender_id: newMsg.sender_id,\r\n sender_name: newMsg.sender_name,\r\n timestamp: new Date(newMsg.timestamp).getTime(),\r\n target: newMsg.target,\r\n });\r\n\r\n this.events.onChatMessage?.(msg);\r\n break;\r\n }\r\n case \"SCREEN_SHARE_START\": {\r\n const peerId = msg.peerId;\r\n\r\n this.state.updateParticipantMedia(peerId, {\r\n isScreenSharing: true,\r\n remoteScreenStreamId: msg.stream_id,\r\n });\r\n\r\n if (!this.state.presenterId) {\r\n this.state.setPresenterId(peerId);\r\n }\r\n\r\n // Fix: Use screenStream instead of the regular camera stream\r\n const screenStream =\r\n this.state.getParticipant(peerId)?.media?.screenStream;\r\n\r\n this.events.onScreenShareStarted?.(peerId, screenStream || null!);\r\n break;\r\n }\r\n\r\n case \"SCREEN_SHARE_STOP\": {\r\n const peerId = msg.peerId;\r\n this.state.updateParticipantMedia(peerId, { isScreenSharing: false });\r\n if (this.state.presenterId === peerId) {\r\n this.state.setPresenterId(null);\r\n }\r\n this.events.onScreenShareStopped?.(peerId);\r\n break;\r\n }\r\n case \"ERROR\": {\r\n const fatal = msg?.fatal === true;\r\n\r\n this.emitError(\r\n \"WS_ERROR\",\r\n msg?.message || \"Unknown error\",\r\n msg,\r\n !fatal,\r\n );\r\n\r\n if (fatal) {\r\n this.disconnect();\r\n }\r\n\r\n return;\r\n }\r\n }\r\n }\r\n\r\n // ---------------- PEER ----------------\r\n private createPeer(id: string) {\r\n if (!this.localStream) throw new Error(\"No local stream\");\r\n console.log(\r\n \"Adding tracks\",\r\n this.localStream.getTracks().map((t) => ({\r\n kind: t.kind,\r\n enabled: t.enabled,\r\n state: t.readyState,\r\n })),\r\n );\r\n\r\n const pc = new RTCPeerConnection({\r\n iceServers: [\r\n {\r\n urls: \"stun:stun.relay.metered.ca:80\",\r\n },\r\n {\r\n urls: \"turn:global.relay.metered.ca:80\",\r\n username: \"25aed888d2d360e9fae0e812\",\r\n credential: \"WPYstojO9Wf3+HsQ\",\r\n },\r\n {\r\n urls: \"turn:global.relay.metered.ca:80?transport=tcp\",\r\n username: \"25aed888d2d360e9fae0e812\",\r\n credential: \"WPYstojO9Wf3+HsQ\",\r\n },\r\n {\r\n urls: \"turn:global.relay.metered.ca:443\",\r\n username: \"25aed888d2d360e9fae0e812\",\r\n credential: \"WPYstojO9Wf3+HsQ\",\r\n },\r\n {\r\n urls: \"turns:global.relay.metered.ca:443?transport=tcp\",\r\n username: \"25aed888d2d360e9fae0e812\",\r\n credential: \"WPYstojO9Wf3+HsQ\",\r\n },\r\n ],\r\n });\r\n\r\n pc.ontrack = (event) => {\r\n console.log(`Track received: ${event.track.kind}`, {\r\n trackId: event.track.id,\r\n streamCount: event.streams.length,\r\n streamTrackCount: event.streams[0]?.getTracks().length,\r\n });\r\n const incomingStream = event.streams[0];\r\n const participant = this.state.getParticipant(id);\r\n\r\n const isScreenStream =\r\n incomingStream.id === participant?.media?.remoteScreenStreamId;\r\n\r\n if (isScreenStream) {\r\n const videoTrack =\r\n event.track.kind === \"video\"\r\n ? event.track\r\n : incomingStream.getVideoTracks()[0] ||\r\n participant?.media?.screenTrack;\r\n\r\n this.state.updateParticipantMedia(id, {\r\n screenStream: incomingStream,\r\n screenTrack: videoTrack,\r\n\r\n isScreenSharing: true,\r\n });\r\n\r\n if (!this.state.presenterId) {\r\n this.state.setPresenterId(id);\r\n }\r\n\r\n this.events.onScreenShareStarted?.(id, incomingStream);\r\n } else {\r\n this.state.updateParticipantMedia(id, {\r\n stream: incomingStream,\r\n cameraTrack: incomingStream.getVideoTracks()[0],\r\n audioTrack: incomingStream.getAudioTracks()[0],\r\n });\r\n this.events.onTrack?.(incomingStream, id);\r\n }\r\n };\r\n\r\n pc.onicecandidate = (e) => {\r\n if (!e.candidate) return;\r\n this.send({\r\n type: \"ICE\",\r\n payload: JSON.stringify(e.candidate),\r\n sender: this.myId,\r\n target: id,\r\n });\r\n };\r\n\r\n pc.oniceconnectionstatechange = () => {\r\n console.log(`ICE Connection State: ${pc.iceConnectionState}`);\r\n if (\r\n pc.iceConnectionState === \"failed\" ||\r\n pc.iceConnectionState === \"disconnected\"\r\n ) {\r\n // Trigger a UI notification to the user that the connection is unstable\r\n this.emitError(\r\n \"ICE_FAILED\",\r\n \"Connection failed. Please check your network or refresh.\",\r\n );\r\n }\r\n };\r\n\r\n pc.onconnectionstatechange = () => {\r\n if (pc.connectionState === \"failed\") {\r\n try {\r\n pc.restartIce();\r\n } catch {}\r\n }\r\n };\r\n\r\n this.localStream.getTracks().forEach((track) => {\r\n pc.addTrack(track, this.localStream!);\r\n });\r\n\r\n if (this.isScreenSharing && this.screenStream) {\r\n this.screenSenders[id] = [];\r\n this.screenStream.getTracks().forEach((track) => {\r\n const sender = pc.addTrack(track, this.screenStream!);\r\n this.screenSenders[id].push(sender);\r\n });\r\n }\r\n\r\n return pc;\r\n }\r\n\r\n // ---------------- OFFER ----------------\r\n private async createOffer(id: string, isRenegotiation = false) {\r\n if (!isRenegotiation && this.initiators.has(id)) {\r\n console.debug(\r\n `[Offer] Already initiating with ${id}, skipping duplicate`,\r\n );\r\n return;\r\n }\r\n\r\n if (isRenegotiation && this.peers[id]) {\r\n const pc = this.peers[id];\r\n if (pc.signalingState !== \"stable\") {\r\n console.warn(\r\n `[Offer] Cannot renegotiate: peer in state \"${pc.signalingState}\"`,\r\n );\r\n return;\r\n }\r\n }\r\n\r\n if (!isRenegotiation) {\r\n this.initiators.add(id);\r\n }\r\n if (!this.peers[id]) {\r\n this.peers[id] = this.createPeer(id);\r\n }\r\n\r\n const pc = this.peers[id];\r\n\r\n try {\r\n const offer = await pc.createOffer();\r\n await pc.setLocalDescription(offer);\r\n\r\n this.send({\r\n type: \"OFFER\",\r\n payload: offer.sdp,\r\n sender: this.myId,\r\n target: id,\r\n });\r\n\r\n console.debug(`[Offer] Sent to ${id}`);\r\n } catch (err) {\r\n console.error(`[Offer] Failed for ${id}:`, err);\r\n this.emitError(\r\n \"OFFER_FAILED\",\r\n `Failed to create offer for ${id}`,\r\n err,\r\n true,\r\n );\r\n }\r\n }\r\n\r\n // ---------------- ANSWER ----------------\r\n private async handleOffer(sdp: string, id: string) {\r\n if (this.initiators.has(id) && this.peers[id]) {\r\n const pc = this.peers[id];\r\n if (pc.signalingState !== \"stable\") {\r\n console.warn(\r\n `[Signaling] Offer collision with ${id}. We initiated, ignoring their offer.`,\r\n );\r\n return; // We initiated, so ignore their offer\r\n }\r\n }\r\n\r\n if (!this.peers[id]) {\r\n this.peers[id] = this.createPeer(id);\r\n }\r\n\r\n const pc = this.peers[id];\r\n\r\n try {\r\n // ✅ Only set remote description if we're not already in negotiation\r\n if (\r\n pc.signalingState !== \"stable\" &&\r\n pc.signalingState !== \"have-local-offer\"\r\n ) {\r\n console.warn(\r\n `[Signaling] Cannot accept OFFER in state \"${pc.signalingState}\"`,\r\n );\r\n return;\r\n }\r\n\r\n await pc.setRemoteDescription({\r\n type: \"offer\",\r\n sdp,\r\n });\r\n\r\n const pending = this.pendingIceCandidates[id] || [];\r\n\r\n for (const candidate of pending) {\r\n try {\r\n await pc.addIceCandidate(candidate);\r\n } catch (err) {\r\n console.warn(\"[ICE] Failed to add candidate:\", err);\r\n }\r\n }\r\n\r\n delete this.pendingIceCandidates[id];\r\n\r\n const answer = await pc.createAnswer();\r\n\r\n await pc.setLocalDescription(answer);\r\n await this.flushIce(id, pc);\r\n\r\n this.send({\r\n type: \"ANSWER\",\r\n payload: answer.sdp,\r\n sender: this.myId,\r\n target: id,\r\n });\r\n\r\n console.debug(`[Answer] Sent to ${id}`);\r\n } catch (err) {\r\n console.error(`[Signaling] Failed to handle OFFER from ${id}:`, err);\r\n this.emitError(\r\n \"OFFER_HANDLING_FAILED\",\r\n `Failed to handle offer from ${id}`,\r\n err,\r\n true,\r\n );\r\n }\r\n }\r\n\r\n // ---------------- CLEANUP ----------------\r\n private closePeer(id: string) {\r\n const pc = this.peers[id];\r\n\r\n if (!pc) return;\r\n\r\n pc.ontrack = null;\r\n pc.onicecandidate = null;\r\n pc.onconnectionstatechange = null;\r\n\r\n pc.close();\r\n\r\n delete this.peers[id];\r\n\r\n this.initiators.delete(id);\r\n\r\n this.state.removeParticipant(id);\r\n }\r\n\r\n async startScreenShare() {\r\n try {\r\n if (this.state.presenterId && this.state.presenterId !== this.myId) {\r\n throw new Error(\"Another user is already sharing their screen.\");\r\n }\r\n if (!navigator.mediaDevices?.getDisplayMedia) {\r\n throw new Error(\"Screen sharing not supported on this device\");\r\n }\r\n\r\n this.screenStream = await navigator.mediaDevices.getDisplayMedia({\r\n video: true,\r\n // audio: true,\r\n });\r\n\r\n this.isScreenSharing = true;\r\n\r\n this.state.updateLocalParticipant({\r\n media: {\r\n isScreenSharing: true,\r\n screenStream: this.screenStream,\r\n screenTrack: this.screenStream.getVideoTracks()[0],\r\n },\r\n });\r\n\r\n this.state.setPresenterId(this.myId);\r\n // Handle the user clicking browser's built-in \"Stop Sharing\" button\r\n this.screenStream.getVideoTracks()[0].onended = () => {\r\n this.stopScreenShare();\r\n };\r\n\r\n Object.entries(this.peers).forEach(([peerId, pc]) => {\r\n this.screenSenders[peerId] = [];\r\n this.screenStream!.getTracks().forEach((track) => {\r\n const sender = pc.addTrack(track, this.screenStream!);\r\n this.screenSenders[peerId].push(sender);\r\n });\r\n\r\n // Renegotiate peer connection descriptors to notify remote side of new track footprint\r\n this.createOffer(peerId, true);\r\n });\r\n\r\n this.send({\r\n type: \"SCREEN_SHARE_START\",\r\n sender: this.myId,\r\n room_id: this.roomId,\r\n stream_id: this.screenStream.id.replace(/[{}]/g, \"\"),\r\n });\r\n\r\n return this.screenStream;\r\n } catch (err: any) {\r\n this.emitError(\r\n \"SCREEN_SHARE_FAILED\",\r\n err?.message || \"Failed to start screen sharing\",\r\n err,\r\n true,\r\n );\r\n\r\n this.isScreenSharing = false;\r\n this.screenStream = null;\r\n throw err;\r\n }\r\n }\r\n\r\n stopScreenShare() {\r\n if (!this.screenStream) return;\r\n\r\n this.screenStream.getTracks().forEach((t) => t.stop());\r\n\r\n // Remove tracks cleanly from WebRTC channel pathways across your peers\r\n Object.entries(this.peers).forEach(([peerId, pc]) => {\r\n const senders = this.screenSenders[peerId] || [];\r\n senders.forEach((sender) => {\r\n try {\r\n pc.removeTrack(sender);\r\n } catch (err) {\r\n console.warn(err);\r\n }\r\n });\r\n delete this.screenSenders[peerId];\r\n\r\n // Renegotiate layout expectations to scale down stream bounds\r\n this.createOffer(peerId, true);\r\n });\r\n\r\n this.screenStream = null;\r\n this.isScreenSharing = false;\r\n\r\n this.state.updateLocalParticipant({\r\n media: {\r\n isScreenSharing: false,\r\n screenStream: null,\r\n screenTrack: undefined,\r\n },\r\n });\r\n\r\n if (this.state.presenterId === this.myId) {\r\n this.state.setPresenterId(null);\r\n }\r\n\r\n this.send({\r\n type: \"SCREEN_SHARE_STOP\",\r\n sender: this.myId,\r\n room_id: this.roomId,\r\n });\r\n }\r\n\r\n sendChatMessage(payload: ChatInput) {\r\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\r\n console.warn(\"WS not connected\");\r\n return;\r\n }\r\n\r\n if (!this.roomId) {\r\n console.warn(\"No roomId set\");\r\n return;\r\n }\r\n\r\n const isPrivate = !!payload?.target;\r\n\r\n const senderName = this.state.localParticipant?.name || \"Anonymous\";\r\n\r\n const msg: ChatMessage = {\r\n id: crypto.randomUUID(),\r\n sender_id: this.myId,\r\n sender_name: senderName,\r\n text: payload.message.trim(),\r\n timestamp: Date.now(),\r\n reply_to: payload.reply_to ?? null,\r\n target: payload.target ?? null,\r\n };\r\n\r\n // optimistic UI update\r\n this.state.addChatMessage(msg);\r\n\r\n // send protocol payload (clean + consistent)\r\n this.send({\r\n type: \"CHAT_MESSAGE\",\r\n message: payload.message.trim(),\r\n user_id: this.myId,\r\n sender_name: senderName,\r\n room_id: this.roomId,\r\n target: isPrivate ? (payload.target ?? null) : null,\r\n reply_to: payload.reply_to ?? null,\r\n\r\n client_ts: Date.now(),\r\n });\r\n }\r\n\r\n disconnect() {\r\n this.stopScreenShare();\r\n\r\n Object.values(this.peers).forEach((pc) => pc.close());\r\n this.peers = {};\r\n this.initiators.clear();\r\n\r\n this.stopHeartbeat();\r\n\r\n if (this.ws) {\r\n this.ws.close();\r\n this.ws = null;\r\n }\r\n\r\n if (this.localStream) {\r\n this.localStream.getTracks().forEach((track) => track.stop());\r\n this.localStream = null;\r\n }\r\n\r\n this.roomId = null;\r\n\r\n // Clear and notify\r\n this.state.localParticipant = null;\r\n this.state.notify(\"localParticipant\");\r\n\r\n this.state.participants.clear();\r\n this.state.notify(\"participants\");\r\n\r\n this.state.clearChat();\r\n this.state.setPresenterId(null);\r\n }\r\n\r\n private async flushIce(id: string, pc: RTCPeerConnection) {\r\n const pending = this.pendingIceCandidates[id];\r\n if (!pending?.length) return;\r\n\r\n for (const candidate of pending) {\r\n try {\r\n await pc.addIceCandidate(candidate);\r\n } catch (e) {\r\n console.warn(\"ICE flush error\", e);\r\n }\r\n }\r\n\r\n delete this.pendingIceCandidates[id];\r\n }\r\n\r\n private send(msg: any) {\r\n this.ws?.send(JSON.stringify(msg));\r\n }\r\n}\r\n","import { useEffect, useState } from \"react\";\r\nimport { StateScope } from \"../types/meeting\";\r\nimport { MeetingState } from \"../core/MeetingState\";\r\n\r\nexport function useMeetingStore<T>(\r\n stateManager: MeetingState,\r\n scope: StateScope,\r\n selector: (state: MeetingState) => T,\r\n): T {\r\n const [state, setState] = useState(() => selector(stateManager));\r\n\r\n useEffect(() => {\r\n // Update local react state whenever the SDK notifies this scope\r\n const unsubscribe = stateManager.subscribe(scope, () => {\r\n setState(selector(stateManager));\r\n });\r\n\r\n return unsubscribe;\r\n }, [stateManager, scope, selector]);\r\n\r\n return state;\r\n}\r\n","import { useEffect } from \"react\";\r\nimport { useMeetingContext } from \"./MeetingProvider\";\r\n\r\nexport const useMeeting = (handlers?: { onError?: (err: any) => void }) => {\r\n const ctx = useMeetingContext();\r\n\r\n useEffect(() => {\r\n if (!handlers?.onError) return;\r\n\r\n const unsubscribe = ctx.onError(handlers.onError);\r\n return unsubscribe;\r\n }, [handlers?.onError]);\r\n\r\n return ctx;\r\n};\r\n","import { useEffect, useState } from \"react\";\r\nimport { useMeetingContext } from \"./MeetingProvider\";\r\nimport { Participant } from \"../types/meeting\";\r\n\r\nexport const useParticipants = () => {\r\n const { sdk } = useMeetingContext();\r\n\r\n const [participants, setParticipants] = useState<Participant[]>(() =>\r\n sdk.state.getParticipants(),\r\n );\r\n\r\n useEffect(() => {\r\n const update = () => {\r\n setParticipants(sdk.state.getParticipants());\r\n };\r\n\r\n update();\r\n\r\n const unsub = sdk.state.subscribe(\"participants\", update);\r\n\r\n return unsub;\r\n }, [sdk]);\r\n\r\n return participants;\r\n};\r\n","import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport { useMeetingContext } from \"./MeetingProvider\";\r\nimport { Participant } from \"../types/meeting\";\r\n\r\nexport const useRemoteMedia = (participantId: string) => {\r\n const { sdk } = useMeetingContext();\r\n const [participant, setParticipant] = useState<Participant | null>(\r\n () => sdk.state.getParticipant(participantId) || null,\r\n );\r\n\r\n const buildMediaStream = (participant: Participant | null) => {\r\n if (!participant?.media) return null;\r\n\r\n const stream = new MediaStream();\r\n\r\n const videoTrack = participant.media.stream?.getVideoTracks?.()?.[0];\r\n const audioTrack = participant.media.stream?.getAudioTracks?.()?.[0];\r\n\r\n if (videoTrack) stream.addTrack(videoTrack);\r\n if (audioTrack) stream.addTrack(audioTrack);\r\n\r\n return stream;\r\n };\r\n\r\n useEffect(() => {\r\n const unsub = sdk.state.subscribe(`participant:${participantId}`, () => {\r\n const p = sdk.state.getParticipant(participantId);\r\n setParticipant(p ? { ...p } : null);\r\n });\r\n return unsub;\r\n }, [participantId, sdk]);\r\n\r\n // Video Callback Ref\r\n const videoRef = useCallback(\r\n (node: HTMLVideoElement | null) => {\r\n if (!node) return;\r\n\r\n const stream = participant?.media?.stream;\r\n if (!stream) return;\r\n\r\n // IMPORTANT: always force rebind (no conditions)\r\n node.srcObject = stream;\r\n\r\n node.muted = true;\r\n node.playsInline = true;\r\n node.autoplay = true;\r\n\r\n node.play().catch((err) => {\r\n // ignore autoplay restrictions\r\n console.log(\"can't play video:\", err);\r\n });\r\n },\r\n [participant?.media?.stream],\r\n );\r\n\r\n // Audio Callback Ref\r\n const audioRef = useCallback(\r\n (node: HTMLAudioElement | null) => {\r\n if (!node) return;\r\n\r\n const stream = participant?.media?.stream;\r\n if (!stream) return;\r\n\r\n node.srcObject = stream;\r\n\r\n node.muted = false;\r\n\r\n node.play().catch((err) => {\r\n console.log(\"can't play Audio:\", err);\r\n });\r\n },\r\n [participant?.media?.stream],\r\n );\r\n\r\n return {\r\n videoRef,\r\n audioRef,\r\n isCamActive: !!participant?.media?.camEnabled,\r\n isMicEnabled: !!participant?.media?.micEnabled,\r\n };\r\n};\r\n"],"mappings":"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","names":["useEffect","useRef","useState","peerId","pc","useState","useEffect","useRef","useEffect","useEffect","useEffect","useState","useState","useEffect","useCallback","useEffect","useState","useState","participant","useEffect","useCallback"]}
|
|
1
|
+
{"version":3,"sources":["../src/react/useLocalParticipant.tsx","../src/react/MeetingProvider.tsx","../src/config/ws.ts","../src/core/MeetingState.ts","../src/core/VideoCore.ts","../src/react/useMeetingStore.ts","../src/react/useMeeting.ts","../src/react/useParticipants.ts","../src/react/useRemoteMedia.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport { useMeetingContext } from \"./MeetingProvider\";\r\nimport { Participant } from \"../types/meeting\";\r\n\r\nexport const useLocalParticipant = () => {\r\n const { sdk } = useMeetingContext();\r\n\r\n // Guard the initial state to ensure it matches Participant | null\r\n const [localParticipant, setLocalParticipant] = useState<Participant | null>(\r\n () => {\r\n const current = sdk.state.localParticipant;\r\n return current && current.id ? (current as Participant) : null;\r\n },\r\n );\r\n\r\n useEffect(() => {\r\n const unsubscribe = sdk.state.subscribe(\"localParticipant\", () => {\r\n const current = sdk.state.localParticipant;\r\n\r\n // Safe Type-Guard: Only update if the object has a finalized id\r\n if (current && current.id) {\r\n setLocalParticipant({ ...current } as Participant);\r\n } else {\r\n setLocalParticipant(null);\r\n }\r\n });\r\n\r\n return unsubscribe;\r\n }, [sdk]);\r\n\r\n const lastStreamRef = useRef<MediaStream | null>(null);\r\n\r\n const videoRef = useCallback(\r\n (video: HTMLVideoElement | null) => {\r\n if (!video) return;\r\n\r\n const stream = localParticipant?.media?.stream;\r\n if (!stream) return;\r\n\r\n if (lastStreamRef.current === stream) return;\r\n lastStreamRef.current = stream;\r\n\r\n video.srcObject = stream;\r\n video.autoplay = true;\r\n video.playsInline = true;\r\n\r\n video.play().catch((err) => {\r\n console.warn(`Autoplay failed for local view:`, err);\r\n });\r\n },\r\n [localParticipant?.media?.stream],\r\n );\r\n\r\n return {\r\n participant: localParticipant,\r\n videoRef,\r\n };\r\n};\r\n","import React, { createContext, useContext, useMemo, useRef } from \"react\";\r\nimport { VideoSDKCore } from \"../core/VideoCore\";\r\nimport {\r\n ChatInput,\r\n ChatMessage,\r\n MeetingConfig,\r\n Participant,\r\n PubSubTopic,\r\n} from \"../types/meeting\";\r\nimport { useMeetingStore } from \"./useMeetingStore\";\r\n\r\ntype PubSubHandle = {\r\n messages: ChatMessage[];\r\n publish: (input: ChatInput) => void;\r\n};\r\n\r\ntype MeetingContextValue = {\r\n sdk: VideoSDKCore;\r\n\r\n join: (config: MeetingConfig) => Promise<void>;\r\n leave: () => void;\r\n\r\n toggleMic: () => void;\r\n toggleCam: () => void;\r\n\r\n startScreenShare: () => Promise<MediaStream>;\r\n stopScreenShare: () => void;\r\n\r\n sendMessage: (input: ChatInput) => void;\r\n\r\n meetingId: string | null;\r\n localParticipant: Participant | null;\r\n participants: Map<string, Participant>;\r\n messages: ChatMessage[];\r\n presenterId: string | null;\r\n usePubSub: (topic: PubSubTopic) => PubSubHandle;\r\n onError: (cb: (err: any) => void) => () => void;\r\n};\r\n\r\nconst MeetingContext = createContext<MeetingContextValue | null>(null);\r\n\r\nexport const MeetingProvider = ({\r\n config,\r\n children,\r\n}: {\r\n config: MeetingConfig;\r\n children: React.ReactNode;\r\n}) => {\r\n const sdkRef = useRef<VideoSDKCore | null>(null);\r\n const errorListeners = useRef(new Set<(err: any) => void>());\r\n if (!sdkRef.current) {\r\n sdkRef.current = new VideoSDKCore({\r\n onError: (err) => {\r\n errorListeners.current.forEach((fn) => fn(err));\r\n },\r\n });\r\n }\r\n\r\n const sdk = sdkRef.current;\r\n const presenterId = useMeetingStore(\r\n sdk.state,\r\n \"presenter\",\r\n (s) => s.presenterId,\r\n );\r\n const participants = useMeetingStore(\r\n sdk.state,\r\n \"participants\",\r\n (s) => s.participants,\r\n );\r\n const localParticipant = useMeetingStore(\r\n sdk.state,\r\n \"localParticipant\",\r\n (s) => s.localParticipant,\r\n );\r\n const messages = useMeetingStore(sdk.state, \"chat\", (s) =>\r\n s.getChatMessages(),\r\n );\r\n\r\n const value = useMemo<MeetingContextValue>(() => {\r\n if (!sdkRef.current) {\r\n sdkRef.current = new VideoSDKCore({\r\n onError: (err) => {\r\n errorListeners.current.forEach((fn) => fn(err));\r\n },\r\n });\r\n }\r\n\r\n return {\r\n sdk,\r\n\r\n join: (joinConfig: MeetingConfig) =>\r\n sdk.joinMeeting({\r\n ...config,\r\n ...joinConfig,\r\n }),\r\n leave: () => sdk.disconnect(),\r\n toggleMic: sdk.toggleMic.bind(sdk),\r\n toggleCam: sdk.toggleCam.bind(sdk),\r\n startScreenShare: sdk.startScreenShare.bind(sdk),\r\n stopScreenShare: sdk.stopScreenShare.bind(sdk),\r\n sendMessage: sdk.sendChatMessage.bind(sdk),\r\n\r\n meetingId: sdk.getMeetingId(),\r\n localParticipant,\r\n participants,\r\n messages,\r\n presenterId,\r\n usePubSub: (topic: PubSubTopic) => {\r\n if (topic !== \"SECURE_CHAT\") {\r\n throw new Error(`Unsupported PubSub argument: \"${topic}\"`);\r\n }\r\n return {\r\n messages: sdk.state.getChatMessages(),\r\n publish: sdk.sendChatMessage.bind(sdk),\r\n };\r\n },\r\n onError: (cb: (err: any) => void) => {\r\n errorListeners.current.add(cb);\r\n\r\n return () => {\r\n errorListeners.current.delete(cb);\r\n };\r\n },\r\n };\r\n }, [config, sdk, localParticipant, participants, messages, presenterId]);\r\n\r\n return (\r\n <MeetingContext.Provider value={value}>{children}</MeetingContext.Provider>\r\n );\r\n};\r\n\r\nexport const useMeetingContext = () => {\r\n const ctx = useContext(MeetingContext);\r\n if (!ctx)\r\n throw new Error(\"useMeetingContext must be used inside <MeetingProvider>\");\r\n return ctx;\r\n};\r\n","export const SDK_CONFIG = {\r\n wsUrl: \"wss://rust-video-server-sfyf.onrender.com/ws\",\r\n};\r\n","import {\r\n ChatMessage,\r\n Listener,\r\n Participant,\r\n ParticipantMedia,\r\n StateScope,\r\n} from \"../types/meeting\";\r\n\r\ntype LocalParticipantPatch = {\r\n id?: string;\r\n name?: string;\r\n media?: Partial<ParticipantMedia>;\r\n};\r\n\r\nexport class MeetingState {\r\n participants = new Map<string, Participant>();\r\n localParticipant: Participant | null = null;\r\n localStream: MediaStream | null = null;\r\n chatMessages = new Map<string, ChatMessage>();\r\n presenterId: string | null = null;\r\n\r\n private listeners = new Map<StateScope, Set<Listener>>();\r\n\r\n // ---- reactive system ----\r\n\r\n subscribe(scope: StateScope, fn: Listener): () => void {\r\n if (!this.listeners.has(scope)) {\r\n this.listeners.set(scope, new Set());\r\n }\r\n this.listeners.get(scope)!.add(fn);\r\n\r\n return () => {\r\n this.listeners.get(scope)?.delete(fn);\r\n };\r\n }\r\n\r\n notify(scope: StateScope) {\r\n this.listeners.get(scope)?.forEach((fn) => fn());\r\n }\r\n\r\n setPresenterId(id: string | null) {\r\n if (this.presenterId === id) return;\r\n this.presenterId = id;\r\n this.notify(\"presenter\");\r\n this.notify(\"participants\");\r\n }\r\n\r\n // ---- participants ----\r\n\r\n addParticipant(p: Participant) {\r\n if (this.participants.has(p.id)) return false;\r\n // Fix: Immutable Map update\r\n const next = new Map(this.participants);\r\n next.set(p.id, p);\r\n this.participants = next;\r\n\r\n this.notify(\"participants\");\r\n return true;\r\n }\r\n\r\n removeParticipant(id: string) {\r\n // Fix: Immutable Map update\r\n const next = new Map(this.participants);\r\n next.delete(id);\r\n this.participants = next;\r\n\r\n this.notify(\"participants\");\r\n }\r\n\r\n updateParticipantMedia(\r\n id: string,\r\n patch: Partial<NonNullable<Participant[\"media\"]>>,\r\n ) {\r\n const p = this.participants.get(id);\r\n if (!p) return;\r\n\r\n const updated: Participant = {\r\n ...p,\r\n media: {\r\n stream: null,\r\n screenStream: undefined,\r\n cameraTrack: undefined,\r\n screenTrack: undefined,\r\n audioTrack: undefined,\r\n micEnabled: true,\r\n camEnabled: true,\r\n isScreenSharing: false,\r\n ...p.media, // preserve existing media items if they happen to exist\r\n ...patch, // apply the incoming stream updates\r\n },\r\n };\r\n\r\n // Keep your clean immutable map update\r\n const next = new Map(this.participants);\r\n next.set(id, updated);\r\n this.participants = next;\r\n\r\n this.notify(`participant:${id}`);\r\n this.notify(\"participants\");\r\n }\r\n\r\n updateLocalParticipant(patch: LocalParticipantPatch) {\r\n const prev = this.localParticipant;\r\n\r\n if (!prev) {\r\n this.localParticipant = {\r\n id: patch.id ?? \"\",\r\n name: patch.name ?? \"\",\r\n media: {\r\n stream: patch.media?.stream ?? null, // ◄ FIX: Capture the stream from the patch here\r\n screenStream: patch.media?.screenStream,\r\n cameraTrack: patch.media?.cameraTrack,\r\n screenTrack: patch.media?.screenTrack,\r\n audioTrack: patch.media?.audioTrack,\r\n micEnabled: patch.media?.micEnabled ?? true,\r\n camEnabled: patch.media?.camEnabled ?? true,\r\n isScreenSharing: patch.media?.isScreenSharing ?? false,\r\n },\r\n };\r\n\r\n this.notify(\"localParticipant\");\r\n return;\r\n }\r\n\r\n const prevMedia = prev.media ?? {\r\n stream: null,\r\n screenStream: undefined,\r\n cameraTrack: undefined,\r\n screenTrack: undefined,\r\n audioTrack: undefined,\r\n micEnabled: true,\r\n camEnabled: true,\r\n isScreenSharing: false,\r\n };\r\n\r\n const nextMedia: ParticipantMedia = {\r\n stream: patch.media?.stream ?? prevMedia.stream,\r\n screenStream: patch.media?.screenStream ?? prevMedia.screenStream,\r\n cameraTrack: patch.media?.cameraTrack ?? prevMedia.cameraTrack,\r\n screenTrack: patch.media?.screenTrack ?? prevMedia.screenTrack,\r\n audioTrack: patch.media?.audioTrack ?? prevMedia.audioTrack,\r\n micEnabled: patch.media?.micEnabled ?? prevMedia.micEnabled,\r\n camEnabled: patch.media?.camEnabled ?? prevMedia.camEnabled,\r\n isScreenSharing:\r\n patch.media?.isScreenSharing ?? prevMedia.isScreenSharing,\r\n };\r\n\r\n this.localParticipant = {\r\n ...prev,\r\n id: patch.id ?? prev.id,\r\n name: patch.name ?? prev.name,\r\n media: nextMedia,\r\n };\r\n\r\n this.notify(\"localParticipant\");\r\n }\r\n\r\n // ---- chat ----\r\n\r\n addChatMessage(msg: ChatMessage) {\r\n this.chatMessages.set(msg.id, msg);\r\n this.notify(\"chat\");\r\n }\r\n\r\n getChatMessages() {\r\n return Array.from(this.chatMessages.values()).sort(\r\n (a, b) => a.timestamp - b.timestamp,\r\n );\r\n }\r\n\r\n clearChat() {\r\n this.chatMessages.clear();\r\n this.notify(\"chat\");\r\n }\r\n\r\n // ---- helpers ----\r\n\r\n getParticipants() {\r\n return Array.from(this.participants.values());\r\n }\r\n\r\n getParticipant(id: string) {\r\n return this.participants.get(id) ?? null;\r\n }\r\n\r\n resetRemoteState() {\r\n this.participants.clear();\r\n this.chatMessages.clear();\r\n this.presenterId = null;\r\n this.notify(\"participants\");\r\n this.notify(\"chat\");\r\n this.notify(\"presenter\");\r\n }\r\n}\r\n","import { SDK_CONFIG } from \"../config/ws\";\r\nimport {\r\n ChatInput,\r\n ChatMessage,\r\n Events,\r\n MeetingConfig,\r\n SDKError,\r\n} from \"../types/meeting\";\r\nimport { MeetingState } from \"./MeetingState\";\r\n\r\nexport class VideoSDKCore {\r\n private ws: WebSocket | null = null;\r\n private peers: Record<string, RTCPeerConnection> = {};\r\n private initiators = new Set<string>();\r\n private lastPong = Date.now();\r\n private intentionalDisconnect = false;\r\n\r\n private myId: string;\r\n private roomId: string | null = null;\r\n private localStream: MediaStream | null = null;\r\n private screenStream: MediaStream | null = null;\r\n private isScreenSharing = false;\r\n private screenSenders: Record<string, RTCRtpSender[]> = {};\r\n\r\n private pingInterval: any = null;\r\n private pendingIceCandidates: Record<string, RTCIceCandidateInit[]> = {};\r\n private reconnectAttempts = 0;\r\n private reconnectTimer?: number;\r\n private participantName = \"\";\r\n public readonly state: MeetingState;\r\n private joinResolver?: () => void;\r\n private joinRejecter?: (e: any) => void;\r\n private emitError(\r\n code: string,\r\n message: string,\r\n raw?: any,\r\n recoverable = true,\r\n ) {\r\n const err: SDKError = {\r\n code,\r\n message,\r\n raw,\r\n roomId: this.roomId,\r\n userId: this.myId,\r\n recoverable,\r\n };\r\n\r\n this.events.onError?.(err);\r\n\r\n this.joinRejecter?.(err);\r\n this.joinRejecter = undefined;\r\n\r\n console.error(\"[MeetingSDK Error]\", err);\r\n }\r\n\r\n constructor(\r\n private events: Events = {},\r\n private url: string = SDK_CONFIG.wsUrl,\r\n ) {\r\n this.state = new MeetingState();\r\n this.events = events;\r\n this.url = url;\r\n\r\n this.myId = localStorage.getItem(\"vsdk_id\") || crypto.randomUUID();\r\n\r\n localStorage.setItem(\"vsdk_id\", this.myId);\r\n }\r\n\r\n // ---------------- STREAM ----------------\r\n async initLocal(video: HTMLVideoElement, name: string) {\r\n this.participantName = name;\r\n\r\n if (!this.localStream) {\r\n this.localStream = await navigator.mediaDevices.getUserMedia({\r\n video: true,\r\n audio: true,\r\n });\r\n }\r\n\r\n video.srcObject = this.localStream;\r\n\r\n // Fix: Supply mandatory fields to satisfy the Participant type constraint\r\n this.state.updateLocalParticipant({\r\n id: this.myId,\r\n name: this.participantName,\r\n media: {\r\n stream: this.localStream,\r\n micEnabled: true,\r\n camEnabled: true,\r\n isScreenSharing: false,\r\n },\r\n });\r\n\r\n this.state.localStream = this.localStream;\r\n }\r\n\r\n // ---------------- CONNECT ----------------\r\n async connect(roomId: string, name: string) {\r\n this.roomId = roomId;\r\n\r\n this.reset();\r\n\r\n return new Promise<void>((resolve, reject) => {\r\n this.joinResolver = resolve;\r\n this.joinRejecter = reject;\r\n this.ws = new WebSocket(this.url);\r\n\r\n this.ws.onopen = () => {\r\n this.send({\r\n type: \"JOIN\",\r\n room_id: roomId,\r\n user_id: this.myId,\r\n sender_name: name,\r\n });\r\n };\r\n\r\n this.ws.onerror = (err) => {\r\n this.emitError(\"WS_ERROR\", \"WebSocket encountered an error\", err, true);\r\n };\r\n\r\n this.ws.onclose = (e) => {\r\n this.joinRejecter?.({\r\n code: \"WS_CLOSED\",\r\n message: \"Connection closed before join completed\",\r\n raw: e,\r\n });\r\n\r\n this.joinRejecter = undefined;\r\n\r\n if (this.intentionalDisconnect) {\r\n return; // do NOT reconnect\r\n }\r\n\r\n if (e.code === 1000 || e.code === 1001) {\r\n return;\r\n }\r\n\r\n this.scheduleReconnect();\r\n };\r\n\r\n this.ws.onmessage = async (e) => {\r\n await this.handle(JSON.parse(e.data));\r\n };\r\n });\r\n }\r\n\r\n async joinMeeting(config: MeetingConfig) {\r\n const { roomId, name, audioMuted = false, videoMuted = false } = config;\r\n\r\n if (!roomId || !name) {\r\n throw new Error(\"roomId and name are required to join meeting\");\r\n }\r\n\r\n this.participantName = name;\r\n\r\n // Reuse existing stream if initLocal already configured it\r\n if (!this.localStream) {\r\n this.localStream = await navigator.mediaDevices.getUserMedia({\r\n video: true,\r\n audio: true,\r\n });\r\n }\r\n\r\n this.localStream.getAudioTracks().forEach((t) => {\r\n t.enabled = !audioMuted;\r\n });\r\n this.localStream.getVideoTracks().forEach((t) => {\r\n t.enabled = !videoMuted;\r\n });\r\n\r\n this.state.updateLocalParticipant({\r\n id: this.myId,\r\n name: this.participantName,\r\n media: {\r\n stream: this.localStream,\r\n micEnabled: !audioMuted,\r\n camEnabled: !videoMuted,\r\n isScreenSharing: false,\r\n },\r\n });\r\n\r\n this.state.localStream = this.localStream;\r\n\r\n await this.connect(roomId, name);\r\n }\r\n\r\n /** Expose the roomId without making it fully public */\r\n getMeetingId(): string | null {\r\n return this.roomId;\r\n }\r\n\r\n toggleMic() {\r\n const mediaState = this.state.localParticipant?.media;\r\n if (!mediaState) return;\r\n\r\n const nextEnabled = !mediaState.micEnabled;\r\n\r\n this.localStream\r\n ?.getAudioTracks()\r\n .forEach((t) => (t.enabled = nextEnabled));\r\n\r\n // Update state layer\r\n this.state.updateLocalParticipant({\r\n id: this.myId,\r\n name: this.participantName,\r\n media: {\r\n ...mediaState,\r\n micEnabled: nextEnabled,\r\n },\r\n });\r\n\r\n // Notify peers\r\n this.send({\r\n type: \"MEDIA_STATE\",\r\n kind: \"audio\",\r\n enabled: nextEnabled,\r\n });\r\n }\r\n\r\n toggleCam() {\r\n const mediaState = this.state.localParticipant?.media;\r\n if (!mediaState) return;\r\n\r\n const nextEnabled = !mediaState.camEnabled;\r\n\r\n this.localStream\r\n ?.getVideoTracks()\r\n .forEach((t) => (t.enabled = nextEnabled));\r\n\r\n // Update state layer\r\n this.state.updateLocalParticipant({\r\n id: this.myId,\r\n name: this.participantName,\r\n media: {\r\n ...mediaState,\r\n camEnabled: nextEnabled,\r\n },\r\n });\r\n\r\n // Notify peers\r\n this.send({\r\n type: \"MEDIA_STATE\",\r\n kind: \"video\",\r\n enabled: nextEnabled,\r\n });\r\n }\r\n\r\n private scheduleReconnect() {\r\n if (!this.roomId) return;\r\n\r\n const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);\r\n\r\n clearTimeout(this.reconnectTimer);\r\n\r\n this.reconnectTimer = window.setTimeout(async () => {\r\n try {\r\n await this.connect(this.roomId!, this.participantName);\r\n\r\n this.reconnectAttempts = 0;\r\n } catch {\r\n this.reconnectAttempts++;\r\n this.scheduleReconnect();\r\n }\r\n }, delay);\r\n }\r\n\r\n private startHeartbeat() {\r\n this.stopHeartbeat();\r\n\r\n this.pingInterval = setInterval(() => {\r\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\r\n\r\n this.send({\r\n type: \"PING\",\r\n client_ts: Date.now(),\r\n });\r\n }, 20000); // every 20s\r\n }\r\n private stopHeartbeat() {\r\n if (this.pingInterval) {\r\n clearInterval(this.pingInterval);\r\n this.pingInterval = null;\r\n }\r\n }\r\n\r\n // ---------------- RESET ----------------\r\n private reset() {\r\n Object.values(this.peers).forEach((pc) => pc.close());\r\n\r\n this.peers = {};\r\n this.initiators.clear();\r\n this.pendingIceCandidates = {};\r\n\r\n this.state.resetRemoteState();\r\n }\r\n\r\n // ---------------- HANDLE SIGNALS ----------------\r\n private async handle(msg: any) {\r\n if (msg.sender === this.myId) return;\r\n\r\n switch (msg.type) {\r\n case \"PONG\":\r\n this.lastPong = Date.now();\r\n break;\r\n case \"OFFER\":\r\n await this.handleOffer(msg.payload, msg.sender);\r\n break;\r\n\r\n case \"ANSWER\": {\r\n const pc = this.peers[msg.sender];\r\n\r\n if (!pc) return;\r\n\r\n if (pc.signalingState !== \"have-local-offer\") {\r\n console.warn(\r\n `[Signaling] Unexpected ANSWER in state \"${pc.signalingState}\", ignoring`,\r\n );\r\n return;\r\n }\r\n\r\n try {\r\n await pc.setRemoteDescription({\r\n type: \"answer\",\r\n sdp: msg.payload,\r\n });\r\n\r\n await this.flushIce(msg.sender, pc);\r\n } catch (err) {\r\n console.error(\"[Signaling] Failed to apply answer:\", err);\r\n this.emitError(\r\n \"ANSWER_FAILED\",\r\n `Failed to apply answer from ${msg.sender}`,\r\n err,\r\n true,\r\n );\r\n }\r\n break;\r\n }\r\n case \"ICE\": {\r\n const candidate = JSON.parse(msg.payload);\r\n\r\n let pc = this.peers[msg.sender];\r\n\r\n if (!pc) {\r\n this.pendingIceCandidates[msg.sender] ??= [];\r\n this.pendingIceCandidates[msg.sender].push(candidate);\r\n break;\r\n }\r\n\r\n if (!pc.remoteDescription) {\r\n this.pendingIceCandidates[msg.sender] ??= [];\r\n this.pendingIceCandidates[msg.sender].push(candidate);\r\n break;\r\n }\r\n\r\n try {\r\n await pc.addIceCandidate(candidate);\r\n } catch (err) {\r\n console.warn(\"ICE error:\", err);\r\n }\r\n\r\n break;\r\n }\r\n case \"EXISTING_USERS\":\r\n if (msg.presenterId) {\r\n this.state.setPresenterId(msg.presenterId);\r\n\r\n // Trigger your event so the UI knows to render the stage\r\n this.events.onScreenShareStarted?.(msg.presenterId, null!);\r\n }\r\n\r\n for (const p of msg.participants || []) {\r\n if (!p?.id || p.id === this.myId) continue;\r\n this.state.addParticipant(p);\r\n this.events.onUserJoined?.(p);\r\n await this.createOffer(p.id);\r\n }\r\n break;\r\n\r\n case \"JOINED\": {\r\n this.intentionalDisconnect = false;\r\n this.reconnectAttempts = 0;\r\n this.startHeartbeat();\r\n this.joinResolver?.();\r\n this.joinResolver = undefined;\r\n this.joinRejecter = undefined;\r\n break;\r\n }\r\n case \"USER_JOINED\": {\r\n const p = msg.participant;\r\n\r\n if (!p?.id || p.id === this.myId) return;\r\n\r\n this.state.addParticipant(p);\r\n\r\n this.events.onUserJoined?.(p);\r\n await this.createOffer(p.id);\r\n\r\n break;\r\n }\r\n\r\n case \"USER_LEFT\":\r\n const peerId = msg.participant.id;\r\n this.closePeer(peerId);\r\n\r\n this.state.removeParticipant(peerId);\r\n\r\n this.events.onUserLeft?.(peerId);\r\n\r\n break;\r\n\r\n case \"MEDIA_STATE_CHANGE\": {\r\n const peerId = msg.peerId;\r\n const { kind, enabled } = msg;\r\n\r\n // 1. Sync the app state layer for UI rendering components\r\n if (kind === \"audio\") {\r\n this.state.updateParticipantMedia(peerId, { micEnabled: enabled });\r\n this.events.onMicToggled?.(peerId, enabled);\r\n } else if (kind === \"video\") {\r\n this.state.updateParticipantMedia(peerId, { camEnabled: enabled });\r\n this.events.onCamToggled?.(peerId, enabled);\r\n }\r\n\r\n break;\r\n }\r\n\r\n case \"CHAT_MESSAGE\": {\r\n const newMsg = msg.data;\r\n\r\n if (newMsg.sender_id === this.myId) break; // already added optimistically\r\n this.state.addChatMessage({\r\n id: newMsg.id,\r\n text: newMsg.message,\r\n sender_id: newMsg.sender_id,\r\n sender_name: newMsg.sender_name,\r\n timestamp: new Date(newMsg.timestamp).getTime(),\r\n target: newMsg.target,\r\n });\r\n\r\n this.events.onChatMessage?.(msg);\r\n break;\r\n }\r\n case \"SCREEN_SHARE_START\": {\r\n const peerId = msg.peerId;\r\n\r\n this.state.updateParticipantMedia(peerId, {\r\n isScreenSharing: true,\r\n remoteScreenStreamId: msg.stream_id,\r\n });\r\n\r\n if (!this.state.presenterId) {\r\n this.state.setPresenterId(peerId);\r\n }\r\n\r\n // Fix: Use screenStream instead of the regular camera stream\r\n const screenStream =\r\n this.state.getParticipant(peerId)?.media?.screenStream;\r\n\r\n this.events.onScreenShareStarted?.(peerId, screenStream || null!);\r\n break;\r\n }\r\n\r\n case \"SCREEN_SHARE_STOP\": {\r\n const peerId = msg.peerId;\r\n this.state.updateParticipantMedia(peerId, { isScreenSharing: false });\r\n if (this.state.presenterId === peerId) {\r\n this.state.setPresenterId(null);\r\n }\r\n this.events.onScreenShareStopped?.(peerId);\r\n break;\r\n }\r\n case \"ERROR\": {\r\n const fatal = msg?.fatal === true;\r\n\r\n this.emitError(\r\n \"WS_ERROR\",\r\n msg?.message || \"Unknown error\",\r\n msg,\r\n !fatal,\r\n );\r\n\r\n if (fatal) {\r\n this.disconnect();\r\n }\r\n\r\n return;\r\n }\r\n }\r\n }\r\n\r\n // ---------------- PEER ----------------\r\n private createPeer(id: string) {\r\n if (!this.localStream) throw new Error(\"No local stream\");\r\n console.log(\r\n \"Adding tracks\",\r\n this.localStream.getTracks().map((t) => ({\r\n kind: t.kind,\r\n enabled: t.enabled,\r\n state: t.readyState,\r\n })),\r\n );\r\n\r\n const pc = new RTCPeerConnection({\r\n iceServers: [\r\n {\r\n urls: \"stun:stun.relay.metered.ca:80\",\r\n },\r\n {\r\n urls: \"turn:global.relay.metered.ca:80\",\r\n username: \"25aed888d2d360e9fae0e812\",\r\n credential: \"WPYstojO9Wf3+HsQ\",\r\n },\r\n {\r\n urls: \"turn:global.relay.metered.ca:80?transport=tcp\",\r\n username: \"25aed888d2d360e9fae0e812\",\r\n credential: \"WPYstojO9Wf3+HsQ\",\r\n },\r\n {\r\n urls: \"turn:global.relay.metered.ca:443\",\r\n username: \"25aed888d2d360e9fae0e812\",\r\n credential: \"WPYstojO9Wf3+HsQ\",\r\n },\r\n {\r\n urls: \"turns:global.relay.metered.ca:443?transport=tcp\",\r\n username: \"25aed888d2d360e9fae0e812\",\r\n credential: \"WPYstojO9Wf3+HsQ\",\r\n },\r\n ],\r\n });\r\n\r\n pc.ontrack = (event) => {\r\n console.log(`Track received: ${event.track.kind}`, {\r\n trackId: event.track.id,\r\n streamCount: event.streams.length,\r\n streamTrackCount: event.streams[0]?.getTracks().length,\r\n });\r\n const incomingStream =\r\n event.streams?.[0] || new MediaStream([event.track]);\r\n const participant = this.state.getParticipant(id);\r\n\r\n const isScreenStream =\r\n incomingStream.id === participant?.media?.remoteScreenStreamId;\r\n\r\n if (event.track.kind === \"video\") {\r\n event.track.onunmute = () => {\r\n console.log(\"Video track unmuted for\", id);\r\n };\r\n }\r\n\r\n if (isScreenStream) {\r\n const videoTrack =\r\n event.track.kind === \"video\"\r\n ? event.track\r\n : incomingStream.getVideoTracks()[0] ||\r\n participant?.media?.screenTrack;\r\n\r\n this.state.updateParticipantMedia(id, {\r\n screenStream: incomingStream,\r\n screenTrack: videoTrack,\r\n\r\n isScreenSharing: true,\r\n });\r\n\r\n if (!this.state.presenterId) {\r\n this.state.setPresenterId(id);\r\n }\r\n\r\n this.events.onScreenShareStarted?.(id, incomingStream);\r\n } else {\r\n this.state.updateParticipantMedia(id, {\r\n stream: incomingStream,\r\n cameraTrack: incomingStream.getVideoTracks()[0],\r\n audioTrack: incomingStream.getAudioTracks()[0],\r\n });\r\n this.events.onTrack?.(incomingStream, id);\r\n }\r\n };\r\n\r\n pc.onicecandidate = (e) => {\r\n if (!e.candidate) return;\r\n this.send({\r\n type: \"ICE\",\r\n payload: JSON.stringify(e.candidate),\r\n sender: this.myId,\r\n target: id,\r\n });\r\n };\r\n\r\n pc.oniceconnectionstatechange = () => {\r\n console.log(`ICE Connection State: ${pc.iceConnectionState}`);\r\n };\r\n\r\n pc.onconnectionstatechange = () => {\r\n if (pc.connectionState === \"failed\") {\r\n try {\r\n pc.restartIce();\r\n } catch {}\r\n }\r\n };\r\n\r\n this.localStream.getTracks().forEach((track) => {\r\n pc.addTrack(track, this.localStream!);\r\n });\r\n\r\n if (this.isScreenSharing && this.screenStream) {\r\n this.screenSenders[id] = [];\r\n this.screenStream.getTracks().forEach((track) => {\r\n const sender = pc.addTrack(track, this.screenStream!);\r\n this.screenSenders[id].push(sender);\r\n });\r\n }\r\n\r\n return pc;\r\n }\r\n\r\n // ---------------- OFFER ----------------\r\n private async createOffer(id: string, isRenegotiation = false) {\r\n if (!isRenegotiation && this.initiators.has(id)) {\r\n console.debug(\r\n `[Offer] Already initiating with ${id}, skipping duplicate`,\r\n );\r\n }\r\n\r\n if (isRenegotiation && this.peers[id]) {\r\n const pc = this.peers[id];\r\n if (pc.signalingState !== \"stable\") {\r\n console.warn(\r\n `[Offer] Cannot renegotiate: peer in state \"${pc.signalingState}\"`,\r\n );\r\n }\r\n }\r\n\r\n if (!isRenegotiation) {\r\n this.initiators.add(id);\r\n }\r\n if (!this.peers[id]) {\r\n this.peers[id] = this.createPeer(id);\r\n }\r\n\r\n const pc = this.peers[id];\r\n\r\n try {\r\n const offer = await pc.createOffer();\r\n await pc.setLocalDescription(offer);\r\n\r\n this.send({\r\n type: \"OFFER\",\r\n payload: offer.sdp,\r\n sender: this.myId,\r\n target: id,\r\n });\r\n\r\n console.debug(`[Offer] Sent to ${id}`);\r\n } catch (err) {\r\n console.error(`[Offer] Failed for ${id}:`, err);\r\n this.emitError(\r\n \"OFFER_FAILED\",\r\n `Failed to create offer for ${id}`,\r\n err,\r\n true,\r\n );\r\n }\r\n }\r\n\r\n // ---------------- ANSWER ----------------\r\n private async handleOffer(sdp: string, id: string) {\r\n if (!this.peers[id]) {\r\n this.peers[id] = this.createPeer(id);\r\n }\r\n\r\n const pc = this.peers[id];\r\n\r\n try {\r\n // ✅ Only set remote description if we're not already in negotiation\r\n if (\r\n pc.signalingState !== \"stable\" &&\r\n pc.signalingState !== \"have-local-offer\"\r\n ) {\r\n console.warn(\r\n `[Signaling] Cannot accept OFFER in state \"${pc.signalingState}\"`,\r\n );\r\n return;\r\n }\r\n\r\n await pc.setRemoteDescription({\r\n type: \"offer\",\r\n sdp,\r\n });\r\n\r\n const pending = this.pendingIceCandidates[id] || [];\r\n\r\n for (const candidate of pending) {\r\n try {\r\n await pc.addIceCandidate(candidate);\r\n } catch (err) {\r\n console.warn(\"[ICE] Failed to add candidate:\", err);\r\n }\r\n }\r\n\r\n delete this.pendingIceCandidates[id];\r\n\r\n const answer = await pc.createAnswer();\r\n\r\n await pc.setLocalDescription(answer);\r\n await this.flushIce(id, pc);\r\n\r\n this.send({\r\n type: \"ANSWER\",\r\n payload: answer.sdp,\r\n sender: this.myId,\r\n target: id,\r\n });\r\n\r\n console.debug(`[Answer] Sent to ${id}`);\r\n } catch (err) {\r\n console.error(`[Signaling] Failed to handle OFFER from ${id}:`, err);\r\n this.emitError(\r\n \"OFFER_HANDLING_FAILED\",\r\n `Failed to handle offer from ${id}`,\r\n err,\r\n true,\r\n );\r\n }\r\n }\r\n\r\n // ---------------- CLEANUP ----------------\r\n private closePeer(id: string) {\r\n const pc = this.peers[id];\r\n\r\n if (!pc) return;\r\n\r\n pc.ontrack = null;\r\n pc.onicecandidate = null;\r\n pc.onconnectionstatechange = null;\r\n\r\n pc.close();\r\n\r\n delete this.peers[id];\r\n\r\n this.initiators.delete(id);\r\n\r\n this.state.removeParticipant(id);\r\n }\r\n\r\n async startScreenShare() {\r\n try {\r\n if (this.state.presenterId && this.state.presenterId !== this.myId) {\r\n throw new Error(\"Another user is already sharing their screen.\");\r\n }\r\n if (!navigator.mediaDevices?.getDisplayMedia) {\r\n throw new Error(\"Screen sharing not supported on this device\");\r\n }\r\n\r\n this.screenStream = await navigator.mediaDevices.getDisplayMedia({\r\n video: true,\r\n // audio: true,\r\n });\r\n\r\n this.isScreenSharing = true;\r\n\r\n this.state.updateLocalParticipant({\r\n media: {\r\n isScreenSharing: true,\r\n screenStream: this.screenStream,\r\n screenTrack: this.screenStream.getVideoTracks()[0],\r\n },\r\n });\r\n\r\n this.state.setPresenterId(this.myId);\r\n // Handle the user clicking browser's built-in \"Stop Sharing\" button\r\n this.screenStream.getVideoTracks()[0].onended = () => {\r\n this.stopScreenShare();\r\n };\r\n\r\n Object.entries(this.peers).forEach(([peerId, pc]) => {\r\n this.screenSenders[peerId] = [];\r\n this.screenStream!.getTracks().forEach((track) => {\r\n const sender = pc.addTrack(track, this.screenStream!);\r\n this.screenSenders[peerId].push(sender);\r\n });\r\n\r\n // Renegotiate peer connection descriptors to notify remote side of new track footprint\r\n this.createOffer(peerId, true);\r\n });\r\n\r\n this.send({\r\n type: \"SCREEN_SHARE_START\",\r\n sender: this.myId,\r\n room_id: this.roomId,\r\n stream_id: this.screenStream.id.replace(/[{}]/g, \"\"),\r\n });\r\n\r\n return this.screenStream;\r\n } catch (err: any) {\r\n this.emitError(\r\n \"SCREEN_SHARE_FAILED\",\r\n err?.message || \"Failed to start screen sharing\",\r\n err,\r\n true,\r\n );\r\n\r\n this.isScreenSharing = false;\r\n this.screenStream = null;\r\n throw err;\r\n }\r\n }\r\n\r\n stopScreenShare() {\r\n if (!this.screenStream) return;\r\n\r\n this.screenStream.getTracks().forEach((t) => t.stop());\r\n\r\n // Remove tracks cleanly from WebRTC channel pathways across your peers\r\n Object.entries(this.peers).forEach(([peerId, pc]) => {\r\n const senders = this.screenSenders[peerId] || [];\r\n senders.forEach((sender) => {\r\n try {\r\n pc.removeTrack(sender);\r\n } catch (err) {\r\n console.warn(err);\r\n }\r\n });\r\n delete this.screenSenders[peerId];\r\n\r\n // Renegotiate layout expectations to scale down stream bounds\r\n this.createOffer(peerId, true);\r\n });\r\n\r\n this.screenStream = null;\r\n this.isScreenSharing = false;\r\n\r\n this.state.updateLocalParticipant({\r\n media: {\r\n isScreenSharing: false,\r\n screenStream: null,\r\n screenTrack: undefined,\r\n },\r\n });\r\n\r\n if (this.state.presenterId === this.myId) {\r\n this.state.setPresenterId(null);\r\n }\r\n\r\n this.send({\r\n type: \"SCREEN_SHARE_STOP\",\r\n sender: this.myId,\r\n room_id: this.roomId,\r\n });\r\n }\r\n\r\n sendChatMessage(payload: ChatInput) {\r\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\r\n console.warn(\"WS not connected\");\r\n return;\r\n }\r\n\r\n if (!this.roomId) {\r\n console.warn(\"No roomId set\");\r\n return;\r\n }\r\n\r\n const isPrivate = !!payload?.target;\r\n\r\n const senderName = this.state.localParticipant?.name || \"Anonymous\";\r\n\r\n const msg: ChatMessage = {\r\n id: crypto.randomUUID(),\r\n sender_id: this.myId,\r\n sender_name: senderName,\r\n text: payload.message.trim(),\r\n timestamp: Date.now(),\r\n reply_to: payload.reply_to ?? null,\r\n target: payload.target ?? null,\r\n };\r\n\r\n // optimistic UI update\r\n this.state.addChatMessage(msg);\r\n\r\n // send protocol payload (clean + consistent)\r\n this.send({\r\n type: \"CHAT_MESSAGE\",\r\n message: payload.message.trim(),\r\n user_id: this.myId,\r\n sender_name: senderName,\r\n room_id: this.roomId,\r\n target: isPrivate ? (payload.target ?? null) : null,\r\n reply_to: payload.reply_to ?? null,\r\n\r\n client_ts: Date.now(),\r\n });\r\n }\r\n\r\n disconnect() {\r\n this.intentionalDisconnect = true;\r\n this.stopScreenShare();\r\n\r\n Object.values(this.peers).forEach((pc) => pc.close());\r\n this.peers = {};\r\n this.initiators.clear();\r\n\r\n this.stopHeartbeat();\r\n\r\n if (this.ws) {\r\n this.ws.close();\r\n this.ws = null;\r\n }\r\n\r\n if (this.localStream) {\r\n this.localStream.getTracks().forEach((track) => track.stop());\r\n this.localStream = null;\r\n }\r\n\r\n this.roomId = null;\r\n\r\n // Clear and notify\r\n this.state.localParticipant = null;\r\n this.state.notify(\"localParticipant\");\r\n\r\n this.state.participants.clear();\r\n this.state.notify(\"participants\");\r\n\r\n this.state.clearChat();\r\n this.state.setPresenterId(null);\r\n }\r\n\r\n private async flushIce(id: string, pc: RTCPeerConnection) {\r\n const pending = this.pendingIceCandidates[id];\r\n if (!pending?.length) return;\r\n\r\n for (const candidate of pending) {\r\n try {\r\n await pc.addIceCandidate(candidate);\r\n } catch (e) {\r\n console.warn(\"ICE flush error\", e);\r\n }\r\n }\r\n\r\n delete this.pendingIceCandidates[id];\r\n }\r\n\r\n private send(msg: any) {\r\n this.ws?.send(JSON.stringify(msg));\r\n }\r\n}\r\n","import { useEffect, useState } from \"react\";\r\nimport { StateScope } from \"../types/meeting\";\r\nimport { MeetingState } from \"../core/MeetingState\";\r\n\r\nexport function useMeetingStore<T>(\r\n stateManager: MeetingState,\r\n scope: StateScope,\r\n selector: (state: MeetingState) => T,\r\n): T {\r\n const [state, setState] = useState(() => selector(stateManager));\r\n\r\n useEffect(() => {\r\n // Update local react state whenever the SDK notifies this scope\r\n const unsubscribe = stateManager.subscribe(scope, () => {\r\n setState(selector(stateManager));\r\n });\r\n\r\n return unsubscribe;\r\n }, [stateManager, scope, selector]);\r\n\r\n return state;\r\n}\r\n","import { useEffect } from \"react\";\r\nimport { useMeetingContext } from \"./MeetingProvider\";\r\n\r\nexport const useMeeting = (handlers?: { onError?: (err: any) => void }) => {\r\n const ctx = useMeetingContext();\r\n\r\n useEffect(() => {\r\n if (!handlers?.onError) return;\r\n\r\n const unsubscribe = ctx.onError(handlers.onError);\r\n return unsubscribe;\r\n }, [handlers?.onError]);\r\n\r\n return ctx;\r\n};\r\n","import { useEffect, useState } from \"react\";\r\nimport { useMeetingContext } from \"./MeetingProvider\";\r\nimport { Participant } from \"../types/meeting\";\r\n\r\nexport const useParticipants = () => {\r\n const { sdk } = useMeetingContext();\r\n\r\n const [participants, setParticipants] = useState<Participant[]>(() =>\r\n sdk.state.getParticipants(),\r\n );\r\n\r\n useEffect(() => {\r\n const update = () => {\r\n setParticipants(sdk.state.getParticipants());\r\n };\r\n\r\n update();\r\n\r\n const unsub = sdk.state.subscribe(\"participants\", update);\r\n\r\n return unsub;\r\n }, [sdk]);\r\n\r\n return participants;\r\n};\r\n","import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport { useMeetingContext } from \"./MeetingProvider\";\r\nimport { Participant } from \"../types/meeting\";\r\n\r\nexport const useRemoteMedia = (participantId: string) => {\r\n const { sdk } = useMeetingContext();\r\n const videoRef = useRef<HTMLVideoElement | null>(null);\r\n const audioRef = useRef<HTMLAudioElement | null>(null);\r\n\r\n const [participant, setParticipant] = useState<Participant | null>(\r\n () => sdk.state.getParticipant(participantId) || null,\r\n );\r\n\r\n useEffect(() => {\r\n const unsub = sdk.state.subscribe(`participant:${participantId}`, () => {\r\n const p = sdk.state.getParticipant(participantId);\r\n setParticipant(p ? { ...p } : null);\r\n });\r\n\r\n return unsub;\r\n }, [participantId, sdk]);\r\n\r\n useEffect(() => {\r\n const stream = participant?.media?.stream;\r\n if (!stream) return;\r\n\r\n // VIDEO\r\n if (videoRef.current) {\r\n videoRef.current.srcObject = stream;\r\n videoRef.current.muted = true;\r\n videoRef.current.playsInline = true;\r\n videoRef.current.play().catch(() => {});\r\n }\r\n\r\n // AUDIO\r\n if (audioRef.current) {\r\n audioRef.current.srcObject = stream;\r\n audioRef.current.play().catch(() => {});\r\n }\r\n }, [participant?.media?.stream]);\r\n\r\n return {\r\n videoRef,\r\n audioRef,\r\n isCamActive: !!participant?.media?.camEnabled,\r\n isMicEnabled: !!participant?.media?.micEnabled,\r\n };\r\n};\r\n"],"mappings":"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","names":["useEffect","useRef","useState","peerId","pc","useState","useEffect","useRef","useEffect","useEffect","useEffect","useState","useState","useEffect","useEffect","useRef","useState","useRef","useState","useEffect"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@afosecure/meetingsdk",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "A modern, lightweight React SDK for building peer-to-peer video communication applications. Built on WebRTC with a clean, composable rust API.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|