Это мой класс My Manager: < /p>
Код: Выделить всё
import { v4 as uuidv4 } from "uuid";
export class PeerService {
private ws: WebSocket;
private peerList: Map;
private localStream: MediaStream | null = null;
public remoteStreams: Map = new Map();
constructor(soc: WebSocket, localStream?: MediaStream) {
this.peerList = new Map();
this.ws = soc;
this.localStream = localStream || null;
}
// Add local stream to be shared with peers
async addLocalStream(stream: MediaStream) {
console.log("local stream");
this.localStream = stream;
// Add tracks to all existing peer connections
this.peerList.forEach((pc) => {
stream.getTracks().forEach((track) => {
pc.addTrack(track, stream);
});
});
}
// Remove local stream
removeLocalStream() {
if (this.localStream) {
this.peerList.forEach((pc) => {
const senders = pc.getSenders();
senders.forEach((sender) => {
if (
sender.track &&
this.localStream?.getTracks().includes(sender.track)
) {
pc.removeTrack(sender);
}
});
});
this.localStream = null;
}
}
// Get remote stream for a specific peer
getRemoteStream(peerID: string): MediaStream | null {
return this.remoteStreams.get(peerID) || null;
}
// Get all remote streams
getAllRemoteStreams(): Map {
return this.remoteStreams;
}
private setupTrackHandlers(pc: RTCPeerConnection, peerID: string) {
// Handle incoming remote tracks
pc.ontrack = (event: RTCTrackEvent) => {
console.log("tracks adde");
const remoteStream = event.streams[0];
console.log(event.streams);
if (remoteStream) {
this.remoteStreams.set(peerID, remoteStream);
// console.log(this.remoteStreams)
window.dispatchEvent(
new CustomEvent("remoteStreamAdded", {
detail: { peerID, stream: remoteStream },
})
);
}
};
if (this.localStream) {
console.log("localllllllllll");
this.localStream.getTracks().forEach((track) => {
pc.addTrack(track, this.localStream!);
});
}
}
async addPeer() {
console.log(
"adddd poeererererewrawgdsfg hdsfg jsfoghsdfogndsofngdsangakjb"
);
const peerID = uuidv4();
const pc = new RTCPeerConnection();
console.log("add peer");
this.setupTrackHandlers(pc, peerID);
pc.onicecandidate = (event: any) => {
// console.log("ws in PEER MANAGER", this.ws);
if (event.candidate && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(
JSON.stringify({
type: "ice-candidate",
payload: {
candidate: event.candidate,
peerID,
},
})
);
}
};
let offer = await pc.createOffer();
await pc.setLocalDescription(new RTCSessionDescription(offer));
console.log(pc.signalingState);
// console.log(`during offer peer :${peerID}`,pc)
if (this.ws) {
this.ws.send(
JSON.stringify({
type: "offer",
payload: {
peerID,
sdp: offer,
},
})
);
}
this.peerList.set(peerID, pc);
}
async handleSignal(message: any) {
console.log(message);
console.log(this.peerList);
let message_type = message.type;
/* let pc;
if (message.type !== "offer"){
console.log(message)
pc = this.peerList.get(message.payload.peerID);} */
//console.log(pc );
//console.log(message)
/* if (!pc) {
console.log("peer connection not found ");
return;
} */
console.log(message_type);
switch (message_type) {
case "offer": {
const peerID = message.payload.peerID;
const peerConnection = new RTCPeerConnection();
// Optional: Monitor ICE state
peerConnection.oniceconnectionstatechange = () => {
console.log(
"ICE state for",
peerID,
"→",
peerConnection.iceConnectionState
);
};
// ✅ Add local tracks BEFORE setting remote description
this.setupTrackHandlers(peerConnection, peerID); // This must add tracks if localStream exists
// ✅ Set remote description
await peerConnection.setRemoteDescription(
new RTCSessionDescription(message.payload.sdp)
);
// ✅ Store the peer connection immediately under the correct key
this.peerList.set(peerID, peerConnection);
// ✅ Create and send answer
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(
JSON.stringify({
type: "answer",
payload: {
peerID,
sdp: answer,
},
})
);
}
break;
}
case "answer":
console.log("answer");
const pc = this.peerList.get(message.payload.peerID);
console.log(pc);
if (!pc) return;
// if(pc.connectionState === )
// console.log(pc.signalingState);
// console.log(`during asnwer peer :${message.payload.peerID}`,pc)
await pc?.setRemoteDescription(
new RTCSessionDescription(message.payload.sdp)
);
break;
case "ice-candidate": {
const pc = this.peerList.get(message.payload.peerID);
if (!pc) return;
await pc?.addIceCandidate(
new RTCIceCandidate(message.payload.candidate)
);
break;
}
}
}
closePeer(peerID: string) {
const pc = this.peerList.get(peerID);
pc?.close();
this.peerList.delete(peerID);
this.remoteStreams.delete(peerID);
window.dispatchEvent(
new CustomEvent("remoteStreamRemoved", {
detail: { peerID },
})
);
}
closeAll() {
this.peerList.forEach((pc, peerId) => {
pc.close();
});
this.peerList.clear();
this.remoteStreams.clear();
this.removeLocalStream();
}
}
export default PeerService;
import React, { useState, useEffect, useRef } from "react";
import { useNavigate, useParams } from "react-router-dom";
import {
Mic,
MicOff,
Video,
VideoOff,
PhoneOff,
Users,
MessageSquare,
Share,
Settings,
} from "lucide-react";
import Button from "../ui/Button";
import { useMeetings } from "../../contexts/MeetingsContext";
import { useAuth } from "../../contexts/AuthContext";
import ParticipantGrid from "./ParticipantGrid";
import { Participant } from "../../types";
import { useSoc } from "../../hooks/usesoc";
import { PeerService } from "../../utils/peer";
const VideoConference: React.FC = () => {
const { getCurrentMeeting, leaveMeeting } = useMeetings();
const { user } = useAuth();
const meeting = getCurrentMeeting();
const [isMuted, setIsMuted] = useState(false);
const [isVideoOn, setIsVideoOn] = useState(true);
const [showParticipants, setShowParticipants] = useState(false);
const [showChat, setShowChat] = useState(false);
const [participants, setParticipants] = useState
([]);
const localVid = useRef(null);
const [stat, setStat] = useState(false);
const remoteVid = useRef(null);
const { roomid } = useParams();
const peerManager = useRef(null);
useEffect(() => {
const mockParticipants: Participant[] = [
{
id: user?.id || "1",
name: user?.name || "You",
email: user?.email || "",
avatar: user?.avatar,
isMuted: true,
isVideoOn: true,
isHost: true,
mediaStream: localVid,
},
];
setParticipants(mockParticipants);
}, [user]);
const soc = useSoc();
async function playVideoFromCamera() {
try {
const constraints: MediaStreamConstraints = {
video: {
width: { ideal: 1280 },
height: { ideal: 720 },
frameRate: { ideal: 30 },
facingMode: "user",
},
audio: {
echoCancellation: true,
noiseSuppression: true,
sampleRate: 44100,
},
};
console.log("Requesting user media with constraints:", constraints);
const stream = await navigator.mediaDevices.getUserMedia(constraints);
console.log("Media stream obtained:", stream);
console.log("Video tracks:", stream.getVideoTracks());
console.log("Audio tracks:", stream.getAudioTracks());
return stream;
} catch (error) {
console.error("Error opening video camera.", error);
return null;
}
}
function changeParticipants(s: Participant) {
setParticipants((prev: any) => {
console.log("remote stream", s.mediaStream);
return [...prev, s];
});
}
function populateRemoteStreams() {
peerManager.current?.remoteStreams.forEach((stream, peerId) => {
const alreadyExists = participants.some((p) => p.id === peerId);
if (alreadyExists) return;
console.log("pouplate");
const newRef = React.createRef();
changeParticipants({
id: peerId,
name: "yo",
avatar: "isudgfius",
isMuted: true,
isVideoOn: true,
isHost: false,
mediaStream: newRef,
stream: stream,
});
});
}
// Set up video independently of WebSocket
useEffect(() => {
if (peerManager.current == null) {
playVideoFromCamera().then(async (stream) => {
if (stream && localVid.current !== null) {
localVid.current.srcObject = stream;
// Force the video to play
localVid.current
.play()
.catch((e) => console.error("Error playing video:", e));
}
if (stream && stat) {
peerManager.current = new PeerService(
soc.current as WebSocket,
stream
);
peerManager.current.addPeer().then(async () => {
// await peerManager.current?.addLocalStream(stream as MediaStream);
});
// const stream = await playVideoFromCamera()
}
});
}
}, [stat]);
useEffect(() => {
let check = peerManager.current?.remoteStreams.size;
function checkRemoteStreams() {
if (peerManager.current) {
// console.log("Remote streams:", peerManager.current.remoteStreams);
if (check != peerManager.current?.remoteStreams.size) {
populateRemoteStreams();
check = peerManager.current.remoteStreams.size;
}
}
}
const intervalId = setInterval(checkRemoteStreams, 1000);
return () => clearInterval(intervalId);
}, []);
useEffect(() => {
if (soc.current)
soc.current.onopen = () => {
setStat(true);
};
if (!soc.current || soc.current.readyState !== WebSocket.OPEN) {
return;
}
if (soc.current)
soc.current.send(
JSON.stringify({
type: "create-room",
room_id: roomid,
})
);
}, [roomid, stat]);
// WebSocket message handling
useEffect(() => {
if (!soc.current) return;
const socket = soc.current;
socket.onmessage = (m) => {
if (peerManager.current)
peerManager.current.handleSignal(JSON.parse(m.data));
};
}, [soc.current?.readyState, stat]);
// Update local participant when video stream is available
useEffect(() => {
if (localVid.current && localVid.current.srcObject) {
setParticipants((prev) =>
prev.map((p) =>
p.id === user?.id
? { ...p, isVideoOn: true, mediaStream: localVid }
: p
)
);
}
}, [localVid.current?.srcObject, user?.id]);
const handleLeaveMeeting = () => {
leaveMeeting();
// navigate("/dashboard");
};
const toggleMute = () => {
setIsMuted(!isMuted);
// Update participant state
setParticipants((prev) =>
prev.map((p) => (p.id === user?.id ? { ...p, isMuted: !isMuted } : p))
);
};
const toggleVideo = () => {
setIsVideoOn(!isVideoOn);
// Update participant state
setParticipants((prev) =>
prev.map((p) => (p.id === user?.id ? { ...p, isVideoOn: !isVideoOn } : p))
);
};
const handleShare = () => {
// Copy meeting link to clipboard
const meetingLink = `${window.location.origin}/video-conference/${roomid}`;
navigator.clipboard.writeText(meetingLink).then(() => {
// Could add a toast notification here
console.log("Meeting link copied to clipboard");
});
};
const handleSettings = () => {
// Open settings modal or panel
console.log("Settings clicked");
};
return (
{/* Header */}
{meeting?.title || `Room: ${roomid}`}
Meeting ID: {meeting?.meetingCode || roomid}
•
{participants.length} participants
setShowParticipants(!showParticipants)}
leftIcon={}
>
Participants
{participants.length}
setShowChat(!showChat)}
leftIcon={}
>
Chat
{/* Main Content */}
{/* Video Grid */}
{/* Sidebar */}
{(showParticipants || showChat) && (
{showParticipants && (
Participants ({participants.length})
{participants.map((participant) => (
{!participant.isVideoOn ? (
[img]{
`

}
alt={participant.name}
className="w-8 h-8 rounded-full object-cover"
/>
) : (
)}
{participant.name}
{participant.isHost && (
(Host)
)}
{participant.isMuted ? (
) : (
)}
{participant.isVideoOn ? (
) : (
)}
))}
)}
{showChat && (
Chat
Chat feature coming soon...
)}
)}
{/* Controls */}
{isMuted ? (
) : (
)}
{isVideoOn ? (
) : (
)}
);
};
export default VideoConference;
< /code>
participantgrid.tsx
import React, { useEffect, useRef } from "react";
import { Mic, MicOff, Video, VideoOff, Crown } from "lucide-react";
import { Participant } from "../../types";
interface ParticipantGridProps {
participants: Participant[];
}
const ParticipantGrid: React.FC = ({ participants }) => {
const videoRefs = useRef({});
useEffect(() => {
participants.forEach((participant) => {
const videoEl = videoRefs.current[participant.id];
if (
participant.isVideoOn &&
videoEl &&
participant.stream &&
participant.stream.getTracks().length > 0 &&
videoEl.srcObject !== participant.stream
) {
videoEl.srcObject = null;
videoEl.srcObject = participant.stream;
videoEl
.play()
.then(() => console.log("Playing:", participant.id))
.catch((error) => {
console.error("Error playing video for", participant.id, error);
});
videoEl.onloadedmetadata = () => {
videoEl.play().catch((error) => {
console.error("Error playing video for", participant.id, error);
});
};
}
});
}, [participants]);
const getGridCols = () => {
const count = participants.length;
if (count === 1) return "grid-cols-1";
if (count === 2) return "grid-cols-2";
if (count {
console.log("Loaded metadata for", participant.id);
}}
onError={(e) =>
console.error("Video error for", participant.id, e)
}
/>
{/* */}
) : (
{participant.name?.charAt(0).toUpperCase()}
)
) : (
[img]{
`

}
alt={participant.name}
className="w-20 h-20 rounded-full object-cover border-4 border-gray-600"
/>
Camera off
)}
{/* Participant Info */}
{participant.name}
{participant.isHost && (
)}
{participant.isMuted ? (
) : (
)}
{/* Speaking Indicator */}
{!participant.isMuted && (
)}
))}
);
};
export default ParticipantGrid;
< /code>
Когда я пытаюсь использовать удаленные потоки, которые они регистрируются>
Подробнее здесь: https://stackoverflow.com/questions/796 ... d-in-react