Skip to content

Commit 48a68af

Browse files
author
smoghe-bw
committed
fix: ensure telephone-event codec included in DTMF setCodecPreferences
1 parent 56ece97 commit 48a68af

4 files changed

Lines changed: 38 additions & 174 deletions

File tree

src/bandwidthRtc.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ class BandwidthRtc {
211211
return devices;
212212
}
213213

214-
sendDtmf(tone: string, streamId?: string, duration?: number, interToneGap?: number) {
214+
sendDtmf(tone: string, streamId?: string, duration: number = 100, interToneGap: number = 70) {
215215
if (!this.delegate) {
216216
throw new BandwidthRtcError("You must call 'connect' before 'sendDtmf'");
217217
}

src/dtmfSender.test.ts

Lines changed: 0 additions & 33 deletions
This file was deleted.

src/dtmfSender.ts

Lines changed: 0 additions & 127 deletions
This file was deleted.

src/v1/bandwidthRtc.ts

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,24 @@ const RTC_CONFIGURATION: RTCConfiguration = {
3636
bundlePolicy: "max-bundle",
3737
rtcpMuxPolicy: "require",
3838
};
39+
3940
const HEARTBEAT_DATA_CHANNEL_LABEL = "__heartbeat__";
4041
const DIAGNOSTICS_DATA_CHANNEL_LABEL = "__diagnostics__";
4142

43+
const PEER_CONNECTION_TYPE_PUBLISH = "publish";
44+
const PEER_CONNECTION_TYPE_SUBSCRIBE = "subscribe";
45+
46+
const TRACK_KIND_AUDIO = "audio";
47+
const TRACK_KIND_VIDEO = "video";
48+
const TELEPHONE_EVENT_MIME_TYPE = "audio/telephone-event";
49+
50+
const HEARTBEAT_PING = "PING";
51+
const HEARTBEAT_PONG = "PONG";
52+
const DATA_CHANNEL_STATE_OPEN = "open";
53+
54+
const CONNECTION_STATE_FAILED = "failed";
55+
const CONNECTION_STATE_DISCONNECTED = "disconnected";
56+
4257
export class BandwidthRtc {
4358
private options?: RtcOptions;
4459

@@ -290,7 +305,7 @@ export class BandwidthRtc {
290305
* @param duration Tone duration in milliseconds (default: 100). Must be between 40 and 6000.
291306
* @param interToneGap Gap between tones in milliseconds (default: 70). Minimum 30.
292307
*/
293-
sendDtmf(tone: string, streamId?: string, duration?: number, interToneGap?: number) {
308+
sendDtmf(tone: string, streamId?: string, duration: number = 100, interToneGap: number = 70) {
294309
if (streamId) {
295310
this.localDtmfSenders.get(streamId)?.insertDTMF(tone, duration, interToneGap);
296311
} else {
@@ -378,7 +393,7 @@ export class BandwidthRtc {
378393
),
379394
);
380395
logger.debug("publish metadata", publishMetadata);
381-
const remoteSdpAnswer = await this.signaling.offerSdp("publish", localSdpOffer.sdp!);
396+
const remoteSdpAnswer = await this.signaling.offerSdp(PEER_CONNECTION_TYPE_PUBLISH, localSdpOffer.sdp!);
382397

383398
await this.publishingPeerConnection!.setLocalDescription(localSdpOffer);
384399
logger.debug("remoteSdpAnswer", remoteSdpAnswer);
@@ -426,7 +441,7 @@ export class BandwidthRtc {
426441
}
427442

428443
await this.subscribingPeerConnection!.setLocalDescription(localSdpAnswer);
429-
await this.signaling.answerSdp(localSdpAnswer.sdp, "subscribe");
444+
await this.signaling.answerSdp(localSdpAnswer.sdp, PEER_CONNECTION_TYPE_SUBSCRIBE);
430445

431446
this.subscribingPeerConnectionSdpRevision = subscribeSdpOffer.sdpRevision;
432447
logger.debug(`set current SDP revision to ${this.subscribingPeerConnectionSdpRevision}`);
@@ -441,7 +456,7 @@ export class BandwidthRtc {
441456
const publishOnTrackHandler = (event: RTCTrackEvent) => {
442457
logger.debug("publish ontrack event", event);
443458
};
444-
this.publishingPeerConnection = await this.setupPeerConnection("publish", publishOnTrackHandler, setMediaPreferencesResponse.publishSdpOffer.sdpOffer);
459+
this.publishingPeerConnection = await this.setupPeerConnection(PEER_CONNECTION_TYPE_PUBLISH, publishOnTrackHandler, setMediaPreferencesResponse.publishSdpOffer.sdpOffer);
445460

446461
let streamTracks: Map<MediaStream, Set<MediaStreamTrack>> = new Map();
447462

@@ -502,7 +517,7 @@ export class BandwidthRtc {
502517
}
503518
};
504519
this.subscribingPeerConnection = await this.setupPeerConnection(
505-
"subscribe",
520+
PEER_CONNECTION_TYPE_SUBSCRIBE,
506521
subscriptionOnTrackHandler,
507522
setMediaPreferencesResponse.subscribeSdpOffer.sdpOffer,
508523
);
@@ -522,7 +537,7 @@ export class BandwidthRtc {
522537
const pc = event.target as RTCPeerConnection;
523538
let connectionState = pc.connectionState;
524539
logger.debug("onconnectionstatechange", connectionState, pc);
525-
if (connectionState === "failed") {
540+
if (connectionState === CONNECTION_STATE_FAILED) {
526541
logger.warn("Connection failed, attempting to restart ICE TODO");
527542
// await this.offerPublishSdp(true);
528543
// connectionState = pc.connectionState;
@@ -568,9 +583,9 @@ export class BandwidthRtc {
568583
// Handle heartbeat messages
569584
dataChannel.onmessage = (event) => {
570585
logger.debug("Heartbeat Data Channel message", event.data);
571-
if (event.data == "PING" && dataChannel.readyState === "open") {
586+
if (event.data == HEARTBEAT_PING && dataChannel.readyState === DATA_CHANNEL_STATE_OPEN) {
572587
logger.debug("Received PING, sending PONG");
573-
dataChannel.send("PONG");
588+
dataChannel.send(HEARTBEAT_PONG);
574589
}
575590
};
576591
} else if (dataChannel.label === DIAGNOSTICS_DATA_CHANNEL_LABEL) {
@@ -587,7 +602,7 @@ export class BandwidthRtc {
587602
const pc = event.target as RTCPeerConnection;
588603
logger.debug("onconnectionstatechange", pc.connectionState, pc);
589604
const connectionState = pc.connectionState;
590-
if (connectionState === "disconnected") {
605+
if (connectionState === CONNECTION_STATE_DISCONNECTED) {
591606
logger.warn("Peer disconnected, connection may be reestablished");
592607
}
593608
} catch (err) {
@@ -655,14 +670,23 @@ export class BandwidthRtc {
655670
// RTCDTMFSender. rtpSender.dtmf can be null when the browser doesn't
656671
// support DTMF for this track, so guard before storing.
657672
const dtmfSender = transceiver.sender.dtmf;
658-
if (track.kind === "audio" && dtmfSender && !this.localDtmfSenders.has(mediaStream.id)) {
673+
if (track.kind === TRACK_KIND_AUDIO && dtmfSender && !this.localDtmfSenders.has(mediaStream.id)) {
659674
this.localDtmfSenders.set(mediaStream.id, dtmfSender);
660675
}
661676

662677
if (codecPreferences) {
663-
if (track.kind === "audio" && codecPreferences.audio) {
664-
transceiver.setCodecPreferences(codecPreferences.audio);
665-
} else if (track.kind === "video" && codecPreferences.video) {
678+
if (track.kind === TRACK_KIND_AUDIO && codecPreferences.audio) {
679+
// setCodecPreferences is a strict allowlist: any codec omitted from the
680+
// list is dropped from the SDP offer. telephone-event must always be
681+
// present so that RTCDTMFSender can send RFC 4733 DTMF packets.
682+
const hasTelephoneEvent = codecPreferences.audio.some((c) => c.mimeType.toLowerCase() === TELEPHONE_EVENT_MIME_TYPE);
683+
if (!hasTelephoneEvent) {
684+
const telephoneEventCodec = RTCRtpSender.getCapabilities(TRACK_KIND_AUDIO)?.codecs.find((c) => c.mimeType.toLowerCase() === TELEPHONE_EVENT_MIME_TYPE);
685+
transceiver.setCodecPreferences(telephoneEventCodec ? [...codecPreferences.audio, telephoneEventCodec] : codecPreferences.audio);
686+
} else {
687+
transceiver.setCodecPreferences(codecPreferences.audio);
688+
}
689+
} else if (track.kind === TRACK_KIND_VIDEO && codecPreferences.video) {
666690
transceiver.setCodecPreferences(codecPreferences.video);
667691
}
668692
}

0 commit comments

Comments
 (0)