pion / example-webrtc-applications

Examples of WebRTC applications that are large, or use 3rd party libraries

Home Page:https://pion.ly/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

sfu-ws example with trickle ICE not able to send rtp out to browser

sampleref opened this issue · comments

I tried to modify the given https://github.com/pion/example-webrtc-applications/tree/master/sfu-ws example to support trickleICE as below,

  1. Changes for trickle enable
m := webrtc.MediaEngine{}
	s := webrtc.SettingEngine{}
	s.SetTrickle(true)
	// Setup the codecs you want to use.
	m.RegisterCodec(webrtc.NewRTPVP8Codec(webrtc.DefaultPayloadTypeVP8, 90000))
	m.RegisterCodec(webrtc.NewRTPOpusCodec(webrtc.DefaultPayloadTypeOpus, 48000))
	// Create the API object with the MediaEngine
	api := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithSettingEngine(s))
  1. Below WsConn Method is similar to Room with Trickle ICE support for a single pubReceiverC and subSenderC
func WsConn(w http.ResponseWriter, r *http.Request) {
	// Websocket client
	c, err := upgrader.Upgrade(w, r, nil)
	lutil.CheckError(err)
	// Make sure we close the connection when the function returns
	defer c.Close()
	for {
		var msg JsonMsg
		// Read in a new message as JSON and map it to a Message object
		err := c.ReadJSON(&msg)
		if err != nil {
			log.Printf("error: %v", err)
			delete(ConnMap, c)
			break
		}
		if msg.PeerType == "pub" {
			if msg.Sdp != "" {
				// Create a new RTCPeerConnection
				pubReceiverC, err = Api.NewPeerConnection(peerConnectionConfig)
				lutil.CheckError(err)

				_, err = pubReceiverC.AddTransceiverFromKind(webrtc.RTPCodecTypeAudio)
				lutil.CheckError(err)

				_, err = pubReceiverC.AddTransceiverFromKind(webrtc.RTPCodecTypeVideo)
				lutil.CheckError(err)

				pubReceiverC.OnTrack(func(remoteTrack *webrtc.Track, receiver *webrtc.RTPReceiver) {
					if remoteTrack.PayloadType() == webrtc.DefaultPayloadTypeVP8 || remoteTrack.PayloadType() == webrtc.DefaultPayloadTypeVP9 || remoteTrack.PayloadType() == webrtc.DefaultPayloadTypeH264 {
						// Create a local video track, all our SFU clients will be fed via this track
						var err error
						videoTrackLockC.Lock()
						videoTrackC, err = pubReceiverC.NewTrack(remoteTrack.PayloadType(), remoteTrack.SSRC(), "video", "pion")
						videoTrackLockC.Unlock()
						lutil.CheckError(err)

						// Send a PLI on an interval so that the publisher is pushing a keyframe every rtcpPLIInterval
						go func() {
							ticker := time.NewTicker(rtcpPLIInterval)
							for range ticker.C {
								lutil.CheckError(pubReceiverC.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: videoTrackC.SSRC()}}))
							}
						}()

						rtpBuf := make([]byte, 1400)
						for {
							i, err := remoteTrack.Read(rtpBuf)
							lutil.CheckError(err)
							videoTrackLockC.RLock()
							_, err = videoTrackC.Write(rtpBuf[:i])
							videoTrackLockC.RUnlock()

							if err != io.ErrClosedPipe {
								lutil.CheckError(err)
							}
						}
					} else {
						// Create a local audio track, all our SFU clients will be fed via this track
						var err error
						audioTrackLockC.Lock()
						audioTrackC, err = pubReceiverC.NewTrack(remoteTrack.PayloadType(), remoteTrack.SSRC(), "audio", "pion")
						audioTrackLockC.Unlock()
						lutil.CheckError(err)

						rtpBuf := make([]byte, 1400)
						for {
							i, err := remoteTrack.Read(rtpBuf)
							lutil.CheckError(err)
							audioTrackLockC.RLock()
							_, err = audioTrackC.Write(rtpBuf[:i])
							audioTrackLockC.RUnlock()
							if err != io.ErrClosedPipe {
								lutil.CheckError(err)
							}
						}
					}
				})

				// Set the remote SessionDescription
				lutil.CheckError(pubReceiverC.SetRemoteDescription(
					webrtc.SessionDescription{
						SDP:  string(msg.Sdp),
						Type: webrtc.SDPTypeOffer,
					}))

				// Create answer
				answer, err := pubReceiverC.CreateAnswer(nil)
				lutil.CheckError(err)

				// Sets the LocalDescription, and starts our UDP listeners
				lutil.CheckError(pubReceiverC.SetLocalDescription(answer))

				// Send server sdp to publisher
				var sdpMsg = JsonMsg{PeerId: msg.PeerId, Sdp: answer.SDP, PeerType: msg.PeerType, IceCandidate: struct {
					Candidate     string
					SDPMid        string
					SDPMLineIndex uint16
				}{Candidate: "", SDPMid: "", SDPMLineIndex: 0}}
				lutil.CheckError(c.WriteJSON(sdpMsg))

				// Register incoming channel
				pubReceiverC.OnDataChannel(func(d *webrtc.DataChannel) {
					d.OnMessage(func(msg webrtc.DataChannelMessage) {
						// Broadcast the data to subSenders
						broadcastHub.broadcastChannel <- msg.Data
					})
				})

				// Register for ice candidate generation
				pubReceiverC.OnICECandidate(func(candidate *webrtc.ICECandidate) {
					var iceMsg = JsonMsg{PeerId: msg.PeerId, Sdp: "", IceCandidate: struct {
						Candidate     string
						SDPMid        string
						SDPMLineIndex uint16
					}{Candidate: candidate.ToJSON().Candidate, SDPMid: *candidate.ToJSON().SDPMid, SDPMLineIndex: *candidate.ToJSON().SDPMLineIndex},
						PeerType: msg.PeerType}
					lutil.CheckError(c.WriteJSON(iceMsg))
				})

			} else if &msg.IceCandidate != nil && msg.IceCandidate.Candidate != "" {
				var iceCandidate = webrtc.ICECandidateInit{Candidate: msg.IceCandidate.Candidate, SDPMid: &msg.IceCandidate.SDPMid,
					SDPMLineIndex: &msg.IceCandidate.SDPMLineIndex, UsernameFragment: ""}
				pubReceiverC.AddICECandidate(iceCandidate)
			} else {
				fmt.Println("PUB: None of matching msg sdp or ice")
			}
		} else {
			if msg.Sdp != "" {
				// Create a new RTCPeerConnection
				subSenderC, err = Api.NewPeerConnection(peerConnectionConfig)
				lutil.CheckError(err)

				_, err = subSenderC.AddTransceiverFromKind(webrtc.RTPCodecTypeAudio)
				lutil.CheckError(err)

				_, err = subSenderC.AddTransceiverFromKind(webrtc.RTPCodecTypeVideo)
				lutil.CheckError(err)
				// Waiting for publisher track finish
				for {
					videoTrackLockC.RLock()
					if videoTrackC == nil {
						videoTrackLockC.RUnlock()
						fmt.Println("Waiting for local video track to come up...")
						time.Sleep(100 * time.Millisecond)
					} else {
						videoTrackLockC.RUnlock()
						break
					}
				}

				// Add local video track
				videoTrackLockC.RLock()
				_, err = subSenderC.AddTrack(videoTrackC)
				videoTrackLockC.RUnlock()
				lutil.CheckError(err)

				// Add local audio track
				audioTrackLockC.RLock()
				_, err = subSenderC.AddTrack(audioTrackC)
				audioTrackLockC.RUnlock()
				lutil.CheckError(err)

				// Set the remote SessionDescription
				lutil.CheckError(subSenderC.SetRemoteDescription(
					webrtc.SessionDescription{
						SDP:  string(msg.Sdp),
						Type: webrtc.SDPTypeOffer,
					}))

				// Create answer
				answer, err := subSenderC.CreateAnswer(nil)
				lutil.CheckError(err)

				// Sets the LocalDescription, and starts our UDP listeners
				lutil.CheckError(subSenderC.SetLocalDescription(answer))

				// Send server sdp to publisher
				var sdpMsg = JsonMsg{PeerId: msg.PeerId, Sdp: answer.SDP, PeerType: msg.PeerType, IceCandidate: struct {
					Candidate     string
					SDPMid        string
					SDPMLineIndex uint16
				}{Candidate: "", SDPMid: "", SDPMLineIndex: 0}}
				lutil.CheckError(c.WriteJSON(sdpMsg))

				// Register incoming channel
				subSenderC.OnDataChannel(func(d *webrtc.DataChannel) {
					d.OnMessage(func(msg webrtc.DataChannelMessage) {
						// Broadcast the data to subSenders
						broadcastHub.broadcastChannel <- msg.Data
					})
				})

				// Register for ice candidate generation
				subSenderC.OnICECandidate(func(candidate *webrtc.ICECandidate) {
					var iceMsg = JsonMsg{PeerId: msg.PeerId, Sdp: "", IceCandidate: struct {
						Candidate     string
						SDPMid        string
						SDPMLineIndex uint16
					}{Candidate: candidate.ToJSON().Candidate, SDPMid: *candidate.ToJSON().SDPMid, SDPMLineIndex: *candidate.ToJSON().SDPMLineIndex},
						PeerType: msg.PeerType}
					lutil.CheckError(c.WriteJSON(iceMsg))
				})

			} else if &msg.IceCandidate != nil && msg.IceCandidate.Candidate != "" {
				var iceCandidate = webrtc.ICECandidateInit{Candidate: msg.IceCandidate.Candidate, SDPMid: &msg.IceCandidate.SDPMid,
					SDPMLineIndex: &msg.IceCandidate.SDPMLineIndex, UsernameFragment: ""}
				subSenderC.AddICECandidate(iceCandidate)
			} else {
				fmt.Println("SUB: None of matching msg sdp or ice")
			}
		}
	}
}
  1. I have small JSON wrapper struct to handle JSON Message for SDP and ICE Messages as below, which is used in above function
type JsonMsg struct {
	PeerId       string
	Sdp          string
	IceCandidate struct {
		Candidate     string
		SDPMid        string
		SDPMLineIndex uint16
	}
	PeerType string
}

Rest all is same with a set of variables

videoTrackC     *webrtc.Track
   audioTrackC     *webrtc.Track
   videoTrackLockC = sync.RWMutex{}
   audioTrackLockC = sync.RWMutex{}

      pubReceiverC *webrtc.PeerConnection
   subSenderC   *webrtc.PeerConnection

The behavior seen is when tried from Chrome Browser with 2 instances one as publisher and other as subscriber, the default Room function works fine

But for above written WsConn function with Trickle ICE

  1. pubReceiverC ICE is connected and RTP packets are seen sent from browser and received at remoteTrack.Read(rtpBuf)
  2. subSenderC ICE is connected fine, Added with local tracks, is not sending any RTP, to browser

The only changes I did to given example is to modify websocket json a bit to support SDP and ICE with some extra info and apply the ICE messages as they arrive to respective peer connection

awesome work! I am going to migrate sfu-ws to /v3 this weekend. Really appreciate you starting early :)

Hey @sampleref

I am going to rewrite this entirely. The more time I spent with it the more I realized we needed massive changes. I will close this PR with a new sfu-ws example!

Hello @Sean-Der

Sure, Thanks for updating this example

I hope the master version which is updated recently would be using /v3 and can be referred

@sampleref when you get a chance tell me what you think! Trickle ICE is always on by default and tried to simplify as much as possible.

Sure I shall verify this at earliest and update here

Trickle ICE for which I actually started this discussion is working with /v3, Thanks for the support

Hello @Sean-Der
I have created a kind of push to talk app https://github.com/sampleref/gortpdemo/blob/master/ptt/ptthandler.go
where

  1. Local tracks one audio and video is shared to all recipients on peer connection, example
    https://github.com/sampleref/gortpdemo/blob/03ebf6ceff899ad17130a5c39fb235dbd680d87b/ptt/ptthandler.go#L87

  2. Multiple chrome peers connect with offer and answer and stay connected, but rtp from them is not sent to anyone

  3. On press of a key(floor) on being available, the RTP from that peer/track is pushed into local tracks in the above where RTP in turn flows to all of the recipients added with this local tracks

Given above scenario, what I observe is most of the attempts on writing RTP packets to local tracks which happen on and off from multiple peer connections( though only one at a time) three scenarios happen

  1. No RTP packets are pushed to Peers/Browsers from go code
  2. RTP packets are send out, and shown receiving in chrome://webrtc-internals but not played out
  3. Immediately on allowing at https://github.com/sampleref/gortpdemo/blob/03ebf6ceff899ad17130a5c39fb235dbd680d87b/ptt/ptthandler.go#L158 No RTP is sent, but after some random duration it works

Above 3 scenarios, not sure if any buffering is affecting this behavior here

I am using /v3 and not exactly sure if my use case can be addressed in above way

Please suggest if there is some better approach doing this or if anything missing

@sampleref That's not good! So here are the things that stand out to me.

  • An api object must not be shared amount PeerConnection, that could be causing problems!
  • TrackLocal is thread safe, you don't need to hold the lock when calling Write
  • You have a timing issue. The browser sees a giant gap in audio and is trying to get the lost samples. I think what you may need to always send packets, but instead send an empty Payload.

I will pull down the repo and give it a shot. Asking in https://pion.ly/slack might be better! GitHub issues are async (and I am the only one that really responds to them) but you get the whole community in Slack

@Sean-Der

I could address first 2 items mentioned, in https://github.com/sampleref/gortpdemo/blob/master/ptt/ptthandler.go

Still unable to listen audio on receiver track on browser, but shown as packets/bytes received in chrome://webrtc-internals

  • An api object must not be shared amount PeerConnection, that could be causing problems! - Done, using new api for each peer connection

  • TrackLocal is thread safe, you don't need to hold the lock when calling Write - Done

  • You have a timing issue. The browser sees a giant gap in audio and is trying to get the lost samples. I think what you may need to always send packets, but instead send an empty Payload.
    -> This one I am unable to figure out how to debug/validate, Can you please elaborate how to identify that this gap in audio is happening and how to approach - "always send packets, but instead send an empty Payload"