activeSession(); if ($existing) { return $existing; } app(LicenseService::class)->assertCanStartSession($room->organization, $room->branch_id); $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() ->where('status', 'joined') ->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, ): Participant { $userRef = $user?->ownerRef(); $existing = $session->participants() ->when($userRef, fn ($q) => $q->where('user_ref', $userRef)) ->when(! $userRef && $email, fn ($q) => $q->where('email', $email)) ->whereIn('status', ['invited', 'waiting', 'joined']) ->first(); if ($existing) { $existing->update([ 'status' => 'joined', 'joined_at' => now(), 'left_at' => null, 'display_name' => $displayName ?? $existing->display_name, 'ip_address' => request()?->ip(), ]); return $existing; } $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' => 'joined', 'joined_at' => now(), 'ip_address' => request()?->ip(), ]); $this->updatePeakParticipants($session); AuditLogger::record( $session->owner_ref, 'participant.joined', $session->room->organization_id, $userRef, Participant::class, $participant->id, ); return $participant; } 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); $mediaRoom = $breakoutService->mediaRoomForParticipant($participant); $canPublish = $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]); } } }