mirror of https://github.com/OpenVidu/openvidu.git
ov-components: implement read-only mode and customizable controls for recording activity
parent
98c7e3f751
commit
b659400c88
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,53 +59,85 @@
|
|||
|
||||
<!-- 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 -->
|
||||
<div class="item recording-action-buttons">
|
||||
<!-- Stop recording button -->
|
||||
<button *ngIf="recordingAlive" mat-flat-button id="stop-recording-btn" (click)="stopRecording()">
|
||||
<span>{{ 'TOOLBAR.STOP_RECORDING' | translate }}</span>
|
||||
</button>
|
||||
|
||||
<!-- Start recording button -->
|
||||
<div
|
||||
*ngIf="recordingStatus === recStatusEnum.STOPPED"
|
||||
[matTooltip]="!hasRoomTracksPublished ? ('PANEL.RECORDING.NO_TRACKS_PUBLISHED' | translate) : ''"
|
||||
[matTooltipDisabled]="hasRoomTracksPublished"
|
||||
class="start-recording-button-container"
|
||||
>
|
||||
<button
|
||||
[disabled]="!hasRoomTracksPublished"
|
||||
[ngClass]="{ 'disable-recording-btn': !hasRoomTracksPublished }"
|
||||
mat-flat-button
|
||||
id="start-recording-btn"
|
||||
(click)="startRecording()"
|
||||
>
|
||||
<span>{{ 'TOOLBAR.START_RECORDING' | translate }}</span>
|
||||
@if (!isReadOnlyMode) {
|
||||
<div class="item recording-action-buttons">
|
||||
<!-- Stop recording button -->
|
||||
<button *ngIf="recordingAlive" mat-flat-button id="stop-recording-btn" (click)="stopRecording()">
|
||||
<span>{{ 'TOOLBAR.STOP_RECORDING' | translate }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Recording status messages -->
|
||||
<div class="recording-status-messages">
|
||||
<span *ngIf="recordingStatus === recStatusEnum.STARTING" class="recording-message">
|
||||
{{ 'PANEL.RECORDING.STARTING' | translate }}
|
||||
</span>
|
||||
|
||||
<span *ngIf="recordingStatus === recStatusEnum.STOPPING" class="recording-message">
|
||||
{{ 'PANEL.RECORDING.STOPPING' | translate }}
|
||||
</span>
|
||||
|
||||
<div *ngIf="recordingStatus === recStatusEnum.FAILED" class="recording-error-container">
|
||||
<span class="recording-error">Message: {{ recordingError }}</span>
|
||||
<button mat-flat-button id="reset-recording-status-btn" (click)="resetStatus()">
|
||||
<span>{{ 'PANEL.RECORDING.ACCEPT' | translate }}</span>
|
||||
<!-- Start recording button -->
|
||||
<div
|
||||
*ngIf="recordingStatus === recStatusEnum.STOPPED"
|
||||
[matTooltip]="!hasRoomTracksPublished ? ('PANEL.RECORDING.NO_TRACKS_PUBLISHED' | translate) : ''"
|
||||
[matTooltipDisabled]="hasRoomTracksPublished"
|
||||
class="start-recording-button-container"
|
||||
>
|
||||
<button
|
||||
[disabled]="!hasRoomTracksPublished"
|
||||
[ngClass]="{ 'disable-recording-btn': !hasRoomTracksPublished }"
|
||||
mat-flat-button
|
||||
id="start-recording-btn"
|
||||
(click)="startRecording()"
|
||||
>
|
||||
<span>{{ 'TOOLBAR.START_RECORDING' | translate }}</span>
|
||||
</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">
|
||||
{{ 'PANEL.RECORDING.STARTING' | translate }}
|
||||
</span>
|
||||
|
||||
<span *ngIf="recordingStatus === recStatusEnum.STOPPING" class="recording-message">
|
||||
{{ 'PANEL.RECORDING.STOPPING' | translate }}
|
||||
</span>
|
||||
|
||||
<div *ngIf="recordingStatus === recStatusEnum.FAILED" class="recording-error-container">
|
||||
<span class="recording-error">Message: {{ recordingError }}</span>
|
||||
<button mat-flat-button id="reset-recording-status-btn" (click)="resetStatus()">
|
||||
<span>{{ 'PANEL.RECORDING.ACCEPT' | translate }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</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,37 +174,72 @@
|
|||
</div>
|
||||
|
||||
<!-- Recording action buttons -->
|
||||
<div *ngIf="recording.status !== recStatusEnum.STARTED" id="recording-action-buttons" class="recording-actions">
|
||||
<button
|
||||
mat-icon-button
|
||||
(click)="play(recording)"
|
||||
id="play-recording-btn"
|
||||
matTooltip="{{ 'PANEL.RECORDING.PLAY' | translate }}"
|
||||
class="action-button play-button"
|
||||
>
|
||||
<mat-icon>play_arrow</mat-icon>
|
||||
</button>
|
||||
@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"
|
||||
matTooltip="{{ 'PANEL.RECORDING.PLAY' | translate }}"
|
||||
class="action-button play-button"
|
||||
>
|
||||
<mat-icon>play_arrow</mat-icon>
|
||||
</button>
|
||||
|
||||
<button
|
||||
mat-icon-button
|
||||
(click)="download(recording)"
|
||||
id="download-recording-btn"
|
||||
matTooltip="{{ 'PANEL.RECORDING.DOWNLOAD' | translate }}"
|
||||
class="action-button download-button"
|
||||
>
|
||||
<mat-icon>download</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
|
||||
mat-icon-button
|
||||
(click)="deleteRecording(recording)"
|
||||
id="delete-recording-btn"
|
||||
matTooltip="{{ 'PANEL.RECORDING.DELETE' | translate }}"
|
||||
class="action-button delete-button"
|
||||
>
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
*ngIf="showControls.download"
|
||||
mat-icon-button
|
||||
(click)="download(recording)"
|
||||
id="download-recording-btn"
|
||||
matTooltip="{{ 'PANEL.RECORDING.DOWNLOAD' | translate }}"
|
||||
class="action-button download-button"
|
||||
>
|
||||
<mat-icon>download</mat-icon>
|
||||
</button>
|
||||
|
||||
<button
|
||||
*ngIf="showControls.delete"
|
||||
mat-icon-button
|
||||
(click)="deleteRecording(recording)"
|
||||
id="delete-recording-btn"
|
||||
matTooltip="{{ 'PANEL.RECORDING.DELETE' | translate }}"
|
||||
class="action-button delete-button"
|
||||
>
|
||||
<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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,17 +618,19 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
}
|
||||
|
||||
private subscribeToFullscreenChanged() {
|
||||
fromEvent(document, 'fullscreenchange').pipe(takeUntil(this.destroy$)).subscribe(() => {
|
||||
const isFullscreen = Boolean(document.fullscreenElement);
|
||||
if (isFullscreen) {
|
||||
this.cdkOverlayService.setSelector('#session-container');
|
||||
} else {
|
||||
this.cdkOverlayService.setSelector('body');
|
||||
}
|
||||
this.isFullscreenActive = isFullscreen;
|
||||
this.onFullscreenEnabledChanged.emit(this.isFullscreenActive);
|
||||
this.cd.detectChanges();
|
||||
});
|
||||
fromEvent(document, 'fullscreenchange')
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(() => {
|
||||
const isFullscreen = Boolean(document.fullscreenElement);
|
||||
if (isFullscreen) {
|
||||
this.cdkOverlayService.setSelector('#session-container');
|
||||
} else {
|
||||
this.cdkOverlayService.setSelector('body');
|
||||
}
|
||||
this.isFullscreenActive = isFullscreen;
|
||||
this.onFullscreenEnabledChanged.emit(this.isFullscreenActive);
|
||||
this.cd.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
private subscribeToMenuToggling() {
|
||||
|
@ -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() {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,91 +633,80 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
|
|||
}
|
||||
|
||||
private subscribeToVideconferenceDirectives() {
|
||||
this.libService.token$
|
||||
.pipe(
|
||||
skip(1),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe((token: string) => {
|
||||
try {
|
||||
if (!token) {
|
||||
this.log.e('Token is empty');
|
||||
return;
|
||||
}
|
||||
|
||||
const livekitUrl = this.libService.getLivekitUrl();
|
||||
this.openviduService.initializeAndSetToken(token, livekitUrl);
|
||||
this.log.d('Token has been successfully set. Room is ready to join');
|
||||
this.isRoomReady = true;
|
||||
this.showPrejoin = false;
|
||||
} catch (error) {
|
||||
this.log.e('Error trying to set token', error);
|
||||
this._tokenError = error;
|
||||
this.libService.token$.pipe(skip(1), takeUntil(this.destroy$)).subscribe((token: string) => {
|
||||
try {
|
||||
if (!token) {
|
||||
this.log.e('Token is empty');
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
this.libService.tokenError$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((error: any) => {
|
||||
if (!error) return;
|
||||
|
||||
this.log.e('Token error received', error);
|
||||
const livekitUrl = this.libService.getLivekitUrl();
|
||||
this.openviduService.initializeAndSetToken(token, livekitUrl);
|
||||
this.log.d('Token has been successfully set. Room is ready to join');
|
||||
this.isRoomReady = true;
|
||||
this.showPrejoin = false;
|
||||
} catch (error) {
|
||||
this.log.e('Error trying to set token', error);
|
||||
this._tokenError = error;
|
||||
}
|
||||
});
|
||||
|
||||
if (!this.showPrejoin) {
|
||||
this.actionService.openDialog(error.name, error.message, false);
|
||||
}
|
||||
});
|
||||
this.libService.tokenError$.pipe(takeUntil(this.destroy$)).subscribe((error: any) => {
|
||||
if (!error) return;
|
||||
|
||||
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
|
||||
this.log.e('Token error received', error);
|
||||
this._tokenError = error;
|
||||
|
||||
// Ensure we have a participant name before proceeding with the join
|
||||
this.log.d('Prejoin page is hidden, checking participant name');
|
||||
// Check if we have a participant name already
|
||||
if (this.latestParticipantName) {
|
||||
// We have a name, proceed immediately
|
||||
this._onReadyToJoin();
|
||||
} else {
|
||||
// No name yet - set up a one-time subscription to wait for it
|
||||
this.libService.participantName$
|
||||
.pipe(
|
||||
filter((name) => !!name),
|
||||
take(1),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe(() => {
|
||||
// Now we have the name in latestParticipantName
|
||||
this._onReadyToJoin();
|
||||
});
|
||||
// Add safety timeout in case name never arrives
|
||||
setTimeout(() => {
|
||||
if (!this.latestParticipantName) {
|
||||
this.log.w('No participant name received after timeout, proceeding anyway');
|
||||
const storedName = this.storageSrv.getParticipantName();
|
||||
if (storedName) {
|
||||
this.latestParticipantName = storedName;
|
||||
this.libService.setParticipantName(storedName);
|
||||
}
|
||||
this._onReadyToJoin();
|
||||
if (!this.showPrejoin) {
|
||||
this.actionService.openDialog(error.name, error.message, false);
|
||||
}
|
||||
});
|
||||
|
||||
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
|
||||
|
||||
// Ensure we have a participant name before proceeding with the join
|
||||
this.log.d('Prejoin page is hidden, checking participant name');
|
||||
// Check if we have a participant name already
|
||||
if (this.latestParticipantName) {
|
||||
// We have a name, proceed immediately
|
||||
this._onReadyToJoin();
|
||||
} else {
|
||||
// No name yet - set up a one-time subscription to wait for it
|
||||
this.libService.participantName$
|
||||
.pipe(
|
||||
filter((name) => !!name),
|
||||
take(1),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe(() => {
|
||||
// Now we have the name in latestParticipantName
|
||||
this._onReadyToJoin();
|
||||
});
|
||||
// Add safety timeout in case name never arrives
|
||||
setTimeout(() => {
|
||||
if (!this.latestParticipantName) {
|
||||
this.log.w('No participant name received after timeout, proceeding anyway');
|
||||
const storedName = this.storageSrv.getParticipantName();
|
||||
if (storedName) {
|
||||
this.latestParticipantName = storedName;
|
||||
this.libService.setParticipantName(storedName);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
this._onReadyToJoin();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
// this.cd.markForCheck();
|
||||
});
|
||||
}
|
||||
// this.cd.markForCheck();
|
||||
});
|
||||
|
||||
this.libService.participantName$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((name: string) => {
|
||||
if (name) {
|
||||
this.latestParticipantName = name;
|
||||
this.storageSrv.setParticipantName(name);
|
||||
}
|
||||
});
|
||||
this.libService.participantName$.pipe(takeUntil(this.destroy$)).subscribe((name: string) => {
|
||||
if (name) {
|
||||
this.latestParticipantName = name;
|
||||
this.storageSrv.setParticipantName(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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": "直播",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -56,7 +56,8 @@
|
|||
"PARTICIPANTS": "Participants",
|
||||
"CHAT": "Chat",
|
||||
"ACTIVITES": "Activités",
|
||||
"NO_TRACKS_PUBLISHED": "Partagez de l’audio ou de la vidéo pour commencer l’enregistrement."
|
||||
"NO_TRACKS_PUBLISHED": "Partagez de l’audio ou de la vidéo pour commencer l’enregistrement.",
|
||||
"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 l’audio ou de la vidéo pour commencer l’enregistrement."
|
||||
"NO_TRACKS_PUBLISHED": "Partagez de l’audio ou de la vidéo pour commencer l’enregistrement.",
|
||||
"NO_RECORDINGS_AVAILABLE": "Aucun enregistrement disponible pour le moment"
|
||||
},
|
||||
"STREAMING": {
|
||||
"TITLE": "Streaming",
|
||||
|
|
|
@ -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": "स्ट्रीमिंग",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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": "ストリーミング",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue