ov-components: implement read-only mode and customizable controls for recording activity

master
Carlos Santos 2025-07-18 12:08:49 +02:00
parent 98c7e3f751
commit b659400c88
21 changed files with 686 additions and 185 deletions

View File

@ -17,6 +17,8 @@
(onRecordingDeleteRequested)="onRecordingDeleteRequested.emit($event)"
(onRecordingDownloadClicked)="onRecordingDownloadClicked.emit($event)"
(onRecordingPlayClicked)="onRecordingPlayClicked.emit($event)"
(onViewRecordingClicked)="onViewRecordingClicked.emit($event)"
(onViewRecordingsClicked)="onViewRecordingsClicked.emit()"
></ov-recording-activity>
<ov-broadcasting-activity
*ngIf="showBroadcastingActivity"

View File

@ -54,6 +54,21 @@ export class ActivitiesPanelComponent implements OnInit {
*/
@Output() onRecordingPlayClicked: EventEmitter<RecordingPlayClickedEvent> = new EventEmitter<RecordingPlayClickedEvent>();
/**
* @internal
* Provides event notifications that fire when view recordings button has been clicked.
* This event is triggered when the user wants to view all recordings in an external page.
*/
@Output() onViewRecordingsClicked: EventEmitter<void> = new EventEmitter<void>();
/**
* @internal
* Provides event notifications that fire when view recording button has been clicked.
* This event is triggered when the user wants to view a specific recording in an external page.
* It provides the recording ID as event data.
*/
@Output() onViewRecordingClicked: EventEmitter<string> = new EventEmitter<string>();
/**
* Provides event notifications that fire when start broadcasting button is clicked.
* It provides the {@link BroadcastingStartRequestedEvent} payload as event data.

View File

@ -27,7 +27,10 @@
<mat-icon class="blink" *ngIf="recordingStatus === recStatusEnum.STARTED">radio_button_checked</mat-icon>
</div>
<h3 matListItemTitle class="activity-title">{{ 'PANEL.RECORDING.TITLE' | translate }}</h3>
<p matListItemLine class="activity-subtitle">{{ 'PANEL.RECORDING.SUBTITLE' | translate }}</p>
<p matListItemLine class="activity-subtitle">
{{ isReadOnlyMode ? ('PANEL.RECORDING.VIEW_ONLY_SUBTITLE' | translate) : ('PANEL.RECORDING.SUBTITLE' | translate) }}
</p>
<div class="activity-action-buttons" matListItemMeta>
<div
id="recording-status"
@ -56,11 +59,26 @@
<!-- Empty state content -->
<div *ngIf="recordingList.length === 0" class="empty-state">
<h2 class="recording-title">{{ 'PANEL.RECORDING.CONTENT_TITLE' | translate }}</h2>
<span class="recording-subtitle">{{ 'PANEL.RECORDING.CONTENT_SUBTITLE' | translate }}</span>
<h2 class="recording-title">
{{
isReadOnlyMode
? ('PANEL.RECORDING.VIEW_ONLY_CONTENT_TITLE' | translate)
: ('PANEL.RECORDING.CONTENT_TITLE' | translate)
}}
</h2>
<span class="recording-subtitle">
{{
isReadOnlyMode
? recordingList.length === 0
? ('PANEL.RECORDING.NO_RECORDINGS_AVAILABLE' | translate)
: ('PANEL.RECORDING.VIEW_ONLY_CONTENT_SUBTITLE' | translate)
: ('PANEL.RECORDING.CONTENT_SUBTITLE' | translate)
}}
</span>
</div>
<!-- Recording control buttons -->
@if (!isReadOnlyMode) {
<div class="item recording-action-buttons">
<!-- Stop recording button -->
<button *ngIf="recordingAlive" mat-flat-button id="stop-recording-btn" (click)="stopRecording()">
@ -85,6 +103,14 @@
</button>
</div>
<!-- View all recordings button -->
<div class="item recording-action-buttons">
<button mat-flat-button id="view-recordings-btn" (click)="viewAllRecordings()" class="view-recordings-button">
<span>{{ 'TOOLBAR.VIEW_RECORDINGS' | translate }}</span>
<mat-icon class="external-link-icon">open_in_new</mat-icon>
</button>
</div>
<!-- Recording status messages -->
<div class="recording-status-messages">
<span *ngIf="recordingStatus === recStatusEnum.STARTING" class="recording-message">
@ -103,6 +129,15 @@
</div>
</div>
</div>
} @else {
<!-- View all recordings button -->
<div class="item recording-action-buttons">
<button mat-flat-button id="view-recordings-btn" (click)="viewAllRecordings()" class="view-recordings-button">
<span>{{ 'TOOLBAR.VIEW_RECORDINGS' | translate }}</span>
<mat-icon class="external-link-icon">open_in_new</mat-icon>
</button>
</div>
}
</div>
<mat-divider *ngIf="recordingList.length > 0"></mat-divider>
@ -139,8 +174,10 @@
</div>
<!-- Recording action buttons -->
@if (!isReadOnlyMode) {
<div *ngIf="recording.status !== recStatusEnum.STARTED" id="recording-action-buttons" class="recording-actions">
<button
*ngIf="showControls.play"
mat-icon-button
(click)="play(recording)"
id="play-recording-btn"
@ -150,7 +187,26 @@
<mat-icon>play_arrow</mat-icon>
</button>
@if (showControls.externalView) {
<div
*ngIf="recording.status !== recStatusEnum.STARTED"
id="recording-action-buttons"
class="recording-actions"
>
<button
mat-icon-button
(click)="onViewRecordingClicked.emit(recording.id)"
id="watch-recording-btn"
matTooltip="{{ 'PANEL.RECORDING.WATCH' | translate }}"
class="action-button watch-button"
>
<mat-icon>open_in_new</mat-icon>
</button>
</div>
}
<button
*ngIf="showControls.download"
mat-icon-button
(click)="download(recording)"
id="download-recording-btn"
@ -161,6 +217,7 @@
</button>
<button
*ngIf="showControls.delete"
mat-icon-button
(click)="deleteRecording(recording)"
id="delete-recording-btn"
@ -170,6 +227,19 @@
<mat-icon>delete</mat-icon>
</button>
</div>
} @else {
<div *ngIf="recording.status !== recStatusEnum.STARTED" id="recording-action-buttons" class="recording-actions">
<button
mat-icon-button
(click)="onViewRecordingClicked.emit(recording.id)"
id="watch-recording-btn"
matTooltip="{{ 'PANEL.RECORDING.WATCH' | translate }}"
class="action-button watch-button"
>
<mat-icon>open_in_new</mat-icon>
</button>
</div>
}
</mat-list-item>
</mat-list>
</div>

View File

@ -145,8 +145,7 @@
}
.recording-action-buttons {
margin-top: 20px;
margin-bottom: 20px;
margin: 5px 0px;
}
#start-recording-btn {
@ -155,6 +154,17 @@
color: var(--ov-secondary-action-color);
}
#view-recordings-btn {
width: 100%;
background-color: var(--ov-accent-action-color);
color: var(--ov-secondary-action-color);
margin-bottom: 10px;
mat-icon {
margin-right: 8px;
}
}
.start-recording-button-container {
width: 100%;
display: inline-block;

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, OnDestroy, Output } from '@angular/core';
import { Subject, takeUntil } from 'rxjs';
import {
RecordingDeleteRequestedEvent,
@ -16,6 +16,7 @@ import { RecordingService } from '../../../../services/recording/recording.servi
import { OpenViduService } from '../../../../services/openvidu/openvidu.service';
import { ILogger } from '../../../../models/logger.model';
import { LoggerService } from '../../../../services/logger/logger.service';
import { OpenViduComponentsConfigService } from '../../../../services/config/directive-config.service';
/**
* The **RecordingActivityComponent** is the component that allows showing the recording activity.
@ -31,7 +32,7 @@ import { LoggerService } from '../../../../services/logger/logger.service';
// TODO: Allow to add more than one recording type
// TODO: Allow to choose where the recording is stored (s3, google cloud, etc)
// TODO: Allow to choose the layout of the recording
export class RecordingActivityComponent implements OnInit {
export class RecordingActivityComponent implements OnInit, OnDestroy {
/**
* @internal
*/
@ -67,6 +68,20 @@ export class RecordingActivityComponent implements OnInit {
*/
@Output() onRecordingPlayClicked: EventEmitter<RecordingPlayClickedEvent> = new EventEmitter<RecordingPlayClickedEvent>();
/**
* @internal
* Provides event notifications that fire when view recordings button has been clicked.
* This event is triggered when the user wants to view all recordings in an external page.
*/
@Output() onViewRecordingsClicked: EventEmitter<void> = new EventEmitter<void>();
/**
* @internal
* This event is fired when the user clicks on the view recording button.
* It provides the recording ID as event data.
*/
@Output() onViewRecordingClicked: EventEmitter<string> = new EventEmitter<string>();
/**
* @internal
*/
@ -108,6 +123,27 @@ export class RecordingActivityComponent implements OnInit {
* @internal
*/
mouseHovering: boolean = false;
/**
* @internal
*/
isReadOnlyMode: boolean = false;
/**
* @internal
*/
viewButtonText: string = 'PANEL.RECORDING.VIEW';
/**
* @internal
*/
showControls: { play?: boolean; download?: boolean; delete?: boolean; externalView?: boolean } = {
play: true,
download: true,
delete: true,
externalView: false
};
private log: ILogger;
private destroy$ = new Subject<void>();
@ -120,7 +156,8 @@ export class RecordingActivityComponent implements OnInit {
private actionService: ActionService,
private openviduService: OpenViduService,
private cd: ChangeDetectorRef,
private loggerSrv: LoggerService
private loggerSrv: LoggerService,
private libService: OpenViduComponentsConfigService
) {
this.log = this.loggerSrv.get('RecordingActivityComponent');
}
@ -131,6 +168,7 @@ export class RecordingActivityComponent implements OnInit {
ngOnInit(): void {
this.subscribeToRecordingStatus();
this.subscribeToTracksChanges();
this.subscribeToConfigChanges();
}
/**
@ -240,6 +278,46 @@ export class RecordingActivityComponent implements OnInit {
this.recordingService.playRecording(recording);
}
/**
* @internal
*/
viewRecording(recording: RecordingInfo) {
// This method can be overridden or emit a custom event for navigation
// For now, it uses the same behavior as play, but can be customized
if (!recording.filename) {
this.log.e('Error viewing recording. Recording filename is undefined');
return;
}
const payload: RecordingPlayClickedEvent = {
roomName: this.openviduService.getRoomName(),
recordingId: recording.id
};
this.onRecordingPlayClicked.emit(payload);
// You can customize this to navigate to a different page instead
this.recordingService.playRecording(recording);
}
/**
* @internal
*/
viewAllRecordings() {
this.onViewRecordingsClicked.emit();
}
private subscribeToConfigChanges() {
this.libService.recordingActivityReadOnly$.pipe(takeUntil(this.destroy$)).subscribe((readOnly: boolean) => {
this.isReadOnlyMode = readOnly;
this.cd.markForCheck();
});
this.libService.recordingActivityShowControls$
.pipe(takeUntil(this.destroy$))
.subscribe((controls: { play?: boolean; download?: boolean; delete?: boolean; externalView?: boolean }) => {
this.showControls = controls;
this.cd.markForCheck();
});
}
private subscribeToRecordingStatus() {
this.recordingService.recordingStatusObs.pipe(takeUntil(this.destroy$)).subscribe((event: RecordingStatusInfo) => {
const { status, recordingList, error } = event;

View File

@ -137,6 +137,14 @@
</span>
</button>
<!-- View recordings button -->
@if (!isMinimal && showViewRecordingsButton) {
<button mat-menu-item id="view-recordings-btn" (click)="onViewRecordingsClicked.emit()">
<mat-icon>video_library</mat-icon>
<span>{{ 'TOOLBAR.VIEW_RECORDINGS' | translate }}</span>
</button>
}
<!-- Broadcasting button -->
<button
*ngIf="!isMinimal && showBroadcastingButton"

View File

@ -145,6 +145,12 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
@Output() onBroadcastingStopRequested: EventEmitter<BroadcastingStopRequestedEvent> =
new EventEmitter<BroadcastingStopRequestedEvent>();
/**
* @internal
* This event is fired when the user clicks on the view recordings button.
*/
@Output() onViewRecordingsClicked: EventEmitter<void> = new EventEmitter<void>();
/**
* @ignore
*/
@ -240,6 +246,11 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
*/
showRecordingButton: boolean = true;
/**
* @ignore
*/
showViewRecordingsButton: boolean = false;
/**
* @ignore
*/
@ -312,6 +323,11 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
*/
recordingStatus: RecordingStatus = RecordingStatus.STOPPED;
/**
* @ignore
*/
isRecordingReadOnlyMode: boolean = false;
/**
* @ignore
*/
@ -602,7 +618,9 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
}
private subscribeToFullscreenChanged() {
fromEvent(document, 'fullscreenchange').pipe(takeUntil(this.destroy$)).subscribe(() => {
fromEvent(document, 'fullscreenchange')
.pipe(takeUntil(this.destroy$))
.subscribe(() => {
const isFullscreen = Boolean(document.fullscreenElement);
if (isFullscreen) {
this.cdkOverlayService.setSelector('#session-container');
@ -661,6 +679,11 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
}
private subscribeToRecordingStatus() {
this.libService.recordingActivityReadOnly$.pipe(takeUntil(this.destroy$)).subscribe((readOnly: boolean) => {
this.isRecordingReadOnlyMode = readOnly;
this.cd.markForCheck();
});
this.recordingService.recordingStatusObs.pipe(takeUntil(this.destroy$)).subscribe((event: RecordingStatusInfo) => {
const { status, startedAt } = event;
this.recordingStatus = status;
@ -696,6 +719,11 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
this.brandingLogo = value;
this.cd.markForCheck();
});
this.libService.toolbarViewRecordingsButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
this.showViewRecordingsButton = value;
this.checkDisplayMoreOptions();
this.cd.markForCheck();
});
this.libService.cameraButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
this.showCameraButton = value;
this.cd.markForCheck();
@ -766,8 +794,9 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
this.cd.markForCheck();
});
this.libService.toolbarAdditionalButtonsPosition$.pipe(takeUntil(this.destroy$)).subscribe(
(value: ToolbarAdditionalButtonsPosition) => {
this.libService.toolbarAdditionalButtonsPosition$
.pipe(takeUntil(this.destroy$))
.subscribe((value: ToolbarAdditionalButtonsPosition) => {
// Using Promise.resolve() to defer change detection until the next microtask.
// This ensures that Angular's change detection has the latest value before updating the view.
// Without this, Angular's OnPush strategy might not immediately reflect the change,
@ -777,8 +806,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
this.additionalButtonsPosition = value;
this.cd.markForCheck();
});
}
);
});
}
private subscribeToCaptionsToggling() {

View File

@ -69,6 +69,7 @@
(onRecordingStartRequested)="onRecordingStartRequested.emit($event)"
(onRecordingStopRequested)="onRecordingStopRequested.emit($event)"
(onBroadcastingStopRequested)="onBroadcastingStopRequested.emit($event)"
(onViewRecordingsClicked)="onViewRecordingsClicked.emit()"
>
<ng-template #toolbarAdditionalButtons>
<ng-container *ngTemplateOutlet="openviduAngularToolbarAdditionalButtonsTemplate"></ng-container>
@ -133,6 +134,8 @@
(onRecordingDeleteRequested)="onRecordingDeleteRequested.emit($event)"
(onRecordingDownloadClicked)="onRecordingDownloadClicked.emit($event)"
(onRecordingPlayClicked)="onRecordingPlayClicked.emit($event)"
(onViewRecordingClicked)="onViewRecordingClicked.emit($event)"
(onViewRecordingsClicked)="onViewRecordingsClicked.emit()"
(onBroadcastingStartRequested)="onBroadcastingStartRequested.emit($event)"
(onBroadcastingStopRequested)="onBroadcastingStopRequested.emit($event)"
></ov-activities-panel>

View File

@ -326,6 +326,13 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
*/
@Output() onRecordingPlayClicked: EventEmitter<RecordingPlayClickedEvent> = new EventEmitter<RecordingPlayClickedEvent>();
/**
* @internal
* This event is fired when the user clicks on the view recording button.
* It provides the recording ID as event data.
*/
@Output() onViewRecordingClicked: EventEmitter<string> = new EventEmitter<string>();
/**
* Provides event notifications that fire when download recording button is clicked from {@link ActivitiesPanelComponent}.
* It provides the {@link RecordingDownloadClickedEvent} payload as event data.
@ -346,6 +353,12 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
@Output() onBroadcastingStopRequested: EventEmitter<BroadcastingStopRequestedEvent> =
new EventEmitter<BroadcastingStopRequestedEvent>();
/**
* @internal
* This event is fired when the user clicks on the view recordings button.
*/
@Output() onViewRecordingsClicked: EventEmitter<void> = new EventEmitter<void>();
/**
* Provides event notifications that fire when Room is created for the local participant.
* It provides the {@link https://openvidu.io/latest/docs/getting-started/#room Room} payload as event data.
@ -620,12 +633,7 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
}
private subscribeToVideconferenceDirectives() {
this.libService.token$
.pipe(
skip(1),
takeUntil(this.destroy$)
)
.subscribe((token: string) => {
this.libService.token$.pipe(skip(1), takeUntil(this.destroy$)).subscribe((token: string) => {
try {
if (!token) {
this.log.e('Token is empty');
@ -643,9 +651,7 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
}
});
this.libService.tokenError$
.pipe(takeUntil(this.destroy$))
.subscribe((error: any) => {
this.libService.tokenError$.pipe(takeUntil(this.destroy$)).subscribe((error: any) => {
if (!error) return;
this.log.e('Token error received', error);
@ -656,9 +662,7 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
}
});
this.libService.prejoin$
.pipe(takeUntil(this.destroy$))
.subscribe((value: boolean) => {
this.libService.prejoin$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
this.showPrejoin = value;
if (!this.showPrejoin) {
// Emit token ready if the prejoin page won't be shown
@ -698,9 +702,7 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
// this.cd.markForCheck();
});
this.libService.participantName$
.pipe(takeUntil(this.destroy$))
.subscribe((name: string) => {
this.libService.participantName$.pipe(takeUntil(this.destroy$)).subscribe((name: string) => {
if (name) {
this.latestParticipantName = name;
this.storageSrv.setParticipantName(name);

View File

@ -10,7 +10,10 @@ import {
FallbackLogoDirective,
LayoutRemoteParticipantsDirective,
PrejoinDisplayParticipantName,
ToolbarBrandingLogoDirective
ToolbarBrandingLogoDirective,
ToolbarViewRecordingsButtonDirective,
RecordingActivityReadOnlyDirective,
RecordingActivityShowControlsDirective
} from './internals.directive';
import { ParticipantPanelItemMuteButtonDirective } from './participant-panel-item.directive';
import {
@ -65,6 +68,8 @@ const directives = [
PrejoinDirective,
PrejoinDisplayParticipantName,
VideoEnabledDirective,
RecordingActivityReadOnlyDirective,
RecordingActivityShowControlsDirective,
AudioEnabledDirective,
ShowDisconnectionDialogDirective,
RecordingStreamBaseUrlDirective,
@ -84,6 +89,7 @@ const directives = [
ToolbarDisplayLogoDirective,
ToolbarSettingsButtonDirective,
ToolbarAdditionalButtonsPossitionDirective,
ToolbarViewRecordingsButtonDirective,
StreamDisplayParticipantNameDirective,
StreamDisplayAudioDetectionDirective,
StreamVideoControlsDirective,

View File

@ -161,3 +161,181 @@ export class PrejoinDisplayParticipantName implements OnDestroy {
this.libService.setPrejoinDisplayParticipantName(value);
}
}
/**
* @internal
*
* The **recordingActivityReadOnly** directive sets the recording activity panel to read-only mode.
* In this mode, users can only view recordings without the ability to start, stop, or delete them.
*
* It is only available for {@link VideoconferenceComponent}.
*
* Default: `false`
*
* @example
* <ov-videoconference [recordingActivityReadOnly]="true"></ov-videoconference>
*/
@Directive({
selector: 'ov-videoconference[recordingActivityReadOnly]',
standalone: false
})
export class RecordingActivityReadOnlyDirective implements OnDestroy {
/**
* @ignore
*/
@Input() set recordingActivityReadOnly(value: boolean) {
this.update(value);
}
/**
* @ignore
*/
constructor(
public elementRef: ElementRef,
private libService: OpenViduComponentsConfigService
) {}
/**
* @ignore
*/
ngOnDestroy(): void {
this.clear();
}
/**
* @ignore
*/
clear() {
this.update(false);
}
/**
* @ignore
*/
update(value: boolean) {
this.libService.setRecordingActivityReadOnly(value);
}
}
/**
*
* @internal
*
* The **recordingActivityShowControls** directive allows to show/hide specific recording controls (play, download, delete, externalView).
* You can pass an object with boolean properties to control which buttons are shown.
*
* It is only available for {@link VideoconferenceComponent}.
*
* Default: `{ play: true, download: true, delete: true, externalView: false }`
*
* @example
* <ov-videoconference [recordingActivityShowControls]="{ play: false, download: true, delete: false }"></ov-videoconference>
*/
@Directive({
selector: 'ov-videoconference[recordingActivityShowControls]',
standalone: false
})
export class RecordingActivityShowControlsDirective implements OnDestroy {
/**
* @ignore
*/
@Input() set recordingActivityShowControls(value: { play?: boolean; download?: boolean; delete?: boolean; externalView?: boolean }) {
this.update(value);
}
/**
* @ignore
*/
constructor(
public elementRef: ElementRef,
private libService: OpenViduComponentsConfigService
) {}
/**
* @ignore
*/
ngOnDestroy(): void {
this.clear();
}
/**
* @ignore
*/
clear() {
this.update({ play: true, download: true, delete: true, externalView: false });
}
/**
* @ignore
*/
update(value: { play?: boolean; download?: boolean; delete?: boolean; externalView?: boolean }) {
this.libService.setRecordingActivityShowControls(value);
}
}
/**
* @internal
* The **viewRecordingsButton** directive allows show/hide the view recordings toolbar button.
*
* Default: `false`
*
* It can be used in the parent element {@link VideoconferenceComponent} specifying the name of the `toolbar` component:
*
* @example
* <ov-videoconference [toolbarViewRecordingsButton]="true"></ov-videoconference>
*
* \
* And it also can be used in the {@link ToolbarComponent}.
* @example
* <ov-toolbar [viewRecordingsButton]="true"></ov-toolbar>
*
* When the button is clicked, it will fire the `onViewRecordingsClicked` event.
*/
@Directive({
selector: 'ov-videoconference[toolbarViewRecordingsButton], ov-toolbar[viewRecordingsButton]',
standalone: false
})
export class ToolbarViewRecordingsButtonDirective implements AfterViewInit, OnDestroy {
/**
* @ignore
*/
@Input() set toolbarViewRecordingsButton(value: boolean) {
this.viewRecordingsValue = value;
this.update(this.viewRecordingsValue);
}
/**
* @ignore
*/
@Input() set viewRecordingsButton(value: boolean) {
this.viewRecordingsValue = value;
this.update(this.viewRecordingsValue);
}
private viewRecordingsValue: boolean = false;
/**
* @ignore
*/
constructor(
public elementRef: ElementRef,
private libService: OpenViduComponentsConfigService
) {}
ngAfterViewInit() {
this.update(this.viewRecordingsValue);
}
ngOnDestroy(): void {
this.clear();
}
private clear() {
this.viewRecordingsValue = false;
this.update(true);
}
private update(value: boolean) {
if (this.libService.getToolbarViewRecordingsButton() !== value) {
this.libService.setToolbarViewRecordingsButton(value);
}
}
}

View File

@ -56,7 +56,8 @@
"PARTICIPANTS": "参与者",
"CHAT": "聊天",
"ACTIVITIES": "活动",
"NO_TRACKS_PUBLISHED": "请分享音频或视频以开始录制。"
"NO_TRACKS_PUBLISHED": "请分享音频或视频以开始录制。",
"VIEW_RECORDINGS": "查看录像"
},
"STREAM": {
"SETTINGS": "设置",
@ -115,6 +116,10 @@
"SUBTITLE": "为后人记录你的会议",
"CONTENT_TITLE": "记录你的视频通话",
"CONTENT_SUBTITLE": "当录音完成后,你将可以轻松地下载它",
"VIEW_ONLY_SUBTITLE": "查看和访问房间录音",
"VIEW_ONLY_CONTENT_TITLE": "视频通话录音",
"VIEW_ONLY_CONTENT_SUBTITLE": "在这里您可以访问所有可用的录音",
"WATCH": "观看",
"STARTING": "开始录音",
"STOPPING": "停止录制",
"IN_PROGRESS": "录音中",
@ -126,7 +131,8 @@
"DOWNLOAD": "下载",
"RECORDINGS": "录制",
"NO_MODERATOR": "只有主持人可以开始录音",
"NO_TRACKS_PUBLISHED": "请分享音频或视频以开始录制。"
"NO_TRACKS_PUBLISHED": "请分享音频或视频以开始录制。",
"NO_RECORDINGS_AVAILABLE": "目前没有可用的录音"
},
"STREAMING": {
"TITLE": "直播",

View File

@ -56,7 +56,8 @@
"PARTICIPANTS": "Participants",
"CHAT": "Chat",
"ACTIVITIES": "Activities",
"NO_TRACKS_PUBLISHED": "Share audio or video to start recording."
"NO_TRACKS_PUBLISHED": "Share audio or video to start recording.",
"VIEW_RECORDINGS": "View recordings"
},
"STREAM": {
"SETTINGS": "Settings",
@ -115,6 +116,13 @@
"SUBTITLE": "Record your meeting for posterity",
"CONTENT_TITLE": "Record your video call",
"CONTENT_SUBTITLE": "When recording has finished you will be able to download it with ease",
"VIEW_ONLY_TITLE": "Available recordings",
"VIEW_ONLY_SUBTITLE": "View and access room recordings",
"VIEW_ONLY_CONTENT_TITLE": "Video call recordings",
"VIEW_ONLY_CONTENT_SUBTITLE": "Here you can access all available recordings",
"VIEW": "View",
"WATCH": "Watch",
"ACCESS": "Access",
"STARTING": "Starting recording",
"STOPPING": "Stopping recording",
"IN_PROGRESS": "Recording in progress ...",
@ -126,7 +134,9 @@
"DOWNLOAD": "Download",
"RECORDINGS": "RECORDINGS",
"NO_MODERATOR": "Only the MODERATOR can start the recording",
"NO_TRACKS_PUBLISHED": "Share audio or video to start recording."
"NO_TRACKS_PUBLISHED": "Share audio or video to start recording.",
"NO_RECORDINGS_AVAILABLE": "No recordings available at this time",
"BROWSE_RECORDINGS": "Browse saved recordings"
},
"STREAMING": {
"TITLE": "Streaming",

View File

@ -56,7 +56,8 @@
"PARTICIPANTS": "Participantes",
"CHAT": "Chat",
"ACTIVITIES": "Actividades",
"NO_TRACKS_PUBLISHED": "Comparte audio o video para poder empezar a grabar."
"NO_TRACKS_PUBLISHED": "Comparte audio o video para poder empezar a grabar.",
"VIEW_RECORDINGS": "Ver grabaciones"
},
"STREAM": {
"SETTINGS": "Ajustes",
@ -115,6 +116,10 @@
"SUBTITLE": "Graba tus llamadas para la posteridad",
"CONTENT_TITLE": "Graba tu video conferencia",
"CONTENT_SUBTITLE": "Cuando la grabación haya finalizado, podrás descargarla con facilidad",
"VIEW_ONLY_SUBTITLE": "Visualiza y accede a las grabaciones de la sala",
"VIEW_ONLY_CONTENT_TITLE": "Grabaciones de la video conferencia",
"VIEW_ONLY_CONTENT_SUBTITLE": "Aquí puedes acceder a todas las grabaciones disponibles",
"WATCH": "Visualizar",
"STARTING": "Iniciando grabación...",
"STOPPING": "Parando grabación",
"IN_PROGRESS": "Grabación en curso",
@ -126,7 +131,8 @@
"DOWNLOAD": "Descargar",
"RECORDINGS": "GRABACIONES",
"NO_MODERATOR": "Sólo el MODERADOR puede iniciar la grabación",
"NO_TRACKS_PUBLISHED": "Comparte audio o video para poder empezar a grabar."
"NO_TRACKS_PUBLISHED": "Comparte audio o video para poder empezar a grabar.",
"NO_RECORDINGS_AVAILABLE": "No hay grabaciones disponibles en este momento"
},
"STREAMING": {
"TITLE": "Streaming",

View File

@ -56,7 +56,8 @@
"PARTICIPANTS": "Participants",
"CHAT": "Chat",
"ACTIVITES": "Activités",
"NO_TRACKS_PUBLISHED": "Partagez de laudio ou de la vidéo pour commencer lenregistrement."
"NO_TRACKS_PUBLISHED": "Partagez de laudio ou de la vidéo pour commencer lenregistrement.",
"VIEW_RECORDINGS": "Voir les enregistrements"
},
"STREAM": {
"SETTINGS": "Paramètres",
@ -115,6 +116,10 @@
"SUBTITLE": "Enregistrez votre réunion pour la postérité",
"CONTENT_TITLE": "Enregistrez votre appel vidéo",
"CONTENT_SUBTITLE": "Une fois l'enregistrement terminé, vous pourrez le télécharger facilement",
"VIEW_ONLY_SUBTITLE": "Visualisez et accédez aux enregistrements de la salle",
"VIEW_ONLY_CONTENT_TITLE": "Enregistrements d'appel vidéo",
"VIEW_ONLY_CONTENT_SUBTITLE": "Ici vous pouvez accéder à tous les enregistrements disponibles",
"WATCH": "Regarder",
"STARTING": "Début de l'enregistrement",
"STOPPING": "Arrêt de l'enregistrement",
"IN_PROGRESS": "Enregistrement en cours",
@ -126,7 +131,8 @@
"DOWNLOAD": "Télécharger",
"RECORDINGS": "ENREGISTREMENTS",
"NO_MODERATOR": "Seul le MODERATEUR peut lancer l'enregistrement",
"NO_TRACKS_PUBLISHED": "Partagez de laudio ou de la vidéo pour commencer lenregistrement."
"NO_TRACKS_PUBLISHED": "Partagez de laudio ou de la vidéo pour commencer lenregistrement.",
"NO_RECORDINGS_AVAILABLE": "Aucun enregistrement disponible pour le moment"
},
"STREAMING": {
"TITLE": "Streaming",

View File

@ -56,7 +56,8 @@
"PARTICIPANTS": "सदस्य",
"CHAT": "बातचीत",
"ACTIVITIES": "गतिविधियाँ",
"NO_TRACKS_PUBLISHED": "रिकॉर्डिंग शुरू करने के लिए ऑडियो या वीडियो साझा करें।"
"NO_TRACKS_PUBLISHED": "रिकॉर्डिंग शुरू करने के लिए ऑडियो या वीडियो साझा करें।",
"VIEW_RECORDINGS": "रिकॉर्डिंग देखें"
},
"STREAM": {
"SETTINGS": "सेटिंग्स",
@ -115,6 +116,10 @@
"SUBTITLE": "अपनी बैठक को भावी पीढ़ी के लिए रिकॉर्ड करें",
"CONTENT_TITLE": "अपना वीडियो कॉल रिकॉर्ड करें",
"CONTENT_SUBTITLE": "रिकॉर्डिंग समाप्त हो जाने पर आप इसे आसानी से डाउनलोड कर सकेंगे",
"VIEW_ONLY_SUBTITLE": "कमरे की रिकॉर्डिंग देखें और एक्सेस करें",
"VIEW_ONLY_CONTENT_TITLE": "वीडियो कॉल रिकॉर्डिंग",
"VIEW_ONLY_CONTENT_SUBTITLE": "यहाँ आप सभी उपलब्ध रिकॉर्डिंग तक पहुँच सकते हैं",
"WATCH": "देखना",
"STARTING": "रिकॉर्डिंग शुरू कर रहा है",
"STOPPING": "रिकॉर्डिंग बंद करना",
"IN_PROGRESS": "रिकॉर्डिंग चल रही है",
@ -126,7 +131,8 @@
"DOWNLOAD": "डाउनलोड",
"RECORDINGS": "रिकॉर्डिंग",
"NO_MODERATOR": "केवल मॉडरेटर ही रिकॉर्डिंग शुरू कर सकता है",
"NO_TRACKS_PUBLISHED": "रिकॉर्डिंग शुरू करने के लिए ऑडियो या वीडियो साझा करें।"
"NO_TRACKS_PUBLISHED": "रिकॉर्डिंग शुरू करने के लिए ऑडियो या वीडियो साझा करें।",
"NO_RECORDINGS_AVAILABLE": "इस समय कोई रिकॉर्डिंग उपलब्ध नहीं है"
},
"STREAMING": {
"TITLE": "स्ट्रीमिंग",

View File

@ -56,7 +56,8 @@
"PARTICIPANTS": "Partecipanti",
"CHAT": "Chat",
"ACTIVITIES": "Attività",
"NO_TRACKS_PUBLISHED": "Condividi audio o video per iniziare la registrazione."
"NO_TRACKS_PUBLISHED": "Condividi audio o video per iniziare la registrazione.",
"VIEW_RECORDINGS": "Visualizza registrazioni"
},
"STREAM": {
"SETTINGS": "Impostazioni",
@ -115,6 +116,10 @@
"SUBTITLE": "Registra la tua riunione per i posteri",
"CONTENT_TITLE": "Registra la tua videochiamata",
"CONTENT_SUBTITLE": "Al termine della registrazione potrete scaricarla con facilità",
"VIEW_ONLY_SUBTITLE": "Visualizza e accedi alle registrazioni della sala",
"VIEW_ONLY_CONTENT_TITLE": "Registrazioni di videochiamate",
"VIEW_ONLY_CONTENT_SUBTITLE": "Qui puoi accedere a tutte le registrazioni disponibili",
"WATCH": "Guardare",
"STARTING": "Avvio della registrazione",
"STOPPING": "Interruzione della registrazione",
"IN_PROGRESS": "Registrazione in corso",
@ -126,7 +131,8 @@
"DOWNLOAD": "Scarica",
"RECORDINGS": "REGISTRAZIONI",
"NO_MODERATOR": "Solo il MODERATORE può avviare la registrazione",
"NO_TRACKS_PUBLISHED": "Condividi audio o video per iniziare la registrazione."
"NO_TRACKS_PUBLISHED": "Condividi audio o video per iniziare la registrazione.",
"NO_RECORDINGS_AVAILABLE": "Nessuna registrazione disponibile al momento"
},
"STREAMING": {
"TITLE": "Streaming",

View File

@ -56,7 +56,8 @@
"PARTICIPANTS": "参加者",
"CHAT": "チャット",
"ACTIVITIES": "アクティビティ",
"NO_TRACKS_PUBLISHED": "録音を開始するには、音声または動画を共有してください。"
"NO_TRACKS_PUBLISHED": "録音を開始するには、音声または動画を共有してください。",
"VIEW_RECORDINGS": "録画を表示"
},
"STREAM": {
"SETTINGS": "設定",
@ -115,6 +116,10 @@
"SUBTITLE": "会議を録画して保存する",
"CONTENT_TITLE": "ビデオ通話を録音する",
"CONTENT_SUBTITLE": "録画が完了したら、簡単にダウンロードできます",
"VIEW_ONLY_SUBTITLE": "ルームの録画を表示してアクセスする",
"VIEW_ONLY_CONTENT_TITLE": "ビデオ通話の録画",
"VIEW_ONLY_CONTENT_SUBTITLE": "ここで利用可能なすべての録画にアクセスできます",
"WATCH": "視聴する",
"STARTING": "録画開始",
"STOPPING": "録音停止",
"IN_PROGRESS": "録画中",
@ -126,7 +131,8 @@
"DOWNLOAD": "保存",
"RECORDINGS": "録画",
"NO_MODERATOR": "録画を開始できるのは、モデレーターのみです",
"NO_TRACKS_PUBLISHED": "録音を開始するには、音声または動画を共有してください。"
"NO_TRACKS_PUBLISHED": "録音を開始するには、音声または動画を共有してください。",
"NO_RECORDINGS_AVAILABLE": "現在利用可能な録画はありません"
},
"STREAMING": {
"TITLE": "ストリーミング",

View File

@ -56,7 +56,8 @@
"PARTICIPANTS": "Deelnemers",
"CHAT": "Chat",
"ACTIVITIES": "Activiteiten",
"NO_TRACKS_PUBLISHED": "Deel audio of video om met opnemen te beginnen."
"NO_TRACKS_PUBLISHED": "Deel audio of video om met opnemen te beginnen.",
"VIEW_RECORDINGS": "Opnames bekijken"
},
"STREAM": {
"SETTINGS": "Instellingen",
@ -115,6 +116,10 @@
"SUBTITLE": "Neem uw vergadering op voor het nageslacht",
"CONTENT_TITLE": "Neem uw videogesprek op",
"CONTENT_SUBTITLE": "Als de opname klaar is kunt u deze met gemak downloaden",
"VIEW_ONLY_SUBTITLE": "Bekijk en toegang tot kameropnames",
"VIEW_ONLY_CONTENT_TITLE": "Videogesprek opnames",
"VIEW_ONLY_CONTENT_SUBTITLE": "Hier heeft u toegang tot alle beschikbare opnames",
"WATCH": "Bekijken",
"STARTING": "Beginnen met opnemen",
"STOPPING": "Opname stoppen",
"IN_PROGRESS": "Opname in uitvoering",
@ -126,7 +131,8 @@
"DOWNLOAD": "Downloaden",
"RECORDINGS": "OPNAME",
"NO_MODERATOR": "Alleen de MOEDERATOR kan de opname starten",
"NO_TRACKS_PUBLISHED": "Deel audio of video om met opnemen te beginnen."
"NO_TRACKS_PUBLISHED": "Deel audio of video om met opnemen te beginnen.",
"NO_RECORDINGS_AVAILABLE": "Momenteel zijn er geen opnames beschikbaar"
},
"STREAMING": {
"TITLE": "Streaming",

View File

@ -56,7 +56,8 @@
"PARTICIPANTS": "Participantes",
"CHAT": "Chat",
"ACTIVITIES": "Actividades",
"NO_TRACKS_PUBLISHED": "Compartilhe áudio ou vídeo para começar a gravar."
"NO_TRACKS_PUBLISHED": "Compartilhe áudio ou vídeo para começar a gravar.",
"VIEW_RECORDINGS": "Ver gravações"
},
"STREAM": {
"SETTINGS": "Configurações",
@ -115,6 +116,10 @@
"SUBTITLE": "Grave a sua reunião para a posteridade",
"CONTENT_TITLE": "Grave a sua videochamada",
"CONTENT_SUBTITLE": "Quando a gravação tiver terminado, poderá descarregá-la com facilidade",
"VIEW_ONLY_SUBTITLE": "Visualize e acesse gravações da sala",
"VIEW_ONLY_CONTENT_TITLE": "Gravações de videochamada",
"VIEW_ONLY_CONTENT_SUBTITLE": "Aqui você pode acessar todas as gravações disponíveis",
"WATCH": "Assistir",
"STARTING": "Começar a gravação",
"STOPPING": "Parando a gravação",
"IN_PROGRESS": "Gravação em andamento",
@ -126,7 +131,8 @@
"DOWNLOAD": "Download",
"RECORDINGS": "GRAVAÇÕES",
"NO_MODERATOR": "Só o MODERADOR pode iniciar a gravação",
"NO_TRACKS_PUBLISHED": "Compartilhe áudio ou vídeo para começar a gravar."
"NO_TRACKS_PUBLISHED": "Compartilhe áudio ou vídeo para começar a gravar.",
"NO_RECORDINGS_AVAILABLE": "Nenhuma gravação disponível no momento"
},
"STREAMING": {
"TITLE": "Streaming",

View File

@ -96,6 +96,8 @@ export class OpenViduComponentsConfigService {
backgroundEffectsButton$: Observable<boolean>;
private recordingButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
recordingButton$: Observable<boolean>;
private toolbarViewRecordingsButton = <BehaviorSubject<boolean>>new BehaviorSubject(false);
toolbarViewRecordingsButton$: Observable<boolean>;
private broadcastingButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
broadcastingButton$: Observable<boolean>;
private recordingActivity = <BehaviorSubject<boolean>>new BehaviorSubject(true);
@ -103,6 +105,14 @@ export class OpenViduComponentsConfigService {
private broadcastingActivity = <BehaviorSubject<boolean>>new BehaviorSubject(true);
broadcastingActivity$: Observable<boolean>;
// Recording activity configuration
private recordingActivityReadOnly = <BehaviorSubject<boolean>>new BehaviorSubject(false);
recordingActivityReadOnly$: Observable<boolean>;
private recordingActivityShowControls = <BehaviorSubject<{ play?: boolean; download?: boolean; delete?: boolean }>>(
new BehaviorSubject({ play: true, download: true, delete: true })
);
recordingActivityShowControls$: Observable<{ play?: boolean; download?: boolean; delete?: boolean; externalView?: boolean }>;
// Admin
private adminRecordingsList: BehaviorSubject<RecordingInfo[]> = new BehaviorSubject(<RecordingInfo[]>[]);
adminRecordingsList$: Observable<RecordingInfo[]>;
@ -142,6 +152,7 @@ export class OpenViduComponentsConfigService {
this.displayLogo$ = this.displayLogo.asObservable();
this.brandingLogo$ = this.brandingLogo.asObservable();
this.recordingButton$ = this.recordingButton.asObservable();
this.toolbarViewRecordingsButton$ = this.toolbarViewRecordingsButton.asObservable();
this.broadcastingButton$ = this.broadcastingButton.asObservable();
this.toolbarSettingsButton$ = this.toolbarSettingsButton.asObservable();
this.captionsButton$ = this.captionsButton.asObservable();
@ -154,6 +165,8 @@ export class OpenViduComponentsConfigService {
this.participantItemMuteButton$ = this.participantItemMuteButton.asObservable();
// Recording activity observables
this.recordingActivity$ = this.recordingActivity.asObservable();
this.recordingActivityReadOnly$ = this.recordingActivityReadOnly.asObservable();
this.recordingActivityShowControls$ = this.recordingActivityShowControls.asObservable();
// Broadcasting activity
this.broadcastingActivity$ = this.broadcastingActivity.asObservable();
// Admin dashboard
@ -357,6 +370,18 @@ export class OpenViduComponentsConfigService {
return this.recordingButton.getValue();
}
setToolbarViewRecordingsButton(toolbarViewRecordingsButton: boolean) {
this.toolbarViewRecordingsButton.next(toolbarViewRecordingsButton);
}
getToolbarViewRecordingsButton(): boolean {
return this.toolbarViewRecordingsButton.getValue();
}
showToolbarViewRecordingsButton(): boolean {
return this.getToolbarViewRecordingsButton();
}
setBroadcastingButton(broadcastingButton: boolean) {
this.broadcastingButton.next(broadcastingButton);
}
@ -468,4 +493,22 @@ export class OpenViduComponentsConfigService {
setLayoutRemoteParticipants(participants: ParticipantModel[] | undefined) {
this.layoutRemoteParticipants.next(participants);
}
// Recording Activity Configuration
setRecordingActivityReadOnly(readOnly: boolean) {
this.recordingActivityReadOnly.next(readOnly);
}
isRecordingActivityReadOnly(): boolean {
return this.recordingActivityReadOnly.getValue();
}
setRecordingActivityShowControls(controls: { play?: boolean; download?: boolean; delete?: boolean }) {
this.recordingActivityShowControls.next(controls);
}
getRecordingActivityShowControls(): { play?: boolean; download?: boolean; delete?: boolean } {
return this.recordingActivityShowControls.getValue();
}
}