Skip to content

Commit dbfc8a7

Browse files
committed
Improve the README and test regarding OnNegotiationNeeded event.
1 parent 0927670 commit dbfc8a7

File tree

3 files changed

+59
-15
lines changed

3 files changed

+59
-15
lines changed

README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,14 @@ When a client wants to publish a track to the SFU, usually the flow is like this
6969
See the [client_test.go](./client_test.go) for more details on how to publish and subscribe to tracks.
7070

7171

72-
### Renegotiation
73-
Usually, renegotiation will be needed when a new track is added or removed. The renegotiation can be initiated by the SFU if a new client joins the room and publishes a new track to the room. To broadcast the track to all clients, the SFU must renegotiate with each client to inform them about the new tracks.
74-
72+
### Client Renegotiation
73+
Usually, renegotiation will be needed when a new track is added or removed. The renegotiation can be initiated by the SFU if a new client joins the room and publishes a new track to the room. To broadcast the track to all clients, the SFU must renegotiate with each client to inform them about the new tracks. The renegotiation is can be handled in peerConnection.OnNegotiationNeeded event. This event will be triggered when the client needs to renegotiate with the SFU.
74+
75+
> [!IMPORTANT]
76+
> When adding a track and using the `OnNegotiationNeeded` event, issues may arise if you previously added a single `recvOnly` transceiver and then add more than one `sendonly` or `sendrecv` transceiver. The `OnNegotiationNeeded` event may be triggered multiple times. To prevent this, follow these rules:
77+
> - If you add a `recvonly` transceiver and want to add more tracks, use `PeerConnection.AddTrack()` so the send direction of the existing transceiver will be used.
78+
> - Always balance the number of transceivers by adding the same number of additional tracks as the `recvonly` transceivers previously added. For example, if you added 2 `recvonly` transceivers, you can add 2 tracks with `PeerConnection.AddTrack()`. Adding more than 2 tracks will cause `OnNegotiationNeeded` to be called multiple times. It is common to add `recvonly` transceivers first for audio and video, then add more tracks later when the client wants to share the screen or other media.
79+
7580
#### Renegotiation from the SFU
7681
To wait for the renegotiation process from SFU to make sure you receive a new track once published in a room, you can do this:
7782
1. When creating a new client after calling `currentRoom.addClient()` method, you can listen to the `client.OnRenegotiation` event. The event will be triggered when the SFU is trying to renegotiate with the client.

negotiation_test.go

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ connected:
7171
assert.Equal(t, ClientStateActive, client.state.Load())
7272
}
7373

74-
func TestOnNegotiationNeededCalledOnce(t *testing.T) {
74+
func TestOnNegotiationNeededCalledTwice(t *testing.T) {
7575
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
7676
defer cancel()
7777

@@ -158,7 +158,7 @@ func TestOnNegotiationNeededCalledOnce(t *testing.T) {
158158

159159
client.OnAllowedRemoteRenegotiation(func() {
160160
TestLogger.Infof("allowed remote renegotiation")
161-
negotiate(pc, client, TestLogger, true)
161+
162162
})
163163

164164
// Override OnNegotiationNeeded to count calls and still perform negotiation
@@ -185,17 +185,58 @@ func TestOnNegotiationNeededCalledOnce(t *testing.T) {
185185
}
186186
})
187187

188-
// Add tracks to trigger negotiation
188+
// Add track to trigger negotiation
189+
pc.AddTransceiverFromKind(webrtc.RTPCodecTypeVideo, webrtc.RTPTransceiverInit{
190+
Direction: webrtc.RTPTransceiverDirectionRecvonly,
191+
})
192+
pc.AddTransceiverFromKind(webrtc.RTPCodecTypeAudio, webrtc.RTPTransceiverInit{
193+
Direction: webrtc.RTPTransceiverDirectionRecvonly,
194+
})
195+
196+
// Wait until the first negotiation is done
197+
timeout := time.After(15 * time.Second)
198+
for {
199+
select {
200+
case <-time.After(200 * time.Millisecond):
201+
if negotiationCount > 0 {
202+
t.Logf("Negotiation needed called %d times", negotiationCount)
203+
goto negotiationDone
204+
}
205+
case <-timeout:
206+
t.Fatal("Timeout waiting for negotiation needed to be called")
207+
case <-ctx.Done():
208+
t.Fatal("Test context cancelled")
209+
}
210+
}
211+
212+
negotiationDone:
213+
214+
// Add more tracks to re-trigger negotiation
189215
tracks, _ := GetStaticTracks(clientContext, clientContext, "test-peer", false)
190216
SetPeerConnectionTracks(clientContext, pc, tracks)
191217

192-
// Wait a bit for any potential additional negotiation calls
193-
time.Sleep(3 * time.Second)
218+
// Wait for the second negotiation to be done
219+
timeout = time.After(15 * time.Second)
220+
for {
221+
select {
222+
case <-time.After(200 * time.Millisecond):
223+
if negotiationCount > 1 {
224+
t.Logf("Negotiation needed called %d times", negotiationCount)
225+
goto finalCheck
226+
}
227+
case <-timeout:
228+
t.Fatal("Timeout waiting for second negotiation needed to be called")
229+
case <-ctx.Done():
230+
t.Fatal("Test context cancelled")
231+
}
232+
}
233+
234+
finalCheck:
194235

195236
// Verify OnNegotiationNeeded was called exactly once
196237
mu.Lock()
197238
finalCount := negotiationCount
198239
mu.Unlock()
199240

200-
assert.Equal(t, 1, finalCount, "OnNegotiationNeeded should be called exactly once, but was called %d times", finalCount)
241+
assert.Equal(t, 2, finalCount, "OnNegotiationNeeded should be called exactly twice, but was called %d times", finalCount)
201242
}

testhelper.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -265,9 +265,7 @@ func GetStaticAudioTrack(ctx, iceConnectedCtx context.Context, trackID, streamID
265265

266266
func SetPeerConnectionTracks(ctx context.Context, peerConnection *webrtc.PeerConnection, tracks []*webrtc.TrackLocalStaticSample) {
267267
for _, track := range tracks {
268-
rtpTranscv, trackErr := peerConnection.AddTransceiverFromTrack(track, webrtc.RTPTransceiverInit{
269-
Direction: webrtc.RTPTransceiverDirectionSendonly,
270-
})
268+
sender, trackErr := peerConnection.AddTrack(track)
271269
if trackErr != nil {
272270
panic(trackErr)
273271
}
@@ -280,8 +278,8 @@ func SetPeerConnectionTracks(ctx context.Context, peerConnection *webrtc.PeerCon
280278
ctxx, cancel := context.WithCancel(ctx)
281279

282280
defer func() {
283-
if rtpTranscv != nil {
284-
_ = rtpTranscv.Stop()
281+
if sender != nil {
282+
_ = sender.Stop()
285283
}
286284
}()
287285

@@ -292,7 +290,7 @@ func SetPeerConnectionTracks(ctx context.Context, peerConnection *webrtc.PeerCon
292290
case <-ctxx.Done():
293291
return
294292
default:
295-
if _, _, rtcpErr := rtpTranscv.Sender().Read(rtcpBuf); rtcpErr != nil {
293+
if _, _, rtcpErr := sender.Read(rtcpBuf); rtcpErr != nil {
296294
return
297295
}
298296

0 commit comments

Comments
 (0)