added force end transmition for dispatchers
This commit is contained in:
@@ -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;
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import livekitRouter from "./livekit";
|
|
||||||
import dispatcherRotuer from "./dispatcher";
|
import dispatcherRotuer from "./dispatcher";
|
||||||
import missionRouter from "./mission";
|
import missionRouter from "./mission";
|
||||||
import statusRouter from "./status";
|
import statusRouter from "./status";
|
||||||
@@ -8,7 +7,6 @@ import reportRouter from "./report";
|
|||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.use("/livekit", livekitRouter);
|
|
||||||
router.use("/dispatcher", dispatcherRotuer);
|
router.use("/dispatcher", dispatcherRotuer);
|
||||||
router.use("/mission", missionRouter);
|
router.use("/mission", missionRouter);
|
||||||
router.use("/status", statusRouter);
|
router.use("/status", statusRouter);
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ export const handleConnectDispatch =
|
|||||||
esimatedLogoutTime: parsedLogoffDate?.toISOString() || null,
|
esimatedLogoutTime: parsedLogoffDate?.toISOString() || null,
|
||||||
lastHeartbeat: new Date().toISOString(),
|
lastHeartbeat: new Date().toISOString(),
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
zone: selectedZone,
|
||||||
loginTime: new Date().toISOString(),
|
loginTime: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -72,20 +73,29 @@ export const handleConnectDispatch =
|
|||||||
io.to("dispatchers").emit("dispatchers-update");
|
io.to("dispatchers").emit("dispatchers-update");
|
||||||
io.to("pilots").emit("dispatchers-update");
|
io.to("pilots").emit("dispatchers-update");
|
||||||
|
|
||||||
// dispatch-events
|
socket.on("stop-other-transmition", async ({ ownRole, otherRole }) => {
|
||||||
socket.on("ptt", async ({ shouldTransmit, channel }) => {
|
const aircrafts = await prisma.connectedAircraft.findMany({
|
||||||
if (shouldTransmit) {
|
where: {
|
||||||
io.to("dispatchers").emit("other-ptt", {
|
Station: {
|
||||||
publicUser: getPublicUser(user),
|
bosCallsignShort: otherRole,
|
||||||
channel,
|
},
|
||||||
source: "Leitstelle",
|
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 () => {
|
socket.on("disconnect", async () => {
|
||||||
|
|||||||
@@ -18,12 +18,16 @@ import { useAudioStore } from "_store/audioStore";
|
|||||||
import { cn } from "_helpers/cn";
|
import { cn } from "_helpers/cn";
|
||||||
import { ConnectionQuality } from "livekit-client";
|
import { ConnectionQuality } from "livekit-client";
|
||||||
import { ROOMS } from "_data/livekitRooms";
|
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 = () => {
|
export const Audio = () => {
|
||||||
const connection = usePilotConnectionStore();
|
const connection = usePilotConnectionStore();
|
||||||
const [showSource, setShowSource] = useState(false);
|
const [showSource, setShowSource] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
speakingParticipants,
|
||||||
isTalking,
|
isTalking,
|
||||||
toggleTalking,
|
toggleTalking,
|
||||||
connect,
|
connect,
|
||||||
@@ -33,50 +37,105 @@ export const Audio = () => {
|
|||||||
remoteParticipants,
|
remoteParticipants,
|
||||||
room,
|
room,
|
||||||
message,
|
message,
|
||||||
source,
|
removeMessage,
|
||||||
} = useAudioStore();
|
} = useAudioStore();
|
||||||
const [selectedRoom, setSelectedRoom] = useState<string>("LST_01");
|
const [selectedRoom, setSelectedRoom] = useState<string>("LST_01");
|
||||||
|
|
||||||
useEffect(() => {
|
const { selectedStation, status: pilotState } = usePilotConnectionStore((state) => state);
|
||||||
setShowSource(true);
|
|
||||||
}, [source, isTalking]);
|
const { selectedZone, status: dispatcherState } = useDispatchConnectionStore((state) => state);
|
||||||
|
const session = useSession();
|
||||||
|
|
||||||
|
const [recentSpeakers, setRecentSpeakers] = useState<typeof speakingParticipants>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const joinRoom = async () => {
|
if (speakingParticipants.length > 0) {
|
||||||
if (connection.status != "connected") return;
|
setRecentSpeakers(speakingParticipants);
|
||||||
if (state === "connected") return;
|
} else if (recentSpeakers.length > 0) {
|
||||||
connect(selectedRoom);
|
const timeout = setTimeout(() => {
|
||||||
};
|
setRecentSpeakers([]);
|
||||||
|
}, 10000);
|
||||||
joinRoom();
|
return () => clearTimeout(timeout);
|
||||||
|
}
|
||||||
return () => {
|
|
||||||
disconnect();
|
|
||||||
};
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="bg-base-200 rounded-box flex items-center gap-2 p-1">
|
<div className="bg-base-200 rounded-box flex items-center gap-2 p-1">
|
||||||
{state === "error" && <div className="h-4 flex items-center">{message}</div>}
|
{message && (
|
||||||
{showSource && source && (
|
|
||||||
<div
|
<div
|
||||||
className="tooltip tooltip-left tooltip-error font-semibold"
|
className="tooltip tooltip-left tooltip-warning font-semibold"
|
||||||
|
data-tip="Nachricht entfernen"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className={cn("btn btn-sm btn-ghost border-warning bg-transparent ")}
|
||||||
|
onClick={() => {
|
||||||
|
removeMessage();
|
||||||
|
// Probably via socket event to set ppt = false for participant
|
||||||
|
if (!canStopOtherSpeakers) return;
|
||||||
|
speakingParticipants.forEach((p) => {
|
||||||
|
dispatchSocket.emit("stop-other-transmition", {
|
||||||
|
ownRole: role,
|
||||||
|
otherRole: p.attributes.role,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{message}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{(displayedSpeakers.length || message) && (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"tooltip-left tooltip-error font-semibold",
|
||||||
|
canStopOtherSpeakers && "tooltip",
|
||||||
|
)}
|
||||||
data-tip="Funkspruch unterbrechen"
|
data-tip="Funkspruch unterbrechen"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-soft border-none bg-transparent hover:bg-error"
|
className={cn(
|
||||||
onClick={() => {}}
|
"btn btn-sm btn-soft border-none bg-transparent",
|
||||||
|
canStopOtherSpeakers && "hover:bg-error",
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
if (!canStopOtherSpeakers) return;
|
||||||
|
speakingParticipants.forEach((p) => {
|
||||||
|
dispatchSocket.emit("stop-other-transmition", {
|
||||||
|
ownRole: role,
|
||||||
|
otherRole: p.attributes.role,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{source}
|
{displayedSpeakers.map((p) => p.attributes.role).join(", ") || ""}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (state === "connected") toggleTalking();
|
if (state === "connected") toggleTalking();
|
||||||
if (state === "error" || state === "disconnected") connect(selectedRoom);
|
if (!role) return;
|
||||||
|
if (state === "error" || state === "disconnected") connect(selectedRoom, role);
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
"btn btn-sm btn-soft border-none hover:bg-inherit",
|
"btn btn-sm btn-soft border-none hover:bg-inherit",
|
||||||
@@ -111,9 +170,10 @@ export const Audio = () => {
|
|||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-ghost text-left flex items-center justify-start gap-2 relative"
|
className="btn btn-sm btn-ghost text-left flex items-center justify-start gap-2 relative"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
if (!role) return;
|
||||||
if (selectedRoom === r) return;
|
if (selectedRoom === r) return;
|
||||||
setSelectedRoom(r);
|
setSelectedRoom(r);
|
||||||
connect(r);
|
connect(r, role);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{room?.name === r && (
|
{room?.name === r && (
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ const PopupContent = ({
|
|||||||
{missions.map((mission) => {
|
{missions.map((mission) => {
|
||||||
const needsAction =
|
const needsAction =
|
||||||
HPGValidationRequired(mission.missionStationIds, aircrafts, mission.hpgMissionString) &&
|
HPGValidationRequired(mission.missionStationIds, aircrafts, mission.hpgMissionString) &&
|
||||||
mission.hpgValidationState !== "VALID";
|
mission.hpgValidationState !== HpgValidationState.VALID &&
|
||||||
|
mission.state === "draft";
|
||||||
|
|
||||||
const markerColor = needsAction
|
const markerColor = needsAction
|
||||||
? MISSION_STATUS_COLORS["attention"]
|
? MISSION_STATUS_COLORS["attention"]
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useAudioStore } from "_store/audioStore";
|
||||||
import {
|
import {
|
||||||
LocalParticipant,
|
LocalParticipant,
|
||||||
LocalTrackPublication,
|
LocalTrackPublication,
|
||||||
@@ -13,6 +14,20 @@ export const handleTrackSubscribed = (
|
|||||||
publication: RemoteTrackPublication,
|
publication: RemoteTrackPublication,
|
||||||
participant: RemoteParticipant,
|
participant: RemoteParticipant,
|
||||||
) => {
|
) => {
|
||||||
|
console.log("Track subscribed:", track, publication, participant);
|
||||||
|
if (!track.isMuted) {
|
||||||
|
useAudioStore.getState().addSpeakingParticipant(participant);
|
||||||
|
}
|
||||||
|
track.on("unmuted", () => {
|
||||||
|
useAudioStore.getState().addSpeakingParticipant(participant);
|
||||||
|
|
||||||
|
console.log("Track unmuted:", track);
|
||||||
|
});
|
||||||
|
track.on("muted", () => {
|
||||||
|
useAudioStore.getState().removeSpeakingParticipant(participant);
|
||||||
|
|
||||||
|
console.log("Track muted:", track);
|
||||||
|
});
|
||||||
if (track.kind === Track.Kind.Video || track.kind === Track.Kind.Audio) {
|
if (track.kind === Track.Kind.Video || track.kind === Track.Kind.Audio) {
|
||||||
// attach it to a new HTMLVideoElement or HTMLAudioElement
|
// attach it to a new HTMLVideoElement or HTMLAudioElement
|
||||||
const element = track.attach();
|
const element = track.attach();
|
||||||
@@ -37,10 +52,6 @@ export const handleLocalTrackUnpublished = (
|
|||||||
publication.track?.detach();
|
publication.track?.detach();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const handleActiveSpeakerChange = (speakers: Participant[]) => {
|
|
||||||
// show UI indicators when participant is speaking
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleDisconnect = () => {
|
export const handleDisconnect = () => {
|
||||||
console.log("disconnected from room");
|
console.log("disconnected from room");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ import { PublicUser } from "@repo/db";
|
|||||||
import { dispatchSocket } from "dispatch/socket";
|
import { dispatchSocket } from "dispatch/socket";
|
||||||
import { serverApi } from "_helpers/axios";
|
import { serverApi } from "_helpers/axios";
|
||||||
import {
|
import {
|
||||||
handleActiveSpeakerChange,
|
|
||||||
handleDisconnect,
|
handleDisconnect,
|
||||||
handleLocalTrackUnpublished,
|
handleLocalTrackUnpublished,
|
||||||
handleTrackSubscribed,
|
handleTrackSubscribed,
|
||||||
handleTrackUnsubscribed,
|
handleTrackUnsubscribed,
|
||||||
} from "_helpers/liveKitEventHandler";
|
} from "_helpers/liveKitEventHandler";
|
||||||
import { ConnectionQuality, Room, RoomEvent } from "livekit-client";
|
import { ConnectionQuality, Participant, Room, RoomEvent } from "livekit-client";
|
||||||
import { pilotSocket } from "pilot/socket";
|
import { pilotSocket } from "pilot/socket";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
let interval: NodeJS.Timeout;
|
let interval: NodeJS.Timeout;
|
||||||
|
|
||||||
@@ -18,19 +18,22 @@ type TalkState = {
|
|||||||
micDeviceId: string | null;
|
micDeviceId: string | null;
|
||||||
micVolume: number;
|
micVolume: number;
|
||||||
isTalking: boolean;
|
isTalking: boolean;
|
||||||
source: string;
|
removeMessage: () => void;
|
||||||
state: "connecting" | "connected" | "disconnected" | "error";
|
state: "connecting" | "connected" | "disconnected" | "error";
|
||||||
message: string | null;
|
message: string | null;
|
||||||
connectionQuality: ConnectionQuality;
|
connectionQuality: ConnectionQuality;
|
||||||
remoteParticipants: number;
|
remoteParticipants: number;
|
||||||
toggleTalking: () => void;
|
toggleTalking: () => void;
|
||||||
setMic: (micDeviceId: string | null, volume: number) => void;
|
setMic: (micDeviceId: string | null, volume: number) => void;
|
||||||
connect: (roomName: string) => void;
|
connect: (roomName: string, role: string) => void;
|
||||||
disconnect: () => void;
|
disconnect: () => void;
|
||||||
|
speakingParticipants: Participant[];
|
||||||
|
addSpeakingParticipant: (participant: Participant) => void;
|
||||||
|
removeSpeakingParticipant: (speakingParticipants: Participant) => void;
|
||||||
room: Room | null;
|
room: Room | null;
|
||||||
};
|
};
|
||||||
const getToken = async (roomName: string) => {
|
const getToken = async (roomName: string) => {
|
||||||
const response = await serverApi.get(`/livekit/token?roomName=${roomName}`);
|
const response = await axios.get(`/api/livekit-token?roomName=${roomName}`);
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
return data.token;
|
return data.token;
|
||||||
};
|
};
|
||||||
@@ -39,12 +42,31 @@ export const useAudioStore = create<TalkState>((set, get) => ({
|
|||||||
isTalking: false,
|
isTalking: false,
|
||||||
message: null,
|
message: null,
|
||||||
micDeviceId: null,
|
micDeviceId: null,
|
||||||
|
speakingParticipants: [],
|
||||||
micVolume: 1,
|
micVolume: 1,
|
||||||
state: "disconnected",
|
state: "disconnected",
|
||||||
source: "",
|
source: "",
|
||||||
remoteParticipants: 0,
|
remoteParticipants: 0,
|
||||||
connectionQuality: ConnectionQuality.Unknown,
|
connectionQuality: ConnectionQuality.Unknown,
|
||||||
room: null,
|
room: null,
|
||||||
|
addSpeakingParticipant: (participant) => {
|
||||||
|
set((state) => {
|
||||||
|
if (!state.speakingParticipants.some((p) => p.identity === participant.identity)) {
|
||||||
|
return { speakingParticipants: [...state.speakingParticipants, participant] };
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
removeMessage: () => {
|
||||||
|
set({ message: null });
|
||||||
|
},
|
||||||
|
removeSpeakingParticipant: (participant) => {
|
||||||
|
set((state) => ({
|
||||||
|
speakingParticipants: state.speakingParticipants.filter(
|
||||||
|
(p) => !(p.identity === participant.identity),
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
},
|
||||||
setMic: (micDeviceId, micVolume) => {
|
setMic: (micDeviceId, micVolume) => {
|
||||||
set({ micDeviceId, micVolume });
|
set({ micDeviceId, micVolume });
|
||||||
},
|
},
|
||||||
@@ -71,9 +93,9 @@ export const useAudioStore = create<TalkState>((set, get) => ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
set((state) => ({ isTalking: !state.isTalking, source: "web-app" }));
|
set((state) => ({ isTalking: !state.isTalking }));
|
||||||
},
|
},
|
||||||
connect: async (roomName) => {
|
connect: async (roomName, role) => {
|
||||||
set({ state: "connecting" });
|
set({ state: "connecting" });
|
||||||
console.log("Connecting to room: ", roomName);
|
console.log("Connecting to room: ", roomName);
|
||||||
try {
|
try {
|
||||||
@@ -107,9 +129,11 @@ export const useAudioStore = create<TalkState>((set, get) => ({
|
|||||||
// Track events
|
// Track events
|
||||||
.on(RoomEvent.TrackSubscribed, handleTrackSubscribed)
|
.on(RoomEvent.TrackSubscribed, handleTrackSubscribed)
|
||||||
.on(RoomEvent.TrackUnsubscribed, handleTrackUnsubscribed)
|
.on(RoomEvent.TrackUnsubscribed, handleTrackUnsubscribed)
|
||||||
.on(RoomEvent.ActiveSpeakersChanged, handleActiveSpeakerChange)
|
|
||||||
.on(RoomEvent.LocalTrackUnpublished, handleLocalTrackUnpublished);
|
.on(RoomEvent.LocalTrackUnpublished, handleLocalTrackUnpublished);
|
||||||
await room.connect(url, token, {});
|
await room.connect(url, token, {});
|
||||||
|
room.localParticipant.setAttributes({
|
||||||
|
role,
|
||||||
|
});
|
||||||
set({ room });
|
set({ room });
|
||||||
|
|
||||||
interval = setInterval(() => {
|
interval = setInterval(() => {
|
||||||
@@ -137,14 +161,12 @@ interface PTTData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handlePTT = (data: PTTData) => {
|
const handlePTT = (data: PTTData) => {
|
||||||
console.log("PTT", data);
|
|
||||||
const { shouldTransmit, source } = data;
|
const { shouldTransmit, source } = data;
|
||||||
const { room } = useAudioStore.getState();
|
const { room } = useAudioStore.getState();
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
|
|
||||||
useAudioStore.setState({
|
useAudioStore.setState({
|
||||||
isTalking: shouldTransmit,
|
isTalking: shouldTransmit,
|
||||||
source,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (shouldTransmit) {
|
if (shouldTransmit) {
|
||||||
@@ -154,16 +176,19 @@ const handlePTT = (data: PTTData) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOtherPTT = (data: { publicUser: PublicUser; channel: string; source: string }) => {
|
const handleForceEndTransmission = ({ by }: { by?: string }) => {
|
||||||
const currentChannel = useAudioStore.getState().room?.name;
|
const { room } = useAudioStore.getState();
|
||||||
console.log("Other PTT", data);
|
|
||||||
if (data.channel === currentChannel)
|
if (!room) return;
|
||||||
|
room.localParticipant.setMicrophoneEnabled(false);
|
||||||
useAudioStore.setState({
|
useAudioStore.setState({
|
||||||
source: data.source,
|
isTalking: false,
|
||||||
|
message: `Ruf beendet durch ${by || "unknown"}`,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
pilotSocket.on("ptt", handlePTT);
|
pilotSocket.on("ptt", handlePTT);
|
||||||
pilotSocket.on("other-ptt", handleOtherPTT);
|
|
||||||
dispatchSocket.on("ptt", handlePTT);
|
dispatchSocket.on("ptt", handlePTT);
|
||||||
dispatchSocket.on("other-ptt", handleOtherPTT);
|
|
||||||
|
pilotSocket.on("force-end-transmission", handleForceEndTransmission);
|
||||||
|
dispatchSocket.on("force-end-transmission", handleForceEndTransmission);
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export const useDispatchConnectionStore = create<ConnectionStore>((set) => ({
|
|||||||
|
|
||||||
dispatchSocket.on("connect", () => {
|
dispatchSocket.on("connect", () => {
|
||||||
const { logoffTime, selectedZone } = useDispatchConnectionStore.getState();
|
const { logoffTime, selectedZone } = useDispatchConnectionStore.getState();
|
||||||
useAudioStore.getInitialState().connect("LST_01");
|
useAudioStore.getState().connect("LST_01", selectedZone || "Leitstelle");
|
||||||
dispatchSocket.emit("connect-dispatch", {
|
dispatchSocket.emit("connect-dispatch", {
|
||||||
logoffTime,
|
logoffTime,
|
||||||
selectedZone,
|
selectedZone,
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ pilotSocket.on("connect", () => {
|
|||||||
usePilotConnectionStore.setState({ status: "connected", message: "" });
|
usePilotConnectionStore.setState({ status: "connected", message: "" });
|
||||||
const { logoffTime, selectedStation } = usePilotConnectionStore.getState();
|
const { logoffTime, selectedStation } = usePilotConnectionStore.getState();
|
||||||
dispatchSocket.disconnect();
|
dispatchSocket.disconnect();
|
||||||
useAudioStore.getInitialState().connect("LST_01");
|
useAudioStore.getState().connect("LST_01", selectedStation?.bosCallsignShort || "pilot");
|
||||||
|
|
||||||
pilotSocket.emit("connect-pilot", {
|
pilotSocket.emit("connect-pilot", {
|
||||||
logoffTime,
|
logoffTime,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { getServerSession } from "api/auth/[...nextauth]/auth";
|
|||||||
import { ROOMS } from "_data/livekitRooms";
|
import { ROOMS } from "_data/livekitRooms";
|
||||||
import { AccessToken } from "livekit-server-sdk";
|
import { AccessToken } from "livekit-server-sdk";
|
||||||
import { NextRequest } from "next/server";
|
import { NextRequest } from "next/server";
|
||||||
import { prisma } from "@repo/db";
|
import { getPublicUser, prisma } from "@repo/db";
|
||||||
|
|
||||||
/* if (!process.env.LIVEKIT_API_KEY) throw new Error("LIVEKIT_API_KEY not set");
|
/* if (!process.env.LIVEKIT_API_KEY) throw new Error("LIVEKIT_API_KEY not set");
|
||||||
if (!process.env.LIVEKIT_API_SECRET)
|
if (!process.env.LIVEKIT_API_SECRET)
|
||||||
@@ -38,8 +38,13 @@ export const GET = async (request: NextRequest) => {
|
|||||||
roomJoin: true,
|
roomJoin: true,
|
||||||
canPublish: true,
|
canPublish: true,
|
||||||
canSubscribe: true,
|
canSubscribe: true,
|
||||||
|
canUpdateOwnMetadata: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
at.attributes = {
|
||||||
|
publicId: user.publicId,
|
||||||
|
};
|
||||||
|
|
||||||
const token = await at.toJwt();
|
const token = await at.toJwt();
|
||||||
|
|
||||||
return Response.json({ token });
|
return Response.json({ token });
|
||||||
@@ -349,6 +349,7 @@ export const MissionForm = () => {
|
|||||||
/>
|
/>
|
||||||
{form.watch("type") === "sekundär" && (
|
{form.watch("type") === "sekundär" && (
|
||||||
<input
|
<input
|
||||||
|
{...form.register("addressMissionLocation")}
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Zielkrankenhaus"
|
placeholder="Zielkrankenhaus"
|
||||||
className="input input-primary input-bordered w-full"
|
className="input input-primary input-bordered w-full"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ model ConnectedDispatcher {
|
|||||||
publicUser Json
|
publicUser Json
|
||||||
lastHeartbeat DateTime @default(now())
|
lastHeartbeat DateTime @default(now())
|
||||||
loginTime DateTime @default(now())
|
loginTime DateTime @default(now())
|
||||||
|
zone String @default("LST_1")
|
||||||
esimatedLogoutTime DateTime?
|
esimatedLogoutTime DateTime?
|
||||||
logoutTime DateTime?
|
logoutTime DateTime?
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ model Mission {
|
|||||||
addressCity String?
|
addressCity String?
|
||||||
addressZip String?
|
addressZip String?
|
||||||
addressAdditionalInfo String?
|
addressAdditionalInfo String?
|
||||||
|
addressMissionLocation String?
|
||||||
addressOSMways Json[] @default([])
|
addressOSMways Json[] @default([])
|
||||||
missionKeywordCategory String?
|
missionKeywordCategory String?
|
||||||
missionKeywordName String?
|
missionKeywordName String?
|
||||||
|
|||||||
Reference in New Issue
Block a user