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
@@ -7,6 +7,7 @@ use App\Models\Participant;
use App\Models\Recording;
use App\Models\Room;
use App\Models\Session;
use App\Services\Meet\BreakoutService;
use App\Services\Meet\ChatService;
use App\Services\Meet\Media\MediaProviderInterface;
use App\Services\Meet\ParticipantPresenter;
@@ -69,6 +70,8 @@ class MeetingRoomController extends Controller
$authUser = $request->user();
$isRoomHost = $authUser && $authUser->ownerRef() === $room->host_user_ref;
$canManageConference = $participant->isHost() || $isRoomHost;
$breakoutService = app(BreakoutService::class);
$inBreakout = $breakoutService->isInActiveBreakout($participant);
return view('meet.room.show', [
'session' => $session,
@@ -87,13 +90,14 @@ class MeetingRoomController extends Controller
'presenterRefs' => (array) $session->presenter_refs,
'isAudioOnly' => $isAudioOnly,
'isSpace' => $room->isSpace(),
'canPublish' => $room->isSpace()
? $spaces->canPublish($room, $participant)
: $webinars->canPublish($room, $participant),
'canPublish' => $this->sessions->canParticipantPublish($participant),
'isAttendee' => $participant->isAttendee() || $spaces->isListener($room, $participant),
'usesStageLayout' => $stageLayout->usesStageLayout($room),
'usesStageLayout' => $stageLayout->usesStageLayout($room) && ! $inBreakout,
'inBreakout' => $inBreakout,
'stageConfig' => $stageLayout->config($room),
'canManageConference' => $canManageConference,
'programmeSnapshot' => $room->setting('programme_snapshot'),
'linkedProgrammeItems' => (array) $room->setting('linked_programme_items', []),
]);
}
@@ -229,7 +233,11 @@ class MeetingRoomController extends Controller
]);
}
$this->currentParticipant($request, $session);
$participant = $this->currentParticipant($request, $session);
app(BreakoutService::class)->closeExpired($session);
$participant->refresh();
$session->load(['participants' => fn ($q) => $q->whereIn('status', ['joined', 'waiting'])]);
@@ -295,9 +303,24 @@ class MeetingRoomController extends Controller
'original_name' => $f->original_name,
'file_size' => $f->file_size,
]),
'media' => $this->sessions->mediaSessionState($participant),
'breakouts_active' => app(BreakoutService::class)->hasActiveBreakouts($session),
], app(StageLayoutService::class)->config($session->room)));
}
public function mediaToken(Request $request, Session $session): JsonResponse
{
abort_unless($session->isLive(), 404);
$participant = $this->currentParticipant($request, $session);
return response()->json(array_merge(
['token' => $this->sessions->generateMediaToken($participant)],
$this->sessions->mediaSessionState($participant->fresh()),
['breakouts_active' => app(BreakoutService::class)->hasActiveBreakouts($session)],
));
}
public function startRecording(Request $request, Session $session): JsonResponse
{
$participant = $this->currentParticipant($request, $session);