1/*
2 * Copyright 2017 The Chromium Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
5 */
6/*jshint esversion: 6 */
7
8'use strict';
9
10const $ = document.getElementById.bind(document);
11
12function logError(err) {
13  console.error(err);
14}
15
16
17class FeedTable {
18  constructor() {
19    this.numCols = 5;
20    this.col = 0;
21    this.testTable = document.getElementById('test-table');
22    this.row = this.testTable.insertRow(-1);
23  }
24
25  addNewAudioCell() {
26    if (this.col == this.numCols) {
27      this.row = this.testTable.insertRow(-1);
28      this.col = 0;
29    }
30    var newCell = this.row.insertCell(-1);
31    var audio = document.createElement('audio');
32    audio.autoplay = false;
33    newCell.appendChild(audio);
34    this.col++;
35    return audio;
36  }
37}
38
39
40class PeerConnection {
41  constructor(audioElement) {
42    this.localConnection = null;
43    this.remoteConnection = null;
44    this.remoteAudio = audioElement;
45  }
46
47  start() {
48    const onGetUserMediaSuccess = this.onGetUserMediaSuccess.bind(this);
49    return navigator.mediaDevices
50        .getUserMedia({audio: true, video: true})
51        .then(onGetUserMediaSuccess);
52  }
53
54  onGetUserMediaSuccess(stream) {
55    this.localConnection = new RTCPeerConnection(null);
56    this.localConnection.onicecandidate = (event) => {
57      this.onIceCandidate(this.remoteConnection, event);
58    };
59    this.localConnection.addStream(stream);
60
61    this.remoteConnection = new RTCPeerConnection(null);
62    this.remoteConnection.onicecandidate = (event) => {
63      this.onIceCandidate(this.localConnection, event);
64    };
65    this.remoteConnection.onaddstream = (e) => {
66      this.remoteAudio.srcObject = e.stream;
67    };
68
69    var onCreateOfferSuccess = this.onCreateOfferSuccess.bind(this);
70    this.localConnection
71        .createOffer({offerToReceiveAudio: 1, offerToReceiveVideo: 1})
72        .then(onCreateOfferSuccess, logError);
73  }
74
75  onCreateOfferSuccess(desc) {
76    this.localConnection.setLocalDescription(desc);
77    this.remoteConnection.setRemoteDescription(desc);
78
79    var onCreateAnswerSuccess = this.onCreateAnswerSuccess.bind(this);
80    this.remoteConnection.createAnswer().then(onCreateAnswerSuccess, logError);
81  }
82
83  onCreateAnswerSuccess(desc) {
84    this.remoteConnection.setLocalDescription(desc);
85    this.localConnection.setRemoteDescription(desc);
86  }
87
88  onIceCandidate(connection, event) {
89    if (event.candidate) {
90      connection.addIceCandidate(new RTCIceCandidate(event.candidate));
91    }
92  }
93}
94
95
96class TestRunner {
97  constructor(runtimeSeconds) {
98    this.runtimeSeconds = runtimeSeconds;
99    this.audioElements = [];
100    this.peerConnections = [];
101    this.feedTable = new FeedTable();
102    this.iteration = 0;
103    this.startTime;
104    this.lastIterationTime;
105  }
106
107  addPeerConnection() {
108    const audioElement = this.feedTable.addNewAudioCell();
109    this.audioElements.push(audioElement);
110    this.peerConnections.push(new PeerConnection(audioElement));
111  }
112
113  startTest() {
114    this.startTime = Date.now();
115    let promises = testRunner.peerConnections.map((conn) => conn.start());
116    Promise.all(promises)
117        .then(() => {
118          this.startTime = Date.now();
119          this.audioElements.forEach((feed) => feed.play());
120          this.pauseAndPlayLoop();
121        })
122        .catch((e) => {throw e});
123  }
124
125  pauseAndPlayLoop() {
126    this.iteration++;
127    const status = this.getStatus();
128    this.lastIterationTime = Date.now();
129    $('status').textContent = status
130    if (status != 'ok-done') {
131      setTimeout(() => this.pauseAndPlayLoop());
132    } else {
133      // Finished, pause the audio.
134      this.audioElements.forEach((feed) => feed.pause());
135    }
136  }
137
138  getStatus() {
139    if (this.iteration == 0) {
140      return 'not-started';
141    }
142    const timeSpent = Date.now() - this.startTime;
143    if (timeSpent >= this.runtimeSeconds * 1000) {
144      return 'ok-done';
145    } else {
146      return `running, iteration: ${this.iteration}`;
147    }
148  }
149
150  getResults() {
151    const runTimeMillis = this.lastIterationTime - this.startTime;
152    return {'runTimeSeconds': runTimeMillis / 1000};
153  }
154}
155
156
157let testRunner;
158
159function run(runtimeSeconds, numPeerConnections) {
160  testRunner = new TestRunner(runtimeSeconds);
161  for (let i = 0; i < numPeerConnections; i++) {
162    testRunner.addPeerConnection();
163  }
164  testRunner.startTest();
165}
166
167