good progress on the tree although everything broken, more structure and really should just start fresh
This commit is contained in:
@@ -9,6 +9,7 @@ interface IAuthProvider {
|
||||
user?: User;
|
||||
jwtToken?: string;
|
||||
setJwtToken?: (jwtToken: string) => void;
|
||||
handleLogout?: () => void;
|
||||
}
|
||||
|
||||
const AuthContext = React.createContext<IAuthProvider>({});
|
||||
@@ -34,6 +35,21 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
}, [jwtToken, fetchUser]);
|
||||
|
||||
const handleLogout = useCallback(() => {
|
||||
setJwtToken(undefined);
|
||||
|
||||
// ?should this be set here? what's a better way of
|
||||
NirvanaApi._jwtToken = null;
|
||||
|
||||
fetchUser()
|
||||
.then((response) => {
|
||||
toast.success('authenticated user');
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error('logged out');
|
||||
});
|
||||
}, [fetchUser, setJwtToken]);
|
||||
|
||||
const handleSetJwtToken = useCallback(
|
||||
(newJwtToken: string) => {
|
||||
setJwtToken(newJwtToken);
|
||||
@@ -43,7 +59,12 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{ user: userFetchState.value?.user, setJwtToken: handleSetJwtToken, jwtToken }}
|
||||
value={{
|
||||
user: userFetchState.value?.user,
|
||||
setJwtToken: handleSetJwtToken,
|
||||
jwtToken,
|
||||
handleLogout,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
import React, { useEffect, useState, useCallback, useContext } from 'react';
|
||||
import useRooms from './RoomsProvider';
|
||||
import useSockets from './SocketProvider';
|
||||
|
||||
import MasterLineData from '@nirvana/core/models/masterLineData.model';
|
||||
import { LineMemberState } from '@nirvana/core/models/line.model';
|
||||
|
||||
import {
|
||||
ConnectToLineRequest,
|
||||
ServerRequestChannels,
|
||||
ServerResponseChannels,
|
||||
SomeoneConnectedResponse,
|
||||
SomeoneTunedResponse,
|
||||
SomeoneUntunedFromLineResponse,
|
||||
StartBroadcastingRequest,
|
||||
StopBroadcastingRequest,
|
||||
TuneToLineRequest,
|
||||
UntuneFromLineRequest,
|
||||
UserStartedBroadcastingResponse,
|
||||
UserStoppedBroadcastingResponse,
|
||||
} from '@nirvana/core/sockets/channels';
|
||||
|
||||
type LineIdToMasterLine = {
|
||||
[lineId: string]: MasterLineData;
|
||||
};
|
||||
|
||||
interface IRealTimeRoomProvider {
|
||||
roomsMap: LineIdToMasterLine;
|
||||
}
|
||||
|
||||
const RealTimeRoomContext = React.createContext<IRealTimeRoomProvider>({ roomsMap: {} });
|
||||
|
||||
export function RealTimeRoomProvider({ children }: { children: React.ReactChild }) {
|
||||
const { rooms } = useRooms();
|
||||
const { $ws } = useSockets();
|
||||
const [realTimeRoomMap, setRealTimeRoomMap] = useState<LineIdToMasterLine>({});
|
||||
|
||||
useEffect(() => {
|
||||
if (rooms.value) {
|
||||
/**
|
||||
* initiate listeners
|
||||
*/
|
||||
|
||||
/**
|
||||
* TODO: P0 : notified that someone else added me to a line they created
|
||||
*/
|
||||
// 1. get the new line from axios or just from the ws itself
|
||||
// 2. add it to the lines map with all of the right data
|
||||
// 3. make sure to connect to it as if it's inbox material
|
||||
|
||||
// when me or anyone just initially connects to line
|
||||
$ws.on(ServerResponseChannels.SOMEONE_CONNECTED_TO_LINE, (res: SomeoneConnectedResponse) => {
|
||||
console.log(
|
||||
`connected to line...here are all of the updated in the conected line ${res.lineId}...this isn't reliable considering it's not updated later`,
|
||||
res.allConnectedIntoUserIds,
|
||||
);
|
||||
|
||||
setRealTimeRoomMap((prevLinesMap) => {
|
||||
const newMap = { ...prevLinesMap };
|
||||
|
||||
if (newMap[res.lineId])
|
||||
newMap[res.lineId].connectedMemberIds = [
|
||||
...(newMap[res.lineId]?.connectedMemberIds ?? []),
|
||||
res.userId,
|
||||
];
|
||||
|
||||
return newMap;
|
||||
});
|
||||
});
|
||||
|
||||
// someone tuning in, including perhaps me | either toggled in or just temporary
|
||||
$ws.on(ServerResponseChannels.SOMEONE_TUNED_INTO_LINE, (res: SomeoneTunedResponse) => {
|
||||
console.log(`here are all of updated users in the tuned in room`, res.allTunedIntoUserIds);
|
||||
|
||||
// TODO: if toggled in, make sure to update the current line member in the lines map so that
|
||||
// we can know to untune if user selects another line
|
||||
|
||||
// below, we are setting the list of tuned in folks based on fresh list from the server
|
||||
// better than just adding and removing? i think so, but have to handle not interrupting existing peer connections as this changes
|
||||
setRealTimeRoomMap((prevLinesMap) => {
|
||||
const newMap = { ...prevLinesMap };
|
||||
|
||||
if (newMap[res.lineId]) {
|
||||
newMap[res.lineId].tunedInMemberIds = res.allTunedIntoUserIds;
|
||||
|
||||
// if user is me, make sure to show me my updated line member association
|
||||
if (newMap[res.lineId].currentUserMember?.userId.toString() === res.userId) {
|
||||
newMap[res.lineId].currentUserMember.lastVisitDate = new Date();
|
||||
newMap[res.lineId].currentUserMember.state = res.toggledIn
|
||||
? LineMemberState.TUNED
|
||||
: LineMemberState.INBOX;
|
||||
}
|
||||
|
||||
// TODO: update the relevant lineMember (based on which userId is given): state and last visit date if current user is joining
|
||||
// and not just if it's the current user toggling in
|
||||
// right now, we just want user to know number of folks tuned and no need to expose who is toggle tuned...that lineMember can be stale
|
||||
}
|
||||
|
||||
return newMap;
|
||||
});
|
||||
});
|
||||
|
||||
$ws.on(
|
||||
ServerResponseChannels.SOMEONE_UNTUNED_FROM_LINE,
|
||||
(res: SomeoneUntunedFromLineResponse) => {
|
||||
console.log(
|
||||
`here are all of updated users in the tuned in room`,
|
||||
res.allTunedIntoUserIds,
|
||||
);
|
||||
|
||||
// TODO: if toggled in, make sure to update the current line member in the lines map so that
|
||||
// we can know to untune if user selects another line
|
||||
|
||||
setRealTimeRoomMap((prevLinesMap) => {
|
||||
const newMap = { ...prevLinesMap };
|
||||
|
||||
// TODO: update the relevant lineMember (based on which userId is given): state and last visit date if current user is joining
|
||||
|
||||
if (newMap[res.lineId]) newMap[res.lineId].tunedInMemberIds = res.allTunedIntoUserIds;
|
||||
|
||||
return newMap;
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
$ws.on(
|
||||
ServerResponseChannels.SOMEONE_STARTED_BROADCASTING,
|
||||
(res: UserStartedBroadcastingResponse) => {
|
||||
console.log('someone is starting to broadcast');
|
||||
|
||||
setRealTimeRoomMap((prevLinesMap) => {
|
||||
const newMap = { ...prevLinesMap };
|
||||
|
||||
if (newMap[res.lineId])
|
||||
newMap[res.lineId].currentBroadcastersUserIds = [
|
||||
...(newMap[res.lineId].currentBroadcastersUserIds ?? []),
|
||||
res.userId,
|
||||
];
|
||||
|
||||
return newMap;
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
$ws.on(
|
||||
ServerResponseChannels.SOMEONE_STOPPED_BROADCASTING,
|
||||
(res: UserStoppedBroadcastingResponse) => {
|
||||
setRealTimeRoomMap((prevLinesMap) => {
|
||||
const newMap = { ...prevLinesMap };
|
||||
|
||||
if (newMap[res.lineId]?.currentBroadcastersUserIds) {
|
||||
newMap[res.lineId].currentBroadcastersUserIds = newMap[
|
||||
res.lineId
|
||||
].currentBroadcastersUserIds.filter(
|
||||
(broadcasterUserId) => broadcasterUserId !== res.userId,
|
||||
);
|
||||
}
|
||||
|
||||
return newMap;
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}, [rooms.value, $ws]);
|
||||
|
||||
/** handlers for emitting events to server */
|
||||
const handleConnectToLine = useCallback(
|
||||
(lineId: string) => {
|
||||
$ws.emit(ServerRequestChannels.CONNECT_TO_LINE, new ConnectToLineRequest(lineId));
|
||||
},
|
||||
[$ws],
|
||||
);
|
||||
|
||||
/**
|
||||
* toggle into a specific line
|
||||
* @param temporary: denotes whether we are just listening in or want to persist "toggling" it on so that it shows up in overlay
|
||||
* TODO: have loading state for this particular part of context value
|
||||
*/
|
||||
const handleTuneToLine = useCallback(
|
||||
(lineId: string, turnToggleOn = false) => {
|
||||
// they already are in the socket room for updates including media connections and disconnections
|
||||
// but set the flag so that the line row can know whether or not to start the webrtc process
|
||||
// and know when to get out or disconnect from the webrtc when the flag turns off
|
||||
|
||||
$ws.emit(ServerRequestChannels.TUNE_INTO_LINE, new TuneToLineRequest(lineId, turnToggleOn));
|
||||
},
|
||||
[$ws],
|
||||
);
|
||||
|
||||
const handleUnTuneToLine = useCallback(
|
||||
(lineId: string) => {
|
||||
// they already are in the socket room for updates including media connections and disconnections
|
||||
// but set the flag so that the line row can know whether or not to start the webrtc process
|
||||
// and know when to get out or disconnect from the webrtc when the flag turns off
|
||||
|
||||
$ws.emit(ServerRequestChannels.UNTUNE_FROM_LINE, new UntuneFromLineRequest(lineId));
|
||||
},
|
||||
[$ws],
|
||||
);
|
||||
|
||||
/**
|
||||
* This is when the user wants to tell everyone that they are streaming/broadcasting/buzzing to
|
||||
* a specific line, whether they are toggle tuned or temporarily tuned in
|
||||
*
|
||||
* Note: They could be using push to talk or toggle broadcast
|
||||
*
|
||||
* Note: this is handling the data transmission as is the responsibility of this overall context provider
|
||||
* and not necessarily the aspect of enabling and disabling streaming as webrtc is reliable enough for that
|
||||
*
|
||||
* @param lineId the line that the current user is talking into
|
||||
*/
|
||||
const handleStartBroadcast = useCallback(
|
||||
(lineId: string) => {
|
||||
// emit telling people
|
||||
$ws.emit(ServerRequestChannels.BROADCAST_TO_LINE, new StartBroadcastingRequest(lineId));
|
||||
},
|
||||
[$ws],
|
||||
);
|
||||
|
||||
const handleStopBroadcast = useCallback(
|
||||
(lineId: string) => {
|
||||
// emit telling people
|
||||
$ws.emit(ServerRequestChannels.STOP_BROADCAST_TO_LINE, new StopBroadcastingRequest(lineId));
|
||||
|
||||
// ?handle recording here as well? or record the incoming stream instead?
|
||||
// ?could just take in the audiochunks here and all, but not sure yet
|
||||
},
|
||||
[$ws],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (rooms.value?.data?.masterLines?.length > 0) {
|
||||
setRealTimeRoomMap((prevMappings) => {
|
||||
// go through the lines from the persistent store
|
||||
|
||||
// get all of the id's and map assign to the main object
|
||||
|
||||
// ?prolly have no previous at this point...but I'm okay with override since this
|
||||
// ?useeffect is triggered on the refetching of the persistent store so we
|
||||
// ?are prolly looking to do a full app refresh and connection refresh
|
||||
const newMap = { ...prevMappings };
|
||||
|
||||
rooms.value.data.masterLines.forEach((masterLine) => {
|
||||
const lineId = masterLine.lineDetails._id.toString();
|
||||
newMap[lineId] = masterLine;
|
||||
|
||||
handleConnectToLine(lineId);
|
||||
|
||||
// tune into lines that I should be
|
||||
if (masterLine.currentUserMember.state === LineMemberState.TUNED) {
|
||||
// don't need to turn toggle on, it's already on
|
||||
// ! CHANGE PARADIGM TO NOT INCLUDING TOGGLING IN REQUESTS WITH THE SAME AS TUNING IN
|
||||
// right now, writing just because of later logic
|
||||
handleTuneToLine(lineId, true);
|
||||
}
|
||||
});
|
||||
|
||||
return newMap;
|
||||
});
|
||||
}
|
||||
}, [rooms.value, handleConnectToLine, handleTuneToLine, setRealTimeRoomMap]);
|
||||
|
||||
return (
|
||||
<RealTimeRoomContext.Provider value={{ roomsMap: realTimeRoomMap }}>
|
||||
{' '}
|
||||
</RealTimeRoomContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export default function useRealTimeRooms() {
|
||||
return useContext(RealTimeRoomContext);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
import { getUserLines } from '../api/NirvanaApi';
|
||||
import { AsyncState } from 'react-use/lib/useAsyncFn';
|
||||
import GetUserLinesResponse from '@nirvana/core/responses/getUserLines.response';
|
||||
import NirvanaResponse from '@nirvana/core/responses/nirvanaResponse';
|
||||
|
||||
/**
|
||||
* NOTE: lines, rooms, conversations are synonymous
|
||||
*/
|
||||
|
||||
interface IRoomProvider {
|
||||
rooms?: AsyncState<NirvanaResponse<GetUserLinesResponse>>;
|
||||
}
|
||||
|
||||
const RoomsContext = React.createContext<IRoomProvider>({});
|
||||
|
||||
/**
|
||||
* in charge of the main cach object for room data
|
||||
* modified with real time socket data
|
||||
*/
|
||||
export function RoomsProvider({ children }: { children: React.ReactChild }) {
|
||||
const rooms = useAsync(getUserLines);
|
||||
|
||||
// TODO: refetch handler
|
||||
|
||||
return <RoomsContext.Provider value={{ rooms }}>{children}</RoomsContext.Provider>;
|
||||
}
|
||||
|
||||
export default function useRooms() {
|
||||
return useContext(RoomsContext);
|
||||
}
|
||||
@@ -1,16 +1,25 @@
|
||||
import React from 'react';
|
||||
import { Toaster } from 'react-hot-toast';
|
||||
import { RoomsProvider } from '../providers/RoomsProvider';
|
||||
import { RealTimeRoomProvider } from '../providers/RealTimeRoomProvider';
|
||||
import { AuthProvider } from '../providers/AuthProvider';
|
||||
import { ElectronProvider } from '../providers/ElectronProvider';
|
||||
import { SocketProvider } from '../providers/SocketProvider';
|
||||
import ProtectedRoute from './protected/ProtectedRoute';
|
||||
import Terminal from './protected/terminal/Terminal';
|
||||
|
||||
export default function ElectronApp() {
|
||||
return (
|
||||
<ElectronProvider>
|
||||
<AuthProvider>
|
||||
<ProtectedRoute>
|
||||
<SocketProvider>yo its connected dawg</SocketProvider>
|
||||
<SocketProvider>
|
||||
<RoomsProvider>
|
||||
<RealTimeRoomProvider>
|
||||
<Terminal />
|
||||
</RealTimeRoomProvider>
|
||||
</RoomsProvider>
|
||||
</SocketProvider>
|
||||
</ProtectedRoute>
|
||||
</AuthProvider>
|
||||
|
||||
|
||||
@@ -0,0 +1,387 @@
|
||||
import { Avatar, Skeleton, Tooltip } from 'antd';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import useRealTimeRooms from '../../../providers/RealTimeRoomProvider';
|
||||
import useElectron from '../../../providers/ElectronProvider';
|
||||
import useAuth from '../../../providers/AuthProvider';
|
||||
import NavBar from './navbar/Navbar';
|
||||
import { FiActivity, FiHeadphones, FiSettings, FiSun } from 'react-icons/fi';
|
||||
import { LineMemberState } from '@nirvana/core/models/line.model';
|
||||
import MasterLineData from '@nirvana/core/models/masterLineData.model';
|
||||
import LineRow from './lines/lineRow.tsx';
|
||||
import useRooms from '../../../providers/RoomsProvider';
|
||||
import toast from 'react-hot-toast';
|
||||
import { FaPlus } from 'react-icons/fa';
|
||||
import LineIcon from './lines/lineIcon';
|
||||
|
||||
export default function Terminal() {
|
||||
const { rooms: initialRoomsState } = useRooms();
|
||||
const { roomsMap } = useRealTimeRooms();
|
||||
const { desktopMode } = useElectron();
|
||||
|
||||
const { user } = useAuth();
|
||||
|
||||
const [selectedLine, setSelectedLine] = useState<MasterLineData>();
|
||||
|
||||
const allLines: MasterLineData[] = useMemo(() => {
|
||||
const masterLines: MasterLineData[] = Object.values(roomsMap);
|
||||
|
||||
// TODO: sort based on the audio blocks and currentMember lastActiveDate
|
||||
|
||||
return masterLines.filter(
|
||||
(masterLine) => masterLine.currentUserMember.state === LineMemberState.INBOX,
|
||||
);
|
||||
}, [roomsMap]);
|
||||
|
||||
const toggleTunedLines = useMemo(() => {
|
||||
const masterLines: MasterLineData[] = Object.values(roomsMap);
|
||||
|
||||
return masterLines.filter(
|
||||
(masterLine) => masterLine.currentUserMember.state === LineMemberState.TUNED,
|
||||
);
|
||||
}, [roomsMap]);
|
||||
|
||||
/** show user line details on click of one line */
|
||||
const handleSelectLine = useCallback(
|
||||
(newLineIdToSelect: string) => {
|
||||
toast('selecting line!! NOT IMPLEMENTED');
|
||||
},
|
||||
[setSelectedLine],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-1">
|
||||
<NavBar />
|
||||
|
||||
<div className="flex flex-row flex-1">
|
||||
<div className="flex flex-col bg-white w-[400px] relative group">
|
||||
{/* tuned in lines block */}
|
||||
<div className="bg-gray-100 flex flex-col shadow-lg">
|
||||
{/* tuned in header + general controls */}
|
||||
|
||||
<Tooltip placement="right" title={'These are your active rooms...'}>
|
||||
<div className="flex flex-row items-center py-3 px-4 pb-0">
|
||||
<span className="flex flex-row gap-2 items-center justify-start text-gray-400 animate-pulse">
|
||||
<FiActivity className="text-sm" />
|
||||
|
||||
<h2 className="text-inherit text-sm">Rooms</h2>
|
||||
|
||||
<p className="text-slate-300 text-xs">{`${toggleTunedLines?.length || 0}/3`}</p>
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
{/* list of toggle tuned lines */}
|
||||
<div className="flex flex-col mt-2">
|
||||
{toggleTunedLines.map((masterLineData) => (
|
||||
<LineRow
|
||||
key={`terminalListLines-${masterLineData.lineDetails._id.toString()}`}
|
||||
masterLineData={masterLineData}
|
||||
handleSelectLine={() => {
|
||||
console.log('asdf');
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!(allLines.length > 0) && !(toggleTunedLines.length > 0) && (
|
||||
<span className="text-gray-300 text-sm my-5 text-center">
|
||||
You have no lines! <br /> Create one to connect to your team instantly.
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* rest of the lines */}
|
||||
<div className={'flex flex-col'}>
|
||||
{initialRoomsState.loading ? (
|
||||
<Skeleton />
|
||||
) : (
|
||||
allLines.map((masterLineData) => (
|
||||
<LineRow
|
||||
key={`terminalListLines-${masterLineData.lineDetails._id.toString()}`}
|
||||
masterLineData={masterLineData}
|
||||
handleSelectLine={handleSelectLine}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="absolute bottom-3 right-3 z-10 scale-0
|
||||
group-hover:scale-100 ease-in-out hover:transition group-hover:transition delay-100 duration-200"
|
||||
>
|
||||
<button
|
||||
className="flex flex-row gap-2 items-center justify-evenly
|
||||
shadow-xl bg-gray-800 p-2 text-white text-xs"
|
||||
>
|
||||
<FaPlus />
|
||||
<span>New line</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedLine ? (
|
||||
<LineDetailsTerminal selectedLine={selectedLine} />
|
||||
) : (
|
||||
<div
|
||||
className="flex flex-col flex-1 justify-center items-center bg-gray-100
|
||||
border-l border-l-gray-200"
|
||||
>
|
||||
<span className="text-xl text-gray-800">{`Hi ${user.givenName}!`}</span>
|
||||
<span className="text-md text-gray-400">{"You're all set!"}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LineDetailsTerminal({ selectedLine }: { selectedLine: MasterLineData }) {
|
||||
const { user } = useAuth();
|
||||
|
||||
console.log('selected line', selectedLine);
|
||||
|
||||
const isUserToggleTuned = useMemo(
|
||||
() => selectedLine?.currentUserMember?.state === LineMemberState.TUNED,
|
||||
[selectedLine],
|
||||
);
|
||||
|
||||
// seeing if I am in the list of broadcasters
|
||||
// the source of truth from the socket connections telling me if my clicking actually made a round trip
|
||||
const isUserBroadcasting = useMemo(
|
||||
() => selectedLine?.currentBroadcastersUserIds?.includes(user._id.toString()),
|
||||
[user, selectedLine],
|
||||
);
|
||||
|
||||
// showing all tuned in members...they may not hear me since they might be doing something else
|
||||
// but feeling of presentness
|
||||
const tunedProfiles = useMemo(() => {
|
||||
const pictureSources: { name: string; pictureSrc: string }[] = [];
|
||||
|
||||
selectedLine.tunedInMemberIds?.forEach((tunedInMemberUserId) => {
|
||||
// don't want to see my own picture
|
||||
// TODO: don't show myself!?
|
||||
if (tunedInMemberUserId === user._id.toString()) {
|
||||
pictureSources.push();
|
||||
|
||||
pictureSources.push({
|
||||
name: user.givenName,
|
||||
pictureSrc: user.picture,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const otherUserObject = selectedLine.otherUserObjects?.find(
|
||||
(userObj) => userObj._id.toString() === tunedInMemberUserId,
|
||||
);
|
||||
if (otherUserObject?.picture)
|
||||
pictureSources.push({
|
||||
name: otherUserObject.givenName,
|
||||
pictureSrc: otherUserObject.picture,
|
||||
});
|
||||
});
|
||||
|
||||
return pictureSources;
|
||||
}, [selectedLine, user]);
|
||||
|
||||
// pics for the line icons
|
||||
const profilePictures = useMemo(() => {
|
||||
const pictureSources: string[] = [];
|
||||
|
||||
// ?don't add in my image as that's useless contextually?
|
||||
// if (userData?.user?.picture) pictureSources.push(userData.user.picture);
|
||||
|
||||
selectedLine.otherUserObjects?.forEach((otherUser) => {
|
||||
if (otherUser.picture) pictureSources.push(otherUser.picture);
|
||||
});
|
||||
|
||||
return pictureSources;
|
||||
}, [selectedLine, user]);
|
||||
|
||||
// TODO get from realtime provider to do this action
|
||||
|
||||
const handleToggleTuneToLine = () => toast('NOT IMPLEMENTATED: toggle tuning');
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col flex-1 bg-gray-100
|
||||
border-l border-l-gray-200 relative"
|
||||
>
|
||||
{/* line details */}
|
||||
<div
|
||||
className="p-4
|
||||
flex flex-row items-center gap-2 justify-end border-b-gray-200 border-b"
|
||||
>
|
||||
{profilePictures && <LineIcon grayscale={false} sourceImages={profilePictures} />}
|
||||
|
||||
<div className="flex flex-col items-start mr-auto group">
|
||||
<span className="flex flex-row gap-2 items-center">
|
||||
<h2 className={`text-lg text-slate-800 font-semibold`}>
|
||||
{selectedLine.lineDetails.name || selectedLine.otherUserObjects[0].givenName}
|
||||
</h2>
|
||||
|
||||
<button
|
||||
className={`p-1 hidden group-hover:flex justify-center items-center hover:bg-gray-300
|
||||
transition-all hover:scale-105`}
|
||||
>
|
||||
<FiSettings className="text-gray-400 text-xs" />
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<span className="flex flex-row gap-2 items-center">
|
||||
<span className="text-gray-300 text-xs">{`${
|
||||
selectedLine.otherMembers?.length + 1 ?? 0
|
||||
} members`}</span>
|
||||
<span className="h-1 w-1 bg-gray-800 rounded-full"></span>
|
||||
<span className="text-teal-500 text-xs">{`${
|
||||
selectedLine.tunedInMemberIds?.length ?? 0
|
||||
} in this room`}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* TODO: move to on hover of line row */}
|
||||
<Tooltip
|
||||
placement="left"
|
||||
title={`${isUserToggleTuned ? 'click to untoggle' : 'click to stay tuned in'}`}
|
||||
>
|
||||
<button
|
||||
className={`p-2 flex justify-center items-center shadow-lg
|
||||
hover:scale-105 transition-all animate-pulse ${
|
||||
isUserToggleTuned ? 'bg-gray-800 text-white' : 'text-black'
|
||||
}`}
|
||||
onClick={() =>
|
||||
isUserToggleTuned ? handleToggleTuneToLine() : handleToggleTuneToLine()
|
||||
}
|
||||
>
|
||||
<FiActivity className="text-md" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
{/* line timeline */}
|
||||
<div className="flex flex-col items-center gap-2 my-2 mx-auto max-w-lg w-full">
|
||||
<span className={'text-gray-300 text-sm cursor-pointer hover:underline'}>load more</span>
|
||||
|
||||
<span className={'text-gray-300 text-sm'}>yesterday</span>
|
||||
|
||||
<div className={'rounded border border-gray-200 flex flex-col w-full'}>
|
||||
{selectedLine.otherUserObjects.map((otherUser) => (
|
||||
<div
|
||||
key={`yesterdayLineHistory-${otherUser._id}`}
|
||||
className="flex flex-row p-2 gap-2 items-center bg-transparent
|
||||
border-b border-b-gray-200 last:border-b-0"
|
||||
>
|
||||
<Avatar
|
||||
key={`linehistory-${1}`}
|
||||
src={otherUser.picture}
|
||||
shape="square"
|
||||
size={'default'}
|
||||
// grayscale if not playing?
|
||||
className={`${true && 'grayscale'}`}
|
||||
/>
|
||||
<span className="text-gray-500">{otherUser.givenName}</span>
|
||||
|
||||
<span className="ml-auto text-xs text-gray-300">{`${
|
||||
Math.floor(Math.random() * 10) + 1
|
||||
}:${Math.floor(Math.random() * 100) + 10}pm |`}</span>
|
||||
|
||||
<span className="text-gray-400 text-md">{`${
|
||||
Math.floor(Math.random() * 60) + 1
|
||||
} seconds`}</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div
|
||||
className="flex flex-row p-2 gap-2 items-center bg-transparent
|
||||
border-b border-b-gray-200 last:border-b-0"
|
||||
>
|
||||
<Avatar
|
||||
key={`linehistory-${1}`}
|
||||
src={user.picture}
|
||||
shape="square"
|
||||
size={'default'}
|
||||
// grayscale if not playing?
|
||||
className={`${true && 'grayscale'}`}
|
||||
/>
|
||||
<span className="text-gray-500">{'Arjun Patel'}</span>
|
||||
|
||||
<span className="ml-auto text-xs text-gray-300">{`${
|
||||
Math.floor(Math.random() * 10) + 1
|
||||
}:${Math.floor(Math.random() * 100) + 10}pm |`}</span>
|
||||
|
||||
<span className="text-gray-400 text-md">{`${
|
||||
Math.floor(Math.random() * 60) + 1
|
||||
} seconds`}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span className={'text-gray-300 text-sm'}>today</span>
|
||||
|
||||
<div className={'rounded border border-gray-400 flex flex-col w-full shadow-xl'}>
|
||||
{selectedLine.otherUserObjects.map((otherUser) => (
|
||||
// TODO: show the shadow if it's unheard
|
||||
<div
|
||||
key={`todayLineHistory-${otherUser._id}`}
|
||||
className="flex flex-row p-2 gap-2 items-center bg-transparent
|
||||
border-b border-b-gray-400 last:border-b-0"
|
||||
>
|
||||
<Avatar
|
||||
key={`linehistory-${1}`}
|
||||
src={otherUser.picture}
|
||||
shape="square"
|
||||
size={'default'}
|
||||
// grayscale if not playing?
|
||||
className={`${false && 'grayscale'}`}
|
||||
/>
|
||||
<span className="text-gray-600 font-semibold">{otherUser.name}</span>
|
||||
|
||||
<span className="ml-auto text-xs text-gray-400">{`${
|
||||
Math.floor(Math.random() * 10) + 1
|
||||
}:${Math.floor(Math.random() * 100) + 10}pm |`}</span>
|
||||
|
||||
<span className="text-gray-500 text-md">{`${
|
||||
Math.floor(Math.random() * 60) + 1
|
||||
} seconds`}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* live broadcasters */}
|
||||
<span className={'flex flex-row gap-2 items-center text-teal-500 text-sm'}>
|
||||
<FiSun className="animate-ping" />
|
||||
<span>right now</span>
|
||||
</span>
|
||||
<div className={'rounded border border-teal-500 flex flex-col w-full shadow-2xl'}>
|
||||
{selectedLine.otherUserObjects.map((otherUser) => (
|
||||
<div
|
||||
key={`rightNowLineHistory-${otherUser._id}`}
|
||||
className="flex flex-row p-2 gap-2 items-center bg-transparent"
|
||||
>
|
||||
<Avatar
|
||||
key={`linehistory-${1}`}
|
||||
src={otherUser.picture}
|
||||
shape="square"
|
||||
size={'large'}
|
||||
// grayscale if not playing?
|
||||
className={`shadow-lg`}
|
||||
/>
|
||||
<span className="text-gray-600 font-semibold">{otherUser.name}</span>
|
||||
|
||||
<Tooltip title={'audio only'}>
|
||||
<FiHeadphones className="text-gray-600 text-md ml-auto" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className={`p-3 absolute right-3 bottom-3 flex justify-center items-center shadow-2xl
|
||||
hover:scale-105 transition-all ${
|
||||
isUserBroadcasting ? 'bg-teal-800 text-white' : 'text-teal-800 border-teal-800 border'
|
||||
}`}
|
||||
>
|
||||
<FiSun className="text-lg" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Avatar } from "antd";
|
||||
import React from "react";
|
||||
import { useMemo } from "react";
|
||||
|
||||
function LineIcon({
|
||||
sourceImages,
|
||||
grayscale = true,
|
||||
}: {
|
||||
sourceImages: string[];
|
||||
grayscale: boolean;
|
||||
}) {
|
||||
const isMultiple = useMemo(() => {
|
||||
if (sourceImages?.length > 1) return true;
|
||||
|
||||
return false;
|
||||
}, [sourceImages]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isMultiple ? (
|
||||
<div
|
||||
className={`relative ${
|
||||
grayscale ? "grayscale" : ""
|
||||
} h-[32px] w-[32px]`}
|
||||
>
|
||||
{sourceImages.map((avatarSrc, index) => {
|
||||
if (index === 0) {
|
||||
return (
|
||||
<Avatar
|
||||
key={`lineIcon-${avatarSrc}-${index}`}
|
||||
src={avatarSrc}
|
||||
shape="square"
|
||||
size={"small"}
|
||||
className={`shadow-lg absolute bottom-0 left-0 bg-slate-200`}
|
||||
/>
|
||||
);
|
||||
} else if (index === 1) {
|
||||
return (
|
||||
<Avatar
|
||||
key={`lineIcon-${avatarSrc}-${index}`}
|
||||
src={avatarSrc}
|
||||
shape="square"
|
||||
size={"small"}
|
||||
className={`absolute top-0 right-0 bg-slate-200`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: add third in the frame if we want later
|
||||
return <></>;
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
sourceImages?.map((avatarSrc, index) => (
|
||||
<Avatar
|
||||
key={`lineIcon-${avatarSrc}-${index}`}
|
||||
src={avatarSrc}
|
||||
shape="square"
|
||||
size={"default"}
|
||||
className={`${grayscale && "grayscale"} shadow-lg`}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(LineIcon);
|
||||
@@ -0,0 +1,457 @@
|
||||
import { FiActivity, FiSun } from "react-icons/fi";
|
||||
import {
|
||||
RtcAnswerRequest,
|
||||
RtcCallRequest,
|
||||
RtcNewUserResponse,
|
||||
RtcReceiveAnswerResponse,
|
||||
ServerResponseChannels,
|
||||
SomeoneUntunedFromLineResponse,
|
||||
} from "@nirvana/core/sockets/channels";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { $selectedLineId } from "../../../controller/recoil";
|
||||
import { Avatar } from "antd";
|
||||
import LineIcon from "../lineIcon";
|
||||
import { LineMemberState } from "@nirvana/core/models/line.model";
|
||||
import MasterLineData from "@nirvana/core/models/masterLineData.model";
|
||||
import Peer from "simple-peer";
|
||||
import { ServerRequestChannels } from "../../../../../core/sockets/channels";
|
||||
import moment from "moment";
|
||||
import toast from "react-hot-toast";
|
||||
import { useGetUserDetails } from "../../../controller/index";
|
||||
import { useLineDataProvider } from "../../../controller/lineDataProvider";
|
||||
import { useRecoilState } from "recoil";
|
||||
|
||||
// todo: send a much more comprehensive master line object? or just add properties to the
|
||||
// masterLineData object so that we don't have different models to maintain between client and server
|
||||
|
||||
export default function LineRow({
|
||||
masterLineData,
|
||||
handleSelectLine,
|
||||
}: {
|
||||
masterLineData: MasterLineData;
|
||||
handleSelectLine: (lineId: string) => void;
|
||||
}) {
|
||||
const [selectedLineId, setSelectedLineId] = useRecoilState($selectedLineId);
|
||||
const { data: userData } = useGetUserDetails();
|
||||
|
||||
useEffect(() => {
|
||||
console.warn("mounting linerow");
|
||||
|
||||
return () => console.warn("UNMOUNTING line row");
|
||||
}, []);
|
||||
|
||||
// take the source of truth list of memeberIds tuned in, and see if I'm in it
|
||||
const isUserTunedIn = useMemo(
|
||||
() =>
|
||||
masterLineData.tunedInMemberIds?.includes(userData?.user?._id.toString()),
|
||||
[masterLineData.tunedInMemberIds, userData]
|
||||
);
|
||||
|
||||
/**
|
||||
* TODO: slowly add to this and fix based on added features
|
||||
* */
|
||||
const renderActivityIcon = useMemo(() => {
|
||||
// if there is someone or me broadcasting here
|
||||
if (masterLineData.currentBroadcastersUserIds?.length > 0)
|
||||
return <FiSun className="text-teal-500 animate-pulse" />;
|
||||
|
||||
if (isUserTunedIn)
|
||||
return <FiActivity className="text-black animate-pulse" />;
|
||||
|
||||
// if there is new activity blocks for me
|
||||
if (masterLineData.currentUserMember.lastVisitDate)
|
||||
return (
|
||||
<span className="h-2 w-2 rounded-full bg-slate-800 animate-pulse"></span>
|
||||
);
|
||||
|
||||
return (
|
||||
<span className="h-2 w-2 rounded-full bg-white animate-pulse"></span>
|
||||
);
|
||||
}, [masterLineData, isUserTunedIn]);
|
||||
|
||||
const renderRightActivity = useMemo(() => {
|
||||
// TODO: get the profile pictures of the broadcasters
|
||||
if (masterLineData.currentBroadcastersUserIds?.length > 0)
|
||||
return (
|
||||
<Avatar.Group
|
||||
key={`lineRowRightActivityGroup-${masterLineData.lineDetails._id.toString()}`}
|
||||
maxCount={2}
|
||||
maxPopoverTrigger="click"
|
||||
size="small"
|
||||
maxStyle={{
|
||||
color: "#f56a00",
|
||||
backgroundColor: "#fde3cf",
|
||||
cursor: "pointer",
|
||||
borderRadius: "0",
|
||||
}}
|
||||
className="shadow-lg"
|
||||
>
|
||||
{masterLineData.otherUserObjects?.map((otherUser, index) => (
|
||||
<Avatar
|
||||
key={`lineListActivitySection-${otherUser._id.toString()}-${index}`}
|
||||
src={otherUser.picture ?? ""}
|
||||
shape="square"
|
||||
size={"small"}
|
||||
/>
|
||||
))}
|
||||
</Avatar.Group>
|
||||
);
|
||||
|
||||
// if there is new activity/black dot, then show relative time as little bolder? or too much?
|
||||
|
||||
// TODO: compare last visit date to latest audio block
|
||||
if (false)
|
||||
return (
|
||||
<span className={`text-gray-400 ml-auto text-xs font-semibold`}>
|
||||
{moment(masterLineData.currentUserMember.lastVisitDate).fromNow(true)}
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<span className={`text-gray-300 ml-auto text-xs `}>
|
||||
{moment(masterLineData.currentUserMember.lastVisitDate).fromNow(true)}
|
||||
</span>
|
||||
);
|
||||
}, [masterLineData]);
|
||||
|
||||
const profilePictures = useMemo(() => {
|
||||
const pictureSources: string[] = [];
|
||||
|
||||
// ?don't add in my image as that's useless contextually?
|
||||
// if (userData?.user?.picture) pictureSources.push(userData.user.picture);
|
||||
|
||||
masterLineData.otherUserObjects?.forEach((otherUser) => {
|
||||
if (otherUser.picture) pictureSources.push(otherUser.picture);
|
||||
});
|
||||
|
||||
return pictureSources;
|
||||
}, [masterLineData, userData]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
onClick={() =>
|
||||
handleSelectLine(masterLineData.lineDetails._id.toString())
|
||||
}
|
||||
className={`flex flex-row items-center justify-start gap-2 p-2 px-4 h-14 hover:bg-gray-200 cursor-pointer transition-all
|
||||
last:border-b-0 border-b border-b-gray-200 relative z-50 rounded ${
|
||||
selectedLineId === masterLineData.lineDetails._id.toString() &&
|
||||
"bg-gray-200 scale-110 shadow-2xl translate-x-3"
|
||||
}`}
|
||||
>
|
||||
{/* status dot */}
|
||||
<div className="flex-shrink-0 h-4 w-4">{renderActivityIcon}</div>
|
||||
|
||||
{profilePictures && (
|
||||
<LineIcon grayscale={!isUserTunedIn} sourceImages={profilePictures} />
|
||||
)}
|
||||
|
||||
<h2
|
||||
className={`text-inherit text-md max-w-[220px] truncate text-slate-800 ${
|
||||
masterLineData.currentUserMember.lastVisitDate
|
||||
? "font-semibold"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{masterLineData.lineDetails.name ||
|
||||
masterLineData.otherUserObjects[0].givenName}
|
||||
</h2>
|
||||
|
||||
<div className="ml-auto flex-shrink-0">{renderRightActivity}</div>
|
||||
</div>
|
||||
|
||||
{/* mounts and unmounts based on if in the room or now */}
|
||||
{/* {isUserTunedIn && (
|
||||
<StreamRoom
|
||||
lineId={masterLineData.lineDetails._id.toString()}
|
||||
currentBroadcasters={masterLineData.currentBroadcastersUserIds}
|
||||
tunedInUsers={masterLineData.tunedInMemberIds}
|
||||
/>
|
||||
)} */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
type PeerMap = { [userId: string]: Peer };
|
||||
|
||||
function StreamRoom({
|
||||
lineId,
|
||||
tunedInUsers,
|
||||
currentBroadcasters,
|
||||
}: {
|
||||
lineId: string;
|
||||
tunedInUsers?: string[];
|
||||
currentBroadcasters?: string[];
|
||||
}) {
|
||||
// ?could move this up the tree and pass it down? or set it in the header?
|
||||
// local stream specifically for this stream room
|
||||
const [localStream, setLocalStream] = useState<MediaStream>();
|
||||
const userStreamTagRef = useRef<HTMLVideoElement>(null);
|
||||
const { data: userDetails } = useGetUserDetails();
|
||||
|
||||
// local peer map of userIds to peers
|
||||
const [userPeers, setUserPeers] = useState<PeerMap>({});
|
||||
|
||||
// ws listen to events of user disconnecting and such or rely on tunedin members prop
|
||||
const { $ws } = useLineDataProvider();
|
||||
|
||||
// todo: get the right constraints based on user settings...audio, video, or none? header selections? make a decision
|
||||
useEffect(() => {
|
||||
if (userDetails) {
|
||||
navigator.mediaDevices
|
||||
.getUserMedia({
|
||||
video: false,
|
||||
audio: {
|
||||
echoCancellation: true,
|
||||
autoGainControl: true,
|
||||
},
|
||||
})
|
||||
.then((userStream) => {
|
||||
setLocalStream(userStream);
|
||||
|
||||
if (userStreamTagRef?.current)
|
||||
userStreamTagRef.current.srcObject = userStream;
|
||||
|
||||
// alternative to dom element
|
||||
// const audio = new Audio();
|
||||
// audio.autoplay = true;
|
||||
// audio.srcObject = userStream;
|
||||
|
||||
// !TEST STUFF : removing distortion with headphones in
|
||||
// test distortion to go away on disabling audio
|
||||
// setTimeout(() => {
|
||||
// console.log("stopping audio stream ");
|
||||
|
||||
// userStream.getTracks().forEach((track) => {
|
||||
// track.enabled = !track.enabled;
|
||||
|
||||
// track.stop();
|
||||
// });
|
||||
// }, 0);
|
||||
|
||||
// take the initial list of tunedInUsers without my own id
|
||||
const everyOtherTunedUserId = tunedInUsers.filter(
|
||||
(tunedUserId) => tunedUserId !== userDetails?.user._id.toString()
|
||||
);
|
||||
|
||||
const localPeerConnections: PeerMap = {};
|
||||
|
||||
everyOtherTunedUserId.forEach((otherTunedInUserId) => {
|
||||
// create local peer objects for them
|
||||
const localPeerInitiator = new Peer({
|
||||
initiator: true,
|
||||
stream: userStream,
|
||||
trickle: false, // prevents the multiple tries on different ice servers and signal from getting called a bunch of times
|
||||
});
|
||||
|
||||
// set it for our map that we will iterate through to display streams in other child view
|
||||
localPeerConnections[otherTunedInUserId] = localPeerInitiator;
|
||||
|
||||
// notify each one with specific signal
|
||||
localPeerInitiator.on("signal", (signal) => {
|
||||
$ws.emit(
|
||||
ServerRequestChannels.RTC_CALL_REQUEST,
|
||||
new RtcCallRequest(lineId, otherTunedInUserId, signal)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
setUserPeers(localPeerConnections);
|
||||
|
||||
// answer calls
|
||||
$ws.on(
|
||||
`${ServerResponseChannels.RTC_NEW_USER_JOINED_RESPONSE_PREFIX}:${lineId}`,
|
||||
(res: RtcNewUserResponse) => {
|
||||
// create a local peer connection for this new user
|
||||
|
||||
console.log(
|
||||
"ooo newbie joined room, I guess I will accept it and send him my signal"
|
||||
);
|
||||
console.log(res);
|
||||
|
||||
var peerForMeAndNewbie = new Peer({
|
||||
initiator: false,
|
||||
trickle: false, // prevents the multiple tries on different ice servers and signal from getting called a bunch of times
|
||||
stream: userStream, // add in my own stream that I got before
|
||||
});
|
||||
|
||||
peerForMeAndNewbie.on("signal", (signal) => {
|
||||
console.log(
|
||||
"as the answerer, I am going to send back my signal so that the newbie can update his local peer for me"
|
||||
);
|
||||
$ws.emit(
|
||||
ServerRequestChannels.RTC_ANSWER_REQUEST,
|
||||
new RtcAnswerRequest(lineId, res.newUserId, signal)
|
||||
);
|
||||
});
|
||||
|
||||
peerForMeAndNewbie.signal(res.simplePeerSignal);
|
||||
|
||||
setUserPeers((prevUsersPeers) => {
|
||||
const newUserPeers = { ...prevUsersPeers };
|
||||
|
||||
newUserPeers[res.newUserId] = peerForMeAndNewbie;
|
||||
|
||||
return newUserPeers;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// take care of answers recevied
|
||||
$ws.on(
|
||||
`${ServerResponseChannels.RTC_RECEIVING_ANSWER_RESPONSE_PREFIX}:${lineId}`,
|
||||
(res: RtcReceiveAnswerResponse) => {
|
||||
console.log(
|
||||
`oooo some master received my call and accepted it ${JSON.stringify(
|
||||
res
|
||||
)}`
|
||||
);
|
||||
console.log(res);
|
||||
|
||||
// find the peer we created earlier for this master
|
||||
// ?is this okay? using the setter to get the current state?
|
||||
setUserPeers((previousUserPeersMap) => {
|
||||
const newUserPeerMap = { ...previousUserPeersMap };
|
||||
|
||||
console.log(
|
||||
"here is the current peers map",
|
||||
previousUserPeersMap
|
||||
);
|
||||
|
||||
const peerForAnswerer = newUserPeerMap[res.answererUserId];
|
||||
|
||||
if (peerForAnswerer) {
|
||||
peerForAnswerer.signal(res.simplePeerSignal);
|
||||
} else {
|
||||
console.error(
|
||||
"could not find the peer we created before for this master"
|
||||
);
|
||||
}
|
||||
|
||||
// note needed to change state so that the peer object that gets iterated in dom isn't the old referenced one/we trigger refresh for the child component
|
||||
return newUserPeerMap;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
$ws.on(
|
||||
`${ServerResponseChannels.SOMEONE_UNTUNED_FROM_LINE}:${lineId}`,
|
||||
(res: SomeoneUntunedFromLineResponse) => {
|
||||
// find the peer object and remove from our userPeers map
|
||||
// will also cause unmounting the child component which destroys peer object but also can destroy here
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
|
||||
toast.error(
|
||||
"Make sure that you have permissions enabled and microphone connected"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: p1 : destroy peers on load
|
||||
// ! remove ws handlers so that the same channels don't get triggered twice when I retune into this line
|
||||
return () => {
|
||||
$ws.removeListener(
|
||||
`${ServerResponseChannels.RTC_RECEIVING_ANSWER_RESPONSE_PREFIX}:${lineId}`
|
||||
);
|
||||
$ws.removeListener(
|
||||
`${ServerResponseChannels.RTC_NEW_USER_JOINED_RESPONSE_PREFIX}:${lineId}`
|
||||
);
|
||||
};
|
||||
}, [userDetails, setUserPeers]);
|
||||
|
||||
// calculate diff to clean our userPeerMap to unmount and detroy certain peer connections
|
||||
useEffect(() => {}, [tunedInUsers]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("keeping an eye on user peers map");
|
||||
console.log(userPeers);
|
||||
}, [userPeers]);
|
||||
|
||||
// todo, someone tell the main object that I am finally connected after everything...different than tuned in
|
||||
|
||||
// TODO: p1...when the peer map user count > tunedIn.length, then we get rid of the right person from list cuzz they have officially left or disconnected
|
||||
useEffect(() => {
|
||||
console.log("change in tuned in users in the streaming room!!!");
|
||||
|
||||
setUserPeers((prevUserPeersMap) => {
|
||||
// go through the userIds here
|
||||
// if tunedIn users doesn't have a userId, this guy prolly disconnected
|
||||
|
||||
const newMap = { ...prevUserPeersMap }; //ensures going through the list of peers again to remove specific ones
|
||||
|
||||
const otherUserIdsPeers = Object.keys(prevUserPeersMap);
|
||||
|
||||
otherUserIdsPeers.forEach((otherUserId) => {
|
||||
// problem if we are trying to show stream of someone who is not tuned in
|
||||
if (!tunedInUsers.includes(otherUserId)) {
|
||||
console.log("user left with id:", otherUserId);
|
||||
const disconnectedLocalPeer = newMap[otherUserId];
|
||||
|
||||
if (disconnectedLocalPeer) {
|
||||
disconnectedLocalPeer.peer?.destroy();
|
||||
delete newMap[otherUserId];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return newMap;
|
||||
});
|
||||
}, [tunedInUsers]);
|
||||
|
||||
// todo: when user isUserBroadcasting is false, disable localUserStream in this component
|
||||
// and also emit, which should already have been done
|
||||
|
||||
// we only loop through peers that are associated to user Ids which exist in the currentBroadcasters array
|
||||
return (
|
||||
<>
|
||||
<audio autoPlay muted ref={userStreamTagRef} controls />
|
||||
this is the video of people
|
||||
{userPeers &&
|
||||
Object.entries(userPeers).map(([userId, peer]) => (
|
||||
<PeerStreamRenderer
|
||||
key={userId}
|
||||
isBroadcasting={currentBroadcasters?.includes(userId)}
|
||||
peer={peer}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* take in the specified peer and whether or not he is broadcasting
|
||||
*/
|
||||
function PeerStreamRenderer({
|
||||
peer,
|
||||
isBroadcasting,
|
||||
}: {
|
||||
peer: Peer;
|
||||
isBroadcasting: boolean;
|
||||
}) {
|
||||
const streamRef = useRef<HTMLVideoElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
peer.on("stream", (remotePeerStream: MediaStream) => {
|
||||
console.log(
|
||||
"stream coming in from remote peer...BUT, only going to show once they broadcast"
|
||||
);
|
||||
|
||||
if (streamRef?.current) streamRef.current.srcObject = remotePeerStream;
|
||||
});
|
||||
|
||||
return () => peer.destroy();
|
||||
}, [streamRef]);
|
||||
|
||||
// when someone is broadcasting, we enable their stream
|
||||
|
||||
return (
|
||||
<>
|
||||
<audio muted={!isBroadcasting} autoPlay controls ref={streamRef} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
import { Avatar, Dropdown, Menu } from 'antd';
|
||||
import { FiLogOut } from 'react-icons/fi';
|
||||
import React, { useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { FaSearch } from 'react-icons/fa';
|
||||
import useAuth from '../../../../providers/AuthProvider';
|
||||
import useElectron from '../../../../providers/ElectronProvider';
|
||||
|
||||
/**
|
||||
* TODO: add in video mode
|
||||
*
|
||||
*/
|
||||
export default function NavBar() {
|
||||
const { user, handleLogout } = useAuth();
|
||||
const { desktopMode } = useElectron();
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [searchInput, setSearchInput] = useState<string>('');
|
||||
|
||||
/** hide the search bar in the header so that it's cleaner for these two modes */
|
||||
const shouldHideSearch = useMemo(() => {
|
||||
if (desktopMode === 'overlayOnly') return true;
|
||||
|
||||
return false;
|
||||
}, [desktopMode]);
|
||||
|
||||
const selectSearch = () => {
|
||||
inputRef.current?.focus();
|
||||
};
|
||||
|
||||
const onSearchChange = (e) => {
|
||||
setSearchInput(e.target.value);
|
||||
};
|
||||
|
||||
// todo: do I need a mute mode? isn't that just flow state
|
||||
// might confuse user overall
|
||||
const profileMenu = (
|
||||
<Menu
|
||||
items={[
|
||||
// {
|
||||
// label: (
|
||||
// <span onClick={handleMuteToggle}>
|
||||
// {mediaSettings.isMuted ? "Unmute" : "Mute"}
|
||||
// </span>
|
||||
// ),
|
||||
// icon: <> {mediaSettings.isMuted ? <FiMicOff /> : <FiMic />} </>,
|
||||
// key: `profile-menu-${1}`,
|
||||
// },
|
||||
// {
|
||||
// label: <span>Audio Only</span>,
|
||||
// icon: <> {mediaSettings.mode === 'audio' ? <FiCheck /> : <></>} </>,
|
||||
// disabled: false,
|
||||
// key: `profile-menu-${2}`,
|
||||
// },
|
||||
// {
|
||||
// label: (
|
||||
// <Tooltip title="coming soon">
|
||||
// <span>Video</span>
|
||||
// </Tooltip>
|
||||
// ),
|
||||
// icon: <>{mediaSettings.mode === 'video' ? <FiCheck /> : <></>} </>,
|
||||
// disabled: true,
|
||||
// key: `profile-menu-${3}`,
|
||||
// },
|
||||
// {
|
||||
// label: (
|
||||
// <Tooltip title="coming soon">
|
||||
// <span>Screen</span>
|
||||
// </Tooltip>
|
||||
// ),
|
||||
// icon: <>{mediaSettings.mode === 'screen' ? <FiCheck /> : <></>} </>,
|
||||
// disabled: true,
|
||||
// key: `profile-menu-${3}`,
|
||||
// },
|
||||
|
||||
{
|
||||
type: 'divider',
|
||||
key: `profile-menu-${4}`,
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
handleLogout();
|
||||
}}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
>
|
||||
Sign Out
|
||||
</button>
|
||||
),
|
||||
icon: <FiLogOut />,
|
||||
key: `profile-menu-${5}`,
|
||||
danger: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="flex flex-row items-center bg-gray-100 p-4 border-b border-b-gray-200"
|
||||
id="titlebar"
|
||||
>
|
||||
{/* <Tooltip title={desktopMode === 'flowState' ? 'unplugged' : 'connected'} placement="right">
|
||||
<div onClick={onHeaderFocus}>
|
||||
<Logo type="small" />
|
||||
</div>
|
||||
</Tooltip> */}
|
||||
|
||||
{!shouldHideSearch && (
|
||||
<div className="mx-auto flex flex-row items-center space-x-2">
|
||||
<FaSearch className="text-xs text-gray-300" />
|
||||
<input
|
||||
placeholder="Type / to search"
|
||||
className="bg-transparent placeholder-gray-300 placeholder:text-xs focus:outline-none"
|
||||
ref={inputRef}
|
||||
onChange={onSearchChange}
|
||||
value={searchInput}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* todo: move this ghost button to components */}
|
||||
{/* {desktopMode === 'flowState' ? (
|
||||
<button
|
||||
onClick={() => setDesktopMode('terminal')}
|
||||
className="ml-auto text-gray-300 text-xs p-3 transition-all hover:bg-gray-200"
|
||||
>
|
||||
connect
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => setDesktopMode('flowState')}
|
||||
className="text-gray-300 text-xs p-3 transition-all hover:bg-gray-200"
|
||||
>
|
||||
flow state
|
||||
</button>
|
||||
)} */}
|
||||
|
||||
<Dropdown overlay={profileMenu}>
|
||||
<div className={'cursor-pointer ml-2'}>
|
||||
{user.picture && (
|
||||
<Avatar
|
||||
key={`userHeaderProfilePicture`}
|
||||
className="shadow-md hover:scale-110 transition-all"
|
||||
size={'large'}
|
||||
alt={user.name}
|
||||
src={user.picture}
|
||||
shape="square"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Dropdown>
|
||||
|
||||
{/* menu for the output options */}
|
||||
{/* <Menu
|
||||
open={menuOpen}
|
||||
id="user-output-selection-menu"
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleCloseMenu}
|
||||
MenuListProps={{
|
||||
"aria-labelledby": "basic-button",
|
||||
}}
|
||||
>
|
||||
<MenuItem onClick={() => setOutputMode("audio")}>
|
||||
<ListItemIcon>
|
||||
<HeadsetMicSharp fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>Audio Only</ListItemText>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem onClick={() => setOutputMode("video")}>
|
||||
<ListItemIcon>
|
||||
<VideocamSharp fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>Video</ListItemText>
|
||||
</MenuItem>
|
||||
</Menu> */}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FiActivity, FiSun } from "react-icons/fi";
|
||||
import { FiActivity, FiSun } from 'react-icons/fi';
|
||||
import {
|
||||
RtcAnswerRequest,
|
||||
RtcCallRequest,
|
||||
@@ -6,21 +6,19 @@ import {
|
||||
RtcReceiveAnswerResponse,
|
||||
ServerResponseChannels,
|
||||
SomeoneUntunedFromLineResponse,
|
||||
} from "@nirvana/core/sockets/channels";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
} from '@nirvana/core/sockets/channels';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { $selectedLineId } from "../../../controller/recoil";
|
||||
import { Avatar } from "antd";
|
||||
import LineIcon from "../lineIcon";
|
||||
import { LineMemberState } from "@nirvana/core/models/line.model";
|
||||
import MasterLineData from "@nirvana/core/models/masterLineData.model";
|
||||
import Peer from "simple-peer";
|
||||
import { ServerRequestChannels } from "../../../../../core/sockets/channels";
|
||||
import moment from "moment";
|
||||
import toast from "react-hot-toast";
|
||||
import { useGetUserDetails } from "../../../controller/index";
|
||||
import { useLineDataProvider } from "../../../controller/lineDataProvider";
|
||||
import { useRecoilState } from "recoil";
|
||||
import { Avatar } from 'antd';
|
||||
import LineIcon from '../lineIcon';
|
||||
import { LineMemberState } from '@nirvana/core/models/line.model';
|
||||
import MasterLineData from '@nirvana/core/models/masterLineData.model';
|
||||
import Peer from 'simple-peer';
|
||||
import { ServerRequestChannels } from '@nirvana/core/sockets/channels';
|
||||
import moment from 'moment';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import useAuth from '../../../../desktop/src/providers/AuthProvider';
|
||||
|
||||
// todo: send a much more comprehensive master line object? or just add properties to the
|
||||
// masterLineData object so that we don't have different models to maintain between client and server
|
||||
@@ -33,19 +31,18 @@ export default function LineRow({
|
||||
handleSelectLine: (lineId: string) => void;
|
||||
}) {
|
||||
const [selectedLineId, setSelectedLineId] = useRecoilState($selectedLineId);
|
||||
const { data: userData } = useGetUserDetails();
|
||||
const { user } = useAuth();
|
||||
|
||||
useEffect(() => {
|
||||
console.warn("mounting linerow");
|
||||
console.warn('mounting linerow');
|
||||
|
||||
return () => console.warn("UNMOUNTING line row");
|
||||
return () => console.warn('UNMOUNTING line row');
|
||||
}, []);
|
||||
|
||||
// take the source of truth list of memeberIds tuned in, and see if I'm in it
|
||||
const isUserTunedIn = useMemo(
|
||||
() =>
|
||||
masterLineData.tunedInMemberIds?.includes(userData?.user?._id.toString()),
|
||||
[masterLineData.tunedInMemberIds, userData]
|
||||
() => masterLineData.tunedInMemberIds?.includes(userData?.user?._id.toString()),
|
||||
[masterLineData.tunedInMemberIds, userData],
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -56,18 +53,13 @@ export default function LineRow({
|
||||
if (masterLineData.currentBroadcastersUserIds?.length > 0)
|
||||
return <FiSun className="text-teal-500 animate-pulse" />;
|
||||
|
||||
if (isUserTunedIn)
|
||||
return <FiActivity className="text-black animate-pulse" />;
|
||||
if (isUserTunedIn) return <FiActivity className="text-black animate-pulse" />;
|
||||
|
||||
// if there is new activity blocks for me
|
||||
if (masterLineData.currentUserMember.lastVisitDate)
|
||||
return (
|
||||
<span className="h-2 w-2 rounded-full bg-slate-800 animate-pulse"></span>
|
||||
);
|
||||
return <span className="h-2 w-2 rounded-full bg-slate-800 animate-pulse"></span>;
|
||||
|
||||
return (
|
||||
<span className="h-2 w-2 rounded-full bg-white animate-pulse"></span>
|
||||
);
|
||||
return <span className="h-2 w-2 rounded-full bg-white animate-pulse"></span>;
|
||||
}, [masterLineData, isUserTunedIn]);
|
||||
|
||||
const renderRightActivity = useMemo(() => {
|
||||
@@ -80,19 +72,19 @@ export default function LineRow({
|
||||
maxPopoverTrigger="click"
|
||||
size="small"
|
||||
maxStyle={{
|
||||
color: "#f56a00",
|
||||
backgroundColor: "#fde3cf",
|
||||
cursor: "pointer",
|
||||
borderRadius: "0",
|
||||
color: '#f56a00',
|
||||
backgroundColor: '#fde3cf',
|
||||
cursor: 'pointer',
|
||||
borderRadius: '0',
|
||||
}}
|
||||
className="shadow-lg"
|
||||
>
|
||||
{masterLineData.otherUserObjects?.map((otherUser, index) => (
|
||||
<Avatar
|
||||
key={`lineListActivitySection-${otherUser._id.toString()}-${index}`}
|
||||
src={otherUser.picture ?? ""}
|
||||
src={otherUser.picture ?? ''}
|
||||
shape="square"
|
||||
size={"small"}
|
||||
size={'small'}
|
||||
/>
|
||||
))}
|
||||
</Avatar.Group>
|
||||
@@ -131,31 +123,24 @@ export default function LineRow({
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
onClick={() =>
|
||||
handleSelectLine(masterLineData.lineDetails._id.toString())
|
||||
}
|
||||
onClick={() => handleSelectLine(masterLineData.lineDetails._id.toString())}
|
||||
className={`flex flex-row items-center justify-start gap-2 p-2 px-4 h-14 hover:bg-gray-200 cursor-pointer transition-all
|
||||
last:border-b-0 border-b border-b-gray-200 relative z-50 rounded ${
|
||||
selectedLineId === masterLineData.lineDetails._id.toString() &&
|
||||
"bg-gray-200 scale-110 shadow-2xl translate-x-3"
|
||||
'bg-gray-200 scale-110 shadow-2xl translate-x-3'
|
||||
}`}
|
||||
>
|
||||
{/* status dot */}
|
||||
<div className="flex-shrink-0 h-4 w-4">{renderActivityIcon}</div>
|
||||
|
||||
{profilePictures && (
|
||||
<LineIcon grayscale={!isUserTunedIn} sourceImages={profilePictures} />
|
||||
)}
|
||||
{profilePictures && <LineIcon grayscale={!isUserTunedIn} sourceImages={profilePictures} />}
|
||||
|
||||
<h2
|
||||
className={`text-inherit text-md max-w-[220px] truncate text-slate-800 ${
|
||||
masterLineData.currentUserMember.lastVisitDate
|
||||
? "font-semibold"
|
||||
: ""
|
||||
masterLineData.currentUserMember.lastVisitDate ? 'font-semibold' : ''
|
||||
}`}
|
||||
>
|
||||
{masterLineData.lineDetails.name ||
|
||||
masterLineData.otherUserObjects[0].givenName}
|
||||
{masterLineData.lineDetails.name || masterLineData.otherUserObjects[0].givenName}
|
||||
</h2>
|
||||
|
||||
<div className="ml-auto flex-shrink-0">{renderRightActivity}</div>
|
||||
@@ -210,8 +195,7 @@ function StreamRoom({
|
||||
.then((userStream) => {
|
||||
setLocalStream(userStream);
|
||||
|
||||
if (userStreamTagRef?.current)
|
||||
userStreamTagRef.current.srcObject = userStream;
|
||||
if (userStreamTagRef?.current) userStreamTagRef.current.srcObject = userStream;
|
||||
|
||||
// alternative to dom element
|
||||
// const audio = new Audio();
|
||||
@@ -232,7 +216,7 @@ function StreamRoom({
|
||||
|
||||
// take the initial list of tunedInUsers without my own id
|
||||
const everyOtherTunedUserId = tunedInUsers.filter(
|
||||
(tunedUserId) => tunedUserId !== userDetails?.user._id.toString()
|
||||
(tunedUserId) => tunedUserId !== userDetails?.user._id.toString(),
|
||||
);
|
||||
|
||||
const localPeerConnections: PeerMap = {};
|
||||
@@ -249,10 +233,10 @@ function StreamRoom({
|
||||
localPeerConnections[otherTunedInUserId] = localPeerInitiator;
|
||||
|
||||
// notify each one with specific signal
|
||||
localPeerInitiator.on("signal", (signal) => {
|
||||
localPeerInitiator.on('signal', (signal) => {
|
||||
$ws.emit(
|
||||
ServerRequestChannels.RTC_CALL_REQUEST,
|
||||
new RtcCallRequest(lineId, otherTunedInUserId, signal)
|
||||
new RtcCallRequest(lineId, otherTunedInUserId, signal),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -266,7 +250,7 @@ function StreamRoom({
|
||||
// create a local peer connection for this new user
|
||||
|
||||
console.log(
|
||||
"ooo newbie joined room, I guess I will accept it and send him my signal"
|
||||
'ooo newbie joined room, I guess I will accept it and send him my signal',
|
||||
);
|
||||
console.log(res);
|
||||
|
||||
@@ -276,13 +260,13 @@ function StreamRoom({
|
||||
stream: userStream, // add in my own stream that I got before
|
||||
});
|
||||
|
||||
peerForMeAndNewbie.on("signal", (signal) => {
|
||||
peerForMeAndNewbie.on('signal', (signal) => {
|
||||
console.log(
|
||||
"as the answerer, I am going to send back my signal so that the newbie can update his local peer for me"
|
||||
'as the answerer, I am going to send back my signal so that the newbie can update his local peer for me',
|
||||
);
|
||||
$ws.emit(
|
||||
ServerRequestChannels.RTC_ANSWER_REQUEST,
|
||||
new RtcAnswerRequest(lineId, res.newUserId, signal)
|
||||
new RtcAnswerRequest(lineId, res.newUserId, signal),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -295,7 +279,7 @@ function StreamRoom({
|
||||
|
||||
return newUserPeers;
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// take care of answers recevied
|
||||
@@ -303,9 +287,7 @@ function StreamRoom({
|
||||
`${ServerResponseChannels.RTC_RECEIVING_ANSWER_RESPONSE_PREFIX}:${lineId}`,
|
||||
(res: RtcReceiveAnswerResponse) => {
|
||||
console.log(
|
||||
`oooo some master received my call and accepted it ${JSON.stringify(
|
||||
res
|
||||
)}`
|
||||
`oooo some master received my call and accepted it ${JSON.stringify(res)}`,
|
||||
);
|
||||
console.log(res);
|
||||
|
||||
@@ -314,25 +296,20 @@ function StreamRoom({
|
||||
setUserPeers((previousUserPeersMap) => {
|
||||
const newUserPeerMap = { ...previousUserPeersMap };
|
||||
|
||||
console.log(
|
||||
"here is the current peers map",
|
||||
previousUserPeersMap
|
||||
);
|
||||
console.log('here is the current peers map', previousUserPeersMap);
|
||||
|
||||
const peerForAnswerer = newUserPeerMap[res.answererUserId];
|
||||
|
||||
if (peerForAnswerer) {
|
||||
peerForAnswerer.signal(res.simplePeerSignal);
|
||||
} else {
|
||||
console.error(
|
||||
"could not find the peer we created before for this master"
|
||||
);
|
||||
console.error('could not find the peer we created before for this master');
|
||||
}
|
||||
|
||||
// note needed to change state so that the peer object that gets iterated in dom isn't the old referenced one/we trigger refresh for the child component
|
||||
return newUserPeerMap;
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
$ws.on(
|
||||
@@ -340,15 +317,13 @@ function StreamRoom({
|
||||
(res: SomeoneUntunedFromLineResponse) => {
|
||||
// find the peer object and remove from our userPeers map
|
||||
// will also cause unmounting the child component which destroys peer object but also can destroy here
|
||||
}
|
||||
},
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
|
||||
toast.error(
|
||||
"Make sure that you have permissions enabled and microphone connected"
|
||||
);
|
||||
toast.error('Make sure that you have permissions enabled and microphone connected');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -356,11 +331,9 @@ function StreamRoom({
|
||||
// ! remove ws handlers so that the same channels don't get triggered twice when I retune into this line
|
||||
return () => {
|
||||
$ws.removeListener(
|
||||
`${ServerResponseChannels.RTC_RECEIVING_ANSWER_RESPONSE_PREFIX}:${lineId}`
|
||||
);
|
||||
$ws.removeListener(
|
||||
`${ServerResponseChannels.RTC_NEW_USER_JOINED_RESPONSE_PREFIX}:${lineId}`
|
||||
`${ServerResponseChannels.RTC_RECEIVING_ANSWER_RESPONSE_PREFIX}:${lineId}`,
|
||||
);
|
||||
$ws.removeListener(`${ServerResponseChannels.RTC_NEW_USER_JOINED_RESPONSE_PREFIX}:${lineId}`);
|
||||
};
|
||||
}, [userDetails, setUserPeers]);
|
||||
|
||||
@@ -368,7 +341,7 @@ function StreamRoom({
|
||||
useEffect(() => {}, [tunedInUsers]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("keeping an eye on user peers map");
|
||||
console.log('keeping an eye on user peers map');
|
||||
console.log(userPeers);
|
||||
}, [userPeers]);
|
||||
|
||||
@@ -376,7 +349,7 @@ function StreamRoom({
|
||||
|
||||
// TODO: p1...when the peer map user count > tunedIn.length, then we get rid of the right person from list cuzz they have officially left or disconnected
|
||||
useEffect(() => {
|
||||
console.log("change in tuned in users in the streaming room!!!");
|
||||
console.log('change in tuned in users in the streaming room!!!');
|
||||
|
||||
setUserPeers((prevUserPeersMap) => {
|
||||
// go through the userIds here
|
||||
@@ -389,7 +362,7 @@ function StreamRoom({
|
||||
otherUserIdsPeers.forEach((otherUserId) => {
|
||||
// problem if we are trying to show stream of someone who is not tuned in
|
||||
if (!tunedInUsers.includes(otherUserId)) {
|
||||
console.log("user left with id:", otherUserId);
|
||||
console.log('user left with id:', otherUserId);
|
||||
const disconnectedLocalPeer = newMap[otherUserId];
|
||||
|
||||
if (disconnectedLocalPeer) {
|
||||
@@ -426,19 +399,13 @@ function StreamRoom({
|
||||
/**
|
||||
* take in the specified peer and whether or not he is broadcasting
|
||||
*/
|
||||
function PeerStreamRenderer({
|
||||
peer,
|
||||
isBroadcasting,
|
||||
}: {
|
||||
peer: Peer;
|
||||
isBroadcasting: boolean;
|
||||
}) {
|
||||
function PeerStreamRenderer({ peer, isBroadcasting }: { peer: Peer; isBroadcasting: boolean }) {
|
||||
const streamRef = useRef<HTMLVideoElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
peer.on("stream", (remotePeerStream: MediaStream) => {
|
||||
peer.on('stream', (remotePeerStream: MediaStream) => {
|
||||
console.log(
|
||||
"stream coming in from remote peer...BUT, only going to show once they broadcast"
|
||||
'stream coming in from remote peer...BUT, only going to show once they broadcast',
|
||||
);
|
||||
|
||||
if (streamRef?.current) streamRef.current.srcObject = remotePeerStream;
|
||||
|
||||
Reference in New Issue
Block a user