RTCDTMFSender-ontonechange.https.html (9928B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <title>RTCDTMFSender.prototype.ontonechange</title> 4 <meta name="timeout" content="long"> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 <script src="RTCPeerConnection-helper.js"></script> 8 <script src="RTCDTMFSender-helper.js"></script> 9 <script> 10 'use strict'; 11 12 // Test is based on the following editor draft: 13 // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html 14 15 // The following helper functions are called from RTCPeerConnection-helper.js 16 // generateAnswer 17 18 // The following helper functions are called from RTCDTMFSender-helper.js 19 // test_tone_change_events 20 // getTransceiver 21 22 /* 23 7. Peer-to-peer DTMF 24 partial interface RTCRtpSender { 25 readonly attribute RTCDTMFSender? dtmf; 26 }; 27 28 interface RTCDTMFSender : EventTarget { 29 void insertDTMF(DOMString tones, 30 optional unsigned long duration = 100, 31 optional unsigned long interToneGap = 70); 32 attribute EventHandler ontonechange; 33 readonly attribute DOMString toneBuffer; 34 }; 35 36 [Constructor(DOMString type, RTCDTMFToneChangeEventInit eventInitDict)] 37 interface RTCDTMFToneChangeEvent : Event { 38 readonly attribute DOMString tone; 39 }; 40 */ 41 42 /* 43 7.2. insertDTMF 44 11. If a Playout task is scheduled to be run; abort these steps; otherwise queue 45 a task that runs the following steps (Playout task): 46 3. If toneBuffer is an empty string, fire an event named tonechange with an 47 empty string at the RTCDTMFSender object and abort these steps. 48 4. Remove the first character from toneBuffer and let that character be tone. 49 6. Queue a task to be executed in duration + interToneGap ms from now that 50 runs the steps labelled Playout task. 51 7. Fire an event named tonechange with a string consisting of tone at the 52 RTCDTMFSender object. 53 */ 54 test_tone_change_events((t, dtmfSender) => { 55 dtmfSender.insertDTMF('123'); 56 }, [ 57 ['1', '23', 0], 58 ['2', '3', 170], 59 ['3', '', 170], 60 ['', '', 170] 61 ], 'insertDTMF() with default duration and intertoneGap should fire tonechange events at the expected time'); 62 63 test_tone_change_events((t, dtmfSender) => { 64 dtmfSender.insertDTMF('abc', 100, 70); 65 }, [ 66 ['A', 'BC', 0], 67 ['B', 'C', 170], 68 ['C', '', 170], 69 ['', '', 170] 70 ], 'insertDTMF() with explicit duration and intertoneGap should fire tonechange events at the expected time'); 71 72 /* 73 7.2. insertDTMF 74 10. If toneBuffer is an empty string, abort these steps. 75 */ 76 async_test(t => { 77 createDtmfSender() 78 .then(dtmfSender => { 79 dtmfSender.addEventListener('tonechange', 80 t.unreached_func('Expect no tonechange event to be fired')); 81 82 dtmfSender.insertDTMF('', 100, 70); 83 84 t.step_timeout(t.step_func_done(), 300); 85 }) 86 .catch(t.step_func(err => { 87 assert_unreached(`Unexpected promise rejection: ${err}`); 88 })); 89 }, `insertDTMF('') should not fire any tonechange event, including for '' tone`); 90 91 /* 92 7.2. insertDTMF 93 8. If the value of the duration parameter is less than 40, set it to 40. 94 If, on the other hand, the value is greater than 6000, set it to 6000. 95 */ 96 test_tone_change_events((t, dtmfSender) => { 97 dtmfSender.insertDTMF('ABC', 10, 70); 98 }, [ 99 ['A', 'BC', 0], 100 ['B', 'C', 110], 101 ['C', '', 110], 102 ['', '', 110] 103 ], 'insertDTMF() with duration less than 40 should be clamped to 40'); 104 105 /* 106 7.2. insertDTMF 107 9. If the value of the interToneGap parameter is less than 30, set it to 30. 108 */ 109 test_tone_change_events((t, dtmfSender) => { 110 dtmfSender.insertDTMF('ABC', 100, 10); 111 }, [ 112 ['A', 'BC', 0], 113 ['B', 'C', 130], 114 ['C', '', 130], 115 ['', '', 130] 116 ], 117 'insertDTMF() with interToneGap less than 30 should be clamped to 30'); 118 119 /* 120 [w3c/webrtc-pc#1373] 121 This step is added to handle the "," character correctly. "," supposed to delay the next 122 tonechange event by 2000ms. 123 124 7.2. insertDTMF 125 11.5. If tone is "," delay sending tones for 2000 ms on the associated RTP media 126 stream, and queue a task to be executed in 2000 ms from now that runs the 127 steps labelled Playout task. 128 */ 129 test_tone_change_events((t, dtmfSender) => { 130 dtmfSender.insertDTMF('A,B', 100, 70); 131 132 }, [ 133 ['A', ',B', 0], 134 [',', 'B', 170], 135 ['B', '', 2000], 136 ['', '', 170] 137 ], 'insertDTMF with comma should delay next tonechange event for a constant 2000ms'); 138 139 /* 140 7.2. insertDTMF 141 11.1. If transceiver.stopped is true, abort these steps. 142 */ 143 test_tone_change_events((t, dtmfSender, pc) => { 144 const transceiver = getTransceiver(pc); 145 dtmfSender.addEventListener('tonechange', ev => { 146 if(ev.tone === 'B') { 147 transceiver.stop(); 148 } 149 }); 150 151 dtmfSender.insertDTMF('ABC', 100, 70); 152 }, [ 153 ['A', 'BC', 0], 154 ['B', 'C', 170] 155 ], 'insertDTMF() with transceiver stopped in the middle should stop future tonechange events from firing'); 156 157 /* 158 7.2. insertDTMF 159 3. If a Playout task is scheduled to be run, abort these steps; 160 otherwise queue a task that runs the following steps (Playout task): 161 */ 162 test_tone_change_events((t, dtmfSender) => { 163 dtmfSender.addEventListener('tonechange', ev => { 164 if(ev.tone === 'B') { 165 dtmfSender.insertDTMF('12', 100, 70); 166 } 167 }); 168 169 dtmfSender.insertDTMF('ABC', 100, 70); 170 }, [ 171 ['A', 'BC', 0], 172 ['B', 'C', 170], 173 ['1', '2', 170], 174 ['2', '', 170], 175 ['', '', 170] 176 ], 'Calling insertDTMF() in the middle of tonechange events should cause future tonechanges to be updated to new tones'); 177 178 179 /* 180 7.2. insertDTMF 181 3. If a Playout task is scheduled to be run, abort these steps; 182 otherwise queue a task that runs the following steps (Playout task): 183 */ 184 test_tone_change_events((t, dtmfSender) => { 185 dtmfSender.addEventListener('tonechange', ev => { 186 if(ev.tone === 'B') { 187 dtmfSender.insertDTMF('12', 100, 70); 188 dtmfSender.insertDTMF('34', 100, 70); 189 } 190 }); 191 192 dtmfSender.insertDTMF('ABC', 100, 70); 193 }, [ 194 ['A', 'BC', 0], 195 ['B', 'C', 170], 196 ['3', '4', 170], 197 ['4', '', 170], 198 ['', '', 170] 199 ], 'Calling insertDTMF() multiple times in the middle of tonechange events should cause future tonechanges to be updated the last provided tones'); 200 201 /* 202 7.2. insertDTMF 203 3. If a Playout task is scheduled to be run, abort these steps; 204 otherwise queue a task that runs the following steps (Playout task): 205 */ 206 test_tone_change_events((t, dtmfSender) => { 207 dtmfSender.addEventListener('tonechange', ev => { 208 if(ev.tone === 'B') { 209 dtmfSender.insertDTMF(''); 210 } 211 }); 212 213 dtmfSender.insertDTMF('ABC', 100, 70); 214 }, [ 215 ['A', 'BC', 0], 216 ['B', 'C', 170], 217 ['', '', 170] 218 ], `Calling insertDTMF('') in the middle of tonechange events should stop future tonechange events from firing`); 219 220 /* 221 7.2. insertDTMF 222 11.2. If transceiver.currentDirection is recvonly or inactive, abort these steps. 223 */ 224 promise_test(async t => { 225 const pc = new RTCPeerConnection(); 226 t.add_cleanup(() => pc.close()); 227 const dtmfSender = await createDtmfSender(pc); 228 const pc2 = pc.otherPc; 229 assert_true(pc2 instanceof RTCPeerConnection, 230 'Expect pc2 to be a RTCPeerConnection'); 231 t.add_cleanup(() => pc2.close()); 232 const transceiver = pc.getTransceivers()[0]; 233 assert_equals(transceiver.sender.dtmf, dtmfSender); 234 235 // Since setRemoteDescription happens in parallel with tonechange event, 236 // We use a flag and allow tonechange events to be fired as long as 237 // the promise returned by setRemoteDescription is not yet resolved. 238 let remoteDescriptionIsSet = false; 239 240 // We only do basic tone verification and not check timing here 241 let expectedTones = ['A', 'B', 'C', 'D', '']; 242 243 const firstTone = new Promise(resolve => { 244 const onToneChange = t.step_func(ev => { 245 assert_false(remoteDescriptionIsSet, 246 'Expect no tonechange event to be fired after currentDirection is changed to recvonly'); 247 248 const { tone } = ev; 249 const expectedTone = expectedTones.shift(); 250 assert_equals(tone, expectedTone, 251 `Expect fired event.tone to be ${expectedTone}`); 252 253 if(tone === 'A') { 254 resolve(); 255 } 256 }); 257 dtmfSender.addEventListener('tonechange', onToneChange); 258 }); 259 260 dtmfSender.insertDTMF('ABCD', 100, 70); 261 await firstTone; 262 263 // Only change transceiver.direction after the first 264 // tonechange event, to make sure that tonechange is triggered 265 // then stopped 266 transceiver.direction = 'recvonly'; 267 await exchangeOfferAnswer(pc, pc2); 268 assert_equals(transceiver.currentDirection, 'inactive'); 269 remoteDescriptionIsSet = true; 270 271 await new Promise(resolve => t.step_timeout(resolve, 300)); 272 }, `Setting transceiver.currentDirection to recvonly in the middle of tonechange events should stop future tonechange events from firing`); 273 274 /* Section 7.3 - Tone change event */ 275 test(t => { 276 let ev = new RTCDTMFToneChangeEvent('tonechange', {'tone': '1'}); 277 assert_equals(ev.type, 'tonechange'); 278 assert_equals(ev.tone, '1'); 279 }, 'Tone change event constructor works'); 280 281 test(t => { 282 let ev = new RTCDTMFToneChangeEvent('worngname', {}); 283 }, 'Tone change event with unexpected name should not crash'); 284 285 test(t => { 286 const ev1 = new RTCDTMFToneChangeEvent('tonechange', {}); 287 assert_equals(ev1.tone, ''); 288 289 assert_equals(RTCDTMFToneChangeEvent.constructor.length, 1); 290 const ev2 = new RTCDTMFToneChangeEvent('tonechange'); 291 assert_equals(ev2.tone, ''); 292 }, 'Tone change event init optional parameters'); 293 294 </script>