openvidu-components: Added settings panel for configuring media devices

pull/739/head
csantosm 2022-06-16 14:01:07 +02:00
parent 3908dfc146
commit c8264fdb1b
58 changed files with 1029 additions and 371 deletions

View File

@ -2,7 +2,7 @@ import { Component, OnInit, Output, EventEmitter, ChangeDetectionStrategy, Chang
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { PanelType } from '../../../models/panel.model'; import { PanelType } from '../../../models/panel.model';
import { OpenViduAngularConfigService } from '../../../services/config/openvidu-angular.config.service'; import { OpenViduAngularConfigService } from '../../../services/config/openvidu-angular.config.service';
import { PanelService } from '../../../services/panel/panel.service'; import { PanelEvent, PanelService } from '../../../services/panel/panel.service';
@Component({ @Component({
selector: 'ov-activities-panel', selector: 'ov-activities-panel',
@ -114,8 +114,8 @@ export class ActivitiesPanelComponent implements OnInit {
private subscribeToPanelToggling() { private subscribeToPanelToggling() {
this.panelSubscription = this.panelService.panelOpenedObs.subscribe( this.panelSubscription = this.panelService.panelOpenedObs.subscribe(
(ev: { opened: boolean; type?: PanelType | string; expand?: string }) => { (ev: PanelEvent) => {
if (ev.type === PanelType.ACTIVITIES) { if (ev.type === PanelType.ACTIVITIES && !!ev.expand) {
this.expandedPanel = ev.expand; this.expandedPanel = ev.expand;
} }
} }

View File

@ -14,6 +14,11 @@
<ng-container *ngTemplateOutlet="backgroundEffectsPanelTemplate"></ng-container> <ng-container *ngTemplateOutlet="backgroundEffectsPanelTemplate"></ng-container>
</ng-container> </ng-container>
<!-- Settings panel -->
<ng-container *ngIf="isSettingsPanelOpened">
<ng-container *ngTemplateOutlet="settingsPanelTemplate"></ng-container>
</ng-container>
<!-- Activities panel --> <!-- Activities panel -->
<ng-container *ngIf="isActivitiesPanelOpened"> <ng-container *ngIf="isActivitiesPanelOpened">
<ng-container *ngTemplateOutlet="activitiesPanelTemplate"></ng-container> <ng-container *ngTemplateOutlet="activitiesPanelTemplate"></ng-container>

View File

@ -8,7 +8,7 @@ import {
ActivitiesPanelDirective ActivitiesPanelDirective
} from '../../directives/template/openvidu-angular.directive'; } from '../../directives/template/openvidu-angular.directive';
import { PanelType } from '../../models/panel.model'; import { PanelType } from '../../models/panel.model';
import { PanelService } from '../../services/panel/panel.service'; import { PanelEvent, PanelService } from '../../services/panel/panel.service';
/** /**
* *
@ -64,6 +64,11 @@ export class PanelComponent implements OnInit {
*/ */
@ContentChild('backgroundEffectsPanel', { read: TemplateRef }) backgroundEffectsPanelTemplate: TemplateRef<any>; @ContentChild('backgroundEffectsPanel', { read: TemplateRef }) backgroundEffectsPanelTemplate: TemplateRef<any>;
/**
* @ignore
*/
@ContentChild('settingsPanel', { read: TemplateRef }) settingsPanelTemplate: TemplateRef<any>;
/** /**
* @ignore * @ignore
*/ */
@ -91,11 +96,22 @@ export class PanelComponent implements OnInit {
set externalBackgroundEffectsPanel(externalBackgroundEffectsPanel: BackgroundEffectsPanelDirective) { set externalBackgroundEffectsPanel(externalBackgroundEffectsPanel: BackgroundEffectsPanelDirective) {
// This directive will has value only when BACKGROUND EFFECTS PANEL component tagged with '*ovBackgroundEffectsPanel' // This directive will has value only when BACKGROUND EFFECTS PANEL component tagged with '*ovBackgroundEffectsPanel'
// is inside of the PANEL component tagged with '*ovPanel' // is inside of the PANEL component tagged with '*ovPanel'
if (externalBackgroundEffectsPanel) { // TODO: backgroundEffectsPanel does not provides customization
this.backgroundEffectsPanelTemplate = externalBackgroundEffectsPanel.template; // if (externalBackgroundEffectsPanel) {
} // this.backgroundEffectsPanelTemplate = externalBackgroundEffectsPanel.template;
// }
} }
// TODO: settingsPanel does not provides customization
// @ContentChild(SettingsPanelDirective)
// set externalSettingsPanel(externalSettingsPanel: SettingsPanelDirective) {
// This directive will has value only when SETTINGS PANEL component tagged with '*ovSettingsPanel'
// is inside of the PANEL component tagged with '*ovPanel'
// if (externalSettingsPanel) {
// this.settingsPanelTemplate = externalSettingsPanel.template;
// }
// }
@ContentChild(ActivitiesPanelDirective) @ContentChild(ActivitiesPanelDirective)
set externalActivitiesPanel(externalActivitiesPanel: ActivitiesPanelDirective) { set externalActivitiesPanel(externalActivitiesPanel: ActivitiesPanelDirective) {
// This directive will has value only when ACTIVITIES PANEL component tagged with '*ovActivitiesPanel' // This directive will has value only when ACTIVITIES PANEL component tagged with '*ovActivitiesPanel'
@ -126,6 +142,7 @@ export class PanelComponent implements OnInit {
isParticipantsPanelOpened: boolean; isParticipantsPanelOpened: boolean;
isChatPanelOpened: boolean; isChatPanelOpened: boolean;
isBackgroundEffectsPanelOpened: boolean; isBackgroundEffectsPanelOpened: boolean;
isSettingsPanelOpened: boolean;
isActivitiesPanelOpened: boolean; isActivitiesPanelOpened: boolean;
/** /**
@ -150,12 +167,11 @@ export class PanelComponent implements OnInit {
} }
private subscribeToPanelToggling() { private subscribeToPanelToggling() {
this.panelSubscription = this.panelService.panelOpenedObs this.panelSubscription = this.panelService.panelOpenedObs.pipe(skip(1)).subscribe((ev: PanelEvent) => {
.pipe(skip(1))
.subscribe((ev: { opened: boolean; type?: PanelType | string }) => {
this.isChatPanelOpened = ev.opened && ev.type === PanelType.CHAT; this.isChatPanelOpened = ev.opened && ev.type === PanelType.CHAT;
this.isParticipantsPanelOpened = ev.opened && ev.type === PanelType.PARTICIPANTS; this.isParticipantsPanelOpened = ev.opened && ev.type === PanelType.PARTICIPANTS;
this.isBackgroundEffectsPanelOpened = ev.opened && ev.type === PanelType.BACKGROUND_EFFECTS; this.isBackgroundEffectsPanelOpened = ev.opened && ev.type === PanelType.BACKGROUND_EFFECTS;
this.isSettingsPanelOpened = ev.opened && ev.type === PanelType.SETTINGS;
this.isActivitiesPanelOpened = ev.opened && ev.type === PanelType.ACTIVITIES; this.isActivitiesPanelOpened = ev.opened && ev.type === PanelType.ACTIVITIES;
this.isExternalPanelOpened = ev.opened && ev.type !== PanelType.PARTICIPANTS && ev.type !== PanelType.CHAT; this.isExternalPanelOpened = ev.opened && ev.type !== PanelType.PARTICIPANTS && ev.type !== PanelType.CHAT;
this.cd.markForCheck(); this.cd.markForCheck();

View File

@ -0,0 +1,29 @@
.settings-container {
display: flex;
padding: 10px;
}
.item-menu {
padding-right: 5px;
border-right: 1px solid var(--ov-secondary-color);
width: 200px;
}
.item-content {
padding: 16px;
flex-grow: 1;
}
.option {
border-radius: var(--ov-panel-radius);
}
.lang-container button {
width: 100%;
}
mat-list-option[aria-selected='true'] {
background: var(--ov-light-color);
}
::ng-deep .mat-list-item-content {
padding: 5px !important;
}

View File

@ -0,0 +1,43 @@
<div class="panel-container" id="background-effects-container" fxLayout="column" fxLayoutAlign="space-evenly none">
<div class="panel-header-container" fxFlex="55px" fxLayoutAlign="start center">
<h3 class="panel-title">{{ 'PANEL.SETTINGS.TITLE' | translate }}</h3>
<button class="panel-close-button" mat-icon-button matTooltip="{{ 'PANEL.CLOSE' | translate }}" (click)="close()">
<mat-icon>close</mat-icon>
</button>
</div>
<div class="settings-container" fxFlex="100%" fxLayoutAlign="space-evenly none">
<div class="item-menu">
<mat-selection-list #settings [multiple]="false">
<mat-list-option class="option" [selected]="true" value="general">
<mat-icon mat-list-icon>manage_accounts</mat-icon>
<div mat-line>{{ 'PANEL.SETTINGS.GENERAL' | translate }}</div>
</mat-list-option>
<mat-list-option class="option" value="video">
<mat-icon mat-list-icon>videocam</mat-icon>
<div mat-line>{{ 'PANEL.SETTINGS.VIDEO' | translate }}</div>
</mat-list-option>
<mat-list-option class="option" value="audio">
<mat-icon mat-list-icon>mic</mat-icon>
<div mat-line>{{ 'PANEL.SETTINGS.AUDIO' | translate }}</div>
</mat-list-option>
</mat-selection-list>
</div>
<div class="item-content">
<div *ngIf="settings.selectedOptions.selected[0]?.value === 'general'">
<ov-nickname-input></ov-nickname-input>
<mat-list>
<mat-list-item>
<mat-icon mat-list-icon>language</mat-icon>
<div mat-line>{{ 'PANEL.SETTINGS.LANGUAGE' | translate }}:</div>
<ov-lang-selector></ov-lang-selector>
</mat-list-item>
</mat-list>
</div>
<ov-video-devices-select *ngIf="settings.selectedOptions.selected[0]?.value === 'video'"></ov-video-devices-select>
<ov-audio-devices-select *ngIf="settings.selectedOptions.selected[0]?.value === 'audio'"></ov-audio-devices-select>
</div>
</div>
</div>

View File

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

View File

@ -0,0 +1,20 @@
import { Component, HostListener, OnInit } from '@angular/core';
import { MatOptionSelectionChange } from '@angular/material/core';
import { PanelType } from '../../../models/panel.model';
import { PanelService } from '../../../services/panel/panel.service';
@Component({
selector: 'ov-settings-panel',
templateUrl: './settings-panel.component.html',
styleUrls: ['../panel.component.css', './settings-panel.component.css']
})
export class SettingsPanelComponent implements OnInit {
selectedOption: string;
constructor(private panelService: PanelService) {}
ngOnInit() {}
close() {
this.panelService.togglePanel(PanelType.SETTINGS);
}
}

View File

@ -11,11 +11,6 @@
flex: 1 1 auto; flex: 1 1 auto;
} }
.lang-button {
background-color: var(--ov-logo-background-color);
color: var(--ov-text-color);
}
#branding-logo { #branding-logo {
background-color: var(--ov-logo-background-color); background-color: var(--ov-logo-background-color);
border-radius: var(--ov-panel-radius); border-radius: var(--ov-panel-radius);
@ -81,31 +76,6 @@ hr {
margin-bottom: 0px !important; margin-bottom: 0px !important;
} }
#nickname-input-container,
.device-container-element {
display: flex;
}
#nickname-input-container button,
.device-container-element button {
margin: auto 10px auto auto;
}
#nickname-input-container button.mat-button-disabled {
color: #000000 !important;
}
#nickname-input-container mat-form-field,
.device-container-element mat-form-field {
width: 100%;
margin-top: 10px;
color: #000000;
}
#nickname-input-container mat-form-field {
color: #000000;
}
.mat-form-field-appearance-fill .mat-form-field-flex { .mat-form-field-appearance-fill .mat-form-field-flex {
/* background-color: var(--ov-text-color); */ /* background-color: var(--ov-text-color); */
border-radius: var(--ov-video-radius); border-radius: var(--ov-video-radius);
@ -118,11 +88,6 @@ hr {
display: block !important; display: block !important;
} }
#camera-button {
border-radius: var(--ov-buttons-radius);
/* background-color: var(--ov-secondary-color) !important; */
/* color: var(--ov-text-color) !important; */
}
.join-btn-container { .join-btn-container {
width: inherit; width: inherit;
@ -137,37 +102,6 @@ hr {
border-radius: var(--ov-video-radius); border-radius: var(--ov-video-radius);
} }
.warn-btn {
color: var(--ov-text-color);
background-color: var(--ov-warn-color) !important;
}
.active-btn {
color: var(--ov-text-color);
background-color: var(--ov-tertiary-color) !important;
}
.media-btn {
margin: auto;
}
::ng-deep .mat-button-toggle-appearance-standard .mat-button-toggle-label-content {
padding: 1px !important;
}
::ng-deep .mat-input-element {
caret-color: #000000;
}
::ng-deep .mat-primary .mat-option.mat-selected:not(.mat-option-disabled) {
color: #000000;
}
::ng-deep .mat-form-field-label {
color: var(--ov-panel-text-color) !important;
}
::ng-deep .mat-form-field.mat-focused .mat-form-field-ripple {
background-color: var(--ov-panel-text-color) !important;
}
@media only screen and (max-width: 480px) { @media only screen and (max-width: 480px) {
.container, .container,

View File

@ -3,15 +3,8 @@
<!-- <span>OpenVidu Call</span> --> <!-- <span>OpenVidu Call</span> -->
<span class="spacer"></span> <span class="spacer"></span>
<button mat-flat-button [matMenuTriggerFor]="menu" class="lang-button" *ngIf="!isMinimal"> <ov-lang-selector *ngIf="!isMinimal"></ov-lang-selector>
<span>{{langSelected?.name}}</span>
<mat-icon>expand_more</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item *ngFor="let lang of languages" (click)="onLangSelected(lang.ISO)">
<span>{{lang.name}}</span>
</button>
</mat-menu>
</mat-toolbar> </mat-toolbar>
<div class="container" id="prejoin-container" fxLayout.gt-sm="row" fxLayout.lt-md="column"> <div class="container" id="prejoin-container" fxLayout.gt-sm="row" fxLayout.lt-md="column">
@ -50,98 +43,19 @@
<div fxFlex.gt-sm="100%" fxFlex.lt-md="33%" fxLayoutAlign="center center" fxFlexFill class="nickname-container"> <div fxFlex.gt-sm="100%" fxFlex.lt-md="33%" fxLayoutAlign="center center" fxFlexFill class="nickname-container">
<h4 *ngIf="windowSize >= 960">{{ 'PREJOIN.NICKNAME_SECTION' | translate }}</h4> <h4 *ngIf="windowSize >= 960">{{ 'PREJOIN.NICKNAME_SECTION' | translate }}</h4>
<hr *ngIf="windowSize >= 960" /> <hr *ngIf="windowSize >= 960" />
<div id="nickname-input-container"> <ov-nickname-input></ov-nickname-input>
<button mat-icon-button disabled>
<mat-icon>person</mat-icon>
</button>
<mat-form-field appearance="standard">
<mat-label>{{ 'PREJOIN.NICKNAME' | translate }}</mat-label>
<input
matInput
type="text"
maxlength="20"
[(ngModel)]="nickname"
(change)="updateNickname()"
autocomplete="off"
/>
</mat-form-field>
</div>
<!-- <mat-button-toggle-group style="border-radius: 20px">
<button mat-icon-button class="media-btn">
<mat-icon matTooltip="Mute your audio">mic</mat-icon>
</button>
<mat-button-toggle class="split-button-1 drop-down-button" [matMenuTriggerFor]="dropdownMenuOne">
<mat-icon>arrow_drop_down</mat-icon>
</mat-button-toggle>
</mat-button-toggle-group>
<mat-menu #dropdownMenuOne="matMenu">
<button mat-menu-item>One</button>
<button mat-menu-item>Two</button>
<button mat-menu-item>Three</button>
</mat-menu> -->
</div> </div>
<div fxFlex.gt-sm="100%" fxFlex.lt-md="33%" fxLayoutAlign="center center" fxFlexFill class="buttons-container"> <div fxFlex.gt-sm="100%" fxFlex.lt-md="33%" fxLayoutAlign="center center" fxFlexFill class="buttons-container">
<h4 *ngIf="windowSize >= 960">{{ 'PREJOIN.DEVICE_SECTION' | translate }}</h4> <h4 *ngIf="windowSize >= 960">{{ 'PREJOIN.DEVICE_SECTION' | translate }}</h4>
<hr *ngIf="windowSize >= 960" /> <hr *ngIf="windowSize >= 960" />
<!-- Camera --> <!-- Camera -->
<div class="device-container-element"> <ov-video-devices-select></ov-video-devices-select>
<button
mat-icon-button
id="camera-button"
[disabled]="!hasVideoDevices || videoMuteChanging"
[class.warn-btn]="isVideoMuted"
(click)="toggleCam()"
>
<mat-icon *ngIf="!isVideoMuted" matTooltip="{{ 'TOOLBAR.MUTE_VIDEO' | translate }}" id="videocam"
>videocam</mat-icon
>
<mat-icon *ngIf="isVideoMuted" matTooltip="{{ 'TOOLBAR.UNMUTE_VIDEO' | translate }}" id="videocam_off"
>videocam_off</mat-icon
>
</button>
<mat-form-field>
<mat-label>{{ 'PREJOIN.VIDEO_DEVICE' | translate }}</mat-label>
<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>
</mat-select>
</mat-form-field>
</div>
<!-- Microphone --> <!-- Microphone -->
<div class="device-container-element"> <ov-audio-devices-select></ov-audio-devices-select>
<button
mat-icon-button
id="microhpone-button"
[disabled]="!hasAudioDevices"
[class.warn-btn]="isAudioMuted"
(click)="toggleMic()"
>
<mat-icon *ngIf="!isAudioMuted" matTooltip="{{ 'TOOLBAR.MUTE_AUDIO' | translate }}" id="mic">mic</mat-icon>
<mat-icon *ngIf="isAudioMuted" matTooltip="{{ 'TOOLBAR.UNMUTE_AUDIO' | translate }}" id="mic_off"
>mic_off</mat-icon
>
</button>
<mat-form-field>
<mat-label>{{ 'PREJOIN.AUDIO_DEVICE' | translate }}</mat-label>
<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>
</mat-select>
</mat-form-field>
</div>
</div> </div>
<div fxFlex.gt-sm="60%" fxLayout.lt-md="column" fxLayoutAlign="center center" fxFlexFill class="join-btn-container"> <div fxFlex.gt-sm="60%" fxLayout.lt-md="column" fxLayoutAlign="center center" fxFlexFill class="join-btn-container">

View File

@ -2,7 +2,6 @@ import { Component, HostListener, OnDestroy, OnInit, Output, EventEmitter, ViewC
import { MatMenuTrigger } from '@angular/material/menu'; import { MatMenuTrigger } from '@angular/material/menu';
import { MatSelect } from '@angular/material/select'; import { MatSelect } from '@angular/material/select';
import { PublisherProperties } from 'openvidu-browser';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { CustomDevice } from '../../models/device.model'; import { CustomDevice } from '../../models/device.model';
import { ILogger } from '../../models/logger.model'; import { ILogger } from '../../models/logger.model';
@ -10,15 +9,10 @@ import { PanelType } from '../../models/panel.model';
import { ParticipantAbstractModel } from '../../models/participant.model'; import { ParticipantAbstractModel } from '../../models/participant.model';
import { CdkOverlayService } from '../../services/cdk-overlay/cdk-overlay.service'; import { CdkOverlayService } from '../../services/cdk-overlay/cdk-overlay.service';
import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service'; import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service';
import { DeviceService } from '../../services/device/device.service';
import { LayoutService } from '../../services/layout/layout.service'; import { LayoutService } from '../../services/layout/layout.service';
import { LoggerService } from '../../services/logger/logger.service'; import { LoggerService } from '../../services/logger/logger.service';
import { OpenViduService } from '../../services/openvidu/openvidu.service';
import { PanelService } from '../../services/panel/panel.service'; import { PanelService } from '../../services/panel/panel.service';
import { ParticipantService } from '../../services/participant/participant.service'; import { ParticipantService } from '../../services/participant/participant.service';
import { StorageService } from '../../services/storage/storage.service';
import { TranslateService } from '../../services/translate/translate.service';
import { VirtualBackgroundService } from '../../services/virtual-background/virtual-background.service';
/** /**
* @internal * @internal
@ -39,8 +33,6 @@ export class PreJoinComponent implements OnInit, OnDestroy, AfterViewInit {
@ViewChild(MatSelect) matSelect: MatSelect; @ViewChild(MatSelect) matSelect: MatSelect;
@Output() onJoinButtonClicked = new EventEmitter<any>(); @Output() onJoinButtonClicked = new EventEmitter<any>();
languages: { name: string; ISO: string }[] = [];
langSelected: { name: string; ISO: string };
cameras: CustomDevice[]; cameras: CustomDevice[];
microphones: CustomDevice[]; microphones: CustomDevice[];
cameraSelected: CustomDevice; cameraSelected: CustomDevice;
@ -79,15 +71,10 @@ export class PreJoinComponent implements OnInit, OnDestroy, AfterViewInit {
constructor( constructor(
private layoutService: LayoutService, private layoutService: LayoutService,
private deviceSrv: DeviceService,
private loggerSrv: LoggerService, private loggerSrv: LoggerService,
private openviduService: OpenViduService,
private participantService: ParticipantService, private participantService: ParticipantService,
protected panelService: PanelService, protected panelService: PanelService,
private libService: OpenViduAngularConfigService, private libService: OpenViduAngularConfigService,
private storageSrv: StorageService,
private backgroundService: VirtualBackgroundService,
private translateService: TranslateService,
protected cdkSrv: CdkOverlayService protected cdkSrv: CdkOverlayService
) { ) {
this.log = this.loggerSrv.get('PreJoinComponent'); this.log = this.loggerSrv.get('PreJoinComponent');
@ -96,20 +83,8 @@ export class PreJoinComponent implements OnInit, OnDestroy, AfterViewInit {
ngOnInit() { ngOnInit() {
this.subscribeToPrejoinDirectives(); this.subscribeToPrejoinDirectives();
this.subscribeToLocalParticipantEvents(); this.subscribeToLocalParticipantEvents();
this.languages = this.translateService.getLanguagesInfo();
this.langSelected = this.translateService.getLangSelected();
this.windowSize = window.innerWidth; this.windowSize = window.innerWidth;
this.hasVideoDevices = this.deviceSrv.hasVideoDeviceAvailable();
this.hasAudioDevices = this.deviceSrv.hasAudioDeviceAvailable();
this.microphones = this.deviceSrv.getMicrophones();
this.cameras = this.deviceSrv.getCameras();
this.cameraSelected = this.deviceSrv.getCameraSelected();
this.microphoneSelected = this.deviceSrv.getMicrophoneSelected();
this.isVideoMuted = this.deviceSrv.isVideoMuted();
this.isAudioMuted = this.deviceSrv.isAudioMuted();
this.isLoading = false; this.isLoading = false;
} }
@ -133,79 +108,6 @@ export class PreJoinComponent implements OnInit, OnDestroy, AfterViewInit {
this.panelService.closePanel(); this.panelService.closePanel();
} }
async onCameraSelected(event: any) {
const videoSource = event?.value;
// Is New deviceId different from the old one?
if (this.deviceSrv.needUpdateVideoTrack(videoSource)) {
const mirror = this.deviceSrv.cameraNeedsMirror(videoSource);
//TODO: Uncomment this when replaceTrack issue is fixed
// const pp: PublisherProperties = { videoSource, audioSource: false, mirror };
// await this.openviduService.replaceTrack(VideoType.CAMERA, pp);
// TODO: Remove this when replaceTrack issue is fixed
const pp: PublisherProperties = { videoSource, audioSource: this.microphoneSelected.device, mirror };
// Reapply Virtual Background to new Publisher if necessary
const backgroundSelected = this.backgroundService.backgroundSelected.getValue();
if (this.backgroundService.isBackgroundApplied()) {
await this.backgroundService.removeBackground();
}
await this.openviduService.republishTrack(pp);
if (this.backgroundService.isBackgroundApplied()) {
await this.backgroundService.applyBackground(this.backgroundService.backgrounds.find((b) => b.id === backgroundSelected));
}
this.deviceSrv.setCameraSelected(videoSource);
this.cameraSelected = this.deviceSrv.getCameraSelected();
}
}
async onMicrophoneSelected(event: any) {
const audioSource = event?.value;
if (this.deviceSrv.needUpdateAudioTrack(audioSource)) {
//TODO: Uncomment this when replaceTrack issue is fixed
// const pp: PublisherProperties = { audioSource, videoSource: false };
// await this.openviduService.replaceTrack(VideoType.CAMERA, pp);
// TODO: Remove this when replaceTrack issue is fixed
const mirror = this.deviceSrv.cameraNeedsMirror(this.cameraSelected.device);
const pp: PublisherProperties = { videoSource: this.cameraSelected.device, audioSource, mirror };
await this.openviduService.republishTrack(pp);
this.deviceSrv.setMicSelected(audioSource);
this.microphoneSelected = this.deviceSrv.getMicrophoneSelected();
}
}
onLangSelected(lang: string) {
this.translateService.setLanguage(lang);
this.storageSrv.setLang(lang);
this.langSelected = this.translateService.getLangSelected();
}
async toggleCam() {
this.videoMuteChanging = true;
const publish = this.isVideoMuted;
await this.openviduService.publishVideo(publish);
this.isVideoMuted = !this.isVideoMuted;
this.storageSrv.setVideoMuted(this.isVideoMuted);
if (this.isVideoMuted && this.panelService.isExternalPanelOpened()) {
this.panelService.togglePanel(PanelType.BACKGROUND_EFFECTS);
}
this.videoMuteChanging = false;
}
toggleMic() {
const publish = this.isAudioMuted;
this.openviduService.publishAudio(publish);
this.isAudioMuted = !this.isAudioMuted;
this.storageSrv.setAudioMuted(this.isAudioMuted);
}
updateNickname() {
this.nickname = this.nickname === '' ? this.participantService.getMyNickname() : this.nickname;
this.participantService.setMyNickname(this.nickname);
this.storageSrv.setNickname(this.nickname);
}
joinSession() { joinSession() {
this.onJoinButtonClicked.emit(); this.onJoinButtonClicked.emit();
this.panelService.closePanel(); this.panelService.closePanel();

View File

@ -24,6 +24,11 @@
z-index: 1; z-index: 1;
} }
.big {
width: 650px;
max-width: 100%;
}
.mat-drawer.mat-drawer-side { .mat-drawer.mat-drawer-side {
z-index: 0 !important; z-index: 0 !important;
} }
@ -44,7 +49,8 @@
background-color: var(--ov-primary-color); background-color: var(--ov-primary-color);
} }
#toolbar-container, #footer-container { #toolbar-container,
#footer-container {
background-color: var(--ov-primary-color); background-color: var(--ov-primary-color);
min-width: 400px !important; min-width: 400px !important;
width: 100%; width: 100%;
@ -63,8 +69,6 @@
min-width: 400px !important; min-width: 400px !important;
} }
.reconnecting-container { .reconnecting-container {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -74,10 +78,28 @@
position: absolute; position: absolute;
} }
@media only screen and (max-width: 600px) { @media only screen and (max-width: 600px) {
#session-container { #session-container {
width: 100%; width: 100%;
/* position: fixed; */ /* position: fixed; */
} }
} }
::ng-deep .mat-button-toggle-appearance-standard .mat-button-toggle-label-content {
padding: 1px !important;
}
::ng-deep .mat-input-element {
caret-color: #000000;
}
::ng-deep .mat-primary .mat-option.mat-selected:not(.mat-option-disabled) {
color: #000000;
}
::ng-deep .mat-form-field-label {
color: var(--ov-panel-text-color) !important;
}
::ng-deep .mat-form-field.mat-focused .mat-form-field-ripple {
background-color: var(--ov-panel-text-color) !important;
}

View File

@ -1,10 +1,11 @@
<div id="session-container"> <div id="session-container">
<mat-sidenav-container #videoContainer class="sidenav-container"> <mat-sidenav-container #container #videoContainer class="sidenav-container">
<mat-sidenav <mat-sidenav
#sidenav #sidenav
mode="{{ sidenavMode }}" mode="{{ sidenavMode }}"
position="end" position="end"
class="sidenav-menu" class="sidenav-menu"
[ngClass]="{big: settingsPanelOpened}"
fixedInViewport="true" fixedInViewport="true"
fixedTopGap="0" fixedTopGap="0"
fixedBottomGap="0" fixedBottomGap="0"

View File

@ -1,5 +1,6 @@
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef,
Component, Component,
ContentChild, ContentChild,
ElementRef, ElementRef,
@ -31,12 +32,12 @@ import { TokenService } from '../../services/token/token.service';
import { ActionService } from '../../services/action/action.service'; import { ActionService } from '../../services/action/action.service';
import { Signal } from '../../models/signal.model'; import { Signal } from '../../models/signal.model';
import { ParticipantService } from '../../services/participant/participant.service'; import { ParticipantService } from '../../services/participant/participant.service';
import { MatSidenav } from '@angular/material/sidenav'; import { MatDrawerContainer, MatSidenav } from '@angular/material/sidenav';
import { SidenavMode } from '../../models/layout.model'; import { SidenavMode } from '../../models/layout.model';
import { LayoutService } from '../../services/layout/layout.service'; import { LayoutService } from '../../services/layout/layout.service';
import { Subscription, skip } from 'rxjs'; import { Subscription, skip } from 'rxjs';
import { PanelType } from '../../models/panel.model'; import { PanelType } from '../../models/panel.model';
import { PanelService } from '../../services/panel/panel.service'; import { PanelEvent, PanelService } from '../../services/panel/panel.service';
import { RecordingService } from '../../services/recording/recording.service'; import { RecordingService } from '../../services/recording/recording.service';
import { TranslateService } from '../../services/translate/translate.service'; import { TranslateService } from '../../services/translate/translate.service';
import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service'; import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service';
@ -66,6 +67,9 @@ export class SessionComponent implements OnInit {
sideMenu: MatSidenav; sideMenu: MatSidenav;
sidenavMode: SidenavMode = SidenavMode.SIDE; sidenavMode: SidenavMode = SidenavMode.SIDE;
settingsPanelOpened: boolean;
drawer: MatDrawerContainer;
protected readonly SIDENAV_WIDTH_LIMIT_MODE = 790; protected readonly SIDENAV_WIDTH_LIMIT_MODE = 790;
protected menuSubscription: Subscription; protected menuSubscription: Subscription;
@ -87,7 +91,8 @@ export class SessionComponent implements OnInit {
protected panelService: PanelService, protected panelService: PanelService,
private recordingService: RecordingService, private recordingService: RecordingService,
private translateService: TranslateService, private translateService: TranslateService,
private platformService: PlatformService private platformService: PlatformService,
private cd: ChangeDetectorRef
) { ) {
this.log = this.loggerSrv.get('SessionComponent'); this.log = this.loggerSrv.get('SessionComponent');
} }
@ -123,6 +128,22 @@ export class SessionComponent implements OnInit {
}, 0); }, 0);
} }
@ViewChild('container')
set container(container: MatDrawerContainer) {
setTimeout(() => {
if (container) {
this.drawer = container;
this.drawer._contentMarginChanges.subscribe(() => {
setTimeout(() => {
this.stopUpdateLayoutInterval();
this.layoutService.update();
this.drawer.autosize = false;
}, 250);
});
}
}, 0);
}
async ngOnInit() { async ngOnInit() {
if (!this.usedInPrejoinPage) { if (!this.usedInPrejoinPage) {
if (!this.tokenService.getScreenToken()) { if (!this.tokenService.getScreenToken()) {
@ -170,24 +191,31 @@ export class SessionComponent implements OnInit {
protected subscribeToTogglingMenu() { protected subscribeToTogglingMenu() {
this.sideMenu.openedChange.subscribe(() => { this.sideMenu.openedChange.subscribe(() => {
if (this.updateLayoutInterval) { this.stopUpdateLayoutInterval();
clearInterval(this.updateLayoutInterval);
}
this.layoutService.update(); this.layoutService.update();
}); });
this.sideMenu.openedStart.subscribe(() => { this.sideMenu.openedStart.subscribe(() => {
this.updateLayoutInterval = setInterval(() => this.layoutService.update(), 50); this.startUpdateLayoutInterval();
}); });
this.sideMenu.closedStart.subscribe(() => { this.sideMenu.closedStart.subscribe(() => {
this.updateLayoutInterval = setInterval(() => this.layoutService.update(), 50); this.startUpdateLayoutInterval();
}); });
this.menuSubscription = this.panelService.panelOpenedObs this.menuSubscription = this.panelService.panelOpenedObs.pipe(skip(1)).subscribe((ev: PanelEvent) => {
.pipe(skip(1))
.subscribe((ev: { opened: boolean; type?: PanelType | string }) => {
if (this.sideMenu) { if (this.sideMenu) {
this.settingsPanelOpened = ev.opened && ev.type === PanelType.SETTINGS;
if (this.sideMenu.opened && ev.opened) {
if (ev.type === PanelType.SETTINGS || ev.oldType === PanelType.SETTINGS) {
// Switch from SETTINGS to another panel and vice versa.
// As the SETTINGS panel will be bigger than others, the sidenav container must be updated.
// Setting autosize to 'true' allows update it.
this.drawer.autosize = true;
this.startUpdateLayoutInterval();
}
}
ev.opened ? this.sideMenu.open() : this.sideMenu.close(); ev.opened ? this.sideMenu.open() : this.sideMenu.close();
} }
}); });
@ -324,4 +352,16 @@ export class SessionComponent implements OnInit {
this.recordingService.stopRecording(event); this.recordingService.stopRecording(event);
}); });
} }
private startUpdateLayoutInterval() {
this.updateLayoutInterval = setInterval(() => {
this.layoutService.update();
}, 50);
}
private stopUpdateLayoutInterval() {
if (this.updateLayoutInterval) {
clearInterval(this.updateLayoutInterval);
}
}
} }

View File

@ -0,0 +1,17 @@
.device-container-element mat-form-field {
width: 100%;
margin-top: 10px;
color: #000000;
}
.device-container-element button {
margin: auto 10px auto auto;
}
.device-container-element {
display: flex;
}
.warn-btn {
color: var(--ov-text-color);
background-color: var(--ov-warn-color) !important;
}

View File

@ -0,0 +1,26 @@
<div class="device-container-element">
<button
mat-icon-button
id="microhpone-button"
[disabled]="!hasAudioDevices"
[class.warn-btn]="isAudioMuted"
(click)="toggleMic()"
>
<mat-icon *ngIf="!isAudioMuted" matTooltip="{{ 'TOOLBAR.MUTE_AUDIO' | translate }}" id="mic">mic</mat-icon>
<mat-icon *ngIf="isAudioMuted" matTooltip="{{ 'TOOLBAR.UNMUTE_AUDIO' | translate }}" id="mic_off"
>mic_off</mat-icon
>
</button>
<mat-form-field>
<mat-label>{{ 'PREJOIN.AUDIO_DEVICE' | translate }}</mat-label>
<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>
</mat-select>
</mat-form-field>
</div>

View File

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

View File

@ -0,0 +1,80 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { PublisherProperties } from 'openvidu-browser';
import { DeviceService } from '../../../services/device/device.service';
import { OpenViduService } from '../../../services/openvidu/openvidu.service';
import { StorageService } from '../../../services/storage/storage.service';
import { CustomDevice } from '../../../models/device.model';
import { ParticipantAbstractModel } from '../../../models/participant.model';
import { ParticipantService } from '../../../services/participant/participant.service';
import { Subscription } from 'rxjs';
/**
* @internal
*/
@Component({
selector: 'ov-audio-devices-select',
templateUrl: './audio-devices.component.html',
styleUrls: ['./audio-devices.component.css']
})
export class AudioDevicesComponent implements OnInit, OnDestroy {
hasAudioDevices: boolean;
isAudioMuted: boolean;
microphoneSelected: CustomDevice;
microphones: CustomDevice[] = [];
private localParticipantSubscription: Subscription;
constructor(
private openviduService: OpenViduService,
private deviceSrv: DeviceService,
private storageSrv: StorageService,
protected participantService: ParticipantService
) {}
ngOnInit(): void {
this.subscribeToParticipantMediaProperties();
this.hasAudioDevices = this.deviceSrv.hasAudioDeviceAvailable();
this.microphones = this.deviceSrv.getMicrophones();
this.microphoneSelected = this.deviceSrv.getMicrophoneSelected();
this.isAudioMuted = this.deviceSrv.isAudioMuted();
if (this.openviduService.isSessionConnected()) {
this.isAudioMuted = !this.participantService.isMyAudioActive();
} else {
this.isAudioMuted = this.deviceSrv.isAudioMuted();
}
}
ngOnDestroy() {
if (this.localParticipantSubscription) this.localParticipantSubscription.unsubscribe();
}
toggleMic() {
const publish = this.isAudioMuted;
this.openviduService.publishAudio(publish);
}
async onMicrophoneSelected(event: any) {
const audioSource = event?.value;
if (this.deviceSrv.needUpdateAudioTrack(audioSource)) {
//TODO: Uncomment this when replaceTrack issue is fixed
// const pp: PublisherProperties = { audioSource, videoSource: false };
// await this.openviduService.replaceTrack(VideoType.CAMERA, pp);
// TODO: Remove this when replaceTrack issue is fixed
const mirror = this.deviceSrv.cameraNeedsMirror(this.deviceSrv.getCameraSelected().device);
const pp: PublisherProperties = { videoSource: this.deviceSrv.getCameraSelected().device, audioSource, mirror };
await this.openviduService.republishTrack(pp);
this.deviceSrv.setMicSelected(audioSource);
this.microphoneSelected = this.deviceSrv.getMicrophoneSelected();
}
}
private subscribeToParticipantMediaProperties() {
this.localParticipantSubscription = this.participantService.localParticipantObs.subscribe((p: ParticipantAbstractModel) => {
if (p) {
this.isAudioMuted = !p.hasAudioActive();
this.storageSrv.setAudioMuted(this.isAudioMuted);
}
});
}
}

View File

@ -0,0 +1,4 @@
.lang-button {
background-color: var(--ov-logo-background-color);
color: var(--ov-text-color);
}

View File

@ -0,0 +1,9 @@
<button mat-flat-button [matMenuTriggerFor]="menu" class="lang-button">
<span>{{langSelected?.name}}</span>
<mat-icon>expand_more</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item *ngFor="let lang of languages" (click)="onLangSelected(lang.ISO)">
<span>{{lang.name}}</span>
</button>
</mat-menu>

View File

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

View File

@ -0,0 +1,26 @@
import { Component, OnInit } from '@angular/core';
import { StorageService } from '../../../services/storage/storage.service';
import { TranslateService } from '../../../services/translate/translate.service';
@Component({
selector: 'ov-lang-selector',
templateUrl: './lang-selector.component.html',
styleUrls: ['./lang-selector.component.css']
})
export class LangSelectorComponent implements OnInit {
langSelected: { name: string; ISO: string };
languages: { name: string; ISO: string }[] = [];
constructor(private translateService: TranslateService, private storageSrv: StorageService) {}
ngOnInit(): void {
this.languages = this.translateService.getLanguagesInfo();
this.langSelected = this.translateService.getLangSelected();
}
onLangSelected(lang: string) {
this.translateService.setLanguage(lang);
this.storageSrv.setLang(lang);
this.langSelected = this.translateService.getLangSelected();
}
}

View File

@ -0,0 +1,21 @@
#nickname-input-container {
display: flex;
}
#nickname-input-container button {
margin: auto 10px auto auto;
}
#nickname-input-container button.mat-button-disabled {
color: #000000 !important;
}
#nickname-input-container mat-form-field {
width: 100%;
margin-top: 10px;
color: #000000;
}
#nickname-input-container mat-form-field {
color: #000000;
}

View File

@ -0,0 +1,16 @@
<div id="nickname-input-container">
<button mat-icon-button disabled>
<mat-icon>person</mat-icon>
</button>
<mat-form-field appearance="standard">
<mat-label>{{ 'PREJOIN.NICKNAME' | translate }}</mat-label>
<input
matInput
type="text"
maxlength="20"
[(ngModel)]="nickname"
(change)="updateNickname()"
autocomplete="off"
/>
</mat-form-field>
</div>

View File

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

View File

@ -0,0 +1,36 @@
import { Component, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { ParticipantAbstractModel } from '../../../models/participant.model';
import { ParticipantService } from '../../../services/participant/participant.service';
import { StorageService } from '../../../services/storage/storage.service';
@Component({
selector: 'ov-nickname-input',
templateUrl: './nickname-input.component.html',
styleUrls: ['./nickname-input.component.css']
})
export class NicknameInputComponent implements OnInit {
nickname: string;
localParticipantSubscription: Subscription;
constructor(private participantService: ParticipantService, private storageSrv: StorageService) {}
ngOnInit(): void {
this.subscribeToParticipantProperties();
this.nickname = this.participantService.getMyNickname();
}
updateNickname() {
this.nickname = this.nickname === '' ? this.participantService.getMyNickname() : this.nickname;
this.participantService.setMyNickname(this.nickname);
this.storageSrv.setNickname(this.nickname);
}
private subscribeToParticipantProperties() {
this.localParticipantSubscription = this.participantService.localParticipantObs.subscribe((p: ParticipantAbstractModel) => {
if (p) {
this.nickname = p.getNickname();
}
});
}
}

View File

@ -0,0 +1,25 @@
#camera-button {
border-radius: var(--ov-buttons-radius);
/* background-color: var(--ov-secondary-color) !important; */
/* color: var(--ov-text-color) !important; */
}
.device-container-element mat-form-field {
width: 100%;
margin-top: 10px;
color: #000000;
}
.device-container-element button {
margin: auto 10px auto auto;
}
.device-container-element {
display: flex;
}
.warn-btn {
color: var(--ov-text-color);
background-color: var(--ov-warn-color) !important;
}

View File

@ -0,0 +1,24 @@
<div class="device-container-element">
<button
mat-icon-button
id="camera-button"
[disabled]="!hasVideoDevices || videoMuteChanging"
[class.warn-btn]="isVideoMuted"
(click)="toggleCam()"
>
<mat-icon *ngIf="!isVideoMuted" matTooltip="{{ 'TOOLBAR.MUTE_VIDEO' | translate }}" id="videocam">videocam</mat-icon>
<mat-icon *ngIf="isVideoMuted" matTooltip="{{ 'TOOLBAR.UNMUTE_VIDEO' | translate }}" id="videocam_off">videocam_off</mat-icon>
</button>
<mat-form-field>
<mat-label>{{ 'PREJOIN.VIDEO_DEVICE' | translate }}</mat-label>
<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>
</mat-select>
</mat-form-field>
</div>

View File

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

View File

@ -0,0 +1,102 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { PublisherProperties } from 'openvidu-browser';
import { Subscription } from 'rxjs';
import { CustomDevice } from '../../../models/device.model';
import { PanelType } from '../../../models/panel.model';
import { ParticipantAbstractModel } from '../../../models/participant.model';
import { DeviceService } from '../../../services/device/device.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';
import { VirtualBackgroundService } from '../../../services/virtual-background/virtual-background.service';
/**
* @internal
*/
@Component({
selector: 'ov-video-devices-select',
templateUrl: './video-devices.component.html',
styleUrls: ['./video-devices.component.css']
})
export class VideoDevicesComponent implements OnInit, OnDestroy {
videoMuteChanging: boolean;
isVideoMuted: boolean;
cameraSelected: CustomDevice;
hasVideoDevices: boolean;
cameras: CustomDevice[];
localParticipantSubscription: Subscription;
constructor(
private openviduService: OpenViduService,
protected panelService: PanelService,
private storageSrv: StorageService,
private deviceSrv: DeviceService,
protected participantService: ParticipantService,
private backgroundService: VirtualBackgroundService
) {}
ngOnInit(): void {
this.subscribeToParticipantMediaProperties();
this.hasVideoDevices = this.deviceSrv.hasVideoDeviceAvailable();
this.cameras = this.deviceSrv.getCameras();
this.cameraSelected = this.deviceSrv.getCameraSelected();
if (this.openviduService.isSessionConnected()) {
this.isVideoMuted = !this.participantService.getLocalParticipant().isCameraVideoActive();
} else {
this.isVideoMuted = this.deviceSrv.isVideoMuted();
}
}
async ngOnDestroy() {
this.cameras = [];
if (this.localParticipantSubscription) this.localParticipantSubscription.unsubscribe();
}
async toggleCam() {
this.videoMuteChanging = true;
const publish = this.isVideoMuted;
await this.openviduService.publishVideo(publish);
this.storageSrv.setVideoMuted(this.isVideoMuted);
if (this.isVideoMuted && this.panelService.isExternalPanelOpened()) {
this.panelService.togglePanel(PanelType.BACKGROUND_EFFECTS);
}
this.videoMuteChanging = false;
}
async onCameraSelected(event: any) {
const videoSource = event?.value;
// Is New deviceId different from the old one?
if (this.deviceSrv.needUpdateVideoTrack(videoSource)) {
const mirror = this.deviceSrv.cameraNeedsMirror(videoSource);
//TODO: Uncomment this when replaceTrack issue is fixed
// const pp: PublisherProperties = { videoSource, audioSource: false, mirror };
// await this.openviduService.replaceTrack(VideoType.CAMERA, pp);
// TODO: Remove this when replaceTrack issue is fixed
const pp: PublisherProperties = { videoSource, audioSource: this.deviceSrv.getMicrophoneSelected().device, mirror };
// Reapply Virtual Background to new Publisher if necessary
const backgroundSelected = this.backgroundService.backgroundSelected.getValue();
if (this.backgroundService.isBackgroundApplied()) {
await this.backgroundService.removeBackground();
}
await this.openviduService.republishTrack(pp);
if (this.backgroundService.isBackgroundApplied()) {
const bgSelected = this.backgroundService.backgrounds.find((b) => b.id === backgroundSelected);
if (bgSelected) {
await this.backgroundService.applyBackground(bgSelected);
}
}
this.deviceSrv.setCameraSelected(videoSource);
this.cameraSelected = this.deviceSrv.getCameraSelected();
}
}
protected subscribeToParticipantMediaProperties() {
this.localParticipantSubscription = this.participantService.localParticipantObs.subscribe((p: ParticipantAbstractModel) => {
if (p) {
this.isVideoMuted = !p.isCameraVideoActive();
}
});
}
}

View File

@ -212,7 +212,7 @@ export class StreamComponent implements OnInit {
* @ignore * @ignore
*/ */
toggleNicknameForm() { toggleNicknameForm() {
if (this._stream.participant.local) { if (this._stream?.participant?.local) {
this.toggleNickname = !this.toggleNickname; this.toggleNickname = !this.toggleNickname;
} }
} }
@ -260,7 +260,7 @@ export class StreamComponent implements OnInit {
this.showAudioDetection = value; this.showAudioDetection = value;
// this.cd.markForCheck(); // this.cd.markForCheck();
}); });
this.settingsButtonSub = this.libService.settingsButtonObs.subscribe((value: boolean) => { this.settingsButtonSub = this.libService.streamSettingsButtonObs.subscribe((value: boolean) => {
this.showSettingsButton = value; this.showSettingsButton = value;
// this.cd.markForCheck(); // this.cd.markForCheck();
}); });

View File

@ -124,6 +124,15 @@
.mat-icon-button[disabled] { .mat-icon-button[disabled] {
color: #fff; color: #fff;
} }
.divider {
margin: 8px 0px;
}
::ng-deep .mat-menu-item {
/* margin-bottom: 10px; */
height: 40px;
line-height: 40px;
}
@media (max-width: 750px) { @media (max-width: 750px) {
#session-name { #session-name {
display: none; display: none;
@ -143,10 +152,8 @@
} }
} }
::ng-deep .mat-menu-panel {
margin-bottom: 10px;
}
@keyframes blinker { @keyframes blinker {
50% { opacity: 0.3; } 50% {
opacity: 0.3;
}
} }

View File

@ -102,6 +102,14 @@
<mat-icon>auto_awesome</mat-icon> <mat-icon>auto_awesome</mat-icon>
<span>{{ 'TOOLBAR.BACKGROUND' | translate }}</span> <span>{{ 'TOOLBAR.BACKGROUND' | translate }}</span>
</button> </button>
<mat-divider class="divider" *ngIf="!isMinimal && showSettingsButton"></mat-divider>
<!-- Settings button -->
<button *ngIf="!isMinimal && showSettingsButton" mat-menu-item id="settings-btn" (click)="toggleSettings()">
<mat-icon>settings</mat-icon>
<span>{{ 'TOOLBAR.SETTINGS' | translate }}</span>
</button>
</mat-menu> </mat-menu>
<!-- External additional buttons --> <!-- External additional buttons -->

View File

@ -15,7 +15,7 @@ import {
import { first, skip, Subscription } from 'rxjs'; import { first, skip, Subscription } from 'rxjs';
import { TokenService } from '../../services/token/token.service'; import { TokenService } from '../../services/token/token.service';
import { ChatService } from '../../services/chat/chat.service'; import { ChatService } from '../../services/chat/chat.service';
import { PanelService } from '../../services/panel/panel.service'; import { PanelEvent, PanelService } from '../../services/panel/panel.service';
import { DocumentService } from '../../services/document/document.service'; import { DocumentService } from '../../services/document/document.service';
import { OpenViduService } from '../../services/openvidu/openvidu.service'; import { OpenViduService } from '../../services/openvidu/openvidu.service';
@ -272,6 +272,11 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
*/ */
showRecordingButton: boolean = true; showRecordingButton: boolean = true;
/**
* @ignore
*/
showSettingsButton: boolean = true;
/** /**
* @ignore * @ignore
*/ */
@ -344,6 +349,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
private displayLogoSub: Subscription; private displayLogoSub: Subscription;
private displaySessionNameSub: Subscription; private displaySessionNameSub: Subscription;
private screenSizeSub: Subscription; private screenSizeSub: Subscription;
private settingsButtonSub: Subscription;
private currentWindowHeight = window.innerHeight; private currentWindowHeight = window.innerHeight;
/** /**
@ -432,6 +438,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
if (this.activitiesPanelButtonSub) this.activitiesPanelButtonSub.unsubscribe(); if (this.activitiesPanelButtonSub) this.activitiesPanelButtonSub.unsubscribe();
if (this.recordingSubscription) this.recordingSubscription.unsubscribe(); if (this.recordingSubscription) this.recordingSubscription.unsubscribe();
if (this.screenSizeSub) this.screenSizeSub.unsubscribe(); if (this.screenSizeSub) this.screenSizeSub.unsubscribe();
if (this.settingsButtonSub) this.settingsButtonSub.unsubscribe();
} }
/** /**
@ -522,6 +529,13 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
this.panelService.togglePanel(PanelType.BACKGROUND_EFFECTS); this.panelService.togglePanel(PanelType.BACKGROUND_EFFECTS);
} }
/**
* @ignore
*/
toggleSettings() {
this.panelService.togglePanel(PanelType.SETTINGS);
}
/** /**
* @ignore * @ignore
*/ */
@ -564,8 +578,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
}); });
} }
protected subscribeToMenuToggling() { protected subscribeToMenuToggling() {
this.panelTogglingSubscription = this.panelService.panelOpenedObs.subscribe( this.panelTogglingSubscription = this.panelService.panelOpenedObs.subscribe((ev: PanelEvent) => {
(ev: { opened: boolean; type?: PanelType | string }) => {
this.isChatOpened = ev.opened && ev.type === PanelType.CHAT; this.isChatOpened = ev.opened && ev.type === PanelType.CHAT;
this.isParticipantsOpened = ev.opened && ev.type === PanelType.PARTICIPANTS; this.isParticipantsOpened = ev.opened && ev.type === PanelType.PARTICIPANTS;
this.isActivitiesOpened = ev.opened && ev.type === PanelType.ACTIVITIES; this.isActivitiesOpened = ev.opened && ev.type === PanelType.ACTIVITIES;
@ -573,8 +586,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
this.unreadMessages = 0; this.unreadMessages = 0;
} }
this.cd.markForCheck(); this.cd.markForCheck();
} });
);
} }
protected subscribeToChatMessages() { protected subscribeToChatMessages() {
@ -590,7 +602,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
this.localParticipantSubscription = this.participantService.localParticipantObs.subscribe((p: ParticipantAbstractModel) => { this.localParticipantSubscription = this.participantService.localParticipantObs.subscribe((p: ParticipantAbstractModel) => {
if (p) { if (p) {
this.isWebcamVideoActive = p.isCameraVideoActive(); this.isWebcamVideoActive = p.isCameraVideoActive();
this.isAudioActive = p.isCameraAudioActive() || p.isScreenAudioActive(); this.isAudioActive = p.hasAudioActive();
this.isScreenShareActive = p.isScreenActive(); this.isScreenShareActive = p.isScreenActive();
this.isSessionCreator = p.getRole() === OpenViduRole.MODERATOR; this.isSessionCreator = p.getRole() === OpenViduRole.MODERATOR;
this.cd.markForCheck(); this.cd.markForCheck();
@ -627,11 +639,17 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
this.cd.markForCheck(); this.cd.markForCheck();
}); });
this.recordingButtonSub = this.libService.recordingButton.subscribe((value: boolean) => { this.recordingButtonSub = this.libService.recordingButtonObs.subscribe((value: boolean) => {
this.showRecordingButton = value; this.showRecordingButton = value;
this.checkDisplayMoreOptions(); this.checkDisplayMoreOptions();
this.cd.markForCheck(); this.cd.markForCheck();
}); });
this.settingsButtonSub = this.libService.toolbarSettingsButtonObs.subscribe((value: boolean) => {
this.showSettingsButton = value;
this.checkDisplayMoreOptions();
this.cd.markForCheck();
});
this.chatPanelButtonSub = this.libService.chatPanelButtonObs.subscribe((value: boolean) => { this.chatPanelButtonSub = this.libService.chatPanelButtonObs.subscribe((value: boolean) => {
this.showChatPanelButton = value; this.showChatPanelButton = value;
this.cd.markForCheck(); this.cd.markForCheck();
@ -661,12 +679,12 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
private subscribeToScreenSize() { private subscribeToScreenSize() {
this.screenSizeSub = this.documentService.screenSizeObs.subscribe((change: MediaChange[]) => { this.screenSizeSub = this.documentService.screenSizeObs.subscribe((change: MediaChange[]) => {
console.log(change[0].mqAlias)
this.screenSize = change[0].mqAlias; this.screenSize = change[0].mqAlias;
}); });
} }
private checkDisplayMoreOptions() { private checkDisplayMoreOptions() {
this.showMoreOptionsButton = this.showFullscreenButton || this.showBackgroundEffectsButton || this.showRecordingButton; this.showMoreOptionsButton =
this.showFullscreenButton || this.showBackgroundEffectsButton || this.showRecordingButton || this.showSettingsButton;
} }
} }

View File

@ -75,6 +75,10 @@
<ov-background-effects-panel id="default-background-effects-panel"></ov-background-effects-panel> <ov-background-effects-panel id="default-background-effects-panel"></ov-background-effects-panel>
</ng-template> </ng-template>
<ng-template #settingsPanel>
<ov-settings-panel id="default-settings-panel"></ov-settings-panel>
</ng-template>
<ng-template #activitiesPanel> <ng-template #activitiesPanel>
<ng-container *ngTemplateOutlet="openviduAngularActivitiesPanelTemplate"></ng-container> <ng-container *ngTemplateOutlet="openviduAngularActivitiesPanelTemplate"></ng-container>
</ng-template> </ng-template>

View File

@ -688,7 +688,7 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
// The devices are initialized without labels in Firefox. // The devices are initialized without labels in Firefox.
// We need to force an update after publisher is allowed. // We need to force an update after publisher is allowed.
if (this.deviceSrv.areEmptyLabels()) { if (this.deviceSrv.areEmptyLabels()) {
await this.deviceSrv.forceUpdate(); await this.deviceSrv.initializeDevices();
if (this.deviceSrv.hasAudioDeviceAvailable()) { if (this.deviceSrv.hasAudioDeviceAvailable()) {
const audioLabel = this.participantService.getMyCameraPublisher()?.stream?.getMediaStream()?.getAudioTracks()[0]?.label; const audioLabel = this.participantService.getMyCameraPublisher()?.stream?.getMediaStream()?.getAudioTracks()[0]?.label;
this.deviceSrv.setMicSelected(audioLabel); this.deviceSrv.setMicSelected(audioLabel);

View File

@ -19,7 +19,8 @@ import {
ToolbarDisplayLogoDirective, ToolbarDisplayLogoDirective,
ToolbarActivitiesPanelButtonDirective, ToolbarActivitiesPanelButtonDirective,
ToolbarBackgroundEffectsButtonDirective, ToolbarBackgroundEffectsButtonDirective,
ToolbarRecordingButtonDirective ToolbarRecordingButtonDirective,
ToolbarSettingsButtonDirective
} from './toolbar.directive'; } from './toolbar.directive';
import { import {
AudioMutedDirective, AudioMutedDirective,
@ -47,6 +48,7 @@ import {
ToolbarActivitiesPanelButtonDirective, ToolbarActivitiesPanelButtonDirective,
ToolbarDisplaySessionNameDirective, ToolbarDisplaySessionNameDirective,
ToolbarDisplayLogoDirective, ToolbarDisplayLogoDirective,
ToolbarSettingsButtonDirective,
StreamDisplayParticipantNameDirective, StreamDisplayParticipantNameDirective,
StreamDisplayAudioDetectionDirective, StreamDisplayAudioDetectionDirective,
StreamSettingsButtonDirective, StreamSettingsButtonDirective,
@ -75,6 +77,7 @@ import {
ToolbarActivitiesPanelButtonDirective, ToolbarActivitiesPanelButtonDirective,
ToolbarDisplaySessionNameDirective, ToolbarDisplaySessionNameDirective,
ToolbarDisplayLogoDirective, ToolbarDisplayLogoDirective,
ToolbarSettingsButtonDirective,
StreamDisplayParticipantNameDirective, StreamDisplayParticipantNameDirective,
StreamDisplayAudioDetectionDirective, StreamDisplayAudioDetectionDirective,
StreamSettingsButtonDirective, StreamSettingsButtonDirective,

View File

@ -142,8 +142,8 @@ export class StreamSettingsButtonDirective implements AfterViewInit, OnDestroy {
} }
update(value: boolean) { update(value: boolean) {
if (this.libService.settingsButton.getValue() !== value) { if (this.libService.streamSettingsButton.getValue() !== value) {
this.libService.settingsButton.next(value); this.libService.streamSettingsButton.next(value);
} }
} }

View File

@ -242,6 +242,65 @@ export class ToolbarBackgroundEffectsButtonDirective implements AfterViewInit, O
} }
} }
/**
* The **settingsButton** directive allows show/hide the settings toolbar button.
*
* Default: `true`
*
* It can be used in the parent element {@link VideoconferenceComponent} specifying the name of the `toolbar` component:
*
* @example
* <ov-videoconference [toolbarSettingsButton]="false"></ov-videoconference>
*
* \
* And it also can be used in the {@link ToolbarComponent}.
* @example
* <ov-toolbar [settingsButton]="false"></ov-toolbar>
*/
@Directive({
selector: 'ov-videoconference[toolbarSettingsButton], ov-toolbar[settingsButton]'
})
export class ToolbarSettingsButtonDirective implements AfterViewInit, OnDestroy {
/**
* @ignore
*/
@Input() set toolbarSettingsButton(value: boolean) {
this.settingsValue = value;
this.update(this.settingsValue);
}
/**
* @ignore
*/
@Input() set settingsButton(value: boolean) {
this.settingsValue = value;
this.update(this.settingsValue);
}
private settingsValue: boolean = true;
/**
* @ignore
*/
constructor(public elementRef: ElementRef, private libService: OpenViduAngularConfigService) {}
ngAfterViewInit() {
this.update(this.settingsValue);
}
ngOnDestroy(): void {
this.clear();
}
private clear() {
this.settingsValue = true;
this.update(true);
}
private update(value: boolean) {
if (this.libService.toolbarSettingsButton.getValue() !== value) {
this.libService.toolbarSettingsButton.next(value);
}
}
}
/** /**
* The **leaveButton** directive allows show/hide the leave toolbar button. * The **leaveButton** directive allows show/hide the leave toolbar button.
* *

View File

@ -259,7 +259,7 @@ export class PanelDirective {
* this.subscribeToPanelToggling(); * this.subscribeToPanelToggling();
* } * }
* subscribeToPanelToggling() { * subscribeToPanelToggling() {
* this.panelService.panelOpenedObs.subscribe((ev: { opened: boolean; type?: PanelType | string }) => { * this.panelService.panelOpenedObs.subscribe((ev: PanelEvent) => {
* this.showExternalPanel = ev.opened && ev.type === 'my-panel'; * this.showExternalPanel = ev.opened && ev.type === 'my-panel';
* this.showExternalPanel2 = ev.opened && ev.type === 'my-panel2'; * this.showExternalPanel2 = ev.opened && ev.type === 'my-panel2';
* }); * });

View File

@ -37,6 +37,7 @@
"BACKGROUND":"背景效果", "BACKGROUND":"背景效果",
"START_RECORDING": "开始录音", "START_RECORDING": "开始录音",
"STOP_RECORDING": "停止录制", "STOP_RECORDING": "停止录制",
"SETTINGS":"设置",
"LEAVE":"离开会议", "LEAVE":"离开会议",
"PARTICIPANTS":"参与者", "PARTICIPANTS":"参与者",
"CHAT":"聊天", "CHAT":"聊天",
@ -64,6 +65,13 @@
"CAMERA": "摄像头", "CAMERA": "摄像头",
"SCREEN": "屏幕" "SCREEN": "屏幕"
}, },
"SETTINGS": {
"TITLE": "设置",
"GENERAL": "一般的",
"VIDEO": "视频",
"AUDIO": "声音的",
"LANGUAGE": "语"
},
"BACKGROUND": { "BACKGROUND": {
"TITLE": "背景效果", "TITLE": "背景效果",
"BLURRED_SECTION": "没有效果和模糊的背景", "BLURRED_SECTION": "没有效果和模糊的背景",

View File

@ -37,6 +37,7 @@
"BACKGROUND": "Hintergrund-Effekte", "BACKGROUND": "Hintergrund-Effekte",
"START_RECORDING": "Aufzeichnung starten", "START_RECORDING": "Aufzeichnung starten",
"STOP_RECORDING": "Aufzeichnung stoppen", "STOP_RECORDING": "Aufzeichnung stoppen",
"SETTINGS": "Einstellungen",
"LEAVE": "Die Sitzung verlassen", "LEAVE": "Die Sitzung verlassen",
"PARTICIPANTS": "Teilnehmer", "PARTICIPANTS": "Teilnehmer",
"CHAT": "Chat", "CHAT": "Chat",
@ -64,6 +65,13 @@
"CAMERA": "KAMERA", "CAMERA": "KAMERA",
"SCREEN": "BILDSCHIRM" "SCREEN": "BILDSCHIRM"
}, },
"SETTINGS": {
"TITLE": "Einstellungen",
"GENERAL": "Allgemein",
"VIDEO": "Video",
"AUDIO": "Audio",
"LANGUAGE": "Sprache"
},
"BACKGROUND": { "BACKGROUND": {
"TITLE": "Hintergrund-Effekte", "TITLE": "Hintergrund-Effekte",
"BLURRED_SECTION": "Keine Effekte und unscharfer Hintergrund", "BLURRED_SECTION": "Keine Effekte und unscharfer Hintergrund",

View File

@ -38,6 +38,7 @@
"BACKGROUND": "Background effects", "BACKGROUND": "Background effects",
"START_RECORDING": "Start recording", "START_RECORDING": "Start recording",
"STOP_RECORDING": "Stop recording", "STOP_RECORDING": "Stop recording",
"SETTINGS": "Settings",
"LEAVE": "Leave the session", "LEAVE": "Leave the session",
"PARTICIPANTS": "Participants", "PARTICIPANTS": "Participants",
"CHAT": "Chat", "CHAT": "Chat",
@ -65,6 +66,13 @@
"CAMERA": "CAMERA", "CAMERA": "CAMERA",
"SCREEN": "SCREEN" "SCREEN": "SCREEN"
}, },
"SETTINGS": {
"TITLE": "Settings",
"GENERAL": "General",
"VIDEO": "Video",
"AUDIO": "Audio",
"LANGUAGE": "Language"
},
"BACKGROUND": { "BACKGROUND": {
"TITLE": "Background effects", "TITLE": "Background effects",
"BLURRED_SECTION": "No effects and blurred background", "BLURRED_SECTION": "No effects and blurred background",

View File

@ -37,6 +37,7 @@
"BACKGROUND": "Efectos de fondo", "BACKGROUND": "Efectos de fondo",
"START_RECORDING": "Iniciar grabación", "START_RECORDING": "Iniciar grabación",
"STOP_RECORDING": "Detener grabación", "STOP_RECORDING": "Detener grabación",
"SETTINGS": "Configuración",
"LEAVE": "Salir de la sesión", "LEAVE": "Salir de la sesión",
"PARTICIPANTS": "Participantes", "PARTICIPANTS": "Participantes",
"CHAT": "Chat", "CHAT": "Chat",
@ -64,6 +65,13 @@
"CAMERA": "CÁMARA", "CAMERA": "CÁMARA",
"SCREEN": "PANTALLA" "SCREEN": "PANTALLA"
}, },
"SETTINGS": {
"TITLE": "Configuración",
"GENERAL": "General",
"VIDEO": "Video",
"AUDIO": "Audio",
"LANGUAGE": "Idioma"
},
"BACKGROUND": { "BACKGROUND": {
"TITLE": "Efectos de fondo", "TITLE": "Efectos de fondo",
"BLURRED_SECTION": "Sin efectos y fondo desenfocado", "BLURRED_SECTION": "Sin efectos y fondo desenfocado",

View File

@ -37,6 +37,7 @@
"BACKGROUND": "Effets de fond", "BACKGROUND": "Effets de fond",
"START_RECORDING": "démarrer l'enregistrement", "START_RECORDING": "démarrer l'enregistrement",
"STOP_RECORDING": "Arrêter l'enregistrement", "STOP_RECORDING": "Arrêter l'enregistrement",
"SETTINGS": "Paramètres",
"LEAVE": "Quitter la session", "LEAVE": "Quitter la session",
"PARTICIPANTS": "Participants", "PARTICIPANTS": "Participants",
"CHAT": "Chat", "CHAT": "Chat",
@ -64,6 +65,13 @@
"CAMERA": "CAMÉRA", "CAMERA": "CAMÉRA",
"SCREEN": "ÉCRAN" "SCREEN": "ÉCRAN"
}, },
"SETTINGS": {
"TITLE": "Paramètres",
"GENERAL": "Général",
"VIDEO": "Vidéo",
"AUDIO": "l'audio",
"LANGUAGE": "Langue"
},
"BACKGROUND": { "BACKGROUND": {
"TITLE": "Effets de fond", "TITLE": "Effets de fond",
"BLURRED_SECTION": "Aucun effet et arrière-plan flou", "BLURRED_SECTION": "Aucun effet et arrière-plan flou",

View File

@ -37,6 +37,7 @@
"BACKGROUND": "पृष्ठभूमि प्रभाव", "BACKGROUND": "पृष्ठभूमि प्रभाव",
"START_RECORDING": "रिकॉर्डिंग प्रारंभ करें", "START_RECORDING": "रिकॉर्डिंग प्रारंभ करें",
"STOP_RECORDING": "रिकॉर्डिंग रोकें", "STOP_RECORDING": "रिकॉर्डिंग रोकें",
"SETTINGS": "सेटिंग्स",
"LEAVE": "सत्र छोड़ें", "LEAVE": "सत्र छोड़ें",
"PARTICIPANTS": "सदस्य", "PARTICIPANTS": "सदस्य",
"CHAT": "बातचीत", "CHAT": "बातचीत",
@ -64,6 +65,13 @@
"CAMERA": "कैमरा", "CAMERA": "कैमरा",
"SCREEN": "स्क्रीन" "SCREEN": "स्क्रीन"
}, },
"SETTINGS": {
"TITLE": "सेटिंग्स",
"GENERAL": "सामान्य",
"VIDEO": "वीडियो",
"AUDIO": "ऑडियो",
"LANGUAGE": "भाषा"
},
"BACKGROUND": { "BACKGROUND": {
"TITLE": "पृष्ठभूमि प्रभाव", "TITLE": "पृष्ठभूमि प्रभाव",
"BLURRED_SECTION": "कोई प्रभाव नहीं है और पृष्ठभूमि धुंधली है", "BLURRED_SECTION": "कोई प्रभाव नहीं है और पृष्ठभूमि धुंधली है",

View File

@ -37,6 +37,7 @@
"BACKGROUND": "Effetti di sfondo", "BACKGROUND": "Effetti di sfondo",
"START_RECORDING": "Avvia registrazione", "START_RECORDING": "Avvia registrazione",
"STOP_RECORDING": "Interrompi registrazione", "STOP_RECORDING": "Interrompi registrazione",
"SETTINGS": "Impostazioni",
"LEAVE": "Abbandona la sessione", "LEAVE": "Abbandona la sessione",
"PARTICIPANTS": "Partecipanti", "PARTICIPANTS": "Partecipanti",
"CHAT": "Chat", "CHAT": "Chat",
@ -64,6 +65,13 @@
"CAMERA": "CAMERA", "CAMERA": "CAMERA",
"SCREEN": "SCREEN" "SCREEN": "SCREEN"
}, },
"SETTINGS": {
"TITLE": "Impostazioni",
"GENERAL": "Generale",
"VIDEO": "video",
"AUDIO": "Audio",
"LANGUAGE":"Lingua"
},
"BACKGROUND": { "BACKGROUND": {
"TITLE": "Effetti di sfondo", "TITLE": "Effetti di sfondo",
"BLURRED_SECTION": "Nessun effetto e sfondo sfocato", "BLURRED_SECTION": "Nessun effetto e sfondo sfocato",

View File

@ -37,6 +37,7 @@
"BACKGROUND": "背景効果", "BACKGROUND": "背景効果",
"START_RECORDING": "録画開始", "START_RECORDING": "録画開始",
"STOP_RECORDING": "録画の停止", "STOP_RECORDING": "録画の停止",
"SETTINGS": "設定",
"LEAVE": "セッションを終了する", "LEAVE": "セッションを終了する",
"PARTICIPANTS": "参加者", "PARTICIPANTS": "参加者",
"CHAT": "チャット", "CHAT": "チャット",
@ -64,6 +65,13 @@
"CAMERA": "カメラ", "CAMERA": "カメラ",
"SCREEN": "スクリーン" "SCREEN": "スクリーン"
}, },
"SETTINGS": {
"TITLE": "設定",
"GENERAL": "全般的",
"VIDEO": "ビデオ",
"AUDIO": "オーディオ",
"LANGUAGE":"言語"
},
"BACKGROUND": { "BACKGROUND": {
"TITLE": "背景効果", "TITLE": "背景効果",
"BLURRED_SECTION": "エフェクトなし、ぼやけた背景", "BLURRED_SECTION": "エフェクトなし、ぼやけた背景",

View File

@ -37,6 +37,7 @@
"BACKGROUND": "Achtergrondeffecten", "BACKGROUND": "Achtergrondeffecten",
"START_RECORDING": "Start opname", "START_RECORDING": "Start opname",
"STOP_RECORDING": "Stop opname", "STOP_RECORDING": "Stop opname",
"SETTINGS": "Instellingen",
"LEAVE": "Verlaat de sessie", "LEAVE": "Verlaat de sessie",
"PARTICIPANTS": "Deelnemers", "PARTICIPANTS": "Deelnemers",
"CHAT": "Chat", "CHAT": "Chat",
@ -64,6 +65,13 @@
"CAMERA": "CAMERA", "CAMERA": "CAMERA",
"SCREEN": "SCHERM" "SCREEN": "SCHERM"
}, },
"SETTINGS": {
"TITLE": "Instellingen",
"GENERAL": "Algemeen",
"VIDEO": "Video",
"AUDIO": "Audio",
"LANGUAGE":"Taal"
},
"BACKGROUND": { "BACKGROUND": {
"TITLE": "Achtergrondeffecten", "TITLE": "Achtergrondeffecten",
"BLURRED_SECTION": "Geen effecten en onscherpe achtergrond", "BLURRED_SECTION": "Geen effecten en onscherpe achtergrond",

View File

@ -37,6 +37,7 @@
"BACKGROUND": "Efeitos de fundo", "BACKGROUND": "Efeitos de fundo",
"START_RECORDING": "Iniciar_gravação", "START_RECORDING": "Iniciar_gravação",
"STOP_RECORDING": "Parar de gravar", "STOP_RECORDING": "Parar de gravar",
"SETTINGS": "Configurações",
"LEAVE": "Sair da sessão", "LEAVE": "Sair da sessão",
"PARTICIPANTS": "Participantes", "PARTICIPANTS": "Participantes",
"CHAT": "Chat", "CHAT": "Chat",
@ -64,6 +65,13 @@
"CAMERA": "CÂMERA", "CAMERA": "CÂMERA",
"SCREEN": "TELA" "SCREEN": "TELA"
}, },
"SETTINGS": {
"TITLE": "Configurações",
"GENERAL": "Em geral",
"VIDEO": "Vídeo",
"AUDIO": "Áudio",
"LANGUAGE":"Linguagem"
},
"BACKGROUND": { "BACKGROUND": {
"TITLE": "Efeitos de fundo", "TITLE": "Efeitos de fundo",
"BLURRED_SECTION": "Sem efeitos e fundo desfocado", "BLURRED_SECTION": "Sem efeitos e fundo desfocado",

View File

@ -2,6 +2,7 @@ export enum PanelType {
CHAT = 'chat', CHAT = 'chat',
PARTICIPANTS = 'participants', PARTICIPANTS = 'participants',
BACKGROUND_EFFECTS = 'background-effects', BACKGROUND_EFFECTS = 'background-effects',
ACTIVITIES = 'activities' ACTIVITIES = 'activities',
SETTINGS = 'settings'
} }

View File

@ -95,13 +95,28 @@ export abstract class ParticipantAbstractModel {
/** /**
* @internal * @internal
*/ */
public isCameraAudioActive(): boolean { hasAudioActive(): boolean {
const cameraConnection = this.getCameraConnection(); const cameraConnection = this.getCameraConnection();
if (cameraConnection) { const screenConnection = this.getScreenConnection();
return cameraConnection.connected && cameraConnection.streamManager?.stream?.audioActive;
} if (cameraConnection.connected) {
return this.isCameraAudioActive();
} else if (screenConnection.connected) {
return this.isScreenAudioActive(); return this.isScreenAudioActive();
} }
return false;
}
/**
* @internal
*/
private isCameraAudioActive(): boolean {
const cameraConnection = this.getCameraConnection();
if (cameraConnection?.connected) {
return cameraConnection.streamManager?.stream?.audioActive;
}
return false;
}
/** /**
* @internal * @internal
@ -116,8 +131,8 @@ export abstract class ParticipantAbstractModel {
*/ */
isScreenAudioActive(): boolean { isScreenAudioActive(): boolean {
const screenConnection = this.getScreenConnection(); const screenConnection = this.getScreenConnection();
if (screenConnection) { if (screenConnection?.connected) {
return screenConnection?.connected && screenConnection?.streamManager?.stream?.audioActive; return screenConnection?.streamManager?.stream?.audioActive;
} }
return false; return false;
} }

View File

@ -48,11 +48,16 @@ import { AvatarProfileComponent } from './components/avatar-profile/avatar-profi
import { OpenViduAngularDirectiveModule } from './directives/template/openvidu-angular.directive.module'; import { OpenViduAngularDirectiveModule } from './directives/template/openvidu-angular.directive.module';
import { ApiDirectiveModule } from './directives/api/api.directive.module'; import { ApiDirectiveModule } from './directives/api/api.directive.module';
import { BackgroundEffectsPanelComponent } from './components/panel/background-effects-panel/background-effects-panel.component'; import { BackgroundEffectsPanelComponent } from './components/panel/background-effects-panel/background-effects-panel.component';
import { SettingsPanelComponent } from './components/panel/settings-panel/settings-panel.component';
import { ActivitiesPanelComponent } from './components/panel/activities-panel/activities-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'; import { RecordingActivityComponent } from './components/panel/activities-panel/recording-activity-panel/recording-activity.component';
import { AdminDashboardComponent } from './admin/dashboard/dashboard.component'; import { AdminDashboardComponent } from './admin/dashboard/dashboard.component';
import { AdminLoginComponent } from './admin/login/login.component'; import { AdminLoginComponent } from './admin/login/login.component';
import { AppMaterialModule } from './openvidu-angular.material.module'; import { AppMaterialModule } from './openvidu-angular.material.module';
import { VideoDevicesComponent } from './components/settings/video-devices/video-devices.component';
import { AudioDevicesComponent } from './components/settings/audio-devices/audio-devices.component';
import { NicknameInputComponent } from './components/settings/nickname-input/nickname-input.component';
import { LangSelectorComponent } from './components/settings/lang-selector/lang-selector.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -78,7 +83,12 @@ import { AppMaterialModule } from './openvidu-angular.material.module';
PanelComponent, PanelComponent,
AvatarProfileComponent, AvatarProfileComponent,
PreJoinComponent, PreJoinComponent,
VideoDevicesComponent,
AudioDevicesComponent,
NicknameInputComponent,
LangSelectorComponent,
BackgroundEffectsPanelComponent, BackgroundEffectsPanelComponent,
SettingsPanelComponent,
ActivitiesPanelComponent, ActivitiesPanelComponent,
RecordingActivityComponent, RecordingActivityComponent,
AdminDashboardComponent, AdminDashboardComponent,
@ -118,6 +128,7 @@ import { AppMaterialModule } from './openvidu-angular.material.module';
ParticipantsPanelComponent, ParticipantsPanelComponent,
ParticipantPanelItemComponent, ParticipantPanelItemComponent,
BackgroundEffectsPanelComponent, BackgroundEffectsPanelComponent,
SettingsPanelComponent,
ActivitiesPanelComponent, ActivitiesPanelComponent,
ChatPanelComponent, ChatPanelComponent,
SessionComponent, SessionComponent,

View File

@ -28,6 +28,9 @@ export class OpenViduAngularConfigService {
fullscreenButton = <BehaviorSubject<boolean>>new BehaviorSubject(true); fullscreenButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
fullscreenButtonObs: Observable<boolean>; fullscreenButtonObs: Observable<boolean>;
toolbarSettingsButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
toolbarSettingsButtonObs: Observable<boolean>;
leaveButton = <BehaviorSubject<boolean>>new BehaviorSubject(true); leaveButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
leaveButtonObs: Observable<boolean>; leaveButtonObs: Observable<boolean>;
@ -49,8 +52,8 @@ export class OpenViduAngularConfigService {
displayParticipantNameObs: Observable<boolean>; displayParticipantNameObs: Observable<boolean>;
displayAudioDetection = <BehaviorSubject<boolean>>new BehaviorSubject(true); displayAudioDetection = <BehaviorSubject<boolean>>new BehaviorSubject(true);
displayAudioDetectionObs: Observable<boolean>; displayAudioDetectionObs: Observable<boolean>;
settingsButton = <BehaviorSubject<boolean>>new BehaviorSubject(true); streamSettingsButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
settingsButtonObs: Observable<boolean>; streamSettingsButtonObs: Observable<boolean>;
participantItemMuteButton = <BehaviorSubject<boolean>>new BehaviorSubject(true); participantItemMuteButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
participantItemMuteButtonObs: Observable<boolean>; participantItemMuteButtonObs: Observable<boolean>;
backgroundEffectsButton = <BehaviorSubject<boolean>>new BehaviorSubject(true); backgroundEffectsButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
@ -88,10 +91,11 @@ export class OpenViduAngularConfigService {
this.displaySessionNameObs = this.displaySessionName.asObservable(); this.displaySessionNameObs = this.displaySessionName.asObservable();
this.displayLogoObs = this.displayLogo.asObservable(); this.displayLogoObs = this.displayLogo.asObservable();
this.recordingButtonObs = this.recordingButton.asObservable(); this.recordingButtonObs = this.recordingButton.asObservable();
this.toolbarSettingsButtonObs = this.toolbarSettingsButton.asObservable();
//Stream observables //Stream observables
this.displayParticipantNameObs = this.displayParticipantName.asObservable(); this.displayParticipantNameObs = this.displayParticipantName.asObservable();
this.displayAudioDetectionObs = this.displayAudioDetection.asObservable(); this.displayAudioDetectionObs = this.displayAudioDetection.asObservable();
this.settingsButtonObs = this.settingsButton.asObservable(); this.streamSettingsButtonObs = this.streamSettingsButton.asObservable();
// Participant item observables // Participant item observables
this.participantItemMuteButtonObs = this.participantItemMuteButton.asObservable(); this.participantItemMuteButtonObs = this.participantItemMuteButton.asObservable();
// Recording activity observables // Recording activity observables
@ -107,7 +111,7 @@ export class OpenViduAngularConfigService {
return this.configuration; return this.configuration;
} }
isProduction(): boolean { isProduction(): boolean {
return this.configuration?.production; return this.configuration?.production || false;
} }
hasParticipantFactory(): boolean { hasParticipantFactory(): boolean {

View File

@ -39,16 +39,15 @@ export class DeviceService {
private libSrv: OpenViduAngularConfigService private libSrv: OpenViduAngularConfigService
) { ) {
this.log = this.loggerSrv.get('DevicesService'); this.log = this.loggerSrv.get('DevicesService');
} }
async forceUpdate() { // async forceUpdate() {
this.cameras = []; // await this.initializeDevices();
this.microphones = []; // }
await this.initializeDevices();
}
async initializeDevices() { async initializeDevices() {
this.cameras = [];
this.microphones = [];
try { try {
this.OV = new OpenVidu(); this.OV = new OpenVidu();
// Forcing media permissions request. // Forcing media permissions request.

View File

@ -76,6 +76,10 @@ export class OpenViduService {
this.ovEdition = edition; this.ovEdition = edition;
} }
isSessionConnected(): boolean {
return !!this.webcamSession.connection;
}
/** /**
* @internal * @internal
*/ */

View File

@ -4,6 +4,13 @@ import { ILogger } from '../../models/logger.model';
import { PanelType } from '../../models/panel.model'; import { PanelType } from '../../models/panel.model';
import { LoggerService } from '../logger/logger.service'; import { LoggerService } from '../logger/logger.service';
export interface PanelEvent {
opened: boolean;
type?: PanelType | string;
expand?: string;
oldType?: PanelType | string;
}
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
@ -11,14 +18,15 @@ export class PanelService {
/** /**
* Panel Observable which pushes the panel status in every update. * Panel Observable which pushes the panel status in every update.
*/ */
panelOpenedObs: Observable<{ opened: boolean; type?: PanelType | string }>; panelOpenedObs: Observable<PanelEvent>;
protected log: ILogger; protected log: ILogger;
protected isChatOpened: boolean = false; protected isChatOpened: boolean = false;
protected isParticipantsOpened: boolean = false; protected isParticipantsOpened: boolean = false;
protected isActivitiesOpened: boolean = false; protected isActivitiesOpened: boolean = false;
private isExternalOpened: boolean = false; private isExternalOpened: boolean = false;
private externalType: string; private externalType: string;
protected _panelOpened = <BehaviorSubject<{ opened: boolean; type?: PanelType | string, expand?: string }>>new BehaviorSubject({ opened: false }); protected _panelOpened = <BehaviorSubject<PanelEvent>>new BehaviorSubject({ opened: false });
private panelMap: Map<string, boolean> = new Map();
/** /**
* @internal * @internal
@ -26,6 +34,7 @@ export class PanelService {
constructor(protected loggerSrv: LoggerService) { constructor(protected loggerSrv: LoggerService) {
this.log = this.loggerSrv.get('PanelService'); this.log = this.loggerSrv.get('PanelService');
this.panelOpenedObs = this._panelOpened.asObservable(); this.panelOpenedObs = this._panelOpened.asObservable();
Object.values(PanelType).forEach((panel) => this.panelMap.set(panel, false));
} }
/** /**
@ -33,45 +42,43 @@ export class PanelService {
* If the type is differente, it will switch to the properly panel. * If the type is differente, it will switch to the properly panel.
*/ */
togglePanel(type: PanelType | string, expand?: string) { togglePanel(type: PanelType | string, expand?: string) {
let nextOpenedValue: boolean = false;
if (this.panelMap.has(type)) {
this.log.d(`Toggling ${type} menu`); this.log.d(`Toggling ${type} menu`);
let opened: boolean;
if (type === PanelType.CHAT) { this.panelMap.forEach((opened: boolean, panel: string) => {
this.isChatOpened = !this.isChatOpened; if (panel === type) {
this.isParticipantsOpened = false; // Toggle panel
this.isExternalOpened = false; this.panelMap.set(panel, !opened);
this.isActivitiesOpened = false nextOpenedValue = !opened;
opened = this.isChatOpened;
} else if (type === PanelType.PARTICIPANTS) {
this.isParticipantsOpened = !this.isParticipantsOpened;
this.isChatOpened = false;
this.isExternalOpened = false;
this.isActivitiesOpened = false;
opened = this.isParticipantsOpened;
} else if (type === PanelType.ACTIVITIES) {
this.isActivitiesOpened = !this.isActivitiesOpened;
this.isChatOpened = false;
this.isExternalOpened = false;
this.isParticipantsOpened = false;
opened = this.isActivitiesOpened;
} else { } else {
// Close others
this.panelMap.set(panel, false);
}
});
} else {
// Panel is external
this.log.d('Toggling external panel'); this.log.d('Toggling external panel');
this.isChatOpened = false; this.isChatOpened = false;
this.isParticipantsOpened = false; this.isParticipantsOpened = false;
this.isActivitiesOpened = false; this.isActivitiesOpened = false;
// Open when is close or is opened with another type // Open when is closed or is opened with another type
this.isExternalOpened = !this.isExternalOpened || this.externalType !== type; this.isExternalOpened = !this.isExternalOpened || this.externalType !== type;
this.externalType = !this.isExternalOpened ? '' : type; this.externalType = !this.isExternalOpened ? '' : type;
opened = this.isExternalOpened; nextOpenedValue = this.isExternalOpened;
} }
this._panelOpened.next({ opened, type, expand }); const oldType = this._panelOpened.getValue().type;
this._panelOpened.next({ opened: nextOpenedValue, type, expand, oldType });
} }
/** /**
* @internal * @internal
*/ */
isPanelOpened(): boolean { isPanelOpened(): boolean {
return this.isChatPanelOpened() || this.isParticipantsPanelOpened() || this.isActivitiesPanelOpened() || this.isExternalPanelOpened(); return (
this.isChatPanelOpened() || this.isParticipantsPanelOpened() || this.isActivitiesPanelOpened() || this.isExternalPanelOpened()
);
} }
/** /**

View File

@ -196,7 +196,7 @@ export class ParticipantService {
} }
isMyAudioActive(): boolean { isMyAudioActive(): boolean {
return this.localParticipant?.isCameraAudioActive() || this.localParticipant?.isScreenAudioActive(); return this.localParticipant?.hasAudioActive();
} }
/** /**

View File

@ -26,6 +26,7 @@ export * from './lib/components/videoconference/videoconference.component';
export * from './lib/components/toolbar/toolbar.component'; export * from './lib/components/toolbar/toolbar.component';
export * from './lib/components/panel/panel.component'; export * from './lib/components/panel/panel.component';
export * from './lib/components/panel/chat-panel/chat-panel.component'; export * from './lib/components/panel/chat-panel/chat-panel.component';
export * from './lib/components/panel/settings-panel/settings-panel.component';
export * from './lib/components/panel/background-effects-panel/background-effects-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/activities-panel/activities-panel.component';
export * from './lib/components/panel/participants-panel/participants-panel/participants-panel.component'; export * from './lib/components/panel/participants-panel/participants-panel/participants-panel.component';