@@ -25,6 +25,10 @@ import (
25
25
// a resource may register with a SharedConn which supports WebRTC.
26
26
type OnTrackCB func (tr * webrtc.TrackRemote , r * webrtc.RTPReceiver )
27
27
28
+ //nolint
29
+ // The following describes the SharedConn lifetime for viam-server's modmanager communicating with
30
+ // modules it has spawned.
31
+ //
28
32
// SharedConn wraps both a GRPC connection & (optionally) a peer connection & controls access to both.
29
33
// For modules, the grpc connection is over a Unix socket. The WebRTC `PeerConnection` is made
30
34
// separately. The `SharedConn` continues to implement the `rpc.ClientConn` interface by pairing up
@@ -71,20 +75,70 @@ type SharedConn struct {
71
75
// `peerConnMu` synchronizes changes to the underlying `peerConn`. Such that calls consecutive
72
76
// calls to `GrpcConn` and `PeerConn` will return connections from the same (or newer, but not
73
77
// prior) "generations".
74
- peerConnMu sync.Mutex
75
- peerConn * webrtc.PeerConnection
76
- peerConnReady <- chan struct {}
77
- peerConnClosed <- chan struct {}
78
+ peerConnMu sync.Mutex
79
+ peerConn * webrtc.PeerConnection
80
+ peerConnReady <- chan struct {}
78
81
// peerConnFailed gets closed when a PeerConnection fails to connect. The peerConn pointer is
79
82
// set to nil before this channel is closed.
80
83
peerConnFailed chan struct {}
81
84
82
85
onTrackCBByTrackNameMu sync.Mutex
83
86
onTrackCBByTrackName map [string ]OnTrackCB
84
87
88
+ // isConnectedToViamServer identifies whether this SharedConn is running inside a viam-server
89
+ // talking to a module, or a module talking to a viam-server. We use this to determine whether
90
+ // to use long names or short names for PeerConnection video (or audio) track names.
91
+ isConnectedToViamServer bool
92
+
85
93
logger logging.Logger
86
94
}
87
95
96
+ // NewSharedConnForModule acts as a constructor for `SharedConn` for modules that are communicating
97
+ // back to their parent viam-server.
98
+ func NewSharedConnForModule (grpcConn rpc.ClientConn , peerConn * webrtc.PeerConnection , logger logging.Logger ) * SharedConn {
99
+ // We must be passed a ready connection.
100
+ pcReady := make (chan struct {})
101
+ close (pcReady )
102
+
103
+ ret := & SharedConn {
104
+ peerConn : peerConn ,
105
+ peerConnReady : pcReady ,
106
+ // We were passed in a ready connection. Only create this for when `Close` is called.
107
+ peerConnFailed : make (chan struct {}),
108
+ onTrackCBByTrackName : make (map [string ]OnTrackCB ),
109
+ isConnectedToViamServer : true ,
110
+ logger : logger ,
111
+ }
112
+ ret .grpcConn .ReplaceConn (grpcConn )
113
+
114
+ ret .peerConn .OnTrack (func (trackRemote * webrtc.TrackRemote , rtpReceiver * webrtc.RTPReceiver ) {
115
+ ret .onTrackCBByTrackNameMu .Lock ()
116
+ onTrackCB , ok := ret .onTrackCBByTrackName [trackRemote .StreamID ()]
117
+ ret .onTrackCBByTrackNameMu .Unlock ()
118
+ if ! ok {
119
+ msg := "Callback not found for StreamID: %s, keys(resOnTrackCBs): %#v"
120
+ ret .logger .Errorf (msg , trackRemote .StreamID (), maps .Keys (ret .onTrackCBByTrackName ))
121
+ return
122
+ }
123
+ onTrackCB (trackRemote , rtpReceiver )
124
+ })
125
+
126
+ return ret
127
+ }
128
+
129
+ // IsConnectedToModule returns whether this shared conn is being used to communicate with a module.
130
+ func (sc * SharedConn ) IsConnectedToModule () bool {
131
+ return ! sc .isConnectedToViamServer
132
+ }
133
+
134
+ // IsConnectedToViamServer returns whether this shared conn is being used to communicate with a
135
+ // viam-server. Note this implies the client is running within a module process. Typical
136
+ // clients/remote connections are a pure webrtc connection. As opposed to a frankenstein tcp/unix
137
+ // socket + webrtc connection.
138
+ func (sc * SharedConn ) IsConnectedToViamServer () bool {
139
+ return sc .isConnectedToViamServer
140
+ }
141
+
88
142
// Invoke forwards to the underlying GRPC Connection.
89
143
func (sc * SharedConn ) Invoke (
90
144
ctx context.Context ,
@@ -143,8 +197,8 @@ func (sc *SharedConn) PeerConn() *webrtc.PeerConnection {
143
197
return ret
144
198
}
145
199
146
- // ResetConn acts as a constructor for `SharedConn`. ResetConn replaces the underlying
147
- // connection objects in addition to some other initialization.
200
+ // ResetConn acts as a constructor for `SharedConn` inside the viam-server (not modules). ResetConn
201
+ // replaces the underlying connection objects in addition to some other initialization.
148
202
//
149
203
// The first call to `ResetConn` is guaranteed to happen before any access to connection objects
150
204
// happens. But subequent calls can be entirely asynchronous to components/services accessing
@@ -193,7 +247,16 @@ func (sc *SharedConn) ResetConn(conn rpc.ClientConn, moduleLogger logging.Logger
193
247
}
194
248
195
249
sc .peerConn = peerConn
196
- sc .peerConnReady , sc .peerConnClosed , err = rpc .ConfigureForRenegotiation (peerConn , rpc .PeerRoleClient , sc .logger )
250
+ // When communicating with modules, we may both call `AddStream` on the module (we are the
251
+ // client) _and_ the module may call `AddStream` on us (for example, to stream video data from a
252
+ // different module). Thus we must use the `PeerRoleServer` role such that we install an
253
+ // `OnNegotiationNeeded` callback for when the stream server handle for `AddStream` attempts to
254
+ // call `peerConn.AddTrack`.
255
+ //
256
+ // That said, it's not been rigorously exercised that when both ends initiate a renegotiation
257
+ // for different video tracks, that we end up in a state where both video tracks are
258
+ // successfully created.
259
+ sc .peerConnReady , _ , err = rpc .ConfigureForRenegotiation (peerConn , rpc .PeerRoleServer , sc .logger )
197
260
if err != nil {
198
261
sc .logger .Warnw ("Unable to create optional renegotiation channel for module. Ignoring." , "err" , err )
199
262
return
0 commit comments