Skip to content

Commit 8eb7729

Browse files
committed
use nanostores to manage state and participants
1 parent a5f85ca commit 8eb7729

File tree

3 files changed

+37
-30
lines changed

3 files changed

+37
-30
lines changed

client-web/index.html

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@
1010
</head>
1111

1212
<body>
13-
<my-element>
14-
<h1>Vite + Lit</h1>
15-
</my-element>
13+
<video></video>
1614
</body>
1715

1816
</html>

client-web/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ import { ClientCore } from "./lib";
66
maxDownstreams: 1,
77
});
88

9+
client.$state.listen((v) => console.log(v));
10+
client.$participants.listen((newValue, _, changed) => {
11+
// assign this to a video element
12+
console.log(changed);
13+
});
914
await client.connect("default", `alice-${Math.round(Math.random() * 100)}`);
1015

1116
const stream = await navigator.mediaDevices.getUserMedia({

client-web/src/lib/core.ts

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ import {
44
ParticipantStream,
55
ParticipantSubscription,
66
ServerMessage,
7-
VideoSubscription,
87
} from "./sfu.ts";
9-
import { atom, type PreinitializedWritableAtom } from "nanostores";
8+
import {
9+
atom,
10+
map,
11+
type PreinitializedMapStore,
12+
type PreinitializedWritableAtom,
13+
} from "nanostores";
1014

1115
const MAX_DOWNSTREAMS = 16;
1216
const LAST_N_AUDIO = 3;
@@ -44,12 +48,12 @@ export class ClientCore {
4448

4549
#videoSlots: VideoSlot[];
4650
#audioSlots: RTCRtpTransceiver[];
47-
48-
#participants: Record<ParticipantId, ParticipantMeta>;
4951
#timeoutId: ReturnType<typeof setTimeout> | null;
5052

51-
onStateChanged = (state: RTCPeerConnectionState) => {};
52-
onNewParticipant = (participant: ParticipantMeta) => {};
53+
$participants: PreinitializedMapStore<
54+
Record<ParticipantId, PreinitializedMapStore<ParticipantMeta>>
55+
>;
56+
$state: PreinitializedWritableAtom<RTCPeerConnectionState>;
5357

5458
constructor(cfg: ClientCoreConfig) {
5559
this.#sfuUrl = cfg.sfuUrl;
@@ -60,17 +64,17 @@ export class ClientCore {
6064
this.#closed = false;
6165
this.#videoSlots = [];
6266
this.#audioSlots = [];
63-
this.#participants = {};
6467
this.#sequence = 0;
6568
this.#timeoutId = null;
69+
this.$participants = map({});
70+
this.$state = atom("new");
6671

6772
this.#pc = new RTCPeerConnection();
6873
this.#pc.onconnectionstatechange = () => {
6974
const connectionState = this.#pc.connectionState;
7075
console.debug(`PeerConnection state changed: ${connectionState}`);
71-
if (connectionState === "connected") {
72-
this.onStateChanged(connectionState);
73-
} else if (
76+
this.$state.set(connectionState);
77+
if (
7478
connectionState === "failed" || connectionState === "closed" ||
7579
connectionState === "disconnected"
7680
) {
@@ -154,15 +158,18 @@ export class ClientCore {
154158
for (const stream of streams) {
155159
if (!stream.media) {
156160
// participant has left
157-
delete this.#participants[stream.participantId];
161+
this.$participants.setKey(stream.participantId, undefined);
158162
continue;
159163
}
160164

161-
if (stream.participantId in this.#participants) {
162-
const participant = this.#participants[stream.participantId];
163-
participant.media = stream.media;
164-
participant.externalParticipantId = stream.externalParticipantId;
165-
participant.participantId = stream.participantId;
165+
if (stream.participantId in this.$participants.get()) {
166+
const participant = this.$participants.get()[stream.participantId];
167+
participant.setKey("media", stream.media);
168+
participant.setKey(
169+
"externalParticipantId",
170+
stream.externalParticipantId,
171+
);
172+
participant.setKey("participantId", stream.participantId);
166173
} else {
167174
const meta: ParticipantMeta = {
168175
externalParticipantId: stream.externalParticipantId,
@@ -171,15 +178,20 @@ export class ClientCore {
171178
stream: new MediaStream(),
172179
maxHeight: 0, // default invisible until the UI tells us to render
173180
};
174-
this.#participants[stream.participantId] = meta;
181+
182+
const reactiveMeta = atom(meta);
183+
reactiveMeta.listen((_) => {
184+
this.#triggerSubscriptionFeedback();
185+
});
186+
this.$participants.setKey(stream.participantId, atom(meta));
175187
newParticipants.push(meta);
176188
}
177189
}
178190

179191
// TODO: should we bin pack the old participants first?
180192
for (const slot of this.#videoSlots) {
181193
if (slot.participantId) {
182-
if (slot.participantId in this.#participants) {
194+
if (slot.participantId in this.$participants) {
183195
continue;
184196
}
185197

@@ -207,22 +219,14 @@ export class ClientCore {
207219
this.#closed = true;
208220
}
209221

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-
218222
#triggerSubscriptionFeedback() {
219223
if (this.#timeoutId) {
220224
return;
221225
}
222226

223227
this.#timeoutId = setTimeout(() => {
224228
const subscriptions: ParticipantSubscription[] = Object.values(
225-
this.#participants,
229+
this.$participants,
226230
).map((p) => ({
227231
participantId: p.participantId,
228232
videoSettings: {

0 commit comments

Comments
 (0)