@@ -6,9 +6,11 @@ import {
6
6
ServerMessage ,
7
7
VideoSubscription ,
8
8
} from "./sfu.ts" ;
9
+ import { atom , type PreinitializedWritableAtom } from "nanostores" ;
9
10
10
11
const MAX_DOWNSTREAMS = 16 ;
11
12
const LAST_N_AUDIO = 3 ;
13
+ const DEBOUNCE_DELAY_MS = 500 ;
12
14
13
15
// Internal Ids
14
16
type ParticipantId = string ;
@@ -18,10 +20,12 @@ interface VideoSlot {
18
20
participantId ?: ParticipantId ;
19
21
}
20
22
21
- interface ParticipantMeta {
23
+ export interface ParticipantMeta {
22
24
externalParticipantId : string ;
23
25
participantId : string ;
24
26
media : MediaConfig ;
27
+ stream : MediaStream ;
28
+ maxHeight : number ;
25
29
}
26
30
27
31
export interface ClientCoreConfig {
@@ -42,9 +46,10 @@ export class ClientCore {
42
46
#audioSlots: RTCRtpTransceiver [ ] ;
43
47
44
48
#participants: Record < ParticipantId , ParticipantMeta > ;
49
+ #timeoutId: ReturnType < typeof setTimeout > | null ;
45
50
46
51
onStateChanged = ( state : RTCPeerConnectionState ) => { } ;
47
- onTrack = ( track : RTCPeerConnection ) => { } ;
52
+ onNewParticipant = ( participant : ParticipantMeta ) => { } ;
48
53
49
54
constructor ( cfg : ClientCoreConfig ) {
50
55
this . #sfuUrl = cfg . sfuUrl ;
@@ -57,6 +62,7 @@ export class ClientCore {
57
62
this . #audioSlots = [ ] ;
58
63
this . #participants = { } ;
59
64
this . #sequence = 0 ;
65
+ this . #timeoutId = null ;
60
66
61
67
this . #pc = new RTCPeerConnection ( ) ;
62
68
this . #pc. onconnectionstatechange = ( ) => {
@@ -162,6 +168,8 @@ export class ClientCore {
162
168
externalParticipantId : stream . externalParticipantId ,
163
169
participantId : stream . participantId ,
164
170
media : stream . media ,
171
+ stream : new MediaStream ( ) ,
172
+ maxHeight : 0 , // default invisible until the UI tells us to render
165
173
} ;
166
174
this . #participants[ stream . participantId ] = meta ;
167
175
newParticipants . push ( meta ) ;
@@ -185,6 +193,8 @@ export class ClientCore {
185
193
186
194
slot . participantId = participant . participantId ;
187
195
}
196
+
197
+ this . #triggerSubscriptionFeedback( ) ;
188
198
}
189
199
190
200
#close( error ?: string ) {
@@ -197,6 +207,37 @@ export class ClientCore {
197
207
this . #closed = true ;
198
208
}
199
209
210
+ updateSubscription ( participantId : string , maxHeight : number ) {
211
+ if ( participantId in this . #participants) {
212
+ this . #participants[ participantId ] . maxHeight = maxHeight ;
213
+ }
214
+
215
+ this . #triggerSubscriptionFeedback( ) ;
216
+ }
217
+
218
+ #triggerSubscriptionFeedback( ) {
219
+ if ( this . #timeoutId) {
220
+ return ;
221
+ }
222
+
223
+ this . #timeoutId = setTimeout ( ( ) => {
224
+ const subscriptions : ParticipantSubscription [ ] = Object . values (
225
+ this . #participants,
226
+ ) . map ( ( p ) => ( {
227
+ participantId : p . participantId ,
228
+ videoSettings : {
229
+ maxHeight : p . maxHeight ,
230
+ } ,
231
+ } ) ) ;
232
+ this . #sendRpc( {
233
+ oneofKind : "videoSubscription" ,
234
+ videoSubscription : {
235
+ subscriptions,
236
+ } ,
237
+ } ) ;
238
+ } , DEBOUNCE_DELAY_MS ) ;
239
+ }
240
+
200
241
async connect ( room : string , participant : string ) {
201
242
if ( this . #closed) {
202
243
const errorMessage =
@@ -260,6 +301,9 @@ export class ClientCore {
260
301
}
261
302
262
303
disconnect ( ) {
304
+ if ( this . #timeoutId) {
305
+ clearTimeout ( this . #timeoutId) ;
306
+ }
263
307
this . #pc. close ( ) ;
264
308
}
265
309
0 commit comments