Skip to content

Commit 4aeb13d

Browse files
authored
Expose ParticipantState (#848)
1 parent eac9b06 commit 4aeb13d

File tree

4 files changed

+92
-35
lines changed

4 files changed

+92
-35
lines changed

lib/livekit_client.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,15 @@ export 'src/track/remote/audio.dart';
5050
export 'src/track/remote/remote.dart';
5151
export 'src/track/remote/video.dart';
5252
export 'src/track/track.dart';
53+
export 'src/types/attribute_typings.dart';
5354
export 'src/types/data_stream.dart';
5455
export 'src/types/other.dart';
5556
export 'src/types/participant_permissions.dart';
57+
export 'src/types/participant_state.dart';
5658
export 'src/types/rpc.dart';
5759
export 'src/types/transcription_segment.dart';
5860
export 'src/types/video_dimensions.dart';
5961
export 'src/types/video_encoding.dart';
6062
export 'src/types/video_parameters.dart';
6163
export 'src/widgets/screen_select_dialog.dart';
6264
export 'src/widgets/video_track_renderer.dart';
63-
export 'src/types/attribute_typings.dart';

lib/src/events.dart

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import 'track/processor.dart';
2626
import 'track/track.dart';
2727
import 'types/other.dart';
2828
import 'types/participant_permissions.dart';
29+
import 'types/participant_state.dart' show ParticipantState;
2930
import 'types/transcription_segment.dart';
3031

3132
/// Base type for all LiveKit events.
@@ -372,10 +373,21 @@ class ParticipantMetadataUpdatedEvent with RoomEvent, ParticipantEvent {
372373
String toString() => '${runtimeType}(participant: ${participant})';
373374
}
374375

376+
class ParticipantStateUpdatedEvent with RoomEvent, ParticipantEvent {
377+
final Participant participant;
378+
final ParticipantState state;
379+
const ParticipantStateUpdatedEvent({
380+
required this.participant,
381+
required this.state,
382+
});
383+
384+
@override
385+
String toString() => '${runtimeType}(participant: ${participant})';
386+
}
387+
375388
/// [Pariticpant]'s [ConnectionQuality] has updated.
376389
/// Emitted by [Room] and [Participant].
377-
class ParticipantConnectionQualityUpdatedEvent
378-
with RoomEvent, ParticipantEvent {
390+
class ParticipantConnectionQualityUpdatedEvent with RoomEvent, ParticipantEvent {
379391
final Participant participant;
380392
final ConnectionQuality connectionQuality;
381393
const ParticipantConnectionQualityUpdatedEvent({

lib/src/participant/participant.dart

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import '../publication/track_publication.dart';
2626
import '../support/disposable.dart';
2727
import '../types/other.dart';
2828
import '../types/participant_permissions.dart';
29+
import '../types/participant_state.dart';
2930
import '../utils.dart';
3031

3132
/// Represents a Participant in the room, notifies changes via delegates as
@@ -38,8 +39,8 @@ import '../utils.dart';
3839
3940
/// Base for [RemoteParticipant] and [LocalParticipant],
4041
/// can not be instantiated directly.
41-
abstract class Participant<T extends TrackPublication>
42-
extends DisposableChangeNotifier with EventsEmittable<ParticipantEvent> {
42+
abstract class Participant<T extends TrackPublication> extends DisposableChangeNotifier
43+
with EventsEmittable<ParticipantEvent> {
4344
/// Reference to [Room]
4445
@internal
4546
final Room room;
@@ -80,16 +81,18 @@ abstract class Participant<T extends TrackPublication>
8081
ParticipantPermissions get permissions => _permissions;
8182

8283
/// Attributes associated with the participant
83-
UnmodifiableMapView<String, String> get attributes =>
84-
UnmodifiableMapView(_attributes);
84+
UnmodifiableMapView<String, String> get attributes => UnmodifiableMapView(_attributes);
8585
Map<String, String> _attributes = {};
8686

87+
// Participant state
88+
ParticipantState get state => _state;
89+
ParticipantState _state = ParticipantState.unknown;
90+
8791
/// when the participant joined the room
8892
DateTime get joinedAt {
8993
final pi = _participantInfo;
9094
if (pi != null) {
91-
return DateTime.fromMillisecondsSinceEpoch(pi.joinedAt.toInt() * 1000,
92-
isUtc: true);
95+
return DateTime.fromMillisecondsSinceEpoch(pi.joinedAt.toInt() * 1000, isUtc: true);
9396
}
9497
return DateTime.now();
9598
}
@@ -176,15 +179,22 @@ abstract class Participant<T extends TrackPublication>
176179
}
177180
}
178181

182+
void _setParticipantState(ParticipantState state) {
183+
final didChange = _state != state;
184+
_state = state;
185+
if (didChange) {
186+
[events, room.events].emit(ParticipantStateUpdatedEvent(
187+
participant: this,
188+
state: state,
189+
));
190+
}
191+
}
192+
179193
void _setAttributes(Map<String, String> attrs) {
180-
final diff = mapDiff(_attributes, attrs)
181-
.map((k, v) => MapEntry(k as String, v as String));
194+
final diff = mapDiff(_attributes, attrs).map((k, v) => MapEntry(k as String, v as String));
182195
_attributes = attrs;
183196
if (diff.isNotEmpty) {
184-
[
185-
events,
186-
room.events
187-
].emit(ParticipantAttributesChanged(participant: this, attributes: diff));
197+
[events, room.events].emit(ParticipantAttributesChanged(participant: this, attributes: diff));
188198
}
189199
}
190200

@@ -201,9 +211,7 @@ abstract class Participant<T extends TrackPublication>
201211
@internal
202212
Future<bool> updateFromInfo(lk_models.ParticipantInfo info) async {
203213
logger.fine('LocalParticipant.updateFromInfo(info: $info)');
204-
if (_participantInfo != null &&
205-
_participantInfo!.sid == info.sid &&
206-
_participantInfo!.version > info.version) {
214+
if (_participantInfo != null && _participantInfo!.sid == info.sid && _participantInfo!.version > info.version) {
207215
return false;
208216
}
209217

@@ -219,6 +227,7 @@ abstract class Participant<T extends TrackPublication>
219227
_setAttributes(info.attributes);
220228
_participantInfo = info;
221229
setPermissions(info.permission.toLKType());
230+
_setParticipantState(info.state.toLKType());
222231

223232
return true;
224233
}
@@ -278,19 +287,14 @@ abstract class Participant<T extends TrackPublication>
278287
T? getTrackPublicationBySource(TrackSource source) {
279288
if (source == TrackSource.unknown) return null;
280289
// try to find by source
281-
final result =
282-
trackPublications.values.firstWhereOrNull((e) => e.source == source);
290+
final result = trackPublications.values.firstWhereOrNull((e) => e.source == source);
283291
if (result != null) return result;
284292
// try to find by compatibility
285-
return trackPublications.values
286-
.where((e) => e.source == TrackSource.unknown)
287-
.firstWhereOrNull((e) =>
288-
(source == TrackSource.microphone && e.kind == TrackType.AUDIO) ||
289-
(source == TrackSource.camera && e.kind == TrackType.VIDEO) ||
290-
(source == TrackSource.screenShareVideo &&
291-
e.kind == TrackType.VIDEO) ||
292-
(source == TrackSource.screenShareAudio &&
293-
e.kind == TrackType.AUDIO));
293+
return trackPublications.values.where((e) => e.source == TrackSource.unknown).firstWhereOrNull((e) =>
294+
(source == TrackSource.microphone && e.kind == TrackType.AUDIO) ||
295+
(source == TrackSource.camera && e.kind == TrackType.VIDEO) ||
296+
(source == TrackSource.screenShareVideo && e.kind == TrackType.VIDEO) ||
297+
(source == TrackSource.screenShareAudio && e.kind == TrackType.AUDIO));
294298
}
295299

296300
/// Convenience property to check whether [TrackSource.camera] is published or not.
@@ -300,20 +304,17 @@ abstract class Participant<T extends TrackPublication>
300304

301305
/// Convenience property to check whether [TrackSource.microphone] is published or not.
302306
bool isMicrophoneEnabled() {
303-
return !(getTrackPublicationBySource(TrackSource.microphone)?.muted ??
304-
true);
307+
return !(getTrackPublicationBySource(TrackSource.microphone)?.muted ?? true);
305308
}
306309

307310
/// Convenience property to check whether [TrackSource.screenShareVideo] is published or not.
308311
bool isScreenShareEnabled() {
309-
return !(getTrackPublicationBySource(TrackSource.screenShareVideo)?.muted ??
310-
true);
312+
return !(getTrackPublicationBySource(TrackSource.screenShareVideo)?.muted ?? true);
311313
}
312314

313315
/// Convenience property to check whether [TrackSource.screenShareAudio] is published or not.
314316
bool isScreenShareAudioEnabled() {
315-
return !(getTrackPublicationBySource(TrackSource.screenShareAudio)?.muted ??
316-
true);
317+
return !(getTrackPublicationBySource(TrackSource.screenShareAudio)?.muted ?? true);
317318
}
318319

319320
/// (Equality operator) [Participant.hashCode] is same as [sid.hashCode].
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2025 LiveKit, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import '../proto/livekit_models.pb.dart' as lk_models;
16+
17+
/// Represents the state of a participant in the room.
18+
enum ParticipantState {
19+
/// Websocket is connected, but no offer has been sent yet
20+
joining,
21+
22+
/// Server has received the client's offer
23+
joined,
24+
25+
/// ICE connectivity has been established
26+
active,
27+
28+
/// Websocket has disconnected
29+
disconnected,
30+
31+
/// Unknown state
32+
unknown,
33+
}
34+
35+
extension ParticipantStateExt on lk_models.ParticipantInfo_State {
36+
ParticipantState toLKType() => switch (this) {
37+
lk_models.ParticipantInfo_State.JOINING => ParticipantState.joining,
38+
lk_models.ParticipantInfo_State.JOINED => ParticipantState.joined,
39+
lk_models.ParticipantInfo_State.ACTIVE => ParticipantState.active,
40+
lk_models.ParticipantInfo_State.DISCONNECTED => ParticipantState.disconnected,
41+
_ => ParticipantState.unknown,
42+
};
43+
}

0 commit comments

Comments
 (0)