openvidu-testapp refactoring to StreamManager openvidu-brwoser API

pull/73/head
pabloFuente 2018-05-29 18:32:49 +02:00
parent 6684ff414c
commit 0315a75187
17 changed files with 1074 additions and 720 deletions

View File

@ -20,7 +20,8 @@
"prefix": "app",
"styles": [
"styles.css",
"openvidu-theme.scss"
"openvidu-theme.scss",
"material-icons.css"
],
"scripts": [],
"environmentSource": "environments/environment.ts",

View File

@ -10,6 +10,8 @@ import { AppComponent } from './app.component';
import { TestSessionsComponent } from './components/test-sessions/test-sessions.component';
import { TestApirestComponent } from './components/test-apirest/test-apirest.component';
import { OpenviduInstanceComponent } from './components/openvidu-instance/openvidu-instance.component';
import { VideoComponent } from './components/video/video.component';
import { OpenViduVideoComponent } from './components/video/ov-video.component';
import { ExtensionDialogComponent } from './components/dialogs/extension-dialog.component';
import { LocalRecordingDialogComponent } from './components/dialogs/local-recording-dialog.component';
@ -19,16 +21,20 @@ import { TestFeedService } from './services/test-feed.service';
import { MuteSubscribersService } from './services/mute-subscribers.service';
import { SessionPropertiesDialogComponent } from './components/dialogs/session-properties-dialog.component';
import { SessionApiDialogComponent } from './components/dialogs/session-api-dialog.component';
import { EventsDialogComponent } from './components/dialogs/events-dialog.component';
@NgModule({
declarations: [
AppComponent,
OpenviduInstanceComponent,
VideoComponent,
OpenViduVideoComponent,
TestSessionsComponent,
TestApirestComponent,
ExtensionDialogComponent,
SessionPropertiesDialogComponent,
SessionApiDialogComponent,
EventsDialogComponent,
LocalRecordingDialogComponent
],
imports: [
@ -49,6 +55,7 @@ import { SessionApiDialogComponent } from './components/dialogs/session-api-dial
ExtensionDialogComponent,
SessionPropertiesDialogComponent,
SessionApiDialogComponent,
EventsDialogComponent,
LocalRecordingDialogComponent
],
bootstrap: [AppComponent]

View File

@ -0,0 +1,47 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
@Component({
selector: 'app-events-dialog',
template: `
<h2 mat-dialog-title>{{target}} events</h2>
<mat-dialog-content>
<mat-slide-toggle [(ngModel)]="checkAll" (change)="updateAll()" [color]="'warn'"><i>ALL</i></mat-slide-toggle>
<mat-divider></mat-divider>
<mat-slide-toggle *ngFor="let event of eventNamesArray()"
[(ngModel)]="eventCollection[event]"
[color]="'warn'">{{event}}
</mat-slide-toggle>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button id="close-dialog-btn" [mat-dialog-close]="eventCollection">CLOSE</button>
</mat-dialog-actions>
`,
styles: [
'mat-dialog-content { display: inline; }',
'mat-divider { margin-top: 5px; margin-bottom: 5px }'
]
})
export class EventsDialogComponent {
target = '';
checkAll = true;
eventCollection: any = {};
constructor(public dialogRef: MatDialogRef<EventsDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data) {
this.target = data.target;
this.eventCollection = data.eventCollection;
}
updateAll() {
Object.keys(this.eventCollection).forEach(key => {
this.eventCollection[key] = this.checkAll;
});
}
eventNamesArray(): String[] {
return Object.keys(this.eventCollection);
}
}

View File

@ -29,13 +29,13 @@ export class LocalRecordingDialogComponent {
public myReference: MatDialogRef<LocalRecordingDialogComponent>;
private recorder: LocalRecorder;
recorder: LocalRecorder;
private uploading = false;
private endpoint = '';
private uploadIcon: string;
private iconColor: string;
private iconClass = '';
uploading = false;
endpoint = '';
uploadIcon: string;
iconColor: string;
iconClass = '';
constructor(@Inject(MAT_DIALOG_DATA) public data: any) {
this.recorder = data.recorder;

View File

@ -5,7 +5,7 @@ import { Session } from 'openvidu-browser';
import { OpenVidu as OpenViduAPI } from 'openvidu-node-client';
@Component({
selector: 'app-session-properties-dialog',
selector: 'app-session-api-dialog',
template: `
<div>
<h2 mat-dialog-title>API REST</h2>

View File

@ -34,7 +34,7 @@ mat-card.session-card {
.inner-card {
border: 1px solid #e1e1e1;
padding: 10px 15px 10px 15px;
padding: 10px 3px 10px 10px;
background: #ffffff;
margin-top: 5px;
}
@ -138,19 +138,25 @@ mat-expansion-panel-header {
}
.mat-icon-custom {
width: 29px;
height: 29px;
line-height: 18px;
width: 24px;
height: 24px;
line-height: 17px;
margin-bottom: -4px;
}
.mat-icon-custom-ic {
width: 20px;
height: 20px;
font-size: 20px;
line-height: 20px;
width: 18px;
height: 18px;
font-size: 18px;
line-height: 18px;
}
.session-btns-div {
margin-top: -14px;
margin-right: -14px;
margin-left: 5px;
}
.publisher-btns-div {
margin-top: -7px;
}

View File

@ -8,20 +8,25 @@
<div fxLayout="row">
<mat-form-field style="margin-right: 10px">
<input matInput placeholder="Session name" id="session-name-input" name="sessionName" [(ngModel)]="sessionName" [disabled]="session">
<input matInput placeholder="Session name" [id]="'session-name-input-' + index" name="sessionName" [(ngModel)]="sessionName"
[disabled]="session">
</mat-form-field>
<mat-form-field>
<input matInput placeholder="Client data" id="client-data-input" name="clientData" [(ngModel)]="clientData" [disabled]="session">
<input matInput placeholder="Client data" [id]="'client-data-input-'+ index" name="clientData" [(ngModel)]="clientData" [disabled]="session">
</mat-form-field>
<div fxLayout="column" class="session-btns-div">
<button mat-icon-button title="Session properties" id="session-settings-btn" class="mat-icon-custom" (click)="openSessionPropertiesDialog()" [disabled]="session">
<button mat-icon-button title="Session properties" [id]="'session-settings-btn-' + index" class="mat-icon-custom" (click)="openSessionPropertiesDialog()"
[disabled]="session">
<mat-icon class="mat-icon-custom-ic" aria-label="Session properties button">settings</mat-icon>
</button>
<button mat-icon-button title="Session API" id="session-api-btn" class="mat-icon-custom" (click)="openSessionApiDialog()">
<button mat-icon-button title="Session API" [id]="'session-api-btn-' + index" class="mat-icon-custom" (click)="openSessionApiDialog()">
<mat-icon class="mat-icon-custom-ic" aria-label="Session API button">cloud_circle</mat-icon>
</button>
<button mat-icon-button title="Session events" [id]="'session-events-btn-' + index" class="mat-icon-custom" (click)="openSessionEventsDialog()">
<mat-icon class="mat-icon-custom-ic" aria-label="Session events button">notifications</mat-icon>
</button>
</div>
</div>
@ -36,7 +41,7 @@
<div class="inner-card" fxLayout="row" fxLayoutAlign="start start">
<div fxFlex="60">
<div fxFlex="55">
<div>
<h4>Send</h4>
<div>
@ -57,7 +62,7 @@
</div>
</div>
<div fxFlex="40">
<div fxFlex="35">
<mat-radio-group [(ngModel)]="optionsVideo" [disabled]="session || disableRadioButtons" [ngModelOptions]="{standalone: true}">
<div>
<mat-radio-button class="video-radio" value="video" [checked]="checkRadioVideo && optionsVideo==='video'">Video</mat-radio-button>
@ -71,6 +76,19 @@
<br>to remote</mat-checkbox>
</div>
<div fxFlex="10">
<div fxLayout="column" class="publisher-btns-div">
<button mat-icon-button title="Publisher properties" [id]="'session-settings-btn-' + index" class="mat-icon-custom" (click)="advancedPublisherOptions()"
[disabled]="(!sendAudio && !sendVideo) || !publishTo">
<mat-icon class="mat-icon-custom-ic" aria-label="Session properties button">settings</mat-icon>
</button>
<button mat-icon-button title="Add new publisher to running session" [id]="'session-api-btn-' + index" class="mat-icon-custom" (click)="addNewPublisher()"
[disabled]="!session || ((!sendAudio && !sendVideo) || !publishTo)">
<mat-icon class="mat-icon-custom-ic" aria-label="Session API button">add_circle</mat-icon>
</button>
</div>
</div>
</div>
</form>
@ -85,21 +103,9 @@
<div class="session-card-inner">
<div class="session-title">{{sessionName}}</div>
<div class="session-actions">
<button class="change-publisher-btn" *ngIf="publishTo" (click)="changePublisher()" title="Change publisher">
<mat-icon aria-label="Change publisher button">switch_video</mat-icon>
</button>
<button class="publish-btn" *ngIf="publishTo" (click)="publishUnpublish()" title="Publish/Unpublish">
<mat-icon aria-label="Publish Unpublish button">{{publishIcon}}</mat-icon>
</button>
<button class="message-btn" (click)="sendMessage()" title="Broadcast message">
<mat-icon aria-label="Send message button" style="font-size: 20px">chat</mat-icon>
</button>
<button class="video-btn" *ngIf="publishTo && sendVideoChange" (click)="toggleVideo()" title="Mute/Unmute video">
<mat-icon aria-label="Mute video button">{{videoIcon}}</mat-icon>
</button>
<button class="audio-btn" *ngIf="publishTo && sendAudioChange" (click)="toggleAudio()" title="Mute/Unmute audio">
<mat-icon aria-label="Mute audio button">{{audioIcon}}</mat-icon>
</button>
<button class="leave-btn" (click)="leaveSession()" title="Leave session">
<mat-icon aria-label="Leave button">clear</mat-icon>
</button>
@ -123,6 +129,10 @@
</div>
<div [attr.id]="'remote-vid-' + session.connection.connectionId" fxFlex="240px" class="video-container">
<div [attr.id]="'local-vid-' + session.connection.connectionId"></div>
<app-video *ngIf="this.publisher" [streamManager]="this.publisher" [OV]="OV" (updateEventListInParent)="udpateEventFromChild($event)">
</app-video>
<app-video *ngFor="let subscriber of this.subscribers" [streamManager]="subscriber" [OV]="OV" (updateEventListInParent)="udpateEventFromChild($event)" (reSubbed)="updateSubscriberFromChild($event)">
</app-video>
</div>
</div>
</mat-card>

View File

@ -7,7 +7,7 @@ import { Subscription } from 'rxjs/Subscription';
import {
OpenVidu, Session, Subscriber, Publisher, Stream, Connection,
LocalRecorder, VideoInsertMode, StreamEvent, ConnectionEvent,
SessionDisconnectedEvent, SignalEvent, RecordingEvent, VideoElementEvent
SessionDisconnectedEvent, SignalEvent, RecordingEvent, VideoElementEvent, PublisherSpeakingEvent, StreamManagerEvent, StreamManager
} from 'openvidu-browser';
import {
OpenVidu as OpenViduAPI,
@ -22,10 +22,10 @@ import { ExtensionDialogComponent } from '../dialogs/extension-dialog.component'
import { LocalRecordingDialogComponent } from '../dialogs/local-recording-dialog.component';
import { TestFeedService } from '../../services/test-feed.service';
import { MuteSubscribersService } from '../../services/mute-subscribers.service';
import { EventsDialogComponent } from '../dialogs/events-dialog.component';
import { SessionPropertiesDialogComponent } from '../dialogs/session-properties-dialog.component';
import { SessionApiDialogComponent } from '../dialogs/session-api-dialog.component';
declare var $: any;
export interface SessionConf {
subscribeTo: boolean;
@ -56,6 +56,9 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
@Input()
sessionConf: SessionConf;
@Input()
index: number;
// Session join data
clientData: string;
sessionName: string;
@ -91,7 +94,7 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
OV: OpenVidu;
session: Session;
publisher: Publisher;
subscribers = {};
subscribers: Subscriber[] = [];
// OpenVidu Node Client objects
sessionProperties: SessionPropertiesAPI = {
@ -102,17 +105,18 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
customSessionId: ''
};
// Session audio and video status
audioMuted = false;
videoMuted = false;
unpublished = false;
publisherChanged = false;
audioIcon = 'mic';
videoIcon = 'videocam';
publishIcon = 'stop';
sendAudioChange: boolean;
sendVideoChange: boolean;
sessionEvents = {
connectionCreated: true,
connectionDestroyed: true,
sessionDisconnected: true,
streamCreated: true,
streamDestroyed: true,
recordingStarted: true,
recordingStopped: true,
signal: true,
publisherStartSpeaking: false,
publisherStopSpeaking: false
};
events: OpenViduEvent[] = [];
@ -127,8 +131,7 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
private changeDetector: ChangeDetectorRef,
private dialog: MatDialog,
private recordDialog: MatDialog,
private testFeedService: TestFeedService,
private muteSubscribersService: MuteSubscribersService,
private testFeedService: TestFeedService
) {
this.generateSessionInfo();
}
@ -147,13 +150,6 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
if (this.sessionConf.startSession) {
this.joinSession();
}
this.muteSubscribersSubscription = this.muteSubscribersService.mutedEvent$.subscribe(
muteOrUnmute => {
Object.keys(this.subscribers).forEach((key) => {
this.subscribers[key].videoElement.muted = muteOrUnmute;
});
});
}
ngOnChanges(changes: SimpleChanges) {
@ -166,7 +162,6 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
}
ngOnDestroy() {
if (!!this.muteSubscribersSubscription) { this.muteSubscribersSubscription.unsubscribe(); }
this.leaveSession();
}
@ -199,24 +194,24 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
this.session = this.OV.initSession();
this.addSessionEvents(this.session);
this.updateSessionEvents({
connectionCreated: false,
connectionDestroyed: false,
sessionDisconnected: false,
streamCreated: false,
streamDestroyed: false,
recordingStarted: false,
recordingStopped: false,
signal: false,
publisherStartSpeaking: true,
publisherStopSpeaking: true
}, true);
this.session.connect(token, this.clientData)
.then(() => {
this.changeDetector.detectChanges();
if (this.publishTo) {
this.audioMuted = !this.activeAudio;
this.videoMuted = !this.activeVideo;
this.unpublished = false;
this.updateAudioIcon();
this.updateVideoIcon();
this.updatePublishIcon();
this.sendAudioChange = this.sendAudio;
this.sendVideoChange = this.sendVideo;
// this.asyncInitPublisher();
this.syncInitPublisher();
}
@ -226,113 +221,14 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
});
}
private leaveSession(): void {
if (!!this.publisherRecorder) {
this.restartPublisherRecord();
}
Object.keys(this.subscribers).forEach((key) => {
if (!!this.subscribers[key].recorder) {
this.restartSubscriberRecord(key);
}
});
if (this.session) {
this.session.disconnect();
}
this.session = null;
this.OV = null;
}
private toggleAudio() {
this.publisher.publishAudio(this.audioMuted);
this.audioMuted = !this.audioMuted;
this.updateAudioIcon();
}
private toggleVideo() {
this.publisher.publishVideo(this.videoMuted);
this.videoMuted = !this.videoMuted;
this.updateVideoIcon();
}
private updateAudioIcon() {
this.audioMuted ? this.audioIcon = 'mic_off' : this.audioIcon = 'mic';
}
private updateVideoIcon() {
this.videoMuted ? this.videoIcon = 'videocam_off' : this.videoIcon = 'videocam';
}
private updatePublishIcon() {
this.unpublished ? this.publishIcon = 'play_arrow' : this.publishIcon = 'stop';
}
private appendSubscriberData(videoElement: HTMLVideoElement, connection: Connection): void {
const dataNode = document.createElement('div');
dataNode.className = 'data-node';
dataNode.id = 'data-' + this.session.connection.connectionId + '-' + connection.connectionId;
dataNode.innerHTML = '<p class="name">' + connection.data + '</p>' +
'<button id="sub-btn-' + this.session.connection.connectionId + '-' + connection.connectionId +
'" class="sub-btn" title="Subscribe/Unsubscribe"><mat-icon id="sub-icon-' + this.session.connection.connectionId +
'-' + connection.connectionId + '" aria-label="Subscribe or unsubscribe" class="mat-icon material-icons" role="img"' +
'aria-hidden="true">notifications</mat-icon></button>' +
'<button id="sub-video-btn-' + this.session.connection.connectionId + '-' + connection.connectionId +
'" class="sub-btn sub-video-btn" title="Subscribe/Unsubscribe Video"><mat-icon id="sub-video-icon-' +
this.session.connection.connectionId +
'-' + connection.connectionId + '" aria-label="Subscribe or unsubscribe" class="mat-icon material-icons" role="img"' +
'aria-hidden="true">videocam</mat-icon></button>' +
'<button id="sub-audio-btn-' + this.session.connection.connectionId + '-' + connection.connectionId +
'" class="sub-btn sub-audio-btn" title="Subscribe/Unsubscribe Audio"><mat-icon id="sub-audio-icon-' +
this.session.connection.connectionId +
'-' + connection.connectionId + '" aria-label="Subscribe or unsubscribe" class="mat-icon material-icons" role="img"' +
'aria-hidden="true">mic</mat-icon></button>' +
'<button id="record-btn-' + this.session.connection.connectionId + '-' + connection.connectionId +
'" class="sub-btn rec-btn" title="Record"><mat-icon id="record-icon-' +
this.session.connection.connectionId + '-' + connection.connectionId +
'" aria-label="Start/Stop recording" class="mat-icon material-icons" role="img"' +
'aria-hidden="true">fiber_manual_record</mat-icon></button>' +
'<button style="display:none" id="pause-btn-' + this.session.connection.connectionId + '-' + connection.connectionId +
'" class="sub-btn rec-btn rec-pause-btn" title="Pause/Resume"><mat-icon id="pause-icon-' +
this.session.connection.connectionId + '-' + connection.connectionId +
'" aria-label="Pause/Resume recording" class="mat-icon material-icons" role="img"' +
'aria-hidden="true">pause</mat-icon></button>';
videoElement.parentNode.insertBefore(dataNode, videoElement.nextSibling);
document.getElementById('sub-btn-' + this.session.connection.connectionId + '-' + connection.connectionId)
.addEventListener('click', this.subUnsubFromSubscriber.bind(this, connection.connectionId));
document.getElementById('sub-video-btn-' + this.session.connection.connectionId + '-' + connection.connectionId)
.addEventListener('click', this.subUnsubFromSubscriberVideo.bind(this, connection.connectionId));
document.getElementById('sub-audio-btn-' + this.session.connection.connectionId + '-' + connection.connectionId)
.addEventListener('click', this.subUnsubFromSubscriberAudio.bind(this, connection.connectionId));
document.getElementById('record-btn-' + this.session.connection.connectionId + '-' + connection.connectionId)
.addEventListener('click', this.recordSubscriber.bind(this, connection.connectionId));
document.getElementById('pause-btn-' + this.session.connection.connectionId + '-' + connection.connectionId)
.addEventListener('click', this.pauseSubscriber.bind(this, connection.connectionId));
}
private appendPublisherData(videoElement: HTMLVideoElement): void {
const dataNode = document.createElement('div');
dataNode.className = 'data-node';
dataNode.id = 'data-' + this.session.connection.connectionId + '-' + this.session.connection.connectionId;
dataNode.innerHTML =
'<button id="local-record-btn-' + this.session.connection.connectionId +
'" class="sub-btn rec-btn publisher-rec-btn" title="Record"><mat-icon id="local-record-icon-' + this.session.connection.connectionId +
'" aria-label="Start/Stop local recording" class="mat-icon material-icons" role="img" aria-hidden="true">' +
'fiber_manual_record</mat-icon></button>' +
'<button style="display:none" id="local-pause-btn-' + this.session.connection.connectionId +
'" class="sub-btn rec-btn publisher-rec-btn publisher-rec-pause-btn" title="Pause/Resume">' +
'<mat-icon id="local-pause-icon-' + this.session.connection.connectionId +
'" aria-label="Pause/Resume local recording" class="mat-icon material-icons" role="img" aria-hidden="true">' +
'pause</mat-icon></button>';
videoElement.parentNode.insertBefore(dataNode, videoElement.nextSibling);
document.getElementById('local-record-btn-' + this.session.connection.connectionId).addEventListener('click',
this.recordPublisher.bind(this));
document.getElementById('local-pause-btn-' + this.session.connection.connectionId).addEventListener('click',
this.pausePublisher.bind(this));
}
private removeUserData(connectionId: string): void {
$('#remote-vid-' + this.session.connection.connectionId)
.find('#data-' + this.session.connection.connectionId + '-' + connectionId).remove();
delete this.session;
delete this.OV;
delete this.publisher;
this.subscribers = [];
}
private updateEventList(event: string, content: string) {
@ -433,432 +329,122 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
// this.initGrayVideo();
}
recordPublisher(): void {
if (!this.publisherRecording) {
this.publisherRecorder = this.OV.initLocalRecorder(this.publisher.stream);
this.publisherRecorder.record();
this.publisherRecording = true;
document.getElementById('local-record-icon-' + this.session.connection.connectionId).innerHTML = 'stop';
document.getElementById('local-pause-btn-' + this.session.connection.connectionId).style.display = 'block';
} else {
this.publisherRecorder.stop()
.then(() => {
let dialogRef: MatDialogRef<LocalRecordingDialogComponent>;
dialogRef = this.recordDialog.open(LocalRecordingDialogComponent, {
disableClose: true,
data: {
recorder: this.publisherRecorder
}
});
dialogRef.componentInstance.myReference = dialogRef;
updateSessionEvents(oldValues, firstTime) {
dialogRef.afterOpen().subscribe(() => {
this.afterOpenPreview(this.publisherRecorder);
});
dialogRef.afterClosed().subscribe(() => {
this.afterClosePreview();
});
})
.catch((error) => {
console.error('Error stopping LocalRecorder: ' + error);
});
}
}
pausePublisher(): void {
if (!this.publisherPaused) {
this.publisherRecorder.pause();
document.getElementById('local-pause-icon-' + this.session.connection.connectionId).innerHTML = 'play_arrow';
} else {
this.publisherRecorder.resume();
document.getElementById('local-pause-icon-' + this.session.connection.connectionId).innerHTML = 'pause';
}
this.publisherPaused = !this.publisherPaused;
}
recordSubscriber(connectionId: string): void {
const subscriber: Subscriber = this.subscribers[connectionId].subscriber;
const recording = this.subscribers[connectionId].recording;
if (!recording) {
this.subscribers[connectionId].recorder = this.OV.initLocalRecorder(subscriber.stream);
this.subscribers[connectionId].recorder.record();
this.subscribers[connectionId].recording = true;
document.getElementById('record-icon-' + this.session.connection.connectionId + '-' + connectionId).innerHTML = 'stop';
document.getElementById('pause-btn-' + this.session.connection.connectionId + '-' + connectionId).style.display = 'block';
} else {
this.subscribers[connectionId].recorder.stop()
.then(() => {
let dialogRef: MatDialogRef<LocalRecordingDialogComponent>;
dialogRef = this.recordDialog.open(LocalRecordingDialogComponent, {
disableClose: true,
data: {
recorder: this.subscribers[connectionId].recorder
}
});
dialogRef.componentInstance.myReference = dialogRef;
dialogRef.afterOpen().subscribe(() => {
this.afterOpenPreview(this.subscribers[connectionId].recorder);
});
dialogRef.afterClosed().subscribe(() => {
this.afterClosePreview(connectionId);
});
})
.catch((error) => {
console.error('Error stopping LocalRecorder: ' + error);
});
}
}
pauseSubscriber(connectionId: string): void {
const subscriber: Subscriber = this.subscribers[connectionId].subscriber;
const subscriberPaused = this.subscribers[connectionId].paused;
if (!subscriberPaused) {
this.subscribers[connectionId].recorder.pause();
document.getElementById('pause-icon-' + this.session.connection.connectionId + '-' + connectionId).innerHTML = 'play_arrow';
} else {
this.subscribers[connectionId].recorder.resume();
document.getElementById('pause-icon-' + this.session.connection.connectionId + '-' + connectionId).innerHTML = 'pause';
}
this.subscribers[connectionId].paused = !this.subscribers[connectionId].paused;
}
publishUnpublish(): void {
if (this.unpublished) {
this.session.publish(this.publisher)
.then(() => {
console.log(this.publisher);
})
.catch(e => {
console.error(e);
});
} else {
this.session.unpublish(this.publisher);
this.removeUserData(this.session.connection.connectionId);
this.restartPublisherRecord();
}
this.unpublished = !this.unpublished;
this.updatePublishIcon();
}
changePublisher() {
let screenChange;
if (!this.publisherChanged) {
if (this.sendAudio && !this.sendVideo) {
this.sendAudioChange = false;
this.sendVideoChange = true;
screenChange = false;
} else if (!this.sendAudio && this.sendVideo) {
this.sendAudioChange = true;
this.sendVideoChange = false;
} else if (this.sendAudio && this.sendVideo && this.optionsVideo === 'video') {
this.sendAudioChange = false;
this.sendVideoChange = true;
screenChange = true;
} else if (this.sendAudio && this.sendVideo && this.optionsVideo === 'screen') {
this.sendAudioChange = false;
this.sendVideoChange = true;
screenChange = false;
}
} else {
this.sendAudioChange = this.sendAudio;
this.sendVideoChange = this.sendVideo;
screenChange = this.optionsVideo === 'screen' ? true : false;
}
this.audioMuted = false;
this.videoMuted = false;
this.unpublished = false;
this.updateAudioIcon();
this.updateVideoIcon();
this.updatePublishIcon();
const otherPublisher = this.OV.initPublisher(
'local-vid-' + this.session.connection.connectionId,
{
audioSource: this.sendAudioChange ? undefined : false,
videoSource: this.sendVideoChange ? (screenChange ? 'screen' : undefined) : false,
publishAudio: (!this.publisherChanged) ? true : !this.audioMuted,
publishVideo: (!this.publisherChanged) ? true : !this.videoMuted,
resolution: '640x480',
frameRate: 30,
insertMode: VideoInsertMode.APPEND
},
(err) => {
if (err) {
console.warn(err);
this.openviduError = err;
if (err.name === 'SCREEN_EXTENSION_NOT_INSTALLED') {
this.dialog.open(ExtensionDialogComponent, {
data: { url: err.message },
disableClose: true,
width: '250px'
});
if (this.sessionEvents.streamCreated !== oldValues.streamCreated || firstTime) {
this.session.off('streamCreated');
if (this.sessionEvents.streamCreated) {
this.session.on('streamCreated', (event: StreamEvent) => {
this.changeDetector.detectChanges();
if (this.subscribeTo) {
this.syncSubscribe(this.session, event);
}
}
});
this.addPublisherEvents(otherPublisher);
otherPublisher.once('accessAllowed', () => {
if (!this.unpublished) {
this.session.unpublish(this.publisher);
this.publisher = otherPublisher;
this.removeUserData(this.session.connection.connectionId);
this.restartPublisherRecord();
}
this.session.publish(otherPublisher);
});
this.publisherChanged = !this.publisherChanged;
}
subUnsubFromSubscriber(connectionId: string) {
let subscriber: Subscriber = this.subscribers[connectionId].subscriber;
if (this.subscribers[connectionId].subbed) {
this.session.unsubscribe(subscriber);
this.restartSubscriberRecord(connectionId);
document.getElementById('data-' + this.session.connection.connectionId + '-' + connectionId).style.marginLeft = '0';
document.getElementById('sub-icon-' + this.session.connection.connectionId + '-' + connectionId).innerHTML = 'notifications_off';
document.getElementById('record-btn-' + this.session.connection.connectionId + '-' + connectionId).remove();
document.getElementById('pause-btn-' + this.session.connection.connectionId + '-' + connectionId).remove();
document.getElementById('sub-video-btn-' + this.session.connection.connectionId + '-' + connectionId).remove();
document.getElementById('sub-audio-btn-' + this.session.connection.connectionId + '-' + connectionId).remove();
this.subscribers[connectionId].subbedVideo = false;
this.subscribers[connectionId].subbedAudio = false;
} else {
this.session.subscribeAsync(subscriber.stream, 'remote-vid-' + this.session.connection.connectionId)
.then(sub => {
subscriber = sub;
this.subscribers[connectionId].subscriber = subscriber;
subscriber.on('videoElementCreated', (e: VideoElementEvent) => {
if (!subscriber.stream.hasVideo) {
$(e.element).css({ 'background-color': '#4d4d4d' });
$(e.element).attr('poster', 'assets/images/volume.png');
}
this.subscribers[connectionId].videoElement = e.element;
this.updateEventList('videoElementCreated', e.element.id);
});
subscriber.on('videoPlaying', (e: VideoElementEvent) => {
this.removeUserData(connectionId);
this.appendSubscriberData(e.element, subscriber.stream.connection);
this.updateEventList('videoPlaying', e.element.id);
});
})
.catch(err => {
console.error(err);
this.updateEventList('streamCreated', event.stream.streamId);
});
/*subscriber = this.session.subscribe(subscriber.stream, 'remote-vid-' + this.session.connection.connectionId);
this.subscribers[connectionId].subscriber = subscriber;
subscriber.on('videoElementCreated', (e) => {
if (!subscriber.stream.hasVideo) {
$(e.element).css({ 'background-color': '#4d4d4d' });
$(e.element).attr('poster', 'assets/images/volume.png');
}
this.subscribers[connectionId].videoElement = e.element;
this.updateEventList('videoElementCreated', e.element.id);
});
subscriber.on('videoPlaying', (e) => {
this.removeUserData(connectionId);
this.appendSubscriberData(e.element, subscriber.stream.connection);
this.updateEventList('videoPlaying', e.element.id);
});*/
}
this.subscribers[connectionId].subbed = !this.subscribers[connectionId].subbed;
}
subUnsubFromSubscriberVideo(connectionId: string) {
this.subscribers[connectionId].subscriber.subscribeToVideo(!!this.subscribers[connectionId].subbedVideo);
this.subscribers[connectionId].subbedVideo = !this.subscribers[connectionId].subbedVideo;
document.getElementById('sub-video-icon-' + this.session.connection.connectionId + '-' + connectionId).innerHTML =
this.subscribers[connectionId].subbedVideo ? 'videocam_off' : 'videocam';
}
subUnsubFromSubscriberAudio(connectionId: string) {
this.subscribers[connectionId].subscriber.subscribeToAudio(!!this.subscribers[connectionId].subbedAudio);
this.subscribers[connectionId].subbedAudio = !this.subscribers[connectionId].subbedAudio;
document.getElementById('sub-audio-icon-' + this.session.connection.connectionId + '-' + connectionId).innerHTML =
this.subscribers[connectionId].subbedAudio ? 'mic_off' : 'mic';
}
addSessionEvents(session: Session) {
session.on('streamCreated', (event: StreamEvent) => {
this.changeDetector.detectChanges();
if (this.subscribeTo) {
// this.syncSubscribe(session, event);
this.asyncSubscribe(session, event);
}
this.updateEventList('streamCreated', event.stream.connection.connectionId);
});
session.on('streamDestroyed', (event: StreamEvent) => {
this.removeUserData(event.stream.connection.connectionId);
this.updateEventList('streamDestroyed', event.stream.connection.connectionId);
});
session.on('connectionCreated', (event: ConnectionEvent) => {
this.updateEventList('connectionCreated', event.connection.connectionId);
});
session.on('connectionDestroyed', (event: ConnectionEvent) => {
this.updateEventList('connectionDestroyed', event.connection.connectionId);
});
session.on('sessionDisconnected', (event: SessionDisconnectedEvent) => {
this.updateEventList('sessionDisconnected', 'No data');
if (event.reason === 'networkDisconnect') {
this.session = null;
this.OV = null;
}
});
session.on('signal', (event: SignalEvent) => {
this.updateEventList('signal', event.from.connectionId + '-' + event.data);
});
session.on('recordingStarted', (event: RecordingEvent) => {
this.updateEventList('recordingStarted', event.id);
});
session.on('recordingStopped', (event: RecordingEvent) => {
this.updateEventList('recordingStopped', event.id);
});
/*session.on('publisherStartSpeaking', (event) => {
console.log('Publisher start speaking');
});
session.on('publisherStopSpeaking', (event) => {
console.log('Publisher stop speaking');
});*/
}
addPublisherEvents(publisher: Publisher) {
publisher.on('videoElementCreated', (event: VideoElementEvent) => {
if (this.publishTo &&
(!this.sendVideoChange ||
this.sendVideoChange &&
!(this.optionsVideo !== 'screen') &&
this.openviduError &&
this.openviduError.name === 'NO_VIDEO_DEVICE')) {
$(event.element).css({ 'background-color': '#4d4d4d' });
$(event.element).attr('poster', 'assets/images/volume.png');
}
this.updateEventList('videoElementCreated', event.element.id);
});
publisher.on('accessAllowed', (e) => {
this.updateEventList('accessAllowed', '');
});
publisher.on('accessDenied', (e) => {
this.updateEventList('accessDenied', '');
});
publisher.on('accessDialogOpened', (e) => {
this.updateEventList('accessDialogOpened', '');
});
publisher.on('accessDialogClosed', (e) => {
this.updateEventList('accessDialogClosed', '');
});
publisher.on('videoPlaying', (e: VideoElementEvent) => {
this.appendPublisherData(e.element);
this.updateEventList('videoPlaying', e.element.id);
});
publisher.on('remoteVideoPlaying', (e: VideoElementEvent) => {
this.appendPublisherData(e.element);
this.updateEventList('remoteVideoPlaying', e.element.id);
});
publisher.on('streamCreated', (e: StreamEvent) => {
this.updateEventList('streamCreated', e.stream.connection.connectionId);
});
publisher.on('streamDestroyed', (e: StreamEvent) => {
this.updateEventList('streamDestroyed', e.stream.connection.connectionId);
});
publisher.on('videoElementDestroyed', (e: VideoElementEvent) => {
this.updateEventList('videoElementDestroyed', '(Publisher)');
});
}
private afterOpenPreview(recorder: LocalRecorder): void {
this.muteSubscribersService.updateMuted(true);
recorder.preview('recorder-preview').controls = true;
}
private afterClosePreview(connectionId?: string): void {
this.muteSubscribersService.updateMuted(false);
if (!!connectionId) {
this.restartSubscriberRecord(connectionId);
} else {
this.restartPublisherRecord();
}
}
private restartPublisherRecord(): void {
if (!!this.session) {
let el: HTMLElement = document.getElementById('local-record-icon-' + this.session.connection.connectionId);
if (!!el) {
el.innerHTML = 'fiber_manual_record';
}
el = document.getElementById('local-pause-icon-' + this.session.connection.connectionId);
if (!!el) {
el.innerHTML = 'pause';
}
el = document.getElementById('local-pause-btn-' + this.session.connection.connectionId);
if (!!el) {
el.style.display = 'none';
}
}
this.publisherPaused = false;
this.publisherRecording = false;
if (!!this.publisherRecorder) {
this.publisherRecorder.clean();
}
}
private restartSubscriberRecord(connectionId: string): void {
if (!!this.session) {
let el: HTMLElement = document.getElementById('record-icon-' + this.session.connection.connectionId + '-' + connectionId);
if (!!el) {
el.innerHTML = 'fiber_manual_record';
}
el = document.getElementById('pause-icon-' + this.session.connection.connectionId + '-' + connectionId);
if (!!el) {
el.innerHTML = 'pause';
}
el = document.getElementById('pause-btn-' + this.session.connection.connectionId + '-' + connectionId);
if (!!el) {
el.style.display = 'none';
if (this.sessionEvents.streamDestroyed !== oldValues.streamDestroyed || firstTime) {
this.session.off('streamDestroyed');
if (this.sessionEvents.streamDestroyed) {
this.session.on('streamDestroyed', (event: StreamEvent) => {
const index = this.subscribers.indexOf(<Subscriber>event.stream.streamManager);
if (index > -1) {
this.subscribers.splice(index, 1);
}
this.updateEventList('streamDestroyed', event.stream.streamId);
});
}
}
this.subscribers[connectionId].recording = false;
this.subscribers[connectionId].paused = false;
if (!!this.subscribers[connectionId].recorder) {
this.subscribers[connectionId].recorder.clean();
if (this.sessionEvents.connectionCreated !== oldValues.connectionCreated || firstTime) {
this.session.off('connectionCreated');
if (this.sessionEvents.connectionCreated) {
this.session.on('connectionCreated', (event: ConnectionEvent) => {
this.updateEventList('connectionCreated', event.connection.connectionId);
});
}
}
if (this.sessionEvents.connectionDestroyed !== oldValues.connectionDestroyed || firstTime) {
this.session.off('connectionDestroyed');
if (this.sessionEvents.connectionDestroyed) {
this.session.on('connectionDestroyed', (event: ConnectionEvent) => {
delete this.subscribers[event.connection.connectionId];
this.updateEventList('connectionDestroyed', event.connection.connectionId);
});
}
}
if (this.sessionEvents.sessionDisconnected !== oldValues.sessionDisconnected || firstTime) {
this.session.off('sessionDisconnected');
if (this.sessionEvents.sessionDisconnected) {
this.session.on('sessionDisconnected', (event: SessionDisconnectedEvent) => {
this.updateEventList('sessionDisconnected', 'No data');
if (event.reason === 'networkDisconnect') {
this.session = null;
this.OV = null;
}
});
}
}
if (this.sessionEvents.signal !== oldValues.signal || firstTime) {
this.session.off('signal');
if (this.sessionEvents.signal) {
this.session.on('signal', (event: SignalEvent) => {
this.updateEventList('signal', event.from.connectionId + '-' + event.data);
});
}
}
if (this.sessionEvents.recordingStarted !== oldValues.recordingStarted || firstTime) {
this.session.off('recordingStarted');
if (this.sessionEvents.recordingStarted) {
this.session.on('recordingStarted', (event: RecordingEvent) => {
this.updateEventList('recordingStarted', event.id);
});
}
}
if (this.sessionEvents.recordingStopped !== oldValues.recordingStopped || firstTime) {
this.session.off('recordingStopped');
if (this.sessionEvents.recordingStopped) {
this.session.on('recordingStopped', (event: RecordingEvent) => {
this.updateEventList('recordingStopped', event.id);
});
}
}
if (this.sessionEvents.publisherStartSpeaking !== oldValues.publisherStartSpeaking || firstTime) {
this.session.off('publisherStartSpeaking');
if (this.sessionEvents.publisherStartSpeaking) {
this.session.on('publisherStartSpeaking', (event: PublisherSpeakingEvent) => {
this.updateEventList('publisherStartSpeaking', event.connection.connectionId);
});
}
}
if (this.sessionEvents.publisherStopSpeaking !== oldValues.publisherStopSpeaking || firstTime) {
this.session.off('publisherStopSpeaking');
if (this.sessionEvents.publisherStopSpeaking) {
this.session.on('publisherStopSpeaking', (event: PublisherSpeakingEvent) => {
this.updateEventList('publisherStopSpeaking', event.connection.connectionId);
});
}
}
}
syncInitPublisher() {
this.publisher = this.OV.initPublisher(
'local-vid-' + this.session.connection.connectionId,
undefined,
{
audioSource: this.sendAudio ? undefined : false,
videoSource: this.sendVideo ? (this.optionsVideo === 'screen' ? 'screen' : undefined) : false,
publishAudio: this.activeAudio,
publishVideo: this.activeVideo,
resolution: '640x480',
frameRate: 30,
insertMode: VideoInsertMode.APPEND
frameRate: 30
},
(err) => {
if (err) {
@ -874,8 +460,6 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
}
});
this.addPublisherEvents(this.publisher);
if (this.subscribeToRemote) {
this.publisher.subscribeToRemote();
}
@ -897,7 +481,6 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
})
.then(publisher => {
this.publisher = publisher;
this.addPublisherEvents(this.publisher);
if (this.subscribeToRemote) {
this.publisher.subscribeToRemote();
}
@ -925,75 +508,7 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
}
syncSubscribe(session: Session, event) {
const subscriber: Subscriber = session.subscribe(event.stream, 'remote-vid-' + session.connection.connectionId);
this.subscribers[subscriber.stream.connection.connectionId] = {
'subscriber': subscriber,
'subbed': true,
'recorder': undefined,
'recording': false,
'paused': false,
'videoElement': undefined
};
subscriber.on('videoElementCreated', (e: VideoElementEvent) => {
if (!event.stream.hasVideo) {
$(e.element).css({ 'background-color': '#4d4d4d' });
$(e.element).attr('poster', 'assets/images/volume.png');
}
this.subscribers[subscriber.stream.connection.connectionId].videoElement = e.element;
this.updateEventList('videoElementCreated', e.element.id);
});
subscriber.on('videoPlaying', (e: VideoElementEvent) => {
this.appendSubscriberData(e.element, subscriber.stream.connection);
this.updateEventList('videoPlaying', e.element.id);
});
subscriber.on('videoElementDestroyed', (e) => {
this.updateEventList('videoElementDestroyed', '(Subscriber)');
});
}
asyncSubscribe(session: Session, event) {
session.subscribeAsync(event.stream, 'remote-vid-' + session.connection.connectionId)
.then(subscriber => {
this.subscribers[subscriber.stream.connection.connectionId] = {
'subscriber': subscriber,
'subbed': true,
'recorder': undefined,
'recording': false,
'paused': false,
'videoElement': undefined
};
subscriber.on('videoElementCreated', (e: VideoElementEvent) => {
if (!event.stream.hasVideo) {
$(e.element).css({ 'background-color': '#4d4d4d' });
$(e.element).attr('poster', 'assets/images/volume.png');
}
this.subscribers[subscriber.stream.connection.connectionId].videoElement = e.element;
this.updateEventList('videoElementCreated', e.element.id);
});
subscriber.on('videoPlaying', (e: VideoElementEvent) => {
this.appendSubscriberData(e.element, subscriber.stream.connection);
this.updateEventList('videoPlaying', e.element.id);
});
subscriber.on('videoElementDestroyed', (e) => {
this.updateEventList('videoElementDestroyed', '(Subscriber)');
});
})
.catch(err => {
console.error(err);
});
}
enableSpeakingEvents() {
this.session.on('publisherStartSpeaking', (event) => {
});
this.session.on('publisherStopSpeaking', (event) => {
});
}
disableSpeakingEvents() {
this.session.off('publisherStartSpeaking');
this.session.off('publisherStopSpeaking');
this.subscribers.push(session.subscribe(event.stream, undefined));
}
initGrayVideo(): void {
@ -1050,7 +565,7 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
this.sessionName = this.sessionProperties.customSessionId;
}
}
document.getElementById('session-settings-btn').classList.remove('cdk-program-focused');
document.getElementById('session-settings-btn-' + this.index).classList.remove('cdk-program-focused');
});
}
@ -1064,7 +579,54 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
});
dialogRef.afterClosed().subscribe((result: string) => {
document.getElementById('session-api-btn').classList.remove('cdk-program-focused');
document.getElementById('session-api-btn-' + this.index).classList.remove('cdk-program-focused');
});
}
openSessionEventsDialog() {
const oldValues = {
connectionCreated: this.sessionEvents.connectionCreated,
connectionDestroyed: this.sessionEvents.connectionDestroyed,
sessionDisconnected: this.sessionEvents.sessionDisconnected,
streamCreated: this.sessionEvents.streamCreated,
streamDestroyed: this.sessionEvents.streamDestroyed,
recordingStarted: this.sessionEvents.recordingStarted,
recordingStopped: this.sessionEvents.recordingStopped,
signal: this.sessionEvents.signal,
publisherStartSpeaking: this.sessionEvents.publisherStartSpeaking,
publisherStopSpeaking: this.sessionEvents.publisherStopSpeaking
};
const dialogRef = this.dialog.open(EventsDialogComponent, {
data: {
eventCollection: this.sessionEvents,
target: 'Session'
},
width: '280px',
autoFocus: false,
disableClose: true
});
dialogRef.afterClosed().subscribe((result) => {
if (!!this.session && JSON.stringify(this.sessionEvents) !== JSON.stringify(oldValues)) {
this.updateSessionEvents(oldValues, false);
}
this.sessionEvents = {
connectionCreated: result.connectionCreated,
connectionDestroyed: result.connectionDestroyed,
sessionDisconnected: result.sessionDisconnected,
streamCreated: result.streamCreated,
streamDestroyed: result.streamDestroyed,
recordingStarted: result.recordingStarted,
recordingStopped: result.recordingStopped,
signal: result.signal,
publisherStartSpeaking: result.publisherStartSpeaking,
publisherStopSpeaking: result.publisherStopSpeaking
};
document.getElementById('session-events-btn-' + this.index).classList.remove('cdk-program-focused');
});
}
@ -1079,4 +641,15 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
});
}
udpateEventFromChild(event) {
this.updateEventList(event.event, event.content);
}
updateSubscriberFromChild(newSubscriber: Subscriber) {
const oldSubscriber = this.subscribers.filter(sub => {
return sub.stream.streamId === newSubscriber.stream.streamId;
})[0];
this.subscribers[this.subscribers.indexOf(oldSubscriber)] = newSubscriber;
}
}

View File

@ -16,7 +16,7 @@
</div>
<div class="instance-div">
<app-openvidu-instance *ngFor="let user of users" [openviduUrl]="openviduUrl" [openviduSecret]="openviduSecret" [sessionConf]="user">
<app-openvidu-instance *ngFor="let user of users; let i=index" [openviduUrl]="openviduUrl" [openviduSecret]="openviduSecret" [sessionConf]="user" [index]="i">
</app-openvidu-instance>
</div>

View File

@ -0,0 +1,28 @@
import { Component, Input, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
import { StreamManager } from 'openvidu-browser';
@Component({
selector: 'app-ov-video',
template: '<video #videoElement [poster]=""></video>'
})
export class OpenViduVideoComponent implements AfterViewInit {
@ViewChild('videoElement') elementRef: ElementRef;
@Input() poster = '';
_streamManager: StreamManager;
ngAfterViewInit() {
this._streamManager.addVideoElement(this.elementRef.nativeElement);
}
@Input()
set streamManager(streamManager: StreamManager) {
this._streamManager = streamManager;
if (!!this.elementRef) {
this._streamManager.addVideoElement(this.elementRef.nativeElement);
}
}
}

View File

@ -0,0 +1,98 @@
app-ov-video {
float: left;
height: 90px;
}
.data-node {
width: 120px;
height: 90px;
float: left;
position: relative;
margin-left: -120px;
margin-top: 0;
}
p {
display: inline-flex;
vertical-align: top;
margin: 0;
background: #ffffff;
padding-left: 5px;
padding-right: 5px;
color: #797979;
font-weight: 100;
font-size: 14px;
}
.material-icons {
font-size: 17px;
width: 17px;
height: 17px;
line-height: 20px;
}
.video-btn {
border: none;
background: rgba(255, 255, 255, 0.75);
cursor: pointer;
padding: 0;
height: 20px;
float: left;
}
.video-btn:hover {
color: #4d4d4d;
}
.video-btn.top-row {
margin-top: 0;
display: inline-flex;
float: right;
}
.rec-btn {
float: right;
color: #ac0000;
}
.rec-btn:hover {
color: #ac000082;
}
.events-btn {
float: right;
}
.top-left-rounded {
border-top-left-radius: 2px;
}
.top-right-rounded {
border-top-right-radius: 2px;
}
.bottom-left-rounded {
border-bottom-left-radius: 2px;
}
.bottom-right-rounded {
border-bottom-right-radius: 2px;
}
.grey-background {
background-color: #4d4d4d
}
.top-div {
position: absolute;
top: 0;
right: 0;
width: 100%;
}
.bottom-div {
position: absolute;
bottom: 0;
height: 20px;
width: 100%;
}

View File

@ -0,0 +1,58 @@
<div>
<app-ov-video [streamManager]="streamManager" [poster]="videoPoster" class="{{videoClasses}}"></app-ov-video>
<div *ngIf="!streamManager.remote && showButtons" class="data-node">
<div class="top-div">
<button class="video-btn events-btn bottom-left-rounded" title="Publisher events" (click)="openPublisherEventsDialog()">
<mat-icon aria-label="Publisher events" class="mat-icon material-icons" role="img" aria-hidden="true">notifications</mat-icon>
</button>
</div>
<div class="bottom-div">
<button class="video-btn pub-btn" title="Publish/Unpublish" (click)="pubUnpub()">
<mat-icon aria-label="Publish or unpublish" class="mat-icon material-icons" role="img" aria-hidden="true">{{pubSubIcon}}</mat-icon>
</button>
<button class="video-btn pub-video-btn" title="Publish/Unpublish Video" (click)="pubUnpubVideo()">
<mat-icon aria-label="Publish or unpublish video" class="mat-icon material-icons" role="img" aria-hidden="true">{{pubSubVideoIcon}}</mat-icon>
</button>
<button class="video-btn pub-audio-btn" title="Publish/Unpublish Audio" (click)="pubUnpubAudio()">
<mat-icon aria-label="Publish or unpublish audio" class="mat-icon material-icons" role="img" aria-hidden="true">{{pubSubAudioIcon}}</mat-icon>
</button>
<button class="video-btn change-publisher-btn" title="Change publisher" (click)="changePub()">
<mat-icon aria-label="Change publisher" class="mat-icon material-icons" role="img" aria-hidden="true">switch_video</mat-icon>
</button>
<button class="video-btn rec-btn publisher-rec-btn" title="Record" (click)="record()">
<mat-icon aria-label="Start/Stop local recording" class="mat-icon material-icons" role="img" aria-hidden="true">
{{recordIcon}}</mat-icon>
</button>
<button *ngIf="!!pauseRecordIcon" class="video-btn rec-btn publisher-rec-btn publisher-rec-pause-btn" title="Pause/Resume"
(click)="pauseRecord()">
<mat-icon aria-label="Pause/Resume local recording" class="mat-icon material-icons" role="img" aria-hidden="true">
{{pauseRecordIcon}}</mat-icon>
</button>
</div>
</div>
<div *ngIf="streamManager.remote && showButtons" class="data-node">
<div class="top-div">
<p class="name bottom-right-rounded">{{streamManager.stream.connection.data}}</p>
<button *ngIf="subbed" class="video-btn events-btn bottom-left-rounded" title="Subscriber events" (click)="openSubscriberEventsDialog()">
<mat-icon aria-label="Subscriber events" class="mat-icon material-icons" role="img" aria-hidden="true">notifications</mat-icon>
</button>
</div>
<div class="bottom-div">
<button class="video-btn sub-btn" title="Subscribe/Unsubscribe" (click)="subUnsub()">
<mat-icon aria-label="Subscribe or unsubscribe" class="mat-icon material-icons" role="img" aria-hidden="true">{{pubSubIcon}}</mat-icon>
</button>
<button *ngIf="!!pubSubVideoIcon" class="video-btn sub-video-btn" title="Subscribe/Unsubscribe Video" (click)="subUnsubVideo()">
<mat-icon aria-label="Subscribe or unsubscribe video" class="mat-icon material-icons" role="img" aria-hidden="true">{{pubSubVideoIcon}}</mat-icon>
</button>
<button *ngIf="!!pubSubAudioIcon" class="video-btn sub-audio-btn" title="Subscribe/Unsubscribe Audio" (click)="subUnsubAudio()">
<mat-icon aria-label="Subscribe or unsubscribe audio" class="mat-icon material-icons" role="img" aria-hidden="true">{{pubSubAudioIcon}}</mat-icon>
</button>
<button *ngIf="!!recordIcon" class="video-btn rec-btn" title="Record" (click)="record()">
<mat-icon aria-label="Start/Stop recording" class="mat-icon material-icons" role="img" aria-hidden="true">{{recordIcon}}</mat-icon>
</button>
<button *ngIf="!!pauseRecordIcon" class="video-btn rec-btn rec-pause-btn" title="Pause/Resume" (click)="pauseRecord()">
<mat-icon aria-label="Pause/Resume recording" class="mat-icon material-icons" role="img" aria-hidden="true">{{pauseRecordIcon}}</mat-icon>
</button>
</div>
</div>
</div>

View File

@ -0,0 +1,569 @@
import { Component, Input, OnInit, ViewChild, ElementRef, Output, EventEmitter, OnDestroy } from '@angular/core';
import { MatDialog, MatDialogRef } from '@angular/material';
import {
StreamManager,
StreamManagerEvent,
VideoElementEvent,
Subscriber,
Session,
LocalRecorder,
OpenVidu,
Publisher,
StreamEvent,
VideoInsertMode
} from 'openvidu-browser';
import { EventsDialogComponent } from '../dialogs/events-dialog.component';
import { MuteSubscribersService } from '../../services/mute-subscribers.service';
import { Subscription } from 'rxjs/Subscription';
import { LocalRecordingDialogComponent } from '../dialogs/local-recording-dialog.component';
import { ExtensionDialogComponent } from '../dialogs/extension-dialog.component';
import { OpenViduVideoComponent } from './ov-video.component';
@Component({
selector: 'app-video',
templateUrl: './video.component.html',
styleUrls: ['./video.component.css']
})
export class VideoComponent implements OnInit, OnDestroy {
@Input() streamManager: StreamManager;
@Input() OV: OpenVidu;
@Input() eventCollection: any;
@Output() updateEventListInParent = new EventEmitter();
@Output() reSubbed = new EventEmitter();
subbed = true;
subbedVideo = true;
subbedAudio = true;
recorder = undefined;
recording = false;
recordingPaused = false;
videoElement = undefined;
showButtons = false;
videoClasses = '';
videoPoster = '';
unpublished = false;
publisherChanged = false;
audioMuted = false;
videoMuted = false;
sendAudio = true;
sendVideo = true;
sendAudioChange = false;
sendVideoChange = false;
optionsVideo = '';
private muteSubscribersSubscription: Subscription;
// Icons
pubSubIcon = 'stop';
pubSubVideoIcon = 'videocam';
pubSubAudioIcon = 'mic';
recordIcon = 'fiber_manual_record';
pauseRecordIcon = '';
constructor(private dialog: MatDialog, private muteSubscribersService: MuteSubscribersService
) { }
ngOnInit() {
if (this.streamManager.remote) {
// Init subscriber events
this.eventCollection = {
videoElementCreated: true,
videoElementDestroyed: true,
streamPlaying: true
};
this.updateSubscriberEvents({
videoElementCreated: false,
videoElementDestroyed: false,
streamPlaying: false
});
} else {
// Init publisher events
this.eventCollection = {
videoElementCreated: true,
videoElementDestroyed: true,
streamPlaying: true,
accessAllowed: true,
accessDenied: true,
accessDialogOpened: true,
accessDialogClosed: true,
streamCreated: true,
streamDestroyed: true
};
this.updatePublisherEvents(
<Publisher>this.streamManager,
{
videoElementCreated: false,
videoElementDestroyed: false,
streamPlaying: false,
accessAllowed: false,
accessDenied: false,
accessDialogOpened: false,
accessDialogClosed: false,
streamCreated: false,
streamDestroyed: false
});
this.sendAudio = this.streamManager.stream.hasAudio;
this.sendVideo = this.streamManager.stream.hasVideo;
this.optionsVideo = this.streamManager.stream.typeOfVideo;
}
this.muteSubscribersSubscription = this.muteSubscribersService.mutedEvent$.subscribe(muteOrUnmute => {
this.streamManager.videos.forEach(v => {
v.video.muted = muteOrUnmute;
});
});
}
ngOnDestroy() {
if (!!this.recorder) {
this.recorder.clean();
}
if (!!this.muteSubscribersSubscription) { this.muteSubscribersSubscription.unsubscribe(); }
}
subUnsub() {
const subscriber: Subscriber = <Subscriber>this.streamManager;
if (this.subbed) {
this.streamManager.stream.session.unsubscribe(subscriber);
this.restartRecorder();
this.pubSubVideoIcon = '';
this.pubSubAudioIcon = '';
this.recordIcon = '';
this.pauseRecordIcon = '';
this.pubSubIcon = 'play_arrow';
this.subbedVideo = false;
this.subbedAudio = false;
} else {
const oldValues = {
videoElementCreated: this.eventCollection.videoElementCreated,
videoElementDestroyed: this.eventCollection.videoElementDestroyed,
streamPlaying: this.eventCollection.streamPlaying
};
this.streamManager = this.streamManager.stream.session.subscribe(subscriber.stream, undefined);
this.reSubbed.emit(this.streamManager);
this.pubSubVideoIcon = 'videocam';
this.pubSubAudioIcon = 'mic';
this.recordIcon = 'fiber_manual_record';
this.pauseRecordIcon = '';
this.pubSubIcon = 'stop';
this.subbedVideo = true;
this.subbedAudio = true;
this.updateSubscriberEvents(oldValues);
}
this.subbed = !this.subbed;
}
subUnsubVideo(connectionId: string) {
const subscriber: Subscriber = <Subscriber>this.streamManager;
this.subbedVideo = !this.subbedVideo;
subscriber.subscribeToVideo(this.subbedVideo);
this.pubSubVideoIcon = this.subbedVideo ? 'videocam' : 'videocam_off';
}
subUnsubAudio(connectionId: string) {
const subscriber: Subscriber = <Subscriber>this.streamManager;
this.subbedAudio = !this.subbedAudio;
subscriber.subscribeToAudio(this.subbedAudio);
this.pubSubAudioIcon = this.subbedAudio ? 'mic' : 'mic_off';
}
pubUnpub() {
const publisher: Publisher = <Publisher>this.streamManager;
if (this.unpublished) {
this.streamManager.stream.session.publish(publisher)
.then(() => {
console.log(publisher);
})
.catch(e => {
console.error(e);
});
} else {
this.streamManager.stream.session.unpublish(publisher);
}
this.unpublished = !this.unpublished;
this.unpublished ? this.pubSubIcon = 'play_arrow' : this.pubSubIcon = 'stop';
}
pubUnpubVideo() {
const publisher: Publisher = <Publisher>this.streamManager;
publisher.publishVideo(this.videoMuted);
this.videoMuted = !this.videoMuted;
this.pubSubVideoIcon = this.videoMuted ? 'videocam_off' : 'videocam';
}
pubUnpubAudio() {
const publisher: Publisher = <Publisher>this.streamManager;
publisher.publishAudio(this.audioMuted);
this.audioMuted = !this.audioMuted;
this.pubSubAudioIcon = this.audioMuted ? 'mic_off' : 'mic';
}
changePub() {
let screenChange;
if (!this.publisherChanged) {
if (this.sendAudio && !this.sendVideo) {
this.sendAudioChange = false;
this.sendVideoChange = true;
screenChange = false;
} else if (!this.sendAudio && this.sendVideo) {
this.sendAudioChange = true;
this.sendVideoChange = false;
} else if (this.sendAudio && this.sendVideo && this.optionsVideo === 'CAMERA') {
this.sendAudioChange = false;
this.sendVideoChange = true;
screenChange = true;
} else if (this.sendAudio && this.sendVideo && this.optionsVideo === 'SCREEN') {
this.sendAudioChange = false;
this.sendVideoChange = true;
screenChange = false;
}
} else {
this.sendAudioChange = this.sendAudio;
this.sendVideoChange = this.sendVideo;
screenChange = this.optionsVideo === 'SCREEN' ? true : false;
}
this.audioMuted = false;
this.videoMuted = false;
this.unpublished = false;
const otherPublisher = this.OV.initPublisher(
undefined,
{
audioSource: this.sendAudioChange ? undefined : false,
videoSource: this.sendVideoChange ? (screenChange ? 'screen' : undefined) : false,
publishAudio: (!this.publisherChanged) ? true : !this.audioMuted,
publishVideo: (!this.publisherChanged) ? true : !this.videoMuted,
resolution: '640x480',
frameRate: 30,
insertMode: VideoInsertMode.APPEND
},
(err) => {
if (err) {
console.warn(err);
if (err.name === 'SCREEN_EXTENSION_NOT_INSTALLED') {
this.dialog.open(ExtensionDialogComponent, {
data: { url: err.message },
disableClose: true,
width: '250px'
});
}
}
});
this.updatePublisherEvents(otherPublisher, {
videoElementCreated: !this.eventCollection.videoElementCreated,
videoElementDestroyed: !this.eventCollection.videoElementDestroyed,
streamPlaying: !this.eventCollection.streamPlaying,
accessAllowed: !this.eventCollection.accessAllowed,
accessDenied: !this.eventCollection.accessDenied,
accessDialogOpened: !this.eventCollection.accessDialogOpened,
accessDialogClosed: !this.eventCollection.accessDialogClosed,
streamCreated: !this.eventCollection.streamCreated,
streamDestroyed: !this.eventCollection.streamDestroyed
});
otherPublisher.once('accessAllowed', () => {
if (!this.unpublished) {
this.streamManager.stream.session.unpublish(<Publisher>this.streamManager);
this.streamManager = otherPublisher;
}
this.streamManager.stream.session.publish(otherPublisher).then(() => {
console.log(this.streamManager);
});
});
this.publisherChanged = !this.publisherChanged;
}
updateSubscriberEvents(oldValues) {
const sub: Subscriber = <Subscriber>this.streamManager;
if (this.eventCollection.videoElementCreated) {
if (!oldValues.videoElementCreated) {
sub.on('videoElementCreated', (event: VideoElementEvent) => {
if (!sub.stream.hasVideo) {
this.videoClasses = 'grey-background';
this.videoPoster = 'assets/images/volume.png';
} else {
this.videoClasses = '';
this.videoPoster = '';
}
this.updateEventListInParent.emit({
event: 'videoElementCreated',
content: event.element.id
});
});
}
} else {
sub.off('videoElementCreated');
}
if (this.eventCollection.videoElementDestroyed) {
if (!oldValues.videoElementDestroyed) {
sub.on('videoElementDestroyed', (event: VideoElementEvent) => {
this.showButtons = false;
this.updateEventListInParent.emit({
event: 'videoElementDestroyed',
content: event.element.id
});
});
}
} else {
sub.off('videoElementDestroyed');
}
if (this.eventCollection.streamPlaying) {
if (!oldValues.streamPlaying) {
sub.on('streamPlaying', (event: StreamManagerEvent) => {
this.showButtons = true;
this.updateEventListInParent.emit({
event: 'streamPlaying',
content: this.streamManager.stream.streamId
});
});
}
} else {
sub.off('streamPlaying');
}
}
updatePublisherEvents(pub: Publisher, oldValues: any) {
if (this.eventCollection.videoElementCreated) {
if (!oldValues.videoElementCreated) {
pub.on('videoElementCreated', (event: VideoElementEvent) => {
if (!pub.stream.hasVideo) {
this.videoClasses = 'grey-background';
this.videoPoster = 'assets/images/volume.png';
} else {
this.videoClasses = '';
this.videoPoster = '';
}
this.updateEventListInParent.emit({
event: 'videoElementCreated',
content: event.element.id
});
});
}
} else {
pub.off('videoElementCreated');
}
if (this.eventCollection.accessAllowed) {
if (!oldValues.accessAllowed) {
pub.on('accessAllowed', (e) => {
this.updateEventListInParent.emit({
event: 'accessAllowed',
content: ''
});
});
}
} else {
pub.off('accessAllowed');
}
if (this.eventCollection.accessDenied) {
if (!oldValues.accessDenied) {
pub.on('accessDenied', (e) => {
this.updateEventListInParent.emit({
event: 'accessDenied',
content: ''
});
});
}
} else {
pub.off('accessDenied');
}
if (this.eventCollection.accessDialogOpened) {
if (!oldValues.accessDialogOpened) {
pub.on('accessDialogOpened', (e) => {
this.updateEventListInParent.emit({
event: 'accessDialogOpened',
content: ''
});
});
}
} else {
pub.off('accessDialogOpened');
}
if (this.eventCollection.accessDialogClosed) {
if (!oldValues.accessDialogClosed) {
pub.on('accessDialogClosed', (e) => {
this.updateEventListInParent.emit({
event: 'accessDialogClosed',
content: ''
});
});
}
} else {
pub.off('accessDialogClosed');
}
if (this.eventCollection.streamCreated) {
if (!oldValues.streamCreated) {
pub.on('streamCreated', (e: StreamEvent) => {
this.updateEventListInParent.emit({
event: 'streamCreated',
content: e.stream.streamId
});
});
}
} else {
pub.off('streamCreated');
}
if (this.eventCollection.streamDestroyed) {
if (!oldValues.streamDestroyed) {
pub.on('streamDestroyed', (e: StreamEvent) => {
this.updateEventListInParent.emit({
event: 'streamDestroyed',
content: e.stream.streamId
});
});
}
} else {
pub.off('streamDestroyed');
}
if (this.eventCollection.videoElementDestroyed) {
if (!oldValues.videoElementDestroyed) {
pub.on('videoElementDestroyed', (e: VideoElementEvent) => {
this.updateEventListInParent.emit({
event: 'videoElementDestroyed',
content: '(Publisher)'
});
});
}
} else {
pub.off('videoElementDestroyed');
}
if (this.eventCollection.streamPlaying) {
if (!oldValues.streamPlaying) {
pub.on('streamPlaying', (event: StreamManagerEvent) => {
this.showButtons = true;
this.updateEventListInParent.emit({
event: 'streamPlaying',
content: this.streamManager.stream.streamId
});
});
}
} else {
pub.off('streamPlaying');
}
}
openSubscriberEventsDialog() {
const oldValues = {
videoElementCreated: this.eventCollection.videoElementCreated,
videoElementDestroyed: this.eventCollection.videoElementDestroyed,
streamPlaying: this.eventCollection.streamPlaying
};
const dialogRef = this.dialog.open(EventsDialogComponent, {
data: {
eventCollection: this.eventCollection,
target: 'Subscriber'
},
width: '280px',
autoFocus: false,
disableClose: true
});
dialogRef.afterClosed().subscribe((result) => {
this.updateSubscriberEvents(oldValues);
});
}
openPublisherEventsDialog() {
const oldValues = {
videoElementCreated: this.eventCollection.videoElementCreated,
videoElementDestroyed: this.eventCollection.videoElementDestroyed,
streamPlaying: this.eventCollection.streamPlaying,
accessAllowed: this.eventCollection.accessAllowed,
accessDenied: this.eventCollection.accessDenied,
accessDialogOpened: this.eventCollection.accessDialogOpened,
accessDialogClosed: this.eventCollection.accessDialogClosed,
streamCreated: this.eventCollection.streamCreated,
streamDestroyed: this.eventCollection.streamDestroyed
};
const dialogRef = this.dialog.open(EventsDialogComponent, {
data: {
eventCollection: this.eventCollection,
target: 'Publisher'
},
width: '280px',
autoFocus: false,
disableClose: true
});
dialogRef.afterClosed().subscribe((result) => {
this.updatePublisherEvents(<Publisher>this.streamManager, oldValues);
});
}
record(): void {
if (!this.recording) {
this.recorder = this.OV.initLocalRecorder(this.streamManager.stream);
this.recorder.record();
this.recording = true;
this.recordIcon = 'stop';
this.pauseRecordIcon = 'pause';
} else {
this.recorder.stop()
.then(() => {
let dialogRef: MatDialogRef<LocalRecordingDialogComponent>;
dialogRef = this.dialog.open(LocalRecordingDialogComponent, {
disableClose: true,
data: {
recorder: this.recorder
}
});
dialogRef.componentInstance.myReference = dialogRef;
dialogRef.afterOpen().subscribe(() => {
this.muteSubscribersService.updateMuted(true);
this.recorder.preview('recorder-preview').controls = true;
});
dialogRef.afterClosed().subscribe(() => {
this.muteSubscribersService.updateMuted(false);
this.restartRecorder();
});
})
.catch((error) => {
console.error('Error stopping LocalRecorder: ' + error);
});
}
}
pauseRecord(): void {
if (!this.recordingPaused) {
this.recorder.pause();
this.pauseRecordIcon = 'play_arrow';
} else {
this.recorder.resume();
this.pauseRecordIcon = 'pause';
}
this.recordingPaused = !this.recordingPaused;
}
private restartRecorder() {
this.recording = false;
this.recordingPaused = false;
this.recordIcon = 'fiber_manual_record';
this.pauseRecordIcon = '';
if (!!this.recorder) {
this.recorder.clean();
}
}
}

View File

@ -9,11 +9,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha256-k2WSCIexGzOj3Euiig+TlR8gA0EmPjuc79OEeY5L45g="
crossorigin="anonymous"></script>
<script type="text/javascript">
window.myEvents = '';
</script>

View File

@ -0,0 +1,23 @@
/* fallback */
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(/assets/fonts/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2');
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
}

View File

@ -38,67 +38,6 @@ button {
line-height: 15px !important;
}
.video-container video {
float: left;
}
.video-container div.data-node {
width: 120px;
height: 90px;
float: left;
position: relative;
margin-left: -120px;
margin-top: 0;
}
.video-container p {
margin-top: 0;
width: fit-content;
background: #ffffff;
padding-left: 5px;
padding-right: 5px;
color: #797979;
font-weight: 100;
font-size: 14px;
border-bottom-right-radius: 2px;
}
.video-container div.data-node .sub-btn {
outline: 0;
border: none;
background: rgba(255, 255, 255, 0.75);
cursor: pointer;
padding: 0;
margin-top: 40px;
border-top-right-radius: 2px;
}
.video-container div.data-node .sub-btn:hover {
color: #4d4d4d;
}
.video-container div.data-node .rec-btn {
float: right;
border-top-right-radius: 0;
border-top-left-radius: 2px;
color: #ac0000;
}
.video-container div.data-node .rec-btn:hover {
color: #ac000082;
}
.video-container div.data-node .rec-btn.publisher-rec-btn {
margin-top: 70px;
}
.video-container div.data-node .material-icons {
font-size: 17px;
width: 17px;
height: 17px;
line-height: 20px;
}
.mat-expansion-panel-body {
font-size: 9.5px !important;
padding: 0 9px 0px !important;