-
Notifications
You must be signed in to change notification settings - Fork 127
Connection State: Error Handling + Cleaning / refactoring / #3521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: voip-team/rebased-multiSFU
Are you sure you want to change the base?
Connection State: Error Handling + Cleaning / refactoring / #3521
Conversation
Updated the test because now name will be the userId instead of default display name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice work with fixing things up as you go. For the sake of getting our two branches merged into one as soon as possible, I went ahead and applied some changes directly for things which seemed hopefully uncontrovertial - if you like you can have a look through them. The rest of my comments are mostly about things from my branch that seem to have been lost in the previous merge conflicts.
I did also confirm that the app runs and connects to an SFU successfully.
src/livekit/MatrixAudioRenderer.tsx
Outdated
* This list needs to be composed based on the matrixRTC members so that we do not play audio from users | ||
* that are not expected to be in the rtc session. | ||
*/ | ||
// TODO: Why do we have this structure? looks like we only need the valid/active participants (not the room member or id)? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just laziness, really. The CallViewModel needs the ID and room member internally to create the media tiles, so I took the same data and made it public for the purpose of audio rendering without making the public type any stricter. I've remedied this in: c96e81b
// dependency references. | ||
import "matrix-js-sdk/lib/browser-index"; | ||
|
||
import { StrictMode } from "react"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, I didn't mean to commit my temporary disabling of strict mode. e346c8c
src/state/MuteStates.ts
Outdated
) {} | ||
} | ||
|
||
// TODO there is another MuteStates in src/room/MuteStates.tsx ?? why |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dead code, we can remove it. 5da780e
src/state/Async.ts
Outdated
import { catchError, from, map, type Observable, of, startWith } from "rxjs"; | ||
|
||
// TODO where are all the comments? ::cry:: | ||
// There used to be an unitialized state!, a state might not start in loading |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO the "uninitialized" state doesn't belong here. In cases where it might be tempting to use "uninitialized", I'd like it if we explicitly add such a case into our types as needed (Async<T>
→ Async<T | undefined>
) or keep an an eye out for better ways to model the problem. With just these three states, there's a very clear mapping between async values and Promises.
I added hopefully a decent amount of comments: b1d1437
/** | ||
* The focus server to connect to. | ||
*/ | ||
public readonly localTransport: LivekitTransport; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this called localTransport
and not transport
? It's often remote.
// Pair with their associated LiveKit participant (if any) | ||
.map((membership) => { | ||
// Uses flatMap to filter out memberships with no associated rtc participant ([]) | ||
.flatMap((membership) => { | ||
const id = `${membership.sender}:${membership.deviceId}`; | ||
const participant = participants.find((p) => p.identity === id); | ||
return { participant, membership }; | ||
return participant ? [{ participant, membership }] : []; | ||
}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think something went wrong here with the merge. If you take a look at 1820cac I was trying to make sure that even RTC memberships with no associated LiveKit participant are included in publishingParticipants$
. (One could add them back in CallViewModel, but it seemed way easier and more efficient to just avoid filtering them out here.)
The type that I was aiming for was:
Connection.publishingParticipants$: Behavior<{
participant: RemoteParticipant | undefined;
membership: CallMembership;
}[]>
Once we get the CallViewModel tests back up and running we'll be back to a state where this can't regress without a red flag.
scope | ||
.behavior<ConnectionState>(connectionStateObserver(this.livekitRoom)) | ||
.subscribe((connectionState) => { | ||
const current = this._focusedConnectionState$.value; | ||
// Only update the state if we are already connected to the LiveKit room. | ||
if (current.state === "ConnectedToLkRoom") { | ||
this._focusedConnectionState$.next({ | ||
state: "ConnectedToLkRoom", | ||
connectionState, | ||
focus: current.focus, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps a more elegant way to do this would be to change the type to:
{
state: "ConnectedToLkRoom";
- connectionState: ConnectionState;
+ connectionState: Observable<ConnectionState>;
focus: LivekitTransport;
}
Then (on line 111) you can just write:
this._focusedConnectionState$.next({
state: "ConnectedToLkRoom",
focus: this.localTransport,
connectionState: connectionStateObserver(this.livekitRoom),
});
and remove this line 212 code.
// Setup track processor syncing (blur) | ||
this.observeTrackProcessors(scope, room, trackerProcessorState$); | ||
// Observe mute state changes and update LiveKit microphone/camera states accordingly | ||
this.observeMuteStates(scope); | ||
// Observe media device changes and update LiveKit active devices accordingly | ||
this.observeMediaDevices(scope, devices, controlledAudioDevices); | ||
|
||
this.workaroundRestartAudioInputTrackChrome(devices, scope); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, this cleaner constructor is also a welcome change
scope.onEnd(() => { | ||
this.muteStates.audio.unsetHandler(); | ||
this.muteStates.video.unsetHandler(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In 86fb026 I moved these calls to unsetHandler
to the stop
method as scope.onEnd
happens much later than what we need for SFU-hopping. I believe they should stay there in stop
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, scratch that, we should just give the Connection class a proper ObservableScope of its own that ends at the right time (when CallViewModel is about to drop the Connection object).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can work on that.
// TODO this can throw errors? It will also prompt for permissions if not already granted | ||
const tracks = await this.livekitRoom.localParticipant.createTracks({ | ||
audio: this.muteStates.audio.enabled$.value, | ||
video: this.muteStates.video.enabled$.value, | ||
}); | ||
if (this.stopped) return; | ||
for (const track of tracks) { | ||
// TODO: handle errors? Needs the signaling connection to be up, but it has some retries internally | ||
// with a timeout. | ||
await this.livekitRoom.localParticipant.publishTrack(track); | ||
if (this.stopped) return; | ||
// TODO: check if the connection is still active? and break the loop if not? | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should restore this diff from 86fb026:
@@ -130,23 +131,35 @@ export class PublishConnection extends Connection {
if (!this.stopped) await this.livekitRoom.connect(url, jwt);
if (!this.stopped) {
- const tracks = await this.livekitRoom.localParticipant.createTracks({
- audio: this.muteStates.audio.enabled$.value,
- video: this.muteStates.video.enabled$.value,
- });
- for (const track of tracks) {
- await this.livekitRoom.localParticipant.publishTrack(track);
+ // TODO-MULTI-SFU: Prepublish a microphone track
+ const audio = this.muteStates.audio.enabled$.value;
+ const video = this.muteStates.video.enabled$.value;
+ // createTracks throws if called with audio=false and video=false
+ if (audio || video) {
+ const tracks = await this.livekitRoom.localParticipant.createTracks({
+ audio,
+ video,
+ });
+ for (const track of tracks) {
+ await this.livekitRoom.localParticipant.publishTrack(track);
+ }
}
}
}
(by which I mean just the TODO
and the audio || video
guard.)
useCallViewKeyboardShortcuts() changed a param from `setMicrophoneMuted` to `setAudioEnabled`, the boolean arg of the callback is inverse tht it used to be
Signed-off-by: Timo K <[email protected]>
0fd4143
to
de5f519
Compare
Better reviewed commit per commit