diff --git a/bitratecontroller.go b/bitratecontroller.go index d5265ca..e67b8a2 100644 --- a/bitratecontroller.go +++ b/bitratecontroller.go @@ -7,7 +7,7 @@ import ( "time" "github.com/pion/interceptor/pkg/cc" - "github.com/pion/webrtc/v3" + "github.com/pion/webrtc/v4" ) var ( diff --git a/client.go b/client.go index dbea128..70a2351 100644 --- a/client.go +++ b/client.go @@ -23,7 +23,7 @@ import ( "github.com/pion/interceptor/pkg/stats" "github.com/pion/logging" "github.com/pion/rtcp" - "github.com/pion/webrtc/v3" + "github.com/pion/webrtc/v4" ) type ClientState int @@ -60,26 +60,28 @@ var ( ) type ClientOptions struct { - IdleTimeout time.Duration - Type string - EnableVoiceDetection bool - EnablePlayoutDelay bool + IdleTimeout time.Duration `json:"idle_timeout"` + Type string `json:"type"` + EnableVoiceDetection bool `json:"enable_voice_detection"` + EnablePlayoutDelay bool `json:"enable_playout_delay"` + EnableOpusDTX bool `json:"enable_opus_dtx"` + EnableOpusInbandFEC bool `json:"enable_opus_inband_fec"` // Configure the minimum playout delay that will be used by the client // Recommendation: // 0 ms: Certain gaming scenarios (likely without audio) where we will want to play the frame as soon as possible. Also, for remote desktop without audio where rendering a frame asap makes sense // 100/150/200 ms: These could be the max target latency for interactive streaming use cases depending on the actual application (gaming, remoting with audio, interactive scenarios) // 400 ms: Application that want to ensure a network glitch has very little chance of causing a freeze can start with a minimum delay target that is high enough to deal with network issues. Video streaming is one example. - MinPlayoutDelay uint16 + MinPlayoutDelay uint16 `json:"min_playout_delay"` // Configure the minimum playout delay that will be used by the client // Recommendation: // 0 ms: Certain gaming scenarios (likely without audio) where we will want to play the frame as soon as possible. Also, for remote desktop without audio where rendering a frame asap makes sense // 100/150/200 ms: These could be the max target latency for interactive streaming use cases depending on the actual application (gaming, remoting with audio, interactive scenarios) // 400 ms: Application that want to ensure a network glitch has very little chance of causing a freeze can start with a minimum delay target that is high enough to deal with network issues. Video streaming is one example. - MaxPlayoutDelay uint16 - JitterBufferMinWait time.Duration - JitterBufferMaxWait time.Duration + MaxPlayoutDelay uint16 `json:"max_playout_delay"` + JitterBufferMinWait time.Duration `json:"jitter_buffer_min_wait"` + JitterBufferMaxWait time.Duration `json:"jitter_buffer_max_wait"` // On unstable network, the packets can be arrived unordered which may affected the nack and packet loss counts, set this to true to allow the SFU to handle reordered packet - ReorderPackets bool + ReorderPackets bool `json:"reorder_packets"` Log logging.LeveledLogger } @@ -184,6 +186,8 @@ func DefaultClientOptions() ClientOptions { Type: ClientTypePeer, EnableVoiceDetection: true, EnablePlayoutDelay: true, + EnableOpusDTX: true, + EnableOpusInbandFEC: true, MinPlayoutDelay: 100, MaxPlayoutDelay: 200, JitterBufferMinWait: 20 * time.Millisecond, @@ -416,7 +420,7 @@ func NewClient(s *SFU, id string, name string, peerConnectionConfig webrtc.Confi case webrtc.PeerConnectionStateNew: // do nothing client.startIdleTimeout(opts.IdleTimeout) - case webrtc.PeerConnectionState(webrtc.Unknown): + case webrtc.PeerConnectionState(webrtc.PeerConnectionStateUnknown): // clean up client.afterClosed() } @@ -742,7 +746,65 @@ func (c *Client) negotiateQueuOp(offer webrtc.SessionDescription) (*webrtc.Sessi c.pendingRemoteCandidates = nil - return c.peerConnection.PC().LocalDescription(), nil + sdp := c.setOpusSDP(*c.peerConnection.PC().LocalDescription()) + + return &sdp, nil +} + +func (c *Client) setOpusSDP(sdp webrtc.SessionDescription) webrtc.SessionDescription { + if c.options.EnableOpusDTX { + var regex, err = regexp.Compile(`a=rtpmap:(\d+) opus\/(\d+)\/(\d+)`) + if err != nil { + c.log.Errorf("client: error on compile regex ", err) + return sdp + } + var opusLine = regex.FindString(sdp.SDP) + + if opusLine == "" { + c.log.Errorf("client: error opus line not found") + return sdp + } + + regex, err = regexp.Compile(`(\d+)`) + if err != nil { + c.log.Errorf("client: error on compile regex ", err) + return sdp + } + + var opusNo = regex.FindString(opusLine) + if opusNo == "" { + c.log.Errorf("client: error opus no not found") + return sdp + } + + fmtpRegex, err := regexp.Compile(`a=fmtp:` + opusNo + ` .+`) + if err != nil { + c.log.Errorf("client: error on compile regex ", err) + return sdp + } + + var fmtpLine = fmtpRegex.FindString(sdp.SDP) + if fmtpLine == "" { + c.log.Errorf("client: error fmtp line not found") + return sdp + } + + var newFmtpLine = "" + + if c.options.EnableOpusDTX && !strings.Contains(fmtpLine, "usedtx=1") { + newFmtpLine += ";usedtx=1" + } + + if c.options.EnableOpusInbandFEC && !strings.Contains(fmtpLine, "useinbandfec=1") { + newFmtpLine += ";useinbandfec=1" + } + + if newFmtpLine != "" { + sdp.SDP = strings.Replace(sdp.SDP, fmtpLine, fmtpLine+newFmtpLine, -1) + } + } + + return sdp } func (c *Client) renegotiate() { diff --git a/client_test.go b/client_test.go index f7b0ba9..735d625 100644 --- a/client_test.go +++ b/client_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/pion/webrtc/v3" + "github.com/pion/webrtc/v4" "github.com/stretchr/testify/require" ) diff --git a/clienttrack.go b/clienttrack.go index 8a45c7e..8c1acc7 100644 --- a/clienttrack.go +++ b/clienttrack.go @@ -5,7 +5,7 @@ import ( "sync" "github.com/pion/rtp" - "github.com/pion/webrtc/v3" + "github.com/pion/webrtc/v4" ) type iClientTrack interface { diff --git a/clienttrackred.go b/clienttrackred.go index 72c01ca..7773f37 100644 --- a/clienttrackred.go +++ b/clienttrackred.go @@ -6,7 +6,7 @@ import ( "github.com/inlivedev/sfu/pkg/rtppool" "github.com/pion/rtp" - "github.com/pion/webrtc/v3" + "github.com/pion/webrtc/v4" ) var ( diff --git a/clienttracksimulcast.go b/clienttracksimulcast.go index dbc72d3..8f13001 100644 --- a/clienttracksimulcast.go +++ b/clienttracksimulcast.go @@ -7,7 +7,7 @@ import ( "github.com/inlivedev/sfu/pkg/packetmap" "github.com/pion/rtp" - "github.com/pion/webrtc/v3" + "github.com/pion/webrtc/v4" ) type simulcastClientTrack struct { diff --git a/codec.go b/codec.go index 56636bd..7daa5a2 100644 --- a/codec.go +++ b/codec.go @@ -6,8 +6,8 @@ import ( "github.com/pion/rtp" "github.com/pion/rtp/codecs" - "github.com/pion/webrtc/v3" - "github.com/pion/webrtc/v3/pkg/media" + "github.com/pion/webrtc/v4" + "github.com/pion/webrtc/v4/pkg/media" "golang.org/x/exp/slices" ) diff --git a/datachannel.go b/datachannel.go index c39919c..9bc1f38 100644 --- a/datachannel.go +++ b/datachannel.go @@ -6,7 +6,7 @@ import ( "sync" "time" - "github.com/pion/webrtc/v3" + "github.com/pion/webrtc/v4" ) var ( diff --git a/datachannel_test.go b/datachannel_test.go index a3ec0db..60e9072 100644 --- a/datachannel_test.go +++ b/datachannel_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/pion/webrtc/v3" + "github.com/pion/webrtc/v4" "github.com/stretchr/testify/require" ) diff --git a/examples/http-websocket/index.html b/examples/http-websocket/index.html index bd2124f..3e00b4b 100644 --- a/examples/http-websocket/index.html +++ b/examples/http-websocket/index.html @@ -153,6 +153,30 @@ const maxBw = 2500*1000 + let mutedMic = false + + let mutedCam = false + + const toggleMic = async (e) => { + peerConnection.getSenders().forEach(sender => { + if (sender.track.kind === 'audio') { + sender.track.enabled = !mutedMic + mutedMic = !mutedMic + e.target.innerText = !sender.track.enabled?'Unmute Mic':'Mute Mic' + } + }) + } + + const toggleCam = async (e) => { + peerConnection.getSenders().forEach(sender => { + if (sender.track.kind === 'video') { + sender.track.enabled = !mutedCam + mutedCam = !mutedCam + e.target.innerText = !sender.track.enabled?'Unmute Cam':'Mute Cam' + } + }) + } + const startWs =async ()=>{ let debug = false const urlParams = new URLSearchParams(window.location.search); @@ -273,6 +297,49 @@ start('vp9') } + function setOpusSDP(sdp){ + let fmtlineOpus = '' + + let fmtNo = null + // find fmtline for opus from SDP + const opusFmtLines = sdp.match(/a=rtpmap:(\d+) opus\/(\d+)\/(\d+)/) + if (opusFmtLines) { + // extract only the first digit + const matchesNo = opusFmtLines[0].match(/(\d+)/) + if (matchesNo) { + fmtNo = matchesNo[0] + } + } + + console.log('fmtNo', fmtNo) + + // find fmtp line for opus from SDP + const opusFmtpLines = sdp.match(new RegExp(`a=fmtp:${fmtNo} .+`)) + if (opusFmtpLines) { + fmtlineOpus = opusFmtpLines[0] + } + + // check if DTX is enabled and if not, add it to the SDP + if (!fmtlineOpus.includes('usedtx=1')) { + fmtlineOpus += ';usedtx=1' + } + + // check if inbandfec is enabled and if not, add it to the SDP + if (!fmtlineOpus.includes('useinbandfec=1')) { + fmtlineOpus += ';useinbandfec=1' + } + + console.log('fmtlineOpus', fmtlineOpus) + + // replace the old fmtp line with the new one + sdp = sdp.replace( + new RegExp(`a=fmtp:${fmtNo} .+`), + `a=fmtp:${fmtNo} ${fmtlineOpus}` + ) + + return sdp + } + async function start(codec) { await startWs() document.getElementById("btnStart").disabled = true; @@ -553,6 +620,8 @@ } const offer=await peerConnection.createOffer() + + // offer.sdp = setOpusSDP(offer.sdp) await peerConnection.setLocalDescription(offer) @@ -957,6 +1026,8 @@

Low

document.addEventListener("DOMContentLoaded", function(event) { document.getElementById("btnStart").onclick = startH264; document.getElementById("btnStartVP9").onclick = startVP9; + document.getElementById("btnToggleMic").onclick = toggleMic; + document.getElementById("btnToggleCam").onclick = toggleCam; document.getElementById("btnShareScreen").onclick = shareScreen; document.getElementById("btnStats").onclick = toggleStats; document.getElementById("selectQuality").onchange = switchQuality; @@ -979,6 +1050,8 @@

Outbound Video