| /* |
| * Copyright 2017 The Chromium Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| const $ = document.getElementById.bind(document); |
| |
| function logError(err) { |
| console.error(err); |
| } |
| |
| /** |
| * FeedTable stores all video elements. |
| */ |
| class FeedTable { |
| constructor() { |
| this.numCols = 5; |
| this.col = 0; |
| this.testTable = document.getElementById('test-table'); |
| this.row = this.testTable.insertRow(-1); |
| } |
| |
| addNewCell(elementType) { |
| if (this.col === this.numCols) { |
| this.row = this.testTable.insertRow(-1); |
| this.col = 0; |
| } |
| var newCell = this.row.insertCell(-1); |
| var element = document.createElement(elementType); |
| element.autoplay = false; |
| newCell.appendChild(element); |
| this.col++; |
| return element; |
| } |
| } |
| |
| /** |
| * A simple loopback connection; |
| * - localConnection is fed video from local camera |
| * - localConnection is linked to remoteConnection |
| * - remoteConnection is displayed in the given videoElement |
| */ |
| class PeerConnection { |
| /** |
| * @param {Object} element - An 'audio' or 'video' element. |
| * @param {Object} constraints - The constraints for the peer connection. |
| */ |
| constructor(element, constraints) { |
| this.localConnection = null; |
| this.remoteConnection = null; |
| this.remoteView = element; |
| this.constraints = constraints; |
| } |
| |
| start() { |
| return navigator.mediaDevices |
| .getUserMedia(this.constraints) |
| .then((stream) => { |
| this.onGetUserMediaSuccess(stream); |
| }); |
| } |
| |
| onGetUserMediaSuccess(stream) { |
| this.localConnection = new RTCPeerConnection(null); |
| this.localConnection.onicecandidate = (event) => { |
| this.onIceCandidate(this.remoteConnection, event); |
| }; |
| this.localConnection.addStream(stream); |
| |
| this.remoteConnection = new RTCPeerConnection(null); |
| this.remoteConnection.onicecandidate = (event) => { |
| this.onIceCandidate(this.localConnection, event); |
| }; |
| this.remoteConnection.onaddstream = (e) => { |
| this.remoteView.srcObject = e.stream; |
| }; |
| |
| this.localConnection |
| .createOffer({offerToReceiveAudio: 1, offerToReceiveVideo: 1}) |
| .then((offerDesc) => { |
| this.onCreateOfferSuccess(offerDesc); |
| }, logError); |
| } |
| |
| onCreateOfferSuccess(offerDesc) { |
| this.localConnection.setLocalDescription(offerDesc); |
| this.remoteConnection.setRemoteDescription(offerDesc); |
| |
| this.remoteConnection.createAnswer().then( |
| (answerDesc) => { |
| this.onCreateAnswerSuccess(answerDesc); |
| }, logError); |
| } |
| |
| onCreateAnswerSuccess(answerDesc) { |
| this.remoteConnection.setLocalDescription(answerDesc); |
| this.localConnection.setRemoteDescription(answerDesc); |
| } |
| |
| onIceCandidate(connection, event) { |
| if (event.candidate) { |
| connection.addIceCandidate(new RTCIceCandidate(event.candidate)); |
| } |
| } |
| } |
| |
| class TestRunner { |
| constructor(runtimeSeconds, pausePlayIterationDelayMillis) { |
| this.runtimeSeconds = runtimeSeconds; |
| this.pausePlayIterationDelayMillis = pausePlayIterationDelayMillis; |
| this.elements = []; |
| this.peerConnections = []; |
| this.feedTable = new FeedTable(); |
| this.iteration = 0; |
| this.startTime = null; |
| this.lastIterationTime = null; |
| } |
| |
| addPeerConnection(elementType) { |
| const element = this.feedTable.addNewCell(elementType); |
| const constraints = {audio: true}; |
| if (elementType === 'video') { |
| constraints.video = { |
| width: {exact: 300} |
| }; |
| } else if (elementType === 'audio') { |
| constraints.video = false; |
| } else { |
| throw new Error('elementType must be one of "audio" or "video"'); |
| } |
| this.elements.push(element); |
| this.peerConnections.push(new PeerConnection(element, constraints)); |
| } |
| |
| startTest() { |
| this.startTime = Date.now(); |
| let promises = testRunner.peerConnections.map((conn) => conn.start()); |
| Promise.all(promises) |
| .then(() => { |
| this.startTime = Date.now(); |
| this.pauseAndPlayLoop(); |
| }) |
| .catch((e) => { |
| throw e; |
| }); |
| } |
| |
| pauseAndPlayLoop() { |
| this.iteration++; |
| this.elements.forEach((feed) => { |
| if (Math.random() >= 0.5) { |
| feed.play(); |
| } else { |
| feed.pause(); |
| } |
| }); |
| const status = this.getStatus(); |
| this.lastIterationTime = Date.now(); |
| $('status').textContent = status; |
| if (status !== 'ok-done') { |
| setTimeout( |
| () => { |
| this.pauseAndPlayLoop(); |
| }, this.pausePlayIterationDelayMillis); |
| } else { // We're done. Pause all feeds. |
| this.elements.forEach((feed) => { |
| feed.pause(); |
| }); |
| } |
| } |
| |
| getStatus() { |
| if (this.iteration === 0) { |
| return 'not-started'; |
| } |
| const timeSpent = Date.now() - this.startTime; |
| if (timeSpent >= this.runtimeSeconds * 1000) { |
| return 'ok-done'; |
| } |
| return `running, iteration: ${this.iteration}`; |
| } |
| |
| getResults() { |
| const runTimeMillis = this.lastIterationTime - this.startTime; |
| return {'runTimeSeconds': runTimeMillis / 1000}; |
| } |
| } |
| |
| let testRunner; |
| |
| function startTest( |
| runtimeSeconds, numPeerConnections, pausePlayIterationDelayMillis, |
| elementType) { |
| testRunner = new TestRunner( |
| runtimeSeconds, pausePlayIterationDelayMillis); |
| for (let i = 0; i < numPeerConnections; i++) { |
| testRunner.addPeerConnection(elementType); |
| } |
| testRunner.startTest(); |
| } |