From 6950e24e547024bcf5a5e787f8cdfdfe025f2db7 Mon Sep 17 00:00:00 2001 From: PxlLoewe <72106766+PxlLoewe@users.noreply.github.com> Date: Fri, 30 May 2025 22:47:02 -0700 Subject: [PATCH] added force end transmition for dispatchers --- apps/dispatch-server/routes/livekit.ts | 34 ------ apps/dispatch-server/routes/router.ts | 2 - .../socket-events/connect-dispatch.ts | 36 +++--- apps/dispatch/app/_components/Audio.tsx | 108 ++++++++++++++---- .../map/_components/MarkerCluster.tsx | 3 +- .../app/_helpers/liveKitEventHandler.ts | 19 ++- apps/dispatch/app/_store/audioStore.ts | 63 +++++++--- .../app/_store/dispatch/connectionStore.ts | 2 +- .../app/_store/pilot/connectionStore.ts | 2 +- .../app/api/{token => livekit-token}/route.ts | 7 +- .../_components/pannel/MissionForm.tsx | 1 + .../prisma/schema/connectedDispatcher.prisma | 1 + .../database/prisma/schema/mission.prisma | 1 + 13 files changed, 179 insertions(+), 100 deletions(-) delete mode 100644 apps/dispatch-server/routes/livekit.ts rename apps/dispatch/app/api/{token => livekit-token}/route.ts (90%) diff --git a/apps/dispatch-server/routes/livekit.ts b/apps/dispatch-server/routes/livekit.ts deleted file mode 100644 index 1fb3acc9..00000000 --- a/apps/dispatch-server/routes/livekit.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Router } from "express"; -import { AccessToken } from "livekit-server-sdk"; - -if (!process.env.LIVEKIT_API_KEY) throw new Error("LIVEKIT_API_KEY not set"); -if (!process.env.LIVEKIT_API_SECRET) throw new Error("LIVEKIT_API_SECRET not set"); - -const createToken = async (roomName: string) => { - // If this room doesn't exist, it'll be automatically created when the first - // participant joins - // Identifier to be used for participant. - // It's available as LocalParticipant.identity with livekit-client SDK - // TODO: Move function to dispatch nextjs app as API route to use authentication of nextAuth - const participantName = "quickstart-username" + Math.random().toString(36).substring(7); - - const at = new AccessToken(process.env.LIVEKIT_API_KEY, process.env.LIVEKIT_API_SECRET, { - identity: participantName, - // Token to expire after 10 minutes - ttl: "10m", - }); - at.addGrant({ roomJoin: true, room: roomName }); - - return await at.toJwt(); -}; - -const router: Router = Router(); - -router.get("/token", async (req, res) => { - const roomName = req.query.roomName as string; - res.send({ - token: await createToken(roomName), - }); -}); - -export default router; diff --git a/apps/dispatch-server/routes/router.ts b/apps/dispatch-server/routes/router.ts index 695e3d60..7d75a3c1 100644 --- a/apps/dispatch-server/routes/router.ts +++ b/apps/dispatch-server/routes/router.ts @@ -1,5 +1,4 @@ import { Router } from "express"; -import livekitRouter from "./livekit"; import dispatcherRotuer from "./dispatcher"; import missionRouter from "./mission"; import statusRouter from "./status"; @@ -8,7 +7,6 @@ import reportRouter from "./report"; const router: Router = Router(); -router.use("/livekit", livekitRouter); router.use("/dispatcher", dispatcherRotuer); router.use("/mission", missionRouter); router.use("/status", statusRouter); diff --git a/apps/dispatch-server/socket-events/connect-dispatch.ts b/apps/dispatch-server/socket-events/connect-dispatch.ts index c58d6deb..ace95790 100644 --- a/apps/dispatch-server/socket-events/connect-dispatch.ts +++ b/apps/dispatch-server/socket-events/connect-dispatch.ts @@ -62,6 +62,7 @@ export const handleConnectDispatch = esimatedLogoutTime: parsedLogoffDate?.toISOString() || null, lastHeartbeat: new Date().toISOString(), userId: user.id, + zone: selectedZone, loginTime: new Date().toISOString(), }, }); @@ -72,20 +73,29 @@ export const handleConnectDispatch = io.to("dispatchers").emit("dispatchers-update"); io.to("pilots").emit("dispatchers-update"); - // dispatch-events - socket.on("ptt", async ({ shouldTransmit, channel }) => { - if (shouldTransmit) { - io.to("dispatchers").emit("other-ptt", { - publicUser: getPublicUser(user), - channel, - source: "Leitstelle", + socket.on("stop-other-transmition", async ({ ownRole, otherRole }) => { + const aircrafts = await prisma.connectedAircraft.findMany({ + where: { + Station: { + bosCallsignShort: otherRole, + }, + logoutTime: null, + }, + include: { + Station: true, + }, + }); + const dispatchers = await prisma.connectedDispatcher.findMany({ + where: { + zone: otherRole, + logoutTime: null, + }, + }); + [...aircrafts, ...dispatchers].forEach((entry) => { + io.to(`user:${entry.userId}`).emit("force-end-transmission", { + by: ownRole, }); - io.to("piots").emit("other-ptt", { - publicUser: getPublicUser(user), - channel, - source: "Leitstelle", - }); - } + }); }); socket.on("disconnect", async () => { diff --git a/apps/dispatch/app/_components/Audio.tsx b/apps/dispatch/app/_components/Audio.tsx index d16dfbbc..684f1e41 100644 --- a/apps/dispatch/app/_components/Audio.tsx +++ b/apps/dispatch/app/_components/Audio.tsx @@ -18,12 +18,16 @@ import { useAudioStore } from "_store/audioStore"; import { cn } from "_helpers/cn"; import { ConnectionQuality } from "livekit-client"; import { ROOMS } from "_data/livekitRooms"; +import { useDispatchConnectionStore } from "_store/dispatch/connectionStore"; +import { useSession } from "next-auth/react"; +import { dispatchSocket } from "dispatch/socket"; export const Audio = () => { const connection = usePilotConnectionStore(); const [showSource, setShowSource] = useState(false); const { + speakingParticipants, isTalking, toggleTalking, connect, @@ -33,50 +37,105 @@ export const Audio = () => { remoteParticipants, room, message, - source, + removeMessage, } = useAudioStore(); const [selectedRoom, setSelectedRoom] = useState("LST_01"); - useEffect(() => { - setShowSource(true); - }, [source, isTalking]); + const { selectedStation, status: pilotState } = usePilotConnectionStore((state) => state); + + const { selectedZone, status: dispatcherState } = useDispatchConnectionStore((state) => state); + const session = useSession(); + + const [recentSpeakers, setRecentSpeakers] = useState([]); useEffect(() => { - const joinRoom = async () => { - if (connection.status != "connected") return; - if (state === "connected") return; - connect(selectedRoom); - }; - - joinRoom(); - - return () => { - disconnect(); - }; + if (speakingParticipants.length > 0) { + setRecentSpeakers(speakingParticipants); + } else if (recentSpeakers.length > 0) { + const timeout = setTimeout(() => { + setRecentSpeakers([]); + }, 10000); + return () => clearTimeout(timeout); + } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [connection.status]); + }, [speakingParticipants]); + + useEffect(() => { + if (message && state !== "error") { + const timeout = setTimeout(() => { + removeMessage(); + }, 10000); + return () => clearTimeout(timeout); + } + }, [message, removeMessage, state]); + + const displayedSpeakers = speakingParticipants.length > 0 ? speakingParticipants : recentSpeakers; + + const canStopOtherSpeakers = dispatcherState === "connected"; + + const role = + (dispatcherState === "connected" && selectedZone) || + (pilotState == "connected" && selectedStation?.bosCallsignShort) || + session.data?.user?.publicId; return ( <>
- {state === "error" &&
{message}
} - {showSource && source && ( + {message && (
+ +
+ )} + {(displayedSpeakers.length || message) && ( +
)}