config = $config; $this->urlGenerator = $urlGenerator; $this->crypto = $crypto; $this->eventDispatcher = $eventDispatcher; $this->l10n = $l10n; $this->urlHelper = $urlHelper; $this->defaults = $defaults; } private function getServer(): BigBlueButton { if (!$this->server) { $apiUrl = $this->config->getAppValue('bbb', 'api.url'); $secret = $this->config->getAppValue('bbb', 'api.secret'); $this->server = new BigBlueButton($apiUrl, $secret); } return $this->server; } /** * Create join url. * * @return string join url */ public function createJoinUrl(Room $room, float $creationTime, string $displayname, bool $isModerator, ?string $uid = null) { $password = $isModerator ? $room->moderatorPassword : $room->attendeePassword; $joinMeetingParams = new JoinMeetingParameters($room->uid, $displayname, $password); // ensure that float is not converted to a string in scientific notation $joinMeetingParams->setCreationTime(sprintf("%.0f", $creationTime)); $joinMeetingParams->setJoinViaHtml5(true); $joinMeetingParams->setRedirect(true); $joinMeetingParams->setGuest($uid === null); $joinMeetingParams->addUserData('bbb_listen_only_mode', $room->getListenOnly()); $joinMeetingParams->addUserData('bbb_skip_check_audio_on_first_join', !$room->getMediaCheck()); // 2.3 $joinMeetingParams->addUserData('bbb_skip_video_preview_on_first_join', !$room->getMediaCheck()); // 2.3 $joinMeetingParams->addUserData('bbb_auto_swap_layout', $room->getCleanLayout()); $joinMeetingParams->addUserData('bbb_show_participants_on_login', !$room->getCleanLayout()); $joinMeetingParams->addUserData('bbb_show_public_chat_on_login', !$room->getCleanLayout()); if ($this->config->getAppValue('bbb', 'join.theme') === 'true') { $primaryColor = $this->defaults->getColorPrimary(); $textColor = $this->defaults->getTextColorPrimary(); $joinMeetingParams->addUserData('bbb_custom_style', ":root{--nc-primary-color:$primaryColor;--nc-primary-text-color:$textColor;--nc-bg-color:#444;--color-primary:var(--nc-primary-color);--btn-primary-color:var(--nc-primary-text-color);--color-text:#222;--loader-bg:var(--nc-bg-color);--user-list-bg:#fff;--user-list-text:#222;--list-item-bg-hover:#f5f5f5;--item-focus-border:var(--nc-primary-color);--color-off-white:#fff;--color-gray-dark:var(--nc-bg-color);}body{background-color:var(--nc-bg-color);}.overlay--1aTlbi{background-color:var(--nc-bg-color);}.userlistPad--o5KDX{border-right: 1px solid #ededed;}.scrollStyle--Ckr4w{background: transparent;}.item--yl1AH:hover, .item--yl1AH:focus{color:--nc-primary-text-color;}#message-input:focus{box-shadow:0 0 0 1px var(--nc-primary-color);border-color:--nc-primary-color;}.active--Z1SuO2X{border-radius:5px;}"); } if ($uid) { $joinMeetingParams->setUserId($uid); $joinMeetingParams->setAvatarURL($this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => $uid, 'size' => 32])); } return $this->getServer()->getJoinMeetingURL($joinMeetingParams); } /** * Create meeting room. * * @return float|int creation time */ public function createMeeting(Room $room, Presentation $presentation = null) { $bbb = $this->getServer(); $meetingParams = $this->buildMeetingParams($room, $presentation); try { $response = $bbb->createMeeting($meetingParams); } catch (\Exception $e) { throw new \Exception('Can not process create request: ' . $bbb->getCreateMeetingUrl($meetingParams)); } if (!$response->success()) { throw new \Exception('Can not create meeting'); } if ($response->getMessageKey() !== 'duplicateWarning') { $this->eventDispatcher->dispatch(MeetingStartedEvent::class, new MeetingStartedEvent($room)); } return $response->getCreationTime(); } private function buildMeetingParams(Room $room, Presentation $presentation = null): CreateMeetingParameters { $createMeetingParams = new CreateMeetingParameters($room->uid, $room->name); $createMeetingParams->setAttendeePassword($room->attendeePassword); $createMeetingParams->setModeratorPassword($room->moderatorPassword); $createMeetingParams->setRecord($room->record); $createMeetingParams->setAllowStartStopRecording($room->record); $createMeetingParams->setLogoutUrl($this->urlGenerator->getBaseUrl()); $mac = $this->crypto->calculateHMAC($room->uid); $endMeetingUrl = $this->urlGenerator->linkToRouteAbsolute('bbb.hook.meetingEnded', ['token' => $room->uid, 'mac' => $mac]); $createMeetingParams->setEndCallbackUrl($endMeetingUrl); $recordingReadyUrl = $this->urlGenerator->linkToRouteAbsolute('bbb.hook.recordingReady', ['token' => $room->uid, 'mac' => $mac]); $createMeetingParams->setRecordingReadyCallbackUrl($recordingReadyUrl); $invitationUrl = $this->urlHelper->linkToInvitationAbsolute($room); $createMeetingParams->setModeratorOnlyMessage($this->l10n->t('To invite someone to the meeting, send them this link: %s', [$invitationUrl])); if (!empty($room->welcome)) { $createMeetingParams->setWelcomeMessage($room->welcome); } if ($room->maxParticipants > 0) { $createMeetingParams->setMaxParticipants($room->maxParticipants); } if ($presentation !== null && $presentation->isValid()) { /** @psalm-suppress InvalidArgument */ $createMeetingParams->addPresentation($presentation->getUrl(), null, $presentation->getFilename()); } if ($room->access === Room::ACCESS_WAITING_ROOM) { $createMeetingParams->setGuestPolicyAskModerator(); } return $createMeetingParams; } public function getRecording(string $recordId) { $recordingParams = new GetRecordingsParameters(); $recordingParams->setRecordId($recordId); $recordingParams->setState('any'); $response = $this->getServer()->getRecordings($recordingParams); if (!$response->success()) { throw new \Exception('Could not process get recording request'); } $records = $response->getRecords(); if (count($records) === 0) { throw new \Exception('Found no record with given id'); } return $this->recordToArray($records[0]); } public function getRecordings(Room $room): array { $recordingParams = new GetRecordingsParameters(); $recordingParams->setMeetingId($room->uid); $recordingParams->setState('processing,processed,published,unpublished'); $response = $this->getServer()->getRecordings($recordingParams); if (!$response->success()) { throw new \Exception('Could not process get recordings request'); } $records = $response->getRecords(); return array_map(function ($record) { return $this->recordToArray($record); }, $records); } public function deleteRecording(string $recordingId): bool { $deleteParams = new DeleteRecordingsParameters($recordingId); $response = $this->getServer()->deleteRecordings($deleteParams); return $response->isDeleted(); } /** * @return (array|bool|int|string)[] * * @psalm-return array{id: string, meetingId: string, name: string, published: bool, state: string, startTime: string, participants: int, type: string, length: string, url: string, metas: array} */ private function recordToArray(Record $record): array { return [ 'id' => $record->getRecordId(), 'meetingId' => $record->getMeetingId(), 'name' => $record->getName(), 'published' => $record->isPublished(), 'state' => $record->getState(), 'startTime' => $record->getStartTime(), 'participants' => $record->getParticipantCount(), 'type' => $record->getPlaybackType(), 'length' => $record->getPlaybackLength(), 'url' => $record->getPlaybackUrl(), 'metas' => $record->getMetas(), ]; } public function check(string $url, string $secret): string { $server = new BigBlueButton($url, $secret); $meetingParams = new IsMeetingRunningParameters('foobar'); try { $response = $server->isMeetingRunning($meetingParams); if (!$response->success() && !$response->failed()) { return 'invalid-url'; } if (!$response->success()) { return 'invalid-secret'; } return 'success'; } catch (\Exception $e) { return 'invalid-url'; } } /** * @param null|string $url */ public function getVersion(?string $url = null) { $server = $url === null ? $this->getServer() : new BigBlueButton($url, ''); return $server->getApiVersion()->getVersion(); } public function isRunning(Room $room): bool { $isMeetingRunningParams = new IsMeetingRunningParameters($room->getUid()); $response = $this->getServer()->isMeetingRunning($isMeetingRunningParams); return $response->success() && $response->isRunning(); } }