• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/**
17 * FeedTable stores all elements.
18 */
19class FeedTable {
20  constructor() {
21    this.numCols = 5;
22    this.col = 0;
23    this.testTable = $('test-table');
24    this.row = this.testTable.insertRow(-1);
25  }
26
27  addNewCell(elementType) {
28    if (this.col == this.numCols) {
29      this.row = this.testTable.insertRow(-1);
30      this.col = 0;
31    }
32    var newCell = this.row.insertCell(-1);
33    var element = document.createElement(elementType);
34    element.autoplay = false;
35    newCell.appendChild(element);
36    this.col++;
37    return element;
38  }
39}
40
41/**
42 * A simple loopback connection;
43 * - localConnection is fed video/audio from source
44 * - localConnection is linked to remoteConnection
45 * - remoteConnection is displayed in the given videoElement
46 */
47class PeerConnection {
48
49  /**
50   * @param {!HTMLMediaElement} element - And 'audio' or 'video' element.
51   * @param {!Object} constraints - The constraints for the peer connection.
52   */
53  constructor(element, constraints) {
54    this.localConnection = null;
55    this.remoteConnection = null;
56    this.remoteElement = element;
57    this.constraints = constraints;
58  }
59
60  start() {
61    return navigator.mediaDevices
62        .getUserMedia(this.constraints)
63        .then((stream) => {
64            this.onGetUserMediaSuccess(stream)
65        });
66  };
67
68  onGetUserMediaSuccess(stream) {
69    this.localConnection = new RTCPeerConnection(null);
70    this.localConnection.onicecandidate = (event) => {
71      this.onIceCandidate(this.remoteConnection, event);
72    };
73    this.localConnection.addStream(stream);
74
75    this.remoteConnection = new RTCPeerConnection(null);
76    this.remoteConnection.onicecandidate = (event) => {
77      this.onIceCandidate(this.localConnection, event);
78    };
79    this.remoteConnection.onaddstream = (e) => {
80      this.remoteElement.srcObject = e.stream;
81    };
82
83    this.localConnection
84        .createOffer({offerToReceiveAudio: 1, offerToReceiveVideo: 1})
85        .then((desc) => {this.onCreateOfferSuccess(desc)}, logError);
86  };
87
88  onCreateOfferSuccess(desc) {
89    this.localConnection.setLocalDescription(desc);
90    this.remoteConnection.setRemoteDescription(desc);
91
92    this.remoteConnection.createAnswer().then(
93        (desc) => {this.onCreateAnswerSuccess(desc)}, logError);
94  };
95
96  onCreateAnswerSuccess(desc) {
97    this.remoteConnection.setLocalDescription(desc);
98    this.localConnection.setRemoteDescription(desc);
99  };
100
101  onIceCandidate(connection, event) {
102    if (event.candidate) {
103      connection.addIceCandidate(new RTCIceCandidate(event.candidate));
104    }
105  };
106}
107
108
109class TestRunner {
110  constructor(runtimeSeconds, pausePlayIterationDelayMillis) {
111    this.runtimeSeconds = runtimeSeconds;
112    this.pausePlayIterationDelayMillis = pausePlayIterationDelayMillis;
113    this.elements = [];
114    this.peerConnections = [];
115    this.feedTable = new FeedTable();
116    this.iteration = 0;
117    this.startTime;
118    this.lastIterationTime;
119  }
120
121  addPeerConnection(elementType) {
122    const element = this.feedTable.addNewCell(elementType);
123    const constraints = {audio: true};
124    if (elementType === 'video') {
125      constraints.video = {
126        width: {exact: 300}
127      };
128    } else if (elementType === 'audio') {
129      constraints.video = false;
130    } else {
131      throw new Error('elementType must be one of "audio" or "video"');
132    }
133    this.elements.push(element);
134    this.peerConnections.push(new PeerConnection(element, constraints));
135  }
136
137  startTest() {
138    this.startTime = Date.now();
139    let promises = testRunner.peerConnections.map((conn) => conn.start());
140    Promise.all(promises)
141        .then(() => {
142          this.startTime = Date.now();
143          this.pauseAndPlayLoop();
144        })
145        .catch((e) => {throw e});
146  }
147
148  pauseAndPlayLoop() {
149    this.iteration++;
150    this.elements.forEach((feed) => {
151      if (Math.random() >= 0.5) {
152        feed.play();
153      } else {
154        feed.pause();
155      }
156    });
157    const status = this.getStatus();
158    this.lastIterationTime = Date.now();
159    $('status').textContent = status
160    if (status != 'ok-done') {
161      setTimeout(
162          () => {this.pauseAndPlayLoop()}, this.pausePlayIterationDelayMillis);
163    } else {  // We're done. Pause all feeds.
164      this.elements.forEach((feed) => {
165        feed.pause();
166      });
167    }
168  }
169
170  getStatus() {
171    if (this.iteration == 0) {
172      return 'not-started';
173    }
174    const timeSpent = Date.now() - this.startTime;
175    if (timeSpent >= this.runtimeSeconds * 1000) {
176      return 'ok-done';
177    } else {
178      return `running, iteration: ${this.iteration}`;
179    }
180  }
181
182  getResults() {
183    const runTimeMillis = this.lastIterationTime - this.startTime;
184    return {'runTimeSeconds': runTimeMillis / 1000};
185  }
186}
187
188let testRunner;
189
190function startTest(
191    runtimeSeconds, numPeerConnections, pausePlayIterationDelayMillis,
192    elementType) {
193  testRunner = new TestRunner(
194      runtimeSeconds, pausePlayIterationDelayMillis);
195  for (let i = 0; i < numPeerConnections; i++) {
196    testRunner.addPeerConnection(elementType);
197  }
198  testRunner.startTest();
199}
200