good progress with server and client back and forth
- getting there slowly with console logs and understanding the steps along the way
This commit is contained in:
@@ -6,6 +6,7 @@ import { UserService } from "./services/user.service";
|
||||
import { UserStatus } from "@nirvana/core/models";
|
||||
import cors from "cors";
|
||||
import getConversationRoutes from "./routes/conversation";
|
||||
import getRtcRoutes from "./routes/rtc";
|
||||
import getSearchRoutes from "./routes/search";
|
||||
import getUserRoutes from "./routes/user";
|
||||
|
||||
@@ -23,12 +24,16 @@ app.get("/", (req: Request, res: Response) => {
|
||||
res.send("hello world.");
|
||||
});
|
||||
|
||||
app.use("/api/rtc", getRtcRoutes());
|
||||
|
||||
app.use("/api/user", getUserRoutes());
|
||||
app.use("/api/search", getSearchRoutes());
|
||||
app.use("/api/conversations", getConversationRoutes());
|
||||
|
||||
const PORT = 5000;
|
||||
var server = app.listen(PORT, () => console.log("express running"));
|
||||
var server = app.listen(PORT, () =>
|
||||
console.log(`express running on port ${PORT}`)
|
||||
);
|
||||
|
||||
// socket IO stuff
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
"google-auth-library": "^7.14.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"mongodb": "^4.4.1",
|
||||
"socket.io": "^4.4.1"
|
||||
"socket.io": "^4.4.1",
|
||||
"wrtc": "^0.4.7"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "NODE_ENV=development nodemon",
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import express, { Application, Request, Response } from "express";
|
||||
|
||||
const webrtc = require("wrtc");
|
||||
|
||||
export default function getRtcRoutes() {
|
||||
const router = express.Router();
|
||||
|
||||
router.use(express.json());
|
||||
|
||||
// get user details based on id token
|
||||
router.post("/join/:lineId", handleJoin);
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
// a mapping between a certain line and the streams for it
|
||||
const linesStreams: {
|
||||
[lineId: string]: any[];
|
||||
} = {};
|
||||
|
||||
async function handleJoin(req: Request, res: Response) {
|
||||
try {
|
||||
const { lineId } = req.params;
|
||||
const { sdp } = req.body;
|
||||
|
||||
const peer = new webrtc.RTCPeerConnection({
|
||||
iceServers: [
|
||||
{
|
||||
urls: "stun:stun.stunprotocol.org",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// allow this peer connection to receive other peoples' streams
|
||||
peer.ontrack = (e: any) => handleStreams(e, peer, lineId);
|
||||
|
||||
// allow this peer connection for this specific line to get tracks for this line already
|
||||
linesStreams[lineId]?.forEach((stream: any) => {
|
||||
stream.getTracks().forEach((track: any) => {
|
||||
console.log(`have tracks in this line`, track, stream);
|
||||
peer.addTrack(track, stream);
|
||||
});
|
||||
});
|
||||
|
||||
// create connection between server and client who hit this endpoint
|
||||
const desc = new webrtc.RTCSessionDescription(sdp);
|
||||
await peer.setRemoteDescription(desc);
|
||||
const answer = await peer.createAnswer();
|
||||
await peer.setLocalDescription(answer);
|
||||
|
||||
const payload = {
|
||||
sdp: peer.localDescription,
|
||||
};
|
||||
|
||||
res
|
||||
.status(200)
|
||||
.json({ message: `You can now broadcast to ${lineId}!`, data: payload });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
res.status(500).json({
|
||||
message: "Problem in setting up peer connection with media server",
|
||||
error,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleStreams(e: any, peer: any, lineId: string) {
|
||||
// if the room/line exists already, then add to the list of streams
|
||||
if (lineId in linesStreams) {
|
||||
const newStreamsForLine = [...(linesStreams[lineId] ?? []), ...e.streams];
|
||||
|
||||
linesStreams[lineId] = newStreamsForLine;
|
||||
} else {
|
||||
// create a new list of streams
|
||||
linesStreams[lineId] = [...e.streams];
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@nirvana/components": "*",
|
||||
"axios": "^0.27.2",
|
||||
"next": "12.1.4",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import axios from "axios";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
const lines = ["line1", "line2", "line3", "line4", "line5"];
|
||||
|
||||
// todo: join different combinations of lines based on page params
|
||||
// for now, every browser tab/client joins all 5 lines and streams to one specific one
|
||||
|
||||
export default function VideoChat() {
|
||||
const [selectedLine, setSelectedLine] = useState<string>(lines[0]);
|
||||
const videoRef = useRef<HTMLVideoElement>();
|
||||
|
||||
// connect to media server for line connections as a broadcaster and consumer
|
||||
useEffect(() => {
|
||||
navigator.mediaDevices
|
||||
.getUserMedia({ video: true, audio: true })
|
||||
.then((mediaStream: any) => {
|
||||
console.log("mediaStream", mediaStream);
|
||||
|
||||
if (videoRef.current) {
|
||||
toast("got local user video stream");
|
||||
videoRef.current.srcObject = mediaStream;
|
||||
videoRef.current.play();
|
||||
|
||||
// create a unique peer connection to server for each line
|
||||
lines.forEach((lineId) => {
|
||||
// create the peer and add the tranceiver
|
||||
const peer = createPeer(lineId);
|
||||
|
||||
console.log(`peer connection for ${lineId}`, peer);
|
||||
|
||||
peer.addTransceiver("video");
|
||||
|
||||
mediaStream.getTracks().forEach((track: any) => {
|
||||
console.log(`adding track to peer connection`);
|
||||
peer.addTrack(track, mediaStream);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [lines]);
|
||||
|
||||
const createPeer = (lineId: string) => {
|
||||
const peer = new RTCPeerConnection({
|
||||
iceServers: [
|
||||
{
|
||||
urls: "stun:stun.stunprotocol.org",
|
||||
},
|
||||
],
|
||||
});
|
||||
peer.ontrack = handleTrackEvent;
|
||||
peer.onnegotiationneeded = () => handleNegotiationNeededEvent(peer, lineId);
|
||||
|
||||
return peer;
|
||||
};
|
||||
|
||||
const handleNegotiationNeededEvent = async (
|
||||
peer: RTCPeerConnection,
|
||||
lineId: string
|
||||
) => {
|
||||
// create offer
|
||||
const offer = await peer.createOffer();
|
||||
await peer.setLocalDescription(offer);
|
||||
const payload = {
|
||||
sdp: peer.localDescription,
|
||||
};
|
||||
|
||||
// send offer to server to accept
|
||||
const { data } = await axios.post(
|
||||
`http://localhost:5000/api/rtc/join/${lineId}`,
|
||||
payload
|
||||
);
|
||||
|
||||
// receive server acceptance and set remote configs
|
||||
const desc = new RTCSessionDescription(data.data.sdp);
|
||||
peer.setRemoteDescription(desc).catch((e) => console.log(e));
|
||||
|
||||
console.log(`done handling negotiation with media server`);
|
||||
};
|
||||
|
||||
// render tracks on the server peer sending something
|
||||
function handleTrackEvent(e: any) {
|
||||
const incomingStream = e.streams[0];
|
||||
|
||||
console.log("incoming stream", e);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="text-white p-10">
|
||||
<h1 className="text-xl">This is a baller video app</h1>
|
||||
|
||||
<ol>
|
||||
<li>
|
||||
- user joins 5 lines hardcoded lines (stream to and accept any streams
|
||||
in those lines)
|
||||
</li>
|
||||
<li>- user can see which line certain content is coming from</li>
|
||||
<li>- user can set medium: share screen or video (along with audio)</li>
|
||||
<li>- user can select medium they want to send to</li>
|
||||
<li>- user hold tilde to stream selected medium in a specific line</li>
|
||||
<li>
|
||||
- user can receive transmitted stream content from any line, and see
|
||||
which line it's streamed to
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h1 className="text-3xl mt-10">Your Video</h1>
|
||||
<span>toggle medium (todo)</span>
|
||||
<video
|
||||
ref={videoRef}
|
||||
id={"userVideo"}
|
||||
muted
|
||||
height={"300"}
|
||||
width={"300"}
|
||||
/>
|
||||
|
||||
<h1 className="text-3xl mt-10">Select a Line</h1>
|
||||
{lines.map((lineId) => (
|
||||
<a
|
||||
id={`${lineId}-Selection`}
|
||||
className={`cursor-pointer p-5 ${
|
||||
selectedLine === lineId ? "text-green-400" : "text-white"
|
||||
}`}
|
||||
onClick={() => setSelectedLine(lineId)}
|
||||
>
|
||||
{lineId}
|
||||
</a>
|
||||
))}
|
||||
|
||||
<h1 className="text-3xl mt-10">
|
||||
Hold tilde (`) to stream to `{selectedLine}`
|
||||
</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user