Send updated offers to subscribers after publisher renegotiations

When a publisher has a connection the publisher can update the
connection (for example, to add a video track to an audio only
connection) by sending an updated offer to Janus. Janus detects that,
adjusts the connection and then sends back an answer. Once the publisher
connection is updated Janus starts a renegotiation for the subscribers
and generates the offers to be sent to them.

The signaling server did not handle the event, so the offers were not
sent and the subscriber connections were not updated. Now the offers are
sent as needed, which makes possible for the renegotiation to be
completed by the clients.

In this case the "offer" message will also include an "update" parameter
so clients can differentiate between offers to create new connections or
update the existing one.

Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
This commit is contained in:
Daniel Calviño Sánchez 2022-02-10 20:19:25 +01:00
parent 7fbd0b4580
commit 020cbaf49d
6 changed files with 71 additions and 1 deletions

View file

@ -647,4 +647,5 @@ type AnswerOfferMessage struct {
Type string `json:"type"`
RoomType string `json:"roomType"`
Payload map[string]interface{} `json:"payload"`
Update bool `json:"update,omitempty"`
}

View file

@ -563,6 +563,34 @@ func (s *ClientSession) SetClient(client *Client) *Client {
return prev
}
func (s *ClientSession) sendOffer(client McuClient, sender string, streamType string, offer map[string]interface{}) {
offer_message := &AnswerOfferMessage{
To: s.PublicId(),
From: sender,
Type: "offer",
RoomType: streamType,
Payload: offer,
Update: true,
}
offer_data, err := json.Marshal(offer_message)
if err != nil {
log.Println("Could not serialize offer", offer_message, err)
return
}
response_message := &ServerMessage{
Type: "message",
Message: &MessageServerMessage{
Sender: &MessageServerMessageSender{
Type: "session",
SessionId: sender,
},
Data: (*json.RawMessage)(&offer_data),
},
}
s.sendMessageUnlocked(response_message)
}
func (s *ClientSession) sendCandidate(client McuClient, sender string, streamType string, candidate interface{}) {
candidate_message := &AnswerOfferMessage{
To: s.PublicId(),
@ -628,6 +656,18 @@ func (s *ClientSession) SendMessages(messages []*ServerMessage) bool {
return true
}
func (s *ClientSession) OnUpdateOffer(client McuClient, offer map[string]interface{}) {
s.mu.Lock()
defer s.mu.Unlock()
for _, sub := range s.subscribers {
if sub.Id() == client.Id() {
s.sendOffer(client, sub.Publisher(), client.StreamType(), offer)
return
}
}
}
func (s *ClientSession) OnIceCandidate(client McuClient, candidate interface{}) {
s.mu.Lock()
defer s.mu.Unlock()

View file

@ -50,6 +50,8 @@ const (
type McuListener interface {
PublicId() string
OnUpdateOffer(client McuClient, offer map[string]interface{})
OnIceCandidate(client McuClient, candidate interface{})
OnIceCompleted(client McuClient)

View file

@ -1063,7 +1063,12 @@ func (p *mcuJanusSubscriber) handleEvent(event *janus.EventMsg) {
log.Printf("Subscriber %d: associated room has been destroyed, closing", p.handleId)
go p.Close(ctx)
case "event":
// Ignore events like selected substream / temporal layer.
// Handle renegotiations, but ignore other events like selected
// substream / temporal layer.
if getPluginStringValue(event.Plugindata, pluginVideoRoom, "configured") == "ok" &&
event.Jsep != nil && event.Jsep["type"] == "offer" && event.Jsep["sdp"] != nil {
p.listener.OnUpdateOffer(p, event.Jsep)
}
case "slow_link":
// Ignore, processed through "handleSlowLink" in the general events.
default:

View file

@ -107,6 +107,8 @@ func (c *mcuProxyPubSubCommon) doSendMessage(ctx context.Context, msg *ProxyClie
func (c *mcuProxyPubSubCommon) doProcessPayload(client McuClient, msg *PayloadProxyServerMessage) {
switch msg.Type {
case "offer":
c.listener.OnUpdateOffer(client, msg.Payload["offer"].(map[string]interface{}))
case "candidate":
c.listener.OnIceCandidate(client, msg.Payload["candidate"])
default:

View file

@ -122,6 +122,26 @@ func (s *ProxySession) SetClient(client *ProxyClient) *ProxyClient {
return prev
}
func (s *ProxySession) OnUpdateOffer(client signaling.McuClient, offer map[string]interface{}) {
id := s.proxy.GetClientId(client)
if id == "" {
log.Printf("Received offer %+v from unknown %s client %s (%+v)", offer, client.StreamType(), client.Id(), client)
return
}
msg := &signaling.ProxyServerMessage{
Type: "payload",
Payload: &signaling.PayloadProxyServerMessage{
Type: "offer",
ClientId: id,
Payload: map[string]interface{}{
"offer": offer,
},
},
}
s.sendMessage(msg)
}
func (s *ProxySession) OnIceCandidate(client signaling.McuClient, candidate interface{}) {
id := s.proxy.GetClientId(client)
if id == "" {