openvidu-components: Added openvidu videoconference components

pull/685/head
csantosm 2022-01-20 11:53:56 +01:00
parent 673058381f
commit 3c97ceac37
9 changed files with 307 additions and 69 deletions

View File

@ -9,6 +9,7 @@
"@angular/forms": "^13.0.0",
"@angular/flex-layout": "^13.0.0-beta.36",
"autolinker": "^3.14.3",
"buffer": "^6.0.3",
"openvidu-browser": "^2.20.0"
},
"dependencies": {

View File

@ -0,0 +1,23 @@
#call-container, #room-container {
height: 100%;
}
#user-settings-container {
height: inherit;
}
.error-icon {
color: var(--ov-warn-color);
}
#spinner {
position: absolute;
top: 40%;
bottom: 0;
left: 0;
right: 0;
margin: auto;
text-align: -webkit-center;
text-align: -moz-center;
color: var(--ov-primary-color);
}

View File

@ -0,0 +1,30 @@
<div id="call-container">
<div id="user-settings-container" *ngIf="!joinSessionClicked && !closeClicked">
<ov-user-settings (onJoinClicked)="onJoinClicked()" (onCloseClicked)="onLeaveSessionClicked()"></ov-user-settings>
</div>
<div id="spinner" *ngIf="joinSessionClicked && !isSessionAlive && !error">
<mat-spinner [diameter]="50"></mat-spinner>
<span>Joining the room ...</span>
</div>
<div id="spinner" *ngIf="joinSessionClicked && !isSessionAlive && error">
<mat-icon class="error-icon">error</mat-icon>
<span>{{errorMessage}}</span>
</div>
<div id="room-container" *ngIf="joinSessionClicked && isSessionAlive && !error">
<ov-room [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-room>
</div>
</div>

View File

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

View File

@ -0,0 +1,72 @@
import { Component, Input, OnInit, Output } from '@angular/core';
import { RestService } from '../../services/rest/rest.service';
@Component({
selector: 'ov-videoconference',
templateUrl: './videoconference.component.html',
styleUrls: ['./videoconference.component.css']
})
export class VideoconferenceComponent implements OnInit {
@Input() sessionName: string;
@Input() userName: string;
@Input() openviduServerUrl: string;
@Input() openviduSecret: string;
@Input() tokens: string[];
joinSessionClicked: boolean = false;
closeClicked: boolean = false;
isSessionAlive: boolean = false;
_tokens: { webcam: string; screen: string };
error: boolean = false;
errorMessage: string = '';
constructor(private restService: RestService) {}
ngOnInit() {}
async onJoinClicked() {
if (!this.tokens || this.tokens?.length === 0) {
//No tokens received
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?.length < 2) {
// 1 token received
this._tokens = {
webcam: this.tokens[0],
screen: await this.restService.getToken(this.sessionName, this.openviduServerUrl, this.openviduSecret)
};
} else {
// 2 tokens received.
this._tokens = {
webcam: this.tokens[0],
screen: this.tokens[1]
};
}
this.joinSessionClicked = true;
this.isSessionAlive = true;
}
onLeaveSessionClicked() {
this.isSessionAlive = false;
this.closeClicked = true;
}
onMicClicked() {}
onCamClicked() {}
onScreenShareClicked() {}
onSpeakerLayoutClicked() {}
}

View File

@ -56,31 +56,33 @@ import { SidenavMenuService } from './services/sidenav-menu/sidenav-menu.service
import { ParticipantService } from './services/participant/participant.service';
import { ParticipantItemComponent } from './components/participants-panel/participant-item/participant-item.component';
import { ParticipantPanelComponent } from './components/participants-panel/participant-panel/participant-panel.component';
import { VideoconferenceComponent } from './components/videoconference/videoconference.component';
@NgModule({
declarations: [
UserSettingsComponent,
VideoComponent,
ToolbarComponent,
ChatComponent,
RoomComponent,
LayoutComponent,
ParticipantComponent,
DialogTemplateComponent,
LinkifyPipe,
TooltipListPipe,
ParticipantConnectionsPipe,
ConnectionsEnabledPipe,
NicknamePipe,
ParticipantItemComponent,
ParticipantPanelComponent
],
imports: [
CommonModule,
HttpClientModule,
FormsModule,
declarations: [
UserSettingsComponent,
VideoComponent,
ToolbarComponent,
ChatComponent,
RoomComponent,
LayoutComponent,
ParticipantComponent,
DialogTemplateComponent,
LinkifyPipe,
TooltipListPipe,
ParticipantConnectionsPipe,
ConnectionsEnabledPipe,
NicknamePipe,
ParticipantItemComponent,
ParticipantPanelComponent,
VideoconferenceComponent
],
imports: [
CommonModule,
HttpClientModule,
FormsModule,
ReactiveFormsModule,
RouterModule.forRoot([]),
RouterModule.forRoot([]),
MatButtonModule,
MatCardModule,
MatToolbarModule,
@ -99,53 +101,46 @@ import { ParticipantPanelComponent } from './components/participants-panel/parti
MatSnackBarModule,
FlexLayoutModule,
MatMenuModule,
MatDividerModule,
MatListModule
],
providers: [
ActionService,
CdkOverlayContainer,
MatDividerModule,
MatListModule
],
providers: [
ActionService,
CdkOverlayContainer,
{ provide: OverlayContainer, useClass: CdkOverlayContainer },
ChatService,
SidenavMenuService,
DeviceService,
DocumentService,
LayoutService,
LoggerService,
PlatformService,
ParticipantService,
StorageService,
TokenService,
WebrtcService
],
exports: [
UserSettingsComponent,
ToolbarComponent,
ChatComponent,
RoomComponent,
LayoutComponent,
ParticipantComponent,
VideoComponent,
ParticipantConnectionsPipe,
CommonModule
],
entryComponents: [
DialogTemplateComponent
]
ChatService,
SidenavMenuService,
DeviceService,
DocumentService,
LayoutService,
LoggerService,
PlatformService,
ParticipantService,
StorageService,
TokenService,
WebrtcService
],
exports: [
VideoconferenceComponent,
UserSettingsComponent,
ToolbarComponent,
ChatComponent,
RoomComponent,
LayoutComponent,
ParticipantComponent,
VideoComponent,
ParticipantConnectionsPipe,
CommonModule
],
entryComponents: [DialogTemplateComponent]
})
export class OpenviduAngularModule {
static forRoot(environment): ModuleWithProviders<OpenviduAngularModule> {
// console.log(`${library.name} config: ${environment}`);
const libConfig: LibConfig = { environment };
return {
ngModule: OpenviduAngularModule,
providers: [LibraryConfigService , {provide: 'LIB_CONFIG', useValue: libConfig}]
};
}
}
static forRoot(environment): ModuleWithProviders<OpenviduAngularModule> {
// console.log(`${library.name} config: ${environment}`);
const libConfig: LibConfig = { environment };
return {
ngModule: OpenviduAngularModule,
providers: [LibraryConfigService, { provide: 'LIB_CONFIG', useValue: libConfig }]
};
}
}

View File

@ -0,0 +1,16 @@
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();
});
});

View File

@ -0,0 +1,75 @@
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');
}
}

View File

@ -21,6 +21,7 @@ export * from './lib/services/cdk-overlay/cdk-overlay.service';
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/toolbar/toolbar.component';
export * from './lib/components/chat/chat.component';