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/.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/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/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/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 60916913a..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 @@ -881,7 +916,7 @@ class Room extends DisposableChangeNotifier with EventsEmittable { // localParticipant & remote participants final allParticipants = { - if (localParticipant != null) localParticipant!.sid: localParticipant!, + ?localParticipant?.sid: ?localParticipant, ..._remoteParticipants.bySid, }; @@ -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/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/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/pubspec.lock b/pubspec.lock index 57d525b41..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: @@ -460,10 +475,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: @@ -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: @@ -745,10 +768,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..5fe01e262 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: @@ -55,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/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 e63989328..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; @@ -258,19 +276,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()), ); @@ -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/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+'))); + }); + }); +} 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: