diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 05a110838..8121b9a02 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -77,6 +77,8 @@ jobs: channel: 'stable' - name: Install project dependencies run: flutter pub get + - name: Install xcode platform support for iOS + run: xcodebuild -downloadPlatform iOS - name: Build for iOS working-directory: ./example run: flutter build ios --release --no-codesign @@ -170,4 +172,4 @@ jobs: run: flutter pub get - name: Build for Web working-directory: ./example - run: flutter build web --wasm \ No newline at end of file + run: flutter build web --wasm diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ef1194ad..cb40bd338 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # CHANGELOG +## 2.5.0+hotfix.3 + +* fix: Fix @internal conflicting imports from package meta or flutter/foundation. + +## 2.5.0+hotfix.2 + +* hotfix: bump libwebrtc to m137.7151.03 to support H.265 (#837) +* feat: Expose ParticipantState (#848) +* chore: Update protobuf to v1.39.2 (#847) + +## 2.5.0+hotfix.1 + +* fix: Switch to livekit fork of noise for 16KB page support (#839) +* hotfix: bump libwebrtc to m137.7151.01 for android (#837) +* fix: tighten VideoTrackRenderer Widget (#695) + ## 2.5.0 * Bump flutter-webrtc to 1.0.0. diff --git a/android/build.gradle b/android/build.gradle index 52a0b0449..6ea991acd 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -52,7 +52,7 @@ android { dependencies { testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.mockito:mockito-core:5.0.0") - implementation 'io.github.webrtc-sdk:android:137.7151.01' + implementation 'io.github.webrtc-sdk:android:137.7151.03' implementation 'io.livekit:noise:2.0.0' } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index e03d73465..9f9f73831 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Tue Feb 15 15:44:59 JST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 94adc3a3f..f018a6181 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 10cf64010..6365f60e1 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Thu Nov 14 15:55:14 CST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/example/android/settings.gradle b/example/android/settings.gradle index c73e3b40a..06b2ef256 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -19,7 +19,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version '8.7.2' apply false - id "org.jetbrains.kotlin.android" version "1.8.22" apply false + id "org.jetbrains.kotlin.android" version "2.1.0" apply false } include ":app" diff --git a/example/lib/pages/connect.dart b/example/lib/pages/connect.dart index a45436ff7..4d9e831b6 100644 --- a/example/lib/pages/connect.dart +++ b/example/lib/pages/connect.dart @@ -329,7 +329,8 @@ class _ConnectPageState extends State { 'AV1', 'VP9', 'VP8', - 'H264' + 'H264', + 'H265' ].map>((String value) { return DropdownMenuItem( value: value, diff --git a/ios/livekit_client.podspec b/ios/livekit_client.podspec index dde89354e..bae4ad60a 100644 --- a/ios/livekit_client.podspec +++ b/ios/livekit_client.podspec @@ -16,6 +16,6 @@ Pod::Spec.new do |s| s.static_framework = true s.dependency 'Flutter' - s.dependency 'WebRTC-SDK', '137.7151.02' + s.dependency 'WebRTC-SDK', '137.7151.03' s.dependency 'flutter_webrtc' end diff --git a/lib/src/core/engine.dart b/lib/src/core/engine.dart index b5cdd2946..e8e09b6a3 100644 --- a/lib/src/core/engine.dart +++ b/lib/src/core/engine.dart @@ -16,9 +16,10 @@ import 'dart:async'; +import 'package:flutter/foundation.dart' hide internal; + import 'package:collection/collection.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import 'package:meta/meta.dart'; @@ -34,7 +35,6 @@ import '../proto/livekit_models.pb.dart' as lk_models; import '../proto/livekit_rtc.pb.dart' as lk_rtc; import '../publication/local.dart'; import '../support/disposable.dart'; -import '../support/platform.dart' show lkPlatformIsTest, lkPlatformIs, PlatformType; import '../support/region_url_provider.dart'; import '../support/websocket.dart'; import '../track/local/local.dart'; @@ -44,6 +44,9 @@ import '../types/other.dart'; import 'signal_client.dart'; import 'transport.dart'; +import '../support/platform.dart' + show lkPlatformIsTest, lkPlatformIs, PlatformType; + const maxRetryDelay = 7000; const defaultRetryDelaysInMs = [ diff --git a/lib/src/core/room.dart b/lib/src/core/room.dart index c6d5bc4f1..7a019d86d 100644 --- a/lib/src/core/room.dart +++ b/lib/src/core/room.dart @@ -14,7 +14,7 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; +import 'package:flutter/foundation.dart' hide internal; import 'package:collection/collection.dart'; import 'package:http/http.dart' as http; @@ -1102,17 +1102,17 @@ extension RoomHardwareManagementMethods on Room { final currentDeviceId = engine.roomOptions.defaultCameraCaptureOptions.deviceId; + // Always update roomOptions so future tracks use the correct device + engine.roomOptions = engine.roomOptions.copyWith( + defaultCameraCaptureOptions: roomOptions.defaultCameraCaptureOptions + .copyWith(deviceId: device.deviceId), + ); + try { if (track != null && selectedVideoInputDeviceId != device.deviceId) { await track.switchCamera(device.deviceId); Hardware.instance.selectedVideoInput = device; } - - // Always update roomOptions so future tracks use the correct device - engine.roomOptions = engine.roomOptions.copyWith( - defaultCameraCaptureOptions: roomOptions.defaultCameraCaptureOptions - .copyWith(deviceId: device.deviceId), - ); } catch (e) { // if the switching actually fails, reset it to the previous deviceId engine.roomOptions = engine.roomOptions.copyWith( diff --git a/lib/src/core/signal_client.dart b/lib/src/core/signal_client.dart index 21be53781..a25ed8f27 100644 --- a/lib/src/core/signal_client.dart +++ b/lib/src/core/signal_client.dart @@ -15,7 +15,7 @@ import 'dart:async'; import 'dart:collection'; -import 'package:flutter/foundation.dart'; +import 'package:flutter/foundation.dart' hide internal; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:fixnum/fixnum.dart'; diff --git a/lib/src/data_stream/stream_reader.dart b/lib/src/data_stream/stream_reader.dart index ba458b705..44dd300fc 100644 --- a/lib/src/data_stream/stream_reader.dart +++ b/lib/src/data_stream/stream_reader.dart @@ -18,8 +18,8 @@ abstract class BaseStreamReader { BaseStreamReader(T info, DataStreamController stream, this._totalByteSize) { - this.reader = stream; - this._info = info; + reader = stream; + _info = info; } void handleChunkReceived(DataStream_Chunk chunk); diff --git a/lib/src/data_stream/stream_writer.dart b/lib/src/data_stream/stream_writer.dart index 1d2832a80..bfb702cb4 100644 --- a/lib/src/data_stream/stream_writer.dart +++ b/lib/src/data_stream/stream_writer.dart @@ -24,7 +24,7 @@ class BaseStreamWriter { Future close() async { await writableStream.close(); - this.onClose?.call(); + onClose?.call(); } } diff --git a/lib/src/events.dart b/lib/src/events.dart index 31cbbaf09..a8e1b47bc 100644 --- a/lib/src/events.dart +++ b/lib/src/events.dart @@ -387,7 +387,8 @@ class ParticipantStateUpdatedEvent with RoomEvent, ParticipantEvent { /// [Pariticpant]'s [ConnectionQuality] has updated. /// Emitted by [Room] and [Participant]. -class ParticipantConnectionQualityUpdatedEvent with RoomEvent, ParticipantEvent { +class ParticipantConnectionQualityUpdatedEvent + with RoomEvent, ParticipantEvent { final Participant participant; final ConnectionQuality connectionQuality; const ParticipantConnectionQualityUpdatedEvent({ diff --git a/lib/src/managers/broadcast_manager.dart b/lib/src/managers/broadcast_manager.dart index 913d7a7b2..590bddb8b 100644 --- a/lib/src/managers/broadcast_manager.dart +++ b/lib/src/managers/broadcast_manager.dart @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:flutter/foundation.dart'; +import 'package:flutter/foundation.dart' hide internal; import 'package:meta/meta.dart'; diff --git a/lib/src/options.dart b/lib/src/options.dart index 1f64401d1..bd63f796d 100644 --- a/lib/src/options.dart +++ b/lib/src/options.dart @@ -356,7 +356,7 @@ class AudioPublishOptions extends PublishOptions { final backupCodecs = ['vp8', 'h264']; -final videoCodecs = ['vp8', 'h264', 'vp9', 'av1']; +final videoCodecs = ['vp8', 'h264', 'h265', 'vp9', 'av1']; bool isBackupCodec(String codec) { return backupCodecs.contains(codec.toLowerCase()); diff --git a/lib/src/participant/local.dart b/lib/src/participant/local.dart index 823513819..679e37fa9 100644 --- a/lib/src/participant/local.dart +++ b/lib/src/participant/local.dart @@ -18,7 +18,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:math'; -import 'package:flutter/foundation.dart'; +import 'package:flutter/foundation.dart' hide internal; import 'package:async/async.dart'; import 'package:fixnum/fixnum.dart'; diff --git a/lib/src/participant/participant.dart b/lib/src/participant/participant.dart index 1d83ea3d4..d36d84aec 100644 --- a/lib/src/participant/participant.dart +++ b/lib/src/participant/participant.dart @@ -39,8 +39,8 @@ import '../utils.dart'; /// Base for [RemoteParticipant] and [LocalParticipant], /// can not be instantiated directly. -abstract class Participant extends DisposableChangeNotifier - with EventsEmittable { +abstract class Participant + extends DisposableChangeNotifier with EventsEmittable { /// Reference to [Room] @internal final Room room; @@ -81,7 +81,8 @@ abstract class Participant extends DisposableChangeN ParticipantPermissions get permissions => _permissions; /// Attributes associated with the participant - UnmodifiableMapView get attributes => UnmodifiableMapView(_attributes); + UnmodifiableMapView get attributes => + UnmodifiableMapView(_attributes); Map _attributes = {}; // Participant state @@ -92,7 +93,8 @@ abstract class Participant extends DisposableChangeN DateTime get joinedAt { final pi = _participantInfo; if (pi != null) { - return DateTime.fromMillisecondsSinceEpoch(pi.joinedAt.toInt() * 1000, isUtc: true); + return DateTime.fromMillisecondsSinceEpoch(pi.joinedAt.toInt() * 1000, + isUtc: true); } return DateTime.now(); } @@ -191,10 +193,14 @@ abstract class Participant extends DisposableChangeN } void _setAttributes(Map attrs) { - final diff = mapDiff(_attributes, attrs).map((k, v) => MapEntry(k as String, v as String)); + final diff = mapDiff(_attributes, attrs) + .map((k, v) => MapEntry(k as String, v as String)); _attributes = attrs; if (diff.isNotEmpty) { - [events, room.events].emit(ParticipantAttributesChanged(participant: this, attributes: diff)); + [ + events, + room.events + ].emit(ParticipantAttributesChanged(participant: this, attributes: diff)); } } @@ -211,7 +217,9 @@ abstract class Participant extends DisposableChangeN @internal Future updateFromInfo(lk_models.ParticipantInfo info) async { logger.fine('LocalParticipant.updateFromInfo(info: $info)'); - if (_participantInfo != null && _participantInfo!.sid == info.sid && _participantInfo!.version > info.version) { + if (_participantInfo != null && + _participantInfo!.sid == info.sid && + _participantInfo!.version > info.version) { return false; } @@ -287,14 +295,19 @@ abstract class Participant extends DisposableChangeN T? getTrackPublicationBySource(TrackSource source) { if (source == TrackSource.unknown) return null; // try to find by source - final result = trackPublications.values.firstWhereOrNull((e) => e.source == source); + 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. @@ -304,17 +317,20 @@ abstract class Participant extends DisposableChangeN /// Convenience property to check whether [TrackSource.microphone] is published or not. bool isMicrophoneEnabled() { - return !(getTrackPublicationBySource(TrackSource.microphone)?.muted ?? true); + return !(getTrackPublicationBySource(TrackSource.microphone)?.muted ?? + true); } /// Convenience property to check whether [TrackSource.screenShareVideo] is published or not. bool isScreenShareEnabled() { - return !(getTrackPublicationBySource(TrackSource.screenShareVideo)?.muted ?? true); + return !(getTrackPublicationBySource(TrackSource.screenShareVideo)?.muted ?? + true); } /// Convenience property to check whether [TrackSource.screenShareAudio] is published or not. bool isScreenShareAudioEnabled() { - return !(getTrackPublicationBySource(TrackSource.screenShareAudio)?.muted ?? true); + return !(getTrackPublicationBySource(TrackSource.screenShareAudio)?.muted ?? + true); } /// (Equality operator) [Participant.hashCode] is same as [sid.hashCode]. diff --git a/lib/src/track/audio_visualizer.dart b/lib/src/track/audio_visualizer.dart index 9f4982ae0..0db094462 100644 --- a/lib/src/track/audio_visualizer.dart +++ b/lib/src/track/audio_visualizer.dart @@ -1,8 +1,8 @@ import 'package:uuid/uuid.dart' as uuid; -import '../support/disposable.dart'; import '../events.dart' show AudioVisualizerEvent; import '../managers/event.dart' show EventsEmittable; +import '../support/disposable.dart'; import 'local/local.dart' show AudioTrack; import 'audio_visualizer_native.dart' diff --git a/lib/src/track/local/local.dart b/lib/src/track/local/local.dart index d96a05fa0..c15636cd8 100644 --- a/lib/src/track/local/local.dart +++ b/lib/src/track/local/local.dart @@ -14,7 +14,7 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; +import 'package:flutter/foundation.dart' hide internal; import 'package:flutter/material.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; diff --git a/lib/src/track/local/video.dart b/lib/src/track/local/video.dart index ee06df1c3..63d566db5 100644 --- a/lib/src/track/local/video.dart +++ b/lib/src/track/local/video.dart @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; + +import 'package:collection/collection.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import '../../events.dart'; diff --git a/lib/src/track/track.dart b/lib/src/track/track.dart index c72f4dcc0..3c10ea94e 100644 --- a/lib/src/track/track.dart +++ b/lib/src/track/track.dart @@ -14,7 +14,7 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; +import 'package:flutter/foundation.dart' hide internal; import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import 'package:meta/meta.dart'; diff --git a/lib/src/types/participant_state.dart b/lib/src/types/participant_state.dart index 6691b5e5c..8f278686e 100644 --- a/lib/src/types/participant_state.dart +++ b/lib/src/types/participant_state.dart @@ -37,7 +37,8 @@ extension ParticipantStateExt on lk_models.ParticipantInfo_State { 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, + lk_models.ParticipantInfo_State.DISCONNECTED => + ParticipantState.disconnected, _ => ParticipantState.unknown, }; } diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 3a45de6a1..8a786a5d4 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -16,7 +16,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math' as math; -import 'package:flutter/foundation.dart'; +import 'package:flutter/foundation.dart' hide internal; import 'package:collection/collection.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; diff --git a/macos/livekit_client.podspec b/macos/livekit_client.podspec index 285fa6420..5000c68cc 100644 --- a/macos/livekit_client.podspec +++ b/macos/livekit_client.podspec @@ -16,6 +16,6 @@ Pod::Spec.new do |s| s.static_framework = true s.dependency 'FlutterMacOS' - s.dependency 'WebRTC-SDK', '137.7151.02' + s.dependency 'WebRTC-SDK', '137.7151.03' s.dependency 'flutter_webrtc' end diff --git a/pubspec.yaml b/pubspec.yaml index 1bd520538..17b6d38a2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ name: livekit_client description: Flutter Client SDK for LiveKit. Build real-time video and audio into your apps. Supports iOS, Android, and Web. -version: 2.5.0 +version: 2.5.0+hotfix.3 homepage: https://github.com/livekit/client-sdk-flutter environment: @@ -37,7 +37,7 @@ dependencies: uuid: ^4.5.1 synchronized: ^3.0.0+3 protobuf: ^4.1.0 - flutter_webrtc: ^1.0.0 + flutter_webrtc: ^1.1.0 device_info_plus: ^11.3.0 dart_webrtc: ^1.5.3+hotfix.5 sdp_transform: ^0.3.2 diff --git a/test/core/data_stream_test.dart b/test/core/data_stream_test.dart index 046722b1b..e550e31aa 100644 --- a/test/core/data_stream_test.dart +++ b/test/core/data_stream_test.dart @@ -20,7 +20,6 @@ import 'dart:io'; import 'dart:math'; import 'package:flutter_test/flutter_test.dart'; -import 'package:uuid/uuid.dart' show Uuid; import 'package:livekit_client/livekit_client.dart'; import '../mock/e2e_container.dart'; @@ -97,12 +96,8 @@ void main() { }); }); - final streamId = Uuid().v4(); var stream = await room.localParticipant?.streamText(StreamTextOptions( topic: 'chat-stream', - streamId: streamId, - totalSize: 10000, - attachedStreamIds: [], )); await stream?.write('a' * 10); await stream?.write('b' * 10); @@ -202,10 +197,8 @@ void main() { 'bytes content = ${content}, \n string content = ${utf8.decode(content)}'); }); - final streamId = Uuid().v4(); var stream = await room.localParticipant?.streamBytes(StreamBytesOptions( topic: 'bytes-stream', - streamId: streamId, totalSize: 30, )); await stream?.write(utf8.encode('a' * 10)); diff --git a/test/core/room_e2e_test.dart b/test/core/room_e2e_test.dart index caf4def7a..4fb51e595 100644 --- a/test/core/room_e2e_test.dart +++ b/test/core/room_e2e_test.dart @@ -52,6 +52,8 @@ void main() { expect( room.events.streamCtrl.stream, emitsInOrder([ + predicate( + (event) => event.participant.sid == remoteParticipantData.sid), predicate( (event) => event.participant.sid == remoteParticipantData.sid, ),