adding all of the boilerplate for managing a room
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
ConnectToLineRequest,
|
ConnectToLineRequest,
|
||||||
|
RtcCallRequest,
|
||||||
ServerRequestChannels,
|
ServerRequestChannels,
|
||||||
ServerResponseChannels,
|
ServerResponseChannels,
|
||||||
SomeoneConnectedResponse,
|
SomeoneConnectedResponse,
|
||||||
@@ -11,20 +12,21 @@ import {
|
|||||||
import Conversation, { MemberState } from '@nirvana/core/models/conversation.model';
|
import Conversation, { MemberState } from '@nirvana/core/models/conversation.model';
|
||||||
import { ConversationMap, MasterConversation } from '../util/types';
|
import { ConversationMap, MasterConversation } from '../util/types';
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { Updater, useImmer } from 'use-immer';
|
||||||
import {
|
import {
|
||||||
checkIfOnOnOneExists,
|
checkIfOnOnOneExists,
|
||||||
createConversation,
|
createConversation,
|
||||||
getConversationById,
|
getConversationById,
|
||||||
getConversations,
|
getConversations,
|
||||||
} from '../api/NirvanaApi';
|
} from '../api/NirvanaApi';
|
||||||
|
import { useAsyncFn, useEffectOnce } from 'react-use';
|
||||||
|
|
||||||
import CreateConversationRequest from '@nirvana/core/requests/CreateConversationRequest.request';
|
import CreateConversationRequest from '@nirvana/core/requests/CreateConversationRequest.request';
|
||||||
|
import Peer from 'simple-peer';
|
||||||
import { Typography } from '@mui/material';
|
import { Typography } from '@mui/material';
|
||||||
import User from '@nirvana/core/models/user.model';
|
import User from '@nirvana/core/models/user.model';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { useAsyncFn } from 'react-use';
|
|
||||||
import useAuth from './AuthProvider';
|
import useAuth from './AuthProvider';
|
||||||
import { useImmer } from 'use-immer';
|
|
||||||
import useSockets from './SocketProvider';
|
import useSockets from './SocketProvider';
|
||||||
|
|
||||||
// responsible for firing socket events from the local client
|
// responsible for firing socket events from the local client
|
||||||
@@ -326,6 +328,7 @@ export function ConversationProvider({ children }: { children: React.ReactNode }
|
|||||||
{userTunedInConversations.map((tunedConversation) => (
|
{userTunedInConversations.map((tunedConversation) => (
|
||||||
<Room
|
<Room
|
||||||
key={`streamRoom-${tunedConversation._id.toString()}`}
|
key={`streamRoom-${tunedConversation._id.toString()}`}
|
||||||
|
setConversationMap={setConversationMap}
|
||||||
conversation={tunedConversation}
|
conversation={tunedConversation}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@@ -337,13 +340,11 @@ export default function useConversations() {
|
|||||||
return React.useContext(ConversationContext);
|
return React.useContext(ConversationContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
const videoConstraints = false;
|
const videoConstraints = {
|
||||||
|
frameRate: 15,
|
||||||
// {
|
width: { max: 100 },
|
||||||
// frameRate: 30,
|
height: { max: 200 },
|
||||||
// width: { max: 100 },
|
};
|
||||||
// height: { max: 200 },
|
|
||||||
// };
|
|
||||||
|
|
||||||
const iceServers = [
|
const iceServers = [
|
||||||
// { urls: 'stun:stun.l.google.com:19302' },
|
// { urls: 'stun:stun.l.google.com:19302' },
|
||||||
@@ -416,15 +417,25 @@ function useDevices() {
|
|||||||
// - unmount if you want to leave room
|
// - unmount if you want to leave room
|
||||||
// - have event listeners specific to this room
|
// - have event listeners specific to this room
|
||||||
// - take in new users that come into the room
|
// - take in new users that come into the room
|
||||||
function Room({ conversation }: { conversation: MasterConversation; mediaStream?: MediaStream }) {
|
function Room({
|
||||||
|
conversation,
|
||||||
|
setConversationMap,
|
||||||
|
}: {
|
||||||
|
conversation: MasterConversation;
|
||||||
|
mediaStream?: MediaStream;
|
||||||
|
setConversationMap: Updater<ConversationMap>;
|
||||||
|
}) {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
const { $ws } = useSockets();
|
||||||
|
|
||||||
// have internal state to manage details of this "room"
|
// have internal state to manage details of this "room"
|
||||||
|
|
||||||
// ============== STREAMING ===============
|
// ============== STREAMING ===============
|
||||||
|
|
||||||
// handle incoming calls and accept calls and create objects for them
|
// handle incoming calls and accept calls and create objects for them
|
||||||
useEffect(() => {
|
useEffectOnce(() => {
|
||||||
|
const localPeersForRoom: Peer[] = [];
|
||||||
|
|
||||||
// ?will there be race condition where this room component is rendered but we don't have the latest
|
// ?will there be race condition where this room component is rendered but we don't have the latest
|
||||||
// ?list of tunedin folks and so we may just end up calling select few?
|
// ?list of tunedin folks and so we may just end up calling select few?
|
||||||
// ?in this case, start with initiating event to get all people in room first
|
// ?in this case, start with initiating event to get all people in room first
|
||||||
@@ -434,10 +445,98 @@ function Room({ conversation }: { conversation: MasterConversation; mediaStream?
|
|||||||
const allOtherUserIds = conversation.tunedInUsers.filter(
|
const allOtherUserIds = conversation.tunedInUsers.filter(
|
||||||
(memberUserId) => memberUserId !== user._id.toString(),
|
(memberUserId) => memberUserId !== user._id.toString(),
|
||||||
);
|
);
|
||||||
|
toast.success('calling bunch of people');
|
||||||
|
|
||||||
// initiate listeners for all of them
|
navigator.mediaDevices
|
||||||
|
.getUserMedia({ video: videoConstraints, audio: true })
|
||||||
|
.then((localMediaStream: MediaStream) => {
|
||||||
|
// for each person, create peer object
|
||||||
|
allOtherUserIds.forEach((otherUserId) => {
|
||||||
|
const connectingToast = toast.loading('calling peer for a snappy experience');
|
||||||
|
|
||||||
|
// ========= PEER CREATION =============
|
||||||
|
// make sure this peer gets destroyed when it's time to remove this listener
|
||||||
|
const localPeerConnection = new Peer({
|
||||||
|
initiator: true,
|
||||||
|
stream: localMediaStream,
|
||||||
|
trickle: true, // prevents the multiple tries on different ice servers and signal from getting called a bunch of times,
|
||||||
|
config: {
|
||||||
|
iceServers,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========= PEER EVENT HANDLERS =============
|
||||||
|
localPeerConnection.on('signal', (signal) => {
|
||||||
|
console.log('have a signal to make call to someone ');
|
||||||
|
|
||||||
|
$ws.emit(
|
||||||
|
ServerRequestChannels.RTC_CALL_SOMEONE_FOR_LINE,
|
||||||
|
new RtcCallRequest(otherUserId, conversation._id.toString(), signal),
|
||||||
|
);
|
||||||
|
|
||||||
|
toast.dismiss(connectingToast);
|
||||||
|
|
||||||
|
// notifying globally that we are in the process now
|
||||||
|
setConversationMap((draft) => {
|
||||||
|
if (draft[conversation._id.toString()]) {
|
||||||
|
draft[conversation._id.toString()].room = {
|
||||||
|
...(draft[conversation._id.toString()].room ?? {}),
|
||||||
|
otherUserId: { peer: localPeerConnection },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
draft[conversation._id.toString()].isConnectingToRoom = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
localPeerConnection.on('stream', (remoteStream: MediaStream) => {
|
||||||
|
// globally updating conversation so that other views can render what they want
|
||||||
|
setConversationMap((draft) => {
|
||||||
|
if (draft[conversation._id.toString()].room[otherUserId]) {
|
||||||
|
toast.success('got stream from remote, going to add to our ');
|
||||||
|
draft[conversation._id.toString()].room[otherUserId].stream = remoteStream;
|
||||||
|
remoteStream.getTracks().forEach((track) => {
|
||||||
|
// TODO: add particular track to right place
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
localPeerConnection.on('connect', () => {
|
||||||
|
toast.success('successfully connected to another peer');
|
||||||
|
});
|
||||||
|
|
||||||
|
localPeerConnection.on('track', (track, stream) => {
|
||||||
|
// TODO: add to room contents
|
||||||
|
|
||||||
|
toast('a peer added a track to a stream');
|
||||||
|
});
|
||||||
|
|
||||||
|
localPeerConnection.on('close', () => {
|
||||||
|
// the person will be removed from the tuned in list, but the connections here are decoupled from that flow
|
||||||
|
// we want to manage the room within the master conversation and remove it for ourselves
|
||||||
|
|
||||||
|
// TODO: update the map to remove the user and remove
|
||||||
|
|
||||||
|
toast.error('peer connection was closed');
|
||||||
|
});
|
||||||
|
|
||||||
|
localPeerConnection.on('error', (err) => {
|
||||||
|
console.error(err);
|
||||||
|
toast.error('there was a problem with the peer connection');
|
||||||
|
});
|
||||||
|
|
||||||
|
localPeersForRoom.push(localPeerConnection);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// go through all peer connections and destroy them
|
||||||
|
localPeersForRoom.forEach((peerConnection) => {
|
||||||
|
peerConnection.destroy();
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleJoinRoom = useCallback((roomId: string) => {
|
const handleJoinRoom = useCallback((roomId: string) => {
|
||||||
// call up folks and then have listeners for the peer connection objects that we created
|
// call up folks and then have listeners for the peer connection objects that we created
|
||||||
|
|||||||
@@ -16,9 +16,12 @@ export type MasterConversation = Conversation & {
|
|||||||
[userId: string]: {
|
[userId: string]: {
|
||||||
peer: Peer;
|
peer: Peer;
|
||||||
stream?: MediaStream;
|
stream?: MediaStream;
|
||||||
tracks?: MediaStreamTrack[];
|
audioTrack?: MediaStreamTrack;
|
||||||
|
videoTrack?: MediaStreamTrack;
|
||||||
|
screenTrack?: MediaStreamTrack;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
isConnectingToRoom?: boolean;
|
||||||
|
|
||||||
// all of audio clips, links, media, etc.
|
// all of audio clips, links, media, etc.
|
||||||
content?: ContentBlock[];
|
content?: ContentBlock[];
|
||||||
|
|||||||
Reference in New Issue
Block a user