Merge pull request #2148 from famedly/karthi/grp-call-state-stream-refactor

unify group call stream stuff into matrixRTCEventStream
This commit is contained in:
td 2025-10-21 11:41:39 +02:00 committed by GitHub
commit 5a59ba4cf4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 1312 additions and 15 deletions

View File

@ -2753,6 +2753,14 @@ class FakeMatrixApi extends BaseClient {
(var reqI) => {
'event_id': '42',
},
'/client/v3/rooms/!calls%3Aexample.com/state/com.famedly.call.member/%40test%3AfakeServer.notExisting':
(var reqI) => {
'event_id': 'call_member_42',
},
'/client/v3/rooms/!calls%3Aexample.com/state/com.famedly.call.member/%40remoteuser%3Aexample.com':
(var reqI) => {
'event_id': 'call_member_remote_42',
},
'/client/v3/directory/list/room/!localpart%3Aexample.com': (var req) =>
{},
'/client/v3/room_keys/version/5': (var req) => {},

View File

@ -133,7 +133,9 @@ class MeshBackend extends CallBackend {
Future<void> _addCall(GroupCallSession groupCall, CallSession call) async {
_callSessions.add(call);
_initCall(groupCall, call);
// ignore: deprecated_member_use_from_same_package
groupCall.onGroupCallEvent.add(GroupCallStateChange.callsChanged);
groupCall.matrixRTCEventStream.add(CallAddedEvent(call));
}
/// init a peer call from group calls.
@ -183,7 +185,10 @@ class MeshBackend extends CallBackend {
_registerListenersBeforeCallAdd(replacementCall);
_initCall(groupCall, replacementCall);
// ignore: deprecated_member_use_from_same_package
groupCall.onGroupCallEvent.add(GroupCallStateChange.callsChanged);
groupCall.matrixRTCEventStream
.add(CallReplacedEvent(existingCall, replacementCall));
}
/// Removes a peer call from group calls.
@ -196,7 +201,9 @@ class MeshBackend extends CallBackend {
_callSessions.removeWhere((element) => call.callId == element.callId);
// ignore: deprecated_member_use_from_same_package
groupCall.onGroupCallEvent.add(GroupCallStateChange.callsChanged);
groupCall.matrixRTCEventStream.add(CallRemovedEvent(call));
}
Future<void> _disposeCall(
@ -375,7 +382,10 @@ class MeshBackend extends CallBackend {
if (nextActiveSpeaker != null && _activeSpeaker != nextActiveSpeaker) {
_activeSpeaker = nextActiveSpeaker;
// ignore: deprecated_member_use_from_same_package
groupCall.onGroupCallEvent.add(GroupCallStateChange.activeSpeakerChanged);
groupCall.matrixRTCEventStream
.add(GroupCallActiveSpeakerChanged(_activeSpeaker!));
}
_activeSpeakerLoopTimeout?.cancel();
_activeSpeakerLoopTimeout = Timer(
@ -401,8 +411,12 @@ class MeshBackend extends CallBackend {
) {
_screenshareStreams.add(stream);
onStreamAdd.add(stream);
// ignore: deprecated_member_use_from_same_package
groupCall.onGroupCallEvent
// ignore: deprecated_member_use_from_same_package
.add(GroupCallStateChange.screenshareStreamsChanged);
groupCall.matrixRTCEventStream
.add(GroupCallStreamAdded(GroupCallStreamType.screenshare));
}
Future<void> _replaceScreenshareStream(
@ -423,8 +437,12 @@ class MeshBackend extends CallBackend {
_screenshareStreams.replaceRange(streamIndex, 1, [replacementStream]);
await existingStream.dispose();
// ignore: deprecated_member_use_from_same_package
groupCall.onGroupCallEvent
// ignore: deprecated_member_use_from_same_package
.add(GroupCallStateChange.screenshareStreamsChanged);
groupCall.matrixRTCEventStream
.add(GroupCallStreamReplaced(GroupCallStreamType.screenshare));
}
Future<void> _removeScreenshareStream(
@ -450,8 +468,12 @@ class MeshBackend extends CallBackend {
await stopMediaStream(stream.stream);
}
// ignore: deprecated_member_use_from_same_package
groupCall.onGroupCallEvent
// ignore: deprecated_member_use_from_same_package
.add(GroupCallStateChange.screenshareStreamsChanged);
groupCall.matrixRTCEventStream
.add(GroupCallStreamRemoved(GroupCallStreamType.screenshare));
}
Future<void> _onCallStateChanged(CallSession call, CallState state) async {
@ -486,8 +508,12 @@ class MeshBackend extends CallBackend {
) async {
_userMediaStreams.add(stream);
onStreamAdd.add(stream);
// ignore: deprecated_member_use_from_same_package
groupCall.onGroupCallEvent
// ignore: deprecated_member_use_from_same_package
.add(GroupCallStateChange.userMediaStreamsChanged);
groupCall.matrixRTCEventStream
.add(GroupCallStreamAdded(GroupCallStreamType.userMedia));
}
Future<void> _replaceUserMediaStream(
@ -508,8 +534,12 @@ class MeshBackend extends CallBackend {
_userMediaStreams.replaceRange(streamIndex, 1, [replacementStream]);
await existingStream.dispose();
// ignore: deprecated_member_use_from_same_package
groupCall.onGroupCallEvent
// ignore: deprecated_member_use_from_same_package
.add(GroupCallStateChange.userMediaStreamsChanged);
groupCall.matrixRTCEventStream
.add(GroupCallStreamReplaced(GroupCallStreamType.userMedia));
}
Future<void> _removeUserMediaStream(
@ -536,12 +566,19 @@ class MeshBackend extends CallBackend {
await stopMediaStream(stream.stream);
}
// ignore: deprecated_member_use_from_same_package
groupCall.onGroupCallEvent
// ignore: deprecated_member_use_from_same_package
.add(GroupCallStateChange.userMediaStreamsChanged);
groupCall.matrixRTCEventStream
.add(GroupCallStreamRemoved(GroupCallStreamType.userMedia));
if (_activeSpeaker == stream.participant && _userMediaStreams.isNotEmpty) {
_activeSpeaker = _userMediaStreams[0].participant;
// ignore: deprecated_member_use_from_same_package
groupCall.onGroupCallEvent.add(GroupCallStateChange.activeSpeakerChanged);
groupCall.matrixRTCEventStream
.add(GroupCallActiveSpeakerChanged(_activeSpeaker!));
}
}
@ -663,7 +700,9 @@ class MeshBackend extends CallBackend {
}
}
// ignore: deprecated_member_use_from_same_package
groupCall.onGroupCallEvent.add(GroupCallStateChange.localMuteStateChanged);
groupCall.matrixRTCEventStream.add(GroupCallLocalMutedChanged(muted, kind));
return;
}
@ -799,8 +838,12 @@ class MeshBackend extends CallBackend {
_addScreenshareStream(groupCall, localScreenshareStream!);
// ignore: deprecated_member_use_from_same_package
groupCall.onGroupCallEvent
// ignore: deprecated_member_use_from_same_package
.add(GroupCallStateChange.localScreenshareStateChanged);
groupCall.matrixRTCEventStream
.add(GroupCallLocalScreenshareStateChanged(true));
for (final call in _callSessions) {
await call.addLocalStream(
await localScreenshareStream!.stream!.clone(),
@ -813,7 +856,10 @@ class MeshBackend extends CallBackend {
return;
} catch (e, s) {
Logs().e('[VOIP] Enabling screensharing error', e, s);
// ignore: deprecated_member_use_from_same_package
groupCall.onGroupCallEvent.add(GroupCallStateChange.error);
groupCall.matrixRTCEventStream
.add(GroupCallStateError(e.toString(), s));
return;
}
} else {
@ -826,8 +872,12 @@ class MeshBackend extends CallBackend {
await groupCall.sendMemberStateEvent();
// ignore: deprecated_member_use_from_same_package
groupCall.onGroupCallEvent
// ignore: deprecated_member_use_from_same_package
.add(GroupCallStateChange.localMuteStateChanged);
groupCall.matrixRTCEventStream
.add(GroupCallLocalScreenshareStateChanged(false));
return;
}
}

View File

@ -54,9 +54,11 @@ class GroupCallSession {
String groupCallId;
@Deprecated('Use matrixRTCEventStream instead')
final CachedStreamController<GroupCallState> onGroupCallState =
CachedStreamController();
@Deprecated('Use matrixRTCEventStream instead')
final CachedStreamController<GroupCallStateChange> onGroupCallEvent =
CachedStreamController();
@ -105,8 +107,11 @@ class GroupCallSession {
void setState(GroupCallState newState) {
state = newState;
// ignore: deprecated_member_use_from_same_package
onGroupCallState.add(newState);
// ignore: deprecated_member_use_from_same_package
onGroupCallEvent.add(GroupCallStateChange.groupCallStateChanged);
matrixRTCEventStream.add(GroupCallStateChanged(newState));
}
bool hasLocalParticipant() {
@ -313,6 +318,7 @@ class GroupCallSession {
.add(ParticipantsLeftEvent(participants: anyLeft.toList()));
}
// ignore: deprecated_member_use_from_same_package
onGroupCallEvent.add(GroupCallStateChange.participantsChanged);
}
}

View File

@ -5,15 +5,18 @@ import 'package:matrix/matrix.dart';
/// often.
sealed class MatrixRTCCallEvent {}
/// Event type for participants change
sealed class ParticipantsChangeEvent implements MatrixRTCCallEvent {}
final class ParticipantsJoinEvent implements ParticipantsChangeEvent {
/// The participants who joined the call
final List<CallParticipant> participants;
ParticipantsJoinEvent({required this.participants});
}
final class ParticipantsLeftEvent implements ParticipantsChangeEvent {
/// The participants who left the call
final List<CallParticipant> participants;
ParticipantsLeftEvent({required this.participants});
@ -46,3 +49,89 @@ final class CallReactionRemovedEvent implements CallReactionEvent {
required this.redactedEventId,
});
}
/// Group call active speaker changed event
final class GroupCallActiveSpeakerChanged implements MatrixRTCCallEvent {
final CallParticipant participant;
GroupCallActiveSpeakerChanged(this.participant);
}
/// Group calls changed event type
sealed class GroupCallChanged implements MatrixRTCCallEvent {}
/// Group call, call added event
final class CallAddedEvent implements GroupCallChanged {
final CallSession call;
CallAddedEvent(this.call);
}
/// Group call, call removed event
final class CallRemovedEvent implements GroupCallChanged {
final CallSession call;
CallRemovedEvent(this.call);
}
/// Group call, call replaced event
final class CallReplacedEvent extends GroupCallChanged {
final CallSession existingCall, replacementCall;
CallReplacedEvent(this.existingCall, this.replacementCall);
}
enum GroupCallStreamType {
userMedia,
screenshare,
}
/// Group call stream added event
final class GroupCallStreamAdded implements MatrixRTCCallEvent {
final GroupCallStreamType type;
GroupCallStreamAdded(this.type);
}
/// Group call stream removed event
final class GroupCallStreamRemoved implements MatrixRTCCallEvent {
final GroupCallStreamType type;
GroupCallStreamRemoved(this.type);
}
/// Group call stream replaced event
final class GroupCallStreamReplaced implements MatrixRTCCallEvent {
final GroupCallStreamType type;
GroupCallStreamReplaced(this.type);
}
/// Group call local screenshare state changed event
final class GroupCallLocalScreenshareStateChanged
implements MatrixRTCCallEvent {
final bool screensharing;
GroupCallLocalScreenshareStateChanged(this.screensharing);
}
/// Group call local muted changed event
final class GroupCallLocalMutedChanged implements MatrixRTCCallEvent {
final bool muted;
final MediaInputKind kind;
GroupCallLocalMutedChanged(this.muted, this.kind);
}
enum GroupCallState {
localCallFeedUninitialized,
initializingLocalCallFeed,
localCallFeedInitialized,
entering,
entered,
ended
}
/// Group call state changed event
final class GroupCallStateChanged implements MatrixRTCCallEvent {
final GroupCallState state;
GroupCallStateChanged(this.state);
}
/// Group call error event
final class GroupCallStateError implements MatrixRTCCallEvent {
final String msg;
final dynamic err;
GroupCallStateError(this.msg, this.err);
}

View File

@ -165,6 +165,7 @@ class GroupCallError extends Error {
}
}
@Deprecated('Use the events implementing MatrixRTCCallEvent instead')
enum GroupCallStateChange {
groupCallStateChanged,
activeSpeakerChanged,
@ -176,12 +177,3 @@ enum GroupCallStateChange {
participantsChanged,
error
}
enum GroupCallState {
localCallFeedUninitialized,
initializingLocalCallFeed,
localCallFeedInitialized,
entering,
entered,
ended
}

File diff suppressed because it is too large Load Diff

View File

@ -86,18 +86,49 @@ class MockEncryptionKeyProvider implements EncryptionKeyProvider {
}
}
class MockMediaDeviceInfo implements MediaDeviceInfo {
@override
final String deviceId;
@override
final String kind;
@override
final String label;
@override
final String? groupId;
MockMediaDeviceInfo({
required this.deviceId,
required this.kind,
required this.label,
this.groupId,
});
}
class MockMediaDevices implements MediaDevices {
@override
Function(dynamic event)? ondevicechange;
@override
Future<List<MediaDeviceInfo>> enumerateDevices() {
throw UnimplementedError();
Future<List<MediaDeviceInfo>> enumerateDevices() async {
return [
MockMediaDeviceInfo(
deviceId: 'default_audio_input',
kind: 'audioinput',
label: 'Default Audio Input',
),
MockMediaDeviceInfo(
deviceId: 'default_video_input',
kind: 'videoinput',
label: 'Default Video Input',
),
];
}
@override
Future<MediaStream> getDisplayMedia(Map<String, dynamic> mediaConstraints) {
throw UnimplementedError();
Future<MediaStream> getDisplayMedia(
Map<String, dynamic> mediaConstraints,
) async {
return MockMediaStream('', '');
}
@override
@ -160,6 +191,9 @@ class MockRTCPeerConnection implements RTCPeerConnection {
@override
Function(RTCTrackEvent event)? onTrack;
// Mock stats to simulate audio levels
double mockAudioLevel = 0.0;
@override
RTCSignalingState? get signalingState => throw UnimplementedError();
@ -276,8 +310,23 @@ class MockRTCPeerConnection implements RTCPeerConnection {
@override
Future<List<StatsReport>> getStats([MediaStreamTrack? track]) async {
// Mock implementation for getting stats
Logs().i('Mock: Getting stats');
return [];
Logs().i('Mock: Getting stats with audioLevel: $mockAudioLevel');
return [
MockStatsReport(
type: 'inbound-rtp',
values: {
'kind': 'audio',
'audioLevel': mockAudioLevel,
},
),
MockStatsReport(
type: 'media-source',
values: {
'kind': 'audio',
'audioLevel': mockAudioLevel,
},
),
];
}
@override
@ -850,3 +899,45 @@ class MockVideoRenderer implements VideoRenderer {
Logs().i('Mock: Disposing VideoRenderer');
}
}
class MockStatsReport implements StatsReport {
@override
final String type;
@override
final Map<String, dynamic> values;
@override
final String id;
@override
final double timestamp;
MockStatsReport({
required this.type,
required this.values,
this.id = 'mock-stats-id',
this.timestamp = 0.0,
});
}
class MockRTCTrackEvent implements RTCTrackEvent {
@override
final MediaStreamTrack track;
@override
final RTCRtpReceiver? receiver;
@override
final List<MediaStream> streams;
@override
final RTCRtpTransceiver? transceiver;
MockRTCTrackEvent({
required this.track,
this.receiver,
required this.streams,
this.transceiver,
});
}