| <!doctype html> | |
| <meta charset=utf-8> | |
| <title>RTCPeerConnection Set Session Description - Transceiver Tests</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 | |
| /* | |
| 4.3.2. Interface Definition | |
| [Constructor(optional RTCConfiguration configuration)] | |
| interface RTCPeerConnection : EventTarget { | |
| Promise<void> setLocalDescription( | |
| RTCSessionDescriptionInit description); | |
| Promise<void> setRemoteDescription( | |
| RTCSessionDescriptionInit description); | |
| ... | |
| }; | |
| 4.6.2. RTCSessionDescription Class | |
| dictionary RTCSessionDescriptionInit { | |
| required RTCSdpType type; | |
| DOMString sdp = ""; | |
| }; | |
| 4.6.1. RTCSdpType | |
| enum RTCSdpType { | |
| "offer", | |
| "pranswer", | |
| "answer", | |
| "rollback" | |
| }; | |
| 5.4. RTCRtpTransceiver Interface | |
| interface RTCRtpTransceiver { | |
| readonly attribute DOMString? mid; | |
| [SameObject] | |
| readonly attribute RTCRtpSender sender; | |
| [SameObject] | |
| readonly attribute RTCRtpReceiver receiver; | |
| readonly attribute RTCRtpTransceiverDirection direction; | |
| readonly attribute RTCRtpTransceiverDirection? currentDirection; | |
| ... | |
| }; | |
| */ | |
| /* | |
| 4.3.1.6. Set the RTCSessionSessionDescription | |
| 7. If description is set as a local description, then run the following steps for | |
| each media description in description that is not yet associated with an | |
| RTCRtpTransceiver object: | |
| 1. Let transceiver be the RTCRtpTransceiver used to create the media | |
| description. | |
| 2. Set transceiver's mid value to the mid of the corresponding media | |
| description. | |
| */ | |
| promise_test(t => { | |
| const pc = new RTCPeerConnection(); | |
| t.add_cleanup(() => pc.close()); | |
| const transceiver = pc.addTransceiver('audio'); | |
| assert_equals(transceiver.mid, null); | |
| return pc.createOffer() | |
| .then(offer => { | |
| assert_equals(transceiver.mid, null, | |
| 'Expect transceiver.mid to still be null after createOffer'); | |
| return pc.setLocalDescription(offer) | |
| .then(() => { | |
| assert_equals(typeof transceiver.mid, 'string', | |
| 'Expect transceiver.mid to set to valid string value'); | |
| assert_equals(offer.sdp.includes(`\r\na=mid:${transceiver.mid}`), true, | |
| 'Expect transceiver mid to be found in offer SDP'); | |
| }); | |
| }); | |
| }, 'setLocalDescription(offer) with m= section should assign mid to corresponding transceiver'); | |
| /* | |
| 4.3.1.6. Set the RTCSessionSessionDescription | |
| 8. If description is set as a remote description, then run the following steps | |
| for each media description in description: | |
| 2. If no suitable transceiver is found (transceiver is unset), run the following | |
| steps: | |
| 1. Create an RTCRtpSender, sender, from the media description. | |
| 2. Create an RTCRtpReceiver, receiver, from the media description. | |
| 3. Create an RTCRtpTransceiver with sender, receiver and direction, and let | |
| transceiver be the result. | |
| 3. Set transceiver's mid value to the mid of the corresponding media description. | |
| */ | |
| promise_test(t => { | |
| const pc1 = new RTCPeerConnection(); | |
| t.add_cleanup(() => pc1.close()); | |
| const pc2 = new RTCPeerConnection(); | |
| t.add_cleanup(() => pc2.close()); | |
| const transceiver1 = pc1.addTransceiver('audio'); | |
| assert_array_equals(pc1.getTransceivers(), [transceiver1]); | |
| assert_array_equals(pc2.getTransceivers(), []); | |
| return pc1.createOffer() | |
| .then(offer => { | |
| return Promise.all([ | |
| pc1.setLocalDescription(offer), | |
| pc2.setRemoteDescription(offer) | |
| ]) | |
| .then(() => { | |
| const transceivers = pc2.getTransceivers(); | |
| assert_equals(transceivers.length, 1, | |
| 'Expect new transceiver added to pc2 after setRemoteDescription'); | |
| const [ transceiver2 ] = transceivers; | |
| assert_equals(typeof transceiver2.mid, 'string', | |
| 'Expect transceiver2.mid to be set'); | |
| assert_equals(transceiver1.mid, transceiver2.mid, | |
| 'Expect transceivers of both side to have the same mid'); | |
| assert_equals(offer.sdp.includes(`\r\na=mid:${transceiver2.mid}`), true, | |
| 'Expect transceiver mid to be found in offer SDP'); | |
| }); | |
| }); | |
| }, 'setRemoteDescription(offer) with m= section and no existing transceiver should create corresponding transceiver'); | |
| /* | |
| 4.3.1.6. Set the RTCSessionSessionDescription | |
| 9. If description is of type "rollback", then run the following steps: | |
| 1. If the mid value of an RTCRtpTransceiver was set to a non-null value by | |
| the RTCSessionDescription that is being rolled back, set the mid value | |
| of that transceiver to null, as described by [JSEP] (section 4.1.8.2.). | |
| */ | |
| promise_test(t => { | |
| const pc = new RTCPeerConnection(); | |
| t.add_cleanup(() => pc.close()); | |
| const transceiver = pc.addTransceiver('audio'); | |
| assert_equals(transceiver.mid, null); | |
| return pc.createOffer() | |
| .then(offer => { | |
| assert_equals(transceiver.mid, null); | |
| return pc.setLocalDescription(offer); | |
| }) | |
| .then(() => { | |
| assert_not_equals(transceiver.mid, null); | |
| return pc.setLocalDescription({ type: 'rollback' }); | |
| }) | |
| .then(() => { | |
| assert_equals(transceiver.mid, null, | |
| 'Expect transceiver.mid to become null again after rollback'); | |
| }); | |
| }, 'setLocalDescription(rollback) should unset transceiver.mid'); | |
| promise_test(t => { | |
| const pc = new RTCPeerConnection(); | |
| t.add_cleanup(() => pc.close()); | |
| const transceiver1 = pc.addTransceiver('audio'); | |
| assert_equals(transceiver1.mid, null); | |
| return pc.createOffer() | |
| .then(offer => | |
| pc.setLocalDescription(offer) | |
| .then(() => generateAnswer(offer))) | |
| .then(answer => pc.setRemoteDescription(answer)) | |
| .then(() => { | |
| // pc is back to stable state | |
| // create another transceiver | |
| const transceiver2 = pc.addTransceiver('video'); | |
| assert_not_equals(transceiver1.mid, null); | |
| assert_equals(transceiver2.mid, null); | |
| return pc.createOffer() | |
| .then(offer => pc.setLocalDescription(offer)) | |
| .then(() => { | |
| assert_not_equals(transceiver1.mid, null); | |
| assert_not_equals(transceiver2.mid, null, | |
| 'Expect transceiver2.mid to become set'); | |
| return pc.setLocalDescription({ type: 'rollback' }); | |
| }) | |
| .then(() => { | |
| assert_not_equals(transceiver1.mid, null, | |
| 'Expect transceiver1.mid to stay set'); | |
| assert_equals(transceiver2.mid, null, | |
| 'Expect transceiver2.mid to be rolled back to null'); | |
| }); | |
| }) | |
| }, 'setLocalDescription(rollback) should only unset transceiver mids associated with current round'); | |
| /* | |
| 4.3.1.6. Set the RTCSessionSessionDescription | |
| 9. If description is of type "rollback", then run the following steps: | |
| 2. If an RTCRtpTransceiver was created by applying the RTCSessionDescription | |
| that is being rolled back, and a track has not been attached to it via | |
| addTrack, remove that transceiver from connection's set of transceivers, | |
| as described by [JSEP] (section 4.1.8.2.). | |
| */ | |
| promise_test(t => { | |
| const pc1 = new RTCPeerConnection(); | |
| t.add_cleanup(() => pc1.close()); | |
| const pc2 = new RTCPeerConnection(); | |
| t.add_cleanup(() => pc2.close()); | |
| pc1.addTransceiver('audio'); | |
| return pc1.createOffer() | |
| .then(offer => pc2.setRemoteDescription(offer)) | |
| .then(() => { | |
| const transceivers = pc2.getTransceivers(); | |
| assert_equals(transceivers.length, 1); | |
| const [ transceiver ] = transceivers; | |
| assert_equals(typeof transceiver.mid, 'string', | |
| 'Expect transceiver.mid to be set'); | |
| return pc2.setRemoteDescription({ type: 'rollback' }) | |
| .then(() => { | |
| assert_equals(transceiver.mid, null, | |
| 'Expect transceiver.mid to be unset'); | |
| assert_array_equals(pc2.getTransceivers(), [], | |
| `Expect transceiver to be removed from pc2's transceiver list`); | |
| }); | |
| }); | |
| }, 'setRemoteDescription(rollback) should remove newly created transceiver from transceiver list'); | |
| promise_test(async t => { | |
| const pc1 = new RTCPeerConnection(); | |
| t.add_cleanup(() => pc1.close()); | |
| const pc2 = new RTCPeerConnection(); | |
| t.add_cleanup(() => pc2.close()); | |
| pc1.addTransceiver('audio'); | |
| const offer = await pc1.createOffer(); | |
| await pc1.setLocalDescription(offer); | |
| await pc2.setRemoteDescription(offer); | |
| pc2.getTransceivers()[0].stop(); | |
| const answer = await pc2.createAnswer(); | |
| await pc1.setRemoteDescription(answer); | |
| assert_equals(pc1.getTransceivers()[0].currentDirection, 'inactive', 'A stopped m-line should give an inactive transceiver'); | |
| }, 'setRemoteDescription should set transceiver inactive if its corresponding m section is rejected'); | |
| /* | |
| TODO | |
| - Steps for transceiver direction is added to tip of tree draft, but not yet | |
| published as editor's draft | |
| 4.3.1.6. Set the RTCSessionSessionDescription | |
| 8. If description is set as a remote description, then run the following steps | |
| for each media description in description: | |
| 1. As described by [JSEP] (section 5.9.), attempt to find an existing | |
| RTCRtpTransceiver object, transceiver, to represent the media description. | |
| 3. If the media description has no MID, and transceiver's mid is unset, generate | |
| a random value as described in [JSEP] (section 5.9.). | |
| 4. If the direction of the media description is sendrecv or sendonly, and | |
| transceiver.receiver.track has not yet been fired in a track event, process | |
| the remote track for the media description, given transceiver. | |
| 5. If the media description is rejected, and transceiver is not already stopped, | |
| stop the RTCRtpTransceiver transceiver. | |
| [JSEP] | |
| 5.9. Applying a Remote Description | |
| - If the m= section is not associated with any RtpTransceiver | |
| (possibly because it was dissociated in the previous step), | |
| either find an RtpTransceiver or create one according to the | |
| following steps: | |
| - If the m= section is sendrecv or recvonly, and there are | |
| RtpTransceivers of the same type that were added to the | |
| PeerConnection by addTrack and are not associated with any | |
| m= section and are not stopped, find the first (according to | |
| the canonical order described in Section 5.2.1) such | |
| RtpTransceiver. | |
| - If no RtpTransceiver was found in the previous step, create | |
| one with a recvonly direction. | |
| */ | |
| </script> |