Deploy Ladill Meet / deploy (push) Successful in 40s
Remove duplicate host record button on desktop and split invite-to-speak from make co-host so listeners must accept before speaking while co-host is applied immediately. Co-authored-by: Cursor <cursoragent@cursor.com>
182 lines
5.3 KiB
PHP
182 lines
5.3 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Meet;
|
|
|
|
use App\Models\Participant;
|
|
use App\Models\Room;
|
|
use App\Models\User;
|
|
|
|
class SpaceService
|
|
{
|
|
/**
|
|
* Resolve participant role for an audio-only room join.
|
|
*/
|
|
public function resolveJoinRole(Room $room, ?User $user, string $fallbackRole = 'guest'): string
|
|
{
|
|
if ($room->type !== 'space') {
|
|
return $fallbackRole;
|
|
}
|
|
|
|
if ($user && $user->ownerRef() === $room->host_user_ref) {
|
|
return 'host';
|
|
}
|
|
|
|
$speakerRefs = (array) $room->setting('speaker_refs', []);
|
|
|
|
if ($user && in_array($user->ownerRef(), $speakerRefs, true)) {
|
|
return 'panelist';
|
|
}
|
|
|
|
return 'attendee';
|
|
}
|
|
|
|
public function canPublish(Room $room, Participant $participant): bool
|
|
{
|
|
if ($room->type !== 'space') {
|
|
return true;
|
|
}
|
|
|
|
return in_array($participant->role, ['host', 'co_host', 'panelist'], true);
|
|
}
|
|
|
|
public function isListener(Room $room, Participant $participant): bool
|
|
{
|
|
return $room->type === 'space' && $participant->role === 'attendee';
|
|
}
|
|
|
|
public function isAudioOnly(Room $room): bool
|
|
{
|
|
return $room->isSpace() || (bool) $room->setting('audio_only', false);
|
|
}
|
|
|
|
/**
|
|
* Whether a live-room feature is available for this room type.
|
|
* Audio rooms (Spaces) mirror Twitter Spaces — no breakouts, chat, Q&A, etc.
|
|
*/
|
|
public function allowsRoomFeature(Room $room, string $feature): bool
|
|
{
|
|
if (! $room->isSpace()) {
|
|
return true;
|
|
}
|
|
|
|
return ! in_array($feature, [
|
|
'breakouts',
|
|
'chat',
|
|
'qa',
|
|
'polls',
|
|
'files',
|
|
'whiteboard',
|
|
'programme',
|
|
'live_stream',
|
|
'lock',
|
|
], true);
|
|
}
|
|
|
|
public function inviteToSpeak(Room $room, Participant $participant): Participant
|
|
{
|
|
abort_unless($room->isSpace(), 422);
|
|
abort_unless(in_array($participant->role, ['attendee', 'guest', 'participant'], true), 422);
|
|
abort_unless(! $participant->speak_requested, 422, 'An invitation is already pending.');
|
|
|
|
$participant->update([
|
|
'speak_requested' => true,
|
|
'hand_raised' => false,
|
|
]);
|
|
|
|
return $participant->fresh();
|
|
}
|
|
|
|
public function acceptSpeakInvitation(Room $room, Participant $participant): Participant
|
|
{
|
|
abort_unless($room->isSpace(), 422);
|
|
abort_unless(in_array($participant->role, ['attendee', 'guest', 'participant'], true), 422);
|
|
abort_unless($participant->speak_requested, 422, 'No speak invitation to accept.');
|
|
|
|
return $this->promoteToSpeaker($room, $participant);
|
|
}
|
|
|
|
public function declineSpeakInvitation(Room $room, Participant $participant): Participant
|
|
{
|
|
abort_unless($room->isSpace(), 422);
|
|
abort_unless($participant->speak_requested, 422);
|
|
|
|
$participant->update(['speak_requested' => false]);
|
|
|
|
return $participant->fresh();
|
|
}
|
|
|
|
public function dismissSpeakInvitation(Room $room, Participant $participant): Participant
|
|
{
|
|
abort_unless($room->isSpace(), 422);
|
|
abort_unless($participant->speak_requested, 422);
|
|
|
|
$participant->update(['speak_requested' => false]);
|
|
|
|
return $participant->fresh();
|
|
}
|
|
|
|
public function promoteToSpeaker(Room $room, Participant $participant): Participant
|
|
{
|
|
abort_unless($room->isSpace(), 422);
|
|
abort_unless(in_array($participant->role, ['attendee', 'guest', 'participant'], true), 422);
|
|
|
|
$participant->update([
|
|
'role' => 'panelist',
|
|
'hand_raised' => false,
|
|
'speak_requested' => false,
|
|
]);
|
|
|
|
if ($participant->user_ref) {
|
|
$speakerRefs = collect((array) $room->setting('speaker_refs', []))
|
|
->push($participant->user_ref)
|
|
->unique()
|
|
->values()
|
|
->all();
|
|
|
|
$room->update([
|
|
'settings' => array_merge($room->settings ?? [], [
|
|
'speaker_refs' => $speakerRefs,
|
|
]),
|
|
]);
|
|
}
|
|
|
|
return $participant->fresh();
|
|
}
|
|
|
|
public function promoteToCoHost(Room $room, Participant $participant): Participant
|
|
{
|
|
abort_unless($room->isSpace(), 422);
|
|
abort_unless($participant->role === 'panelist', 422);
|
|
|
|
$participant->update(['role' => 'co_host']);
|
|
|
|
return $participant->fresh();
|
|
}
|
|
|
|
public function demoteFromSpeaker(Room $room, Participant $participant): Participant
|
|
{
|
|
abort_unless($room->isSpace(), 422);
|
|
abort_unless(in_array($participant->role, ['panelist', 'co_host'], true), 422);
|
|
|
|
$participant->update([
|
|
'role' => 'attendee',
|
|
'speak_requested' => false,
|
|
]);
|
|
|
|
if ($participant->user_ref) {
|
|
$speakerRefs = collect((array) $room->setting('speaker_refs', []))
|
|
->reject(fn (string $ref) => $ref === $participant->user_ref)
|
|
->values()
|
|
->all();
|
|
|
|
$room->update([
|
|
'settings' => array_merge($room->settings ?? [], [
|
|
'speaker_refs' => $speakerRefs,
|
|
]),
|
|
]);
|
|
}
|
|
|
|
return $participant->fresh();
|
|
}
|
|
}
|