| <!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); |
| |
| assert_false(pc1.getTransceivers()[0].stopped, 'Transceiver is not stopped'); |
| |
| await pc2.setRemoteDescription(offer); |
| pc2.getTransceivers()[0].stop(); |
| const answer = await pc2.createAnswer(); |
| |
| await pc1.setRemoteDescription(answer); |
| |
| assert_true(pc1.getTransceivers()[0].stopped, 'Transceiver is stopped'); |
| 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'); |
| }, 'setRemoteDescription should stop the transceiver 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> |