| <!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(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(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') { | |
| // Set a timeout to make sure the toneBuffer | |
| // is changed after the tonechange event listener | |
| // by test_tone_change_events is called. | |
| // This is to correctly test the expected toneBuffer. | |
| t.step_timeout(() => { | |
| dtmfSender.insertDTMF('12', 100, 70); | |
| }, 10); | |
| } | |
| }); | |
| 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') { | |
| t.step_timeout(() => { | |
| dtmfSender.insertDTMF('12', 100, 70); | |
| dtmfSender.insertDTMF('34', 100, 70); | |
| }, 10); | |
| } | |
| }); | |
| 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') { | |
| t.step_timeout(() => { | |
| dtmfSender.insertDTMF(''); | |
| }, 10); | |
| } | |
| }); | |
| 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(); | |
| 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.setDirection('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`); | |
| </script> |