Skip to content

Video Rooms

Planner includes video conferencing powered by LiveKit SFU (Selective Forwarding Unit).

Architecture

  • LiveKit Server — Docker service (ports 7880 HTTP, 7881 WS, 3478 TURN, 50000-60000 UDP media)
  • Backendlivekit-server-sdk for room management and token generation
  • Frontendlivekit-client (WebRTC) for connecting to LiveKit SFU
  • WebSocket/video namespace for signaling relay

Room Types

TypeDescription
instantImmediate call + participant notifications
scheduledScheduled conference + calendar event + reminders

API Endpoints

MethodPathDescription
GET/api/video-roomsList rooms (filter: active/scheduled/ended)
POST/api/video-roomsCreate room
GET/api/video-rooms/:idRoom details
PATCH/api/video-rooms/:idUpdate room
DELETE/api/video-rooms/:idEnd/delete room
POST/api/video-rooms/:id/joinJoin (returns LiveKit token)
POST/api/video-rooms/:id/leaveLeave room
GET/api/video-rooms/:id/participantsList participants
POST/api/video-rooms/:id/tokenGet LiveKit token
POST/api/video-rooms/:id/recording/startStart recording
POST/api/video-rooms/:id/recording/stopStop recording
GET/api/video-rooms/:id/recording/statusRecording status

WebSocket Events (/video)

Client → Server

EventDescription
joinJoin room (room:<id>)
leaveLeave room
signalWebRTC signaling relay

Server → Client

EventDescription
video:participant_joinedParticipant connected
video:participant_leftParticipant disconnected
video:room_updatedRoom updated
video:call_startedInstant call started
video:recording_startedRecording started
video:recording_stoppedRecording stopped

Frontend

Video Chat Store

typescript
const store = useVideoChatStore()
const { rooms, currentRoom, participants } = storeToRefs(store)

await store.fetchRooms()
await store.createRoom({ type, name, scheduledAt? })
await store.joinRoom(roomId)  // gets LiveKit token
await store.leaveRoom(roomId)
await store.endRoom(roomId)

Composables

typescript
const {
  isConnected, remoteParticipants,
  isMuted, isScreenSharing, isRecording, isPiP
} = useLiveKit()

await liveKit.connect(tokenData)  // connect to LiveKit
liveKit.toggleMute('audio')      // toggle microphone
liveKit.toggleMute('video')      // toggle camera
liveKit.toggleScreenShare()      // screen sharing
liveKit.startRecording(roomId)   // start recording
liveKit.enterPiP()               // Picture-in-Picture

Components

  • VideoRoomsView.vue (/video-rooms) — hub with tabs: Active, Scheduled, Past
  • VideoRoomView.vue (/video-rooms/:id) — active room: VideoGrid + VideoControls + participants sidebar + invite link copy
  • VideoGrid — participant video grid
  • ParticipantTile — single participant tile
  • VideoControls — control panel (mute, camera, screen share, recording, leave)
  • ScheduleVideoDialog — scheduled room creation dialog
  • VideoRoomCard — room card in list

Incoming Call Dialog

When an instant video call is started, participants receive an IncomingCallDialog.vue popup:

  • Displays caller name and room name
  • Accept → joins the room and connects to LiveKit
  • Decline → sends a decline notification
  • Auto-dismisses after 30 seconds

Key Implementation Details

  • Scheduled rooms create a CalendarEvent with type video
  • Reminders are sent 15 minutes before start (cron every 5 minutes)
  • Picture-in-Picture auto-enables on tab hide
  • Orphaned LiveKit rooms are cleaned up on module init