pull/424/merge
Linus Groschke 2026-04-27 17:54:42 +02:00 committed by GitHub
commit 93a2fd70cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 1195 additions and 1709 deletions

View File

@ -9,7 +9,7 @@
}
],
"require": {
"littleredbutton/bigbluebutton-api-php": "^4.0"
"littleredbutton/bigbluebutton-api-php": "^6.2"
},
"require-dev": {
"phpunit/phpunit": "^8.5 || ^9.3",

View File

@ -4,6 +4,7 @@ namespace OCA\BigBlueButton\BigBlueButton;
use BigBlueButton\BigBlueButton;
use BigBlueButton\Core\Record;
use BigBlueButton\Enum\Role;
use BigBlueButton\Parameters\CreateMeetingParameters;
use BigBlueButton\Parameters\DeleteRecordingsParameters;
use BigBlueButton\Parameters\GetRecordingsParameters;
@ -62,13 +63,10 @@ class API {
* @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);
$joinMeetingParams = new JoinMeetingParameters($room->uid, $displayname, $isModerator ? Role::MODERATOR : Role::VIEWER);
// ensure that float is not converted to a string in scientific notation
$joinMeetingParams->setCreateTime(sprintf("%.0f", $creationTime));
$joinMeetingParams->setJoinViaHtml5(true);
$joinMeetingParams->setCreateTime(intval(sprintf("%.0f", $creationTime)));
$joinMeetingParams->setRedirect(true);
// set the guest parameter for everyone but moderators to send all users to the waiting room if setting is selected
@ -130,8 +128,6 @@ class API {
private function buildMeetingParams(Room $room, ?Presentation $presentation = null): CreateMeetingParameters {
$createMeetingParams = new CreateMeetingParameters($room->uid, $room->name);
$createMeetingParams->setAttendeePW($room->attendeePassword);
$createMeetingParams->setModeratorPW($room->moderatorPassword);
$createMeetingParams->setRecord($room->record);
$createMeetingParams->setAllowStartStopRecording($room->record);
$createMeetingParams->setLogoutURL($this->urlGenerator->getBaseUrl());
@ -239,6 +235,16 @@ class API {
* @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 {
$formats = [];
foreach ($record->getPlaybackFormats() as $format) {
$formats[] = [
'type' => $format->getType(),
'length' => $format->getLength(),
'url' => $format->getUrl()
];
}
return [
'id' => $record->getRecordId(),
'meetingId' => $record->getMeetingId(),
@ -247,10 +253,8 @@ class API {
'state' => $record->getState(),
'startTime' => $record->getStartTime(),
'participants' => $record->getParticipantCount(),
'type' => $record->getPlaybackType(),
'length' => $record->getPlaybackLength(),
'url' => $record->getPlaybackUrl(),
'metas' => $record->getMetas(),
'formats' => $formats,
'metas' => $record->getMetas()
];
}

2837
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -65,12 +65,16 @@ export type Recording = {
state: 'processing' | 'processed' | 'published' | 'unpublished' | 'deleted';
startTime: number;
participants: number;
type: string;
length: number;
url: string;
formats: RecordingFormat[];
meta: any;
}
export type RecordingFormat = {
type: string
length: number
url: string
}
export interface ShareWithOption {
label: string;
value: {
@ -210,12 +214,12 @@ class Api {
return response.data;
}
public async storeRecording(recording: Recording, path: string) {
public async storeRecording(recording: Recording, format: RecordingFormat, path: string) {
const startDate = new Date(recording.startTime);
const filename = `${encodeURIComponent(recording.name + ' ' + startDate.toISOString().replace(/:/g, '-').substr(0,19))}.url`;
const url = OC.linkToRemote(`dav/files/${OC.currentUser}${path}/${filename}`);
await axios.put(url, `[InternetShortcut]\nURL=${recording.url}`);
await axios.put(url, `[InternetShortcut]\nURL=${format.url}`);
return filename;
}

View File

@ -1,16 +1,17 @@
import React from 'react';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import { Recording } from '../Common/Api';
import {Recording, RecordingFormat} from '../Common/Api';
type Props = {
recording: Recording;
format: RecordingFormat;
isAdmin : boolean;
deleteRecording: (recording: Recording) => void;
storeRecording: (recording: Recording) => void;
storeRecording: (recording: Recording, format: RecordingFormat) => void;
publishRecording: (recording: Recording, publish: boolean) => void;
}
const RecordingRow = ({recording, isAdmin, deleteRecording, storeRecording, publishRecording}: Props): JSX.Element => {
const RecordingRow = ({recording, format, isAdmin, deleteRecording, storeRecording, publishRecording}: Props): JSX.Element => {
function checkPublished(recording: Recording, onChange: (value: boolean) => void) {
return (
@ -28,19 +29,19 @@ const RecordingRow = ({recording, isAdmin, deleteRecording, storeRecording, publ
return (
<tr key={recording.id}>
<td className="start icon-col">
<a href={recording.url} className="action-item" target="_blank" rel="noopener noreferrer" title={t('bbb', 'Open recording')}>
<a href={format.url} className="action-item" target="_blank" rel="noopener noreferrer" title={t('bbb', 'Open recording')}>
<span className="icon icon-external icon-visible"></span>
</a>
</td>
<td className="share icon-col">
<CopyToClipboard text={recording.url} options={{format:'text/plain'}}>
<CopyToClipboard text={format.url} options={{format:'text/plain'}}>
<button className="action-item copy-to-clipboard" title={t('bbb', 'Copy to clipboard')}>
<span className="icon icon-clippy icon-visible" ></span>
</button>
</CopyToClipboard>
</td>
<td className="icon-col">
<button className="action-item" onClick={() => storeRecording(recording)} title={t('bbb', 'Save as file')}>
<button className="action-item" onClick={() => storeRecording(recording, format)} title={t('bbb', 'Save as file')}>
<span className="icon icon-add-shortcut icon-visible"></span>
</button>
</td>
@ -48,13 +49,13 @@ const RecordingRow = ({recording, isAdmin, deleteRecording, storeRecording, publ
{(new Date(recording.startTime)).toLocaleString()}
</td>
<td>
{recording.length === 0 ? '< 1 min' : (recording.length + ' min')}
{format.length === 0 ? '< 1 min' : (format.length + ' min')}
</td>
<td>
{n('bbb', '%n participant', '%n participants', recording.participants)}
</td>
<td>
{recording.type}
{format.type}
</td>
<td>
{isAdmin && checkPublished(recording, (checked) => {

View File

@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import { api, Recording, Room, Restriction, Access, Permission } from '../Common/Api';
import {api, Recording, Room, Restriction, Access, Permission, RecordingFormat} from '../Common/Api';
import EditRoom from './EditRoom';
import RecordingRow from './RecordingRow';
import EditableValue from './EditableValue';
@ -98,9 +98,9 @@ const RoomRow = (props: Props): JSX.Element => {
}, undefined, 'httpd/unix-directory');
}
function storeRecording(recording: Recording) {
function storeRecording(recording: Recording, format: RecordingFormat) {
OC.dialogs.filepicker(t('bbb', 'Select target folder'), (path: string) => {
api.storeRecording(recording, path).then((filename) => {
api.storeRecording(recording, format, path).then((filename) => {
OC.dialogs.info(
t('bbb', 'URL to presentation was stored in "{path}" as "{filename}".', { path: path + '/', filename }),
t('bbb', 'Link stored'),
@ -288,7 +288,7 @@ const RoomRow = (props: Props): JSX.Element => {
<td colSpan={11}>
<table>
<tbody>
{recordings?.sort((r1, r2) => r1.startTime - r2.startTime).map(recording => <RecordingRow key={recording.id} isAdmin={adminRoom} recording={recording} deleteRecording={deleteRecording} storeRecording={storeRecording} publishRecording={publishRecording} />)}
{recordings?.sort((r1, r2) => r1.startTime - r2.startTime).map(recording => recording.formats.map(format => <RecordingRow key={recording.id} isAdmin={adminRoom} recording={recording} format={format} deleteRecording={deleteRecording} storeRecording={storeRecording} publishRecording={publishRecording} />))}
</tbody>
</table>
</td>