Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VBLOCKS-1805 | Fix dtmf tones overrides #176

Merged
merged 2 commits into from
Jun 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
2.6.1 (In Progress)
=====================

Bug Fixes
---------

- Fixed an issue where custom DTMF sounds would not play. With this release, custom DTMF sounds should now play when configured during device initialization.

```ts
const device = new Device(token, {
sounds: {
dtmf8: 'http://mysite.com/8_button.mp3',
// Other custom sounds
},
// Other options
});
```

2.6.0 (June 20, 2023)
=====================

Expand Down
24 changes: 14 additions & 10 deletions lib/twilio/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,7 @@ class Call extends EventEmitter {
throw new InvalidArgumentError('Illegal character passed into sendDigits');
}

const customSounds = this._options.customSounds || {};
const sequence: string[] = [];
digits.split('').forEach((digit: string) => {
let dtmf = (digit !== 'w') ? `dtmf${digit}` : '';
Expand All @@ -823,22 +824,20 @@ class Call extends EventEmitter {
sequence.push(dtmf);
});

// Binds soundCache to be used in recursion until all digits have been played.
(function playNextDigit(soundCache, dialtonePlayer) {
const digit: string | undefined = sequence.shift();

const playNextDigit = () => {
const digit = sequence.shift() as Device.SoundName | undefined;
if (digit) {
if (dialtonePlayer) {
dialtonePlayer.play(digit);
if (this._options.dialtonePlayer && !customSounds[digit]) {
this._options.dialtonePlayer.play(digit);
} else {
soundCache.get(digit as Device.SoundName).play();
this._soundcache.get(digit).play();
}
}

if (sequence.length) {
setTimeout(playNextDigit.bind(null, soundCache), 200);
setTimeout(() => playNextDigit(), 200);
}
})(this._soundcache, this._options.dialtonePlayer);
};
playNextDigit();

const dtmfSender = this._mediaHandler.getOrCreateDTMFSender();

Expand Down Expand Up @@ -1817,6 +1816,11 @@ namespace Call {
*/
codecPreferences?: Codec[];

/**
* A mapping of custom sound URLs by sound name.
*/
customSounds?: Partial<Record<Device.SoundName, string>>;

/**
* A DialTone player, to play mock DTMF sounds.
*/
Expand Down
1 change: 1 addition & 0 deletions lib/twilio/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,7 @@ class Device extends EventEmitter {
this._removeCall(this._activeCall);
},
codecPreferences: this._options.codecPreferences,
customSounds: this._options.sounds,
dialtonePlayer: Device._dialtonePlayer,
dscp: this._options.dscp,
forceAggressiveIceNomination: this._options.forceAggressiveIceNomination,
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 66 additions & 19 deletions tests/unit/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1158,25 +1158,72 @@ describe('Call', function() {
sinon.assert.callCount(sender.insertDTMF, 3);
});

it('should play the sound for each letter', () => {
const sender = { insertDTMF: sinon.spy() };
mediaHandler.getOrCreateDTMFSender = () => sender;
conn.sendDigits('123w456w#*w');

clock.tick(1 + 200);
sinon.assert.callCount(soundcache.get(Device.SoundName.Dtmf1).play, 1);
sinon.assert.callCount(soundcache.get(Device.SoundName.Dtmf2).play, 1);
sinon.assert.callCount(soundcache.get(Device.SoundName.Dtmf3).play, 0);

clock.tick(1 + (200 * 7) + (500 * 3));
sinon.assert.callCount(soundcache.get(Device.SoundName.Dtmf1).play, 1);
sinon.assert.callCount(soundcache.get(Device.SoundName.Dtmf2).play, 1);
sinon.assert.callCount(soundcache.get(Device.SoundName.Dtmf3).play, 1);
sinon.assert.callCount(soundcache.get(Device.SoundName.Dtmf4).play, 1);
sinon.assert.callCount(soundcache.get(Device.SoundName.Dtmf5).play, 1);
sinon.assert.callCount(soundcache.get(Device.SoundName.Dtmf6).play, 1);
sinon.assert.callCount(soundcache.get(Device.SoundName.DtmfS).play, 1);
sinon.assert.callCount(soundcache.get(Device.SoundName.DtmfH).play, 1);
describe('when playing sound', () => {
const digits = '0123w456789w*#w';
let dialtonePlayer: any;

[{
name: 'dialtonePlayer',
getOptions: () => {
dialtonePlayer = { play: sinon.stub() };
options.dialtonePlayer = dialtonePlayer;
return options;
},
assert: (dtmf: string, digit: string) => {
sinon.assert.callCount(dialtonePlayer.play, digits.replace(/w/g, '').indexOf(digit) + 1);
sinon.assert.calledWithExactly(dialtonePlayer.play, dtmf);
},
},{
name: 'soundcache',
getOptions: () => options,
assert: (dtmf: string) => {
const playStub = soundcache.get(dtmf as Device.SoundName).play;
sinon.assert.callCount(playStub, 1);
},
},{
name: 'customSounds',
getOptions: () => {
dialtonePlayer = { play: sinon.stub() };

return {
...options,
dialtonePlayer,
customSounds: Object.values(Device.SoundName)
.filter(name => name.includes('dtmf'))
.reduce((customSounds, name) => {
customSounds[name] = name;
return customSounds;
}, {} as any)
};
},
assert: (dtmf: string, digit: string) => {
const playStub = soundcache.get(dtmf as Device.SoundName).play;
sinon.assert.callCount(playStub, 1);
},
}].forEach(({ name, assert, getOptions }) => {
it(`should play the sound for each letter using ${name}`, () => {
const o = getOptions();
conn = new Call(config, o);

const sender = { insertDTMF: sinon.spy() };
mediaHandler.getOrCreateDTMFSender = () => sender;
conn.sendDigits(digits);

clock.tick(1);
digits.split('').forEach((digit) => {
if (digit === 'w') {
clock.tick(200);
return;
}
let dtmf = `dtmf${digit}`;
if (dtmf === 'dtmf*') { dtmf = 'dtmfs'; }
if (dtmf === 'dtmf#') { dtmf = 'dtmfh'; }

assert(dtmf, digit);
clock.tick(200);
});
});
})
});

it('should call pstream.dtmf if connected', () => {
Expand Down