feat(livekit): close the room when a pod is deleted
DELETE /api/pods/:id now also calls closeRoom (RoomServiceClient.deleteRoom) to end the live LiveKit room and disconnect anyone connected. PodView listens for RoomEvent.Disconnected and returns the user to the pod list, so a deleted pod cleanly kicks everyone out instead of stranding them in a dead room. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,19 @@ function svc(): RoomServiceClient {
|
|||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close a pod's LiveKit room, disconnecting everyone. Best-effort: a missing/
|
||||||
|
* already-empty room is fine. Called when a pod is deleted.
|
||||||
|
*/
|
||||||
|
export async function closeRoom(podId: string): Promise<void> {
|
||||||
|
if (!isConfigured()) return;
|
||||||
|
try {
|
||||||
|
await svc().deleteRoom(podId);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`[livekit] closeRoom ${podId} failed: ${(e as Error).message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Who is currently connected in each pod's LiveKit room, as display names,
|
* Who is currently connected in each pod's LiveKit room, as display names,
|
||||||
* keyed by pod id (= room name). Empty rooms are omitted. Returns {} if
|
* keyed by pod id (= room name). Empty rooms are omitted. Returns {} if
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
removeMember,
|
removeMember,
|
||||||
seedDefaultPods,
|
seedDefaultPods,
|
||||||
} from './pods/store.js';
|
} from './pods/store.js';
|
||||||
import { getPresence } from './livekit/rooms.js';
|
import { getPresence, closeRoom } from './livekit/rooms.js';
|
||||||
import type { InterventionOutcome } from '@podman/shared';
|
import type { InterventionOutcome } from '@podman/shared';
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
@@ -121,6 +121,7 @@ app.patch('/api/pods/:id', async (req, res) => {
|
|||||||
app.delete('/api/pods/:id', async (req, res) => {
|
app.delete('/api/pods/:id', async (req, res) => {
|
||||||
const ok = await deletePod(req.params.id);
|
const ok = await deletePod(req.params.id);
|
||||||
if (!ok) return res.status(404).json({ error: 'pod not found' });
|
if (!ok) return res.status(404).json({ error: 'pod not found' });
|
||||||
|
await closeRoom(req.params.id); // end the live LiveKit room too (kicks anyone connected)
|
||||||
res.json({ ok: true });
|
res.json({ ok: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ export function PodView({
|
|||||||
const audioRef = useRef<HTMLDivElement>(null);
|
const audioRef = useRef<HTMLDivElement>(null);
|
||||||
const beatRef = useRef<BeatHandle | null>(null);
|
const beatRef = useRef<BeatHandle | null>(null);
|
||||||
const screenTrackRef = useRef<MediaStreamTrack | null>(null);
|
const screenTrackRef = useRef<MediaStreamTrack | null>(null);
|
||||||
|
const onLeaveRef = useRef(onLeave);
|
||||||
|
onLeaveRef.current = onLeave;
|
||||||
|
|
||||||
// Subscribe to live room state: participants, active speakers, remote audio.
|
// Subscribe to live room state: participants, active speakers, remote audio.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -63,13 +65,16 @@ export function PodView({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const onAudioGone = (track: RemoteTrack) => track.detach().forEach((el) => el.remove());
|
const onAudioGone = (track: RemoteTrack) => track.detach().forEach((el) => el.remove());
|
||||||
|
// Room closed out from under us (e.g. the pod was deleted) → back to the list.
|
||||||
|
const onDisconnected = () => onLeaveRef.current();
|
||||||
|
|
||||||
room
|
room
|
||||||
.on(RoomEvent.ParticipantConnected, refresh)
|
.on(RoomEvent.ParticipantConnected, refresh)
|
||||||
.on(RoomEvent.ParticipantDisconnected, refresh)
|
.on(RoomEvent.ParticipantDisconnected, refresh)
|
||||||
.on(RoomEvent.ActiveSpeakersChanged, refresh)
|
.on(RoomEvent.ActiveSpeakersChanged, refresh)
|
||||||
.on(RoomEvent.TrackSubscribed, onAudio)
|
.on(RoomEvent.TrackSubscribed, onAudio)
|
||||||
.on(RoomEvent.TrackUnsubscribed, onAudioGone);
|
.on(RoomEvent.TrackUnsubscribed, onAudioGone)
|
||||||
|
.on(RoomEvent.Disconnected, onDisconnected);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
room
|
room
|
||||||
@@ -77,7 +82,8 @@ export function PodView({
|
|||||||
.off(RoomEvent.ParticipantDisconnected, refresh)
|
.off(RoomEvent.ParticipantDisconnected, refresh)
|
||||||
.off(RoomEvent.ActiveSpeakersChanged, refresh)
|
.off(RoomEvent.ActiveSpeakersChanged, refresh)
|
||||||
.off(RoomEvent.TrackSubscribed, onAudio)
|
.off(RoomEvent.TrackSubscribed, onAudio)
|
||||||
.off(RoomEvent.TrackUnsubscribed, onAudioGone);
|
.off(RoomEvent.TrackUnsubscribed, onAudioGone)
|
||||||
|
.off(RoomEvent.Disconnected, onDisconnected);
|
||||||
};
|
};
|
||||||
}, [room, me]);
|
}, [room, me]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user