From 4e29ef2d9fe6b75783f90ddfef765d0d63760612 Mon Sep 17 00:00:00 2001 From: KevinVision Date: Thu, 16 Jan 2025 19:09:46 +0800 Subject: [PATCH] Delete rejected transceiver. --- examples/delete-transceiver/README.md | 18 ++++ examples/delete-transceiver/index.html | 117 ++++++++++++++++++++++ examples/delete-transceiver/main.go | 133 +++++++++++++++++++++++++ peerconnection.go | 59 +++++++++-- sdp.go | 12 ++- 5 files changed, 330 insertions(+), 9 deletions(-) create mode 100644 examples/delete-transceiver/README.md create mode 100644 examples/delete-transceiver/index.html create mode 100644 examples/delete-transceiver/main.go diff --git a/examples/delete-transceiver/README.md b/examples/delete-transceiver/README.md new file mode 100644 index 00000000000..3dfa7fdc616 --- /dev/null +++ b/examples/delete-transceiver/README.md @@ -0,0 +1,18 @@ +# delete-transceiver +delete-transceiver demonstrates Pion WebRTC's ability to delete rejected transceivers. + +## Instructions + +### Download delete-transceiver +This example requires you to clone the repo since it is serving static HTML. + +``` +git clone https://github.com/pion/webrtc.git +cd webrtc/examples/delete-transceiver +``` + +### Run delete-transceiver +Execute `go run *.go` + +### Open the Web UI +Open [http://localhost:8080](http://localhost:8080). This remote peerconnection will use single port 8443. diff --git a/examples/delete-transceiver/index.html b/examples/delete-transceiver/index.html new file mode 100644 index 00000000000..a7190376137 --- /dev/null +++ b/examples/delete-transceiver/index.html @@ -0,0 +1,117 @@ + + + + + ice-single-port + + + +

ICE Selected Pairs

+

+ + + + + \ No newline at end of file diff --git a/examples/delete-transceiver/main.go b/examples/delete-transceiver/main.go new file mode 100644 index 00000000000..695ae14de87 --- /dev/null +++ b/examples/delete-transceiver/main.go @@ -0,0 +1,133 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + +//go:build !js +// +build !js + +// delete-transceiver demonstrates Pion WebRTC's ability to delete rejected transceivers. +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pion/ice/v4" + "github.com/pion/webrtc/v4" +) + +var api *webrtc.API //nolint +var peerConnection *webrtc.PeerConnection + +// Everything below is the Pion WebRTC API! Thanks for using it ❤️. +func doOffer(w http.ResponseWriter, r *http.Request) { + var err error + peerConnection, err = api.NewPeerConnection(webrtc.Configuration{}) + if err != nil { + panic(err) + } + + // Set the handler for ICE connection state + // This will notify you when the peer has connected/disconnected + peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) { + fmt.Printf("ICE Connection State has changed: %s\n", connectionState.String()) + }) + + // Send the current time via a DataChannel to the remote peer every 3 seconds + peerConnection.OnDataChannel(func(d *webrtc.DataChannel) { + d.OnOpen(func() { + for range time.Tick(time.Second * 3) { + if err = d.SendText(time.Now().String()); err != nil { + panic(err) + } + } + }) + }) + + var offer webrtc.SessionDescription + if err = json.NewDecoder(r.Body).Decode(&offer); err != nil { + panic(err) + } + + if err = peerConnection.SetRemoteDescription(offer); err != nil { + panic(err) + } + + // Create channel that is blocked until ICE Gathering is complete + gatherComplete := webrtc.GatheringCompletePromise(peerConnection) + + answer, err := peerConnection.CreateAnswer(nil) + if err != nil { + panic(err) + } else if err = peerConnection.SetLocalDescription(answer); err != nil { + panic(err) + } + + // Block until ICE Gathering is complete, disabling trickle ICE + // we do this because we only can exchange one signaling message + // in a production application you should exchange ICE Candidates via OnICECandidate + <-gatherComplete + + response, err := json.Marshal(*peerConnection.LocalDescription()) + if err != nil { + panic(err) + } + + w.Header().Set("Content-Type", "application/json") + if _, err := w.Write(response); err != nil { + panic(err) + } +} + +func doUpdate(w http.ResponseWriter, r *http.Request) { + var err error + var offer webrtc.SessionDescription + if err = json.NewDecoder(r.Body).Decode(&offer); err != nil { + panic(err) + } + + if err = peerConnection.SetRemoteDescription(offer); err != nil { + panic(err) + } + + answer, err := peerConnection.CreateAnswer(nil) + if err != nil { + panic(err) + } else if err = peerConnection.SetLocalDescription(answer); err != nil { + panic(err) + } + response, err := json.Marshal(answer) + if err != nil { + panic(err) + } + + w.Header().Set("Content-Type", "application/json") + if _, err := w.Write(response); err != nil { + panic(err) + } +} + +func main() { + // Create a SettingEngine, this allows non-standard WebRTC behavior + settingEngine := webrtc.SettingEngine{} + + // Listen on UDP Port 8443, will be used for all WebRTC traffic + mux, err := ice.NewMultiUDPMuxFromPort(8443) + if err != nil { + panic(err) + } + fmt.Printf("Listening for WebRTC traffic at %d\n", 8443) + settingEngine.SetICEUDPMux(mux) + + // Create a new API using our SettingEngine + api = webrtc.NewAPI(webrtc.WithSettingEngine(settingEngine)) + + http.Handle("/", http.FileServer(http.Dir("."))) + http.HandleFunc("/doOffer", doOffer) + http.HandleFunc("/doUpdate", doUpdate) + + fmt.Println("Open http://localhost:8080 to access this demo") + // nolint: gosec + panic(http.ListenAndServe(":8080", nil)) +} diff --git a/peerconnection.go b/peerconnection.go index 5e613c224cc..6f86653f1f1 100644 --- a/peerconnection.go +++ b/peerconnection.go @@ -847,7 +847,6 @@ func (pc *PeerConnection) CreateAnswer(*AnswerOptions) (SessionDescription, erro if err != nil { return SessionDescription{}, err } - desc := SessionDescription{ Type: SDPTypeAnswer, SDP: string(sdpBytes), @@ -1007,7 +1006,8 @@ func (pc *PeerConnection) SetLocalDescription(desc SessionDescription) error { weAnswer := desc.Type == SDPTypeAnswer remoteDesc := pc.RemoteDescription() if weAnswer && remoteDesc != nil { - _ = setRTPTransceiverCurrentDirection(&desc, currentTransceivers, false) + rejected := []string{} + _ = setRTPTransceiverCurrentDirection(&desc, currentTransceivers, false, &rejected) if err := pc.startRTPSenders(currentTransceivers); err != nil { return err } @@ -1015,6 +1015,9 @@ func (pc *PeerConnection) SetLocalDescription(desc SessionDescription) error { pc.ops.Enqueue(func() { pc.startRTP(haveLocalDescription, remoteDesc, currentTransceivers) }) + pc.mu.Lock() + pc.removeRTPTransceiver(rejected) + pc.mu.Unlock() } mediaSection, ok := selectCandidateMediaSection(desc.parsed) @@ -1184,7 +1187,8 @@ func (pc *PeerConnection) SetRemoteDescription(desc SessionDescription) error { if isRenegotiation { if weOffer { - _ = setRTPTransceiverCurrentDirection(&desc, currentTransceivers, true) + rejected := []string{} + _ = setRTPTransceiverCurrentDirection(&desc, currentTransceivers, true, &rejected) if err = pc.startRTPSenders(currentTransceivers); err != nil { return err } @@ -1192,6 +1196,9 @@ func (pc *PeerConnection) SetRemoteDescription(desc SessionDescription) error { pc.ops.Enqueue(func() { pc.startRTP(true, &desc, currentTransceivers) }) + pc.mu.Lock() + pc.removeRTPTransceiver(rejected) + pc.mu.Unlock() } return nil } @@ -1214,7 +1221,7 @@ func (pc *PeerConnection) SetRemoteDescription(desc SessionDescription) error { // Start the networking in a new routine since it will block until // the connection is actually established. if weOffer { - _ = setRTPTransceiverCurrentDirection(&desc, currentTransceivers, true) + _ = setRTPTransceiverCurrentDirection(&desc, currentTransceivers, true, nil) if err := pc.startRTPSenders(currentTransceivers); err != nil { return err } @@ -1277,7 +1284,8 @@ func (pc *PeerConnection) startReceiver(incoming trackDetails, receiver *RTPRece } } -func setRTPTransceiverCurrentDirection(answer *SessionDescription, currentTransceivers []*RTPTransceiver, weOffer bool) error { +// rejected is only meaningful when SessionDescription is answer +func setRTPTransceiverCurrentDirection(answer *SessionDescription, currentTransceivers []*RTPTransceiver, weOffer bool, rejected *[]string) error { currentTransceivers = append([]*RTPTransceiver{}, currentTransceivers...) for _, media := range answer.parsed.MediaDescriptions { midValue := getMidValue(media) @@ -1318,7 +1326,10 @@ func setRTPTransceiverCurrentDirection(answer *SessionDescription, currentTransc if !weOffer && direction == RTPTransceiverDirectionSendonly && t.Sender() == nil { direction = RTPTransceiverDirectionInactive } - + // reject transceiver if it is inactive + if rejected != nil && media.MediaName.Port.Value == 0 && direction == RTPTransceiverDirectionInactive { + *rejected = append(*rejected, midValue) + } t.setCurrentDirection(direction) } return nil @@ -2264,6 +2275,31 @@ func (pc *PeerConnection) addRTPTransceiver(t *RTPTransceiver) { pc.onNegotiationNeeded() } +// removeRTPTransceiver remove inactive +// and fires onNegotiationNeeded; +// caller of this method should hold `pc.mu` lock +func (pc *PeerConnection) removeRTPTransceiver(mids []string) { + if len(mids) == 0 { + return + } + n := 0 + for _, transceiver := range pc.rtpTransceivers { + needDelete := false + for _, mid := range mids { + if transceiver.Mid() == mid { + transceiver.Stop() + needDelete = true + } + } + if !needDelete { + pc.rtpTransceivers[n] = transceiver + n++ + } + } + pc.rtpTransceivers = pc.rtpTransceivers[:n] + pc.onNegotiationNeeded() +} + // CurrentLocalDescription represents the local description that was // successfully negotiated the last time the PeerConnection transitioned // into the stable state plus any local candidates that have been generated @@ -2628,7 +2664,16 @@ func (pc *PeerConnection) generateMatchedSDP(transceivers []*RTPTransceiver, use mediaTransceivers := []*RTPTransceiver{t} extensions, _ := rtpExtensionsFromMediaDescription(media) - mediaSections = append(mediaSections, mediaSection{id: midValue, transceivers: mediaTransceivers, matchExtensions: extensions, rids: getRids(media)}) + rejected := false + if media.MediaName.Port.Value == 0 { + for _, attr := range media.Attributes { + if attr.Key == sdp.AttrKeyInactive { + rejected = true + break + } + } + } + mediaSections = append(mediaSections, mediaSection{id: midValue, transceivers: mediaTransceivers, matchExtensions: extensions, rids: getRids(media), rejected: rejected}) } } diff --git a/sdp.go b/sdp.go index b07a55326c9..dc8c95723dc 100644 --- a/sdp.go +++ b/sdp.go @@ -467,10 +467,12 @@ func addTransceiverSDP( media.WithValueAttribute("rtcp-fb", fmt.Sprintf("%d %s %s", codec.PayloadType, feedback.Type, feedback.Parameter)) } } - if len(codecs) == 0 { + if len(codecs) == 0 || mediaSection.rejected { // If we are sender and we have no codecs throw an error early if t.Sender() != nil { - return false, ErrSenderWithNoCodecs + if !mediaSection.rejected { + return false, ErrSenderWithNoCodecs + } } // Explicitly reject track if we don't have the codec @@ -492,8 +494,13 @@ func addTransceiverSDP( Address: "0.0.0.0", }, }, + Attributes: []sdp.Attribute{ + {Key: RTPTransceiverDirectionInactive.String(), Value: ""}, + {Key: "mid", Value: midValue}, + }, }) return false, nil + } directions := []RTPTransceiverDirection{} @@ -564,6 +571,7 @@ type mediaSection struct { data bool matchExtensions map[string]int rids []*simulcastRid + rejected bool } func bundleMatchFromRemote(matchBundleGroup *string) func(mid string) bool {