| <!doctype html> |
| <meta charset=utf-8> |
| <title>RTCPeerConnection BUNDLE</title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="../RTCPeerConnection-helper.js"></script> |
| <script src="/webrtc/third_party/sdp/sdp.js"></script> |
| <script> |
| 'use strict'; |
| promise_test(async t => { |
| const caller = new RTCPeerConnection(); |
| t.add_cleanup(() => caller.close()); |
| const calleeAudio = new RTCPeerConnection(); |
| t.add_cleanup(() => calleeAudio.close()); |
| const calleeVideo = new RTCPeerConnection(); |
| t.add_cleanup(() => calleeVideo.close()); |
| |
| const stream = await getNoiseStream({audio: true, video: true}); |
| t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); |
| stream.getTracks().forEach(track => caller.addTrack(track, stream)); |
| |
| let metadataToBeLoaded; |
| calleeVideo.ontrack = (e) => { |
| const stream = e.streams[0]; |
| const v = document.createElement('video'); |
| v.autoplay = true; |
| v.srcObject = stream; |
| v.id = stream.id |
| metadataToBeLoaded = new Promise((resolve) => { |
| v.addEventListener('loadedmetadata', () => { |
| resolve(); |
| }); |
| }); |
| }; |
| |
| caller.addEventListener('icecandidate', (e) => { |
| // route depending on sdpMlineIndex |
| if (e.candidate) { |
| const target = e.candidate.sdpMLineIndex === 0 ? calleeAudio : calleeVideo; |
| target.addIceCandidate({sdpMid: e.candidate.sdpMid, candidate: e.candidate.candidate}); |
| } else { |
| calleeAudio.addIceCandidate(); |
| calleeVideo.addIceCandidate(); |
| } |
| }); |
| calleeAudio.addEventListener('icecandidate', (e) => { |
| if (e.candidate) { |
| caller.addIceCandidate({sdpMid: e.candidate.sdpMid, candidate: e.candidate.candidate}); |
| } |
| // Note: caller.addIceCandidate is only called for video to avoid calling it twice. |
| }); |
| calleeVideo.addEventListener('icecandidate', (e) => { |
| if (e.candidate) { |
| caller.addIceCandidate({sdpMid: e.candidate.sdpMid, candidate: e.candidate.candidate}); |
| } else { |
| caller.addIceCandidate(); |
| } |
| }); |
| |
| const offer = await caller.createOffer(); |
| const sections = SDPUtils.splitSections(offer.sdp); |
| // Remove the a=group:BUNDLE from the SDP when signaling. |
| const bundle = SDPUtils.matchPrefix(sections[0], 'a=group:BUNDLE')[0]; |
| sections[0] = sections[0].replace(bundle + '\r\n', ''); |
| |
| const audioSdp = sections[0] + sections[1]; |
| const videoSdp = sections[0] + sections[2]; |
| |
| await calleeAudio.setRemoteDescription({type: 'offer', sdp: audioSdp}); |
| await calleeVideo.setRemoteDescription({type: 'offer', sdp: videoSdp}); |
| await caller.setLocalDescription(offer); |
| |
| const answerAudio = await calleeAudio.createAnswer(); |
| const answerVideo = await calleeVideo.createAnswer(); |
| const audioSections = SDPUtils.splitSections(answerAudio.sdp); |
| const videoSections = SDPUtils.splitSections(answerVideo.sdp); |
| |
| // Remove the fingerprint from the session part of the SDP if present |
| // and move it to the media section. |
| SDPUtils.matchPrefix(audioSections[0], 'a=fingerprint:').forEach(line => { |
| audioSections[0] = audioSections[0].replace(line + '\r\n', ''); |
| audioSections[1] += line + '\r\n'; |
| }); |
| SDPUtils.matchPrefix(videoSections[0], 'a=fingerprint:').forEach(line => { |
| videoSections[0] = videoSections[0].replace(line + '\r\n', ''); |
| videoSections[1] += line + '\r\n'; |
| }); |
| |
| const sdp = audioSections[0] + audioSections[1] + videoSections[1]; |
| await caller.setRemoteDescription({type: 'answer', sdp}); |
| await calleeAudio.setLocalDescription(answerAudio); |
| await calleeVideo.setLocalDescription(answerVideo); |
| |
| await metadataToBeLoaded; |
| assert_equals(calleeAudio.connectionState, 'connected'); |
| assert_equals(calleeVideo.connectionState, 'connected'); |
| }, 'Connect audio and video to two independent PeerConnections'); |
| </script> |