fixes #117
This commit is contained in:
@@ -25,6 +25,7 @@ import { useSounds } from "_components/Audio/useSounds";
|
|||||||
export const Audio = () => {
|
export const Audio = () => {
|
||||||
const {
|
const {
|
||||||
speakingParticipants,
|
speakingParticipants,
|
||||||
|
resetSpeakingParticipants,
|
||||||
isTalking,
|
isTalking,
|
||||||
toggleTalking,
|
toggleTalking,
|
||||||
transmitBlocked,
|
transmitBlocked,
|
||||||
@@ -104,7 +105,7 @@ export const Audio = () => {
|
|||||||
data-tip="Nachricht entfernen"
|
data-tip="Nachricht entfernen"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className={cn("btn btn-sm btn-ghost border-warning bg-transparent ")}
|
className={cn("btn btn-sm btn-ghost border-warning bg-transparent")}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
removeMessage();
|
removeMessage();
|
||||||
}}
|
}}
|
||||||
@@ -123,9 +124,9 @@ export const Audio = () => {
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className={cn(
|
className={cn(
|
||||||
"btn btn-sm btn-soft bg-transparent border",
|
"btn btn-sm btn-soft border bg-transparent",
|
||||||
canStopOtherSpeakers && speakingParticipants.length > 0 && "hover:bg-error",
|
canStopOtherSpeakers && speakingParticipants.length > 0 && "hover:bg-error",
|
||||||
speakingParticipants.length > 0 && " hover:bg-errorborder",
|
speakingParticipants.length > 0 && "hover:bg-errorborder",
|
||||||
isReceivingBlick && "border-warning",
|
isReceivingBlick && "border-warning",
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -133,6 +134,7 @@ export const Audio = () => {
|
|||||||
const payload = JSON.stringify({
|
const payload = JSON.stringify({
|
||||||
by: role,
|
by: role,
|
||||||
});
|
});
|
||||||
|
resetSpeakingParticipants("dich");
|
||||||
speakingParticipants.forEach(async (p) => {
|
speakingParticipants.forEach(async (p) => {
|
||||||
await room?.localParticipant.performRpc({
|
await room?.localParticipant.performRpc({
|
||||||
destinationIdentity: p.identity,
|
destinationIdentity: p.identity,
|
||||||
@@ -159,24 +161,24 @@ export const Audio = () => {
|
|||||||
transmitBlocked && "bg-yellow-500 hover:bg-yellow-500",
|
transmitBlocked && "bg-yellow-500 hover:bg-yellow-500",
|
||||||
state === "disconnected" && "bg-red-500 hover:bg-red-500",
|
state === "disconnected" && "bg-red-500 hover:bg-red-500",
|
||||||
state === "error" && "bg-red-500 hover:bg-red-500",
|
state === "error" && "bg-red-500 hover:bg-red-500",
|
||||||
state === "connecting" && "bg-yellow-500 hover:bg-yellow-500 cursor-default",
|
state === "connecting" && "cursor-default bg-yellow-500 hover:bg-yellow-500",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{state === "connected" && <Mic className="w-5 h-5" />}
|
{state === "connected" && <Mic className="h-5 w-5" />}
|
||||||
{state === "disconnected" && <WifiOff className="w-5 h-5" />}
|
{state === "disconnected" && <WifiOff className="h-5 w-5" />}
|
||||||
{state === "connecting" && <PlugZap className="w-5 h-5" />}
|
{state === "connecting" && <PlugZap className="h-5 w-5" />}
|
||||||
{state === "error" && <ServerCrash className="w-5 h-5" />}
|
{state === "error" && <ServerCrash className="h-5 w-5" />}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{state === "connected" && (
|
{state === "connected" && (
|
||||||
<details className="dropdown relative z-[1050]">
|
<details className="dropdown relative z-[1050]">
|
||||||
<summary className="dropdown btn btn-ghost flex items-center gap-1">
|
<summary className="dropdown btn btn-ghost flex items-center gap-1">
|
||||||
{connectionQuality === ConnectionQuality.Excellent && <Signal className="w-5 h-5" />}
|
{connectionQuality === ConnectionQuality.Excellent && <Signal className="h-5 w-5" />}
|
||||||
{connectionQuality === ConnectionQuality.Good && <SignalMedium className="w-5 h-5" />}
|
{connectionQuality === ConnectionQuality.Good && <SignalMedium className="h-5 w-5" />}
|
||||||
{connectionQuality === ConnectionQuality.Poor && <SignalLow className="w-5 h-5" />}
|
{connectionQuality === ConnectionQuality.Poor && <SignalLow className="h-5 w-5" />}
|
||||||
{connectionQuality === ConnectionQuality.Lost && <ZapOff className="w-5 h-5" />}
|
{connectionQuality === ConnectionQuality.Lost && <ZapOff className="h-5 w-5" />}
|
||||||
{connectionQuality === ConnectionQuality.Unknown && (
|
{connectionQuality === ConnectionQuality.Unknown && (
|
||||||
<ShieldQuestion className="w-5 h-5" />
|
<ShieldQuestion className="h-5 w-5" />
|
||||||
)}
|
)}
|
||||||
<div className="badge badge-sm badge-soft badge-success">{remoteParticipants}</div>
|
<div className="badge badge-sm badge-soft badge-success">{remoteParticipants}</div>
|
||||||
</summary>
|
</summary>
|
||||||
@@ -184,7 +186,7 @@ export const Audio = () => {
|
|||||||
{ROOMS.map((r) => (
|
{ROOMS.map((r) => (
|
||||||
<li key={r}>
|
<li key={r}>
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-ghost text-left flex items-center justify-start gap-2 relative"
|
className="btn btn-sm btn-ghost relative flex items-center justify-start gap-2 text-left"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!role) return;
|
if (!role) return;
|
||||||
if (selectedRoom === r) return;
|
if (selectedRoom === r) return;
|
||||||
@@ -193,7 +195,7 @@ export const Audio = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{room?.name === r && (
|
{room?.name === r && (
|
||||||
<Disc className="text-success text-sm absolute left-2" width={15} />
|
<Disc className="text-success absolute left-2 text-sm" width={15} />
|
||||||
)}
|
)}
|
||||||
<span className="flex-1 text-center">{r}</span>
|
<span className="flex-1 text-center">{r}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -201,12 +203,12 @@ export const Audio = () => {
|
|||||||
))}
|
))}
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-ghost text-left flex items-center justify-start gap-2 relative"
|
className="btn btn-sm btn-ghost relative flex items-center justify-start gap-2 text-left"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
disconnect();
|
disconnect();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<WifiOff className="text-error text-sm absolute left-2" width={15} />
|
<WifiOff className="text-error absolute left-2 text-sm" width={15} />
|
||||||
<span className="flex-1 text-center">Disconnect</span>
|
<span className="flex-1 text-center">Disconnect</span>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -25,28 +25,29 @@ import { usePilotConnectionStore } from "_store/pilot/connectionStore";
|
|||||||
let interval: NodeJS.Timeout;
|
let interval: NodeJS.Timeout;
|
||||||
|
|
||||||
type TalkState = {
|
type TalkState = {
|
||||||
|
addSpeakingParticipant: (participant: Participant) => void;
|
||||||
|
connect: (roomName: string, role: string) => void;
|
||||||
|
connectionQuality: ConnectionQuality;
|
||||||
|
disconnect: () => void;
|
||||||
|
isTalking: boolean;
|
||||||
|
localRadioTrack: LocalTrackPublication | undefined;
|
||||||
|
message: string | null;
|
||||||
|
removeMessage: () => void;
|
||||||
|
removeSpeakingParticipant: (speakingParticipants: Participant) => void;
|
||||||
|
remoteParticipants: number;
|
||||||
|
resetSpeakingParticipants: (source: string) => void;
|
||||||
|
room: Room | null;
|
||||||
|
setSettings: (settings: Partial<TalkState["settings"]>) => void;
|
||||||
settings: {
|
settings: {
|
||||||
micDeviceId: string | null;
|
micDeviceId: string | null;
|
||||||
micVolume: number;
|
micVolume: number;
|
||||||
radioVolume: number;
|
radioVolume: number;
|
||||||
dmeVolume: number;
|
dmeVolume: number;
|
||||||
};
|
};
|
||||||
isTalking: boolean;
|
|
||||||
transmitBlocked: boolean;
|
|
||||||
removeMessage: () => void;
|
|
||||||
state: "connecting" | "connected" | "disconnected" | "error";
|
|
||||||
message: string | null;
|
|
||||||
connectionQuality: ConnectionQuality;
|
|
||||||
remoteParticipants: number;
|
|
||||||
toggleTalking: () => void;
|
|
||||||
setSettings: (settings: Partial<TalkState["settings"]>) => void;
|
|
||||||
connect: (roomName: string, role: string) => void;
|
|
||||||
disconnect: () => void;
|
|
||||||
speakingParticipants: Participant[];
|
speakingParticipants: Participant[];
|
||||||
addSpeakingParticipant: (participant: Participant) => void;
|
state: "connecting" | "connected" | "disconnected" | "error";
|
||||||
removeSpeakingParticipant: (speakingParticipants: Participant) => void;
|
toggleTalking: () => void;
|
||||||
room: Room | null;
|
transmitBlocked: boolean;
|
||||||
localRadioTrack: LocalTrackPublication | undefined;
|
|
||||||
};
|
};
|
||||||
const getToken = async (roomName: string) => {
|
const getToken = async (roomName: string) => {
|
||||||
const response = await axios.get(`/api/livekit-token?roomName=${roomName}`);
|
const response = await axios.get(`/api/livekit-token?roomName=${roomName}`);
|
||||||
@@ -71,6 +72,15 @@ export const useAudioStore = create<TalkState>((set, get) => ({
|
|||||||
remoteParticipants: 0,
|
remoteParticipants: 0,
|
||||||
connectionQuality: ConnectionQuality.Unknown,
|
connectionQuality: ConnectionQuality.Unknown,
|
||||||
room: null,
|
room: null,
|
||||||
|
resetSpeakingParticipants: (source: string) => {
|
||||||
|
set({
|
||||||
|
speakingParticipants: [],
|
||||||
|
isTalking: false,
|
||||||
|
transmitBlocked: false,
|
||||||
|
message: `Ruf beendet durch ${source || "eine unsichtbare Macht"}`,
|
||||||
|
});
|
||||||
|
get().room?.localParticipant.setMicrophoneEnabled(false);
|
||||||
|
},
|
||||||
addSpeakingParticipant: (participant) => {
|
addSpeakingParticipant: (participant) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
if (!state.speakingParticipants.some((p) => p.identity === participant.identity)) {
|
if (!state.speakingParticipants.some((p) => p.identity === participant.identity)) {
|
||||||
@@ -201,10 +211,15 @@ export const useAudioStore = create<TalkState>((set, get) => ({
|
|||||||
|
|
||||||
set({ localRadioTrack: publishedTrack });
|
set({ localRadioTrack: publishedTrack });
|
||||||
|
|
||||||
set({ state: "connected", room, message: null });
|
set({ state: "connected", room, isTalking: false, message: null });
|
||||||
})
|
})
|
||||||
.on(RoomEvent.Disconnected, () => {
|
.on(RoomEvent.Disconnected, () => {
|
||||||
set({ state: "disconnected", speakingParticipants: [], transmitBlocked: false });
|
set({
|
||||||
|
state: "disconnected",
|
||||||
|
speakingParticipants: [],
|
||||||
|
transmitBlocked: false,
|
||||||
|
isTalking: false,
|
||||||
|
});
|
||||||
|
|
||||||
handleDisconnect();
|
handleDisconnect();
|
||||||
})
|
})
|
||||||
@@ -223,17 +238,22 @@ export const useAudioStore = create<TalkState>((set, get) => ({
|
|||||||
|
|
||||||
room.registerRpcMethod("force-mute", async (data: RpcInvocationData) => {
|
room.registerRpcMethod("force-mute", async (data: RpcInvocationData) => {
|
||||||
const { by } = JSON.parse(data.payload);
|
const { by } = JSON.parse(data.payload);
|
||||||
room.localParticipant.setMicrophoneEnabled(false);
|
get().resetSpeakingParticipants(by);
|
||||||
useAudioStore.setState({
|
return "OK";
|
||||||
isTalking: false,
|
|
||||||
message: `Ruf beendet durch ${by || "eine unsichtbare Macht"}`,
|
|
||||||
});
|
|
||||||
return `Hello, ${data.callerIdentity}!`;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
interval = setInterval(() => {
|
interval = setInterval(() => {
|
||||||
|
// Filter forgotten participants
|
||||||
|
const oldSpeakingParticipants = get().speakingParticipants;
|
||||||
|
const speakingParticipants = oldSpeakingParticipants.filter((oP) => {
|
||||||
|
return Array.from(room.remoteParticipants.values()).find(
|
||||||
|
(p) => p.identity === oP.identity,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
set({
|
set({
|
||||||
remoteParticipants: room.numParticipants === 0 ? 0 : room.numParticipants - 1, // Unreliable and delayed
|
remoteParticipants: room.numParticipants === 0 ? 0 : room.numParticipants - 1, // Unreliable and delayed
|
||||||
|
speakingParticipants,
|
||||||
});
|
});
|
||||||
}, 500);
|
}, 500);
|
||||||
} catch (error: Error | unknown) {
|
} catch (error: Error | unknown) {
|
||||||
|
|||||||
Reference in New Issue
Block a user