Files
grungnet/src/network/NetworkedParty.js

154 lines
4.4 KiB
JavaScript

import React, { useState, useEffect, useCallback, useRef } from "react";
import { useToasts } from "react-toast-notifications";
// Load session for auto complete
// eslint-disable-next-line no-unused-vars
import Session from "./Session";
import { isStreamStopped, omit } from "../helpers/shared";
import { useParty } from "../contexts/PartyContext";
import Party from "../components/party/Party";
/**
* @typedef {object} NetworkedPartyProps
* @property {string} gameId
* @property {Session} session
*/
/**
* @param {NetworkedPartyProps} props
*/
function NetworkedParty({ gameId, session }) {
const partyState = useParty();
const [stream, setStream] = useState(null);
const [partyStreams, setPartyStreams] = useState({});
const { addToast } = useToasts();
function handleStreamStart(localStream) {
setStream(localStream);
const tracks = localStream.getTracks();
for (let track of tracks) {
// Only add the audio track of the stream to the remote peer
if (track.kind === "audio") {
for (let player of Object.values(partyState)) {
session.startStreamTo(player.sessionId, track, localStream);
}
}
}
}
const handleStreamEnd = useCallback(
(localStream) => {
setStream(null);
const tracks = localStream.getTracks();
for (let track of tracks) {
track.stop();
// Only sending audio so only remove the audio track
if (track.kind === "audio") {
for (let player of Object.values(partyState)) {
session.endStreamTo(player.sessionId, track, localStream);
}
}
}
},
[session, partyState]
);
// Keep a reference to players who have just joined to show the joined notification
const joinedPlayersRef = useRef([]);
useEffect(() => {
if (joinedPlayersRef.current.length > 0) {
for (let id of joinedPlayersRef.current) {
if (partyState[id]) {
addToast(`${partyState[id].nickname} joined the party`);
}
}
joinedPlayersRef.current = [];
}
}, [partyState, addToast]);
useEffect(() => {
function handlePlayerJoined(sessionId) {
if (stream) {
const tracks = stream.getTracks();
for (let track of tracks) {
if (track.kind === "audio") {
session.startStreamTo(sessionId, track, stream);
}
}
}
// Add player to join notification list
// Can't just show the notification here as the partyState data isn't populated at this point
joinedPlayersRef.current.push(sessionId);
}
function handlePlayerLeft(sessionId) {
if (partyState[sessionId]) {
addToast(`${partyState[sessionId].nickname} left the party`);
}
}
function handlePeerTrackAdded({ peer, stream: remoteStream }) {
setPartyStreams((prevStreams) => ({
...prevStreams,
[peer.id]: remoteStream,
}));
}
function handlePeerTrackRemoved({ peer, stream: remoteStream }) {
if (isStreamStopped(remoteStream)) {
setPartyStreams((prevStreams) => omit(prevStreams, [peer.id]));
} else {
setPartyStreams((prevStreams) => ({
...prevStreams,
[peer.id]: remoteStream,
}));
}
}
session.on("playerJoined", handlePlayerJoined);
session.on("playerLeft", handlePlayerLeft);
session.on("peerTrackAdded", handlePeerTrackAdded);
session.on("peerTrackRemoved", handlePeerTrackRemoved);
return () => {
session.off("playerJoined", handlePlayerJoined);
session.off("playerLeft", handlePlayerLeft);
session.off("peerTrackAdded", handlePeerTrackAdded);
session.off("peerTrackRemoved", handlePeerTrackRemoved);
};
});
useEffect(() => {
if (stream) {
const tracks = stream.getTracks();
// Detect when someone has ended the screen sharing
// by looking at the streams video track onended
// the audio track doesn't seem to trigger this event
for (let track of tracks) {
if (track.kind === "video") {
track.onended = function () {
handleStreamEnd(stream);
};
}
}
}
}, [stream, handleStreamEnd]);
return (
<>
<Party
gameId={gameId}
onStreamStart={handleStreamStart}
onStreamEnd={handleStreamEnd}
stream={stream}
partyStreams={partyStreams}
/>
</>
);
}
export default NetworkedParty;