mirror of https://github.com/sualko/cloud_bbb
Merge b1369aa02c into f9456b2830
commit
93a2fd70cc
|
|
@ -9,7 +9,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"littleredbutton/bigbluebutton-api-php": "^4.0"
|
"littleredbutton/bigbluebutton-api-php": "^6.2"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^8.5 || ^9.3",
|
"phpunit/phpunit": "^8.5 || ^9.3",
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ namespace OCA\BigBlueButton\BigBlueButton;
|
||||||
|
|
||||||
use BigBlueButton\BigBlueButton;
|
use BigBlueButton\BigBlueButton;
|
||||||
use BigBlueButton\Core\Record;
|
use BigBlueButton\Core\Record;
|
||||||
|
use BigBlueButton\Enum\Role;
|
||||||
use BigBlueButton\Parameters\CreateMeetingParameters;
|
use BigBlueButton\Parameters\CreateMeetingParameters;
|
||||||
use BigBlueButton\Parameters\DeleteRecordingsParameters;
|
use BigBlueButton\Parameters\DeleteRecordingsParameters;
|
||||||
use BigBlueButton\Parameters\GetRecordingsParameters;
|
use BigBlueButton\Parameters\GetRecordingsParameters;
|
||||||
|
|
@ -62,13 +63,10 @@ class API {
|
||||||
* @return string join url
|
* @return string join url
|
||||||
*/
|
*/
|
||||||
public function createJoinUrl(Room $room, float $creationTime, string $displayname, bool $isModerator, ?string $uid = null) {
|
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, $isModerator ? Role::MODERATOR : Role::VIEWER);
|
||||||
|
|
||||||
$joinMeetingParams = new JoinMeetingParameters($room->uid, $displayname, $password);
|
|
||||||
|
|
||||||
// ensure that float is not converted to a string in scientific notation
|
// ensure that float is not converted to a string in scientific notation
|
||||||
$joinMeetingParams->setCreateTime(sprintf("%.0f", $creationTime));
|
$joinMeetingParams->setCreateTime(intval(sprintf("%.0f", $creationTime)));
|
||||||
$joinMeetingParams->setJoinViaHtml5(true);
|
|
||||||
$joinMeetingParams->setRedirect(true);
|
$joinMeetingParams->setRedirect(true);
|
||||||
|
|
||||||
// set the guest parameter for everyone but moderators to send all users to the waiting room if setting is selected
|
// 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 {
|
private function buildMeetingParams(Room $room, ?Presentation $presentation = null): CreateMeetingParameters {
|
||||||
$createMeetingParams = new CreateMeetingParameters($room->uid, $room->name);
|
$createMeetingParams = new CreateMeetingParameters($room->uid, $room->name);
|
||||||
$createMeetingParams->setAttendeePW($room->attendeePassword);
|
|
||||||
$createMeetingParams->setModeratorPW($room->moderatorPassword);
|
|
||||||
$createMeetingParams->setRecord($room->record);
|
$createMeetingParams->setRecord($room->record);
|
||||||
$createMeetingParams->setAllowStartStopRecording($room->record);
|
$createMeetingParams->setAllowStartStopRecording($room->record);
|
||||||
$createMeetingParams->setLogoutURL($this->urlGenerator->getBaseUrl());
|
$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}
|
* @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 {
|
private function recordToArray(Record $record): array {
|
||||||
|
$formats = [];
|
||||||
|
|
||||||
|
foreach ($record->getPlaybackFormats() as $format) {
|
||||||
|
$formats[] = [
|
||||||
|
'type' => $format->getType(),
|
||||||
|
'length' => $format->getLength(),
|
||||||
|
'url' => $format->getUrl()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => $record->getRecordId(),
|
'id' => $record->getRecordId(),
|
||||||
'meetingId' => $record->getMeetingId(),
|
'meetingId' => $record->getMeetingId(),
|
||||||
|
|
@ -247,10 +253,8 @@ class API {
|
||||||
'state' => $record->getState(),
|
'state' => $record->getState(),
|
||||||
'startTime' => $record->getStartTime(),
|
'startTime' => $record->getStartTime(),
|
||||||
'participants' => $record->getParticipantCount(),
|
'participants' => $record->getParticipantCount(),
|
||||||
'type' => $record->getPlaybackType(),
|
'formats' => $formats,
|
||||||
'length' => $record->getPlaybackLength(),
|
'metas' => $record->getMetas()
|
||||||
'url' => $record->getPlaybackUrl(),
|
|
||||||
'metas' => $record->getMetas(),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -65,12 +65,16 @@ export type Recording = {
|
||||||
state: 'processing' | 'processed' | 'published' | 'unpublished' | 'deleted';
|
state: 'processing' | 'processed' | 'published' | 'unpublished' | 'deleted';
|
||||||
startTime: number;
|
startTime: number;
|
||||||
participants: number;
|
participants: number;
|
||||||
type: string;
|
formats: RecordingFormat[];
|
||||||
length: number;
|
|
||||||
url: string;
|
|
||||||
meta: any;
|
meta: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type RecordingFormat = {
|
||||||
|
type: string
|
||||||
|
length: number
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface ShareWithOption {
|
export interface ShareWithOption {
|
||||||
label: string;
|
label: string;
|
||||||
value: {
|
value: {
|
||||||
|
|
@ -210,12 +214,12 @@ class Api {
|
||||||
return response.data;
|
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 startDate = new Date(recording.startTime);
|
||||||
const filename = `${encodeURIComponent(recording.name + ' ' + startDate.toISOString().replace(/:/g, '-').substr(0,19))}.url`;
|
const filename = `${encodeURIComponent(recording.name + ' ' + startDate.toISOString().replace(/:/g, '-').substr(0,19))}.url`;
|
||||||
const url = OC.linkToRemote(`dav/files/${OC.currentUser}${path}/${filename}`);
|
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;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||||
import { Recording } from '../Common/Api';
|
import {Recording, RecordingFormat} from '../Common/Api';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
recording: Recording;
|
recording: Recording;
|
||||||
|
format: RecordingFormat;
|
||||||
isAdmin : boolean;
|
isAdmin : boolean;
|
||||||
deleteRecording: (recording: Recording) => void;
|
deleteRecording: (recording: Recording) => void;
|
||||||
storeRecording: (recording: Recording) => void;
|
storeRecording: (recording: Recording, format: RecordingFormat) => void;
|
||||||
publishRecording: (recording: Recording, publish: boolean) => 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) {
|
function checkPublished(recording: Recording, onChange: (value: boolean) => void) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -28,19 +29,19 @@ const RecordingRow = ({recording, isAdmin, deleteRecording, storeRecording, publ
|
||||||
return (
|
return (
|
||||||
<tr key={recording.id}>
|
<tr key={recording.id}>
|
||||||
<td className="start icon-col">
|
<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>
|
<span className="icon icon-external icon-visible"></span>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td className="share icon-col">
|
<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')}>
|
<button className="action-item copy-to-clipboard" title={t('bbb', 'Copy to clipboard')}>
|
||||||
<span className="icon icon-clippy icon-visible" ></span>
|
<span className="icon icon-clippy icon-visible" ></span>
|
||||||
</button>
|
</button>
|
||||||
</CopyToClipboard>
|
</CopyToClipboard>
|
||||||
</td>
|
</td>
|
||||||
<td className="icon-col">
|
<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>
|
<span className="icon icon-add-shortcut icon-visible"></span>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -48,13 +49,13 @@ const RecordingRow = ({recording, isAdmin, deleteRecording, storeRecording, publ
|
||||||
{(new Date(recording.startTime)).toLocaleString()}
|
{(new Date(recording.startTime)).toLocaleString()}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{recording.length === 0 ? '< 1 min' : (recording.length + ' min')}
|
{format.length === 0 ? '< 1 min' : (format.length + ' min')}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{n('bbb', '%n participant', '%n participants', recording.participants)}
|
{n('bbb', '%n participant', '%n participants', recording.participants)}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{recording.type}
|
{format.type}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{isAdmin && checkPublished(recording, (checked) => {
|
{isAdmin && checkPublished(recording, (checked) => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
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 EditRoom from './EditRoom';
|
||||||
import RecordingRow from './RecordingRow';
|
import RecordingRow from './RecordingRow';
|
||||||
import EditableValue from './EditableValue';
|
import EditableValue from './EditableValue';
|
||||||
|
|
@ -98,9 +98,9 @@ const RoomRow = (props: Props): JSX.Element => {
|
||||||
}, undefined, 'httpd/unix-directory');
|
}, undefined, 'httpd/unix-directory');
|
||||||
}
|
}
|
||||||
|
|
||||||
function storeRecording(recording: Recording) {
|
function storeRecording(recording: Recording, format: RecordingFormat) {
|
||||||
OC.dialogs.filepicker(t('bbb', 'Select target folder'), (path: string) => {
|
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(
|
OC.dialogs.info(
|
||||||
t('bbb', 'URL to presentation was stored in "{path}" as "{filename}".', { path: path + '/', filename }),
|
t('bbb', 'URL to presentation was stored in "{path}" as "{filename}".', { path: path + '/', filename }),
|
||||||
t('bbb', 'Link stored'),
|
t('bbb', 'Link stored'),
|
||||||
|
|
@ -288,7 +288,7 @@ const RoomRow = (props: Props): JSX.Element => {
|
||||||
<td colSpan={11}>
|
<td colSpan={11}>
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue