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>
138 lines
4.5 KiB
PHP
138 lines
4.5 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Meet;
|
|
|
|
use App\Models\BreakoutAssignment;
|
|
use App\Models\BreakoutRoom;
|
|
use App\Models\Message;
|
|
use App\Models\Participant;
|
|
use App\Models\Session;
|
|
use App\Services\Meet\Media\MediaProviderInterface;
|
|
use Illuminate\Support\Str;
|
|
|
|
class BreakoutService
|
|
{
|
|
public function __construct(
|
|
protected MediaProviderInterface $media,
|
|
) {}
|
|
|
|
/**
|
|
* @param array<int, array{participant_id: int, breakout_room_id?: int}>|null $assignments
|
|
* @return \Illuminate\Support\Collection<int, BreakoutRoom>
|
|
*/
|
|
public function createRooms(Session $session, int $count, ?int $timerMinutes = null, ?array $assignments = null)
|
|
{
|
|
$timerMinutes ??= config('meet.breakouts.default_timer_minutes', 15);
|
|
$closesAt = now()->addMinutes($timerMinutes);
|
|
$rooms = collect();
|
|
|
|
for ($i = 1; $i <= $count; $i++) {
|
|
$mediaName = $session->media_room_name.'-breakout-'.Str::random(8);
|
|
$this->media->createRoom($mediaName);
|
|
|
|
$rooms->push(BreakoutRoom::create([
|
|
'owner_ref' => $session->owner_ref,
|
|
'session_id' => $session->id,
|
|
'name' => 'Breakout '.$i,
|
|
'media_room_name' => $mediaName,
|
|
'status' => 'open',
|
|
'closes_at' => $closesAt,
|
|
]));
|
|
}
|
|
|
|
$participants = $session->participants()
|
|
->where('status', 'joined')
|
|
->whereNotIn('role', ['host', 'co_host'])
|
|
->get();
|
|
|
|
if ($assignments) {
|
|
foreach ($assignments as $assignment) {
|
|
$this->assignParticipant(
|
|
$rooms->firstWhere('id', $assignment['breakout_room_id']) ?? $rooms->first(),
|
|
Participant::findOrFail($assignment['participant_id']),
|
|
);
|
|
}
|
|
} else {
|
|
$participants->values()->each(function (Participant $participant, int $index) use ($rooms) {
|
|
$this->assignParticipant($rooms[$index % $rooms->count()], $participant);
|
|
});
|
|
}
|
|
|
|
return $rooms;
|
|
}
|
|
|
|
public function assignParticipant(BreakoutRoom $breakout, Participant $participant): void
|
|
{
|
|
BreakoutAssignment::updateOrCreate(
|
|
['breakout_room_id' => $breakout->id, 'participant_id' => $participant->id],
|
|
);
|
|
$participant->update(['breakout_room_id' => $breakout->id]);
|
|
}
|
|
|
|
public function moveParticipant(Participant $participant, BreakoutRoom $breakout): void
|
|
{
|
|
$this->assignParticipant($breakout, $participant);
|
|
}
|
|
|
|
public function returnToMain(Participant $participant): void
|
|
{
|
|
$participant->update(['breakout_room_id' => null]);
|
|
BreakoutAssignment::where('participant_id', $participant->id)->delete();
|
|
}
|
|
|
|
public function broadcast(Session $session, string $message, string $senderName): Message
|
|
{
|
|
return Message::create([
|
|
'owner_ref' => $session->owner_ref,
|
|
'session_id' => $session->id,
|
|
'sender_name' => $senderName,
|
|
'type' => 'breakout_broadcast',
|
|
'body' => $message,
|
|
]);
|
|
}
|
|
|
|
public function closeAll(Session $session): void
|
|
{
|
|
$session->breakoutRooms()->update(['status' => 'closed']);
|
|
$session->participants()->whereNotNull('breakout_room_id')->update(['breakout_room_id' => null]);
|
|
BreakoutAssignment::whereIn('breakout_room_id', $session->breakoutRooms()->pluck('id'))->delete();
|
|
}
|
|
|
|
public function hasActiveBreakouts(Session $session): bool
|
|
{
|
|
return $session->breakoutRooms()->where('status', 'open')->exists();
|
|
}
|
|
|
|
public function closeExpired(Session $session): bool
|
|
{
|
|
if (! $session->breakoutRooms()->where('status', 'open')->where('closes_at', '<=', now())->exists()) {
|
|
return false;
|
|
}
|
|
|
|
$this->closeAll($session);
|
|
|
|
return true;
|
|
}
|
|
|
|
public function isInActiveBreakout(Participant $participant): bool
|
|
{
|
|
if (! $participant->breakout_room_id) {
|
|
return false;
|
|
}
|
|
|
|
return BreakoutRoom::query()
|
|
->where('id', $participant->breakout_room_id)
|
|
->where('status', 'open')
|
|
->exists();
|
|
}
|
|
|
|
public function mediaRoomForParticipant(Participant $participant): string
|
|
{
|
|
if ($participant->breakout_room_id) {
|
|
return $participant->breakoutRoom?->media_room_name ?? $participant->session->media_room_name;
|
|
}
|
|
|
|
return $participant->session->media_room_name;
|
|
}
|
|
}
|