| <!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> |