mirror of https://github.com/OpenVidu/openvidu.git
Merge branch 'ov_components'
commit
dc6f38c368
|
@ -0,0 +1,32 @@
|
|||
name: openvidu-angular E2E
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'openvidu-components-angular/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
webcomponent_e2e:
|
||||
name: WebComponent E2E tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Launch Selenium Chromedriver
|
||||
run: docker run --shm-size="2g" --network host selenium/standalone-chrome:latest
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
- name: Launch openvidu-server-kms
|
||||
run: docker run -p 4443:4443 --rm -d -e OPENVIDU_SECRET=MY_SECRET openvidu/openvidu-server-kms:latest
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
npm install --prefix openvidu-components-angular && \
|
||||
npm install --prefix openvidu-components-angular/webcomponent-test-e2e
|
||||
- name: Build Webcomponent
|
||||
run: npm run webcomponent:prepare-test-e2e --prefix openvidu-components-angular
|
||||
- name: Start webapp
|
||||
run: npm run serve --prefix openvidu-components-angular/webcomponent-test-e2e &
|
||||
- name: Running E2E tests
|
||||
run: npm run test-ci --prefix openvidu-components-angular/webcomponent-test-e2e
|
|
@ -26,10 +26,16 @@ nbactions.xml
|
|||
*/.settings/*
|
||||
*/.tscache/*
|
||||
|
||||
openvidu-components-angular/node_modules/
|
||||
|
||||
openvidu-components-angular/.angular/
|
||||
|
||||
**/node_modules/
|
||||
**/.angular/
|
||||
**/.vscode
|
||||
openvidu-components-angular/dist/
|
||||
|
||||
openvidu-components-angular/coverage/**
|
||||
openvidu-components-angular/openvidu-webcomponent/
|
||||
openvidu-components-angular/openvidu-angular-doc/
|
||||
|
||||
openvidu-components-angular/webcomponent-test-e2e/dist/
|
||||
|
||||
openvidu-components-angular/webcomponent-test-e2e/web/openvidu-webcomponent-dev.css
|
||||
|
||||
openvidu-components-angular/webcomponent-test-e2e/web/openvidu-webcomponent-dev.js
|
||||
|
|
|
@ -5,12 +5,13 @@ const VERSION = require("./package.json").version;
|
|||
module.exports.buildWebcomponent = async () => {
|
||||
console.log("Building OpenVidu Web Component (" + VERSION + ")");
|
||||
const tutorialWcPath = "../../openvidu-tutorials/openvidu-webcomponent/web";
|
||||
const e2eWcPath = "../webcomponent-test-e2e/web";
|
||||
const e2eWcPath = "./webcomponent-test-e2e/web";
|
||||
|
||||
try {
|
||||
await buildElement();
|
||||
await copyFiles(tutorialWcPath);
|
||||
await copyFiles(e2eWcPath);
|
||||
await renameWebComponentTestName(e2eWcPath);
|
||||
|
||||
console.log("OpenVidu Web Component (" + VERSION + ") built");
|
||||
} catch (error) {
|
||||
|
@ -20,14 +21,9 @@ module.exports.buildWebcomponent = async () => {
|
|||
|
||||
async function buildElement() {
|
||||
const files = [
|
||||
// "./dist/openvidu-call/runtime.js",
|
||||
// "./dist/openvidu-call/polyfills.js",
|
||||
// "./dist/openvidu-call/scripts.js",
|
||||
// "./dist/openvidu-call/main.js",
|
||||
"./dist/openvidu-webcomponent/runtime.js",
|
||||
"./dist/openvidu-webcomponent/main.js",
|
||||
"./dist/openvidu-webcomponent/polyfills.js",
|
||||
// "./dist/openvidu-webcomponent/scripts.js",
|
||||
"./dist/openvidu-webcomponent/polyfills.js"
|
||||
];
|
||||
|
||||
try {
|
||||
|
@ -50,6 +46,11 @@ async function buildElement() {
|
|||
}
|
||||
}
|
||||
|
||||
function renameWebComponentTestName(dir) {
|
||||
fs.renameSync(`${dir}/openvidu-webcomponent-${VERSION}.js`, `${dir}/openvidu-webcomponent-dev.js`);
|
||||
fs.renameSync(`${dir}/openvidu-webcomponent-${VERSION}.css`, `${dir}/openvidu-webcomponent-dev.css`);
|
||||
}
|
||||
|
||||
async function copyFiles(destination) {
|
||||
if (fs.existsSync(destination)) {
|
||||
try {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -9,6 +9,7 @@
|
|||
"lib:copy": "cp dist/openvidu-angular/openvidu-angular-*.tgz ../openvidu-tutorials/openvidu-angular-components",
|
||||
"lib:test": "ng test openvidu-angular --no-watch --code-coverage",
|
||||
"webcomponent:build": "./node_modules/@angular/cli/bin/ng.js build openvidu-webcomponent --configuration production && node ./openvidu-webcomponent-build.js",
|
||||
"webcomponent:prepare-test-e2e": "npm run lib:build && npm run webcomponent:build",
|
||||
"bundle-report": "ng build openvidu-webcomponent --stats-json --configuration production && webpack-bundle-analyzer dist/openvidu-webcomponent/stats.json",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
|
@ -27,14 +28,13 @@
|
|||
"@angular/router": "13.0.0",
|
||||
"autolinker": "3.14.3",
|
||||
"buffer": "^6.0.3",
|
||||
"ng-dynamic-component": "10.1.0",
|
||||
"openvidu-browser": "2.21.0-beta1",
|
||||
"openvidu-browser": "^2.21.0",
|
||||
"rxjs": "7.4.0",
|
||||
"tslib": "2.3.1",
|
||||
"zone.js": "0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "13.0.1",
|
||||
"@angular-devkit/build-angular": "13.2.5",
|
||||
"@angular/cli": "13.0.1",
|
||||
"@angular/compiler": "13.0.0",
|
||||
"@angular/compiler-cli": "13.0.0",
|
||||
|
|
|
@ -7,10 +7,3 @@
|
|||
|
||||
# You can see what browsers were selected by your queries by running:
|
||||
# npx browserslist
|
||||
|
||||
last 1 Chrome version
|
||||
last 1 Firefox version
|
||||
last 2 Edge major versions
|
||||
last 2 Safari major versions
|
||||
last 2 iOS major versions
|
||||
Firefox ESR
|
||||
|
|
|
@ -10,8 +10,7 @@
|
|||
"@angular/flex-layout": "^13.0.0-beta.36",
|
||||
"autolinker": "^3.14.3",
|
||||
"buffer": "^6.0.3",
|
||||
"openvidu-browser": "^2.20.0",
|
||||
"ng-dynamic-component": "^10.1.0"
|
||||
"openvidu-browser": "^2.21.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
@keyframes normal {
|
||||
0%{
|
||||
height: 20%;
|
||||
}
|
||||
50%{
|
||||
height: 40%;
|
||||
}
|
||||
100%{
|
||||
height: 20%;
|
||||
}
|
||||
}
|
||||
@keyframes loud {
|
||||
0%{
|
||||
height: 30%;
|
||||
}
|
||||
50%{
|
||||
height: 80%;
|
||||
|
||||
}
|
||||
100%{
|
||||
height: 30%;
|
||||
}
|
||||
}
|
||||
|
||||
.audio-container{
|
||||
background-color: var(--ov-tertiary-color);
|
||||
padding: 5px;
|
||||
max-width: 15px;
|
||||
max-height: 15px;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
border-radius: var(--ov-buttons-radius);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.stick{
|
||||
margin: auto;
|
||||
height: 80%;
|
||||
width: 3px;
|
||||
background: var(--ov-light-color);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.pause {
|
||||
animation-play-state:paused;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.play {
|
||||
animation-duration: 1.2s;
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-iteration-count: infinite;
|
||||
animation-play-state:running;
|
||||
}
|
||||
|
||||
|
||||
.normal{
|
||||
animation-name: normal;
|
||||
}
|
||||
|
||||
.loud{
|
||||
animation-name: loud;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<div class="audio-container">
|
||||
<div class="stick normal" [ngClass]="isSpeaking ? 'play' : 'pause'"></div>
|
||||
<div class="stick loud" [ngClass]="isSpeaking ? 'play' : 'pause'"></div>
|
||||
<div class="stick normal" [ngClass]="isSpeaking ? 'play' : 'pause'"></div>
|
||||
</div>
|
|
@ -0,0 +1,49 @@
|
|||
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
|
||||
import { PublisherSpeakingEvent, StreamManager } from 'openvidu-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'ov-audio-wave',
|
||||
templateUrl: './audio-wave.component.html',
|
||||
styleUrls: ['./audio-wave.component.css']
|
||||
})
|
||||
export class AudioWaveComponent implements OnInit, OnDestroy {
|
||||
isSpeaking: boolean = false;
|
||||
audioVolume: number = 0;
|
||||
|
||||
private _streamManager: StreamManager;
|
||||
|
||||
@Input()
|
||||
set streamManager(streamManager: StreamManager) {
|
||||
this._streamManager = streamManager;
|
||||
|
||||
if(this._streamManager) {
|
||||
this._streamManager.on('publisherStartSpeaking', (event: PublisherSpeakingEvent) => {
|
||||
this.isSpeaking = true;
|
||||
});
|
||||
|
||||
this._streamManager.on('publisherStopSpeaking', (event: PublisherSpeakingEvent) => {
|
||||
this.isSpeaking = false;
|
||||
});
|
||||
|
||||
// streamManager.on('streamAudioVolumeChange', (event: any) => {
|
||||
// // The loudest sounds on your system will be at 0dB
|
||||
// // and silence in webaudio is -100dB.
|
||||
// this.audioVolume = 100 + event.value.newValue;
|
||||
// console.log('Publisher audio volume change from ' + event.value.oldValue + ' to' + event.value.newValue);
|
||||
// console.log('AUDIO VOLUME', this.audioVolume);
|
||||
// });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
constructor() {}
|
||||
ngOnDestroy(): void {
|
||||
if(this._streamManager){
|
||||
this._streamManager.off('publisherStartSpeaking');
|
||||
this._streamManager.off('publisherStopSpeaking');
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
.poster {
|
||||
position: absolute;
|
||||
display: inline-grid;
|
||||
z-index: 1;
|
||||
margin: auto;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 70px;
|
||||
width: 70px;
|
||||
border-radius: var(--ov-video-radius);
|
||||
border: 2px solid var(--ov-light-color);
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
#poster-text {
|
||||
padding: 20px;
|
||||
font-weight: bold;
|
||||
font-size: 40px;
|
||||
margin: auto;
|
||||
}
|
|
@ -1,20 +1,20 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ParticipantItemComponent } from './participant-item.component';
|
||||
import { AvatarProfileComponent } from './avatar-profile.component';
|
||||
|
||||
describe('ParticipantItemComponent', () => {
|
||||
let component: ParticipantItemComponent;
|
||||
let fixture: ComponentFixture<ParticipantItemComponent>;
|
||||
describe('AvatarProfileComponent', () => {
|
||||
let component: AvatarProfileComponent;
|
||||
let fixture: ComponentFixture<AvatarProfileComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ParticipantItemComponent ]
|
||||
declarations: [ AvatarProfileComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ParticipantItemComponent);
|
||||
fixture = TestBed.createComponent(AvatarProfileComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
|
@ -0,0 +1,22 @@
|
|||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ov-avatar-profile',
|
||||
template: `
|
||||
<div class="poster" [ngStyle]="{ 'background-color': color }">
|
||||
<span id="poster-text">{{ letter }}</span>
|
||||
</div>
|
||||
`,
|
||||
styleUrls: ['./avatar-profile.component.css']
|
||||
})
|
||||
export class AvatarProfileComponent {
|
||||
letter: string;
|
||||
|
||||
@Input()
|
||||
set name(nickname: string) {
|
||||
this.letter = nickname[0];
|
||||
}
|
||||
@Input() color;
|
||||
|
||||
constructor() {}
|
||||
}
|
|
@ -3,14 +3,17 @@
|
|||
}
|
||||
|
||||
|
||||
.bounds {
|
||||
position: absolute;
|
||||
.layout {
|
||||
position: relative;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
min-width: 350px !important;
|
||||
min-height: 100%;
|
||||
width: inherit;
|
||||
height: -webkit-fill-available;
|
||||
height: -moz-available;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -19,9 +22,6 @@
|
|||
* http://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
.custom-class {
|
||||
min-height: 0px !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* OT Base styles
|
||||
|
@ -35,9 +35,7 @@
|
|||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font-family: 'Ubuntu', sans-serif;
|
||||
vertical-align: baseline;
|
||||
transition: all .1s linear;
|
||||
}
|
||||
|
||||
.OT_dialog-centering {
|
||||
|
@ -217,8 +215,11 @@
|
|||
.OT_publisher,
|
||||
.OT_subscriber {
|
||||
position: relative;
|
||||
min-width: 48px;
|
||||
min-height: 48px;
|
||||
min-width: 0px;
|
||||
min-height: 0px;
|
||||
margin: 3px;
|
||||
transition-duration: 0.1s;
|
||||
transition-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
.OT_publisher .OT_video-element,
|
||||
|
@ -227,20 +228,10 @@
|
|||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
-webkit-transform-origin: 0 0;
|
||||
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
|
||||
/* Styles that are applied when the video element should be mirrored */
|
||||
.OT_publisher.OT_mirrored .OT_video-element {
|
||||
-webkit-transform: scale(-1, 1);
|
||||
transform: scale(-1, 1);
|
||||
-webkit-transform-origin: 50% 50%;
|
||||
transform-origin: 50% 50%;
|
||||
}
|
||||
|
||||
.OT_subscriber_error {
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
|
|
|
@ -1,22 +1,18 @@
|
|||
<div id="layout" class="bounds">
|
||||
<div id="layout" class="layout">
|
||||
<div
|
||||
class="OT_root OT_publisher"
|
||||
id="localUser"
|
||||
*ngFor="let connection of localParticipant | connections"
|
||||
[ngClass]="{ OV_small: !connection.streamManager?.stream?.videoActive }"
|
||||
*ngFor="let stream of localParticipant | streams"
|
||||
[ngClass]="{ OV_big: stream.videoEnlarged }"
|
||||
>
|
||||
<ng-template #localStream [ngComponentOutlet]="_localStreamComponent" [ndcDynamicInputs]="{ participant: connection }">
|
||||
</ng-template>
|
||||
<ng-container *ngTemplateOutlet="streamTemplate; context: { $implicit: stream }"></ng-container>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngFor="let connection of remoteParticipants | connections"
|
||||
*ngFor="let stream of remoteParticipants | streams"
|
||||
class="OT_root OT_publisher"
|
||||
id="remote-participant"
|
||||
[ngClass]="{ OV_small: !connection.streamManager?.stream?.videoActive }"
|
||||
[ngClass]="{ OV_big: stream.videoEnlarged }"
|
||||
>
|
||||
<!-- Dynamic ov-stream component injected -->
|
||||
<ng-template #remoteStream [ngComponentOutlet]="_remoteStreamComponent" [ndcDynamicInputs]="{ participant: connection }">
|
||||
</ng-template>
|
||||
<ng-container *ngTemplateOutlet="streamTemplate; context: { $implicit: stream }"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,30 +1,48 @@
|
|||
import { AfterViewInit, Component, OnDestroy, OnInit, Type, ViewChild, ViewContainerRef } from '@angular/core';
|
||||
import {
|
||||
AfterViewInit,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ContentChild,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
TemplateRef
|
||||
} from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { ParticipantService } from '../../services/participant/participant.service';
|
||||
import { ParticipantAbstractModel } from '../../models/participant.model';
|
||||
import { LayoutService } from '../../services/layout/layout.service';
|
||||
import { LibraryComponents } from '../../config/lib.config';
|
||||
import { LibraryConfigService } from '../../services/library-config/library-config.service';
|
||||
import { StreamDirective } from '../../directives/template/openvidu-angular.directive';
|
||||
import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ov-layout',
|
||||
templateUrl: './layout.component.html',
|
||||
styleUrls: ['./layout.component.css']
|
||||
styleUrls: ['./layout.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
_localStreamComponent: Type<any>;
|
||||
_remoteStreamComponent: Type<any>;
|
||||
@ContentChild('stream', { read: TemplateRef }) streamTemplate: TemplateRef<any>;
|
||||
|
||||
@ContentChild(StreamDirective)
|
||||
set externalStream(externalStream: StreamDirective) {
|
||||
// This directive will has value only when STREAM component tagget with '*ovStream' directive
|
||||
// is inside of the layout component tagged with '*ovLayout' directive
|
||||
if (externalStream) {
|
||||
this.streamTemplate = externalStream.template;
|
||||
}
|
||||
}
|
||||
|
||||
localParticipant: ParticipantAbstractModel;
|
||||
remoteParticipants: ParticipantAbstractModel[] = [];
|
||||
protected localParticipantSubs: Subscription;
|
||||
protected remoteParticipantsSubs: Subscription;
|
||||
protected updateLayoutInterval: NodeJS.Timer;
|
||||
|
||||
constructor(
|
||||
protected libraryConfigSrv: LibraryConfigService,
|
||||
protected layoutService: LayoutService,
|
||||
protected participantService: ParticipantService
|
||||
protected participantService: ParticipantService,
|
||||
private libService: OpenViduAngularConfigService,
|
||||
private cd: ChangeDetectorRef
|
||||
) {}
|
||||
|
||||
@ViewChild('localStream', { static: false, read: ViewContainerRef })
|
||||
|
@ -56,24 +74,35 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
this.subscribeToParticipants();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {}
|
||||
ngAfterViewInit() {
|
||||
let timeout: number = 0;
|
||||
// if (this.libService.isWebcomponent()) {
|
||||
// timeout = 0;
|
||||
// }
|
||||
|
||||
this.layoutService.initialize(timeout);
|
||||
this.layoutService.update(timeout);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.localParticipant = null;
|
||||
this.remoteParticipants = [];
|
||||
if (this.localParticipantSubs) this.localParticipantSubs.unsubscribe();
|
||||
if (this.remoteParticipantsSubs) this.remoteParticipantsSubs.unsubscribe();
|
||||
this.layoutService.clear();
|
||||
}
|
||||
|
||||
protected subscribeToParticipants() {
|
||||
this.localParticipantSubs = this.participantService.localParticipantObs.subscribe((p) => {
|
||||
this.localParticipant = p;
|
||||
this.layoutService.update();
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
|
||||
this.remoteParticipantsSubs = this.participantService.remoteParticipantsObs.subscribe((participants) => {
|
||||
this.remoteParticipants = [...participants];
|
||||
this.remoteParticipants = participants;
|
||||
this.layoutService.update();
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
#chat-container {
|
||||
margin: 20px;
|
||||
background-color: var(--ov-light-color);
|
||||
border-radius: 5px;
|
||||
height: -webkit-fill-available;
|
||||
height: -moz-available;
|
||||
border-radius: var(--ov-panel-radius);
|
||||
max-height: calc(100% - 40px);
|
||||
min-height: calc(100% - 40px);
|
||||
}
|
||||
|
||||
.header-container {
|
||||
|
@ -19,6 +19,7 @@
|
|||
|
||||
.header-container button {
|
||||
margin-left: auto;
|
||||
border-radius: var(--ov-buttons-radius);
|
||||
}
|
||||
|
||||
.text-container{
|
||||
|
@ -43,7 +44,7 @@
|
|||
background-color: var(--ov-light-dark-color);
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
border-radius: 5px;
|
||||
border-radius: var(--ov-panel-radius);
|
||||
}
|
||||
|
||||
.input-container textarea {
|
||||
|
@ -89,14 +90,17 @@
|
|||
|
||||
.msg-content {
|
||||
position: relative;
|
||||
border-radius: 5px;
|
||||
border-radius: var(--ov-panel-radius);
|
||||
padding: 8px;
|
||||
color: #000000;
|
||||
width: auto;
|
||||
max-width: 95%;
|
||||
font-size: 13px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
#send-btn {
|
||||
border-radius: var(--ov-buttons-radius);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div class="text-container" fxFlex="30px">
|
||||
<div class="text-container" fxFlex="20px">
|
||||
<p class="text-info">Messages will be removed at the end of the session</p>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { AfterViewInit, Component, ElementRef, HostListener, OnInit, ViewChild } from '@angular/core';
|
||||
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, HostListener, OnInit, ViewChild } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { ChatMessage } from '../../../models/chat.model';
|
||||
import { MenuType } from '../../../models/menu.model';
|
||||
|
@ -8,7 +8,8 @@ import { SidenavMenuService } from '../../../services/sidenav-menu/sidenav-menu.
|
|||
@Component({
|
||||
selector: 'ov-chat-panel',
|
||||
templateUrl: './chat-panel.component.html',
|
||||
styleUrls: ['./chat-panel.component.css']
|
||||
styleUrls: ['./chat-panel.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ChatPanelComponent implements OnInit, AfterViewInit {
|
||||
@ViewChild('chatScroll') chatScroll: ElementRef;
|
||||
|
@ -19,7 +20,7 @@ export class ChatPanelComponent implements OnInit, AfterViewInit {
|
|||
|
||||
private chatMessageSubscription: Subscription;
|
||||
|
||||
constructor(private chatService: ChatService, private menuService: SidenavMenuService) {}
|
||||
constructor(private chatService: ChatService, private menuService: SidenavMenuService, private cd: ChangeDetectorRef) {}
|
||||
|
||||
@HostListener('document:keydown.escape', ['$event'])
|
||||
onKeydownHandler(event: KeyboardEvent) {
|
||||
|
@ -52,8 +53,10 @@ export class ChatPanelComponent implements OnInit, AfterViewInit {
|
|||
}
|
||||
|
||||
sendMessage(): void {
|
||||
this.chatService.sendMessage(this.message);
|
||||
this.message = '';
|
||||
if(!!this.message) {
|
||||
this.chatService.sendMessage(this.message);
|
||||
this.message = '';
|
||||
}
|
||||
}
|
||||
|
||||
scrollToBottom(): void {
|
||||
|
@ -73,6 +76,7 @@ export class ChatPanelComponent implements OnInit, AfterViewInit {
|
|||
this.messageList = messages;
|
||||
if (this.menuService.isMenuOpened()) {
|
||||
this.scrollToBottom();
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
<!-- CHAT panel -->
|
||||
<ng-container *ngIf="isChatPanelOpened">
|
||||
<ng-container *ngTemplateOutlet="chatPanelTemplate"></ng-container>
|
||||
</ng-container>
|
||||
|
||||
|
||||
<ng-container *ngIf="isChatPanelOpened" #chat></ng-container>
|
||||
<!-- PARTICIPANTS panel -->
|
||||
<ng-container *ngIf="isParticipantsPanelOpened">
|
||||
<ng-container *ngTemplateOutlet="participantsPanelTemplate"></ng-container>
|
||||
</ng-container>
|
||||
|
||||
|
||||
<ng-container *ngIf="isParticipantsPanelOpened" #participants></ng-container>
|
|
@ -1,43 +1,41 @@
|
|||
import { Component, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, OnInit, TemplateRef } from '@angular/core';
|
||||
import { skip, Subscription } from 'rxjs';
|
||||
import { LibraryComponents } from '../../config/lib.config';
|
||||
import { ChatPanelDirective, ParticipantsPanelDirective } from '../../directives/template/openvidu-angular.directive';
|
||||
import { MenuType } from '../../models/menu.model';
|
||||
import { LibraryConfigService } from '../../services/library-config/library-config.service';
|
||||
import { SidenavMenuService } from '../../services/sidenav-menu/sidenav-menu.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ov-panel',
|
||||
templateUrl: './panel.component.html',
|
||||
styleUrls: ['./panel.component.css']
|
||||
styleUrls: ['./panel.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class PanelComponent implements OnInit, OnDestroy {
|
||||
export class PanelComponent implements OnInit {
|
||||
@ContentChild('participantsPanel', { read: TemplateRef }) participantsPanelTemplate: TemplateRef<any>;
|
||||
@ContentChild('chatPanel', { read: TemplateRef }) chatPanelTemplate: TemplateRef<any>;
|
||||
|
||||
@ContentChild(ParticipantsPanelDirective)
|
||||
set externalParticipantPanel(externalParticipantsPanel: ParticipantsPanelDirective) {
|
||||
// This directive will has value only when PARTICIPANTS PANEL component tagged with '*ovParticipantsPanel'
|
||||
// is inside of the PANEL component tagged with '*ovPanel'
|
||||
if (externalParticipantsPanel) {
|
||||
this.participantsPanelTemplate = externalParticipantsPanel.template;
|
||||
}
|
||||
}
|
||||
|
||||
@ContentChild(ChatPanelDirective)
|
||||
set externalChatPanel(externalChatPanel: ChatPanelDirective) {
|
||||
// This directive will has value only when CHAT PANEL component tagged with '*ovChatPanel'
|
||||
// is inside of the PANEL component tagged with '*ovPanel'
|
||||
if (externalChatPanel) {
|
||||
this.chatPanelTemplate = externalChatPanel.template;
|
||||
}
|
||||
}
|
||||
|
||||
isParticipantsPanelOpened: boolean;
|
||||
isChatPanelOpened: boolean;
|
||||
menuSubscription: Subscription;
|
||||
|
||||
@ViewChild('chat', { static: false, read: ViewContainerRef })
|
||||
set chat(reference: ViewContainerRef) {
|
||||
setTimeout(() => {
|
||||
if (reference) {
|
||||
const component = this.libraryConfigSrv.getDynamicComponent(LibraryComponents.CHAT_PANEL);
|
||||
reference.clear();
|
||||
reference.createComponent(component);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
@ViewChild('participants', { static: false, read: ViewContainerRef })
|
||||
set participants(reference: ViewContainerRef) {
|
||||
setTimeout(() => {
|
||||
if (reference) {
|
||||
const component = this.libraryConfigSrv.getDynamicComponent(LibraryComponents.PARTICIPANTS_PANEL);
|
||||
reference.clear();
|
||||
reference.createComponent(component);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
constructor(protected libraryConfigSrv: LibraryConfigService, protected menuService: SidenavMenuService) {}
|
||||
constructor(protected menuService: SidenavMenuService, private cd: ChangeDetectorRef) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.subscribeToPanelToggling();
|
||||
|
@ -46,6 +44,7 @@ export class PanelComponent implements OnInit, OnDestroy {
|
|||
this.menuSubscription = this.menuService.menuOpenedObs.pipe(skip(1)).subscribe((ev: { opened: boolean; type?: MenuType }) => {
|
||||
this.isChatPanelOpened = ev.opened && ev.type === MenuType.CHAT;
|
||||
this.isParticipantsPanelOpened = ev.opened && ev.type === MenuType.PARTICIPANTS;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
.participant-subtitle {
|
||||
font-style: italic;
|
||||
font-size: 11px !important;
|
||||
}
|
||||
.participant-nickname {
|
||||
font-weight: bold !important;
|
||||
}
|
||||
.participant-action-buttons{
|
||||
display: flex;
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
mat-list-item {
|
||||
height: max-content !important;
|
||||
padding-bottom: 10px !important;
|
||||
}
|
||||
|
||||
mat-list {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.participant-avatar {
|
||||
display: contents;
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<mat-list>
|
||||
<mat-list-item>
|
||||
<mat-icon matListAvatar class="participant-avatar">person</mat-icon>
|
||||
<h3 matLine class="participant-nickname">{{ name }}</h3>
|
||||
<p matLine class="participant-subtitle">{{ connections }}</p>
|
||||
<!-- <p matLine>
|
||||
<span class="participant-subtitle"></span>
|
||||
</p> -->
|
||||
</mat-list-item>
|
||||
<mat-divider *ngIf="showDividerLine"></mat-divider>
|
||||
</mat-list>
|
|
@ -1,16 +0,0 @@
|
|||
import { Component, Input, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ov-participant-item',
|
||||
templateUrl: './participant-item.component.html',
|
||||
styleUrls: ['./participant-item.component.css']
|
||||
})
|
||||
export class ParticipantItemComponent implements OnInit {
|
||||
@Input() name: string;
|
||||
@Input() connections: string;
|
||||
@Input() showDividerLine: boolean;
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<mat-list>
|
||||
<mat-list-item>
|
||||
<mat-icon matListAvatar class="participant-avatar">person</mat-icon>
|
||||
<h3 matLine class="participant-nickname">{{ _participant.nickname }}</h3>
|
||||
<p matLine class="participant-subtitle">{{ _participant | streamTypesEnabled }}</p>
|
||||
<!-- <p matLine>
|
||||
<span class="participant-subtitle"></span>
|
||||
</p> -->
|
||||
|
||||
<div class="participant-action-buttons">
|
||||
<button
|
||||
mat-icon-button
|
||||
id="mute-btn"
|
||||
*ngIf="!_participant.local && showMuteButton"
|
||||
[class.warn-btn]="_participant.isMutedForcibly"
|
||||
(click)="toggleMuteForcibly()"
|
||||
>
|
||||
<mat-icon *ngIf="!_participant.isMutedForcibly">volume_up</mat-icon>
|
||||
<mat-icon *ngIf="_participant.isMutedForcibly">volume_off</mat-icon>
|
||||
</button>
|
||||
|
||||
<!-- External item elements -->
|
||||
<ng-container *ngIf="participantPanelItemElementsTemplate">
|
||||
<ng-container *ngTemplateOutlet="participantPanelItemElementsTemplate"></ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
|
@ -0,0 +1,25 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ParticipantPanelItemComponent } from './participant-panel-item.component';
|
||||
|
||||
describe('ParticipantPanelItemComponent', () => {
|
||||
let component: ParticipantPanelItemComponent;
|
||||
let fixture: ComponentFixture<ParticipantPanelItemComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ParticipantPanelItemComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ParticipantPanelItemComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,52 @@
|
|||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, Input, OnDestroy, OnInit, TemplateRef } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { ParticipantPanelItemElementsDirective } from '../../../../directives/template/openvidu-angular.directive';
|
||||
import { ParticipantAbstractModel } from '../../../../models/participant.model';
|
||||
import { OpenViduAngularConfigService } from '../../../../services/config/openvidu-angular.config.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ov-participant-panel-item',
|
||||
templateUrl: './participant-panel-item.component.html',
|
||||
styleUrls: ['./participant-panel-item.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ParticipantPanelItemComponent implements OnInit, OnDestroy {
|
||||
@ContentChild('participantPanelItemElements', { read: TemplateRef }) participantPanelItemElementsTemplate: TemplateRef<any>;
|
||||
showMuteButton: boolean = true;
|
||||
private muteButtonSub: Subscription;
|
||||
|
||||
@ContentChild(ParticipantPanelItemElementsDirective)
|
||||
set externalItemElements(externalItemElements: ParticipantPanelItemElementsDirective) {
|
||||
// This directive will has value only when ITEM ELEMENTS component tagget with '*ovParticipantPanelItemElements' directive
|
||||
// is inside of the P PANEL ITEM component tagged with '*ovParticipantPanelItem' directive
|
||||
if (externalItemElements) {
|
||||
this.participantPanelItemElementsTemplate = externalItemElements.template;
|
||||
}
|
||||
}
|
||||
|
||||
@Input()
|
||||
set participant(p: ParticipantAbstractModel) {
|
||||
this._participant = p;
|
||||
}
|
||||
|
||||
_participant: ParticipantAbstractModel;
|
||||
constructor(private libService: OpenViduAngularConfigService, private cd: ChangeDetectorRef) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.subscribeToParticipantPanelItemDirectives();
|
||||
}
|
||||
ngOnDestroy(): void {
|
||||
if (this.muteButtonSub) this.muteButtonSub.unsubscribe();
|
||||
}
|
||||
|
||||
toggleMuteForcibly() {
|
||||
this._participant.setMutedForcibly(!this._participant.isMutedForcibly);
|
||||
}
|
||||
|
||||
private subscribeToParticipantPanelItemDirectives() {
|
||||
this.muteButtonSub = this.libService.participantItemMuteButton.subscribe((value: boolean) => {
|
||||
this.showMuteButton = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
.participants-container {
|
||||
margin: 20px;
|
||||
background-color: var(--ov-light-color);
|
||||
border-radius: 5px;
|
||||
height: -webkit-fill-available;
|
||||
height: -moz-available;
|
||||
border-radius: var(--ov-panel-radius);
|
||||
max-height: calc(100% - 40px);
|
||||
min-height: calc(100% - 40px);
|
||||
}
|
||||
|
||||
.header-container {
|
||||
|
@ -19,6 +19,7 @@
|
|||
|
||||
.header-container button {
|
||||
margin-left: auto;
|
||||
border-radius: var(--ov-buttons-radius);
|
||||
}
|
||||
|
||||
.local-participant-container, .remote-participants-container {
|
||||
|
@ -26,6 +27,8 @@
|
|||
}
|
||||
|
||||
.scrollable {
|
||||
height: calc(100% - 60px);
|
||||
max-height: calc(100% - 60px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class="participants-container">
|
||||
<div class="participants-container" id="participants-container">
|
||||
<div class="header-container">
|
||||
<h3>Participants</h3>
|
||||
<button mat-icon-button matTooltip="Close" (click)="close()">
|
||||
|
@ -8,26 +8,16 @@
|
|||
|
||||
<div class="scrollable">
|
||||
|
||||
<div class="local-participant-container">
|
||||
<ov-participant-item
|
||||
[name]="localParticipant | nickname"
|
||||
[connections]="localParticipant | connectionsEnabled"
|
||||
[showDividerLine]="true"
|
||||
></ov-participant-item>
|
||||
<div class="local-participant-container" *ngIf="localParticipant">
|
||||
<ng-container *ngTemplateOutlet="participantPanelItemTemplate; context: { $implicit: localParticipant }"></ng-container>
|
||||
<mat-divider *ngIf="true"></mat-divider>
|
||||
</div>
|
||||
|
||||
<!-- <div class="message-container">
|
||||
<p *ngIf="remoteParticipants.length === 0" class="text-info">No remote participants connected</p>
|
||||
<p *ngIf="remoteParticipants.length > 0" class="text-info">{{ remoteParticipants.length }} remote participants connected</p>
|
||||
</div> -->
|
||||
<div class="remote-participants-container" id="remote-participants-container" *ngIf="remoteParticipants.length > 0">
|
||||
|
||||
<div class="remote-participants-container" *ngIf="remoteParticipants.length > 0">
|
||||
<ov-participant-item
|
||||
*ngFor="let p of remoteParticipants; let lastItem = last"
|
||||
[name]="p | nickname"
|
||||
[connections]="p | connectionsEnabled"
|
||||
[showDividerLine]="!lastItem"
|
||||
></ov-participant-item>
|
||||
<div *ngFor="let participant of this.remoteParticipants" id="remote-participant-item">
|
||||
<ng-container *ngTemplateOutlet="participantPanelItemTemplate; context: { $implicit: participant }"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -1,33 +1,67 @@
|
|||
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||
import { ParticipantAbstractModel, ParticipantModel } from '../../../../models/participant.model';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, OnDestroy, OnInit, TemplateRef } from '@angular/core';
|
||||
import { ParticipantAbstractModel } from '../../../../models/participant.model';
|
||||
import { ParticipantService } from '../../../../services/participant/participant.service';
|
||||
import { SidenavMenuService } from '../../../../services/sidenav-menu/sidenav-menu.service';
|
||||
import { SidenavMenuService } from '../../../..//services/sidenav-menu/sidenav-menu.service';
|
||||
import { ParticipantPanelItemDirective } from '../../../../directives/template/openvidu-angular.directive';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'ov-participants-panel',
|
||||
templateUrl: './participants-panel.component.html',
|
||||
styleUrls: ['./participants-panel.component.css']
|
||||
styleUrls: ['./participants-panel.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ParticipantsPanelComponent implements OnInit {
|
||||
export class ParticipantsPanelComponent implements OnInit, OnDestroy {
|
||||
localParticipant: any;
|
||||
remoteParticipants: ParticipantAbstractModel[] = [];
|
||||
|
||||
constructor(protected participantService: ParticipantService, protected menuService: SidenavMenuService, private cd: ChangeDetectorRef) {}
|
||||
private localParticipantSubs: Subscription;
|
||||
private remoteParticipantsSubs: Subscription;
|
||||
@ContentChild('participantPanelItem', { read: TemplateRef }) participantPanelItemTemplate: TemplateRef<any>;
|
||||
|
||||
@ContentChild(ParticipantPanelItemDirective)
|
||||
set externalParticipantPanelItem(externalParticipantPanelItem: ParticipantPanelItemDirective) {
|
||||
// This directive will has value only when PARTICIPANT PANEL ITEM component tagged with '*ovParticipantPanelItem'
|
||||
// is inside of the PARTICIPANTS PANEL component tagged with '*ovParticipantsPanel'
|
||||
if (externalParticipantPanelItem) {
|
||||
this.participantPanelItemTemplate = externalParticipantPanelItem.template;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected participantService: ParticipantService,
|
||||
protected menuService: SidenavMenuService,
|
||||
private cd: ChangeDetectorRef
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.participantService.localParticipantObs.subscribe((p: ParticipantModel) => {
|
||||
|
||||
this.localParticipantSubs = this.participantService.localParticipantObs.subscribe((p: ParticipantAbstractModel) => {
|
||||
this.localParticipant = p;
|
||||
// Mark for re-rendering using an impure pipe 'connectionsEnabled'
|
||||
// Mark for re-rendering using an impure pipe 'streamsTypesEnabled'
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
|
||||
this.participantService.remoteParticipantsObs.subscribe((p: ParticipantModel[]) => {
|
||||
this.remoteParticipants = p;
|
||||
// Mark for re-rendering using an impure pipe 'connectionsEnabled'
|
||||
this.remoteParticipantsSubs = this.participantService.remoteParticipantsObs.subscribe((p: ParticipantAbstractModel[]) => {
|
||||
// Workaround which forc the objects references update
|
||||
// After one entirely day trying to make it works, this is the only way
|
||||
const remoteParticipantsAux = [];
|
||||
p.forEach((par) => {
|
||||
remoteParticipantsAux.push(Object.create(par));
|
||||
});
|
||||
this.remoteParticipants = remoteParticipantsAux;
|
||||
// Mark for re-rendering using an impure pipe 'streamsTypesEnabled'
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.localParticipantSubs) this.localParticipantSubs.unsubscribe();
|
||||
if (this.remoteParticipantsSubs) this.remoteParticipantsSubs.unsubscribe;
|
||||
}
|
||||
|
||||
close() {
|
||||
this.menuService.closeMenu();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
.container {
|
||||
height: 100%;
|
||||
padding: 80px;
|
||||
background-color: var(--ov-light-dark-color);
|
||||
}
|
||||
|
||||
#layout-container {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-bottom: 1px;
|
||||
font-weight: bold;
|
||||
}
|
||||
hr {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ov-layout {
|
||||
height: -webkit-fill-available;
|
||||
height: -moz-available;
|
||||
width: -webkit-fill-available;
|
||||
width: -moz-available;
|
||||
}
|
||||
|
||||
.media-panel {
|
||||
background-color: var(--ov-light-dark-color);
|
||||
}
|
||||
.media-panel-container {
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.nickname-container {
|
||||
/* padding: 10px; */
|
||||
display: block !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: var(--ov-primary-color);
|
||||
}
|
||||
|
||||
#nickname-input-container mat-form-field {
|
||||
color: var(--ov-primary-color);
|
||||
}
|
||||
|
||||
.mat-form-field-appearance-fill .mat-form-field-flex {
|
||||
background-color: var(--ov-light-color);
|
||||
border-radius: var(--ov-video-radius);
|
||||
}
|
||||
|
||||
|
||||
.buttons-container{
|
||||
border-radius: 5px;
|
||||
padding: 10px 0px;
|
||||
height: 100px;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
#camera-button {
|
||||
border-radius: var(--ov-buttons-radius);
|
||||
/* background-color: var(--ov-secondary-color) !important; */
|
||||
/* color: var(--ov-light-color) !important; */
|
||||
|
||||
}
|
||||
|
||||
.join-btn-container {
|
||||
width: inherit;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#join-button {
|
||||
width: 100%;
|
||||
font-weight: bold;
|
||||
color: var(--ov-light-color);
|
||||
background-color: var(--ov-tertiary-color);
|
||||
border-radius: var(--ov-video-radius);
|
||||
|
||||
}
|
||||
|
||||
.warn-btn {
|
||||
color: var(--ov-light-color);
|
||||
background-color: var(--ov-warn-color) !important;
|
||||
}
|
||||
|
||||
.active-btn {
|
||||
color: var(--ov-light-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;
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: 480px) {
|
||||
.container, .media-panel-container, .buttons-container {
|
||||
padding: 0;
|
||||
}
|
||||
.nickname-container, .buttons-container, .join-btn-container {
|
||||
width: 90% !important;
|
||||
margin: auto;
|
||||
}
|
||||
.join-btn-container {
|
||||
padding: 0px 10px;
|
||||
}
|
||||
.media-panel {
|
||||
align-items: flex-start !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 480px) and (max-width: 960px) {
|
||||
.container, .media-panel-container, .buttons-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.nickname-container, .buttons-container, .join-btn-container{
|
||||
width: 80% !important;
|
||||
min-width: 80% !important;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.buttons-container, .media-panel-container {
|
||||
padding-top: 0px;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.media-panel {
|
||||
align-items: flex-start !important;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
<div class="container" id="prejoin-container" fxLayout.gt-sm="row" fxLayout.lt-md="column">
|
||||
<div fxFlex.gt-sm="65%" fxFlex.lt-md="55%" fxLayoutAlign="center center" id="layout-container">
|
||||
<ov-layout>
|
||||
<ng-template #stream let-stream>
|
||||
<ov-stream [stream]="stream" [displayParticipantName]="false" [settingsButton]="false"></ov-stream>
|
||||
</ng-template>
|
||||
</ov-layout>
|
||||
</div>
|
||||
<div fxFlex.gt-sm="35%" fxFlex.lt-md="45%" fxLayoutAlign="center center" class="media-panel" *ngIf="localParticipant">
|
||||
<div fxLayout="column" fxLayoutGap="10px" class="media-panel-container">
|
||||
<div fxLayout.gt-sm="column" fxLayout.lt-md="column" fxLayoutGap="10px" fxFlex="33%">
|
||||
<div fxFlex.gt-sm="100%" fxFlex.lt-md="33%" fxLayoutAlign="center center" fxFlexFill class="nickname-container">
|
||||
<h4 *ngIf="windowSize >= 960">Set your name</h4>
|
||||
<hr *ngIf="windowSize >= 960"/>
|
||||
<div id="nickname-input-container">
|
||||
<button mat-icon-button disabled>
|
||||
<mat-icon>person</mat-icon>
|
||||
</button>
|
||||
<mat-form-field appearance="standard">
|
||||
<mat-label>Nickname</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 fxFlex.gt-sm="100%" fxFlex.lt-md="33%" fxLayoutAlign="center center" fxFlexFill class="buttons-container">
|
||||
<h4 *ngIf="windowSize >= 960">Choose your devices</h4>
|
||||
<hr *ngIf="windowSize >= 960"/>
|
||||
<!-- Camera -->
|
||||
<div class="device-container-element">
|
||||
<button
|
||||
mat-icon-button
|
||||
id="camera-button"
|
||||
[disabled]="!hasVideoDevices"
|
||||
[class.warn-btn]="isVideoMuted"
|
||||
(click)="toggleCam()"
|
||||
>
|
||||
<mat-icon *ngIf="!isVideoMuted" matTooltip="Mute Camera" id="videocam">videocam</mat-icon>
|
||||
<mat-icon *ngIf="isVideoMuted" matTooltip="Unmute Camera" id="videocam_off">videocam_off</mat-icon>
|
||||
</button>
|
||||
<mat-form-field>
|
||||
<mat-label>Video devices</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 -->
|
||||
<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="Mute Audio" id="mic">mic</mat-icon>
|
||||
<mat-icon *ngIf="isAudioMuted" matTooltip="Unmute Audio" id="mic_off">mic_off</mat-icon>
|
||||
</button>
|
||||
<mat-form-field>
|
||||
<mat-label>Audio devices</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 fxFlex.gt-sm="60%" fxLayout.lt-md="column" fxLayoutAlign="center center" fxFlexFill class="join-btn-container">
|
||||
<button mat-flat-button (click)="joinSession()" form="nicknameForm" id="join-button">Join session</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,25 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PreJoinComponent } from './pre-join.component';
|
||||
|
||||
describe('PreJoinComponent', () => {
|
||||
let component: PreJoinComponent;
|
||||
let fixture: ComponentFixture<PreJoinComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ PreJoinComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PreJoinComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,235 @@
|
|||
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 { 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 { DeviceService } from '../../services/device/device.service';
|
||||
import { LayoutService } from '../../services/layout/layout.service';
|
||||
import { LoggerService } from '../../services/logger/logger.service';
|
||||
import { OpenViduService } from '../../services/openvidu/openvidu.service';
|
||||
import { ParticipantService } from '../../services/participant/participant.service';
|
||||
import { StorageService } from '../../services/storage/storage.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ov-pre-join',
|
||||
templateUrl: './pre-join.component.html',
|
||||
styleUrls: ['./pre-join.component.css']
|
||||
})
|
||||
export class PreJoinComponent implements OnInit, OnDestroy {
|
||||
@Output() onJoinButtonClicked = new EventEmitter<any>();
|
||||
cameras: CustomDevice[];
|
||||
microphones: CustomDevice[];
|
||||
cameraSelected: CustomDevice;
|
||||
microphoneSelected: CustomDevice;
|
||||
isVideoMuted: boolean;
|
||||
isAudioMuted: boolean;
|
||||
localParticipant: ParticipantAbstractModel;
|
||||
windowSize: number;
|
||||
hasVideoDevices: boolean;
|
||||
hasAudioDevices: boolean;
|
||||
isLoading = true;
|
||||
nickname: string;
|
||||
private log: ILogger;
|
||||
private localParticipantSubscription: Subscription;
|
||||
private screenShareStateSubscription: Subscription;
|
||||
|
||||
@HostListener('window:resize')
|
||||
sizeChange() {
|
||||
this.windowSize = window.innerWidth;
|
||||
this.layoutService.update();
|
||||
}
|
||||
|
||||
constructor(
|
||||
private layoutService: LayoutService,
|
||||
private deviceSrv: DeviceService,
|
||||
private loggerSrv: LoggerService,
|
||||
private openviduService: OpenViduService,
|
||||
private participantService: ParticipantService,
|
||||
private storageSrv: StorageService
|
||||
) {
|
||||
this.log = this.loggerSrv.get('PreJoinComponent');
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.subscribeToLocalParticipantEvents();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.localParticipantSubscription) {
|
||||
this.localParticipantSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
if (this.screenShareStateSubscription) {
|
||||
this.screenShareStateSubscription.unsubscribe();
|
||||
}
|
||||
this.deviceSrv.clear();
|
||||
}
|
||||
|
||||
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 };
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 };
|
||||
// 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.microphoneSelected = audioSource;
|
||||
this.deviceSrv.setMicSelected(this.microphoneSelected);
|
||||
}
|
||||
if (this.isAudioMuted) {
|
||||
// Enable microphone
|
||||
this.openviduService.publishAudio(this.participantService.getMyCameraPublisher(), true);
|
||||
this.isAudioMuted = true;
|
||||
}
|
||||
}
|
||||
|
||||
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.disableWebcamUser();
|
||||
this.openviduService.publishAudio(this.participantService.getMyScreenPublisher(), publish);
|
||||
} else if (this.participantService.isOnlyMyScreenActive()) {
|
||||
// Cam will be published, enable webcam
|
||||
this.participantService.enableWebcamUser();
|
||||
}
|
||||
|
||||
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.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() {
|
||||
this.onJoinButtonClicked.emit();
|
||||
}
|
||||
|
||||
private subscribeToLocalParticipantEvents() {
|
||||
this.localParticipantSubscription = this.participantService.localParticipantObs.subscribe((p) => {
|
||||
this.localParticipant = p;
|
||||
this.nickname = this.localParticipant.getNickname();
|
||||
});
|
||||
}
|
||||
|
||||
//? After test in Chrome and Firefox, the devices always have labels.
|
||||
//? It's not longer needed
|
||||
// private handlePublisherSuccess(publisher: Publisher) {
|
||||
// publisher.once('accessAllowed', async () => {
|
||||
// if (this.deviceSrv.areEmptyLabels()) {
|
||||
// await this.deviceSrv.forceUpdate();
|
||||
// if (this.hasAudioDevices) {
|
||||
// const audioLabel = publisher?.stream?.getMediaStream()?.getAudioTracks()[0]?.label;
|
||||
// this.deviceSrv.setMicSelected(audioLabel);
|
||||
// }
|
||||
|
||||
// if (this.hasVideoDevices) {
|
||||
// const videoLabel = publisher?.stream?.getMediaStream()?.getVideoTracks()[0]?.label;
|
||||
// this.deviceSrv.setCameraSelected(videoLabel);
|
||||
// }
|
||||
// this.setDevicesInfo();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
}
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
.sidenav-container {
|
||||
position: relative;
|
||||
height: calc(100% - 80px);
|
||||
min-height: calc(100% - 80px);
|
||||
height: calc(100% - 70px);
|
||||
min-height: calc(100% - 70px);
|
||||
padding-top: 10px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
@ -17,7 +17,7 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 360px;
|
||||
width: 380px;
|
||||
background-color: var(--ov-primary-color);
|
||||
border-left: none;
|
||||
position: absolute;
|
||||
|
@ -30,6 +30,11 @@
|
|||
min-height: -moz-available;
|
||||
}
|
||||
|
||||
#layout-container {
|
||||
height: inherit;
|
||||
width: inherit;
|
||||
}
|
||||
|
||||
.mat-drawer-container {
|
||||
background-color: var(--ov-primary-color);
|
||||
}
|
||||
|
@ -38,7 +43,7 @@
|
|||
background-color: var(--ov-primary-color);
|
||||
min-width: 400px !important;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
height: 70px;
|
||||
}
|
||||
|
||||
#footer {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<div id="session-container">
|
||||
<mat-sidenav-container class="sidenav-container">
|
||||
<!-- OPENVIDU PANELS -->
|
||||
<mat-sidenav
|
||||
#sidenav
|
||||
mode="{{ sidenavMode }}"
|
||||
|
@ -10,15 +9,17 @@
|
|||
fixedTopGap="0"
|
||||
fixedBottomGap="0"
|
||||
>
|
||||
<ng-template #panel></ng-template>
|
||||
<ng-container *ngTemplateOutlet="panelTemplate"></ng-container>
|
||||
</mat-sidenav>
|
||||
|
||||
<!-- OPENVIDU LAYOUT -->
|
||||
<mat-sidenav-content class="sidenav-main">
|
||||
<!-- Dynamic layout injection -->
|
||||
<ng-template #layout></ng-template>
|
||||
<div id="layout-container">
|
||||
<ng-container *ngTemplateOutlet="layoutTemplate"></ng-container>
|
||||
</div>
|
||||
</mat-sidenav-content>
|
||||
</mat-sidenav-container>
|
||||
|
||||
<ng-template #toolbar></ng-template>
|
||||
<div id="footer-container">
|
||||
<ng-container *ngTemplateOutlet="toolbarTemplate"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ContentChild,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
TemplateRef,
|
||||
ViewChild,
|
||||
ViewContainerRef
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { Subscriber, Session, StreamEvent, StreamPropertyChangedEvent, SessionDisconnectedEvent, ConnectionEvent } from 'openvidu-browser';
|
||||
|
||||
|
@ -17,7 +16,7 @@ import { ILogger } from '../../models/logger.model';
|
|||
|
||||
import { ChatService } from '../../services/chat/chat.service';
|
||||
import { LoggerService } from '../../services/logger/logger.service';
|
||||
import { WebrtcService } from '../../services/webrtc/webrtc.service';
|
||||
import { OpenViduService } from '../../services/openvidu/openvidu.service';
|
||||
import { TokenService } from '../../services/token/token.service';
|
||||
import { ActionService } from '../../services/action/action.service';
|
||||
import { Signal } from '../../models/signal.model';
|
||||
|
@ -37,11 +36,15 @@ import { LibraryComponents } from '../../config/lib.config';
|
|||
@Component({
|
||||
selector: 'ov-session',
|
||||
templateUrl: './session.component.html',
|
||||
styleUrls: ['./session.component.css']
|
||||
styleUrls: ['./session.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class SessionComponent implements OnInit {
|
||||
@Input() tokens: { webcam: string; screen: string };
|
||||
// @Output() _session = new EventEmitter<any>();
|
||||
@ContentChild('toolbar', { read: TemplateRef }) toolbarTemplate: TemplateRef<any>;
|
||||
@ContentChild('panel', { read: TemplateRef }) panelTemplate: TemplateRef<any>;
|
||||
@ContentChild('layout', { read: TemplateRef }) layoutTemplate: TemplateRef<any>;
|
||||
|
||||
@Output() onSessionCreated = new EventEmitter<any>();
|
||||
// @Output() _publisher = new EventEmitter<any>();
|
||||
// @Output() _error = new EventEmitter<any>();
|
||||
|
||||
|
@ -63,14 +66,13 @@ export class SessionComponent implements OnInit {
|
|||
|
||||
constructor(
|
||||
protected actionService: ActionService,
|
||||
protected webrtcService: WebrtcService,
|
||||
protected openviduService: OpenViduService,
|
||||
protected participantService: ParticipantService,
|
||||
protected loggerSrv: LoggerService,
|
||||
protected chatService: ChatService,
|
||||
protected tokenService: TokenService,
|
||||
protected layoutService: LayoutService,
|
||||
protected menuService: SidenavMenuService,
|
||||
protected libraryConfigSrv: LibraryConfigService
|
||||
protected menuService: SidenavMenuService
|
||||
) {
|
||||
this.log = this.loggerSrv.get('SessionComponent');
|
||||
}
|
||||
|
@ -130,12 +132,8 @@ export class SessionComponent implements OnInit {
|
|||
}
|
||||
|
||||
async ngOnInit() {
|
||||
if (this.webrtcService.getWebcamSession() === null) {
|
||||
this.webrtcService.initialize();
|
||||
await this.webrtcService.initDefaultPublisher(undefined);
|
||||
}
|
||||
this.session = this.webrtcService.getWebcamSession();
|
||||
this.sessionScreen = this.webrtcService.getScreenSession();
|
||||
this.session = this.openviduService.getWebcamSession();
|
||||
this.sessionScreen = this.openviduService.getScreenSession();
|
||||
this.subscribeToConnectionCreatedAndDestroyed();
|
||||
this.subscribeToStreamCreated();
|
||||
this.subscribeToStreamDestroyed();
|
||||
|
@ -143,18 +141,14 @@ export class SessionComponent implements OnInit {
|
|||
this.subscribeToNicknameChanged();
|
||||
this.chatService.subscribeToChat();
|
||||
this.subscribeToReconnection();
|
||||
|
||||
this.tokenService.setWebcamToken(this.tokens.webcam);
|
||||
this.tokenService.setScreenToken(this.tokens.screen);
|
||||
this.onSessionCreated.emit(this.session);
|
||||
|
||||
await this.connectToSession();
|
||||
// Workaround, firefox does not have audio when publisher join with muted camera
|
||||
// if (this.platformService.isFirefox() && !this.localUserService.hasCameraVideoActive()) {
|
||||
// this.webrtcService.publishVideo(this.localUserService.getMyCameraPublisher(), true);
|
||||
// this.webrtcService.publishVideo(this.localUserService.getMyCameraPublisher(), false);
|
||||
// this.openviduService.publishVideo(this.localUserService.getMyCameraPublisher(), true);
|
||||
// this.openviduService.publishVideo(this.localUserService.getMyCameraPublisher(), false);
|
||||
// }
|
||||
|
||||
// this._session.emit(this.session);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
@ -164,14 +158,15 @@ export class SessionComponent implements OnInit {
|
|||
this.participantService.clear();
|
||||
this.session = null;
|
||||
this.sessionScreen = null;
|
||||
this.layoutService.clear();
|
||||
this.isChatPanelOpened = false;
|
||||
this.isParticipantsPanelOpened = false;
|
||||
if (this.menuSubscription) this.menuSubscription.unsubscribe();
|
||||
if (this.layoutWidthSubscription) this.layoutWidthSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
leaveSession() {
|
||||
this.log.d('Leaving session...');
|
||||
this.webrtcService.disconnect();
|
||||
this.openviduService.disconnect();
|
||||
}
|
||||
|
||||
protected subscribeToTogglingMenu() {
|
||||
|
@ -203,17 +198,17 @@ export class SessionComponent implements OnInit {
|
|||
|
||||
private async connectToSession(): Promise<void> {
|
||||
try {
|
||||
if (this.participantService.areBothEnabled()) {
|
||||
await this.webrtcService.connectSession(this.webrtcService.getWebcamSession(), this.tokenService.getWebcamToken());
|
||||
await this.webrtcService.connectSession(this.webrtcService.getScreenSession(), this.tokenService.getScreenToken());
|
||||
await this.webrtcService.publish(this.participantService.getMyCameraPublisher());
|
||||
await this.webrtcService.publish(this.participantService.getMyScreenPublisher());
|
||||
} else if (this.participantService.isOnlyMyScreenEnabled()) {
|
||||
await this.webrtcService.connectSession(this.webrtcService.getScreenSession(), this.tokenService.getScreenToken());
|
||||
await this.webrtcService.publish(this.participantService.getMyScreenPublisher());
|
||||
if (this.participantService.haveICameraAndScreenActive()) {
|
||||
await this.openviduService.connectSession(this.openviduService.getWebcamSession(), this.tokenService.getWebcamToken());
|
||||
await this.openviduService.connectSession(this.openviduService.getScreenSession(), this.tokenService.getScreenToken());
|
||||
await this.openviduService.publish(this.participantService.getMyCameraPublisher());
|
||||
await this.openviduService.publish(this.participantService.getMyScreenPublisher());
|
||||
} else if (this.participantService.isOnlyMyScreenActive()) {
|
||||
await this.openviduService.connectSession(this.openviduService.getScreenSession(), this.tokenService.getScreenToken());
|
||||
await this.openviduService.publish(this.participantService.getMyScreenPublisher());
|
||||
} else {
|
||||
await this.webrtcService.connectSession(this.webrtcService.getWebcamSession(), this.tokenService.getWebcamToken());
|
||||
await this.webrtcService.publish(this.participantService.getMyCameraPublisher());
|
||||
await this.openviduService.connectSession(this.openviduService.getWebcamSession(), this.tokenService.getWebcamToken());
|
||||
await this.openviduService.publish(this.participantService.getMyCameraPublisher());
|
||||
}
|
||||
} catch (error) {
|
||||
// this._error.emit({ error: error.error, messgae: error.message, code: error.code, status: error.status });
|
||||
|
@ -226,7 +221,7 @@ export class SessionComponent implements OnInit {
|
|||
this.session.on('connectionCreated', (event: ConnectionEvent) => {
|
||||
const connectionId = event.connection?.connectionId;
|
||||
const nickname: string = this.participantService.getNicknameFromConnectionData(event.connection.data);
|
||||
const isRemoteConnection: boolean = !this.webrtcService.isMyOwnConnection(connectionId);
|
||||
const isRemoteConnection: boolean = !this.openviduService.isMyOwnConnection(connectionId);
|
||||
const isCameraConnection: boolean = !nickname?.includes(`_${VideoType.SCREEN}`);
|
||||
const data = event.connection?.data;
|
||||
|
||||
|
@ -235,16 +230,16 @@ export class SessionComponent implements OnInit {
|
|||
this.participantService.addRemoteConnection(connectionId, data, null);
|
||||
|
||||
//Sending nicnkanme signal to new participants
|
||||
if (this.webrtcService.needSendNicknameSignal()) {
|
||||
const data = { clientData: this.participantService.getWebcamNickname() };
|
||||
this.webrtcService.sendSignal(Signal.NICKNAME_CHANGED, [event.connection], data);
|
||||
if (this.openviduService.needSendNicknameSignal()) {
|
||||
const data = { clientData: this.participantService.getMyNickname() };
|
||||
this.openviduService.sendSignal(Signal.NICKNAME_CHANGED, [event.connection], data);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.session.on('connectionDestroyed', (event: ConnectionEvent) => {
|
||||
const nickname: string = this.participantService.getNicknameFromConnectionData(event.connection.data);
|
||||
const isRemoteConnection: boolean = !this.webrtcService.isMyOwnConnection(event.connection.connectionId);
|
||||
const isRemoteConnection: boolean = !this.openviduService.isMyOwnConnection(event.connection.connectionId);
|
||||
const isCameraConnection: boolean = !nickname?.includes(`_${VideoType.SCREEN}`);
|
||||
// Deleting participant when connection is destroyed
|
||||
if (isRemoteConnection && isCameraConnection) {
|
||||
|
@ -258,7 +253,7 @@ export class SessionComponent implements OnInit {
|
|||
const connectionId = event.stream?.connection?.connectionId;
|
||||
const data = event.stream?.connection?.data;
|
||||
|
||||
const isRemoteConnection: boolean = !this.webrtcService.isMyOwnConnection(connectionId);
|
||||
const isRemoteConnection: boolean = !this.openviduService.isMyOwnConnection(connectionId);
|
||||
if (isRemoteConnection) {
|
||||
const subscriber: Subscriber = this.session.subscribe(event.stream, undefined);
|
||||
this.participantService.addRemoteConnection(connectionId, data, subscriber);
|
||||
|
@ -271,30 +266,27 @@ export class SessionComponent implements OnInit {
|
|||
this.session.on('streamDestroyed', (event: StreamEvent) => {
|
||||
const connectionId = event.stream.connection.connectionId;
|
||||
this.participantService.removeConnectionByConnectionId(connectionId);
|
||||
// event.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
private subscribeToStreamPropertyChange() {
|
||||
// this.session.on('streamPropertyChanged', (event: StreamPropertyChangedEvent) => {
|
||||
// const connectionId = event.stream.connection.connectionId;
|
||||
// const isRemoteConnection: boolean = !this.webrtcService.isMyOwnConnection(connectionId);
|
||||
// if (isRemoteConnection) {
|
||||
// if (event.changedProperty === 'videoActive') {
|
||||
// // this.participantService.updateUsers();
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
this.session.on('streamPropertyChanged', (event: StreamPropertyChangedEvent) => {
|
||||
const connectionId = event.stream.connection.connectionId;
|
||||
const isRemoteConnection: boolean = !this.openviduService.isMyOwnConnection(connectionId);
|
||||
if (isRemoteConnection) {
|
||||
this.participantService.updateRemoteParticipants();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private subscribeToNicknameChanged() {
|
||||
this.session.on(`signal:${Signal.NICKNAME_CHANGED}`, (event: any) => {
|
||||
const connectionId = event.from.connectionId;
|
||||
const isRemoteConnection: boolean = !this.webrtcService.isMyOwnConnection(connectionId);
|
||||
const isRemoteConnection: boolean = !this.openviduService.isMyOwnConnection(connectionId);
|
||||
|
||||
if (isRemoteConnection) {
|
||||
const nickname = this.participantService.getNicknameFromConnectionData(event.data);
|
||||
this.participantService.setNickname(connectionId, nickname);
|
||||
this.participantService.setRemoteNickname(connectionId, nickname);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
/* Fixes layout bug. The OT_root is created with the entire layout width and it has a weird UX behaviour */
|
||||
.no-size {
|
||||
height: 0px !important;
|
||||
width: 0px !important;
|
||||
}
|
||||
|
||||
|
||||
.nickname {
|
||||
padding: 0px;
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
border-radius: 5px;
|
||||
border-radius: var(--ov-panel-radius);
|
||||
color: #ffffff;
|
||||
font-family: 'Roboto','RobotoDraft',Helvetica,Arial,sans-serif;
|
||||
}
|
||||
|
@ -11,12 +18,13 @@
|
|||
padding: 5px;
|
||||
color: #ffffff;
|
||||
font-weight: bold;
|
||||
border-radius: 2px;
|
||||
border-radius: var(--ov-panel-radius);
|
||||
}
|
||||
|
||||
#dialogNickname {
|
||||
#nickname-input-container {
|
||||
background-color: #0000005e;
|
||||
border-radius: 3px;
|
||||
border-radius: var(--ov-panel-radius);
|
||||
|
||||
}
|
||||
|
||||
#closeButton {
|
||||
|
@ -30,6 +38,13 @@
|
|||
padding: 10px;
|
||||
}
|
||||
|
||||
#audio-wave-container {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.fullscreen {
|
||||
top: 40px;
|
||||
}
|
||||
|
@ -51,7 +66,7 @@
|
|||
font-size: 100%;
|
||||
}
|
||||
|
||||
.status-icons, .videoButtons {
|
||||
.status-icons, #settings-container {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
|
@ -62,30 +77,31 @@
|
|||
left: 0;
|
||||
}
|
||||
|
||||
.status-icons button, .videoButtons button {
|
||||
.status-icons button, #settings-container button {
|
||||
color: #ffffff;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
margin: 5px;
|
||||
border-radius: var(--ov-buttons-radius);
|
||||
}
|
||||
|
||||
.status-icons button {
|
||||
background-color: var(--ov-warn-color);
|
||||
}
|
||||
|
||||
.status-icons .mat-icon-button, .videoButtons .mat-icon-button{
|
||||
.status-icons .mat-icon-button, #settings-container .mat-icon-button{
|
||||
line-height: 0px;
|
||||
}
|
||||
|
||||
.status-icons mat-icon, .videoButtons mat-icon {
|
||||
.status-icons mat-icon, #settings-container mat-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.videoButtons{
|
||||
#settings-container{
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.videoButtons button {
|
||||
#settings-container button {
|
||||
background-color: var(--ov-dark-color);
|
||||
}
|
||||
|
||||
|
@ -96,7 +112,7 @@
|
|||
position: absolute;
|
||||
overflow: hidden;
|
||||
background-color: #000000;
|
||||
border-radius: 5px;
|
||||
border-radius: var(--ov-video-radius);
|
||||
}
|
||||
|
||||
mat-error,
|
||||
|
|
|
@ -1,77 +1,68 @@
|
|||
<div
|
||||
*ngIf="this._participant"
|
||||
class="OT_widget-container"
|
||||
[id]="'container-' + this._participant.streamManager?.stream?.streamId"
|
||||
#streamComponent
|
||||
*ngIf="this._stream"
|
||||
class="OT_widget-container no-size"
|
||||
[id]="'container-' + this._stream.streamManager?.stream?.streamId"
|
||||
#streamContainer
|
||||
>
|
||||
<div class="nickname" [class.fullscreen]="isFullscreen">
|
||||
<div *ngIf="!isMinimal && showNickname" id="nickname-container" class="nickname" [class.fullscreen]="isFullscreen">
|
||||
<div (click)="toggleNicknameForm()" class="nicknameContainer" selected *ngIf="!toggleNickname">
|
||||
<span id="nickname">{{ this._participant.nickname }}</span>
|
||||
<span *ngIf="_participant.local || (_participant.streamManager && !_participant.streamManager?.remote)"> (edit)</span>
|
||||
<span id="nickname" *ngIf="this._stream.type === 'CAMERA'">{{ this._stream.participant.nickname }}</span>
|
||||
<span id="nickname" *ngIf="this._stream.type === 'SCREEN'">{{ this._stream.participant.nickname }}_SCREEN</span>
|
||||
</div>
|
||||
<div *ngIf="toggleNickname && !this._participant.streamManager?.remote" [class.fullscreen]="isFullscreen" id="dialogNickname">
|
||||
<form id="nicknameForm">
|
||||
<mat-form-field color="primary">
|
||||
<input
|
||||
matInput
|
||||
#nicknameInput
|
||||
placeholder="Nick: {{ this._participant.nickname }}"
|
||||
[formControl]="nicknameFormControl"
|
||||
[errorStateMatcher]="matcher"
|
||||
(keypress)="eventKeyPress($event)"
|
||||
autocomplete="off"
|
||||
(focusout)="toggleNicknameForm()"
|
||||
/>
|
||||
<mat-error *ngIf="nicknameFormControl.hasError('required')"> Nickname is <strong>required</strong> </mat-error>
|
||||
<mat-error *ngIf="nicknameFormControl.hasError('maxlength')"> Nickname is <strong>too long!</strong> </mat-error>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
<div *ngIf="toggleNickname && !this._stream.streamManager?.remote" [class.fullscreen]="isFullscreen" id="nickname-input-container">
|
||||
<input
|
||||
matInput
|
||||
#nicknameInput
|
||||
autocomplete="off"
|
||||
maxlength="20"
|
||||
[(ngModel)]="this.nickname"
|
||||
(keypress)="updateNickname($event)"
|
||||
(focusout)="updateNickname($event)"
|
||||
/>
|
||||
</div>
|
||||
<!-- Custom network quality element -->
|
||||
<ng-content select="[network-quality]"></ng-content>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!isMinimal && showAudioDetection && _stream.type === 'CAMERA'" id="audio-wave-container">
|
||||
<ov-audio-wave [streamManager]="_stream.streamManager"></ov-audio-wave>
|
||||
</div>
|
||||
|
||||
<ov-avatar-profile
|
||||
*ngIf="!_stream.streamManager?.stream?.videoActive && _stream.type === 'CAMERA'"
|
||||
[name]="_stream.participant.nickname"
|
||||
[color]="_stream.participant.colorProfile"
|
||||
></ov-avatar-profile>
|
||||
|
||||
<ov-video
|
||||
(dblclick)="toggleVideoSize()"
|
||||
[streamManager]="this._participant.streamManager"
|
||||
[mutedSound]="mutedSound"
|
||||
(toggleVideoSizeEvent)="toggleVideoSize($event)"
|
||||
(dblclick)="toggleVideoEnlarged()"
|
||||
[streamManager]="_stream.streamManager"
|
||||
[mutedSound]="_stream?.participant?.isMutedForcibly"
|
||||
></ov-video>
|
||||
|
||||
|
||||
<div class="status-icons">
|
||||
<!-- <div id="statusMic" *ngIf="!this._participant.streamManager?.stream?.audioActive">
|
||||
<mat-icon>mic_off</mat-icon>
|
||||
</div> -->
|
||||
|
||||
<button mat-icon-button id="statusMic" *ngIf="!this._participant.streamManager?.stream?.audioActive" disabled>
|
||||
<button mat-icon-button id="statusMic" *ngIf="!this._stream.streamManager?.stream?.audioActive" disabled>
|
||||
<mat-icon>mic_off</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="videoButtons">
|
||||
|
||||
<!-- Custom hand notification -->
|
||||
<ng-content select="[hand-notification]"></ng-content>
|
||||
|
||||
<div *ngIf="!isMinimal && showSettingsButton" id="settings-container" class="videoButtons">
|
||||
<button mat-icon-button (click)="toggleVideoMenu($event)" matTooltip="Settings" aria-label="Video settings menu">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
<span [matMenuTriggerFor]="menu"></span>
|
||||
<mat-menu #menu="matMenu" yPosition="above" xPosition="before">
|
||||
<button mat-menu-item id="videoZoomButton" (click)="toggleVideoSize()">
|
||||
<button mat-menu-item id="videoZoomButton" (click)="toggleVideoEnlarged()">
|
||||
<mat-icon>{{ this.videoSizeIcon }}</mat-icon>
|
||||
<span *ngIf="videoSizeIcon === videoSizeIconEnum.NORMAL">Zoom out</span>
|
||||
<span *ngIf="videoSizeIcon === videoSizeIconEnum.BIG">Zoom in</span>
|
||||
</button>
|
||||
<button mat-menu-item id="volumeButton" *ngIf="this._participant.streamManager?.remote" (click)="toggleSound()">
|
||||
<mat-icon *ngIf="!mutedSound">volume_up</mat-icon>
|
||||
<span *ngIf="!mutedSound">Mute sound</span>
|
||||
<button mat-menu-item id="volumeButton" *ngIf="!this._stream.local" (click)="toggleSound()">
|
||||
<mat-icon *ngIf="!_stream.participant.isMutedForcibly">volume_up</mat-icon>
|
||||
<span *ngIf="!_stream.participant.isMutedForcibly">Mute sound</span>
|
||||
|
||||
<mat-icon *ngIf="mutedSound">volume_off</mat-icon>
|
||||
<span *ngIf="mutedSound">Unmute sound</span>
|
||||
<mat-icon *ngIf="_stream.participant.isMutedForcibly">volume_off</mat-icon>
|
||||
<span *ngIf="_stream.participant.isMutedForcibly">Unmute sound</span>
|
||||
</button>
|
||||
<!-- <button mat-menu-item *ngIf="this._participant.streamManager?.stream?.videoActive" id="fullscreenButton" (click)="toggleFullscreen()">
|
||||
<!-- <button mat-menu-item *ngIf="this._stream.streamManager?.stream?.videoActive" id="fullscreenButton" (click)="toggleFullscreen()">
|
||||
<mat-icon *ngIf="!isFullscreenEnabled">fullscreen</mat-icon>
|
||||
<span *ngIf="!isFullscreenEnabled">Fullscreen</span>
|
||||
|
||||
|
@ -83,7 +74,7 @@
|
|||
mat-menu-item
|
||||
(click)="replaceScreenTrack()"
|
||||
id="changeScreenButton"
|
||||
*ngIf="!this._participant.streamManager?.remote && this._participant.streamManager?.stream?.typeOfVideo === videoTypeEnum.SCREEN"
|
||||
*ngIf="!this._stream.streamManager?.remote && this._stream.streamManager?.stream?.typeOfVideo === videoTypeEnum.SCREEN"
|
||||
>
|
||||
<mat-icon>picture_in_picture</mat-icon>
|
||||
<span>Replace screen</span>
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
import { Component, ElementRef, HostListener, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
|
||||
import { FormControl, Validators } from '@angular/forms';
|
||||
import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { MatMenuPanel, MatMenuTrigger } from '@angular/material/menu';
|
||||
import { NicknameMatcher } from '../../matchers/nickname.matcher';
|
||||
import { VideoSizeIcon } from '../../models/icon.model';
|
||||
import { ScreenType, VideoType } from '../../models/video-type.model';
|
||||
import { Storage } from '../../models/storage.model';
|
||||
import { DocumentService } from '../../services/document/document.service';
|
||||
import { CdkOverlayService } from '../../services/cdk-overlay/cdk-overlay.service';
|
||||
import { WebrtcService } from '../../services/webrtc/webrtc.service';
|
||||
import { OpenViduService } from '../../services/openvidu/openvidu.service';
|
||||
import { LayoutService } from '../../services/layout/layout.service';
|
||||
import { StorageService } from '../../services/storage/storage.service';
|
||||
import { Signal } from '../../models/signal.model';
|
||||
import { LayoutClass } from '../../models/layout.model';
|
||||
import { PublisherProperties } from 'openvidu-browser';
|
||||
import { StreamModel } from '../../models/participant.model';
|
||||
import { ParticipantService } from '../../services/participant/participant.service';
|
||||
import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ov-stream',
|
||||
|
@ -22,41 +20,57 @@ import { ParticipantService } from '../../services/participant/participant.servi
|
|||
styleUrls: ['./stream.component.css']
|
||||
})
|
||||
export class StreamComponent implements OnInit {
|
||||
videoSizeIconEnum = VideoSizeIcon;
|
||||
videoTypeEnum = VideoType;
|
||||
videoSizeIcon: VideoSizeIcon = VideoSizeIcon.BIG;
|
||||
mutedSound: boolean;
|
||||
toggleNickname: boolean;
|
||||
nicknameFormControl: FormControl;
|
||||
matcher: NicknameMatcher;
|
||||
_participant: StreamModel;
|
||||
|
||||
@ViewChild('streamComponent', { read: ViewContainerRef }) streamComponent: ViewContainerRef;
|
||||
@ViewChild(MatMenuTrigger) public menuTrigger: MatMenuTrigger;
|
||||
@ViewChild('menu') menu: MatMenuPanel;
|
||||
|
||||
videoSizeIconEnum = VideoSizeIcon;
|
||||
videoTypeEnum = VideoType;
|
||||
videoSizeIcon: VideoSizeIcon = VideoSizeIcon.BIG;
|
||||
toggleNickname: boolean;
|
||||
_stream: StreamModel;
|
||||
nickname: string;
|
||||
|
||||
isMinimal: boolean = false;
|
||||
showNickname: boolean = true;
|
||||
showAudioDetection: boolean = true;
|
||||
showSettingsButton: boolean = true;
|
||||
|
||||
private _streamContainer: ElementRef;
|
||||
|
||||
private minimalSub: Subscription;
|
||||
private displayParticipantNameSub: Subscription;
|
||||
private displayAudioDetectionSub: Subscription;
|
||||
private settingsButtonSub: Subscription;
|
||||
|
||||
constructor(
|
||||
protected documentService: DocumentService,
|
||||
protected openViduWebRTCService: WebrtcService,
|
||||
protected openviduService: OpenViduService,
|
||||
protected layoutService: LayoutService,
|
||||
protected participantService: ParticipantService,
|
||||
protected storageService: StorageService,
|
||||
protected cdkSrv: CdkOverlayService
|
||||
protected cdkSrv: CdkOverlayService,
|
||||
private libService: OpenViduAngularConfigService
|
||||
) {}
|
||||
|
||||
// @HostListener('document:fullscreenchange', ['$event'])
|
||||
// @HostListener('document:webkitfullscreenchange', ['$event'])
|
||||
// @HostListener('document:mozfullscreenchange', ['$event'])
|
||||
// @HostListener('document:MSFullscreenChange', ['$event'])
|
||||
// onFullscreenHandler(event) {
|
||||
// this.isFullscreenEnabled = !this.isFullscreenEnabled;
|
||||
// }
|
||||
@ViewChild('streamContainer', { static: false, read: ElementRef })
|
||||
set streamContainer(streamContainer: ElementRef) {
|
||||
setTimeout(() => {
|
||||
if (streamContainer) {
|
||||
this._streamContainer = streamContainer;
|
||||
// Remove 'no-size' css class for showing the element in the view.
|
||||
// This is a workaround for fixing a layout bug which provide a bad UX with each new elements created.
|
||||
this.documentService.removeNoSizeElementClass(this._streamContainer.nativeElement);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
@Input()
|
||||
set participant(participant: StreamModel) {
|
||||
this._participant = participant;
|
||||
this.checkVideoSizeBigIcon(this._participant.videoEnlarged);
|
||||
this.nicknameFormControl = new FormControl(this._participant.nickname, [Validators.maxLength(25), Validators.required]);
|
||||
set stream(stream: StreamModel) {
|
||||
this._stream = stream;
|
||||
this.checkVideoEnlarged();
|
||||
if (this._stream.participant) {
|
||||
this.nickname = this._stream.participant.nickname;
|
||||
}
|
||||
}
|
||||
|
||||
@ViewChild('nicknameInput')
|
||||
|
@ -67,28 +81,22 @@ export class StreamComponent implements OnInit {
|
|||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.matcher = new NicknameMatcher();
|
||||
this.subscribeToStreamDirectives();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.cdkSrv.setSelector('body');
|
||||
if (this.settingsButtonSub) this.settingsButtonSub.unsubscribe();
|
||||
if (this.displayAudioDetectionSub) this.displayAudioDetectionSub.unsubscribe();
|
||||
if (this.displayParticipantNameSub) this.displayParticipantNameSub.unsubscribe();
|
||||
}
|
||||
|
||||
toggleVideoSize(resetAll?) {
|
||||
const element = this.documentService.getHTMLElementByClassName(this.streamComponent.element.nativeElement, LayoutClass.ROOT_ELEMENT);
|
||||
if (!!resetAll) {
|
||||
this.documentService.removeAllBigElementClass();
|
||||
this.participantService.resetUsersZoom();
|
||||
this.participantService.resetUsersZoom();
|
||||
}
|
||||
|
||||
this.documentService.toggleBigElementClass(element);
|
||||
|
||||
if (!!this._participant.streamManager?.stream?.connection?.connectionId) {
|
||||
if (this.openViduWebRTCService.isMyOwnConnection(this._participant.streamManager?.stream?.connection?.connectionId)) {
|
||||
this.participantService.toggleZoom(this._participant.streamManager?.stream?.connection?.connectionId);
|
||||
toggleVideoEnlarged() {
|
||||
if (!!this._stream.streamManager?.stream?.connection?.connectionId) {
|
||||
if (this.openviduService.isMyOwnConnection(this._stream.streamManager?.stream?.connection?.connectionId)) {
|
||||
this.participantService.toggleMyVideoEnlarged(this._stream.streamManager?.stream?.connection?.connectionId);
|
||||
} else {
|
||||
this.participantService.toggleUserZoom(this._participant.streamManager?.stream?.connection?.connectionId);
|
||||
this.participantService.toggleRemoteVideoEnlarged(this._stream.streamManager?.stream?.connection?.connectionId);
|
||||
}
|
||||
}
|
||||
this.layoutService.update();
|
||||
|
@ -99,26 +107,27 @@ export class StreamComponent implements OnInit {
|
|||
this.menuTrigger.closeMenu();
|
||||
return;
|
||||
}
|
||||
this.cdkSrv.setSelector('#container-' + this._participant.streamManager?.stream?.streamId);
|
||||
this.cdkSrv.setSelector('#container-' + this._stream.streamManager?.stream?.streamId);
|
||||
this.menuTrigger.openMenu();
|
||||
}
|
||||
|
||||
toggleSound() {
|
||||
this.mutedSound = !this.mutedSound;
|
||||
this._stream.participant.setMutedForcibly(!this._stream.participant.isMutedForcibly);
|
||||
}
|
||||
|
||||
toggleNicknameForm() {
|
||||
if (this._participant.local) {
|
||||
if (this._stream.participant.local) {
|
||||
this.toggleNickname = !this.toggleNickname;
|
||||
}
|
||||
}
|
||||
|
||||
eventKeyPress(event) {
|
||||
if (event && event.keyCode === 13 && this.nicknameFormControl.valid) {
|
||||
const nickname = this.nicknameFormControl.value;
|
||||
this.participantService.setNickname(this._participant.connectionId, nickname);
|
||||
this.storageService.set(Storage.USER_NICKNAME, nickname);
|
||||
this.openViduWebRTCService.sendSignal(Signal.NICKNAME_CHANGED, undefined, {clientData: nickname});
|
||||
updateNickname(event) {
|
||||
if (event?.keyCode === 13 || event?.type === 'focusout') {
|
||||
if (!!this.nickname) {
|
||||
this.participantService.setMyNickname(this.nickname);
|
||||
this.storageService.setNickname(this.nickname);
|
||||
this.openviduService.sendSignal(Signal.NICKNAME_CHANGED, undefined, { clientData: this.nickname });
|
||||
}
|
||||
this.toggleNicknameForm();
|
||||
}
|
||||
}
|
||||
|
@ -127,14 +136,32 @@ export class StreamComponent implements OnInit {
|
|||
const properties: PublisherProperties = {
|
||||
videoSource: ScreenType.SCREEN,
|
||||
publishVideo: true,
|
||||
publishAudio: !this.participantService.isMyCameraEnabled(),
|
||||
publishAudio: !this.participantService.isMyCameraActive(),
|
||||
mirror: false
|
||||
};
|
||||
await this.openViduWebRTCService.replaceTrack(this.participantService.getMyScreenPublisher(), properties);
|
||||
await this.openviduService.replaceTrack(VideoType.SCREEN, properties);
|
||||
}
|
||||
|
||||
protected checkVideoSizeBigIcon(videoEnlarged: boolean) {
|
||||
this.videoSizeIcon = videoEnlarged ? VideoSizeIcon.NORMAL : VideoSizeIcon.BIG;
|
||||
private checkVideoEnlarged() {
|
||||
this.videoSizeIcon = this._stream.videoEnlarged ? VideoSizeIcon.NORMAL : VideoSizeIcon.BIG;
|
||||
}
|
||||
|
||||
private subscribeToStreamDirectives() {
|
||||
|
||||
this.minimalSub = this.libService.minimalObs.subscribe((value: boolean) => {
|
||||
this.isMinimal = value;
|
||||
});
|
||||
this.displayParticipantNameSub = this.libService.displayParticipantNameObs.subscribe((value: boolean) => {
|
||||
this.showNickname = value;
|
||||
// this.cd.markForCheck();
|
||||
});
|
||||
this.displayAudioDetectionSub = this.libService.displayAudioDetectionObs.subscribe((value: boolean) => {
|
||||
this.showAudioDetection = value;
|
||||
// this.cd.markForCheck();
|
||||
});
|
||||
this.settingsButtonSub = this.libService.settingsButtonObs.subscribe((value: boolean) => {
|
||||
this.showSettingsButton = value;
|
||||
// this.cd.markForCheck();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,12 +10,19 @@
|
|||
right: 0;
|
||||
}
|
||||
|
||||
#img-container {
|
||||
#info-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
#media-buttons-container {
|
||||
max-height: 100% !important;
|
||||
}
|
||||
|
||||
#media-buttons-container button, ::ng-deep #media-buttons-container>.ng-star-inserted {
|
||||
#media-buttons-container > button,
|
||||
::ng-deep #media-buttons-container > button,
|
||||
#media-buttons-container:not(#media-buttons-container > button) button,
|
||||
/* Applying css for external additional buttons*/
|
||||
::ng-deep #media-buttons-container:not(#media-buttons-container > button) button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: var(--ov-secondary-color);
|
||||
|
@ -26,7 +33,7 @@
|
|||
background-color: var(--ov-warn-color) !important;
|
||||
}
|
||||
|
||||
.active-btn {
|
||||
.active-btn, ::ng-deep .active-btn {
|
||||
background-color: var(--ov-tertiary-color) !important;
|
||||
}
|
||||
|
||||
|
@ -34,15 +41,19 @@
|
|||
font-size: 24px;
|
||||
}
|
||||
|
||||
#company-logo {
|
||||
#media-buttons-container button, #menu-buttons-container button {
|
||||
border-radius: var(--ov-buttons-radius);
|
||||
}
|
||||
|
||||
#branding-logo {
|
||||
background-color: var(--ov-dark-color);
|
||||
border-radius: 5px;
|
||||
border-radius: var(--ov-panel-radius);
|
||||
max-width: 35px;
|
||||
max-height: 35px;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
#session-title {
|
||||
#session-name {
|
||||
font-family: 'Ubuntu', sans-serif;
|
||||
font-weight: bold;
|
||||
font-size: 15px;
|
||||
|
@ -56,7 +67,7 @@
|
|||
position: absolute;
|
||||
top: 12px;
|
||||
right: 33px;
|
||||
border-radius: 50%;
|
||||
border-radius: var(--ov-buttons-radius);
|
||||
background-color: #ffa600;
|
||||
border: 1px solid #000;
|
||||
z-index: 99999;
|
||||
|
@ -64,7 +75,7 @@
|
|||
|
||||
#leave-btn {
|
||||
background-color: var(--ov-warn-color) !important;
|
||||
border-radius: 10px !important;
|
||||
border-radius: var(--ov-leave-button-radius) !important;
|
||||
width: 60px !important;
|
||||
}
|
||||
|
||||
|
@ -86,13 +97,13 @@
|
|||
color: #fff;
|
||||
}
|
||||
@media (max-width: 750px) {
|
||||
#session-title {
|
||||
#session-name {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 850px) {
|
||||
#company-logo {
|
||||
#branding-logo {
|
||||
display: none;
|
||||
}
|
||||
}
|
|
@ -1,73 +1,68 @@
|
|||
<mat-toolbar id="toolbar" role="heading" fxLayout fxLayoutAlign="center" fxLayoutGap="10px">
|
||||
<div fxFlex="20%" fxLayoutAlign="start center">
|
||||
<div id="img-container">
|
||||
<img id="company-logo" alt="OpenVidu Logo" [src]="logoUrl" />
|
||||
<span id="session-title" *ngIf="session && session.sessionId">{{ session.sessionId }}</span>
|
||||
<div id="info-container">
|
||||
<img *ngIf="!isMinimal && showLogo" id="branding-logo" src="assets/images/logo.png" ovLogo />
|
||||
<span id="session-name" *ngIf="!isMinimal && session && session.sessionId && showSessionName">{{ session.sessionId }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div fxFlex="60%" fxFlexOrder="2" fxLayoutAlign="center center" id="media-buttons-container">
|
||||
<!-- Microphone button -->
|
||||
<button
|
||||
id="navMicrophoneButton"
|
||||
id="mic-btn"
|
||||
mat-icon-button
|
||||
(click)="toggleMicrophone()"
|
||||
[disabled]="isConnectionLost"
|
||||
*ngIf="hasAudioDevices"
|
||||
[class.warn-btn]="!isWebcamAudioEnabled"
|
||||
[disabled]="isConnectionLost || !hasAudioDevices"
|
||||
[class.warn-btn]="!isWebcamAudioActive"
|
||||
>
|
||||
<mat-icon *ngIf="isWebcamAudioEnabled" matTooltip="Mute your audio">mic</mat-icon>
|
||||
<mat-icon *ngIf="!isWebcamAudioEnabled" matTooltip="Unmute your audio">mic_off</mat-icon>
|
||||
<mat-icon *ngIf="isWebcamAudioActive" matTooltip="Mute your audio" id="mic">mic</mat-icon>
|
||||
<mat-icon *ngIf="!isWebcamAudioActive" matTooltip="Unmute your audio" id="mic_off">mic_off</mat-icon>
|
||||
</button>
|
||||
|
||||
<!-- Camera button -->
|
||||
<button
|
||||
id="navCameraButton"
|
||||
id="camera-btn"
|
||||
mat-icon-button
|
||||
(click)="toggleCamera()"
|
||||
[disabled]="isConnectionLost"
|
||||
*ngIf="hasVideoDevices"
|
||||
[class.warn-btn]="!isWebcamVideoEnabled"
|
||||
[disabled]="isConnectionLost || !hasVideoDevices"
|
||||
[class.warn-btn]="!isWebcamVideoActive"
|
||||
>
|
||||
<mat-icon *ngIf="isWebcamVideoEnabled" matTooltip="Mute your cam">videocam</mat-icon>
|
||||
<mat-icon *ngIf="!isWebcamVideoEnabled" matTooltip="Unmute your cam">videocam_off</mat-icon>
|
||||
<mat-icon *ngIf="isWebcamVideoActive" matTooltip="Mute your cam" id="videocam">videocam</mat-icon>
|
||||
<mat-icon *ngIf="!isWebcamVideoActive" matTooltip="Unmute your cam" id="videocam_off">videocam_off</mat-icon>
|
||||
</button>
|
||||
|
||||
<!-- Screenshare button -->
|
||||
<button
|
||||
id="navScreenButton"
|
||||
mat-icon-button
|
||||
*ngIf="!isMinimal && showScreenshareButton"
|
||||
id="screenshare-btn"
|
||||
(click)="toggleScreenShare()"
|
||||
[disabled]="isConnectionLost"
|
||||
[class.active-btn]="isScreenShareEnabled"
|
||||
[class.active-btn]="isScreenShareActive"
|
||||
>
|
||||
<mat-icon *ngIf="!isScreenShareEnabled" matTooltip="Enable screen share">screen_share</mat-icon>
|
||||
<mat-icon *ngIf="isScreenShareEnabled" matTooltip="Disable screen share">screen_share</mat-icon>
|
||||
<mat-icon *ngIf="!isScreenShareActive" matTooltip="Enable screen share">screen_share</mat-icon>
|
||||
<mat-icon *ngIf="isScreenShareActive" matTooltip="Disable screen share">screen_share</mat-icon>
|
||||
</button>
|
||||
|
||||
<!-- Replace Screen track button -->
|
||||
<!-- <button
|
||||
id="navReplaceScreenButton"
|
||||
mat-icon-button
|
||||
*ngIf="isScreenShareEnabled"
|
||||
(click)="replaceScreenTrack()"
|
||||
[disabled]="isConnectionLost"
|
||||
>
|
||||
<mat-icon matTooltip="Replace screen track">picture_in_picture</mat-icon>
|
||||
</button> -->
|
||||
|
||||
<!-- Fullscreen button -->
|
||||
<button mat-icon-button (click)="toggleFullscreen()" [disabled]="isConnectionLost" [class.active-btn]="isFullscreenEnabled">
|
||||
<mat-icon *ngIf="isFullscreenEnabled" matTooltip="Exit Fullscreen">fullscreen_exit</mat-icon>
|
||||
<mat-icon *ngIf="!isFullscreenEnabled" matTooltip="Fullscreen">fullscreen</mat-icon>
|
||||
<button
|
||||
mat-icon-button
|
||||
id="fullscreen-btn"
|
||||
*ngIf="!isMinimal && showFullscreenButton"
|
||||
(click)="toggleFullscreen()"
|
||||
[disabled]="isConnectionLost"
|
||||
[class.active-btn]="isFullscreenActive"
|
||||
>
|
||||
<mat-icon *ngIf="isFullscreenActive" matTooltip="Exit Fullscreen">fullscreen_exit</mat-icon>
|
||||
<mat-icon *ngIf="!isFullscreenActive" matTooltip="Fullscreen">fullscreen</mat-icon>
|
||||
</button>
|
||||
|
||||
<!-- Custom centered buttons -->
|
||||
<ng-container *ngIf="centeredButtonsTemplate">
|
||||
<ng-container *ngTemplateOutlet="centeredButtonsTemplate"></ng-container>
|
||||
<!-- External additional buttons -->
|
||||
<ng-container *ngIf="toolbarAdditionalButtonsTemplate">
|
||||
<ng-container *ngTemplateOutlet="toolbarAdditionalButtonsTemplate"></ng-container>
|
||||
</ng-container>
|
||||
|
||||
<!-- Leave session button -->
|
||||
<button mat-icon-button (click)="leaveSession()" id="leave-btn">
|
||||
<button mat-icon-button *ngIf="showLeaveButton" (click)="leaveSession()" id="leave-btn">
|
||||
<mat-icon matTooltip="Leave the session">call_end</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -75,23 +70,22 @@
|
|||
<!-- Default participants button -->
|
||||
<button
|
||||
mat-icon-button
|
||||
id="participants-panel-btn"
|
||||
*ngIf="!isMinimal && showParticipantsPanelButton"
|
||||
matTooltip="Show articipants"
|
||||
(click)="toggleMenu('participants')"
|
||||
(click)="toggleParticipantsPanel()"
|
||||
[disabled]="isConnectionLost"
|
||||
[class.active-btn]="isParticipantsOpened"
|
||||
>
|
||||
<mat-icon>people</mat-icon>
|
||||
</button>
|
||||
|
||||
<div #customMenuButton>
|
||||
<ng-content select="[menu-button]"></ng-content>
|
||||
</div>
|
||||
|
||||
<!-- Default chat button -->
|
||||
<button
|
||||
mat-icon-button
|
||||
*ngIf="!customMenuButton.innerHTML"
|
||||
(click)="toggleMenu('chat')"
|
||||
id="chat-panel-btn"
|
||||
*ngIf="!isMinimal && showChatPanelButton"
|
||||
(click)="toggleChatPanel()"
|
||||
[disabled]="isConnectionLost"
|
||||
[class.active-btn]="isChatOpened"
|
||||
>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
AfterViewInit,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ContentChild,
|
||||
EventEmitter,
|
||||
|
@ -7,8 +8,7 @@ import {
|
|||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
TemplateRef,
|
||||
ViewChild
|
||||
TemplateRef
|
||||
} from '@angular/core';
|
||||
import { skip, Subscription } from 'rxjs';
|
||||
import { TokenService } from '../../services/token/token.service';
|
||||
|
@ -16,7 +16,7 @@ import { ChatService } from '../../services/chat/chat.service';
|
|||
import { SidenavMenuService } from '../../services/sidenav-menu/sidenav-menu.service';
|
||||
import { DocumentService } from '../../services/document/document.service';
|
||||
|
||||
import { WebrtcService } from '../../services/webrtc/webrtc.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';
|
||||
|
@ -25,140 +25,175 @@ import { ActionService } from '../../services/action/action.service';
|
|||
import { DeviceService } from '../../services/device/device.service';
|
||||
import { ChatMessage } from '../../models/chat.model';
|
||||
import { ParticipantService } from '../../services/participant/participant.service';
|
||||
import { LibraryConfigService } from '../../services/library-config/library-config.service';
|
||||
import { MenuType } from '../../models/menu.model';
|
||||
import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service';
|
||||
import { ToolbarAdditionalButtonsDirective } from '../../directives/template/openvidu-angular.directive';
|
||||
|
||||
@Component({
|
||||
selector: 'ov-toolbar',
|
||||
templateUrl: './toolbar.component.html',
|
||||
styleUrls: ['./toolbar.component.css']
|
||||
styleUrls: ['./toolbar.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ToolbarComponent implements OnInit, OnDestroy {
|
||||
@ContentChild('centeredButtons', { read: TemplateRef }) centeredButtonsTemplate: TemplateRef<any>;
|
||||
@ContentChild('toolbarAdditionalButtons', { read: TemplateRef }) toolbarAdditionalButtonsTemplate: TemplateRef<any>;
|
||||
|
||||
@Output() onMicClicked = new EventEmitter<any>();
|
||||
@Output() onCamClicked = new EventEmitter<any>();
|
||||
@Output() onScreenShareClicked = new EventEmitter<any>();
|
||||
@Output() onLeaveSessionClicked = new EventEmitter<any>();
|
||||
@Output() onChatClicked = new EventEmitter<any>();
|
||||
@ContentChild(ToolbarAdditionalButtonsDirective)
|
||||
set externalAdditionalButtons(externalAdditionalButtons: ToolbarAdditionalButtonsDirective) {
|
||||
// This directive will has value only when ADDITIONAL BUTTONS component tagget with '*ovToolbarAdditionalButtons' directive
|
||||
// is inside of the TOOLBAR component tagged with '*ovToolbar' directive
|
||||
if (externalAdditionalButtons) {
|
||||
this.toolbarAdditionalButtonsTemplate = externalAdditionalButtons.template;
|
||||
}
|
||||
}
|
||||
|
||||
@Output() onLeaveButtonClicked = new EventEmitter<any>();
|
||||
@Output() onCameraButtonClicked = new EventEmitter<any>();
|
||||
@Output() onMicrophoneButtonClicked = new EventEmitter<any>();
|
||||
@Output() onFullscreenButtonClicked = new EventEmitter<any>();
|
||||
@Output() onScreenshareButtonClicked = new EventEmitter<any>();
|
||||
@Output() onParticipantsPanelButtonClicked = new EventEmitter<any>();
|
||||
@Output() onChatPanelButtonClicked = new EventEmitter<any>();
|
||||
|
||||
session: Session;
|
||||
unreadMessages: number = 0;
|
||||
messageList: ChatMessage[] = [];
|
||||
isScreenShareEnabled: boolean;
|
||||
isWebcamVideoEnabled: boolean;
|
||||
isWebcamAudioEnabled: boolean;
|
||||
isScreenShareActive: boolean;
|
||||
isWebcamVideoActive: boolean;
|
||||
isWebcamAudioActive: boolean;
|
||||
isConnectionLost: boolean;
|
||||
hasVideoDevices: boolean;
|
||||
hasAudioDevices: boolean;
|
||||
isFullscreenEnabled: boolean = false;
|
||||
isFullscreenActive: boolean = false;
|
||||
isChatOpened: boolean = false;
|
||||
isParticipantsOpened: boolean = false;
|
||||
logoUrl = 'assets/images/openvidu_globe.png';
|
||||
|
||||
protected log: ILogger;
|
||||
protected menuTogglingSubscription: Subscription;
|
||||
protected chatMessagesSubscription: Subscription;
|
||||
protected screenShareStateSubscription: Subscription;
|
||||
protected webcamVideoStateSubscription: Subscription;
|
||||
protected webcamAudioStateSubscription: Subscription;
|
||||
isMinimal: boolean = false;
|
||||
showScreenshareButton = true;
|
||||
showFullscreenButton: boolean = true;
|
||||
showLeaveButton: boolean = true;
|
||||
showParticipantsPanelButton: boolean = true;
|
||||
showChatPanelButton: boolean = true;
|
||||
showLogo: boolean = true;
|
||||
showSessionName: boolean = true;
|
||||
|
||||
private log: ILogger;
|
||||
private minimalSub: Subscription;
|
||||
private menuTogglingSubscription: Subscription;
|
||||
private chatMessagesSubscription: Subscription;
|
||||
private localParticipantSubscription: Subscription;
|
||||
private screenshareButtonSub: Subscription;
|
||||
private fullscreenButtonSub: Subscription;
|
||||
private leaveButtonSub: Subscription;
|
||||
private participantsPanelButtonSub: Subscription;
|
||||
private chatPanelButtonSub: Subscription;
|
||||
private displayLogoSub: Subscription;
|
||||
private displaySessionNameSub: Subscription;
|
||||
private currentWindowHeight = window.innerHeight;
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
sizeChange(event) {
|
||||
if (this.currentWindowHeight >= window.innerHeight) {
|
||||
// The user has exit the fullscreen mode
|
||||
this.isFullscreenActive = false;
|
||||
this.currentWindowHeight = window.innerHeight;
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('document:keydown', ['$event'])
|
||||
keyDown(event: KeyboardEvent) {
|
||||
if (event.key === 'F11') {
|
||||
event.preventDefault();
|
||||
this.toggleFullscreen();
|
||||
this.currentWindowHeight = window.innerHeight;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected libraryConfigSrv: LibraryConfigService,
|
||||
protected documentService: DocumentService,
|
||||
protected chatService: ChatService,
|
||||
protected menuService: SidenavMenuService,
|
||||
protected tokenService: TokenService,
|
||||
protected participantService: ParticipantService,
|
||||
protected webrtcService: WebrtcService,
|
||||
protected openviduService: OpenViduService,
|
||||
protected oVDevicesService: DeviceService,
|
||||
protected actionService: ActionService,
|
||||
protected loggerSrv: LoggerService
|
||||
protected loggerSrv: LoggerService,
|
||||
private cd: ChangeDetectorRef,
|
||||
private libService: OpenViduAngularConfigService
|
||||
) {
|
||||
this.log = this.loggerSrv.get('ToolbarComponent');
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.menuTogglingSubscription) {
|
||||
this.menuTogglingSubscription.unsubscribe();
|
||||
}
|
||||
if (this.chatMessagesSubscription) {
|
||||
this.chatMessagesSubscription.unsubscribe();
|
||||
}
|
||||
if (this.screenShareStateSubscription) {
|
||||
this.screenShareStateSubscription.unsubscribe();
|
||||
}
|
||||
if (this.webcamVideoStateSubscription) {
|
||||
this.webcamVideoStateSubscription.unsubscribe();
|
||||
}
|
||||
if (this.webcamAudioStateSubscription) {
|
||||
this.webcamAudioStateSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
sizeChange(event) {
|
||||
const maxHeight = window.screen.height;
|
||||
const maxWidth = window.screen.width;
|
||||
const curHeight = window.innerHeight;
|
||||
const curWidth = window.innerWidth;
|
||||
this.isFullscreenEnabled = maxWidth == curWidth && maxHeight == curHeight;
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.subscribeToToolbarDirectives();
|
||||
|
||||
await this.oVDevicesService.initializeDevices();
|
||||
this.hasVideoDevices = this.oVDevicesService.hasVideoDeviceAvailable();
|
||||
this.hasAudioDevices = this.oVDevicesService.hasAudioDeviceAvailable();
|
||||
this.session = this.webrtcService.getWebcamSession();
|
||||
this.session = this.openviduService.getWebcamSession();
|
||||
|
||||
this.subscribeToUserMediaProperties();
|
||||
this.subscribeToReconnection();
|
||||
|
||||
this.subscribeToMenuToggling();
|
||||
this.subscribeToChatMessages();
|
||||
}
|
||||
|
||||
toggleMicrophone() {
|
||||
this.onMicClicked.emit();
|
||||
ngOnDestroy(): void {
|
||||
if (this.menuTogglingSubscription) this.menuTogglingSubscription.unsubscribe();
|
||||
if (this.chatMessagesSubscription) this.chatMessagesSubscription.unsubscribe();
|
||||
if (this.localParticipantSubscription) this.localParticipantSubscription.unsubscribe();
|
||||
if (this.screenshareButtonSub) this.screenshareButtonSub.unsubscribe();
|
||||
if (this.fullscreenButtonSub) this.fullscreenButtonSub.unsubscribe();
|
||||
if (this.leaveButtonSub) this.leaveButtonSub.unsubscribe();
|
||||
if (this.participantsPanelButtonSub) this.participantsPanelButtonSub.unsubscribe();
|
||||
if (this.chatPanelButtonSub) this.chatPanelButtonSub.unsubscribe();
|
||||
if (this.displayLogoSub) this.displayLogoSub.unsubscribe();
|
||||
if (this.displaySessionNameSub) this.displaySessionNameSub.unsubscribe();
|
||||
}
|
||||
|
||||
if (this.participantService.isMyCameraEnabled()) {
|
||||
this.webrtcService.publishAudio(
|
||||
toggleMicrophone() {
|
||||
this.onMicrophoneButtonClicked.emit();
|
||||
|
||||
if (this.participantService.isMyCameraActive()) {
|
||||
this.openviduService.publishAudio(
|
||||
this.participantService.getMyCameraPublisher(),
|
||||
!this.participantService.hasCameraAudioActive()
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.webrtcService.publishAudio(this.participantService.getMyScreenPublisher(), !this.participantService.hasScreenAudioActive());
|
||||
this.openviduService.publishAudio(this.participantService.getMyScreenPublisher(), !this.participantService.hasScreenAudioActive());
|
||||
}
|
||||
|
||||
async toggleCamera() {
|
||||
this.onCamClicked.emit();
|
||||
this.onCameraButtonClicked.emit();
|
||||
|
||||
try {
|
||||
const publishVideo = !this.participantService.hasCameraVideoActive();
|
||||
const publishAudio = this.participantService.hasCameraAudioActive();
|
||||
// Disabling webcam
|
||||
if (this.participantService.areBothEnabled()) {
|
||||
this.webrtcService.publishVideo(this.participantService.getMyCameraPublisher(), publishVideo);
|
||||
if (this.participantService.haveICameraAndScreenActive()) {
|
||||
this.openviduService.publishVideo(this.participantService.getMyCameraPublisher(), publishVideo);
|
||||
this.participantService.disableWebcamUser();
|
||||
this.webrtcService.unpublish(this.participantService.getMyCameraPublisher());
|
||||
this.webrtcService.publishAudio(this.participantService.getMyScreenPublisher(), publishAudio);
|
||||
this.openviduService.unpublish(this.participantService.getMyCameraPublisher());
|
||||
this.openviduService.publishAudio(this.participantService.getMyScreenPublisher(), publishAudio);
|
||||
return;
|
||||
}
|
||||
// Enabling webcam
|
||||
if (this.participantService.isOnlyMyScreenEnabled()) {
|
||||
if (this.participantService.isOnlyMyScreenActive()) {
|
||||
const hasAudio = this.participantService.hasScreenAudioActive();
|
||||
|
||||
if (!this.webrtcService.isWebcamSessionConnected()) {
|
||||
await this.webrtcService.connectSession(this.webrtcService.getWebcamSession(), this.tokenService.getWebcamToken());
|
||||
if (!this.openviduService.isWebcamSessionConnected()) {
|
||||
await this.openviduService.connectSession(this.openviduService.getWebcamSession(), this.tokenService.getWebcamToken());
|
||||
}
|
||||
await this.webrtcService.publish(this.participantService.getMyCameraPublisher());
|
||||
this.webrtcService.publishAudio(this.participantService.getMyScreenPublisher(), false);
|
||||
this.webrtcService.publishAudio(this.participantService.getMyCameraPublisher(), hasAudio);
|
||||
await this.openviduService.publish(this.participantService.getMyCameraPublisher());
|
||||
this.openviduService.publishAudio(this.participantService.getMyScreenPublisher(), false);
|
||||
this.openviduService.publishAudio(this.participantService.getMyCameraPublisher(), hasAudio);
|
||||
this.participantService.enableWebcamUser();
|
||||
}
|
||||
// Muting/unmuting webcam
|
||||
this.webrtcService.publishVideo(this.participantService.getMyCameraPublisher(), publishVideo);
|
||||
this.openviduService.publishVideo(this.participantService.getMyCameraPublisher(), 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);
|
||||
|
@ -166,19 +201,19 @@ export class ToolbarComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
async toggleScreenShare() {
|
||||
this.onScreenShareClicked.emit();
|
||||
this.onScreenshareButtonClicked.emit();
|
||||
|
||||
try {
|
||||
// Disabling screenShare
|
||||
if (this.participantService.areBothEnabled()) {
|
||||
if (this.participantService.haveICameraAndScreenActive()) {
|
||||
this.participantService.disableScreenUser();
|
||||
this.webrtcService.unpublish(this.participantService.getMyScreenPublisher());
|
||||
this.openviduService.unpublish(this.participantService.getMyScreenPublisher());
|
||||
return;
|
||||
}
|
||||
|
||||
// Enabling screenShare
|
||||
if (this.participantService.isOnlyMyCameraEnabled()) {
|
||||
const willThereBeWebcam = this.participantService.isMyCameraEnabled() && this.participantService.hasCameraVideoActive();
|
||||
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,
|
||||
|
@ -187,7 +222,7 @@ export class ToolbarComponent implements OnInit, OnDestroy {
|
|||
publishAudio: hasAudio,
|
||||
mirror: false
|
||||
};
|
||||
const screenPublisher = this.webrtcService.initPublisher(undefined, properties);
|
||||
const screenPublisher = await this.openviduService.initPublisher(undefined, properties);
|
||||
|
||||
screenPublisher.once('accessAllowed', async (event) => {
|
||||
// Listen to event fired when native stop button is clicked
|
||||
|
@ -199,17 +234,20 @@ export class ToolbarComponent implements OnInit, OnDestroy {
|
|||
this.toggleScreenShare();
|
||||
});
|
||||
this.log.d('ACCESS ALOWED screenPublisher');
|
||||
this.participantService.enableScreenUser(screenPublisher);
|
||||
this.participantService.activeMyScreenShare(screenPublisher);
|
||||
|
||||
if (!this.webrtcService.isScreenSessionConnected()) {
|
||||
await this.webrtcService.connectSession(this.webrtcService.getScreenSession(), this.tokenService.getScreenToken());
|
||||
if (!this.openviduService.isScreenSessionConnected()) {
|
||||
await this.openviduService.connectSession(
|
||||
this.openviduService.getScreenSession(),
|
||||
this.tokenService.getScreenToken()
|
||||
);
|
||||
}
|
||||
await this.webrtcService.publish(this.participantService.getMyScreenPublisher());
|
||||
// this.webrtcService.sendNicknameSignal();
|
||||
await this.openviduService.publish(this.participantService.getMyScreenPublisher());
|
||||
// this.openviduService.sendNicknameSignal();
|
||||
if (!this.participantService.hasCameraVideoActive()) {
|
||||
// Disabling webcam
|
||||
this.participantService.disableWebcamUser();
|
||||
this.webrtcService.unpublish(this.participantService.getMyCameraPublisher());
|
||||
this.openviduService.unpublish(this.participantService.getMyCameraPublisher());
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -224,45 +262,41 @@ export class ToolbarComponent implements OnInit, OnDestroy {
|
|||
|
||||
// Disabling screnShare and enabling webcam
|
||||
const hasAudio = this.participantService.hasScreenAudioActive();
|
||||
if (!this.webrtcService.isWebcamSessionConnected()) {
|
||||
await this.webrtcService.connectSession(this.webrtcService.getWebcamSession(), this.tokenService.getWebcamToken());
|
||||
if (!this.openviduService.isWebcamSessionConnected()) {
|
||||
await this.openviduService.connectSession(this.openviduService.getWebcamSession(), this.tokenService.getWebcamToken());
|
||||
}
|
||||
await this.webrtcService.publish(this.participantService.getMyCameraPublisher());
|
||||
this.webrtcService.publishAudio(this.participantService.getMyScreenPublisher(), false);
|
||||
this.webrtcService.publishAudio(this.participantService.getMyCameraPublisher(), hasAudio);
|
||||
await this.openviduService.publish(this.participantService.getMyCameraPublisher());
|
||||
this.openviduService.publishAudio(this.participantService.getMyScreenPublisher(), false);
|
||||
this.openviduService.publishAudio(this.participantService.getMyCameraPublisher(), hasAudio);
|
||||
this.participantService.enableWebcamUser();
|
||||
this.participantService.disableScreenUser();
|
||||
this.webrtcService.unpublish(this.participantService.getMyScreenPublisher());
|
||||
this.openviduService.unpublish(this.participantService.getMyScreenPublisher());
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
async replaceScreenTrack() {
|
||||
const properties: PublisherProperties = {
|
||||
videoSource: ScreenType.SCREEN,
|
||||
publishVideo: true,
|
||||
publishAudio: !this.participantService.isMyCameraEnabled(),
|
||||
mirror: false
|
||||
};
|
||||
await this.webrtcService.replaceTrack(this.participantService.getMyScreenPublisher(), properties);
|
||||
}
|
||||
|
||||
leaveSession() {
|
||||
this.log.d('Leaving session...');
|
||||
this.webrtcService.disconnect();
|
||||
this.onLeaveSessionClicked.emit();
|
||||
this.openviduService.disconnect();
|
||||
this.onLeaveButtonClicked.emit();
|
||||
}
|
||||
|
||||
toggleMenu(type: string) {
|
||||
this.menuService.toggleMenu(<MenuType>type);
|
||||
this.onChatClicked.emit();
|
||||
toggleParticipantsPanel() {
|
||||
this.onParticipantsPanelButtonClicked.emit();
|
||||
this.menuService.toggleMenu(MenuType.PARTICIPANTS);
|
||||
}
|
||||
|
||||
toggleChatPanel() {
|
||||
this.onChatPanelButtonClicked.emit();
|
||||
this.menuService.toggleMenu(MenuType.CHAT);
|
||||
}
|
||||
|
||||
toggleFullscreen() {
|
||||
this.documentService.toggleFullscreen('room-container');
|
||||
this.isFullscreenEnabled = !this.isFullscreenEnabled;
|
||||
this.isFullscreenActive = !this.isFullscreenActive;
|
||||
this.documentService.toggleFullscreen('session-container');
|
||||
this.onFullscreenButtonClicked.emit();
|
||||
}
|
||||
|
||||
protected subscribeToReconnection() {
|
||||
|
@ -292,18 +326,52 @@ export class ToolbarComponent implements OnInit, OnDestroy {
|
|||
this.unreadMessages++;
|
||||
}
|
||||
this.messageList = messages;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
}
|
||||
protected subscribeToUserMediaProperties() {
|
||||
this.screenShareStateSubscription = this.participantService.screenShareState.subscribe((enabled) => {
|
||||
this.isScreenShareEnabled = enabled;
|
||||
this.localParticipantSubscription = this.participantService.localParticipantObs.subscribe((p) => {
|
||||
if(p) {
|
||||
this.isWebcamVideoActive = p.isCameraVideoActive();
|
||||
this.isWebcamAudioActive = p.isCameraAudioActive();
|
||||
this.isScreenShareActive = p.isScreenActive();
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.webcamVideoStateSubscription = this.participantService.webcamVideoActive.subscribe((enabled) => {
|
||||
this.isWebcamVideoEnabled = enabled;
|
||||
private subscribeToToolbarDirectives() {
|
||||
this.minimalSub = this.libService.minimalObs.subscribe((value: boolean) => {
|
||||
this.isMinimal = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
this.webcamAudioStateSubscription = this.participantService.webcamAudioActive.subscribe((enabled) => {
|
||||
this.isWebcamAudioEnabled = enabled;
|
||||
this.screenshareButtonSub = this.libService.screenshareButtonObs.subscribe((value: boolean) => {
|
||||
this.showScreenshareButton = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
this.fullscreenButtonSub = this.libService.fullscreenButtonObs.subscribe((value: boolean) => {
|
||||
this.showFullscreenButton = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
this.leaveButtonSub = this.libService.leaveButtonObs.subscribe((value: boolean) => {
|
||||
this.showLeaveButton = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
this.chatPanelButtonSub = this.libService.chatPanelButtonObs.subscribe((value: boolean) => {
|
||||
this.showChatPanelButton = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
this.participantsPanelButtonSub = this.libService.participantsPanelButtonObs.subscribe((value: boolean) => {
|
||||
this.showParticipantsPanelButton = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
this.displayLogoSub = this.libService.displayLogoObs.subscribe((value: boolean) => {
|
||||
this.showLogo = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
this.displaySessionNameSub = this.libService.displaySessionNameObs.subscribe((value: boolean) => {
|
||||
this.showSessionName = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.card-header {
|
||||
background-color: var(--ov-primary-color);
|
||||
border-radius: 10px 10px 0px 0px;
|
||||
border-radius: var(--ov-panel-radius) var(--ov-panel-radius) 0px 0px;
|
||||
text-align: center;
|
||||
height: 50px;
|
||||
}
|
||||
|
@ -47,6 +47,12 @@
|
|||
bottom: 0px;
|
||||
}
|
||||
|
||||
#audio-wave-container {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 30px;
|
||||
}
|
||||
|
||||
.cameraMessageContainer {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
@ -61,6 +67,7 @@
|
|||
}
|
||||
.deviceButton {
|
||||
box-shadow: none;
|
||||
border-radius: var(--ov-buttons-radius);
|
||||
background-color: var(--ov-secondary-color) !important;
|
||||
color: var(--ov-light-color) !important;
|
||||
}
|
||||
|
@ -959,7 +966,7 @@
|
|||
background-clip: padding-box;
|
||||
border: 1px solid #999999;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 10px;
|
||||
border-radius: var(--ov-panel-radius);
|
||||
-webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
|
||||
box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
|
||||
outline: 0;
|
||||
|
@ -1074,7 +1081,7 @@
|
|||
padding: 35px;
|
||||
}
|
||||
.videoContainer{
|
||||
width: 90%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
|
|
|
@ -19,21 +19,24 @@
|
|||
<div class="row align-items-center">
|
||||
<div class="col-sm-6 col-md-6 col-lg-6 leftSection">
|
||||
<div class="spinner-container" *ngIf="isLoading">
|
||||
<mat-spinner></mat-spinner>
|
||||
<mat-spinner [diameter]="50"></mat-spinner>
|
||||
</div>
|
||||
<div class="videoContainer" *ngIf="!isLoading">
|
||||
<div *ngFor="let connection of localParticipant | connections">
|
||||
<div *ngFor="let stream of localParticipant | streams">
|
||||
<!-- Only webcam video will be shown if webcamera is available -->
|
||||
<ov-video
|
||||
*ngIf="(connection.type === 'CAMERA' && hasVideoDevices) || connection.type === 'SCREEN'"
|
||||
[streamManager]="connection.streamManager"
|
||||
[ngClass]="{ ovVideoSmall: localParticipant.connections.size > 1 && connection.type === 'CAMERA' }"
|
||||
*ngIf="(stream.type === 'CAMERA' && hasVideoDevices) || stream.type === 'SCREEN'"
|
||||
[streamManager]="stream.streamManager"
|
||||
[ngClass]="{ ovVideoSmall: localParticipant.streams.size > 1 && stream.type === 'CAMERA' }"
|
||||
></ov-video>
|
||||
<div class="cameraMessageContainer" *ngIf="connection.type === 'CAMERA' && !hasVideoDevices">
|
||||
<div class="cameraMessageContainer" *ngIf="stream.type === 'CAMERA' && !hasVideoDevices">
|
||||
<span *ngIf="!hasVideoDevices && !hasAudioDevices">Oops! Camera and microphone are not available</span>
|
||||
<span *ngIf="!hasVideoDevices && hasAudioDevices">Oops! Camera is not available</span>
|
||||
<span *ngIf="hasVideoDevices && !hasAudioDevices">Oops! Microphone is not available</span>
|
||||
</div>
|
||||
<div id="audio-wave-container" *ngIf="stream.type === 'CAMERA'">
|
||||
<ov-audio-wave [streamManager]="stream.streamManager"></ov-audio-wave>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -48,19 +51,16 @@
|
|||
(click)="toggleCam()"
|
||||
class="deviceButton"
|
||||
id="configCardCameraButton"
|
||||
[class.warn-btn]="!isVideoActive"
|
||||
[class.warn-btn]="isVideoMuted"
|
||||
>
|
||||
<mat-icon *ngIf="isVideoActive" matTooltip="Camera Enabled">videocam</mat-icon>
|
||||
<mat-icon *ngIf="!isVideoActive" matTooltip="Camera Disabled">videocam_off</mat-icon>
|
||||
<mat-icon *ngIf="!isVideoMuted" matTooltip="Camera Enabled">videocam</mat-icon>
|
||||
<mat-icon *ngIf="isVideoMuted" matTooltip="Camera Disabled">videocam_off</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="two" fxFlex="80" fxLayoutAlign="center center">
|
||||
<mat-form-field class="alternate-theme">
|
||||
<mat-select
|
||||
placeholder="Camera Options"
|
||||
[ngModel]="isVideoActive && !!cameraSelected ? cameraSelected.device : 'None'"
|
||||
(selectionChange)="onCameraSelected($event)"
|
||||
>
|
||||
<mat-label>Camera Options</mat-label>
|
||||
<mat-select [disabled]="isVideoMuted" (selectionChange)="onCameraSelected($event)">
|
||||
<mat-option *ngFor="let camera of cameras" [value]="camera.device">
|
||||
{{ camera.label }}
|
||||
</mat-option>
|
||||
|
@ -78,19 +78,16 @@
|
|||
(click)="toggleMic()"
|
||||
class="deviceButton"
|
||||
id="configCardMicrophoneButton"
|
||||
[class.warn-btn]="!isAudioActive"
|
||||
[class.warn-btn]="isAudioMuted"
|
||||
>
|
||||
<mat-icon *ngIf="isAudioActive" matTooltip="Microphone Enabled">mic</mat-icon>
|
||||
<mat-icon *ngIf="!isAudioActive" matTooltip="Microphone Disabled">mic_off</mat-icon>
|
||||
<mat-icon *ngIf="!isAudioMuted" matTooltip="Microphone Enabled">mic</mat-icon>
|
||||
<mat-icon *ngIf="isAudioMuted" matTooltip="Microphone Disabled">mic_off</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="two" fxFlex="80" fxLayoutAlign="center center">
|
||||
<mat-form-field class="alternate-theme">
|
||||
<mat-select
|
||||
placeholder="Microphone Options"
|
||||
[ngModel]="isAudioActive && microphoneSelected ? microphoneSelected.device : 'None'"
|
||||
(selectionChange)="onMicrophoneSelected($event)"
|
||||
>
|
||||
<mat-label>Microphone Options</mat-label>
|
||||
<mat-select [disabled]="isAudioMuted" (selectionChange)="onMicrophoneSelected($event)">
|
||||
<mat-option *ngFor="let microphone of microphones" [value]="microphone.device">
|
||||
{{ microphone.label }}
|
||||
</mat-option>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, ElementRef, EventEmitter, HostListener, Input, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, HostListener, Input, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { FormControl, Validators } from '@angular/forms';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
|
@ -7,15 +7,14 @@ import { Publisher, PublisherProperties } from 'openvidu-browser';
|
|||
|
||||
import { ILogger } from '../../models/logger.model';
|
||||
import { CustomDevice } from '../../models/device.model';
|
||||
import { Storage } from '../../models/storage.model';
|
||||
import { ScreenType } from '../../models/video-type.model';
|
||||
import { ScreenType, VideoType } from '../../models/video-type.model';
|
||||
|
||||
import { NicknameMatcher } from '../../matchers/nickname.matcher';
|
||||
|
||||
import { DeviceService } from '../../services/device/device.service';
|
||||
import { LoggerService } from '../../services/logger/logger.service';
|
||||
import { StorageService } from '../../services/storage/storage.service';
|
||||
import { WebrtcService } from '../../services/webrtc/webrtc.service';
|
||||
import { OpenViduService } from '../../services/openvidu/openvidu.service';
|
||||
import { ActionService } from '../../services/action/action.service';
|
||||
import { ParticipantService } from '../../services/participant/participant.service';
|
||||
import { ParticipantAbstractModel } from '../../models/participant.model';
|
||||
|
@ -23,7 +22,8 @@ import { ParticipantAbstractModel } from '../../models/participant.model';
|
|||
@Component({
|
||||
selector: 'ov-user-settings',
|
||||
templateUrl: './user-settings.component.html',
|
||||
styleUrls: ['./user-settings.component.css']
|
||||
styleUrls: ['./user-settings.component.css'],
|
||||
// changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class UserSettingsComponent implements OnInit, OnDestroy {
|
||||
@ViewChild('bodyCard') bodyCard: ElementRef;
|
||||
|
@ -36,26 +36,26 @@ export class UserSettingsComponent implements OnInit, OnDestroy {
|
|||
microphones: CustomDevice[];
|
||||
cameraSelected: CustomDevice;
|
||||
microphoneSelected: CustomDevice;
|
||||
isVideoActive = true;
|
||||
isAudioActive = true;
|
||||
isVideoMuted: boolean;
|
||||
isAudioMuted: boolean;
|
||||
screenShareEnabled: boolean;
|
||||
localParticipant: ParticipantAbstractModel;
|
||||
columns: number;
|
||||
|
||||
nicknameFormControl = new FormControl('', [Validators.maxLength(25), Validators.required]);
|
||||
nicknameFormControl = new FormControl('', [Validators.maxLength(20), Validators.required]);
|
||||
matcher = new NicknameMatcher();
|
||||
hasVideoDevices: boolean;
|
||||
hasAudioDevices: boolean;
|
||||
isLoading = true;
|
||||
private log: ILogger;
|
||||
private oVUsersSubscription: Subscription;
|
||||
private localParticipantSubscription: Subscription;
|
||||
private screenShareStateSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
private actionService: ActionService,
|
||||
private deviceSrv: DeviceService,
|
||||
private loggerSrv: LoggerService,
|
||||
private openViduWebRTCService: WebrtcService,
|
||||
private openviduService: OpenViduService,
|
||||
private participantService: ParticipantService,
|
||||
private storageSrv: StorageService
|
||||
) {
|
||||
|
@ -68,10 +68,11 @@ export class UserSettingsComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.subscribeToLocalParticipantEvents();
|
||||
this.openViduWebRTCService.initialize();
|
||||
await this.deviceSrv.initializeDevices();
|
||||
const nickname = this.storageSrv.get(Storage.USER_NICKNAME) || this.generateRandomNickname();
|
||||
|
||||
this.subscribeToLocalParticipantEvents();
|
||||
this.openviduService.initialize();
|
||||
const nickname = this.storageSrv.getNickname() || this.generateRandomNickname();
|
||||
this.nicknameFormControl.setValue(nickname);
|
||||
this.columns = window.innerWidth > 900 ? 2 : 1;
|
||||
this.setDevicesInfo();
|
||||
|
@ -79,12 +80,11 @@ export class UserSettingsComponent implements OnInit, OnDestroy {
|
|||
await this.initwebcamPublisher();
|
||||
}
|
||||
this.isLoading = false;
|
||||
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.oVUsersSubscription) {
|
||||
this.oVUsersSubscription.unsubscribe();
|
||||
if (this.localParticipantSubscription) {
|
||||
this.localParticipantSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
if (this.screenShareStateSubscription) {
|
||||
|
@ -95,73 +95,78 @@ export class UserSettingsComponent implements OnInit, OnDestroy {
|
|||
|
||||
async onCameraSelected(event: any) {
|
||||
const videoSource = event?.value;
|
||||
if (!!videoSource) {
|
||||
// Is New deviceId different from the old one?
|
||||
if (this.deviceSrv.needUpdateVideoTrack(videoSource)) {
|
||||
const mirror = this.deviceSrv.cameraNeedsMirror(videoSource);
|
||||
await this.openViduWebRTCService.republishTrack(videoSource, null, mirror);
|
||||
this.deviceSrv.setCameraSelected(videoSource);
|
||||
this.cameraSelected = this.deviceSrv.getCameraSelected();
|
||||
}
|
||||
// Publish Webcam video
|
||||
this.openViduWebRTCService.publishVideo(this.participantService.getMyCameraPublisher(), true);
|
||||
this.isVideoActive = true;
|
||||
// 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 };
|
||||
await this.openviduService.republishTrack(pp);
|
||||
|
||||
} else {
|
||||
// Videosource is 'null' because of the user has selected 'None' or muted the camera
|
||||
// Unpublish webcam
|
||||
this.openViduWebRTCService.publishVideo(this.participantService.getMyCameraPublisher(), false);
|
||||
//TODO: save 'None' device in storage
|
||||
// this.deviceSrv.setCameraSelected(videoSource);
|
||||
// this.cameraSelected = this.deviceSrv.getCameraSelected();
|
||||
this.isVideoActive = false;
|
||||
|
||||
this.cameraSelected = videoSource;
|
||||
this.deviceSrv.setCameraSelected(this.cameraSelected);
|
||||
}
|
||||
if (this.isVideoMuted) {
|
||||
// Publish Webcam video
|
||||
this.openviduService.publishVideo(this.participantService.getMyCameraPublisher(), true);
|
||||
this.isVideoMuted = false;
|
||||
}
|
||||
}
|
||||
|
||||
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 };
|
||||
// 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);
|
||||
|
||||
if (!!audioSource) {
|
||||
// Is New deviceId different than older?
|
||||
if (this.deviceSrv.needUpdateAudioTrack(audioSource)) {
|
||||
const mirror = this.deviceSrv.cameraNeedsMirror(this.cameraSelected.device);
|
||||
await this.openViduWebRTCService.republishTrack(null, audioSource, mirror);
|
||||
this.deviceSrv.setMicSelected(audioSource);
|
||||
this.microphoneSelected = this.deviceSrv.getMicrophoneSelected();
|
||||
}
|
||||
// Publish microphone
|
||||
this.publishAudio(true);
|
||||
this.isAudioActive = true;
|
||||
return;
|
||||
this.microphoneSelected = audioSource;
|
||||
this.deviceSrv.setMicSelected(this.microphoneSelected);
|
||||
}
|
||||
if (this.isAudioMuted) {
|
||||
// Enable microphone
|
||||
this.openviduService.publishAudio(this.participantService.getMyCameraPublisher(), true);
|
||||
this.isAudioMuted = true;
|
||||
}
|
||||
// Unpublish microhpone
|
||||
this.publishAudio(false);
|
||||
this.isAudioActive = false;
|
||||
}
|
||||
|
||||
toggleCam() {
|
||||
this.isVideoActive = !this.isVideoActive;
|
||||
this.openViduWebRTCService.publishVideo(this.participantService.getMyCameraPublisher(), this.isVideoActive);
|
||||
|
||||
if (this.participantService.areBothEnabled()) {
|
||||
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.disableWebcamUser();
|
||||
this.openViduWebRTCService.publishAudio(this.participantService.getMyScreenPublisher(), this.isAudioActive);
|
||||
} else if (this.participantService.isOnlyMyScreenEnabled()) {
|
||||
this.openviduService.publishAudio(this.participantService.getMyScreenPublisher(), publish);
|
||||
} else if (this.participantService.isOnlyMyScreenActive()) {
|
||||
// Cam will be published, enable webcam
|
||||
this.participantService.enableWebcamUser();
|
||||
}
|
||||
|
||||
this.isVideoMuted = !this.isVideoMuted;
|
||||
this.storageSrv.setVideoMuted(this.isVideoMuted);
|
||||
}
|
||||
|
||||
toggleScreenShare() {
|
||||
async toggleScreenShare() {
|
||||
// Disabling screenShare
|
||||
if (this.participantService.areBothEnabled()) {
|
||||
if (this.participantService.haveICameraAndScreenActive()) {
|
||||
this.participantService.disableScreenUser();
|
||||
return;
|
||||
}
|
||||
|
||||
// Enabling screenShare
|
||||
if (this.participantService.isOnlyMyCameraEnabled()) {
|
||||
const willThereBeWebcam = this.participantService.isMyCameraEnabled() && this.participantService.hasCameraVideoActive();
|
||||
const hasAudio = willThereBeWebcam ? false : this.hasAudioDevices && this.isAudioActive;
|
||||
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,
|
||||
|
@ -169,7 +174,7 @@ export class UserSettingsComponent implements OnInit, OnDestroy {
|
|||
publishAudio: hasAudio,
|
||||
mirror: false
|
||||
};
|
||||
const screenPublisher = this.openViduWebRTCService.initPublisher(undefined, properties);
|
||||
const screenPublisher = await this.openviduService.initPublisher(undefined, properties);
|
||||
|
||||
screenPublisher.on('accessAllowed', (event) => {
|
||||
screenPublisher.stream
|
||||
|
@ -179,7 +184,7 @@ export class UserSettingsComponent implements OnInit, OnDestroy {
|
|||
this.log.d('Clicked native stop button. Stopping screen sharing');
|
||||
this.toggleScreenShare();
|
||||
});
|
||||
this.participantService.enableScreenUser(screenPublisher);
|
||||
this.participantService.activeMyScreenShare(screenPublisher);
|
||||
if (!this.participantService.hasCameraVideoActive()) {
|
||||
this.participantService.disableWebcamUser();
|
||||
}
|
||||
|
@ -199,8 +204,10 @@ export class UserSettingsComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
toggleMic() {
|
||||
this.isAudioActive = !this.isAudioActive;
|
||||
this.publishAudio(this.isAudioActive);
|
||||
const publish = this.isAudioMuted;
|
||||
this.openviduService.publishAudio(this.participantService.getMyCameraPublisher(), publish);
|
||||
this.isAudioMuted = !this.isAudioMuted;
|
||||
this.storageSrv.setAudioMuted(this.isAudioMuted);
|
||||
}
|
||||
|
||||
eventKeyPress(event) {
|
||||
|
@ -216,8 +223,8 @@ export class UserSettingsComponent implements OnInit, OnDestroy {
|
|||
joinSession() {
|
||||
if (this.nicknameFormControl.valid) {
|
||||
const nickname = this.nicknameFormControl.value;
|
||||
this.participantService.setNickname(this.participantService.getMyCameraConnectionId(), nickname);
|
||||
this.storageSrv.set(Storage.USER_NICKNAME, nickname);
|
||||
this.participantService.setMyNickname(nickname);
|
||||
this.storageSrv.setNickname(nickname);
|
||||
return this.onJoinClicked.emit();
|
||||
}
|
||||
this.scrollToBottom();
|
||||
|
@ -235,9 +242,8 @@ export class UserSettingsComponent implements OnInit, OnDestroy {
|
|||
this.cameraSelected = this.deviceSrv.getCameraSelected();
|
||||
this.microphoneSelected = this.deviceSrv.getMicrophoneSelected();
|
||||
|
||||
this.isVideoActive = this.hasVideoDevices && this.cameraSelected.label !== 'None';
|
||||
this.isAudioActive = this.hasAudioDevices && this.microphoneSelected.label !== 'None';
|
||||
|
||||
this.isVideoMuted = this.deviceSrv.isVideoMuted();
|
||||
this.isAudioMuted = this.deviceSrv.isAudioMuted();
|
||||
}
|
||||
|
||||
private scrollToBottom(): void {
|
||||
|
@ -246,25 +252,16 @@ export class UserSettingsComponent implements OnInit, OnDestroy {
|
|||
} catch (err) {}
|
||||
}
|
||||
|
||||
private publishAudio(audio: boolean) {
|
||||
this.participantService.isMyCameraEnabled()
|
||||
? this.openViduWebRTCService.publishAudio(this.participantService.getMyCameraPublisher(), audio)
|
||||
: this.openViduWebRTCService.publishAudio(this.participantService.getMyScreenPublisher(), audio);
|
||||
}
|
||||
|
||||
private subscribeToLocalParticipantEvents() {
|
||||
this.oVUsersSubscription = this.participantService.localParticipantObs.subscribe((p) => {
|
||||
this.localParticipantSubscription = this.participantService.localParticipantObs.subscribe((p) => {
|
||||
this.localParticipant = p;
|
||||
});
|
||||
this.screenShareStateSubscription = this.participantService.screenShareState.subscribe((enabled) => {
|
||||
this.screenShareEnabled = enabled;
|
||||
this.screenShareEnabled = p.isScreenActive();
|
||||
});
|
||||
}
|
||||
|
||||
private async initwebcamPublisher() {
|
||||
const publisher = await this.openViduWebRTCService.initDefaultPublisher(undefined);
|
||||
const publisher = await this.openviduService.initDefaultPublisher(undefined);
|
||||
if (publisher) {
|
||||
|
||||
// this.handlePublisherSuccess(publisher);
|
||||
this.handlePublisherError(publisher);
|
||||
}
|
||||
|
|
|
@ -8,17 +8,6 @@ video {
|
|||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
border-radius: 5px;
|
||||
border-radius: var(--ov-video-radius);
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.poster_img {
|
||||
position: absolute;
|
||||
z-index: 888;
|
||||
max-width: 60%;
|
||||
max-height: 60%;
|
||||
bottom: 25%;
|
||||
margin: auto;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
import { AfterViewInit, Component, ElementRef, Input, EventEmitter, Output, ViewChild } from '@angular/core';
|
||||
import { AfterViewInit, Component, ElementRef, Input, ViewChild } from '@angular/core';
|
||||
import { StreamManager } from 'openvidu-browser';
|
||||
import { VideoType } from '../../models/video-type.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ov-video',
|
||||
template: `
|
||||
<img *ngIf="!_streamManager?.stream?.videoActive && (type === 'CAMERA' || !type)" class="poster_img" alt="OpenVidu Logo" src="assets/images/poster.png" />
|
||||
<video
|
||||
class="OT_video-element"
|
||||
#videoElement
|
||||
[attr.id]="streamManager && _streamManager.stream ? 'video-' + _streamManager.stream.streamId : 'video-undefined'"
|
||||
[muted]="mutedSound"
|
||||
|
@ -16,11 +16,7 @@ import { VideoType } from '../../models/video-type.model';
|
|||
})
|
||||
export class VideoComponent implements AfterViewInit {
|
||||
@Input() mutedSound: boolean;
|
||||
|
||||
@Output() toggleVideoSizeEvent = new EventEmitter<any>();
|
||||
|
||||
_streamManager: StreamManager;
|
||||
|
||||
_videoElement: ElementRef;
|
||||
type: VideoType = VideoType.CAMERA;
|
||||
|
||||
|
@ -39,27 +35,18 @@ export class VideoComponent implements AfterViewInit {
|
|||
|
||||
@Input()
|
||||
set streamManager(streamManager: StreamManager) {
|
||||
setTimeout(() => {
|
||||
if (streamManager) {
|
||||
this._streamManager = streamManager;
|
||||
if (!!this._videoElement && this._streamManager) {
|
||||
this.type = <VideoType>this._streamManager?.stream?.typeOfVideo;
|
||||
if (this.type === VideoType.SCREEN) {
|
||||
this._videoElement.nativeElement.style.objectFit = 'contain';
|
||||
this._videoElement.nativeElement.style.background = '#272727';
|
||||
this.enableVideoSizeBig();
|
||||
// this._videoElement.nativeElement.style.background = '#272727';
|
||||
} else {
|
||||
this._videoElement.nativeElement.style.objectFit = 'cover';
|
||||
}
|
||||
this._streamManager.addVideoElement(this._videoElement.nativeElement);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
enableVideoSizeBig() {
|
||||
// Doing video size bigger.
|
||||
// Timeout because of connectionId is null and icon does not change
|
||||
setTimeout(() => {
|
||||
this.toggleVideoSizeEvent.emit(true);
|
||||
}, 590);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
#user-settings-container {
|
||||
#pre-join-container {
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,32 +1,104 @@
|
|||
<div id="call-container">
|
||||
<div id="user-settings-container" *ngIf="!joinSessionClicked && !closeClicked">
|
||||
<ov-user-settings (onJoinClicked)="_onJoinClicked()" (onCloseClicked)="onLeaveSessionClicked()"></ov-user-settings>
|
||||
<div id="pre-join-container" *ngIf="showPrejoin && participantReady && !joinSessionClicked">
|
||||
<ov-pre-join (onJoinButtonClicked)="_onJoinButtonClicked()"></ov-pre-join>
|
||||
<!-- <ov-user-settings (onJoinClicked)="_onJoinClicked()" (onCloseClicked)="onLeaveSessionClicked()"></ov-user-settings> -->
|
||||
</div>
|
||||
|
||||
<div id="spinner" *ngIf="joinSessionClicked && !isSessionAlive && !error">
|
||||
<div id="spinner" *ngIf="(joinSessionClicked || !showPrejoin) && !participantReady && !error">
|
||||
<mat-spinner [diameter]="50"></mat-spinner>
|
||||
<span>Joining the room ...</span>
|
||||
</div>
|
||||
|
||||
<div id="spinner" *ngIf="joinSessionClicked && !isSessionAlive && error">
|
||||
<div id="spinner" *ngIf="joinSessionClicked && !participantReady && error">
|
||||
<mat-icon class="error-icon">error</mat-icon>
|
||||
<span>{{ errorMessage }}</span>
|
||||
</div>
|
||||
|
||||
<div id="session-container" *ngIf="joinSessionClicked && isSessionAlive && !error">
|
||||
<ov-session [tokens]="_tokens">
|
||||
<div id="session-container" *ngIf="(joinSessionClicked || !showPrejoin) && participantReady && canPublish && !error">
|
||||
<ov-session (onSessionCreated)="_onSessionCreated($event)">
|
||||
<ng-template #toolbar>
|
||||
<ng-container *ngIf="openviduAngularToolbarTemplate">
|
||||
<ng-container *ngTemplateOutlet="openviduAngularToolbarTemplate"></ng-container>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
|
||||
<!-- Default toolbar -->
|
||||
<!--<ng-template #toolbar toolbar>
|
||||
<ov-toolbar
|
||||
(onCamClicked)="onCamClicked()"
|
||||
(onMicClicked)="onMicClicked()"
|
||||
(onScreenShareClicked)="onScreenShareClicked()"
|
||||
(onLeaveSessionClicked)="onLeaveSessionClicked()"
|
||||
></ov-toolbar>
|
||||
</ng-template>-->
|
||||
<ng-template #panel>
|
||||
<ng-container *ngIf="openviduAngularPanelTemplate">
|
||||
<ng-container *ngTemplateOutlet="openviduAngularPanelTemplate"></ng-container>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
|
||||
<!-- <ov-layout layout></ov-layout> -->
|
||||
<ng-template #layout>
|
||||
<ng-container *ngIf="openviduAngularLayoutTemplate">
|
||||
<ng-container *ngTemplateOutlet="openviduAngularLayoutTemplate"></ng-container>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</ov-session>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #defaultToolbar>
|
||||
<ov-toolbar
|
||||
(onLeaveButtonClicked)="onLeaveButtonClicked()"
|
||||
(onCameraButtonClicked)="onCameraButtonClicked()"
|
||||
(onMicrophoneButtonClicked)="onMicrophoneButtonClicked()"
|
||||
(onScreenshareButtonClicked)="onScreenshareButtonClicked()"
|
||||
(onFullscreenButtonClicked)="onFullscreenButtonClicked()"
|
||||
(onParticipantsPanelButtonClicked)="onParticipantsPanelButtonClicked()"
|
||||
(onChatPanelButtonClicked)="onChatPanelButtonClicked()"
|
||||
>
|
||||
|
||||
<ng-template #toolbarAdditionalButtons>
|
||||
<ng-container *ngTemplateOutlet="openviduAngularToolbarAdditionalButtonsTemplate"></ng-container>
|
||||
</ng-template>
|
||||
|
||||
</ov-toolbar>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #defaultPanel>
|
||||
<ov-panel>
|
||||
<ng-template #chatPanel>
|
||||
<ng-container *ngTemplateOutlet="openviduAngularChatPanelTemplate"></ng-container>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #participantsPanel>
|
||||
<ng-container *ngTemplateOutlet="openviduAngularParticipantsPanelTemplate"></ng-container>
|
||||
</ng-template>
|
||||
</ov-panel>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #defaultChatPanel>
|
||||
<ov-chat-panel></ov-chat-panel>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #defaultParticipantsPanel>
|
||||
<ov-participants-panel>
|
||||
<ng-template #participantPanelItem let-participant>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="openviduAngularParticipantPanelItemTemplate; context: { $implicit: participant }"
|
||||
></ng-container>
|
||||
</ng-template>
|
||||
</ov-participants-panel>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #defaultParticipantPanelItem let-participant>
|
||||
<ov-participant-panel-item [participant]="participant">
|
||||
|
||||
<ng-template #participantPanelItemElements>
|
||||
<ng-container *ngTemplateOutlet="openviduAngularParticipantPanelItemElementsTemplate; context: { $implicit: participant }"></ng-container>
|
||||
</ng-template>
|
||||
|
||||
</ov-participant-panel-item>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #defaultLayout>
|
||||
<ov-layout>
|
||||
<ng-template #stream let-stream>
|
||||
<ng-container *ngTemplateOutlet="openviduAngularStreamTemplate; context: { $implicit: stream }"> </ng-container>
|
||||
</ng-template>
|
||||
</ov-layout>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #defaultStream let-stream>
|
||||
<ov-stream [stream]="stream"></ov-stream>
|
||||
</ng-template>
|
||||
|
|
|
@ -1,135 +1,304 @@
|
|||
import {
|
||||
AfterViewInit,
|
||||
Component,
|
||||
ContentChild,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ViewChild,
|
||||
ChangeDetectorRef,
|
||||
AfterViewInit,
|
||||
ViewContainerRef
|
||||
TemplateRef,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { LibraryConfigService } from '../../services/library-config/library-config.service';
|
||||
import { ToolbarComponent } from '../toolbar/toolbar.component';
|
||||
import { OpenViduErrorName, Session } from 'openvidu-browser';
|
||||
import { Subscription } from 'rxjs';
|
||||
import {
|
||||
ChatPanelDirective,
|
||||
LayoutDirective,
|
||||
PanelDirective,
|
||||
ParticipantPanelItemDirective,
|
||||
ParticipantPanelItemElementsDirective,
|
||||
ParticipantsPanelDirective,
|
||||
StreamDirective,
|
||||
ToolbarAdditionalButtonsDirective,
|
||||
ToolbarDirective
|
||||
} from '../../directives/template/openvidu-angular.directive';
|
||||
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 { OpenViduService } from '../../services/openvidu/openvidu.service';
|
||||
import { ParticipantService } from '../../services/participant/participant.service';
|
||||
import { StorageService } from '../../services/storage/storage.service';
|
||||
import { TokenService } from '../../services/token/token.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ov-videoconference',
|
||||
templateUrl: './videoconference.component.html',
|
||||
styleUrls: ['./videoconference.component.css']
|
||||
})
|
||||
export class VideoconferenceComponent implements OnInit, AfterViewInit {
|
||||
@Input() sessionName: string;
|
||||
@Input() userName: string;
|
||||
@Input() openviduServerUrl: string;
|
||||
@Input() openviduSecret: string;
|
||||
// @Input() tokens: { webcam: string; screen: string };
|
||||
export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
// *** Toolbar ***
|
||||
@ContentChild(ToolbarDirective) externalToolbar: ToolbarDirective;
|
||||
@ContentChild(ToolbarAdditionalButtonsDirective) externalToolbarAdditionalButtons: ToolbarAdditionalButtonsDirective;
|
||||
|
||||
@Output() onJoinClicked = new EventEmitter<any>();
|
||||
@Output() onCloseClicked = new EventEmitter<any>();
|
||||
// *** Panels ***
|
||||
@ContentChild(PanelDirective) externalPanel: PanelDirective;
|
||||
@ContentChild(ChatPanelDirective) externalChatPanel: ChatPanelDirective;
|
||||
@ContentChild(ParticipantsPanelDirective) externalParticipantsPanel: ParticipantsPanelDirective;
|
||||
@ContentChild(ParticipantPanelItemDirective) externalParticipantPanelItem: ParticipantPanelItemDirective;
|
||||
@ContentChild(ParticipantPanelItemElementsDirective) externalParticipantPanelItemElements: ParticipantPanelItemElementsDirective;
|
||||
|
||||
joinSessionClicked: boolean = false;
|
||||
closeClicked: boolean = false;
|
||||
isSessionAlive: boolean = false;
|
||||
_tokens: { webcam: string; screen: string };
|
||||
error: boolean = false;
|
||||
errorMessage: string = '';
|
||||
// *** Layout ***
|
||||
@ContentChild(LayoutDirective) externalLayout: LayoutDirective;
|
||||
@ContentChild(StreamDirective) externalStream: StreamDirective;
|
||||
|
||||
_toolbar: ViewContainerRef;
|
||||
@ViewChild('defaultToolbar', { static: false, read: TemplateRef }) defaultToolbarTemplate: TemplateRef<any>;
|
||||
@ViewChild('defaultPanel', { static: false, read: TemplateRef }) defaultPanelTemplate: TemplateRef<any>;
|
||||
@ViewChild('defaultChatPanel', { static: false, read: TemplateRef }) defaultChatPanelTemplate: TemplateRef<any>;
|
||||
@ViewChild('defaultParticipantsPanel', { static: false, read: TemplateRef }) defaultParticipantsPanelTemplate: TemplateRef<any>;
|
||||
@ViewChild('defaultParticipantPanelItem', { static: false, read: TemplateRef }) defaultParticipantPanelItemTemplate: TemplateRef<any>;
|
||||
@ViewChild('defaultLayout', { static: false, read: TemplateRef }) defaultLayoutTemplate: TemplateRef<any>;
|
||||
@ViewChild('defaultStream', { static: false, read: TemplateRef }) defaultStreamTemplate: TemplateRef<any>;
|
||||
|
||||
constructor(protected libraryConfigSrv: LibraryConfigService, private cd: ChangeDetectorRef) {}
|
||||
openviduAngularToolbarTemplate: TemplateRef<any>;
|
||||
openviduAngularToolbarAdditionalButtonsTemplate: TemplateRef<any>;
|
||||
openviduAngularPanelTemplate: TemplateRef<any>;
|
||||
openviduAngularChatPanelTemplate: TemplateRef<any>;
|
||||
openviduAngularParticipantsPanelTemplate: TemplateRef<any>;
|
||||
openviduAngularParticipantPanelItemTemplate: TemplateRef<any>;
|
||||
openviduAngularParticipantPanelItemElementsTemplate: TemplateRef<any>;
|
||||
openviduAngularLayoutTemplate: TemplateRef<any>;
|
||||
openviduAngularStreamTemplate: TemplateRef<any>;
|
||||
|
||||
// @ViewChild('toolbar', { static: false, read: ViewContainerRef })
|
||||
// set toolbar(reference: ViewContainerRef) {
|
||||
// setTimeout(() => {
|
||||
// console.log('setting ref', reference);
|
||||
// this._toolbar = reference;
|
||||
|
||||
// if (this._toolbar) {
|
||||
// let component = ToolbarComponent;
|
||||
// if (this.libraryConfigSrv.isCustomComponentDefined('ov-toolbar')) {
|
||||
// component = this.libraryConfigSrv.getToolbarComponent();
|
||||
// }
|
||||
// this._toolbar?.clear();
|
||||
// this._toolbar.createComponent(component);
|
||||
// }
|
||||
// }, 100);
|
||||
// }
|
||||
|
||||
ngAfterViewInit() {
|
||||
// if(this.customToolbar && this.libraryConfigSrv.isCustomComponentDefined('ov-toolbar')){
|
||||
// const viewContainerRef = this.customToolbar.viewContainerRef;
|
||||
// viewContainerRef.clear();
|
||||
// const componentRef = viewContainerRef.createComponent<any>(this.libraryConfigSrv.getToolbarComponent());
|
||||
// } else {
|
||||
// this.customToolbar.viewContainerRef.clear();
|
||||
// const viewContainerRef = this.toolbar.viewContainerRef;
|
||||
// viewContainerRef.clear();
|
||||
// viewContainerRef.createComponent<any>(ToolbarComponent);
|
||||
// }
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
@Input('tokens')
|
||||
@Input()
|
||||
set tokens(tokens: { webcam: string; screen: string }) {
|
||||
if (!!tokens?.webcam || !!this.tokens?.screen) {
|
||||
// 1 token received
|
||||
this.cd.detectChanges();
|
||||
// this.cd.markForCheck();
|
||||
this._tokens = tokens;
|
||||
this.joinSessionClicked = true;
|
||||
this.isSessionAlive = true;
|
||||
} else {
|
||||
if (!tokens || (!tokens.webcam && !tokens.screen)) {
|
||||
//No tokens received
|
||||
throw new Error('No tokens received');
|
||||
// throw new Error('No tokens received');
|
||||
this.log.w('No tokens received');
|
||||
} else {
|
||||
if (tokens.webcam || tokens.screen) {
|
||||
this.tokenService.setWebcamToken(tokens.webcam);
|
||||
this.tokenService.setScreenToken(tokens.screen);
|
||||
this.canPublish = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async _onJoinClicked() {
|
||||
this.onJoinClicked.emit();
|
||||
// if (!this.tokens || (!this.tokens?.webcam && !this.tokens?.screen)) {
|
||||
// //No tokens received
|
||||
// *** Events ***
|
||||
|
||||
// if (!!this.sessionName && !!this.openviduServerUrl && !!this.openviduSecret) {
|
||||
// // Generate tokens
|
||||
// this._tokens = {
|
||||
// webcam: await this.restService.getToken(this.sessionName, this.openviduServerUrl, this.openviduSecret),
|
||||
// screen: await this.restService.getToken(this.sessionName, this.openviduServerUrl, this.openviduSecret)
|
||||
// };
|
||||
// } else {
|
||||
// // No tokens received and can't generate them
|
||||
// this.error = true;
|
||||
// this.errorMessage = `Cannot access to OpenVidu Server with url '${this.openviduServerUrl}' to genere tokens for session '${this.sessionName}'`;
|
||||
// throw this.errorMessage;
|
||||
// }
|
||||
// } else if (!this.tokens?.webcam || !this.tokens?.screen) {
|
||||
// // 1 token received
|
||||
// const aditionalToken = await this.restService.getToken(this.sessionName, this.openviduServerUrl, this.openviduSecret);
|
||||
// this._tokens = {
|
||||
// webcam: !!this.tokens.webcam ? this.tokens.webcam : aditionalToken,
|
||||
// screen: !!this.tokens.screen ? this.tokens.screen : aditionalToken
|
||||
// };
|
||||
// } else {
|
||||
// // 2 tokens received.
|
||||
// this._tokens = {
|
||||
// webcam: this.tokens.webcam,
|
||||
// screen: this.tokens.screen
|
||||
// };
|
||||
// }
|
||||
// this.joinSessionClicked = true;
|
||||
// this.isSessionAlive = true;
|
||||
}
|
||||
onLeaveSessionClicked() {
|
||||
this.isSessionAlive = false;
|
||||
this.closeClicked = true;
|
||||
// Event sent when user click on the join button in pre-join page
|
||||
@Output() onJoinButtonClicked = new EventEmitter<any>();
|
||||
// Event sent when user click on the join button in pre-join page
|
||||
@Output() onToolbarLeaveButtonClicked = new EventEmitter<any>();
|
||||
@Output() onToolbarCameraButtonClicked = new EventEmitter<any>();
|
||||
@Output() onToolbarMicrophoneButtonClicked = new EventEmitter<any>();
|
||||
@Output() onToolbarScreenshareButtonClicked = new EventEmitter<any>();
|
||||
@Output() onToolbarFullscreenButtonClicked = new EventEmitter<any>();
|
||||
@Output() onToolbarParticipantsPanelButtonClicked = new EventEmitter<any>();
|
||||
@Output() onToolbarChatPanelButtonClicked = new EventEmitter<any>();
|
||||
|
||||
// Event sent when participant has joined the session
|
||||
// @Output() onParticipantJoined = new EventEmitter<any>();
|
||||
|
||||
// Event sent when session has been created
|
||||
@Output() onSessionCreated = new EventEmitter<any>();
|
||||
// Event sent when participant has been created
|
||||
@Output() onParticipantCreated = new EventEmitter<any>();
|
||||
|
||||
|
||||
joinSessionClicked: boolean = false;
|
||||
participantReady: boolean = false;
|
||||
canPublish: boolean = false;
|
||||
error: boolean = false;
|
||||
errorMessage: string = '';
|
||||
showPrejoin: boolean = true;
|
||||
private externalParticipantName: string;
|
||||
private prejoinSub: Subscription;
|
||||
private participantNameSub: Subscription;
|
||||
private log: ILogger;
|
||||
|
||||
constructor(
|
||||
private loggerSrv: LoggerService,
|
||||
private storageSrv: StorageService,
|
||||
private participantService: ParticipantService,
|
||||
private deviceSrv: DeviceService,
|
||||
private openviduService: OpenViduService,
|
||||
private actionService: ActionService,
|
||||
private libService: OpenViduAngularConfigService,
|
||||
private tokenService: TokenService
|
||||
) {
|
||||
this.log = this.loggerSrv.get('VideoconferenceComponent');
|
||||
}
|
||||
|
||||
onMicClicked() {}
|
||||
async ngOnInit() {
|
||||
this.subscribeToVideconferenceDirectives();
|
||||
await this.deviceSrv.initializeDevices();
|
||||
const nickname = this.externalParticipantName || this.storageSrv.getNickname() || `OpenVidu_User${Math.floor(Math.random() * 100)}`;
|
||||
const props: ParticipantProperties = {
|
||||
local: true,
|
||||
nickname
|
||||
};
|
||||
this.participantService.initLocalParticipant(props);
|
||||
this.openviduService.initialize();
|
||||
|
||||
onCamClicked() {}
|
||||
if (this.deviceSrv.hasVideoDeviceAvailable() || this.deviceSrv.hasAudioDeviceAvailable()) {
|
||||
await this.initwebcamPublisher();
|
||||
}
|
||||
|
||||
onScreenShareClicked() {}
|
||||
this.onParticipantCreated.emit(this.participantService.getLocalParticipant());
|
||||
}
|
||||
|
||||
onSpeakerLayoutClicked() {}
|
||||
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();
|
||||
if (this.participantNameSub) this.participantNameSub.unsubscribe();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
if (this.externalToolbar) {
|
||||
this.openviduAngularToolbarTemplate = this.externalToolbar.template;
|
||||
this.log.d('Setting EXTERNAL TOOLBAR');
|
||||
} else {
|
||||
if (this.externalToolbarAdditionalButtons) {
|
||||
this.log.d('Setting EXTERNAL TOOLBAR ADDITIONAL BUTTONS');
|
||||
this.openviduAngularToolbarAdditionalButtonsTemplate = this.externalToolbarAdditionalButtons.template;
|
||||
}
|
||||
this.openviduAngularToolbarTemplate = this.defaultToolbarTemplate;
|
||||
this.log.d('Setting DEFAULT TOOLBAR');
|
||||
}
|
||||
|
||||
if (this.externalPanel) {
|
||||
this.openviduAngularPanelTemplate = this.externalPanel.template;
|
||||
this.log.d('Setting EXTERNAL PANEL');
|
||||
} else {
|
||||
this.log.d('Setting DEFAULT PANEL');
|
||||
|
||||
if (this.externalParticipantsPanel) {
|
||||
this.openviduAngularParticipantsPanelTemplate = this.externalParticipantsPanel.template;
|
||||
this.log.d('Setting EXTERNAL PARTICIPANTS PANEL');
|
||||
} else {
|
||||
this.log.d('Setting DEFAULT PARTICIPANTS PANEL');
|
||||
if (this.externalParticipantPanelItem) {
|
||||
this.openviduAngularParticipantPanelItemTemplate = this.externalParticipantPanelItem.template;
|
||||
this.log.d('Setting EXTERNAL P ITEM');
|
||||
} else {
|
||||
if (this.externalParticipantPanelItemElements) {
|
||||
this.log.d('Setting EXTERNAL PARTICIPANT PANEL ITEM ELEMENT');
|
||||
this.openviduAngularParticipantPanelItemElementsTemplate = this.externalParticipantPanelItemElements.template;
|
||||
}
|
||||
this.openviduAngularParticipantPanelItemTemplate = this.defaultParticipantPanelItemTemplate;
|
||||
this.log.d('Setting DEFAULT P ITEM');
|
||||
}
|
||||
this.openviduAngularParticipantsPanelTemplate = this.defaultParticipantsPanelTemplate;
|
||||
}
|
||||
|
||||
if (this.externalChatPanel) {
|
||||
this.openviduAngularChatPanelTemplate = this.externalChatPanel.template;
|
||||
this.log.d('Setting EXTERNAL CHAT PANEL');
|
||||
} else {
|
||||
this.openviduAngularChatPanelTemplate = this.defaultChatPanelTemplate;
|
||||
this.log.d('Setting DEFAULT CHAT PANEL');
|
||||
}
|
||||
this.openviduAngularPanelTemplate = this.defaultPanelTemplate;
|
||||
}
|
||||
|
||||
if (this.externalLayout) {
|
||||
this.openviduAngularLayoutTemplate = this.externalLayout.template;
|
||||
this.log.d('Setting EXTERNAL LAYOUT');
|
||||
} else {
|
||||
this.log.d('Setting DEAFULT LAYOUT');
|
||||
|
||||
if (this.externalStream) {
|
||||
this.openviduAngularStreamTemplate = this.externalStream.template;
|
||||
this.log.d('Setting EXTERNAL STREAM');
|
||||
} else {
|
||||
this.openviduAngularStreamTemplate = this.defaultStreamTemplate;
|
||||
this.log.d('Setting DEFAULT STREAM');
|
||||
}
|
||||
this.openviduAngularLayoutTemplate = this.defaultLayoutTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
_onJoinButtonClicked() {
|
||||
this.joinSessionClicked = true;
|
||||
this.onJoinButtonClicked.emit();
|
||||
}
|
||||
onLeaveButtonClicked() {
|
||||
this.joinSessionClicked = false;
|
||||
this.participantReady = false;
|
||||
this.onToolbarLeaveButtonClicked.emit();
|
||||
}
|
||||
onCameraButtonClicked() {
|
||||
this.onToolbarCameraButtonClicked.emit();
|
||||
}
|
||||
|
||||
onMicrophoneButtonClicked() {
|
||||
this.onToolbarMicrophoneButtonClicked.emit();
|
||||
}
|
||||
onScreenshareButtonClicked() {
|
||||
this.onToolbarScreenshareButtonClicked.emit();
|
||||
}
|
||||
onFullscreenButtonClicked() {
|
||||
this.onToolbarFullscreenButtonClicked.emit();
|
||||
}
|
||||
onParticipantsPanelButtonClicked() {
|
||||
this.onToolbarParticipantsPanelButtonClicked.emit();
|
||||
}
|
||||
onChatPanelButtonClicked() {
|
||||
this.onToolbarChatPanelButtonClicked.emit();
|
||||
}
|
||||
_onSessionCreated(event: Session) {
|
||||
this.onSessionCreated.emit(event);
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
this.participantNameSub = this.libService.participantName.subscribe((nickname: string) => {
|
||||
this.externalParticipantName = nickname;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
import { Type } from '@angular/core';
|
||||
|
||||
export interface LibConfig {
|
||||
environment: {
|
||||
production: boolean;
|
||||
useProdLibrary?: boolean;
|
||||
customComponents?: { LibraryComponents: Type<any> };
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export enum LibraryComponents {
|
||||
TOOLBAR = 'ov-toolbar',
|
||||
LAYOUT = 'ov-layout',
|
||||
PANEL = 'ov-panel',
|
||||
CHAT_PANEL = 'ov-chat-panel',
|
||||
PARTICIPANTS_PANEL = 'ov-participants-panel',
|
||||
STREAM = "ov-stream"
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import { ParticipantProperties, StreamModel } from '../models/participant.model';
|
||||
|
||||
export interface OpenViduAngularConfig {
|
||||
production?: boolean,
|
||||
participantFactory?: ParticipantFactoryFunction,
|
||||
webcomponent?: boolean
|
||||
|
||||
}
|
||||
|
||||
export type ParticipantFactoryFunction = (props: ParticipantProperties, streamModel: StreamModel) => any;
|
|
@ -0,0 +1,66 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { ParticipantPanelItemMuteButtonDirective } from './participant-panel-item.directive';
|
||||
import {
|
||||
StreamDisplayParticipantNameDirective,
|
||||
StreamDisplayAudioDetectionDirective,
|
||||
StreamSettingsButtonDirective
|
||||
} from './stream.directive';
|
||||
import {
|
||||
ToolbarScreenshareButtonDirective,
|
||||
ToolbarFullscreenButtonDirective,
|
||||
ToolbarLeaveButtonDirective,
|
||||
ToolbarParticipantsPanelButtonDirective,
|
||||
ToolbarChatPanelButtonDirective,
|
||||
ToolbarDisplaySessionNameDirective,
|
||||
ToolbarDisplayLogoDirective,
|
||||
LogoDirective
|
||||
} from './toolbar.directive';
|
||||
import {
|
||||
AudioMutedDirective,
|
||||
MinimalDirective,
|
||||
PrejoinDirective,
|
||||
VideoMutedDirective,
|
||||
ParticipantNameDirective
|
||||
} from './videoconference.directive';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
MinimalDirective,
|
||||
PrejoinDirective,
|
||||
VideoMutedDirective,
|
||||
AudioMutedDirective,
|
||||
ToolbarScreenshareButtonDirective,
|
||||
ToolbarFullscreenButtonDirective,
|
||||
ToolbarLeaveButtonDirective,
|
||||
ToolbarParticipantsPanelButtonDirective,
|
||||
ToolbarChatPanelButtonDirective,
|
||||
ToolbarDisplaySessionNameDirective,
|
||||
ToolbarDisplayLogoDirective,
|
||||
StreamDisplayParticipantNameDirective,
|
||||
StreamDisplayAudioDetectionDirective,
|
||||
StreamSettingsButtonDirective,
|
||||
LogoDirective,
|
||||
ParticipantPanelItemMuteButtonDirective,
|
||||
ParticipantNameDirective
|
||||
],
|
||||
exports: [
|
||||
MinimalDirective,
|
||||
PrejoinDirective,
|
||||
VideoMutedDirective,
|
||||
AudioMutedDirective,
|
||||
ToolbarScreenshareButtonDirective,
|
||||
ToolbarFullscreenButtonDirective,
|
||||
ToolbarLeaveButtonDirective,
|
||||
ToolbarParticipantsPanelButtonDirective,
|
||||
ToolbarChatPanelButtonDirective,
|
||||
ToolbarDisplaySessionNameDirective,
|
||||
ToolbarDisplayLogoDirective,
|
||||
StreamDisplayParticipantNameDirective,
|
||||
StreamDisplayAudioDetectionDirective,
|
||||
StreamSettingsButtonDirective,
|
||||
LogoDirective,
|
||||
ParticipantPanelItemMuteButtonDirective,
|
||||
ParticipantNameDirective
|
||||
]
|
||||
})
|
||||
export class ApiDirectiveModule {}
|
|
@ -0,0 +1,37 @@
|
|||
import { Directive, AfterViewInit, OnDestroy, Input, ElementRef } from '@angular/core';
|
||||
import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service';
|
||||
|
||||
@Directive({
|
||||
selector: 'ov-videoconference[participantPanelItemMuteButton], ov-participant-panel-item[muteButton]'
|
||||
})
|
||||
export class ParticipantPanelItemMuteButtonDirective implements AfterViewInit, OnDestroy {
|
||||
@Input() set participantPanelItemMuteButton(value: boolean) {
|
||||
this.muteValue = value;
|
||||
this.update(this.muteValue);
|
||||
}
|
||||
@Input() set muteButton(value: boolean) {
|
||||
this.muteValue = value;
|
||||
this.update(this.muteValue);
|
||||
}
|
||||
|
||||
muteValue: boolean = true;
|
||||
|
||||
constructor(public elementRef: ElementRef, private libService: OpenViduAngularConfigService) {}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.update(this.muteValue);
|
||||
}
|
||||
ngOnDestroy(): void {
|
||||
this.clear();
|
||||
}
|
||||
clear() {
|
||||
this.muteValue = true;
|
||||
this.update(true);
|
||||
}
|
||||
|
||||
update(value: boolean) {
|
||||
if (this.libService.participantItemMuteButton.getValue() !== value) {
|
||||
this.libService.participantItemMuteButton.next(value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
import { AfterViewInit, Directive, ElementRef, Input, OnDestroy } from '@angular/core';
|
||||
import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service';
|
||||
|
||||
@Directive({
|
||||
selector: 'ov-videoconference[streamDisplayParticipantName], ov-stream[displayParticipantName]'
|
||||
})
|
||||
export class StreamDisplayParticipantNameDirective implements AfterViewInit, OnDestroy {
|
||||
@Input() set streamDisplayParticipantName(value: boolean) {
|
||||
this.displayParticipantNameValue = value;
|
||||
this.update(this.displayParticipantNameValue);
|
||||
}
|
||||
@Input() set displayParticipantName(value: boolean) {
|
||||
this.displayParticipantNameValue = value;
|
||||
this.update(this.displayParticipantNameValue);
|
||||
}
|
||||
|
||||
displayParticipantNameValue: boolean;
|
||||
|
||||
constructor(public elementRef: ElementRef, private libService: OpenViduAngularConfigService) {}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.clear();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.update(this.displayParticipantNameValue);
|
||||
}
|
||||
|
||||
update(value: boolean) {
|
||||
if (this.libService.displayParticipantName.getValue() !== value) {
|
||||
this.libService.displayParticipantName.next(value);
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.update(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: 'ov-videoconference[streamDisplayAudioDetection], ov-stream[displayAudioDetection]'
|
||||
})
|
||||
export class StreamDisplayAudioDetectionDirective implements AfterViewInit, OnDestroy {
|
||||
@Input() set streamDisplayAudioDetection(value: boolean) {
|
||||
this.displayAudioDetectionValue = value;
|
||||
this.update(this.displayAudioDetectionValue);
|
||||
}
|
||||
@Input() set displayAudioDetection(value: boolean) {
|
||||
this.displayAudioDetectionValue = value;
|
||||
this.update(this.displayAudioDetectionValue);
|
||||
}
|
||||
|
||||
displayAudioDetectionValue: boolean;
|
||||
|
||||
constructor(public elementRef: ElementRef, private libService: OpenViduAngularConfigService) {}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.update(this.displayAudioDetectionValue);
|
||||
}
|
||||
ngOnDestroy(): void {
|
||||
this.clear();
|
||||
}
|
||||
|
||||
update(value: boolean) {
|
||||
if (this.libService.displayAudioDetection.getValue() !== value) {
|
||||
this.libService.displayAudioDetection.next(value);
|
||||
}
|
||||
}
|
||||
clear() {
|
||||
this.update(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: 'ov-videoconference[streamSettingsButton], ov-stream[settingsButton]'
|
||||
})
|
||||
export class StreamSettingsButtonDirective implements AfterViewInit, OnDestroy {
|
||||
@Input() set streamSettingsButton(value: boolean) {
|
||||
this.settingsValue = value;
|
||||
this.update(this.settingsValue);
|
||||
}
|
||||
@Input() set settingsButton(value: boolean) {
|
||||
this.settingsValue = value;
|
||||
this.update(this.settingsValue);
|
||||
}
|
||||
|
||||
settingsValue: boolean;
|
||||
|
||||
constructor(public elementRef: ElementRef, private libService: OpenViduAngularConfigService) {}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.update(this.settingsValue);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.clear();
|
||||
}
|
||||
|
||||
update(value: boolean) {
|
||||
if (this.libService.settingsButton.getValue() !== value) {
|
||||
this.libService.settingsButton.next(value);
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.update(true);
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,112 @@
|
|||
import { Directive, Input, ElementRef, OnDestroy, OnInit } from '@angular/core';
|
||||
import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service';
|
||||
|
||||
@Directive({
|
||||
selector: 'ov-videoconference[minimal]'
|
||||
})
|
||||
export class MinimalDirective implements OnDestroy {
|
||||
@Input() set minimal(value: boolean) {
|
||||
this.update(value);
|
||||
}
|
||||
constructor(public elementRef: ElementRef, private libService: OpenViduAngularConfigService) {}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.clear();
|
||||
}
|
||||
clear() {
|
||||
this.update(false);
|
||||
}
|
||||
update(value: boolean) {
|
||||
if (this.libService.minimal.getValue() !== value) {
|
||||
this.libService.minimal.next(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: 'ov-videoconference[participantName]'
|
||||
})
|
||||
export class ParticipantNameDirective implements OnInit {
|
||||
// Avoiding update participantName dynamically.
|
||||
// The participantName must be updated from UI
|
||||
@Input() participantName: string;
|
||||
constructor(public elementRef: ElementRef, private libService: OpenViduAngularConfigService) {}
|
||||
ngOnInit(): void {
|
||||
this.update(this.participantName);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.clear();
|
||||
}
|
||||
clear() {
|
||||
this.update('');
|
||||
}
|
||||
update(value: string) {
|
||||
this.libService.participantName.next(value);
|
||||
}
|
||||
}
|
||||
|
||||
@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({
|
||||
selector: 'ov-videoconference[videoMuted]'
|
||||
})
|
||||
export class VideoMutedDirective implements OnDestroy {
|
||||
@Input() set videoMuted(value: boolean) {
|
||||
this.update(value);
|
||||
}
|
||||
constructor(public elementRef: ElementRef, private libService: OpenViduAngularConfigService) {}
|
||||
ngOnDestroy(): void {
|
||||
this.clear();
|
||||
}
|
||||
clear() {
|
||||
this.update(false);
|
||||
}
|
||||
update(value: boolean) {
|
||||
if (this.libService.videoMuted.getValue() !== value) {
|
||||
this.libService.videoMuted.next(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: 'ov-videoconference[audioMuted]'
|
||||
})
|
||||
export class AudioMutedDirective implements OnDestroy {
|
||||
@Input() set audioMuted(value: boolean) {
|
||||
this.update(value);
|
||||
}
|
||||
constructor(public elementRef: ElementRef, private libService: OpenViduAngularConfigService) {}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.clear();
|
||||
}
|
||||
clear() {
|
||||
this.update(false);
|
||||
}
|
||||
update(value: boolean) {
|
||||
if (this.libService.audioMuted.getValue() !== value) {
|
||||
this.libService.audioMuted.next(value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import {
|
||||
ChatPanelDirective,
|
||||
LayoutDirective,
|
||||
PanelDirective,
|
||||
ParticipantPanelItemElementsDirective,
|
||||
ParticipantPanelItemDirective,
|
||||
ParticipantsPanelDirective,
|
||||
StreamDirective,
|
||||
ToolbarAdditionalButtonsDirective,
|
||||
ToolbarDirective
|
||||
} from './openvidu-angular.directive';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
ChatPanelDirective,
|
||||
LayoutDirective,
|
||||
PanelDirective,
|
||||
ParticipantPanelItemDirective,
|
||||
ParticipantsPanelDirective,
|
||||
StreamDirective,
|
||||
ToolbarDirective,
|
||||
ToolbarAdditionalButtonsDirective,
|
||||
ParticipantPanelItemElementsDirective
|
||||
],
|
||||
exports: [
|
||||
ChatPanelDirective,
|
||||
LayoutDirective,
|
||||
PanelDirective,
|
||||
ParticipantPanelItemDirective,
|
||||
ParticipantsPanelDirective,
|
||||
StreamDirective,
|
||||
ToolbarDirective,
|
||||
ToolbarAdditionalButtonsDirective,
|
||||
ParticipantPanelItemElementsDirective
|
||||
]
|
||||
})
|
||||
export class OpenViduAngularDirectiveModule {}
|
|
@ -0,0 +1,65 @@
|
|||
import { Directive, TemplateRef, ViewContainerRef } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[ovToolbar]'
|
||||
})
|
||||
export class ToolbarDirective {
|
||||
constructor(public template: TemplateRef<any>, public viewContainer: ViewContainerRef) {}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[ovToolbarAdditionalButtons]'
|
||||
})
|
||||
export class ToolbarAdditionalButtonsDirective {
|
||||
constructor(public template: TemplateRef<any>, public viewContainer: ViewContainerRef) {}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[ovPanel]'
|
||||
})
|
||||
export class PanelDirective {
|
||||
constructor(public template: TemplateRef<any>, public viewContainer: ViewContainerRef) {}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[ovChatPanel]'
|
||||
})
|
||||
export class ChatPanelDirective {
|
||||
constructor(public template: TemplateRef<any>, public viewContainer: ViewContainerRef) {}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[ovParticipantsPanel]'
|
||||
})
|
||||
export class ParticipantsPanelDirective {
|
||||
constructor(public template: TemplateRef<any>, public viewContainer: ViewContainerRef) {}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[ovParticipantPanelItem]'
|
||||
})
|
||||
export class ParticipantPanelItemDirective {
|
||||
constructor(public template: TemplateRef<any>, public viewContainer: ViewContainerRef) {}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[ovParticipantPanelItemElements]'
|
||||
})
|
||||
export class ParticipantPanelItemElementsDirective {
|
||||
constructor(public template: TemplateRef<any>, public viewContainer: ViewContainerRef) {}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[ovLayout]'
|
||||
})
|
||||
export class LayoutDirective {
|
||||
constructor(public template: TemplateRef<any>, public container: ViewContainerRef) {}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[ovStream]'
|
||||
})
|
||||
export class StreamDirective {
|
||||
constructor(public template: TemplateRef<any>, public container: ViewContainerRef) {}
|
||||
}
|
||||
|
|
@ -2,7 +2,8 @@ export enum LayoutClass {
|
|||
ROOT_ELEMENT = 'OT_root',
|
||||
BIG_ELEMENT = 'OV_big',
|
||||
SMALL_ELEMENT = 'OV_small',
|
||||
SIDENAV_CONTAINER = 'sidenav-container'
|
||||
SIDENAV_CONTAINER = 'sidenav-container',
|
||||
NO_SIZE_ELEMENT = 'no-size'
|
||||
}
|
||||
|
||||
export enum SidenavMode {
|
||||
|
@ -140,7 +141,7 @@ export class OpenViduLayout {
|
|||
}
|
||||
|
||||
getLayoutContainer(): HTMLElement {
|
||||
return this.layoutContainer
|
||||
return this.layoutContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,40 +2,58 @@ import { Publisher, StreamManager } from 'openvidu-browser';
|
|||
import { VideoType } from './video-type.model';
|
||||
|
||||
export interface StreamModel {
|
||||
local: boolean;
|
||||
connected: boolean;
|
||||
nickname: string;
|
||||
type: VideoType;
|
||||
streamManager: StreamManager;
|
||||
videoEnlarged: boolean;
|
||||
connectionId: string;
|
||||
participant?: ParticipantAbstractModel
|
||||
}
|
||||
|
||||
export interface ParticipantProperties {
|
||||
local: boolean;
|
||||
nickname: string;
|
||||
id?: string;
|
||||
colorProfile?: string;
|
||||
isMutedForcibly?: boolean;
|
||||
}
|
||||
|
||||
export abstract class ParticipantAbstractModel {
|
||||
connections: Map<VideoType, StreamModel> = new Map();
|
||||
streams: Map<VideoType, StreamModel> = new Map();
|
||||
id: string;
|
||||
local: boolean;
|
||||
nickname: string;
|
||||
colorProfile: string;
|
||||
isMutedForcibly: boolean;
|
||||
|
||||
constructor(model?: StreamModel, id?: string) {
|
||||
constructor(props: ParticipantProperties, model?: StreamModel) {
|
||||
this.id = props.id ? props.id : new Date().getTime().toString();
|
||||
this.local = props.local;
|
||||
this.nickname = props.nickname;
|
||||
this.colorProfile = !!props.colorProfile ? props.colorProfile : `hsl(${Math.random()*360}, 100%, 80%)`;
|
||||
this.isMutedForcibly = typeof props.isMutedForcibly === 'boolean' ? props.isMutedForcibly : false;
|
||||
let streamModel: StreamModel = {
|
||||
local: model ? model.local : true,
|
||||
connected: true,
|
||||
nickname: model ? model.nickname : 'OpenVidu_User',
|
||||
connected: model ? model.connected : true,
|
||||
type: model ? model.type : VideoType.CAMERA,
|
||||
streamManager: model ? model.streamManager : null,
|
||||
videoEnlarged: model ? model.videoEnlarged : false,
|
||||
connectionId: model ? model.connectionId : null
|
||||
connectionId: model ? model.connectionId : null,
|
||||
participant: this
|
||||
};
|
||||
this.connections.set(streamModel.type, streamModel);
|
||||
this.id = id ? id : new Date().getTime().toString();
|
||||
this.streams.set(streamModel.type, streamModel);
|
||||
}
|
||||
|
||||
addConnection(streamModel: StreamModel) {
|
||||
this.connections.set(streamModel.type, streamModel);
|
||||
streamModel.participant = this;
|
||||
this.streams.set(streamModel.type, streamModel);
|
||||
}
|
||||
|
||||
public isCameraAudioActive(): boolean {
|
||||
const cameraConnection = this.getCameraConnection();
|
||||
return cameraConnection.connected && cameraConnection.streamManager.stream.audioActive;
|
||||
if(cameraConnection) {
|
||||
return cameraConnection.connected && cameraConnection.streamManager?.stream?.audioActive;
|
||||
}
|
||||
return this.isScreenAudioActive();;
|
||||
}
|
||||
|
||||
public isCameraVideoActive(): boolean {
|
||||
|
@ -44,25 +62,28 @@ export abstract class ParticipantAbstractModel {
|
|||
}
|
||||
isScreenAudioActive(): boolean {
|
||||
const screenConnection = this.getScreenConnection();
|
||||
return screenConnection?.connected && screenConnection?.streamManager?.stream?.audioActive;
|
||||
if(screenConnection){
|
||||
return screenConnection?.connected && screenConnection?.streamManager?.stream?.audioActive;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
hasConnectionType(type: VideoType): boolean {
|
||||
return this.connections.has(type);
|
||||
return this.streams.has(type);
|
||||
}
|
||||
|
||||
public getCameraConnection(): StreamModel {
|
||||
return this.connections.get(VideoType.CAMERA);
|
||||
return this.streams.get(VideoType.CAMERA);
|
||||
}
|
||||
|
||||
public getScreenConnection(): StreamModel {
|
||||
return this.connections.get(VideoType.SCREEN);
|
||||
return this.streams.get(VideoType.SCREEN);
|
||||
}
|
||||
|
||||
getConnectionTypesEnabled(): VideoType[] {
|
||||
getConnectionTypesActive(): VideoType[] {
|
||||
let connType = [];
|
||||
if (this.isCameraEnabled()) connType.push(VideoType.CAMERA);
|
||||
if (this.isScreenEnabled()) connType.push(VideoType.SCREEN);
|
||||
if (this.isCameraActive()) connType.push(VideoType.CAMERA);
|
||||
if (this.isScreenActive()) connType.push(VideoType.SCREEN);
|
||||
|
||||
return connType;
|
||||
}
|
||||
|
@ -74,43 +95,51 @@ export abstract class ParticipantAbstractModel {
|
|||
this.getScreenConnection().connectionId = connectionId;
|
||||
}
|
||||
|
||||
removeConnection(connectionId: string) {
|
||||
this.connections.delete(this.getConnectionById(connectionId).type);
|
||||
removeConnection(connectionId: string): StreamModel {
|
||||
const removeStream = this.getConnectionById(connectionId);
|
||||
this.streams.delete(removeStream.type);
|
||||
return removeStream;
|
||||
}
|
||||
|
||||
hasConnectionId(connectionId: string): boolean {
|
||||
return Array.from(this.connections.values()).some((conn) => conn.connectionId === connectionId);
|
||||
return Array.from(this.streams.values()).some((conn) => conn.connectionId === connectionId);
|
||||
}
|
||||
|
||||
getConnectionById(connectionId: string): StreamModel {
|
||||
return Array.from(this.connections.values()).find((conn) => conn.connectionId === connectionId);
|
||||
return Array.from(this.streams.values()).find((conn) => conn.connectionId === connectionId);
|
||||
}
|
||||
|
||||
getAvailableConnections(): StreamModel[] {
|
||||
return Array.from(this.connections.values()).filter((conn) => conn.connected);
|
||||
return Array.from(this.streams.values()).filter((conn) => conn.connected);
|
||||
}
|
||||
|
||||
isLocal(): boolean {
|
||||
return Array.from(this.connections.values()).every((conn) => conn.local);
|
||||
return this.local;
|
||||
// return Array.from(this.streams.values()).every((conn) => conn.local);
|
||||
}
|
||||
|
||||
setNickname(nickname: string) {
|
||||
this.connections.forEach((conn) => {
|
||||
if (conn.type === VideoType.CAMERA) {
|
||||
conn.nickname = nickname;
|
||||
} else {
|
||||
conn.nickname = `${nickname}_${conn.type}`;
|
||||
}
|
||||
});
|
||||
this.nickname = nickname;
|
||||
// this.streams.forEach((conn) => {
|
||||
// if (conn.type === VideoType.CAMERA) {
|
||||
// conn.nickname = nickname;
|
||||
// } else {
|
||||
// conn.nickname = `${nickname}_${conn.type}`;
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
getCameraNickname(): string {
|
||||
return this.getCameraConnection()?.nickname;
|
||||
getNickname() {
|
||||
return this.nickname;
|
||||
}
|
||||
|
||||
getScreenNickname(): string {
|
||||
return this.getScreenConnection()?.nickname;
|
||||
}
|
||||
// getCameraNickname(): string {
|
||||
// return this.getCameraConnection()?.nickname;
|
||||
// }
|
||||
|
||||
// getScreenNickname(): string {
|
||||
// return this.getScreenConnection()?.nickname;
|
||||
// }
|
||||
|
||||
setCameraPublisher(publisher: Publisher) {
|
||||
const cameraConnection = this.getCameraConnection();
|
||||
|
@ -123,13 +152,13 @@ export abstract class ParticipantAbstractModel {
|
|||
}
|
||||
|
||||
setPublisher(connType: VideoType, publisher: StreamManager) {
|
||||
const connection = this.connections.get(connType);
|
||||
const connection = this.streams.get(connType);
|
||||
if(connection) {
|
||||
connection.streamManager = publisher;
|
||||
}
|
||||
}
|
||||
|
||||
isCameraEnabled(): boolean {
|
||||
isCameraActive(): boolean {
|
||||
return this.getCameraConnection()?.connected;
|
||||
}
|
||||
|
||||
|
@ -143,11 +172,11 @@ export abstract class ParticipantAbstractModel {
|
|||
if (cameraConnection) cameraConnection.connected = false;
|
||||
}
|
||||
|
||||
isScreenEnabled(): boolean {
|
||||
isScreenActive(): boolean {
|
||||
return this.getScreenConnection()?.connected;
|
||||
}
|
||||
|
||||
enablescreen() {
|
||||
enableScreen() {
|
||||
const screenConnection = this.getScreenConnection();
|
||||
if (screenConnection) screenConnection.connected = true;
|
||||
}
|
||||
|
@ -156,12 +185,21 @@ export abstract class ParticipantAbstractModel {
|
|||
const screenConnection = this.getScreenConnection();
|
||||
if (screenConnection) screenConnection.connected = false;
|
||||
}
|
||||
|
||||
setAllVideoEnlarged(enlarged: boolean) {
|
||||
this.connections.forEach((conn) => (conn.videoEnlarged = enlarged));
|
||||
this.streams.forEach((conn) => (conn.videoEnlarged = enlarged));
|
||||
}
|
||||
|
||||
setCameraEnlarged(enlarged: boolean) {
|
||||
this.streams.get(VideoType.CAMERA).videoEnlarged = enlarged;
|
||||
}
|
||||
setScreenEnlarged(enlarged: boolean) {
|
||||
this.streams.get(VideoType.SCREEN).videoEnlarged = enlarged;
|
||||
}
|
||||
|
||||
|
||||
toggleVideoEnlarged(connectionId: string) {
|
||||
this.connections.forEach((conn) => {
|
||||
this.streams.forEach((conn) => {
|
||||
if (conn.connectionId === connectionId) {
|
||||
conn.videoEnlarged = !conn.videoEnlarged;
|
||||
}
|
||||
|
@ -169,7 +207,11 @@ export abstract class ParticipantAbstractModel {
|
|||
}
|
||||
|
||||
someHasVideoEnlarged(): boolean {
|
||||
return Array.from(this.connections.values()).some((conn) => conn.videoEnlarged);
|
||||
return Array.from(this.streams.values()).some((conn) => conn.videoEnlarged);
|
||||
}
|
||||
|
||||
setMutedForcibly(muted: boolean){
|
||||
this.isMutedForcibly = muted;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
export enum Storage{
|
||||
USER_NICKNAME = 'openviduCallNickname',
|
||||
VIDEO_DEVICE = 'openviduCallVideoDevice',
|
||||
AUDIO_DEVICE = 'openviduCallAudioDevice'
|
||||
AUDIO_DEVICE = 'openviduCallAudioDevice',
|
||||
AUDIO_MUTED = 'openviduCallAudioMuted',
|
||||
VIDEO_MUTED = 'openviduCallVideoMuted'
|
||||
}
|
|
@ -26,7 +26,7 @@ import { CommonModule } from '@angular/common';
|
|||
import { ModuleWithProviders, NgModule } from '@angular/core';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
|
||||
import { UserSettingsComponent } from './components/user-settings/user-settings.component';
|
||||
// import { UserSettingsComponent } from './components/user-settings/user-settings.component';
|
||||
import { ToolbarComponent } from './components/toolbar/toolbar.component';
|
||||
import { VideoComponent } from './components/video/video.component';
|
||||
import { ChatPanelComponent } from './components/panel/chat-panel/chat-panel.component';
|
||||
|
@ -36,36 +36,37 @@ import { StreamComponent } from './components/stream/stream.component';
|
|||
import { DialogTemplateComponent } from './components/material/dialog.component';
|
||||
|
||||
import { LinkifyPipe } from './pipes/linkify.pipe';
|
||||
import { TooltipListPipe } from './pipes/tooltip-list.pipe';
|
||||
import { ConnectionsEnabledPipe, NicknamePipe, ParticipantConnectionsPipe } from './pipes/participant-connections.pipe';
|
||||
import { StreamTypesEnabledPipe, ParticipantStreamsPipe } from './pipes/participant.pipe';
|
||||
|
||||
import { LibConfig } from './config/lib.config';
|
||||
import { OpenViduAngularConfig } from './config/openvidu-angular.config';
|
||||
import { CdkOverlayContainer } from './config/custom-cdk-overlay';
|
||||
import { DeviceService } from './services/device/device.service';
|
||||
import { LoggerService } from './services/logger/logger.service';
|
||||
import { PlatformService } from './services/platform/platform.service';
|
||||
import { StorageService } from './services/storage/storage.service';
|
||||
import { TokenService } from './services/token/token.service';
|
||||
import { LibraryConfigService } from './services/library-config/library-config.service';
|
||||
import { WebrtcService } from './services/webrtc/webrtc.service';
|
||||
import { OpenViduAngularConfigService } from './services/config/openvidu-angular.config.service';
|
||||
import { OpenViduService } from './services/openvidu/openvidu.service';
|
||||
import { ActionService } from './services/action/action.service';
|
||||
import { ChatService } from './services/chat/chat.service';
|
||||
import { DocumentService } from './services/document/document.service';
|
||||
import { LayoutService } from './services/layout/layout.service';
|
||||
import { SidenavMenuService } from './services/sidenav-menu/sidenav-menu.service';
|
||||
import { ParticipantService } from './services/participant/participant.service';
|
||||
import { ParticipantItemComponent } from './components/panel/participants-panel/participant-item/participant-item.component';
|
||||
import { ParticipantPanelItemComponent } from './components/panel/participants-panel/participant-panel-item/participant-panel-item.component';
|
||||
import { ParticipantsPanelComponent } from './components/panel/participants-panel/participants-panel/participants-panel.component';
|
||||
import { VideoconferenceComponent } from './components/videoconference/videoconference.component';
|
||||
import { PanelComponent } from './components/panel/panel.component';
|
||||
import { AudioWaveComponent } from './components/audio-wave/audio-wave.component';
|
||||
import { PreJoinComponent } from './components/pre-join/pre-join.component';
|
||||
|
||||
// Used for loading dynamic components because of Inputs and Outputs are not supported in the official Angular way
|
||||
// https://github.com/angular/angular/issues/15360
|
||||
import { DynamicIoModule } from 'ng-dynamic-component';
|
||||
import { AvatarProfileComponent } from './components/avatar-profile/avatar-profile.component';
|
||||
import { OpenViduAngularDirectiveModule } from './directives/template/openvidu-angular.directive.module';
|
||||
import { ApiDirectiveModule } from './directives/api/api.directive.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
UserSettingsComponent,
|
||||
// UserSettingsComponent,
|
||||
VideoComponent,
|
||||
ToolbarComponent,
|
||||
ChatPanelComponent,
|
||||
|
@ -74,14 +75,15 @@ import { DynamicIoModule } from 'ng-dynamic-component';
|
|||
StreamComponent,
|
||||
DialogTemplateComponent,
|
||||
LinkifyPipe,
|
||||
TooltipListPipe,
|
||||
ParticipantConnectionsPipe,
|
||||
ConnectionsEnabledPipe,
|
||||
NicknamePipe,
|
||||
ParticipantItemComponent,
|
||||
ParticipantStreamsPipe,
|
||||
StreamTypesEnabledPipe,
|
||||
ParticipantPanelItemComponent,
|
||||
ParticipantsPanelComponent,
|
||||
VideoconferenceComponent,
|
||||
PanelComponent,
|
||||
AudioWaveComponent,
|
||||
PanelComponent,
|
||||
AvatarProfileComponent,
|
||||
PreJoinComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
@ -109,7 +111,8 @@ import { DynamicIoModule } from 'ng-dynamic-component';
|
|||
MatMenuModule,
|
||||
MatDividerModule,
|
||||
MatListModule,
|
||||
DynamicIoModule
|
||||
OpenViduAngularDirectiveModule,
|
||||
ApiDirectiveModule
|
||||
],
|
||||
providers: [
|
||||
ActionService,
|
||||
|
@ -125,29 +128,37 @@ import { DynamicIoModule } from 'ng-dynamic-component';
|
|||
ParticipantService,
|
||||
StorageService,
|
||||
TokenService,
|
||||
WebrtcService
|
||||
OpenViduService
|
||||
],
|
||||
exports: [
|
||||
VideoconferenceComponent,
|
||||
UserSettingsComponent,
|
||||
// UserSettingsComponent,
|
||||
ToolbarComponent,
|
||||
PanelComponent,
|
||||
ParticipantsPanelComponent,
|
||||
ParticipantPanelItemComponent,
|
||||
ChatPanelComponent,
|
||||
SessionComponent,
|
||||
LayoutComponent,
|
||||
StreamComponent,
|
||||
VideoComponent,
|
||||
ParticipantConnectionsPipe,
|
||||
CommonModule
|
||||
AudioWaveComponent,
|
||||
PreJoinComponent,
|
||||
ParticipantStreamsPipe,
|
||||
StreamTypesEnabledPipe,
|
||||
CommonModule,
|
||||
OpenViduAngularDirectiveModule,
|
||||
ApiDirectiveModule
|
||||
],
|
||||
entryComponents: [DialogTemplateComponent]
|
||||
})
|
||||
export class OpenviduAngularModule {
|
||||
static forRoot(environment): ModuleWithProviders<OpenviduAngularModule> {
|
||||
export class OpenViduAngularModule {
|
||||
static forRoot(config): ModuleWithProviders<OpenViduAngularModule> {
|
||||
// console.log(`${library.name} config: ${environment}`);
|
||||
const libConfig: LibConfig = { environment };
|
||||
const libConfig: OpenViduAngularConfig = config;
|
||||
return {
|
||||
ngModule: OpenviduAngularModule,
|
||||
providers: [LibraryConfigService, { provide: 'LIB_CONFIG', useValue: libConfig }]
|
||||
ngModule: OpenViduAngularModule,
|
||||
providers: [OpenViduAngularConfigService, { provide: 'OPENVIDU_ANGULAR_CONFIG', useValue: libConfig }]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { StreamModel, ParticipantAbstractModel } from '../models/participant.model';
|
||||
|
||||
@Pipe({ name: 'connections' })
|
||||
export class ParticipantConnectionsPipe implements PipeTransform {
|
||||
constructor() {}
|
||||
|
||||
transform(participants: ParticipantAbstractModel[] | ParticipantAbstractModel): StreamModel[] {
|
||||
let connections: StreamModel[] = [];
|
||||
if (Array.isArray(participants)) {
|
||||
participants.forEach((p) => {
|
||||
connections = connections.concat(Array.from(p.connections.values()));
|
||||
});
|
||||
} else {
|
||||
connections = Array.from(participants.connections.values());
|
||||
}
|
||||
return connections;
|
||||
}
|
||||
}
|
||||
|
||||
@Pipe({ name: 'connectionsEnabled', pure: false })
|
||||
export class ConnectionsEnabledPipe implements PipeTransform {
|
||||
constructor() {}
|
||||
|
||||
transform(participant: ParticipantAbstractModel): string {
|
||||
return `(${participant.getConnectionTypesEnabled().toString().replace(',', ', ')})`;
|
||||
}
|
||||
}
|
||||
|
||||
@Pipe({ name: 'nickname', pure: false })
|
||||
export class NicknamePipe implements PipeTransform {
|
||||
constructor() {}
|
||||
transform(participant: ParticipantAbstractModel): string {
|
||||
return participant.getCameraNickname();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { StreamModel, ParticipantAbstractModel } from '../models/participant.model';
|
||||
|
||||
@Pipe({ name: 'streams' })
|
||||
export class ParticipantStreamsPipe implements PipeTransform {
|
||||
constructor() {}
|
||||
|
||||
transform(participants: ParticipantAbstractModel[] | ParticipantAbstractModel): StreamModel[] {
|
||||
let streams: StreamModel[] = [];
|
||||
if(participants && Object.keys(participants).length > 0){
|
||||
if (Array.isArray(participants)) {
|
||||
participants.forEach((p) => {
|
||||
streams = streams.concat(p.getAvailableConnections());
|
||||
});
|
||||
} else {
|
||||
|
||||
streams = participants.getAvailableConnections();
|
||||
}
|
||||
}
|
||||
return streams;
|
||||
}
|
||||
}
|
||||
|
||||
@Pipe({ name: 'streamTypesEnabled' })
|
||||
export class StreamTypesEnabledPipe implements PipeTransform {
|
||||
constructor() {}
|
||||
|
||||
transform(participant: ParticipantAbstractModel): string {
|
||||
const activeStreams = participant?.getConnectionTypesActive().toString();
|
||||
return `(${activeStreams.replace(',', ', ')})`;
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
// import { Pipe, PipeTransform } from '@angular/core';
|
||||
// import { SettingsModel } from '../models/settings.model';
|
||||
|
||||
// @Pipe({ name: 'hasChat', pure: true })
|
||||
// export class HasChatPipe implements PipeTransform {
|
||||
// constructor() {}
|
||||
// transform(ovSettings: SettingsModel): boolean {
|
||||
// return !ovSettings || ovSettings.hasChat();
|
||||
// }
|
||||
// }
|
||||
|
||||
// @Pipe({ name: 'hasAudio', pure: true })
|
||||
// export class HasAudioPipe implements PipeTransform {
|
||||
// constructor() {}
|
||||
// transform(ovSettings: SettingsModel): boolean {
|
||||
// return !ovSettings || ovSettings.hasAudio();
|
||||
// }
|
||||
// }
|
||||
|
||||
// @Pipe({ name: 'hasVideo', pure: true })
|
||||
// export class HasVideoPipe implements PipeTransform {
|
||||
// constructor() {}
|
||||
// transform(ovSettings: SettingsModel): boolean {
|
||||
// return !ovSettings || ovSettings.hasVideo();
|
||||
// }
|
||||
// }
|
||||
|
||||
// @Pipe({ name: 'isAutoPublish', pure: true })
|
||||
// export class IsAutoPublishPipe implements PipeTransform {
|
||||
// constructor() {}
|
||||
// transform(ovSettings: SettingsModel): boolean {
|
||||
// return !ovSettings || ovSettings.isAutoPublish();
|
||||
// }
|
||||
// }
|
||||
|
||||
// @Pipe({ name: 'hasScreenSharing', pure: true })
|
||||
// export class HasScreenSharingPipe implements PipeTransform {
|
||||
// constructor() {}
|
||||
// transform(ovSettings: SettingsModel): boolean {
|
||||
// return !ovSettings || ovSettings.hasScreenSharing();
|
||||
// }
|
||||
// }
|
||||
|
||||
// @Pipe({ name: 'hasFullscreen', pure: true })
|
||||
// export class HasFullscreenPipe implements PipeTransform {
|
||||
// constructor() {}
|
||||
// transform(ovSettings: SettingsModel): boolean {
|
||||
// return !ovSettings || ovSettings.hasFullscreen();
|
||||
// }
|
||||
// }
|
||||
|
||||
// @Pipe({ name: 'hasLayoutSpeaking', pure: true })
|
||||
// export class HasLayoutSpeakingPipe implements PipeTransform {
|
||||
// constructor() {}
|
||||
// transform(ovSettings: SettingsModel): boolean {
|
||||
// return !ovSettings || ovSettings.hasLayoutSpeaking();
|
||||
// }
|
||||
// }
|
||||
|
||||
// @Pipe({ name: 'hasExit', pure: true })
|
||||
// export class HasExitPipe implements PipeTransform {
|
||||
// constructor() {}
|
||||
// transform(ovSettings: SettingsModel): boolean {
|
||||
// return !ovSettings || ovSettings.hasExit();
|
||||
// }
|
||||
// }
|
||||
|
||||
// @Pipe({ name: 'hasToolbar', pure: true })
|
||||
// export class HasToolbarPipe implements PipeTransform {
|
||||
// constructor() {}
|
||||
// transform(ovSettings: SettingsModel): boolean {
|
||||
// return !ovSettings || ovSettings.hasToolbar();
|
||||
// }
|
||||
// }
|
||||
|
||||
// @Pipe({ name: 'hasFooter', pure: true })
|
||||
// export class HasFooterPipe implements PipeTransform {
|
||||
// constructor() {}
|
||||
// transform(ovSettings: SettingsModel): boolean {
|
||||
// return !ovSettings || ovSettings.hasFooter();
|
||||
// }
|
||||
// }
|
|
@ -1,13 +0,0 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({ name: 'tooltipList', pure: true })
|
||||
export class TooltipListPipe implements PipeTransform {
|
||||
transform(lines: string[]): string {
|
||||
let list = '';
|
||||
lines.forEach((line) => {
|
||||
list += '• ' + line + '\n';
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ import { ChatMessage } from '../../models/chat.model';
|
|||
import { INotificationOptions } from '../../models/notification-options.model';
|
||||
|
||||
import { ActionService } from '../action/action.service';
|
||||
import { WebrtcService } from '../webrtc/webrtc.service';
|
||||
import { OpenViduService } from '../openvidu/openvidu.service';
|
||||
import { LoggerService } from '../logger/logger.service';
|
||||
import { Signal } from '../../models/signal.model';
|
||||
import { SidenavMenuService } from '../sidenav-menu/sidenav-menu.service';
|
||||
|
@ -19,27 +19,29 @@ import { MenuType } from '../../models/menu.model';
|
|||
})
|
||||
export class ChatService {
|
||||
messagesObs: Observable<ChatMessage[]>;
|
||||
|
||||
private messageSound: HTMLAudioElement;
|
||||
protected _messageList = <BehaviorSubject<ChatMessage[]>>new BehaviorSubject([]);
|
||||
protected messageList: ChatMessage[] = [];
|
||||
protected log: ILogger;
|
||||
constructor(
|
||||
protected loggerSrv: LoggerService,
|
||||
protected webrtcService: WebrtcService,
|
||||
protected openviduService: OpenViduService,
|
||||
protected participantService: ParticipantService,
|
||||
protected menuService: SidenavMenuService,
|
||||
protected actionService: ActionService
|
||||
) {
|
||||
this.log = this.loggerSrv.get('ChatService');
|
||||
this.messagesObs = this._messageList.asObservable();
|
||||
this.messageSound = new Audio('assets/audio/message_sound.mp3');
|
||||
this.messageSound.volume = 0.5;
|
||||
}
|
||||
|
||||
subscribeToChat() {
|
||||
const session = this.webrtcService.getWebcamSession();
|
||||
const session = this.openviduService.getWebcamSession();
|
||||
session.on(`signal:${Signal.CHAT}`, (event: any) => {
|
||||
const connectionId = event.from.connectionId;
|
||||
const data = JSON.parse(event.data);
|
||||
const isMyOwnConnection = this.webrtcService.isMyOwnConnection(connectionId);
|
||||
const isMyOwnConnection = this.openviduService.isMyOwnConnection(connectionId);
|
||||
this.messageList.push({
|
||||
isLocal: isMyOwnConnection,
|
||||
nickname: data.nickname,
|
||||
|
@ -52,6 +54,8 @@ export class ChatService {
|
|||
buttonActionText: 'READ'
|
||||
};
|
||||
this.launchNotification(notificationOptions);
|
||||
this.messageSound.play().catch(() => {});
|
||||
|
||||
}
|
||||
this._messageList.next(this.messageList);
|
||||
});
|
||||
|
@ -62,10 +66,10 @@ export class ChatService {
|
|||
if (message !== '' && message !== ' ') {
|
||||
const data = {
|
||||
message: message,
|
||||
nickname: this.participantService.getWebcamNickname()
|
||||
nickname: this.participantService.getMyNickname()
|
||||
};
|
||||
|
||||
this.webrtcService.sendSignal(Signal.CHAT, undefined, data);
|
||||
this.openviduService.sendSignal(Signal.CHAT, undefined, data);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { OpenViduAngularConfig } from '../../config/openvidu-angular.config';
|
||||
|
||||
@Injectable()
|
||||
export class OpenViduAngularConfigServiceMock {
|
||||
|
||||
private configuration: OpenViduAngularConfig;
|
||||
|
||||
constructor() {
|
||||
this.configuration = {production: false};
|
||||
}
|
||||
|
||||
getConfig(): OpenViduAngularConfig {
|
||||
return this.configuration;
|
||||
}
|
||||
isProduction(): boolean {
|
||||
return this.configuration?.production;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import { TestBed } from '@angular/core/testing';
|
||||
import { OpenViduAngularConfig } from '../../config/openvidu-angular.config';
|
||||
|
||||
import { OpenViduAngularConfigService } from './openvidu-angular.config.service';
|
||||
|
||||
describe('OpenViduAngularConfigService', () => {
|
||||
let service: OpenViduAngularConfigService;
|
||||
const config: OpenViduAngularConfig = { production: false };
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
OpenViduAngularConfigService,
|
||||
{provide: 'LIB_CONFIG', useValue: config}]
|
||||
});
|
||||
service = TestBed.inject(OpenViduAngularConfigService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,94 @@
|
|||
import { Inject, Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { OpenViduAngularConfig, ParticipantFactoryFunction } from '../../config/openvidu-angular.config';
|
||||
|
||||
// import { version } from '../../../../package.json';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class OpenViduAngularConfigService {
|
||||
private configuration: OpenViduAngularConfig;
|
||||
minimal = <BehaviorSubject<boolean>>new BehaviorSubject(false);
|
||||
minimalObs: Observable<boolean>;
|
||||
participantName = <BehaviorSubject<string>>new BehaviorSubject('');
|
||||
participantNameObs: Observable<string>;
|
||||
prejoin = <BehaviorSubject<boolean>>new BehaviorSubject(true);
|
||||
prejoinObs: Observable<boolean>;
|
||||
|
||||
videoMuted = <BehaviorSubject<boolean>>new BehaviorSubject(false);
|
||||
videoMutedObs: Observable<boolean>;
|
||||
audioMuted = <BehaviorSubject<boolean>>new BehaviorSubject(false);
|
||||
audioMutedObs: Observable<boolean>;
|
||||
screenshareButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
|
||||
screenshareButtonObs: Observable<boolean>;
|
||||
|
||||
fullscreenButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
|
||||
fullscreenButtonObs: Observable<boolean>;
|
||||
|
||||
leaveButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
|
||||
leaveButtonObs: Observable<boolean>;
|
||||
|
||||
participantsPanelButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
|
||||
participantsPanelButtonObs: Observable<boolean>;
|
||||
|
||||
chatPanelButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
|
||||
chatPanelButtonObs: Observable<boolean>;
|
||||
|
||||
displaySessionName = <BehaviorSubject<boolean>>new BehaviorSubject(true);
|
||||
displaySessionNameObs: Observable<boolean>;
|
||||
|
||||
displayLogo = <BehaviorSubject<boolean>>new BehaviorSubject(true);
|
||||
displayLogoObs: Observable<boolean>;
|
||||
displayParticipantName = <BehaviorSubject<boolean>>new BehaviorSubject(true);
|
||||
displayParticipantNameObs: Observable<boolean>;
|
||||
displayAudioDetection = <BehaviorSubject<boolean>>new BehaviorSubject(true);
|
||||
displayAudioDetectionObs: Observable<boolean>;
|
||||
settingsButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
|
||||
settingsButtonObs: Observable<boolean>;
|
||||
participantItemMuteButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
|
||||
participantItemMuteButtonObs: Observable<boolean>;
|
||||
|
||||
constructor(@Inject('OPENVIDU_ANGULAR_CONFIG') config: OpenViduAngularConfig) {
|
||||
this.configuration = config;
|
||||
console.log(this.configuration);
|
||||
if(this.isProduction()) console.log('OpenVidu Angular Production Mode');
|
||||
this.minimalObs = this.minimal.asObservable();
|
||||
this.participantNameObs = this.participantName.asObservable();
|
||||
this.prejoinObs = this.prejoin.asObservable();
|
||||
this.videoMutedObs = this.videoMuted.asObservable();
|
||||
this.audioMutedObs = this.audioMuted.asObservable();
|
||||
//Toolbar observables
|
||||
this.screenshareButtonObs = this.screenshareButton.asObservable();
|
||||
this.fullscreenButtonObs = this.fullscreenButton.asObservable();
|
||||
this.leaveButtonObs = this.leaveButton.asObservable();
|
||||
this.participantsPanelButtonObs = this.participantsPanelButton.asObservable();
|
||||
this.chatPanelButtonObs = this.chatPanelButton.asObservable();
|
||||
this.displaySessionNameObs = this.displaySessionName.asObservable();
|
||||
this.displayLogoObs = this.displayLogo.asObservable();
|
||||
//Stream observables
|
||||
this.displayParticipantNameObs = this.displayParticipantName.asObservable();
|
||||
this.displayAudioDetectionObs = this.displayAudioDetection.asObservable();
|
||||
this.settingsButtonObs = this.settingsButton.asObservable();
|
||||
// Participant item observables
|
||||
this.participantItemMuteButtonObs = this.participantItemMuteButton.asObservable();
|
||||
}
|
||||
|
||||
getConfig(): OpenViduAngularConfig {
|
||||
return this.configuration;
|
||||
}
|
||||
isProduction(): boolean {
|
||||
return this.configuration?.production;
|
||||
}
|
||||
|
||||
// isWebcomponent(): boolean {
|
||||
// return this.configuration?.webcomponent;
|
||||
// }
|
||||
|
||||
hasParticipantFactory(): boolean {
|
||||
return typeof this.getConfig().participantFactory === "function";
|
||||
}
|
||||
|
||||
getParticipantFactory(): ParticipantFactoryFunction {
|
||||
return this.getConfig().participantFactory;
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Device, OpenVidu } from 'openvidu-browser';
|
||||
import { Device, OpenVidu, OpenViduError, OpenViduErrorName } from 'openvidu-browser';
|
||||
|
||||
import { CameraType, DeviceType, CustomDevice } from '../../models/device.model';
|
||||
import { ILogger } from '../../models/logger.model';
|
||||
import { Storage } from '../../models/storage.model';
|
||||
import { OpenViduAngularConfigService } from '../config/openvidu-angular.config.service';
|
||||
|
||||
import { LoggerService } from '../logger/logger.service';
|
||||
import { PlatformService } from '../platform/platform.service';
|
||||
|
@ -23,7 +23,18 @@ export class DeviceService {
|
|||
private videoDevicesDisabled: boolean;
|
||||
private audioDevicesDisabled: boolean;
|
||||
|
||||
constructor(private loggerSrv: LoggerService, private platformSrv: PlatformService, private storageSrv: StorageService) {
|
||||
// Initialized with Storage.VIDEO_MUTED info saved on storage
|
||||
private _isVideoMuted: boolean;
|
||||
// Initialized with Storage.AUDIO_MUTED info saved on storage
|
||||
private _isAudioMuted: boolean;
|
||||
private deviceAccessDeniedError: boolean = false;
|
||||
|
||||
constructor(
|
||||
private loggerSrv: LoggerService,
|
||||
private platformSrv: PlatformService,
|
||||
private storageSrv: StorageService,
|
||||
private libSrv: OpenViduAngularConfigService
|
||||
) {
|
||||
this.log = this.loggerSrv.get('DevicesService');
|
||||
this.OV = new OpenVidu();
|
||||
}
|
||||
|
@ -33,44 +44,57 @@ export class DeviceService {
|
|||
}
|
||||
|
||||
async initializeDevices() {
|
||||
// Requesting media permissions. Sometimes, browser doens't launch the media permissions modal.
|
||||
const mediaStream = await this.OV.getUserMedia({audioSource: undefined, videoSource: undefined });
|
||||
try {
|
||||
// Forcing media permissions request.
|
||||
// Sometimes, browser doens't launch the media permissions modal.
|
||||
const mediaStream = await this.OV.getUserMedia({ audioSource: undefined, videoSource: undefined });
|
||||
mediaStream?.getAudioTracks().forEach((track) => track.stop());
|
||||
mediaStream?.getVideoTracks().forEach((track) => track.stop());
|
||||
} catch (error) {
|
||||
this.deviceAccessDeniedError = (<OpenViduError>error).name === OpenViduErrorName.DEVICE_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
this.devices = await this.OV.getDevices();
|
||||
const customDevices = this.initializeCustomDevices(this.devices);
|
||||
this.cameras = customDevices.cameras;
|
||||
this.microphones = customDevices.microphones;
|
||||
|
||||
mediaStream?.getAudioTracks().forEach((track) => track.stop());
|
||||
mediaStream?.getVideoTracks().forEach((track) => track.stop());
|
||||
this._isVideoMuted = this.storageSrv.isVideoMuted() || this.libSrv.videoMuted.getValue();
|
||||
this._isAudioMuted = this.storageSrv.isAudioMuted() || this.libSrv.audioMuted.getValue();;
|
||||
|
||||
this.log.d('Media devices',customDevices);
|
||||
this.log.d('Media devices', customDevices);
|
||||
}
|
||||
|
||||
private initializeCustomDevices(defaultVDevices: Device[]) {
|
||||
const FIRST_POSITION = 0;
|
||||
const defaultMicrophones = defaultVDevices.filter((device) => device.kind === DeviceType.AUDIO_INPUT);
|
||||
const defaultCameras = defaultVDevices.filter((device) => device.kind === DeviceType.VIDEO_INPUT);
|
||||
const defaultMicrophones: Device[] = defaultVDevices.filter((device) => device.kind === DeviceType.AUDIO_INPUT);
|
||||
const defaultCameras: Device[] = defaultVDevices.filter((device) => device.kind === DeviceType.VIDEO_INPUT);
|
||||
const customDevices: { cameras: CustomDevice[]; microphones: CustomDevice[] } = {
|
||||
cameras: [{ label: 'None', device: null, type: null }],
|
||||
microphones: [{ label: 'None', device: null, type: null }]
|
||||
cameras: [],
|
||||
microphones: []
|
||||
};
|
||||
|
||||
if (this.hasAudioDeviceAvailable) {
|
||||
if (defaultMicrophones.length > 0) {
|
||||
defaultMicrophones.forEach((device: Device) => {
|
||||
customDevices.microphones.push({ label: device.label, device: device.deviceId });
|
||||
});
|
||||
|
||||
// Settings microphone selected
|
||||
// Setting microphone selected
|
||||
const storageMicrophone = this.getMicrophoneFromStogare();
|
||||
if (!!storageMicrophone) {
|
||||
this.microphoneSelected = storageMicrophone;
|
||||
} else if (customDevices.microphones.length > 0) {
|
||||
this.microphoneSelected = customDevices.microphones.find((d) => d.label !== 'None');
|
||||
if (this.deviceAccessDeniedError && customDevices.microphones.length > 1) {
|
||||
// We assume that the default device is already in use
|
||||
// Assign an alternative device with the aim of avoiding the DEVICE_ALREADY_IN_USE error
|
||||
this.microphoneSelected = customDevices.microphones[1];
|
||||
} else {
|
||||
this.microphoneSelected = customDevices.microphones[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.hasVideoDeviceAvailable) {
|
||||
if (defaultCameras.length > 0) {
|
||||
defaultCameras.forEach((device: Device, index: number) => {
|
||||
const myDevice: CustomDevice = {
|
||||
label: device.label,
|
||||
|
@ -96,12 +120,26 @@ export class DeviceService {
|
|||
if (!!storageCamera) {
|
||||
this.cameraSelected = storageCamera;
|
||||
} else if (customDevices.cameras.length > 0) {
|
||||
this.cameraSelected = customDevices.cameras.find((d) => d.label !== 'None');
|
||||
if (this.deviceAccessDeniedError && customDevices.cameras.length > 1) {
|
||||
// We assume that the default device is already in use
|
||||
// Assign an alternative device with the aim of avoiding the DEVICE_ALREADY_IN_USE error
|
||||
this.cameraSelected = customDevices.cameras[1];
|
||||
} else {
|
||||
this.cameraSelected = customDevices.cameras[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
return customDevices;
|
||||
}
|
||||
|
||||
isVideoMuted(): boolean {
|
||||
return this.hasVideoDeviceAvailable() && this._isVideoMuted;
|
||||
}
|
||||
|
||||
isAudioMuted(): boolean {
|
||||
return this.hasAudioDeviceAvailable() && this._isAudioMuted;
|
||||
}
|
||||
|
||||
getCameraSelected(): CustomDevice {
|
||||
return this.cameraSelected;
|
||||
}
|
||||
|
@ -137,11 +175,11 @@ export class DeviceService {
|
|||
}
|
||||
|
||||
hasVideoDeviceAvailable(): boolean {
|
||||
return !this.videoDevicesDisabled && !this.cameras.every((device) => device.label === 'None');
|
||||
return !this.videoDevicesDisabled && this.cameras.length > 0;
|
||||
}
|
||||
|
||||
hasAudioDeviceAvailable(): boolean {
|
||||
return !this.audioDevicesDisabled && !this.microphones.every((device) => device.label === 'None');
|
||||
return !this.audioDevicesDisabled && this.microphones.length > 0;
|
||||
}
|
||||
|
||||
cameraNeedsMirror(deviceField: string): boolean {
|
||||
|
@ -161,7 +199,7 @@ export class DeviceService {
|
|||
}
|
||||
|
||||
clear() {
|
||||
this.OV = new OpenVidu();
|
||||
// this.OV = new OpenVidu();
|
||||
this.devices = [];
|
||||
this.cameras = [];
|
||||
this.microphones = [];
|
||||
|
@ -180,24 +218,24 @@ export class DeviceService {
|
|||
}
|
||||
|
||||
private getMicrophoneFromStogare(): CustomDevice {
|
||||
let storageDevice = this.storageSrv.get(Storage.AUDIO_DEVICE);
|
||||
if (!!storageDevice) {
|
||||
const storageDevice: CustomDevice = this.storageSrv.getAudioDevice();
|
||||
if (!!storageDevice && this.microphones.some((device) => device.device === storageDevice.device)) {
|
||||
return storageDevice;
|
||||
}
|
||||
}
|
||||
|
||||
private getCameraFromStorage() {
|
||||
let storageDevice = this.storageSrv.get(Storage.VIDEO_DEVICE);
|
||||
if (!!storageDevice) {
|
||||
const storageDevice: CustomDevice = this.storageSrv.getVideoDevice();
|
||||
if (!!storageDevice && this.cameras.some((device) => device.device === storageDevice.device)) {
|
||||
return storageDevice;
|
||||
}
|
||||
}
|
||||
|
||||
private saveCameraToStorage(cam: CustomDevice) {
|
||||
this.storageSrv.set(Storage.VIDEO_DEVICE, cam);
|
||||
this.storageSrv.setVideoDevice(cam);
|
||||
}
|
||||
|
||||
private saveMicrophoneToStorage(mic: CustomDevice) {
|
||||
this.storageSrv.set(Storage.AUDIO_DEVICE, mic);
|
||||
this.storageSrv.setAudioDevice(mic);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,23 +48,8 @@ export class DocumentService {
|
|||
}
|
||||
}
|
||||
|
||||
removeAllBigElementClass() {
|
||||
const elements: HTMLCollectionOf<Element> = document.getElementsByClassName(LayoutClass.BIG_ELEMENT);
|
||||
while (elements.length > 0) {
|
||||
this.removeBigElementClass(elements[0]);
|
||||
}
|
||||
}
|
||||
|
||||
removeBigElementClass(element: HTMLElement | Element) {
|
||||
element?.classList.remove(LayoutClass.BIG_ELEMENT);
|
||||
}
|
||||
|
||||
toggleBigElementClass(element: HTMLElement | Element) {
|
||||
if (element?.className.includes(LayoutClass.BIG_ELEMENT)) {
|
||||
this.removeBigElementClass(element);
|
||||
} else {
|
||||
element.classList.add(LayoutClass.BIG_ELEMENT);
|
||||
}
|
||||
removeNoSizeElementClass(element: HTMLElement | Element) {
|
||||
element?.classList.remove(LayoutClass.NO_SIZE_ELEMENT);
|
||||
}
|
||||
|
||||
isSmallElement(element: HTMLElement | Element): boolean {
|
||||
|
|
|
@ -17,50 +17,53 @@ export class LayoutService {
|
|||
}
|
||||
|
||||
initialize(timeout: number = null) {
|
||||
if (!!timeout) {
|
||||
if (typeof timeout === 'number' && timeout >= 0) {
|
||||
setTimeout(() => {
|
||||
this._initialize();
|
||||
this.sendLayoutWidthEvent();
|
||||
}, timeout);
|
||||
} else {
|
||||
this._initialize();
|
||||
this.sendLayoutWidthEvent();
|
||||
}
|
||||
this.sendLayoutWidthEvent();
|
||||
}
|
||||
|
||||
private _initialize() {
|
||||
this.openviduLayout = new OpenViduLayout();
|
||||
this.openviduLayoutOptions = this.getOptions();
|
||||
this.openviduLayout.initLayoutContainer(document.getElementById('layout'), this.openviduLayoutOptions);
|
||||
const element = document.getElementById('layout');
|
||||
this.openviduLayout.initLayoutContainer(element, this.openviduLayoutOptions);
|
||||
}
|
||||
|
||||
getOptions(): OpenViduLayoutOptions {
|
||||
private getOptions(): OpenViduLayoutOptions {
|
||||
const options = {
|
||||
maxRatio: 3 / 2, // The narrowest ratio that will be used (default 2x3)
|
||||
minRatio: 9 / 15, // The widest ratio that will be used (default 16x9)
|
||||
minRatio: 9 / 16, // The widest ratio that will be used (default 16x9)
|
||||
fixedRatio: false /* If this is true then the aspect ratio of the video is maintained
|
||||
and minRatio and maxRatio are ignored (default false) */,
|
||||
bigClass: LayoutClass.BIG_ELEMENT, // The class to add to elements that should be sized bigger
|
||||
smallClass: LayoutClass.SMALL_ELEMENT,
|
||||
bigPercentage: 0.85, // The maximum percentage of space the big ones should take up
|
||||
bigPercentage: 0.7, // The maximum percentage of space the big ones should take up
|
||||
bigFixedRatio: false, // fixedRatio for the big ones
|
||||
bigMaxRatio: 3 / 2, // The narrowest ratio to use for the big elements (default 2x3)
|
||||
bigMaxRatio: 9 / 16, // The narrowest ratio to use for the big elements (default 2x3)
|
||||
bigMinRatio: 9 / 16, // The widest ratio to use for the big elements (default 16x9)
|
||||
bigFirst: true, // Whether to place the big one in the top left (true) or bottom right
|
||||
animate: true // Whether you want to animate the transitions. Invalid property, to disable it remove transition: all .1s linear;
|
||||
animate: true // Whether you want to animate the transitions. Deprecated property, to disable it remove the transaction property on OT_publisher css class
|
||||
};
|
||||
return options;
|
||||
}
|
||||
|
||||
update(timeout?: number) {
|
||||
if (!!this.openviduLayout) {
|
||||
if (!timeout) {
|
||||
update(timeout: number = null) {
|
||||
const updateAux = () => {
|
||||
if (!!this.openviduLayout) {
|
||||
this.openviduLayout.updateLayout();
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this.openviduLayout.updateLayout();
|
||||
}, timeout);
|
||||
this.sendLayoutWidthEvent();
|
||||
}
|
||||
this.sendLayoutWidthEvent();
|
||||
};
|
||||
if (typeof timeout === 'number' && timeout >= 0) {
|
||||
setTimeout(() => updateAux(), timeout);
|
||||
} else {
|
||||
updateAux();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,10 +77,10 @@ export class LayoutService {
|
|||
|
||||
private sendLayoutWidthEvent() {
|
||||
const sidenavLayoutElement = this.documentService.getHTMLElementByClassName(
|
||||
this.openviduLayout.getLayoutContainer(),
|
||||
this.openviduLayout?.getLayoutContainer(),
|
||||
LayoutClass.SIDENAV_CONTAINER
|
||||
);
|
||||
if(sidenavLayoutElement && sidenavLayoutElement.clientWidth) {
|
||||
if (sidenavLayoutElement && sidenavLayoutElement.clientWidth) {
|
||||
this._layoutWidthObs.next(sidenavLayoutElement.clientWidth);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { LibConfig } from '../../config/lib.config';
|
||||
|
||||
@Injectable()
|
||||
export class LibraryConfigServiceMock {
|
||||
|
||||
private configuration: LibConfig;
|
||||
|
||||
constructor() {
|
||||
this.configuration = {environment: {production: false}};
|
||||
}
|
||||
|
||||
getConfig(): LibConfig {
|
||||
return this.configuration;
|
||||
}
|
||||
isProduction(): boolean {
|
||||
return this.configuration?.environment?.production;
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
import { TestBed } from '@angular/core/testing';
|
||||
import { LibConfig } from '../../config/lib.config';
|
||||
|
||||
import { LibraryConfigService } from './library-config.service';
|
||||
|
||||
describe('LibraryConfigService', () => {
|
||||
let service: LibraryConfigService;
|
||||
const libConfig: LibConfig = { environment: {production: false} };
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
LibraryConfigService,
|
||||
{provide: 'LIB_CONFIG', useValue: libConfig}]
|
||||
});
|
||||
service = TestBed.inject(LibraryConfigService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -1,91 +0,0 @@
|
|||
import { Inject, Injectable, Type } from '@angular/core';
|
||||
import { LayoutComponent } from '../../components/layout/layout.component';
|
||||
import { ChatPanelComponent } from '../../components/panel/chat-panel/chat-panel.component';
|
||||
import { PanelComponent } from '../../components/panel/panel.component';
|
||||
import { ParticipantsPanelComponent } from '../../components/panel/participants-panel/participants-panel/participants-panel.component';
|
||||
import { StreamComponent } from '../../components/stream/stream.component';
|
||||
import { ToolbarComponent } from '../../components/toolbar/toolbar.component';
|
||||
import { LibConfig, LibraryComponents } from '../../config/lib.config';
|
||||
|
||||
// import { version } from '../../../../package.json';
|
||||
|
||||
@Injectable()
|
||||
export class LibraryConfigService {
|
||||
|
||||
private configuration: LibConfig;
|
||||
|
||||
constructor(@Inject('LIB_CONFIG') config: LibConfig) {
|
||||
this.configuration = config;
|
||||
console.log(this.configuration);
|
||||
if(this.isProduction()) console.log('Production Mode');
|
||||
// console.log(version)
|
||||
}
|
||||
|
||||
getConfig(): LibConfig {
|
||||
return this.configuration;
|
||||
}
|
||||
isProduction(): boolean {
|
||||
return this.configuration?.environment?.production;
|
||||
}
|
||||
|
||||
getDynamicComponent(name: LibraryComponents) {
|
||||
let component: Type<any>;
|
||||
|
||||
switch (name) {
|
||||
case LibraryComponents.LAYOUT:
|
||||
component = LayoutComponent;
|
||||
if (this.isCustomComponentDefined(LibraryComponents.LAYOUT)) {
|
||||
component = this.getCustomComponent(LibraryComponents.LAYOUT);
|
||||
}
|
||||
return component;
|
||||
|
||||
case LibraryComponents.TOOLBAR:
|
||||
|
||||
component = ToolbarComponent;
|
||||
if (this.isCustomComponentDefined(LibraryComponents.TOOLBAR)) {
|
||||
component = this.getCustomComponent(LibraryComponents.TOOLBAR);
|
||||
}
|
||||
return component;
|
||||
|
||||
case LibraryComponents.PANEL:
|
||||
|
||||
component = PanelComponent;
|
||||
// Full custom panel
|
||||
// if (this.isCustomComponentDefined(LibraryComponents.PANEL)) {
|
||||
// component = this.getCustomComponent(LibraryComponents.PANEL);
|
||||
// }
|
||||
return component;
|
||||
|
||||
case LibraryComponents.CHAT_PANEL:
|
||||
|
||||
component = ChatPanelComponent;
|
||||
if (this.isCustomComponentDefined(LibraryComponents.CHAT_PANEL)) {
|
||||
component = this.getCustomComponent(LibraryComponents.CHAT_PANEL);
|
||||
}
|
||||
return component;
|
||||
|
||||
case LibraryComponents.PARTICIPANTS_PANEL:
|
||||
component = ParticipantsPanelComponent;
|
||||
if (this.isCustomComponentDefined(LibraryComponents.PARTICIPANTS_PANEL)) {
|
||||
component = this.getCustomComponent(LibraryComponents.PARTICIPANTS_PANEL);
|
||||
}
|
||||
return component;
|
||||
|
||||
case LibraryComponents.STREAM:
|
||||
component = StreamComponent;
|
||||
if (this.isCustomComponentDefined(LibraryComponents.STREAM)) {
|
||||
component = this.getCustomComponent(LibraryComponents.STREAM);
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
isCustomComponentDefined(component: string): boolean {
|
||||
return !!this.configuration?.environment?.customComponents && !!this.configuration.environment.customComponents[component];
|
||||
}
|
||||
|
||||
getCustomComponent(component: string){
|
||||
return this.configuration.environment.customComponents[component];
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { ILogService } from '../../models/logger.model';
|
||||
|
||||
import { LibraryConfigService } from '../library-config/library-config.service';
|
||||
import { OpenViduAngularConfigService } from '../config/openvidu-angular.config.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
@ -15,7 +15,7 @@ export class LoggerService implements ILogService {
|
|||
['[', '] ERROR: ']
|
||||
];
|
||||
|
||||
constructor(private libraryConfigSrv: LibraryConfigService) {
|
||||
constructor(private openviduAngularConfigSrv: OpenViduAngularConfigService) {
|
||||
}
|
||||
|
||||
private getLoggerFns(prefix: string) {
|
||||
|
@ -28,7 +28,7 @@ export class LoggerService implements ILogService {
|
|||
}
|
||||
|
||||
public get(prefix: string) {
|
||||
const prodMode = this.libraryConfigSrv.isProduction();
|
||||
const prodMode = this.openviduAngularConfigSrv.isProduction();
|
||||
const loggerService = this;
|
||||
return {
|
||||
d: function(...args: any[]) {
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Signal } from '../../models/signal.model';
|
|||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class WebrtcServiceMock {
|
||||
export class OpenViduServiceMock {
|
||||
private OV: OpenVidu = null;
|
||||
private OVScreen: OpenVidu = null;
|
||||
|
|
@ -4,15 +4,15 @@ import { LoggerServiceMock } from '../logger/logger.service.mock';
|
|||
|
||||
import { LocalUserService } from '../local-user/local-user.service';
|
||||
import { LocalUserServiceMock } from '../local-user/local-user.service.mock';
|
||||
import { WebrtcService } from './webrtc.service';
|
||||
import { OpenViduService } from './openvidu.service';
|
||||
import { PlatformService } from '../platform/platform.service';
|
||||
import { PlatformServiceMock } from '../platform/platform.service.mock';
|
||||
import { LibraryConfigService } from '../library-config/library-config.service';
|
||||
import { LibraryConfigServiceMock } from '../library-config/library-config.service.mock';
|
||||
|
||||
|
||||
describe('WebrtcService', () => {
|
||||
let service: WebrtcService;
|
||||
describe('OpenViduService', () => {
|
||||
let service: OpenViduService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
|
@ -23,7 +23,7 @@ describe('WebrtcService', () => {
|
|||
{ provide: LibraryConfigService, useClass: LibraryConfigServiceMock }
|
||||
]
|
||||
});
|
||||
service = TestBed.inject(WebrtcService);
|
||||
service = TestBed.inject(OpenViduService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
|
@ -1,11 +1,20 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Connection, OpenVidu, Publisher, PublisherProperties, Session, SignalOptions } from 'openvidu-browser';
|
||||
import {
|
||||
Connection,
|
||||
OpenVidu,
|
||||
OpenViduError,
|
||||
OpenViduErrorName,
|
||||
Publisher,
|
||||
PublisherProperties,
|
||||
Session,
|
||||
SignalOptions
|
||||
} from 'openvidu-browser';
|
||||
|
||||
import { LoggerService } from '../../services/logger/logger.service';
|
||||
import { LoggerService } from '../logger/logger.service';
|
||||
|
||||
import { ILogger } from '../../models/logger.model';
|
||||
import { Signal } from '../../models/signal.model';
|
||||
import { LibraryConfigService } from '../library-config/library-config.service';
|
||||
import { OpenViduAngularConfigService } from '../config/openvidu-angular.config.service';
|
||||
import { PlatformService } from '../platform/platform.service';
|
||||
import { DeviceService } from '../device/device.service';
|
||||
import { CameraType } from '../../models/device.model';
|
||||
|
@ -15,40 +24,42 @@ import { ParticipantService } from '../participant/participant.service';
|
|||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class WebrtcService {
|
||||
export class OpenViduService {
|
||||
protected OV: OpenVidu = null;
|
||||
protected OVScreen: OpenVidu = null;
|
||||
protected webcamSession: Session = null;
|
||||
protected screenSession: Session = null;
|
||||
protected videoSource = undefined;
|
||||
protected audioSource = undefined;
|
||||
protected screenMediaStream: MediaStream = null;
|
||||
protected webcamMediaStream: MediaStream = null;
|
||||
protected log: ILogger;
|
||||
|
||||
constructor(
|
||||
protected libraryConfigSrv: LibraryConfigService,
|
||||
protected openviduAngularConfigSrv: OpenViduAngularConfigService,
|
||||
protected platformService: PlatformService,
|
||||
protected loggerSrv: LoggerService,
|
||||
private participantService: ParticipantService,
|
||||
protected deviceService: DeviceService
|
||||
) {
|
||||
this.log = this.loggerSrv.get('WebRTCService');
|
||||
this.log = this.loggerSrv.get('OpenViduService');
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.OV = new OpenVidu();
|
||||
if (this.libraryConfigSrv.isProduction()) this.OV.enableProdMode();
|
||||
if (this.openviduAngularConfigSrv.isProduction()) this.OV.enableProdMode();
|
||||
this.webcamSession = this.OV.initSession();
|
||||
|
||||
// Initialize screen session only if it is not mobile platform
|
||||
if (!this.platformService.isMobile()) {
|
||||
this.OVScreen = new OpenVidu();
|
||||
if (this.libraryConfigSrv.isProduction()) this.OVScreen.enableProdMode();
|
||||
if (this.openviduAngularConfigSrv.isProduction()) this.OVScreen.enableProdMode();
|
||||
this.screenSession = this.OVScreen.initSession();
|
||||
}
|
||||
}
|
||||
|
||||
getSession(): Session {
|
||||
return this.getWebcamSession();
|
||||
}
|
||||
|
||||
getWebcamSession(): Session {
|
||||
return this.webcamSession;
|
||||
}
|
||||
|
@ -67,7 +78,7 @@ export class WebrtcService {
|
|||
|
||||
async connectSession(session: Session, token: string): Promise<void> {
|
||||
if (!!token && session) {
|
||||
const nickname = this.participantService.getWebcamNickname();
|
||||
const nickname = this.participantService.getMyNickname();
|
||||
const participantId = this.participantService.getMyParticipantId();
|
||||
if (session === this.webcamSession) {
|
||||
this.log.d('Connecting webcam session');
|
||||
|
@ -90,60 +101,43 @@ export class WebrtcService {
|
|||
}
|
||||
}
|
||||
|
||||
private disconnectSession(session: Session) {
|
||||
if (session) {
|
||||
if (session.sessionId === this.webcamSession?.sessionId) {
|
||||
this.log.d('Disconnecting webcam session');
|
||||
this.webcamSession?.disconnect();
|
||||
this.webcamSession = null;
|
||||
} else if (session.sessionId === this.screenSession?.sessionId) {
|
||||
this.log.d('Disconnecting screen session');
|
||||
this.screenSession?.disconnect();
|
||||
this.screenSession = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.disconnectSession(this.webcamSession);
|
||||
this.disconnectSession(this.screenSession);
|
||||
this.videoSource = undefined;
|
||||
this.audioSource = undefined;
|
||||
this.stopTracks(this.participantService.getMyCameraPublisher()?.stream?.getMediaStream());
|
||||
this.stopTracks(this.participantService.getMyScreenPublisher()?.stream?.getMediaStream());
|
||||
// this.stopTracks(this.participantService.getMyCameraPublisher()?.stream?.getMediaStream());
|
||||
// this.stopTracks(this.participantService.getMyScreenPublisher()?.stream?.getMediaStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a publisher checking devices saved on storage or if participant have devices available.
|
||||
*/
|
||||
async initDefaultPublisher(targetElement: string | HTMLElement): Promise<Publisher> {
|
||||
await this.deviceService.initializeDevices();
|
||||
|
||||
const hasVideoDevices = this.deviceService.hasVideoDeviceAvailable();
|
||||
const hasAudioDevices = this.deviceService.hasAudioDeviceAvailable();
|
||||
const isVideoActive = hasVideoDevices && this.deviceService.getCameraSelected().label !== 'None';
|
||||
const isAudioActive = hasAudioDevices && this.deviceService.getMicrophoneSelected().label !== 'None';
|
||||
const isVideoActive = !this.deviceService.isVideoMuted();
|
||||
const isAudioActive = !this.deviceService.isAudioMuted();
|
||||
|
||||
let videoSource = null;
|
||||
let audioSource = null;
|
||||
|
||||
if(isVideoActive){
|
||||
if (hasVideoDevices) {
|
||||
// Video is active, assign the device selected
|
||||
videoSource = this.deviceService.getCameraSelected().device
|
||||
} else if(!isVideoActive && hasVideoDevices) {
|
||||
videoSource = this.deviceService.getCameraSelected().device;
|
||||
} else if (!isVideoActive && hasVideoDevices) {
|
||||
// Video is muted, assign the default device
|
||||
videoSource = undefined;
|
||||
// videoSource = undefined;
|
||||
}
|
||||
|
||||
if(isAudioActive){
|
||||
if (hasAudioDevices) {
|
||||
// Audio is active, assign the device selected
|
||||
audioSource = this.deviceService.getMicrophoneSelected().device
|
||||
} else if(!isAudioActive && hasAudioDevices) {
|
||||
audioSource = this.deviceService.getMicrophoneSelected().device;
|
||||
} else if (!isAudioActive && hasAudioDevices) {
|
||||
// Audio is muted, assign the default device
|
||||
audioSource = undefined;
|
||||
// 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;
|
||||
|
@ -154,40 +148,19 @@ export class WebrtcService {
|
|||
publishAudio: isAudioActive,
|
||||
mirror
|
||||
};
|
||||
if (isVideoActive || isAudioActive) {
|
||||
const publisher = this.initPublisher(targetElement, properties);
|
||||
if (hasVideoDevices || hasAudioDevices) {
|
||||
const publisher = await this.initPublisher(targetElement, properties);
|
||||
this.participantService.setMyCameraPublisher(publisher);
|
||||
this.participantService.updateLocalParticipant();
|
||||
return publisher;
|
||||
} else {
|
||||
this.participantService.setMyCameraPublisher(null);
|
||||
}
|
||||
}
|
||||
|
||||
initPublisher(targetElement: string | HTMLElement, properties: PublisherProperties): Publisher {
|
||||
async initPublisher(targetElement: string | HTMLElement, properties: PublisherProperties): Promise<Publisher> {
|
||||
this.log.d('Initializing publisher with properties: ', properties);
|
||||
|
||||
const publisher = this.OV.initPublisher(targetElement, properties);
|
||||
// this.participantService.setMyCameraPublisher(publisher);
|
||||
publisher.once('streamPlaying', () => {
|
||||
(<HTMLElement>publisher.videos[0].video).parentElement.classList.remove('custom-class');
|
||||
});
|
||||
return publisher;
|
||||
}
|
||||
|
||||
//TODO: This method is used by replaceTrack. Check if it's neecessary
|
||||
private destroyPublisher(publisher: Publisher): void {
|
||||
if (!!publisher) {
|
||||
// publisher.off('streamAudioVolumeChange');
|
||||
if (publisher.stream.getWebRtcPeer()) {
|
||||
publisher.stream.disposeWebRtcPeer();
|
||||
}
|
||||
publisher.stream.disposeMediaStream();
|
||||
if (publisher.id === this.participantService.getMyCameraPublisher().id) {
|
||||
this.participantService.setMyCameraPublisher(publisher);
|
||||
} else if (publisher.id === this.participantService.getMyScreenPublisher().id) {
|
||||
this.participantService.setMyScreenPublisher(publisher);
|
||||
}
|
||||
}
|
||||
return await this.OV.initPublisherAsync(targetElement, properties);
|
||||
}
|
||||
|
||||
async publish(publisher: Publisher): Promise<void> {
|
||||
|
@ -220,30 +193,29 @@ export class WebrtcService {
|
|||
publishVideo(publisher: Publisher, value: boolean): void {
|
||||
if (!!publisher) {
|
||||
publisher.publishVideo(value);
|
||||
// Send event to subscribers because of video has changed and view must update
|
||||
this.participantService.updateUsersStatus();
|
||||
this.participantService.updateLocalParticipant();
|
||||
}
|
||||
}
|
||||
|
||||
publishAudio(publisher: Publisher, value: boolean): void {
|
||||
if (!!publisher) {
|
||||
publisher.publishAudio(value);
|
||||
// Send event to subscribers because of video has changed and view must update
|
||||
this.participantService.updateUsersStatus();
|
||||
this.participantService.updateLocalParticipant();
|
||||
}
|
||||
}
|
||||
|
||||
republishTrack(videoSource: string, audioSource: string, mirror: boolean = true): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// TODO: Remove this method when replaceTrack issue is fixed
|
||||
// https://github.com/OpenVidu/openvidu/pull/700
|
||||
republishTrack(properties: PublisherProperties): Promise<void> {
|
||||
const {videoSource, audioSource, mirror} = properties;
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (!!videoSource) {
|
||||
this.log.d('Replacing video track ' + videoSource);
|
||||
this.videoSource = videoSource;
|
||||
// this.stopVideoTracks(this.webcamUser.getStreamManager().stream.getMediaStream());
|
||||
}
|
||||
if (!!audioSource) {
|
||||
this.log.d('Replacing audio track ' + audioSource);
|
||||
this.audioSource = audioSource;
|
||||
// this.stopAudioTracks(this.webcamUser.getStreamManager().stream.getMediaStream());
|
||||
}
|
||||
this.destroyPublisher(this.participantService.getMyCameraPublisher());
|
||||
const properties: PublisherProperties = {
|
||||
|
@ -254,7 +226,7 @@ export class WebrtcService {
|
|||
mirror
|
||||
};
|
||||
|
||||
const publisher = this.initPublisher(undefined, properties);
|
||||
const publisher = await this.initPublisher(undefined, properties);
|
||||
this.participantService.setMyCameraPublisher(publisher);
|
||||
|
||||
publisher.once('streamPlaying', () => {
|
||||
|
@ -276,33 +248,56 @@ export class WebrtcService {
|
|||
};
|
||||
this.webcamSession.signal(signalOptions);
|
||||
|
||||
if (type === Signal.NICKNAME_CHANGED && !!this.getScreenSession().connection) {
|
||||
signalOptions.data = JSON.stringify({ clientData: this.participantService.getScreenNickname() });
|
||||
this.getScreenSession()?.signal(signalOptions);
|
||||
}
|
||||
// TODO: Check if it is necessary
|
||||
// if (type === Signal.NICKNAME_CHANGED && !!this.getScreenSession().connection) {
|
||||
// signalOptions.data = JSON.stringify({ clientData: this.participantService.getScreenNickname() });
|
||||
// this.getScreenSession()?.signal(signalOptions);
|
||||
// }
|
||||
}
|
||||
|
||||
async replaceTrack(currentPublisher: Publisher, newProperties: PublisherProperties) {
|
||||
async replaceTrack(videoType: VideoType, props: PublisherProperties) {
|
||||
try {
|
||||
if (!!currentPublisher) {
|
||||
if (currentPublisher === this.participantService.getMyCameraPublisher()) {
|
||||
// This branch has been disabled because echo problems replacing audio track.
|
||||
// this.stopAudioTracks(this.webcamMediaStream);
|
||||
// this.stopVideoTracks(this.webcamMediaStream);
|
||||
// this.webcamMediaStream = await this.OV.getUserMedia(newProperties);
|
||||
// const videoTrack: MediaStreamTrack = this.webcamMediaStream.getVideoTracks()[0];
|
||||
// const audioTrack: MediaStreamTrack = this.webcamMediaStream.getAudioTracks()[0];
|
||||
// if(!!videoTrack){
|
||||
// await this.participantService.getMyCameraPublisher().replaceTrack(videoTrack);
|
||||
// }
|
||||
// if(!!audioTrack) {
|
||||
// await this.participantService.getMyCameraPublisher().replaceTrack(audioTrack);
|
||||
// }
|
||||
} else if (currentPublisher === this.participantService.getMyScreenPublisher()) {
|
||||
const newScreenMediaStream = await this.OVScreen.getUserMedia(newProperties);
|
||||
this.stopTracks(this.screenMediaStream);
|
||||
this.log.d(`Replacing ${videoType} track`, props);
|
||||
|
||||
if (videoType === VideoType.CAMERA) {
|
||||
//TODO: Uncomment this section when replaceTrack issue is fixed
|
||||
// https://github.com/OpenVidu/openvidu/pull/700
|
||||
throw('Replace track feature has a bug. We are trying to fix it');
|
||||
// let mediaStream: MediaStream;
|
||||
// const oldMediaStream = this.participantService.getMyCameraPublisher().stream.getMediaStream();
|
||||
// const isFirefoxPlatform = this.platformService.isFirefox();
|
||||
// const isReplacingAudio = !!props.audioSource;
|
||||
// const isReplacingVideo = !!props.videoSource;
|
||||
|
||||
// if (isReplacingVideo) {
|
||||
// if (isFirefoxPlatform) {
|
||||
// // Firefox throw an exception trying to get a new MediaStreamTrack if the older one is not stopped
|
||||
// // NotReadableError: Concurrent mic process limit. Stopping tracks before call to getUserMedia
|
||||
// oldMediaStream.getVideoTracks()[0].stop();
|
||||
// }
|
||||
// mediaStream = await this.createMediaStream(props);
|
||||
// // Replace video track
|
||||
// const videoTrack: MediaStreamTrack = mediaStream.getVideoTracks()[0];
|
||||
// await this.participantService.getMyCameraPublisher().replaceTrack(videoTrack);
|
||||
// } else if (isReplacingAudio) {
|
||||
// if (isFirefoxPlatform) {
|
||||
// // Firefox throw an exception trying to get a new MediaStreamTrack if the older one is not stopped
|
||||
// // NotReadableError: Concurrent mic process limit. Stopping tracks before call to getUserMedia
|
||||
// oldMediaStream.getAudioTracks()[0].stop();
|
||||
// }
|
||||
// mediaStream = await this.createMediaStream(props);
|
||||
// // Replace audio track
|
||||
// const audioTrack: MediaStreamTrack = mediaStream.getAudioTracks()[0];
|
||||
// await this.participantService.getMyCameraPublisher().replaceTrack(audioTrack);
|
||||
// }
|
||||
} else if (videoType === VideoType.SCREEN) {
|
||||
let newScreenMediaStream;
|
||||
try {
|
||||
newScreenMediaStream = await this.OVScreen.getUserMedia(props);
|
||||
this.participantService.getMyScreenPublisher().stream.getMediaStream().getVideoTracks()[0].stop();
|
||||
await this.participantService.getMyScreenPublisher().replaceTrack(newScreenMediaStream.getVideoTracks()[0]);
|
||||
this.screenMediaStream = newScreenMediaStream;
|
||||
} catch (error) {
|
||||
this.log.w('Cannot create the new MediaStream', error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -310,9 +305,56 @@ export class WebrtcService {
|
|||
}
|
||||
}
|
||||
|
||||
private destroyPublisher(publisher: Publisher): void {
|
||||
if (!!publisher) {
|
||||
if (publisher.stream.getWebRtcPeer()) {
|
||||
publisher.stream.disposeWebRtcPeer();
|
||||
}
|
||||
publisher.stream.disposeMediaStream();
|
||||
if (publisher.id === this.participantService.getMyCameraPublisher().id) {
|
||||
this.participantService.setMyCameraPublisher(publisher);
|
||||
} else if (publisher.id === this.participantService.getMyScreenPublisher().id) {
|
||||
this.participantService.setMyScreenPublisher(publisher);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async createMediaStream(pp: PublisherProperties): Promise<MediaStream> {
|
||||
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 ((<OpenViduError>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;
|
||||
}
|
||||
}
|
||||
|
||||
needSendNicknameSignal(): boolean {
|
||||
const oldNickname: string = JSON.parse(this.webcamSession.connection.data).clientData;
|
||||
return oldNickname !== this.participantService.getWebcamNickname();
|
||||
let oldNickname: string;
|
||||
try {
|
||||
const connData = JSON.parse(this.webcamSession.connection.data.split('%/%')[0]);
|
||||
oldNickname = connData.clientData;
|
||||
} catch (error) {
|
||||
this.log.e(error);
|
||||
}
|
||||
return oldNickname !== this.participantService.getMyNickname();
|
||||
}
|
||||
|
||||
isMyOwnConnection(connectionId: string): boolean {
|
||||
|
@ -331,11 +373,25 @@ export class WebrtcService {
|
|||
return remoteCameraConnections;
|
||||
}
|
||||
|
||||
private stopTracks(mediaStream: MediaStream) {
|
||||
if (mediaStream) {
|
||||
mediaStream?.getAudioTracks().forEach((track) => track.stop());
|
||||
mediaStream?.getVideoTracks().forEach((track) => track.stop());
|
||||
// this.webcamMediaStream?.getAudioTracks().forEach((track) => track.stop());
|
||||
private disconnectSession(session: Session) {
|
||||
if (session) {
|
||||
if (session.sessionId === this.webcamSession?.sessionId) {
|
||||
this.log.d('Disconnecting webcam session');
|
||||
this.webcamSession?.disconnect();
|
||||
this.webcamSession = null;
|
||||
} else if (session.sessionId === this.screenSession?.sessionId) {
|
||||
this.log.d('Disconnecting screen session');
|
||||
this.screenSession?.disconnect();
|
||||
this.screenSession = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// private stopTracks(mediaStream: MediaStream) {
|
||||
// if (mediaStream) {
|
||||
// mediaStream?.getAudioTracks().forEach((track) => track.stop());
|
||||
// mediaStream?.getVideoTracks().forEach((track) => track.stop());
|
||||
// // this.webcamMediaStream?.getAudioTracks().forEach((track) => track.stop());
|
||||
// }
|
||||
// }
|
||||
}
|
|
@ -2,8 +2,9 @@ import { Injectable } from '@angular/core';
|
|||
import { Publisher, Subscriber } from 'openvidu-browser';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { ILogger } from '../../models/logger.model';
|
||||
import { StreamModel, ParticipantAbstractModel, ParticipantModel } from '../../models/participant.model';
|
||||
import { StreamModel, ParticipantAbstractModel, ParticipantModel, ParticipantProperties } from '../../models/participant.model';
|
||||
import { VideoType } from '../../models/video-type.model';
|
||||
import { OpenViduAngularConfigService } from '../config/openvidu-angular.config.service';
|
||||
import { LoggerService } from '../logger/logger.service';
|
||||
|
||||
@Injectable({
|
||||
|
@ -12,13 +13,7 @@ import { LoggerService } from '../logger/logger.service';
|
|||
export class ParticipantService {
|
||||
//Local participants observables
|
||||
localParticipantObs: Observable<ParticipantAbstractModel>;
|
||||
screenShareState: Observable<boolean>;
|
||||
webcamVideoActive: Observable<boolean>;
|
||||
webcamAudioActive: Observable<boolean>;
|
||||
protected _localParticipant = <BehaviorSubject<ParticipantAbstractModel>>new BehaviorSubject(null);
|
||||
protected _screensharing = <BehaviorSubject<boolean>>new BehaviorSubject(false);
|
||||
protected _cameraVideoActive = <BehaviorSubject<boolean>>new BehaviorSubject(true);
|
||||
protected _cameraAudioActive = <BehaviorSubject<boolean>>new BehaviorSubject(true);
|
||||
|
||||
//Remote participants observable
|
||||
remoteParticipantsObs: Observable<ParticipantAbstractModel[]>;
|
||||
|
@ -29,20 +24,21 @@ export class ParticipantService {
|
|||
|
||||
protected log: ILogger;
|
||||
|
||||
constructor(protected loggerSrv: LoggerService) {
|
||||
constructor(protected openviduAngularConfigSrv: OpenViduAngularConfigService, protected loggerSrv: LoggerService) {
|
||||
this.log = this.loggerSrv.get('ParticipantService');
|
||||
// this.participantsObs = this._participants.asObservable();
|
||||
|
||||
this.localParticipantObs = this._localParticipant.asObservable();
|
||||
this.remoteParticipantsObs = this._remoteParticipants.asObservable();
|
||||
this.screenShareState = this._screensharing.asObservable();
|
||||
this.webcamVideoActive = this._cameraVideoActive.asObservable();
|
||||
this.webcamAudioActive = this._cameraAudioActive.asObservable();
|
||||
}
|
||||
|
||||
this.localParticipant = this.newParticipant();
|
||||
initLocalParticipant(props: ParticipantProperties) {
|
||||
this.localParticipant = this.newParticipant(props);
|
||||
this.updateLocalParticipant();
|
||||
}
|
||||
|
||||
getLocalParticipant(): ParticipantAbstractModel {
|
||||
return this.localParticipant;
|
||||
}
|
||||
getMyParticipantId(): string {
|
||||
return this.localParticipant.id;
|
||||
}
|
||||
|
@ -84,98 +80,85 @@ export class ParticipantService {
|
|||
this.updateLocalParticipant();
|
||||
}
|
||||
|
||||
enableScreenUser(screenPublisher: Publisher) {
|
||||
activeMyScreenShare(screenPublisher: Publisher) {
|
||||
this.log.d('Enabling screen publisher');
|
||||
|
||||
const steramModel: StreamModel = {
|
||||
local: true,
|
||||
type: VideoType.SCREEN,
|
||||
videoEnlarged: true,
|
||||
streamManager: screenPublisher,
|
||||
nickname: `${this.localParticipant.getCameraNickname()}_${VideoType.SCREEN}`,
|
||||
connected: true,
|
||||
connectionId: null
|
||||
};
|
||||
|
||||
this.resetRemoteStreamsToNormalSize();
|
||||
this.resetMyStreamsToNormalSize();
|
||||
this.localParticipant.addConnection(steramModel);
|
||||
|
||||
this._screensharing.next(true);
|
||||
|
||||
this.updateLocalParticipant();
|
||||
}
|
||||
|
||||
disableScreenUser() {
|
||||
this.localParticipant.disableScreen();
|
||||
this.updateLocalParticipant();
|
||||
this._screensharing.next(false);
|
||||
}
|
||||
|
||||
updateUsersStatus() {
|
||||
this._cameraVideoActive.next(this.localParticipant.isCameraVideoActive());
|
||||
if (this.isMyCameraEnabled()) {
|
||||
this._cameraAudioActive.next(this.localParticipant.isCameraAudioActive());
|
||||
} else {
|
||||
this._cameraAudioActive.next(this.hasScreenAudioActive());
|
||||
}
|
||||
setMyNickname(nickname: string) {
|
||||
this.localParticipant.setNickname(nickname);
|
||||
this.updateLocalParticipant();
|
||||
}
|
||||
|
||||
setNickname(connectionId: string, nickname: string) {
|
||||
if (this.localParticipant.hasConnectionId(connectionId)) {
|
||||
this.localParticipant.setNickname(nickname);
|
||||
// this.updateLocalParticipant();
|
||||
} else {
|
||||
const participant = this.getRemoteParticipantByConnectionId(connectionId);
|
||||
if (participant) {
|
||||
participant.setNickname(nickname);
|
||||
// this.updateRemoteParticipants();
|
||||
}
|
||||
}
|
||||
getMyNickname(): string {
|
||||
return this.localParticipant.nickname;
|
||||
}
|
||||
// getWebcamNickname(): string {
|
||||
// return this.localParticipant.getCameraNickname();
|
||||
// }
|
||||
|
||||
getWebcamNickname(): string {
|
||||
return this.localParticipant.getCameraNickname();
|
||||
}
|
||||
// getScreenNickname(): string {
|
||||
// return this.localParticipant.getScreenNickname();
|
||||
// }
|
||||
|
||||
getScreenNickname(): string {
|
||||
return this.localParticipant.getScreenNickname();
|
||||
}
|
||||
|
||||
resetUsersZoom() {
|
||||
this.localParticipant?.setAllVideoEnlarged(false);
|
||||
}
|
||||
|
||||
toggleZoom(connectionId: string) {
|
||||
toggleMyVideoEnlarged(connectionId: string) {
|
||||
this.localParticipant.toggleVideoEnlarged(connectionId);
|
||||
}
|
||||
|
||||
|
||||
resetMyStreamsToNormalSize() {
|
||||
if(this.localParticipant.someHasVideoEnlarged()){
|
||||
this.localParticipant.setAllVideoEnlarged(false);
|
||||
this.updateLocalParticipant();
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.disableScreenUser();
|
||||
this.localParticipant = this.newParticipant();
|
||||
this._screensharing.next(false);
|
||||
// this.localParticipant = this.newParticipant();
|
||||
// this._screensharing.next(false);
|
||||
this.remoteParticipants = [];
|
||||
this._remoteParticipants = <BehaviorSubject<ParticipantAbstractModel[]>>new BehaviorSubject([]);
|
||||
this.remoteParticipantsObs = this._remoteParticipants.asObservable();
|
||||
this.updateLocalParticipant();
|
||||
}
|
||||
|
||||
isMyCameraEnabled(): boolean {
|
||||
return this.localParticipant.isCameraEnabled();
|
||||
isMyCameraActive(): boolean {
|
||||
return this.localParticipant.isCameraActive();
|
||||
}
|
||||
|
||||
isMyScreenEnabled(): boolean {
|
||||
return this.localParticipant.isScreenEnabled();
|
||||
isMyScreenActive(): boolean {
|
||||
return this.localParticipant.isScreenActive();
|
||||
}
|
||||
|
||||
isOnlyMyCameraEnabled(): boolean {
|
||||
return this.isMyCameraEnabled() && !this.isMyScreenEnabled();
|
||||
isOnlyMyCameraActive(): boolean {
|
||||
return this.isMyCameraActive() && !this.isMyScreenActive();
|
||||
}
|
||||
|
||||
isOnlyMyScreenEnabled(): boolean {
|
||||
return this.isMyScreenEnabled() && !this.isMyCameraEnabled();
|
||||
isOnlyMyScreenActive(): boolean {
|
||||
return this.isMyScreenActive() && !this.isMyCameraActive();
|
||||
}
|
||||
|
||||
areBothEnabled(): boolean {
|
||||
return this.isMyCameraEnabled() && this.isMyScreenEnabled();
|
||||
haveICameraAndScreenActive(): boolean {
|
||||
return this.isMyCameraActive() && this.isMyScreenActive();
|
||||
}
|
||||
|
||||
hasCameraVideoActive(): boolean {
|
||||
|
@ -190,13 +173,8 @@ export class ParticipantService {
|
|||
return this.localParticipant.isScreenAudioActive();
|
||||
}
|
||||
|
||||
protected updateLocalParticipant() {
|
||||
// Cloning localParticipant object for not applying changes on the global variable
|
||||
let participantsWithConnectionAvailable: ParticipantAbstractModel = Object.assign(this.newParticipant(), this.localParticipant);
|
||||
const availableConnections = participantsWithConnectionAvailable.getAvailableConnections();
|
||||
const availableConnectionsMap = new Map(availableConnections.map((conn) => [conn.type, conn]));
|
||||
participantsWithConnectionAvailable.connections = availableConnectionsMap;
|
||||
this._localParticipant.next(participantsWithConnectionAvailable);
|
||||
updateLocalParticipant() {
|
||||
this._localParticipant.next(Object.assign(Object.create(this.localParticipant),this.localParticipant));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -205,35 +183,51 @@ export class ParticipantService {
|
|||
|
||||
addRemoteConnection(connectionId:string, data: string, subscriber: Subscriber) {
|
||||
|
||||
const steramModel: StreamModel = {
|
||||
local: false,
|
||||
type: this.getTypeConnectionData(data),
|
||||
videoEnlarged: false,
|
||||
const type: VideoType = this.getTypeConnectionData(data);
|
||||
const streamModel: StreamModel = {
|
||||
type,
|
||||
videoEnlarged: type === VideoType.SCREEN,
|
||||
streamManager: subscriber,
|
||||
nickname: this.getNicknameFromConnectionData(data),
|
||||
connected: true,
|
||||
connected: true,
|
||||
connectionId
|
||||
};
|
||||
|
||||
const participantId = this.getParticipantIdFromData(data);
|
||||
// Avoiding create a new participant if participantId param is not exist in connection data
|
||||
// participant Id is necessary for allowing to have multiple connection in one participant
|
||||
const participantId = this.getParticipantIdFromData(data) || connectionId;
|
||||
|
||||
const participantAdded = this.getRemoteParticipantById(participantId);
|
||||
if (!!participantAdded) {
|
||||
this.log.d('Adding connection to existing participant: ', participantId);
|
||||
if(participantAdded.hasConnectionType(steramModel.type)) {
|
||||
if(participantAdded.hasConnectionType(streamModel.type)) {
|
||||
this.log.d('Participant has publisher, updating it');
|
||||
participantAdded.setPublisher(steramModel.type, subscriber);
|
||||
participantAdded.setPublisher(streamModel.type, subscriber);
|
||||
} else {
|
||||
this.log.d('Participant has not publisher, adding it');
|
||||
participantAdded.addConnection(steramModel);
|
||||
if(streamModel.type === VideoType.SCREEN) {
|
||||
this.resetRemoteStreamsToNormalSize();
|
||||
this.resetMyStreamsToNormalSize();
|
||||
}
|
||||
participantAdded.addConnection(streamModel);
|
||||
}
|
||||
} else {
|
||||
this.log.d('Creating new participant with id: ', participantId);
|
||||
const remoteParticipant = this.newParticipant(steramModel, participantId);
|
||||
this.log.w('Creating new participant with id: ', participantId);
|
||||
const props: ParticipantProperties = {
|
||||
nickname: this.getNicknameFromConnectionData(data),
|
||||
local: false,
|
||||
id: participantId
|
||||
}
|
||||
const remoteParticipant = this.newParticipant(props, streamModel);
|
||||
this.remoteParticipants.push(remoteParticipant);
|
||||
}
|
||||
this.updateRemoteParticipants();
|
||||
}
|
||||
|
||||
resetRemoteStreamsToNormalSize() {
|
||||
this.remoteParticipants.forEach(participant => participant.setAllVideoEnlarged(false));
|
||||
this.updateRemoteParticipants();
|
||||
}
|
||||
|
||||
removeConnectionByConnectionId(connectionId: string) {
|
||||
this.log.w('Deleting connection: ', connectionId);
|
||||
let participant = null;
|
||||
|
@ -244,16 +238,28 @@ export class ParticipantService {
|
|||
}
|
||||
|
||||
if (participant) {
|
||||
participant.removeConnection(connectionId);
|
||||
const removeStream: StreamModel = participant.removeConnection(connectionId);
|
||||
//TODO: Timeout of X seconds?? Its possible sometimes the connections map was empty but must not be deleted
|
||||
if (participant.connections.size === 0) {
|
||||
if (participant.streams.size === 0) {
|
||||
// Remove participants without connections
|
||||
this.remoteParticipants = this.remoteParticipants.filter((p) => p !== participant);
|
||||
}
|
||||
if(removeStream.type === VideoType.SCREEN){
|
||||
const remoteScreens = this.remoteParticipants.filter(p => p.isScreenActive());
|
||||
if(remoteScreens.length > 0){
|
||||
// Enlarging the last screen connection active
|
||||
const lastScreenActive = remoteScreens[remoteScreens.length -1];
|
||||
lastScreenActive.setScreenEnlarged(true);
|
||||
} else if(this.localParticipant.isScreenActive()) {
|
||||
// Enlarging my screen if thereare not any remote screen active
|
||||
this.localParticipant.setScreenEnlarged(true);
|
||||
}
|
||||
}
|
||||
|
||||
this.updateRemoteParticipants();
|
||||
}
|
||||
}
|
||||
protected getRemoteParticipantByConnectionId(connectionId: string): ParticipantAbstractModel {
|
||||
getRemoteParticipantByConnectionId(connectionId: string): ParticipantAbstractModel {
|
||||
return this.remoteParticipants.find((p) => p.hasConnectionId(connectionId));
|
||||
}
|
||||
|
||||
|
@ -264,15 +270,11 @@ export class ParticipantService {
|
|||
return this.remoteParticipants.some((p) => p.someHasVideoEnlarged());
|
||||
}
|
||||
|
||||
toggleUserZoom(connectionId: string) {
|
||||
toggleRemoteVideoEnlarged(connectionId: string) {
|
||||
const p = this.getRemoteParticipantByConnectionId(connectionId);
|
||||
p.toggleVideoEnlarged(connectionId);
|
||||
}
|
||||
|
||||
resetRemotesZoom() {
|
||||
this.remoteParticipants.forEach((u) => u.setAllVideoEnlarged(false));
|
||||
}
|
||||
|
||||
getNicknameFromConnectionData(data: string): string {
|
||||
try {
|
||||
return JSON.parse(data).clientData;
|
||||
|
@ -281,6 +283,18 @@ export class ParticipantService {
|
|||
}
|
||||
}
|
||||
|
||||
setRemoteNickname(connectionId: string, nickname: string) {
|
||||
const participant = this.getRemoteParticipantByConnectionId(connectionId);
|
||||
if (participant) {
|
||||
participant.setNickname(nickname);
|
||||
// this.updateRemoteParticipants();
|
||||
}
|
||||
}
|
||||
|
||||
updateRemoteParticipants() {
|
||||
this._remoteParticipants.next([...this.remoteParticipants]);
|
||||
}
|
||||
|
||||
protected getTypeConnectionData(data: string): VideoType {
|
||||
try {
|
||||
return JSON.parse(data).type;
|
||||
|
@ -297,10 +311,11 @@ export class ParticipantService {
|
|||
}
|
||||
}
|
||||
|
||||
protected updateRemoteParticipants() {
|
||||
this._remoteParticipants.next(this.remoteParticipants);
|
||||
}
|
||||
protected newParticipant(steramModel?: StreamModel, participantId?: string) {
|
||||
return new ParticipantModel(steramModel, participantId);
|
||||
protected newParticipant(props: ParticipantProperties, streamModel?: StreamModel) {
|
||||
|
||||
if(this.openviduAngularConfigSrv.hasParticipantFactory()){
|
||||
return this.openviduAngularConfigSrv.getParticipantFactory().apply(this, [props, streamModel]);
|
||||
}
|
||||
return new ParticipantModel(props, streamModel);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { RestService } from './rest.service';
|
||||
|
||||
describe('RestService', () => {
|
||||
let service: RestService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(RestService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -1,75 +0,0 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { catchError, throwError } from 'rxjs';
|
||||
import { Buffer} from 'buffer';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class RestService {
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
async getToken(sessionId: string, openviduServerUrl: string, openviduSecret: string): Promise<string> {
|
||||
if (!!openviduServerUrl && !!openviduSecret) {
|
||||
const _sessionId = await this.createSession(sessionId, openviduServerUrl, openviduSecret);
|
||||
return await this.createToken(_sessionId, openviduServerUrl, openviduSecret);
|
||||
} else {
|
||||
return Promise.reject(`Error requesting a token to ${openviduServerUrl} with session id: ${sessionId}`);
|
||||
}
|
||||
}
|
||||
|
||||
private createSession(sessionId: string, openviduServerUrl: string, openviduSecret: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const body = JSON.stringify({ customSessionId: sessionId });
|
||||
const options = {
|
||||
headers: new HttpHeaders({
|
||||
Authorization: 'Basic ' + this.btoa('OPENVIDUAPP:' + openviduSecret),
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
};
|
||||
return this.http
|
||||
.post<any>(openviduServerUrl + '/openvidu/api/sessions', body, options)
|
||||
.pipe(
|
||||
catchError((error) => {
|
||||
if (error.status === 409) {
|
||||
resolve(sessionId);
|
||||
}
|
||||
if (error.statusText === 'Unknown Error') {
|
||||
reject({ status: 401, message: 'ERR_CERT_AUTHORITY_INVALID' });
|
||||
}
|
||||
return throwError(() => new Error(error));
|
||||
})
|
||||
)
|
||||
.subscribe((response) => {
|
||||
resolve(response.id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private createToken(sessionId: string, openviduServerUrl: string, openviduSecret: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const body = JSON.stringify({});
|
||||
const options = {
|
||||
headers: new HttpHeaders({
|
||||
Authorization: 'Basic ' + this.btoa('OPENVIDUAPP:' + openviduSecret),
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
};
|
||||
return this.http
|
||||
.post<any>(openviduServerUrl + '/openvidu/api/sessions/' + sessionId + '/connection', body, options)
|
||||
.pipe(
|
||||
catchError((error) => {
|
||||
reject(error);
|
||||
return throwError(() => new Error(error));
|
||||
})
|
||||
)
|
||||
.subscribe((response) => {
|
||||
resolve(response.token);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private btoa(str: string): string {
|
||||
return Buffer.from(str).toString('base64');
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { ILogger } from '../../models/logger.model';
|
||||
import { LoggerService } from '../logger/logger.service';
|
||||
import { Storage } from '../../models/storage.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
@ -13,12 +14,49 @@ export class StorageService {
|
|||
this.log = this.loggerSrv.get('StorageService');
|
||||
}
|
||||
|
||||
public set(key: string, item: any) {
|
||||
getNickname(): string {
|
||||
return this.get(Storage.USER_NICKNAME);
|
||||
}
|
||||
|
||||
setNickname(name: string){
|
||||
this.set(Storage.USER_NICKNAME, name);
|
||||
}
|
||||
getVideoDevice() {
|
||||
return this.get(Storage.VIDEO_DEVICE);
|
||||
}
|
||||
|
||||
setVideoDevice(device: any) {
|
||||
this.set(Storage.VIDEO_DEVICE, device);
|
||||
}
|
||||
|
||||
getAudioDevice() {
|
||||
return this.get(Storage.AUDIO_DEVICE);
|
||||
}
|
||||
|
||||
setAudioDevice(device: any) {
|
||||
this.set(Storage.AUDIO_DEVICE, device);
|
||||
}
|
||||
isVideoMuted(): boolean {
|
||||
return this.get(Storage.VIDEO_MUTED) === 'true';
|
||||
}
|
||||
setVideoMuted(muted: boolean) {
|
||||
this.set(Storage.VIDEO_MUTED, `${muted}`);
|
||||
}
|
||||
|
||||
isAudioMuted(): boolean {
|
||||
return this.get(Storage.AUDIO_MUTED) === 'true';
|
||||
}
|
||||
|
||||
setAudioMuted(muted: boolean) {
|
||||
this.set(Storage.AUDIO_MUTED, `${muted}`);
|
||||
}
|
||||
|
||||
private set(key: string, item: any) {
|
||||
const value = JSON.stringify({ item: item });
|
||||
// this.log.d('Storing on localStorage "' + key + '" with value "' + value + '"');
|
||||
this.storage.setItem(key, value);
|
||||
}
|
||||
public get(key: string): any {
|
||||
private get(key: string): any {
|
||||
const value = JSON.parse(this.storage.getItem(key));
|
||||
return value?.item ? value.item : null;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { ILogger } from '../../models/logger.model';
|
||||
import { LoggerService } from '../logger/logger.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
@ -8,19 +6,14 @@ import { LoggerService } from '../logger/logger.service';
|
|||
export class TokenService {
|
||||
private webcamToken = '';
|
||||
private screenToken = '';
|
||||
private sessionId = '';
|
||||
private log: ILogger;
|
||||
|
||||
constructor(private loggerSrv: LoggerService) {
|
||||
this.log = this.loggerSrv.get('TokenService');
|
||||
}
|
||||
constructor() {}
|
||||
|
||||
|
||||
setWebcamToken(token: string){
|
||||
setWebcamToken(token: string) {
|
||||
this.webcamToken = token;
|
||||
}
|
||||
|
||||
setScreenToken(token: string){
|
||||
setScreenToken(token: string) {
|
||||
this.screenToken = token;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
export * from './lib/openvidu-angular.module';
|
||||
|
||||
// Services
|
||||
export * from './lib/services/webrtc/webrtc.service';
|
||||
export * from './lib/services/openvidu/openvidu.service';
|
||||
export * from './lib/services/participant/participant.service';
|
||||
export * from './lib/services/chat/chat.service';
|
||||
export * from './lib/services/platform/platform.service';
|
||||
export * from './lib/services/logger/logger.service';
|
||||
export * from './lib/services/library-config/library-config.service';
|
||||
export * from './lib/services/config/openvidu-angular.config.service';
|
||||
export * from './lib/services/document/document.service';
|
||||
export * from './lib/services/token/token.service';
|
||||
export * from './lib/services/device/device.service';
|
||||
|
@ -22,21 +22,35 @@ export * from './lib/services/storage/storage.service';
|
|||
|
||||
// Components
|
||||
export * from './lib/components/videoconference/videoconference.component';
|
||||
export * from './lib/components/user-settings/user-settings.component';
|
||||
// export * from './lib/components/user-settings/user-settings.component';
|
||||
export * from './lib/components/toolbar/toolbar.component';
|
||||
export * from './lib/components/panel/panel.component';
|
||||
export * from './lib/components/panel/chat-panel/chat-panel.component';
|
||||
export * from './lib/components/panel/participants-panel/participants-panel/participants-panel.component';
|
||||
export * from './lib/components/panel/participants-panel/participant-panel-item/participant-panel-item.component';
|
||||
export * from './lib/components/session/session.component';
|
||||
export * from './lib/components/layout/layout.component';
|
||||
export * from './lib/components/stream/stream.component';
|
||||
export * from './lib/components/video/video.component';
|
||||
export * from './lib/components/audio-wave/audio-wave.component';
|
||||
export * from './lib/components/pre-join/pre-join.component';
|
||||
|
||||
// Models
|
||||
export * from './lib/models/participant.model';
|
||||
export * from './lib/config/lib.config';
|
||||
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';
|
||||
|
||||
// Pipes
|
||||
export * from './lib/pipes/participant-connections.pipe';
|
||||
export * from './lib/pipes/participant.pipe';
|
||||
|
||||
// Directives
|
||||
export * from './lib/directives/api/api.directive.module';
|
||||
export * from './lib/directives/template/openvidu-angular.directive.module';
|
||||
|
||||
export * from './lib/directives/template/openvidu-angular.directive';
|
||||
export * from './lib/directives/api/toolbar.directive';
|
||||
export * from './lib/directives/api/stream.directive';
|
||||
export * from './lib/directives/api/videoconference.directive';
|
||||
export * from './lib/directives/api/participant-panel-item.directive';
|
||||
|
|
|
@ -17,7 +17,7 @@ import { LayoutTestComponent } from './components/layout-test/layout-test.compon
|
|||
import { StreamTestComponent } from './components/stream-test/stream-test.component';
|
||||
|
||||
import {
|
||||
OpenviduAngularModule,
|
||||
OpenViduAngularModule,
|
||||
UserSettingsComponent,
|
||||
ChatPanelComponent,
|
||||
ToolbarComponent,
|
||||
|
@ -41,7 +41,7 @@ import { MatButtonModule } from '@angular/material/button';
|
|||
BrowserModule,
|
||||
MatButtonModule,
|
||||
BrowserAnimationsModule,
|
||||
OpenviduAngularModule.forRoot(environment),
|
||||
OpenViduAngularModule.forRoot(environment),
|
||||
AppRoutingModule // Order is important, AppRoutingModule must be the last import for useHash working
|
||||
],
|
||||
providers: [VideoconferenceComponent, UserSettingsComponent, ToolbarComponent, ChatPanelComponent, SessionComponent, LayoutComponent],
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
<div id="stream-test-container">
|
||||
|
||||
<ov-stream [participant]="participant"></ov-stream>
|
||||
<ov-stream [stream]="stream"></ov-stream>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
@ -8,11 +8,11 @@ import { ParticipantModel } from 'openvidu-angular';
|
|||
})
|
||||
export class StreamTestComponent implements OnInit {
|
||||
|
||||
participant: ParticipantModel;
|
||||
stream: ParticipantModel;
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.participant = new ParticipantModel();
|
||||
this.stream = new ParticipantModel();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,20 +1,95 @@
|
|||
<div id="call-container">
|
||||
<div id="userSettings" *ngIf="!joinSessionClicked && !closeClicked || !isSessionAlive">
|
||||
<ov-user-settings (onJoinClicked)="onJoinClicked()" (onCloseClicked)="onCloseClicked()"></ov-user-settings>
|
||||
</div>
|
||||
<ov-videoconference
|
||||
[tokens]="tokens"
|
||||
(onJoinButtonClicked)="onJoinClicked()"
|
||||
(onToolbarLeaveButtonClicked)="onToolbarLeaveButtonClicked()"
|
||||
(onToolbarCameraButtonClicked)="onToolbarCameraButtonClicked()"
|
||||
(onToolbarMicrophoneButtonClicked)="onToolbarMicrophoneButtonClicked()"
|
||||
(onToolbarScreenshareButtonClicked)="onToolbarScreenshareButtonClicked()"
|
||||
(onToolbarFullscreenButtonClicked)="onToolbarFullscreenButtonClicked()"
|
||||
(onToolbarParticipantsPanelButtonClicked)="onToolbarParticipantsPanelButtonClicked()"
|
||||
(onToolbarChatPanelButtonClicked)="onToolbarChatPanelButtonClicked()"
|
||||
[prejoin]="false"
|
||||
>
|
||||
|
||||
<div *ngIf="joinSessionClicked && isSessionAlive" style="height: 100%;">
|
||||
<ov-session [tokens]="tokens">
|
||||
<ng-template #toolbar>
|
||||
<ov-toolbar
|
||||
(onCamClicked)="onCamClicked()"
|
||||
(onMicClicked)="onMicClicked()"
|
||||
(onScreenShareClicked)="onScreenShareClicked()"
|
||||
(onSpeakerLayoutClicked)="onSpeakerLayoutClicked()"
|
||||
(onLeaveSessionClicked)="onLeaveSessionClicked()"
|
||||
></ov-toolbar>
|
||||
</ng-template>
|
||||
<ov-layout layout></ov-layout>
|
||||
</ov-session>
|
||||
</div>
|
||||
<!-- <div *ovParticipantPanelItemElements>
|
||||
<p>EXTRA INFO</p>
|
||||
</div> -->
|
||||
|
||||
<!-- <div *ovParticipantPanelItemElements="let participant">
|
||||
<p>N: {{participant?.nickname}}</p>
|
||||
</div>
|
||||
|
||||
<ov-toolbar
|
||||
*ovToolbar
|
||||
[screenshareButton]="true"
|
||||
(onLeaveButtonClicked)="onLeaveButtonClicked()"
|
||||
(onCameraButtonClicked)="onCameraButtonClicked()"
|
||||
(onMicrophoneButtonClicked)="onMicrophoneButtonClicked()"
|
||||
(onScreenshareButtonClicked)="onScreenshareButtonClicked()"
|
||||
(onFullscreenButtonClicked)="onFullscreenButtonClicked()"
|
||||
(onParticipantsPanelButtonClicked)="onParticipantsPanelButtonClicked()"
|
||||
(onChatPanelButtonClicked)="onChatPanelButtonClicked()"
|
||||
>
|
||||
|
||||
<div *ovToolbarAdditionalButtons>
|
||||
<button mat-icon-button>
|
||||
<mat-icon matTooltip="Exit Fullscreen">fullscreen_exit</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button>
|
||||
<mat-icon matTooltip="Exit Fullscreen">fullscreen_exit</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
</ov-toolbar> -->
|
||||
|
||||
<!-- <div *ovToolbar [screenshareButton]="value" [fullscreenButton]="value">
|
||||
<ov-toolbar (onCamClicked)="onCamClicked()"></ov-toolbar>
|
||||
</div> -->
|
||||
|
||||
<!--
|
||||
<ov-panel *ovPanel>
|
||||
|
||||
<ov-chat-panel *ovChatPanel></ov-chat-panel>
|
||||
|
||||
<ov-participants-panel *ovParticipantsPanel>
|
||||
|
||||
<div *ovParticipantPanelItem="let participant">
|
||||
<ov-participant-panel-item [participant]="participant"></ov-participant-panel-item>
|
||||
|
||||
<button mat-icon-button id="hand-notification" *ngIf="participant.hasHandRaised">
|
||||
<mat-icon>front_hand</mat-icon>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</ov-participants-panel>
|
||||
</ov-panel> -->
|
||||
|
||||
<!-- <ov-toolbar chatPanel (onCamClicked)="onCamClicked()"></ov-toolbar> -->
|
||||
|
||||
<!-- <div *ovParticipantPanelItem="let participant">
|
||||
<ov-participant-panel-item [participant]="participant" [muteButton]="false">
|
||||
<div *ovParticipantPanelItemElements>
|
||||
<button mat-icon-button id="hand-notification" >
|
||||
<mat-icon>front_hand</mat-icon>
|
||||
</button>
|
||||
<span> OK</span>
|
||||
</div>
|
||||
</ov-participant-panel-item>
|
||||
</div> -->
|
||||
|
||||
<!-- <ov-layout *ovLayout >
|
||||
|
||||
|
||||
<div *ovStream="let stream">
|
||||
<p>EXTERNAL STREAM INSIDE OF LAYOUT</p>
|
||||
<ov-stream [stream]="stream"></ov-stream>
|
||||
</div>
|
||||
|
||||
</ov-layout> -->
|
||||
|
||||
<!-- <div *ovStream="let stream">
|
||||
<p>EXTERNAL STREAM</p>
|
||||
<ov-stream [stream]="stream"></ov-stream>
|
||||
</div> -->
|
||||
</ov-videoconference>
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { RestService } from '../services/rest.service';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-call',
|
||||
templateUrl: './call.component.html',
|
||||
styleUrls: ['./call.component.scss']
|
||||
})
|
||||
export class CallComponent implements OnInit {
|
||||
sessionId = 'prueba-majestuosa-amable';
|
||||
value = true;
|
||||
sessionId = 'qqqq';
|
||||
tokens: { webcam: string; screen: string };
|
||||
|
||||
joinSessionClicked: boolean = false;
|
||||
|
@ -17,33 +17,71 @@ export class CallComponent implements OnInit {
|
|||
|
||||
constructor(private restService: RestService, private router: Router) {}
|
||||
|
||||
ngOnInit() {
|
||||
// this.onJoinClicked();
|
||||
async ngOnInit() {
|
||||
this.tokens = {
|
||||
webcam: await this.restService.getToken(this.sessionId),
|
||||
screen: await this.restService.getToken(this.sessionId)
|
||||
};
|
||||
}
|
||||
|
||||
async onJoinClicked() {
|
||||
console.warn('VC JOIN BUTTON CLICKED');
|
||||
|
||||
this.tokens = {
|
||||
webcam: await this.restService.getToken(this.sessionId),
|
||||
screen: await this.restService.getToken(this.sessionId)
|
||||
};
|
||||
|
||||
this.joinSessionClicked = true;
|
||||
this.isSessionAlive = true;
|
||||
}
|
||||
onCloseClicked() {
|
||||
this.closeClicked = true;
|
||||
this.router.navigate([`/`]);
|
||||
// setInterval(() => {
|
||||
// this.value = !this.value;
|
||||
// }, 1000);
|
||||
}
|
||||
|
||||
onMicClicked() {}
|
||||
onToolbarCameraButtonClicked() {
|
||||
console.warn('VC camera CLICKED');
|
||||
}
|
||||
onToolbarMicrophoneButtonClicked() {
|
||||
console.warn('VC microphone CLICKED');
|
||||
}
|
||||
onToolbarScreenshareButtonClicked() {
|
||||
console.warn('VC screenshare CLICKED');
|
||||
}
|
||||
onToolbarFullscreenButtonClicked() {
|
||||
console.warn('VC fullscreen CLICKED');
|
||||
}
|
||||
onToolbarParticipantsPanelButtonClicked() {
|
||||
console.warn('VC participants CLICKED');
|
||||
}
|
||||
onToolbarChatPanelButtonClicked() {
|
||||
console.warn('VC chat CLICKED');
|
||||
}
|
||||
|
||||
onCamClicked() {}
|
||||
|
||||
onScreenShareClicked() {}
|
||||
|
||||
onSpeakerLayoutClicked() {}
|
||||
|
||||
onLeaveSessionClicked() {
|
||||
onToolbarLeaveButtonClicked() {
|
||||
this.isSessionAlive = false;
|
||||
console.log('VC LEAVE BUTTON CLICKED');
|
||||
}
|
||||
|
||||
onCameraButtonClicked() {
|
||||
console.warn('TOOLBAR camera CLICKED');
|
||||
}
|
||||
onMicrophoneButtonClicked() {
|
||||
console.warn('TOOLBAR microphone CLICKED');
|
||||
}
|
||||
onScreenshareButtonClicked() {
|
||||
console.warn('TOOLBAR screenshare CLICKED');
|
||||
}
|
||||
onFullscreenButtonClicked() {
|
||||
console.warn('TOOLBAR fullscreen CLICKED');
|
||||
}
|
||||
onParticipantsPanelButtonClicked() {
|
||||
console.warn('TOOLBAR participants CLICKED');
|
||||
}
|
||||
onChatPanelButtonClicked() {
|
||||
console.warn('TOOLBAR chat CLICKED');
|
||||
}
|
||||
|
||||
onLeaveButtonClicked() {
|
||||
this.isSessionAlive = false;
|
||||
console.log('TOOLBAR LEAVE CLICKED');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,21 +56,13 @@ $openvidu-components-theme: mat.define-light-theme((
|
|||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
// Default openvidu-webcomponent colors
|
||||
:root {
|
||||
|
||||
--ov-primary-color: #303030;
|
||||
--ov-secondary-color: #586063;
|
||||
--ov-tertiary-color: #598eff;
|
||||
--ov-warn-color: #EB5144;
|
||||
--ov-accent-color: #ffae35;
|
||||
|
||||
--ov-dark-color: #1d1d1d;
|
||||
--ov-dark-light-color: #43484A;
|
||||
|
||||
--ov-light-color: #ffffff;
|
||||
--ov-light-dark-color: #f1f1f1;
|
||||
}
|
||||
|
||||
html, body { height: 100%; overflow: hidden;}
|
||||
body { margin: 0; font-family: 'Roboto','RobotoDraft',Helvetica,Arial,sans-serif;}
|
||||
body { margin: 0; font-family: 'Roboto','RobotoDraft',Helvetica,Arial,sans-serif;}
|
||||
|
||||
#poster-text {
|
||||
padding: 0px !important;
|
||||
}
|
||||
ov-chat-panel .text-info {
|
||||
margin: 3px auto !important;
|
||||
}
|
|
@ -1,69 +1,218 @@
|
|||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Component, ElementRef, Input, OnInit, Output, EventEmitter } from '@angular/core';
|
||||
import { ILogger, LoggerService, OpenViduService } from 'openvidu-angular';
|
||||
import { Session } from 'openvidu-browser';
|
||||
import { ParticipantAbstractModel } from '../../../projects/openvidu-angular/src/lib/models/participant.model';
|
||||
|
||||
export interface SessionConfig {
|
||||
sessionName: string;
|
||||
userName: string;
|
||||
tokens: { webcam: string; screen: string };
|
||||
export interface TokenModel {
|
||||
webcam: string;
|
||||
screen: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `<ov-videoconference
|
||||
*ngIf="successParams"
|
||||
[sessionName]="_sessionConfig.sessionName"
|
||||
[userName]="_sessionConfig.userName"
|
||||
[openviduServerUrl]="openviduServerUrl"
|
||||
[openviduSecret]="openviduSecret"
|
||||
[tokens]="_sessionConfig.tokens"
|
||||
></ov-videoconference>`
|
||||
template: `
|
||||
<ov-videoconference
|
||||
*ngIf="success"
|
||||
[participantName]="_participantName"
|
||||
[tokens]="_tokens"
|
||||
[minimal]="_minimal"
|
||||
[prejoin]="_prejoin"
|
||||
[videoMuted]="_videoMuted"
|
||||
[audioMuted]="_audioMuted"
|
||||
[toolbarScreenshareButton]="_toolbarScreenshareButton"
|
||||
[toolbarFullscreenButton]="_toolbarFullscreenButton"
|
||||
[toolbarLeaveButton]="_toolbarLeaveButton"
|
||||
[toolbarChatPanelButton]="_toolbarChatPanelButton"
|
||||
[toolbarParticipantsPanelButton]="_toolbarParticipantsPanelButton"
|
||||
[toolbarDisplayLogo]="_toolbarDisplayLogo"
|
||||
[toolbarDisplaySessionName]="_toolbarDisplaySessionName"
|
||||
[streamDisplayParticipantName]="_streamDisplayParticipantName"
|
||||
[streamDisplayAudioDetection]="_streamDisplayAudioDetection"
|
||||
[streamSettingsButton]="_streamSettingsButton"
|
||||
[participantPanelItemMuteButton]="_participantPanelItemMuteButton"
|
||||
(onJoinButtonClicked)="_onJoinButtonClicked()"
|
||||
(onToolbarLeaveButtonClicked)="_onToolbarLeaveButtonClicked()"
|
||||
(onToolbarCameraButtonClicked)="_onToolbarCameraButtonClicked()"
|
||||
(onToolbarMicrophoneButtonClicked)="_onToolbarMicrophoneButtonClicked()"
|
||||
(onToolbarScreenshareButtonClicked)="_onToolbarScreenshareButtonClicked()"
|
||||
(onToolbarParticipantsPanelButtonClicked)="_onToolbarParticipantsPanelButtonClicked()"
|
||||
(onToolbarChatPanelButtonClicked)="_onToolbarChatPanelButtonClicked()"
|
||||
(onToolbarFullscreenButtonClicked)="_onToolbarFullscreenButtonClicked()"
|
||||
(onSessionCreated)="_onSessionCreated($event)"
|
||||
(onParticipantCreated)="_onParticipantCreated($event)"
|
||||
></ov-videoconference>
|
||||
`
|
||||
})
|
||||
export class OpenviduWebComponentComponent implements OnInit {
|
||||
@Input() openviduServerUrl: string;
|
||||
@Input() openviduSecret: string;
|
||||
_sessionConfig: SessionConfig;
|
||||
_tokens: TokenModel;
|
||||
_minimal: boolean = false;
|
||||
_participantName: string;
|
||||
_prejoin: boolean = true;
|
||||
_videoMuted: boolean = false;
|
||||
_audioMuted: boolean = false;
|
||||
_toolbarScreenshareButton: boolean = true;
|
||||
_toolbarFullscreenButton: boolean = true;
|
||||
_toolbarLeaveButton: boolean = true;
|
||||
_toolbarChatPanelButton: boolean = true;
|
||||
_toolbarParticipantsPanelButton: boolean = true;
|
||||
_toolbarDisplayLogo: boolean = true;
|
||||
_toolbarDisplaySessionName: boolean = true;
|
||||
_streamDisplayParticipantName: boolean = true;
|
||||
_streamDisplayAudioDetection: boolean = true;
|
||||
_streamSettingsButton: boolean = true;
|
||||
_participantPanelItemMuteButton: boolean = true;
|
||||
|
||||
successParams: boolean = false;
|
||||
@Input() set minimal(value: string | boolean) {
|
||||
this._minimal = this.castToBoolean(value);
|
||||
}
|
||||
@Input() set participantName(value: string) {
|
||||
this._participantName = value;
|
||||
}
|
||||
@Input() set prejoin(value: string | boolean) {
|
||||
this._prejoin = this.castToBoolean(value);
|
||||
}
|
||||
@Input() set videoMuted(value: string | boolean) {
|
||||
this._videoMuted = this.castToBoolean(value);
|
||||
}
|
||||
@Input() set audioMuted(value: string | boolean) {
|
||||
this._audioMuted = this.castToBoolean(value);
|
||||
}
|
||||
|
||||
constructor() {}
|
||||
@Input() set toolbarScreenshareButton(value: string | boolean) {
|
||||
this._toolbarScreenshareButton = this.castToBoolean(value);
|
||||
}
|
||||
@Input() set toolbarFullscreenButton(value: string | boolean) {
|
||||
this._toolbarFullscreenButton = this.castToBoolean(value);
|
||||
}
|
||||
@Input() set toolbarLeaveButton(value: string | boolean) {
|
||||
this._toolbarLeaveButton = this.castToBoolean(value);
|
||||
}
|
||||
@Input() set toolbarChatPanelButton(value: string | boolean) {
|
||||
this._toolbarChatPanelButton = this.castToBoolean(value);
|
||||
}
|
||||
@Input() set toolbarParticipantsPanelButton(value: string | boolean) {
|
||||
this._toolbarParticipantsPanelButton = this.castToBoolean(value);
|
||||
}
|
||||
@Input() set toolbarDisplayLogo(value: string | boolean) {
|
||||
this._toolbarDisplayLogo = this.castToBoolean(value);
|
||||
}
|
||||
@Input() set toolbarDisplaySessionName(value: string | boolean) {
|
||||
this._toolbarDisplaySessionName = this.castToBoolean(value);
|
||||
}
|
||||
@Input() set streamDisplayParticipantName(value: string | boolean) {
|
||||
this._streamDisplayParticipantName = this.castToBoolean(value);
|
||||
}
|
||||
@Input() set streamDisplayAudioDetection(value: string | boolean) {
|
||||
this._streamDisplayAudioDetection = this.castToBoolean(value);
|
||||
}
|
||||
@Input() set streamSettingsButton(value: string | boolean) {
|
||||
this._streamSettingsButton = this.castToBoolean(value);
|
||||
}
|
||||
@Input() set participantPanelItemMuteButton(value: string | boolean) {
|
||||
this._participantPanelItemMuteButton = this.castToBoolean(value);
|
||||
}
|
||||
|
||||
@Output() onJoinButtonClicked = new EventEmitter<any>();
|
||||
@Output() onToolbarLeaveButtonClicked = new EventEmitter<any>();
|
||||
@Output() onToolbarCameraButtonClicked = new EventEmitter<any>();
|
||||
@Output() onToolbarMicrophoneButtonClicked = new EventEmitter<any>();
|
||||
@Output() onToolbarScreenshareButtonClicked = new EventEmitter<any>();
|
||||
@Output() onToolbarParticipantsPanelButtonClicked = new EventEmitter<any>();
|
||||
@Output() onToolbarChatPanelButtonClicked = new EventEmitter<any>();
|
||||
@Output() onToolbarFullscreenButtonClicked = new EventEmitter<any>();
|
||||
@Output() onSessionCreated = new EventEmitter<any>();
|
||||
@Output() onParticipantCreated = new EventEmitter<any>();
|
||||
|
||||
success: boolean = false;
|
||||
private log: ILogger;
|
||||
|
||||
constructor(private loggerService: LoggerService, private host: ElementRef, private openviduService: OpenViduService) {
|
||||
this.log = this.loggerService.get('WebComponent');
|
||||
this.host.nativeElement.leaveSession = this.leaveSession.bind(this);
|
||||
}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
@Input('sessionConfig')
|
||||
set sessionConfig(config: SessionConfig | string) {
|
||||
console.log('Webcomponent sessionConfig: ', config);
|
||||
if (typeof config === 'string') {
|
||||
@Input('tokens')
|
||||
set tokens(value: TokenModel | string) {
|
||||
this.log.d('Webcomponent tokens: ', value);
|
||||
try {
|
||||
this._tokens = this.castToJson(value);
|
||||
this.success = !!this._tokens?.webcam && !!this._tokens?.screen;
|
||||
} catch (error) {
|
||||
this.log.e(error);
|
||||
if (!this.success) {
|
||||
this.log.e('Parameters received are incorrect: ', value);
|
||||
this.log.e('Session cannot start');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onJoinButtonClicked() {
|
||||
this.onJoinButtonClicked.emit();
|
||||
}
|
||||
|
||||
_onToolbarLeaveButtonClicked() {
|
||||
this.onToolbarLeaveButtonClicked.emit();
|
||||
}
|
||||
|
||||
_onToolbarCameraButtonClicked() {
|
||||
this.onToolbarCameraButtonClicked.emit();
|
||||
}
|
||||
|
||||
_onToolbarMicrophoneButtonClicked() {
|
||||
this.onToolbarMicrophoneButtonClicked.emit();
|
||||
}
|
||||
_onToolbarScreenshareButtonClicked() {
|
||||
this.onToolbarScreenshareButtonClicked.emit();
|
||||
}
|
||||
_onToolbarParticipantsPanelButtonClicked() {
|
||||
this.onToolbarParticipantsPanelButtonClicked.emit();
|
||||
}
|
||||
_onToolbarChatPanelButtonClicked() {
|
||||
this.onToolbarChatPanelButtonClicked.emit();
|
||||
}
|
||||
_onToolbarFullscreenButtonClicked() {
|
||||
this.onToolbarFullscreenButtonClicked.emit();
|
||||
}
|
||||
_onSessionCreated(event: Session) {
|
||||
this.onSessionCreated.emit(event);
|
||||
}
|
||||
|
||||
_onParticipantCreated(event: ParticipantAbstractModel) {
|
||||
this.onParticipantCreated.emit(event);
|
||||
}
|
||||
|
||||
leaveSession() {
|
||||
this.openviduService.disconnect();
|
||||
}
|
||||
|
||||
private castToBoolean(value: string | boolean): boolean {
|
||||
if (typeof value === 'boolean') {
|
||||
return value;
|
||||
} else if (typeof value === 'string') {
|
||||
if (value !== 'true' && value !== 'false') {
|
||||
throw new Error('Parameter has an incorrect string value.');
|
||||
}
|
||||
return value === 'true';
|
||||
} else {
|
||||
throw new Error('Parameter has not a valid type. The parameters must to be string or boolean.');
|
||||
}
|
||||
}
|
||||
|
||||
private castToJson(value: TokenModel | string) {
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
console.log('STRING')
|
||||
config = JSON.parse(config);
|
||||
return JSON.parse(value);
|
||||
} catch (error) {
|
||||
console.error('Unexpected JSON', error);
|
||||
this.log.e('Unexpected JSON', error);
|
||||
throw 'Unexpected JSON';
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isEmpty(<SessionConfig>config)) {
|
||||
// Leaving session when sessionConfig is empty
|
||||
} else if (typeof value === 'object') {
|
||||
return value;
|
||||
} else {
|
||||
console.log("URL",this.openviduServerUrl);
|
||||
console.log('SECRET',this.openviduSecret);
|
||||
this.successParams = this.isCorrectParams(<SessionConfig>config);
|
||||
this._sessionConfig = <SessionConfig>config;
|
||||
if (!this.successParams) {
|
||||
console.error('Parameters received are incorrect: ', config);
|
||||
console.error('Session cannot start');
|
||||
}
|
||||
throw new Error(
|
||||
'Parameter has not a valid type. The parameters must to be string or TokenModel {webcam:string, screen: string}.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private isCorrectParams(config: SessionConfig): boolean {
|
||||
|
||||
console.log(config)
|
||||
const canGenerateToken = !!config.sessionName && !!config.userName && !!this.openviduServerUrl && !!this.openviduSecret;
|
||||
const hasToken = !!config.tokens?.webcam && !!config.tokens?.screen && !!config.userName;
|
||||
|
||||
return canGenerateToken || hasToken;
|
||||
}
|
||||
|
||||
private isEmpty(config: SessionConfig): boolean {
|
||||
return Object.keys(config).length === 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,23 +5,23 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
|||
|
||||
import { OpenviduWebComponentComponent } from './openvidu-webcomponent.component';
|
||||
|
||||
import { OpenviduAngularModule, VideoconferenceComponent } from 'openvidu-angular';
|
||||
import { OpenViduAngularModule, VideoconferenceComponent } from 'openvidu-angular';
|
||||
import { environment } from '../../environments/environment';
|
||||
|
||||
import { createCustomElement } from '@angular/elements';
|
||||
|
||||
@NgModule({
|
||||
declarations: [OpenviduWebComponentComponent],
|
||||
imports: [CommonModule, BrowserModule, BrowserAnimationsModule, OpenviduAngularModule.forRoot(environment)],
|
||||
imports: [CommonModule, BrowserModule, BrowserAnimationsModule, OpenViduAngularModule.forRoot(environment)],
|
||||
// exports: [OpenviduWebComponentComponent],
|
||||
providers: [{ provide: APP_BASE_HREF, useValue: '/' }, VideoconferenceComponent]
|
||||
providers: [{ provide: APP_BASE_HREF, useValue: '/' }, VideoconferenceComponent],
|
||||
})
|
||||
export class OpenviduWebComponentModule implements DoBootstrap {
|
||||
constructor(private injector: Injector) {}
|
||||
|
||||
ngDoBootstrap(): void {
|
||||
const element = createCustomElement(OpenviduWebComponentComponent, {
|
||||
injector: this.injector
|
||||
injector: this.injector,
|
||||
});
|
||||
|
||||
customElements.define('openvidu-webcomponent', element);
|
||||
|
|
|
@ -27,54 +27,54 @@ export class RestService {
|
|||
}
|
||||
}
|
||||
|
||||
private createSession(sessionId: string, openviduServerUrl: string, openviduSecret: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const body = JSON.stringify({ customSessionId: sessionId });
|
||||
const options = {
|
||||
headers: new HttpHeaders({
|
||||
Authorization: 'Basic ' + btoa('OPENVIDUAPP:' + openviduSecret),
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
};
|
||||
return this.http
|
||||
.post<any>(openviduServerUrl + '/openvidu/api/sessions', body, options)
|
||||
.pipe(
|
||||
catchError((error) => {
|
||||
if (error.status === 409) {
|
||||
resolve(sessionId);
|
||||
}
|
||||
if (error.statusText === 'Unknown Error') {
|
||||
reject({ status: 401, message: 'ERR_CERT_AUTHORITY_INVALID' });
|
||||
}
|
||||
return observableThrowError(error);
|
||||
})
|
||||
)
|
||||
.subscribe((response) => {
|
||||
resolve(response.id);
|
||||
});
|
||||
});
|
||||
}
|
||||
// private createSession(sessionId: string, openviduServerUrl: string, openviduSecret: string): Promise<string> {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// const body = JSON.stringify({ customSessionId: sessionId });
|
||||
// const options = {
|
||||
// headers: new HttpHeaders({
|
||||
// Authorization: 'Basic ' + btoa('OPENVIDUAPP:' + openviduSecret),
|
||||
// 'Content-Type': 'application/json'
|
||||
// })
|
||||
// };
|
||||
// return this.http
|
||||
// .post<any>(openviduServerUrl + '/openvidu/api/sessions', body, options)
|
||||
// .pipe(
|
||||
// catchError((error) => {
|
||||
// if (error.status === 409) {
|
||||
// resolve(sessionId);
|
||||
// }
|
||||
// if (error.statusText === 'Unknown Error') {
|
||||
// reject({ status: 401, message: 'ERR_CERT_AUTHORITY_INVALID' });
|
||||
// }
|
||||
// return observableThrowError(error);
|
||||
// })
|
||||
// )
|
||||
// .subscribe((response) => {
|
||||
// resolve(response.id);
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
private createToken(sessionId: string, openviduServerUrl: string, openviduSecret: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const body = JSON.stringify({});
|
||||
const options = {
|
||||
headers: new HttpHeaders({
|
||||
Authorization: 'Basic ' + btoa('OPENVIDUAPP:' + openviduSecret),
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
};
|
||||
return this.http
|
||||
.post<any>(openviduServerUrl + '/openvidu/api/sessions/' + sessionId + '/connection', body, options)
|
||||
.pipe(
|
||||
catchError((error) => {
|
||||
reject(error);
|
||||
return observableThrowError(error);
|
||||
})
|
||||
)
|
||||
.subscribe((response) => {
|
||||
resolve(response.token);
|
||||
});
|
||||
});
|
||||
}
|
||||
// private createToken(sessionId: string, openviduServerUrl: string, openviduSecret: string): Promise<string> {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// const body = JSON.stringify({});
|
||||
// const options = {
|
||||
// headers: new HttpHeaders({
|
||||
// Authorization: 'Basic ' + btoa('OPENVIDUAPP:' + openviduSecret),
|
||||
// 'Content-Type': 'application/json'
|
||||
// })
|
||||
// };
|
||||
// return this.http
|
||||
// .post<any>(openviduServerUrl + '/openvidu/api/sessions/' + sessionId + '/connection', body, options)
|
||||
// .pipe(
|
||||
// catchError((error) => {
|
||||
// reject(error);
|
||||
// return observableThrowError(error);
|
||||
// })
|
||||
// )
|
||||
// .subscribe((response) => {
|
||||
// resolve(response.token);
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export const environment = {
|
||||
production: true
|
||||
production: true,
|
||||
};
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// The list of file replacements can be found in `angular.json`.
|
||||
|
||||
export const environment = {
|
||||
production: false
|
||||
production: false,
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
@ -36,9 +36,8 @@ $openvidu-components-theme: mat.define-light-theme((
|
|||
@include mat.all-component-themes($openvidu-components-theme);
|
||||
|
||||
|
||||
// Custom openvidu-components colors
|
||||
// Custom openvidu-components styles
|
||||
:root {
|
||||
|
||||
--ov-primary-color: #303030;
|
||||
--ov-secondary-color: #586063;
|
||||
--ov-tertiary-color: #598eff;
|
||||
|
@ -50,6 +49,11 @@ $openvidu-components-theme: mat.define-light-theme((
|
|||
|
||||
--ov-light-color: #ffffff;
|
||||
--ov-light-dark-color: #f1f1f1;
|
||||
|
||||
--ov-buttons-radius: 50%; // border-radius property
|
||||
--ov-leave-button-radius: 10px;
|
||||
--ov-video-radius: 5px;
|
||||
--ov-panel-radius: 5px;
|
||||
}
|
||||
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue