@@ -36,9 +36,24 @@ const RTC_CONFIGURATION: RTCConfiguration = {
3636 bundlePolicy : "max-bundle" ,
3737 rtcpMuxPolicy : "require" ,
3838} ;
39+
3940const HEARTBEAT_DATA_CHANNEL_LABEL = "__heartbeat__" ;
4041const 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+
4257export 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