From c115ababdb5e4b35658566308c816adb82459beb Mon Sep 17 00:00:00 2001 From: Youenn Fablet Date: Fri, 7 Dec 2018 10:27:12 -0800 Subject: [PATCH] Trimmed down version of the changes --- webrtc/RTCDataChannel-send.html | 16 +-- webrtc/RTCPeerConnection-helper.js | 195 ++++++++++++++++++++++------- webrtc/tools/.eslintrc.js | 2 +- 3 files changed, 159 insertions(+), 54 deletions(-) diff --git a/webrtc/RTCDataChannel-send.html b/webrtc/RTCDataChannel-send.html index 7cacaa4d37bb76..cb8b4edc3fae15 100644 --- a/webrtc/RTCDataChannel-send.html +++ b/webrtc/RTCDataChannel-send.html @@ -14,7 +14,7 @@ // createDataChannelPair // awaitMessage // blobToArrayBuffer - // assert_equals_array_buffer + // assert_equals_typed_array /* 6.2. RTCDataChannel @@ -140,7 +140,7 @@ assert_true(messageBuffer instanceof ArrayBuffer, 'Expect messageBuffer to be an ArrayBuffer'); - assert_equals_array_buffer(messageBuffer, helloBuffer.buffer); + assert_equals_typed_array(messageBuffer, helloBuffer.buffer); }); }, 'Data channel should be able to send Uint8Array message and receive as ArrayBuffer'); @@ -162,7 +162,7 @@ assert_true(messageBuffer instanceof ArrayBuffer, 'Expect messageBuffer to be an ArrayBuffer'); - assert_equals_array_buffer(messageBuffer, helloBuffer.buffer); + assert_equals_typed_array(messageBuffer, helloBuffer.buffer); }); }, 'Data channel should be able to send ArrayBuffer message and receive as ArrayBuffer'); @@ -183,7 +183,7 @@ assert_true(messageBuffer instanceof ArrayBuffer, 'Expect messageBuffer to be an ArrayBuffer'); - assert_equals_array_buffer(messageBuffer, helloBuffer.buffer); + assert_equals_typed_array(messageBuffer, helloBuffer.buffer); }); }, 'Data channel should be able to send Blob message and receive as ArrayBuffer'); @@ -210,7 +210,7 @@ assert_true(messageBuffer instanceof ArrayBuffer, 'Expect messageBuffer to be an ArrayBuffer'); - assert_equals_array_buffer(messageBuffer, helloBuffer.buffer); + assert_equals_typed_array(messageBuffer, helloBuffer.buffer); }); }, 'Data channel should be able to send ArrayBuffer message and receive as Blob'); @@ -240,7 +240,7 @@ assert_true(messageBuffer instanceof ArrayBuffer, 'Expect messageBuffer to be an ArrayBuffer'); - assert_equals_array_buffer(messageBuffer, helloBuffer.buffer); + assert_equals_typed_array(messageBuffer, helloBuffer.buffer); }); }, 'Data channel binaryType should receive message as Blob by default'); @@ -253,9 +253,9 @@ receivedMessages.push(data); if(receivedMessages.length === 3) { - assert_equals_array_buffer(receivedMessages[0], helloBuffer.buffer); + assert_equals_typed_array(receivedMessages[0], helloBuffer.buffer); assert_equals(receivedMessages[1], unicodeString); - assert_equals_array_buffer(receivedMessages[2], helloBuffer.buffer); + assert_equals_typed_array(receivedMessages[2], helloBuffer.buffer); t.done(); } diff --git a/webrtc/RTCPeerConnection-helper.js b/webrtc/RTCPeerConnection-helper.js index 9baf840df21321..a2a3e296462bfc 100644 --- a/webrtc/RTCPeerConnection-helper.js +++ b/webrtc/RTCPeerConnection-helper.js @@ -159,39 +159,95 @@ function test_never_resolve(testFunc, testName) { }, testName); } +// Helper class to queue ICE candidates of a peer connection +// Can be passed to `exchangeIceCandidates` which will automatically empty the queue. +class IceCandidateQueue { + constructor(pc) { + this.pc = pc; + this._queue = []; + this._listener = (event) => this._handleIceCandidate(event); + + // Queue ICE candidates + this.pc.addEventListener('icecandidate', this._listener); + } + + // Unbind the event listener and return all queued candidates + disband() { + this.pc.removeEventListener('icecandidate', this._listener); + return this._queue; + } + + _handleIceCandidate(event) { + this._queue.push(event.candidate); + } +} + // Helper function to exchange ice candidates between // two local peer connections -function exchangeIceCandidates(pc1, pc2) { - // private function - function doExchange(localPc, remotePc) { - localPc.addEventListener('icecandidate', event => { - const { candidate } = event; +function exchangeIceCandidates(pc1OrQueue, pc2OrQueue) { + const handleCandidate = (remotePc, candidate) => { + // candidate may be null to indicate end of candidate gathering. + // There is ongoing discussion on w3c/webrtc-pc#1213 + // that there should be an empty candidate string event + // for end of candidate for each m= section. + if (candidate && remotePc.signalingState !== 'closed') { + remotePc.addIceCandidate(candidate); + } + }; + + const exchangeCandidates = (localPcOrQueue, remotePcOrQueue) => { + let localPc = localPcOrQueue; + let remotePc = remotePcOrQueue; + if (remotePcOrQueue instanceof IceCandidateQueue) { + remotePc = remotePcOrQueue.pc; + } - // candidate may be null to indicate end of candidate gathering. - // There is ongoing discussion on w3c/webrtc-pc#1213 - // that there should be an empty candidate string event - // for end of candidate for each m= section. - if(candidate && remotePc.signalingState !== 'closed') { - remotePc.addIceCandidate(candidate); + // Queue? Disband it first + if (localPcOrQueue instanceof IceCandidateQueue) { + localPc = localPcOrQueue.pc; + for (const candidate of localPcOrQueue.disband()) { + handleCandidate(remotePc, candidate); } + } + + // Exchange further candidates + localPc.addEventListener('icecandidate', event => { + const { candidate } = event; + handleCandidate(remotePc, candidate); }); - } + }; - doExchange(pc1, pc2); - doExchange(pc2, pc1); + exchangeCandidates(pc1OrQueue, pc2OrQueue); + exchangeCandidates(pc2OrQueue, pc1OrQueue); } // Helper function for doing one round of offer/answer exchange // betweeen two local peer connections -function doSignalingHandshake(localPc, remotePc) { +function doSignalingHandshake(localPc, remotePc, options={}) { return localPc.createOffer() - .then(offer => Promise.all([ - localPc.setLocalDescription(offer), - remotePc.setRemoteDescription(offer)])) + .then(offer => { + // Modify offer if callback has been provided + if (options.modifyOffer) { + offer = options.modifyOffer(offer); + } + + // Apply offer + return Promise.all([ + localPc.setLocalDescription(offer), + remotePc.setRemoteDescription(offer)]) + }) .then(() => remotePc.createAnswer()) - .then(answer => Promise.all([ - remotePc.setLocalDescription(answer), - localPc.setRemoteDescription(answer)])) + .then(answer => { + // Modify answer if callback has been provided + if (options.modifyAnswer) { + answer = options.modifyAnswer(answer); + } + + // Apply answer + return Promise.all([ + remotePc.setLocalDescription(answer), + localPc.setRemoteDescription(answer)]) + }); } // Helper function to create a pair of connected data channel. @@ -295,23 +351,25 @@ function blobToArrayBuffer(blob) { }); } -// Assert that two ArrayBuffer objects have the same byte values -function assert_equals_array_buffer(buffer1, buffer2) { - assert_true(buffer1 instanceof ArrayBuffer, - 'Expect buffer to be instance of ArrayBuffer'); - - assert_true(buffer2 instanceof ArrayBuffer, - 'Expect buffer to be instance of ArrayBuffer'); +// Assert that two TypedArray or ArrayBuffer objects have the same byte values +function assert_equals_typed_array(array1, array2) { + const [view1, view2] = [array1, array2].map((array) => { + if (array instanceof ArrayBuffer) { + return new DataView(array); + } else { + assert_true(array.buffer instanceof ArrayBuffer, + 'Expect buffer to be instance of ArrayBuffer'); + return new DataView(array.buffer, array.byteOffset, array.byteLength); + } + }); - assert_equals(buffer1.byteLength, buffer2.byteLength, - 'Expect both array buffers to be of the same byte length'); + assert_equals(view1.byteLength, view2.byteLength, + 'Expect both arrays to be of the same byte length'); - const byteLength = buffer1.byteLength; - const byteArray1 = new Uint8Array(buffer1); - const byteArray2 = new Uint8Array(buffer2); + const byteLength = view1.byteLength; - for(let i=0; i { + resolve = resolve_; + reject = reject_; + if (executor) { + return executor(resolve_, reject_); + } }); - this.resolve = promiseResolve; - this.reject = promiseReject; + + this._done = false; + this._resolve = resolve; + this._reject = reject; + } + + /** + * Return whether the promise is done (resolved or rejected). + */ + get done() { + return this._done; + } + + /** + * Resolve the promise. + */ + resolve(...args) { + this._done = true; + return this._resolve(...args); + } + + /** + * Reject the promise. + */ + reject(...args) { + this._done = true; + return this._reject(...args); } } @@ -526,3 +611,23 @@ function findTransceiverForSender(pc, sender) { } return null; } + +// Contains a set of values and will yell at you if you try to add a value twice. +class UniqueSet extends Set { + constructor(items) { + super(); + if (items !== undefined) { + for (const item of items) { + this.add(item); + } + } + } + + add(value, message) { + if (message === undefined) { + message = `Value '${value}' needs to be unique but it is already in the set`; + } + assert_true(!this.has(value), message); + super.add(value); + } +} diff --git a/webrtc/tools/.eslintrc.js b/webrtc/tools/.eslintrc.js index 1778f8505e04cf..914a09dc32c741 100644 --- a/webrtc/tools/.eslintrc.js +++ b/webrtc/tools/.eslintrc.js @@ -105,7 +105,7 @@ module.exports = { createDataChannelPair: true, awaitMessage: true, blobToArrayBuffer: true, - assert_equals_array_buffer: true, + assert_equals_typed_array: true, generateMediaStreamTrack: true, getTrackFromUserMedia: true, getUserMediaTracksAndStreams: true,