From c309757ae98268bc8d1fe1a2347669761ab053f2 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Fri, 7 Aug 2026 16:28:29 -0400 Subject: [PATCH 1/3] fix: raise minimum SDK to Flutter 3.38 / Dart 3.10 Groundwork for delivering the Rust core through Dart's Native Assets, which is only stable from Flutter 3.38 / Dart 3.10 onwards. The newer language version turns on lints the tree does not yet satisfy: - `*.g.dart` joins the analyzer exclusions. AGENTS.md already described json_serializable output as excluded, but only the protobuf patterns actually were; 47 of the 48 new `use_null_aware_elements` hits are in generated files nobody edits by hand. - The remaining `use_null_aware_elements` hit, in `Room`, and four `unnecessary_underscores` hits in the RPC tests, are fixed directly. Crossing Dart 3.7 also switches `dart format` to the tall style, which reformats the whole tree. That is mechanical and lands separately, in the commit that follows, so it does not bury this one. `dart format --set-exit-if-changed` is therefore red at this commit and green again at the next. No functional change. --- .changes/raise-minimum-sdk-flutter-338 | 1 + analysis_options.yaml | 4 +++- lib/src/core/room.dart | 2 +- pubspec.lock | 8 ++++---- pubspec.yaml | 6 ++++-- test/core/rpc_test.dart | 8 ++++---- 6 files changed, 17 insertions(+), 12 deletions(-) create mode 100644 .changes/raise-minimum-sdk-flutter-338 diff --git a/.changes/raise-minimum-sdk-flutter-338 b/.changes/raise-minimum-sdk-flutter-338 new file mode 100644 index 000000000..62404643a --- /dev/null +++ b/.changes/raise-minimum-sdk-flutter-338 @@ -0,0 +1 @@ +major type="changed" "Raise the minimum SDK to Flutter 3.38 / Dart 3.10, required for Native Assets" diff --git a/analysis_options.yaml b/analysis_options.yaml index b7cb2942b..a19eddbd8 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -22,12 +22,14 @@ analyzer: avoid_print: ignore deprecated_member_use_from_same_package: ignore - # Exclude protobuf files + # Exclude generated files: protobuf, and json_serializable output. Neither is + # hand-edited, so lint hits there can only be fixed by changing the generator. exclude: - "**/*.pb.dart" - "**/*.pbenum.dart" - "**/*.pbjson.dart" - "**/*.pbserver.dart" + - "**/*.g.dart" # - 'web/*.dart' # Xcode vendors Swift package checkouts under build when Swift Package # Manager is enabled and this package ships Package.swift. diff --git a/lib/src/core/room.dart b/lib/src/core/room.dart index 60916913a..44939b750 100644 --- a/lib/src/core/room.dart +++ b/lib/src/core/room.dart @@ -881,7 +881,7 @@ class Room extends DisposableChangeNotifier with EventsEmittable { // localParticipant & remote participants final allParticipants = { - if (localParticipant != null) localParticipant!.sid: localParticipant!, + ?localParticipant?.sid: ?localParticipant, ..._remoteParticipants.bySid, }; diff --git a/pubspec.lock b/pubspec.lock index 57d525b41..83c25ab59 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -460,10 +460,10 @@ packages: dependency: "direct main" description: name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.0" mime: dependency: transitive description: @@ -745,10 +745,10 @@ packages: dependency: transitive description: name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.10" timing: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c4cd66b57..9baebd151 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,8 +23,10 @@ false_secrets: - /test/support/certificate_pinning_io_test.dart environment: - sdk: ">=3.6.0 <4.0.0" - flutter: ">=3.27.0" + # Native Assets (build hooks / code assets) is how livekit_uniffi delivers the + # Rust core; it is only stable from Flutter 3.38 / Dart 3.10 onwards. + sdk: ">=3.10.0 <4.0.0" + flutter: ">=3.38.0" dependencies: flutter_web_plugins: diff --git a/test/core/rpc_test.dart b/test/core/rpc_test.dart index e63989328..db901de43 100644 --- a/test/core/rpc_test.dart +++ b/test/core/rpc_test.dart @@ -258,19 +258,19 @@ void main() { test('RPC v2 text stream topics are reserved for SDK internals', () async { expect( - () => v2Room.registerTextStreamHandler(kRpcRequestTopic, (_, __) async {}), + () => v2Room.registerTextStreamHandler(kRpcRequestTopic, (_, _) async {}), throwsA(isA()), ); expect( - () => v2Room.registerTextStreamHandler(kRpcResponseTopic, (_, __) async {}), + () => v2Room.registerTextStreamHandler(kRpcResponseTopic, (_, _) async {}), throwsA(isA()), ); expect( - () => v2Room.registerTextStreamHandler('lk.rpc_future', (_, __) async {}), + () => v2Room.registerTextStreamHandler('lk.rpc_future', (_, _) async {}), throwsA(isA()), ); expect( - () => v2Room.registerByteStreamHandler(kRpcRequestTopic, (_, __) async {}), + () => v2Room.registerByteStreamHandler(kRpcRequestTopic, (_, _) async {}), throwsA(isA()), ); From 5074c77d2c94191cba688c53281618eb694c6c85 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Fri, 7 Aug 2026 16:29:02 -0400 Subject: [PATCH 2/3] style: reformat with dart format for the Dart 3.10 tall style Pure output of `dart format .`. No functional or semantic change. The formatter picks its style from the package language version, so the SDK bump in the previous commit crosses Dart 3.7 and switches the whole tree from the short style to the tall one. `dart format --set-exit-if-changed` is CI-enforced, so this has to land -- it is split out only so the bump and its lint fixes stay readable. Reproduce with `dart format .` on the parent commit. --- lib/src/agent/agent.dart | 12 +- .../chat/transcription_stream_receiver.dart | 11 +- lib/src/agent/room_agent.dart | 16 +- lib/src/agent/session.dart | 24 +- lib/src/audio/audio_frame_capture.dart | 3 +- lib/src/audio/audio_frame_capture_native.dart | 14 +- lib/src/audio/audio_frame_capture_web.dart | 14 +- lib/src/audio/audio_manager.dart | 10 +- lib/src/audio/audio_processing_state.dart | 50 +- lib/src/audio/audio_session.dart | 42 +- lib/src/connection_check/checks/checker.dart | 20 +- .../checks/connection_protocol.dart | 3 +- lib/src/connection_check/checks/turn.dart | 3 +- .../connection_check/checks/websocket.dart | 12 +- lib/src/core/engine.dart | 386 ++++++++------ lib/src/core/room.dart | 211 ++++---- lib/src/core/signal_client.dart | 276 +++++----- lib/src/core/transport.dart | 11 +- lib/src/data_stream/stream_reader.dart | 42 +- lib/src/e2ee/e2ee_manager.dart | 68 ++- lib/src/e2ee/events.dart | 3 +- lib/src/e2ee/key_provider.dart | 15 +- lib/src/events.dart | 84 ++-- lib/src/extensions.dart | 220 ++++---- lib/src/hardware/hardware.dart | 14 +- lib/src/internal/events.dart | 58 ++- lib/src/internal/types.dart | 4 +- lib/src/json/agent_attributes.g.dart | 43 +- lib/src/managers/event.dart | 21 +- lib/src/options.dart | 75 ++- lib/src/participant/local.dart | 146 +++--- lib/src/participant/participant.dart | 70 ++- lib/src/participant/remote.dart | 50 +- .../preconnect/pre_connect_audio_buffer.dart | 50 +- lib/src/publication/local.dart | 6 +- lib/src/publication/remote.dart | 64 +-- lib/src/publication/track_publication.dart | 20 +- lib/src/rpc/rpc_client_manager.dart | 28 +- lib/src/rpc/rpc_server_manager.dart | 34 +- lib/src/support/byte_ring_buffer.dart | 4 +- lib/src/support/http_client/io.dart | 12 +- lib/src/support/native.dart | 14 +- lib/src/support/native_audio.dart | 86 ++-- lib/src/support/platform.dart | 8 +- lib/src/support/platform/web.dart | 5 +- lib/src/support/region_url_provider.dart | 32 +- .../support/webrtc_initialize_options.dart | 11 +- lib/src/support/websocket.dart | 16 +- lib/src/support/websocket/io.dart | 3 +- lib/src/support/websocket/web.dart | 8 +- lib/src/token_source/caching.dart | 15 +- lib/src/token_source/caching.g.dart | 12 +- lib/src/token_source/development.dart | 10 +- lib/src/token_source/jwt.g.dart | 52 +- .../token_source/room_configuration.g.dart | 60 +-- lib/src/token_source/token_source.g.dart | 95 ++-- lib/src/track/audio_visualizer_native.dart | 10 +- lib/src/track/audio_visualizer_web.dart | 25 +- lib/src/track/local/audio.dart | 20 +- lib/src/track/local/local.dart | 26 +- lib/src/track/options.dart | 165 +++--- lib/src/track/remote/audio.dart | 21 +- lib/src/track/remote/remote.dart | 12 +- lib/src/track/remote/video.dart | 21 +- lib/src/track/track.dart | 22 +- lib/src/track/web/_audio_analyser.dart | 11 +- lib/src/track/web/_audio_context.dart | 3 +- lib/src/types/audio_encoding.dart | 19 +- lib/src/types/data_stream.dart | 51 +- lib/src/types/other.dart | 32 +- lib/src/types/participant_permissions.dart | 14 +- lib/src/types/participant_state.dart | 12 +- lib/src/types/video_dimensions.dart | 9 +- lib/src/types/video_encoding.dart | 32 +- lib/src/types/video_parameters.dart | 8 +- lib/src/utils.dart | 93 ++-- lib/src/utils/data_packet_buffer.dart | 8 +- lib/src/widgets/screen_select_dialog.dart | 326 ++++++------ lib/src/widgets/video_track_renderer.dart | 67 +-- scripts/create_version.dart | 26 +- test/audio/audio_session_test.dart | 50 +- test/connection_check/checker_test.dart | 52 +- .../connection_check_test.dart | 30 +- .../webrtc_candidate_test.dart | 6 +- test/core/data_stream_test.dart | 261 ++++++---- test/core/disconnect_event_test.dart | 12 +- test/core/room_e2e_test.dart | 54 +- test/core/rpc_test.dart | 359 +++++++------ test/core/signal_client_test.dart | 45 +- .../data_stream_reliability_test.dart | 172 ++++--- test/mock/e2e_container.dart | 66 +-- test/mock/e2ee_fake_manager.dart | 22 +- test/mock/fake_checker.dart | 4 +- test/mock/peerconnection_mock.dart | 14 +- test/preconnect/audio_frame_capture_test.dart | 98 ++-- .../remote_track_publication_test.dart | 39 +- test/support/certificate_pinning_io_test.dart | 40 +- test/support/certificate_pinning_test.dart | 190 +++---- test/support/reusable_completer_test.dart | 18 +- test/token/caching_token_source_test.dart | 16 +- test/token/token_source_test.dart | 21 +- test/utils/data_packet_buffer_test.dart | 6 +- web/e2ee.data_packet_cryptor.dart | 57 ++- web/e2ee.frame_cryptor.dart | 106 ++-- web/e2ee.keyhandler.dart | 9 +- web/e2ee.nalu_utils.dart | 39 +- web/e2ee.sfi_guard.dart | 8 +- web/e2ee.worker.dart | 473 ++++++++++-------- 108 files changed, 3377 insertions(+), 2599 deletions(-) diff --git a/lib/src/agent/agent.dart b/lib/src/agent/agent.dart index e230829fb..017fbe269 100644 --- a/lib/src/agent/agent.dart +++ b/lib/src/agent/agent.dart @@ -143,7 +143,8 @@ class Agent extends ChangeNotifier { final RemoteAudioTrack? nextAudioTrack = _resolveAudioTrack(participant); final RemoteVideoTrack? nextAvatarTrack = _resolveAvatarVideoTrack(participant); - final bool shouldNotify = _state != _AgentLifecycle.connected || + final bool shouldNotify = + _state != _AgentLifecycle.connected || _agentState != nextAgentState || !identical(_audioTrack, nextAudioTrack) || !identical(_avatarVideoTrack, nextAvatarTrack) || @@ -216,13 +217,14 @@ enum AgentFailure { timeout, /// The agent left the room unexpectedly. - left; + left + ; /// A human-readable error message. String get message => switch (this) { - AgentFailure.timeout => 'Agent did not connect', - AgentFailure.left => 'Agent left the room unexpectedly', - }; + AgentFailure.timeout => 'Agent did not connect', + AgentFailure.left => 'Agent left the room unexpectedly', + }; } enum _AgentLifecycle { diff --git a/lib/src/agent/chat/transcription_stream_receiver.dart b/lib/src/agent/chat/transcription_stream_receiver.dart index d864c2396..813f4eece 100644 --- a/lib/src/agent/chat/transcription_stream_receiver.dart +++ b/lib/src/agent/chat/transcription_stream_receiver.dart @@ -73,9 +73,9 @@ class TranscriptionStreamReceiver implements MessageReceiver { this.topic = 'lk.transcription', void Function(String topic, TextStreamHandler handler)? registerHandler, void Function(String topic)? unregisterHandler, - }) : _room = room, - _registerHandler = registerHandler ?? room.registerTextStreamHandler, - _unregisterHandler = unregisterHandler ?? room.unregisterTextStreamHandler; + }) : _room = room, + _registerHandler = registerHandler ?? room.registerTextStreamHandler, + _unregisterHandler = unregisterHandler ?? room.unregisterTextStreamHandler; final Room _room; final String topic; @@ -217,8 +217,9 @@ class TranscriptionStreamReceiver implements MessageReceiver { final displayTimestamp = partial?.timestamp ?? timestamp; final isLocalParticipant = _room.localParticipant?.identity == participantIdentity; - final ReceivedMessageContent content = - isLocalParticipant ? UserTranscript(displayContent) : AgentTranscript(displayContent); + final ReceivedMessageContent content = isLocalParticipant + ? UserTranscript(displayContent) + : AgentTranscript(displayContent); return ReceivedMessage( id: segmentId, diff --git a/lib/src/agent/room_agent.dart b/lib/src/agent/room_agent.dart index 9ff675f44..0fefd7aa6 100644 --- a/lib/src/agent/room_agent.dart +++ b/lib/src/agent/room_agent.dart @@ -28,14 +28,14 @@ extension AgentRoom on Room { /// participants whose `lk.publish_on_behalf` attribute matches the agent's /// identity. Iterable get agentParticipants => remoteParticipants.values.where( - (participant) { - if (participant.kind != ParticipantKind.AGENT) { - return false; - } - final publishOnBehalf = participant.attributes[lkPublishOnBehalfAttributeKey]; - return publishOnBehalf == null || publishOnBehalf.isEmpty; - }, - ); + (participant) { + if (participant.kind != ParticipantKind.AGENT) { + return false; + } + final publishOnBehalf = participant.attributes[lkPublishOnBehalfAttributeKey]; + return publishOnBehalf == null || publishOnBehalf.isEmpty; + }, + ); /// The first agent participant in the room, if one exists. RemoteParticipant? get agentParticipant => agentParticipants.firstOrNull; diff --git a/lib/src/agent/session.dart b/lib/src/agent/session.dart index 43d44922c..6c4dfcede 100644 --- a/lib/src/agent/session.dart +++ b/lib/src/agent/session.dart @@ -66,9 +66,9 @@ class Session extends DisposableChangeNotifier { required SessionOptions options, List? senders, List? receivers, - }) : _tokenSourceConfiguration = tokenSourceConfiguration, - _options = options, - room = options.room { + }) : _tokenSourceConfiguration = tokenSourceConfiguration, + _options = options, + room = options.room { _agent.addListener(notifyListeners); final textMessageSender = TextMessageSender(room: room); @@ -160,9 +160,9 @@ class Session extends DisposableChangeNotifier { ConnectionState _connectionState = ConnectionState.disconnected; bool get isConnected => switch (_connectionState) { - ConnectionState.connecting || ConnectionState.connected || ConnectionState.reconnecting => true, - ConnectionState.disconnected => false, - }; + ConnectionState.connecting || ConnectionState.connected || ConnectionState.reconnecting => true, + ConnectionState.disconnected => false, + }; final LinkedHashMap _messages = LinkedHashMap(); UnmodifiableListView _messagesView = UnmodifiableListView(const []); @@ -285,7 +285,9 @@ class Session extends DisposableChangeNotifier { _messages ..clear() ..addEntries( - messages.sorted((a, b) => a.timestamp.compareTo(b.timestamp)).map( + messages + .sorted((a, b) => a.timestamp.compareTo(b.timestamp)) + .map( (message) => MapEntry(message.id, message), ), ); @@ -407,10 +409,10 @@ class SessionError { final Object cause; String get message => switch (kind) { - SessionErrorKind.connection => 'Connection failed: ${cause}', - SessionErrorKind.sender => 'Message sender failed: ${cause}', - SessionErrorKind.receiver => 'Message receiver failed: ${cause}', - }; + SessionErrorKind.connection => 'Connection failed: ${cause}', + SessionErrorKind.sender => 'Message sender failed: ${cause}', + SessionErrorKind.receiver => 'Message receiver failed: ${cause}', + }; static SessionError connection(Object cause) => SessionError._(SessionErrorKind.connection, cause); diff --git a/lib/src/audio/audio_frame_capture.dart b/lib/src/audio/audio_frame_capture.dart index b92f776ac..00be8adc6 100644 --- a/lib/src/audio/audio_frame_capture.dart +++ b/lib/src/audio/audio_frame_capture.dart @@ -21,7 +21,8 @@ import 'audio_frame_capture_native.dart' if (dart.library.js_interop) 'audio_fra /// PCM sample format for audio frame capture. enum AudioFormat { Int16('int16'), - Float32('float32'); + Float32('float32') + ; final String value; const AudioFormat(this.value); diff --git a/lib/src/audio/audio_frame_capture_native.dart b/lib/src/audio/audio_frame_capture_native.dart index a7b06a1d9..c0aee6c00 100644 --- a/lib/src/audio/audio_frame_capture_native.dart +++ b/lib/src/audio/audio_frame_capture_native.dart @@ -59,12 +59,14 @@ class AudioFrameCaptureNative implements AudioFrameCapture { _streamSubscription = _eventChannel?.receiveBroadcastStream().listen((event) { try { final rawFormat = event['commonFormat'] as String?; - _controller.add(AudioFrame( - sampleRate: event['sampleRate'] as int, - channels: event['channels'] as int, - data: event['data'] as Uint8List, - format: rawFormat == AudioFormat.Float32.value ? AudioFormat.Float32 : AudioFormat.Int16, - )); + _controller.add( + AudioFrame( + sampleRate: event['sampleRate'] as int, + channels: event['channels'] as int, + data: event['data'] as Uint8List, + format: rawFormat == AudioFormat.Float32.value ? AudioFormat.Float32 : AudioFormat.Int16, + ), + ); } catch (e) { logger.warning('[AudioFrameCapture] Error parsing native event: $e'); } diff --git a/lib/src/audio/audio_frame_capture_web.dart b/lib/src/audio/audio_frame_capture_web.dart index 2e7ad4581..13335057f 100644 --- a/lib/src/audio/audio_frame_capture_web.dart +++ b/lib/src/audio/audio_frame_capture_web.dart @@ -150,12 +150,14 @@ class AudioFrameCaptureWeb implements AudioFrameCapture { bytes = float32ToInt16Bytes(srcFloat32, channels, outChannels, frames); } - controller.add(AudioFrame( - sampleRate: actualSampleRate, - channels: outChannels, - data: bytes, - format: _targetFormat, - )); + controller.add( + AudioFrame( + sampleRate: actualSampleRate, + channels: outChannels, + data: bytes, + format: _targetFormat, + ), + ); } catch (e) { logger.warning('[AudioFrameCapture] Error processing worklet frame: $e'); } diff --git a/lib/src/audio/audio_manager.dart b/lib/src/audio/audio_manager.dart index a79289fd6..be25e83e2 100644 --- a/lib/src/audio/audio_manager.dart +++ b/lib/src/audio/audio_manager.dart @@ -373,11 +373,11 @@ class AudioManager { } ResolvedAudioSessionPolicy _resolvedAudioSessionPolicy(AudioSessionOptions options) => ResolvedAudioSessionPolicy( - options: options, - preferSpeakerOutput: _preferSpeakerOutput, - forceSpeakerOutput: _forceSpeakerOutput && _preferSpeakerOutput, - automatic: _isAutomaticConfigurationEnabled, - ); + options: options, + preferSpeakerOutput: _preferSpeakerOutput, + forceSpeakerOutput: _forceSpeakerOutput && _preferSpeakerOutput, + automatic: _isAutomaticConfigurationEnabled, + ); /// How microphone input is muted on iOS/macOS. /// diff --git a/lib/src/audio/audio_processing_state.dart b/lib/src/audio/audio_processing_state.dart index ac0cc6ced..d3709bb7e 100644 --- a/lib/src/audio/audio_processing_state.dart +++ b/lib/src/audio/audio_processing_state.dart @@ -25,16 +25,17 @@ enum AudioProcessingImplementation { disabled('disabled'), software('software'), platform('platform'), - softwareAndPlatform('softwareAndPlatform'); + softwareAndPlatform('softwareAndPlatform') + ; const AudioProcessingImplementation(this.value); final String value; static AudioProcessingImplementation fromValue(String? value) => AudioProcessingImplementation.values.firstWhere( - (e) => e.value == value, - orElse: () => AudioProcessingImplementation.unknown, - ); + (e) => e.value == value, + orElse: () => AudioProcessingImplementation.unknown, + ); } AudioProcessingMode _modeFromValue(String? value) { @@ -56,9 +57,9 @@ class AudioProcessingComponentRequest { }); factory AudioProcessingComponentRequest.fromMap(Map map) => AudioProcessingComponentRequest( - enabled: (map['enabled'] as bool?) ?? false, - mode: _modeFromValue(map['mode'] as String?), - ); + enabled: (map['enabled'] as bool?) ?? false, + mode: _modeFromValue(map['mode'] as String?), + ); final bool enabled; final AudioProcessingMode mode; @@ -84,16 +85,16 @@ class AudioProcessingComponentState { }); factory AudioProcessingComponentState.fromMap(Map map) => AudioProcessingComponentState( - requested: map['requested'] is Map - ? AudioProcessingComponentRequest.fromMap(Map.from(map['requested'] as Map)) - : null, - isSoftwareResolved: (map['isSoftwareResolved'] as bool?) ?? false, - isSoftwareActive: (map['isSoftwareActive'] as bool?) ?? false, - isPlatformAvailable: (map['isPlatformAvailable'] as bool?) ?? false, - isPlatformResolved: (map['isPlatformResolved'] as bool?) ?? false, - isPlatformActive: (map['isPlatformActive'] as bool?) ?? false, - effective: AudioProcessingImplementation.fromValue(map['effective'] as String?), - ); + requested: map['requested'] is Map + ? AudioProcessingComponentRequest.fromMap(Map.from(map['requested'] as Map)) + : null, + isSoftwareResolved: (map['isSoftwareResolved'] as bool?) ?? false, + isSoftwareActive: (map['isSoftwareActive'] as bool?) ?? false, + isPlatformAvailable: (map['isPlatformAvailable'] as bool?) ?? false, + isPlatformResolved: (map['isPlatformResolved'] as bool?) ?? false, + isPlatformActive: (map['isPlatformActive'] as bool?) ?? false, + effective: AudioProcessingImplementation.fromValue(map['effective'] as String?), + ); /// What the caller most recently requested for this component. Null when no /// audio processing options have ever been applied — "nobody asked". @@ -141,15 +142,12 @@ class AudioProcessingState { }); factory AudioProcessingState.fromMap(Map map) => AudioProcessingState( - hasAudioProcessingModule: (map['hasAudioProcessingModule'] as bool?) ?? false, - echoCancellation: - AudioProcessingComponentState.fromMap(Map.from(map['echoCancellation'] as Map)), - noiseSuppression: - AudioProcessingComponentState.fromMap(Map.from(map['noiseSuppression'] as Map)), - autoGainControl: - AudioProcessingComponentState.fromMap(Map.from(map['autoGainControl'] as Map)), - highPassFilter: AudioProcessingComponentState.fromMap(Map.from(map['highPassFilter'] as Map)), - ); + hasAudioProcessingModule: (map['hasAudioProcessingModule'] as bool?) ?? false, + echoCancellation: AudioProcessingComponentState.fromMap(Map.from(map['echoCancellation'] as Map)), + noiseSuppression: AudioProcessingComponentState.fromMap(Map.from(map['noiseSuppression'] as Map)), + autoGainControl: AudioProcessingComponentState.fromMap(Map.from(map['autoGainControl'] as Map)), + highPassFilter: AudioProcessingComponentState.fromMap(Map.from(map['highPassFilter'] as Map)), + ); final bool hasAudioProcessingModule; final AudioProcessingComponentState echoCancellation; diff --git a/lib/src/audio/audio_session.dart b/lib/src/audio/audio_session.dart index 3ff7388c5..516ff394b 100644 --- a/lib/src/audio/audio_session.dart +++ b/lib/src/audio/audio_session.dart @@ -57,7 +57,6 @@ enum AudioSessionManagementMode { } @immutable - /// Experimental: this API may change in a future release. @experimental class AudioSessionOptions { @@ -99,11 +98,10 @@ class AudioSessionOptions { AudioSessionOptions copyWith({ ValueOrAbsent apple = const ValueOrAbsent.absent(), ValueOrAbsent android = const ValueOrAbsent.absent(), - }) => - AudioSessionOptions._( - apple: apple.valueOr(this.apple), - android: android.valueOr(this.android), - ); + }) => AudioSessionOptions._( + apple: apple.valueOr(this.apple), + android: android.valueOr(this.android), + ); } // https://developer.apple.com/documentation/avfaudio/avaudiosession/category @@ -146,7 +144,6 @@ enum AppleAudioMode { } @immutable - /// Experimental: this API may change in a future release. @experimental class AppleAudioSessionConfiguration { @@ -185,12 +182,11 @@ class AppleAudioSessionConfiguration { ValueOrAbsent category = const ValueOrAbsent.absent(), ValueOrAbsent?> categoryOptions = const ValueOrAbsent.absent(), ValueOrAbsent mode = const ValueOrAbsent.absent(), - }) => - AppleAudioSessionConfiguration( - category: category.valueOr(this.category), - categoryOptions: categoryOptions.valueOr(this.categoryOptions), - mode: mode.valueOr(this.mode), - ); + }) => AppleAudioSessionConfiguration( + category: category.valueOr(this.category), + categoryOptions: categoryOptions.valueOr(this.categoryOptions), + mode: mode.valueOr(this.mode), + ); } /// Experimental: this API may change in a future release. @@ -254,7 +250,6 @@ enum AndroidAudioAttributesContentType { } @immutable - /// Experimental: this API may change in a future release. @experimental class AndroidAudioSessionConfiguration { @@ -315,14 +310,13 @@ class AndroidAudioSessionConfiguration { ValueOrAbsent usageType = const ValueOrAbsent.absent(), ValueOrAbsent contentType = const ValueOrAbsent.absent(), ValueOrAbsent forceAudioRouting = const ValueOrAbsent.absent(), - }) => - AndroidAudioSessionConfiguration( - audioMode: audioMode.valueOr(this.audioMode), - manageAudioFocus: manageAudioFocus.valueOr(this.manageAudioFocus), - focusMode: focusMode.valueOr(this.focusMode), - streamType: streamType.valueOr(this.streamType), - usageType: usageType.valueOr(this.usageType), - contentType: contentType.valueOr(this.contentType), - forceAudioRouting: forceAudioRouting.valueOr(this.forceAudioRouting), - ); + }) => AndroidAudioSessionConfiguration( + audioMode: audioMode.valueOr(this.audioMode), + manageAudioFocus: manageAudioFocus.valueOr(this.manageAudioFocus), + focusMode: focusMode.valueOr(this.focusMode), + streamType: streamType.valueOr(this.streamType), + usageType: usageType.valueOr(this.usageType), + contentType: contentType.valueOr(this.contentType), + forceAudioRouting: forceAudioRouting.valueOr(this.forceAudioRouting), + ); } diff --git a/lib/src/connection_check/checks/checker.dart b/lib/src/connection_check/checks/checker.dart index 1bc89e1bf..89bce1d59 100644 --- a/lib/src/connection_check/checks/checker.dart +++ b/lib/src/connection_check/checks/checker.dart @@ -144,10 +144,10 @@ abstract class Checker extends Disposable with EventsEmittable { this.token, { CheckerOptions? options, Room? room, - }) : options = options ?? CheckerOptions(), - connectOptions = options?.connectOptions, - _ownsRoom = room == null, - room = room ?? Room(roomOptions: options?.roomOptions ?? const RoomOptions()) { + }) : options = options ?? CheckerOptions(), + connectOptions = options?.connectOptions, + _ownsRoom = room == null, + room = room ?? Room(roomOptions: options?.roomOptions ?? const RoomOptions()) { onDispose(() async { await events.dispose(); if (_ownsRoom) { @@ -356,10 +356,10 @@ abstract class Checker extends Disposable with EventsEmittable { /// The current snapshot of this check. CheckInfo getInfo() => CheckInfo( - name: name, - description: description, - status: status, - logs: List.unmodifiable(logs), - data: data, - ); + name: name, + description: description, + status: status, + logs: List.unmodifiable(logs), + data: data, + ); } diff --git a/lib/src/connection_check/checks/connection_protocol.dart b/lib/src/connection_check/checks/connection_protocol.dart index ec0cb6b42..b79e07adc 100644 --- a/lib/src/connection_check/checks/connection_protocol.dart +++ b/lib/src/connection_check/checks/connection_protocol.dart @@ -42,7 +42,8 @@ class ProtocolStats { int count = 0; @override - String toString() => '$runtimeType(protocol: ${protocol.name}, packetsSent: $packetsSent, ' + String toString() => + '$runtimeType(protocol: ${protocol.name}, packetsSent: $packetsSent, ' 'packetsLost: $packetsLost, count: $count)'; } diff --git a/lib/src/connection_check/checks/turn.dart b/lib/src/connection_check/checks/turn.dart index 97a8adc67..f0da1ccbe 100644 --- a/lib/src/connection_check/checks/turn.dart +++ b/lib/src/connection_check/checks/turn.dart @@ -28,7 +28,8 @@ class TURNCheck extends Checker { Future perform() async { if (isCloudUrl(Uri.parse(url))) { appendMessage('Using region specific url'); - url = await RegionUrlProvider( + url = + await RegionUrlProvider( url: url, token: token, networkOptions: networkOptions, diff --git a/lib/src/connection_check/checks/websocket.dart b/lib/src/connection_check/checks/websocket.dart index 4d3e98e7c..965c98e08 100644 --- a/lib/src/connection_check/checks/websocket.dart +++ b/lib/src/connection_check/checks/websocket.dart @@ -47,8 +47,10 @@ class WebSocketCheck extends Checker { final regionUrl = await regionProvider.getNextBestRegionUrl(); if (regionUrl != null) { joinRes = await signalJoin(regionUrl); - appendMessage('Fallback to region worked. To avoid initial connections failing, ' - 'ensure you\'re calling room.prepareConnection() ahead of time'); + appendMessage( + 'Fallback to region worked. To avoid initial connections failing, ' + 'ensure you\'re calling room.prepareConnection() ahead of time', + ); } } } @@ -60,8 +62,10 @@ class WebSocketCheck extends Checker { appendMessage('LiveKit Cloud: ${joinRes.serverInfo.region}'); } } else { - appendError('Websocket connection could not be established' - '${lastError != null ? ': ${messageFor(lastError)}' : ''}'); + appendError( + 'Websocket connection could not be established' + '${lastError != null ? ': ${messageFor(lastError)}' : ''}', + ); } } } diff --git a/lib/src/core/engine.dart b/lib/src/core/engine.dart index cf2c4c162..89f990c4b 100644 --- a/lib/src/core/engine.dart +++ b/lib/src/core/engine.dart @@ -174,7 +174,8 @@ class Engine extends Disposable with EventsEmittable { return packet.participantSid; } logger.fine( - 'Reliable packet missing participant SID (identity: ${packet.participantIdentity}), skipping dedupe handling'); + 'Reliable packet missing participant SID (identity: ${packet.participantIdentity}), skipping dedupe handling', + ); return null; } @@ -197,9 +198,9 @@ class Engine extends Disposable with EventsEmittable { SignalClient? signalClient, PeerConnectionCreate? peerConnectionCreate, E2EEManager? e2eeManager, - }) : signalClient = signalClient ?? SignalClient(LiveKitWebSocket.connect), - _peerConnectionCreate = peerConnectionCreate ?? rtc.createPeerConnection, - _e2eeManager = e2eeManager { + }) : signalClient = signalClient ?? SignalClient(LiveKitWebSocket.connect), + _peerConnectionCreate = peerConnectionCreate ?? rtc.createPeerConnection, + _e2eeManager = e2eeManager { if (kDebugMode) { // log all EngineEvents events.listen((event) => logger.fine('[EngineEvent] $objectId $event')); @@ -251,8 +252,10 @@ class Engine extends Disposable with EventsEmittable { // wait for join response await events.waitFor( duration: this.connectOptions.timeouts.connection, - onTimeout: () => throw ConnectException('Timed out waiting for SignalJoinResponseEvent', - reason: ConnectionErrorReason.Timeout), + onTimeout: () => throw ConnectException( + 'Timed out waiting for SignalJoinResponseEvent', + reason: ConnectionErrorReason.Timeout, + ), ); logger.fine('Waiting for engine to connect...'); @@ -262,7 +265,8 @@ class Engine extends Disposable with EventsEmittable { filter: (event) => event.isPrimary && event.state.isConnected(), duration: this.connectOptions.timeouts.connection, onTimeout: () => throw MediaConnectException( - 'Timed out waiting for PeerConnection to connect, please check your network for ice connectivity'), + 'Timed out waiting for PeerConnection to connect, please check your network for ice connectivity', + ), ); events.emit(const EngineConnectedEvent()); } catch (error) { @@ -272,11 +276,13 @@ class Engine extends Disposable with EventsEmittable { // attemptReconnect owns disconnect emission, emitting here as well // would produce two events for one failure if (!_isReconnecting && !_attemptingReconnect) { - events.emit(EngineDisconnectedEvent( - reason: error is CertificatePinningException - ? DisconnectReason.signalingConnectionFailure - : DisconnectReason.joinFailure, - )); + events.emit( + EngineDisconnectedEvent( + reason: error is CertificatePinningException + ? DisconnectReason.signalingConnectionFailure + : DisconnectReason.joinFailure, + ), + ); } rethrow; } @@ -473,11 +479,13 @@ class Engine extends Disposable with EventsEmittable { // Buffer reliable packets for potential resending if (reliability == Reliability.reliable) { - _reliableMessageBuffer.push(BufferedDataPacket( - packet: packet, - message: message, - sequence: packet.sequence, - )); + _reliableMessageBuffer.push( + BufferedDataPacket( + packet: packet, + message: message, + sequence: packet.sequence, + ), + ); } // Don't send during reconnection, but keep message buffered for resending @@ -527,19 +535,24 @@ class Engine extends Disposable with EventsEmittable { _publisherConnectionCompleter = completer; unawaited( - _publisherEnsureConnected().then((_) { - if (!completer.isCompleted) { - completer.complete(); - } - }, onError: (Object error, StackTrace stackTrace) { - if (!completer.isCompleted) { - completer.completeError(error, stackTrace); - } - }).whenComplete(() { - if (identical(_publisherConnectionCompleter, completer)) { - _publisherConnectionCompleter = null; - } - }), + _publisherEnsureConnected() + .then( + (_) { + if (!completer.isCompleted) { + completer.complete(); + } + }, + onError: (Object error, StackTrace stackTrace) { + if (!completer.isCompleted) { + completer.completeError(error, stackTrace); + } + }, + ) + .whenComplete(() { + if (identical(_publisherConnectionCompleter, completer)) { + _publisherConnectionCompleter = null; + } + }), ); return completer.future; @@ -548,8 +561,9 @@ class Engine extends Disposable with EventsEmittable { void _resetPublisherConnection() { final completer = _publisherConnectionCompleter; if (completer != null && !completer.isCompleted) { - completer - .completeError(ConnectException('Publisher connection reset', reason: ConnectionErrorReason.InternalError)); + completer.completeError( + ConnectException('Publisher connection reset', reason: ConnectionErrorReason.InternalError), + ); } _publisherConnectionCompleter = null; } @@ -560,7 +574,7 @@ class Engine extends Disposable with EventsEmittable { lk_models.DataPacket_Value.metrics, lk_models.DataPacket_Value.speaker, lk_models.DataPacket_Value.transcription, - lk_models.DataPacket_Value.encryptedPacket + lk_models.DataPacket_Value.encryptedPacket, ].contains(packet.whichValue()) == false) { switch (packet.whichValue()) { @@ -606,9 +620,10 @@ class Engine extends Disposable with EventsEmittable { } } - Future _buildRtcConfiguration( - {required lk_models.ClientConfigSetting serverResponseForceRelay, - required List serverProvidedIceServers}) async { + Future _buildRtcConfiguration({ + required lk_models.ClientConfigSetting serverResponseForceRelay, + required List serverProvidedIceServers, + }) async { // RTCConfiguration? config; RTCConfiguration rtcConfiguration = connectOptions.rtcConfiguration; @@ -633,10 +648,16 @@ class Engine extends Disposable with EventsEmittable { } Future _createPeerConnections(RTCConfiguration rtcConfiguration) async { - publisher = - await Transport.create(_peerConnectionCreate, rtcConfig: rtcConfiguration, connectOptions: connectOptions); - subscriber = - await Transport.create(_peerConnectionCreate, rtcConfig: rtcConfiguration, connectOptions: connectOptions); + publisher = await Transport.create( + _peerConnectionCreate, + rtcConfig: rtcConfiguration, + connectOptions: connectOptions, + ); + subscriber = await Transport.create( + _peerConnectionCreate, + rtcConfig: rtcConfiguration, + connectOptions: connectOptions, + ); publisher?.pc.onIceCandidate = (rtc.RTCIceCandidate candidate) { logger.fine('publisher onIceCandidate'); @@ -673,10 +694,12 @@ class Engine extends Disposable with EventsEmittable { } subscriber?.pc.onConnectionState = (state) async { - events.emit(EngineSubscriberPeerStateUpdatedEvent( - state: state, - isPrimary: _subscriberPrimary, - )); + events.emit( + EngineSubscriberPeerStateUpdatedEvent( + state: state, + isPrimary: _subscriberPrimary, + ), + ); logger.fine('subscriber connectionState: $state'); if (state.isDisconnected() || state.isFailed()) { await handleReconnect( @@ -690,14 +713,16 @@ class Engine extends Disposable with EventsEmittable { if ([ rtc.RTCPeerConnectionState.RTCPeerConnectionStateClosed, rtc.RTCPeerConnectionState.RTCPeerConnectionStateFailed, - rtc.RTCPeerConnectionState.RTCPeerConnectionStateDisconnected + rtc.RTCPeerConnectionState.RTCPeerConnectionStateDisconnected, ].contains(state)) { _resetPublisherConnection(); } - events.emit(EnginePublisherPeerStateUpdatedEvent( - state: state, - isPrimary: !_subscriberPrimary, - )); + events.emit( + EnginePublisherPeerStateUpdatedEvent( + state: state, + isPrimary: !_subscriberPrimary, + ), + ); logger.fine('publisher connectionState: $state'); if (state.isDisconnected() || state.isFailed()) { await handleReconnect( @@ -733,11 +758,13 @@ class Engine extends Disposable with EventsEmittable { final receiver = event.receiver; events.once((event) async { Timer(const Duration(milliseconds: 10), () { - events.emit(EngineTrackAddedEvent( - track: track, - stream: stream, - receiver: receiver, - )); + events.emit( + EngineTrackAddedEvent( + track: track, + stream: stream, + receiver: receiver, + ), + ); }); }); return; @@ -748,11 +775,13 @@ class Engine extends Disposable with EventsEmittable { return; } - events.emit(EngineTrackAddedEvent( - track: event.track, - stream: stream, - receiver: event.receiver, - )); + events.emit( + EngineTrackAddedEvent( + track: event.track, + stream: stream, + receiver: event.receiver, + ), + ); }; // doesn't get called reliably, doesn't work on mac @@ -768,11 +797,15 @@ class Engine extends Disposable with EventsEmittable { ..maxRetransmits = 0; _lossyDCPub = await publisher?.pc.createDataChannel(_lossyDCLabel, lossyInit); _lossyDCPub?.onMessage = _onDCMessage; - _lossyDCPub?.stateChangeStream.listen((state) => events.emit(PublisherDataChannelStateUpdatedEvent( + _lossyDCPub?.stateChangeStream.listen( + (state) => events.emit( + PublisherDataChannelStateUpdatedEvent( isPrimary: !_subscriberPrimary, state: state, type: Reliability.lossy, - ))); + ), + ), + ); // _onDCStateUpdated(Reliability.lossy, state) _lossyDCPub?.bufferedAmountLowThreshold = 2 * 1024 * 1024; _lossyDCPub?.onBufferedAmountLow = (_) { @@ -788,11 +821,15 @@ class Engine extends Disposable with EventsEmittable { ..ordered = true; _reliableDCPub = await publisher?.pc.createDataChannel(_reliableDCLabel, reliableInit); _reliableDCPub?.onMessage = _onDCMessage; - _reliableDCPub?.stateChangeStream.listen((state) => events.emit(PublisherDataChannelStateUpdatedEvent( + _reliableDCPub?.stateChangeStream.listen( + (state) => events.emit( + PublisherDataChannelStateUpdatedEvent( isPrimary: !_subscriberPrimary, state: state, type: Reliability.reliable, - ))); + ), + ), + ); _reliableDCPub?.bufferedAmountLowThreshold = 2 * 1024 * 1024; _reliableDCPub?.onBufferedAmountLow = (_) { _dcBufferStatus[Reliability.reliable] = @@ -809,23 +846,33 @@ class Engine extends Disposable with EventsEmittable { logger.fine('Server opened DC label: ${dc.label}'); _reliableDCSub = dc; _reliableDCSub?.onMessage = _onDCMessage; - _reliableDCSub?.stateChangeStream.listen((state) => - _reliableDCPub?.stateChangeStream.listen((state) => events.emit(SubscriberDataChannelStateUpdatedEvent( - isPrimary: _subscriberPrimary, - state: state, - type: Reliability.reliable, - )))); + _reliableDCSub?.stateChangeStream.listen( + (state) => _reliableDCPub?.stateChangeStream.listen( + (state) => events.emit( + SubscriberDataChannelStateUpdatedEvent( + isPrimary: _subscriberPrimary, + state: state, + type: Reliability.reliable, + ), + ), + ), + ); break; case _lossyDCLabel: logger.fine('Server opened DC label: ${dc.label}'); _lossyDCSub = dc; _lossyDCSub?.onMessage = _onDCMessage; - _lossyDCSub?.stateChangeStream.listen((event) => - _reliableDCPub?.stateChangeStream.listen((state) => events.emit(SubscriberDataChannelStateUpdatedEvent( - isPrimary: _subscriberPrimary, - state: state, - type: Reliability.lossy, - )))); + _lossyDCSub?.stateChangeStream.listen( + (event) => _reliableDCPub?.stateChangeStream.listen( + (state) => events.emit( + SubscriberDataChannelStateUpdatedEvent( + isPrimary: _subscriberPrimary, + state: state, + type: Reliability.lossy, + ), + ), + ), + ); break; default: logger.warning('Unknown DC label: ${dc.label}'); @@ -877,8 +924,10 @@ class Engine extends Disposable with EventsEmittable { final sequence = dp.sequence; final lastReceived = _reliableReceivedState.get(participantKey) ?? 0; if (sequence <= lastReceived) { - logger.fine('Ignoring duplicate or out-of-order packet: ' - 'sequence=$sequence, lastReceived=$lastReceived, participantSid=$participantKey'); + logger.fine( + 'Ignoring duplicate or out-of-order packet: ' + 'sequence=$sequence, lastReceived=$lastReceived, participantSid=$participantKey', + ); return; } _reliableReceivedState.set(participantKey, sequence); @@ -902,8 +951,10 @@ class Engine extends Disposable with EventsEmittable { final sequence = dp.sequence; final lastReceived = _reliableReceivedState.get(participantKey) ?? 0; if (sequence <= lastReceived) { - logger.fine('Ignoring duplicate or out-of-order packet: ' - 'sequence=$sequence, lastReceived=$lastReceived, participantSid=$participantKey'); + logger.fine( + 'Ignoring duplicate or out-of-order packet: ' + 'sequence=$sequence, lastReceived=$lastReceived, participantSid=$participantKey', + ); return; } _reliableReceivedState.set(participantKey, sequence); @@ -919,46 +970,60 @@ class Engine extends Disposable with EventsEmittable { void _emitDataPacket(lk_models.DataPacket dp, {EncryptionType encryptionType = EncryptionType.kNone}) { if (dp.whichValue() == lk_models.DataPacket_Value.speaker) { // Speaker packet - events.emit(EngineActiveSpeakersUpdateEvent( - speakers: dp.speaker.speakers, - )); + events.emit( + EngineActiveSpeakersUpdateEvent( + speakers: dp.speaker.speakers, + ), + ); } else if (dp.whichValue() == lk_models.DataPacket_Value.user) { // User packet - events.emit(EngineDataPacketReceivedEvent( - packet: dp.user, - kind: dp.kind, - identity: dp.participantIdentity, - )); + events.emit( + EngineDataPacketReceivedEvent( + packet: dp.user, + kind: dp.kind, + identity: dp.participantIdentity, + ), + ); } else if (dp.whichValue() == lk_models.DataPacket_Value.transcription) { // Transcription packet - events.emit(EngineTranscriptionReceivedEvent( - transcription: dp.transcription, - identity: dp.participantIdentity, - )); + events.emit( + EngineTranscriptionReceivedEvent( + transcription: dp.transcription, + identity: dp.participantIdentity, + ), + ); } else if (dp.whichValue() == lk_models.DataPacket_Value.sipDtmf) { // SIP DTMF packet - events.emit(EngineSipDtmfReceivedEvent( - dtmf: dp.sipDtmf, - identity: dp.participantIdentity, - )); + events.emit( + EngineSipDtmfReceivedEvent( + dtmf: dp.sipDtmf, + identity: dp.participantIdentity, + ), + ); } else if (dp.whichValue() == lk_models.DataPacket_Value.rpcRequest) { // RPC Request - events.emit(EngineRPCRequestReceivedEvent( - request: dp.rpcRequest, - identity: dp.participantIdentity, - )); + events.emit( + EngineRPCRequestReceivedEvent( + request: dp.rpcRequest, + identity: dp.participantIdentity, + ), + ); } else if (dp.whichValue() == lk_models.DataPacket_Value.rpcResponse) { // RPC Response - events.emit(EngineRPCResponseReceivedEvent( - response: dp.rpcResponse, - identity: dp.participantIdentity, - )); + events.emit( + EngineRPCResponseReceivedEvent( + response: dp.rpcResponse, + identity: dp.participantIdentity, + ), + ); } else if (dp.whichValue() == lk_models.DataPacket_Value.rpcAck) { // RPC Ack - events.emit(EngineRPCAckReceivedEvent( - ack: dp.rpcAck, - identity: dp.participantIdentity, - )); + events.emit( + EngineRPCAckReceivedEvent( + ack: dp.rpcAck, + identity: dp.participantIdentity, + ), + ); } else if (dp.whichValue() == lk_models.DataPacket_Value.streamHeader) { // Data Stream Header events.emit( @@ -1014,9 +1079,11 @@ class Engine extends Disposable with EventsEmittable { _isClosed = true; await cleanUp(); - events.emit(EngineDisconnectedEvent( - reason: DisconnectReason.reconnectAttemptsExceeded, - )); + events.emit( + EngineDisconnectedEvent( + reason: DisconnectReason.reconnectAttemptsExceeded, + ), + ); return; } @@ -1026,11 +1093,13 @@ class Engine extends Disposable with EventsEmittable { delay += math.Random().nextInt(1000); } - events.emit(EngineAttemptReconnectEvent( - attempt: _reconnectAttempts + 1, - maxAttempts: _reconnectCount, - nextRetryDelaysInMs: delay, - )); + events.emit( + EngineAttemptReconnectEvent( + attempt: _reconnectAttempts + 1, + maxAttempts: _reconnectCount, + nextRetryDelaysInMs: delay, + ), + ); _clearReconnectTimeout(); if (token != null && _regionUrlProvider != null) { @@ -1079,8 +1148,9 @@ class Engine extends Disposable with EventsEmittable { duration: connectOptions.timeouts.connection * 10, filter: (event) => !event.state.contains(ConnectivityResult.none), onTimeout: () => throw ConnectException( - 'attemptReconnect: Timed out waiting for SignalConnectivityChangedEvent', - reason: ConnectionErrorReason.Timeout), + 'attemptReconnect: Timed out waiting for SignalConnectivityChangedEvent', + reason: ConnectionErrorReason.Timeout, + ), ); } @@ -1117,11 +1187,13 @@ class Engine extends Disposable with EventsEmittable { // drops the event while fullReconnectOnNext is still true and // cleanUp() is what resets it await cleanUp(); - events.emit(EngineDisconnectedEvent( - reason: e is CertificatePinningException - ? DisconnectReason.signalingConnectionFailure - : DisconnectReason.disconnected, - )); + events.emit( + EngineDisconnectedEvent( + reason: e is CertificatePinningException + ? DisconnectReason.signalingConnectionFailure + : DisconnectReason.disconnected, + ), + ); } } finally { _attemptingReconnect = false; @@ -1150,17 +1222,21 @@ class Engine extends Disposable with EventsEmittable { await events.waitFor( duration: connectOptions.timeouts.connection, - onTimeout: () => throw ConnectException('resumeConnection: Timed out waiting for SignalReconnectedEvent', - reason: ConnectionErrorReason.Timeout), + onTimeout: () => throw ConnectException( + 'resumeConnection: Timed out waiting for SignalReconnectedEvent', + reason: ConnectionErrorReason.Timeout, + ), ); logger.fine('resumeConnection: reason: ${reason.name}'); if (_hasPublished) { logger.fine('resumeConnection: negotiating publisher...'); - await publisher!.createAndSendOffer(const RTCOfferOptions( - iceRestart: true, - )); + await publisher!.createAndSendOffer( + const RTCOfferOptions( + iceRestart: true, + ), + ); } final isConnected = (await primary?.pc.getConnectionState())?.isConnected() ?? false; @@ -1283,9 +1359,9 @@ class Engine extends Disposable with EventsEmittable { } void _setUpEngineListeners() => events.on((event) async { - // send queued requests if engine re-connected - signalClient.sendQueuedRequests(); - }); + // send queued requests if engine re-connected + signalClient.sendQueuedRequests(); + }); void _setUpSignalListeners() => _signalListener ..on((event) async { @@ -1300,14 +1376,17 @@ class Engine extends Disposable with EventsEmittable { _clientConfiguration = event.response.clientConfiguration; - logger.fine('onConnected subscriberPrimary: ${_subscriberPrimary}, ' - 'serverVersion: ${event.response.serverVersion}, ' - 'iceServers: ${event.response.iceServers}, ' - 'forceRelay: ${event.response.clientConfiguration.forceRelay}'); + logger.fine( + 'onConnected subscriberPrimary: ${_subscriberPrimary}, ' + 'serverVersion: ${event.response.serverVersion}, ' + 'iceServers: ${event.response.iceServers}, ' + 'forceRelay: ${event.response.clientConfiguration.forceRelay}', + ); final rtcConfiguration = await _buildRtcConfiguration( - serverResponseForceRelay: event.response.clientConfiguration.forceRelay, - serverProvidedIceServers: _serverProvidedIceServers); + serverResponseForceRelay: event.response.clientConfiguration.forceRelay, + serverProvidedIceServers: _serverProvidedIceServers, + ); if (publisher == null && subscriber == null) { await _createPeerConnections(rtcConfiguration); @@ -1333,14 +1412,17 @@ class Engine extends Disposable with EventsEmittable { _clientConfiguration = event.response.clientConfiguration; - logger.fine('Handle ReconnectResponse: ' - 'iceServers: ${event.response.iceServers}, ' - 'forceRelay: ${event.response.clientConfiguration.forceRelay}, ' - 'lastMessageSeq: ${event.response.lastMessageSeq}'); + logger.fine( + 'Handle ReconnectResponse: ' + 'iceServers: ${event.response.iceServers}, ' + 'forceRelay: ${event.response.clientConfiguration.forceRelay}, ' + 'lastMessageSeq: ${event.response.lastMessageSeq}', + ); final rtcConfiguration = await _buildRtcConfiguration( - serverResponseForceRelay: event.response.clientConfiguration.forceRelay, - serverProvidedIceServers: _serverProvidedIceServers); + serverResponseForceRelay: event.response.clientConfiguration.forceRelay, + serverProvidedIceServers: _serverProvidedIceServers, + ); await publisher?.pc.setConfiguration(rtcConfiguration.toMap()); await subscriber?.pc.setConfiguration(rtcConfiguration.toMap()); @@ -1372,8 +1454,10 @@ class Engine extends Disposable with EventsEmittable { ..on((event) async { logger.fine('Signal disconnected ${event.reason}'); if (event.reason == DisconnectReason.disconnected && !_isClosed) { - await handleReconnect(ClientDisconnectReason.signal, - reconnectReason: lk_models.ReconnectReason.RR_SIGNAL_DISCONNECTED); + await handleReconnect( + ClientDisconnectReason.signal, + reconnectReason: lk_models.ReconnectReason.RR_SIGNAL_DISCONNECTED, + ); } // signalingConnectionFailure is intentionally not relayed as // EngineDisconnectedEvent here. The signal client emits it while the @@ -1387,8 +1471,10 @@ class Engine extends Disposable with EventsEmittable { return; } final signalingState = await subscriber!.pc.getSignalingState(); - logger.fine('[$objectId] Received server offer(type: ${event.sd.type}, ' - '$signalingState)'); + logger.fine( + '[$objectId] Received server offer(type: ${event.sd.type}, ' + '$signalingState)', + ); logger.finer('sdp: ${event.sd.sdp}'); await subscriber!.setRemoteDescription(event.sd); @@ -1424,9 +1510,11 @@ class Engine extends Disposable with EventsEmittable { } }) ..on((event) async { - events.emit(EngineLocalTrackSubscribedEvent( - trackSid: event.trackSid, - )); + events.emit( + EngineLocalTrackSubscribedEvent( + trackSid: event.trackSid, + ), + ); }) ..on((event) { logger.fine('Server refreshed the token'); diff --git a/lib/src/core/room.dart b/lib/src/core/room.dart index 44939b750..ae2d8bb36 100644 --- a/lib/src/core/room.dart +++ b/lib/src/core/room.dart @@ -161,10 +161,10 @@ class Room extends DisposableChangeNotifier with EventsEmittable { // getter would surprise SDK consumers — filter them out here. @internal Map get textStreamHandlers => Map.fromEntries( - _textStreamHandlers.entries.where( - (e) => e.key != kRpcRequestTopic && e.key != kRpcResponseTopic, - ), - ); + _textStreamHandlers.entries.where( + (e) => e.key != kRpcRequestTopic && e.key != kRpcResponseTopic, + ), + ); @internal Map get byteStreamHandlers => _byteStreamHandlers; @@ -174,11 +174,12 @@ class Room extends DisposableChangeNotifier with EventsEmittable { ConnectOptions connectOptions = const ConnectOptions(), RoomOptions roomOptions = const RoomOptions(), Engine? engine, - }) : engine = engine ?? - Engine( - connectOptions: connectOptions, - roomOptions: roomOptions, - ) { + }) : engine = + engine ?? + Engine( + connectOptions: connectOptions, + roomOptions: roomOptions, + ) { // _engineListener = this.engine.createListener(); _setUpEngineListeners(); @@ -276,8 +277,10 @@ class Room extends DisposableChangeNotifier with EventsEmittable { }) async { var roomOptions = this.roomOptions; if (lkPlatformIs(PlatformType.web) && (roomOptions.networkOptions.certificatePinning?.isEnabled ?? false)) { - throw UnsupportedError('Certificate pinning is not supported on Flutter web, ' - 'remove certificatePinning from NetworkOptions when targeting web'); + throw UnsupportedError( + 'Certificate pinning is not supported on Flutter web, ' + 'remove certificatePinning from NetworkOptions when targeting web', + ); } connectOptions ??= ConnectOptions(); _pendingTrackQueue.updateTtl(connectOptions.timeouts.subscribe); @@ -317,11 +320,16 @@ class Room extends DisposableChangeNotifier with EventsEmittable { // trigger the first fetch without waiting for a response // if initial connection fails, this will speed up picking regional url // on subsequent runs - unawaited(_regionUrlProvider?.fetchRegionSettings().then((settings) { - _regionUrlProvider?.setServerReportedRegions(settings); - }).catchError((e) { - logger.warning('could not fetch region settings $e'); - })); + unawaited( + _regionUrlProvider + ?.fetchRegionSettings() + .then((settings) { + _regionUrlProvider?.setServerReportedRegions(settings); + }) + .catchError((e) { + logger.warning('could not fetch region settings $e'); + }), + ); } // Bridge a legacy RoomOptions speaker preference into the process-wide @@ -392,8 +400,10 @@ class Room extends DisposableChangeNotifier with EventsEmittable { ..on((event) async { // Dynacast is off or is unsupported if (!roomOptions.dynacast || _serverVersion == '0.15.1') { - logger.fine('Received subscribed quality update' - ' but Dynacast is off or server version is not supported.'); + logger.fine( + 'Received subscribed quality update' + ' but Dynacast is off or server version is not supported.', + ); return; } // Find the publication @@ -416,15 +426,20 @@ class Room extends DisposableChangeNotifier with EventsEmittable { } } else if (event.subscribedQualities.isNotEmpty) { final videoTrack = publication.track as LocalVideoTrack; - await videoTrack.setPublishingLayers(videoTrack, event.subscribedQualities, - isSVC: isSVCCodec(videoTrack.codec ?? '')); + await videoTrack.setPublishingLayers( + videoTrack, + event.subscribedQualities, + isSVC: isSVCCodec(videoTrack.codec ?? ''), + ); } }) ..on((event) async { - logger.fine('SignalSubscriptionPermissionUpdateEvent ' - 'participantSid:${event.participantSid} ' - 'trackSid:${event.trackSid} ' - 'allowed:${event.allowed}'); + logger.fine( + 'SignalSubscriptionPermissionUpdateEvent ' + 'participantSid:${event.participantSid} ' + 'trackSid:${event.trackSid} ' + 'allowed:${event.allowed}', + ); // find participant final participant = _remoteParticipants.bySid[event.participantSid]; @@ -438,11 +453,13 @@ class Room extends DisposableChangeNotifier with EventsEmittable { } // await publication.updateSubscriptionAllowed(event.allowed); - emitWhenConnected(TrackSubscriptionPermissionChangedEvent( - participant: participant, - publication: publication, - state: publication.subscriptionState, - )); + emitWhenConnected( + TrackSubscriptionPermissionChangedEvent( + participant: participant, + publication: publication, + state: publication.subscriptionState, + ), + ); }) ..on((event) async => _applyRoomUpdate(event.room)) ..on((event) async { @@ -471,8 +488,10 @@ class Room extends DisposableChangeNotifier with EventsEmittable { _serverVersion = event.response.serverVersion; _serverRegion = event.response.serverRegion; - logger.fine('[Engine] Received JoinResponse, ' - 'serverVersion: ${event.response.serverVersion}'); + logger.fine( + '[Engine] Received JoinResponse, ' + 'serverVersion: ${event.response.serverVersion}', + ); _localParticipant ??= await LocalParticipant.createFromInfo( room: this, @@ -503,11 +522,15 @@ class Room extends DisposableChangeNotifier with EventsEmittable { // Only enable microphone if preconnect buffer is not active if (audioEnabled && !preConnectAudioBuffer.isRecording) { if (audio.track != null) { - await _localParticipant!.publishAudioTrack(audio.track as LocalAudioTrack, - publishOptions: roomOptions.defaultAudioPublishOptions); + await _localParticipant!.publishAudioTrack( + audio.track as LocalAudioTrack, + publishOptions: roomOptions.defaultAudioPublishOptions, + ); } else { - await _localParticipant! - .setMicrophoneEnabled(true, audioCaptureOptions: roomOptions.defaultAudioCaptureOptions); + await _localParticipant!.setMicrophoneEnabled( + true, + audioCaptureOptions: roomOptions.defaultAudioCaptureOptions, + ); } } @@ -515,11 +538,15 @@ class Room extends DisposableChangeNotifier with EventsEmittable { final bool videoEnabled = video.enabled == true || video.track != null; if (videoEnabled) { if (video.track != null) { - await _localParticipant!.publishVideoTrack(video.track as LocalVideoTrack, - publishOptions: roomOptions.defaultVideoPublishOptions); + await _localParticipant!.publishVideoTrack( + video.track as LocalVideoTrack, + publishOptions: roomOptions.defaultVideoPublishOptions, + ); } else { - await _localParticipant! - .setCameraEnabled(true, cameraCaptureOptions: roomOptions.defaultCameraCaptureOptions); + await _localParticipant!.setCameraEnabled( + true, + cameraCaptureOptions: roomOptions.defaultCameraCaptureOptions, + ); } } @@ -527,18 +554,24 @@ class Room extends DisposableChangeNotifier with EventsEmittable { final bool screenEnabled = screen.enabled == true || screen.track != null; if (screenEnabled) { if (screen.track != null) { - await _localParticipant!.publishVideoTrack(screen.track as LocalVideoTrack, - publishOptions: roomOptions.defaultVideoPublishOptions); + await _localParticipant!.publishVideoTrack( + screen.track as LocalVideoTrack, + publishOptions: roomOptions.defaultVideoPublishOptions, + ); } else { - await _localParticipant! - .setScreenShareEnabled(true, screenShareCaptureOptions: roomOptions.defaultScreenShareCaptureOptions); + await _localParticipant!.setScreenShareEnabled( + true, + screenShareCaptureOptions: roomOptions.defaultScreenShareCaptureOptions, + ); } } } for (final info in event.response.otherParticipants) { - logger.fine('Creating RemoteParticipant: sid = ${info.sid}(identity:${info.identity}) ' - 'tracks:${info.tracks.map((e) => e.sid)}'); + logger.fine( + 'Creating RemoteParticipant: sid = ${info.sid}(identity:${info.identity}) ' + 'tracks:${info.tracks.map((e) => e.sid)}', + ); await _getOrCreateRemoteParticipant(info); } @@ -599,11 +632,13 @@ class Room extends DisposableChangeNotifier with EventsEmittable { await _sendSyncState(); }) ..on((event) async { - events.emit(RoomAttemptReconnectEvent( - attempt: event.attempt, - maxAttemptsRetry: event.maxAttempts, - nextRetryDelaysInMs: event.nextRetryDelaysInMs, - )); + events.emit( + RoomAttemptReconnectEvent( + attempt: event.attempt, + maxAttemptsRetry: event.maxAttempts, + nextRetryDelaysInMs: event.nextRetryDelaysInMs, + ), + ); notifyListeners(); }) ..on((event) async { @@ -849,30 +884,30 @@ class Room extends DisposableChangeNotifier with EventsEmittable { } Future _flushPendingTracks({RemoteParticipant? participant}) => _pendingTrackQueue.flush( - isConnected: connectionState == ConnectionState.connected, - participantSid: participant?.sid, - subscriber: (pending) async { - final target = participant ?? _remoteParticipants.bySid[pending.participantSid]; - if (target == null) return false; - try { - await target.addSubscribedMediaTrack( - pending.track, - pending.stream, - pending.trackSid, - receiver: pending.receiver, - audioOutputOptions: roomOptions.defaultAudioOutputOptions, - ); - return true; - } on TrackSubscriptionExceptionEvent catch (event) { - logger.severe('Track subscription failed during flush: ${event}'); - events.emit(event); - return true; - } catch (exception) { - logger.warning('Unknown exception during pending track flush: ${exception}'); - return false; - } - }, - ); + isConnected: connectionState == ConnectionState.connected, + participantSid: participant?.sid, + subscriber: (pending) async { + final target = participant ?? _remoteParticipants.bySid[pending.participantSid]; + if (target == null) return false; + try { + await target.addSubscribedMediaTrack( + pending.track, + pending.stream, + pending.trackSid, + receiver: pending.receiver, + audioOutputOptions: roomOptions.defaultAudioOutputOptions, + ); + return true; + } on TrackSubscriptionExceptionEvent catch (event) { + logger.severe('Track subscription failed during flush: ${event}'); + events.emit(event); + return true; + } catch (exception) { + logger.warning('Unknown exception during pending track flush: ${exception}'); + return false; + } + }, + ); // from data channel // updates are sent only when there's a change to speaker ordering @@ -936,11 +971,13 @@ class Room extends DisposableChangeNotifier with EventsEmittable { if (trackPublication == null) continue; // update the stream state await trackPublication.updateStreamState(update.state.toLKType()); - emitWhenConnected(TrackStreamStateUpdatedEvent( - participant: participant, - publication: trackPublication, - streamState: update.state.toLKType(), - )); + emitWhenConnected( + TrackStreamStateUpdatedEvent( + participant: participant, + publication: trackPublication, + streamState: update.state.toLKType(), + ), + ); } } @@ -1187,11 +1224,12 @@ extension RoomDebugMethods on Room { return; } engine.signalClient.sendSimulateScenario( - speakerUpdate: speakerUpdate, - nodeFailure: nodeFailure, - migration: migration, - serverLeave: serverLeave, - switchCandidate: switchCandidate); + speakerUpdate: speakerUpdate, + nodeFailure: nodeFailure, + migration: migration, + serverLeave: serverLeave, + switchCandidate: switchCandidate, + ); } } @@ -1439,7 +1477,10 @@ extension DataStreamRoomMethods on Room { @internal Future handleStreamHeader( - lk_models.DataStream_Header streamHeader, String participantIdentity, EncryptionType encryptionType) async { + lk_models.DataStream_Header streamHeader, + String participantIdentity, + EncryptionType encryptionType, + ) async { if (streamHeader.hasByteHeader()) { final streamHandlerCallback = _byteStreamHandlers[streamHeader.topic]; diff --git a/lib/src/core/signal_client.dart b/lib/src/core/signal_client.dart index f6ce6fc0a..bc407a478 100644 --- a/lib/src/core/signal_client.dart +++ b/lib/src/core/signal_client.dart @@ -116,10 +116,12 @@ class SignalClient extends Disposable with EventsEmittable { } else { logger.info('Connectivity changed, ${_connectivityResult} => ${result}'); } - events.emit(SignalConnectivityChangedEvent( - oldState: _connectivityResult, - state: result, - )); + events.emit( + SignalConnectivityChangedEvent( + oldState: _connectivityResult, + state: result, + ), + ); _connectivityResult = result; } }); @@ -205,11 +207,13 @@ class SignalClient extends Disposable with EventsEmittable { networkOptions: roomOptions.networkOptions, ); if (validateResponse.statusCode != 200) { - finalError = ConnectException(validateResponse.body, - reason: validateResponse.statusCode >= 400 - ? ConnectionErrorReason.NotAllowed - : ConnectionErrorReason.InternalError, - statusCode: validateResponse.statusCode); + finalError = ConnectException( + validateResponse.body, + reason: validateResponse.statusCode >= 400 + ? ConnectionErrorReason.NotAllowed + : ConnectionErrorReason.InternalError, + statusCode: validateResponse.statusCode, + ); } } catch (error) { if (socketError.runtimeType != error.runtimeType) { @@ -223,12 +227,15 @@ class SignalClient extends Disposable with EventsEmittable { } Future sendLeave() async { - _sendRequest(lk_rtc.SignalRequest( + _sendRequest( + lk_rtc.SignalRequest( leave: lk_rtc.LeaveRequest( - reason: lk_models.DisconnectReason.CLIENT_INITIATED, - // server doesn't process this field, keeping it here to indicate the intent of a full disconnect - action: lk_rtc.LeaveRequest_Action.DISCONNECT, - ))); + reason: lk_models.DisconnectReason.CLIENT_INITIATED, + // server doesn't process this field, keeping it here to indicate the intent of a full disconnect + action: lk_rtc.LeaveRequest_Action.DISCONNECT, + ), + ), + ); } // resets internal state to a re-usable state @@ -286,29 +293,37 @@ class SignalClient extends Disposable with EventsEmittable { events.emit(SignalOfferEvent(sd: msg.offer.toSDKType())); break; case lk_rtc.SignalResponse_Message.trickle: - events.emit(SignalTrickleEvent( - candidate: RTCIceCandidateExt.fromJson(msg.trickle.candidateInit), - target: msg.trickle.target, - )); + events.emit( + SignalTrickleEvent( + candidate: RTCIceCandidateExt.fromJson(msg.trickle.candidateInit), + target: msg.trickle.target, + ), + ); break; case lk_rtc.SignalResponse_Message.update: events.emit(SignalParticipantUpdateEvent(participants: msg.update.participants)); break; case lk_rtc.SignalResponse_Message.trackPublished: - events.emit(SignalLocalTrackPublishedEvent( - cid: msg.trackPublished.cid, - track: msg.trackPublished.track, - )); + events.emit( + SignalLocalTrackPublishedEvent( + cid: msg.trackPublished.cid, + track: msg.trackPublished.track, + ), + ); break; case lk_rtc.SignalResponse_Message.trackUnpublished: - events.emit(SignalTrackUnpublishedEvent( - trackSid: msg.trackUnpublished.trackSid, - )); + events.emit( + SignalTrackUnpublishedEvent( + trackSid: msg.trackUnpublished.trackSid, + ), + ); break; case lk_rtc.SignalResponse_Message.trackSubscribed: - events.emit(SignalLocalTrackSubscribedEvent( - trackSid: msg.trackSubscribed.trackSid, - )); + events.emit( + SignalLocalTrackSubscribedEvent( + trackSid: msg.trackSubscribed.trackSid, + ), + ); break; case lk_rtc.SignalResponse_Message.speakersChanged: events.emit(SignalSpeakersChangedEvent(speakers: msg.speakersChanged.speakers)); @@ -317,38 +332,48 @@ class SignalClient extends Disposable with EventsEmittable { events.emit(SignalRoomUpdateEvent(room: msg.roomUpdate.room)); break; case lk_rtc.SignalResponse_Message.connectionQuality: - events.emit(SignalConnectionQualityUpdateEvent( - updates: msg.connectionQuality.updates, - )); + events.emit( + SignalConnectionQualityUpdateEvent( + updates: msg.connectionQuality.updates, + ), + ); break; case lk_rtc.SignalResponse_Message.leave: events.emit(SignalLeaveEvent(request: msg.leave)); break; case lk_rtc.SignalResponse_Message.mute: - events.emit(SignalRemoteMuteTrackEvent( - sid: msg.mute.sid, - muted: msg.mute.muted, - )); + events.emit( + SignalRemoteMuteTrackEvent( + sid: msg.mute.sid, + muted: msg.mute.muted, + ), + ); break; case lk_rtc.SignalResponse_Message.streamStateUpdate: - events.emit(SignalStreamStateUpdatedEvent( - updates: msg.streamStateUpdate.streamStates, - )); + events.emit( + SignalStreamStateUpdatedEvent( + updates: msg.streamStateUpdate.streamStates, + ), + ); break; case lk_rtc.SignalResponse_Message.subscribedQualityUpdate: - events.emit(SignalSubscribedQualityUpdatedEvent( - trackSid: msg.subscribedQualityUpdate.trackSid, - // ignore: deprecated_member_use_from_same_package - subscribedQualities: msg.subscribedQualityUpdate.subscribedQualities, - subscribedCodecs: msg.subscribedQualityUpdate.subscribedCodecs, - )); + events.emit( + SignalSubscribedQualityUpdatedEvent( + trackSid: msg.subscribedQualityUpdate.trackSid, + // ignore: deprecated_member_use_from_same_package + subscribedQualities: msg.subscribedQualityUpdate.subscribedQualities, + subscribedCodecs: msg.subscribedQualityUpdate.subscribedCodecs, + ), + ); break; case lk_rtc.SignalResponse_Message.subscriptionPermissionUpdate: - events.emit(SignalSubscriptionPermissionUpdateEvent( - participantSid: msg.subscriptionPermissionUpdate.participantSid, - trackSid: msg.subscriptionPermissionUpdate.trackSid, - allowed: msg.subscriptionPermissionUpdate.allowed, - )); + events.emit( + SignalSubscriptionPermissionUpdateEvent( + participantSid: msg.subscriptionPermissionUpdate.participantSid, + trackSid: msg.subscriptionPermissionUpdate.trackSid, + allowed: msg.subscriptionPermissionUpdate.allowed, + ), + ); break; case lk_rtc.SignalResponse_Message.refreshToken: events.emit(SignalTokenUpdatedEvent(token: msg.refreshToken)); @@ -402,11 +427,13 @@ class SignalClient extends Disposable with EventsEmittable { final now = DateTime.timestamp().millisecondsSinceEpoch; // Send both ping and pingReq for compatibility with old and new servers _sendRequest(lk_rtc.SignalRequest()..ping = Int64(now)); - _sendRequest(lk_rtc.SignalRequest() - ..pingReq = lk_rtc.Ping( - timestamp: Int64(now), - rtt: Int64(_rtt), - )); + _sendRequest( + lk_rtc.SignalRequest() + ..pingReq = lk_rtc.Ping( + timestamp: Int64(now), + rtt: Int64(_rtt), + ), + ); } void _startPingInterval() { @@ -447,37 +474,45 @@ class SignalClient extends Disposable with EventsEmittable { extension SignalClientRequests on SignalClient { @internal - void sendOffer(rtc.RTCSessionDescription offer) => _sendRequest(lk_rtc.SignalRequest( - offer: offer.toPBType(), - )); + void sendOffer(rtc.RTCSessionDescription offer) => _sendRequest( + lk_rtc.SignalRequest( + offer: offer.toPBType(), + ), + ); @internal - void sendAnswer(rtc.RTCSessionDescription answer) => _sendRequest(lk_rtc.SignalRequest( - answer: answer.toPBType(), - )); + void sendAnswer(rtc.RTCSessionDescription answer) => _sendRequest( + lk_rtc.SignalRequest( + answer: answer.toPBType(), + ), + ); @internal void sendIceCandidate(rtc.RTCIceCandidate candidate, lk_rtc.SignalTarget target) => _sendRequest( - lk_rtc.SignalRequest( - trickle: lk_rtc.TrickleRequest( - candidateInit: candidate.toJson(), - target: target, - ), - ), - ); + lk_rtc.SignalRequest( + trickle: lk_rtc.TrickleRequest( + candidateInit: candidate.toJson(), + target: target, + ), + ), + ); @internal - void sendMuteTrack(String trackSid, bool muted) => _sendRequest(lk_rtc.SignalRequest( - mute: lk_rtc.MuteTrackRequest( - sid: trackSid, - muted: muted, - ), - )); + void sendMuteTrack(String trackSid, bool muted) => _sendRequest( + lk_rtc.SignalRequest( + mute: lk_rtc.MuteTrackRequest( + sid: trackSid, + muted: muted, + ), + ), + ); @internal - void sendAddTrack(lk_rtc.AddTrackRequest req) => _sendRequest(lk_rtc.SignalRequest( - addTrack: req, - )); + void sendAddTrack(lk_rtc.AddTrackRequest req) => _sendRequest( + lk_rtc.SignalRequest( + addTrack: req, + ), + ); @internal int sendUpdateLocalMetadata(lk_rtc.UpdateParticipantMetadata metadata) { @@ -488,26 +523,31 @@ extension SignalClientRequests on SignalClient { } @internal - void sendUpdateTrackSettings(lk_rtc.UpdateTrackSettings settings) => _sendRequest(lk_rtc.SignalRequest( - trackSetting: settings, - )); + void sendUpdateTrackSettings(lk_rtc.UpdateTrackSettings settings) => _sendRequest( + lk_rtc.SignalRequest( + trackSetting: settings, + ), + ); @internal - void sendUpdateSubscription(lk_rtc.UpdateSubscription subscription) => _sendRequest(lk_rtc.SignalRequest( - subscription: subscription, - )); + void sendUpdateSubscription(lk_rtc.UpdateSubscription subscription) => _sendRequest( + lk_rtc.SignalRequest( + subscription: subscription, + ), + ); @internal void sendUpdateSubscriptionPermissions({ required bool allParticipants, required Iterable trackPermissions, - }) => - _sendRequest(lk_rtc.SignalRequest( - subscriptionPermission: lk_rtc.SubscriptionPermission( - allParticipants: allParticipants, - trackPermissions: trackPermissions, - ), - )); + }) => _sendRequest( + lk_rtc.SignalRequest( + subscriptionPermission: lk_rtc.SubscriptionPermission( + allParticipants: allParticipants, + trackPermissions: trackPermissions, + ), + ), + ); @internal void sendSyncState({ @@ -518,18 +558,19 @@ extension SignalClientRequests on SignalClient { required Iterable? dataChannelInfo, required List trackSidsDisabled, List? dataChannelReceiveStates, - }) => - _sendRequest(lk_rtc.SignalRequest( - syncState: lk_rtc.SyncState( - answer: answer, - offer: offer, - subscription: subscription, - publishTracks: publishTracks, - dataChannels: dataChannelInfo, - trackSidsDisabled: trackSidsDisabled, - datachannelReceiveStates: dataChannelReceiveStates, - ), - )); + }) => _sendRequest( + lk_rtc.SignalRequest( + syncState: lk_rtc.SyncState( + answer: answer, + offer: offer, + subscription: subscription, + publishTracks: publishTracks, + dataChannels: dataChannelInfo, + trackSidsDisabled: trackSidsDisabled, + datachannelReceiveStates: dataChannelReceiveStates, + ), + ), + ); @internal void sendSimulateScenario({ @@ -538,28 +579,29 @@ extension SignalClientRequests on SignalClient { bool? migration, bool? serverLeave, bool? switchCandidate, - }) => - _sendRequest(lk_rtc.SignalRequest( - simulate: lk_rtc.SimulateScenario( - speakerUpdate: speakerUpdate, - nodeFailure: nodeFailure, - migration: migration, - serverLeave: serverLeave, - switchCandidateProtocol: (switchCandidate != null && switchCandidate) ? lk_rtc.CandidateProtocol.TCP : null, - ), - )); + }) => _sendRequest( + lk_rtc.SignalRequest( + simulate: lk_rtc.SimulateScenario( + speakerUpdate: speakerUpdate, + nodeFailure: nodeFailure, + migration: migration, + serverLeave: serverLeave, + switchCandidateProtocol: (switchCandidate != null && switchCandidate) ? lk_rtc.CandidateProtocol.TCP : null, + ), + ), + ); } // private methods extension on lk_rtc.SignalRequest { // returns if this request can be queued bool _canQueue() => ![ - // list of types that cannot be queued - lk_rtc.SignalRequest_Message.syncState, - lk_rtc.SignalRequest_Message.trickle, - lk_rtc.SignalRequest_Message.answer, - lk_rtc.SignalRequest_Message.simulate - ].contains(whichMessage()); + // list of types that cannot be queued + lk_rtc.SignalRequest_Message.syncState, + lk_rtc.SignalRequest_Message.trickle, + lk_rtc.SignalRequest_Message.answer, + lk_rtc.SignalRequest_Message.simulate, + ].contains(whichMessage()); } // internal methods diff --git a/lib/src/core/transport.dart b/lib/src/core/transport.dart index bb7ecd3dd..0f9a04bbb 100644 --- a/lib/src/core/transport.dart +++ b/lib/src/core/transport.dart @@ -55,8 +55,8 @@ class TrackBitrateInfo { } typedef TransportOnOffer = void Function(rtc.RTCSessionDescription offer); -typedef PeerConnectionCreate = Future Function(Map configuration, - [Map constraints]); +typedef PeerConnectionCreate = + Future Function(Map configuration, [Map constraints]); /// a wrapper around PeerConnection class Transport extends Disposable { @@ -104,8 +104,11 @@ class Transport extends Disposable { }); } - static Future create(PeerConnectionCreate peerConnectionCreate, - {RTCConfiguration? rtcConfig, required ConnectOptions connectOptions}) async { + static Future create( + PeerConnectionCreate peerConnectionCreate, { + RTCConfiguration? rtcConfig, + required ConnectOptions connectOptions, + }) async { rtcConfig ??= const RTCConfiguration(); logger.fine('[PCTransport] creating ${rtcConfig.toMap()}'); final pc = await peerConnectionCreate(rtcConfig.toMap()); diff --git a/lib/src/data_stream/stream_reader.dart b/lib/src/data_stream/stream_reader.dart index 950038cc6..4b3b95137 100644 --- a/lib/src/data_stream/stream_reader.dart +++ b/lib/src/data_stream/stream_reader.dart @@ -73,12 +73,21 @@ class ByteStreamReader extends BaseStreamReader> StreamSubscription? _streamSubscription; @override - StreamSubscription listen(void Function(DataStream_Chunk event)? onData, - {Function? onError, void Function()? onDone, bool? cancelOnError}) { - _streamSubscription ??= reader!.streamController.stream.listen((DataStream_Chunk data) { - handleChunkReceived(data); - onData?.call(data); - }, onError: onError, onDone: onDone, cancelOnError: cancelOnError); + StreamSubscription listen( + void Function(DataStream_Chunk event)? onData, { + Function? onError, + void Function()? onDone, + bool? cancelOnError, + }) { + _streamSubscription ??= reader!.streamController.stream.listen( + (DataStream_Chunk data) { + handleChunkReceived(data); + onData?.call(data); + }, + onError: onError, + onDone: onDone, + cancelOnError: cancelOnError, + ); return _streamSubscription!; } @@ -124,12 +133,21 @@ class TextStreamReader extends BaseStreamReader with Str StreamSubscription? _streamSubscription; @override - StreamSubscription listen(void Function(DataStream_Chunk event)? onData, - {Function? onError, void Function()? onDone, bool? cancelOnError}) { - _streamSubscription ??= reader!.streamController.stream.listen((DataStream_Chunk data) { - handleChunkReceived(data); - onData?.call(data); - }, onError: onError, onDone: onDone, cancelOnError: cancelOnError); + StreamSubscription listen( + void Function(DataStream_Chunk event)? onData, { + Function? onError, + void Function()? onDone, + bool? cancelOnError, + }) { + _streamSubscription ??= reader!.streamController.stream.listen( + (DataStream_Chunk data) { + handleChunkReceived(data); + onData?.call(data); + }, + onError: onError, + onDone: onDone, + cancelOnError: cancelOnError, + ); return _streamSubscription!; } diff --git a/lib/src/e2ee/e2ee_manager.dart b/lib/src/e2ee/e2ee_manager.dart index 7f4cd1cb7..0dd6856d8 100644 --- a/lib/src/e2ee/e2ee_manager.dart +++ b/lib/src/e2ee/e2ee_manager.dart @@ -54,9 +54,10 @@ class E2EEManager { return; } final frameCryptor = await _addRtpSender( - sender: event.publication.track!.sender!, - identity: event.participant.identity, - sid: event.publication.sid); + sender: event.publication.track!.sender!, + identity: event.participant.identity, + sid: event.publication.sid, + ); if (kIsWeb && event.publication.track!.codec != null) { await frameCryptor.updateCodec(event.publication.track!.codec!); } @@ -65,11 +66,13 @@ class E2EEManager { print('Sender::onFrameCryptorStateChanged: $state, trackId: $trackId'); } final participant = event.participant; - [event.participant.events, participant.room.events].emit(TrackE2EEStateEvent( - participant: participant, - publication: event.publication, - state: _e2eeStateFromFrameCryptoState(state), - )); + [event.participant.events, participant.room.events].emit( + TrackE2EEStateEvent( + participant: participant, + publication: event.publication, + state: _e2eeStateFromFrameCryptoState(state), + ), + ); }; }) ..on((event) async { @@ -100,11 +103,13 @@ class E2EEManager { print('Receiver::onFrameCryptorStateChanged: $state, trackId: $trackId'); } final participant = event.participant; - [event.participant.events, participant.room.events].emit(TrackE2EEStateEvent( - participant: participant, - publication: event.publication, - state: _e2eeStateFromFrameCryptoState(state), - )); + [event.participant.events, participant.room.events].emit( + TrackE2EEStateEvent( + participant: participant, + publication: event.publication, + state: _e2eeStateFromFrameCryptoState(state), + ), + ); }; }) ..on((event) async { @@ -117,7 +122,9 @@ class E2EEManager { } }); _dataPacketCryptor ??= await dataPacketCryptorFactory.createDataPacketCryptor( - algorithm: _algorithm, keyProvider: _keyProvider.keyProvider); + algorithm: _algorithm, + keyProvider: _keyProvider.keyProvider, + ); } } @@ -144,10 +151,17 @@ class E2EEManager { _dataPacketCryptor = null; } - Future _addRtpSender( - {required RTCRtpSender sender, required String identity, required String sid}) async { + Future _addRtpSender({ + required RTCRtpSender sender, + required String identity, + required String sid, + }) async { final frameCryptor = await frameCryptorFactory.createFrameCryptorForRtpSender( - participantId: identity, sender: sender, algorithm: _algorithm, keyProvider: _keyProvider.keyProvider); + participantId: identity, + sender: sender, + algorithm: _algorithm, + keyProvider: _keyProvider.keyProvider, + ); _frameCryptors[{identity: sid}] = frameCryptor; await frameCryptor.setEnabled(_enabled); logger.info('_addRtpSender, setKeyIndex: ${_keyProvider.getLatestIndex(identity)}'); @@ -155,10 +169,17 @@ class E2EEManager { return frameCryptor; } - Future _addRtpReceiver( - {required RTCRtpReceiver receiver, required String identity, required String sid}) async { + Future _addRtpReceiver({ + required RTCRtpReceiver receiver, + required String identity, + required String sid, + }) async { final frameCryptor = await frameCryptorFactory.createFrameCryptorForRtpReceiver( - participantId: identity, receiver: receiver, algorithm: _algorithm, keyProvider: _keyProvider.keyProvider); + participantId: identity, + receiver: receiver, + algorithm: _algorithm, + keyProvider: _keyProvider.keyProvider, + ); _frameCryptors[{identity: sid}] = frameCryptor; await frameCryptor.setEnabled(_enabled); logger.info('_addRtpReceiver, setKeyIndex: ${_keyProvider.getLatestIndex(identity)}'); @@ -251,7 +272,10 @@ class E2EEManager { if (participantId == null || _dataPacketCryptor == null) { throw Exception('DataPacketCryptor is not initialized'); } - return await _dataPacketCryptor! - .encrypt(participantId: participantId, keyIndex: _keyProvider.getLatestIndex(participantId), data: data); + return await _dataPacketCryptor!.encrypt( + participantId: participantId, + keyIndex: _keyProvider.getLatestIndex(participantId), + data: data, + ); } } diff --git a/lib/src/e2ee/events.dart b/lib/src/e2ee/events.dart index bb85d05fb..8b9caea38 100644 --- a/lib/src/e2ee/events.dart +++ b/lib/src/e2ee/events.dart @@ -37,6 +37,7 @@ class TrackE2EEStateEvent with RoomEvent, ParticipantEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(participant: ${participant}, publication: ${publication}, state: ${state})'; } diff --git a/lib/src/e2ee/key_provider.dart b/lib/src/e2ee/key_provider.dart index f2fcdd3ca..4a883549e 100644 --- a/lib/src/e2ee/key_provider.dart +++ b/lib/src/e2ee/key_provider.dart @@ -76,13 +76,14 @@ class BaseKeyProvider implements KeyProvider { bool? discardFrameWhenCryptorNotReady, }) async { final rtc.KeyProviderOptions options = rtc.KeyProviderOptions( - sharedKey: sharedKey, - ratchetSalt: Uint8List.fromList((ratchetSalt ?? defaultRatchetSalt).codeUnits), - ratchetWindowSize: ratchetWindowSize ?? defaultRatchetWindowSize, - uncryptedMagicBytes: Uint8List.fromList((uncryptedMagicBytes ?? defaultMagicBytes).codeUnits), - failureTolerance: failureTolerance ?? defaultFailureTolerance, - keyRingSize: keyRingSize ?? defaultKeyRingSize, - discardFrameWhenCryptorNotReady: defaultDiscardFrameWhenCryptorNotReady); + sharedKey: sharedKey, + ratchetSalt: Uint8List.fromList((ratchetSalt ?? defaultRatchetSalt).codeUnits), + ratchetWindowSize: ratchetWindowSize ?? defaultRatchetWindowSize, + uncryptedMagicBytes: Uint8List.fromList((uncryptedMagicBytes ?? defaultMagicBytes).codeUnits), + failureTolerance: failureTolerance ?? defaultFailureTolerance, + keyRingSize: keyRingSize ?? defaultKeyRingSize, + discardFrameWhenCryptorNotReady: defaultDiscardFrameWhenCryptorNotReady, + ); final keyProvider = await rtc.frameCryptorFactory.createDefaultKeyProvider(options); return BaseKeyProvider(keyProvider, options); } diff --git a/lib/src/events.dart b/lib/src/events.dart index 816be2bd6..9195f452a 100644 --- a/lib/src/events.dart +++ b/lib/src/events.dart @@ -102,7 +102,8 @@ class RoomAttemptReconnectEvent with RoomEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(attempt: ${attempt}, maxAttemptsRetry: ${maxAttemptsRetry}, ' 'nextRetryDelaysInMs: ${nextRetryDelaysInMs})'; } @@ -153,7 +154,8 @@ class ParticipantAttributesChanged with RoomEvent, ParticipantEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(participant: ${participant}, attributes: ${attributes})'; } @@ -205,7 +207,8 @@ class ActiveSpeakersChangedEvent with RoomEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(speakers: ${speakers.map((e) => e.toString()).join(', ')})'; } @@ -221,7 +224,8 @@ class TrackPublishedEvent with RoomEvent, ParticipantEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(participant: ${participant}, publication: ${publication})'; } @@ -236,7 +240,8 @@ class TrackUnpublishedEvent with RoomEvent, ParticipantEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(participant: ${participant}, publication: ${publication})'; } @@ -251,7 +256,8 @@ class LocalTrackPublishedEvent with RoomEvent, ParticipantEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(participant: ${participant}, publication: ${publication})'; } @@ -276,7 +282,8 @@ class LocalTrackUnpublishedEvent with RoomEvent, ParticipantEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(participant: ${participant}, publication: ${publication})'; } @@ -294,7 +301,8 @@ class TrackSubscribedEvent with RoomEvent, ParticipantEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(participant: ${participant}, publication: ${publication}, ' 'track: ${track})'; } @@ -312,7 +320,8 @@ class TrackSubscriptionExceptionEvent with RoomEvent, ParticipantEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(participant: ${participant}, sid: ${sid}, reason: ${reason})'; } @@ -330,7 +339,8 @@ class TrackUnsubscribedEvent with RoomEvent, ParticipantEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(participant: ${participant}, publication: ${publication}, ' 'track: ${track})'; } @@ -346,7 +356,8 @@ class TrackMutedEvent with RoomEvent, ParticipantEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(participant: ${participant}, publication: ${publication})'; } @@ -361,7 +372,8 @@ class TrackUnmutedEvent with RoomEvent, ParticipantEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(participant: ${participant}, publication: ${publication})'; } @@ -379,7 +391,8 @@ class TrackStreamStateUpdatedEvent with RoomEvent, ParticipantEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(participant: ${participant}, publication: ${publication}, ' 'streamState: ${streamState})'; } @@ -397,7 +410,8 @@ class ParticipantMetadataUpdatedEvent with RoomEvent, ParticipantEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(participant: ${participant}, metadata: ${metadata})'; } @@ -424,7 +438,8 @@ class ParticipantConnectionQualityUpdatedEvent with RoomEvent, ParticipantEvent }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(participant: ${participant}, connectionQuality: ${connectionQuality})'; } @@ -444,7 +459,8 @@ class DataReceivedEvent with RoomEvent, ParticipantEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(participant: ${participant}, topic: ${topic}, data: ${data})'; } @@ -459,7 +475,8 @@ class SpeakingChangedEvent with ParticipantEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(participant: ${participant}, speaking: ${speaking})'; } @@ -478,7 +495,8 @@ class TrackSubscriptionPermissionChangedEvent with RoomEvent, ParticipantEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(participant: ${participant}, publication: ${publication}, ' 'state: ${state})'; } @@ -497,7 +515,8 @@ class ParticipantPermissionsUpdatedEvent with RoomEvent, ParticipantEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(participant: ${participant}, permissions: ${permissions}, ' 'oldPermissions: ${oldPermissions})'; } @@ -514,7 +533,8 @@ class TranscriptionEvent with RoomEvent, ParticipantEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(participant: ${participant}, publication: ${publication}, ' 'segments: ${segments})'; } @@ -528,7 +548,8 @@ class ParticipantNameUpdatedEvent with RoomEvent, ParticipantEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(participant: ${participant}, name: ${name})'; } @@ -551,7 +572,8 @@ class AudioSenderStatsEvent with TrackEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(stats: ${stats}, currentBitrate: ${currentBitrate})'; } @@ -566,7 +588,8 @@ class VideoSenderStatsEvent with TrackEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(stats: ${stats}, bitrateForLayers: ${bitrateForLayers}, ' 'currentBitrate: ${currentBitrate})'; } @@ -580,7 +603,8 @@ class AudioReceiverStatsEvent with TrackEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(stats: ${stats}, currentBitrate: ${currentBitrate})'; } @@ -593,7 +617,8 @@ class VideoReceiverStatsEvent with TrackEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(stats: ${stats}, currentBitrate: ${currentBitrate})'; } @@ -618,7 +643,8 @@ class TrackProcessorUpdateEvent with TrackEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(track: ${track}, processor: ${processor})'; } @@ -633,7 +659,8 @@ class PreConnectAudioBufferStartedEvent with RoomEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(sampleRate: ${sampleRate}, timeout: ${timeout})'; } @@ -648,7 +675,8 @@ class PreConnectAudioBufferStoppedEvent with RoomEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(bufferedSize: ${bufferedSize}, isDataSent: ${isBufferSent})'; } diff --git a/lib/src/extensions.dart b/lib/src/extensions.dart index a2886aedd..27f8347a3 100644 --- a/lib/src/extensions.dart +++ b/lib/src/extensions.dart @@ -28,10 +28,10 @@ import 'types/other.dart'; extension DataPacketKindExt on lk_models.DataPacket_Kind { Reliability toSDKType() => switch (this) { - lk_models.DataPacket_Kind.RELIABLE => Reliability.reliable, - lk_models.DataPacket_Kind.LOSSY => Reliability.lossy, - _ => Reliability.lossy, - }; + lk_models.DataPacket_Kind.RELIABLE => Reliability.reliable, + lk_models.DataPacket_Kind.LOSSY => Reliability.lossy, + _ => Reliability.lossy, + }; } extension LiveKitEventExt on Iterable> { @@ -40,10 +40,10 @@ extension LiveKitEventExt on Iterable> { extension ICEServerExt on lk_rtc.ICEServer { RTCIceServer toSDKType() => RTCIceServer( - urls: urls, - username: username.isNotEmpty ? username : null, - credential: credential.isNotEmpty ? credential : null, - ); + urls: urls, + username: username.isNotEmpty ? username : null, + credential: credential.isNotEmpty ? credential : null, + ); } extension IterableExt on Iterable { @@ -56,22 +56,22 @@ extension ObjectExt on Object { extension ProtocolVersionExt on ProtocolVersion { String toStringValue() => switch (this) { - ProtocolVersion.v2 => '2', - ProtocolVersion.v3 => '3', - ProtocolVersion.v4 => '4', - ProtocolVersion.v5 => '5', - ProtocolVersion.v6 => '6', - ProtocolVersion.v7 => '7', - ProtocolVersion.v8 => '8', - ProtocolVersion.v9 => '9', - ProtocolVersion.v10 => '10', - ProtocolVersion.v11 => '11', - ProtocolVersion.v12 => '12', - ProtocolVersion.v13 => '13', - ProtocolVersion.v14 => '14', - ProtocolVersion.v15 => '15', - ProtocolVersion.v16 => '16', - }; + ProtocolVersion.v2 => '2', + ProtocolVersion.v3 => '3', + ProtocolVersion.v4 => '4', + ProtocolVersion.v5 => '5', + ProtocolVersion.v6 => '6', + ProtocolVersion.v7 => '7', + ProtocolVersion.v8 => '8', + ProtocolVersion.v9 => '9', + ProtocolVersion.v10 => '10', + ProtocolVersion.v11 => '11', + ProtocolVersion.v12 => '12', + ProtocolVersion.v13 => '13', + ProtocolVersion.v14 => '14', + ProtocolVersion.v15 => '15', + ProtocolVersion.v16 => '16', + }; } extension ClientProtocolVersionExt on ClientProtocolVersion { @@ -82,16 +82,16 @@ extension ClientProtocolVersionExt on ClientProtocolVersion { extension ReliabilityExt on Reliability { lk_models.DataPacket_Kind toPBType() => switch (this) { - Reliability.reliable => lk_models.DataPacket_Kind.RELIABLE, - Reliability.lossy => lk_models.DataPacket_Kind.LOSSY, - }; + Reliability.reliable => lk_models.DataPacket_Kind.RELIABLE, + Reliability.lossy => lk_models.DataPacket_Kind.LOSSY, + }; } extension RTCDataChannelExt on rtc.RTCDataChannel { lk_rtc.DataChannelInfo toLKInfoType() => lk_rtc.DataChannelInfo( - id: id, - label: label, - ); + id: id, + label: label, + ); } extension RTCIceCandidateExt on rtc.RTCIceCandidate { @@ -111,18 +111,18 @@ extension RTCPeerConnectionStateExt on rtc.RTCPeerConnectionState { bool isConnected() => this == rtc.RTCPeerConnectionState.RTCPeerConnectionStateConnected; bool isDisconnected() => [ - rtc.RTCPeerConnectionState.RTCPeerConnectionStateClosed, - rtc.RTCPeerConnectionState.RTCPeerConnectionStateDisconnected, - ].contains(this); + rtc.RTCPeerConnectionState.RTCPeerConnectionStateClosed, + rtc.RTCPeerConnectionState.RTCPeerConnectionStateDisconnected, + ].contains(this); bool isFailed() => this == rtc.RTCPeerConnectionState.RTCPeerConnectionStateFailed; } extension RTCIceTransportPolicyExt on RTCIceTransportPolicy { String toStringValue() => switch (this) { - RTCIceTransportPolicy.all => 'all', - RTCIceTransportPolicy.relay => 'relay', - }; + RTCIceTransportPolicy.all => 'all', + RTCIceTransportPolicy.relay => 'relay', + }; } // not so neat to directly expose protobuf types so we @@ -141,81 +141,81 @@ extension SessionDescriptionExt on lk_rtc.SessionDescription { extension ConnectionQualityExt on lk_models.ConnectionQuality { ConnectionQuality toLKType() => switch (this) { - lk_models.ConnectionQuality.LOST => ConnectionQuality.lost, - lk_models.ConnectionQuality.POOR => ConnectionQuality.poor, - lk_models.ConnectionQuality.GOOD => ConnectionQuality.good, - lk_models.ConnectionQuality.EXCELLENT => ConnectionQuality.excellent, - _ => ConnectionQuality.unknown, - }; + lk_models.ConnectionQuality.LOST => ConnectionQuality.lost, + lk_models.ConnectionQuality.POOR => ConnectionQuality.poor, + lk_models.ConnectionQuality.GOOD => ConnectionQuality.good, + lk_models.ConnectionQuality.EXCELLENT => ConnectionQuality.excellent, + _ => ConnectionQuality.unknown, + }; } extension VideoQualityExt on lk_models.VideoQuality { VideoQuality toLKType() => switch (this) { - lk_models.VideoQuality.HIGH => VideoQuality.HIGH, - lk_models.VideoQuality.MEDIUM => VideoQuality.MEDIUM, - lk_models.VideoQuality.LOW => VideoQuality.LOW, - _ => VideoQuality.LOW, - }; + lk_models.VideoQuality.HIGH => VideoQuality.HIGH, + lk_models.VideoQuality.MEDIUM => VideoQuality.MEDIUM, + lk_models.VideoQuality.LOW => VideoQuality.LOW, + _ => VideoQuality.LOW, + }; } extension PBVideoQualityExt on VideoQuality { lk_models.VideoQuality toPBType() => switch (this) { - VideoQuality.HIGH => lk_models.VideoQuality.HIGH, - VideoQuality.MEDIUM => lk_models.VideoQuality.MEDIUM, - VideoQuality.LOW => lk_models.VideoQuality.LOW, - }; + VideoQuality.HIGH => lk_models.VideoQuality.HIGH, + VideoQuality.MEDIUM => lk_models.VideoQuality.MEDIUM, + VideoQuality.LOW => lk_models.VideoQuality.LOW, + }; } extension TrackTypeExt on lk_models.TrackType { TrackType toLKType() => switch (this) { - lk_models.TrackType.AUDIO => TrackType.AUDIO, - lk_models.TrackType.VIDEO => TrackType.VIDEO, - lk_models.TrackType.DATA => TrackType.DATA, - _ => TrackType.AUDIO, - }; + lk_models.TrackType.AUDIO => TrackType.AUDIO, + lk_models.TrackType.VIDEO => TrackType.VIDEO, + lk_models.TrackType.DATA => TrackType.DATA, + _ => TrackType.AUDIO, + }; } extension PBTrackTypeExt on TrackType { lk_models.TrackType toPBType() => switch (this) { - TrackType.AUDIO => lk_models.TrackType.AUDIO, - TrackType.VIDEO => lk_models.TrackType.VIDEO, - TrackType.DATA => lk_models.TrackType.DATA, - }; + TrackType.AUDIO => lk_models.TrackType.AUDIO, + TrackType.VIDEO => lk_models.TrackType.VIDEO, + TrackType.DATA => lk_models.TrackType.DATA, + }; } extension PBTrackSourceExt on lk_models.TrackSource { TrackSource toLKType() => switch (this) { - lk_models.TrackSource.CAMERA => TrackSource.camera, - lk_models.TrackSource.MICROPHONE => TrackSource.microphone, - lk_models.TrackSource.SCREEN_SHARE => TrackSource.screenShareVideo, - lk_models.TrackSource.SCREEN_SHARE_AUDIO => TrackSource.screenShareAudio, - _ => TrackSource.unknown, - }; + lk_models.TrackSource.CAMERA => TrackSource.camera, + lk_models.TrackSource.MICROPHONE => TrackSource.microphone, + lk_models.TrackSource.SCREEN_SHARE => TrackSource.screenShareVideo, + lk_models.TrackSource.SCREEN_SHARE_AUDIO => TrackSource.screenShareAudio, + _ => TrackSource.unknown, + }; } extension LKTrackSourceExt on TrackSource { lk_models.TrackSource toPBType() => switch (this) { - TrackSource.camera => lk_models.TrackSource.CAMERA, - TrackSource.microphone => lk_models.TrackSource.MICROPHONE, - TrackSource.screenShareVideo => lk_models.TrackSource.SCREEN_SHARE, - TrackSource.screenShareAudio => lk_models.TrackSource.SCREEN_SHARE_AUDIO, - TrackSource.unknown => lk_models.TrackSource.UNKNOWN, - }; + TrackSource.camera => lk_models.TrackSource.CAMERA, + TrackSource.microphone => lk_models.TrackSource.MICROPHONE, + TrackSource.screenShareVideo => lk_models.TrackSource.SCREEN_SHARE, + TrackSource.screenShareAudio => lk_models.TrackSource.SCREEN_SHARE_AUDIO, + TrackSource.unknown => lk_models.TrackSource.UNKNOWN, + }; } extension PBStreamStateExt on lk_rtc.StreamState { StreamState toLKType() => switch (this) { - lk_rtc.StreamState.ACTIVE => StreamState.active, - _ => StreamState.paused, - }; + lk_rtc.StreamState.ACTIVE => StreamState.active, + _ => StreamState.paused, + }; } extension ParticipantTrackPermissionExt on ParticipantTrackPermission { lk_rtc.TrackPermission toPBType() => lk_rtc.TrackPermission( - participantIdentity: participantIdentity, - allTracks: allTracksAllowed, - trackSids: allowedTrackSids, - ); + participantIdentity: participantIdentity, + allTracks: allTracksAllowed, + trackSids: allowedTrackSids, + ); } extension WidgetsBindingCompatible on WidgetsBinding { @@ -225,49 +225,49 @@ extension WidgetsBindingCompatible on WidgetsBinding { extension EncryptionTypeExt on lk_models.Encryption_Type { EncryptionType toLkType() => switch (this) { - lk_models.Encryption_Type.NONE => EncryptionType.kNone, - lk_models.Encryption_Type.GCM => EncryptionType.kGcm, - lk_models.Encryption_Type.CUSTOM => EncryptionType.kCustom, - _ => EncryptionType.kNone, - }; + lk_models.Encryption_Type.NONE => EncryptionType.kNone, + lk_models.Encryption_Type.GCM => EncryptionType.kGcm, + lk_models.Encryption_Type.CUSTOM => EncryptionType.kCustom, + _ => EncryptionType.kNone, + }; } extension DisconnectReasonExt on lk_models.DisconnectReason { DisconnectReason toSDKType() => switch (this) { - lk_models.DisconnectReason.UNKNOWN_REASON => DisconnectReason.unknown, - lk_models.DisconnectReason.CLIENT_INITIATED => DisconnectReason.clientInitiated, - lk_models.DisconnectReason.DUPLICATE_IDENTITY => DisconnectReason.duplicateIdentity, - lk_models.DisconnectReason.SERVER_SHUTDOWN => DisconnectReason.serverShutdown, - lk_models.DisconnectReason.PARTICIPANT_REMOVED => DisconnectReason.participantRemoved, - lk_models.DisconnectReason.ROOM_DELETED => DisconnectReason.roomDeleted, - lk_models.DisconnectReason.STATE_MISMATCH => DisconnectReason.stateMismatch, - lk_models.DisconnectReason.JOIN_FAILURE => DisconnectReason.joinFailure, - _ => DisconnectReason.unknown, - }; + lk_models.DisconnectReason.UNKNOWN_REASON => DisconnectReason.unknown, + lk_models.DisconnectReason.CLIENT_INITIATED => DisconnectReason.clientInitiated, + lk_models.DisconnectReason.DUPLICATE_IDENTITY => DisconnectReason.duplicateIdentity, + lk_models.DisconnectReason.SERVER_SHUTDOWN => DisconnectReason.serverShutdown, + lk_models.DisconnectReason.PARTICIPANT_REMOVED => DisconnectReason.participantRemoved, + lk_models.DisconnectReason.ROOM_DELETED => DisconnectReason.roomDeleted, + lk_models.DisconnectReason.STATE_MISMATCH => DisconnectReason.stateMismatch, + lk_models.DisconnectReason.JOIN_FAILURE => DisconnectReason.joinFailure, + _ => DisconnectReason.unknown, + }; } extension ParticipantTypeExt on lk_models.ParticipantInfo_Kind { ParticipantKind toLKType() => switch (this) { - lk_models.ParticipantInfo_Kind.STANDARD => ParticipantKind.STANDARD, - lk_models.ParticipantInfo_Kind.INGRESS => ParticipantKind.INGRESS, - lk_models.ParticipantInfo_Kind.EGRESS => ParticipantKind.EGRESS, - lk_models.ParticipantInfo_Kind.SIP => ParticipantKind.SIP, - lk_models.ParticipantInfo_Kind.AGENT => ParticipantKind.AGENT, - _ => ParticipantKind.STANDARD, - }; + lk_models.ParticipantInfo_Kind.STANDARD => ParticipantKind.STANDARD, + lk_models.ParticipantInfo_Kind.INGRESS => ParticipantKind.INGRESS, + lk_models.ParticipantInfo_Kind.EGRESS => ParticipantKind.EGRESS, + lk_models.ParticipantInfo_Kind.SIP => ParticipantKind.SIP, + lk_models.ParticipantInfo_Kind.AGENT => ParticipantKind.AGENT, + _ => ParticipantKind.STANDARD, + }; } extension DegradationPreferenceExt on DegradationPreference { rtc.RTCDegradationPreference toRTCType() => switch (this) { - // WebRTC defines DISABLED as an alias for MAINTAIN_FRAMERATE_AND_RESOLUTION - // ignore: deprecated_member_use_from_same_package - DegradationPreference.disabled => rtc.RTCDegradationPreference.MAINTAIN_FRAMERATE_AND_RESOLUTION, - DegradationPreference.maintainFramerate => rtc.RTCDegradationPreference.MAINTAIN_FRAMERATE, - DegradationPreference.maintainResolution => rtc.RTCDegradationPreference.MAINTAIN_RESOLUTION, - DegradationPreference.balanced => rtc.RTCDegradationPreference.BALANCED, - DegradationPreference.maintainFramerateAndResolution => - rtc.RTCDegradationPreference.MAINTAIN_FRAMERATE_AND_RESOLUTION, - }; + // WebRTC defines DISABLED as an alias for MAINTAIN_FRAMERATE_AND_RESOLUTION + // ignore: deprecated_member_use_from_same_package + DegradationPreference.disabled => rtc.RTCDegradationPreference.MAINTAIN_FRAMERATE_AND_RESOLUTION, + DegradationPreference.maintainFramerate => rtc.RTCDegradationPreference.MAINTAIN_FRAMERATE, + DegradationPreference.maintainResolution => rtc.RTCDegradationPreference.MAINTAIN_RESOLUTION, + DegradationPreference.balanced => rtc.RTCDegradationPreference.BALANCED, + DegradationPreference.maintainFramerateAndResolution => + rtc.RTCDegradationPreference.MAINTAIN_FRAMERATE_AND_RESOLUTION, + }; } extension RoomOptionsEx on RoomOptions { diff --git a/lib/src/hardware/hardware.dart b/lib/src/hardware/hardware.dart index 37874dca0..4c5d22f81 100644 --- a/lib/src/hardware/hardware.dart +++ b/lib/src/hardware/hardware.dart @@ -52,11 +52,13 @@ class MediaDevice { class Hardware { Hardware._internal() { rtc.navigator.mediaDevices.ondevicechange = _onDeviceChange; - unawaited(enumerateDevices().then((devices) { - selectedAudioInput ??= devices.firstWhereOrNull((element) => element.kind == 'audioinput'); - selectedAudioOutput ??= devices.firstWhereOrNull((element) => element.kind == 'audiooutput'); - selectedVideoInput ??= devices.firstWhereOrNull((element) => element.kind == 'videoinput'); - })); + unawaited( + enumerateDevices().then((devices) { + selectedAudioInput ??= devices.firstWhereOrNull((element) => element.kind == 'audioinput'); + selectedAudioOutput ??= devices.firstWhereOrNull((element) => element.kind == 'audiooutput'); + selectedVideoInput ??= devices.firstWhereOrNull((element) => element.kind == 'videoinput'); + }), + ); } static final Hardware instance = Hardware._internal(); @@ -159,7 +161,7 @@ class Hardware { constraints['deviceId'] = device.deviceId; } else { constraints['optional'] = [ - {'sourceId': device.deviceId} + {'sourceId': device.deviceId}, ]; } } diff --git a/lib/src/internal/events.dart b/lib/src/internal/events.dart index 4dfeaa6bc..63c54a831 100644 --- a/lib/src/internal/events.dart +++ b/lib/src/internal/events.dart @@ -44,9 +44,9 @@ class EngineSubscriberPeerStateUpdatedEvent extends EnginePeerStateUpdatedEvent required rtc.RTCPeerConnectionState state, required bool isPrimary, }) : super( - state: state, - isPrimary: isPrimary, - ); + state: state, + isPrimary: isPrimary, + ); @override String toString() => '${runtimeType}(state: ${state}, isPrimary: ${isPrimary})'; @@ -58,9 +58,9 @@ class EnginePublisherPeerStateUpdatedEvent extends EnginePeerStateUpdatedEvent { required rtc.RTCPeerConnectionState state, required bool isPrimary, }) : super( - state: state, - isPrimary: isPrimary, - ); + state: state, + isPrimary: isPrimary, + ); @override String toString() => '${runtimeType}(state: ${state}, isPrimary: ${isPrimary})'; } @@ -247,7 +247,8 @@ class EngineAttemptReconnectEvent with InternalEvent, EngineEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(attempt: ${attempt}, maxAttempts: ${maxAttempts}, ' 'nextRetryDelaysInMs: ${nextRetryDelaysInMs})'; } @@ -471,7 +472,8 @@ class SignalLeaveEvent with SignalEvent, InternalEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(canReconnect: ${canReconnect}, action: ${action}, reason: ${reason}, regions: ${regions})'; } @@ -511,7 +513,8 @@ class SignalSubscribedQualityUpdatedEvent with SignalEvent, InternalEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(trackSid: ${trackSid}, subscribedQualities: ${subscribedQualities}, ' 'subscribedCodecs: ${subscribedCodecs})'; } @@ -528,7 +531,8 @@ class SignalSubscriptionPermissionUpdateEvent with SignalEvent, InternalEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(participantSid: ${participantSid}, trackSid: ${trackSid}, allowed: ${allowed})'; } @@ -549,7 +553,8 @@ class SignalRequestResponseEvent with SignalEvent, InternalEvent { const SignalRequestResponseEvent({required this.response}); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(requestId: ${response.requestId}, reason: ${response.reason})'; } @@ -624,7 +629,8 @@ class EngineTranscriptionReceivedEvent with EngineEvent, InternalEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(transcription: ${transcription}, identity: ${identity})'; } @@ -702,7 +708,8 @@ class EngineDataStreamHeaderEvent with EngineEvent, InternalEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(header: ${header}, identity: ${identity}, encryptionType: ${encryptionType})'; } @@ -718,7 +725,8 @@ class EngineDataStreamChunkEvent with EngineEvent, InternalEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(chunk: ${chunk}, identity: ${identity}, encryptionType: ${encryptionType})'; } @@ -734,7 +742,8 @@ class EngineDataStreamTrailerEvent with EngineEvent, InternalEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(trailer: ${trailer}, identity: ${identity}, encryptionType: ${encryptionType})'; } @@ -750,7 +759,8 @@ abstract class DataChannelStateUpdatedEvent with EngineEvent, InternalEvent { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(isPrimary: ${isPrimary}, type: ${type}, state: ${state})'; } @@ -761,10 +771,10 @@ class PublisherDataChannelStateUpdatedEvent extends DataChannelStateUpdatedEvent required Reliability type, required rtc.RTCDataChannelState state, }) : super( - isPrimary: isPrimary, - type: type, - state: state, - ); + isPrimary: isPrimary, + type: type, + state: state, + ); } @internal @@ -774,10 +784,10 @@ class SubscriberDataChannelStateUpdatedEvent extends DataChannelStateUpdatedEven required Reliability type, required rtc.RTCDataChannelState state, }) : super( - isPrimary: isPrimary, - type: type, - state: state, - ); + isPrimary: isPrimary, + type: type, + state: state, + ); } @internal diff --git a/lib/src/internal/types.dart b/lib/src/internal/types.dart index d0976d120..fa2d05853 100644 --- a/lib/src/internal/types.dart +++ b/lib/src/internal/types.dart @@ -24,6 +24,6 @@ class RTCOfferOptions { }); Map toMap() => { - if (iceRestart) 'iceRestart': true, - }; + if (iceRestart) 'iceRestart': true, + }; } diff --git a/lib/src/json/agent_attributes.g.dart b/lib/src/json/agent_attributes.g.dart index b9b1ab253..5c4998fbd 100644 --- a/lib/src/json/agent_attributes.g.dart +++ b/lib/src/json/agent_attributes.g.dart @@ -7,22 +7,21 @@ part of 'agent_attributes.dart'; // ************************************************************************** AgentAttributes _$AgentAttributesFromJson(Map json) => AgentAttributes( - lkAgentInputs: - (json['lk.agent.inputs'] as List?)?.map((e) => $enumDecode(_$AgentInputEnumMap, e)).toList(), - lkAgentOutputs: - (json['lk.agent.outputs'] as List?)?.map((e) => $enumDecode(_$AgentOutputEnumMap, e)).toList(), - lkAgentState: $enumDecodeNullable(_$AgentStateEnumMap, json['lk.agent.state']), - lkPublishOnBehalf: json['lk.publish_on_behalf'] as String?, - ); + lkAgentInputs: (json['lk.agent.inputs'] as List?)?.map((e) => $enumDecode(_$AgentInputEnumMap, e)).toList(), + lkAgentOutputs: (json['lk.agent.outputs'] as List?) + ?.map((e) => $enumDecode(_$AgentOutputEnumMap, e)) + .toList(), + lkAgentState: $enumDecodeNullable(_$AgentStateEnumMap, json['lk.agent.state']), + lkPublishOnBehalf: json['lk.publish_on_behalf'] as String?, +); Map _$AgentAttributesToJson(AgentAttributes instance) => { - if (instance.lkAgentInputs?.map((e) => _$AgentInputEnumMap[e]!).toList() case final value?) - 'lk.agent.inputs': value, - if (instance.lkAgentOutputs?.map((e) => _$AgentOutputEnumMap[e]!).toList() case final value?) - 'lk.agent.outputs': value, - if (_$AgentStateEnumMap[instance.lkAgentState] case final value?) 'lk.agent.state': value, - if (instance.lkPublishOnBehalf case final value?) 'lk.publish_on_behalf': value, - }; + if (instance.lkAgentInputs?.map((e) => _$AgentInputEnumMap[e]!).toList() case final value?) 'lk.agent.inputs': value, + if (instance.lkAgentOutputs?.map((e) => _$AgentOutputEnumMap[e]!).toList() case final value?) + 'lk.agent.outputs': value, + if (_$AgentStateEnumMap[instance.lkAgentState] case final value?) 'lk.agent.state': value, + if (instance.lkPublishOnBehalf case final value?) 'lk.publish_on_behalf': value, +}; const _$AgentInputEnumMap = { AgentInput.audio: 'audio', @@ -44,13 +43,13 @@ const _$AgentStateEnumMap = { }; TranscriptionAttributes _$TranscriptionAttributesFromJson(Map json) => TranscriptionAttributes( - lkSegmentId: json['lk.segment_id'] as String?, - lkTranscribedTrackId: json['lk.transcribed_track_id'] as String?, - lkTranscriptionFinal: _boolFromJson(json['lk.transcription_final']), - ); + lkSegmentId: json['lk.segment_id'] as String?, + lkTranscribedTrackId: json['lk.transcribed_track_id'] as String?, + lkTranscriptionFinal: _boolFromJson(json['lk.transcription_final']), +); Map _$TranscriptionAttributesToJson(TranscriptionAttributes instance) => { - if (instance.lkSegmentId case final value?) 'lk.segment_id': value, - if (instance.lkTranscribedTrackId case final value?) 'lk.transcribed_track_id': value, - if (_boolToJson(instance.lkTranscriptionFinal) case final value?) 'lk.transcription_final': value, - }; + if (instance.lkSegmentId case final value?) 'lk.segment_id': value, + if (instance.lkTranscribedTrackId case final value?) 'lk.transcribed_track_id': value, + if (_boolToJson(instance.lkTranscriptionFinal) case final value?) 'lk.transcription_final': value, +}; diff --git a/lib/src/managers/event.dart b/lib/src/managers/event.dart index 7e915865f..46f99facc 100644 --- a/lib/src/managers/event.dart +++ b/lib/src/managers/event.dart @@ -105,8 +105,8 @@ class EventsListener extends EventsListenable { this.emitter, { bool synchronized = false, }) : super( - synchronized: synchronized, - ); + synchronized: synchronized, + ); } // ensures all listeners will close on dispose @@ -171,15 +171,14 @@ abstract class EventsListenable extends Disposable { CancelListenFunc on( FutureOr Function(E) then, { bool Function(E)? filter, - }) => - listen((event) async { - // event must be E - if (event is! E) return; - // filter must be true (if filter is used) - if (filter != null && !filter(event)) return; - // cast to E - await then(event); - }); + }) => listen((event) async { + // event must be E + if (event is! E) return; + // filter must be true (if filter is used) + if (filter != null && !filter(event)) return; + // cast to E + await then(event); + }); /// convenience method to listen & filter a specific event type, just once. CancelListenFunc? once( diff --git a/lib/src/options.dart b/lib/src/options.dart index 7add61050..34d193f6a 100644 --- a/lib/src/options.dart +++ b/lib/src/options.dart @@ -186,9 +186,9 @@ class CertificatePinningRule { }); List get allPins => [ - ...primaryPins, - ...backupPins, - ]; + ...primaryPins, + ...backupPins, + ]; bool get hasSpkiPins => allPins.isNotEmpty; @@ -455,18 +455,19 @@ class VideoPublishOptions extends PublishOptions { final BackupVideoCodec backupVideoCodec; - const VideoPublishOptions( - {super.name, - super.stream, - this.videoCodec = defaultVideoCodec, - this.videoEncoding, - this.screenShareEncoding, - this.simulcast = true, - this.videoSimulcastLayers = const [], - this.screenShareSimulcastLayers = const [], - this.backupVideoCodec = defualtBackupVideoCodec, - this.scalabilityMode, - this.degradationPreference}); + const VideoPublishOptions({ + super.name, + super.stream, + this.videoCodec = defaultVideoCodec, + this.videoEncoding, + this.screenShareEncoding, + this.simulcast = true, + this.videoSimulcastLayers = const [], + this.screenShareSimulcastLayers = const [], + this.backupVideoCodec = defualtBackupVideoCodec, + this.scalabilityMode, + this.degradationPreference, + }); VideoPublishOptions copyWith({ VideoEncoding? videoEncoding, @@ -480,20 +481,19 @@ class VideoPublishOptions extends PublishOptions { String? scalabilityMode, String? name, String? stream, - }) => - VideoPublishOptions( - videoEncoding: videoEncoding ?? this.videoEncoding, - screenShareEncoding: screenShareEncoding ?? this.screenShareEncoding, - simulcast: simulcast ?? this.simulcast, - videoSimulcastLayers: videoSimulcastLayers ?? this.videoSimulcastLayers, - screenShareSimulcastLayers: screenShareSimulcastLayers ?? this.screenShareSimulcastLayers, - videoCodec: videoCodec ?? this.videoCodec, - backupVideoCodec: backupVideoCodec ?? this.backupVideoCodec, - degradationPreference: degradationPreference ?? this.degradationPreference, - scalabilityMode: scalabilityMode ?? this.scalabilityMode, - name: name ?? this.name, - stream: stream ?? this.stream, - ); + }) => VideoPublishOptions( + videoEncoding: videoEncoding ?? this.videoEncoding, + screenShareEncoding: screenShareEncoding ?? this.screenShareEncoding, + simulcast: simulcast ?? this.simulcast, + videoSimulcastLayers: videoSimulcastLayers ?? this.videoSimulcastLayers, + screenShareSimulcastLayers: screenShareSimulcastLayers ?? this.screenShareSimulcastLayers, + videoCodec: videoCodec ?? this.videoCodec, + backupVideoCodec: backupVideoCodec ?? this.backupVideoCodec, + degradationPreference: degradationPreference ?? this.degradationPreference, + scalabilityMode: scalabilityMode ?? this.scalabilityMode, + name: name ?? this.name, + stream: stream ?? this.stream, + ); @override String toString() => '${runtimeType}(videoEncoding: ${videoEncoding}, simulcast: ${simulcast})'; @@ -535,15 +535,14 @@ class AudioPublishOptions extends PublishOptions { String? stream, bool? red, bool? preConnect, - }) => - AudioPublishOptions( - encoding: encoding ?? this.encoding, - dtx: dtx ?? this.dtx, - name: name ?? this.name, - stream: stream ?? this.stream, - red: red ?? this.red, - preConnect: preConnect ?? this.preConnect, - ); + }) => AudioPublishOptions( + encoding: encoding ?? this.encoding, + dtx: dtx ?? this.dtx, + name: name ?? this.name, + stream: stream ?? this.stream, + red: red ?? this.red, + preConnect: preConnect ?? this.preConnect, + ); @override String toString() => '${runtimeType}(encoding: ${encoding}, dtx: ${dtx}, red: ${red}, preConnect: ${preConnect})'; diff --git a/lib/src/participant/local.dart b/lib/src/participant/local.dart index b367f6c9e..89a7e71c4 100644 --- a/lib/src/participant/local.dart +++ b/lib/src/participant/local.dart @@ -73,11 +73,11 @@ class LocalParticipant extends Participant { required String identity, required String name, }) : super( - room: room, - sid: sid, - identity: identity, - name: name, - ); + room: room, + sid: sid, + identity: identity, + name: name, + ); @internal static Future createFromInfo({ @@ -244,10 +244,12 @@ class LocalParticipant extends Participant { await removePublishedTrack(pub.sid); }); - [events, room.events].emit(LocalTrackPublishedEvent( - participant: this, - publication: pub, - )); + [events, room.events].emit( + LocalTrackPublishedEvent( + participant: this, + publication: pub, + ), + ); return pub; } catch (error) { @@ -360,10 +362,12 @@ class LocalParticipant extends Participant { ]; if (publishOptions.backupVideoCodec.enabled && publishOptions.backupVideoCodec.codec != publishOptions.videoCodec) { - simulcastCodecs.add(lk_rtc.SimulcastCodec( - codec: publishOptions.backupVideoCodec.codec.toLowerCase(), - cid: '', - )); + simulcastCodecs.add( + lk_rtc.SimulcastCodec( + codec: publishOptions.backupVideoCodec.codec.toLowerCase(), + cid: '', + ), + ); } final layers = Utils.computeVideoLayers( @@ -398,11 +402,14 @@ class LocalParticipant extends Participant { //TOOD: } else if (isVideoCodec(options.videoCodec) && encodings?.first.maxBitrate != null) { // Apply start bitrate for all video codecs to prevent initial blurriness - room.engine.publisher?.setTrackBitrateInfo(TrackBitrateInfo( + room.engine.publisher?.setTrackBitrateInfo( + TrackBitrateInfo( cid: track.getCid(), transceiver: track.transceiver, codec: options.videoCodec, - maxbr: encodings![0].maxBitrate! ~/ 1000)); + maxbr: encodings![0].maxBitrate! ~/ 1000, + ), + ); } await room.engine.negotiate(); @@ -412,7 +419,8 @@ class LocalParticipant extends Participant { final req = lk_rtc.AddTrackRequest( cid: track.getCid(), - name: publishOptions.name ?? + name: + publishOptions.name ?? (track.source == TrackSource.screenShareVideo ? VideoPublishOptions.defaultScreenShareName : VideoPublishOptions.defaultCameraName), @@ -496,11 +504,14 @@ class LocalParticipant extends Participant { //TOOD: } else if (isVideoCodec(publishOptions.videoCodec) && encodings?.first.maxBitrate != null) { // Apply start bitrate for all video codecs to prevent initial blurriness - room.engine.publisher?.setTrackBitrateInfo(TrackBitrateInfo( + room.engine.publisher?.setTrackBitrateInfo( + TrackBitrateInfo( cid: track.getCid(), transceiver: track.transceiver, codec: publishOptions.videoCodec, - maxbr: encodings![0].maxBitrate! ~/ 1000)); + maxbr: encodings![0].maxBitrate! ~/ 1000, + ), + ); } await room.engine.negotiate(); @@ -530,10 +541,12 @@ class LocalParticipant extends Participant { await removePublishedTrack(pub.sid); }); - [events, room.events].emit(LocalTrackPublishedEvent( - participant: this, - publication: pub, - )); + [events, room.events].emit( + LocalTrackPublishedEvent( + participant: this, + publication: pub, + ), + ); return pub; } @@ -581,10 +594,12 @@ class LocalParticipant extends Participant { } if (notify) { - [events, room.events].emit(LocalTrackUnpublishedEvent( - participant: this, - publication: pub, - )); + [events, room.events].emit( + LocalTrackUnpublishedEvent( + participant: this, + publication: pub, + ), + ); } await pub.dispose(); @@ -749,20 +764,30 @@ class LocalParticipant extends Participant { } /// Shortcut for publishing a [TrackSource.screenShareVideo] - Future setScreenShareEnabled(bool enabled, - {bool? captureScreenAudio, ScreenShareCaptureOptions? screenShareCaptureOptions}) async { + Future setScreenShareEnabled( + bool enabled, { + bool? captureScreenAudio, + ScreenShareCaptureOptions? screenShareCaptureOptions, + }) async { screenShareCaptureOptions ??= room.roomOptions.defaultScreenShareCaptureOptions; - return setSourceEnabled(TrackSource.screenShareVideo, enabled, - captureScreenAudio: captureScreenAudio, screenShareCaptureOptions: screenShareCaptureOptions); + return setSourceEnabled( + TrackSource.screenShareVideo, + enabled, + captureScreenAudio: captureScreenAudio, + screenShareCaptureOptions: screenShareCaptureOptions, + ); } /// A convenience method to publish a track for a specific [TrackSource]. /// This is the recommended method to publish tracks. - Future setSourceEnabled(TrackSource source, bool enabled, - {bool? captureScreenAudio, - AudioCaptureOptions? audioCaptureOptions, - CameraCaptureOptions? cameraCaptureOptions, - ScreenShareCaptureOptions? screenShareCaptureOptions}) { + Future setSourceEnabled( + TrackSource source, + bool enabled, { + bool? captureScreenAudio, + AudioCaptureOptions? audioCaptureOptions, + CameraCaptureOptions? cameraCaptureOptions, + ScreenShareCaptureOptions? screenShareCaptureOptions, + }) { return _publishRunner.run(() async { if (TrackSource.screenShareVideo == source && lkPlatformIsWebMobile()) { throw TrackCreateException('Screen sharing is not supported on mobile devices'); @@ -886,11 +911,13 @@ class LocalParticipant extends Participant { final oldValue = super.setPermissions(newValue); if (oldValue != null) { // notify - [events, room.events].emit(ParticipantPermissionsUpdatedEvent( - participant: this, - permissions: newValue, - oldPermissions: oldValue, - )); + [events, room.events].emit( + ParticipantPermissionsUpdatedEvent( + participant: this, + permissions: newValue, + oldPermissions: oldValue, + ), + ); } return oldValue; } @@ -950,7 +977,8 @@ class LocalParticipant extends Participant { final req = lk_rtc.AddTrackRequest( cid: cid, - name: options.name ?? + name: + options.name ?? (track.source == TrackSource.screenShareVideo ? VideoPublishOptions.defaultScreenShareName : VideoPublishOptions.defaultCameraName), @@ -1002,14 +1030,16 @@ extension DataStreamParticipantMethods on LocalParticipant { options?.onProgress?.call(totalProgress.toDouble() / len); } - final writer = await streamText(StreamTextOptions( - streamId: streamId, - totalSize: totalTextLength, - destinationIdentities: options?.destinationIdentities ?? [], - topic: options?.topic, - attachedStreamIds: fileIds ?? [], - attributes: options?.attributes ?? {}, - )); + final writer = await streamText( + StreamTextOptions( + streamId: streamId, + totalSize: totalTextLength, + destinationIdentities: options?.destinationIdentities ?? [], + topic: options?.topic, + attachedStreamIds: fileIds ?? [], + attributes: options?.attributes ?? {}, + ), + ); await writer.write(text); // set text part of progress to 1 @@ -1027,11 +1057,12 @@ extension DataStreamParticipantMethods on LocalParticipant { fileIds![curIdx], file, SendFileOptions( - topic: options.topic, - mimeType: mime(basename(file.path)), - onProgress: (progress) { - handleProgress(progress, curIdx + 1); - }), + topic: options.topic, + mimeType: mime(basename(file.path)), + onProgress: (progress) { + handleProgress(progress, curIdx + 1); + }, + ), ); }, ).toList() ?? @@ -1083,8 +1114,11 @@ extension DataStreamParticipantMethods on LocalParticipant { ); await room.engine.sendDataPacket(packet, reliability: Reliability.reliable); - final writableStream = - WritableStream(destinationIdentities: destinationIdentities!, engine: room.engine, streamId: streamId); + final writableStream = WritableStream( + destinationIdentities: destinationIdentities!, + engine: room.engine, + streamId: streamId, + ); onEngineClose() async { await writableStream.close(); diff --git a/lib/src/participant/participant.dart b/lib/src/participant/participant.dart index 5b46fc34c..90a33e654 100644 --- a/lib/src/participant/participant.dart +++ b/lib/src/participant/participant.dart @@ -135,8 +135,10 @@ abstract class Participant extends DisposableChangeN } } - bool get isEncrypted => [...audioTrackPublications, ...videoTrackPublications] - .every((track) => track.encryptionType != EncryptionType.kNone); + bool get isEncrypted => [ + ...audioTrackPublications, + ...videoTrackPublications, + ].every((track) => track.encryptionType != EncryptionType.kNone); @internal bool get hasInfo => _participantInfo != null; @@ -169,20 +171,24 @@ abstract class Participant extends DisposableChangeN lastSpokeAt = DateTime.timestamp(); } - events.emit(SpeakingChangedEvent( - participant: this, - speaking: speaking, - )); + events.emit( + SpeakingChangedEvent( + participant: this, + speaking: speaking, + ), + ); } void _setMetadata(String md) { final changed = _participantInfo?.metadata != md; metadata = md; if (changed) { - [events, room.events].emit(ParticipantMetadataUpdatedEvent( - participant: this, - metadata: md, - )); + [events, room.events].emit( + ParticipantMetadataUpdatedEvent( + participant: this, + metadata: md, + ), + ); } } @@ -190,10 +196,12 @@ abstract class Participant extends DisposableChangeN final didChange = _state != state; _state = state; if (didChange) { - [events, room.events].emit(ParticipantStateUpdatedEvent( - participant: this, - state: state, - )); + [events, room.events].emit( + ParticipantStateUpdatedEvent( + participant: this, + state: state, + ), + ); } } @@ -209,10 +217,12 @@ abstract class Participant extends DisposableChangeN void updateConnectionQuality(ConnectionQuality quality) { if (_connectionQuality == quality) return; _connectionQuality = quality; - [events, room.events].emit(ParticipantConnectionQualityUpdatedEvent( - participant: this, - connectionQuality: _connectionQuality, - )); + [events, room.events].emit( + ParticipantConnectionQualityUpdatedEvent( + participant: this, + connectionQuality: _connectionQuality, + ), + ); } @internal @@ -253,10 +263,12 @@ abstract class Participant extends DisposableChangeN void updateName(String name) { if (_name == name) return; _name = name; - [events, room.events].emit(ParticipantNameUpdatedEvent( - participant: this, - name: name, - )); + [events, room.events].emit( + ParticipantNameUpdatedEvent( + participant: this, + name: name, + ), + ); } @internal @@ -298,11 +310,15 @@ abstract class Participant extends DisposableChangeN final result = trackPublications.values.firstWhereOrNull((e) => e.source == source); if (result != null) return result; // try to find by compatibility - return trackPublications.values.where((e) => e.source == TrackSource.unknown).firstWhereOrNull((e) => - (source == TrackSource.microphone && e.kind == TrackType.AUDIO) || - (source == TrackSource.camera && e.kind == TrackType.VIDEO) || - (source == TrackSource.screenShareVideo && e.kind == TrackType.VIDEO) || - (source == TrackSource.screenShareAudio && e.kind == TrackType.AUDIO)); + return trackPublications.values + .where((e) => e.source == TrackSource.unknown) + .firstWhereOrNull( + (e) => + (source == TrackSource.microphone && e.kind == TrackType.AUDIO) || + (source == TrackSource.camera && e.kind == TrackType.VIDEO) || + (source == TrackSource.screenShareVideo && e.kind == TrackType.VIDEO) || + (source == TrackSource.screenShareAudio && e.kind == TrackType.AUDIO), + ); } /// Convenience property to check whether [TrackSource.camera] is published or not. diff --git a/lib/src/participant/remote.dart b/lib/src/participant/remote.dart index 5a8c9af62..517d19c80 100644 --- a/lib/src/participant/remote.dart +++ b/lib/src/participant/remote.dart @@ -61,11 +61,11 @@ class RemoteParticipant extends Participant { required String identity, required String name, }) : super( - room: room, - sid: sid, - identity: identity, - name: name, - ); + room: room, + sid: sid, + identity: identity, + name: name, + ); /// Creates a fully initialized RemoteParticipant without emitting events. /// @@ -245,11 +245,13 @@ class RemoteParticipant extends Participant { await pub.updateSubscriptionAllowed(true); addTrackPublication(pub); - [events, room.events].emit(TrackSubscribedEvent( - participant: this, - track: track, - publication: pub, - )); + [events, room.events].emit( + TrackSubscribedEvent( + participant: this, + track: track, + publication: pub, + ), + ); await track.start(); } @@ -301,18 +303,22 @@ class RemoteParticipant extends Participant { // if has track if (track != null) { await track.stop(); - [events, room.events].emit(TrackUnsubscribedEvent( - participant: this, - track: track, - publication: pub, - )); + [events, room.events].emit( + TrackUnsubscribedEvent( + participant: this, + track: track, + publication: pub, + ), + ); } if (notify) { - [events, room.events].emit(TrackUnpublishedEvent( - participant: this, - publication: pub, - )); + [events, room.events].emit( + TrackUnpublishedEvent( + participant: this, + publication: pub, + ), + ); } await pub.dispose(); @@ -327,7 +333,7 @@ class RemoteParticipant extends Participant { @internal lk_models.ParticipantTracks participantTracks() => lk_models.ParticipantTracks( - participantSid: sid, - trackSids: trackPublications.values.map((e) => e.sid), - ); + participantSid: sid, + trackSids: trackPublications.values.map((e) => e.sid), + ); } diff --git a/lib/src/preconnect/pre_connect_audio_buffer.dart b/lib/src/preconnect/pre_connect_audio_buffer.dart index c04eca8fd..4427c90d1 100644 --- a/lib/src/preconnect/pre_connect_audio_buffer.dart +++ b/lib/src/preconnect/pre_connect_audio_buffer.dart @@ -77,8 +77,8 @@ class PreConnectAudioBuffer { this._room, { PreConnectOnError? onError, int sampleRate = defaultSampleRate, - }) : _onError = onError, - _requestSampleRate = sampleRate; + }) : _onError = onError, + _requestSampleRate = sampleRate; /// Whether pre-connect recording is currently active. bool get isRecording => _isRecording; @@ -175,17 +175,18 @@ class PreConnectAudioBuffer { // Listen for agent readiness and send the buffer when active. _participantStateListener = _room.events.on( - filter: (event) => event.participant.kind == ParticipantKind.AGENT && event.state == ParticipantState.active, - (event) async { - logger.info('[Preconnect audio] Agent is active: ${event.participant.identity}'); - try { - await sendAudioData(agents: [event.participant.identity]); - _agentReadyManager.complete(); - } catch (error) { - _agentReadyManager.completeError(error); - _onError?.call(error); - } - }); + filter: (event) => event.participant.kind == ParticipantKind.AGENT && event.state == ParticipantState.active, + (event) async { + logger.info('[Preconnect audio] Agent is active: ${event.participant.identity}'); + try { + await sendAudioData(agents: [event.participant.identity]); + _agentReadyManager.complete(); + } catch (error) { + _agentReadyManager.completeError(error); + _onError?.call(error); + } + }, + ); _localTrackPublishedEvent = _room.events.waitFor( duration: Duration(seconds: 10), @@ -193,10 +194,12 @@ class PreConnectAudioBuffer { ); // Emit the started event - _room.events.emit(PreConnectAudioBufferStartedEvent( - sampleRate: _requestSampleRate, - timeout: timeout, - )); + _room.events.emit( + PreConnectAudioBufferStartedEvent( + sampleRate: _requestSampleRate, + timeout: timeout, + ), + ); } /// Stops recording and releases audio capture resources. @@ -227,10 +230,12 @@ class PreConnectAudioBuffer { withError != null ? _agentReadyManager.completeError(withError) : _agentReadyManager.complete(); // Emit the stopped event - _room.events.emit(PreConnectAudioBufferStoppedEvent( - bufferedSize: _buffer.length, - isBufferSent: _isBufferSent, - )); + _room.events.emit( + PreConnectAudioBufferStoppedEvent( + bufferedSize: _buffer.length, + isBufferSent: _isBufferSent, + ), + ); logger.info('[Preconnect audio] stopped recording'); } @@ -332,7 +337,8 @@ class PreConnectAudioBuffer { final double secondsOfAudio = totalFrames / sampleRate; logger.info( - '[Preconnect audio] sent ${(data.length / 1024).toStringAsFixed(1)}KB of audio (${secondsOfAudio.toStringAsFixed(2)} seconds) to ${agents} agent(s)'); + '[Preconnect audio] sent ${(data.length / 1024).toStringAsFixed(1)}KB of audio (${secondsOfAudio.toStringAsFixed(2)} seconds) to ${agents} agent(s)', + ); } /// Updates the callback invoked when pre-connect audio fails. diff --git a/lib/src/publication/local.dart b/lib/src/publication/local.dart index 7521f85cb..a99293f04 100644 --- a/lib/src/publication/local.dart +++ b/lib/src/publication/local.dart @@ -46,7 +46,7 @@ class LocalTrackPublication extends TrackPublication { Future unmute({bool stopOnMute = true}) async => await track?.unmute(stopOnMute: stopOnMute); lk_rtc.TrackPublishedResponse toPBTrackPublishedResponse() => lk_rtc.TrackPublishedResponse( - cid: track?.mediaStreamTrack.id, - track: latestInfo, - ); + cid: track?.mediaStreamTrack.id, + track: latestInfo, + ); } diff --git a/lib/src/publication/remote.dart b/lib/src/publication/remote.dart index c47ba6938..a90bf3c5c 100644 --- a/lib/src/publication/remote.dart +++ b/lib/src/publication/remote.dart @@ -43,10 +43,10 @@ class RemoteTrackPublication extends TrackPublication final RemoteParticipant participant; bool get enabled => !resolveDisabled( - enabledPreference: _enabledPreference, - adaptiveStreamActive: _adaptiveStreamActive, - adaptiveStreamVisible: _adaptiveStreamVisible, - ); + enabledPreference: _enabledPreference, + adaptiveStreamActive: _adaptiveStreamActive, + adaptiveStreamVisible: _adaptiveStreamVisible, + ); /// The user's explicit enable/disable request via [enable] / [disable]. /// [TrackEnabledPreference.unset] means no explicit request, in which case @@ -105,11 +105,13 @@ class RemoteTrackPublication extends TrackPublication _streamState = streamState; [ participant.events, - ].emit(TrackStreamStateUpdatedEvent( - participant: participant, - publication: this, - streamState: streamState, - )); + ].emit( + TrackStreamStateUpdatedEvent( + participant: participant, + publication: this, + streamState: streamState, + ), + ); } // used to report renderer visibility to the server @@ -156,9 +158,9 @@ class RemoteTrackPublication extends TrackPublication }) { // Size maxOfSizes(Size s1, Size s2) => Size( - max(s1.width, s2.width), - max(s1.height, s2.height), - ); + max(s1.width, s2.width), + max(s1.height, s2.height), + ); final videoTrack = track as VideoTrack; @@ -360,11 +362,13 @@ class RemoteTrackPublication extends TrackPublication // Ideally, we should wait for WebRTC's onRemoveTrack event // but it does not work reliably across platforms. // So for now we will assume remove track succeeded. - [participant.events, participant.room.events].emit(TrackUnsubscribedEvent( - participant: participant, - track: track!, - publication: this, - )); + [participant.events, participant.room.events].emit( + TrackUnsubscribedEvent( + participant: participant, + track: track!, + publication: this, + ), + ); // Simply set to null for now await updateTrack(null); } @@ -437,21 +441,25 @@ class RemoteTrackPublication extends TrackPublication // emit events [ participant.events, - ].emit(TrackSubscriptionPermissionChangedEvent( - participant: participant, - publication: this, - state: subscriptionState, - )); + ].emit( + TrackSubscriptionPermissionChangedEvent( + participant: participant, + publication: this, + state: subscriptionState, + ), + ); - if (!_subscriptionAllowed && super.subscribed /* track != null */) { + if (!_subscriptionAllowed && super.subscribed /* track != null */ ) { // Ideally, we should wait for WebRTC's onRemoveTrack event // but it does not work reliably across platforms. // So for now we will assume remove track succeeded. - [participant.events, participant.room.events].emit(TrackUnsubscribedEvent( - participant: participant, - track: track!, - publication: this, - )); + [participant.events, participant.room.events].emit( + TrackUnsubscribedEvent( + participant: participant, + track: track!, + publication: this, + ), + ); // Simply set to null for now await updateTrack(null); } diff --git a/lib/src/publication/track_publication.dart b/lib/src/publication/track_publication.dart index dfc75db6d..187085d93 100644 --- a/lib/src/publication/track_publication.dart +++ b/lib/src/publication/track_publication.dart @@ -69,16 +69,16 @@ abstract class TrackPublication extends Disposable { TrackPublication({ required lk_models.TrackInfo info, required T? track, - }) : sid = info.sid, - name = info.name, - kind = info.type.toLKType(), - source = info.source.toLKType(), - // TODO, figure out the replacements to simulcast, width, height. - // ignore: deprecated_member_use_from_same_package - _simulcasted = info.simulcast, - _metadataMuted = info.muted, - _mimeType = info.mimeType, - _track = track { + }) : sid = info.sid, + name = info.name, + kind = info.type.toLKType(), + source = info.source.toLKType(), + // TODO, figure out the replacements to simulcast, width, height. + // ignore: deprecated_member_use_from_same_package + _simulcasted = info.simulcast, + _metadataMuted = info.muted, + _mimeType = info.mimeType, + _track = track { if (track != null) _attachTrackListener(track); updateFromInfo(info); } diff --git a/lib/src/rpc/rpc_client_manager.dart b/lib/src/rpc/rpc_client_manager.dart index e53d6e25a..803fd1b46 100644 --- a/lib/src/rpc/rpc_client_manager.dart +++ b/lib/src/rpc/rpc_client_manager.dart @@ -160,16 +160,18 @@ class RpcClientManager { throw RpcError(code: RpcError.sendFailed, message: 'No local participant'); } - final writer = await local.streamText(StreamTextOptions( - topic: kRpcRequestTopic, - destinationIdentities: [destinationIdentity], - attributes: { - kRpcAttrRequestId: requestId, - kRpcAttrMethod: method, - kRpcAttrResponseTimeoutMs: responseTimeout.inMilliseconds.toString(), - kRpcAttrVersion: kRpcRequestVersionV2, - }, - )); + final writer = await local.streamText( + StreamTextOptions( + topic: kRpcRequestTopic, + destinationIdentities: [destinationIdentity], + attributes: { + kRpcAttrRequestId: requestId, + kRpcAttrMethod: method, + kRpcAttrResponseTimeoutMs: responseTimeout.inMilliseconds.toString(), + kRpcAttrVersion: kRpcRequestVersionV2, + }, + ), + ); await writer.write(payload); await writer.close(); } @@ -218,8 +220,10 @@ class RpcClientManager { if (senderIdentity != pending.destinationIdentity) { // Identity spoof / cross-talk guard. Do NOT resolve; leave pending entry intact // so the legitimate response (or timeout) can still complete it. - logger.warning('v2 RPC response sender "$senderIdentity" does not match expected destination ' - '"${pending.destinationIdentity}" for request $requestId; ignoring'); + logger.warning( + 'v2 RPC response sender "$senderIdentity" does not match expected destination ' + '"${pending.destinationIdentity}" for request $requestId; ignoring', + ); return; } diff --git a/lib/src/rpc/rpc_server_manager.dart b/lib/src/rpc/rpc_server_manager.dart index c526b5faa..d7eca716f 100644 --- a/lib/src/rpc/rpc_server_manager.dart +++ b/lib/src/rpc/rpc_server_manager.dart @@ -137,19 +137,22 @@ class RpcServerManager { RpcError? responseError; try { - final response = await handler(RpcInvocationData( - requestId: requestId, - callerIdentity: callerIdentity, - payload: payload, - responseTimeoutMs: responseTimeoutMs.toInt(), - )); + final response = await handler( + RpcInvocationData( + requestId: requestId, + callerIdentity: callerIdentity, + payload: payload, + responseTimeoutMs: responseTimeoutMs.toInt(), + ), + ); responsePayload = response; } catch (error) { if (error is RpcError) { responseError = error; } else { logger.warning( - 'Uncaught error returned by RPC handler for $method. Returning RpcError.applicationError instead. $error'); + 'Uncaught error returned by RPC handler for $method. Returning RpcError.applicationError instead. $error', + ); responseError = RpcError(code: RpcError.applicationError, message: error.toString()); } } @@ -254,11 +257,13 @@ class RpcServerManager { logger.warning('Cannot send v2 RPC response: no local participant'); return; } - final writer = await local.streamText(StreamTextOptions( - topic: kRpcResponseTopic, - destinationIdentities: [callerIdentity], - attributes: {kRpcAttrRequestId: requestId}, - )); + final writer = await local.streamText( + StreamTextOptions( + topic: kRpcResponseTopic, + destinationIdentities: [callerIdentity], + attributes: {kRpcAttrRequestId: requestId}, + ), + ); await writer.write(payload); await writer.close(); } @@ -275,8 +280,9 @@ class RpcServerManager { String? truncatedData; if (originalData != null) { - truncatedData = - originalData.length > kRpcMaxPayloadBytes ? originalData.substring(0, kRpcMaxPayloadBytes) : originalData; + truncatedData = originalData.length > kRpcMaxPayloadBytes + ? originalData.substring(0, kRpcMaxPayloadBytes) + : originalData; } if (truncatedMessage.length != originalMessage.length || diff --git a/lib/src/support/byte_ring_buffer.dart b/lib/src/support/byte_ring_buffer.dart index 036f43339..7afe5d26a 100644 --- a/lib/src/support/byte_ring_buffer.dart +++ b/lib/src/support/byte_ring_buffer.dart @@ -19,9 +19,7 @@ import 'dart:typed_data'; /// /// Keeps the newest [capacity] bytes and discards the oldest as needed. class ByteRingBuffer { - ByteRingBuffer(this.capacity) - : assert(capacity > 0), - _buffer = Uint8List(capacity); + ByteRingBuffer(this.capacity) : assert(capacity > 0), _buffer = Uint8List(capacity); final int capacity; final Uint8List _buffer; diff --git a/lib/src/support/http_client/io.dart b/lib/src/support/http_client/io.dart index 4098e6159..2b29394b0 100644 --- a/lib/src/support/http_client/io.dart +++ b/lib/src/support/http_client/io.dart @@ -96,15 +96,19 @@ class _CertificatePinningConnectionFactory { } final rules = _validator.rulesForHost(url.host).where((rule) => rule.isEnabled).toList(growable: false); if (rules.isEmpty) { - logger.warning('Certificate pinning is enabled but no rule matches host ${url.host}, ' - 'this connection uses platform trust only'); + logger.warning( + 'Certificate pinning is enabled but no rule matches host ${url.host}, ' + 'this connection uses platform trust only', + ); } return rules; } io.SecurityContext? _securityContextFor(List rules) { - final trustedCertificates = - rules.where((rule) => rule.hasTrustedCertificates).expand((rule) => rule.trustedCertificates).toList(); + final trustedCertificates = rules + .where((rule) => rule.hasTrustedCertificates) + .expand((rule) => rule.trustedCertificates) + .toList(); if (trustedCertificates.isEmpty) { return null; } diff --git a/lib/src/support/native.dart b/lib/src/support/native.dart index 1cd6719bc..ff5f75802 100644 --- a/lib/src/support/native.dart +++ b/lib/src/support/native.dart @@ -110,15 +110,15 @@ class Native { } static Map _audioProcessingPlatformUnavailable() => { - 'result': false, - 'code': 'rejectedPlatformUnavailable', - 'message': 'Audio processing options are unavailable on this platform.', - }; + 'result': false, + 'code': 'rejectedPlatformUnavailable', + 'message': 'Audio processing options are unavailable on this platform.', + }; static PlatformException _audioProcessingPlatformUnavailableException() => PlatformException( - code: 'rejectedPlatformUnavailable', - message: 'Audio processing options are unavailable on this platform.', - ); + code: 'rejectedPlatformUnavailable', + message: 'Audio processing options are unavailable on this platform.', + ); /// Starts the native WebRTC audio device module recording path with the /// capture-time audio processing options for the local microphone track. diff --git a/lib/src/support/native_audio.dart b/lib/src/support/native_audio.dart index e09fa8e08..66e2f8704 100644 --- a/lib/src/support/native_audio.dart +++ b/lib/src/support/native_audio.dart @@ -17,38 +17,38 @@ import 'value_or_absent.dart'; extension AppleAudioCategoryExt on AppleAudioCategory { String toStringValue() => switch (this) { - AppleAudioCategory.soloAmbient => 'soloAmbient', - AppleAudioCategory.playback => 'playback', - AppleAudioCategory.record => 'record', - AppleAudioCategory.playAndRecord => 'playAndRecord', - AppleAudioCategory.multiRoute => 'multiRoute', - }; + AppleAudioCategory.soloAmbient => 'soloAmbient', + AppleAudioCategory.playback => 'playback', + AppleAudioCategory.record => 'record', + AppleAudioCategory.playAndRecord => 'playAndRecord', + AppleAudioCategory.multiRoute => 'multiRoute', + }; } extension AppleAudioCategoryOptionExt on AppleAudioCategoryOption { String toStringValue() => switch (this) { - AppleAudioCategoryOption.mixWithOthers => 'mixWithOthers', - AppleAudioCategoryOption.duckOthers => 'duckOthers', - AppleAudioCategoryOption.interruptSpokenAudioAndMixWithOthers => 'interruptSpokenAudioAndMixWithOthers', - AppleAudioCategoryOption.allowBluetooth => 'allowBluetooth', - AppleAudioCategoryOption.allowBluetoothA2DP => 'allowBluetoothA2DP', - AppleAudioCategoryOption.allowAirPlay => 'allowAirPlay', - AppleAudioCategoryOption.defaultToSpeaker => 'defaultToSpeaker', - }; + AppleAudioCategoryOption.mixWithOthers => 'mixWithOthers', + AppleAudioCategoryOption.duckOthers => 'duckOthers', + AppleAudioCategoryOption.interruptSpokenAudioAndMixWithOthers => 'interruptSpokenAudioAndMixWithOthers', + AppleAudioCategoryOption.allowBluetooth => 'allowBluetooth', + AppleAudioCategoryOption.allowBluetoothA2DP => 'allowBluetoothA2DP', + AppleAudioCategoryOption.allowAirPlay => 'allowAirPlay', + AppleAudioCategoryOption.defaultToSpeaker => 'defaultToSpeaker', + }; } extension AppleAudioModeExt on AppleAudioMode { String toStringValue() => switch (this) { - AppleAudioMode.default_ => 'default', - AppleAudioMode.gameChat => 'gameChat', - AppleAudioMode.measurement => 'measurement', - AppleAudioMode.moviePlayback => 'moviePlayback', - AppleAudioMode.spokenAudio => 'spokenAudio', - AppleAudioMode.videoChat => 'videoChat', - AppleAudioMode.videoRecording => 'videoRecording', - AppleAudioMode.voiceChat => 'voiceChat', - AppleAudioMode.voicePrompt => 'voicePrompt', - }; + AppleAudioMode.default_ => 'default', + AppleAudioMode.gameChat => 'gameChat', + AppleAudioMode.measurement => 'measurement', + AppleAudioMode.moviePlayback => 'moviePlayback', + AppleAudioMode.spokenAudio => 'spokenAudio', + AppleAudioMode.videoChat => 'videoChat', + AppleAudioMode.videoRecording => 'videoRecording', + AppleAudioMode.voiceChat => 'voiceChat', + AppleAudioMode.voicePrompt => 'voicePrompt', + }; } class NativeAudioConfiguration { @@ -56,31 +56,29 @@ class NativeAudioConfiguration { final Set? appleAudioCategoryOptions; final AppleAudioMode? appleAudioMode; - NativeAudioConfiguration( - { - // for iOS / Mac - this.appleAudioCategory, - this.appleAudioCategoryOptions, - this.appleAudioMode - // Android options - // ... - }); + NativeAudioConfiguration({ + // for iOS / Mac + this.appleAudioCategory, + this.appleAudioCategoryOptions, + this.appleAudioMode, + // Android options + // ... + }); Map toMap() => { - if (appleAudioCategory != null) 'appleAudioCategory': appleAudioCategory!.toStringValue(), - if (appleAudioCategoryOptions != null) - 'appleAudioCategoryOptions': appleAudioCategoryOptions!.map((e) => e.toStringValue()).toList(), - if (appleAudioMode != null) 'appleAudioMode': appleAudioMode!.toStringValue(), - }; + if (appleAudioCategory != null) 'appleAudioCategory': appleAudioCategory!.toStringValue(), + if (appleAudioCategoryOptions != null) + 'appleAudioCategoryOptions': appleAudioCategoryOptions!.map((e) => e.toStringValue()).toList(), + if (appleAudioMode != null) 'appleAudioMode': appleAudioMode!.toStringValue(), + }; NativeAudioConfiguration copyWith({ ValueOrAbsent appleAudioCategory = const ValueOrAbsent.absent(), ValueOrAbsent?> appleAudioCategoryOptions = const ValueOrAbsent.absent(), ValueOrAbsent appleAudioMode = const ValueOrAbsent.absent(), - }) => - NativeAudioConfiguration( - appleAudioCategory: appleAudioCategory.valueOr(this.appleAudioCategory), - appleAudioCategoryOptions: appleAudioCategoryOptions.valueOr(this.appleAudioCategoryOptions), - appleAudioMode: appleAudioMode.valueOr(this.appleAudioMode), - ); + }) => NativeAudioConfiguration( + appleAudioCategory: appleAudioCategory.valueOr(this.appleAudioCategory), + appleAudioCategoryOptions: appleAudioCategoryOptions.valueOr(this.appleAudioCategoryOptions), + appleAudioMode: appleAudioMode.valueOr(this.appleAudioMode), + ); } diff --git a/lib/src/support/platform.dart b/lib/src/support/platform.dart index d6f0308c8..d2367867b 100644 --- a/lib/src/support/platform.dart +++ b/lib/src/support/platform.dart @@ -26,10 +26,10 @@ bool lkPlatformIsWebMobile() => lkPlatformIsWebMobileImplementation(); bool lkPlatformIsApple() => [PlatformType.iOS, PlatformType.macOS].contains(lkPlatform()); bool lkPlatformIsDesktop() => [ - PlatformType.macOS, - PlatformType.windows, - PlatformType.linux, - ].contains(lkPlatform()); + PlatformType.macOS, + PlatformType.windows, + PlatformType.linux, +].contains(lkPlatform()); bool lkPlatformSupportsExplicitAudioRecordingStart() => !lkPlatformIsTest() && [PlatformType.iOS, PlatformType.macOS, PlatformType.android].contains(lkPlatform()); diff --git a/lib/src/support/platform/web.dart b/lib/src/support/platform/web.dart index 7fa7f774b..624cab34d 100644 --- a/lib/src/support/platform/web.dart +++ b/lib/src/support/platform/web.dart @@ -40,8 +40,9 @@ bool isScriptTransformSupported() { bool isInsertableStreamSupported() { return web.window.hasProperty('RTCRtpSender'.toJS).isDefinedAndNotNull && ((web.window.getProperty('RTCRtpSender'.toJS) as JSObject).getProperty( - 'prototype'.toJS, - ) as JSObject) + 'prototype'.toJS, + ) + as JSObject) .getProperty('createEncodedStreams'.toJS) .isDefinedAndNotNull; } diff --git a/lib/src/support/region_url_provider.dart b/lib/src/support/region_url_provider.dart index 6c5045362..5bc0732da 100644 --- a/lib/src/support/region_url_provider.dart +++ b/lib/src/support/region_url_provider.dart @@ -76,8 +76,13 @@ class RegionUrlProvider { if (regionSettingsResponse.statusCode == 200) { final mapData = json.decode(regionSettingsResponse.body); final regions = (mapData['regions'] as List) - .map((region) => lk_models.RegionInfo( - distance: Int64(int.parse(region['distance'])), region: region['region'], url: region['url'])) + .map( + (region) => lk_models.RegionInfo( + distance: Int64(int.parse(region['distance'])), + region: region['region'], + url: region['url'], + ), + ) .toList(); final regionSettings = lk_models.RegionSettings( regions: regions, @@ -86,11 +91,12 @@ class RegionUrlProvider { return regionSettings; } else { throw ConnectException( - 'Could not fetch region settings: ${regionSettingsResponse.body}, status: ${regionSettingsResponse.statusCode}', - reason: regionSettingsResponse.statusCode == 401 - ? ConnectionErrorReason.NotAllowed - : ConnectionErrorReason.InternalError, - statusCode: regionSettingsResponse.statusCode); + 'Could not fetch region settings: ${regionSettingsResponse.body}, status: ${regionSettingsResponse.statusCode}', + reason: regionSettingsResponse.statusCode == 401 + ? ConnectionErrorReason.NotAllowed + : ConnectionErrorReason.InternalError, + statusCode: regionSettingsResponse.statusCode, + ); } } @@ -106,16 +112,16 @@ class RegionUrlProvider { extension RegionInfoExtension on lk_models.RegionInfo { lk_models.RegionInfo fromJson(Map json) => lk_models.RegionInfo( - region: json['region'], - url: json['url'], - distance: json['distance'], - ); + region: json['region'], + url: json['url'], + distance: json['distance'], + ); } extension RegionSettingsExtension on lk_models.RegionSettings { lk_models.RegionSettings fromJson(Map json) => lk_models.RegionSettings( - regions: json['regions'].map((region) => lk_models.RegionInfo.fromJson(region)).toList(), - ); + regions: json['regions'].map((region) => lk_models.RegionInfo.fromJson(region)).toList(), + ); } bool isCloudUrl(Uri uri) { diff --git a/lib/src/support/webrtc_initialize_options.dart b/lib/src/support/webrtc_initialize_options.dart index f902641b6..7c655fe71 100644 --- a/lib/src/support/webrtc_initialize_options.dart +++ b/lib/src/support/webrtc_initialize_options.dart @@ -22,9 +22,8 @@ Map liveKitWebRTCInitializeOptions({ required bool bypassVoiceProcessing, required AudioSessionOptions? initialAudioSessionOptions, required bool includeAndroidAudioConfiguration, -}) => - { - if (bypassVoiceProcessing) 'bypassVoiceProcessing': bypassVoiceProcessing, - if (includeAndroidAudioConfiguration && initialAudioSessionOptions != null) - 'androidAudioConfiguration': androidAudioSessionConfigurationToMap(initialAudioSessionOptions.android), - }; +}) => { + if (bypassVoiceProcessing) 'bypassVoiceProcessing': bypassVoiceProcessing, + if (includeAndroidAudioConfiguration && initialAudioSessionOptions != null) + 'androidAudioConfiguration': androidAudioSessionConfigurationToMap(initialAudioSessionOptions.android), +}; diff --git a/lib/src/support/websocket.dart b/lib/src/support/websocket.dart index 7d0555c4c..12d2baeca 100644 --- a/lib/src/support/websocket.dart +++ b/lib/src/support/websocket.dart @@ -38,12 +38,13 @@ class WebSocketEventHandlers { }); } -typedef WebSocketConnector = Future Function( - Uri uri, { - WebSocketEventHandlers? options, - Map? headers, - NetworkOptions? networkOptions, -}); +typedef WebSocketConnector = + Future Function( + Uri uri, { + WebSocketEventHandlers? options, + Map? headers, + NetworkOptions? networkOptions, + }); abstract class LiveKitWebSocket extends Disposable { void send(List data); @@ -53,6 +54,5 @@ abstract class LiveKitWebSocket extends Disposable { WebSocketEventHandlers? options, Map? headers, NetworkOptions? networkOptions = const NetworkOptions(), - }) => - lkWebSocketConnect(uri, options: options, headers: headers, networkOptions: networkOptions); + }) => lkWebSocketConnect(uri, options: options, headers: headers, networkOptions: networkOptions); } diff --git a/lib/src/support/websocket/io.dart b/lib/src/support/websocket/io.dart index db774a969..89bf47084 100644 --- a/lib/src/support/websocket/io.dart +++ b/lib/src/support/websocket/io.dart @@ -27,8 +27,7 @@ Future lkWebSocketConnect( WebSocketEventHandlers? options, Map? headers, NetworkOptions? networkOptions = const NetworkOptions(), -}) => - LiveKitWebSocketIO.connect(uri, options: options, headers: headers, networkOptions: networkOptions); +}) => LiveKitWebSocketIO.connect(uri, options: options, headers: headers, networkOptions: networkOptions); class LiveKitWebSocketIO extends LiveKitWebSocket { final io.WebSocket _ws; diff --git a/lib/src/support/websocket/web.dart b/lib/src/support/websocket/web.dart index cfed87cfe..bb89f412b 100644 --- a/lib/src/support/websocket/web.dart +++ b/lib/src/support/websocket/web.dart @@ -30,8 +30,7 @@ Future lkWebSocketConnect( WebSocketEventHandlers? options, Map? headers, // |headers| will be ignored on web NetworkOptions? networkOptions = const NetworkOptions(), -}) => - LiveKitWebSocketWeb.connect(uri, options: options, networkOptions: networkOptions); +}) => LiveKitWebSocketWeb.connect(uri, options: options, networkOptions: networkOptions); class LiveKitWebSocketWeb extends LiveKitWebSocket { final web.WebSocket _ws; @@ -50,8 +49,9 @@ class LiveKitWebSocketWeb extends LiveKitWebSocket { logger.warning('$objectId already disposed, ignoring received data.'); return; } - final dynamic data = - event.data.instanceOfString('ArrayBuffer') ? (event.data as JSArrayBuffer).toDart.asUint8List() : event.data; + final dynamic data = event.data.instanceOfString('ArrayBuffer') + ? (event.data as JSArrayBuffer).toDart.asUint8List() + : event.data; options?.onData?.call(data); }); _closeSubscription = _ws.onClose.listen((_) async { diff --git a/lib/src/token_source/caching.dart b/lib/src/token_source/caching.dart index f63e66938..2a7cdd345 100644 --- a/lib/src/token_source/caching.dart +++ b/lib/src/token_source/caching.dart @@ -117,8 +117,8 @@ class CachingTokenSource implements TokenSourceConfigurable { this._wrapped, { TokenStore? store, TokenValidator? validator, - }) : _store = store ?? InMemoryTokenStore(), - _validator = validator ?? _defaultValidator; + }) : _store = store ?? InMemoryTokenStore(), + _validator = validator ?? _defaultValidator; @override Future fetch(TokenRequestOptions options) async { @@ -175,10 +175,9 @@ extension CachedTokenSource on TokenSourceConfigurable { CachingTokenSource cached({ TokenStore? store, TokenValidator? validator, - }) => - CachingTokenSource( - this, - store: store, - validator: validator, - ); + }) => CachingTokenSource( + this, + store: store, + validator: validator, + ); } diff --git a/lib/src/token_source/caching.g.dart b/lib/src/token_source/caching.g.dart index 2874d60ef..1dcb85e50 100644 --- a/lib/src/token_source/caching.g.dart +++ b/lib/src/token_source/caching.g.dart @@ -7,11 +7,11 @@ part of 'caching.dart'; // ************************************************************************** TokenStoreItem _$TokenStoreItemFromJson(Map json) => TokenStoreItem( - options: TokenRequestOptions.fromJson(json['options'] as Map), - response: TokenSourceResponse.fromJson(json['response'] as Map), - ); + options: TokenRequestOptions.fromJson(json['options'] as Map), + response: TokenSourceResponse.fromJson(json['response'] as Map), +); Map _$TokenStoreItemToJson(TokenStoreItem instance) => { - 'options': instance.options.toJson(), - 'response': instance.response.toJson(), - }; + 'options': instance.options.toJson(), + 'response': instance.response.toJson(), +}; diff --git a/lib/src/token_source/development.dart b/lib/src/token_source/development.dart index f5f95d191..e71e343fd 100644 --- a/lib/src/token_source/development.dart +++ b/lib/src/token_source/development.dart @@ -30,11 +30,11 @@ class DevelopmentTokenSource extends EndpointTokenSource { DevelopmentTokenSource({ required String id, }) : super( - url: Uri.parse('https://cloud-api.livekit.io/api/v2/sandbox/connection-details'), - headers: { - 'X-Sandbox-ID': _sanitizeId(id), - }, - ); + url: Uri.parse('https://cloud-api.livekit.io/api/v2/sandbox/connection-details'), + headers: { + 'X-Sandbox-ID': _sanitizeId(id), + }, + ); } /// A token source that queries LiveKit's sandbox token server for development and testing. diff --git a/lib/src/token_source/jwt.g.dart b/lib/src/token_source/jwt.g.dart index 66fd7d5b0..f9304c0a2 100644 --- a/lib/src/token_source/jwt.g.dart +++ b/lib/src/token_source/jwt.g.dart @@ -7,31 +7,31 @@ part of 'jwt.dart'; // ************************************************************************** LiveKitVideoGrant _$LiveKitVideoGrantFromJson(Map json) => LiveKitVideoGrant( - room: json['room'] as String?, - roomCreate: json['room_create'] as bool?, - roomJoin: json['room_join'] as bool?, - roomList: json['room_list'] as bool?, - roomRecord: json['room_record'] as bool?, - roomAdmin: json['room_admin'] as bool?, - canPublish: json['can_publish'] as bool?, - canSubscribe: json['can_subscribe'] as bool?, - canPublishData: json['can_publish_data'] as bool?, - canPublishSources: (json['can_publish_sources'] as List?)?.map((e) => e as String).toList(), - hidden: json['hidden'] as bool?, - recorder: json['recorder'] as bool?, - ); + room: json['room'] as String?, + roomCreate: json['room_create'] as bool?, + roomJoin: json['room_join'] as bool?, + roomList: json['room_list'] as bool?, + roomRecord: json['room_record'] as bool?, + roomAdmin: json['room_admin'] as bool?, + canPublish: json['can_publish'] as bool?, + canSubscribe: json['can_subscribe'] as bool?, + canPublishData: json['can_publish_data'] as bool?, + canPublishSources: (json['can_publish_sources'] as List?)?.map((e) => e as String).toList(), + hidden: json['hidden'] as bool?, + recorder: json['recorder'] as bool?, +); Map _$LiveKitVideoGrantToJson(LiveKitVideoGrant instance) => { - if (instance.room case final value?) 'room': value, - if (instance.roomCreate case final value?) 'room_create': value, - if (instance.roomJoin case final value?) 'room_join': value, - if (instance.roomList case final value?) 'room_list': value, - if (instance.roomRecord case final value?) 'room_record': value, - if (instance.roomAdmin case final value?) 'room_admin': value, - if (instance.canPublish case final value?) 'can_publish': value, - if (instance.canSubscribe case final value?) 'can_subscribe': value, - if (instance.canPublishData case final value?) 'can_publish_data': value, - if (instance.canPublishSources case final value?) 'can_publish_sources': value, - if (instance.hidden case final value?) 'hidden': value, - if (instance.recorder case final value?) 'recorder': value, - }; + if (instance.room case final value?) 'room': value, + if (instance.roomCreate case final value?) 'room_create': value, + if (instance.roomJoin case final value?) 'room_join': value, + if (instance.roomList case final value?) 'room_list': value, + if (instance.roomRecord case final value?) 'room_record': value, + if (instance.roomAdmin case final value?) 'room_admin': value, + if (instance.canPublish case final value?) 'can_publish': value, + if (instance.canSubscribe case final value?) 'can_subscribe': value, + if (instance.canPublishData case final value?) 'can_publish_data': value, + if (instance.canPublishSources case final value?) 'can_publish_sources': value, + if (instance.hidden case final value?) 'hidden': value, + if (instance.recorder case final value?) 'recorder': value, +}; diff --git a/lib/src/token_source/room_configuration.g.dart b/lib/src/token_source/room_configuration.g.dart index 9bfaee4d2..c83f1a0d3 100644 --- a/lib/src/token_source/room_configuration.g.dart +++ b/lib/src/token_source/room_configuration.g.dart @@ -7,39 +7,39 @@ part of 'room_configuration.dart'; // ************************************************************************** RoomAgentDispatch _$RoomAgentDispatchFromJson(Map json) => RoomAgentDispatch( - agentName: json['agent_name'] as String?, - metadata: json['metadata'] as String?, - deployment: json['deployment'] as String?, - ); + agentName: json['agent_name'] as String?, + metadata: json['metadata'] as String?, + deployment: json['deployment'] as String?, +); Map _$RoomAgentDispatchToJson(RoomAgentDispatch instance) => { - if (instance.agentName case final value?) 'agent_name': value, - if (instance.metadata case final value?) 'metadata': value, - if (instance.deployment case final value?) 'deployment': value, - }; + if (instance.agentName case final value?) 'agent_name': value, + if (instance.metadata case final value?) 'metadata': value, + if (instance.deployment case final value?) 'deployment': value, +}; RoomConfiguration _$RoomConfigurationFromJson(Map json) => RoomConfiguration( - name: json['name'] as String?, - emptyTimeout: (json['empty_timeout'] as num?)?.toInt(), - departureTimeout: (json['departure_timeout'] as num?)?.toInt(), - maxParticipants: (json['max_participants'] as num?)?.toInt(), - metadata: json['metadata'] as String?, - minPlayoutDelay: (json['min_playout_delay'] as num?)?.toInt(), - maxPlayoutDelay: (json['max_playout_delay'] as num?)?.toInt(), - syncStreams: json['sync_streams'] as bool?, - agents: (json['agents'] as List?) - ?.map((e) => RoomAgentDispatch.fromJson(e as Map)) - .toList(), - ); + name: json['name'] as String?, + emptyTimeout: (json['empty_timeout'] as num?)?.toInt(), + departureTimeout: (json['departure_timeout'] as num?)?.toInt(), + maxParticipants: (json['max_participants'] as num?)?.toInt(), + metadata: json['metadata'] as String?, + minPlayoutDelay: (json['min_playout_delay'] as num?)?.toInt(), + maxPlayoutDelay: (json['max_playout_delay'] as num?)?.toInt(), + syncStreams: json['sync_streams'] as bool?, + agents: (json['agents'] as List?) + ?.map((e) => RoomAgentDispatch.fromJson(e as Map)) + .toList(), +); Map _$RoomConfigurationToJson(RoomConfiguration instance) => { - if (instance.name case final value?) 'name': value, - if (instance.emptyTimeout case final value?) 'empty_timeout': value, - if (instance.departureTimeout case final value?) 'departure_timeout': value, - if (instance.maxParticipants case final value?) 'max_participants': value, - if (instance.metadata case final value?) 'metadata': value, - if (instance.minPlayoutDelay case final value?) 'min_playout_delay': value, - if (instance.maxPlayoutDelay case final value?) 'max_playout_delay': value, - if (instance.syncStreams case final value?) 'sync_streams': value, - if (instance.agents?.map((e) => e.toJson()).toList() case final value?) 'agents': value, - }; + if (instance.name case final value?) 'name': value, + if (instance.emptyTimeout case final value?) 'empty_timeout': value, + if (instance.departureTimeout case final value?) 'departure_timeout': value, + if (instance.maxParticipants case final value?) 'max_participants': value, + if (instance.metadata case final value?) 'metadata': value, + if (instance.minPlayoutDelay case final value?) 'min_playout_delay': value, + if (instance.maxPlayoutDelay case final value?) 'max_playout_delay': value, + if (instance.syncStreams case final value?) 'sync_streams': value, + if (instance.agents?.map((e) => e.toJson()).toList() case final value?) 'agents': value, +}; diff --git a/lib/src/token_source/token_source.g.dart b/lib/src/token_source/token_source.g.dart index ec930e18c..f672fb7de 100644 --- a/lib/src/token_source/token_source.g.dart +++ b/lib/src/token_source/token_source.g.dart @@ -7,60 +7,61 @@ part of 'token_source.dart'; // ************************************************************************** TokenRequestOptions _$TokenRequestOptionsFromJson(Map json) => TokenRequestOptions( - roomName: json['roomName'] as String?, - participantName: json['participantName'] as String?, - participantIdentity: json['participantIdentity'] as String?, - participantMetadata: json['participantMetadata'] as String?, - participantAttributes: (json['participantAttributes'] as Map?)?.map( - (k, e) => MapEntry(k, e as String), - ), - agentName: json['agentName'] as String?, - agentMetadata: json['agentMetadata'] as String?, - agentDeployment: json['agentDeployment'] as String?, - ); + roomName: json['roomName'] as String?, + participantName: json['participantName'] as String?, + participantIdentity: json['participantIdentity'] as String?, + participantMetadata: json['participantMetadata'] as String?, + participantAttributes: (json['participantAttributes'] as Map?)?.map( + (k, e) => MapEntry(k, e as String), + ), + agentName: json['agentName'] as String?, + agentMetadata: json['agentMetadata'] as String?, + agentDeployment: json['agentDeployment'] as String?, +); Map _$TokenRequestOptionsToJson(TokenRequestOptions instance) => { - if (instance.roomName case final value?) 'roomName': value, - if (instance.participantName case final value?) 'participantName': value, - if (instance.participantIdentity case final value?) 'participantIdentity': value, - if (instance.participantMetadata case final value?) 'participantMetadata': value, - if (instance.participantAttributes case final value?) 'participantAttributes': value, - if (instance.agentName case final value?) 'agentName': value, - if (instance.agentMetadata case final value?) 'agentMetadata': value, - if (instance.agentDeployment case final value?) 'agentDeployment': value, - }; + if (instance.roomName case final value?) 'roomName': value, + if (instance.participantName case final value?) 'participantName': value, + if (instance.participantIdentity case final value?) 'participantIdentity': value, + if (instance.participantMetadata case final value?) 'participantMetadata': value, + if (instance.participantAttributes case final value?) 'participantAttributes': value, + if (instance.agentName case final value?) 'agentName': value, + if (instance.agentMetadata case final value?) 'agentMetadata': value, + if (instance.agentDeployment case final value?) 'agentDeployment': value, +}; TokenSourceRequest _$TokenSourceRequestFromJson(Map json) => TokenSourceRequest( - roomName: json['room_name'] as String?, - participantName: json['participant_name'] as String?, - participantIdentity: json['participant_identity'] as String?, - participantMetadata: json['participant_metadata'] as String?, - participantAttributes: (json['participant_attributes'] as Map?)?.map( - (k, e) => MapEntry(k, e as String), - ), - roomConfiguration: - json['room_config'] == null ? null : RoomConfiguration.fromJson(json['room_config'] as Map), - ); + roomName: json['room_name'] as String?, + participantName: json['participant_name'] as String?, + participantIdentity: json['participant_identity'] as String?, + participantMetadata: json['participant_metadata'] as String?, + participantAttributes: (json['participant_attributes'] as Map?)?.map( + (k, e) => MapEntry(k, e as String), + ), + roomConfiguration: json['room_config'] == null + ? null + : RoomConfiguration.fromJson(json['room_config'] as Map), +); Map _$TokenSourceRequestToJson(TokenSourceRequest instance) => { - if (instance.roomName case final value?) 'room_name': value, - if (instance.participantName case final value?) 'participant_name': value, - if (instance.participantIdentity case final value?) 'participant_identity': value, - if (instance.participantMetadata case final value?) 'participant_metadata': value, - if (instance.participantAttributes case final value?) 'participant_attributes': value, - if (instance.roomConfiguration?.toJson() case final value?) 'room_config': value, - }; + if (instance.roomName case final value?) 'room_name': value, + if (instance.participantName case final value?) 'participant_name': value, + if (instance.participantIdentity case final value?) 'participant_identity': value, + if (instance.participantMetadata case final value?) 'participant_metadata': value, + if (instance.participantAttributes case final value?) 'participant_attributes': value, + if (instance.roomConfiguration?.toJson() case final value?) 'room_config': value, +}; TokenSourceResponse _$TokenSourceResponseFromJson(Map json) => TokenSourceResponse( - serverUrl: json['server_url'] as String, - participantToken: json['participant_token'] as String, - participantName: json['participant_name'] as String?, - roomName: json['room_name'] as String?, - ); + serverUrl: json['server_url'] as String, + participantToken: json['participant_token'] as String, + participantName: json['participant_name'] as String?, + roomName: json['room_name'] as String?, +); Map _$TokenSourceResponseToJson(TokenSourceResponse instance) => { - 'server_url': instance.serverUrl, - 'participant_token': instance.participantToken, - if (instance.participantName case final value?) 'participant_name': value, - if (instance.roomName case final value?) 'room_name': value, - }; + 'server_url': instance.serverUrl, + 'participant_token': instance.participantToken, + if (instance.participantName case final value?) 'participant_name': value, + if (instance.roomName case final value?) 'room_name': value, +}; diff --git a/lib/src/track/audio_visualizer_native.dart b/lib/src/track/audio_visualizer_native.dart index 926049c45..9c54fe8e0 100644 --- a/lib/src/track/audio_visualizer_native.dart +++ b/lib/src/track/audio_visualizer_native.dart @@ -40,10 +40,12 @@ class AudioVisualizerNative extends AudioVisualizer { _eventChannel = EventChannel('io.livekit.audio.visualizer/eventChannel-${mediaStreamTrack.id}-$visualizerId'); _streamSubscription = _eventChannel?.receiveBroadcastStream().listen((event) { - events.emit(AudioVisualizerEvent( - track: _audioTrack!, - event: event, - )); + events.emit( + AudioVisualizerEvent( + track: _audioTrack!, + event: event, + ), + ); }); } diff --git a/lib/src/track/audio_visualizer_web.dart b/lib/src/track/audio_visualizer_web.dart index 8ab28276e..b2012dcf1 100644 --- a/lib/src/track/audio_visualizer_web.dart +++ b/lib/src/track/audio_visualizer_web.dart @@ -67,8 +67,9 @@ class AudioVisualizerWeb extends AudioVisualizer { Float32List chunks = Float32List(visualizerOptions.barCount); for (var i = 0; i < bands; i++) { - final summedVolumes = - normalizedFrequencies.sublist(i * chunkSize, (i + 1) * chunkSize).reduce((acc, val) => (acc += val)); + final summedVolumes = normalizedFrequencies + .sublist(i * chunkSize, (i + 1) * chunkSize) + .reduce((acc, val) => (acc += val)); chunks[i] = (summedVolumes / chunkSize); } @@ -76,10 +77,12 @@ class AudioVisualizerWeb extends AudioVisualizer { chunks = centerBands(chunks); } - events.emit(AudioVisualizerEvent( - track: _audioTrack, - event: chunks, - )); + events.emit( + AudioVisualizerEvent( + track: _audioTrack, + event: chunks, + ), + ); } catch (e) { logger.warning('Error in visualizer: $e'); } @@ -114,10 +117,12 @@ class AudioVisualizerWeb extends AudioVisualizer { return; } - events.emit(AudioVisualizerEvent( - track: _audioTrack!, - event: [], - )); + events.emit( + AudioVisualizerEvent( + track: _audioTrack!, + event: [], + ), + ); _timer?.cancel(); _timer = null; diff --git a/lib/src/track/local/audio.dart b/lib/src/track/local/audio.dart index 4f9bba941..47b21e910 100644 --- a/lib/src/track/local/audio.dart +++ b/lib/src/track/local/audio.dart @@ -69,10 +69,12 @@ class LocalAudioTrack extends LocalTrack with AudioTrack, LocalAudioManagementMi _throwIfAudioProcessingFailed(response); currentOptions = nextOptions; - events.emit(LocalTrackOptionsUpdatedEvent( - track: this, - options: currentOptions, - )); + events.emit( + LocalTrackOptionsUpdatedEvent( + track: this, + options: currentOptions, + ), + ); } num? _currentBitrate; @@ -165,11 +167,11 @@ class LocalAudioTrack extends LocalTrack with AudioTrack, LocalAudioManagementMi rtc.MediaStreamTrack track, this.currentOptions, ) : super( - TrackType.AUDIO, - source, - stream, - track, - ); + TrackType.AUDIO, + source, + stream, + track, + ); /// Creates a new audio track from the default audio input device. static Future create([ diff --git a/lib/src/track/local/local.dart b/lib/src/track/local/local.dart index 3a3cea8c6..33dd4d4de 100644 --- a/lib/src/track/local/local.dart +++ b/lib/src/track/local/local.dart @@ -173,12 +173,12 @@ abstract class LocalTrack extends Track { TrackProcessor? get processor => _processor; LocalTrack(TrackType kind, TrackSource source, rtc.MediaStream mediaStream, rtc.MediaStreamTrack mediaStreamTrack) - : super( - kind, - source, - mediaStream, - mediaStreamTrack, - ) { + : super( + kind, + source, + mediaStream, + mediaStreamTrack, + ) { mediaStreamTrack.onEnded = () { logger.fine('MediaStreamTrack.onEnded()'); events.emit(TrackEndedEvent(track: this)); @@ -249,8 +249,8 @@ abstract class LocalTrack extends Track { 'audio': options is AudioCaptureOptions ? options.toMediaConstraintsMap() : options is ScreenShareCaptureOptions - ? (options).captureScreenAudio - : false, + ? (options).captureScreenAudio + : false, 'video': options is VideoCaptureOptions ? options.toMediaConstraintsMap() : false, }; @@ -329,10 +329,12 @@ abstract class LocalTrack extends Track { await start(); // notify so VideoView can re-compute mirror mode if necessary - events.emit(LocalTrackOptionsUpdatedEvent( - track: this, - options: currentOptions, - )); + events.emit( + LocalTrackOptionsUpdatedEvent( + track: this, + options: currentOptions, + ), + ); } Future setProcessor(TrackProcessor? processor) async { diff --git a/lib/src/track/options.dart b/lib/src/track/options.dart index 3b1b8f0e7..5c8f2bf15 100644 --- a/lib/src/track/options.dart +++ b/lib/src/track/options.dart @@ -38,9 +38,9 @@ enum CameraExposureMode { auto, locked } extension CameraPositionExt on CameraPosition { /// Return a [CameraPosition] which front and back is switched. CameraPosition switched() => switch (this) { - CameraPosition.front => CameraPosition.back, - CameraPosition.back => CameraPosition.front, - }; + CameraPosition.front => CameraPosition.back, + CameraPosition.back => CameraPosition.front, + }; } /// Options used when creating a [LocalVideoTrack] that captures the camera. @@ -66,29 +66,29 @@ class CameraCaptureOptions extends VideoCaptureOptions { this.stopCameraCaptureOnMute = true, TrackProcessor? processor, }) : super( - params: params, - deviceId: deviceId, - maxFrameRate: maxFrameRate, - processor: processor, - ); + params: params, + deviceId: deviceId, + maxFrameRate: maxFrameRate, + processor: processor, + ); CameraCaptureOptions.from({required VideoCaptureOptions captureOptions}) - : cameraPosition = CameraPosition.front, - focusMode = CameraFocusMode.auto, - exposureMode = CameraExposureMode.auto, - stopCameraCaptureOnMute = true, - super( - params: captureOptions.params, - deviceId: captureOptions.deviceId, - maxFrameRate: captureOptions.maxFrameRate, - processor: captureOptions.processor, - ); + : cameraPosition = CameraPosition.front, + focusMode = CameraFocusMode.auto, + exposureMode = CameraExposureMode.auto, + stopCameraCaptureOnMute = true, + super( + params: captureOptions.params, + deviceId: captureOptions.deviceId, + maxFrameRate: captureOptions.maxFrameRate, + processor: captureOptions.processor, + ); @override Map toMediaConstraintsMap() { final constraints = { ...super.toMediaConstraintsMap(), - if (deviceId == null) 'facingMode': cameraPosition == CameraPosition.front ? 'user' : 'environment' + if (deviceId == null) 'facingMode': cameraPosition == CameraPosition.front ? 'user' : 'environment', }; if (deviceId != null && deviceId!.isNotEmpty) { if (kIsWeb) { @@ -99,7 +99,7 @@ class CameraCaptureOptions extends VideoCaptureOptions { } } else { constraints['optional'] = [ - {'sourceId': deviceId} + {'sourceId': deviceId}, ]; } } @@ -119,17 +119,16 @@ class CameraCaptureOptions extends VideoCaptureOptions { VideoParameters? params, bool? stopCameraCaptureOnMute, TrackProcessor? processor, - }) => - CameraCaptureOptions( - cameraPosition: cameraPosition ?? this.cameraPosition, - focusMode: focusMode ?? this.focusMode, - exposureMode: exposureMode ?? this.exposureMode, - deviceId: deviceId ?? this.deviceId, - maxFrameRate: maxFrameRate ?? this.maxFrameRate, - params: params ?? this.params, - stopCameraCaptureOnMute: stopCameraCaptureOnMute ?? this.stopCameraCaptureOnMute, - processor: processor ?? this.processor, - ); + }) => CameraCaptureOptions( + cameraPosition: cameraPosition ?? this.cameraPosition, + focusMode: focusMode ?? this.focusMode, + exposureMode: exposureMode ?? this.exposureMode, + deviceId: deviceId ?? this.deviceId, + maxFrameRate: maxFrameRate ?? this.maxFrameRate, + params: params ?? this.params, + stopCameraCaptureOnMute: stopCameraCaptureOnMute ?? this.stopCameraCaptureOnMute, + processor: processor ?? this.processor, + ); } /// Options used when creating a [LocalVideoTrack] that captures the screen. @@ -158,13 +157,13 @@ class ScreenShareCaptureOptions extends VideoCaptureOptions { VideoParameters params = VideoParametersPresets.screenShareH1080FPS15, }) : super(params: params, deviceId: sourceId, maxFrameRate: maxFrameRate); - ScreenShareCaptureOptions.from( - {this.useiOSBroadcastExtension = false, - this.captureScreenAudio = false, - this.preferCurrentTab = false, - this.selfBrowserSurface, - required VideoCaptureOptions captureOptions}) - : super(params: captureOptions.params); + ScreenShareCaptureOptions.from({ + this.useiOSBroadcastExtension = false, + this.captureScreenAudio = false, + this.preferCurrentTab = false, + this.selfBrowserSurface, + required VideoCaptureOptions captureOptions, + }) : super(params: captureOptions.params); ScreenShareCaptureOptions copyWith({ bool? useiOSBroadcastExtension, @@ -174,16 +173,15 @@ class ScreenShareCaptureOptions extends VideoCaptureOptions { double? maxFrameRate, bool? preferCurrentTab, String? selfBrowserSurface, - }) => - ScreenShareCaptureOptions( - useiOSBroadcastExtension: useiOSBroadcastExtension ?? this.useiOSBroadcastExtension, - captureScreenAudio: captureScreenAudio ?? this.captureScreenAudio, - params: params ?? this.params, - sourceId: sourceId ?? deviceId, - maxFrameRate: maxFrameRate ?? this.maxFrameRate, - preferCurrentTab: preferCurrentTab ?? this.preferCurrentTab, - selfBrowserSurface: selfBrowserSurface ?? this.selfBrowserSurface, - ); + }) => ScreenShareCaptureOptions( + useiOSBroadcastExtension: useiOSBroadcastExtension ?? this.useiOSBroadcastExtension, + captureScreenAudio: captureScreenAudio ?? this.captureScreenAudio, + params: params ?? this.params, + sourceId: sourceId ?? deviceId, + maxFrameRate: maxFrameRate ?? this.maxFrameRate, + preferCurrentTab: preferCurrentTab ?? this.preferCurrentTab, + selfBrowserSurface: selfBrowserSurface ?? this.selfBrowserSurface, + ); @override Map toMediaConstraintsMap() { @@ -251,7 +249,8 @@ abstract class VideoCaptureOptions extends LocalTrackOptions { enum AudioProcessingMode { automatic('auto'), platform('platform'), - software('software'); + software('software') + ; const AudioProcessingMode(this.constraintValue); @@ -281,24 +280,24 @@ class AudioProcessingOptions { }); const AudioProcessingOptions.communication() - : echoCancellation = true, - noiseSuppression = true, - autoGainControl = true, - highPassFilter = true, - echoCancellationMode = AudioProcessingMode.automatic, - noiseSuppressionMode = AudioProcessingMode.automatic, - autoGainControlMode = AudioProcessingMode.automatic, - highPassFilterMode = AudioProcessingMode.automatic; + : echoCancellation = true, + noiseSuppression = true, + autoGainControl = true, + highPassFilter = true, + echoCancellationMode = AudioProcessingMode.automatic, + noiseSuppressionMode = AudioProcessingMode.automatic, + autoGainControlMode = AudioProcessingMode.automatic, + highPassFilterMode = AudioProcessingMode.automatic; const AudioProcessingOptions.noProcessing() - : echoCancellation = false, - noiseSuppression = false, - autoGainControl = false, - highPassFilter = false, - echoCancellationMode = AudioProcessingMode.automatic, - noiseSuppressionMode = AudioProcessingMode.automatic, - autoGainControlMode = AudioProcessingMode.automatic, - highPassFilterMode = AudioProcessingMode.automatic; + : echoCancellation = false, + noiseSuppression = false, + autoGainControl = false, + highPassFilter = false, + echoCancellationMode = AudioProcessingMode.automatic, + noiseSuppressionMode = AudioProcessingMode.automatic, + autoGainControlMode = AudioProcessingMode.automatic, + highPassFilterMode = AudioProcessingMode.automatic; final bool echoCancellation; final bool noiseSuppression; @@ -310,15 +309,15 @@ class AudioProcessingOptions { final AudioProcessingMode highPassFilterMode; Map toMap() => { - 'echoCancellation': echoCancellation, - 'noiseSuppression': noiseSuppression, - 'autoGainControl': autoGainControl, - 'highPassFilter': highPassFilter, - 'echoCancellationMode': echoCancellationMode.constraintValue, - 'noiseSuppressionMode': noiseSuppressionMode.constraintValue, - 'autoGainControlMode': autoGainControlMode.constraintValue, - 'highPassFilterMode': highPassFilterMode.constraintValue, - }; + 'echoCancellation': echoCancellation, + 'noiseSuppression': noiseSuppression, + 'autoGainControl': autoGainControl, + 'highPassFilter': highPassFilter, + 'echoCancellationMode': echoCancellationMode.constraintValue, + 'noiseSuppressionMode': noiseSuppressionMode.constraintValue, + 'autoGainControlMode': autoGainControlMode.constraintValue, + 'highPassFilterMode': highPassFilterMode.constraintValue, + }; } /// Options used when creating a [LocalAudioTrack]. @@ -398,15 +397,15 @@ class AudioCaptureOptions extends LocalTrackOptions implements AudioProcessingOp }); AudioProcessingOptions get processing => AudioProcessingOptions( - echoCancellation: echoCancellation, - noiseSuppression: noiseSuppression, - autoGainControl: autoGainControl, - highPassFilter: highPassFilter, - echoCancellationMode: echoCancellationMode, - noiseSuppressionMode: noiseSuppressionMode, - autoGainControlMode: autoGainControlMode, - highPassFilterMode: highPassFilterMode, - ); + echoCancellation: echoCancellation, + noiseSuppression: noiseSuppression, + autoGainControl: autoGainControl, + highPassFilter: highPassFilter, + echoCancellationMode: echoCancellationMode, + noiseSuppressionMode: noiseSuppressionMode, + autoGainControlMode: autoGainControlMode, + highPassFilterMode: highPassFilterMode, + ); @override Map toMap() => processing.toMap(); diff --git a/lib/src/track/remote/audio.dart b/lib/src/track/remote/audio.dart index 5038df572..916071fdb 100644 --- a/lib/src/track/remote/audio.dart +++ b/lib/src/track/remote/audio.dart @@ -28,15 +28,18 @@ import 'remote.dart'; class RemoteAudioTrack extends RemoteTrack with AudioTrack, RemoteAudioManagementMixin { String? _deviceId; - RemoteAudioTrack(TrackSource source, rtc.MediaStream stream, rtc.MediaStreamTrack track, - {rtc.RTCRtpReceiver? receiver}) - : super( - TrackType.AUDIO, - source, - stream, - track, - receiver: receiver, - ); + RemoteAudioTrack( + TrackSource source, + rtc.MediaStream stream, + rtc.MediaStreamTrack track, { + rtc.RTCRtpReceiver? receiver, + }) : super( + TrackType.AUDIO, + source, + stream, + track, + receiver: receiver, + ); @override Future start() async { diff --git a/lib/src/track/remote/remote.dart b/lib/src/track/remote/remote.dart index 6f6ffdbc6..ae73c0efb 100644 --- a/lib/src/track/remote/remote.dart +++ b/lib/src/track/remote/remote.dart @@ -27,12 +27,12 @@ abstract class RemoteTrack extends Track { rtc.MediaStreamTrack track, { rtc.RTCRtpReceiver? receiver, }) : super( - kind, - source, - stream, - track, - receiver: receiver, - ); + kind, + source, + stream, + track, + receiver: receiver, + ); @override Future start() async { diff --git a/lib/src/track/remote/video.dart b/lib/src/track/remote/video.dart index fbb8f72df..5df726da9 100644 --- a/lib/src/track/remote/video.dart +++ b/lib/src/track/remote/video.dart @@ -24,15 +24,18 @@ import '../local/local.dart'; import 'remote.dart'; class RemoteVideoTrack extends RemoteTrack with VideoTrack { - RemoteVideoTrack(TrackSource source, rtc.MediaStream stream, rtc.MediaStreamTrack track, - {rtc.RTCRtpReceiver? receiver}) - : super( - TrackType.VIDEO, - source, - stream, - track, - receiver: receiver, - ); + RemoteVideoTrack( + TrackSource source, + rtc.MediaStream stream, + rtc.MediaStreamTrack track, { + rtc.RTCRtpReceiver? receiver, + }) : super( + TrackType.VIDEO, + source, + stream, + track, + receiver: receiver, + ); VideoReceiverStats? prevStats; num? _currentBitrate; diff --git a/lib/src/track/track.dart b/lib/src/track/track.dart index 3e029b82b..9c72d2043 100644 --- a/lib/src/track/track.dart +++ b/lib/src/track/track.dart @@ -205,11 +205,13 @@ abstract class Track extends DisposableChangeNotifier with EventsEmittable values.firstWhere((e) => e.value == value); diff --git a/lib/src/types/audio_encoding.dart b/lib/src/types/audio_encoding.dart index ee277299a..031fdf47b 100644 --- a/lib/src/types/audio_encoding.dart +++ b/lib/src/types/audio_encoding.dart @@ -39,12 +39,11 @@ class AudioEncoding { int? maxBitrate, Priority? bitratePriority, Priority? networkPriority, - }) => - AudioEncoding( - maxBitrate: maxBitrate ?? this.maxBitrate, - bitratePriority: bitratePriority ?? this.bitratePriority, - networkPriority: networkPriority ?? this.networkPriority, - ); + }) => AudioEncoding( + maxBitrate: maxBitrate ?? this.maxBitrate, + bitratePriority: bitratePriority ?? this.bitratePriority, + networkPriority: networkPriority ?? this.networkPriority, + ); @override String toString() => @@ -81,8 +80,8 @@ class AudioEncoding { /// Convenience extension for [AudioEncoding]. extension AudioEncodingExt on AudioEncoding { rtc.RTCRtpEncoding toRTCRtpEncoding() => rtc.RTCRtpEncoding( - maxBitrate: maxBitrate, - priority: bitratePriority?.toRtcpPriorityType() ?? rtc.RTCPriorityType.low, - networkPriority: networkPriority?.toRtcpPriorityType(), - ); + maxBitrate: maxBitrate, + priority: bitratePriority?.toRtcpPriorityType() ?? rtc.RTCPriorityType.low, + networkPriority: networkPriority?.toRtcpPriorityType(), + ); } diff --git a/lib/src/types/data_stream.dart b/lib/src/types/data_stream.dart index 4066a2a7e..3cfa23756 100644 --- a/lib/src/types/data_stream.dart +++ b/lib/src/types/data_stream.dart @@ -78,7 +78,8 @@ class StreamTextOptions { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(topic: $topic, destinationIdentities: $destinationIdentities, ' 'streamId: $streamId, totalSize: $totalSize, type: $type, version: $version, ' 'replyToStreamId: $replyToStreamId, attachedStreamIds: $attachedStreamIds)'; @@ -106,7 +107,8 @@ class StreamBytesOptions { }); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(name: $name, mimeType: $mimeType, topic: $topic, destinationIdentities: $destinationIdentities, ' 'attributes: $attributes, streamId: $streamId, totalSize: $totalSize, encryptionType: $encryptionType)'; } @@ -190,18 +192,19 @@ class ByteStreamInfo extends BaseStreamInfo { required String sendingParticipantIdentity, EncryptionType encryptionType = EncryptionType.kNone, }) : super( - id: id, - mimeType: mimeType, - topic: topic, - timestamp: timestamp, - size: size, - attributes: attributes, - sendingParticipantIdentity: sendingParticipantIdentity, - encryptionType: encryptionType, - ); + id: id, + mimeType: mimeType, + topic: topic, + timestamp: timestamp, + size: size, + attributes: attributes, + sendingParticipantIdentity: sendingParticipantIdentity, + encryptionType: encryptionType, + ); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(name: $name, id: $id, mimeType: $mimeType, topic: $topic, ' 'timestamp: $timestamp, size: $size, attributes: $attributes)'; } @@ -211,7 +214,8 @@ enum TextStreamOperationType { create, update, delete, - reaction; + reaction + ; static TextStreamOperationType? fromPBType(lk_models.DataStream_OperationType? type) { if (type == null) return TextStreamOperationType.create; @@ -274,18 +278,19 @@ class TextStreamInfo extends BaseStreamInfo { required String sendingParticipantIdentity, EncryptionType encryptionType = EncryptionType.kNone, }) : super( - id: id, - mimeType: mimeType, - topic: topic, - timestamp: timestamp, - size: size, - attributes: attributes, - encryptionType: encryptionType, - sendingParticipantIdentity: sendingParticipantIdentity, - ); + id: id, + mimeType: mimeType, + topic: topic, + timestamp: timestamp, + size: size, + attributes: attributes, + encryptionType: encryptionType, + sendingParticipantIdentity: sendingParticipantIdentity, + ); @override - String toString() => '${runtimeType}' + String toString() => + '${runtimeType}' '(id: $id, mimeType: $mimeType, topic: $topic, ' 'timestamp: $timestamp, size: $size, attributes: $attributes)'; } diff --git a/lib/src/types/other.dart b/lib/src/types/other.dart index 98cf6be0a..85afa368f 100644 --- a/lib/src/types/other.dart +++ b/lib/src/types/other.dart @@ -51,7 +51,8 @@ enum ClientProtocolVersion implements Comparable { v0(0), /// Spec: `CLIENT_PROTOCOL_DATA_STREAM_RPC`. Supports RPC v2 (data-stream payloads). - v1(1); + v1(1) + ; const ClientProtocolVersion(this.wireValue); @@ -206,7 +207,7 @@ class RTCConfiguration { Map toMap() { final iceServersMap = >[ if (iceServers != null) - for (final e in iceServers!) e.toMap() + for (final e in iceServers!) e.toMap(), ]; return { @@ -227,14 +228,13 @@ class RTCConfiguration { RTCIceTransportPolicy? iceTransportPolicy, bool? encodedInsertableStreams, bool? isDscpEnabled, - }) => - RTCConfiguration( - iceCandidatePoolSize: iceCandidatePoolSize ?? this.iceCandidatePoolSize, - iceServers: iceServers ?? this.iceServers, - iceTransportPolicy: iceTransportPolicy ?? this.iceTransportPolicy, - encodedInsertableStreams: encodedInsertableStreams ?? this.encodedInsertableStreams, - isDscpEnabled: isDscpEnabled ?? this.isDscpEnabled, - ); + }) => RTCConfiguration( + iceCandidatePoolSize: iceCandidatePoolSize ?? this.iceCandidatePoolSize, + iceServers: iceServers ?? this.iceServers, + iceTransportPolicy: iceTransportPolicy ?? this.iceTransportPolicy, + encodedInsertableStreams: encodedInsertableStreams ?? this.encodedInsertableStreams, + isDscpEnabled: isDscpEnabled ?? this.isDscpEnabled, + ); } @immutable @@ -250,10 +250,10 @@ class RTCIceServer { }); Map toMap() => { - if (urls?.isNotEmpty ?? false) 'urls': urls, - if (username?.isNotEmpty ?? false) 'username': username, - if (credential?.isNotEmpty ?? false) 'credential': credential, - }; + if (urls?.isNotEmpty ?? false) 'urls': urls, + if (username?.isNotEmpty ?? false) 'username': username, + if (credential?.isNotEmpty ?? false) 'credential': credential, + }; } @immutable @@ -300,8 +300,8 @@ class AdaptiveStreamPixelDensity { /// `2.0`, `2.75`). The effective value is capped at [maxDensity] (3x) when /// resolved. const AdaptiveStreamPixelDensity.fixed(double density) - : assert(density > 0, 'density must be positive'), - value = density; + : assert(density > 0, 'density must be positive'), + value = density; /// Resolves the effective multiplier, capped at [maxDensity]. For [auto], /// falls back to the supplied [devicePixelRatio]. diff --git a/lib/src/types/participant_permissions.dart b/lib/src/types/participant_permissions.dart index 22ac7faab..5677c7e82 100644 --- a/lib/src/types/participant_permissions.dart +++ b/lib/src/types/participant_permissions.dart @@ -37,11 +37,11 @@ class ParticipantPermissions { extension ParticipantPermissionExt on lk_models.ParticipantPermission { ParticipantPermissions toLKType() => ParticipantPermissions( - canSubscribe: canSubscribe, - canPublish: canPublish, - canPublishData: canPublishData, - hidden: hidden, - canUpdateMetadata: canUpdateMetadata, - canPublishSources: canPublishSources, - ); + canSubscribe: canSubscribe, + canPublish: canPublish, + canPublishData: canPublishData, + hidden: hidden, + canUpdateMetadata: canUpdateMetadata, + canPublishSources: canPublishSources, + ); } diff --git a/lib/src/types/participant_state.dart b/lib/src/types/participant_state.dart index 6691b5e5c..a4473ae35 100644 --- a/lib/src/types/participant_state.dart +++ b/lib/src/types/participant_state.dart @@ -34,10 +34,10 @@ enum ParticipantState { extension ParticipantStateExt on lk_models.ParticipantInfo_State { ParticipantState toLKType() => switch (this) { - lk_models.ParticipantInfo_State.JOINING => ParticipantState.joining, - lk_models.ParticipantInfo_State.JOINED => ParticipantState.joined, - lk_models.ParticipantInfo_State.ACTIVE => ParticipantState.active, - lk_models.ParticipantInfo_State.DISCONNECTED => ParticipantState.disconnected, - _ => ParticipantState.unknown, - }; + lk_models.ParticipantInfo_State.JOINING => ParticipantState.joining, + lk_models.ParticipantInfo_State.JOINED => ParticipantState.joined, + lk_models.ParticipantInfo_State.ACTIVE => ParticipantState.active, + lk_models.ParticipantInfo_State.DISCONNECTED => ParticipantState.disconnected, + _ => ParticipantState.unknown, + }; } diff --git a/lib/src/types/video_dimensions.dart b/lib/src/types/video_dimensions.dart index c975e4b0d..db596cf15 100644 --- a/lib/src/types/video_dimensions.dart +++ b/lib/src/types/video_dimensions.dart @@ -33,11 +33,10 @@ class VideoDimensions { VideoDimensions copyWith({ int? width, int? height, - }) => - VideoDimensions( - width ?? this.width, - height ?? this.height, - ); + }) => VideoDimensions( + width ?? this.width, + height ?? this.height, + ); // ---------------------------------------------------------------------- // equality diff --git a/lib/src/types/video_encoding.dart b/lib/src/types/video_encoding.dart index 4f24694bb..25c5e1eaa 100644 --- a/lib/src/types/video_encoding.dart +++ b/lib/src/types/video_encoding.dart @@ -44,13 +44,12 @@ class VideoEncoding implements Comparable { int? maxBitrate, Priority? bitratePriority, Priority? networkPriority, - }) => - VideoEncoding( - maxFramerate: maxFramerate ?? this.maxFramerate, - maxBitrate: maxBitrate ?? this.maxBitrate, - bitratePriority: bitratePriority ?? this.bitratePriority, - networkPriority: networkPriority ?? this.networkPriority, - ); + }) => VideoEncoding( + maxFramerate: maxFramerate ?? this.maxFramerate, + maxBitrate: maxBitrate ?? this.maxBitrate, + bitratePriority: bitratePriority ?? this.bitratePriority, + networkPriority: networkPriority ?? this.networkPriority, + ); @override String toString() => @@ -98,14 +97,13 @@ extension VideoEncodingExt on VideoEncoding { String? rid, double? scaleResolutionDownBy = 1.0, int? numTemporalLayers, - }) => - rtc.RTCRtpEncoding( - rid: rid, - scaleResolutionDownBy: scaleResolutionDownBy, - maxFramerate: maxFramerate, - maxBitrate: maxBitrate, - numTemporalLayers: numTemporalLayers, - priority: bitratePriority?.toRtcpPriorityType() ?? rtc.RTCPriorityType.low, - networkPriority: networkPriority?.toRtcpPriorityType(), - ); + }) => rtc.RTCRtpEncoding( + rid: rid, + scaleResolutionDownBy: scaleResolutionDownBy, + maxFramerate: maxFramerate, + maxBitrate: maxBitrate, + numTemporalLayers: numTemporalLayers, + priority: bitratePriority?.toRtcpPriorityType() ?? rtc.RTCPriorityType.low, + networkPriority: networkPriority?.toRtcpPriorityType(), + ); } diff --git a/lib/src/types/video_parameters.dart b/lib/src/types/video_parameters.dart index b982b64c6..ee0bfff5c 100644 --- a/lib/src/types/video_parameters.dart +++ b/lib/src/types/video_parameters.dart @@ -63,10 +63,10 @@ class VideoParameters implements Comparable { // https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia // Map toMediaConstraintsMap() => { - 'width': dimensions.width, - 'height': dimensions.height, - 'frameRate': encoding?.maxFramerate ?? 30, - }; + 'width': dimensions.width, + 'height': dimensions.height, + 'frameRate': encoding?.maxFramerate ?? 30, + }; } extension VideoParametersPresets on VideoParameters { diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 316806dba..c29d3ca23 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -44,14 +44,16 @@ extension UriExt on Uri { bool get isSecureScheme => ['https', 'wss'].contains(scheme); } -typedef RetryFuture = Future Function( - int triesLeft, - List errors, -); -typedef RetryCondition = bool Function( - int triesLeft, - List errors, -); +typedef RetryFuture = + Future Function( + int triesLeft, + List errors, + ); +typedef RetryCondition = + bool Function( + int triesLeft, + List errors, + ); // Collection of state-less static methods class Utils { @@ -64,6 +66,7 @@ class Utils { /// thrown objects by the [future]. static Future retry( RetryFuture future, { + /// number of total tries (first try + retries) int tries = 1, Duration delay = const Duration(seconds: 1), @@ -235,8 +238,10 @@ class Utils { return [ VideoParameters( - dimensions: - VideoDimensions((original.dimensions.width / scale).floor(), (original.dimensions.height / scale).floor()), + dimensions: VideoDimensions( + (original.dimensions.width / scale).floor(), + (original.dimensions.height / scale).floor(), + ), encoding: VideoEncoding( maxBitrate: math.max( 150 * 1000, @@ -316,10 +321,12 @@ class Utils { final size = dimensions.max(); final rid = videoRids[i]; if (e.encoding != null) { - result.add(e.encoding!.toRTCRtpEncoding( - rid: rid, - scaleResolutionDownBy: math.max(1, size / e.dimensions.max()), - )); + result.add( + e.encoding!.toRTCRtpEncoding( + rid: rid, + scaleResolutionDownBy: math.max(1, size / e.dimensions.max()), + ), + ); } }); return result; @@ -332,10 +339,11 @@ class Utils { required List requestedPresets, required bool isScreenShare, }) { - final params = (requestedPresets.isNotEmpty - ? requestedPresets - : _computeDefaultSimulcastParams(isScreenShare: isScreenShare, original: original)) - .sorted(); + final params = + (requestedPresets.isNotEmpty + ? requestedPresets + : _computeDefaultSimulcastParams(isScreenShare: isScreenShare, original: original)) + .sorted(); if (params.isEmpty) { return [original]; @@ -373,8 +381,9 @@ class Utils { final rawScaleDownBy = inDimensions.max() / preset.dimensions.max(); final clampedFramerate = math.min(presetEncoding.maxFramerate, topEncoding.maxFramerate); - final clampedBitrate = - rawScaleDownBy <= 1.0 ? math.min(presetEncoding.maxBitrate, topEncoding.maxBitrate) : presetEncoding.maxBitrate; + final clampedBitrate = rawScaleDownBy <= 1.0 + ? math.min(presetEncoding.maxBitrate, topEncoding.maxBitrate) + : presetEncoding.maxBitrate; if (clampedFramerate == presetEncoding.maxFramerate && clampedBitrate == presetEncoding.maxBitrate) { return preset; @@ -495,13 +504,15 @@ class Utils { final sm = ScalabilityMode(scalabilityMode); for (var i = 0; i < sm.spatial; i += 1) { // in legacy SVC, scaleResolutionDownBy cannot be set - encodings.add(rtc.RTCRtpEncoding( - rid: videoRids[2 - i], - maxBitrate: videoEncoding.maxBitrate ~/ math.pow(3, i), - maxFramerate: original.encoding!.maxFramerate, - priority: videoEncoding.bitratePriority?.toRtcpPriorityType() ?? rtc.RTCPriorityType.low, - networkPriority: videoEncoding.networkPriority?.toRtcpPriorityType(), - )); + encodings.add( + rtc.RTCRtpEncoding( + rid: videoRids[2 - i], + maxBitrate: videoEncoding.maxBitrate ~/ math.pow(3, i), + maxFramerate: original.encoding!.maxFramerate, + priority: videoEncoding.bitratePriority?.toRtcpPriorityType() ?? rtc.RTCPriorityType.low, + networkPriority: videoEncoding.networkPriority?.toRtcpPriorityType(), + ), + ); } } else { encodings.add(videoEncoding.toRTCRtpEncoding()); @@ -562,7 +573,7 @@ class Utils { width: dimensions.width, height: dimensions.height, bitrate: 0, - ) + ), ]; } @@ -571,12 +582,14 @@ class Utils { final List layers = []; final maxBitrate = encodings[0].maxBitrate ?? 0; for (var i = 0; i < sm.spatial; i++) { - layers.add(lk_models.VideoLayer( - quality: lk_models.VideoQuality.valueOf(lk_models.VideoQuality.HIGH.value - i), - width: (dimensions.width / math.pow(2, i)).floor(), - height: (dimensions.height / math.pow(2, i)).floor(), - bitrate: (maxBitrate / math.pow(3, i)).ceil(), - )); + layers.add( + lk_models.VideoLayer( + quality: lk_models.VideoQuality.valueOf(lk_models.VideoQuality.HIGH.value - i), + width: (dimensions.width / math.pow(2, i)).floor(), + height: (dimensions.height / math.pow(2, i)).floor(), + bitrate: (maxBitrate / math.pow(3, i)).ceil(), + ), + ); } return layers; } @@ -598,10 +611,10 @@ class Utils { @internal static lk_models.VideoQuality? videoQualityForRid(String? rid) => { - 'f': lk_models.VideoQuality.HIGH, - 'h': lk_models.VideoQuality.MEDIUM, - 'q': lk_models.VideoQuality.LOW, - }[rid]; + 'f': lk_models.VideoQuality.HIGH, + 'h': lk_models.VideoQuality.MEDIUM, + 'q': lk_models.VideoQuality.LOW, + }[rid]; // makes a debounce func, with 1 param @internal @@ -762,8 +775,8 @@ int compareVersions(String v1, String v2) { return parts1.length == parts2.length ? 0 : parts1.length < parts2.length - ? -1 - : 1; + ? -1 + : 1; } List splitUtf8(String s, int n) { diff --git a/lib/src/utils/data_packet_buffer.dart b/lib/src/utils/data_packet_buffer.dart index 3ab2d09ba..2804b5e9e 100644 --- a/lib/src/utils/data_packet_buffer.dart +++ b/lib/src/utils/data_packet_buffer.dart @@ -71,9 +71,11 @@ class DataPacketBuffer { // Log buffer limit enforcement if (removedCount > 0) { - logger.warning('DataPacketBuffer limit reached: removed $removedCount old packets. ' - 'Current: ${_buffer.length} packets, ${(_totalSize / 1024).round()}KB. ' - 'Limits: $maxPacketCount packets, ${(maxBufferSize / 1024).round()}KB'); + logger.warning( + 'DataPacketBuffer limit reached: removed $removedCount old packets. ' + 'Current: ${_buffer.length} packets, ${(_totalSize / 1024).round()}KB. ' + 'Limits: $maxPacketCount packets, ${(maxBufferSize / 1024).round()}KB', + ); } } diff --git a/lib/src/widgets/screen_select_dialog.dart b/lib/src/widgets/screen_select_dialog.dart index db11ce30b..9486588cd 100644 --- a/lib/src/widgets/screen_select_dialog.dart +++ b/lib/src/widgets/screen_select_dialog.dart @@ -23,7 +23,7 @@ import 'package:meta/meta.dart'; class ThumbnailWidget extends StatefulWidget { const ThumbnailWidget({Key? key, required this.source, required this.selected, required this.onTap}) - : super(key: key); + : super(key: key); final rtc.DesktopCapturerSource source; final bool selected; final Function(rtc.DesktopCapturerSource) onTap; @@ -42,16 +42,20 @@ class ThumbnailWidgetState extends State { super.initState(); _name = widget.source.name; _thumbnail = widget.source.thumbnail?.isNotEmpty == true ? widget.source.thumbnail : null; - _subscriptions.add(widget.source.onThumbnailChanged.stream.listen((thumbnail) { - setState(() { - _thumbnail = thumbnail; - }); - })); - _subscriptions.add(widget.source.onNameChanged.stream.listen((name) { - setState(() { - _name = name; - }); - })); + _subscriptions.add( + widget.source.onThumbnailChanged.stream.listen((thumbnail) { + setState(() { + _thumbnail = thumbnail; + }); + }), + ); + _subscriptions.add( + widget.source.onNameChanged.stream.listen((name) { + setState(() { + _name = name; + }); + }), + ); } @override @@ -67,28 +71,32 @@ class ThumbnailWidgetState extends State { return Column( children: [ Expanded( - child: Container( - decoration: widget.selected ? BoxDecoration(border: Border.all(width: 2, color: Colors.blueAccent)) : null, - child: InkWell( - onTap: () { - if (kDebugMode) { - print('Selected source id => ${widget.source.id}'); - } - widget.onTap(widget.source); - }, - child: _thumbnail != null - ? Image.memory( - _thumbnail!, - gaplessPlayback: true, - alignment: Alignment.center, - ) - : Container(), + child: Container( + decoration: widget.selected ? BoxDecoration(border: Border.all(width: 2, color: Colors.blueAccent)) : null, + child: InkWell( + onTap: () { + if (kDebugMode) { + print('Selected source id => ${widget.source.id}'); + } + widget.onTap(widget.source); + }, + child: _thumbnail != null + ? Image.memory( + _thumbnail!, + gaplessPlayback: true, + alignment: Alignment.center, + ) + : Container(), + ), ), - )), + ), Text( _name, style: TextStyle( - fontSize: 12, color: Colors.black87, fontWeight: widget.selected ? FontWeight.bold : FontWeight.normal), + fontSize: 12, + color: Colors.black87, + fontWeight: widget.selected ? FontWeight.bold : FontWeight.normal, + ), ), ], ); @@ -106,19 +114,25 @@ class ScreenSelectDialog extends Dialog { this.shareText = 'Share', }) : super(key: key) { Timer(const Duration(milliseconds: 100), _getSources); - _subscriptions.add(rtc.desktopCapturer.onAdded.stream.listen((source) { - _sources[source.id] = source; - _stateSetter?.call(() {}); - })); + _subscriptions.add( + rtc.desktopCapturer.onAdded.stream.listen((source) { + _sources[source.id] = source; + _stateSetter?.call(() {}); + }), + ); - _subscriptions.add(rtc.desktopCapturer.onRemoved.stream.listen((source) { - _sources.remove(source.id); - _stateSetter?.call(() {}); - })); + _subscriptions.add( + rtc.desktopCapturer.onRemoved.stream.listen((source) { + _sources.remove(source.id); + _stateSetter?.call(() {}); + }), + ); - _subscriptions.add(rtc.desktopCapturer.onThumbnailChanged.stream.listen((source) { - _stateSetter?.call(() {}); - })); + _subscriptions.add( + rtc.desktopCapturer.onThumbnailChanged.stream.listen((source) { + _stateSetter?.call(() {}); + }), + ); } /// Shows the picker and returns the id of the selected capture source, or @@ -223,78 +237,83 @@ class ScreenSelectDialog extends Dialog { return Material( type: MaterialType.transparency, child: Center( - child: Container( - width: 640, - height: 560, - color: Colors.white, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(10), - child: Stack( - children: [ - Align( - alignment: Alignment.topLeft, - child: Text( - titleText, - style: const TextStyle(fontSize: 16, color: Colors.black87), + child: Container( + width: 640, + height: 560, + color: Colors.white, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(10), + child: Stack( + children: [ + Align( + alignment: Alignment.topLeft, + child: Text( + titleText, + style: const TextStyle(fontSize: 16, color: Colors.black87), + ), ), - ), - Align( - alignment: Alignment.topRight, - child: InkWell( - child: const Icon(Icons.close), - onTap: () async => await _cancel(context), + Align( + alignment: Alignment.topRight, + child: InkWell( + child: const Icon(Icons.close), + onTap: () async => await _cancel(context), + ), ), - ), - ], + ], + ), ), - ), - Expanded( - flex: 1, - child: Container( - width: double.infinity, - padding: const EdgeInsets.all(10), - child: StatefulBuilder( - builder: (context, setState) { - _stateSetter = setState; - return DefaultTabController( - length: 2, - child: Column( - children: [ - Container( - constraints: const BoxConstraints.expand(height: 24), - child: TabBar( + Expanded( + flex: 1, + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(10), + child: StatefulBuilder( + builder: (context, setState) { + _stateSetter = setState; + return DefaultTabController( + length: 2, + child: Column( + children: [ + Container( + constraints: const BoxConstraints.expand(height: 24), + child: TabBar( onTap: (value) => Timer(const Duration(milliseconds: 300), () { - _sourceType = value == 0 ? rtc.SourceType.Screen : rtc.SourceType.Window; - unawaited(_getSources()); - }), + _sourceType = value == 0 ? rtc.SourceType.Screen : rtc.SourceType.Window; + unawaited(_getSources()); + }), tabs: [ Tab( - child: Text( - screenTabText, - style: const TextStyle(color: Colors.black54), - )), + child: Text( + screenTabText, + style: const TextStyle(color: Colors.black54), + ), + ), Tab( - child: Text( - windowTabText, - style: const TextStyle(color: Colors.black54), - )), - ]), - ), - const SizedBox( - height: 2, - ), - Expanded( - child: TabBarView(children: [ - Align( - alignment: Alignment.center, - child: GridView.count( - crossAxisSpacing: 8, - crossAxisCount: 2, - children: _sources.entries - .where((element) => element.value.type == rtc.SourceType.Screen) - .map((e) => ThumbnailWidget( + child: Text( + windowTabText, + style: const TextStyle(color: Colors.black54), + ), + ), + ], + ), + ), + const SizedBox( + height: 2, + ), + Expanded( + child: TabBarView( + children: [ + Align( + alignment: Alignment.center, + child: GridView.count( + crossAxisSpacing: 8, + crossAxisCount: 2, + children: _sources.entries + .where((element) => element.value.type == rtc.SourceType.Screen) + .map( + (e) => ThumbnailWidget( onTap: (source) { setState(() { _selectedSource = source; @@ -302,17 +321,20 @@ class ScreenSelectDialog extends Dialog { }, source: e.value, selected: _selectedSource?.id == e.value.id, - )) - .toList(), - )), - Align( - alignment: Alignment.center, - child: GridView.count( - crossAxisSpacing: 8, - crossAxisCount: 3, - children: _sources.entries - .where((element) => element.value.type == rtc.SourceType.Window) - .map((e) => ThumbnailWidget( + ), + ) + .toList(), + ), + ), + Align( + alignment: Alignment.center, + child: GridView.count( + crossAxisSpacing: 8, + crossAxisCount: 3, + children: _sources.entries + .where((element) => element.value.type == rtc.SourceType.Window) + .map( + (e) => ThumbnailWidget( onTap: (source) { setState(() { _selectedSource = source; @@ -320,46 +342,50 @@ class ScreenSelectDialog extends Dialog { }, source: e.value, selected: _selectedSource?.id == e.value.id, - )) - .toList(), - )), - ]), - ) - ], - ), - ); - }, + ), + ) + .toList(), + ), + ), + ], + ), + ), + ], + ), + ); + }, + ), ), ), - ), - SizedBox( - width: double.infinity, - child: OverflowBar( - children: [ - MaterialButton( - child: Text( - cancelText, - style: const TextStyle(color: Colors.black54), + SizedBox( + width: double.infinity, + child: OverflowBar( + children: [ + MaterialButton( + child: Text( + cancelText, + style: const TextStyle(color: Colors.black54), + ), + onPressed: () async { + await _cancel(context); + }, ), - onPressed: () async { - await _cancel(context); - }, - ), - MaterialButton( - color: Theme.of(context).primaryColor, - child: Text( - shareText, + MaterialButton( + color: Theme.of(context).primaryColor, + child: Text( + shareText, + ), + onPressed: () async { + await _ok(context); + }, ), - onPressed: () async { - await _ok(context); - }, - ), - ], + ], + ), ), - ), - ], + ], + ), ), - )), + ), ); } } diff --git a/lib/src/widgets/video_track_renderer.dart b/lib/src/widgets/video_track_renderer.dart index 50d2e7647..e1101436f 100644 --- a/lib/src/widgets/video_track_renderer.dart +++ b/lib/src/widgets/video_track_renderer.dart @@ -304,39 +304,40 @@ class _VideoTrackRendererState extends State { } Widget _videoViewForNative() => FutureBuilder( - future: _initializeRenderer(), - builder: (context, snapshot) { - if ((snapshot.hasData && _renderer != null) || _shouldUsePlatformView) { - return Builder( - key: _viewRegistration.key, - builder: (ctx) { - // let it render before notifying build - WidgetsBindingCompatible.instance?.addPostFrameCallback((timeStamp) { - widget.track.onVideoViewBuild?.call(); - }); - - if (!lkPlatformIsMobile() || widget.track is! LocalVideoTrack) { - return _videoRendererView(); - } - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - return GestureDetector( - onScaleStart: (details) {}, - onScaleUpdate: (details) { - if (details.scale != 1.0) { - setZoom(details.scale); - } - }, - onTapDown: (TapDownDetails details) => onViewFinderTap(details, constraints), - child: _videoRendererView(), - ); - }, - ); - }, - ); - } - return widget.placeholderBuilder?.call(context) ?? const SizedBox.shrink(); - }); + future: _initializeRenderer(), + builder: (context, snapshot) { + if ((snapshot.hasData && _renderer != null) || _shouldUsePlatformView) { + return Builder( + key: _viewRegistration.key, + builder: (ctx) { + // let it render before notifying build + WidgetsBindingCompatible.instance?.addPostFrameCallback((timeStamp) { + widget.track.onVideoViewBuild?.call(); + }); + + if (!lkPlatformIsMobile() || widget.track is! LocalVideoTrack) { + return _videoRendererView(); + } + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return GestureDetector( + onScaleStart: (details) {}, + onScaleUpdate: (details) { + if (details.scale != 1.0) { + setZoom(details.scale); + } + }, + onTapDown: (TapDownDetails details) => onViewFinderTap(details, constraints), + child: _videoRendererView(), + ); + }, + ); + }, + ); + } + return widget.placeholderBuilder?.call(context) ?? const SizedBox.shrink(); + }, + ); // FutureBuilder will cause flickering for flutter web. so using // different rendering methods for web and native. diff --git a/scripts/create_version.dart b/scripts/create_version.dart index 26e176d2b..e62f6d013 100755 --- a/scripts/create_version.dart +++ b/scripts/create_version.dart @@ -68,7 +68,8 @@ enum ChangeKind { security, deprecated, removed, - docs; + docs + ; static ChangeKind? fromString(String value) { return ChangeKind.values.where((e) => e.name == value).firstOrNull; @@ -78,7 +79,8 @@ enum ChangeKind { enum ChangeLevel implements Comparable { patch(0), minor(1), - major(2); + major(2) + ; final int priority; const ChangeLevel(this.priority); @@ -230,16 +232,16 @@ String generateChangelogEntry(SemanticVersion version, List changes) { buffer.writeln(); String prefixFor(ChangeKind kind) => switch (kind) { - ChangeKind.added => 'Added', - ChangeKind.changed => 'Changed', - ChangeKind.fixed => 'Fixed', - ChangeKind.refactor => 'Refactor', - ChangeKind.performance => 'Performance', - ChangeKind.security => 'Security', - ChangeKind.deprecated => 'Deprecated', - ChangeKind.removed => 'Removed', - ChangeKind.docs => 'Docs', - }; + ChangeKind.added => 'Added', + ChangeKind.changed => 'Changed', + ChangeKind.fixed => 'Fixed', + ChangeKind.refactor => 'Refactor', + ChangeKind.performance => 'Performance', + ChangeKind.security => 'Security', + ChangeKind.deprecated => 'Deprecated', + ChangeKind.removed => 'Removed', + ChangeKind.docs => 'Docs', + }; for (final kind in ChangeKind.values) { for (final change in changes.where((c) => c.kind == kind)) { diff --git a/test/audio/audio_session_test.dart b/test/audio/audio_session_test.dart index 3fb2ba019..d02b54d58 100644 --- a/test/audio/audio_session_test.dart +++ b/test/audio/audio_session_test.dart @@ -38,24 +38,22 @@ void main() { bool preferSpeakerOutput = true, bool forceSpeakerOutput = false, bool automatic = true, - }) => - ResolvedAudioSessionPolicy( - options: options, - preferSpeakerOutput: preferSpeakerOutput, - forceSpeakerOutput: forceSpeakerOutput && preferSpeakerOutput, - automatic: automatic, - ).appleConfiguration; + }) => ResolvedAudioSessionPolicy( + options: options, + preferSpeakerOutput: preferSpeakerOutput, + forceSpeakerOutput: forceSpeakerOutput && preferSpeakerOutput, + automatic: automatic, + ).appleConfiguration; AndroidAudioSessionConfiguration resolveAndroidPolicy( AudioSessionOptions options, { bool automatic = true, - }) => - ResolvedAudioSessionPolicy( - options: options, - preferSpeakerOutput: AudioManager.instance.isSpeakerOutputPreferred, - forceSpeakerOutput: AudioManager.instance.isSpeakerOutputForced, - automatic: automatic, - ).androidConfiguration; + }) => ResolvedAudioSessionPolicy( + options: options, + preferSpeakerOutput: AudioManager.instance.isSpeakerOutputPreferred, + forceSpeakerOutput: AudioManager.instance.isSpeakerOutputForced, + automatic: automatic, + ).androidConfiguration; group('AudioSessionManagementMode', () { test('supports automatic, manual, and external call system management', () { @@ -741,11 +739,13 @@ void main() { await expectLater( Native.startLocalRecording({'echoCancellation': true}), - throwsA(isA().having( - (error) => error.code, - 'code', - 'rejectedPlatformUnavailable', - )), + throwsA( + isA().having( + (error) => error.code, + 'code', + 'rejectedPlatformUnavailable', + ), + ), ); }); @@ -760,11 +760,13 @@ void main() { await expectLater( Native.startLocalRecording({'echoCancellation': true}), - throwsA(isA().having( - (error) => error.code, - 'code', - 'rejectedPlatformUnavailable', - )), + throwsA( + isA().having( + (error) => error.code, + 'code', + 'rejectedPlatformUnavailable', + ), + ), ); expect(calls.single.method, 'startLocalRecording'); }); diff --git a/test/connection_check/checker_test.dart b/test/connection_check/checker_test.dart index 45ee44f00..78c6849dc 100644 --- a/test/connection_check/checker_test.dart +++ b/test/connection_check/checker_test.dart @@ -25,9 +25,11 @@ void main() { group('Checker', () { test('reports success when perform completes without errors', () async { - final checker = FakeChecker(onPerform: (checker) async { - checker.addMessage('all good'); - }); + final checker = FakeChecker( + onPerform: (checker) async { + checker.addMessage('all good'); + }, + ); final info = await checker.run(); expect(info.status, CheckStatus.success); expect(info.name, 'FakeChecker'); @@ -39,9 +41,11 @@ void main() { }); test('reports failure when perform throws', () async { - final checker = FakeChecker(onPerform: (checker) async { - throw const CheckException('something went wrong'); - }); + final checker = FakeChecker( + onPerform: (checker) async { + throw const CheckException('something went wrong'); + }, + ); final info = await checker.run(); expect(info.status, CheckStatus.failed); expect(info.logs, hasLength(1)); @@ -51,10 +55,12 @@ void main() { }); test('reports failure when an error is appended', () async { - final checker = FakeChecker(onPerform: (checker) async { - checker.addError('bad'); - checker.addMessage('but continued'); - }); + final checker = FakeChecker( + onPerform: (checker) async { + checker.addError('bad'); + checker.addMessage('but continued'); + }, + ); final info = await checker.run(); expect(info.status, CheckStatus.failed); expect(info.logs, hasLength(2)); @@ -62,9 +68,11 @@ void main() { }); test('warnings do not fail the check', () async { - final checker = FakeChecker(onPerform: (checker) async { - checker.addWarning('be careful'); - }); + final checker = FakeChecker( + onPerform: (checker) async { + checker.addWarning('be careful'); + }, + ); final info = await checker.run(); expect(info.status, CheckStatus.success); expect(info.logs.first.level, CheckLogLevel.warning); @@ -87,19 +95,23 @@ void main() { }); test('skip marks the check as skipped', () async { - final checker = FakeChecker(onPerform: (checker) async { - checker.doSkip(); - }); + final checker = FakeChecker( + onPerform: (checker) async { + checker.doSkip(); + }, + ); final info = await checker.run(); expect(info.status, CheckStatus.skipped); await checker.dispose(); }); test('emits an update event for every log entry and status change', () async { - final checker = FakeChecker(onPerform: (checker) async { - checker.addMessage('one'); - checker.addMessage('two'); - }); + final checker = FakeChecker( + onPerform: (checker) async { + checker.addMessage('one'); + checker.addMessage('two'); + }, + ); final listener = checker.createListener(); final updates = []; listener.on((event) => updates.add(event.info)); diff --git a/test/connection_check/connection_check_test.dart b/test/connection_check/connection_check_test.dart index 76bfa7177..6169f6322 100644 --- a/test/connection_check/connection_check_test.dart +++ b/test/connection_check/connection_check_test.dart @@ -27,15 +27,23 @@ void main() { test('aggregates results of multiple checks', () async { final connectionCheck = ConnectionCheck('ws://www.example.com', 'token'); - final ok = await connectionCheck.runCheck(FakeChecker(onPerform: (checker) async { - checker.addMessage('fine'); - })); + final ok = await connectionCheck.runCheck( + FakeChecker( + onPerform: (checker) async { + checker.addMessage('fine'); + }, + ), + ); expect(ok.status, CheckStatus.success); expect(connectionCheck.isSuccess, true); - final failed = await connectionCheck.runCheck(FakeChecker(onPerform: (checker) async { - throw const CheckException('nope'); - })); + final failed = await connectionCheck.runCheck( + FakeChecker( + onPerform: (checker) async { + throw const CheckException('nope'); + }, + ), + ); expect(failed.status, CheckStatus.failed); expect(connectionCheck.isSuccess, false); @@ -71,9 +79,13 @@ void main() { test('skipped checks do not fail the run', () async { final connectionCheck = ConnectionCheck('ws://www.example.com', 'token'); - final skipped = await connectionCheck.runCheck(FakeChecker(onPerform: (checker) async { - checker.doSkip(); - })); + final skipped = await connectionCheck.runCheck( + FakeChecker( + onPerform: (checker) async { + checker.doSkip(); + }, + ), + ); expect(skipped.status, CheckStatus.skipped); expect(connectionCheck.isSuccess, true); await connectionCheck.dispose(); diff --git a/test/connection_check/webrtc_candidate_test.dart b/test/connection_check/webrtc_candidate_test.dart index c6c532283..ddde75da6 100644 --- a/test/connection_check/webrtc_candidate_test.dart +++ b/test/connection_check/webrtc_candidate_test.dart @@ -30,7 +30,8 @@ void main() { test('parses a passive tcp candidate with extensions', () { final candidate = parseIceCandidate( - 'candidate:1467250027 1 tcp 1518280447 198.51.100.1 443 typ host tcptype passive generation 0'); + 'candidate:1467250027 1 tcp 1518280447 198.51.100.1 443 typ host tcptype passive generation 0', + ); expect(candidate, isNotNull); expect(candidate!.protocol, 'tcp'); expect(candidate.address, '198.51.100.1'); @@ -41,7 +42,8 @@ void main() { test('parses a relay candidate with raddr/rport', () { final candidate = parseIceCandidate( - 'candidate:3098175849 1 udp 25108223 192.0.2.10 60690 typ relay raddr 203.0.113.5 rport 40183'); + 'candidate:3098175849 1 udp 25108223 192.0.2.10 60690 typ relay raddr 203.0.113.5 rport 40183', + ); expect(candidate, isNotNull); expect(candidate!.type, 'relay'); expect(candidate.address, '192.0.2.10'); diff --git a/test/core/data_stream_test.dart b/test/core/data_stream_test.dart index 0e760b863..1b3e94465 100644 --- a/test/core/data_stream_test.dart +++ b/test/core/data_stream_test.dart @@ -65,10 +65,12 @@ void main() { print('received chat message from $participantIdentity: $text'); expect('some text !!!', text); }); - final info = await room.localParticipant?.sendText('some text !!!', - options: SendTextOptions( - topic: 'chat', - )); + final info = await room.localParticipant?.sendText( + 'some text !!!', + options: SendTextOptions( + topic: 'chat', + ), + ); expect(info, isNotNull); }); @@ -81,15 +83,17 @@ void main() { expect(longText, text); }); - final info = await room.localParticipant?.sendText(longText, - options: SendTextOptions( - topic: 'chat-long-text', - onProgress: (progress) { - print('progress: $progress'); - expect(progress, greaterThanOrEqualTo(0.0)); - expect(progress, lessThanOrEqualTo(1.0)); - }, - )); + final info = await room.localParticipant?.sendText( + longText, + options: SendTextOptions( + topic: 'chat-long-text', + onProgress: (progress) { + print('progress: $progress'); + expect(progress, greaterThanOrEqualTo(0.0)); + expect(progress, lessThanOrEqualTo(1.0)); + }, + ), + ); expect(info, isNotNull); }); @@ -97,13 +101,16 @@ void main() { room.registerTextStreamHandler('chat-stream', (TextStreamReader reader, String participantIdentity) async { reader.listen((chunk) { print( - 'received chunk: ${chunk.content.length}, total: ${reader.info?.size}, progress: ${utf8.decode(chunk.content)}'); + 'received chunk: ${chunk.content.length}, total: ${reader.info?.size}, progress: ${utf8.decode(chunk.content)}', + ); }); }); - final stream = await room.localParticipant?.streamText(StreamTextOptions( - topic: 'chat-stream', - )); + final stream = await room.localParticipant?.streamText( + StreamTextOptions( + topic: 'chat-stream', + ), + ); await stream?.write('a' * 10); await stream?.write('b' * 10); await stream?.write('c' * 10); @@ -117,7 +124,7 @@ void main() { 'testfiles/testfile.bin', 'testfiles/testfile2.bin', 'testfiles/testfile3.bin', - 'testfiles/testfile4.bin' + 'testfiles/testfile4.bin', ]; /// create random files @@ -128,15 +135,19 @@ void main() { randomFile.writeAsBytesSync(bytes); } - room.registerTextStreamHandler('chat-stream-with-files', - (TextStreamReader reader, String participantIdentity) async { + room.registerTextStreamHandler('chat-stream-with-files', ( + TextStreamReader reader, + String participantIdentity, + ) async { final receivedText = await reader.readAll(); print('received chat message from $participantIdentity: long text length: ${receivedText.length}'); expect(longText, receivedText); }); - room.registerByteStreamHandler('chat-stream-with-files', - (ByteStreamReader reader, String participantIdentity) async { + room.registerByteStreamHandler('chat-stream-with-files', ( + ByteStreamReader reader, + String participantIdentity, + ) async { final file = await reader.readAll(); final fileName = 'testfiles/copy-${reader.info!.name}'; print('received file from $participantIdentity: ${fileName}'); @@ -147,16 +158,18 @@ void main() { final attachmentsFiles = files.map((e) => File(e)).toList(); - final info = await room.localParticipant?.sendText(longText, - options: SendTextOptions( - topic: 'chat-stream-with-files', - attachments: attachmentsFiles, - onProgress: (progress) { - print('file from chat-stream-with-files: progress: $progress'); - expect(progress, greaterThanOrEqualTo(0.0)); - expect(progress, lessThanOrEqualTo(1.0)); - }, - )); + final info = await room.localParticipant?.sendText( + longText, + options: SendTextOptions( + topic: 'chat-stream-with-files', + attachments: attachmentsFiles, + onProgress: (progress) { + print('file from chat-stream-with-files: progress: $progress'); + expect(progress, greaterThanOrEqualTo(0.0)); + expect(progress, lessThanOrEqualTo(1.0)); + }, + ), + ); expect(info, isNotNull); }); @@ -176,17 +189,21 @@ void main() { print('received ${operationType} message: ${text}'); }); - final info = await room.localParticipant?.sendText('Test ${operationType}', - options: SendTextOptions( - topic: 'chat-operations', - )); + final info = await room.localParticipant?.sendText( + 'Test ${operationType}', + options: SendTextOptions( + topic: 'chat-operations', + ), + ); // Test with streamText and different operation types - final stream = await room.localParticipant?.streamText(StreamTextOptions( - topic: 'chat-operations', - type: operationType, - version: operationType == TextStreamOperationType.update ? 2 : null, - )); + final stream = await room.localParticipant?.streamText( + StreamTextOptions( + topic: 'chat-operations', + type: operationType, + version: operationType == TextStreamOperationType.update ? 2 : null, + ), + ); await stream?.write('Streamed ${operationType}'); await stream?.close(); @@ -212,11 +229,13 @@ void main() { expect(reader.info!.attributes['priority'], 'high'); }); - final info = await room.localParticipant?.sendText('Test message with metadata', - options: SendTextOptions( - topic: 'chat-metadata', - attributes: testAttributes, - )); + final info = await room.localParticipant?.sendText( + 'Test message with metadata', + options: SendTextOptions( + topic: 'chat-metadata', + attributes: testAttributes, + ), + ); expect(info, isNotNull); }); @@ -236,13 +255,15 @@ void main() { }); // Send a reply to an existing stream - final stream = await room.localParticipant?.streamText(StreamTextOptions( - topic: 'chat-replies', - type: TextStreamOperationType.create, - streamId: replyStreamId, - replyToStreamId: originalStreamId, - version: 1, - )); + final stream = await room.localParticipant?.streamText( + StreamTextOptions( + topic: 'chat-replies', + type: TextStreamOperationType.create, + streamId: replyStreamId, + replyToStreamId: originalStreamId, + version: 1, + ), + ); await stream?.write('This is a reply to the original message'); await stream?.close(); }); @@ -255,11 +276,13 @@ void main() { }); // Test AI-generated message - final stream = await room.localParticipant?.streamText(StreamTextOptions( - topic: 'chat-ai-generated', - generated: true, - attributes: {'aiModel': 'gpt-4', 'confidence': '0.95'}, - )); + final stream = await room.localParticipant?.streamText( + StreamTextOptions( + topic: 'chat-ai-generated', + generated: true, + attributes: {'aiModel': 'gpt-4', 'confidence': '0.95'}, + ), + ); await stream?.write('This message was generated by AI'); await stream?.close(); }); @@ -267,18 +290,22 @@ void main() { test('Text Stream With File Attachments', () async { const attachedIds = ['file-123', 'file-456', 'file-789']; final msg = 'Message with file attachments'; - room.registerTextStreamHandler('chat-with-attachments', - (TextStreamReader reader, String participantIdentity) async { + room.registerTextStreamHandler('chat-with-attachments', ( + TextStreamReader reader, + String participantIdentity, + ) async { final text = await reader.readAll(); print('received message with attachments: ${text}'); expect(text, 'Message with file attachments'); }); - final stream = await room.localParticipant?.streamText(StreamTextOptions( - topic: 'chat-with-attachments', - attachedStreamIds: attachedIds, - totalSize: msg.length, // 'Message with file attachments'.length - )); + final stream = await room.localParticipant?.streamText( + StreamTextOptions( + topic: 'chat-with-attachments', + attachedStreamIds: attachedIds, + totalSize: msg.length, // 'Message with file attachments'.length + ), + ); await stream?.write(msg); await stream?.close(); }); @@ -308,15 +335,17 @@ void main() { }); final fileToSend = File(filePath); - final info = await room.localParticipant?.sendFile(fileToSend, - options: SendFileOptions( - topic: 'file', - onProgress: (progress) { - print('progress: ${progress * 100} %'); - expect(progress, greaterThanOrEqualTo(0.0)); - expect(progress, lessThanOrEqualTo(1.0)); - }, - )); + final info = await room.localParticipant?.sendFile( + fileToSend, + options: SendFileOptions( + topic: 'file', + onProgress: (progress) { + print('progress: ${progress * 100} %'); + expect(progress, greaterThanOrEqualTo(0.0)); + expect(progress, lessThanOrEqualTo(1.0)); + }, + ), + ); expect(info, isNotNull); }); @@ -327,10 +356,12 @@ void main() { print('bytes content = ${content}, \n string content = ${utf8.decode(content)}'); }); - final stream = await room.localParticipant?.streamBytes(StreamBytesOptions( - topic: 'bytes-stream', - totalSize: 30, - )); + final stream = await room.localParticipant?.streamBytes( + StreamBytesOptions( + topic: 'bytes-stream', + totalSize: 30, + ), + ); await stream?.write(utf8.encode('a' * 10)); await stream?.write(utf8.encode('b' * 10)); await stream?.write(utf8.encode('c' * 10)); @@ -342,8 +373,10 @@ void main() { const testMimeType = 'application/pdf'; const testFileName = 'test-document.pdf'; - room.registerByteStreamHandler('files-with-metadata', - (ByteStreamReader reader, String participantIdentity) async { + room.registerByteStreamHandler('files-with-metadata', ( + ByteStreamReader reader, + String participantIdentity, + ) async { final chunks = await reader.readAll(); final content = chunks.expand((element) => element).toList(); print('received file: ${reader.info?.name}, size: ${content.length}'); @@ -366,13 +399,15 @@ void main() { expect(content, expectedContent); }); - final stream = await room.localParticipant?.streamBytes(StreamBytesOptions( - topic: 'files-with-metadata', - name: testFileName, - mimeType: testMimeType, - attributes: testAttributes, - totalSize: 100, - )); + final stream = await room.localParticipant?.streamBytes( + StreamBytesOptions( + topic: 'files-with-metadata', + name: testFileName, + mimeType: testMimeType, + attributes: testAttributes, + totalSize: 100, + ), + ); // Simulate PDF content final pdfContent = List.generate(100, (index) => index % 256); @@ -396,11 +431,13 @@ void main() { final futures = []; for (int i = 0; i < expectedCount; i++) { futures.add(() async { - final stream = await room.localParticipant?.streamText(StreamTextOptions( - topic: 'concurrent-streams', - streamId: 'stream-${i}', - type: TextStreamOperationType.create, - )); + final stream = await room.localParticipant?.streamText( + StreamTextOptions( + topic: 'concurrent-streams', + streamId: 'stream-${i}', + type: TextStreamOperationType.create, + ), + ); await stream?.write('Concurrent message ${i}'); await stream?.close(); }()); @@ -424,10 +461,12 @@ void main() { expect(text, largeData); }); - final stream = await room.localParticipant?.streamText(StreamTextOptions( - topic: 'large-chunks', - totalSize: chunkSize, - )); + final stream = await room.localParticipant?.streamText( + StreamTextOptions( + topic: 'large-chunks', + totalSize: chunkSize, + ), + ); await stream?.write(largeData); await stream?.close(); }); @@ -463,20 +502,22 @@ void main() { }); final msg = 'Header validation test message'; // Send a message with comprehensive options - final stream = await room.localParticipant?.streamText(StreamTextOptions( - topic: 'header-validation', - type: TextStreamOperationType.create, - version: 1, - generated: false, - attributes: { - 'test': 'header-validation', - 'complex': 'data-transmission', - 'number': '123', - }, - attachedStreamIds: ['attachment-1', 'attachment-2'], - replyToStreamId: 'parent-message-123', - totalSize: msg.length, // Length of test message - )); + final stream = await room.localParticipant?.streamText( + StreamTextOptions( + topic: 'header-validation', + type: TextStreamOperationType.create, + version: 1, + generated: false, + attributes: { + 'test': 'header-validation', + 'complex': 'data-transmission', + 'number': '123', + }, + attachedStreamIds: ['attachment-1', 'attachment-2'], + replyToStreamId: 'parent-message-123', + totalSize: msg.length, // Length of test message + ), + ); await stream?.write(msg); await stream?.close(); diff --git a/test/core/disconnect_event_test.dart b/test/core/disconnect_event_test.dart index a89bf3561..3864b2f43 100644 --- a/test/core/disconnect_event_test.dart +++ b/test/core/disconnect_event_test.dart @@ -40,8 +40,10 @@ void main() { }); test('emits exactly one disconnected event when pinning fails on initial connect', () async { - container.wsConnector.connectError = - CertificatePinningException('Certificate pin mismatch', host: 'www.example.com'); + container.wsConnector.connectError = CertificatePinningException( + 'Certificate pin mismatch', + host: 'www.example.com', + ); final disconnectedEvents = []; container.room.events.listen((event) { @@ -99,8 +101,10 @@ void main() { } }); - container.wsConnector.connectError = - CertificatePinningException('Certificate pin mismatch', host: 'www.example.com'); + container.wsConnector.connectError = CertificatePinningException( + 'Certificate pin mismatch', + host: 'www.example.com', + ); container.engine.fullReconnectOnNext = true; await container.engine.attemptReconnect(ClientDisconnectReason.reconnectRetry); diff --git a/test/core/room_e2e_test.dart b/test/core/room_e2e_test.dart index 20bfba04a..36a844b08 100644 --- a/test/core/room_e2e_test.dart +++ b/test/core/room_e2e_test.dart @@ -104,10 +104,16 @@ void main() { await cancel(); // Verify participant had tracks when connected event was emitted - expect(participantHadTracksOnConnect, isTrue, - reason: 'Participant should have tracks when ParticipantConnectedEvent is emitted'); - expect(trackCountOnConnect, greaterThan(0), - reason: 'Participant should have at least one track when connected event fires'); + expect( + participantHadTracksOnConnect, + isTrue, + reason: 'Participant should have tracks when ParticipantConnectedEvent is emitted', + ); + expect( + trackCountOnConnect, + greaterThan(0), + reason: 'Participant should have at least one track when connected event fires', + ); // Verify the participant is in the room expect(room.remoteParticipants.length, 1); @@ -138,8 +144,9 @@ void main() { expect( room.events.streamCtrl.stream, emits( - predicate((event) => - event.participant.metadata == participantMetadataChangedResponse.update.participants[0].metadata), + predicate( + (event) => event.participant.metadata == participantMetadataChangedResponse.update.participants[0].metadata, + ), ), ); }); @@ -147,8 +154,11 @@ void main() { test('room metadata update', () async { expect( room.events.streamCtrl.stream, - emits(predicate((event) => - event.metadata == roomUpdateResponse.roomUpdate.room.metadata && room.metadata == event.metadata)), + emits( + predicate( + (event) => event.metadata == roomUpdateResponse.roomUpdate.room.metadata && room.metadata == event.metadata, + ), + ), ); ws.onData(roomUpdateResponse.writeToBuffer()); }); @@ -156,9 +166,13 @@ void main() { test('connection quality', () async { expect( room.events.streamCtrl.stream, - emits(predicate((event) => - event.participant.sid == localParticipantData.sid && - event.connectionQuality == ConnectionQuality.excellent)), + emits( + predicate( + (event) => + event.participant.sid == localParticipantData.sid && + event.connectionQuality == ConnectionQuality.excellent, + ), + ), ); ws.onData(connectionQualityResponse.writeToBuffer()); }); @@ -177,8 +191,10 @@ void main() { }); test('leave', () async { - expect(room.events.streamCtrl.stream, - emits(predicate((event) => event.reason == DisconnectReason.unknown))); + expect( + room.events.streamCtrl.stream, + emits(predicate((event) => event.reason == DisconnectReason.unknown)), + ); ws.onData(leaveResponse.writeToBuffer()); }); @@ -195,11 +211,13 @@ void main() { }); // Emit onTrack before participant update arrives. - container.engine.events.emit(EngineTrackAddedEvent( - track: fakeTrack, - stream: fakeStream, - receiver: null, - )); + container.engine.events.emit( + EngineTrackAddedEvent( + track: fakeTrack, + stream: fakeStream, + receiver: null, + ), + ); // Now deliver participant metadata. ws.onData(participantJoinResponse.writeToBuffer()); diff --git a/test/core/rpc_test.dart b/test/core/rpc_test.dart index db901de43..fe30eda0b 100644 --- a/test/core/rpc_test.dart +++ b/test/core/rpc_test.dart @@ -39,12 +39,14 @@ void main() { expect(room.rpcHandlers.keys.first, 'echo'); - final response = await room.rpcHandlers['echo']!(RpcInvocationData( - requestId: '1', - callerIdentity: room.localParticipant!.identity, - payload: 'hello', - responseTimeoutMs: 10000, - )); + final response = await room.rpcHandlers['echo']!( + RpcInvocationData( + requestId: '1', + callerIdentity: room.localParticipant!.identity, + payload: 'hello', + responseTimeoutMs: 10000, + ), + ); expect(response, 'echo: => ${room.localParticipant!.identity} hello'); @@ -59,11 +61,13 @@ void main() { }); /// test performRpc - final response = await room.localParticipant?.performRpc(PerformRpcParams( - destinationIdentity: room.localParticipant!.identity, - method: 'echo', - payload: 'hello', - )); + final response = await room.localParticipant?.performRpc( + PerformRpcParams( + destinationIdentity: room.localParticipant!.identity, + method: 'echo', + payload: 'hello', + ), + ); expect(response, 'echo: => ${room.localParticipant!.identity} hello'); @@ -73,11 +77,13 @@ void main() { try { room.engine.serverInfo?.version = '1.7.9'; - await room.localParticipant?.performRpc(PerformRpcParams( - destinationIdentity: room.localParticipant!.identity, - method: 'echo', - payload: 'hello', - )); + await room.localParticipant?.performRpc( + PerformRpcParams( + destinationIdentity: room.localParticipant!.identity, + method: 'echo', + payload: 'hello', + ), + ); } catch (e) { if (e is RpcError) { error = e; @@ -104,11 +110,13 @@ void main() { }); RpcError? error; try { - await room.localParticipant?.performRpc(PerformRpcParams( - destinationIdentity: room.localParticipant!.identity, - method: 'echo', - payload: 'hello', - )); + await room.localParticipant?.performRpc( + PerformRpcParams( + destinationIdentity: room.localParticipant!.identity, + method: 'echo', + payload: 'hello', + ), + ); } catch (e) { if (e is RpcError) { error = e; @@ -126,11 +134,13 @@ void main() { RpcError? error; try { - await room.localParticipant?.performRpc(PerformRpcParams( - destinationIdentity: room.localParticipant!.identity, - method: 'no_method', - payload: 'hello', - )); + await room.localParticipant?.performRpc( + PerformRpcParams( + destinationIdentity: room.localParticipant!.identity, + method: 'no_method', + payload: 'hello', + ), + ); } catch (e) { if (e is RpcError) { error = e; @@ -146,11 +156,13 @@ void main() { test('test request playload too large', () async { RpcError? error; try { - await room.localParticipant?.performRpc(PerformRpcParams( - destinationIdentity: room.localParticipant!.identity, - method: 'echo', - payload: 'a' * 1024 * 1024, - )); + await room.localParticipant?.performRpc( + PerformRpcParams( + destinationIdentity: room.localParticipant!.identity, + method: 'echo', + payload: 'a' * 1024 * 1024, + ), + ); } catch (e) { if (e is RpcError) { error = e; @@ -169,11 +181,13 @@ void main() { }); RpcError? error; try { - await room.localParticipant?.performRpc(PerformRpcParams( - destinationIdentity: room.localParticipant!.identity, - method: 'echo', - payload: 'hello', - )); + await room.localParticipant?.performRpc( + PerformRpcParams( + destinationIdentity: room.localParticipant!.identity, + method: 'echo', + payload: 'hello', + ), + ); } catch (e) { if (e is RpcError) { error = e; @@ -192,11 +206,13 @@ void main() { }); try { - await room.localParticipant?.performRpc(PerformRpcParams( - destinationIdentity: room.localParticipant!.identity, - method: 'echo', - payload: 'hello', - )); + await room.localParticipant?.performRpc( + PerformRpcParams( + destinationIdentity: room.localParticipant!.identity, + method: 'echo', + payload: 'hello', + ), + ); } catch (e) { if (e is RpcError) { error = e; @@ -217,12 +233,14 @@ void main() { }); RpcError? error; try { - await room.localParticipant?.performRpc(PerformRpcParams( - destinationIdentity: room.localParticipant!.identity, - method: 'echo', - payload: 'hello', - responseTimeoutMs: Duration(seconds: 2), - )); + await room.localParticipant?.performRpc( + PerformRpcParams( + destinationIdentity: room.localParticipant!.identity, + method: 'echo', + payload: 'hello', + responseTimeoutMs: Duration(seconds: 2), + ), + ); } catch (e) { if (e is RpcError) { error = e; @@ -283,11 +301,13 @@ void main() { return data.payload; }); - final response = await v2Room.localParticipant!.performRpc(PerformRpcParams( - destinationIdentity: v2Room.localParticipant!.identity, - method: 'reserved-topic-echo', - payload: 'ok', - )); + final response = await v2Room.localParticipant!.performRpc( + PerformRpcParams( + destinationIdentity: v2Room.localParticipant!.identity, + method: 'reserved-topic-echo', + payload: 'ok', + ), + ); expect(response, 'ok'); v2Room.unregisterRpcMethod('reserved-topic-echo'); @@ -299,11 +319,13 @@ void main() { return 'echo: ${data.payload}'; }); - final response = await v2Room.localParticipant!.performRpc(PerformRpcParams( - destinationIdentity: v2Room.localParticipant!.identity, - method: 'echo-v2', - payload: 'hello v2', - )); + final response = await v2Room.localParticipant!.performRpc( + PerformRpcParams( + destinationIdentity: v2Room.localParticipant!.identity, + method: 'echo-v2', + payload: 'hello v2', + ), + ); expect(response, 'echo: hello v2'); // Spec: v2 requests must use data streams, never the rpcRequest packet. @@ -320,13 +342,15 @@ void main() { ); // The request and response both flow as text streams on the reserved topics. expect( - hasOutboundPacketWhere((p) => - p.whichValue() == lk_models.DataPacket_Value.streamHeader && p.streamHeader.topic == kRpcRequestTopic), + hasOutboundPacketWhere( + (p) => p.whichValue() == lk_models.DataPacket_Value.streamHeader && p.streamHeader.topic == kRpcRequestTopic, + ), isTrue, ); expect( - hasOutboundPacketWhere((p) => - p.whichValue() == lk_models.DataPacket_Value.streamHeader && p.streamHeader.topic == kRpcResponseTopic), + hasOutboundPacketWhere( + (p) => p.whichValue() == lk_models.DataPacket_Value.streamHeader && p.streamHeader.topic == kRpcResponseTopic, + ), isTrue, ); @@ -343,12 +367,14 @@ void main() { return data.payload; // echo a 20k response }); - final response = await v2Room.localParticipant!.performRpc(PerformRpcParams( - destinationIdentity: v2Room.localParticipant!.identity, - method: 'echo-big', - payload: big, - responseTimeoutMs: const Duration(seconds: 30), - )); + final response = await v2Room.localParticipant!.performRpc( + PerformRpcParams( + destinationIdentity: v2Room.localParticipant!.identity, + method: 'echo-big', + payload: big, + responseTimeoutMs: const Duration(seconds: 30), + ), + ); expect(response.length, 20000); expect(response, big); @@ -360,11 +386,13 @@ void main() { v2Container.capturedDataPackets.clear(); RpcError? error; try { - await v2Room.localParticipant!.performRpc(PerformRpcParams( - destinationIdentity: v2Room.localParticipant!.identity, - method: 'method-does-not-exist', - payload: 'x', - )); + await v2Room.localParticipant!.performRpc( + PerformRpcParams( + destinationIdentity: v2Room.localParticipant!.identity, + method: 'method-does-not-exist', + payload: 'x', + ), + ); } catch (e) { if (e is RpcError) { error = e; @@ -381,16 +409,19 @@ void main() { ); // Error responses are always packets, never streams. expect( - hasOutboundPacketWhere((p) => - p.whichValue() == lk_models.DataPacket_Value.rpcResponse && - p.rpcResponse.hasError() && - p.rpcResponse.error.code == RpcError.unsupportedMethod), + hasOutboundPacketWhere( + (p) => + p.whichValue() == lk_models.DataPacket_Value.rpcResponse && + p.rpcResponse.hasError() && + p.rpcResponse.error.code == RpcError.unsupportedMethod, + ), isTrue, ); // No v2 response stream should be opened on the response topic. expect( - hasOutboundPacketWhere((p) => - p.whichValue() == lk_models.DataPacket_Value.streamHeader && p.streamHeader.topic == kRpcResponseTopic), + hasOutboundPacketWhere( + (p) => p.whichValue() == lk_models.DataPacket_Value.streamHeader && p.streamHeader.topic == kRpcResponseTopic, + ), isFalse, ); }); @@ -403,11 +434,13 @@ void main() { RpcError? error; try { - await v2Room.localParticipant!.performRpc(PerformRpcParams( - destinationIdentity: v2Room.localParticipant!.identity, - method: 'throws-generic', - payload: 'x', - )); + await v2Room.localParticipant!.performRpc( + PerformRpcParams( + destinationIdentity: v2Room.localParticipant!.identity, + method: 'throws-generic', + payload: 'x', + ), + ); } catch (e) { if (e is RpcError) { error = e; @@ -417,15 +450,18 @@ void main() { expect(error?.code, RpcError.applicationError); // Error responses always travel as packets, even between v2 peers. expect( - hasOutboundPacketWhere((p) => - p.whichValue() == lk_models.DataPacket_Value.rpcResponse && - p.rpcResponse.hasError() && - p.rpcResponse.error.code == RpcError.applicationError), + hasOutboundPacketWhere( + (p) => + p.whichValue() == lk_models.DataPacket_Value.rpcResponse && + p.rpcResponse.hasError() && + p.rpcResponse.error.code == RpcError.applicationError, + ), isTrue, ); expect( - hasOutboundPacketWhere((p) => - p.whichValue() == lk_models.DataPacket_Value.streamHeader && p.streamHeader.topic == kRpcResponseTopic), + hasOutboundPacketWhere( + (p) => p.whichValue() == lk_models.DataPacket_Value.streamHeader && p.streamHeader.topic == kRpcResponseTopic, + ), isFalse, reason: 'error responses must not use a data stream', ); @@ -441,11 +477,13 @@ void main() { RpcError? error; try { - await v2Room.localParticipant!.performRpc(PerformRpcParams( - destinationIdentity: v2Room.localParticipant!.identity, - method: 'throws-rpc-error', - payload: 'x', - )); + await v2Room.localParticipant!.performRpc( + PerformRpcParams( + destinationIdentity: v2Room.localParticipant!.identity, + method: 'throws-rpc-error', + payload: 'x', + ), + ); } catch (e) { if (e is RpcError) { error = e; @@ -468,12 +506,14 @@ void main() { RpcError? error; try { - await v2Room.localParticipant!.performRpc(PerformRpcParams( - destinationIdentity: v2Room.localParticipant!.identity, - method: 'hangs', - payload: 'x', - responseTimeoutMs: const Duration(seconds: 2), - )); + await v2Room.localParticipant!.performRpc( + PerformRpcParams( + destinationIdentity: v2Room.localParticipant!.identity, + method: 'hangs', + payload: 'x', + responseTimeoutMs: const Duration(seconds: 2), + ), + ); } catch (e) { if (e is RpcError) { error = e; @@ -492,18 +532,21 @@ void main() { final futures = List.generate( 5, - (i) => v2Room.localParticipant!.performRpc(PerformRpcParams( - destinationIdentity: v2Room.localParticipant!.identity, - method: 'echo-concurrent', - payload: '$i', - )), + (i) => v2Room.localParticipant!.performRpc( + PerformRpcParams( + destinationIdentity: v2Room.localParticipant!.identity, + method: 'echo-concurrent', + payload: '$i', + ), + ), ); final results = await Future.wait(futures); expect(results, ['r-0', 'r-1', 'r-2', 'r-3', 'r-4']); // Five distinct outbound request streams. final requestHeaders = v2Container.capturedDataPackets.where( - (p) => p.whichValue() == lk_models.DataPacket_Value.streamHeader && p.streamHeader.topic == kRpcRequestTopic); + (p) => p.whichValue() == lk_models.DataPacket_Value.streamHeader && p.streamHeader.topic == kRpcRequestTopic, + ); expect(requestHeaders.length, greaterThanOrEqualTo(5)); v2Room.unregisterRpcMethod('echo-concurrent'); @@ -525,18 +568,20 @@ void main() { // isn't reported as an unhandled async error if it lands before `await`. RpcError? error; final caught = v2Room.localParticipant! - .performRpc(PerformRpcParams( - destinationIdentity: 'alice', - method: 'hangs-for-alice', - payload: 'x', - responseTimeoutMs: const Duration(seconds: 30), - )) + .performRpc( + PerformRpcParams( + destinationIdentity: 'alice', + method: 'hangs-for-alice', + payload: 'x', + responseTimeoutMs: const Duration(seconds: 30), + ), + ) .catchError((e) { - if (e is RpcError) { - error = e; - } - return ''; - }); + if (e is RpcError) { + error = e; + } + return ''; + }); // Let the publish settle so pending is registered. await Future.delayed(const Duration(milliseconds: 50)); @@ -564,17 +609,19 @@ void main() { bool resolved = false; RpcError? caught; final future = v2Room.localParticipant! - .performRpc(PerformRpcParams( - destinationIdentity: 'bob', - method: 'hangs-for-bob', - payload: 'x', - responseTimeoutMs: const Duration(seconds: 30), - )) + .performRpc( + PerformRpcParams( + destinationIdentity: 'bob', + method: 'hangs-for-bob', + payload: 'x', + responseTimeoutMs: const Duration(seconds: 30), + ), + ) .then((_) => resolved = true) .catchError((e) { - if (e is RpcError) caught = e; - return false; - }); + if (e is RpcError) caught = e; + return false; + }); // Let the publish settle and the request stream loop back. await Future.delayed(const Duration(milliseconds: 100)); @@ -670,11 +717,13 @@ void main() { return false; }); - final response = await edgeCaseRoom.localParticipant!.performRpc(PerformRpcParams( - destinationIdentity: 'legacy-v1', - method: 'legacy-method', - payload: 'hello', - )); + final response = await edgeCaseRoom.localParticipant!.performRpc( + PerformRpcParams( + destinationIdentity: 'legacy-v1', + method: 'legacy-method', + payload: 'hello', + ), + ); expect(response, 'legacy-response'); expect(requestId, isNotNull); @@ -702,11 +751,13 @@ void main() { RpcError? error; try { - await edgeCaseRoom.localParticipant!.performRpc(PerformRpcParams( - destinationIdentity: 'legacy-large', - method: 'large', - payload: 'a' * (kRpcMaxPayloadBytes + 1), - )); + await edgeCaseRoom.localParticipant!.performRpc( + PerformRpcParams( + destinationIdentity: 'legacy-large', + method: 'large', + payload: 'a' * (kRpcMaxPayloadBytes + 1), + ), + ); } catch (e) { if (e is RpcError) { error = e; @@ -742,11 +793,13 @@ void main() { return false; }); - final response = await edgeCaseRoom.localParticipant!.performRpc(PerformRpcParams( - destinationIdentity: 'fast-v2', - method: 'fast-method', - payload: 'request', - )); + final response = await edgeCaseRoom.localParticipant!.performRpc( + PerformRpcParams( + destinationIdentity: 'fast-v2', + method: 'fast-method', + payload: 'request', + ), + ); expect(response, 'fast-response'); expect(requestId, isNotNull); @@ -775,19 +828,21 @@ void main() { bool resolved = false; RpcError? caught; final future = edgeCaseRoom.localParticipant! - .performRpc(PerformRpcParams( - destinationIdentity: 'missing-id', - method: 'hangs', - payload: 'x', - responseTimeoutMs: const Duration(seconds: 30), - )) + .performRpc( + PerformRpcParams( + destinationIdentity: 'missing-id', + method: 'hangs', + payload: 'x', + responseTimeoutMs: const Duration(seconds: 30), + ), + ) .then((_) => resolved = true) .catchError((e) { - if (e is RpcError) { - caught = e; - } - return false; - }); + if (e is RpcError) { + caught = e; + } + return false; + }); await Future.delayed(const Duration(milliseconds: 100)); edgeCaseContainer.simulateInboundV2RpcResponseStreamWithoutRequestId('missing-id', 'ignored'); diff --git a/test/core/signal_client_test.dart b/test/core/signal_client_test.dart index 49b004878..3e59193f0 100644 --- a/test/core/signal_client_test.dart +++ b/test/core/signal_client_test.dart @@ -40,11 +40,12 @@ void main() { group('connection', () { test('connect', () async { expect( - client.events.streamCtrl.stream, - emitsInOrder([ - predicate((event) => true), - predicate((event) => true), - ])); + client.events.streamCtrl.stream, + emitsInOrder([ + predicate((event) => true), + predicate((event) => true), + ]), + ); await client.connect( exampleUri, token, @@ -55,11 +56,12 @@ void main() { test('reconnect', () async { expect( - client.events.streamCtrl.stream, - emitsInOrder([ - predicate((event) => true), - predicate((event) => true), - ])); + client.events.streamCtrl.stream, + emitsInOrder([ + predicate((event) => true), + predicate((event) => true), + ]), + ); await client.connect( exampleUri, token, @@ -100,10 +102,11 @@ final lk_rtc.SignalResponse joinResponse = lk_rtc.SignalResponse( ); final lk_rtc.SignalResponse offerResponse = lk_rtc.SignalResponse( - offer: lk_rtc.SessionDescription( - sdp: 'remote_offer', - type: 'offer', -)); + offer: lk_rtc.SessionDescription( + sdp: 'remote_offer', + type: 'offer', + ), +); final lk_rtc.SignalResponse participantJoinResponse = lk_rtc.SignalResponse( update: lk_rtc.ParticipantUpdate( @@ -135,12 +138,14 @@ final lk_rtc.SignalResponse roomUpdateResponse = lk_rtc.SignalResponse( ); final lk_rtc.SignalResponse connectionQualityResponse = lk_rtc.SignalResponse( - connectionQuality: lk_rtc.ConnectionQualityUpdate(updates: [ - lk_rtc.ConnectionQualityInfo( - participantSid: localParticipantData.sid, - quality: lk_models.ConnectionQuality.EXCELLENT, - ) - ]), + connectionQuality: lk_rtc.ConnectionQualityUpdate( + updates: [ + lk_rtc.ConnectionQualityInfo( + participantSid: localParticipantData.sid, + quality: lk_models.ConnectionQuality.EXCELLENT, + ), + ], + ), ); final lk_rtc.SignalResponse activeSpeakerResponse = lk_rtc.SignalResponse( diff --git a/test/integration/data_stream_reliability_test.dart b/test/integration/data_stream_reliability_test.dart index 4843fb61a..fef94b649 100644 --- a/test/integration/data_stream_reliability_test.dart +++ b/test/integration/data_stream_reliability_test.dart @@ -75,15 +75,17 @@ void main() { expectedMessages.add(messageContent); try { - final info = await room.localParticipant?.sendText(messageContent, - options: SendTextOptions( - topic: 'reliability-test', - onProgress: (progress) { - // Verify progress is within bounds (0.0-1.0) - expect(progress, greaterThanOrEqualTo(0.0)); - expect(progress, lessThanOrEqualTo(1.0)); - }, - )); + final info = await room.localParticipant?.sendText( + messageContent, + options: SendTextOptions( + topic: 'reliability-test', + onProgress: (progress) { + // Verify progress is within bounds (0.0-1.0) + expect(progress, greaterThanOrEqualTo(0.0)); + expect(progress, lessThanOrEqualTo(1.0)); + }, + ), + ); expect(info, isNotNull); // Small delay between messages to create realistic load @@ -99,18 +101,27 @@ void main() { await receivedCompleter.future.timeout(Duration(seconds: 12)); // Verify all messages received exactly once - expect(receivedMessages.length, equals(messageCount), - reason: 'All ${messageCount} messages should be received exactly once'); + expect( + receivedMessages.length, + equals(messageCount), + reason: 'All ${messageCount} messages should be received exactly once', + ); // Verify no duplicates final uniqueMessages = receivedMessages.toSet(); - expect(uniqueMessages.length, equals(receivedMessages.length), - reason: 'No duplicate messages should be received'); + expect( + uniqueMessages.length, + equals(receivedMessages.length), + reason: 'No duplicate messages should be received', + ); // Verify each expected message was received for (final expectedMessage in expectedMessages) { - expect(receivedMessages, contains(expectedMessage), - reason: 'Expected message should be received: $expectedMessage'); + expect( + receivedMessages, + contains(expectedMessage), + reason: 'Expected message should be received: $expectedMessage', + ); } print('✅ Text stream reliability test passed: All ${messageCount} messages received correctly'); @@ -205,7 +216,8 @@ void main() { // Print first 10 bytes for debugging final firstBytes = fileData.take(10).toList(); print( - 'Received reliable byte stream ${receivedFiles.length}/${chunkCount}: ${fileData.length} bytes from $participantIdentity'); + 'Received reliable byte stream ${receivedFiles.length}/${chunkCount}: ${fileData.length} bytes from $participantIdentity', + ); print(' First 10 bytes: $firstBytes'); if (receivedFiles.length >= chunkCount) { @@ -228,12 +240,14 @@ void main() { final firstBytes = fileData.take(10).toList(); print('Sending file ${i}: ${fileData.length} bytes, first 10: $firstBytes'); - final stream = await room.localParticipant?.streamBytes(StreamBytesOptions( - topic: 'reliability-bytes', - name: 'reliable-test-file-${i}.bin', - mimeType: 'application/octet-stream', - totalSize: chunkSize, - )); + final stream = await room.localParticipant?.streamBytes( + StreamBytesOptions( + topic: 'reliability-bytes', + name: 'reliable-test-file-${i}.bin', + mimeType: 'application/octet-stream', + totalSize: chunkSize, + ), + ); await stream?.write(Uint8List.fromList(fileData)); await stream?.close(); @@ -252,8 +266,11 @@ void main() { expect(receivedFiles.length, equals(chunkCount), reason: 'All ${chunkCount} byte streams should be received'); // Verify data integrity - all expected files should be received (order may vary) - expect(receivedFiles.length, equals(expectedFiles.length), - reason: 'Should receive exactly ${expectedFiles.length} files'); + expect( + receivedFiles.length, + equals(expectedFiles.length), + reason: 'Should receive exactly ${expectedFiles.length} files', + ); // Use deep equality comparison for lists @@ -262,15 +279,19 @@ void main() { final expectedFile = expectedFiles[i]; final matchingFiles = receivedFiles.where((received) => listEquality.equals(received, expectedFile)).toList(); - expect(matchingFiles.length, equals(1), - reason: 'Expected file ${i} should be received exactly once, found ${matchingFiles.length} matches'); + expect( + matchingFiles.length, + equals(1), + reason: 'Expected file ${i} should be received exactly once, found ${matchingFiles.length} matches', + ); } // Verify no unexpected files received for (int i = 0; i < receivedFiles.length; i++) { final receivedFile = receivedFiles[i]; - final matchingExpected = - expectedFiles.where((expected) => listEquality.equals(receivedFile, expected)).toList(); + final matchingExpected = expectedFiles + .where((expected) => listEquality.equals(receivedFile, expected)) + .toList(); expect(matchingExpected.length, equals(1), reason: 'Received file ${i} should match exactly one expected file'); } @@ -314,8 +335,11 @@ void main() { // Verify no duplicates (each sequence should appear exactly once) for (final entry in duplicateTracker.entries) { - expect(entry.value, equals(1), - reason: 'Sequence ${entry.key} should appear exactly once, but appeared ${entry.value} times'); + expect( + entry.value, + equals(1), + reason: 'Sequence ${entry.key} should appear exactly once, but appeared ${entry.value} times', + ); } // Verify correct count of unique sequences @@ -358,8 +382,10 @@ void main() { sendFutures.add(() async { for (int msgId = 0; msgId < messagesPerStream; msgId++) { try { - await room.localParticipant - ?.sendText('Stream${streamId}_Message${msgId}', options: SendTextOptions(topic: topic)); + await room.localParticipant?.sendText( + 'Stream${streamId}_Message${msgId}', + options: SendTextOptions(topic: topic), + ); // Small randomized delay to create realistic concurrent load await Future.delayed(Duration(milliseconds: Random().nextInt(30) + 10)); } catch (e) { @@ -378,24 +404,34 @@ void main() { // Verify all messages received correctly for (int streamId = 0; streamId < concurrentStreams; streamId++) { final topic = 'concurrent-${streamId}'; - expect(receivedMessages[topic]!.length, equals(messagesPerStream), - reason: 'Stream ${streamId} should receive all ${messagesPerStream} messages'); + expect( + receivedMessages[topic]!.length, + equals(messagesPerStream), + reason: 'Stream ${streamId} should receive all ${messagesPerStream} messages', + ); // Verify message content uniqueness within each stream final uniqueInStream = receivedMessages[topic]!.toSet(); - expect(uniqueInStream.length, equals(messagesPerStream), - reason: 'Stream ${streamId} should have ${messagesPerStream} unique messages'); + expect( + uniqueInStream.length, + equals(messagesPerStream), + reason: 'Stream ${streamId} should have ${messagesPerStream} unique messages', + ); // Verify expected messages for (int msgId = 0; msgId < messagesPerStream; msgId++) { final expectedMessage = 'Stream${streamId}_Message${msgId}'; - expect(receivedMessages[topic], contains(expectedMessage), - reason: 'Stream ${streamId} should contain message ${msgId}'); + expect( + receivedMessages[topic], + contains(expectedMessage), + reason: 'Stream ${streamId} should contain message ${msgId}', + ); } } print( - '✅ Concurrent streams test passed: ${concurrentStreams * messagesPerStream} total messages across ${concurrentStreams} streams'); + '✅ Concurrent streams test passed: ${concurrentStreams * messagesPerStream} total messages across ${concurrentStreams} streams', + ); }); test('Mixed Data Types Reliability Test', () async { @@ -423,7 +459,8 @@ void main() { // Print first 10 bytes for debugging final firstBytes = data.take(10).toList(); print( - 'Received mixed byte stream ${receivedBytes.length}/${byteStreams}: ${data.length} bytes, first 10: $firstBytes'); + 'Received mixed byte stream ${receivedBytes.length}/${byteStreams}: ${data.length} bytes, first 10: $firstBytes', + ); if (receivedBytes.length >= byteStreams) { byteCompleter.complete(); @@ -436,8 +473,10 @@ void main() { // Send text messages for (int i = 0; i < textMessages; i++) { futures.add(() async { - await room.localParticipant - ?.sendText('Mixed text message ${i}', options: SendTextOptions(topic: 'mixed-text')); + await room.localParticipant?.sendText( + 'Mixed text message ${i}', + options: SendTextOptions(topic: 'mixed-text'), + ); }()); } @@ -452,11 +491,13 @@ void main() { final firstBytes = data.take(10).toList(); print('Sending mixed byte stream ${i}: ${data.length} bytes, first 10: $firstBytes'); - final stream = await room.localParticipant?.streamBytes(StreamBytesOptions( - topic: 'mixed-bytes', - name: 'mixed-file-${i}.dat', - totalSize: data.length, - )); + final stream = await room.localParticipant?.streamBytes( + StreamBytesOptions( + topic: 'mixed-bytes', + name: 'mixed-file-${i}.dat', + totalSize: data.length, + ), + ); await stream?.write(Uint8List.fromList(data)); await stream?.close(); }()); @@ -498,7 +539,7 @@ void main() { 'testfiles/progress_test_2.bin', 'testfiles/progress_test_3.bin', 'testfiles/progress_test_4.bin', - 'testfiles/progress_test_5.bin' + 'testfiles/progress_test_5.bin', ]; /// Create test files with random data @@ -531,19 +572,21 @@ void main() { // Send text with multiple file attachments - this triggers incremental progress print('Sending text with ${numFiles} file attachments to test incremental progress...'); - final info = await room.localParticipant?.sendText('Message with ${numFiles} attachments', - options: SendTextOptions( - topic: 'progress-test', - attachments: tempFiles, - onProgress: (progress) { - progressValues.add(progress); - print('Progress: ${(progress * 100).toStringAsFixed(1)}% (${progressValues.length} updates)'); - - // Verify progress bounds - expect(progress, greaterThanOrEqualTo(0.0)); - expect(progress, lessThanOrEqualTo(1.0)); - }, - )); + final info = await room.localParticipant?.sendText( + 'Message with ${numFiles} attachments', + options: SendTextOptions( + topic: 'progress-test', + attachments: tempFiles, + onProgress: (progress) { + progressValues.add(progress); + print('Progress: ${(progress * 100).toStringAsFixed(1)}% (${progressValues.length} updates)'); + + // Verify progress bounds + expect(progress, greaterThanOrEqualTo(0.0)); + expect(progress, lessThanOrEqualTo(1.0)); + }, + ), + ); expect(info, isNotNull); await Future.wait([textCompleter.future, receivedCompleter.future]).timeout(Duration(seconds: 15)); @@ -567,8 +610,11 @@ void main() { // Verify progress is non-decreasing for (int i = 1; i < progressValues.length; i++) { - expect(progressValues[i], greaterThanOrEqualTo(progressValues[i - 1]), - reason: 'Progress should be non-decreasing'); + expect( + progressValues[i], + greaterThanOrEqualTo(progressValues[i - 1]), + reason: 'Progress should be non-decreasing', + ); } print('✅ Incremental progress test passed: ${progressValues.length} progress updates from 0% to 100%'); diff --git a/test/mock/e2e_container.dart b/test/mock/e2e_container.dart index 2d6ef706d..25de66597 100644 --- a/test/mock/e2e_container.dart +++ b/test/mock/e2e_container.dart @@ -185,11 +185,13 @@ class E2EContainer { } void simulateInboundRpcAck(String fromIdentity, String requestId) { - deliverInboundDataPacket(lk_models.DataPacket( - kind: lk_models.DataPacket_Kind.RELIABLE, - participantIdentity: fromIdentity, - rpcAck: lk_models.RpcAck(requestId: requestId), - )); + deliverInboundDataPacket( + lk_models.DataPacket( + kind: lk_models.DataPacket_Kind.RELIABLE, + participantIdentity: fromIdentity, + rpcAck: lk_models.RpcAck(requestId: requestId), + ), + ); } void simulateInboundRpcResponse( @@ -198,15 +200,17 @@ class E2EContainer { String? payload, lk_models.RpcError? error, }) { - deliverInboundDataPacket(lk_models.DataPacket( - kind: lk_models.DataPacket_Kind.RELIABLE, - participantIdentity: fromIdentity, - rpcResponse: lk_models.RpcResponse( - requestId: requestId, - payload: error == null ? payload : null, - error: error, + deliverInboundDataPacket( + lk_models.DataPacket( + kind: lk_models.DataPacket_Kind.RELIABLE, + participantIdentity: fromIdentity, + rpcResponse: lk_models.RpcResponse( + requestId: requestId, + payload: error == null ? payload : null, + error: error, + ), ), - )); + ); } /// Simulate a v2 RPC response data stream from [fromIdentity] for [requestId]. @@ -267,28 +271,34 @@ class E2EContainer { attributes: attributes.entries, textHeader: lk_models.DataStream_TextHeader(), ); - deliverInboundDataPacket(lk_models.DataPacket( - kind: lk_models.DataPacket_Kind.RELIABLE, - participantIdentity: fromIdentity, - streamHeader: header, - )); + deliverInboundDataPacket( + lk_models.DataPacket( + kind: lk_models.DataPacket_Kind.RELIABLE, + participantIdentity: fromIdentity, + streamHeader: header, + ), + ); final chunk = lk_models.DataStream_Chunk( streamId: streamId, chunkIndex: Int64(0), content: Uint8List.fromList(body.codeUnits), ); - deliverInboundDataPacket(lk_models.DataPacket( - kind: lk_models.DataPacket_Kind.RELIABLE, - participantIdentity: fromIdentity, - streamChunk: chunk, - )); + deliverInboundDataPacket( + lk_models.DataPacket( + kind: lk_models.DataPacket_Kind.RELIABLE, + participantIdentity: fromIdentity, + streamChunk: chunk, + ), + ); final trailer = lk_models.DataStream_Trailer(streamId: streamId); - deliverInboundDataPacket(lk_models.DataPacket( - kind: lk_models.DataPacket_Kind.RELIABLE, - participantIdentity: fromIdentity, - streamTrailer: trailer, - )); + deliverInboundDataPacket( + lk_models.DataPacket( + kind: lk_models.DataPacket_Kind.RELIABLE, + participantIdentity: fromIdentity, + streamTrailer: trailer, + ), + ); } } diff --git a/test/mock/e2ee_fake_manager.dart b/test/mock/e2ee_fake_manager.dart index 946df0605..9702ed898 100644 --- a/test/mock/e2ee_fake_manager.dart +++ b/test/mock/e2ee_fake_manager.dart @@ -68,18 +68,18 @@ class TestKeyProvider implements rtc.KeyProvider { class TestE2EEManager implements E2EEManager { TestE2EEManager({bool dcEncryptionEnabled = true}) - : _keyProvider = BaseKeyProvider( - TestKeyProvider(), - rtc.KeyProviderOptions( - sharedKey: true, - ratchetSalt: Uint8List(16), - ratchetWindowSize: 16, - keyRingSize: 1, - failureTolerance: -1, - discardFrameWhenCryptorNotReady: false, - ), + : _keyProvider = BaseKeyProvider( + TestKeyProvider(), + rtc.KeyProviderOptions( + sharedKey: true, + ratchetSalt: Uint8List(16), + ratchetWindowSize: 16, + keyRingSize: 1, + failureTolerance: -1, + discardFrameWhenCryptorNotReady: false, ), - _dcEncryptionEnabled = dcEncryptionEnabled; + ), + _dcEncryptionEnabled = dcEncryptionEnabled; final BaseKeyProvider _keyProvider; final bool _dcEncryptionEnabled; diff --git a/test/mock/fake_checker.dart b/test/mock/fake_checker.dart index 5e38d5993..da8253432 100644 --- a/test/mock/fake_checker.dart +++ b/test/mock/fake_checker.dart @@ -20,8 +20,8 @@ class FakeChecker extends Checker { FakeChecker({ Future Function(FakeChecker checker)? onPerform, CheckerOptions? options, - }) : _onPerform = onPerform, - super('ws://www.example.com', 'token', options: options); + }) : _onPerform = onPerform, + super('ws://www.example.com', 'token', options: options); final Future Function(FakeChecker checker)? _onPerform; diff --git a/test/mock/peerconnection_mock.dart b/test/mock/peerconnection_mock.dart index 6868a43e2..80fa01093 100644 --- a/test/mock/peerconnection_mock.dart +++ b/test/mock/peerconnection_mock.dart @@ -167,8 +167,11 @@ class MockPeerConnection extends RTCPeerConnection { } @override - Future addTransceiver( - {MediaStreamTrack? track, RTCRtpMediaType? kind, RTCRtpTransceiverInit? init}) { + Future addTransceiver({ + MediaStreamTrack? track, + RTCRtpMediaType? kind, + RTCRtpTransceiverInit? init, + }) { // TODO: implement addTransceiver throw UnimplementedError(); } @@ -288,9 +291,10 @@ a=rtpmap:32 MPV/90000 throw UnimplementedError(); } - static Future create(Map configuration, - [Map? constraints]) async => - MockPeerConnection(); + static Future create( + Map configuration, [ + Map? constraints, + ]) async => MockPeerConnection(); @override // TODO: implement restartIce diff --git a/test/preconnect/audio_frame_capture_test.dart b/test/preconnect/audio_frame_capture_test.dart index 384975432..8763a8337 100644 --- a/test/preconnect/audio_frame_capture_test.dart +++ b/test/preconnect/audio_frame_capture_test.dart @@ -113,19 +113,23 @@ void main() { final frames = []; final sub = capture.frameStream.listen(frames.add); - capture.emitFrame(AudioFrame( - sampleRate: 24000, - channels: 1, - data: int16Bytes([1000, -1000]), - format: AudioFormat.Int16, - )); + capture.emitFrame( + AudioFrame( + sampleRate: 24000, + channels: 1, + data: int16Bytes([1000, -1000]), + format: AudioFormat.Int16, + ), + ); - capture.emitFrame(AudioFrame( - sampleRate: 24000, - channels: 1, - data: int16Bytes([2000, -2000]), - format: AudioFormat.Int16, - )); + capture.emitFrame( + AudioFrame( + sampleRate: 24000, + channels: 1, + data: int16Bytes([2000, -2000]), + format: AudioFormat.Int16, + ), + ); // Let microtasks run. await Future.delayed(Duration.zero); @@ -268,12 +272,14 @@ void main() { // Simulate 3 frames of 480 samples each (10ms at 48kHz mono int16). for (var i = 0; i < 3; i++) { final samples = List.generate(480, (j) => (j * 10) - 2400); - capture.emitFrame(AudioFrame( - sampleRate: 48000, - channels: 1, - data: int16Bytes(samples), - format: AudioFormat.Int16, - )); + capture.emitFrame( + AudioFrame( + sampleRate: 48000, + channels: 1, + data: int16Bytes(samples), + format: AudioFormat.Int16, + ), + ); } await Future.delayed(Duration.zero); @@ -313,18 +319,22 @@ void main() { }); // Write 60 bytes, then 60 more → should overflow at 100. - capture.emitFrame(AudioFrame( - sampleRate: 24000, - channels: 1, - data: Uint8List(60), - format: AudioFormat.Int16, - )); - capture.emitFrame(AudioFrame( - sampleRate: 24000, - channels: 1, - data: Uint8List(60), - format: AudioFormat.Int16, - )); + capture.emitFrame( + AudioFrame( + sampleRate: 24000, + channels: 1, + data: Uint8List(60), + format: AudioFormat.Int16, + ), + ); + capture.emitFrame( + AudioFrame( + sampleRate: 24000, + channels: 1, + data: Uint8List(60), + format: AudioFormat.Int16, + ), + ); await Future.delayed(Duration.zero); @@ -357,12 +367,14 @@ void main() { // Emit raw float32 data (as if from the worklet). final samples = Float32List.fromList(List.generate(128, (i) => i / 128.0)); - capture.emitFrame(AudioFrame( - sampleRate: 48000, - channels: 1, - data: samples.buffer.asUint8List(), - format: AudioFormat.Float32, - )); + capture.emitFrame( + AudioFrame( + sampleRate: 48000, + channels: 1, + data: samples.buffer.asUint8List(), + format: AudioFormat.Float32, + ), + ); await Future.delayed(Duration.zero); @@ -404,12 +416,14 @@ void main() { // Interleaved stereo: [L0=0.5, R0=-0.5, L1=0.25, R1=-0.25] final stereo = Float32List.fromList([0.5, -0.5, 0.25, -0.25]); - capture.emitFrame(AudioFrame( - sampleRate: 48000, - channels: 2, - data: stereo.buffer.asUint8List(), - format: AudioFormat.Float32, - )); + capture.emitFrame( + AudioFrame( + sampleRate: 48000, + channels: 2, + data: stereo.buffer.asUint8List(), + format: AudioFormat.Float32, + ), + ); await Future.delayed(Duration.zero); diff --git a/test/publication/remote_track_publication_test.dart b/test/publication/remote_track_publication_test.dart index cbde67fe0..91264f209 100644 --- a/test/publication/remote_track_publication_test.dart +++ b/test/publication/remote_track_publication_test.dart @@ -97,8 +97,9 @@ void main() { /// The most recent [lk_rtc.UpdateTrackSettings] the SDK sent for [sid], if any. lk_rtc.UpdateTrackSettings? lastSettingsFor(String sid) { - final matches = - connector.socket.sent.where((r) => r.hasTrackSetting() && r.trackSetting.trackSids.contains(sid)).toList(); + final matches = connector.socket.sent + .where((r) => r.hasTrackSetting() && r.trackSetting.trackSids.contains(sid)) + .toList(); return matches.isEmpty ? null : matches.last.trackSetting; } @@ -149,12 +150,14 @@ void main() { addTearDown(() async => await pub.dispose()); await pub.disable(); - final countAfterFirst = - connector.socket.sent.where((r) => r.hasTrackSetting() && r.trackSetting.trackSids.contains(sid)).length; + final countAfterFirst = connector.socket.sent + .where((r) => r.hasTrackSetting() && r.trackSetting.trackSids.contains(sid)) + .length; await pub.disable(); - final countAfterSecond = - connector.socket.sent.where((r) => r.hasTrackSetting() && r.trackSetting.trackSids.contains(sid)).length; + final countAfterSecond = connector.socket.sent + .where((r) => r.hasTrackSetting() && r.trackSetting.trackSids.contains(sid)) + .length; expect(countAfterFirst, 1); expect(countAfterSecond, 1, reason: 'a second disable() with no state change should not re-send'); @@ -184,11 +187,13 @@ void main() { expect(pub.enabled, isTrue); - await pub.updateTrack(RemoteVideoTrack( - TrackSource.camera, - stream, - track, - )); + await pub.updateTrack( + RemoteVideoTrack( + TrackSource.camera, + stream, + track, + ), + ); await Future.delayed(const Duration(milliseconds: 350)); @@ -220,11 +225,13 @@ void main() { final track = _FakeMediaStreamTrack(id: sid, kind: 'video'); await stream.addTrack(track); - await pub.updateTrack(RemoteVideoTrack( - TrackSource.camera, - stream, - track, - )); + await pub.updateTrack( + RemoteVideoTrack( + TrackSource.camera, + stream, + track, + ), + ); final initialSettings = lastSettingsFor(sid); expect(initialSettings, isNotNull); diff --git a/test/support/certificate_pinning_io_test.dart b/test/support/certificate_pinning_io_test.dart index 479424e5c..428d83b7c 100644 --- a/test/support/certificate_pinning_io_test.dart +++ b/test/support/certificate_pinning_io_test.dart @@ -265,16 +265,18 @@ void main() { final proxy = await _PlainHttpProxyServer.start(); addTearDown(proxy.close); - final client = createSdkIoHttpClient(const NetworkOptions( - certificatePinning: CertificatePinningOptions( - rules: [ - CertificatePinningRule( - hosts: ['pinned.example.com'], - primaryPins: ['sha256/not-the-presented-pin'], - ), - ], + final client = createSdkIoHttpClient( + const NetworkOptions( + certificatePinning: CertificatePinningOptions( + rules: [ + CertificatePinningRule( + hosts: ['pinned.example.com'], + primaryPins: ['sha256/not-the-presented-pin'], + ), + ], + ), ), - )); + ); addTearDown(() => client.close(force: true)); client.findProxy = (_) => 'PROXY $_testServerHost:${proxy.port}'; @@ -294,16 +296,18 @@ void main() { final proxy = await _PlainHttpProxyServer.start(); addTearDown(proxy.close); - final client = createSdkIoHttpClient(const NetworkOptions( - certificatePinning: CertificatePinningOptions( - rules: [ - CertificatePinningRule( - hosts: [_testServerHost], - primaryPins: ['sha256/not-the-presented-pin'], - ), - ], + final client = createSdkIoHttpClient( + const NetworkOptions( + certificatePinning: CertificatePinningOptions( + rules: [ + CertificatePinningRule( + hosts: [_testServerHost], + primaryPins: ['sha256/not-the-presented-pin'], + ), + ], + ), ), - )); + ); addTearDown(() => client.close(force: true)); client.findProxy = (_) => 'PROXY $_testServerHost:${proxy.port}'; diff --git a/test/support/certificate_pinning_test.dart b/test/support/certificate_pinning_test.dart index b7c91e244..432f1d2ec 100644 --- a/test/support/certificate_pinning_test.dart +++ b/test/support/certificate_pinning_test.dart @@ -56,15 +56,17 @@ void main() { final backupPin = certificateSpkiSha256Pin(backupCertificate); final secondBackupPin = certificateSpkiSha256Pin(secondBackupCertificate); - final validator = CertificatePinValidator(CertificatePinningOptions( - rules: [ - CertificatePinningRule( - hosts: const ['*.livekit.cloud'], - primaryPins: [primaryPin], - backupPins: [backupPin, secondBackupPin], - ), - ], - )); + final validator = CertificatePinValidator( + CertificatePinningOptions( + rules: [ + CertificatePinningRule( + hosts: const ['*.livekit.cloud'], + primaryPins: [primaryPin], + backupPins: [backupPin, secondBackupPin], + ), + ], + ), + ); expect( () => validator.validatePeerCertificate( @@ -93,18 +95,20 @@ void main() { final certificate = _certificate(_subjectPublicKeyInfo([1, 2, 3, 4])); final backupCertificate = _certificate(_subjectPublicKeyInfo([5, 6, 7, 8])); final otherCertificate = _certificate(_subjectPublicKeyInfo([9, 10, 11, 12])); - final validator = CertificatePinValidator(CertificatePinningOptions( - rules: [ - CertificatePinningRule( - hosts: const ['*'], - primaryPins: [certificateSpkiSha256Pin(certificate)], - ), - CertificatePinningRule( - hosts: const ['livekit.example.com'], - backupPins: [certificateSpkiSha256Pin(backupCertificate)], - ), - ], - )); + final validator = CertificatePinValidator( + CertificatePinningOptions( + rules: [ + CertificatePinningRule( + hosts: const ['*'], + primaryPins: [certificateSpkiSha256Pin(certificate)], + ), + CertificatePinningRule( + hosts: const ['livekit.example.com'], + backupPins: [certificateSpkiSha256Pin(backupCertificate)], + ), + ], + ), + ); expect( () => validator.validatePeerCertificate( @@ -125,18 +129,20 @@ void main() { test('enforces each configured check type for matching rules', () { final certificate = _certificate(_subjectPublicKeyInfo([1, 2, 3, 4])); final otherCertificate = _certificate(_subjectPublicKeyInfo([5, 6, 7, 8])); - final validator = CertificatePinValidator(CertificatePinningOptions( - rules: [ - CertificatePinningRule( - hosts: const ['livekit.example.com'], - pinnedLeafCertificates: [CertificateBytes.der(certificate)], - ), - CertificatePinningRule( - hosts: const ['*.example.com'], - primaryPins: [certificateSpkiSha256Pin(otherCertificate)], - ), - ], - )); + final validator = CertificatePinValidator( + CertificatePinningOptions( + rules: [ + CertificatePinningRule( + hosts: const ['livekit.example.com'], + pinnedLeafCertificates: [CertificateBytes.der(certificate)], + ), + CertificatePinningRule( + hosts: const ['*.example.com'], + primaryPins: [certificateSpkiSha256Pin(otherCertificate)], + ), + ], + ), + ); // passes the exact leaf check but fails the SPKI check expect( @@ -158,18 +164,20 @@ void main() { test('accepts certificates that satisfy every configured check type', () { final certificate = _certificate(_subjectPublicKeyInfo([1, 2, 3, 4])); - final validator = CertificatePinValidator(CertificatePinningOptions( - rules: [ - CertificatePinningRule( - hosts: const ['livekit.example.com'], - pinnedLeafCertificates: [CertificateBytes.der(certificate)], - ), - CertificatePinningRule( - hosts: const ['*.example.com'], - primaryPins: [certificateSpkiSha256Pin(certificate)], - ), - ], - )); + final validator = CertificatePinValidator( + CertificatePinningOptions( + rules: [ + CertificatePinningRule( + hosts: const ['livekit.example.com'], + pinnedLeafCertificates: [CertificateBytes.der(certificate)], + ), + CertificatePinningRule( + hosts: const ['*.example.com'], + primaryPins: [certificateSpkiSha256Pin(certificate)], + ), + ], + ), + ); expect( () => validator.validatePeerCertificate( @@ -183,14 +191,16 @@ void main() { test('rejects pin mismatches', () { final certificate = _certificate(_subjectPublicKeyInfo([1, 2, 3, 4])); final otherCertificate = _certificate(_subjectPublicKeyInfo([5, 6, 7, 8])); - final validator = CertificatePinValidator(CertificatePinningOptions( - rules: [ - CertificatePinningRule( - hosts: const ['livekit.example.com'], - primaryPins: [certificateSpkiSha256Pin(certificate)], - ), - ], - )); + final validator = CertificatePinValidator( + CertificatePinningOptions( + rules: [ + CertificatePinningRule( + hosts: const ['livekit.example.com'], + primaryPins: [certificateSpkiSha256Pin(certificate)], + ), + ], + ), + ); expect( () => validator.validatePeerCertificate( @@ -203,14 +213,16 @@ void main() { test('ignores hosts without a matching rule', () { final certificate = _certificate(_subjectPublicKeyInfo([1, 2, 3, 4])); - final validator = CertificatePinValidator(const CertificatePinningOptions( - rules: [ - CertificatePinningRule( - hosts: ['livekit.example.com'], - primaryPins: ['sha256/not-a-real-pin'], - ), - ], - )); + final validator = CertificatePinValidator( + const CertificatePinningOptions( + rules: [ + CertificatePinningRule( + hosts: ['livekit.example.com'], + primaryPins: ['sha256/not-a-real-pin'], + ), + ], + ), + ); expect( () => validator.validatePeerCertificate( @@ -223,14 +235,16 @@ void main() { test('wildcard hosts match only a single label', () { final certificate = _certificate(_subjectPublicKeyInfo([1, 2, 3, 4])); - final validator = CertificatePinValidator(CertificatePinningOptions( - rules: [ - CertificatePinningRule( - hosts: const ['*.livekit.cloud'], - primaryPins: [certificateSpkiSha256Pin(certificate)], - ), - ], - )); + final validator = CertificatePinValidator( + CertificatePinningOptions( + rules: [ + CertificatePinningRule( + hosts: const ['*.livekit.cloud'], + primaryPins: [certificateSpkiSha256Pin(certificate)], + ), + ], + ), + ); expect( () => validator.validatePeerCertificate( @@ -251,14 +265,16 @@ void main() { test('multi-label wildcard hosts match any depth', () { final certificate = _certificate(_subjectPublicKeyInfo([1, 2, 3, 4])); final mismatchedCertificate = _certificate(_subjectPublicKeyInfo([5, 6, 7, 8])); - final validator = CertificatePinValidator(CertificatePinningOptions( - rules: [ - CertificatePinningRule( - hosts: const ['**.livekit.cloud'], - primaryPins: [certificateSpkiSha256Pin(certificate)], - ), - ], - )); + final validator = CertificatePinValidator( + CertificatePinningOptions( + rules: [ + CertificatePinningRule( + hosts: const ['**.livekit.cloud'], + primaryPins: [certificateSpkiSha256Pin(certificate)], + ), + ], + ), + ); // matches one label deep expect( @@ -343,7 +359,8 @@ List _certificate(List subjectPublicKeyInfo) { ]); } -List _realCertificateDer() => base64Decode(''' +List _realCertificateDer() => base64Decode( + ''' MIIDxzCCAq+gAwIBAgIUGhRL7309IUNTm6hvItsQIT62H2gwDQYJKoZIhvcNAQEL BQAwVzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRAwDgYDVQQKDAdMaXZlS2l0 MQ0wCwYDVQQLDARUZXN0MRowGAYDVQQDDBFMaXZlS2l0IFRlc3QgQ0EgNjAeFw0y @@ -366,12 +383,13 @@ CxyX1bjWBvpPpwVVVtz9Ydrp5Uvmzd4IrtYJRz/Ty62y2YKmqEVmsfBqBvdxbF5R jOt2XQ4kR0oSVkU+KyVyGtMhNjjQnWjJOuVpo/rdhtEKz4/9B4ofKYgoaeATqoQg Jioy3puXYIMud+Y= ''' - .replaceAll(RegExp(r'\s'), '')); + .replaceAll(RegExp(r'\s'), ''), +); List _subjectPublicKeyInfo(List publicKeyBytes) => _sequence([ - ..._sequence(const []), - ..._bitString(publicKeyBytes), - ]); + ..._sequence(const []), + ..._bitString(publicKeyBytes), +]); List _explicitVersion() => _element(0xa0, _integer(2)); @@ -382,10 +400,10 @@ List _sequence(List value) => _element(0x30, value); List _bitString(List value) => _element(0x03, [0, ...value]); List _element(int tag, List value) => [ - tag, - ..._length(value.length), - ...value, - ]; + tag, + ..._length(value.length), + ...value, +]; List _length(int length) { if (length < 0x80) { diff --git a/test/support/reusable_completer_test.dart b/test/support/reusable_completer_test.dart index 17b8bde5c..9b68d5bdc 100644 --- a/test/support/reusable_completer_test.dart +++ b/test/support/reusable_completer_test.dart @@ -347,14 +347,16 @@ void main() { final futures = []; for (int i = 0; i < 10; i++) { - futures.add(Future(() async { - final future = completer.future; - if (i == 0) { - await Future.delayed(Duration(milliseconds: 1)); - completer.complete('winner'); - } - return future; - })); + futures.add( + Future(() async { + final future = completer.future; + if (i == 0) { + await Future.delayed(Duration(milliseconds: 1)); + completer.complete('winner'); + } + return future; + }), + ); } final results = await Future.wait(futures, eagerError: false); diff --git a/test/token/caching_token_source_test.dart b/test/token/caching_token_source_test.dart index 06469b91a..694d6431a 100644 --- a/test/token/caching_token_source_test.dart +++ b/test/token/caching_token_source_test.dart @@ -124,14 +124,18 @@ void main() { final cachingSource = CachingTokenSource(mockSource); - await cachingSource.fetch(const TokenRequestOptions( - participantAttributes: {'key1': 'value1'}, - )); + await cachingSource.fetch( + const TokenRequestOptions( + participantAttributes: {'key1': 'value1'}, + ), + ); expect(fetchCount, 1); - await cachingSource.fetch(const TokenRequestOptions( - participantAttributes: {'key1': 'value2'}, - )); + await cachingSource.fetch( + const TokenRequestOptions( + participantAttributes: {'key1': 'value2'}, + ), + ); expect(fetchCount, 2); }); diff --git a/test/token/token_source_test.dart b/test/token/token_source_test.dart index c1627a2c4..18b8ce2af 100644 --- a/test/token/token_source_test.dart +++ b/test/token/token_source_test.dart @@ -179,8 +179,8 @@ void main() { { 'agent_name': 'demo-agent', 'metadata': '{"foo":"bar"}', - } - ] + }, + ], }, ); @@ -224,8 +224,8 @@ void main() { final token = _generateToken( roomConfig: { 'agents': [ - {'agent_name': 'assistant'} - ] + {'agent_name': 'assistant'}, + ], }, ); @@ -311,10 +311,12 @@ void main() { } final source = CustomTokenSource(customFunction); - final result = await source.fetch(const TokenRequestOptions( - participantName: 'custom-participant', - roomName: 'custom-room', - )); + final result = await source.fetch( + const TokenRequestOptions( + participantName: 'custom-participant', + roomName: 'custom-room', + ), + ); expect(result.serverUrl, 'https://custom.livekit.io'); expect(result.participantToken, 'custom-token'); @@ -503,7 +505,8 @@ String _generateToken({ }) { final payload = { 'sub': subject ?? 'test-participant', - 'video': video ?? + 'video': + video ?? { 'room': 'test-room', 'room_join': true, diff --git a/test/utils/data_packet_buffer_test.dart b/test/utils/data_packet_buffer_test.dart index 785afe0cb..7b6c95845 100644 --- a/test/utils/data_packet_buffer_test.dart +++ b/test/utils/data_packet_buffer_test.dart @@ -287,8 +287,10 @@ void main() { } // Verify buffer is consistent - final totalSizeCalculated = - buffer.getAll().map((p) => p.message.binary.length).fold(0, (sum, size) => sum + size); + final totalSizeCalculated = buffer + .getAll() + .map((p) => p.message.binary.length) + .fold(0, (sum, size) => sum + size); expect(buffer.totalSize, equals(totalSizeCalculated)); expect(buffer.length, equals(buffer.getAll().length)); diff --git a/web/e2ee.data_packet_cryptor.dart b/web/e2ee.data_packet_cryptor.dart index 3de4f8810..cd98a4897 100644 --- a/web/e2ee.data_packet_cryptor.dart +++ b/web/e2ee.data_packet_cryptor.dart @@ -95,19 +95,23 @@ class E2EEDataPacketCryptor { frameTrailer.setInt8(1, keyIndex); try { - final cipherText = await worker.crypto.subtle - .encrypt( - { - 'name': 'AES-GCM', - 'iv': iv, - }.jsify() as web.AlgorithmIdentifier, - secretKey, - data.toJS, - ) - .toDart as JSArrayBuffer; + final cipherText = + await worker.crypto.subtle + .encrypt( + { + 'name': 'AES-GCM', + 'iv': iv, + }.jsify() + as web.AlgorithmIdentifier, + secretKey, + data.toJS, + ) + .toDart + as JSArrayBuffer; logger.finer( - 'encodeFunction: encrypted buffer: ${data.length}, cipherText: ${cipherText.toDart.asUint8List().length}'); + 'encodeFunction: encrypted buffer: ${data.length}, cipherText: ${cipherText.toDart.asUint8List().length}', + ); return EncryptedPacket( data: cipherText.toDart.asUint8List(), @@ -140,7 +144,8 @@ class E2EEDataPacketCryptor { initialKeySet = keyHandler.getKeySet(initialKeyIndex); logger.finer( - 'decodeFunction: start decrypting data packet length ${payload.length}, ivLength $ivLength, keyIndex $keyIndex, iv $iv'); + 'decodeFunction: start decrypting data packet length ${payload.length}, ivLength $ivLength, keyIndex $keyIndex, iv $iv', + ); /// missingKey flow: /// tries to decrypt once, fails, tries to ratchet once and decrypt again, @@ -154,17 +159,20 @@ class E2EEDataPacketCryptor { var currentkeySet = initialKeySet; Future decryptFrameInternal() async { - decrypted = ((await worker.crypto.subtle - .decrypt( - { - 'name': 'AES-GCM', - 'iv': iv, - }.jsify() as web.AlgorithmIdentifier, - currentkeySet.encryptionKey, - payload.toJS, - ) - .toDart) as JSArrayBuffer) - .toDart; + decrypted = + ((await worker.crypto.subtle + .decrypt( + { + 'name': 'AES-GCM', + 'iv': iv, + }.jsify() + as web.AlgorithmIdentifier, + currentkeySet.encryptionKey, + payload.toJS, + ) + .toDart) + as JSArrayBuffer) + .toDart; logger.finer('decodeFunction::decryptFrameInternal: decrypted: ${decrypted!.asUint8List().length}'); if (decrypted == null) { @@ -207,7 +215,8 @@ class E2EEDataPacketCryptor { keyHandler.decryptionSuccess(); logger.finer( - 'decodeFunction: decryption success, buffer length ${payload.length}, decrypted: ${decrypted!.asUint8List().length}'); + 'decodeFunction: decryption success, buffer length ${payload.length}, decrypted: ${decrypted!.asUint8List().length}', + ); return decrypted!.asUint8List(); } catch (e) { diff --git a/web/e2ee.frame_cryptor.dart b/web/e2ee.frame_cryptor.dart index 0fad34a96..d6fec795c 100644 --- a/web/e2ee.frame_cryptor.dart +++ b/web/e2ee.frame_cryptor.dart @@ -155,7 +155,8 @@ class FrameCryptor { return; } final transformer = web.TransformStream( - {'transform': (operation == 'encode' ? encodeFunction.toJS : decodeFunction.toJS)}.jsify() as JSObject); + {'transform': (operation == 'encode' ? encodeFunction.toJS : decodeFunction.toJS)}.jsify() as JSObject, + ); try { readable.pipeThrough(transformer as web.ReadableWritablePair).pipeTo(writable); } catch (e) { @@ -167,7 +168,7 @@ class FrameCryptor { 'msgType': 'event', 'participantId': participantIdentity, 'state': 'internalError', - 'error': 'Internal error: ${e.toString()}' + 'error': 'Internal error: ${e.toString()}', }); } } @@ -202,7 +203,7 @@ class FrameCryptor { 'trackId': trackId, 'kind': kind, 'state': 'unsupportedCodec', - 'error': 'Unsupported codec for track $trackId, detected codec ${result.detectedCodec}' + 'error': 'Unsupported codec for track $trackId, detected codec ${result.detectedCodec}', }); } throw Exception('Unsupported codec for track $trackId'); @@ -298,7 +299,8 @@ class FrameCryptor { final srcFrame = readFrameInfo(frameObj); logger.fine( - 'encodeFunction: buffer ${srcFrame.buffer.length}, synchronizationSource ${srcFrame.ssrc} frameType ${srcFrame.frameType}'); + 'encodeFunction: buffer ${srcFrame.buffer.length}, synchronizationSource ${srcFrame.ssrc} frameType ${srcFrame.frameType}', + ); final secretKey = keyHandler.getKeySet(currentKeyIndex)?.encryptionKey; final keyIndex = currentKeyIndex; @@ -327,20 +329,24 @@ class FrameCryptor { frameTrailer.setInt8(0, IV_LENGTH); frameTrailer.setInt8(1, keyIndex); - final cipherText = await worker.crypto.subtle - .encrypt( - { - 'name': 'AES-GCM', - 'iv': iv, - 'additionalData': srcFrame.buffer.sublist(0, headerLength), - }.jsify() as web.AlgorithmIdentifier, - secretKey, - srcFrame.buffer.sublist(headerLength, srcFrame.buffer.length).toJS, - ) - .toDart as JSArrayBuffer; + final cipherText = + await worker.crypto.subtle + .encrypt( + { + 'name': 'AES-GCM', + 'iv': iv, + 'additionalData': srcFrame.buffer.sublist(0, headerLength), + }.jsify() + as web.AlgorithmIdentifier, + secretKey, + srcFrame.buffer.sublist(headerLength, srcFrame.buffer.length).toJS, + ) + .toDart + as JSArrayBuffer; logger.finer( - 'encodeFunction: encrypted buffer: ${srcFrame.buffer.length}, cipherText: ${cipherText.toDart.asUint8List().length}'); + 'encodeFunction: encrypted buffer: ${srcFrame.buffer.length}, cipherText: ${cipherText.toDart.asUint8List().length}', + ); final finalBuffer = BytesBuilder(); finalBuffer.add(Uint8List.fromList(srcFrame.buffer.sublist(0, headerLength))); @@ -359,12 +365,13 @@ class FrameCryptor { 'trackId': trackId, 'kind': kind, 'state': 'ok', - 'error': 'encryption ok' + 'error': 'encryption ok', }); } logger.finer( - 'encodeFunction[CryptorError.kOk]: frame enqueued kind $kind,codec $codec headerLength: $headerLength, timestamp: ${srcFrame.timestamp}, ssrc: ${srcFrame.ssrc}, data length: ${srcFrame.buffer.length}, encrypted length: ${finalBuffer.toBytes().length}, iv $iv'); + 'encodeFunction[CryptorError.kOk]: frame enqueued kind $kind,codec $codec headerLength: $headerLength, timestamp: ${srcFrame.timestamp}, ssrc: ${srcFrame.ssrc}, data length: ${srcFrame.buffer.length}, encrypted length: ${finalBuffer.toBytes().length}, iv $iv', + ); } catch (e) { logger.warning('encodeFunction encrypt: e ${e.toString()}'); if (lastError != CryptorError.kEncryptError) { @@ -376,7 +383,7 @@ class FrameCryptor { 'trackId': trackId, 'kind': kind, 'state': 'encryptError', - 'error': e.toString() + 'error': e.toString(), }); } } @@ -408,8 +415,10 @@ class FrameCryptor { if (keyOptions.uncryptedMagicBytes != null) { final magicBytes = keyOptions.uncryptedMagicBytes!; if (srcFrame.buffer.length > magicBytes.length + 1) { - final magicBytesBuffer = - srcFrame.buffer.sublist(srcFrame.buffer.length - magicBytes.length, srcFrame.buffer.length); + final magicBytesBuffer = srcFrame.buffer.sublist( + srcFrame.buffer.length - magicBytes.length, + srcFrame.buffer.length, + ); logger.finer('magicBytesBuffer $magicBytesBuffer, magicBytes $magicBytes'); if (magicBytesBuffer.toString() == magicBytes.toString()) { sifGuard.recordSif(); @@ -417,8 +426,9 @@ class FrameCryptor { final frameType = srcFrame.buffer.sublist(srcFrame.buffer.length - 1)[0]; logger.finer('decodeFunction: skip unencrypted frame, type $frameType'); final finalBuffer = BytesBuilder(); - finalBuffer - .add(Uint8List.fromList(srcFrame.buffer.sublist(0, srcFrame.buffer.length - (magicBytes.length + 1)))); + finalBuffer.add( + Uint8List.fromList(srcFrame.buffer.sublist(0, srcFrame.buffer.length - (magicBytes.length + 1))), + ); logger.fine('decodeFunction: enqueuing silent frame src: ${srcFrame.buffer}'); enqueueFrame(frameObj, controller, finalBuffer); logger.fine('decodeFunction: enqueuing done'); @@ -445,7 +455,8 @@ class FrameCryptor { initialKeyIndex = keyIndex; logger.finer( - 'decodeFunction: start decrypting frame headerLength $headerLength ${srcFrame.buffer.length} frameTrailer $frameTrailer, ivLength $ivLength, keyIndex $keyIndex, iv $iv'); + 'decodeFunction: start decrypting frame headerLength $headerLength ${srcFrame.buffer.length} frameTrailer $frameTrailer, ivLength $ivLength, keyIndex $keyIndex, iv $iv', + ); /// missingKey flow: /// tries to decrypt once, fails, tries to ratchet once and decrypt again, @@ -463,7 +474,7 @@ class FrameCryptor { 'trackId': trackId, 'kind': kind, 'state': 'missingKey', - 'error': 'Missing key for track $trackId' + 'error': 'Missing key for track $trackId', }); } // controller.enqueue(frame); @@ -472,18 +483,21 @@ class FrameCryptor { var currentkeySet = initialKeySet; Future decryptFrameInternal() async { - decrypted = ((await worker.crypto.subtle - .decrypt( - { - 'name': 'AES-GCM', - 'iv': iv, - 'additionalData': srcFrame.buffer.sublist(0, headerLength), - }.jsify() as web.AlgorithmIdentifier, - currentkeySet.encryptionKey, - srcFrame.buffer.sublist(headerLength, srcFrame.buffer.length - ivLength - 2).toJS, - ) - .toDart) as JSArrayBuffer) - .toDart; + decrypted = + ((await worker.crypto.subtle + .decrypt( + { + 'name': 'AES-GCM', + 'iv': iv, + 'additionalData': srcFrame.buffer.sublist(0, headerLength), + }.jsify() + as web.AlgorithmIdentifier, + currentkeySet.encryptionKey, + srcFrame.buffer.sublist(headerLength, srcFrame.buffer.length - ivLength - 2).toJS, + ) + .toDart) + as JSArrayBuffer) + .toDart; logger.finer('decodeFunction::decryptFrameInternal: decrypted: ${decrypted!.asUint8List().length}'); if (decrypted == null) { @@ -497,9 +511,11 @@ class FrameCryptor { if (lastError != CryptorError.kOk && lastError != CryptorError.kKeyRatcheted && ratchetCount > 0) { logger.finer( - 'decodeFunction::decryptFrameInternal: KeyRatcheted: ssrc ${srcFrame.ssrc} timestamp ${srcFrame.timestamp} ratchetCount $ratchetCount participantId: $participantIdentity'); + 'decodeFunction::decryptFrameInternal: KeyRatcheted: ssrc ${srcFrame.ssrc} timestamp ${srcFrame.timestamp} ratchetCount $ratchetCount participantId: $participantIdentity', + ); logger.finer( - 'decodeFunction::decryptFrameInternal: ratchetKey: lastError != CryptorError.kKeyRatcheted, reset state to kKeyRatcheted'); + 'decodeFunction::decryptFrameInternal: ratchetKey: lastError != CryptorError.kKeyRatcheted, reset state to kKeyRatcheted', + ); lastError = CryptorError.kKeyRatcheted; postMessage({ @@ -509,7 +525,7 @@ class FrameCryptor { 'trackId': trackId, 'kind': kind, 'state': 'keyRatcheted', - 'error': 'Key ratcheted ok' + 'error': 'Key ratcheted ok', }); } } @@ -545,7 +561,8 @@ class FrameCryptor { keyHandler.decryptionSuccess(); logger.finer( - 'decodeFunction: decryption success, buffer length ${srcFrame.buffer.length}, decrypted: ${decrypted!.asUint8List().length}'); + 'decodeFunction: decryption success, buffer length ${srcFrame.buffer.length}, decrypted: ${decrypted!.asUint8List().length}', + ); final finalBuffer = BytesBuilder(); @@ -562,12 +579,13 @@ class FrameCryptor { 'trackId': trackId, 'kind': kind, 'state': 'ok', - 'error': 'decryption ok' + 'error': 'decryption ok', }); } logger.fine( - 'decodeFunction[CryptorError.kOk]: decryption success kind $kind, headerLength: $headerLength, timestamp: ${srcFrame.timestamp}, ssrc: ${srcFrame.ssrc}, data length: ${srcFrame.buffer.length}, decrypted length: ${finalBuffer.toBytes().length}, keyindex $keyIndex iv $iv'); + 'decodeFunction[CryptorError.kOk]: decryption success kind $kind, headerLength: $headerLength, timestamp: ${srcFrame.timestamp}, ssrc: ${srcFrame.ssrc}, data length: ${srcFrame.buffer.length}, decrypted length: ${finalBuffer.toBytes().length}, keyindex $keyIndex iv $iv', + ); } catch (e, s) { logger.warning('decodeFunction[CryptorError.kDecryptError]: $e, $s'); if (lastError != CryptorError.kDecryptError) { @@ -579,7 +597,7 @@ class FrameCryptor { 'trackId': trackId, 'kind': kind, 'state': 'decryptError', - 'error': e.toString() + 'error': e.toString(), }); } diff --git a/web/e2ee.keyhandler.dart b/web/e2ee.keyhandler.dart index e0df601b8..bcf7c8c30 100644 --- a/web/e2ee.keyhandler.dart +++ b/web/e2ee.keyhandler.dart @@ -186,8 +186,13 @@ class ParticipantKeyHandler { Future setKey(Uint8List key, {int keyIndex = 0}) async { final keyMaterial = await worker.crypto.subtle - .importKey('raw', key.toJS, {'name': 'PBKDF2'.toJS}.jsify() as JSAny, false, - ['deriveBits', 'deriveKey'].jsify() as JSArray) + .importKey( + 'raw', + key.toJS, + {'name': 'PBKDF2'.toJS}.jsify() as JSAny, + false, + ['deriveBits', 'deriveKey'].jsify() as JSArray, + ) .toDart; final keySet = await deriveKeys( diff --git a/web/e2ee.nalu_utils.dart b/web/e2ee.nalu_utils.dart index 6a013fbe7..d972289ff 100644 --- a/web/e2ee.nalu_utils.dart +++ b/web/e2ee.nalu_utils.dart @@ -188,23 +188,23 @@ bool isH264SliceNALU(int naluType) { /// @returns True if the NALU is a slice bool isH265SliceNALU(int naluType) { return ( - // VCL NALUs (Video Coding Layer) - slice segments - naluType == H265NALUType.TRAIL_N || - naluType == H265NALUType.TRAIL_R || - naluType == H265NALUType.TSA_N || - naluType == H265NALUType.TSA_R || - naluType == H265NALUType.STSA_N || - naluType == H265NALUType.STSA_R || - naluType == H265NALUType.RADL_N || - naluType == H265NALUType.RADL_R || - naluType == H265NALUType.RASL_N || - naluType == H265NALUType.RASL_R || - naluType == H265NALUType.BLA_W_LP || - naluType == H265NALUType.BLA_W_RADL || - naluType == H265NALUType.BLA_N_LP || - naluType == H265NALUType.IDR_W_RADL || - naluType == H265NALUType.IDR_N_LP || - naluType == H265NALUType.CRA_NUT); + // VCL NALUs (Video Coding Layer) - slice segments + naluType == H265NALUType.TRAIL_N || + naluType == H265NALUType.TRAIL_R || + naluType == H265NALUType.TSA_N || + naluType == H265NALUType.TSA_R || + naluType == H265NALUType.STSA_N || + naluType == H265NALUType.STSA_R || + naluType == H265NALUType.RADL_N || + naluType == H265NALUType.RADL_R || + naluType == H265NALUType.RASL_N || + naluType == H265NALUType.RASL_R || + naluType == H265NALUType.BLA_W_LP || + naluType == H265NALUType.BLA_W_RADL || + naluType == H265NALUType.BLA_N_LP || + naluType == H265NALUType.IDR_W_RADL || + naluType == H265NALUType.IDR_N_LP || + naluType == H265NALUType.CRA_NUT); } /// Result of NALU processing for frame encryption @@ -342,5 +342,8 @@ NALUProcessingResult processNALUsForEncryption( } return NALUProcessingResult( - unencryptedBytes: unencryptedBytes, detectedCodec: detectedCodec, requiresNALUProcessing: true); + unencryptedBytes: unencryptedBytes, + detectedCodec: detectedCodec, + requiresNALUProcessing: true, + ); } diff --git a/web/e2ee.sfi_guard.dart b/web/e2ee.sfi_guard.dart index d8eab4fdb..d2d6ba81f 100644 --- a/web/e2ee.sfi_guard.dart +++ b/web/e2ee.sfi_guard.dart @@ -23,10 +23,10 @@ class SifGuard { userFramesSinceSif += 1; } if ( - // reset if we received more user frames than SIFs - userFramesSinceSif > consecutiveSifCount || - // also reset if we got a new user frame and the latest SIF frame hasn't been updated in a while - DateTime.timestamp().millisecondsSinceEpoch - lastSifReceivedAt > MAX_SIF_DURATION) { + // reset if we received more user frames than SIFs + userFramesSinceSif > consecutiveSifCount || + // also reset if we got a new user frame and the latest SIF frame hasn't been updated in a while + DateTime.timestamp().millisecondsSinceEpoch - lastSifReceivedAt > MAX_SIF_DURATION) { reset(); } } diff --git a/web/e2ee.worker.dart b/web/e2ee.worker.dart index 916237b7d..697ab2f1b 100644 --- a/web/e2ee.worker.dart +++ b/web/e2ee.worker.dart @@ -105,15 +105,17 @@ void main() async { final cryptor = getTrackCryptor(participantId.toDart, trackId.toDart, keyProvider); - unawaited(cryptor.setupTransform( - operation: msgType.toDart, - readable: transformer.readable, - writable: transformer.writable, - trackId: trackId.toDart, - kind: kind.toDart, - codec: codec?.toDart, - isReuse: false, - )); + unawaited( + cryptor.setupTransform( + operation: msgType.toDart, + readable: transformer.readable, + writable: transformer.writable, + trackId: trackId.toDart, + kind: kind.toDart, + codec: codec?.toDart, + isReuse: false, + ), + ); }.toJS; } @@ -129,25 +131,28 @@ void main() async { final options = msg['keyOptions']; final keyProviderId = msg['keyProviderId'] as String; final keyProviderOptions = KeyOptions( - sharedKey: options['sharedKey'], - ratchetSalt: Uint8List.fromList(base64Decode(options['ratchetSalt'] as String)), - ratchetWindowSize: options['ratchetWindowSize'], - failureTolerance: options['failureTolerance'] ?? -1, - uncryptedMagicBytes: options['uncryptedMagicBytes'] != null - ? Uint8List.fromList(base64Decode(options['uncryptedMagicBytes'] as String)) - : null, - keyRingSze: options['keyRingSize'] ?? KEYRING_SIZE, - discardFrameWhenCryptorNotReady: options['discardFrameWhenCryptorNotReady'] ?? false); + sharedKey: options['sharedKey'], + ratchetSalt: Uint8List.fromList(base64Decode(options['ratchetSalt'] as String)), + ratchetWindowSize: options['ratchetWindowSize'], + failureTolerance: options['failureTolerance'] ?? -1, + uncryptedMagicBytes: options['uncryptedMagicBytes'] != null + ? Uint8List.fromList(base64Decode(options['uncryptedMagicBytes'] as String)) + : null, + keyRingSze: options['keyRingSize'] ?? KEYRING_SIZE, + discardFrameWhenCryptorNotReady: options['discardFrameWhenCryptorNotReady'] ?? false, + ); logger.config('Init with keyProviderOptions:\n ${keyProviderOptions.toString()}'); final keyProvider = KeyProvider(self, keyProviderId, keyProviderOptions); keyProviders[keyProviderId] = keyProvider; - self.postMessage({ - 'type': 'init', - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'init', + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); break; } case 'keyProviderDispose': @@ -155,11 +160,13 @@ void main() async { final keyProviderId = msg['keyProviderId'] as String; logger.config('Dispose keyProvider $keyProviderId'); keyProviders.remove(keyProviderId); - self.postMessage({ - 'type': 'dispose', - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'dispose', + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); } break; case 'enable': @@ -172,12 +179,14 @@ void main() async { logger.config('Set enable $enabled for trackId ${cryptor.trackId}'); cryptor.setEnabled(enabled); } - self.postMessage({ - 'type': 'cryptorEnabled', - 'enable': enabled, - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'cryptorEnabled', + 'enable': enabled, + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); } break; case 'decode': @@ -192,21 +201,24 @@ void main() async { final keyProviderId = msg['keyProviderId'] as String; logger.config( - 'SetupTransform for kind $kind, trackId $trackId, participantId $participantId, ${readable.runtimeType} ${writable.runtimeType}}'); + 'SetupTransform for kind $kind, trackId $trackId, participantId $participantId, ${readable.runtimeType} ${writable.runtimeType}}', + ); final keyProvider = keyProviders[keyProviderId]; if (keyProvider == null) { logger.warning('KeyProvider not found for $keyProviderId'); - self.postMessage({ - 'type': 'cryptorSetup', - 'participantId': participantId, - 'trackId': trackId, - 'exist': exist, - 'operation': msgType, - 'error': 'KeyProvider not found', - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'cryptorSetup', + 'participantId': participantId, + 'trackId': trackId, + 'exist': exist, + 'operation': msgType, + 'error': 'KeyProvider not found', + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); return; } @@ -221,15 +233,17 @@ void main() async { isReuse: exist && msgType == 'decode', ); - self.postMessage({ - 'type': 'cryptorSetup', - 'participantId': participantId, - 'trackId': trackId, - 'exist': exist, - 'operation': msgType, - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'cryptorSetup', + 'participantId': participantId, + 'trackId': trackId, + 'exist': exist, + 'operation': msgType, + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); cryptor.lastError = CryptorError.kNew; } break; @@ -238,12 +252,14 @@ void main() async { final trackId = msg['trackId'] as String; logger.config('Removing trackId $trackId'); unsetCryptorParticipant(trackId); - self.postMessage({ - 'type': 'cryptorRemoved', - 'trackId': trackId, - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'cryptorRemoved', + 'trackId': trackId, + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); } break; case 'setKey': @@ -255,12 +271,14 @@ void main() async { final keyProvider = keyProviders[keyProviderId]; if (keyProvider == null) { logger.warning('KeyProvider not found for $keyProviderId'); - self.postMessage({ - 'type': 'setKey', - 'error': 'KeyProvider not found', - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'setKey', + 'error': 'KeyProvider not found', + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); return; } final keyProviderOptions = keyProvider.keyProviderOptions; @@ -273,14 +291,16 @@ void main() async { await keyProvider.getParticipantKeyHandler(participantId).setKey(key, keyIndex: keyIndex); } - self.postMessage({ - 'type': 'setKey', - 'participantId': msg['participantId'], - 'sharedKey': keyProviderOptions.sharedKey, - 'keyIndex': keyIndex, - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'setKey', + 'participantId': msg['participantId'], + 'sharedKey': keyProviderOptions.sharedKey, + 'keyIndex': keyIndex, + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); } break; case 'ratchetKey': @@ -292,12 +312,14 @@ void main() async { final keyProvider = keyProviders[keyProviderId]; if (keyProvider == null) { logger.warning('KeyProvider not found for $keyProviderId'); - self.postMessage({ - 'type': 'setKey', - 'error': 'KeyProvider not found', - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'setKey', + 'error': 'KeyProvider not found', + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); return; } final keyProviderOptions = keyProvider.keyProviderOptions; @@ -310,15 +332,17 @@ void main() async { newKey = await keyProvider.getParticipantKeyHandler(participantId).ratchetKey(keyIndex); } - self.postMessage({ - 'type': 'ratchetKey', - 'sharedKey': keyProviderOptions.sharedKey, - 'participantId': participantId, - 'newKey': newKey != null ? base64Encode(newKey) : '', - 'keyIndex': keyIndex, - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'ratchetKey', + 'sharedKey': keyProviderOptions.sharedKey, + 'participantId': participantId, + 'newKey': newKey != null ? base64Encode(newKey) : '', + 'keyIndex': keyIndex, + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); } break; case 'setKeyIndex': @@ -332,12 +356,14 @@ void main() async { c.setKeyIndex(keyIndex); } - self.postMessage({ - 'type': 'setKeyIndex', - 'keyIndex': keyIndex, - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'setKeyIndex', + 'keyIndex': keyIndex, + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); } break; case 'exportKey': @@ -349,12 +375,14 @@ void main() async { final keyProvider = keyProviders[keyProviderId]; if (keyProvider == null) { logger.warning('KeyProvider not found for $keyProviderId'); - self.postMessage({ - 'type': 'setKey', - 'error': 'KeyProvider not found', - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'setKey', + 'error': 'KeyProvider not found', + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); return; } final keyProviderOptions = keyProvider.keyProviderOptions; @@ -366,14 +394,16 @@ void main() async { logger.config('Export key for participant $participantId, keyIndex $keyIndex'); key = await keyProvider.getParticipantKeyHandler(participantId).exportKey(keyIndex); } - self.postMessage({ - 'type': 'exportKey', - 'participantId': participantId, - 'keyIndex': keyIndex, - 'exportedKey': key != null ? base64Encode(key) : '', - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'exportKey', + 'participantId': participantId, + 'keyIndex': keyIndex, + 'exportedKey': key != null ? base64Encode(key) : '', + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); } break; case 'setSifTrailer': @@ -383,12 +413,14 @@ void main() async { final keyProvider = keyProviders[keyProviderId]; if (keyProvider == null) { logger.warning('KeyProvider not found for $keyProviderId'); - self.postMessage({ - 'type': 'setKey', - 'error': 'KeyProvider not found', - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'setKey', + 'error': 'KeyProvider not found', + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); return; } keyProvider.setSifTrailer(sifTrailer); @@ -397,11 +429,13 @@ void main() async { c.setSifTrailer(sifTrailer); } - self.postMessage({ - 'type': 'setSifTrailer', - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'setSifTrailer', + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); } break; case 'updateCodec': @@ -412,11 +446,13 @@ void main() async { final cryptor = participantCryptors.firstWhereOrNull((c) => c.trackId == trackId); cryptor?.updateCodec(codec); - self.postMessage({ - 'type': 'updateCodec', - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'updateCodec', + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); } break; case 'dispose': @@ -426,20 +462,24 @@ void main() async { final cryptor = participantCryptors.firstWhereOrNull((c) => c.trackId == trackId); if (cryptor != null) { cryptor.lastError = CryptorError.kDisposed; - self.postMessage({ - 'type': 'cryptorDispose', - 'participantId': cryptor.participantIdentity, - 'trackId': trackId, - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'cryptorDispose', + 'participantId': cryptor.participantIdentity, + 'trackId': trackId, + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); } else { - self.postMessage({ - 'type': 'cryptorDispose', - 'error': 'cryptor not found', - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'cryptorDispose', + 'error': 'cryptor not found', + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); } } break; @@ -452,49 +492,58 @@ void main() async { final algorithmStr = msg['algorithm'] as String; final algorithm = Algorithm.values.firstWhereOrNull((a) => a.name == algorithmStr); if (algorithm == null) { - self.postMessage({ - 'type': 'dataCryptorEncrypt', - 'error': 'algorithm not found', - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'dataCryptorEncrypt', + 'error': 'algorithm not found', + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); return; } logger.config( - 'Encrypt for dataCryptorId $dataCryptorId, participantId $participantId, keyIndex $keyIndex, data length ${data.length}, algorithm $algorithmStr'); + 'Encrypt for dataCryptorId $dataCryptorId, participantId $participantId, keyIndex $keyIndex, data length ${data.length}, algorithm $algorithmStr', + ); final keyProviderId = msg['keyProviderId'] as String; final keyProvider = keyProviders[keyProviderId]; if (keyProvider == null) { logger.warning('KeyProvider not found for $keyProviderId'); - self.postMessage({ - 'type': 'dataCryptorEncrypt', - 'error': 'KeyProvider not found', - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'dataCryptorEncrypt', + 'error': 'KeyProvider not found', + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); return; } final cryptor = getDataPacketCryptor(participantId, dataCryptorId, keyProvider); try { final encryptedPacket = await cryptor.encrypt(cryptor.keyHandler, data); - self.postMessage({ - 'type': 'dataCryptorEncrypt', - 'participantId': participantId, - 'dataCryptorId': dataCryptorId, - 'data': encryptedPacket!.data, - 'keyIndex': encryptedPacket.keyIndex, - 'iv': encryptedPacket.iv, - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'dataCryptorEncrypt', + 'participantId': participantId, + 'dataCryptorId': dataCryptorId, + 'data': encryptedPacket!.data, + 'keyIndex': encryptedPacket.keyIndex, + 'iv': encryptedPacket.iv, + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); } catch (e) { logger.warning('Error encrypting data: $e'); - self.postMessage({ - 'type': 'dataCryptorEncrypt', - 'error': e.toString(), - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'dataCryptorEncrypt', + 'error': e.toString(), + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); } } break; @@ -508,53 +557,63 @@ void main() async { final algorithmStr = msg['algorithm'] as String; final algorithm = Algorithm.values.firstWhereOrNull((a) => a.name == algorithmStr); if (algorithm == null) { - self.postMessage({ - 'type': 'dataCryptorDecrypt', - 'error': 'algorithm not found', - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'dataCryptorDecrypt', + 'error': 'algorithm not found', + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); return; } logger.config( - 'Decrypt for dataCryptorId $dataCryptorId, participantId $participantId, keyIndex $keyIndex, data length ${data.length}, algorithm $algorithmStr'); + 'Decrypt for dataCryptorId $dataCryptorId, participantId $participantId, keyIndex $keyIndex, data length ${data.length}, algorithm $algorithmStr', + ); final keyProviderId = msg['keyProviderId'] as String; final keyProvider = keyProviders[keyProviderId]; if (keyProvider == null) { logger.warning('KeyProvider not found for $keyProviderId'); - self.postMessage({ - 'type': 'dataCryptorDecrypt', - 'error': 'KeyProvider not found', - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'dataCryptorDecrypt', + 'error': 'KeyProvider not found', + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); return; } final cryptor = getDataPacketCryptor(participantId, dataCryptorId, keyProvider); try { final decryptedData = await cryptor.decrypt( - cryptor.keyHandler, - EncryptedPacket( - data: data, - keyIndex: keyIndex, - iv: iv, - )); - self.postMessage({ - 'type': 'dataCryptorDecrypt', - 'participantId': participantId, - 'dataCryptorId': dataCryptorId, - 'data': decryptedData, - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + cryptor.keyHandler, + EncryptedPacket( + data: data, + keyIndex: keyIndex, + iv: iv, + ), + ); + self.postMessage( + { + 'type': 'dataCryptorDecrypt', + 'participantId': participantId, + 'dataCryptorId': dataCryptorId, + 'data': decryptedData, + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); } catch (e) { logger.warning('Error decrypting data: $e'); - self.postMessage({ - 'type': 'dataCryptorDecrypt', - 'error': e.toString(), - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'dataCryptorDecrypt', + 'error': e.toString(), + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); } } break; @@ -563,12 +622,14 @@ void main() async { final dataCryptorId = msg['dataCryptorId'] as String; logger.config('Dispose for dataCryptorId $dataCryptorId'); unsetDataPacketCryptorParticipant(dataCryptorId); - self.postMessage({ - 'type': 'dataCryptorDispose', - 'dataCryptorId': dataCryptorId, - 'msgId': msgId, - 'msgType': 'response', - }.jsify()); + self.postMessage( + { + 'type': 'dataCryptorDispose', + 'dataCryptorId': dataCryptorId, + 'msgId': msgId, + 'msgType': 'response', + }.jsify(), + ); } break; default: From b62d54a87bf043f353949a8f42686baeaaa41678 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Fri, 7 Aug 2026 15:37:11 -0400 Subject: [PATCH 3/3] feat: wire in the livekit_uniffi Rust core behind a native-only facade Establishes the development loop for calling into `livekit-uniffi` from this SDK, and exercises it with `buildVersion()` -- a synchronous, argument-free call returning a string, so a green test means the whole chain is intact: the build hook resolved a cdylib for the target, Native Assets bundled it, `@Native` bound the symbol, and a value crossed back. `lib/src/uniffi/` splits native/web with the same conditional-import pattern as the rest of the SDK. There is no dynamic library to load on the web, so `uniffi_io.dart` is the only file permitted to import the generated bindings -- reaching them from anywhere web-reachable pulls `dart:ffi` into a web compile and breaks `flutter build web`/`--wasm`. Callers branch on `LiveKitUniffi.isAvailable`. The facade is internal for now; nothing is added to the public API surface. `livekit_uniffi` is not on pub.dev, so both pubspecs override it to a sibling rust-sdks checkout -- overrides do not propagate from a dependency, hence both. Until it is published this branch cannot merge: CI has no rust-sdks checkout, so `flutter pub get` will fail there. Verified: 392 tests pass, web and wasm builds succeed, analyze and format are clean. --- .changes/uniffi-rust-core-dev-wiring | 1 + AGENTS.md | 22 ++++++++++++++ example/pubspec.yaml | 4 +++ lib/src/uniffi/uniffi.dart | 45 ++++++++++++++++++++++++++++ lib/src/uniffi/uniffi_io.dart | 23 ++++++++++++++ lib/src/uniffi/uniffi_web.dart | 27 +++++++++++++++++ pubspec.lock | 23 ++++++++++++++ pubspec.yaml | 11 +++++++ test/uniffi/uniffi_test.dart | 40 +++++++++++++++++++++++++ 9 files changed, 196 insertions(+) create mode 100644 .changes/uniffi-rust-core-dev-wiring create mode 100644 lib/src/uniffi/uniffi.dart create mode 100644 lib/src/uniffi/uniffi_io.dart create mode 100644 lib/src/uniffi/uniffi_web.dart create mode 100644 test/uniffi/uniffi_test.dart diff --git a/.changes/uniffi-rust-core-dev-wiring b/.changes/uniffi-rust-core-dev-wiring new file mode 100644 index 000000000..988c69b75 --- /dev/null +++ b/.changes/uniffi-rust-core-dev-wiring @@ -0,0 +1 @@ +patch type="added" "Wire in the livekit_uniffi Rust core behind a native-only facade, delivered as a bundled cdylib via Native Assets" diff --git a/AGENTS.md b/AGENTS.md index 474959144..5e7661b9e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,6 +32,28 @@ CI (`build.yaml`) runs all of the above plus example-app builds for every platfo Web/native divergence is handled with conditional imports (e.g. `track/processor_native.dart` vs `processor_web.dart`) — new platform-specific code should follow that pattern. +## The Rust core (`livekit_uniffi`) + +`lib/src/uniffi/` wraps `livekit_uniffi`, a Dart package generated from the `livekit-uniffi` crate in the sibling `rust-sdks` repo. It reaches Rust through Dart's Native Assets: the package's `hook/build.dart` bundles a `cdylib` into the host app and the generated bindings call into it with `@Native`. This is why the SDK requires Flutter >= 3.38 / Dart >= 3.10. + +There is no dynamic library to load on the web, so `uniffi.dart` splits native/web the same way the rest of the SDK does. **`uniffi_io.dart` is the only file allowed to import `package:livekit_uniffi/...`** — importing it from anywhere reachable on web pulls `dart:ffi` into a web compile and breaks `flutter build web`/`--wasm`. Guard calls with `LiveKitUniffi.isAvailable`. + +### Local development loop + +`livekit_uniffi` is not on pub.dev yet, so both `pubspec.yaml` and `example/pubspec.yaml` override it to a path in a sibling `rust-sdks` checkout (overrides don't propagate from a dependency, hence both). To produce or refresh it: + +```sh +cd ../rust-sdks/livekit-uniffi +cargo make dart-package # generates packages/dart/: bindings, pubspec, build hook, host cdylib +cd - +flutter pub get +flutter test test/uniffi/ # smoke test: calls buildVersion() across the FFI boundary +``` + +Requires `cargo-make`, `protoc` and `tera`. Re-run `cargo make dart-package` whenever the crate's exported surface changes — the build hook tracks the copied library, so a stale one won't be silently reused. + +Two things to know about that hook: it picks the locally built library purely on target *OS*, not architecture, so a host build can be bundled into an iOS or Android build by mistake — verify the desktop target first when debugging. And its download mode (used when no local library is present) fetches `build-.zip` from a `livekit-uniffi` GitHub release; no release currently carries those assets, so download mode fails until the `cdylib` job is re-enabled in `rust-sdks`. + ## Common pitfalls (from issue history) - `flutter_webrtc` is pinned to an exact version on purpose: livekit_client and flutter_webrtc must agree on the same WebRTC-SDK native pods, and mismatches break user builds (CocoaPods conflicts). Bump it only in sync with a matching WebRTC-SDK version. diff --git a/example/pubspec.yaml b/example/pubspec.yaml index f5ff1805e..c68cc9d5a 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -29,6 +29,10 @@ dependency_overrides: # Pin packages that use Apple APIs unavailable on CI runner's Xcode version. connectivity_plus: '>=7.0.0 <7.1.0' # NWPath.isUltraConstrained device_info_plus: '>=12.3.0 <12.4.0' # NSProcessInfo.isiOSAppOnVision + # Overrides do not propagate from a dependency, so livekit_client's override of + # livekit_uniffi has to be repeated here. Drop both once it is on pub.dev. + livekit_uniffi: + path: ../../rust-sdks/livekit-uniffi/packages/dart dev_dependencies: flutter_test: diff --git a/lib/src/uniffi/uniffi.dart b/lib/src/uniffi/uniffi.dart new file mode 100644 index 000000000..f5716c96b --- /dev/null +++ b/lib/src/uniffi/uniffi.dart @@ -0,0 +1,45 @@ +// Copyright 2026 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'uniffi_io.dart' if (dart.library.js_interop) 'uniffi_web.dart' as impl; + +/// Facade over the Rust core exposed by the `livekit_uniffi` package. +/// +/// `livekit_uniffi` reaches Rust through Dart's Native Assets: its build hook +/// bundles a `cdylib` into the host app and the generated bindings call into it +/// with `@Native`. None of that exists on the web, where there is no dynamic +/// library to load, so every entry point here is split native/web through the +/// same conditional-import pattern the rest of the SDK uses (see +/// `support/platform.dart`). Web builds must never reach the generated +/// bindings -- importing them at all would break `dart compile js`/`wasm`. +/// +/// Callers get [isAvailable] to branch on, and platform-specific code paths +/// stay out of the public API surface. +abstract final class LiveKitUniffi { + /// Whether the Rust core can be called on this platform. + /// + /// False on web. Every other member throws [UnsupportedError] when this is + /// false, rather than returning a silently wrong value. + static bool get isAvailable => impl.isAvailable; + + /// Version string reported by the Rust core. + /// + /// The simplest possible round trip -- a synchronous, argument-free call + /// returning a string -- so it doubles as the smoke test that the whole + /// chain is wired up: build hook resolved the library, `@Native` bound the + /// symbol, and a value came back across the FFI boundary. + /// + /// Throws [UnsupportedError] on web. + static String get buildVersion => impl.buildVersion(); +} diff --git a/lib/src/uniffi/uniffi_io.dart b/lib/src/uniffi/uniffi_io.dart new file mode 100644 index 000000000..983c5aaf2 --- /dev/null +++ b/lib/src/uniffi/uniffi_io.dart @@ -0,0 +1,23 @@ +// Copyright 2026 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:livekit_uniffi/livekit_uniffi.dart' as uniffi; + +/// Native implementation of [LiveKitUniffi]. See `uniffi.dart`. +/// +/// This is the only file in the SDK that may import the generated bindings: +/// the conditional import in `uniffi.dart` keeps it out of web builds. +const bool isAvailable = true; + +String buildVersion() => uniffi.buildVersion(); diff --git a/lib/src/uniffi/uniffi_web.dart b/lib/src/uniffi/uniffi_web.dart new file mode 100644 index 000000000..db2ab1968 --- /dev/null +++ b/lib/src/uniffi/uniffi_web.dart @@ -0,0 +1,27 @@ +// Copyright 2026 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// Web implementation of [LiveKitUniffi]. See `uniffi.dart`. +/// +/// Native Assets bundles a `cdylib`, which the web has no way to load, so the +/// Rust core is simply absent here. This file deliberately does not import +/// `package:livekit_uniffi/...` -- doing so would pull `dart:ffi` into a web +/// compile and fail the build. +const bool isAvailable = false; + +Never buildVersion() => throw UnsupportedError( + 'LiveKitUniffi.buildVersion is not available on web: the Rust core is ' + 'delivered as a native library. Guard calls with ' + 'LiveKitUniffi.isAvailable.', +); diff --git a/pubspec.lock b/pubspec.lock index 83c25ab59..0a8c0f7df 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.7.1" + archive: + dependency: transitive + description: + name: archive + sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff + url: "https://pub.dev" + source: hosted + version: "4.0.9" args: dependency: transitive description: @@ -424,6 +432,13 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.0" + livekit_uniffi: + dependency: "direct main" + description: + path: "../rust-sdks/livekit-uniffi/packages/dart" + relative: true + source: path + version: "0.1.7" logger: dependency: transitive description: @@ -616,6 +631,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.2" + posix: + dependency: transitive + description: + name: posix + sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e + url: "https://pub.dev" + source: hosted + version: "6.5.2" protobuf: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 9baebd151..5fe01e262 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -57,6 +57,17 @@ dependencies: flutter_webrtc: 1.6.0 dart_webrtc: ^1.8.0 + # Rust core (livekit-uniffi), delivered as a bundled cdylib via Native Assets. + # Native platforms only — see lib/src/uniffi/. + livekit_uniffi: ^0.1.7 + +# livekit_uniffi is not published to pub.dev yet, so it resolves out of a sibling +# rust-sdks checkout produced by `cargo make dart-package`. See AGENTS.md. Drop +# this once the package is published. +dependency_overrides: + livekit_uniffi: + path: ../rust-sdks/livekit-uniffi/packages/dart + dev_dependencies: flutter_test: sdk: flutter diff --git a/test/uniffi/uniffi_test.dart b/test/uniffi/uniffi_test.dart new file mode 100644 index 000000000..b0f6df841 --- /dev/null +++ b/test/uniffi/uniffi_test.dart @@ -0,0 +1,40 @@ +// Copyright 2026 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@TestOn('vm') +library; + +import 'package:flutter_test/flutter_test.dart'; + +import 'package:livekit_client/src/uniffi/uniffi.dart'; + +void main() { + // Exercises the whole delivery chain rather than any particular API: the + // build hook resolved a cdylib for this target, Native Assets bundled it, + // `@Native` bound the symbol, and a value crossed back from Rust. If the + // bindgen or the hook regresses, this is what fails first. + group('livekit_uniffi', () { + test('is available on native platforms', () { + expect(LiveKitUniffi.isAvailable, isTrue); + }); + + test('buildVersion returns the Rust core version', () { + final version = LiveKitUniffi.buildVersion; + expect(version, isNotEmpty); + // The crate stamps its own semver, so assert the shape rather than a + // literal that would need bumping on every livekit-uniffi release. + expect(version, matches(RegExp(r'^\d+\.\d+\.\d+'))); + }); + }); +}