openvidu-components: Added prejoin drective

- Hide/show prejoin page
- Refactored prejoin component
- Moved the participant initialization to videoconference
- Set necessary delay for the correct layout initialization
pull/707/head
csantosm 2022-03-10 14:12:43 +01:00
parent ab74818579
commit 7eedba795b
10 changed files with 160 additions and 98 deletions

View File

@ -50,10 +50,10 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
} }
ngAfterViewInit() { ngAfterViewInit() {
let timeout: number = null; let timeout: number = 0;
if (this.libService.isWebcomponent()) { // if (this.libService.isWebcomponent()) {
timeout = 0; // timeout = 0;
} // }
this.layoutService.initialize(timeout); this.layoutService.initialize(timeout);
this.layoutService.update(timeout); this.layoutService.update(timeout);

View File

@ -26,7 +26,6 @@ export class PreJoinComponent implements OnInit, OnDestroy {
microphoneSelected: CustomDevice; microphoneSelected: CustomDevice;
isVideoMuted: boolean; isVideoMuted: boolean;
isAudioMuted: boolean; isAudioMuted: boolean;
screenShareEnabled: boolean;
localParticipant: ParticipantAbstractModel; localParticipant: ParticipantAbstractModel;
windowSize: number; windowSize: number;
hasVideoDevices: boolean; hasVideoDevices: boolean;
@ -45,7 +44,6 @@ export class PreJoinComponent implements OnInit, OnDestroy {
constructor( constructor(
private layoutService: LayoutService, private layoutService: LayoutService,
private actionService: ActionService,
private deviceSrv: DeviceService, private deviceSrv: DeviceService,
private loggerSrv: LoggerService, private loggerSrv: LoggerService,
private openviduService: OpenViduService, private openviduService: OpenViduService,
@ -55,22 +53,20 @@ export class PreJoinComponent implements OnInit, OnDestroy {
this.log = this.loggerSrv.get('PreJoinComponent'); this.log = this.loggerSrv.get('PreJoinComponent');
} }
async ngOnInit() { ngOnInit() {
await this.deviceSrv.initializeDevices();
this.nickname = this.storageSrv.getNickname() || this.generateRandomNickname();
const props: ParticipantProperties = {
local: true,
nickname: this.nickname
};
this.participantService.initLocalParticipant(props);
this.subscribeToLocalParticipantEvents(); this.subscribeToLocalParticipantEvents();
this.openviduService.initialize();
this.windowSize = window.innerWidth; this.windowSize = window.innerWidth;
this.setDevicesInfo(); this.hasVideoDevices = this.deviceSrv.hasVideoDeviceAvailable();
if (this.hasAudioDevices || this.hasVideoDevices) { this.hasAudioDevices = this.deviceSrv.hasAudioDeviceAvailable();
await this.initwebcamPublisher(); 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;
} }
@ -201,7 +197,7 @@ export class PreJoinComponent implements OnInit, OnDestroy {
} }
updateNickname() { updateNickname() {
this.nickname = this.nickname === '' ? this.generateRandomNickname() : this.nickname; this.nickname = this.nickname === '' ? this.participantService.getMyNickname() : this.nickname;
this.participantService.setMyNickname(this.nickname); this.participantService.setMyNickname(this.nickname);
this.storageSrv.setNickname(this.nickname); this.storageSrv.setNickname(this.nickname);
} }
@ -210,33 +206,12 @@ export class PreJoinComponent implements OnInit, OnDestroy {
this.onJoinButtonClicked.emit(); this.onJoinButtonClicked.emit();
} }
private setDevicesInfo() {
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();
}
private subscribeToLocalParticipantEvents() { private subscribeToLocalParticipantEvents() {
this.localParticipantSubscription = this.participantService.localParticipantObs.subscribe((p) => { this.localParticipantSubscription = this.participantService.localParticipantObs.subscribe((p) => {
this.localParticipant = p; this.localParticipant = p;
this.screenShareEnabled = p.isScreenActive();
}); });
} }
private async initwebcamPublisher() {
const publisher = await this.openviduService.initDefaultPublisher(undefined);
if (publisher) {
// this.handlePublisherSuccess(publisher);
this.handlePublisherError(publisher);
}
}
//? After test in Chrome and Firefox, the devices always have labels. //? After test in Chrome and Firefox, the devices always have labels.
//? It's not longer needed //? It's not longer needed
// private handlePublisherSuccess(publisher: Publisher) { // private handlePublisherSuccess(publisher: Publisher) {
@ -256,33 +231,4 @@ export class PreJoinComponent implements OnInit, OnDestroy {
// } // }
// }); // });
// } // }
private handlePublisherError(publisher: Publisher) {
publisher.once('accessDenied', (e: any) => {
let message: string;
if (e.name === OpenViduErrorName.DEVICE_ALREADY_IN_USE) {
this.log.w('Video device already in use. Disabling video device...');
// Allow access to the room with only mic if camera device is already in use
this.hasVideoDevices = false;
this.deviceSrv.disableVideoDevices();
return this.initwebcamPublisher();
}
if (e.name === OpenViduErrorName.DEVICE_ACCESS_DENIED) {
message = 'Access to media devices was not allowed.';
this.hasVideoDevices = false;
this.hasAudioDevices = false;
this.deviceSrv.disableVideoDevices();
this.deviceSrv.disableAudioDevices();
return this.initwebcamPublisher();
} else if (e.name === OpenViduErrorName.NO_INPUT_SOURCE_SET) {
message = 'No video or audio devices have been found. Please, connect at least one.';
}
this.actionService.openDialog(e.name.replace(/_/g, ' '), message, true);
this.log.e(e.message);
});
}
private generateRandomNickname(): string {
return 'OpenVidu_User' + Math.floor(Math.random() * 100);
}
} }

View File

@ -86,10 +86,6 @@ export class SessionComponent implements OnInit {
async ngOnInit() { async ngOnInit() {
if (this.openviduService.getWebcamSession() === null) {
this.openviduService.initialize();
await this.openviduService.initDefaultPublisher(undefined);
}
this.session = this.openviduService.getWebcamSession(); this.session = this.openviduService.getWebcamSession();
this.sessionScreen = this.openviduService.getScreenSession(); this.sessionScreen = this.openviduService.getScreenSession();
this.subscribeToConnectionCreatedAndDestroyed(); this.subscribeToConnectionCreatedAndDestroyed();

View File

@ -331,10 +331,12 @@ export class ToolbarComponent implements OnInit, OnDestroy {
} }
protected subscribeToUserMediaProperties() { protected subscribeToUserMediaProperties() {
this.localParticipantSubscription = this.participantService.localParticipantObs.subscribe((p) => { this.localParticipantSubscription = this.participantService.localParticipantObs.subscribe((p) => {
if(p) {
this.isWebcamVideoActive = p.isCameraVideoActive(); this.isWebcamVideoActive = p.isCameraVideoActive();
this.isWebcamAudioActive = p.isCameraAudioActive(); this.isWebcamAudioActive = p.isCameraAudioActive();
this.isScreenShareActive = p.isScreenActive(); this.isScreenShareActive = p.isScreenActive();
this.cd.markForCheck(); this.cd.markForCheck();
}
}); });
} }

View File

@ -1,20 +1,20 @@
<div id="call-container"> <div id="call-container">
<div id="pre-join-container" *ngIf="!joinSessionClicked"> <div id="pre-join-container" *ngIf="showPrejoin && participantReady && !joinSessionClicked">
<ov-pre-join (onJoinButtonClicked)="_onJoinButtonClicked()"></ov-pre-join> <ov-pre-join (onJoinButtonClicked)="_onJoinButtonClicked()"></ov-pre-join>
<!-- <ov-user-settings (onJoinClicked)="_onJoinClicked()" (onCloseClicked)="onLeaveSessionClicked()"></ov-user-settings> --> <!-- <ov-user-settings (onJoinClicked)="_onJoinClicked()" (onCloseClicked)="onLeaveSessionClicked()"></ov-user-settings> -->
</div> </div>
<div id="spinner" *ngIf="joinSessionClicked && !isSessionAlive && !error"> <div id="spinner" *ngIf="(joinSessionClicked || !showPrejoin) && !participantReady && !error">
<mat-spinner [diameter]="50"></mat-spinner> <mat-spinner [diameter]="50"></mat-spinner>
<span>Joining the room ...</span> <span>Joining the room ...</span>
</div> </div>
<div id="spinner" *ngIf="joinSessionClicked && !isSessionAlive && error"> <div id="spinner" *ngIf="joinSessionClicked && !participantReady && error">
<mat-icon class="error-icon">error</mat-icon> <mat-icon class="error-icon">error</mat-icon>
<span>{{ errorMessage }}</span> <span>{{ errorMessage }}</span>
</div> </div>
<div id="session-container" *ngIf="joinSessionClicked && isSessionAlive && !error"> <div id="session-container" *ngIf="(joinSessionClicked || !showPrejoin) && participantReady && !error">
<ov-session [tokens]="_tokens"> <ov-session [tokens]="_tokens">
<ng-template #toolbar> <ng-template #toolbar>
<ng-container *ngIf="openviduAngularToolbarTemplate"> <ng-container *ngIf="openviduAngularToolbarTemplate">

View File

@ -1,4 +1,17 @@
import { AfterViewInit, Component, ContentChild, EventEmitter, Input, OnInit, Output, TemplateRef, ViewChild } from '@angular/core'; import {
AfterViewInit,
Component,
ContentChild,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
TemplateRef,
ViewChild
} from '@angular/core';
import { OpenViduErrorName } from 'openvidu-browser';
import { Subscription } from 'rxjs';
import { import {
ChatPanelDirective, ChatPanelDirective,
LayoutDirective, LayoutDirective,
@ -11,14 +24,21 @@ import {
ToolbarDirective ToolbarDirective
} from '../../directives/template/openvidu-angular.directive'; } from '../../directives/template/openvidu-angular.directive';
import { ILogger } from '../../models/logger.model'; import { ILogger } from '../../models/logger.model';
import { ParticipantProperties } from '../../models/participant.model';
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 { LoggerService } from '../../services/logger/logger.service';
import { OpenViduService } from '../../services/openvidu/openvidu.service';
import { ParticipantService } from '../../services/participant/participant.service';
import { StorageService } from '../../services/storage/storage.service';
@Component({ @Component({
selector: 'ov-videoconference', selector: 'ov-videoconference',
templateUrl: './videoconference.component.html', templateUrl: './videoconference.component.html',
styleUrls: ['./videoconference.component.css'] styleUrls: ['./videoconference.component.css']
}) })
export class VideoconferenceComponent implements OnInit, AfterViewInit { export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewInit {
// *** Toolbar *** // *** Toolbar ***
@ContentChild(ToolbarDirective) externalToolbar: ToolbarDirective; @ContentChild(ToolbarDirective) externalToolbar: ToolbarDirective;
@ContentChild(ToolbarAdditionalButtonsDirective) externalToolbarAdditionalButtons: ToolbarAdditionalButtonsDirective; @ContentChild(ToolbarAdditionalButtonsDirective) externalToolbarAdditionalButtons: ToolbarAdditionalButtonsDirective;
@ -30,7 +50,6 @@ export class VideoconferenceComponent implements OnInit, AfterViewInit {
@ContentChild(ParticipantPanelItemDirective) externalParticipantPanelItem: ParticipantPanelItemDirective; @ContentChild(ParticipantPanelItemDirective) externalParticipantPanelItem: ParticipantPanelItemDirective;
@ContentChild(ParticipantPanelItemElementsDirective) externalParticipantPanelItemElements: ParticipantPanelItemElementsDirective; @ContentChild(ParticipantPanelItemElementsDirective) externalParticipantPanelItemElements: ParticipantPanelItemElementsDirective;
// *** Layout *** // *** Layout ***
@ContentChild(LayoutDirective) externalLayout: LayoutDirective; @ContentChild(LayoutDirective) externalLayout: LayoutDirective;
@ContentChild(StreamDirective) externalStream: StreamDirective; @ContentChild(StreamDirective) externalStream: StreamDirective;
@ -69,7 +88,6 @@ export class VideoconferenceComponent implements OnInit, AfterViewInit {
webcam: tokens.webcam, webcam: tokens.webcam,
screen: tokens.screen screen: tokens.screen
}; };
this.isSessionAlive = true;
} }
} }
} }
@ -97,17 +115,63 @@ export class VideoconferenceComponent implements OnInit, AfterViewInit {
@Output() onSessionCreated = new EventEmitter<any>(); @Output() onSessionCreated = new EventEmitter<any>();
joinSessionClicked: boolean = false; joinSessionClicked: boolean = false;
isSessionAlive: boolean = false; participantReady: boolean = false;
_tokens: { webcam: string; screen: string }; _tokens: { webcam: string; screen: string };
error: boolean = false; error: boolean = false;
errorMessage: string = ''; errorMessage: string = '';
showPrejoin: boolean = true;
private prejoinSub: Subscription;
private log: ILogger; private log: ILogger;
constructor(private loggerSrv: LoggerService) { constructor(
private loggerSrv: LoggerService,
private storageSrv: StorageService,
private participantService: ParticipantService,
private deviceSrv: DeviceService,
private openviduService: OpenViduService,
private actionService: ActionService,
private libService: OpenViduAngularConfigService
) {
this.log = this.loggerSrv.get('VideoconferenceComponent'); this.log = this.loggerSrv.get('VideoconferenceComponent');
} }
async ngOnInit() {
this.subscribeToVideconferenceDirectives();
await this.deviceSrv.initializeDevices();
const nickname = this.storageSrv.getNickname() || 'OpenVidu_User' + Math.floor(Math.random() * 100);
const props: ParticipantProperties = {
local: true,
nickname
};
this.participantService.initLocalParticipant(props);
this.openviduService.initialize();
if (this.deviceSrv.hasVideoDeviceAvailable() || this.deviceSrv.hasAudioDeviceAvailable()) {
await this.initwebcamPublisher();
}
}
private async initwebcamPublisher() {
try {
const publisher = await this.openviduService.initDefaultPublisher(undefined);
if (publisher) {
publisher.once('accessDenied', (e: any) => {
this.handlePublisherError(e);
});
publisher.once('accessAllowed', () => {
this.participantReady = true;
});
}
} catch (error) {
this.actionService.openDialog(error.name.replace(/_/g, ' '), error.message, true);
this.log.e(error);
}
}
ngOnDestroy(): void {
if (this.prejoinSub) this.prejoinSub.unsubscribe();
}
ngAfterViewInit() { ngAfterViewInit() {
if (this.externalToolbar) { if (this.externalToolbar) {
this.openviduAngularToolbarTemplate = this.externalToolbar.template; this.openviduAngularToolbarTemplate = this.externalToolbar.template;
@ -173,15 +237,13 @@ export class VideoconferenceComponent implements OnInit, AfterViewInit {
} }
} }
ngOnInit() {}
_onJoinButtonClicked() { _onJoinButtonClicked() {
this.joinSessionClicked = true; this.joinSessionClicked = true;
this.onJoinButtonClicked.emit(); this.onJoinButtonClicked.emit();
} }
onLeaveButtonClicked() { onLeaveButtonClicked() {
this.joinSessionClicked = false; this.joinSessionClicked = false;
this.isSessionAlive = false; this.participantReady = false;
this.onToolbarLeaveButtonClicked.emit(); this.onToolbarLeaveButtonClicked.emit();
} }
onCameraButtonClicked() { onCameraButtonClicked() {
@ -203,4 +265,31 @@ export class VideoconferenceComponent implements OnInit, AfterViewInit {
onChatPanelButtonClicked() { onChatPanelButtonClicked() {
this.onToolbarChatPanelButtonClicked.emit(); this.onToolbarChatPanelButtonClicked.emit();
} }
private handlePublisherError(e: any) {
let message: string;
if (e.name === OpenViduErrorName.DEVICE_ALREADY_IN_USE) {
this.log.w('Video device already in use. Disabling video device...');
// Allow access to the room with only mic if camera device is already in use
this.deviceSrv.disableVideoDevices();
return this.initwebcamPublisher();
}
if (e.name === OpenViduErrorName.DEVICE_ACCESS_DENIED) {
message = 'Access to media devices was not allowed.';
this.deviceSrv.disableVideoDevices();
this.deviceSrv.disableAudioDevices();
return this.initwebcamPublisher();
} else if (e.name === OpenViduErrorName.NO_INPUT_SOURCE_SET) {
message = 'No video or audio devices have been found. Please, connect at least one.';
}
this.actionService.openDialog(e.name.replace(/_/g, ' '), message, true);
this.log.e(e.message);
}
private subscribeToVideconferenceDirectives() {
this.prejoinSub = this.libService.prejoin.subscribe((value: boolean) => {
this.showPrejoin = value;
// this.cd.markForCheck();
});
}
} }

View File

@ -15,11 +15,12 @@ import {
ToolbarDisplayLogoDirective, ToolbarDisplayLogoDirective,
LogoDirective LogoDirective
} from './toolbar.directive'; } from './toolbar.directive';
import { AudioMutedDirective, MinimalDirective, VideoMutedDirective } from './videoconference.directive'; import { AudioMutedDirective, MinimalDirective, PrejoinDirective, VideoMutedDirective } from './videoconference.directive';
@NgModule({ @NgModule({
declarations: [ declarations: [
MinimalDirective, MinimalDirective,
PrejoinDirective,
VideoMutedDirective, VideoMutedDirective,
AudioMutedDirective, AudioMutedDirective,
ToolbarScreenshareButtonDirective, ToolbarScreenshareButtonDirective,
@ -37,6 +38,7 @@ import { AudioMutedDirective, MinimalDirective, VideoMutedDirective } from './vi
], ],
exports: [ exports: [
MinimalDirective, MinimalDirective,
PrejoinDirective,
VideoMutedDirective, VideoMutedDirective,
AudioMutedDirective, AudioMutedDirective,
ToolbarScreenshareButtonDirective, ToolbarScreenshareButtonDirective,

View File

@ -23,6 +23,29 @@ export class MinimalDirective implements OnDestroy {
} }
} }
@Directive({
selector: 'ov-videoconference[prejoin]'
})
export class PrejoinDirective implements OnDestroy {
@Input() set prejoin(value: boolean) {
this.update(value);
}
constructor(public elementRef: ElementRef, private libService: OpenViduAngularConfigService) {}
ngOnDestroy(): void {
this.clear();
}
clear() {
this.update(true);
}
update(value: boolean) {
if (this.libService.prejoin.getValue() !== value) {
this.libService.prejoin.next(value);
}
}
}
@Directive({ @Directive({
selector: 'ov-videoconference[videoMuted]' selector: 'ov-videoconference[videoMuted]'
}) })

View File

@ -51,7 +51,7 @@ export abstract class ParticipantAbstractModel {
public isCameraAudioActive(): boolean { public isCameraAudioActive(): boolean {
const cameraConnection = this.getCameraConnection(); const cameraConnection = this.getCameraConnection();
if(cameraConnection) { if(cameraConnection) {
return cameraConnection.connected && cameraConnection.streamManager.stream.audioActive; return cameraConnection.connected && cameraConnection.streamManager?.stream?.audioActive;
} }
return this.isScreenAudioActive();; return this.isScreenAudioActive();;
} }

View File

@ -11,6 +11,9 @@ export class OpenViduAngularConfigService {
minimal = <BehaviorSubject<boolean>>new BehaviorSubject(false); minimal = <BehaviorSubject<boolean>>new BehaviorSubject(false);
minimalObs: Observable<boolean>; minimalObs: Observable<boolean>;
prejoin = <BehaviorSubject<boolean>>new BehaviorSubject(true);
prejoinObs: Observable<boolean>;
videoMuted = <BehaviorSubject<boolean>>new BehaviorSubject(false); videoMuted = <BehaviorSubject<boolean>>new BehaviorSubject(false);
videoMutedObs: Observable<boolean>; videoMutedObs: Observable<boolean>;
audioMuted = <BehaviorSubject<boolean>>new BehaviorSubject(false); audioMuted = <BehaviorSubject<boolean>>new BehaviorSubject(false);
@ -49,6 +52,7 @@ export class OpenViduAngularConfigService {
console.log(this.configuration); console.log(this.configuration);
if(this.isProduction()) console.log('OpenVidu Angular Production Mode'); if(this.isProduction()) console.log('OpenVidu Angular Production Mode');
this.minimalObs = this.minimal.asObservable(); this.minimalObs = this.minimal.asObservable();
this.prejoinObs = this.prejoin.asObservable();
this.videoMutedObs = this.videoMuted.asObservable(); this.videoMutedObs = this.videoMuted.asObservable();
this.audioMutedObs = this.audioMuted.asObservable(); this.audioMutedObs = this.audioMuted.asObservable();
//Toolbar observables //Toolbar observables
@ -74,9 +78,9 @@ export class OpenViduAngularConfigService {
return this.configuration?.production; return this.configuration?.production;
} }
isWebcomponent(): boolean { // isWebcomponent(): boolean {
return this.configuration?.webcomponent; // return this.configuration?.webcomponent;
} // }
hasParticipantFactory(): boolean { hasParticipantFactory(): boolean {
return typeof this.getConfig().participantFactory === "function"; return typeof this.getConfig().participantFactory === "function";