mirror of https://github.com/OpenVidu/openvidu.git
Resolution and framrate selection. Statistics are shwon
parent
f60fae576a
commit
7a3c064068
|
@ -33,7 +33,7 @@ OpenVidu is composed by several modules which require some interconnections in o
|
||||||
|
|
||||||
Here's a simple summary about the structure of OpenVidu:
|
Here's a simple summary about the structure of OpenVidu:
|
||||||
|
|
||||||
<center></center>
|

|
||||||
|
|
||||||
|
|
||||||
- **Kurento Media Server**: External module which provides the low-level functionalities related to the media transmission.
|
- **Kurento Media Server**: External module which provides the low-level functionalities related to the media transmission.
|
||||||
|
|
|
@ -15,7 +15,7 @@ It uses WebSockets and JSON-RPC to interact with the server-side of the Room API
|
||||||
|
|
||||||
Typescript is currently used to develop openvidu-browser. The class diagram is shown below:
|
Typescript is currently used to develop openvidu-browser. The class diagram is shown below:
|
||||||
|
|
||||||
<center></center>
|

|
||||||
|
|
||||||
What is Kurento
|
What is Kurento
|
||||||
---------------
|
---------------
|
||||||
|
|
|
@ -256,7 +256,9 @@ export class Stream {
|
||||||
|
|
||||||
this.participant.addStream(this);
|
this.participant.addStream(this);
|
||||||
|
|
||||||
let constraints = {
|
let constraints = this.mediaConstraints;
|
||||||
|
|
||||||
|
let constraints2 = {
|
||||||
audio: true,
|
audio: true,
|
||||||
video: {
|
video: {
|
||||||
width: {
|
width: {
|
||||||
|
@ -274,7 +276,8 @@ export class Stream {
|
||||||
userStream.getVideoTracks()[0].enabled = this.sendVideo;
|
userStream.getVideoTracks()[0].enabled = this.sendVideo;
|
||||||
|
|
||||||
this.wrStream = userStream;
|
this.wrStream = userStream;
|
||||||
callback(undefined, this);})
|
callback(undefined, this);
|
||||||
|
})
|
||||||
.catch(function (e) {
|
.catch(function (e) {
|
||||||
console.error("Access denied", e);
|
console.error("Access denied", e);
|
||||||
callback(e, undefined);
|
callback(e, undefined);
|
||||||
|
|
|
@ -1,34 +1,75 @@
|
||||||
<div *ngIf="!session">
|
<div *ngIf="!session">
|
||||||
<h1>Join a video session</h1>
|
<h1>Join a video session</h1>
|
||||||
<form (submit)="joinSession()" accept-charset="UTF-8">
|
<form (submit)="joinSession()" accept-charset="UTF-8">
|
||||||
<p>
|
<div>
|
||||||
<label>Participant:</label>
|
<label>Participant:</label>
|
||||||
<input type="text" name="participantId" [(ngModel)]="participantId" required>
|
<input type="text" name="participantId" [(ngModel)]="participantId" required>
|
||||||
</p>
|
</div>
|
||||||
<p>
|
<div>
|
||||||
<label>Session:</label>
|
<label>Session:</label>
|
||||||
<input type="text" name="sessionId" [(ngModel)]="sessionId" required>
|
<input type="text" name="sessionId" [(ngModel)]="sessionId" required>
|
||||||
</p>
|
</div>
|
||||||
<p>
|
<div>
|
||||||
<input type="checkbox" [(ngModel)]="joinWithVideo" id="join-with-video" name="join-with-video"> Send video
|
<input type="checkbox" [(ngModel)]="joinWithVideo" id="join-with-video" name="join-with-video"> Send video
|
||||||
</p>
|
<div *ngIf="joinWithVideo">
|
||||||
<p>
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Constraint</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>width</td>
|
||||||
|
<td><input type="text" id="width" required></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>height</td>
|
||||||
|
<td><input type="text" id="height" required></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>frameRate</td>
|
||||||
|
<td><input type="text" id="frameRate" required></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
<input type="checkbox" [(ngModel)]="joinWithAudio" id="join-with-audio" name="join-with-audio"> Send audio
|
<input type="checkbox" [(ngModel)]="joinWithAudio" id="join-with-audio" name="join-with-audio"> Send audio
|
||||||
</p>
|
</div>
|
||||||
<p>
|
<div>
|
||||||
<input type="submit" name="commit" value="Join!">
|
<input type="submit" name="commit" value="Join!">
|
||||||
</p>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="session">
|
<div *ngIf="session">
|
||||||
<h2>{{sessionId}}</h2>
|
<h2>{{sessionId}}</h2>
|
||||||
<input type="button" (click)="leaveSession()" value="Leave session">
|
<input type="button" (click)="leaveSession()" value="Leave session">
|
||||||
<input type="checkbox" id="toggle-video" name="toggle-video"
|
<input type="checkbox" id="toggle-video" name="toggle-video" [checked]="toggleVideo" (change)="updateToggleVideo($event)"> Toggle your video
|
||||||
[checked]="toggleVideo" (change)="updateToggleVideo($event)"> Toggle your video
|
<input type="checkbox" id="toggle-audio" name="toggle-audio" [checked]="toggleAudio" (change)="updateToggleAudio($event)"> Toggle your audio
|
||||||
<input type="checkbox" id="toggle-audio" name="toggle-audio"
|
|
||||||
[checked]="toggleAudio" (change)="updateToggleAudio($event)"> Toggle your audio
|
|
||||||
<div>
|
<div>
|
||||||
<stream *ngFor="let s of streams" [stream]="s"></stream>
|
<div *ngFor="let s of streams; let i = index" style="display: table-row;">
|
||||||
|
<div style="display: inline; top: 0;">
|
||||||
|
<stream [stream]="s"></stream>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="stats[i]" style="display: inline-block;">
|
||||||
|
<div style="display: inline; margin-left: 50px;">
|
||||||
|
<div *ngIf="stats[i].bitrate">
|
||||||
|
<h2>Bitrate: {{stats[i].bitrate}}</h2>
|
||||||
|
</div>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Time</th>
|
||||||
|
<th>Other attributes</th>
|
||||||
|
</tr>
|
||||||
|
<tr *ngFor="let st of stats[i].statsArray">
|
||||||
|
<td>{{st.type}}</td>
|
||||||
|
<td>{{st.timestamp}}</td>
|
||||||
|
<td>{{getStatAttributes(st.res)}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { Observable } from 'rxjs/Rx';
|
||||||
|
import { enableDebugTools } from '@angular/platform-browser';
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { OpenVidu, Session, Stream } from 'openvidu-browser';
|
import { OpenVidu, Session, Stream } from 'openvidu-browser';
|
||||||
|
|
||||||
|
@ -18,16 +20,24 @@ export class AppComponent {
|
||||||
streams: Stream[] = [];
|
streams: Stream[] = [];
|
||||||
|
|
||||||
// Publish options
|
// Publish options
|
||||||
joinWithVideo: boolean = true;
|
joinWithVideo: boolean = false;
|
||||||
joinWithAudio: boolean = true;
|
joinWithAudio: boolean = false;
|
||||||
toggleVideo: boolean;
|
toggleVideo: boolean;
|
||||||
toggleAudio: boolean;
|
toggleAudio: boolean;
|
||||||
|
|
||||||
|
//Statistics
|
||||||
|
stats = [];
|
||||||
|
bytesPrev = [];
|
||||||
|
timestampPrev = [];
|
||||||
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.generateParticipantInfo();
|
this.generateParticipantInfo();
|
||||||
window.onbeforeunload = () => {
|
window.onbeforeunload = () => {
|
||||||
this.openVidu.close(true);
|
this.openVidu.close(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//this.obtainSupportedConstraints();
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateParticipantInfo() {
|
private generateParticipantInfo() {
|
||||||
|
@ -38,19 +48,32 @@ export class AppComponent {
|
||||||
private addVideoTag(stream: Stream) {
|
private addVideoTag(stream: Stream) {
|
||||||
console.log("Stream added");
|
console.log("Stream added");
|
||||||
this.streams.push(stream);
|
this.streams.push(stream);
|
||||||
|
|
||||||
|
//For statistics
|
||||||
|
this.timestampPrev.push(0);
|
||||||
|
this.bytesPrev.push(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeVideoTag(stream: Stream) {
|
private removeVideoTag(stream: Stream) {
|
||||||
console.log("Stream removed");
|
console.log("Stream removed");
|
||||||
this.streams.slice(this.streams.indexOf(stream), 1);
|
let index = this.streams.indexOf(stream);
|
||||||
|
this.streams.splice(index, 1);
|
||||||
|
|
||||||
|
this.stats.splice(index, 1);
|
||||||
|
this.timestampPrev.splice(index, 1);
|
||||||
|
this.bytesPrev.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
joinSession() {
|
joinSession() {
|
||||||
|
let mediaConstraints = this.generateMediaConstraints();
|
||||||
|
|
||||||
|
console.log(mediaConstraints);
|
||||||
|
|
||||||
var cameraOptions = {
|
var cameraOptions = {
|
||||||
audio: this.joinWithAudio,
|
audio: this.joinWithAudio,
|
||||||
video: this.joinWithVideo,
|
video: this.joinWithVideo,
|
||||||
data: true,
|
data: true,
|
||||||
mediaConstraints: {}
|
mediaConstraints: mediaConstraints
|
||||||
}
|
}
|
||||||
this.joinSessionShared(cameraOptions);
|
this.joinSessionShared(cameraOptions);
|
||||||
}
|
}
|
||||||
|
@ -90,6 +113,8 @@ export class AppComponent {
|
||||||
|
|
||||||
camera.publish();
|
camera.publish();
|
||||||
|
|
||||||
|
this.intervalStats().subscribe();
|
||||||
|
|
||||||
session.addEventListener("stream-added", streamEvent => {
|
session.addEventListener("stream-added", streamEvent => {
|
||||||
this.addVideoTag(streamEvent.stream);
|
this.addVideoTag(streamEvent.stream);
|
||||||
console.log("Stream " + streamEvent.stream + " added");
|
console.log("Stream " + streamEvent.stream + " added");
|
||||||
|
@ -124,4 +149,96 @@ export class AppComponent {
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*obtainSupportedConstraints() {
|
||||||
|
let constraints = Object.keys(navigator.mediaDevices.getSupportedConstraints());
|
||||||
|
this.supportedVideoContstraints = constraints.filter((e) => {
|
||||||
|
return this.mediaTrackSettingsVideo.indexOf(e) > -1;
|
||||||
|
});
|
||||||
|
this.supportedAudioContstraints = constraints.filter((e) => {
|
||||||
|
return this.mediaTrackSettingsAudio.indexOf(e) > -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(constraints);
|
||||||
|
console.log(this.supportedVideoContstraints);
|
||||||
|
console.log(this.supportedAudioContstraints);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
generateMediaConstraints() {
|
||||||
|
let mediaConstraints = {
|
||||||
|
audio: true,
|
||||||
|
video: {}
|
||||||
|
}
|
||||||
|
if (this.joinWithVideo) {
|
||||||
|
mediaConstraints.video['width'] = { exact: Number((<HTMLInputElement>document.getElementById('width')).value) };
|
||||||
|
mediaConstraints.video['height'] = { exact: Number((<HTMLInputElement>document.getElementById('height')).value) };
|
||||||
|
mediaConstraints.video['frameRate'] = { ideal: Number((<HTMLInputElement>document.getElementById('frameRate')).value) };
|
||||||
|
}
|
||||||
|
|
||||||
|
return mediaConstraints;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
intervalStats() {
|
||||||
|
return Observable
|
||||||
|
.interval(1000)
|
||||||
|
.flatMap(() => {
|
||||||
|
let i = 0;
|
||||||
|
for (let str of this.streams) {
|
||||||
|
if (str.getWebRtcPeer().peerConnection) {
|
||||||
|
this.intervalStatsAux(i, str);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
intervalStatsAux(i: number, stream: Stream) {
|
||||||
|
stream.getWebRtcPeer().peerConnection.getStats(null)
|
||||||
|
.then((results) => {
|
||||||
|
this.stats[i] = this.dumpStats(results, i);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
dumpStats(results, i) {
|
||||||
|
var statsArray = [];
|
||||||
|
let bitrate;
|
||||||
|
|
||||||
|
results.forEach((res) => {
|
||||||
|
let date = new Date(res.timestamp);
|
||||||
|
statsArray.push({ res: res, type: res.type, timestamp: date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds() });
|
||||||
|
|
||||||
|
let now = res.timestamp;
|
||||||
|
|
||||||
|
if (res.type === 'inbound-rtp' && res.mediaType === 'video') {
|
||||||
|
// firefox calculates the bitrate for us
|
||||||
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=951496
|
||||||
|
bitrate = Math.floor(res.bitrateMean / 1024);
|
||||||
|
|
||||||
|
} else if (res.type === 'ssrc' && res.bytesReceived && res.googFrameRateReceived) {
|
||||||
|
// chrome does not so we need to do it ourselves
|
||||||
|
var bytes = res.bytesReceived;
|
||||||
|
if (this.timestampPrev[i]) {
|
||||||
|
bitrate = 8 * (bytes - this.bytesPrev[i]) / (now - this.timestampPrev[i]);
|
||||||
|
bitrate = Math.floor(bitrate);
|
||||||
|
}
|
||||||
|
this.bytesPrev[i] = bytes;
|
||||||
|
this.timestampPrev[i] = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
if (bitrate) {
|
||||||
|
bitrate += ' kbits/sec';
|
||||||
|
}
|
||||||
|
return { statsArray: statsArray, bitrate: bitrate };
|
||||||
|
}
|
||||||
|
|
||||||
|
getStatAttributes(stat) {
|
||||||
|
let s = '';
|
||||||
|
Object.keys(stat).forEach((key) => {
|
||||||
|
if (key != 'type' && key != 'timestamp') s += (' | ' + key + ' | ');
|
||||||
|
});
|
||||||
|
return s;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -7,12 +7,10 @@ import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
||||||
styles: [`
|
styles: [`
|
||||||
.participant {
|
.participant {
|
||||||
float: left;
|
float: left;
|
||||||
width: 20%;
|
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
}
|
}
|
||||||
.participant video {
|
.participant video {
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}`],
|
}`],
|
||||||
template: `
|
template: `
|
||||||
<div class='participant'>
|
<div class='participant'>
|
||||||
|
|
Loading…
Reference in New Issue