Files
ladill-meet/app/Services/Meet/SessionService.php
T
isaaccladandCursor 712dc487c6
Deploy Ladill Meet / deploy (push) Successful in 34s
Fix green room Go live and Panels hidden for conference hosts.
Stop registerPresenter from demoting hosts to panelists, recognize room owners as managers, and show stage controls in the green room banner.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-01 22:54:15 +00:00

323 lines
9.9 KiB
PHP

<?php
namespace App\Services\Meet;
use App\Models\Participant;
use App\Models\Room;
use App\Models\Session;
use App\Models\User;
use App\Services\Meet\Media\MediaProviderInterface;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class SessionService
{
public function __construct(
protected MediaProviderInterface $media,
protected RoomService $rooms,
) {}
public function start(Room $room, User $host): Session
{
$existing = $room->activeSession();
if ($existing) {
return $existing;
}
app(LicenseService::class)->assertCanStartSession($room->organization, $room->branch_id, $room);
$session = Session::create([
'owner_ref' => $room->owner_ref,
'room_id' => $room->id,
'media_room_name' => $this->rooms->mediaRoomName($room),
'status' => 'live',
'started_at' => now(),
]);
$room->update(['status' => 'live']);
$this->media->createRoom($session->media_room_name);
$this->joinParticipant($session, $host, 'host');
if ($room->setting('auto_record', false)) {
app(RecordingService::class)->start($session, $host);
}
app(MeetNotificationService::class)->meetingStarted($session, $host);
app(\App\Services\Integrations\WebhookDispatcher::class)->meetingStarted($session);
AuditLogger::record($room->owner_ref, 'session.started', $room->organization_id, $host->ownerRef(), Session::class, $session->id);
return $session;
}
public function end(Session $session, string $actorRef): Session
{
$session->update([
'status' => 'ended',
'ended_at' => now(),
]);
$session->room->update(['status' => 'ended']);
$session->participants()
->whereIn('status', ['joined', 'waiting'])
->update(['status' => 'left', 'left_at' => now()]);
foreach ($session->recordings()->where('status', 'recording')->get() as $recording) {
app(RecordingService::class)->stop($recording, $actorRef);
}
if ($session->room->isRecurring()) {
app(RecurringMeetingService::class)->spawnNextOccurrences($session->room, 1);
}
AuditLogger::record($session->owner_ref, 'session.ended', $session->room->organization_id, $actorRef, Session::class, $session->id);
app(\App\Services\Integrations\WebhookDispatcher::class)->meetingEnded($session);
app(UsageMeteringService::class)->recordSessionUsage($session);
return $session;
}
public function joinParticipant(
Session $session,
?User $user,
string $role,
?string $displayName = null,
?string $email = null,
string $status = 'joined',
): Participant {
$userRef = $user?->ownerRef();
$existing = $this->findExistingParticipant($session, $userRef, $email);
if ($existing) {
$updates = [
'left_at' => null,
'display_name' => $displayName ?? $existing->display_name,
'ip_address' => request()?->ip(),
];
if ($existing->status === 'waiting') {
$updates['status'] = 'waiting';
} else {
$updates['status'] = 'joined';
$updates['joined_at'] = $existing->joined_at ?? now();
}
if (in_array($role, ['host', 'co_host'], true)) {
$updates['role'] = $role;
} elseif ($existing->role === 'guest' && $role !== 'guest') {
$updates['role'] = $role;
}
$existing->update($updates);
return $existing;
}
$joinedAt = $status === 'joined' ? now() : null;
$participant = Participant::create([
'owner_ref' => $session->owner_ref,
'session_id' => $session->id,
'user_ref' => $userRef,
'display_name' => $displayName ?? $user?->name ?? 'Guest',
'email' => $email,
'role' => $role,
'status' => $status,
'joined_at' => $joinedAt,
'ip_address' => request()?->ip(),
]);
if ($status === 'joined') {
$this->updatePeakParticipants($session);
}
if ($status === 'joined') {
AuditLogger::record(
$session->owner_ref,
'participant.joined',
$session->room->organization_id,
$userRef,
Participant::class,
$participant->id,
);
}
return $participant;
}
public function admitParticipant(Participant $participant): Participant
{
abort_unless($participant->status === 'waiting', 422);
$participant->update([
'status' => 'joined',
'joined_at' => now(),
'left_at' => null,
]);
$this->updatePeakParticipants($participant->session);
AuditLogger::record(
$participant->owner_ref,
'participant.joined',
$participant->session->room->organization_id,
$participant->user_ref,
Participant::class,
$participant->id,
);
return $participant->fresh();
}
public function denyParticipant(Participant $participant): Participant
{
abort_unless($participant->status === 'waiting', 422);
$participant->update([
'status' => 'removed',
'left_at' => now(),
]);
AuditLogger::record(
$participant->owner_ref,
'participant.removed',
$participant->session->room->organization_id,
$participant->user_ref,
Participant::class,
$participant->id,
);
return $participant->fresh();
}
protected function findExistingParticipant(Session $session, ?string $userRef, ?string $email): ?Participant
{
$active = fn ($q) => $q->whereIn('status', ['invited', 'waiting', 'joined']);
if ($userRef) {
return $session->participants()
->where('user_ref', $userRef)
->where($active)
->first();
}
if ($email) {
return $session->participants()
->whereNull('user_ref')
->where('email', $email)
->where($active)
->first();
}
$guestUuid = (string) request()?->session()?->get("meet.participant.{$session->uuid}", '');
if ($guestUuid === '') {
return null;
}
return $session->participants()
->where('uuid', $guestUuid)
->whereNull('user_ref')
->where($active)
->first();
}
public function rememberParticipantSession(Request $request, Participant $participant, ?Session $session = null): void
{
$session ??= $participant->session;
$request->session()->put("meet.participant.{$session->uuid}", $participant->uuid);
}
public function joinedParticipantForUser(Session $session, User $user): ?Participant
{
return $session->participants()
->where('user_ref', $user->ownerRef())
->whereIn('status', ['invited', 'waiting', 'joined'])
->first();
}
public function leaveParticipant(Participant $participant): Participant
{
$participant->update([
'status' => 'left',
'left_at' => now(),
'hand_raised' => false,
]);
AuditLogger::record(
$participant->owner_ref,
'participant.left',
$participant->session->room->organization_id,
$participant->user_ref,
Participant::class,
$participant->id,
);
return $participant;
}
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();
return $this->media->generateToken(
$mediaRoom,
$participant->uuid,
$participant->display_name,
[
'can_publish' => $canPublish,
'can_subscribe' => true,
'can_publish_data' => true,
'room_admin' => $isHost,
],
);
}
public function getOrStartSession(Room $room, User $host): Session
{
if ($room->activeSession()) {
return $room->activeSession();
}
return $this->start($room, $host);
}
public function lock(Session $session, string $actorRef): Session
{
$session->update(['is_locked' => true, 'locked_at' => now()]);
AuditLogger::record($session->owner_ref, 'meeting.locked', $session->room->organization_id, $actorRef, Session::class, $session->id);
return $session;
}
public function unlock(Session $session, string $actorRef): Session
{
$session->update(['is_locked' => false, 'locked_at' => null]);
AuditLogger::record($session->owner_ref, 'meeting.unlocked', $session->room->organization_id, $actorRef, Session::class, $session->id);
return $session;
}
protected function updatePeakParticipants(Session $session): void
{
$count = $session->participants()->where('status', 'joined')->count();
if ($count > $session->peak_participants) {
$session->update(['peak_participants' => $count]);
}
}
}