diff --git a/internal/engine/salutejazz/salutejazz.go b/internal/engine/salutejazz/salutejazz.go index d725eed..27f506f 100644 --- a/internal/engine/salutejazz/salutejazz.go +++ b/internal/engine/salutejazz/salutejazz.go @@ -199,6 +199,8 @@ func (s *Session) createPeerConnections(api *webrtc.API, config webrtc.Configura if track.Kind() != webrtc.RTPCodecTypeVideo { return } + logger.Infof("[salutejazz] remote video track: codec=%s stream=%s track=%s", + track.Codec().MimeType, track.StreamID(), track.ID()) if cb := s.videoTrackHandler(); cb != nil { cb(track, receiver) } @@ -560,6 +562,11 @@ func (s *Session) handleSubscriberOffer(payload map[string]any) { } func (s *Session) sendPublisherOffer() { + if err := s.sendPublisherTrackAdds(); err != nil { + logger.Debugf("send publisher track add error: %v", err) + return + } + offer, err := s.pcPub.CreateOffer(nil) if err != nil { logger.Debugf("create pub offer error: %v", err) @@ -588,6 +595,46 @@ func (s *Session) sendPublisherOffer() { s.wsMu.Unlock() } +func (s *Session) sendPublisherTrackAdds() error { + s.videoTrackMu.RLock() + tracks := append([]webrtc.TrackLocal(nil), s.videoTracks...) + s.videoTrackMu.RUnlock() + + for _, track := range tracks { + if track == nil || track.Kind() != webrtc.RTPCodecTypeVideo { + continue + } + if err := s.sendPublisherTrackAdd("VIDEO", "CAMERA", false); err != nil { + return err + } + } + return nil +} + +func (s *Session) sendPublisherTrackAdd(trackType, source string, muted bool) error { + s.wsMu.Lock() + defer s.wsMu.Unlock() + + if err := s.ws.WriteJSON(map[string]any{ + keyRoomID: s.roomID, + keyEvent: "media-in", + "groupId": s.groupID, + keyRequestID: uuid.New().String(), + keyPayload: map[string]any{ + "method": "rtc:track:add", + "cid": uuid.New().String(), + "track": map[string]any{ + "type": trackType, + "source": source, + "muted": muted, + }, + }, + }); err != nil { + return fmt.Errorf("write track add json: %w", err) + } + return nil +} + func (s *Session) handlePublisherAnswer(payload map[string]any) { desc, _ := payload["description"].(map[string]any) sdp, _ := desc["sdp"].(string) diff --git a/internal/engine/salutejazz/session_helpers_test.go b/internal/engine/salutejazz/session_helpers_test.go index fed3941..a422023 100644 --- a/internal/engine/salutejazz/session_helpers_test.go +++ b/internal/engine/salutejazz/session_helpers_test.go @@ -3,8 +3,11 @@ package salutejazz import ( "context" "errors" + "net/http" + "net/http/httptest" "testing" + "github.com/gorilla/websocket" "github.com/pion/webrtc/v4" ) @@ -110,3 +113,59 @@ func TestSessionCanSendVideoOnlyModes(t *testing.T) { t.Fatal("CanSend() = true for closed session") } } + +func TestSendPublisherTrackAddWritesJazzPayload(t *testing.T) { + msgCh := make(chan map[string]any, 1) + upgrader := websocket.Upgrader{} + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + t.Errorf("upgrade websocket: %v", err) + return + } + defer func() { _ = conn.Close() }() + + var msg map[string]any + if err := conn.ReadJSON(&msg); err != nil { + t.Errorf("read json: %v", err) + return + } + msgCh <- msg + })) + defer server.Close() + + wsURL := "ws" + server.URL[len("http"):] + conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil) + if err != nil { + t.Fatalf("dial websocket: %v", err) + } + defer func() { _ = conn.Close() }() + + s := &Session{ + roomID: "room-1", + groupID: "group-1", + ws: conn, + } + if err := s.sendPublisherTrackAdd("VIDEO", "CAMERA", false); err != nil { + t.Fatalf("sendPublisherTrackAdd() error = %v", err) + } + + msg := <-msgCh + if msg[keyRoomID] != "room-1" || msg[keyEvent] != "media-in" || msg["groupId"] != "group-1" { + t.Fatalf("unexpected envelope: %+v", msg) + } + payload, ok := msg[keyPayload].(map[string]any) + if !ok { + t.Fatalf("payload missing or wrong type: %+v", msg[keyPayload]) + } + if payload["method"] != "rtc:track:add" { + t.Fatalf("method = %v, want rtc:track:add", payload["method"]) + } + track, ok := payload["track"].(map[string]any) + if !ok { + t.Fatalf("track missing or wrong type: %+v", payload["track"]) + } + if track["type"] != "VIDEO" || track["source"] != "CAMERA" || track["muted"] != false { + t.Fatalf("track = %+v, want video camera unmuted", track) + } +}