diff --git a/README.md b/README.md
index 7acbbdc4..9499ff51 100644
--- a/README.md
+++ b/README.md
@@ -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:
-

+
- **Kurento Media Server**: External module which provides the low-level functionalities related to the media transmission.
diff --git a/openvidu-browser/README.md b/openvidu-browser/README.md
index b322aa8b..dab8aae7 100644
--- a/openvidu-browser/README.md
+++ b/openvidu-browser/README.md
@@ -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:
-
+
What is Kurento
---------------
diff --git a/openvidu-browser/src/main/resources/ts/Stream.ts b/openvidu-browser/src/main/resources/ts/Stream.ts
index a05978d2..6edd2caa 100644
--- a/openvidu-browser/src/main/resources/ts/Stream.ts
+++ b/openvidu-browser/src/main/resources/ts/Stream.ts
@@ -15,15 +15,15 @@ import 'webrtc-adapter';
declare var navigator: any;
declare var RTCSessionDescription: any;
-function jq(id: string):string {
+function jq(id: string): string {
return id.replace(/(@|:|\.|\[|\]|,)/g, "\\$1");
}
-function show(id: string){
+function show(id: string) {
document.getElementById(jq(id))!.style.display = 'block';
}
-function hide(id: string){
+function hide(id: string) {
document.getElementById(jq(id))!.style.display = 'none';
}
@@ -65,9 +65,9 @@ export class Stream {
private dataChannel: boolean;
private dataChannelOpened = false;
- constructor( private openVidu: OpenVidu, private local: boolean, private room: Session, options: StreamOptions ) {
+ constructor(private openVidu: OpenVidu, private local: boolean, private room: Session, options: StreamOptions) {
- if ( options.id ) {
+ if (options.id) {
this.id = options.id;
} else {
this.id = "webcam";
@@ -99,10 +99,10 @@ export class Stream {
return this.showMyRemote;
}
- mirrorLocalStream( wr ) {
+ mirrorLocalStream(wr) {
this.showMyRemote = true;
this.localMirrored = true;
- if ( wr ) {
+ if (wr) {
this.wrStream = wr;
}
}
@@ -125,25 +125,25 @@ export class Stream {
return this.dataChannelOpened;
}
- onDataChannelOpen( event ) {
- console.log( 'Data channel is opened' );
+ onDataChannelOpen(event) {
+ console.log('Data channel is opened');
this.dataChannelOpened = true;
}
- onDataChannelClosed( event ) {
- console.log( 'Data channel is closed' );
+ onDataChannelClosed(event) {
+ console.log('Data channel is closed');
this.dataChannelOpened = false;
}
- sendData( data ) {
- if ( this.wp === undefined ) {
- throw new Error( 'WebRTC peer has not been created yet' );
+ sendData(data) {
+ if (this.wp === undefined) {
+ throw new Error('WebRTC peer has not been created yet');
}
- if ( !this.dataChannelOpened ) {
- throw new Error( 'Data channel is not opened' );
+ if (!this.dataChannelOpened) {
+ throw new Error('Data channel is not opened');
}
- console.log( "Sending through data channel: " + data );
- this.wp.send( data );
+ console.log("Sending through data channel: " + data);
+ this.wp.send(data);
}
getWrStream() {
@@ -154,86 +154,86 @@ export class Stream {
return this.wp;
}
- addEventListener( eventName: string, listener: any ) {
- this.ee.addListener( eventName, listener );
+ addEventListener(eventName: string, listener: any) {
+ this.ee.addListener(eventName, listener);
}
- showSpinner( spinnerParentId: string ) {
- let progress = document.createElement( 'div' );
+ showSpinner(spinnerParentId: string) {
+ let progress = document.createElement('div');
progress.id = 'progress-' + this.getId();
progress.style.background = "center transparent url('img/spinner.gif') no-repeat";
- let spinnerParent = document.getElementById( spinnerParentId );
- if(spinnerParent){
- spinnerParent.appendChild( progress );
+ let spinnerParent = document.getElementById(spinnerParentId);
+ if (spinnerParent) {
+ spinnerParent.appendChild(progress);
}
}
- hideSpinner( spinnerId?: string ) {
- spinnerId = ( spinnerId === undefined ) ? this.getId() : spinnerId;
- hide( 'progress-' + spinnerId );
+ hideSpinner(spinnerId?: string) {
+ spinnerId = (spinnerId === undefined) ? this.getId() : spinnerId;
+ hide('progress-' + spinnerId);
}
- playOnlyVideo( parentElement, thumbnailId ) {
- this.video = document.createElement( 'video' );
+ playOnlyVideo(parentElement, thumbnailId) {
+ this.video = document.createElement('video');
this.video.id = 'native-video-' + this.getId();
this.video.autoplay = true;
this.video.controls = false;
- if ( this.wrStream ) {
- this.video.src = URL.createObjectURL( this.wrStream );
- show( thumbnailId );
+ if (this.wrStream) {
+ this.video.src = URL.createObjectURL(this.wrStream);
+ show(thumbnailId);
this.hideSpinner();
} else {
- console.log( "No wrStream yet for", this.getId() );
+ console.log("No wrStream yet for", this.getId());
}
- this.videoElements.push( {
+ this.videoElements.push({
thumb: thumbnailId,
video: this.video
});
- if ( this.local ) {
+ if (this.local) {
this.video.muted = true;
}
- if ( typeof parentElement === "string" ) {
- let parentElementDom = document.getElementById( parentElement );
- if(parentElementDom){
- parentElementDom.appendChild( this.video );
+ if (typeof parentElement === "string") {
+ let parentElementDom = document.getElementById(parentElement);
+ if (parentElementDom) {
+ parentElementDom.appendChild(this.video);
}
} else {
- parentElement.appendChild( this.video );
+ parentElement.appendChild(this.video);
}
return this.video;
}
- playThumbnail( thumbnailId ) {
+ playThumbnail(thumbnailId) {
- let container = document.createElement( 'div' );
+ let container = document.createElement('div');
container.className = "participant";
container.id = this.getId();
- let thumbnail = document.getElementById( thumbnailId );
- if(thumbnail){
- thumbnail.appendChild( container );
+ let thumbnail = document.getElementById(thumbnailId);
+ if (thumbnail) {
+ thumbnail.appendChild(container);
}
- this.elements.push( container );
+ this.elements.push(container);
- let name = document.createElement( 'div' );
- container.appendChild( name );
- let userName = this.getId().replace( '_webcam', '' );
- if ( userName.length >= 16 ) {
- userName = userName.substring( 0, 16 ) + "...";
+ let name = document.createElement('div');
+ container.appendChild(name);
+ let userName = this.getId().replace('_webcam', '');
+ if (userName.length >= 16) {
+ userName = userName.substring(0, 16) + "...";
}
- name.appendChild( document.createTextNode( userName ) );
+ name.appendChild(document.createTextNode(userName));
name.id = "name-" + this.getId();
name.className = "name";
name.title = this.getId();
- this.showSpinner( thumbnailId );
+ this.showSpinner(thumbnailId);
- return this.playOnlyVideo( container, thumbnailId );
+ return this.playOnlyVideo(container, thumbnailId);
}
getIdInParticipant() {
@@ -245,7 +245,7 @@ export class Stream {
}
getId() {
- if ( this.participant ) {
+ if (this.participant) {
return this.participant.getId() + "_" + this.id;
} else {
return this.id + "_webcam";
@@ -254,9 +254,11 @@ export class Stream {
requestCameraAccess(callback: Callback) {
- this.participant.addStream( this );
+ this.participant.addStream(this);
- let constraints = {
+ let constraints = this.mediaConstraints;
+
+ let constraints2 = {
audio: true,
video: {
width: {
@@ -268,78 +270,79 @@ export class Stream {
}
};
- navigator.mediaDevices.getUserMedia(constraints)
- .then(userStream => {
- userStream.getAudioTracks()[0].enabled = this.sendAudio;
- userStream.getVideoTracks()[0].enabled = this.sendVideo;
+ navigator.mediaDevices.getUserMedia(constraints)
+ .then(userStream => {
+ userStream.getAudioTracks()[0].enabled = this.sendAudio;
+ userStream.getVideoTracks()[0].enabled = this.sendVideo;
- this.wrStream = userStream;
- callback(undefined, this);})
- .catch(function(e) {
- console.error( "Access denied", e );
- callback(e, undefined);
- });
+ this.wrStream = userStream;
+ callback(undefined, this);
+ })
+ .catch(function (e) {
+ console.error("Access denied", e);
+ callback(e, undefined);
+ });
}
- publishVideoCallback( error, sdpOfferParam, wp ) {
-
- if ( error ) {
- return console.error( "(publish) SDP offer error: "
- + JSON.stringify( error ) );
+ publishVideoCallback(error, sdpOfferParam, wp) {
+
+ if (error) {
+ return console.error("(publish) SDP offer error: "
+ + JSON.stringify(error));
}
- console.log( "Sending SDP offer to publish as "
- + this.getId(), sdpOfferParam );
+ console.log("Sending SDP offer to publish as "
+ + this.getId(), sdpOfferParam);
- this.openVidu.sendRequest( "publishVideo", {
+ this.openVidu.sendRequest("publishVideo", {
sdpOffer: sdpOfferParam,
doLoopback: this.displayMyRemote() || false
- }, ( error, response ) => {
- if ( error ) {
- console.error( "Error on publishVideo: " + JSON.stringify( error ) );
+ }, (error, response) => {
+ if (error) {
+ console.error("Error on publishVideo: " + JSON.stringify(error));
} else {
- this.room.emitEvent( 'stream-published', [{
+ this.room.emitEvent('stream-published', [{
stream: this
- }] )
- this.processSdpAnswer( response.sdpAnswer );
+ }])
+ this.processSdpAnswer(response.sdpAnswer);
}
});
}
- startVideoCallback( error, sdpOfferParam, wp ) {
- if ( error ) {
- return console.error( "(subscribe) SDP offer error: "
- + JSON.stringify( error ) );
+ startVideoCallback(error, sdpOfferParam, wp) {
+ if (error) {
+ return console.error("(subscribe) SDP offer error: "
+ + JSON.stringify(error));
}
- console.log( "Sending SDP offer to subscribe to "
- + this.getId(), sdpOfferParam );
- this.openVidu.sendRequest( "receiveVideoFrom", {
+ console.log("Sending SDP offer to subscribe to "
+ + this.getId(), sdpOfferParam);
+ this.openVidu.sendRequest("receiveVideoFrom", {
sender: this.getId(),
sdpOffer: sdpOfferParam
- }, ( error, response ) => {
- if ( error ) {
- console.error( "Error on recvVideoFrom: " + JSON.stringify( error ) );
+ }, (error, response) => {
+ if (error) {
+ console.error("Error on recvVideoFrom: " + JSON.stringify(error));
} else {
- this.processSdpAnswer( response.sdpAnswer );
+ this.processSdpAnswer(response.sdpAnswer);
}
});
}
- private initWebRtcPeer( sdpOfferCallback ) {
- if ( this.local ) {
+ private initWebRtcPeer(sdpOfferCallback) {
+ if (this.local) {
let userMediaConstraints = {
- audio : this.sendAudio,
- video : this.sendVideo
+ audio: this.sendAudio,
+ video: this.sendVideo
}
-
+
let options: any = {
videoStream: this.wrStream,
mediaConstraints: userMediaConstraints,
- onicecandidate: this.participant.sendIceCandidate.bind( this.participant ),
+ onicecandidate: this.participant.sendIceCandidate.bind(this.participant),
}
-
- if ( this.dataChannel ) {
+
+ if (this.dataChannel) {
options.dataChannelConfig = {
id: this.getChannelName(),
onopen: this.onDataChannelOpen,
@@ -347,20 +350,20 @@ export class Stream {
};
options.dataChannels = true;
}
-
- if ( this.displayMyRemote() ) {
- this.wp = new kurentoUtils.WebRtcPeer.WebRtcPeerSendrecv( options, error => {
- if ( error ) {
- return console.error( error );
+
+ if (this.displayMyRemote()) {
+ this.wp = new kurentoUtils.WebRtcPeer.WebRtcPeerSendrecv(options, error => {
+ if (error) {
+ return console.error(error);
}
- this.wp.generateOffer( sdpOfferCallback.bind( this ) );
+ this.wp.generateOffer(sdpOfferCallback.bind(this));
});
} else {
- this.wp = new kurentoUtils.WebRtcPeer.WebRtcPeerSendonly( options, error => {
- if ( error ) {
- return console.error( error );
+ this.wp = new kurentoUtils.WebRtcPeer.WebRtcPeerSendonly(options, error => {
+ if (error) {
+ return console.error(error);
}
- this.wp.generateOffer( sdpOfferCallback.bind( this ) );
+ this.wp.generateOffer(sdpOfferCallback.bind(this));
});
}
} else {
@@ -370,28 +373,28 @@ export class Stream {
OfferToReceiveAudio: this.recvAudio
}
};
- console.log( "Constraints of generate SDP offer (subscribing)",
- offerConstraints );
+ console.log("Constraints of generate SDP offer (subscribing)",
+ offerConstraints);
let options = {
- onicecandidate: this.participant.sendIceCandidate.bind( this.participant ),
+ onicecandidate: this.participant.sendIceCandidate.bind(this.participant),
connectionConstraints: offerConstraints
}
- this.wp = new kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly( options, error => {
- if ( error ) {
- return console.error( error );
+ this.wp = new kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly(options, error => {
+ if (error) {
+ return console.error(error);
}
- this.wp.generateOffer( sdpOfferCallback.bind( this ) );
+ this.wp.generateOffer(sdpOfferCallback.bind(this));
});
}
- console.log( "Waiting for SDP offer to be generated ("
- + ( this.local ? "local" : "remote" ) + " peer: " + this.getId() + ")" );
+ console.log("Waiting for SDP offer to be generated ("
+ + (this.local ? "local" : "remote") + " peer: " + this.getId() + ")");
}
publish() {
// FIXME: Throw error when stream is not local
- this.initWebRtcPeer( this.publishVideoCallback );
+ this.initWebRtcPeer(this.publishVideoCallback);
// FIXME: Now we have coupled connecting to a room and adding a
// stream to this room. But in the new API, there are two steps.
@@ -405,116 +408,116 @@ export class Stream {
// automatically to all other participants. We use this method only to
// negotiate SDP
- this.initWebRtcPeer( this.startVideoCallback );
+ this.initWebRtcPeer(this.startVideoCallback);
}
- processSdpAnswer( sdpAnswer ) {
+ processSdpAnswer(sdpAnswer) {
- let answer = new RTCSessionDescription( {
+ let answer = new RTCSessionDescription({
type: 'answer',
sdp: sdpAnswer,
});
- console.log( this.getId() + ": set peer connection with recvd SDP answer",
- sdpAnswer );
+ console.log(this.getId() + ": set peer connection with recvd SDP answer",
+ sdpAnswer);
let participantId = this.getId();
let pc = this.wp.peerConnection;
- pc.setRemoteDescription( answer, () => {
+ pc.setRemoteDescription(answer, () => {
// Avoids to subscribe to your own stream remotely
// except when showMyRemote is true
- if ( !this.local || this.displayMyRemote() ) {
+ if (!this.local || this.displayMyRemote()) {
this.wrStream = pc.getRemoteStreams()[0];
- console.log( "Peer remote stream", this.wrStream );
-
- if ( this.wrStream != undefined ) {
-
- this.speechEvent = kurentoUtils.WebRtcPeer.hark( this.wrStream, { threshold: this.room.thresholdSpeaker });
-
- this.speechEvent.on( 'speaking', () => {
- this.room.addParticipantSpeaking( participantId );
- this.room.emitEvent( 'stream-speaking', [{
+ console.log("Peer remote stream", this.wrStream);
+
+ if (this.wrStream != undefined) {
+
+ this.speechEvent = kurentoUtils.WebRtcPeer.hark(this.wrStream, { threshold: this.room.thresholdSpeaker });
+
+ this.speechEvent.on('speaking', () => {
+ this.room.addParticipantSpeaking(participantId);
+ this.room.emitEvent('stream-speaking', [{
participantId: participantId
- }] );
+ }]);
});
- this.speechEvent.on( 'stopped_speaking', () => {
- this.room.removeParticipantSpeaking( participantId );
- this.room.emitEvent( 'stream-stopped-speaking', [{
+ this.speechEvent.on('stopped_speaking', () => {
+ this.room.removeParticipantSpeaking(participantId);
+ this.room.emitEvent('stream-stopped-speaking', [{
participantId: participantId
- }] );
+ }]);
});
}
for (let videoElement of this.videoElements) {
let thumbnailId = videoElement.thumb;
let video = videoElement.video;
- video.src = URL.createObjectURL( this.wrStream );
+ video.src = URL.createObjectURL(this.wrStream);
video.onplay = () => {
- console.log( this.getId() + ': ' + 'Video playing' );
+ console.log(this.getId() + ': ' + 'Video playing');
show(thumbnailId);
- this.hideSpinner( this.getId() );
+ this.hideSpinner(this.getId());
};
}
- this.room.emitEvent( 'stream-subscribed', [{
+ this.room.emitEvent('stream-subscribed', [{
stream: this
- }] );
+ }]);
}
}, error => {
- console.error( this.getId() + ": Error setting SDP to the peer connection: "
- + JSON.stringify( error ) );
+ console.error(this.getId() + ": Error setting SDP to the peer connection: "
+ + JSON.stringify(error));
});
}
unpublish() {
- if ( this.wp ) {
+ if (this.wp) {
this.wp.dispose();
} else {
- if ( this.wrStream ) {
- this.wrStream.getAudioTracks().forEach( function( track ) {
+ if (this.wrStream) {
+ this.wrStream.getAudioTracks().forEach(function (track) {
track.stop && track.stop()
})
- this.wrStream.getVideoTracks().forEach( function( track ) {
+ this.wrStream.getVideoTracks().forEach(function (track) {
track.stop && track.stop()
})
}
}
- if ( this.speechEvent ) {
+ if (this.speechEvent) {
this.speechEvent.stop();
}
- console.log( this.getId() + ": Stream '" + this.id + "' unpublished" );
+ console.log(this.getId() + ": Stream '" + this.id + "' unpublished");
}
dispose() {
- function disposeElement( element ) {
- if ( element && element.parentNode ) {
- element.parentNode.removeChild( element );
+ function disposeElement(element) {
+ if (element && element.parentNode) {
+ element.parentNode.removeChild(element);
}
}
- this.elements.forEach( e => disposeElement( e ) );
+ this.elements.forEach(e => disposeElement(e));
- this.videoElements.forEach( ve => disposeElement( ve ) );
+ this.videoElements.forEach(ve => disposeElement(ve));
- disposeElement( "progress-" + this.getId() );
+ disposeElement("progress-" + this.getId());
- if ( this.wp ) {
+ if (this.wp) {
this.wp.dispose();
} else {
- if ( this.wrStream ) {
- this.wrStream.getAudioTracks().forEach( function( track ) {
+ if (this.wrStream) {
+ this.wrStream.getAudioTracks().forEach(function (track) {
track.stop && track.stop()
})
- this.wrStream.getVideoTracks().forEach( function( track ) {
+ this.wrStream.getVideoTracks().forEach(function (track) {
track.stop && track.stop()
})
}
}
- if ( this.speechEvent ) {
+ if (this.speechEvent) {
this.speechEvent.stop();
}
- console.log( this.getId() + ": Stream '" + this.id + "' disposed" );
+ console.log(this.getId() + ": Stream '" + this.id + "' disposed");
}
}
diff --git a/openvidu-ng-testapp/src/app/app.component.html b/openvidu-ng-testapp/src/app/app.component.html
index e45f6506..1473b5fb 100644
--- a/openvidu-ng-testapp/src/app/app.component.html
+++ b/openvidu-ng-testapp/src/app/app.component.html
@@ -1,34 +1,75 @@
-
Join a video session
-
+
Join a video session
+
\ No newline at end of file
+ {{sessionId}}
+
+ Toggle your video
+ Toggle your audio
+
+
+
+
+
+
+
+
+
Bitrate: {{stats[i].bitrate}}
+
+
+
+ Type
+ Time
+ Other attributes
+
+
+ {{st.type}}
+ {{st.timestamp}}
+ {{getStatAttributes(st.res)}}
+
+
+
+
+
+
+
diff --git a/openvidu-ng-testapp/src/app/app.component.ts b/openvidu-ng-testapp/src/app/app.component.ts
index ca582304..f7aada2c 100644
--- a/openvidu-ng-testapp/src/app/app.component.ts
+++ b/openvidu-ng-testapp/src/app/app.component.ts
@@ -1,3 +1,5 @@
+import { Observable } from 'rxjs/Rx';
+import { enableDebugTools } from '@angular/platform-browser';
import { Component } from '@angular/core';
import { OpenVidu, Session, Stream } from 'openvidu-browser';
@@ -18,16 +20,24 @@ export class AppComponent {
streams: Stream[] = [];
// Publish options
- joinWithVideo: boolean = true;
- joinWithAudio: boolean = true;
+ joinWithVideo: boolean = false;
+ joinWithAudio: boolean = false;
toggleVideo: boolean;
toggleAudio: boolean;
+ //Statistics
+ stats = [];
+ bytesPrev = [];
+ timestampPrev = [];
+
+
constructor() {
this.generateParticipantInfo();
window.onbeforeunload = () => {
this.openVidu.close(true);
}
+
+ //this.obtainSupportedConstraints();
}
private generateParticipantInfo() {
@@ -38,19 +48,32 @@ export class AppComponent {
private addVideoTag(stream: Stream) {
console.log("Stream added");
this.streams.push(stream);
+
+ //For statistics
+ this.timestampPrev.push(0);
+ this.bytesPrev.push(0);
}
private removeVideoTag(stream: Stream) {
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() {
+ let mediaConstraints = this.generateMediaConstraints();
+
+ console.log(mediaConstraints);
+
var cameraOptions = {
audio: this.joinWithAudio,
video: this.joinWithVideo,
data: true,
- mediaConstraints: {}
+ mediaConstraints: mediaConstraints
}
this.joinSessionShared(cameraOptions);
}
@@ -90,6 +113,8 @@ export class AppComponent {
camera.publish();
+ this.intervalStats().subscribe();
+
session.addEventListener("stream-added", streamEvent => {
this.addVideoTag(streamEvent.stream);
console.log("Stream " + streamEvent.stream + " added");
@@ -124,4 +149,96 @@ export class AppComponent {
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((document.getElementById('width')).value) };
+ mediaConstraints.video['height'] = { exact: Number((document.getElementById('height')).value) };
+ mediaConstraints.video['frameRate'] = { ideal: Number((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;
+ }
+}
\ No newline at end of file
diff --git a/openvidu-ng-testapp/src/app/stream.component.ts b/openvidu-ng-testapp/src/app/stream.component.ts
index 15339a4d..55261dda 100644
--- a/openvidu-ng-testapp/src/app/stream.component.ts
+++ b/openvidu-ng-testapp/src/app/stream.component.ts
@@ -7,12 +7,10 @@ import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
styles: [`
.participant {
float: left;
- width: 20%;
margin: 10px;
}
.participant video {
- width: 100%;
- height: auto;
+
}`],
template: `