diff --git a/lib/src/participant/local_participant.dart b/lib/src/participant/local_participant.dart index 4b52e97..5be92d9 100644 --- a/lib/src/participant/local_participant.dart +++ b/lib/src/participant/local_participant.dart @@ -56,6 +56,7 @@ class LocalParticipant extends Participant { var pub = new LocalTrackPublication(trackInfo, track, this); addTrackPublication(pub); + notifyListeners(); return pub; } @@ -83,15 +84,17 @@ class LocalParticipant extends Participant { var pub = new LocalTrackPublication(trackInfo, track, this); addTrackPublication(pub); + notifyListeners(); return pub; } unpublishTrack(Track track) { - var pub = tracks.values.firstWhere((element) => element.track == track); - if (pub == null) { + var existing = tracks.values.where((element) => element.track == track); + if (existing.isEmpty) { return; } + var pub = existing.first; track.stop(); var sender = track.transceiver?.sender; diff --git a/lib/src/participant/participant.dart b/lib/src/participant/participant.dart index 1b77b7f..fdba451 100644 --- a/lib/src/participant/participant.dart +++ b/lib/src/participant/participant.dart @@ -1,3 +1,5 @@ +import 'package:flutter/foundation.dart'; + import 'remote_participant.dart'; import '../proto/livekit_models.pb.dart'; import '../track/remote_track_publication.dart'; @@ -22,7 +24,14 @@ mixin ParticipantDelegate { RemoteParticipant participant, String sid, String? message) {} } -class Participant { +/// Represents a Participant in the room, notifies changes via delegates as +/// well as ChangeNotifier/providers. +/// A change notification is triggered when +/// - speaking status changed +/// - mute status changed +/// - added/removed subscribed tracks +/// - metadata changed +class Participant extends ChangeNotifier { Map audioTracks = {}; Map videoTracks = {}; @@ -45,11 +54,14 @@ class Participant { DateTime? lastSpokeAt; ParticipantDelegate? roomDelegate; + + /// delegate to receive participant callbacks ParticipantDelegate? delegate; ParticipantInfo? _participantInfo; bool _isSpeaking = false; + /// when the participant joined the room DateTime get joinedAt { var pi = _participantInfo; if (pi != null) { @@ -62,6 +74,30 @@ class Participant { /// if participant is currently speaking bool get isSpeaking => _isSpeaking; + /// true if participant is publishing an audio track and is muted + bool get isMuted { + if (audioTracks.values.isEmpty) { + return false; + } + return audioTracks.values.first.muted; + } + + bool get hasAudio => audioTracks.length > 0; + + bool get hasVideo => videoTracks.length > 0; + + /// tracks that are subscribed to + List get subscribedTracks { + List result = []; + for (var track in tracks.values) { + if (track.subscribed) { + result.add(track); + } + } + return result; + } + + /// internal use bool get hasInfo => _participantInfo != null; Participant(this.sid, this.identity); @@ -76,6 +112,7 @@ class Participant { } delegate?.onSpeakingChanged(this, speaking); roomDelegate?.onSpeakingChanged(this, speaking); + notifyListeners(); } _setMetadata(String md) { @@ -84,6 +121,7 @@ class Participant { if (changed) { delegate?.onMetadataChanged(this); roomDelegate?.onMetadataChanged(this); + notifyListeners(); } } @@ -96,6 +134,10 @@ class Participant { this._participantInfo = info; } + muteChanged() { + notifyListeners(); + } + addTrackPublication(TrackPublication pub) { pub.track?.sid = pub.sid; tracks[pub.sid] = pub; diff --git a/lib/src/participant/remote_participant.dart b/lib/src/participant/remote_participant.dart index 87ea531..fd0ad94 100644 --- a/lib/src/participant/remote_participant.dart +++ b/lib/src/participant/remote_participant.dart @@ -63,6 +63,7 @@ class RemoteParticipant extends Participant { delegate?.onTrackSubscribed(this, track, pub); roomDelegate?.onTrackSubscribed(this, track, pub); + notifyListeners(); } @override @@ -95,7 +96,6 @@ class RemoteParticipant extends Participant { delegate?.onTrackPublished(this, pub); roomDelegate?.onTrackPublished(this, pub); } - return; } // remove tracks @@ -120,6 +120,7 @@ class RemoteParticipant extends Participant { track.stop(); delegate?.onTrackUnsubscribed(this, track, pub); roomDelegate?.onTrackUnsubscribed(this, track, pub); + notifyListeners(); } if (sendUnpublish) { delegate?.onTrackUnpublished(this, pub); diff --git a/lib/src/room.dart b/lib/src/room.dart index 263ee66..b4f3f4d 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:collection'; import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; import 'package:tuple/tuple.dart'; diff --git a/lib/src/track/local_track_publication.dart b/lib/src/track/local_track_publication.dart index a47d2c1..6b469ed 100644 --- a/lib/src/track/local_track_publication.dart +++ b/lib/src/track/local_track_publication.dart @@ -10,4 +10,22 @@ class LocalTrackPublication extends TrackPublication { : super.fromInfo(info) { this.track = track; } + + set muted(bool val) { + if (val == muted) { + return; + } + super.muted = val; + track?.mediaTrack.enabled = !val; + _participant.engine.client.sendMuteTrack(sid, val); + + if (val) { + _participant.delegate?.onTrackMuted(_participant, this); + _participant.roomDelegate?.onTrackMuted(_participant, this); + } else { + _participant.delegate?.onTrackUnmuted(_participant, this); + _participant.roomDelegate?.onTrackUnmuted(_participant, this); + } + _participant.muteChanged(); + } } diff --git a/lib/src/track/remote_track_publication.dart b/lib/src/track/remote_track_publication.dart index a640de0..ce18436 100644 --- a/lib/src/track/remote_track_publication.dart +++ b/lib/src/track/remote_track_publication.dart @@ -46,6 +46,7 @@ class RemoteTrackPublication extends TrackPublication { _participant.delegate?.onTrackUnmuted(_participant, this); _participant.roomDelegate?.onTrackUnmuted(_participant, this); } + _participant.muteChanged(); } RemoteTrackPublication(TrackInfo info, this._participant, [Track? track])