【发布时间】:2022-10-01 13:46:08
【问题描述】:
我正在使用peerjs 中的React 和vite 制作一个直播应用程序。
我知道已经有很多这样的问题,但这是针对ReactJs 的。我找不到任何与 React 相关的解决方案。
这是我的webRTC.ts 文件。
import Peer, { DataConnection, MediaConnection } from \"peerjs\";
import socket from \"./socket\";
import { JOIN_LECTURE } from \"./socketaction\";
export const getUserStream: () => Promise<MediaStream> =
(): Promise<MediaStream> => {
return navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
};
export const getScreenStream: () => Promise<MediaStream> =
(): Promise<MediaStream> => {
return navigator.mediaDevices.getDisplayMedia({ video: true, audio: true });
};
export const initPeer: (peer: Peer, name: string) => void = (
peer: Peer,
name: string
) => {
// On Error
peer.on(\"error\", (err: Error): void => {
console.error(err);
});
// On Open
peer.on(\"open\", (id: string): void => {
console.log(\"[p]open\", name);
console.log(id);
});
// On Connection
peer.addListener(\"connection\", (conn: DataConnection): void => {
conn.on(\"data\", (data: unknown): void => {
console.log(data, \"data\");
});
});
};
export const answerCall = (
peer: Peer,
lectureId: string,
cb: (stream: MediaStream, type: \"user\" | \"screen\") => void
): void => {
peer.addListener(\"open\", () => {
socket.emit(JOIN_LECTURE, {
peerId: peer.id,
lectureId: lectureId,
});
// socket.emit(GET_STREAM);
});
peer.on(\"call\", (call: MediaConnection): void => {
call.answer();
call.on(\"stream\", (stream: MediaStream): void => {
cb(stream, call.metadata.streamType);
});
call.on(\"error\", console.error);
call.on(\"close\", (): void => {
console.log(\"call closed\");
});
});
};
export const shareUser = (
peer: Peer,
stream: MediaStream,
studentId: string
) => {
if (peer && studentId) {
console.log(studentId);
const conn = peer.connect(studentId);
conn.on(\"open\", () => {
const call: MediaConnection = peer.call(studentId, stream, {
metadata: {
streamType: \"user\",
},
});
call.on(\"error\", console.error);
call.on(\"close\", (): void => {
console.log(\"call closed\");
});
});
conn.on(\"data\", console.log);
conn.on(\"error\", console.error);
conn.on(\"iceStateChanged\", () => console.log(\"IceStageChanged\"));
}
};
这是我的main.tsx 文件
import ReactDOM from \"react-dom/client\";
import App from \"./App\";
import \"./index.css\";
import { ThemeProvider } from \"@mui/system\";
import MuiTheme from \"./mui-theme\";
import { Provider } from \"react-redux\";
import { store } from \"./redux/store\";
import { ToastContainer } from \"react-toastify\";
import \"react-toastify/dist/ReactToastify.css\";
import { CookiesProvider } from \"react-cookie\";
import { StrictMode } from \"react\";
ReactDOM.createRoot(document.getElementById(\"root\") as HTMLElement).render(
<StrictMode>
<Provider store={store}>
<CookiesProvider>
<ThemeProvider theme={MuiTheme}>
<App />
</ThemeProvider>
<ToastContainer theme=\"dark\" position=\"bottom-right\" />
</CookiesProvider>
</Provider>
</StrictMode>
);
这是我的liveLecture.tsx 文件。
import {
CallEndRounded,
ChatRounded,
DoNotTouchRounded,
FullscreenRounded,
MicOffRounded,
MicRounded,
PanToolRounded,
PausePresentationRounded,
PresentToAllRounded,
SendRounded,
SpeakerNotesOffRounded,
VideocamOffRounded,
VideocamRounded,
ViewComfyOutlined,
ViewComfyRounded,
} from \"@mui/icons-material\";
import { Avatar, IconButton, Input, InputAdornment } from \"@mui/material\";
import { AnyAction, Dispatch } from \"@reduxjs/toolkit\";
import Peer, { DataConnection, MediaConnection } from \"peerjs\";
import { useEffect, useState } from \"react\";
import { useDispatch } from \"react-redux\";
import { useSelector } from \"react-redux\";
import { useParams } from \"react-router-dom\";
import Logo from \"../components/logo\";
import VideoPlayer from \"../components/videoplayer\";
import { User } from \"../redux/slices/user\";
import { RootState } from \"../redux/store\";
import socket from \"../utils/socket\";
import { GET_STREAM, IS_ADMIN, JOIN_LECTURE } from \"../utils/socketaction\";
import {
answerCall,
getScreenStream,
getUserStream,
initPeer,
shareUser,
} from \"../utils/webRTC\";
interface Paused {
video: boolean;
audio: boolean;
}
function LiveLecture() {
const { lectureId } = useParams();
const user: User = useSelector((state: RootState): User => state.user);
const [userStream, setUserStream] = useState<MediaStream | undefined>();
const [screenStream, setScreenStream] = useState<MediaStream | undefined>();
const [isHandRaised, setIsHandRaised] = useState<boolean>(false);
const [isChatOpen, setIsChatOpen] = useState<boolean>(true);
const [isPresentationView, setIsPresentationView] = useState<boolean>(true);
const [paused, setPaused] = useState<Paused>({
audio: true,
video: true,
});
const [isAdmin, setIsAdmin] = useState<boolean>(false);
const [userPeer, setUserPeer] = useState<Peer>(new Peer());
const [screenPeer, setScreenPeer] = useState<Peer>(new Peer());
const [isFullScreen, setIsFullScreen] = useState(false);
const dispatch: Dispatch<AnyAction> = useDispatch();
const shareUserStream = (studentId: string) => {
if (userStream) {
shareUser(userPeer, userStream, studentId);
} else {
getUserStream().then((stream: MediaStream): void => {
setUserStream(stream);
console.log(userPeer, studentId);
shareUser(userPeer, stream, studentId);
});
}
};
useEffect((): (() => void) => {
if (userPeer && lectureId) {
initPeer(userPeer, \"user\");
answerCall(
userPeer,
lectureId,
(stream: MediaStream, type: \"user\" | \"screen\"): void => {
console.log(\"second\");
if (type == \"user\") {
setUserStream(stream);
} else {
setScreenStream(stream);
}
}
);
}
return (): void => {};
}, [userPeer]);
useEffect((): (() => void) => {
if (screenPeer) {
initPeer(screenPeer, \"screen\");
}
return (): void => {};
}, [screenPeer]);
useEffect((): (() => void) => {
socket.on(IS_ADMIN, (admin: boolean) => {
setIsAdmin(admin);
if (admin) {
socket.on(GET_STREAM, (studentPeerId) => {
// call the user
studentPeerId && shareUserStream(studentPeerId);
});
}
console.log(admin);
});
return (): void => {};
}, []);
return (
<div className=\"flex overflow-hidden flex-col w-full h-screen\">
<div className=\"w-full flex px-10 bg-gray-900 shadow-lg py-3\">
<div className=\" flex sm:gap-6 gap-4 divide-x-2 justify-center items-center text-2xl font-semibold text-white\">
<div className=\"hidden sm:block\">
<Logo />
</div>
<div className=\"md:pl-6 pl-4\">Batch Name</div>
<div className=\"select-none ring-2 ring-red-500 bg-white text-red-500 font-bold uppercase px-2 rounded-lg\">
Live
</div>
</div>
</div>
<div className=\"flex py-4 lg:justify-around lg:flex-row bg-secondary-100 grow h-[calc(100vh-16rem)] flex-col\">
<div className=\"flex grow gap-3 flex-col justify-between items-center\">
<div
className={`flex justify-center items-center grow px-4 lg:w-full`}
>
<div
className={`grid ${
!isChatOpen && \"px-10\"
} w-full gap-4 grid-cols-4 grid-row-4`}
>
<div
className={`${
isChatOpen ? \"col-span-1\" : \"col-span-3 sm:col-span-1\"
}`}
>
{/* secondary player */}
<VideoPlayer
stream={isPresentationView ? userStream : screenStream}
/>
</div>
<div
className={`col-span-3 px-2 grow flex justify-center items-center`}
>
{/* primary player */}
<VideoPlayer
isFullScreen={isFullScreen}
stream={isPresentationView ? screenStream : userStream}
/>
</div>
</div>
</div>
<div className=\"justify-center items-center\">
<div className=\"py-2 px-6 rounded-full bg-gray-900 text-gray-500 flex gap-2 sm:gap-6 justify-center items-center\">
{isAdmin ? (
<>
<IconButton
onClick={(): void => {
setPaused(
(pp: Paused): Paused => ({
...pp,
audio: !pp.audio,
})
);
}}
color=\"inherit\"
>
{paused.audio ? (
<MicOffRounded color=\"inherit\" />
) : (
<MicRounded color=\"inherit\" />
)}
</IconButton>
<IconButton
onClick={(): void => {
setPaused(
(pp: Paused): Paused => ({
...pp,
video: !pp.video,
})
);
}}
color=\"inherit\"
>
{paused.video ? (
<VideocamOffRounded color=\"inherit\" />
) : (
<VideocamRounded color=\"inherit\" />
)}
</IconButton>
<IconButton
onClick={() => {
if (!screenStream) {
const f: string | null = prompt(\"fId:\");
if (screenPeer && f) {
getScreenStream().then((stream: MediaStream) => {
setScreenStream(stream);
const conn: DataConnection = screenPeer.connect(f);
conn.on(\"open\", (): void => {
const call: MediaConnection = screenPeer.call(
f,
stream,
{
metadata: {
streamType: \"screen\",
},
}
);
call.on(\"error\", console.error);
call.on(\"close\", (): void => {
console.log(\"call closed\");
});
});
});
}
} else {
screenStream
.getTracks()
.forEach((track: MediaStreamTrack): void =>
track.stop()
);
setScreenStream(undefined);
}
}}
color=\"inherit\"
>
{screenStream ? (
<PausePresentationRounded color=\"inherit\" />
) : (
<PresentToAllRounded color=\"inherit\" />
)}
</IconButton>
<IconButton
onClick={() => {
setIsPresentationView((pipv) => !pipv);
}}
color=\"inherit\"
>
{isPresentationView ? (
<ViewComfyOutlined color=\"inherit\" />
) : (
<ViewComfyRounded color=\"inherit\" />
)}
</IconButton>
</>
) : (
<IconButton color=\"inherit\">
{isHandRaised ? (
<PanToolRounded color=\"inherit\" />
) : (
<DoNotTouchRounded color=\"inherit\" />
)}
</IconButton>
)}
<IconButton
color=\"inherit\"
onClick={(): void => {
setIsChatOpen((pico: boolean): boolean => !pico);
}}
>
{isChatOpen ? (
<SpeakerNotesOffRounded color=\"inherit\" />
) : (
<ChatRounded color=\"inherit\" />
)}
</IconButton>
<IconButton
onClick={() => {
setIsFullScreen((pifs: boolean): boolean => !pifs);
setIsAdmin(true);
}}
color=\"inherit\"
>
<FullscreenRounded color=\"inherit\" />
</IconButton>
<IconButton
onClick={(): void => {
// const f: string | null = window.prompt(\"fId\");
// getUserStream().then((stream: MediaStream): void => {
// setUserStream(stream);
// if (userPeer && f) {
// const conn: DataConnection = userPeer.connect(f);
// conn.on(\"open\", (): void => {
// const call: MediaConnection = userPeer.call(f, stream, {
// metadata: {
// streamType: \"user\",
// },
// });
// call.on(\"error\", console.error);
// call.on(\"close\", (): void => {
// console.log(\"call closed\");
// });
// });
// }
// });
const f: string | null = window.prompt(\"fId\");
f && shareUserStream(f);
}}
sx={{
bgcolor: \"#550000\",
}}
color=\"error\"
>
<div className=\"flex justify-center items-center grow\">
<CallEndRounded />
</div>
</IconButton>
</div>
</div>
</div>
<div className=\"px-3 py-4\">
<div
className={`sm:h-1/4 ${
!isChatOpen && \"hidden\"
} h-2/5 sm:h-1/4 lg:h-[calc(100%-5rem)] w-full lg:min-w-[20rem]`}
>
<div className=\"py-2 px-4 bg-primary-400 text-white shadow-lg rounded-t-lg uppercase font-semibold select-none\">
live chats
</div>
<div className=\"flex px-4 h-full overflow-auto flex-col bg-primary-400\">
<div className=\"flex flex-col gap-2 py-2 py-3overflow-auto\">
<div className=\"flex bg-white rounded-md gap-2 px-4 py-2 items-center\">
<div>
<Avatar />
</div>
<div className=\"flex flex-col gap-1\">
<div className=\"font-semibold text-sm\">Name Name</div>
<div className=\"text-sm\">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
<div className=\"flex bg-white rounded-md gap-2 px-4 py-2 items-center\">
<div>
<Avatar />
</div>
<div className=\"flex flex-col gap-1\">
<div className=\"font-semibold text-sm\">Name Name</div>
<div className=\"text-sm\">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
<div className=\"flex bg-white rounded-md gap-2 px-4 py-2 items-center\">
<div>
<Avatar />
</div>
<div className=\"flex flex-col gap-1\">
<div className=\"font-semibold text-sm\">Name Name</div>
<div className=\"text-sm\">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
<div className=\"flex bg-white rounded-md gap-2 px-4 py-2 items-center\">
<div>
<Avatar />
</div>
<div className=\"flex flex-col gap-1\">
<div className=\"font-semibold text-sm\">Name Name</div>
<div className=\"text-sm\">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
<div className=\"flex bg-white rounded-md gap-2 px-4 py-2 items-center\">
<div>
<Avatar />
</div>
<div className=\"flex flex-col gap-1\">
<div className=\"font-semibold text-sm\">Name Name</div>
<div className=\"text-sm\">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
<div className=\"flex bg-white rounded-md gap-2 px-4 py-2 items-center\">
<div>
<Avatar />
</div>
<div className=\"flex flex-col gap-1\">
<div className=\"font-semibold text-sm\">Name Name</div>
<div className=\"text-sm\">This is some chat message</div>
</div>
</div>
<div className=\"flex bg-white rounded-md gap-2 px-4 py-2 items-center\">
<div>
<Avatar />
</div>
<div className=\"flex flex-col gap-1\">
<div className=\"font-semibold text-sm\">Name Name</div>
<div className=\"text-sm\">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
<div className=\"flex bg-white rounded-md gap-2 px-4 py-2 items-center\">
<div>
<Avatar />
</div>
<div className=\"flex flex-col gap-1\">
<div className=\"font-semibold text-sm\">Name Name</div>
<div className=\"text-sm\">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
<div className=\"flex bg-white rounded-md gap-2 px-4 py-2 items-center\">
<div>
<Avatar />
</div>
<div className=\"flex flex-col gap-1\">
<div className=\"font-semibold text-sm\">Name Name</div>
<div className=\"text-sm\">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
</div>
</div>
<div className=\"py-2 px-4 bg-primary-400 rounded-b-md\">
<Input
// value={message}
// onChange={(e) => {
// setMessage(e.target.value);
// }}
placeholder=\"message\"
inputProps={{
className: \"no-scrollbar\",
}}
fullWidth={true}
className=\"bg-primary-400\"
multiline
maxRows={3}
endAdornment={
<InputAdornment position=\"end\">
<IconButton>
<SendRounded className=\"cursor-pointer\" />
</IconButton>
</InputAdornment>
}
/>
</div>
</div>
</div>
</div>
</div>
);
}
export default LiveLecture;
最后这是我用于 socket.io 连接的服务器代码
socket.on(
JOIN_LECTURE,
({ lectureId, peerId }: { lectureId: string; peerId: string }): void => {
// verify user identity
socket.join(lectureId);
socket.to(lectureId).emit(GET_STREAM, peerId);
console.log(colors.blueBright(`[${socket.id}] joined ${lectureId}`));
// check if the connected user is admin;
// temporarily setting the first user as admin; pink;
const clientsInRoom: Set<string> | undefined =
io.sockets.adapter.rooms.get(lectureId);
const viewerCount: number = (clientsInRoom ? clientsInRoom.size : 0) - 1;
const isAdmin: boolean = !viewerCount;
socket.emit(IS_ADMIN, isAdmin);
// console.log(io.sockets.adapter.rooms);
socket.addListener(disconnect, () => {
console.log(\"disconnect\");
// temporarily transferring admin rights to the next socket; pink;
if (isAdmin) {
const nextAdmin = clientsInRoom?.entries().next();
if (nextAdmin) {
console.log(nextAdmin, \"nextAdmin\");
io.sockets.to(nextAdmin.value).emit(IS_ADMIN, true);
}
}
});
}
);```
标签: javascript reactjs socket.io webrtc peerjs