| <!doctype html> |
| <meta charset=utf-8> |
| <title>RTCDTMFSender.prototype.ontonechange</title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="RTCPeerConnection-helper.js"></script> |
| <script src="RTCDTMFSender-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 |
| |
| // The following helper functions are called from RTCDTMFSender-helper.js |
| // test_tone_change_events |
| // getTransceiver |
| |
| /* |
| 7. Peer-to-peer DTMF |
| partial interface RTCRtpSender { |
| readonly attribute RTCDTMFSender? dtmf; |
| }; |
| |
| interface RTCDTMFSender : EventTarget { |
| void insertDTMF(DOMString tones, |
| optional unsigned long duration = 100, |
| optional unsigned long interToneGap = 70); |
| attribute EventHandler ontonechange; |
| readonly attribute DOMString toneBuffer; |
| }; |
| |
| [Constructor(DOMString type, RTCDTMFToneChangeEventInit eventInitDict)] |
| interface RTCDTMFToneChangeEvent : Event { |
| readonly attribute DOMString tone; |
| }; |
| */ |
| |
| /* |
| 7.2. insertDTMF |
| 11. If a Playout task is scheduled to be run; abort these steps; otherwise queue |
| a task that runs the following steps (Playout task): |
| 3. If toneBuffer is an empty string, fire an event named tonechange with an |
| empty string at the RTCDTMFSender object and abort these steps. |
| 4. Remove the first character from toneBuffer and let that character be tone. |
| 6. Queue a task to be executed in duration + interToneGap ms from now that |
| runs the steps labelled Playout task. |
| 7. Fire an event named tonechange with a string consisting of tone at the |
| RTCDTMFSender object. |
| */ |
| test_tone_change_events((t, dtmfSender) => { |
| dtmfSender.insertDTMF('123'); |
| }, [ |
| ['1', '23', 0], |
| ['2', '3', 170], |
| ['3', '', 170], |
| ['', '', 170] |
| ], 'insertDTMF() with default duration and intertoneGap should fire tonechange events at the expected time'); |
| |
| test_tone_change_events((t, dtmfSender) => { |
| dtmfSender.insertDTMF('abc', 100, 70); |
| }, [ |
| ['A', 'BC', 0], |
| ['B', 'C', 170], |
| ['C', '', 170], |
| ['', '', 170] |
| ], 'insertDTMF() with explicit duration and intertoneGap should fire tonechange events at the expected time'); |
| |
| /* |
| 7.2. insertDTMF |
| 10. If toneBuffer is an empty string, abort these steps. |
| */ |
| async_test(t => { |
| createDtmfSender() |
| .then(dtmfSender => { |
| dtmfSender.addEventListener('tonechange', |
| t.unreached_func('Expect no tonechange event to be fired')); |
| |
| dtmfSender.insertDTMF('', 100, 70); |
| |
| t.step_timeout(t.step_func_done(), 300); |
| }) |
| .catch(t.step_func(err => { |
| assert_unreached(`Unexpected promise rejection: ${err}`); |
| })); |
| }, `insertDTMF('') should not fire any tonechange event, including for '' tone`); |
| |
| /* |
| 7.2. insertDTMF |
| 8. If the value of the duration parameter is less than 40, set it to 40. |
| If, on the other hand, the value is greater than 6000, set it to 6000. |
| */ |
| test_tone_change_events((t, dtmfSender) => { |
| dtmfSender.insertDTMF('ABC', 10, 70); |
| }, [ |
| ['A', 'BC', 0], |
| ['B', 'C', 110], |
| ['C', '', 110], |
| ['', '', 110] |
| ], 'insertDTMF() with duration less than 40 should be clamped to 40'); |
| |
| /* |
| 7.2. insertDTMF |
| 9. If the value of the interToneGap parameter is less than 30, set it to 30. |
| */ |
| test_tone_change_events((t, dtmfSender) => { |
| dtmfSender.insertDTMF('ABC', 100, 10); |
| }, [ |
| ['A', 'BC', 0], |
| ['B', 'C', 130], |
| ['C', '', 130], |
| ['', '', 130] |
| ], |
| 'insertDTMF() with interToneGap less than 30 should be clamped to 30'); |
| |
| /* |
| [w3c/webrtc-pc#1373] |
| This step is added to handle the "," character correctly. "," supposed to delay the next |
| tonechange event by 2000ms. |
| |
| 7.2. insertDTMF |
| 11.5. If tone is "," delay sending tones for 2000 ms on the associated RTP media |
| stream, and queue a task to be executed in 2000 ms from now that runs the |
| steps labelled Playout task. |
| */ |
| test_tone_change_events((t, dtmfSender) => { |
| dtmfSender.insertDTMF('A,B', 100, 70); |
| |
| }, [ |
| ['A', ',B', 0], |
| [',', 'B', 170], |
| ['B', '', 2000], |
| ['', '', 170] |
| ], 'insertDTMF with comma should delay next tonechange event for a constant 2000ms'); |
| |
| /* |
| 7.2. insertDTMF |
| 11.1. If transceiver.stopped is true, abort these steps. |
| */ |
| test_tone_change_events((t, dtmfSender, pc) => { |
| const transceiver = getTransceiver(pc); |
| dtmfSender.addEventListener('tonechange', ev => { |
| if(ev.tone === 'B') { |
| transceiver.stop(); |
| } |
| }); |
| |
| dtmfSender.insertDTMF('ABC', 100, 70); |
| }, [ |
| ['A', 'BC', 0], |
| ['B', 'C', 170] |
| ], 'insertDTMF() with transceiver stopped in the middle should stop future tonechange events from firing'); |
| |
| /* |
| 7.2. insertDTMF |
| 3. If a Playout task is scheduled to be run, abort these steps; |
| otherwise queue a task that runs the following steps (Playout task): |
| */ |
| test_tone_change_events((t, dtmfSender) => { |
| dtmfSender.addEventListener('tonechange', ev => { |
| if(ev.tone === 'B') { |
| dtmfSender.insertDTMF('12', 100, 70); |
| } |
| }); |
| |
| dtmfSender.insertDTMF('ABC', 100, 70); |
| }, [ |
| ['A', 'BC', 0], |
| ['B', 'C', 170], |
| ['1', '2', 170], |
| ['2', '', 170], |
| ['', '', 170] |
| ], 'Calling insertDTMF() in the middle of tonechange events should cause future tonechanges to be updated to new tones'); |
| |
| |
| /* |
| 7.2. insertDTMF |
| 3. If a Playout task is scheduled to be run, abort these steps; |
| otherwise queue a task that runs the following steps (Playout task): |
| */ |
| test_tone_change_events((t, dtmfSender) => { |
| dtmfSender.addEventListener('tonechange', ev => { |
| if(ev.tone === 'B') { |
| dtmfSender.insertDTMF('12', 100, 70); |
| dtmfSender.insertDTMF('34', 100, 70); |
| } |
| }); |
| |
| dtmfSender.insertDTMF('ABC', 100, 70); |
| }, [ |
| ['A', 'BC', 0], |
| ['B', 'C', 170], |
| ['3', '4', 170], |
| ['4', '', 170], |
| ['', '', 170] |
| ], 'Calling insertDTMF() multiple times in the middle of tonechange events should cause future tonechanges to be updated the last provided tones'); |
| |
| /* |
| 7.2. insertDTMF |
| 3. If a Playout task is scheduled to be run, abort these steps; |
| otherwise queue a task that runs the following steps (Playout task): |
| */ |
| test_tone_change_events((t, dtmfSender) => { |
| dtmfSender.addEventListener('tonechange', ev => { |
| if(ev.tone === 'B') { |
| dtmfSender.insertDTMF(''); |
| } |
| }); |
| |
| dtmfSender.insertDTMF('ABC', 100, 70); |
| }, [ |
| ['A', 'BC', 0], |
| ['B', 'C', 170], |
| ['', '', 170] |
| ], `Calling insertDTMF('') in the middle of tonechange events should stop future tonechange events from firing`); |
| |
| /* |
| 7.2. insertDTMF |
| 11.2. If transceiver.currentDirection is recvonly or inactive, abort these steps. |
| */ |
| async_test(t => { |
| const pc = new RTCPeerConnection(); |
| t.add_cleanup(() => pc.close()); |
| const transceiver = pc.addTransceiver('audio', { direction: 'sendrecv' }); |
| const dtmfSender = transceiver.sender.dtmf; |
| |
| // Since setRemoteDescription happens in parallel with tonechange event, |
| // We use a flag and allow tonechange events to be fired as long as |
| // the promise returned by setRemoteDescription is not yet resolved. |
| let remoteDescriptionIsSet = false; |
| |
| // We only do basic tone verification and not check timing here |
| let expectedTones = ['A', 'B', 'C', 'D', '']; |
| |
| const onToneChange = t.step_func(ev => { |
| assert_false(remoteDescriptionIsSet, |
| 'Expect no tonechange event to be fired after currentDirection is changed to recvonly'); |
| |
| const { tone } = ev; |
| const expectedTone = expectedTones.shift(); |
| assert_equals(tone, expectedTone, |
| `Expect fired event.tone to be ${expectedTone}`); |
| |
| // Only change transceiver.currentDirection after the first |
| // tonechange event, to make sure that tonechange is triggered |
| // then stopped |
| if(tone === 'A') { |
| transceiver.direction = 'recvonly'; |
| |
| pc.createOffer() |
| .then(offer => |
| pc.setLocalDescription(offer) |
| .then(() => generateAnswer(offer))) |
| .then(answer => pc.setRemoteDescription(answer)) |
| .then(() => { |
| assert_equals(transceiver.currentDirection, 'recvonly'); |
| remoteDescriptionIsSet = true; |
| |
| // Pass the test if no further tonechange event is |
| // fired in the next 300ms |
| t.step_timeout(t.step_func_done(), 300); |
| }) |
| .catch(t.step_func(err => { |
| assert_unreached(`Unexpected promise rejection: ${err}`); |
| })); |
| } |
| }); |
| |
| dtmfSender.addEventListener('tonechange', onToneChange); |
| dtmfSender.insertDTMF('ABCD', 100, 70); |
| }, `Setting transceiver.currentDirection to recvonly in the middle of tonechange events should stop future tonechange events from firing`); |
| |
| /* Section 7.3 - Tone change event */ |
| test(t => { |
| let ev = new RTCDTMFToneChangeEvent('tonechange', {'tone': '1'}); |
| assert_equals(ev.type, 'tonechange'); |
| assert_equals(ev.tone, '1'); |
| }, 'Tone change event constructor works'); |
| |
| test(t => { |
| let ev = new RTCDTMFToneChangeEvent('worngname', {}); |
| }, 'Tone change event with unexpected name should not crash'); |
| |
| </script> |