diff --git a/.flutter-plugins b/.flutter-plugins index e05e7945..9da64673 100644 --- a/.flutter-plugins +++ b/.flutter-plugins @@ -1,5 +1,5 @@ # This is a generated file; do not edit or check into version control. -flutter_webrtc=/Users/utopia/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.36/ +flutter_webrtc=/Users/utopia/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.47/ path_provider=/Users/utopia/.pub-cache/hosted/pub.dev/path_provider-2.0.15/ path_provider_android=/Users/utopia/.pub-cache/hosted/pub.dev/path_provider_android-2.0.27/ path_provider_foundation=/Users/utopia/.pub-cache/hosted/pub.dev/path_provider_foundation-2.2.3/ diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies index 33a8db6f..bbe050e3 100644 --- a/.flutter-plugins-dependencies +++ b/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_webrtc","path":"/Users/utopia/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.36/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/utopia/.pub-cache/hosted/pub.dev/path_provider_foundation-2.2.3/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"android":[{"name":"flutter_webrtc","path":"/Users/utopia/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.36/","native_build":true,"dependencies":[]},{"name":"path_provider_android","path":"/Users/utopia/.pub-cache/hosted/pub.dev/path_provider_android-2.0.27/","native_build":true,"dependencies":[]}],"macos":[{"name":"flutter_webrtc","path":"/Users/utopia/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.36/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/utopia/.pub-cache/hosted/pub.dev/path_provider_foundation-2.2.3/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"linux":[{"name":"flutter_webrtc","path":"/Users/utopia/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.36/","native_build":true,"dependencies":[]},{"name":"path_provider_linux","path":"/Users/utopia/.pub-cache/hosted/pub.dev/path_provider_linux-2.1.11/","native_build":false,"dependencies":[]}],"windows":[{"name":"flutter_webrtc","path":"/Users/utopia/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.36/","native_build":true,"dependencies":[]},{"name":"path_provider_windows","path":"/Users/utopia/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.7/","native_build":false,"dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"flutter_webrtc","dependencies":["path_provider"]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]}],"date_created":"2023-07-14 02:03:07.751712","version":"3.10.6"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_webrtc","path":"/Users/utopia/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.47/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/utopia/.pub-cache/hosted/pub.dev/path_provider_foundation-2.2.3/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"android":[{"name":"flutter_webrtc","path":"/Users/utopia/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.47/","native_build":true,"dependencies":[]},{"name":"path_provider_android","path":"/Users/utopia/.pub-cache/hosted/pub.dev/path_provider_android-2.0.27/","native_build":true,"dependencies":[]}],"macos":[{"name":"flutter_webrtc","path":"/Users/utopia/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.47/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/utopia/.pub-cache/hosted/pub.dev/path_provider_foundation-2.2.3/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"linux":[{"name":"flutter_webrtc","path":"/Users/utopia/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.47/","native_build":true,"dependencies":[]},{"name":"path_provider_linux","path":"/Users/utopia/.pub-cache/hosted/pub.dev/path_provider_linux-2.1.11/","native_build":false,"dependencies":[]}],"windows":[{"name":"flutter_webrtc","path":"/Users/utopia/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.47/","native_build":true,"dependencies":[]},{"name":"path_provider_windows","path":"/Users/utopia/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.7/","native_build":false,"dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"flutter_webrtc","dependencies":["path_provider"]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]}],"date_created":"2024-01-31 15:41:22.301956","version":"3.16.0"} \ No newline at end of file diff --git a/example/ios/Podfile b/example/ios/Podfile index 77689070..79de4206 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '11.0' +platform :ios, '17.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index ecb25d8c..ccb682fd 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,16 +1,30 @@ PODS: - Flutter (1.0.0) - - flutter_webrtc (0.9.4): + - flutter_foreground_task (0.0.1): - Flutter - - WebRTC-SDK (= 104.5112.02) - - path_provider_ios (0.0.1): + - flutter_webrtc (0.9.36): - Flutter - - WebRTC-SDK (104.5112.02) + - WebRTC-SDK (= 114.5735.08) + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - permission_handler_apple (9.1.1): + - Flutter + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - shared_preferences_ios (0.0.1): + - Flutter + - WebRTC-SDK (114.5735.08) DEPENDENCIES: - Flutter (from `Flutter`) + - flutter_foreground_task (from `.symlinks/plugins/flutter_foreground_task/ios`) - flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`) - - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) SPEC REPOS: trunk: @@ -19,17 +33,29 @@ SPEC REPOS: EXTERNAL SOURCES: Flutter: :path: Flutter + flutter_foreground_task: + :path: ".symlinks/plugins/flutter_foreground_task/ios" flutter_webrtc: :path: ".symlinks/plugins/flutter_webrtc/ios" - path_provider_ios: - :path: ".symlinks/plugins/path_provider_ios/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + permission_handler_apple: + :path: ".symlinks/plugins/permission_handler_apple/ios" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + shared_preferences_ios: + :path: ".symlinks/plugins/shared_preferences_ios/ios" SPEC CHECKSUMS: Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 - flutter_webrtc: aa130dfe1eca6625c2e2e51ce830abb495bdb06e - path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 - WebRTC-SDK: e0589abeb63db07a4ca1f45c82ba0f1a72e61622 + flutter_foreground_task: 21ef182ab0a29a3005cc72cd70e5f45cb7f7f817 + flutter_webrtc: 55df3aaa802114dad390191a46c2c8d535751268 + path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 + permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 + shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 + shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad + WebRTC-SDK: c24d2a6c9f571f2ed42297cb8ffba9557093142b -PODFILE CHECKSUM: 523ef59dddb81e454a2bfcc3b8d1d59094ed14b9 +PODFILE CHECKSUM: 87ebe17c0a601b2b23484129373d6100af2eb36d COCOAPODS: 1.11.3 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 5de65dc2..f403d0cb 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -155,7 +155,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -216,10 +216,12 @@ }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -230,6 +232,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c87d15a3..a6b826db 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ UIViewControllerBasedStatusBarAppearance + UIApplicationSupportsIndirectInputEvents + diff --git a/example/lib/typed_examples/audio_bridge.dart b/example/lib/typed_examples/audio_bridge.dart index e9a41d30..988656a2 100644 --- a/example/lib/typed_examples/audio_bridge.dart +++ b/example/lib/typed_examples/audio_bridge.dart @@ -57,7 +57,7 @@ class _AudioRoomState extends State { // await navigator.mediaDevices.enumerateDevices(); // MediaDeviceInfo microphone = // devices.firstWhere((element) => element.kind == "audioinput"); - await pluginHandle?.initializeMediaDevices(mediaConstraints: {"audio": true, "video": false}); + await pluginHandle?.initializeMediaDevices(context: context, mediaConstraints: {"audio": true, "video": false}); pluginHandle?.joinRoom(myRoom, display: "Shivansh"); // await Helper.selectAudioInput(microphone.deviceId); diff --git a/example/lib/typed_examples/google_meet.dart b/example/lib/typed_examples/google_meet.dart index fb725b04..7b7bfec7 100644 --- a/example/lib/typed_examples/google_meet.dart +++ b/example/lib/typed_examples/google_meet.dart @@ -49,6 +49,9 @@ class _VideoRoomState extends State { super.didChangeDependencies(); if (mounted) { await initialize(); + selectedAudioInputDeviceId = (await audioInputs).first.deviceId; + selectedAudioOutputDeviceId = (await audioOutputs).first.deviceId; + selectedVideoInputDeviceId = (await videoInputs).first.deviceId; } } @@ -134,11 +137,14 @@ class _VideoRoomState extends State { int? feedId = videoState.subStreamsToFeedIdMap[event.mid]?['feed_id']; String? displayName = videoState.feedIdToDisplayStreamsMap[feedId]?['display']; if (feedId != null) { - if (videoState.streamsToBeRendered.containsKey(feedId.toString()) && event.flowing == true && event.track?.kind == "audio") { - var existingRenderer = videoState.streamsToBeRendered[feedId.toString()]; - existingRenderer?.mediaStream?.addTrack(event.track!); - existingRenderer?.videoRenderer.srcObject = existingRenderer.mediaStream; - existingRenderer?.videoRenderer.muted = false; + var existingRenderer = videoState.streamsToBeRendered[feedId.toString()]; + if (existingRenderer != null && event.track?.kind == "audio") { + existingRenderer.mediaStream?.addTrack(event.track!); + existingRenderer.videoRenderer.srcObject = existingRenderer.mediaStream; + if (selectedAudioOutputDeviceId != null) { + existingRenderer.videoRenderer.audioOutput(selectedAudioOutputDeviceId!); + } + existingRenderer.videoRenderer.muted = false; setState(() {}); } if (!videoState.streamsToBeRendered.containsKey(feedId.toString()) && event.flowing == true && event.track?.kind == "video") { @@ -203,7 +209,7 @@ class _VideoRoomState extends State { } manageMuteUIEvents(String mid, String kind, bool muted) async { - int? feedId = videoState.subStreamsToFeedIdMap[mid]?['feed_id']; + int? feedId = videoState.subStreamsToFeedIdMap?[mid]?['feed_id']; if (feedId == null) { return; } @@ -281,17 +287,27 @@ class _VideoRoomState extends State { videoPlugin = await attachPlugin(pop: true); eventMessagesHandler(); await localVideoRenderer.init(); - localVideoRenderer.mediaStream = await videoPlugin?.initializeMediaDevices(simulcastSendEncodings: [ + + localVideoRenderer.mediaStream = await videoPlugin?.initializeMediaDevices(context: context, + simulcastSendEncodings: [ RTCRtpEncoding(active: true, rid: 'h',scalabilityMode: 'L1T2',maxBitrate: 2000000,numTemporalLayers: 0, minBitrate: 1000000, ), RTCRtpEncoding(active: true, rid: 'm',scalabilityMode: 'L1T2', maxBitrate: 1000000,scaleResolutionDownBy: 2), RTCRtpEncoding(active: true, rid: 'l',scalabilityMode: 'L1T2', maxBitrate: 524288, scaleResolutionDownBy: 2), - ], mediaConstraints: { - 'video': { - 'width': {'ideal': 1280}, - 'height': {'ideal': 720} - }, - 'audio': true + ], + mediaConstraints: { + if (selectedAudioInputDeviceId != null) + 'audio': { + 'deviceId': {'exact': selectedAudioInputDeviceId}, + }, + if (selectedVideoInputDeviceId != null) + 'video': { + 'deviceId': {'exact': selectedVideoInputDeviceId}, + } }); + if (!WebRTC.platformIsWeb) { + await Helper.selectAudioInput(selectedAudioInputDeviceId!); + // await Helper. + } localVideoRenderer.videoRenderer.srcObject = localVideoRenderer.mediaStream; localVideoRenderer.publisherName = "You"; localVideoRenderer.publisherId = myId.toString(); @@ -327,7 +343,7 @@ class _VideoRoomState extends State { }); await localScreenSharingRenderer.init(); localScreenSharingRenderer.publisherId = myId.toString(); - localScreenSharingRenderer.mediaStream = await screenPlugin?.initializeMediaDevices(mediaConstraints: { + localScreenSharingRenderer.mediaStream = await screenPlugin?.initializeMediaDevices(context: context,mediaConstraints: { 'video': true, 'audio': true }, useDisplayMediaDevices: true); @@ -407,26 +423,137 @@ class _VideoRoomState extends State { remotePlugin = null; } + var audioInputs = Helper.enumerateDevices('audioinput'); + var audioOutputs = Helper.enumerateDevices('audiooutput'); + var videoInputs = Helper.enumerateDevices('videoinput'); + String? selectedAudioInputDeviceId; + String? selectedAudioOutputDeviceId; + String? selectedVideoInputDeviceId; + dynamic settingsDialog; + + Future showSettingsDialog() async { + settingsDialog = await showDialog( + context: context, + barrierDismissible: false, + builder: (context) { + return StatefulBuilder(builder: ((context, setState) { + return AlertDialog( + title: Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Text('Settings'), + IconButton( + onPressed: () { + Navigator.of(context).pop(this); + }, + icon: Icon(Icons.close)) + ]), + actionsAlignment: MainAxisAlignment.start, + actions: [], + insetPadding: EdgeInsets.zero, + scrollable: true, + content: Form( + key: joinForm, + child: Column(children: [ + Text('Video'), + Divider(), + FutureBuilder>( + builder: (context, snapshot) { + if (snapshot.data != null) { + return DropdownButtonFormField( + decoration: InputDecoration(label: Text('Video Input Device')), + value: selectedVideoInputDeviceId, + items: snapshot.data?.map((e) => DropdownMenuItem(value: e.deviceId, child: Text('${e.label}'))).toList(), + onChanged: (value) async { + setState(() { + selectedVideoInputDeviceId = value; + }); + }); + } + return CircularProgressIndicator(); + }, + future: videoInputs, + ), + Padding( + padding: EdgeInsets.symmetric(vertical: 10), + ), + Text('Audio'), + Divider(), + FutureBuilder>( + builder: (context, snapshot) { + if (snapshot.data != null) { + return DropdownButtonFormField( + decoration: InputDecoration(label: Text('Audio Input Device')), + value: selectedAudioInputDeviceId, + items: snapshot.data?.map((e) => DropdownMenuItem(value: e.deviceId, child: Text('${e.label}'))).toList(), + onChanged: (value) async { + setState(() { + selectedAudioInputDeviceId = value; + }); + }); + } + return CircularProgressIndicator(); + }, + future: audioInputs, + ), + FutureBuilder>( + builder: (context, snapshot) { + if (snapshot.data != null) { + return DropdownButtonFormField( + decoration: InputDecoration(label: Text('Audio Output Device')), + value: selectedAudioOutputDeviceId, + items: snapshot.data?.map((e) => DropdownMenuItem(value: e.deviceId, child: Text('${e.label}'))).toList(), + onChanged: (value) async { + print(value); + setState(() { + selectedAudioOutputDeviceId = value; + }); + if (!WebRTC.platformIsWeb) { + await Helper.selectAudioOutput(selectedAudioOutputDeviceId!); + } + }); + } + return CircularProgressIndicator(); + }, + future: audioOutputs, + ) + ]), + ), + ); + })); + }); + } + Future showJoiningDialog() async { joiningDialog = await showDialog( context: context, + barrierDismissible: false, builder: (context) { return StatefulBuilder(builder: ((context, setState) { return AlertDialog( - actions: [ - TextButton( - onPressed: () async { - if (joinForm.currentState?.validate() == true) { - myRoom = int.parse(room.text); - myPin = pin.text; - myUsername = username.text; - setState(() { - this.joined = true; - }); - await joinRoom(); - } + title: Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Text('Join Room'), + IconButton( + onPressed: () { + Navigator.of(context).pop(this); }, - child: Text('Join')) + icon: Icon(Icons.close)) + ]), + actionsAlignment: MainAxisAlignment.start, + actions: [ + Center( + child: TextButton( + onPressed: () async { + if (joinForm.currentState?.validate() == true) { + myRoom = int.parse(room.text); + myPin = pin.text; + myUsername = username.text; + setState(() { + this.joined = true; + }); + await joinRoom(); + } + }, + child: Text('Join')), + ) ], insetPadding: EdgeInsets.zero, scrollable: true, @@ -451,7 +578,7 @@ class _VideoRoomState extends State { controller: pin, obscureText: true, decoration: InputDecoration(label: Text('Pin')), - ) + ), ]), ), ); @@ -530,7 +657,13 @@ class _VideoRoomState extends State { Icons.switch_camera, color: Colors.white, ), - onPressed: joined ? switchCamera : null) + onPressed: joined ? switchCamera : null), + IconButton( + icon: Icon( + Icons.settings, + color: Colors.white, + ), + onPressed: showSettingsDialog) ], title: const Text('google meet clone'), ), diff --git a/example/lib/typed_examples/sip.dart b/example/lib/typed_examples/sip.dart index 49fd344d..75f2be8d 100644 --- a/example/lib/typed_examples/sip.dart +++ b/example/lib/typed_examples/sip.dart @@ -36,7 +36,7 @@ class _SipExampleState extends State { dynamic _setState; Future localMediaSetup() async { - MediaStream? temp = await sip?.initializeMediaDevices(mediaConstraints: {'audio': true, 'video': false}); + MediaStream? temp = await sip?.initializeMediaDevices(context: context, mediaConstraints: {'audio': true, 'video': false}); localStream = temp; } diff --git a/example/lib/typed_examples/video_call.dart b/example/lib/typed_examples/video_call.dart index ac32eeab..32c7fff6 100644 --- a/example/lib/typed_examples/video_call.dart +++ b/example/lib/typed_examples/video_call.dart @@ -74,7 +74,7 @@ class _VideoCallV2ExampleState extends State { Future localMediaSetup() async { await _localRenderer.initialize(); await publishVideo.initDataChannel(); - await publishVideo.initializeMediaDevices(mediaConstraints: {'audio': true, 'video': true}); + await publishVideo.initializeMediaDevices(context: context, mediaConstraints: {'audio': true, 'video': true}); _localRenderer.srcObject = publishVideo.webRTCHandle?.localStream; } diff --git a/example/pubspec.lock b/example/pubspec.lock index be6550c7..1b088f06 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -69,10 +69,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.18.0" convert: dependency: transitive description: @@ -109,10 +109,10 @@ packages: dependency: transitive description: name: dart_webrtc - sha256: "3f581ea799829fabd6e0b99bd2210146e4d107c7b3ac8495af3510737a5c5c1a" + sha256: "5897a3bdd6c7fded07e80e250260ca4c9cd61f9080911aa308b516e1206745a9" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.3" dartdoc: dependency: transitive description: @@ -172,10 +172,10 @@ packages: dependency: transitive description: name: flutter_webrtc - sha256: f7e3ee080638db1793109a2ca4f1391413907057cbee46a7f9bd1dc2a636d1cd + sha256: "577216727181cb13776a65d3e7cb33e783e740c5496335011aed4a038b28c3fe" url: "https://pub.dev" source: hosted - version: "0.9.36" + version: "0.9.47" glob: dependency: transitive description: @@ -243,26 +243,26 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" package_config: dependency: transitive description: @@ -488,26 +488,26 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -528,10 +528,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.1" typed_data: dependency: transitive description: @@ -564,6 +564,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + url: "https://pub.dev" + source: hosted + version: "0.3.0" web_socket_channel: dependency: transitive description: @@ -576,10 +584,10 @@ packages: dependency: transitive description: name: webrtc_interface - sha256: "0dd96f4d7fb6ba9895930644cebd3f1adb5179caa83cb1760061b2fe9cba5aad" + sha256: "2efbd3e4e5ebeb2914253bcc51dafd3053c4b87b43f3076c74835a9deecbae3a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.2" win32: dependency: transitive description: @@ -605,5 +613,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.0.0 <3.10.6" + dart: ">=3.2.0-194.0.dev <3.10.6" flutter: ">=3.3.0" diff --git a/example/windows/runner/CMakeLists.txt b/example/windows/runner/CMakeLists.txt index b9e550fb..17411a8a 100644 --- a/example/windows/runner/CMakeLists.txt +++ b/example/windows/runner/CMakeLists.txt @@ -20,6 +20,13 @@ add_executable(${BINARY_NAME} WIN32 # that need different build settings. apply_standard_settings(${BINARY_NAME}) +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + # Disable Windows macros that collide with C++ standard library functions. target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") diff --git a/example/windows/runner/Runner.rc b/example/windows/runner/Runner.rc index 5fdea291..0f5c0857 100644 --- a/example/windows/runner/Runner.rc +++ b/example/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD #else -#define VERSION_AS_NUMBER 1,0,0 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif diff --git a/lib/janus_client.dart b/lib/janus_client.dart index 9862b4e2..80e527ce 100644 --- a/lib/janus_client.dart +++ b/lib/janus_client.dart @@ -1,7 +1,9 @@ /// This is a preliminary API providing most WebRTC Operations out of the box using [Janus Server](https://janus.conf.meetecho.com/) + library janus_client; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'dart:async'; import 'dart:io'; @@ -128,6 +130,8 @@ part './interfaces/sip/events/sip_proceeding_event.dart'; part './interfaces/sip/events/sip_calling_event.dart'; +part './widgets/screen_select_dialog.dart'; + class JanusClient { late JanusTransport _transport; String? _apiSecret; diff --git a/lib/janus_plugin.dart b/lib/janus_plugin.dart index edc42458..7080027d 100644 --- a/lib/janus_plugin.dart +++ b/lib/janus_plugin.dart @@ -412,6 +412,40 @@ class JanusPlugin { } } + /// helper method for managing screen capture on web and desktop platforms + Future getDisplayMediaStream(BuildContext context) async { + MediaStream? screenStream; + if (WebRTC.platformIsDesktop) { + final source = await showDialog( + context: context, + builder: (context) => ScreenSelectDialog(), + ); + if (source != null) { + try { + var stream = await navigator.mediaDevices.getDisplayMedia({ + 'video': { + 'deviceId': {'exact': source.id}, + 'mandatory': {'frameRate': 30.0} + }, + 'audio': true + }); + stream.getVideoTracks()[0].onEnded = () { + print('By adding a listener on onEnded you can: 1) catch stop video sharing on Web'); + }; + screenStream = stream; + } catch (e) { + print(e); + } + } + } else if (WebRTC.platformIsWeb) { + screenStream = await navigator.mediaDevices.getDisplayMedia({ + 'audio': false, + 'video': true, + }); + } + return screenStream; + } + ///Helper method that generates MediaStream from your device camera that will be automatically added to peer connection instance internally used by janus client /// /// [useDisplayMediaDevices] : setting this true will give you capabilities to stream your device screen over PeerConnection.
@@ -419,7 +453,8 @@ class JanusPlugin { /// [simulcastSendEncodings] : this list is used to specify encoding for simulcasting or (svc if room codec is vp9)
/// you can use this method to get the stream and show live preview of your camera to RTCVideoRendererView

/// keep in mind this method exist to help in getting started with this library quickly,educational purposes or for basic functionalities, for custom use cases it is recommended to rely on your own implementation of this method using PeerConnection - Future initializeMediaDevices({bool? useDisplayMediaDevices = false, List? simulcastSendEncodings, Map? mediaConstraints}) async { + Future initializeMediaDevices( + {bool? useDisplayMediaDevices = false, required BuildContext context, List? simulcastSendEncodings, Map? mediaConstraints}) async { await _disposeMediaStreams(ignoreRemote: true); List videoDevices = await getVideoInputDevices(); List audioDevices = await getAudioInputDevices(); @@ -443,7 +478,7 @@ class JanusPlugin { _context._logger.fine(mediaConstraints); if (webRTCHandle != null) { if (useDisplayMediaDevices == true) { - webRTCHandle!.localStream = await navigator.mediaDevices.getDisplayMedia(mediaConstraints); + webRTCHandle!.localStream = await getDisplayMediaStream(context); } else { webRTCHandle!.localStream = await navigator.mediaDevices.getUserMedia(mediaConstraints); } diff --git a/lib/widgets/screen_select_dialog.dart b/lib/widgets/screen_select_dialog.dart new file mode 100644 index 00000000..a9bea54d --- /dev/null +++ b/lib/widgets/screen_select_dialog.dart @@ -0,0 +1,279 @@ +part of janus_client; + +class ThumbnailWidget extends StatefulWidget { + const ThumbnailWidget({Key? key, required this.source, required this.selected, required this.onTap}) : super(key: key); + final DesktopCapturerSource source; + final bool selected; + final Function(DesktopCapturerSource) onTap; + + @override + _ThumbnailWidgetState createState() => _ThumbnailWidgetState(); +} + +class _ThumbnailWidgetState extends State { + final List _subscriptions = []; + + @override + void initState() { + super.initState(); + _subscriptions.add(widget.source.onThumbnailChanged.stream.listen((event) { + setState(() {}); + })); + _subscriptions.add(widget.source.onNameChanged.stream.listen((event) { + setState(() {}); + })); + } + + @override + void deactivate() { + _subscriptions.forEach((element) { + element.cancel(); + }); + super.deactivate(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Expanded( + child: Container( + decoration: widget.selected ? BoxDecoration(border: Border.all(width: 2, color: Colors.blueAccent)) : null, + child: InkWell( + onTap: () { + print('Selected source id => ${widget.source.id}'); + widget.onTap(widget.source); + }, + child: widget.source.thumbnail != null + ? Image.memory( + widget.source.thumbnail!, + gaplessPlayback: true, + alignment: Alignment.center, + ) + : Container(), + ), + )), + Text( + widget.source.name, + style: TextStyle(fontSize: 12, color: Colors.black87, fontWeight: widget.selected ? FontWeight.bold : FontWeight.normal), + ), + ], + ); + } +} + +// ignore: must_be_immutable +class ScreenSelectDialog extends Dialog { + ScreenSelectDialog() { + Future.delayed(Duration(milliseconds: 100), () { + _getSources(); + }); + _subscriptions.add(desktopCapturer.onAdded.stream.listen((source) { + _sources[source.id] = source; + _stateSetter?.call(() {}); + })); + + _subscriptions.add(desktopCapturer.onRemoved.stream.listen((source) { + _sources.remove(source.id); + _stateSetter?.call(() {}); + })); + + _subscriptions.add(desktopCapturer.onThumbnailChanged.stream.listen((source) { + _stateSetter?.call(() {}); + })); + } + final Map _sources = {}; + SourceType _sourceType = SourceType.Screen; + DesktopCapturerSource? _selected_source; + final List> _subscriptions = []; + StateSetter? _stateSetter; + Timer? _timer; + + void _ok(context) async { + _timer?.cancel(); + _subscriptions.forEach((element) { + element.cancel(); + }); + Navigator.pop(context, _selected_source); + } + + void _cancel(context) async { + _timer?.cancel(); + _subscriptions.forEach((element) { + element.cancel(); + }); + Navigator.pop(context, null); + } + + Future _getSources() async { + try { + var sources = await desktopCapturer.getSources(types: [_sourceType]); + sources.forEach((element) { + print('name: ${element.name}, id: ${element.id}, type: ${element.type}'); + }); + _timer?.cancel(); + _timer = Timer.periodic(Duration(seconds: 3), (timer) { + desktopCapturer.updateSources(types: [_sourceType]); + }); + _sources.clear(); + sources.forEach((element) { + _sources[element.id] = element; + }); + _stateSetter?.call(() {}); + return; + } catch (e) { + print(e.toString()); + } + } + + @override + Widget build(BuildContext context) { + return Material( + type: MaterialType.transparency, + child: Center( + child: Container( + width: 640, + height: 560, + color: Colors.white, + child: Column( + children: [ + Padding( + padding: EdgeInsets.all(10), + child: Stack( + children: [ + Align( + alignment: Alignment.topLeft, + child: Text( + 'Choose what to share', + style: TextStyle(fontSize: 16, color: Colors.black87), + ), + ), + Align( + alignment: Alignment.topRight, + child: InkWell( + child: Icon(Icons.close), + onTap: () => _cancel(context), + ), + ), + ], + ), + ), + Expanded( + flex: 1, + child: Container( + width: double.infinity, + padding: EdgeInsets.all(10), + child: StatefulBuilder( + builder: (context, setState) { + _stateSetter = setState; + return DefaultTabController( + length: 2, + child: Column( + children: [ + Container( + constraints: BoxConstraints.expand(height: 24), + child: TabBar( + onTap: (value) => Future.delayed(Duration(milliseconds: 300), () { + _sourceType = value == 0 ? SourceType.Screen : SourceType.Window; + _getSources(); + }), + tabs: [ + Tab( + child: Text( + 'Entire Screen', + style: TextStyle(color: Colors.black54), + )), + Tab( + child: Text( + 'Window', + style: TextStyle(color: Colors.black54), + )), + ]), + ), + SizedBox( + height: 2, + ), + Expanded( + child: Container( + child: TabBarView(children: [ + Align( + alignment: Alignment.center, + child: Container( + child: GridView.count( + crossAxisSpacing: 8, + crossAxisCount: 2, + children: _sources.entries + .where((element) => element.value.type == SourceType.Screen) + .map((e) => ThumbnailWidget( + onTap: (source) { + setState(() { + _selected_source = source; + }); + }, + source: e.value, + selected: _selected_source?.id == e.value.id, + )) + .toList(), + ), + )), + Align( + alignment: Alignment.center, + child: Container( + child: GridView.count( + crossAxisSpacing: 8, + crossAxisCount: 3, + children: _sources.entries + .where((element) => element.value.type == SourceType.Window) + .map((e) => ThumbnailWidget( + onTap: (source) { + setState(() { + _selected_source = source; + }); + }, + source: e.value, + selected: _selected_source?.id == e.value.id, + )) + .toList(), + ), + )), + ]), + ), + ) + ], + ), + ); + }, + ), + ), + ), + Container( + width: double.infinity, + child: ButtonBar( + children: [ + MaterialButton( + child: Text( + 'Cancel', + style: TextStyle(color: Colors.black54), + ), + onPressed: () { + _cancel(context); + }, + ), + MaterialButton( + color: Theme.of(context).primaryColor, + child: Text( + 'Share', + ), + onPressed: () { + _ok(context); + }, + ), + ], + ), + ), + ], + ), + )), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 3dca70c4..46a731a6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -69,10 +69,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.18.0" convert: dependency: transitive description: @@ -101,10 +101,10 @@ packages: dependency: transitive description: name: dart_webrtc - sha256: "3f581ea799829fabd6e0b99bd2210146e4d107c7b3ac8495af3510737a5c5c1a" + sha256: "5897a3bdd6c7fded07e80e250260ca4c9cd61f9080911aa308b516e1206745a9" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.3" dartdoc: dependency: "direct main" description: @@ -156,10 +156,10 @@ packages: dependency: "direct main" description: name: flutter_webrtc - sha256: f7e3ee080638db1793109a2ca4f1391413907057cbee46a7f9bd1dc2a636d1cd + sha256: "577216727181cb13776a65d3e7cb33e783e740c5496335011aed4a038b28c3fe" url: "https://pub.dev" source: hosted - version: "0.9.36" + version: "0.9.47" glob: dependency: transitive description: @@ -220,26 +220,26 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" package_config: dependency: transitive description: @@ -353,26 +353,26 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -393,10 +393,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.1" typed_data: dependency: transitive description: @@ -429,6 +429,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + url: "https://pub.dev" + source: hosted + version: "0.3.0" web_socket_channel: dependency: "direct main" description: @@ -441,10 +449,10 @@ packages: dependency: transitive description: name: webrtc_interface - sha256: "0dd96f4d7fb6ba9895930644cebd3f1adb5179caa83cb1760061b2fe9cba5aad" + sha256: "2efbd3e4e5ebeb2914253bcc51dafd3053c4b87b43f3076c74835a9deecbae3a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.2" win32: dependency: transitive description: @@ -470,5 +478,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.0.0 <3.10.6" + dart: ">=3.2.0-194.0.dev <3.10.6" flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index b82f6cbb..52f24049 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: sdk: flutter flutter: sdk: flutter - flutter_webrtc: ^0.9.36 + flutter_webrtc: ^0.9.47 path_provider: ^2.0.11 http: ^1.1.0 web_socket_channel: ^2.2.0