| <!doctype html> | |
| <meta charset=utf-8> | |
| <title>RTCPeerConnection.prototype.removeTrack</title> | |
| <script src="/resources/testharness.js"></script> | |
| <script src="/resources/testharnessreport.js"></script> | |
| <script src="RTCPeerConnection-helper.js"></script> | |
| <script> | |
| 'use strict'; | |
| // Test is based on the following editor draft: | |
| // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html | |
| // The following helper functions are called from RTCPeerConnection-helper.js: | |
| // generateAnswer | |
| // generateMediaStreamTrack | |
| /* | |
| 5.1. RTCPeerConnection Interface Extensions | |
| partial interface RTCPeerConnection { | |
| ... | |
| void removeTrack(RTCRtpSender sender); | |
| RTCRtpTransceiver addTransceiver((MediaStreamTrack or DOMString) trackOrKind, | |
| optional RTCRtpTransceiverInit init); | |
| }; | |
| */ | |
| // Before calling removeTrack can be tested, one needs to add MediaStreamTracks to | |
| // a peer connection. There are two ways for adding MediaStreamTrack: addTrack and | |
| // addTransceiver. addTransceiver is a newer API while addTrack has been implemented | |
| // in current browsers for some time. As a result some of the removeTrack tests have | |
| // two versions so that removeTrack can be partially tested without addTransceiver | |
| // and the transceiver APIs being implemented. | |
| /* | |
| 5.1. removeTrack | |
| 3. If connection's [[isClosed]] slot is true, throw an InvalidStateError. | |
| */ | |
| test(t => { | |
| const pc = new RTCPeerConnection(); | |
| const track = generateMediaStreamTrack('audio'); | |
| const transceiver = pc.addTransceiver(track); | |
| const { sender } = transceiver; | |
| pc.close(); | |
| assert_throws('InvalidStateError', () => pc.removeTrack(sender)); | |
| }, 'addTransceiver - Calling removeTrack when connection is closed should throw InvalidStateError'); | |
| promise_test(t => { | |
| const pc = new RTCPeerConnection(); | |
| return navigator.mediaDevices.getUserMedia({ audio: true }) | |
| .then(mediaStream => { | |
| const tracks = mediaStream.getTracks(); | |
| assert_greater_than(tracks.length, 0, | |
| 'Expect getUserMedia to return at least one audio track'); | |
| const track = tracks[0]; | |
| const sender = pc.addTrack(track, mediaStream); | |
| pc.close(); | |
| assert_throws('InvalidStateError', () => pc.removeTrack(sender)); | |
| }); | |
| }, 'addTrack - Calling removeTrack when connection is closed should throw InvalidStateError'); | |
| test(t => { | |
| const pc = new RTCPeerConnection(); | |
| const track = generateMediaStreamTrack('audio'); | |
| const transceiver = pc.addTransceiver(track); | |
| const { sender } = transceiver; | |
| const pc2 = new RTCPeerConnection(); | |
| pc2.close(); | |
| assert_throws('InvalidStateError', () => pc2.removeTrack(sender)); | |
| }, 'addTransceiver - Calling removeTrack on different connection that is closed should throw InvalidStateError'); | |
| promise_test(t => { | |
| const pc = new RTCPeerConnection(); | |
| return navigator.mediaDevices.getUserMedia({ audio: true }) | |
| .then(mediaStream => { | |
| const tracks = mediaStream.getTracks(); | |
| assert_greater_than(tracks.length, 0, | |
| 'Expect getUserMedia to return at least one audio track'); | |
| const track = tracks[0]; | |
| const sender = pc.addTrack(track, mediaStream); | |
| const pc2 = new RTCPeerConnection(); | |
| pc2.close(); | |
| assert_throws('InvalidStateError', () => pc2.removeTrack(sender)); | |
| }); | |
| }, 'addTrack - Calling removeTrack on different connection that is closed should throw InvalidStateError'); | |
| /* | |
| 5.1. removeTrack | |
| 4. If sender was not created by connection, throw an InvalidAccessError. | |
| */ | |
| test(t => { | |
| const pc = new RTCPeerConnection(); | |
| const track = generateMediaStreamTrack('audio'); | |
| const transceiver = pc.addTransceiver(track); | |
| const { sender } = transceiver; | |
| const pc2 = new RTCPeerConnection(); | |
| assert_throws('InvalidAccessError', () => pc2.removeTrack(sender)); | |
| }, 'addTransceiver - Calling removeTrack on different connection should throw InvalidAccessError'); | |
| promise_test(t => { | |
| const pc = new RTCPeerConnection(); | |
| return navigator.mediaDevices.getUserMedia({ audio: true }) | |
| .then(mediaStream => { | |
| const tracks = mediaStream.getTracks(); | |
| assert_greater_than(tracks.length, 0, | |
| 'Expect getUserMedia to return at least one audio track'); | |
| const track = tracks[0]; | |
| const sender = pc.addTrack(track, mediaStream); | |
| const pc2 = new RTCPeerConnection(); | |
| assert_throws('InvalidAccessError', () => pc2.removeTrack(sender)); | |
| }); | |
| }, 'addTrack - Calling removeTrack on different connection should throw InvalidAccessError') | |
| /* | |
| 5.1. removeTrack | |
| 7. Set sender.track to null. | |
| */ | |
| test(t => { | |
| const pc = new RTCPeerConnection(); | |
| const track = generateMediaStreamTrack('audio'); | |
| const transceiver = pc.addTransceiver(track); | |
| const { sender } = transceiver; | |
| assert_equals(sender.track, track); | |
| assert_equals(transceiver.direction, 'sendrecv'); | |
| assert_equals(transceiver.currentDirection, null); | |
| pc.removeTrack(sender); | |
| assert_equals(sender.track, null); | |
| assert_equals(transceiver.direction, 'sendrecv', | |
| 'direction should not be altered'); | |
| }, 'addTransceiver - Calling removeTrack with valid sender should set sender.track to null'); | |
| promise_test(t => { | |
| const pc = new RTCPeerConnection(); | |
| return navigator.mediaDevices.getUserMedia({ audio: true }) | |
| .then(mediaStream => { | |
| const tracks = mediaStream.getTracks(); | |
| assert_greater_than(tracks.length, 0, | |
| 'Expect getUserMedia to return at least one audio track'); | |
| const track = tracks[0]; | |
| const sender = pc.addTrack(track, mediaStream); | |
| assert_equals(sender.track, track); | |
| pc.removeTrack(sender); | |
| assert_equals(sender.track, null); | |
| }); | |
| }, 'addTrack - Calling removeTrack with valid sender should set sender.track to null'); | |
| /* | |
| 5.1. removeTrack | |
| 7. Set sender.track to null. | |
| 10. If transceiver.currentDirection is sendrecv set transceiver.direction | |
| to recvonly. | |
| */ | |
| promise_test(t => { | |
| const pc = new RTCPeerConnection(); | |
| const track = generateMediaStreamTrack('audio'); | |
| const transceiver = pc.addTransceiver(track); | |
| const { sender } = transceiver; | |
| assert_equals(sender.track, track); | |
| assert_equals(transceiver.direction, 'sendrecv'); | |
| assert_equals(transceiver.currentDirection, null); | |
| return pc.createOffer() | |
| .then(offer => | |
| pc.setLocalDescription(offer) | |
| .then(() => generateAnswer(offer))) | |
| .then(answer => pc.setRemoteDescription(answer)) | |
| .then(() => { | |
| assert_equals(transceiver.currentDirection, 'sendrecv'); | |
| pc.removeTrack(sender); | |
| assert_equals(sender.track, null); | |
| assert_equals(transceiver.direction, 'recvonly'); | |
| assert_equals(transceiver.currentDirection, 'sendrecv', | |
| 'Expect currentDirection to not change'); | |
| }); | |
| }, 'Calling removeTrack with currentDirection sendrecv should set direction to recvonly'); | |
| /* | |
| 5.1. removeTrack | |
| 7. Set sender.track to null. | |
| 11. If transceiver.currentDirection is sendonly set transceiver.direction | |
| to inactive. | |
| */ | |
| promise_test(t => { | |
| const pc = new RTCPeerConnection(); | |
| const track = generateMediaStreamTrack('audio'); | |
| const transceiver = pc.addTransceiver(track, { direction: 'sendonly' }); | |
| const { sender } = transceiver; | |
| assert_equals(sender.track, track); | |
| assert_equals(transceiver.direction, 'sendonly'); | |
| assert_equals(transceiver.currentDirection, null); | |
| return pc.createOffer() | |
| .then(offer => | |
| pc.setLocalDescription(offer) | |
| .then(() => generateAnswer(offer))) | |
| .then(answer => pc.setRemoteDescription(answer)) | |
| .then(() => { | |
| assert_equals(transceiver.currentDirection, 'sendonly'); | |
| pc.removeTrack(sender); | |
| assert_equals(sender.track, null); | |
| assert_equals(transceiver.direction, 'inactive'); | |
| assert_equals(transceiver.currentDirection, 'sendonly', | |
| 'Expect currentDirection to not change'); | |
| }); | |
| }, 'Calling removeTrack with currentDirection sendonly should set direction to inactive'); | |
| /* | |
| 5.1. removeTrack | |
| 7. Set sender.track to null. | |
| 9. If transceiver.currentDirection is recvonly or inactive, | |
| then abort these steps. | |
| */ | |
| promise_test(t => { | |
| const pc = new RTCPeerConnection(); | |
| const track = generateMediaStreamTrack('audio'); | |
| const transceiver = pc.addTransceiver(track, { direction: 'recvonly' }); | |
| const { sender } = transceiver; | |
| assert_equals(sender.track, track); | |
| assert_equals(transceiver.direction, 'recvonly'); | |
| assert_equals(transceiver.currentDirection, null); | |
| return pc.createOffer() | |
| .then(offer => | |
| pc.setLocalDescription(offer) | |
| .then(() => generateAnswer(offer))) | |
| .then(answer => pc.setRemoteDescription(answer)) | |
| .then(() => { | |
| assert_equals(transceiver.currentDirection, 'recvonly'); | |
| pc.removeTrack(sender); | |
| assert_equals(sender.track, null); | |
| assert_equals(transceiver.direction, 'recvonly'); | |
| assert_equals(transceiver.currentDirection, 'recvonly'); | |
| }); | |
| }, 'Calling removeTrack with currentDirection recvonly should not change direction'); | |
| /* | |
| 5.1. removeTrack | |
| 7. Set sender.track to null. | |
| 9. If transceiver.currentDirection is recvonly or inactive, | |
| then abort these steps. | |
| */ | |
| promise_test(t => { | |
| const pc = new RTCPeerConnection(); | |
| const track = generateMediaStreamTrack('audio'); | |
| const transceiver = pc.addTransceiver(track, { direction: 'inactive' }); | |
| const { sender } = transceiver; | |
| assert_equals(sender.track, track); | |
| assert_equals(transceiver.direction, 'inactive'); | |
| assert_equals(transceiver.currentDirection, null); | |
| return pc.createOffer() | |
| .then(offer => | |
| pc.setLocalDescription(offer) | |
| .then(() => generateAnswer(offer))) | |
| .then(answer => pc.setRemoteDescription(answer)) | |
| .then(() => { | |
| assert_equals(transceiver.currentDirection, 'inactive'); | |
| pc.removeTrack(sender); | |
| assert_equals(sender.track, null); | |
| assert_equals(transceiver.direction, 'inactive'); | |
| assert_equals(transceiver.currentDirection, 'inactive'); | |
| }); | |
| }, 'Calling removeTrack with currentDirection inactive should not change direction'); | |
| /* | |
| TODO | |
| 5.1. removeTrack | |
| Stops sending media from sender. The RTCRtpSender will still appear | |
| in getSenders. Doing so will cause future calls to createOffer to | |
| mark the media description for the corresponding transceiver as | |
| recvonly or inactive, as defined in [JSEP] (section 5.2.2.). | |
| When the other peer stops sending a track in this manner, an ended | |
| event is fired at the MediaStreamTrack object. | |
| 6. If sender is not in senders (which indicates that it was removed | |
| due to setting an RTCSessionDescription of type "rollback"), | |
| then abort these steps. | |
| 12. Update the negotiation-needed flag for connection. | |
| */ | |
| </script> |