openvidu-components: Added virtual background and recording features

Added background effects GUI
WIP: Added  activities panel
pull/722/head
csantosm 2022-04-25 16:17:32 +02:00
parent 1655335aa7
commit 570709adc2
37 changed files with 1261 additions and 121 deletions

View File

@ -0,0 +1,79 @@
#activities-container {
margin: 20px;
background-color: var(--ov-panel-background);
border-radius: var(--ov-panel-radius);
max-height: calc(100% - 40px);
min-height: calc(100% - 40px);
}
.header-container {
padding: 10px;
display: flex;
}
.header-container h3 {
margin-left: 5px;
margin-top: auto;
margin-bottom: auto;
font-weight: bold;
}
.header-container button {
margin-left: auto;
border-radius: var(--ov-buttons-radius);
}
.activities-body-container {
display: block !important;
overflow-y: auto;
overflow-x: hidden;
padding: 10px;
}
.activity-icon {
display: inherit;
background-color: var(--ov-light-color);
border-radius: var(--ov-panel-radius);
}
.activity-icon mat-icon {
margin: auto;
}
.activity-subtitle {
font-style: italic;
font-size: 11px !important;
}
.activity-title {
font-weight: bold !important;
}
.activity-action-buttons{
align-self: flex-start;
margin-top: 15px;
font-weight: 600;
}
::ng-deep .mat-list-text {
padding-left: 10px !important;
}
::ng-deep .mat-expansion-panel-header {
padding: 0px 10px !important;
}
::ng-deep .mat-list-base .mat-list-item .mat-list-item-content, .mat-list-base .mat-list-option .mat-list-item-content {
padding: 0px !important;
}
::ng-deep mat-expansion-panel .mat-expansion-panel-body {
padding: 0px !important;
}
::ng-deep .mat-expansion-panel-header-description {
flex-grow: 0 !important;
}
::ng-deep .mat-expansion-panel {
box-shadow: none !important;
}

View File

@ -0,0 +1,14 @@
<div class="panel" id="activities-container" fxLayout="column" fxLayoutAlign="space-evenly none">
<div class="header-container" fxFlex="55px" fxLayoutAlign="start center">
<h3>Activities</h3>
<button mat-icon-button matTooltip="Close" (click)="close()">
<mat-icon>close</mat-icon>
</button>
</div>
<div class="activities-body-container" fxFlex="75%" fxLayoutAlign="space-evenly none">
<mat-accordion>
<ov-recording-activity></ov-recording-activity>
</mat-accordion>
</div>
</div>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivitiesPanelComponent } from './activities-panel.component';
describe('ActivitiesPanelComponent', () => {
let component: ActivitiesPanelComponent;
let fixture: ComponentFixture<ActivitiesPanelComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ActivitiesPanelComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ActivitiesPanelComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,20 @@
import { Component, OnInit } from '@angular/core';
import { PanelType } from '../../../models/panel.model';
import { PanelService } from '../../../services/panel/panel.service';
@Component({
selector: 'ov-activities-panel',
templateUrl: './activities-panel.component.html',
styleUrls: ['./activities-panel.component.css']
})
export class ActivitiesPanelComponent implements OnInit {
constructor(private panelService: PanelService) {}
ngOnInit(): void {}
ngOnDestroy() {}
close() {
this.panelService.togglePanel(PanelType.ACTIVITIES);
}
}

View File

@ -0,0 +1,37 @@
#recording-status {
color: var(--ov-text-color);
display: inline;
padding: 3px;
font-size: 11px;
border-radius: var(--ov-panel-radius);
}
.started {
background-color: #005704;
}
.stopped {
background-color: var(--ov-light-color);
color: var(--ov-panel-text-color) !important;
}
#recording-file-item {
padding: 0px 16px;
}
.recording-action-buttons{
margin: auto;
}
#start-recording-btn {
background-color: var(--ov-tertiary-color);
color: var(--ov-text-color);
}
#stop-recording-btn {
background-color: var(--ov-warn-color);
color: var(--ov-text-color);
}
mat-expansion-panel {
margin: 0pc 0px 15px 0px;
}

View File

@ -0,0 +1,62 @@
<mat-expansion-panel (opened)="panelOpened()" (closed)="panelClosed()">
<mat-expansion-panel-header>
<mat-list>
<mat-list-item>
<div matListAvatar class="activity-icon">
<mat-icon >video_camera_front</mat-icon>
</div>
<h3 matLine class="activity-title">Recording</h3>
<p matLine class="activity-subtitle">Record your meeting for posterity</p>
<div class="activity-action-buttons">
<div
id="recording-status"
[ngClass]="{
started: recordingStatus === recStatusEnum.STARTED,
stopped: recordingStatus === recStatusEnum.STOPPED
}"
>
<span>{{ recordingStatus }}</span>
</div>
</div>
</mat-list-item>
</mat-list>
</mat-expansion-panel-header>
<div *ngIf="opened">
<mat-list *ngIf="isSessionCreator" id="recording-file-item">
<mat-list-item>
<mat-icon *ngIf="recording" matListAvatar class="participant-avatar"> video_file </mat-icon>
<h3 *ngIf="recording" matLine class="participant-nickname">{{ recording.name }}</h3>
<p *ngIf="recording" matLine class="participant-subtitle">
{{ recording.id }}
</p>
<div class="recording-action-buttons">
<button
*ngIf="recording"
mat-stroked-button
id="stop-recording-btn"
matTooltip="Stop recording"
(click)="stopRecording()"
>
Stop recording
</button>
<button
*ngIf="recordingStatus === recStatusEnum.STOPPED"
mat-stroked-button
id="start-recording-btn"
matTooltip="Start recording"
(click)="startRecording()"
>
Start recording
</button>
</div>
</mat-list-item>
<hr />
</mat-list>
<!-- Visalizador de recordings -->
<div>NO RECORDINGS FOUND</div>
</div>
</mat-expansion-panel>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RecordingActivityComponent } from './recording-activity.component';
describe('RecordingActivityComponent', () => {
let component: RecordingActivityComponent;
let fixture: ComponentFixture<RecordingActivityComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ RecordingActivityComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(RecordingActivityComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,87 @@
import { Component, OnInit, Output, EventEmitter } from '@angular/core';
import { Subscription } from 'rxjs';
import { RecordingStatus } from '../../../../models/recording.model';
import { RecordingService, RecordingInfo } from '../../../../services/recording/recording.service';
@Component({
selector: 'ov-recording-activity',
templateUrl: './recording-activity.component.html',
styleUrls: ['./recording-activity.component.css', '../activities-panel.component.css']
})
export class RecordingActivityComponent implements OnInit {
/**
* Provides event notifications that fire when start recording button has been clicked.
*/
@Output() startRecordingClicked: EventEmitter<void> = new EventEmitter<void>();
/**
* Provides event notifications that fire when stop recording button has been clicked.
*/
@Output() stopRecordingClicked: EventEmitter<void> = new EventEmitter<void>();
recordingStatus: RecordingStatus = RecordingStatus.STOPPED;
recStatusEnum = RecordingStatus;
isSessionCreator = true;
recording: RecordingInfo;
recordingSubscription: Subscription;
opened: boolean = false;
constructor(private recordingService: RecordingService) {}
ngOnInit(): void {
this.subscribeToRecordingStatus();
}
ngOnDestroy() {
if (this.recordingSubscription) this.recordingSubscription.unsubscribe();
}
panelOpened() {
//TODO EMITIR EVENTO
this.opened = true;
}
panelClosed() {
//TODO EMITIR EVENTO
this.opened = false;
}
startRecording() {
console.log('START RECORDING');
this.startRecordingClicked.emit();
//TODO: REMOVE
const info: RecordingInfo = {
status: RecordingStatus.STARTED,
id: '1',
name: 'akajo',
reason: null
};
this.recordingService.startRecording(<any>info);
}
stopRecording() {
console.log('STOP RECORDING');
this.stopRecordingClicked.emit();
//TODO: REMOVE
const info: RecordingInfo = {
status: RecordingStatus.STOPPED,
id: '1',
name: 'akajo',
reason: 'lalal'
};
this.recordingService.stopRecording(<any>info);
}
subscribeToRecordingStatus() {
this.recordingSubscription = this.recordingService.recordingStatusObs.subscribe((info: RecordingInfo) => {
if (info) {
this.recordingStatus = info.status;
if (info.status === RecordingStatus.STARTED) {
this.recording = info;
} else {
this.recording = null;
}
}
});
}
}

View File

@ -0,0 +1,47 @@
#background-effects-container {
margin: 20px;
background-color: var(--ov-panel-background);
border-radius: var(--ov-panel-radius);
max-height: calc(100% - 40px);
min-height: calc(100% - 40px);
}
.header-container {
padding: 10px;
display: flex;
}
.header-container h3 {
margin-left: 5px;
margin-top: auto;
margin-bottom: auto;
font-weight: bold;
}
.header-container button {
margin-left: auto;
border-radius: var(--ov-buttons-radius);
}
.effects-container {
display: block !important;
overflow-y: auto;
overflow-x: hidden;
padding: 10px;
}
.effect-button {
margin: 5px;
border-radius: var(--ov-panel-radius);
background-color: var(--ov-light-color);
width: 50px;
height: 50px;
}
.active-effect-btn {
border: 2px solid;
}
#hard-blur-btn .mat-icon {
font-weight: bold !important;
}

View File

@ -0,0 +1,45 @@
<div class="panel" id="background-effects-container" fxLayout="column" fxLayoutAlign="space-evenly none">
<div class="header-container" fxFlex="55px" fxLayoutAlign="start center">
<h3>Background effects</h3>
<button mat-icon-button matTooltip="Close" (click)="close()">
<mat-icon>close</mat-icon>
</button>
</div>
<div class="effects-container" fxFlex="100%" fxLayoutAlign="space-evenly none">
<div>
<h4>Blurred background</h4>
<div>
<button
mat-icon-button
class="effect-button"
[class.active-effect-btn]="effectActive === 'no_effect'"
(click)="effectActive = 'no_effect'"
>
<mat-icon matTooltip="No background effect">block</mat-icon>
</button>
<button
mat-icon-button
class="effect-button"
[class.active-effect-btn]="effectActive === 'soft_blur'"
(click)="effectActive = 'soft_blur'"
>
<mat-icon matTooltip="Soft blur effect">blur_on</mat-icon>
</button>
<button
mat-icon-button
class="effect-button"
id="hard-blur-btn"
[class.active-effect-btn]="effectActive === 'hard_blur'"
(click)="effectActive = 'hard_blur'"
>
<mat-icon matTooltip="Hard blur effect">blur_on</mat-icon>
</button>
</div>
</div>
<hr />
<div>
<h4>Background images</h4>
</div>
</div>
</div>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BackgroundEffectsPanelComponent } from './background-effects-panel.component';
describe('BackgroundEffectsPanelComponent', () => {
let component: BackgroundEffectsPanelComponent;
let fixture: ComponentFixture<BackgroundEffectsPanelComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ BackgroundEffectsPanelComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(BackgroundEffectsPanelComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,24 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { PanelType } from '../../../models/panel.model';
import { PanelService } from '../../../services/panel/panel.service';
@Component({
selector: 'ov-background-effects-panel',
templateUrl: './background-effects-panel.component.html',
styleUrls: ['./background-effects-panel.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BackgroundEffectsPanelComponent implements OnInit {
effectActive: string;
constructor(private panelService: PanelService) { }
ngOnInit(): void {
}
close() {
this.panelService.togglePanel(PanelType.BACKGROUND_EFFECTS);
}
}

View File

@ -9,9 +9,19 @@
<ng-container *ngTemplateOutlet="participantsPanelTemplate"></ng-container>
</ng-container>
<!-- Background effects panel -->
<ng-container *ngIf="isBackgroundEffectsPanelOpened">
<ng-container *ngTemplateOutlet="backgroundEffectsPanelTemplate"></ng-container>
</ng-container>
<!-- Activities panel -->
<ng-container *ngIf="isActivitiesPanelOpened">
<ng-container *ngTemplateOutlet="activitiesPanelTemplate"></ng-container>
</ng-container>
<!-- <ov-activities-panel *ngIf="isActivitiesPanelOpened"></ov-activities-panel> -->
<!-- External additional panels -->
<ng-container *ngIf="additionalPanelsTemplate && isExternalPanelOpened">
<ng-container *ngTemplateOutlet="additionalPanelsTemplate"></ng-container>
</ng-container>

View File

@ -1,6 +1,12 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, OnInit, TemplateRef } from '@angular/core';
import { skip, Subscription } from 'rxjs';
import { ChatPanelDirective, AdditionalPanelsDirective, ParticipantsPanelDirective } from '../../directives/template/openvidu-angular.directive';
import {
ChatPanelDirective,
AdditionalPanelsDirective,
ParticipantsPanelDirective,
BackgroundEffectsPanelDirective,
ActivitiesPanelDirective
} from '../../directives/template/openvidu-angular.directive';
import { PanelType } from '../../models/panel.model';
import { PanelService } from '../../services/panel/panel.service';
@ -53,6 +59,15 @@ export class PanelComponent implements OnInit {
*/
@ContentChild('participantsPanel', { read: TemplateRef }) participantsPanelTemplate: TemplateRef<any>;
/**
* @ignore
*/
@ContentChild('backgroundEffectsPanel', { read: TemplateRef }) backgroundEffectsPanelTemplate: TemplateRef<any>;
/**
* @ignore
*/
@ContentChild('activitiesPanel', { read: TemplateRef }) activitiesPanelTemplate: TemplateRef<any>;
/**
* @ignore
*/
@ -72,6 +87,24 @@ export class PanelComponent implements OnInit {
}
}
@ContentChild(BackgroundEffectsPanelDirective)
set externalBackgroundEffectsPanel(externalBackgroundEffectsPanel: BackgroundEffectsPanelDirective) {
// This directive will has value only when BACKGROUND EFFECTS PANEL component tagged with '*ovBackgroundEffectsPanel'
// is inside of the PANEL component tagged with '*ovPanel'
if (externalBackgroundEffectsPanel) {
this.backgroundEffectsPanelTemplate = externalBackgroundEffectsPanel.template;
}
}
@ContentChild(ActivitiesPanelDirective)
set externalActivitiesPanel(externalActivitiesPanel: ActivitiesPanelDirective) {
// This directive will has value only when ACTIVITIES PANEL component tagged with '*ovActivitiesPanel'
// is inside of the PANEL component tagged with '*ovPanel'
if (externalActivitiesPanel) {
this.activitiesPanelTemplate = externalActivitiesPanel.template;
}
}
@ContentChild(ChatPanelDirective)
set externalChatPanel(externalChatPanel: ChatPanelDirective) {
// This directive will has value only when CHAT PANEL component tagged with '*ovChatPanel'
@ -92,6 +125,8 @@ export class PanelComponent implements OnInit {
isParticipantsPanelOpened: boolean;
isChatPanelOpened: boolean;
isBackgroundEffectsPanelOpened: boolean;
isActivitiesPanelOpened: boolean;
/**
* @internal
@ -115,9 +150,13 @@ export class PanelComponent implements OnInit {
}
private subscribeToPanelToggling() {
this.panelSubscription = this.panelService.panelOpenedObs.pipe(skip(1)).subscribe((ev: { opened: boolean; type?: PanelType | string }) => {
this.panelSubscription = this.panelService.panelOpenedObs
.pipe(skip(1))
.subscribe((ev: { opened: boolean; type?: PanelType | string }) => {
this.isChatPanelOpened = ev.opened && ev.type === PanelType.CHAT;
this.isParticipantsPanelOpened = ev.opened && ev.type === PanelType.PARTICIPANTS;
this.isBackgroundEffectsPanelOpened = ev.opened && ev.type === PanelType.BACKGROUND_EFFECTS;
this.isActivitiesPanelOpened = ev.opened && ev.type === PanelType.ACTIVITIES;
this.isExternalPanelOpened = ev.opened && ev.type !== PanelType.PARTICIPANTS && ev.type !== PanelType.CHAT;
this.cd.markForCheck();
});

View File

@ -16,11 +16,28 @@ hr {
margin: 0;
}
ov-layout {
height: -webkit-fill-available;
height: -moz-available;
width: -webkit-fill-available;
width: -moz-available;
#prejoin-container ::ng-deep .sidenav-container {
padding-top: 0px !important;
}
#prejoin-container ::ng-deep #background-effects-container {
margin: 0px !important;
max-height: 100% !important;
height: 100% !important;
}
#prejoin-container ::ng-deep .mat-drawer-container, #prejoin-container ::ng-deep .sidenav-menu {
background-color: var(--ov-light-color) !important;
}
#prejoin-container ::ng-deep .sidenav-menu {
width: 320px;
}
#background-effects-btn {
position: absolute;
z-index: 1;
background-color: var(--ov-secondary-color);
bottom: 5px;
right: 5px;
}
.media-panel {

View File

@ -1,10 +1,30 @@
<div class="container" id="prejoin-container" fxLayout.gt-sm="row" fxLayout.lt-md="column">
<div fxFlex.gt-sm="65%" fxFlex.lt-md="55%" fxLayoutAlign="center center" id="layout-container">
<ov-session [usedInPrejoinPage]="true">
<ng-template #panel *ngIf="!isMinimal && showBackgroundEffectsButton">
<ov-panel>
<ng-template #backgroundEffectsPanel>
<ov-background-effects-panel></ov-background-effects-panel>
</ng-template>
</ov-panel>
</ng-template>
<ng-template #layout>
<ov-layout>
<ng-template #stream let-stream>
<button
*ngIf="!isMinimal && !isOpenViduCE && showBackgroundEffectsButton"
mat-icon-button
id="background-effects-btn"
(click)="toggleBackgroundEffects()"
>
<mat-icon>auto_awesome</mat-icon>
</button>
<ov-stream [stream]="stream" [displayParticipantName]="false" [settingsButton]="false"></ov-stream>
</ng-template>
</ov-layout>
</ng-template>
</ov-session>
</div>
<div fxFlex.gt-sm="35%" fxFlex.lt-md="45%" fxLayoutAlign="center center" class="media-panel" *ngIf="localParticipant">
<div fxLayout="column" fxLayoutGap="10px" class="media-panel-container">
@ -18,7 +38,14 @@
</button>
<mat-form-field appearance="standard">
<mat-label>Nickname</mat-label>
<input matInput type="text" maxlength="20" [(ngModel)]="nickname" (change)="updateNickname()" autocomplete="off" />
<input
matInput
type="text"
maxlength="20"
[(ngModel)]="nickname"
(change)="updateNickname()"
autocomplete="off"
/>
</mat-form-field>
</div>
<!-- <mat-button-toggle-group style="border-radius: 20px">
@ -54,7 +81,11 @@
</button>
<mat-form-field>
<mat-label>Video devices</mat-label>
<mat-select [disabled]="isVideoMuted || !hasVideoDevices" [value]="cameraSelected?.device" (selectionChange)="onCameraSelected($event)">
<mat-select
[disabled]="isVideoMuted || !hasVideoDevices"
[value]="cameraSelected?.device"
(selectionChange)="onCameraSelected($event)"
>
<mat-option *ngFor="let camera of cameras" [value]="camera.device">
{{ camera.label }}
</mat-option>
@ -76,7 +107,11 @@
</button>
<mat-form-field>
<mat-label>Audio devices</mat-label>
<mat-select [disabled]="isAudioMuted || !hasAudioDevices" [value]="microphoneSelected?.device" (selectionChange)="onMicrophoneSelected($event)">
<mat-select
[disabled]="isAudioMuted || !hasAudioDevices"
[value]="microphoneSelected?.device"
(selectionChange)="onMicrophoneSelected($event)"
>
<mat-option *ngFor="let microphone of microphones" [value]="microphone.device">
{{ microphone.label }}
</mat-option>

View File

@ -1,13 +1,23 @@
import { Component, HostListener, OnDestroy, OnInit, Output, EventEmitter } from '@angular/core';
import {
Component,
HostListener,
OnDestroy,
OnInit,
Output,
EventEmitter
} from '@angular/core';
import { PublisherProperties } from 'openvidu-browser';
import { Subscription } from 'rxjs';
import { CustomDevice } from '../../models/device.model';
import { ILogger } from '../../models/logger.model';
import { PanelType } from '../../models/panel.model';
import { ParticipantAbstractModel } from '../../models/participant.model';
import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service';
import { DeviceService } from '../../services/device/device.service';
import { LayoutService } from '../../services/layout/layout.service';
import { LoggerService } from '../../services/logger/logger.service';
import { OpenViduService } from '../../services/openvidu/openvidu.service';
import { PanelService } from '../../services/panel/panel.service';
import { ParticipantService } from '../../services/participant/participant.service';
import { StorageService } from '../../services/storage/storage.service';
@ -33,9 +43,21 @@ export class PreJoinComponent implements OnInit, OnDestroy {
hasAudioDevices: boolean;
isLoading = true;
nickname: string;
isOpenViduCE: boolean;
/**
* @ignore
*/
showBackgroundEffectsButton: boolean = true;
/**
* @ignore
*/
isMinimal: boolean = false;
private log: ILogger;
private localParticipantSubscription: Subscription;
private screenShareStateSubscription: Subscription;
private minimalSub: Subscription;
private backgroundEffectsButtonSub: Subscription;
@HostListener('window:resize')
sizeChange() {
@ -49,13 +71,17 @@ export class PreJoinComponent implements OnInit, OnDestroy {
private loggerSrv: LoggerService,
private openviduService: OpenViduService,
private participantService: ParticipantService,
protected panelService: PanelService,
private libService: OpenViduAngularConfigService,
private storageSrv: StorageService
) {
this.log = this.loggerSrv.get('PreJoinComponent');
}
ngOnInit() {
this.subscribeToPrejoinDirectives();
this.subscribeToLocalParticipantEvents();
this.isOpenViduCE = this.openviduService.isOpenViduCE();
this.windowSize = window.innerWidth;
this.hasVideoDevices = this.deviceSrv.hasVideoDeviceAvailable();
@ -79,6 +105,9 @@ export class PreJoinComponent implements OnInit, OnDestroy {
if (this.screenShareStateSubscription) {
this.screenShareStateSubscription.unsubscribe();
}
if (this.backgroundEffectsButtonSub) this.backgroundEffectsButtonSub.unsubscribe();
if (this.minimalSub) this.minimalSub.unsubscribe();
}
async onCameraSelected(event: any) {
@ -138,6 +167,10 @@ export class PreJoinComponent implements OnInit, OnDestroy {
this.onJoinButtonClicked.emit();
}
toggleBackgroundEffects() {
this.panelService.togglePanel(PanelType.BACKGROUND_EFFECTS);
}
private subscribeToLocalParticipantEvents() {
this.localParticipantSubscription = this.participantService.localParticipantObs.subscribe((p) => {
this.localParticipant = p;
@ -145,6 +178,17 @@ export class PreJoinComponent implements OnInit, OnDestroy {
});
}
private subscribeToPrejoinDirectives() {
this.minimalSub = this.libService.minimalObs.subscribe((value: boolean) => {
this.isMinimal = value;
// this.cd.markForCheck();
});
this.backgroundEffectsButtonSub = this.libService.backgroundEffectsButton.subscribe((value: boolean) => {
this.showBackgroundEffectsButton = value;
// this.cd.markForCheck();
});
}
//? After test in Chrome and Firefox, the devices always have labels.
//? It's not longer needed
// private handlePublisherSuccess(publisher: Publisher) {

View File

@ -1,5 +1,5 @@
<div id="session-container">
<mat-sidenav-container class="sidenav-container">
<mat-sidenav-container #videoContainer class="sidenav-container">
<mat-sidenav
#sidenav
mode="{{ sidenavMode }}"
@ -19,7 +19,7 @@
</mat-sidenav-content>
</mat-sidenav-container>
<div id="footer-container">
<div id="footer-container" *ngIf="toolbarTemplate">
<ng-container *ngTemplateOutlet="toolbarTemplate"></ng-container>
</div>
</div>

View File

@ -2,14 +2,16 @@ import {
ChangeDetectionStrategy,
Component,
ContentChild,
ElementRef,
EventEmitter,
HostListener,
Input,
OnInit,
Output,
TemplateRef,
ViewChild
} from '@angular/core';
import { Subscriber, Session, StreamEvent, StreamPropertyChangedEvent, SessionDisconnectedEvent, ConnectionEvent } from 'openvidu-browser';
import { Subscriber, Session, StreamEvent, StreamPropertyChangedEvent, SessionDisconnectedEvent, ConnectionEvent, RecordingEvent } from 'openvidu-browser';
import { VideoType } from '../../models/video-type.model';
import { ILogger } from '../../models/logger.model';
@ -27,7 +29,7 @@ import { LayoutService } from '../../services/layout/layout.service';
import { Subscription, skip } from 'rxjs';
import { PanelType } from '../../models/panel.model';
import { PanelService } from '../../services/panel/panel.service';
import { PlatformService } from '../../services/platform/platform.service';
import { RecordingService } from '../../services/recording/recording.service';
/**
* @internal
@ -44,6 +46,7 @@ export class SessionComponent implements OnInit {
@ContentChild('panel', { read: TemplateRef }) panelTemplate: TemplateRef<any>;
@ContentChild('layout', { read: TemplateRef }) layoutTemplate: TemplateRef<any>;
@Input() usedInPrejoinPage = false;
@Output() onSessionCreated = new EventEmitter<any>();
session: Session;
@ -69,7 +72,8 @@ export class SessionComponent implements OnInit {
protected chatService: ChatService,
protected tokenService: TokenService,
protected layoutService: LayoutService,
protected panelService: PanelService
protected panelService: PanelService,
private recordingService: RecordingService
) {
this.log = this.loggerSrv.get('SessionComponent');
}
@ -94,7 +98,19 @@ export class SessionComponent implements OnInit {
}, 0);
}
@ViewChild('videoContainer', {static:false, read: ElementRef })
set videoContainer(container: ElementRef) {
setTimeout(() => {
if (container && !this.toolbarTemplate) {
container.nativeElement.style.height = '100%';
container.nativeElement.style.minHeight = '100%';
this.layoutService.update();
}
}, 0);
}
async ngOnInit() {
if(!this.usedInPrejoinPage){
this.session = this.openviduService.getWebcamSession();
this.sessionScreen = this.openviduService.getScreenSession();
this.subscribeToConnectionCreatedAndDestroyed();
@ -104,6 +120,9 @@ export class SessionComponent implements OnInit {
this.subscribeToNicknameChanged();
this.chatService.subscribeToChat();
this.subscribeToReconnection();
// if(RecordingEnabled){
this.subscribeToRecordingEvents();
// }
this.onSessionCreated.emit(this.session);
await this.connectToSession();
@ -113,6 +132,7 @@ export class SessionComponent implements OnInit {
// this.openviduService.publishVideo(this.participantService.getMyCameraPublisher(), false);
// }
}
}
ngOnDestroy() {
// Reconnecting session is received in Firefox
@ -270,4 +290,14 @@ export class SessionComponent implements OnInit {
}
});
}
private subscribeToRecordingEvents() {
this.session.on('recordingStarted', (event: RecordingEvent) => {
this.recordingService.startRecording(event);
});
this.session.on('recordingStopped', (event: RecordingEvent) => {
this.recordingService.stopRecording(event);
});
}
}

View File

@ -49,10 +49,6 @@
top: 40px;
}
mat-error {
text-align: center;
color: #353535;
}
video {
-o-object-fit: cover;
@ -115,17 +111,6 @@
border-radius: var(--ov-video-radius);
}
mat-error,
::ng-deep .mat-focused .mat-form-field-label,
::ng-deep .mat-form-field-appearance-legacy .mat-form-field-label {
color: #cacaca !important;
}
::ng-deep .mat-form-field-appearance-legacy .mat-form-field-underline,
::ng-deep .mat-form-field.mat-focused .mat-form-field-ripple {
background-color: #cacaca !important;
}
input {
caret-color: #ffffff !important;
}

View File

@ -33,7 +33,8 @@
background-color: var(--ov-warn-color) !important;
}
.active-btn, ::ng-deep .active-btn {
.active-btn,
::ng-deep .active-btn {
background-color: var(--ov-tertiary-color) !important;
}
@ -41,7 +42,8 @@
font-size: 24px;
}
#media-buttons-container button, #menu-buttons-container button {
#media-buttons-container button,
#menu-buttons-container button {
border-radius: var(--ov-buttons-radius);
}
@ -60,6 +62,33 @@
height: fit-content;
padding: 0px 15px;
}
#session-info-container {
display: flex;
}
.colapsed {
flex-direction: column;
}
#recording-tag {
padding: 0 15px;
background-color: var(--ov-warn-color);
border-radius: var(--ov-panel-radius);
width: fit-content;
font-size: 12px;
text-align: center;
line-height: 20px;
margin: auto;
animation: blinker 1.5s linear infinite;
}
#recording-tag mat-icon {
font-size: 16px;
display: inline;
vertical-align: sub;
margin-right: 5px;
}
#point {
width: 10px;
@ -79,12 +108,10 @@
width: 60px !important;
}
.tooltipList {
white-space: pre;
}
#navChatButton .mat-badge-medium.mat-badge-overlap.mat-badge-before .mat-badge-content {
left: -17px;
}
@ -107,3 +134,11 @@
display: none;
}
}
::ng-deep .mat-menu-panel {
margin-bottom: 10px;
}
@keyframes blinker {
50% { opacity: 0.2; }
}

View File

@ -2,7 +2,13 @@
<div fxFlex="20%" fxLayoutAlign="start center" id="info-container">
<div>
<img *ngIf="!isMinimal && showLogo" id="branding-logo" src="assets/images/logo.png" ovLogo />
<div id="session-info-container" [class.colapsed]="isRecording">
<span id="session-name" *ngIf="!isMinimal && session && session.sessionId && showSessionName">{{ session.sessionId }}</span>
<div id="recording-tag" *ngIf="isRecording">
<mat-icon>radio_button_checked</mat-icon>
<span>REC</span>
</div>
</div>
</div>
</div>
<div fxFlex="60%" fxFlexOrder="2" fxLayoutAlign="center center" id="media-buttons-container">
@ -47,7 +53,7 @@
<button
mat-icon-button
id="fullscreen-btn"
*ngIf="!isMinimal && showFullscreenButton"
*ngIf="!isMinimal && showFullscreenButton && !showBackgroundEffectsButton"
(click)="toggleFullscreen()"
[disabled]="isConnectionLost"
[class.active-btn]="isFullscreenActive"
@ -56,6 +62,43 @@
<mat-icon *ngIf="!isFullscreenActive" matTooltip="Fullscreen">fullscreen</mat-icon>
</button>
<button
mat-icon-button
id="more-options-btn"
*ngIf="!isMinimal && !isOpenViduCE && showBackgroundEffectsButton"
[matMenuTriggerFor]="menu"
[disabled]="isConnectionLost"
>
<mat-icon matTooltip="More options">more_vert</mat-icon>
</button>
<mat-menu #menu="matMenu">
<!-- Fullscreen button -->
<button mat-menu-item id="fullscreen-btn" (click)="toggleFullscreen()">
<mat-icon *ngIf="!isFullscreenActive">fullscreen</mat-icon>
<span *ngIf="!isFullscreenActive">Fullscreen</span>
<mat-icon *ngIf="isFullscreenActive">fullscreen_exit</mat-icon>
<span *ngIf="isFullscreenActive">Exit fullscreen</span>
</button>
<!-- Recording button -->
<!-- <button
*ngIf="!isMinimal && showActivitiesPanelButton"
mat-menu-item
id="recording-btn"
(click)="toggleActivitiesPanel('recording')"
>
<mat-icon color="warn">radio_button_checked</mat-icon>
<span>Recording</span>
</button> -->
<!-- Virtual background button -->
<button *ngIf="!isMinimal && showBackgroundEffectsButton" mat-menu-item id="virtual-bg-btn" (click)="toggleBackgroundEffects()">
<mat-icon>auto_awesome</mat-icon>
<span>Background effects</span>
</button>
</mat-menu>
<!-- External additional buttons -->
<ng-container *ngIf="toolbarAdditionalButtonsTemplate">
<ng-container *ngTemplateOutlet="toolbarAdditionalButtonsTemplate"></ng-container>
@ -67,6 +110,19 @@
</button>
</div>
<div fxFlex="20%" fxFlexOrder="3" fxLayoutAlign="end center" id="menu-buttons-container">
<!-- Default activities button -->
<!-- <button
mat-icon-button
id="activities-panel-btn"
*ngIf="!isMinimal && showActivitiesPanelButton"
matTooltip="Activities"
(click)="toggleActivitiesPanel()"
[disabled]="isConnectionLost"
[class.active-btn]="isActivitiesOpened"
>
<mat-icon>category</mat-icon>
</button> -->
<!-- Default participants button -->
<button
mat-icon-button

View File

@ -8,7 +8,8 @@ import {
OnDestroy,
OnInit,
Output,
TemplateRef
TemplateRef,
ViewChild
} from '@angular/core';
import { skip, Subscription } from 'rxjs';
import { TokenService } from '../../services/token/token.service';
@ -32,6 +33,9 @@ import {
} from '../../directives/template/openvidu-angular.directive';
import { ParticipantAbstractModel } from '../../models/participant.model';
import { PlatformService } from '../../services/platform/platform.service';
import { MatMenuTrigger } from '@angular/material/menu';
import { RecordingInfo, RecordingService } from '../../services/recording/recording.service';
import { RecordingStatus } from '../../models/recording.model';
/**
*
@ -49,6 +53,7 @@ import { PlatformService } from '../../services/platform/platform.service';
* | :----------------------------: | :-------: | :---------------------------------------------: |
* | **screenshareButton** | `boolean` | {@link ToolbarScreenshareButtonDirective} |
* | **fullscreenButton** | `boolean` | {@link ToolbarFullscreenButtonDirective} |
* | **backgroundEffectsButton** | `boolean` | {@link ToolbarBackgroundEffectsButtonDirective} |
* | **leaveButton** | `boolean` | {@link ToolbarLeaveButtonDirective} |
* | **chatPanelButton** | `boolean` | {@link ToolbarChatPanelButtonDirective} |
* | **participantsPanelButton** | `boolean` | {@link ToolbarParticipantsPanelButtonDirective} |
@ -159,10 +164,21 @@ export class ToolbarComponent implements OnInit, OnDestroy {
*/
@Output() onParticipantsPanelButtonClicked: EventEmitter<void> = new EventEmitter<any>();
/**
* @internal
* TODO: WIP
* Provides event notifications that fire when background effects button has been clicked.
*/
// @Output() onBackgroundEffectsButtonClicked: EventEmitter<void> = new EventEmitter<any>();
/**
* Provides event notifications that fire when chat panel button has been clicked.
*/
@Output() onChatPanelButtonClicked: EventEmitter<void> = new EventEmitter<any>();
/**
* @ignore
*/
@ViewChild(MatMenuTrigger) public menuTrigger: MatMenuTrigger;
/**
* @ignore
@ -213,6 +229,11 @@ export class ToolbarComponent implements OnInit, OnDestroy {
*/
isParticipantsOpened: boolean = false;
/**
* @ignore
*/
isActivitiesOpened: boolean = false;
/**
* @ignore
*/
@ -225,6 +246,12 @@ export class ToolbarComponent implements OnInit, OnDestroy {
* @ignore
*/
showFullscreenButton: boolean = true;
/**
* @ignore
*/
showBackgroundEffectsButton: boolean = true;
/**
* @ignore
*/
@ -233,6 +260,11 @@ export class ToolbarComponent implements OnInit, OnDestroy {
* @ignore
*/
showParticipantsPanelButton: boolean = true;
/**
* @ignore
*/
showActivitiesPanelButton: boolean = true;
/**
* @ignore
*/
@ -246,6 +278,16 @@ export class ToolbarComponent implements OnInit, OnDestroy {
*/
showSessionName: boolean = true;
/**
* @ignore
*/
isRecording: boolean = false;
/**
* @ignore
*/
isOpenViduCE: boolean;
private log: ILogger;
private minimalSub: Subscription;
private panelTogglingSubscription: Subscription;
@ -253,7 +295,11 @@ export class ToolbarComponent implements OnInit, OnDestroy {
private localParticipantSubscription: Subscription;
private screenshareButtonSub: Subscription;
private fullscreenButtonSub: Subscription;
private backgroundEffectsButtonSub: Subscription;
private leaveButtonSub: Subscription;
private recordingSubscription: Subscription;
private activitiesPanelButtonSub: Subscription;
private participantsPanelButtonSub: Subscription;
private chatPanelButtonSub: Subscription;
private displayLogoSub: Subscription;
@ -275,7 +321,8 @@ export class ToolbarComponent implements OnInit, OnDestroy {
protected loggerSrv: LoggerService,
private cd: ChangeDetectorRef,
private libService: OpenViduAngularConfigService,
private platformService: PlatformService
private platformService: PlatformService,
private recordingService: RecordingService
) {
this.log = this.loggerSrv.get('ToolbarComponent');
}
@ -310,12 +357,14 @@ export class ToolbarComponent implements OnInit, OnDestroy {
await this.oVDevicesService.initializeDevices();
this.hasVideoDevices = this.oVDevicesService.hasVideoDeviceAvailable();
this.hasAudioDevices = this.oVDevicesService.hasAudioDeviceAvailable();
this.isOpenViduCE = this.openviduService.isOpenViduCE();
this.session = this.openviduService.getWebcamSession();
this.subscribeToUserMediaProperties();
this.subscribeToReconnection();
this.subscribeToMenuToggling();
this.subscribeToChatMessages();
this.subscribeToRecordingStatus();
}
ngOnDestroy(): void {
@ -324,12 +373,15 @@ export class ToolbarComponent implements OnInit, OnDestroy {
if (this.localParticipantSubscription) this.localParticipantSubscription.unsubscribe();
if (this.screenshareButtonSub) this.screenshareButtonSub.unsubscribe();
if (this.fullscreenButtonSub) this.fullscreenButtonSub.unsubscribe();
if (this.backgroundEffectsButtonSub) this.backgroundEffectsButtonSub.unsubscribe();
if (this.leaveButtonSub) this.leaveButtonSub.unsubscribe();
if (this.participantsPanelButtonSub) this.participantsPanelButtonSub.unsubscribe();
if (this.chatPanelButtonSub) this.chatPanelButtonSub.unsubscribe();
if (this.displayLogoSub) this.displayLogoSub.unsubscribe();
if (this.displaySessionNameSub) this.displaySessionNameSub.unsubscribe();
if (this.minimalSub) this.minimalSub.unsubscribe();
if (this.activitiesPanelButtonSub) this.activitiesPanelButtonSub.unsubscribe();
if (this.recordingSubscription) this.recordingSubscription.unsubscribe();
}
/**
@ -385,6 +437,23 @@ export class ToolbarComponent implements OnInit, OnDestroy {
this.onLeaveButtonClicked.emit();
}
/**
* TODO: WIP
* @ignore
*/
toggleActivitiesPanel(expandPanel: string) {
// this.onActivitiesPanelButtonClicked.emit();
// this.panelService.togglePanel(PanelType.ACTIVITIES);
}
/**
* @ignore
*/
toggleBackgroundEffects() {
// this.onBackgroundEffectsButtonClicked.emit();
this.panelService.togglePanel(PanelType.BACKGROUND_EFFECTS);
}
/**
* @ignore
*/
@ -425,6 +494,7 @@ export class ToolbarComponent implements OnInit, OnDestroy {
this.panelTogglingSubscription = this.panelService.panelOpenedObs.subscribe((ev: { opened: boolean; type?: PanelType }) => {
this.isChatOpened = ev.opened && ev.type === PanelType.CHAT;
this.isParticipantsOpened = ev.opened && ev.type === PanelType.PARTICIPANTS;
this.isActivitiesOpened = ev.opened && ev.type === PanelType.ACTIVITIES;
if (this.isChatOpened) {
this.unreadMessages = 0;
}
@ -451,6 +521,14 @@ export class ToolbarComponent implements OnInit, OnDestroy {
});
}
subscribeToRecordingStatus() {
//TODO: WIP
// this.recordingSubscription = this.recordingService.recordingStatusObs.pipe(skip(1)).subscribe((info: RecordingInfo) => {
// this.isRecording = info.status === RecordingStatus.STARTED;
// this.cd.markForCheck();
// });
}
private subscribeToToolbarDirectives() {
this.minimalSub = this.libService.minimalObs.subscribe((value: boolean) => {
this.isMinimal = value;
@ -476,6 +554,14 @@ export class ToolbarComponent implements OnInit, OnDestroy {
this.showParticipantsPanelButton = value;
this.cd.markForCheck();
});
// this.activitiesPanelButtonSub = this.libService.activitiesPanelButtonObs.subscribe((value: boolean) => {
// this.showActivitiesPanelButton = value;
// this.cd.markForCheck();
// });
this.backgroundEffectsButtonSub = this.libService.backgroundEffectsButton.subscribe((value: boolean) => {
this.showBackgroundEffectsButton = value;
this.cd.markForCheck();
});
this.displayLogoSub = this.libService.displayLogoObs.subscribe((value: boolean) => {
this.showLogo = value;
this.cd.markForCheck();

View File

@ -1,5 +1,5 @@
<div id="call-container">
<div id="pre-join-container" *ngIf="showPrejoin && participantReady && !joinSessionClicked">
<div id="pre-join-container" *ngIf="showPrejoin && tokensReceived && participantReady && !joinSessionClicked">
<ov-pre-join (onJoinButtonClicked)="_onJoinButtonClicked()"></ov-pre-join>
<!-- <ov-user-settings (onJoinClicked)="_onJoinClicked()" (onCloseClicked)="onLeaveSessionClicked()"></ov-user-settings> -->
</div>
@ -14,7 +14,7 @@
<span>{{ errorMessage }}</span>
</div>
<div id="session-container" *ngIf="(joinSessionClicked || !showPrejoin) && participantReady && canPublish && !error">
<div id="session-container" *ngIf="(joinSessionClicked || !showPrejoin) && participantReady && tokensReceived && !error">
<ov-session (onSessionCreated)="_onSessionCreated($event)">
<ng-template #toolbar>
<ng-container *ngIf="openviduAngularToolbarTemplate">
@ -68,6 +68,14 @@
<ng-container *ngTemplateOutlet="openviduAngularParticipantsPanelTemplate"></ng-container>
</ng-template>
<ng-template #backgroundEffectsPanel>
<ov-background-effects-panel id="default-background-effects-panel"></ov-background-effects-panel>
</ng-template>
<ng-template #activitiesPanel>
<ng-container *ngTemplateOutlet="openviduAngularActivitiesPanelTemplate"></ng-container>
</ng-template>
<ng-template #additionalPanels>
<ng-container *ngTemplateOutlet="openviduAngularAdditionalPanelsTemplate"></ng-container>
</ng-template>
@ -78,6 +86,14 @@
<ov-chat-panel id="default-chat-panel"></ov-chat-panel>
</ng-template>
<ng-template #defaultActivitiesPanel>
<!-- <ov-activities-panel
id="default-activities-panel"
(onStartRecordingClicked)="onStartRecordingClicked()"
(onStopRecordingClicked)="onStopRecordingClicked()"
></ov-activities-panel> -->
</ng-template>
<ng-template #defaultParticipantsPanel>
<ov-participants-panel id="default-participants-panel">
<ng-template #participantPanelItem let-participant>

View File

@ -23,7 +23,8 @@ import {
StreamDirective,
ToolbarAdditionalButtonsDirective,
ToolbarAdditionalPanelButtonsDirective,
ToolbarDirective
ToolbarDirective,
ActivitiesPanelDirective
} from '../../directives/template/openvidu-angular.directive';
import { ILogger } from '../../models/logger.model';
import { ParticipantAbstractModel, ParticipantProperties } from '../../models/participant.model';
@ -32,7 +33,7 @@ import { ActionService } from '../../services/action/action.service';
import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service';
import { DeviceService } from '../../services/device/device.service';
import { LoggerService } from '../../services/logger/logger.service';
import { OpenViduService } from '../../services/openvidu/openvidu.service';
import { OpenViduEdition, OpenViduService } from '../../services/openvidu/openvidu.service';
import { ParticipantService } from '../../services/participant/participant.service';
import { StorageService } from '../../services/storage/storage.service';
import { TokenService } from '../../services/token/token.service';
@ -57,6 +58,7 @@ import { TokenService } from '../../services/token/token.service';
* | **audioMuted** | `boolean` | {@link AudioMutedDirective} |
* | **toolbarScreenshareButton** | `boolean` | {@link ToolbarScreenshareButtonDirective} |
* | **toolbarFullscreenButton** | `boolean` | {@link ToolbarFullscreenButtonDirective} |
* | **toolbarBackgroundEffectsButton** | `boolean` | {@link ToolbarBackgroundEffectsButtonDirective} |
* | **toolbarLeaveButton** | `boolean` | {@link ToolbarLeaveButtonDirective} |
* | **toolbarChatPanelButton** | `boolean` | {@link ToolbarChatPanelButtonDirective} |
* | **toolbarParticipantsPanelButton** | `boolean` | {@link ToolbarParticipantsPanelButtonDirective} |
@ -135,6 +137,11 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
* @internal
*/
@ContentChild(ChatPanelDirective) externalChatPanel: ChatPanelDirective;
/**
* @internal
*/
@ContentChild(ActivitiesPanelDirective) externalActivitiesPanel: ActivitiesPanelDirective;
/**
* @internal
*/
@ -174,6 +181,13 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
* @internal
*/
@ViewChild('defaultParticipantsPanel', { static: false, read: TemplateRef }) defaultParticipantsPanelTemplate: TemplateRef<any>;
/**
* TODO: WIP
* @internal
*/
@ViewChild('defaultActivitiesPanel', { static: false, read: TemplateRef })
defaultActivitiesPanelTemplate: TemplateRef<any>;
/**
* @internal
*/
@ -195,6 +209,12 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
* @internal
*/
openviduAngularToolbarAdditionalButtonsTemplate: TemplateRef<any>;
/**
* TODO: WIP
* @internal
*/
openviduAngularActivitiesPanelTemplate: TemplateRef<any>;
/**
* @internal
*/
@ -244,6 +264,13 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
this.log.w('Tokens received');
this.tokenService.setWebcamToken(tokens.webcam);
const openviduEdition = new URL(tokens.webcam).searchParams.get('edition');
if (!!openviduEdition) {
this.openviduService.setOpenViduEdition(OpenViduEdition.PRO);
} else {
this.openviduService.setOpenViduEdition(OpenViduEdition.CE);
}
if (tokens.screen) {
this.tokenService.setScreenToken(tokens.screen);
} else {
@ -251,7 +278,7 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
this.libService.screenshareButton.next(false);
this.log.w('No screen token found. Screenshare feature will be disabled');
}
this.canPublish = true;
this.tokensReceived = true;
}
}
@ -317,7 +344,7 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
/**
* @internal
*/
canPublish: boolean = false;
tokensReceived: boolean = false;
/**
* @internal
*/
@ -446,6 +473,15 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
this.openviduAngularChatPanelTemplate = this.defaultChatPanelTemplate;
}
// TODO: WIP
// if (this.externalActivitiesPanel) {
// this.log.d('Setting EXTERNAL ACTIVITIES PANEL');
// this.openviduAngularActivitiesPanelTemplate = this.externalActivitiesPanel.template;
// } else {
// this.log.d('Setting DEFAULT ACTIVITIES PANEL');
// this.openviduAngularActivitiesPanelTemplate = this.defaultActivitiesPanelTemplate;
// }
if (this.externalAdditionalPanels) {
this.log.d('Setting EXTERNAL ADDITIONAL PANELS');
this.openviduAngularAdditionalPanelsTemplate = this.externalAdditionalPanels.template;

View File

@ -13,7 +13,9 @@ import {
ToolbarChatPanelButtonDirective,
ToolbarDisplaySessionNameDirective,
ToolbarDisplayLogoDirective,
LogoDirective
LogoDirective,
ToolbarActivitiesPanelButtonDirective,
ToolbarBackgroundEffectsButtonDirective
} from './toolbar.directive';
import {
AudioMutedDirective,
@ -32,9 +34,11 @@ import {
AudioMutedDirective,
ToolbarScreenshareButtonDirective,
ToolbarFullscreenButtonDirective,
ToolbarBackgroundEffectsButtonDirective,
ToolbarLeaveButtonDirective,
ToolbarParticipantsPanelButtonDirective,
ToolbarChatPanelButtonDirective,
ToolbarActivitiesPanelButtonDirective,
ToolbarDisplaySessionNameDirective,
ToolbarDisplayLogoDirective,
StreamDisplayParticipantNameDirective,
@ -51,9 +55,11 @@ import {
AudioMutedDirective,
ToolbarScreenshareButtonDirective,
ToolbarFullscreenButtonDirective,
ToolbarBackgroundEffectsButtonDirective,
ToolbarLeaveButtonDirective,
ToolbarParticipantsPanelButtonDirective,
ToolbarChatPanelButtonDirective,
ToolbarActivitiesPanelButtonDirective,
ToolbarDisplaySessionNameDirective,
ToolbarDisplayLogoDirective,
StreamDisplayParticipantNameDirective,

View File

@ -122,6 +122,65 @@ export class ToolbarFullscreenButtonDirective implements AfterViewInit, OnDestro
}
}
/**
* The **backgroundEffectsButton** directive allows show/hide the background effects toolbar button.
*
* Default: `true`
*
* It can be used in the parent element {@link VideoconferenceComponent} specifying the name of the `toolbar` component:
*
* @example
* <ov-videoconference [toolbarBackgroundEffectsButton]="false"></ov-videoconference>
*
* \
* And it also can be used in the {@link ToolbarComponent}.
* @example
* <ov-toolbar [backgroundEffectsButton]="false"></ov-toolbar>
*/
@Directive({
selector: 'ov-videoconference[toolbarBackgroundEffectsButton], ov-toolbar[backgroundEffectsButton]'
})
export class ToolbarBackgroundEffectsButtonDirective implements AfterViewInit, OnDestroy {
/**
* @ignore
*/
@Input() set toolbarBackgroundEffectsButton(value: boolean) {
this.backgroundEffectsValue = value;
this.update(this.backgroundEffectsValue);
}
/**
* @ignore
*/
@Input() set backgroundEffectsButton(value: boolean) {
this.backgroundEffectsValue = value;
this.update(this.backgroundEffectsValue);
}
private backgroundEffectsValue: boolean = true;
/**
* @ignore
*/
constructor(public elementRef: ElementRef, private libService: OpenViduAngularConfigService) {}
ngAfterViewInit() {
this.update(this.backgroundEffectsValue);
}
ngOnDestroy(): void {
this.clear();
}
private clear() {
this.backgroundEffectsValue = true;
this.update(true);
}
private update(value: boolean) {
if (this.libService.backgroundEffectsButton.getValue() !== value) {
this.libService.backgroundEffectsButton.next(value);
}
}
}
/**
* The **leaveButton** directive allows show/hide the leave toolbar button.
*
@ -302,6 +361,68 @@ export class ToolbarChatPanelButtonDirective implements AfterViewInit, OnDestroy
}
}
/**
* The **activitiesPanelButton** directive allows show/hide the activities panel toolbar button.
*
* Default: `true`
*
* It can be used in the parent element {@link VideoconferenceComponent} specifying the name of the `toolbar` component:
*
* @example
* <ov-videoconference [toolbarActivitiesPanelButton]="false"></ov-videoconference>
*
* \
* And it also can be used in the {@link ToolbarComponent}.
* @example
* <ov-toolbar [activitiesPanelButton]="false"></ov-toolbar>
*
* @internal
*/
@Directive({
selector: 'ov-videoconference[toolbarActivitiesPanelButton], ov-toolbar[activitiesPanelButton]'
})
export class ToolbarActivitiesPanelButtonDirective implements AfterViewInit, OnDestroy {
/**
* @ignore
*/
@Input() set toolbarActivitiesPanelButton(value: boolean) {
this.toolbarActivitiesPanelValue = value;
this.update(this.toolbarActivitiesPanelValue);
}
/**
* @ignore
*/
@Input() set chatPanelButton(value: boolean) {
this.toolbarActivitiesPanelValue = value;
this.update(this.toolbarActivitiesPanelValue);
}
private toolbarActivitiesPanelValue: boolean = true;
/**
* @ignore
*/
constructor(public elementRef: ElementRef, private libService: OpenViduAngularConfigService) {}
ngAfterViewInit() {
this.update(this.toolbarActivitiesPanelValue);
}
ngOnDestroy(): void {
this.clear();
}
private clear() {
this.toolbarActivitiesPanelValue = true;
this.update(true);
}
private update(value: boolean) {
if (this.libService.activitiesPanelButton.getValue() !== value) {
this.libService.activitiesPanelButton.next(value);
}
}
}
/**
* The **displaySessionName** directive allows show/hide the session name.
*

View File

@ -10,7 +10,9 @@ import {
ToolbarAdditionalButtonsDirective,
ToolbarDirective,
ToolbarAdditionalPanelButtonsDirective,
AdditionalPanelsDirective
AdditionalPanelsDirective,
ActivitiesPanelDirective,
BackgroundEffectsPanelDirective
} from './openvidu-angular.directive';
@NgModule({
@ -25,7 +27,9 @@ import {
ToolbarDirective,
ToolbarAdditionalButtonsDirective,
ToolbarAdditionalPanelButtonsDirective,
ParticipantPanelItemElementsDirective
ParticipantPanelItemElementsDirective,
ActivitiesPanelDirective,
BackgroundEffectsPanelDirective
],
exports: [
ChatPanelDirective,
@ -38,7 +42,9 @@ import {
ToolbarDirective,
ToolbarAdditionalButtonsDirective,
ToolbarAdditionalPanelButtonsDirective,
ParticipantPanelItemElementsDirective
ParticipantPanelItemElementsDirective,
ActivitiesPanelDirective,
BackgroundEffectsPanelDirective
]
})
export class OpenViduAngularDirectiveModule {}

View File

@ -364,6 +364,29 @@ export class ChatPanelDirective {
constructor(public template: TemplateRef<any>, public viewContainer: ViewContainerRef) {}
}
/**
* TODO: WIP. backgroundEffectsPanel does not provides customization
* @internal
*/
@Directive({
selector: '[ovBackgroundEffectsPanel]'
})
export class BackgroundEffectsPanelDirective {
constructor(public template: TemplateRef<any>, public viewContainer: ViewContainerRef) {}
}
/**
* TODO: WIP
* @internal
*/
@Directive({
selector: '[ovActivitiesPanel]'
})
export class ActivitiesPanelDirective {
constructor(public template: TemplateRef<any>, public viewContainer: ViewContainerRef) {}
}
/**
* The ***ovParticipantsPanel** directive allows to replace the default participants panel template injecting your own component.
* Here we're going to redefine the participants template in a few code lines.

View File

@ -1,4 +1,7 @@
export enum PanelType {
CHAT = 'chat',
PARTICIPANTS = 'participants'
PARTICIPANTS = 'participants',
BACKGROUND_EFFECTS = 'background-effects',
ACTIVITIES = 'activities'
}

View File

@ -0,0 +1,4 @@
export enum RecordingStatus {
STARTED = 'STARTED',
STOPPED = 'STOPPED'
}

View File

@ -20,6 +20,7 @@ import { OverlayContainer } from '@angular/cdk/overlay';
import { MatMenuModule } from '@angular/material/menu';
import { MatListModule } from '@angular/material/list';
import { MatDividerModule } from '@angular/material/divider';
import {MatExpansionModule} from '@angular/material/expansion';
import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
@ -63,6 +64,9 @@ import { PreJoinComponent } from './components/pre-join/pre-join.component';
import { AvatarProfileComponent } from './components/avatar-profile/avatar-profile.component';
import { OpenViduAngularDirectiveModule } from './directives/template/openvidu-angular.directive.module';
import { ApiDirectiveModule } from './directives/api/api.directive.module';
import { BackgroundEffectsPanelComponent } from './components/panel/background-effects-panel/background-effects-panel.component';
import { ActivitiesPanelComponent } from './components/panel/activities-panel/activities-panel.component';
import { RecordingActivityComponent } from './components/panel/activities-panel/recording-activity-panel/recording-activity.component';
@NgModule({
declarations: [
@ -84,6 +88,9 @@ import { ApiDirectiveModule } from './directives/api/api.directive.module';
PanelComponent,
AvatarProfileComponent,
PreJoinComponent,
BackgroundEffectsPanelComponent,
ActivitiesPanelComponent,
RecordingActivityComponent,
],
imports: [
CommonModule,
@ -111,6 +118,7 @@ import { ApiDirectiveModule } from './directives/api/api.directive.module';
MatMenuModule,
MatDividerModule,
MatListModule,
MatExpansionModule,
OpenViduAngularDirectiveModule,
ApiDirectiveModule
],
@ -137,6 +145,8 @@ import { ApiDirectiveModule } from './directives/api/api.directive.module';
PanelComponent,
ParticipantsPanelComponent,
ParticipantPanelItemComponent,
BackgroundEffectsPanelComponent,
ActivitiesPanelComponent,
ChatPanelComponent,
SessionComponent,
LayoutComponent,

View File

@ -36,6 +36,9 @@ export class OpenViduAngularConfigService {
chatPanelButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
chatPanelButtonObs: Observable<boolean>;
activitiesPanelButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
activitiesPanelButtonObs: Observable<boolean>;
displaySessionName = <BehaviorSubject<boolean>>new BehaviorSubject(true);
displaySessionNameObs: Observable<boolean>;
@ -49,6 +52,8 @@ export class OpenViduAngularConfigService {
settingsButtonObs: Observable<boolean>;
participantItemMuteButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
participantItemMuteButtonObs: Observable<boolean>;
backgroundEffectsButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
backgroundEffectsButtonObs: Observable<boolean>;
constructor(@Inject('OPENVIDU_ANGULAR_CONFIG') config: OpenViduAngularConfig) {
this.configuration = config;
@ -62,9 +67,11 @@ export class OpenViduAngularConfigService {
//Toolbar observables
this.screenshareButtonObs = this.screenshareButton.asObservable();
this.fullscreenButtonObs = this.fullscreenButton.asObservable();
this.backgroundEffectsButtonObs = this.backgroundEffectsButton.asObservable();
this.leaveButtonObs = this.leaveButton.asObservable();
this.participantsPanelButtonObs = this.participantsPanelButton.asObservable();
this.chatPanelButtonObs = this.chatPanelButton.asObservable();
this.activitiesPanelButtonObs = this.activitiesPanelButton.asObservable();
this.displaySessionNameObs = this.displaySessionName.asObservable();
this.displayLogoObs = this.displayLogo.asObservable();
//Stream observables
@ -82,10 +89,6 @@ export class OpenViduAngularConfigService {
return this.configuration?.production;
}
// isWebcomponent(): boolean {
// return this.configuration?.webcomponent;
// }
hasParticipantFactory(): boolean {
return typeof this.getConfig().participantFactory === 'function';
}

View File

@ -13,10 +13,17 @@ import { ScreenType, VideoType } from '../../models/video-type.model';
import { ParticipantService } from '../participant/participant.service';
import { TokenService } from '../token/token.service';
export enum OpenViduEdition {
CE = 'CE',
PRO = 'PRO',
ENTERPRISE = 'ENTERPRISE'
}
@Injectable({
providedIn: 'root'
})
export class OpenViduService {
private ovEdition: OpenViduEdition;
protected OV: OpenVidu = null;
protected OVScreen: OpenVidu = null;
protected webcamSession: Session = null;
@ -60,6 +67,20 @@ export class OpenViduService {
}
}
/**
* @internal
*/
isOpenViduCE(): boolean {
return this.ovEdition === OpenViduEdition.CE;
}
/**
* @internal
*/
setOpenViduEdition(edition: OpenViduEdition) {
this.ovEdition = edition;
}
/**
* @internal
*/

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { RecordingService } from './recording.service';
describe('RecordingService', () => {
let service: RecordingService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(RecordingService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,46 @@
import { Injectable } from '@angular/core';
import { RecordingEvent } from 'openvidu-browser';
import { BehaviorSubject, Observable } from 'rxjs';
import { RecordingStatus } from '../../models/recording.model';
export interface RecordingInfo {
status: RecordingStatus;
id: string;
name?: string;
reason?: string;
}
@Injectable({
providedIn: 'root'
})
export class RecordingService {
/**
* Recording status Observable which pushes the recording state in every update.
*/
recordingStatusObs: Observable<RecordingInfo>;
private recordingStatus = <BehaviorSubject<RecordingInfo>>new BehaviorSubject(null);
constructor() {
this.recordingStatusObs = this.recordingStatus.asObservable();
}
startRecording(event: RecordingEvent) {
const info: RecordingInfo = {
status: RecordingStatus.STARTED,
id: event.id,
name: event.name,
reason: event.reason
};
this.recordingStatus.next(info);
}
stopRecording(event: RecordingEvent) {
const info: RecordingInfo = {
status: RecordingStatus.STOPPED,
id: event.id,
name: event.name,
reason: event.reason
};
this.recordingStatus.next(info);
}
}

View File

@ -26,6 +26,8 @@ export * from './lib/components/videoconference/videoconference.component';
export * from './lib/components/toolbar/toolbar.component';
export * from './lib/components/panel/panel.component';
export * from './lib/components/panel/chat-panel/chat-panel.component';
export * from './lib/components/panel/background-effects-panel/background-effects-panel.component';
export * from './lib/components/panel/activities-panel/activities-panel.component';
export * from './lib/components/panel/participants-panel/participants-panel/participants-panel.component';
export * from './lib/components/panel/participants-panel/participant-panel-item/participant-panel-item.component';
export * from './lib/components/session/session.component';