| <!doctype html> | |
| <meta charset=utf-8> | |
| <title>RTCDTMFSender.prototype.insertDTMF</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 | |
| // createDtmfSender | |
| // 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; | |
| }; | |
| */ | |
| /* | |
| 7.2. insertDTMF | |
| The tones parameter is treated as a series of characters. | |
| The characters 0 through 9, A through D, #, and * generate the associated | |
| DTMF tones. | |
| The characters a to d MUST be normalized to uppercase on entry and are | |
| equivalent to A to D. | |
| As noted in [RTCWEB-AUDIO] Section 3, support for the characters 0 through 9, | |
| A through D, #, and * are required. | |
| The character ',' MUST be supported, and indicates a delay of 2 seconds | |
| before processing the next character in the tones parameter. | |
| All other characters (and only those other characters) MUST be considered | |
| unrecognized. | |
| */ | |
| promise_test(async t => { | |
| const dtmfSender = await createDtmfSender(); | |
| dtmfSender.insertDTMF(''); | |
| dtmfSender.insertDTMF('012345689'); | |
| dtmfSender.insertDTMF('ABCD'); | |
| dtmfSender.insertDTMF('abcd'); | |
| dtmfSender.insertDTMF('#*'); | |
| dtmfSender.insertDTMF(','); | |
| dtmfSender.insertDTMF('0123456789ABCDabcd#*,'); | |
| }, 'insertDTMF() should succeed if tones contains valid DTMF characters'); | |
| /* | |
| 7.2. insertDTMF | |
| 6. If tones contains any unrecognized characters, throw an | |
| InvalidCharacterError. | |
| */ | |
| promise_test(async t => { | |
| const dtmfSender = await createDtmfSender(); | |
| assert_throws_dom('InvalidCharacterError', () => | |
| // 'F' is invalid | |
| dtmfSender.insertDTMF('123FFABC')); | |
| assert_throws_dom('InvalidCharacterError', () => | |
| // 'E' is invalid | |
| dtmfSender.insertDTMF('E')); | |
| assert_throws_dom('InvalidCharacterError', () => | |
| // ' ' is invalid | |
| dtmfSender.insertDTMF('# *')); | |
| }, 'insertDTMF() should throw InvalidCharacterError if tones contains invalid DTMF characters'); | |
| /* | |
| 7.2. insertDTMF | |
| 3. If transceiver.stopped is true, throw an InvalidStateError. | |
| */ | |
| test(t => { | |
| const pc = new RTCPeerConnection(); | |
| const transceiver = pc.addTransceiver('audio'); | |
| const dtmfSender = transceiver.sender.dtmf; | |
| transceiver.stop(); | |
| assert_throws_dom('InvalidStateError', () => dtmfSender.insertDTMF('')); | |
| }, 'insertDTMF() should throw InvalidStateError if transceiver is stopped'); | |
| /* | |
| 7.2. insertDTMF | |
| 4. If transceiver.currentDirection is recvonly or inactive, throw an InvalidStateError. | |
| */ | |
| promise_test(async t => { | |
| const caller = new RTCPeerConnection(); | |
| t.add_cleanup(() => caller.close()); | |
| const callee = new RTCPeerConnection(); | |
| t.add_cleanup(() => callee.close()); | |
| const transceiver = | |
| caller.addTransceiver('audio', { direction: 'recvonly' }); | |
| const dtmfSender = transceiver.sender.dtmf; | |
| const offer = await caller.createOffer(); | |
| await caller.setLocalDescription(offer); | |
| await callee.setRemoteDescription(offer); | |
| const stream = await getNoiseStream({audio: true}); | |
| t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); | |
| const [track] = stream.getTracks(); | |
| callee.addTrack(track, stream); | |
| const answer = await callee.createAnswer(); | |
| await callee.setLocalDescription(answer); | |
| await caller.setRemoteDescription(answer); | |
| assert_equals(transceiver.currentDirection, 'recvonly'); | |
| assert_throws_dom('InvalidStateError', () => dtmfSender.insertDTMF('')); | |
| }, 'insertDTMF() should throw InvalidStateError if transceiver.currentDirection is recvonly'); | |
| promise_test(async t => { | |
| const pc = new RTCPeerConnection(); | |
| t.add_cleanup(() => pc.close()); | |
| const transceiver = | |
| pc.addTransceiver('audio', { direction: 'inactive' }); | |
| const dtmfSender = transceiver.sender.dtmf; | |
| const offer = await pc.createOffer(); | |
| await pc.setLocalDescription(offer); | |
| const answer = await generateAnswer(offer); | |
| await pc.setRemoteDescription(answer); | |
| assert_equals(transceiver.currentDirection, 'inactive'); | |
| assert_throws_dom('InvalidStateError', () => dtmfSender.insertDTMF('')); | |
| }, 'insertDTMF() should throw InvalidStateError if transceiver.currentDirection is inactive'); | |
| /* | |
| 7.2. insertDTMF | |
| The characters a to d MUST be normalized to uppercase on entry and are | |
| equivalent to A to D. | |
| 7. Set the object's toneBuffer attribute to tones. | |
| */ | |
| promise_test(async t => { | |
| const dtmfSender = await createDtmfSender(); | |
| dtmfSender.insertDTMF('123'); | |
| assert_equals(dtmfSender.toneBuffer, '123'); | |
| dtmfSender.insertDTMF('ABC'); | |
| assert_equals(dtmfSender.toneBuffer, 'ABC'); | |
| dtmfSender.insertDTMF('bcd'); | |
| assert_equals(dtmfSender.toneBuffer, 'BCD'); | |
| }, 'insertDTMF() should set toneBuffer to provided tones normalized, with old tones overridden'); | |
| promise_test(async t => { | |
| const pc = new RTCPeerConnection(); | |
| t.add_cleanup(() => pc.close()); | |
| const [track, mediaStream] = await getTrackFromUserMedia('audio'); | |
| const sender = pc.addTrack(track, mediaStream); | |
| await pc.setLocalDescription(await pc.createOffer()); | |
| const dtmfSender = sender.dtmf; | |
| pc.removeTrack(sender); | |
| pc.close(); | |
| assert_throws_dom('InvalidStateError', () => | |
| dtmfSender.insertDTMF('123')); | |
| }, 'insertDTMF() after remove and close should reject'); | |
| </script> |