| <!doctype html> |
| <meta charset=utf-8> |
| <title>RTCRtpTransceiver</title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="RTCPeerConnection-helper.js"></script> |
| <script> |
| 'use strict'; |
| |
| const checkThrows = async (func, exceptionName, description) => { |
| try { |
| await func(); |
| assert_true(false, description + " throws " + exceptionName); |
| } catch (e) { |
| assert_equals(e.name, exceptionName, description + " throws " + exceptionName); |
| } |
| }; |
| |
| const stopTracks = (...streams) => { |
| streams.forEach(stream => stream.getTracks().forEach(track => track.stop())); |
| }; |
| |
| const collectEvents = (target, name, check) => { |
| const events = []; |
| const handler = e => { |
| check(e); |
| events.push(e); |
| }; |
| |
| target.addEventListener(name, handler); |
| |
| const finishCollecting = () => { |
| target.removeEventListener(name, handler); |
| return events; |
| }; |
| |
| return {finish: finishCollecting}; |
| }; |
| |
| const collectAddTrackEvents = stream => { |
| const checkEvent = e => { |
| assert_true(e.track instanceof MediaStreamTrack, "Track is set on event"); |
| assert_true(stream.getTracks().includes(e.track), |
| "track in addtrack event is in the stream"); |
| }; |
| return collectEvents(stream, "addtrack", checkEvent); |
| }; |
| |
| const collectRemoveTrackEvents = stream => { |
| const checkEvent = e => { |
| assert_true(e.track instanceof MediaStreamTrack, "Track is set on event"); |
| assert_true(!stream.getTracks().includes(e.track), |
| "track in removetrack event is not in the stream"); |
| }; |
| return collectEvents(stream, "removetrack", checkEvent); |
| }; |
| |
| const collectTrackEvents = pc => { |
| const checkEvent = e => { |
| assert_true(e.track instanceof MediaStreamTrack, "Track is set on event"); |
| assert_true(e.receiver instanceof RTCRtpReceiver, "Receiver is set on event"); |
| assert_true(e.transceiver instanceof RTCRtpTransceiver, "Transceiver is set on event"); |
| assert_true(Array.isArray(e.streams), "Streams is set on event"); |
| e.streams.forEach(stream => { |
| assert_true(stream.getTracks().includes(e.track), |
| "Each stream in event contains the track"); |
| }); |
| assert_equals(e.receiver, e.transceiver.receiver, |
| "Receiver belongs to transceiver"); |
| assert_equals(e.track, e.receiver.track, |
| "Track belongs to receiver"); |
| }; |
| |
| return collectEvents(pc, "track", checkEvent); |
| }; |
| |
| const setRemoteDescriptionReturnTrackEvents = async (pc, desc) => { |
| const trackEventCollector = collectTrackEvents(pc); |
| await pc.setRemoteDescription(desc); |
| return trackEventCollector.finish(); |
| }; |
| |
| const offerAnswer = async (offerer, answerer) => { |
| const offer = await offerer.createOffer(); |
| await answerer.setRemoteDescription(offer); |
| await offerer.setLocalDescription(offer); |
| const answer = await answerer.createAnswer(); |
| await offerer.setRemoteDescription(answer); |
| await answerer.setLocalDescription(answer); |
| }; |
| |
| const trickle = (t, pc1, pc2) => { |
| pc1.onicecandidate = t.step_func(async e => { |
| if (e.candidate) { |
| try { |
| await pc2.addIceCandidate(e.candidate); |
| } catch (e) { |
| assert_true(false, "addIceCandidate threw error: " + e.name); |
| } |
| } |
| }); |
| }; |
| |
| const iceConnected = pc => { |
| return new Promise((resolve, reject) => { |
| const iceCheck = () => { |
| if (pc.iceConnectionState == "connected") { |
| assert_true(true, "ICE connected"); |
| resolve(); |
| } |
| |
| if (pc.iceConnectionState == "failed") { |
| assert_true(false, "ICE failed"); |
| reject(); |
| } |
| }; |
| |
| iceCheck(); |
| pc.oniceconnectionstatechange = iceCheck; |
| }); |
| }; |
| |
| const negotiationNeeded = pc => { |
| return new Promise(resolve => pc.onnegotiationneeded = resolve); |
| }; |
| |
| const countEvents = (target, name) => { |
| const result = {count: 0}; |
| target.addEventListener(name, e => result.count++); |
| return result; |
| }; |
| |
| const gotMuteEvent = async track => { |
| await new Promise(r => track.addEventListener("mute", r, {once: true})); |
| |
| assert_true(track.muted, "track should be muted after onmute"); |
| }; |
| |
| const gotUnmuteEvent = async track => { |
| await new Promise(r => track.addEventListener("unmute", r, {once: true})); |
| |
| assert_true(!track.muted, "track should not be muted after onunmute"); |
| }; |
| |
| // comparable() - produces copy of object that is JSON comparable. |
| // o = original object (required) |
| // t = template of what to examine. Useful if o is non-enumerable (optional) |
| |
| const comparable = (o, t = o) => { |
| if (typeof o != 'object' || !o) { |
| return o; |
| } |
| if (Array.isArray(t) && Array.isArray(o)) { |
| return o.map((n, i) => comparable(n, t[i])); |
| } |
| return Object.keys(t).sort() |
| .reduce((r, key) => (r[key] = comparable(o[key], t[key]), r), {}); |
| }; |
| |
| const stripKeyQuotes = s => s.replace(/"(\w+)":/g, "$1:"); |
| |
| const hasProps = (observed, expected) => { |
| const observable = comparable(observed, expected); |
| assert_equals(stripKeyQuotes(JSON.stringify(observable)), |
| stripKeyQuotes(JSON.stringify(comparable(expected)))); |
| }; |
| |
| const hasPropsAndUniqueMids = (observed, expected) => { |
| hasProps(observed, expected); |
| |
| const mids = []; |
| observed.forEach((transceiver, i) => { |
| if (!("mid" in expected[i])) { |
| assert_not_equals(transceiver.mid, null); |
| assert_equals(typeof transceiver.mid, "string"); |
| } |
| if (transceiver.mid) { |
| assert_false(mids.includes(transceiver.mid), "mid must be unique"); |
| mids.push(transceiver.mid); |
| } |
| }); |
| }; |
| |
| const checkAddTransceiverNoTrack = async t => { |
| const pc = new RTCPeerConnection(); |
| t.add_cleanup(() => pc.close()); |
| |
| hasProps(pc.getTransceivers(), []); |
| |
| pc.addTransceiver("audio"); |
| pc.addTransceiver("video"); |
| |
| hasProps(pc.getTransceivers(), |
| [ |
| { |
| receiver: {track: {kind: "audio", readyState: "live", muted: true}}, |
| sender: {track: null}, |
| direction: "sendrecv", |
| mid: null, |
| currentDirection: null, |
| stopped: false |
| }, |
| { |
| receiver: {track: {kind: "video", readyState: "live", muted: true}}, |
| sender: {track: null}, |
| direction: "sendrecv", |
| mid: null, |
| currentDirection: null, |
| stopped: false |
| } |
| ]); |
| }; |
| |
| const checkAddTransceiverWithTrack = async t => { |
| const pc = new RTCPeerConnection(); |
| t.add_cleanup(() => pc.close()); |
| |
| const stream = await getNoiseStream({audio: true, video: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const audio = stream.getAudioTracks()[0]; |
| const video = stream.getVideoTracks()[0]; |
| |
| pc.addTransceiver(audio); |
| pc.addTransceiver(video); |
| |
| hasProps(pc.getTransceivers(), |
| [ |
| { |
| receiver: {track: {kind: "audio"}}, |
| sender: {track: audio}, |
| direction: "sendrecv", |
| mid: null, |
| currentDirection: null, |
| stopped: false |
| }, |
| { |
| receiver: {track: {kind: "video"}}, |
| sender: {track: video}, |
| direction: "sendrecv", |
| mid: null, |
| currentDirection: null, |
| stopped: false |
| } |
| ]); |
| }; |
| |
| const checkAddTransceiverWithAddTrack = async t => { |
| const pc = new RTCPeerConnection(); |
| t.add_cleanup(() => pc.close()); |
| |
| const stream = await getNoiseStream({audio: true, video: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const audio = stream.getAudioTracks()[0]; |
| const video = stream.getVideoTracks()[0]; |
| |
| pc.addTrack(audio, stream); |
| pc.addTrack(video, stream); |
| |
| hasProps(pc.getTransceivers(), |
| [ |
| { |
| receiver: {track: {kind: "audio"}}, |
| sender: {track: audio}, |
| direction: "sendrecv", |
| mid: null, |
| currentDirection: null, |
| stopped: false |
| }, |
| { |
| receiver: {track: {kind: "video"}}, |
| sender: {track: video}, |
| direction: "sendrecv", |
| mid: null, |
| currentDirection: null, |
| stopped: false |
| } |
| ]); |
| }; |
| |
| const checkAddTransceiverWithDirection = async t => { |
| const pc = new RTCPeerConnection(); |
| t.add_cleanup(() => pc.close()); |
| |
| pc.addTransceiver("audio", {direction: "recvonly"}); |
| pc.addTransceiver("video", {direction: "recvonly"}); |
| |
| hasProps(pc.getTransceivers(), |
| [ |
| { |
| receiver: {track: {kind: "audio"}}, |
| sender: {track: null}, |
| direction: "recvonly", |
| mid: null, |
| currentDirection: null, |
| stopped: false |
| }, |
| { |
| receiver: {track: {kind: "video"}}, |
| sender: {track: null}, |
| direction: "recvonly", |
| mid: null, |
| currentDirection: null, |
| stopped: false |
| } |
| ]); |
| }; |
| |
| const checkAddTransceiverWithSetRemoteOfferSending = async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| |
| const stream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| pc1.addTransceiver(track, {streams: [stream]}); |
| |
| const offer = await pc1.createOffer(); |
| |
| const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); |
| hasProps(trackEvents, |
| [ |
| { |
| track: pc2.getTransceivers()[0].receiver.track, |
| streams: [{id: stream.id}] |
| } |
| ]); |
| |
| |
| hasPropsAndUniqueMids(pc2.getTransceivers(), |
| [ |
| { |
| receiver: {track: {kind: "audio"}}, |
| sender: {track: null}, |
| direction: "recvonly", |
| currentDirection: null, |
| stopped: false |
| } |
| ]); |
| }; |
| |
| const checkAddTransceiverWithSetRemoteOfferNoSend = async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| |
| const stream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| pc1.addTransceiver(track); |
| pc1.getTransceivers()[0].direction = "recvonly"; |
| |
| const offer = await pc1.createOffer(); |
| const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); |
| hasProps(trackEvents, []); |
| |
| hasPropsAndUniqueMids(pc2.getTransceivers(), |
| [ |
| { |
| receiver: {track: {kind: "audio"}}, |
| sender: {track: null}, |
| // rtcweb-jsep says this is recvonly, w3c-webrtc does not... |
| direction: "recvonly", |
| currentDirection: null, |
| stopped: false |
| } |
| ]); |
| }; |
| |
| const checkAddTransceiverBadKind = async t => { |
| const pc = new RTCPeerConnection(); |
| t.add_cleanup(() => pc.close()); |
| try { |
| pc.addTransceiver("foo"); |
| assert_true(false, 'addTransceiver("foo") throws'); |
| } |
| catch (e) { |
| if (e instanceof TypeError) { |
| assert_true(true, 'addTransceiver("foo") throws a TypeError'); |
| } else { |
| assert_true(false, 'addTransceiver("foo") throws a TypeError'); |
| } |
| } |
| |
| hasProps(pc.getTransceivers(), []); |
| }; |
| |
| const checkMsidNoTrackId = async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| const stream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| pc1.addTrack(track, stream); |
| const offer = await pc1.createOffer(); |
| await pc1.setLocalDescription(offer); |
| // Remove track-id from msid |
| offer.sdp = offer.sdp.replace(/(a=msid:[^ \t]+).*\r\n/g, "$1\r\n"); |
| assert_true(offer.sdp.includes(`a=msid:${stream.id}\r\n`)); |
| await pc2.setRemoteDescription(offer); |
| const answer = await pc2.createAnswer(); |
| await pc1.setRemoteDescription(answer); |
| await pc2.setLocalDescription(answer); |
| }; |
| |
| const checkNoMidOffer = async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| |
| const stream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| pc1.addTrack(track, stream); |
| |
| const offer = await pc1.createOffer(); |
| await pc1.setLocalDescription(offer); |
| |
| // Remove mid attr |
| offer.sdp = offer.sdp.replace("a=mid:", "a=unknownattr:"); |
| await pc2.setRemoteDescription(offer); |
| |
| hasPropsAndUniqueMids(pc2.getTransceivers(), |
| [ |
| { |
| receiver: {track: {kind: "audio"}}, |
| sender: {track: null}, |
| direction: "recvonly", |
| currentDirection: null, |
| stopped: false |
| } |
| ]); |
| |
| const answer = await pc2.createAnswer(); |
| await pc2.setLocalDescription(answer); |
| await pc1.setRemoteDescription(answer); |
| }; |
| |
| const checkNoMidAnswer = async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| |
| const stream = await navigator.mediaDevices.getUserMedia({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| pc1.addTrack(track, stream); |
| |
| const offer = await pc1.createOffer(); |
| await pc1.setLocalDescription(offer); |
| await pc2.setRemoteDescription(offer); |
| let answer = await pc2.createAnswer(); |
| // Remove mid attr |
| answer.sdp = answer.sdp.replace("a=mid:", "a=unknownattr:"); |
| // Remove group attr also |
| answer.sdp = answer.sdp.replace("a=group:", "a=unknownattr:"); |
| await pc1.setRemoteDescription(answer); |
| |
| hasPropsAndUniqueMids(pc1.getTransceivers(), |
| [ |
| { |
| receiver: {track: {kind: "audio"}}, |
| sender: {track: {kind: "audio"}}, |
| direction: "sendrecv", |
| currentDirection: "sendonly", |
| stopped: false |
| } |
| ]); |
| |
| const reoffer = await pc1.createOffer(); |
| await pc1.setLocalDescription(reoffer); |
| }; |
| |
| const checkAddTransceiverNoTrackDoesntPair = async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| |
| pc1.addTransceiver("audio"); |
| pc2.addTransceiver("audio"); |
| |
| const offer = await pc1.createOffer(); |
| const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); |
| hasProps(trackEvents, |
| [ |
| { |
| track: pc2.getTransceivers()[1].receiver.track, |
| streams: [] |
| } |
| ]); |
| |
| hasPropsAndUniqueMids(pc2.getTransceivers(), |
| [ |
| {mid: null}, // no addTrack magic, doesn't auto-pair |
| {} // Created by SRD |
| ]); |
| }; |
| |
| const checkAddTransceiverWithTrackDoesntPair = async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| pc1.addTransceiver("audio"); |
| |
| const stream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| pc2.addTransceiver(track); |
| |
| const offer = await pc1.createOffer(); |
| const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); |
| hasProps(trackEvents, |
| [ |
| { |
| track: pc2.getTransceivers()[1].receiver.track, |
| streams: [] |
| } |
| ]); |
| |
| hasPropsAndUniqueMids(pc2.getTransceivers(), |
| [ |
| {mid: null, sender: {track}}, |
| {sender: {track: null}} // Created by SRD |
| ]); |
| }; |
| |
| const checkAddTransceiverThenReplaceTrackDoesntPair = async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| pc1.addTransceiver("audio"); |
| pc2.addTransceiver("audio"); |
| |
| const stream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| await pc2.getTransceivers()[0].sender.replaceTrack(track); |
| |
| const offer = await pc1.createOffer(); |
| const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); |
| hasProps(trackEvents, |
| [ |
| { |
| track: pc2.getTransceivers()[1].receiver.track, |
| streams: [] |
| } |
| ]); |
| |
| hasPropsAndUniqueMids(pc2.getTransceivers(), |
| [ |
| {mid: null, sender: {track}}, |
| {sender: {track: null}} // Created by SRD |
| ]); |
| }; |
| |
| const checkAddTransceiverThenAddTrackPairs = async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| pc1.addTransceiver("audio"); |
| pc2.addTransceiver("audio"); |
| |
| const stream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| pc2.addTrack(track, stream); |
| |
| const offer = await pc1.createOffer(); |
| const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); |
| hasProps(trackEvents, |
| [ |
| { |
| track: pc2.getTransceivers()[0].receiver.track, |
| streams: [] |
| } |
| ]); |
| |
| hasPropsAndUniqueMids(pc2.getTransceivers(), |
| [ |
| {sender: {track}} |
| ]); |
| }; |
| |
| const checkAddTrackPairs = async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| pc1.addTransceiver("audio"); |
| |
| const stream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| pc2.addTrack(track, stream); |
| |
| const offer = await pc1.createOffer(); |
| const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); |
| hasProps(trackEvents, |
| [ |
| { |
| track: pc2.getTransceivers()[0].receiver.track, |
| streams: [] |
| } |
| ]); |
| |
| hasPropsAndUniqueMids(pc2.getTransceivers(), |
| [ |
| {sender: {track}} |
| ]); |
| }; |
| |
| const checkReplaceTrackNullDoesntPreventPairing = async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| pc1.addTransceiver("audio"); |
| |
| const stream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| pc2.addTrack(track, stream); |
| await pc2.getTransceivers()[0].sender.replaceTrack(null); |
| |
| const offer = await pc1.createOffer(); |
| const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); |
| hasProps(trackEvents, |
| [ |
| { |
| track: pc2.getTransceivers()[0].receiver.track, |
| streams: [] |
| } |
| ]); |
| |
| hasPropsAndUniqueMids(pc2.getTransceivers(), |
| [ |
| {sender: {track: null}} |
| ]); |
| }; |
| |
| const checkRemoveAndReadd = async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| const stream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| pc1.addTrack(track, stream); |
| |
| await offerAnswer(pc1, pc2); |
| |
| pc1.removeTrack(pc1.getSenders()[0]); |
| pc1.addTrack(track, stream); |
| |
| hasProps(pc1.getTransceivers(), |
| [ |
| { |
| sender: {track: null}, |
| direction: "recvonly" |
| }, |
| { |
| sender: {track}, |
| direction: "sendrecv" |
| } |
| ]); |
| |
| // pc1 is offerer |
| await offerAnswer(pc1, pc2); |
| |
| hasProps(pc2.getTransceivers(), |
| [ |
| {currentDirection: "inactive"}, |
| {currentDirection: "recvonly"} |
| ]); |
| |
| pc1.removeTrack(pc1.getSenders()[1]); |
| pc1.addTrack(track, stream); |
| |
| hasProps(pc1.getTransceivers(), |
| [ |
| { |
| sender: {track: null}, |
| direction: "recvonly" |
| }, |
| { |
| sender: {track: null}, |
| direction: "recvonly" |
| }, |
| { |
| sender: {track}, |
| direction: "sendrecv" |
| } |
| ]); |
| |
| // pc1 is answerer. We need to create a new transceiver so pc1 will have |
| // something to attach the re-added track to |
| pc2.addTransceiver("audio"); |
| |
| await offerAnswer(pc2, pc1); |
| |
| hasProps(pc2.getTransceivers(), |
| [ |
| {currentDirection: "inactive"}, |
| {currentDirection: "inactive"}, |
| {currentDirection: "sendrecv"} |
| ]); |
| }; |
| |
| const checkAddTrackExistingTransceiverThenRemove = async t => { |
| const pc = new RTCPeerConnection(); |
| t.add_cleanup(() => pc.close()); |
| pc.addTransceiver("audio"); |
| const stream = await getNoiseStream({audio: true}); |
| const audio = stream.getAudioTracks()[0]; |
| let sender = pc.addTrack(audio, stream); |
| pc.removeTrack(sender); |
| |
| // Cause transceiver to be associated |
| await pc.setLocalDescription(await pc.createOffer()); |
| |
| // Make sure add/remove works still |
| sender = pc.addTrack(audio, stream); |
| pc.removeTrack(sender); |
| |
| stopTracks(stream); |
| }; |
| |
| const checkRemoveTrackNegotiation = async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| const stream = await getNoiseStream({audio: true, video: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const audio = stream.getAudioTracks()[0]; |
| pc1.addTrack(audio, stream); |
| const video = stream.getVideoTracks()[0]; |
| pc1.addTrack(video, stream); |
| // We want both a sendrecv and sendonly transceiver to test that the |
| // appropriate direction changes happen. |
| pc1.getTransceivers()[1].direction = "sendonly"; |
| |
| let offer = await pc1.createOffer(); |
| |
| // Get a reference to the stream |
| let trackEventCollector = collectTrackEvents(pc2); |
| await pc2.setRemoteDescription(offer); |
| let pc2TrackEvents = trackEventCollector.finish(); |
| hasProps(pc2TrackEvents, |
| [ |
| {streams: [{id: stream.id}]}, |
| {streams: [{id: stream.id}]} |
| ]); |
| const receiveStream = pc2TrackEvents[0].streams[0]; |
| |
| // Verify that rollback causes onremovetrack to fire for the added tracks |
| let removetrackEventCollector = collectRemoveTrackEvents(receiveStream); |
| await pc2.setRemoteDescription({type: "rollback"}); |
| let removedtracks = removetrackEventCollector.finish().map(e => e.track); |
| assert_equals(removedtracks.length, 2, |
| "Rollback should have removed two tracks"); |
| assert_true(removedtracks.includes(pc2TrackEvents[0].track), |
| "First track should be removed"); |
| assert_true(removedtracks.includes(pc2TrackEvents[1].track), |
| "Second track should be removed"); |
| |
| offer = await pc1.createOffer(); |
| |
| let addtrackEventCollector = collectAddTrackEvents(receiveStream); |
| trackEventCollector = collectTrackEvents(pc2); |
| await pc2.setRemoteDescription(offer); |
| pc2TrackEvents = trackEventCollector.finish(); |
| let addedtracks = addtrackEventCollector.finish().map(e => e.track); |
| assert_equals(addedtracks.length, 2, |
| "pc2.setRemoteDescription(offer) should've added 2 tracks to receive stream"); |
| assert_true(addedtracks.includes(pc2TrackEvents[0].track), |
| "First track should be added"); |
| assert_true(addedtracks.includes(pc2TrackEvents[1].track), |
| "Second track should be added"); |
| |
| await pc1.setLocalDescription(offer); |
| let answer = await pc2.createAnswer(); |
| await pc1.setRemoteDescription(answer); |
| await pc2.setLocalDescription(answer); |
| pc1.removeTrack(pc1.getSenders()[0]); |
| |
| hasProps(pc1.getSenders(), |
| [ |
| {track: null}, |
| {track: video} |
| ]); |
| |
| hasProps(pc1.getTransceivers(), |
| [ |
| { |
| sender: {track: null}, |
| direction: "recvonly" |
| }, |
| { |
| sender: {track: video}, |
| direction: "sendonly" |
| } |
| ]); |
| |
| await negotiationNeeded(pc1); |
| |
| pc1.removeTrack(pc1.getSenders()[1]); |
| |
| hasProps(pc1.getSenders(), |
| [ |
| {track: null}, |
| {track: null} |
| ]); |
| |
| hasProps(pc1.getTransceivers(), |
| [ |
| { |
| sender: {track: null}, |
| direction: "recvonly" |
| }, |
| { |
| sender: {track: null}, |
| direction: "inactive" |
| } |
| ]); |
| |
| // pc1 as offerer |
| offer = await pc1.createOffer(); |
| |
| removetrackEventCollector = collectRemoveTrackEvents(receiveStream); |
| await pc2.setRemoteDescription(offer); |
| removedtracks = removetrackEventCollector.finish().map(e => e.track); |
| assert_equals(removedtracks.length, 2, "Should have two removed tracks"); |
| assert_true(removedtracks.includes(pc2TrackEvents[0].track), |
| "First track should be removed"); |
| assert_true(removedtracks.includes(pc2TrackEvents[1].track), |
| "Second track should be removed"); |
| |
| addtrackEventCollector = collectAddTrackEvents(receiveStream); |
| await pc2.setRemoteDescription({type: "rollback"}); |
| addedtracks = addtrackEventCollector.finish().map(e => e.track); |
| assert_equals(addedtracks.length, 2, "Rollback should have added two tracks"); |
| |
| // pc2 as offerer |
| offer = await pc2.createOffer(); |
| await pc2.setLocalDescription(offer); |
| await pc1.setRemoteDescription(offer); |
| answer = await pc1.createAnswer(); |
| await pc1.setLocalDescription(answer); |
| |
| removetrackEventCollector = collectRemoveTrackEvents(receiveStream); |
| await pc2.setRemoteDescription(answer); |
| removedtracks = removetrackEventCollector.finish().map(e => e.track); |
| assert_equals(removedtracks.length, 2, "Should have two removed tracks"); |
| |
| hasProps(pc2.getTransceivers(), |
| [ |
| { |
| currentDirection: "inactive" |
| }, |
| { |
| currentDirection: "inactive" |
| } |
| ]); |
| }; |
| |
| const checkSetDirection = async t => { |
| const pc = new RTCPeerConnection(); |
| t.add_cleanup(() => pc.close()); |
| pc.addTransceiver("audio"); |
| |
| pc.getTransceivers()[0].direction = "sendonly"; |
| hasProps(pc.getTransceivers(),[{direction: "sendonly"}]); |
| pc.getTransceivers()[0].direction = "recvonly"; |
| hasProps(pc.getTransceivers(),[{direction: "recvonly"}]); |
| pc.getTransceivers()[0].direction = "inactive"; |
| hasProps(pc.getTransceivers(),[{direction: "inactive"}]); |
| pc.getTransceivers()[0].direction = "sendrecv"; |
| hasProps(pc.getTransceivers(),[{direction: "sendrecv"}]); |
| }; |
| |
| const checkCurrentDirection = async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| |
| const stream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| pc1.addTrack(track, stream); |
| pc2.addTrack(track, stream); |
| hasProps(pc1.getTransceivers(), [{currentDirection: null}]); |
| |
| let offer = await pc1.createOffer(); |
| hasProps(pc1.getTransceivers(), [{currentDirection: null}]); |
| |
| await pc1.setLocalDescription(offer); |
| hasProps(pc1.getTransceivers(), [{currentDirection: null}]); |
| |
| let trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); |
| hasProps(trackEvents, |
| [ |
| { |
| track: pc2.getTransceivers()[0].receiver.track, |
| streams: [{id: stream.id}] |
| } |
| ]); |
| |
| hasProps(pc2.getTransceivers(), [{currentDirection: null}]); |
| |
| let answer = await pc2.createAnswer(); |
| hasProps(pc2.getTransceivers(), [{currentDirection: null}]); |
| |
| await pc2.setLocalDescription(answer); |
| hasProps(pc2.getTransceivers(), [{currentDirection: "sendrecv"}]); |
| |
| trackEvents = await setRemoteDescriptionReturnTrackEvents(pc1, answer); |
| hasProps(trackEvents, |
| [ |
| { |
| track: pc1.getTransceivers()[0].receiver.track, |
| streams: [{id: stream.id}] |
| } |
| ]); |
| |
| hasProps(pc1.getTransceivers(), [{currentDirection: "sendrecv"}]); |
| |
| pc2.getTransceivers()[0].direction = "sendonly"; |
| |
| offer = await pc2.createOffer(); |
| hasProps(pc2.getTransceivers(), [{currentDirection: "sendrecv"}]); |
| |
| await pc2.setLocalDescription(offer); |
| hasProps(pc2.getTransceivers(), [{currentDirection: "sendrecv"}]); |
| |
| trackEvents = await setRemoteDescriptionReturnTrackEvents(pc1, offer); |
| hasProps(trackEvents, []); |
| |
| hasProps(pc1.getTransceivers(), [{currentDirection: "sendrecv"}]); |
| |
| answer = await pc1.createAnswer(); |
| hasProps(pc1.getTransceivers(), [{currentDirection: "sendrecv"}]); |
| |
| await pc1.setLocalDescription(answer); |
| hasProps(pc1.getTransceivers(), [{currentDirection: "recvonly"}]); |
| |
| trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, answer); |
| hasProps(trackEvents, []); |
| |
| hasProps(pc2.getTransceivers(), [{currentDirection: "sendonly"}]); |
| |
| pc2.getTransceivers()[0].direction = "sendrecv"; |
| |
| offer = await pc2.createOffer(); |
| hasProps(pc2.getTransceivers(), [{currentDirection: "sendonly"}]); |
| |
| await pc2.setLocalDescription(offer); |
| hasProps(pc2.getTransceivers(), [{currentDirection: "sendonly"}]); |
| |
| trackEvents = await setRemoteDescriptionReturnTrackEvents(pc1, offer); |
| hasProps(trackEvents, []); |
| |
| hasProps(pc1.getTransceivers(), [{currentDirection: "recvonly"}]); |
| |
| answer = await pc1.createAnswer(); |
| hasProps(pc1.getTransceivers(), [{currentDirection: "recvonly"}]); |
| |
| await pc1.setLocalDescription(answer); |
| hasProps(pc1.getTransceivers(), [{currentDirection: "sendrecv"}]); |
| |
| trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, answer); |
| hasProps(trackEvents, |
| [ |
| { |
| track: pc2.getTransceivers()[0].receiver.track, |
| streams: [{id: stream.id}] |
| } |
| ]); |
| |
| hasProps(pc2.getTransceivers(), [{currentDirection: "sendrecv"}]); |
| }; |
| |
| const checkSendrecvWithNoSendTrack = async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| |
| const stream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| pc1.addTransceiver("audio"); |
| pc1.getTransceivers()[0].direction = "sendrecv"; |
| pc2.addTrack(track, stream); |
| |
| const offer = await pc1.createOffer(); |
| |
| let trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); |
| hasProps(trackEvents, |
| [ |
| { |
| track: pc2.getTransceivers()[0].receiver.track, |
| streams: [] |
| } |
| ]); |
| |
| trickle(t, pc1, pc2); |
| await pc1.setLocalDescription(offer); |
| |
| const answer = await pc2.createAnswer(); |
| trackEvents = await setRemoteDescriptionReturnTrackEvents(pc1, answer); |
| // Spec language doesn't say anything about checking whether the transceiver |
| // is stopped here. |
| hasProps(trackEvents, |
| [ |
| { |
| track: pc1.getTransceivers()[0].receiver.track, |
| streams: [{id: stream.id}] |
| } |
| ]); |
| |
| trickle(t, pc2, pc1); |
| await pc2.setLocalDescription(answer); |
| |
| await iceConnected(pc1); |
| await iceConnected(pc2); |
| }; |
| |
| const checkSendrecvWithTracklessStream = async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| |
| const stream = new MediaStream(); |
| pc1.addTransceiver("audio", {streams: [stream]}); |
| |
| const offer = await pc1.createOffer(); |
| |
| const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); |
| hasProps(trackEvents, |
| [ |
| { |
| track: pc2.getTransceivers()[0].receiver.track, |
| streams: [{id: stream.id}] |
| } |
| ]); |
| }; |
| |
| const checkMute = async t => { |
| const pc1 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| const stream1 = await getNoiseStream({audio: true, video: true}); |
| t.add_cleanup(() => stopTracks(stream1)); |
| const audio1 = stream1.getAudioTracks()[0]; |
| pc1.addTrack(audio1, stream1); |
| const countMuteAudio1 = countEvents(pc1.getTransceivers()[0].receiver.track, "mute"); |
| const countUnmuteAudio1 = countEvents(pc1.getTransceivers()[0].receiver.track, "unmute"); |
| |
| const video1 = stream1.getVideoTracks()[0]; |
| pc1.addTrack(video1, stream1); |
| const countMuteVideo1 = countEvents(pc1.getTransceivers()[1].receiver.track, "mute"); |
| const countUnmuteVideo1 = countEvents(pc1.getTransceivers()[1].receiver.track, "unmute"); |
| |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc2.close()); |
| const stream2 = await getNoiseStream({audio: true, video: true}); |
| t.add_cleanup(() => stopTracks(stream2)); |
| const audio2 = stream2.getAudioTracks()[0]; |
| pc2.addTrack(audio2, stream2); |
| const countMuteAudio2 = countEvents(pc2.getTransceivers()[0].receiver.track, "mute"); |
| const countUnmuteAudio2 = countEvents(pc2.getTransceivers()[0].receiver.track, "unmute"); |
| |
| const video2 = stream2.getVideoTracks()[0]; |
| pc2.addTrack(video2, stream2); |
| const countMuteVideo2 = countEvents(pc2.getTransceivers()[1].receiver.track, "mute"); |
| const countUnmuteVideo2 = countEvents(pc2.getTransceivers()[1].receiver.track, "unmute"); |
| |
| |
| // Check that receive tracks start muted |
| hasProps(pc1.getTransceivers(), |
| [ |
| {receiver: {track: {kind: "audio", muted: true}}}, |
| {receiver: {track: {kind: "video", muted: true}}} |
| ]); |
| |
| hasProps(pc1.getTransceivers(), |
| [ |
| {receiver: {track: {kind: "audio", muted: true}}}, |
| {receiver: {track: {kind: "video", muted: true}}} |
| ]); |
| |
| let offer = await pc1.createOffer(); |
| await pc2.setRemoteDescription(offer); |
| trickle(t, pc1, pc2); |
| await pc1.setLocalDescription(offer); |
| let answer = await pc2.createAnswer(); |
| await pc1.setRemoteDescription(answer); |
| trickle(t, pc2, pc1); |
| await pc2.setLocalDescription(answer); |
| |
| let gotUnmuteAudio1 = gotUnmuteEvent(pc1.getTransceivers()[0].receiver.track); |
| let gotUnmuteVideo1 = gotUnmuteEvent(pc1.getTransceivers()[1].receiver.track); |
| |
| let gotUnmuteAudio2 = gotUnmuteEvent(pc2.getTransceivers()[0].receiver.track); |
| let gotUnmuteVideo2 = gotUnmuteEvent(pc2.getTransceivers()[1].receiver.track); |
| |
| await iceConnected(pc1); |
| await iceConnected(pc2); |
| |
| // Check that receive tracks are unmuted when RTP starts flowing |
| await gotUnmuteAudio1; |
| await gotUnmuteVideo1; |
| await gotUnmuteAudio2; |
| await gotUnmuteVideo2; |
| |
| // Check whether disabling recv locally causes onmute |
| pc1.getTransceivers()[0].direction = "sendonly"; |
| pc1.getTransceivers()[1].direction = "sendonly"; |
| offer = await pc1.createOffer(); |
| await pc2.setRemoteDescription(offer); |
| await pc1.setLocalDescription(offer); |
| answer = await pc2.createAnswer(); |
| const gotMuteAudio1 = gotMuteEvent(pc1.getTransceivers()[0].receiver.track); |
| const gotMuteVideo1 = gotMuteEvent(pc1.getTransceivers()[1].receiver.track); |
| await pc1.setRemoteDescription(answer); |
| await pc2.setLocalDescription(answer); |
| await gotMuteAudio1; |
| await gotMuteVideo1; |
| |
| // Check whether disabling on remote causes onmute |
| pc1.getTransceivers()[0].direction = "inactive"; |
| pc1.getTransceivers()[1].direction = "inactive"; |
| offer = await pc1.createOffer(); |
| const gotMuteAudio2 = gotMuteEvent(pc2.getTransceivers()[0].receiver.track); |
| const gotMuteVideo2 = gotMuteEvent(pc2.getTransceivers()[1].receiver.track); |
| await pc2.setRemoteDescription(offer); |
| await gotMuteAudio2; |
| await gotMuteVideo2; |
| await pc1.setLocalDescription(offer); |
| answer = await pc2.createAnswer(); |
| await pc1.setRemoteDescription(answer); |
| await pc2.setLocalDescription(answer); |
| |
| // Check whether onunmute fires when we turn everything on again |
| pc1.getTransceivers()[0].direction = "sendrecv"; |
| pc1.getTransceivers()[1].direction = "sendrecv"; |
| offer = await pc1.createOffer(); |
| await pc2.setRemoteDescription(offer); |
| await pc1.setLocalDescription(offer); |
| answer = await pc2.createAnswer(); |
| gotUnmuteAudio1 = gotUnmuteEvent(pc1.getTransceivers()[0].receiver.track); |
| gotUnmuteVideo1 = gotUnmuteEvent(pc1.getTransceivers()[1].receiver.track); |
| gotUnmuteAudio2 = gotUnmuteEvent(pc2.getTransceivers()[0].receiver.track); |
| gotUnmuteVideo2 = gotUnmuteEvent(pc2.getTransceivers()[1].receiver.track); |
| await pc1.setRemoteDescription(answer); |
| await pc2.setLocalDescription(answer); |
| await gotUnmuteAudio1; |
| await gotUnmuteVideo1; |
| await gotUnmuteAudio2; |
| await gotUnmuteVideo2; |
| |
| // Wait a little, just in case some stray events fire |
| await new Promise(r => t.step_timeout(r, 100)); |
| |
| assert_equals(1, countMuteAudio1.count, "Got 1 mute event for pc1's audio track"); |
| assert_equals(1, countMuteVideo1.count, "Got 1 mute event for pc1's video track"); |
| assert_equals(1, countMuteAudio2.count, "Got 1 mute event for pc2's audio track"); |
| assert_equals(1, countMuteVideo2.count, "Got 1 mute event for pc2's video track"); |
| assert_equals(2, countUnmuteAudio1.count, "Got 2 unmute events for pc1's audio track"); |
| assert_equals(2, countUnmuteVideo1.count, "Got 2 unmute events for pc1's video track"); |
| assert_equals(2, countUnmuteAudio2.count, "Got 2 unmute events for pc2's audio track"); |
| assert_equals(2, countUnmuteVideo2.count, "Got 2 unmute events for pc2's video track"); |
| }; |
| |
| const checkStop = async t => { |
| const pc1 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| const stream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| pc1.addTrack(track, stream); |
| |
| let offer = await pc1.createOffer(); |
| await pc1.setLocalDescription(offer); |
| |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc2.close()); |
| await pc2.setRemoteDescription(offer); |
| |
| pc2.addTrack(track, stream); |
| |
| const answer = await pc2.createAnswer(); |
| await pc2.setLocalDescription(answer); |
| await pc1.setRemoteDescription(answer); |
| |
| let stoppedTransceiver = pc1.getTransceivers()[0]; |
| let onended = new Promise(resolve => { |
| stoppedTransceiver.receiver.track.onended = resolve; |
| }); |
| stoppedTransceiver.stop(); |
| assert_equals(pc1.getReceivers().length, 0, 'getReceivers does not expose a receiver of a stopped transceiver'); |
| assert_equals(pc1.getSenders().length, 0, 'getSenders does not expose a sender of a stopped transceiver'); |
| |
| await onended; |
| |
| hasPropsAndUniqueMids(pc1.getTransceivers(), |
| [ |
| { |
| sender: {track: {kind: "audio"}}, |
| receiver: {track: {kind: "audio", readyState: "ended"}}, |
| stopped: true, |
| currentDirection: null, |
| direction: "sendrecv" |
| } |
| ]); |
| |
| const transceiver = pc1.getTransceivers()[0]; |
| |
| checkThrows(() => transceiver.sender.setParameters( |
| transceiver.sender.getParameters()), |
| "InvalidStateError", "setParameters on stopped transceiver"); |
| |
| const stream2 = await getNoiseStream({audio: true}); |
| const track2 = stream.getAudioTracks()[0]; |
| checkThrows(() => transceiver.sender.replaceTrack(track2), |
| "InvalidStateError", "replaceTrack on stopped transceiver"); |
| |
| checkThrows(() => transceiver.direction = "sendrecv", |
| "InvalidStateError", "set direction on stopped transceiver"); |
| |
| checkThrows(() => transceiver.sender.dtmf.insertDTMF("111"), |
| "InvalidStateError", "insertDTMF on stopped transceiver"); |
| |
| // Shouldn't throw |
| stoppedTransceiver.stop(); |
| |
| offer = await pc1.createOffer(); |
| await pc1.setLocalDescription(offer); |
| |
| stoppedTransceiver = pc2.getTransceivers()[0]; |
| onended = new Promise(resolve => { |
| stoppedTransceiver.receiver.track.onended = resolve; |
| }); |
| |
| await pc2.setRemoteDescription(offer); |
| |
| await onended; |
| |
| hasProps(pc2.getTransceivers(), |
| [ |
| { |
| sender: {track: {kind: "audio"}}, |
| receiver: {track: {kind: "audio", readyState: "ended"}}, |
| stopped: true, |
| mid: null, |
| currentDirection: null, |
| direction: "sendrecv" |
| } |
| ]); |
| |
| // Shouldn't throw either |
| stoppedTransceiver.stop(); |
| |
| pc1.close(); |
| pc2.close(); |
| |
| // Still shouldn't throw |
| stoppedTransceiver.stop(); |
| }; |
| |
| const checkStopAfterCreateOffer = async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| |
| const stream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| pc1.addTrack(track, stream); |
| pc2.addTrack(track, stream); |
| |
| let offer = await pc1.createOffer(); |
| |
| pc1.getTransceivers()[0].stop(); |
| |
| await pc2.setRemoteDescription(offer) |
| trickle(t, pc1, pc2); |
| await pc1.setLocalDescription(offer); |
| |
| let answer = await pc2.createAnswer(); |
| const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc1, answer); |
| // Spec language doesn't say anything about checking whether the transceiver |
| // is stopped here. |
| hasProps(trackEvents, |
| [ |
| { |
| track: pc1.getTransceivers()[0].receiver.track, |
| streams: [{id: stream.id}] |
| } |
| ]); |
| |
| hasPropsAndUniqueMids(pc1.getTransceivers(), |
| [ |
| { |
| stopped: true, |
| } |
| ]); |
| |
| trickle(t, pc2, pc1); |
| await pc2.setLocalDescription(answer); |
| |
| await negotiationNeeded(pc1); |
| await iceConnected(pc1); |
| await iceConnected(pc2); |
| |
| offer = await pc1.createOffer(); |
| await pc1.setLocalDescription(offer); |
| await pc2.setRemoteDescription(offer); |
| answer = await pc2.createAnswer(); |
| await pc2.setLocalDescription(answer); |
| await pc1.setRemoteDescription(answer); |
| |
| hasProps(pc1.getTransceivers(), |
| [ |
| { |
| stopped: true, |
| mid: null |
| } |
| ]); |
| |
| hasProps(pc2.getTransceivers(), |
| [ |
| { |
| stopped: true, |
| mid: null |
| } |
| ]); |
| }; |
| |
| const checkStopAfterSetLocalOffer = async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| |
| const stream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| pc1.addTrack(track, stream); |
| pc2.addTrack(track, stream); |
| |
| let offer = await pc1.createOffer(); |
| |
| await pc2.setRemoteDescription(offer) |
| trickle(t, pc1, pc2); |
| await pc1.setLocalDescription(offer); |
| |
| pc1.getTransceivers()[0].stop(); |
| |
| let answer = await pc2.createAnswer(); |
| const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc1, answer); |
| // Spec language doesn't say anything about checking whether the transceiver |
| // is stopped here. |
| hasProps(trackEvents, |
| [ |
| { |
| track: pc1.getTransceivers()[0].receiver.track, |
| streams: [{id: stream.id}] |
| } |
| ]); |
| |
| hasPropsAndUniqueMids(pc1.getTransceivers(), |
| [ |
| { |
| stopped: true, |
| } |
| ]); |
| await negotiationNeeded(pc1); |
| |
| trickle(t, pc2, pc1); |
| await pc2.setLocalDescription(answer); |
| |
| await iceConnected(pc1); |
| await iceConnected(pc2); |
| |
| offer = await pc1.createOffer(); |
| await pc1.setLocalDescription(offer); |
| await pc2.setRemoteDescription(offer); |
| answer = await pc2.createAnswer(); |
| await pc2.setLocalDescription(answer); |
| await pc1.setRemoteDescription(answer); |
| |
| hasProps(pc1.getTransceivers(), |
| [ |
| { |
| stopped: true, |
| mid: null |
| } |
| ]); |
| |
| hasProps(pc2.getTransceivers(), |
| [ |
| { |
| stopped: true, |
| mid: null |
| } |
| ]); |
| }; |
| |
| const checkStopAfterSetRemoteOffer = async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| |
| const stream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| pc1.addTrack(track, stream); |
| pc2.addTrack(track, stream); |
| |
| const offer = await pc1.createOffer(); |
| |
| await pc2.setRemoteDescription(offer) |
| await pc1.setLocalDescription(offer); |
| |
| // Stop on _answerer_side now. Should take effect in answer. |
| pc2.getTransceivers()[0].stop(); |
| |
| const answer = await pc2.createAnswer(); |
| const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc1, answer); |
| hasProps(trackEvents, []); |
| |
| hasProps(pc1.getTransceivers(), |
| [ |
| { |
| stopped: true, |
| mid: null |
| } |
| ]); |
| |
| await pc2.setLocalDescription(answer); |
| }; |
| |
| const checkStopAfterCreateAnswer = async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| |
| const stream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| pc1.addTrack(track, stream); |
| pc2.addTrack(track, stream); |
| |
| let offer = await pc1.createOffer(); |
| |
| await pc2.setRemoteDescription(offer) |
| trickle(t, pc1, pc2); |
| await pc1.setLocalDescription(offer); |
| |
| let answer = await pc2.createAnswer(); |
| |
| // Too late for this to go in the answer. ICE should succeed. |
| pc2.getTransceivers()[0].stop(); |
| |
| const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc1, answer); |
| hasProps(trackEvents, |
| [ |
| { |
| track: pc1.getTransceivers()[0].receiver.track, |
| streams: [{id: stream.id}] |
| } |
| ]); |
| |
| hasPropsAndUniqueMids(pc2.getTransceivers(), |
| [ |
| { |
| stopped: true, |
| } |
| ]); |
| |
| trickle(t, pc2, pc1); |
| await pc2.setLocalDescription(answer); |
| |
| await negotiationNeeded(pc2); |
| await iceConnected(pc1); |
| await iceConnected(pc2); |
| |
| offer = await pc1.createOffer(); |
| await pc1.setLocalDescription(offer); |
| await pc2.setRemoteDescription(offer); |
| answer = await pc2.createAnswer(); |
| await pc2.setLocalDescription(answer); |
| await pc1.setRemoteDescription(answer); |
| |
| hasProps(pc1.getTransceivers(), |
| [ |
| { |
| stopped: true, |
| mid: null |
| } |
| ]); |
| |
| hasProps(pc2.getTransceivers(), |
| [ |
| { |
| stopped: true, |
| mid: null |
| } |
| ]); |
| }; |
| |
| const checkStopAfterSetLocalAnswer = async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| |
| const stream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| pc1.addTrack(track, stream); |
| pc2.addTrack(track, stream); |
| |
| let offer = await pc1.createOffer(); |
| |
| await pc2.setRemoteDescription(offer) |
| trickle(t, pc1, pc2); |
| await pc1.setLocalDescription(offer); |
| |
| let answer = await pc2.createAnswer(); |
| |
| const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc1, answer); |
| hasProps(trackEvents, |
| [ |
| { |
| track: pc1.getTransceivers()[0].receiver.track, |
| streams: [{id: stream.id}] |
| } |
| ]); |
| |
| trickle(t, pc2, pc1); |
| await pc2.setLocalDescription(answer); |
| |
| // ICE should succeed. |
| pc2.getTransceivers()[0].stop(); |
| |
| hasPropsAndUniqueMids(pc2.getTransceivers(), |
| [ |
| { |
| stopped: true, |
| } |
| ]); |
| |
| await negotiationNeeded(pc2); |
| await iceConnected(pc1); |
| await iceConnected(pc2); |
| |
| offer = await pc1.createOffer(); |
| await pc1.setLocalDescription(offer); |
| await pc2.setRemoteDescription(offer); |
| answer = await pc2.createAnswer(); |
| await pc2.setLocalDescription(answer); |
| await pc1.setRemoteDescription(answer); |
| |
| hasProps(pc1.getTransceivers(), |
| [ |
| { |
| stopped: true, |
| mid: null |
| } |
| ]); |
| |
| hasProps(pc2.getTransceivers(), |
| [ |
| { |
| stopped: true, |
| mid: null |
| } |
| ]); |
| }; |
| |
| const checkStopAfterClose = async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| |
| const stream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| pc1.addTrack(track, stream); |
| pc2.addTrack(track, stream); |
| |
| const offer = await pc1.createOffer(); |
| await pc2.setRemoteDescription(offer) |
| await pc1.setLocalDescription(offer); |
| const answer = await pc2.createAnswer(); |
| await pc2.setLocalDescription(answer); |
| await pc1.setRemoteDescription(answer); |
| |
| pc1.close(); |
| await checkThrows(() => pc1.getTransceivers()[0].stop(), |
| "InvalidStateError", |
| "Stopping a transceiver on a closed PC should throw."); |
| }; |
| |
| const checkLocalRollback = async t => { |
| const pc = new RTCPeerConnection(); |
| t.add_cleanup(() => pc.close()); |
| |
| const stream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| pc.addTrack(track, stream); |
| |
| let offer = await pc.createOffer(); |
| await pc.setLocalDescription(offer); |
| |
| hasPropsAndUniqueMids(pc.getTransceivers(), |
| [ |
| { |
| receiver: {track: {kind: "audio"}}, |
| sender: {track}, |
| direction: "sendrecv", |
| currentDirection: null, |
| stopped: false |
| } |
| ]); |
| |
| // Verify that rollback doesn't stomp things it should not |
| pc.getTransceivers()[0].direction = "sendonly"; |
| const stream2 = await getNoiseStream({audio: true}); |
| const track2 = stream2.getAudioTracks()[0]; |
| await pc.getTransceivers()[0].sender.replaceTrack(track2); |
| |
| await pc.setLocalDescription({type: "rollback"}); |
| |
| hasProps(pc.getTransceivers(), |
| [ |
| { |
| receiver: {track: {kind: "audio"}}, |
| sender: {track: track2}, |
| direction: "sendonly", |
| mid: null, |
| currentDirection: null, |
| stopped: false |
| } |
| ]); |
| |
| // Make sure stop() isn't rolled back either. |
| offer = await pc.createOffer(); |
| await pc.setLocalDescription(offer); |
| pc.getTransceivers()[0].stop(); |
| await pc.setLocalDescription({type: "rollback"}); |
| |
| hasProps(pc.getTransceivers(), [{ stopped: true }]); |
| }; |
| |
| const checkRollbackAndSetRemoteOfferWithDifferentType = async t => { |
| const pc1 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| |
| const audioStream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(audioStream)); |
| const audioTrack = audioStream.getAudioTracks()[0]; |
| pc1.addTrack(audioTrack, audioStream); |
| |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc2.close()); |
| |
| const videoStream = await getNoiseStream({video: true}); |
| t.add_cleanup(() => stopTracks(videoStream)); |
| const videoTrack = videoStream.getVideoTracks()[0]; |
| pc2.addTrack(videoTrack, videoStream); |
| |
| await pc1.setLocalDescription(await pc1.createOffer()); |
| await pc1.setLocalDescription({type: "rollback"}); |
| |
| hasProps(pc1.getTransceivers(), |
| [ |
| { |
| receiver: {track: {kind: "audio"}}, |
| sender: {track: audioTrack}, |
| direction: "sendrecv", |
| mid: null, |
| currentDirection: null, |
| stopped: false |
| } |
| ]); |
| |
| hasProps(pc2.getTransceivers(), |
| [ |
| { |
| receiver: {track: {kind: "video"}}, |
| sender: {track: videoTrack}, |
| direction: "sendrecv", |
| mid: null, |
| currentDirection: null, |
| stopped: false |
| } |
| ]); |
| |
| await offerAnswer(pc2, pc1); |
| |
| hasPropsAndUniqueMids(pc1.getTransceivers(), |
| [ |
| { |
| receiver: {track: {kind: "audio"}}, |
| sender: {track: audioTrack}, |
| direction: "sendrecv", |
| mid: null, |
| currentDirection: null, |
| stopped: false |
| }, |
| { |
| receiver: {track: {kind: "video"}}, |
| sender: {track: null}, |
| direction: "recvonly", |
| currentDirection: "recvonly", |
| stopped: false |
| } |
| ]); |
| |
| hasPropsAndUniqueMids(pc2.getTransceivers(), |
| [ |
| { |
| receiver: {track: {kind: "video"}}, |
| sender: {track: videoTrack}, |
| direction: "sendrecv", |
| currentDirection: "sendonly", |
| stopped: false |
| } |
| ]); |
| |
| await offerAnswer(pc1, pc2); |
| }; |
| |
| const checkRemoteRollback = async t => { |
| const pc1 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| |
| const stream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| pc1.addTrack(track, stream); |
| |
| let offer = await pc1.createOffer(); |
| |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc2.close()); |
| await pc2.setRemoteDescription(offer); |
| |
| const removedTransceiver = pc2.getTransceivers()[0]; |
| |
| const onended = new Promise(resolve => { |
| removedTransceiver.receiver.track.onended = resolve; |
| }); |
| |
| await pc2.setRemoteDescription({type: "rollback"}); |
| |
| // Transceiver should be _gone_ |
| hasProps(pc2.getTransceivers(), []); |
| |
| hasProps(removedTransceiver, |
| { |
| stopped: true, |
| mid: null, |
| currentDirection: null |
| } |
| ); |
| |
| await onended; |
| |
| hasProps(removedTransceiver, |
| { |
| receiver: {track: {readyState: "ended"}}, |
| stopped: true, |
| mid: null, |
| currentDirection: null |
| } |
| ); |
| |
| // Setting the same offer again should do the same thing as before |
| await pc2.setRemoteDescription(offer); |
| hasPropsAndUniqueMids(pc2.getTransceivers(), |
| [ |
| { |
| receiver: {track: {kind: "audio"}}, |
| sender: {track: null}, |
| direction: "recvonly", |
| currentDirection: null, |
| stopped: false |
| } |
| ]); |
| |
| const mid0 = pc2.getTransceivers()[0].mid; |
| |
| // Give pc2 a track with replaceTrack |
| const stream2 = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream2)); |
| const track2 = stream2.getAudioTracks()[0]; |
| await pc2.getTransceivers()[0].sender.replaceTrack(track2); |
| pc2.getTransceivers()[0].direction = "sendrecv"; |
| hasProps(pc2.getTransceivers(), |
| [ |
| { |
| receiver: {track: {kind: "audio"}}, |
| sender: {track: track2}, |
| direction: "sendrecv", |
| mid: mid0, |
| currentDirection: null, |
| stopped: false |
| } |
| ]); |
| |
| await pc2.setRemoteDescription({type: "rollback"}); |
| |
| // Transceiver should be _gone_, again. replaceTrack doesn't prevent this, |
| // nor does setting direction. |
| hasProps(pc2.getTransceivers(), []); |
| |
| // Setting the same offer for a _third_ time should do the same thing |
| await pc2.setRemoteDescription(offer); |
| hasProps(pc2.getTransceivers(), |
| [ |
| { |
| receiver: {track: {kind: "audio"}}, |
| sender: {track: null}, |
| direction: "recvonly", |
| mid: mid0, |
| currentDirection: null, |
| stopped: false |
| } |
| ]); |
| |
| // We should be able to add the same track again |
| pc2.addTrack(track2, stream2); |
| hasProps(pc2.getTransceivers(), |
| [ |
| { |
| receiver: {track: {kind: "audio"}}, |
| sender: {track: track2}, |
| direction: "sendrecv", |
| mid: mid0, |
| currentDirection: null, |
| stopped: false |
| } |
| ]); |
| |
| await pc2.setRemoteDescription({type: "rollback"}); |
| // Transceiver should _not_ be gone this time, because addTrack touched it. |
| hasProps(pc2.getTransceivers(), |
| [ |
| { |
| receiver: {track: {kind: "audio"}}, |
| sender: {track: track2}, |
| direction: "sendrecv", |
| mid: null, |
| currentDirection: null, |
| stopped: false |
| } |
| ]); |
| |
| // Complete negotiation so we can test interactions with transceiver.stop() |
| await pc1.setLocalDescription(offer); |
| |
| // After all this SRD/rollback, we should still get the track event |
| let trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); |
| hasProps(trackEvents, |
| [ |
| { |
| track: pc2.getTransceivers()[0].receiver.track, |
| streams: [{id: stream.id}] |
| } |
| ]); |
| |
| const answer = await pc2.createAnswer(); |
| await pc2.setLocalDescription(answer); |
| |
| // Make sure all this rollback hasn't messed up the signaling |
| trackEvents = await setRemoteDescriptionReturnTrackEvents(pc1, answer); |
| hasProps(trackEvents, |
| [ |
| { |
| track: pc1.getTransceivers()[0].receiver.track, |
| streams: [{id: stream2.id}] |
| } |
| ]); |
| hasProps(pc1.getTransceivers(), |
| [ |
| { |
| receiver: {track: {kind: "audio"}}, |
| sender: {track}, |
| direction: "sendrecv", |
| mid: mid0, |
| currentDirection: "sendrecv", |
| stopped: false |
| } |
| ]); |
| |
| // Don't bother waiting for ICE and such |
| |
| // Check to see whether rolling back a remote track removal works |
| pc1.getTransceivers()[0].direction = "recvonly"; |
| offer = await pc1.createOffer(); |
| |
| trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); |
| hasProps(trackEvents, []); |
| |
| trackEvents = |
| await setRemoteDescriptionReturnTrackEvents(pc2, {type: "rollback"}); |
| hasProps(trackEvents, |
| [ |
| { |
| track: pc2.getTransceivers()[0].receiver.track, |
| streams: [{id: stream.id}] |
| } |
| ]); |
| |
| // Check to see that stop() cannot be rolled back |
| pc1.getTransceivers()[0].stop(); |
| offer = await pc1.createOffer(); |
| |
| await pc2.setRemoteDescription(offer); |
| hasProps(pc2.getTransceivers(), |
| [ |
| { |
| receiver: {track: {kind: "audio"}}, |
| sender: {track: track2}, |
| direction: "sendrecv", |
| mid: null, |
| currentDirection: null, |
| stopped: true |
| } |
| ]); |
| |
| // stop() cannot be rolled back! |
| await pc2.setRemoteDescription({type: "rollback"}); |
| hasProps(pc2.getTransceivers(), |
| [ |
| { |
| receiver: {track: {kind: "audio"}}, |
| sender: {track: {kind: "audio"}}, |
| direction: "sendrecv", |
| mid: null, |
| currentDirection: null, |
| stopped: true |
| } |
| ]); |
| }; |
| |
| const checkMsectionReuse = async t => { |
| // Use max-compat to make it easier to check for disabled m-sections |
| const pc1 = new RTCPeerConnection({ bundlePolicy: "max-compat" }); |
| const pc2 = new RTCPeerConnection({ bundlePolicy: "max-compat" }); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| |
| const stream = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream)); |
| const track = stream.getAudioTracks()[0]; |
| pc1.addTrack(track, stream); |
| |
| let offer = await pc1.createOffer(); |
| await pc1.setLocalDescription(offer); |
| await pc2.setRemoteDescription(offer); |
| |
| // answerer stops transceiver to reject m-section |
| const stoppedMid0 = pc2.getTransceivers()[0].mid; |
| pc2.getTransceivers()[0].stop(); |
| |
| let answer = await pc2.createAnswer(); |
| await pc2.setLocalDescription(answer); |
| await pc1.setRemoteDescription(answer); |
| |
| hasProps(pc1.getTransceivers(), |
| [ |
| { |
| mid: null, |
| currentDirection: null, |
| stopped: true |
| } |
| ]); |
| |
| hasProps(pc2.getTransceivers(), |
| [ |
| { |
| mid: null, |
| currentDirection: null, |
| stopped: true |
| } |
| ]); |
| |
| // Check that m-section is reused on both ends |
| const stream2 = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream2)); |
| const track2 = stream2.getAudioTracks()[0]; |
| |
| pc1.addTrack(track2, stream2); |
| offer = await pc1.createOffer(); |
| assert_equals(offer.sdp.match(/m=/g).length, 1, |
| "Exactly one m-line in offer, because it was reused"); |
| hasProps(pc1.getTransceivers(), |
| [ |
| { |
| stopped: true |
| }, |
| { |
| sender: {track: track2} |
| } |
| ]); |
| |
| assert_not_equals(pc1.getTransceivers()[1].mid, stoppedMid0); |
| |
| pc2.addTrack(track, stream); |
| offer = await pc2.createOffer(); |
| assert_equals(offer.sdp.match(/m=/g).length, 1, |
| "Exactly one m-line in offer, because it was reused"); |
| hasProps(pc2.getTransceivers(), |
| [ |
| { |
| stopped: true |
| }, |
| { |
| sender: {track} |
| } |
| ]); |
| |
| assert_not_equals(pc2.getTransceivers()[1].mid, stoppedMid0); |
| |
| await pc2.setLocalDescription(offer); |
| await pc1.setRemoteDescription(offer); |
| answer = await pc1.createAnswer(); |
| await pc1.setLocalDescription(answer); |
| await pc2.setRemoteDescription(answer); |
| hasPropsAndUniqueMids(pc1.getTransceivers(), |
| [ |
| { |
| mid: null |
| }, |
| { |
| sender: {track: track2}, |
| currentDirection: "sendrecv" |
| } |
| ]); |
| |
| const mid0 = pc1.getTransceivers()[1].mid; |
| |
| hasProps(pc2.getTransceivers(), |
| [ |
| { |
| mid: null |
| }, |
| { |
| sender: {track}, |
| currentDirection: "sendrecv", |
| mid: mid0 |
| } |
| ]); |
| |
| // stop the transceiver, and add a track. Verify that we don't reuse |
| // prematurely in our offer. (There should be one rejected m-section, and a |
| // new one for the new track) |
| const stoppedMid1 = pc1.getTransceivers()[1].mid; |
| pc1.getTransceivers()[1].stop(); |
| const stream3 = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream3)); |
| const track3 = stream3.getAudioTracks()[0]; |
| pc1.addTrack(track3, stream3); |
| offer = await pc1.createOffer(); |
| assert_equals(offer.sdp.match(/m=/g).length, 2, |
| "Exactly 2 m-lines in offer, because it is too early to reuse"); |
| assert_equals(offer.sdp.match(/m=audio 0 /g).length, 1, |
| "One m-line is rejected"); |
| |
| await pc1.setLocalDescription(offer); |
| |
| let trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); |
| hasProps(trackEvents, |
| [ |
| { |
| track: pc2.getTransceivers()[2].receiver.track, |
| streams: [{id: stream3.id}] |
| } |
| ]); |
| |
| answer = await pc2.createAnswer(); |
| await pc2.setLocalDescription(answer); |
| |
| trackEvents = await setRemoteDescriptionReturnTrackEvents(pc1, answer); |
| hasProps(trackEvents, []); |
| |
| hasPropsAndUniqueMids(pc2.getTransceivers(), |
| [ |
| { |
| mid: null |
| }, |
| { |
| mid: null, |
| stopped: true |
| }, |
| { |
| sender: {track: null}, |
| currentDirection: "recvonly" |
| } |
| ]); |
| |
| // Verify that we don't reuse the mid from the stopped transceiver |
| const mid1 = pc2.getTransceivers()[2].mid; |
| assert_not_equals(mid1, stoppedMid1); |
| |
| pc2.addTrack(track3, stream3); |
| // There are two ways to handle this new track; reuse the recvonly |
| // transceiver created above, or create a new transceiver and reuse the |
| // disabled m-section. We're supposed to do the former. |
| offer = await pc2.createOffer(); |
| assert_equals(offer.sdp.match(/m=/g).length, 2, "Exactly 2 m-lines in offer"); |
| assert_equals(offer.sdp.match(/m=audio 0 /g).length, 1, |
| "One m-line is rejected, because the other was used"); |
| |
| hasProps(pc2.getTransceivers(), |
| [ |
| {}, |
| { |
| stopped: true |
| }, |
| { |
| mid: mid1, |
| sender: {track: track3}, |
| currentDirection: "recvonly", |
| direction: "sendrecv" |
| } |
| ]); |
| |
| // Add _another_ track; this should reuse the disabled m-section |
| const stream4 = await getNoiseStream({audio: true}); |
| t.add_cleanup(() => stopTracks(stream4)); |
| const track4 = stream4.getAudioTracks()[0]; |
| pc2.addTrack(track4, stream4); |
| offer = await pc2.createOffer(); |
| await pc2.setLocalDescription(offer); |
| hasPropsAndUniqueMids(pc2.getTransceivers(), |
| [ |
| { |
| mid: null |
| }, |
| { |
| mid: null |
| }, |
| { |
| mid: mid1 |
| }, |
| { |
| sender: {track: track4}, |
| } |
| ]); |
| |
| // Fourth transceiver should have a new mid |
| assert_not_equals(pc2.getTransceivers()[3].mid, stoppedMid0); |
| assert_not_equals(pc2.getTransceivers()[3].mid, stoppedMid1); |
| |
| assert_equals(offer.sdp.match(/m=/g).length, 2, |
| "Exactly 2 m-lines in offer, because m-section was reused"); |
| assert_equals(offer.sdp.match(/m=audio 0 /g), null, |
| "No rejected m-line, because it was reused"); |
| }; |
| |
| const tests = [ |
| checkAddTransceiverNoTrack, |
| checkAddTransceiverWithTrack, |
| checkAddTransceiverWithAddTrack, |
| checkAddTransceiverWithDirection, |
| checkMsidNoTrackId, |
| checkAddTransceiverWithSetRemoteOfferSending, |
| checkAddTransceiverWithSetRemoteOfferNoSend, |
| checkAddTransceiverBadKind, |
| checkNoMidOffer, |
| checkNoMidAnswer, |
| checkSetDirection, |
| checkCurrentDirection, |
| checkSendrecvWithNoSendTrack, |
| checkSendrecvWithTracklessStream, |
| checkAddTransceiverNoTrackDoesntPair, |
| checkAddTransceiverWithTrackDoesntPair, |
| checkAddTransceiverThenReplaceTrackDoesntPair, |
| checkAddTransceiverThenAddTrackPairs, |
| checkAddTrackPairs, |
| checkReplaceTrackNullDoesntPreventPairing, |
| checkRemoveAndReadd, |
| checkAddTrackExistingTransceiverThenRemove, |
| checkRemoveTrackNegotiation, |
| checkMute, |
| checkStop, |
| checkStopAfterCreateOffer, |
| checkStopAfterSetLocalOffer, |
| checkStopAfterSetRemoteOffer, |
| checkStopAfterCreateAnswer, |
| checkStopAfterSetLocalAnswer, |
| checkStopAfterClose, |
| checkLocalRollback, |
| checkRollbackAndSetRemoteOfferWithDifferentType, |
| checkRemoteRollback, |
| checkMsectionReuse |
| ].forEach(test => promise_test(test, test.name)); |
| |
| </script> |