From 98fbe060a3057baefd46c3823dd3cec08b41b049 Mon Sep 17 00:00:00 2001 From: csantosm <4a.santos@gmail.com> Date: Thu, 31 Mar 2022 12:25:02 +0200 Subject: [PATCH] openvidu-components: Moved component logic to openvidu service Moved toggle screen, toggle video and toggle audio logic from toolbar and prejoin component to openvidu service with the aim of avoiding code duplications and being able to provide a powerful method to the components user --- .../components/pre-join/pre-join.component.ts | 88 +------- .../components/toolbar/toolbar.component.ts | 118 ++-------- .../src/lib/config/openvidu-angular.config.ts | 2 - .../lib/services/openvidu/openvidu.service.ts | 202 +++++++++++++----- .../openvidu-angular/src/public-api.ts | 1 + 5 files changed, 177 insertions(+), 234 deletions(-) diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/components/pre-join/pre-join.component.ts b/openvidu-components-angular/projects/openvidu-angular/src/lib/components/pre-join/pre-join.component.ts index bae20f2d..1801c505 100644 --- a/openvidu-components-angular/projects/openvidu-angular/src/lib/components/pre-join/pre-join.component.ts +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/components/pre-join/pre-join.component.ts @@ -1,11 +1,9 @@ import { Component, HostListener, OnDestroy, OnInit, Output, EventEmitter } from '@angular/core'; -import { Publisher, PublisherProperties } from 'openvidu-browser'; -import { OpenViduErrorName } from 'openvidu-browser/lib/OpenViduInternal/Enums/OpenViduError'; +import { PublisherProperties } from 'openvidu-browser'; import { Subscription } from 'rxjs'; import { CustomDevice } from '../../models/device.model'; import { ILogger } from '../../models/logger.model'; -import { ParticipantAbstractModel, ParticipantProperties } from '../../models/participant.model'; -import { ActionService } from '../../services/action/action.service'; +import { ParticipantAbstractModel } from '../../models/participant.model'; import { DeviceService } from '../../services/device/device.service'; import { LayoutService } from '../../services/layout/layout.service'; import { LoggerService } from '../../services/logger/logger.service'; @@ -96,19 +94,13 @@ export class PreJoinComponent implements OnInit, OnDestroy { const pp: PublisherProperties = { videoSource, audioSource: this.microphoneSelected.device, mirror }; await this.openviduService.republishTrack(pp); - this.cameraSelected = videoSource; - this.deviceSrv.setCameraSelected(this.cameraSelected); - } - if (this.isVideoMuted) { - // Publish Webcam video - this.openviduService.publishVideo(this.participantService.getMyCameraPublisher(), true); - this.isVideoMuted = false; + this.deviceSrv.setCameraSelected(videoSource); + this.cameraSelected = this.deviceSrv.getCameraSelected(); } } async onMicrophoneSelected(event: any) { const audioSource = event?.value; - // Is New deviceId different than older? if (this.deviceSrv.needUpdateAudioTrack(audioSource)) { //TODO: Uncomment this when replaceTrack issue is fixed // const pp: PublisherProperties = { audioSource, videoSource: false }; @@ -118,83 +110,21 @@ export class PreJoinComponent implements OnInit, OnDestroy { const pp: PublisherProperties = { videoSource: this.cameraSelected.device, audioSource, mirror }; await this.openviduService.republishTrack(pp); - this.microphoneSelected = audioSource; - this.deviceSrv.setMicSelected(this.microphoneSelected); - } - if (this.isAudioMuted) { - // Enable microphone - this.openviduService.publishAudio(this.participantService.getMyCameraPublisher(), true); - this.isAudioMuted = true; + this.deviceSrv.setMicSelected(audioSource); + this.microphoneSelected = this.deviceSrv.getMicrophoneSelected(); } } - toggleCam() { + async toggleCam() { const publish = this.isVideoMuted; - this.openviduService.publishVideo(this.participantService.getMyCameraPublisher(), publish); - - if (this.participantService.haveICameraAndScreenActive()) { - // Cam will not published, disable webcam with screensharing active - this.participantService.disableWebcamStream(); - this.openviduService.publishAudio(this.participantService.getMyScreenPublisher(), publish); - } else if (this.participantService.isOnlyMyScreenActive()) { - // Cam will be published, enable webcam - this.participantService.enableWebcamStream(); - } - + await this.openviduService.muteVideo(publish); this.isVideoMuted = !this.isVideoMuted; this.storageSrv.setVideoMuted(this.isVideoMuted); } - // async toggleScreenShare() { - // // Disabling screenShare - // if (this.participantService.haveICameraAndScreenActive()) { - // this.participantService.disableScreenUser(); - // return; - // } - - // // Enabling screenShare - // if (this.participantService.isOnlyMyCameraActive()) { - // const willThereBeWebcam = this.participantService.isMyCameraActive() && this.participantService.hasCameraVideoActive(); - // const hasAudio = willThereBeWebcam ? false : this.hasAudioDevices && this.isAudioMuted; - // const properties: PublisherProperties = { - // videoSource: ScreenType.SCREEN, - // audioSource: this.hasAudioDevices ? undefined : null, - // publishVideo: true, - // publishAudio: hasAudio, - // mirror: false - // }; - // const screenPublisher = await this.openviduService.initPublisher(undefined, properties); - - // screenPublisher.on('accessAllowed', (event) => { - // screenPublisher.stream - // .getMediaStream() - // .getVideoTracks()[0] - // .addEventListener('ended', () => { - // this.log.d('Clicked native stop button. Stopping screen sharing'); - // this.toggleScreenShare(); - // }); - // this.participantService.activeMyScreenShare(screenPublisher); - // if (!this.participantService.hasCameraVideoActive()) { - // this.participantService.disableWebcamUser(); - // } - // }); - - // screenPublisher.on('accessDenied', (error: any) => { - // if (error && error.name === 'SCREEN_SHARING_NOT_SUPPORTED') { - // this.actionService.openDialog('Error sharing screen', 'Your browser does not support screen sharing'); - // } - // }); - // return; - // } - - // // Disabling screnShare and enabling webcam - // this.participantService.enableWebcamUser(); - // this.participantService.disableScreenUser(); - // } - toggleMic() { const publish = this.isAudioMuted; - this.openviduService.publishAudio(this.participantService.getMyCameraPublisher(), publish); + this.openviduService.muteAudio(publish); this.isAudioMuted = !this.isAudioMuted; this.storageSrv.setAudioMuted(this.isAudioMuted); } diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/components/toolbar/toolbar.component.ts b/openvidu-components-angular/projects/openvidu-angular/src/lib/components/toolbar/toolbar.component.ts index 1040f384..424bac85 100644 --- a/openvidu-components-angular/projects/openvidu-angular/src/lib/components/toolbar/toolbar.component.ts +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/components/toolbar/toolbar.component.ts @@ -19,8 +19,7 @@ import { DocumentService } from '../../services/document/document.service'; import { OpenViduService } from '../../services/openvidu/openvidu.service'; import { LoggerService } from '../../services/logger/logger.service'; import { ILogger } from '../../models/logger.model'; -import { ScreenType } from '../../models/video-type.model'; -import { PublisherProperties, Session } from 'openvidu-browser'; +import { Session } from 'openvidu-browser'; import { ActionService } from '../../services/action/action.service'; import { DeviceService } from '../../services/device/device.service'; import { ChatMessage } from '../../models/chat.model'; @@ -286,16 +285,13 @@ export class ToolbarComponent implements OnInit, OnDestroy { /** * @ignore */ - toggleMicrophone() { + async toggleMicrophone() { this.onMicrophoneButtonClicked.emit(); - - if (this.participantService.isMyCameraActive()) { - this.openviduService.publishAudio( - this.participantService.getMyCameraPublisher(), - !this.participantService.hasCameraAudioActive() - ); - } else { - this.openviduService.publishAudio(this.participantService.getMyScreenPublisher(), !this.participantService.hasScreenAudioActive()); + try { + await this.openviduService.muteAudio(!this.isAudioActive); + } catch (error) { + this.log.e('There was an error toggling microphone:', error.code, error.message); + this.actionService.openDialog('There was an error toggling camera', error?.error || error?.message); } } @@ -307,32 +303,10 @@ export class ToolbarComponent implements OnInit, OnDestroy { try { const publishVideo = !this.participantService.hasCameraVideoActive(); - const publishAudio = this.participantService.hasCameraAudioActive(); - // Disabling webcam - if (this.participantService.haveICameraAndScreenActive()) { - this.openviduService.publishVideo(this.participantService.getMyCameraPublisher(), publishVideo); - this.participantService.disableWebcamStream(); - this.openviduService.unpublish(this.participantService.getMyCameraPublisher()); - this.openviduService.publishAudio(this.participantService.getMyScreenPublisher(), publishAudio); - return; - } - // Enabling webcam - if (this.participantService.isOnlyMyScreenActive()) { - const hasAudio = this.participantService.hasScreenAudioActive(); - - if (!this.openviduService.isWebcamSessionConnected()) { - await this.openviduService.connectSession(this.openviduService.getWebcamSession(), this.tokenService.getWebcamToken()); - } - await this.openviduService.publish(this.participantService.getMyCameraPublisher()); - this.openviduService.publishAudio(this.participantService.getMyScreenPublisher(), false); - this.openviduService.publishAudio(this.participantService.getMyCameraPublisher(), hasAudio); - this.participantService.enableWebcamStream(); - } - // Muting/unmuting webcam - this.openviduService.publishVideo(this.participantService.getMyCameraPublisher(), publishVideo); + await this.openviduService.muteVideo(publishVideo); } catch (error) { this.log.e('There was an error toggling camera:', error.code, error.message); - this.actionService.openDialog('There was an error toggling camera:', error?.error || error?.message); + this.actionService.openDialog('There was an error toggling camera', error?.error || error?.message); } } @@ -343,76 +317,12 @@ export class ToolbarComponent implements OnInit, OnDestroy { this.onScreenshareButtonClicked.emit(); try { - // Disabling screenShare - if (this.participantService.haveICameraAndScreenActive()) { - this.participantService.disableScreenStream(); - this.openviduService.unpublish(this.participantService.getMyScreenPublisher()); - return; - } - - // Enabling screenShare - if (this.participantService.isOnlyMyCameraActive()) { - const willThereBeWebcam = this.participantService.isMyCameraActive() && this.participantService.hasCameraVideoActive(); - const hasAudio = willThereBeWebcam ? false : this.hasAudioDevices && this.participantService.hasCameraAudioActive(); - const properties: PublisherProperties = { - videoSource: ScreenType.SCREEN, - audioSource: this.hasAudioDevices ? undefined : null, - publishVideo: true, - publishAudio: hasAudio, - mirror: false - }; - const screenPublisher = await this.openviduService.initPublisher(undefined, properties); - - screenPublisher.once('accessAllowed', async (event) => { - // Listen to event fired when native stop button is clicked - screenPublisher.stream - .getMediaStream() - .getVideoTracks()[0] - .addEventListener('ended', () => { - this.log.d('Clicked native stop button. Stopping screen sharing'); - this.toggleScreenShare(); - }); - this.log.d('ACCESS ALOWED screenPublisher'); - this.participantService.activeMyScreenShare(screenPublisher); - - if (!this.openviduService.isScreenSessionConnected()) { - await this.openviduService.connectSession( - this.openviduService.getScreenSession(), - this.tokenService.getScreenToken() - ); - } - await this.openviduService.publish(this.participantService.getMyScreenPublisher()); - // this.openviduService.sendNicknameSignal(); - if (!this.participantService.hasCameraVideoActive()) { - // Disabling webcam - this.participantService.disableWebcamStream(); - this.openviduService.unpublish(this.participantService.getMyCameraPublisher()); - } - }); - - screenPublisher.once('accessDenied', (error: any) => { - this.log.e(error); - if (error && error.name === 'SCREEN_SHARING_NOT_SUPPORTED') { - this.actionService.openDialog('Error sharing screen', 'Your browser does not support screen sharing'); - } - }); - return; - } - - // Disabling screnShare and enabling webcam - const hasAudio = this.participantService.hasScreenAudioActive(); - if (!this.openviduService.isWebcamSessionConnected()) { - await this.openviduService.connectSession(this.openviduService.getWebcamSession(), this.tokenService.getWebcamToken()); - } - await this.openviduService.publish(this.participantService.getMyCameraPublisher()); - this.openviduService.publishAudio(this.participantService.getMyScreenPublisher(), false); - this.openviduService.publishAudio(this.participantService.getMyCameraPublisher(), hasAudio); - this.participantService.enableWebcamStream(); - this.participantService.disableScreenStream(); - this.openviduService.unpublish(this.participantService.getMyScreenPublisher()); + await this.openviduService.toggleScreenshare(); } catch (error) { - this.log.e('There was an error toggling screen share:', error.code, error.message); - this.actionService.openDialog('There was an error toggling screen share:', error?.error || error?.message); + this.log.e('There was an error toggling screen share', error.code, error.message); + if (error && error.name === 'SCREEN_SHARING_NOT_SUPPORTED') { + this.actionService.openDialog('Error sharing screen', 'Your browser does not support screen sharing'); + } } } diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/config/openvidu-angular.config.ts b/openvidu-components-angular/projects/openvidu-angular/src/lib/config/openvidu-angular.config.ts index 533a3b75..a8f95a74 100644 --- a/openvidu-components-angular/projects/openvidu-angular/src/lib/config/openvidu-angular.config.ts +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/config/openvidu-angular.config.ts @@ -3,8 +3,6 @@ import { ParticipantProperties, StreamModel } from '../models/participant.model' export interface OpenViduAngularConfig { production?: boolean, participantFactory?: ParticipantFactoryFunction, - webcomponent?: boolean - } export type ParticipantFactoryFunction = (props: ParticipantProperties, streamModel: StreamModel) => any; \ No newline at end of file diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/services/openvidu/openvidu.service.ts b/openvidu-components-angular/projects/openvidu-angular/src/lib/services/openvidu/openvidu.service.ts index 2b9ee3f1..c6dca29d 100644 --- a/openvidu-components-angular/projects/openvidu-angular/src/lib/services/openvidu/openvidu.service.ts +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/services/openvidu/openvidu.service.ts @@ -1,14 +1,5 @@ import { Injectable } from '@angular/core'; -import { - Connection, - OpenVidu, - OpenViduError, - OpenViduErrorName, - Publisher, - PublisherProperties, - Session, - SignalOptions -} from 'openvidu-browser'; +import { Connection, OpenVidu, Publisher, PublisherProperties, Session, SignalOptions } from 'openvidu-browser'; import { LoggerService } from '../logger/logger.service'; @@ -18,8 +9,9 @@ import { OpenViduAngularConfigService } from '../config/openvidu-angular.config. import { PlatformService } from '../platform/platform.service'; import { DeviceService } from '../device/device.service'; import { CameraType } from '../../models/device.model'; -import { VideoType } from '../../models/video-type.model'; +import { ScreenType, VideoType } from '../../models/video-type.model'; import { ParticipantService } from '../participant/participant.service'; +import { TokenService } from '../token/token.service'; @Injectable({ providedIn: 'root' @@ -41,7 +33,8 @@ export class OpenViduService { protected platformService: PlatformService, protected loggerSrv: LoggerService, private participantService: ParticipantService, - protected deviceService: DeviceService + protected deviceService: DeviceService, + protected tokenService: TokenService ) { this.log = this.loggerSrv.get('OpenViduService'); } @@ -51,6 +44,11 @@ export class OpenViduService { */ initialize() { this.OV = new OpenVidu(); + this.OV.setAdvancedConfiguration({ + publisherSpeakingEventsOptions: { + interval: 50 + } + }); if (this.openviduAngularConfigSrv.isProduction()) this.OV.enableProdMode(); this.webcamSession = this.OV.initSession(); @@ -163,8 +161,6 @@ export class OpenViduService { // audioSource = undefined; } - // const videoSource = publishVideo ? this.deviceService.getCameraSelected().device : false; - // const audioSource = publishAudio ? this.deviceService.getMicrophoneSelected().device : false; const mirror = this.deviceService.getCameraSelected() && this.deviceService.getCameraSelected().type === CameraType.FRONT; const properties: PublisherProperties = { videoSource, @@ -213,10 +209,10 @@ export class OpenViduService { /** * @internal */ - unpublish(publisher: Publisher): void { + private unpublish(publisher: Publisher): void { if (!!publisher) { if (publisher === this.participantService.getMyCameraPublisher()) { - this.publishAudio(this.participantService.getMyScreenPublisher(), this.participantService.hasCameraAudioActive()); + this.publishAudioAux(this.participantService.getMyScreenPublisher(), this.participantService.hasCameraAudioActive()); this.webcamSession.unpublish(publisher); } else if (publisher === this.participantService.getMyScreenPublisher()) { this.screenSession.unpublish(publisher); @@ -224,20 +220,127 @@ export class OpenViduService { } } - /** - * @internal - */ - publishVideo(publisher: Publisher, value: boolean): void { - if (!!publisher) { - publisher.publishVideo(value); - this.participantService.updateLocalParticipant(); + async muteVideo(mute: boolean): Promise { + const publishAudio = this.participantService.hasCameraAudioActive(); + // const publishVideo = !this.participantService.hasCameraVideoActive(); + + // Disabling webcam + if (this.participantService.haveICameraAndScreenActive()) { + this.publishVideoAux(this.participantService.getMyCameraPublisher(), mute); + this.participantService.disableWebcamStream(); + this.unpublish(this.participantService.getMyCameraPublisher()); + this.publishAudioAux(this.participantService.getMyScreenPublisher(), publishAudio); + } else if (this.participantService.isOnlyMyScreenActive()) { + // Enabling webcam + const hasAudio = this.participantService.hasScreenAudioActive(); + console.warn('Es audio activo?', hasAudio); + if (!this.isWebcamSessionConnected()) { + //TODO: should be the token in th participant? + await this.connectSession(this.getWebcamSession(), this.tokenService.getWebcamToken()); + } + await this.publish(this.participantService.getMyCameraPublisher()); + this.publishVideoAux(this.participantService.getMyCameraPublisher(), true); + this.publishAudioAux(this.participantService.getMyScreenPublisher(), false); + this.publishAudioAux(this.participantService.getMyCameraPublisher(), hasAudio); + this.participantService.enableWebcamStream(); + } else { + // Muting/unmuting webcam + this.publishVideoAux(this.participantService.getMyCameraPublisher(), mute); } } /** * @internal */ - publishAudio(publisher: Publisher, value: boolean): void { + private publishVideoAux(publisher: Publisher, value: boolean): void { + if (!!publisher) { + publisher.publishVideo(value); + this.participantService.updateLocalParticipant(); + } + } + + async muteAudio(value: boolean): Promise { + if (this.participantService.isMyCameraActive()) { + if (this.participantService.isMyScreenActive() && this.participantService.hasScreenAudioActive()) { + this.publishAudioAux(this.participantService.getMyScreenPublisher(), false); + } + + this.publishAudioAux(this.participantService.getMyCameraPublisher(), value); + } else { + this.publishAudioAux(this.participantService.getMyScreenPublisher(), value); + } + } + + async toggleScreenshare() { + if (this.participantService.haveICameraAndScreenActive()) { + // Disabling screenShare + this.participantService.disableScreenStream(); + this.unpublish(this.participantService.getMyScreenPublisher()); + } else if (this.participantService.isOnlyMyCameraActive()) { + // I only have the camera published + const hasAudioDevicesAvailable = this.deviceService.hasAudioDeviceAvailable(); + const willWebcamBePresent = this.participantService.isMyCameraActive() && this.participantService.hasCameraVideoActive(); + const hasAudio = willWebcamBePresent ? false : hasAudioDevicesAvailable && this.participantService.hasCameraAudioActive(); + + console.warn('will be audio active', hasAudio); + const properties: PublisherProperties = { + videoSource: ScreenType.SCREEN, + audioSource: hasAudioDevicesAvailable ? this.deviceService.getMicrophoneSelected().device : null, + publishVideo: true, + publishAudio: hasAudio, + mirror: false + }; + const screenPublisher = await this.initPublisher(undefined, properties); + + screenPublisher.once('accessAllowed', async () => { + // Listen to event fired when native stop button is clicked + screenPublisher.stream + .getMediaStream() + .getVideoTracks()[0] + .addEventListener('ended', async () => { + this.log.d('Clicked native stop button. Stopping screen sharing'); + await this.toggleScreenshare(); + }); + + // Enabling screenShare + this.participantService.activeMyScreenShare(screenPublisher); + + if (!this.isScreenSessionConnected()) { + await this.connectSession(this.getScreenSession(), this.tokenService.getScreenToken()); + } + await this.publish(this.participantService.getMyScreenPublisher()); + if (!this.participantService.hasCameraVideoActive()) { + // Disabling webcam + this.participantService.disableWebcamStream(); + this.unpublish(this.participantService.getMyCameraPublisher()); + } + }); + + screenPublisher.once('accessDenied', (error: any) => { + return Promise.reject(error); + }); + } else { + // I only have my screenshare active and I have no camera or it is muted + const hasAudio = this.participantService.hasScreenAudioActive(); + + // Enable webcam + if (!this.isWebcamSessionConnected()) { + await this.connectSession(this.getWebcamSession(), this.tokenService.getWebcamToken()); + } + await this.publish(this.participantService.getMyCameraPublisher()); + this.publishAudioAux(this.participantService.getMyCameraPublisher(), hasAudio); + this.participantService.enableWebcamStream(); + + // Disabling screenshare + this.participantService.disableScreenStream(); + this.unpublish(this.participantService.getMyScreenPublisher()); + } + } + + /** + * @internal + */ + private publishAudioAux(publisher: Publisher, value: boolean): void { if (!!publisher) { publisher.publishAudio(value); this.participantService.updateLocalParticipant(); @@ -368,32 +471,33 @@ export class OpenViduService { } } - private async createMediaStream(pp: PublisherProperties): Promise { - let mediaStream: MediaStream; - const isFirefoxPlatform = this.platformService.isFirefox(); - const isReplacingAudio = !!pp.audioSource; - const isReplacingVideo = !!pp.videoSource; + //TODO: Uncomment this section when replaceTrack issue is fixed + // private async createMediaStream(pp: PublisherProperties): Promise { + // let mediaStream: MediaStream; + // const isFirefoxPlatform = this.platformService.isFirefox(); + // const isReplacingAudio = !!pp.audioSource; + // const isReplacingVideo = !!pp.videoSource; - try { - mediaStream = await this.OV.getUserMedia(pp); - } catch (error) { - if ((error).name === OpenViduErrorName.DEVICE_ACCESS_DENIED) { - if (isFirefoxPlatform) { - this.log.w('The device requested is not available. Restoring the older one'); - // The track requested is not available so we are getting the old tracks ids for recovering the track - if (isReplacingVideo) { - pp.videoSource = this.deviceService.getCameraSelected().device; - } else if (isReplacingAudio) { - pp.audioSource = this.deviceService.getMicrophoneSelected().device; - } - mediaStream = await this.OV.getUserMedia(pp); - // TODO show error alert informing that the new device is not available - } - } - } finally { - return mediaStream; - } - } + // try { + // mediaStream = await this.OV.getUserMedia(pp); + // } catch (error) { + // if ((error).name === OpenViduErrorName.DEVICE_ACCESS_DENIED) { + // if (isFirefoxPlatform) { + // this.log.w('The device requested is not available. Restoring the older one'); + // // The track requested is not available so we are getting the old tracks ids for recovering the track + // if (isReplacingVideo) { + // pp.videoSource = this.deviceService.getCameraSelected().device; + // } else if (isReplacingAudio) { + // pp.audioSource = this.deviceService.getMicrophoneSelected().device; + // } + // mediaStream = await this.OV.getUserMedia(pp); + // // TODO show error alert informing that the new device is not available + // } + // } + // } finally { + // return mediaStream; + // } + // } /** * @internal diff --git a/openvidu-components-angular/projects/openvidu-angular/src/public-api.ts b/openvidu-components-angular/projects/openvidu-angular/src/public-api.ts index d26e489c..df76a2f0 100644 --- a/openvidu-components-angular/projects/openvidu-angular/src/public-api.ts +++ b/openvidu-components-angular/projects/openvidu-angular/src/public-api.ts @@ -41,6 +41,7 @@ export * from './lib/config/openvidu-angular.config'; export * from './lib/models/logger.model'; export * from './lib/models/video-type.model'; export * from './lib/models/notification-options.model'; +export * from './lib/models/token.model'; // Pipes export * from './lib/pipes/participant.pipe';