Implement conference breakouts with meeting mode and programme lineup UI.
Deploy Ladill Meet / deploy (push) Successful in 1m3s

Attendees reconnect to breakout rooms with publish access while hosts stay on stage; poll and media-token endpoints drive LiveKit reconnection, and the live room shows Events-linked programme when available.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-02 00:06:45 +00:00
co-authored by Cursor
parent e698e75f17
commit fa9b4138af
8 changed files with 545 additions and 22 deletions
+52 -12
View File
@@ -263,30 +263,70 @@ class SessionService
public function generateMediaToken(Participant $participant): string
{
$session = $participant->session;
$room = $session->room;
$isHost = $participant->isHost();
$breakoutService = app(BreakoutService::class);
$webinarService = app(WebinarService::class);
$spaceService = app(\App\Services\Meet\SpaceService::class);
$mediaRoom = $breakoutService->mediaRoomForParticipant($participant);
$canPublish = ($room->isSpace()
? $spaceService->canPublish($room, $participant)
: $webinarService->canPublish($room, $participant)) && $participant->canPublishMedia();
$participant->loadMissing('breakoutRoom');
$state = $this->mediaSessionState($participant);
$mediaRoom = app(BreakoutService::class)->mediaRoomForParticipant($participant);
return $this->media->generateToken(
$mediaRoom,
$participant->uuid,
$participant->display_name,
[
'can_publish' => $canPublish,
'can_publish' => $state['can_publish'],
'can_subscribe' => true,
'can_publish_data' => true,
'room_admin' => $isHost,
'room_admin' => $participant->isHost(),
],
);
}
/** @return array{can_publish: bool, uses_stage_layout: bool, in_breakout: bool, breakout_room_id: ?int, breakout: ?array{uuid: string, name: string, closes_at: ?string}} */
public function mediaSessionState(Participant $participant): array
{
$participant->loadMissing(['breakoutRoom', 'session.room']);
$room = $participant->session->room;
$breakoutService = app(BreakoutService::class);
$inBreakout = $breakoutService->isInActiveBreakout($participant);
$usesStageLayout = app(StageLayoutService::class)->usesStageLayout($room) && ! $inBreakout;
$breakout = null;
if ($inBreakout && $participant->breakoutRoom) {
$breakout = [
'uuid' => $participant->breakoutRoom->uuid,
'name' => $participant->breakoutRoom->name,
'closes_at' => $participant->breakoutRoom->closes_at?->toIso8601String(),
];
}
return [
'can_publish' => $this->canParticipantPublish($participant, $inBreakout),
'uses_stage_layout' => $usesStageLayout,
'in_breakout' => $inBreakout,
'breakout_room_id' => $inBreakout ? $participant->breakout_room_id : null,
'breakout' => $breakout,
];
}
public function canParticipantPublish(Participant $participant, ?bool $inBreakout = null): bool
{
$breakoutService = app(BreakoutService::class);
$inBreakout ??= $breakoutService->isInActiveBreakout($participant);
if ($inBreakout) {
return true;
}
$room = $participant->session->room;
if ($room->isSpace()) {
return app(\App\Services\Meet\SpaceService::class)->canPublish($room, $participant)
&& $participant->canPublishMedia();
}
return app(WebinarService::class)->canPublish($room, $participant)
&& $participant->canPublishMedia();
}
public function getOrStartSession(Room $room, User $host): Session
{
if ($room->activeSession()) {