1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
|
<!doctype html>
<meta charset=utf-8>
<title>RTCDTMFSender.prototype.ontonechange</title>
<meta name="timeout" content="long">
<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.
*/
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const dtmfSender = await createDtmfSender(pc);
const pc2 = pc.otherPc;
assert_true(pc2 instanceof RTCPeerConnection,
'Expect pc2 to be a RTCPeerConnection');
t.add_cleanup(() => pc2.close());
const transceiver = pc.getTransceivers()[0];
assert_equals(transceiver.sender.dtmf, dtmfSender);
// 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 firstTone = new Promise(resolve => {
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}`);
if(tone === 'A') {
resolve();
}
});
dtmfSender.addEventListener('tonechange', onToneChange);
});
dtmfSender.insertDTMF('ABCD', 100, 70);
await firstTone;
// Only change transceiver.direction after the first
// tonechange event, to make sure that tonechange is triggered
// then stopped
transceiver.direction = 'recvonly';
await exchangeOfferAnswer(pc, pc2);
assert_equals(transceiver.currentDirection, 'inactive');
remoteDescriptionIsSet = true;
await new Promise(resolve => t.step_timeout(resolve, 300));
}, `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');
test(t => {
const ev1 = new RTCDTMFToneChangeEvent('tonechange', {});
assert_equals(ev1.tone, '');
assert_equals(RTCDTMFToneChangeEvent.constructor.length, 1);
const ev2 = new RTCDTMFToneChangeEvent('tonechange');
assert_equals(ev2.tone, '');
}, 'Tone change event init optional parameters');
</script>
|