From 23296915a7e2c656d7b63ad37e38b7f85df70a91 Mon Sep 17 00:00:00 2001 From: "duanweiwei1982@gmail.com" Date: Sun, 17 Jul 2022 03:58:04 +0800 Subject: [PATCH 1/6] screen sharing. --- lib/src/call_sample/call_sample.dart | 64 ++++- lib/src/call_sample/signaling.dart | 43 +++- lib/src/widgets/screen_select_dialog.dart | 284 ++++++++++++++++++++++ pubspec.yaml | 2 +- 4 files changed, 374 insertions(+), 19 deletions(-) create mode 100644 lib/src/widgets/screen_select_dialog.dart diff --git a/lib/src/call_sample/call_sample.dart b/lib/src/call_sample/call_sample.dart index 9f6cc0d..9d2982d 100644 --- a/lib/src/call_sample/call_sample.dart +++ b/lib/src/call_sample/call_sample.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'dart:core'; +import '../widgets/screen_select_dialog.dart'; import 'signaling.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; @@ -71,8 +72,7 @@ class _CallSampleState extends State { setState(() { _inCalling = true; }); - } - else { + } else { _reject(); } break; @@ -135,15 +135,19 @@ class _CallSampleState extends State { title: Text("title"), content: Text("accept?"), actions: [ - TextButton( - child: Text("reject"), + MaterialButton( + child: Text( + 'Reject', + style: TextStyle(color: Colors.red), + ), onPressed: () => Navigator.of(context).pop(false), ), - TextButton( - child: Text("accept"), - onPressed: () { - Navigator.of(context).pop(true); - }, + MaterialButton( + child: Text( + 'Accept', + style: TextStyle(color: Colors.green), + ), + onPressed: () => Navigator.of(context).pop(true), ), ], ); @@ -164,8 +168,7 @@ class _CallSampleState extends State { onPressed: () { Navigator.of(context).pop(false); _hangUp(); - }, - + }, ), ], ); @@ -201,6 +204,36 @@ class _CallSampleState extends State { _signaling?.switchCamera(); } + Future selectScreenSourceDialog(BuildContext context) async { + 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} + } + }); + stream.getVideoTracks()[0].onEnded = () { + print( + 'By adding a listener on onEnded you can: 1) catch stop video sharing on Web'); + }; + _signaling?.switchToScreenSharing(stream); + } catch (e) { + print(e); + } + //await _makeCall(source); + } + } + + _switchScreenSharing() { + //_signaling?.switchToScreenSharing(); + } + _muteMic() { _signaling?.muteMic(); } @@ -254,14 +287,20 @@ class _CallSampleState extends State { floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, floatingActionButton: _inCalling ? SizedBox( - width: 200.0, + width: 240.0, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ FloatingActionButton( child: const Icon(Icons.switch_camera), + tooltip: 'Camera', onPressed: _switchCamera, ), + FloatingActionButton( + child: const Icon(Icons.desktop_mac), + tooltip: 'Screen Sharing', + onPressed: () => selectScreenSourceDialog(context), + ), FloatingActionButton( onPressed: _hangUp, tooltip: 'Hangup', @@ -270,6 +309,7 @@ class _CallSampleState extends State { ), FloatingActionButton( child: const Icon(Icons.mic_off), + tooltip: 'Mute Mic', onPressed: _muteMic, ) ])) diff --git a/lib/src/call_sample/signaling.dart b/lib/src/call_sample/signaling.dart index 8fe84a2..2a5dd6e 100644 --- a/lib/src/call_sample/signaling.dart +++ b/lib/src/call_sample/signaling.dart @@ -24,6 +24,11 @@ enum CallState { CallStateBye, } +enum VideoSource { + Camera, + Screen, +} + class Session { Session({required this.sid, required this.pid}); String pid; @@ -46,6 +51,8 @@ class Signaling { Map _sessions = {}; MediaStream? _localStream; List _remoteStreams = []; + List _senders = []; + VideoSource videoSource = VideoSource.Camera; Function(SignalingState state)? onSignalingStateChange; Function(Session session, CallState state)? onCallStateChange; @@ -57,8 +64,7 @@ class Signaling { onDataChannelMessage; Function(Session session, RTCDataChannel dc)? onDataChannel; - String get sdpSemantics => - WebRTC.platformIsWindows ? 'plan-b' : 'unified-plan'; + String get sdpSemantics => 'unified-plan'; Map _iceServers = { 'iceServers': [ @@ -96,10 +102,36 @@ class Signaling { void switchCamera() { if (_localStream != null) { - Helper.switchCamera(_localStream!.getVideoTracks()[0]); + if (videoSource != VideoSource.Camera) { + switchVideoSource(source: VideoSource.Camera); + _senders.forEach((sender) { + if (sender.track!.kind == 'video') { + sender.replaceTrack(_localStream!.getVideoTracks()[0]); + } + }); + videoSource = VideoSource.Camera; + onLocalStream?.call(_localStream!); + } else { + Helper.switchCamera(_localStream!.getVideoTracks()[0]); + } } } + void switchToScreenSharing(MediaStream stream) { + if (_localStream != null && videoSource != VideoSource.Screen) { + switchVideoSource(source: VideoSource.Screen); + _senders.forEach((sender) { + if (sender.track!.kind == 'video') { + sender.replaceTrack(stream.getVideoTracks()[0]); + } + }); + onLocalStream?.call(stream); + videoSource = VideoSource.Screen; + } + } + + void switchVideoSource({VideoSource source = VideoSource.Camera}) {} + void muteMic() { if (_localStream != null) { bool enabled = _localStream!.getAudioTracks()[0].enabled; @@ -190,7 +222,6 @@ class Signaling { newSession.remoteCandidates.clear(); } onCallStateChange?.call(newSession, CallState.CallStateNew); - onCallStateChange?.call(newSession, CallState.CallStateRinging); } break; @@ -356,8 +387,8 @@ class Signaling { onAddRemoteStream?.call(newSession, event.streams[0]); } }; - _localStream!.getTracks().forEach((track) { - pc.addTrack(track, _localStream!); + _localStream!.getTracks().forEach((track) async { + _senders.add(await pc.addTrack(track, _localStream!)); }); break; } diff --git a/lib/src/widgets/screen_select_dialog.dart b/lib/src/widgets/screen_select_dialog.dart new file mode 100644 index 0000000..1697944 --- /dev/null +++ b/lib/src/widgets/screen_select_dialog.dart @@ -0,0 +1,284 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_webrtc/flutter_webrtc.dart'; + +// ignore: must_be_immutable +class ScreenSelectDialog extends Dialog { + ScreenSelectDialog() { + Future.delayed(Duration(milliseconds: 100), () { + _getSources(); + _timer = Timer.periodic(Duration(milliseconds: 2000), (timer) { + _getSources(); + }); + }); + } + List _sources = []; + SourceType _sourceType = SourceType.Screen; + DesktopCapturerSource? _selected_source; + StateSetter? _stateSetter; + Timer? _timer; + + void _pop(context) { + _timer?.cancel(); + Navigator.pop(context, _selected_source); + } + + Future _getSources() async { + try { + var sources = await desktopCapturer.getSources(types: [_sourceType]); + sources.forEach((element) { + print( + 'name: ${element.name}, id: ${element.id}, type: ${element.type}'); + }); + _stateSetter?.call(() { + _sources = sources; + }); + 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: () => _pop(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( + 'Entrire 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 + .where((element) => + element.type == + SourceType.Screen) + .map((e) => Column( + children: [ + Expanded( + child: Container( + decoration: (_selected_source != + null && + _selected_source! + .id == + e.id) + ? BoxDecoration( + border: Border.all( + width: 2, + color: Colors + .blueAccent)) + : null, + child: InkWell( + onTap: () { + print( + 'Selected screen id => ${e.id}'); + setState(() { + _selected_source = + e; + }); + }, + child: + e.thumbnail != null + ? Image.memory( + e.thumbnail!, + scale: 1.0, + repeat: ImageRepeat + .noRepeat, + ) + : Container(), + ), + )), + Text( + e.name, + style: TextStyle( + fontSize: 12, + color: Colors.black87, + fontWeight: (_selected_source != + null && + _selected_source! + .id == + e.id) + ? FontWeight.bold + : FontWeight + .normal), + ), + ], + )) + .toList(), + ), + )), + Align( + alignment: Alignment.center, + child: Container( + child: GridView.count( + crossAxisSpacing: 8, + crossAxisCount: 3, + children: _sources + .where((element) => + element.type == + SourceType.Window) + .map((e) => Column( + children: [ + Expanded( + child: Container( + decoration: (_selected_source != + null && + _selected_source! + .id == + e.id) + ? BoxDecoration( + border: Border.all( + width: 2, + color: Colors + .blueAccent)) + : null, + child: InkWell( + onTap: () { + print( + 'Selected window id => ${e.id}'); + setState(() { + _selected_source = + e; + }); + }, + child: + e.thumbnail != null + ? Image.memory( + e.thumbnail!, + scale: 1.0, + repeat: ImageRepeat + .noRepeat, + ) + : Container(), + ), + )), + Text( + e.name, + style: TextStyle( + fontSize: 12, + color: Colors.black87, + fontWeight: (_selected_source != + null && + _selected_source! + .id == + e.id) + ? FontWeight.bold + : FontWeight + .normal), + ), + ], + )) + .toList(), + ), + )), + ]), + ), + ) + ], + ), + ); + }, + ), + ), + ), + Container( + width: double.infinity, + child: ButtonBar( + children: [ + MaterialButton( + child: Text( + 'Cancel', + style: TextStyle(color: Colors.black54), + ), + onPressed: () { + _pop(context); + }, + ), + MaterialButton( + color: Theme.of(context).primaryColor, + child: Text( + 'Share', + ), + onPressed: () { + _pop(context); + }, + ), + ], + ), + ), + ], + ), + )), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index a8f880d..6b2a06b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: sdk: flutter cupertino_icons: ^1.0.3 - flutter_webrtc: ^0.8.3 + flutter_webrtc: ^0.8.12 shared_preferences: ^2.0.7 http: ^0.13.3 path_provider: ^2.0.2 From 0d29d55fed76ab75d45eaa4472a6ed15c45bcc86 Mon Sep 17 00:00:00 2001 From: "duanweiwei1982@gmail.com" Date: Wed, 27 Jul 2022 09:29:50 +0800 Subject: [PATCH 2/6] chore: fix bugs. --- lib/src/call_sample/call_sample.dart | 55 ++++++----- lib/src/call_sample/signaling.dart | 16 ++-- lib/src/widgets/screen_select_dialog.dart | 111 +++++++++++++++------- pubspec.yaml | 6 +- 4 files changed, 119 insertions(+), 69 deletions(-) diff --git a/lib/src/call_sample/call_sample.dart b/lib/src/call_sample/call_sample.dart index e36aa5c..2c875a7 100644 --- a/lib/src/call_sample/call_sample.dart +++ b/lib/src/call_sample/call_sample.dart @@ -207,33 +207,38 @@ class _CallSampleState extends State { } Future selectScreenSourceDialog(BuildContext context) async { - 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} - } - }); - stream.getVideoTracks()[0].onEnded = () { - print( - 'By adding a listener on onEnded you can: 1) catch stop video sharing on Web'); - }; - _signaling?.switchToScreenSharing(stream); - } catch (e) { - print(e); + 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} + } + }); + 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); + } } - //await _makeCall(source); + } else if (WebRTC.platformIsWeb) { + screenStream = + await navigator.mediaDevices.getDisplayMedia({ + 'audio': false, + 'video': true, + }); } - } - - _switchScreenSharing() { - //_signaling?.switchToScreenSharing(); + if (screenStream != null) _signaling?.switchToScreenSharing(screenStream); } _muteMic() { diff --git a/lib/src/call_sample/signaling.dart b/lib/src/call_sample/signaling.dart index 2a5dd6e..14599ae 100644 --- a/lib/src/call_sample/signaling.dart +++ b/lib/src/call_sample/signaling.dart @@ -52,7 +52,7 @@ class Signaling { MediaStream? _localStream; List _remoteStreams = []; List _senders = []; - VideoSource videoSource = VideoSource.Camera; + VideoSource _videoSource = VideoSource.Camera; Function(SignalingState state)? onSignalingStateChange; Function(Session session, CallState state)? onCallStateChange; @@ -102,14 +102,13 @@ class Signaling { void switchCamera() { if (_localStream != null) { - if (videoSource != VideoSource.Camera) { - switchVideoSource(source: VideoSource.Camera); + if (_videoSource != VideoSource.Camera) { _senders.forEach((sender) { if (sender.track!.kind == 'video') { sender.replaceTrack(_localStream!.getVideoTracks()[0]); } }); - videoSource = VideoSource.Camera; + _videoSource = VideoSource.Camera; onLocalStream?.call(_localStream!); } else { Helper.switchCamera(_localStream!.getVideoTracks()[0]); @@ -118,20 +117,17 @@ class Signaling { } void switchToScreenSharing(MediaStream stream) { - if (_localStream != null && videoSource != VideoSource.Screen) { - switchVideoSource(source: VideoSource.Screen); + if (_localStream != null && _videoSource != VideoSource.Screen) { _senders.forEach((sender) { if (sender.track!.kind == 'video') { sender.replaceTrack(stream.getVideoTracks()[0]); } }); onLocalStream?.call(stream); - videoSource = VideoSource.Screen; + _videoSource = VideoSource.Screen; } } - void switchVideoSource({VideoSource source = VideoSource.Camera}) {} - void muteMic() { if (_localStream != null) { bool enabled = _localStream!.getAudioTracks()[0].enabled; @@ -571,5 +567,7 @@ class Signaling { await session.pc?.close(); await session.dc?.close(); + _senders.clear(); + _videoSource = VideoSource.Camera; } } diff --git a/lib/src/widgets/screen_select_dialog.dart b/lib/src/widgets/screen_select_dialog.dart index 1697944..496c9b0 100644 --- a/lib/src/widgets/screen_select_dialog.dart +++ b/lib/src/widgets/screen_select_dialog.dart @@ -8,22 +8,51 @@ class ScreenSelectDialog extends Dialog { ScreenSelectDialog() { Future.delayed(Duration(milliseconds: 100), () { _getSources(); - _timer = Timer.periodic(Duration(milliseconds: 2000), (timer) { - _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.onNameChanged.stream.listen((source) { + _sources[source.id] = source; + _stateSetter?.call(() {}); + })); + + _subscriptions + .add(desktopCapturer.onThumbnailChanged.stream.listen((source) { + _sources[source.id] = source; + _stateSetter?.call(() {}); + })); } - List _sources = []; + final Map _sources = {}; SourceType _sourceType = SourceType.Screen; DesktopCapturerSource? _selected_source; + final List> _subscriptions = []; StateSetter? _stateSetter; Timer? _timer; - void _pop(context) { + 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]); @@ -32,7 +61,13 @@ class ScreenSelectDialog extends Dialog { 'name: ${element.name}, id: ${element.id}, type: ${element.type}'); }); _stateSetter?.call(() { - _sources = sources; + sources.forEach((element) { + _sources[element.id] = element; + }); + }); + _timer?.cancel(); + _timer = Timer.periodic(Duration(seconds: 3), (timer) { + desktopCapturer.updateSources(types: [_sourceType]); }); return; } catch (e) { @@ -66,7 +101,7 @@ class ScreenSelectDialog extends Dialog { alignment: Alignment.topRight, child: InkWell( child: Icon(Icons.close), - onTap: () => _pop(context), + onTap: () => _cancel(context), ), ), ], @@ -97,7 +132,7 @@ class ScreenSelectDialog extends Dialog { tabs: [ Tab( child: Text( - 'Entrire Screen', + 'Entire Screen', style: TextStyle(color: Colors.black54), )), Tab( @@ -119,9 +154,9 @@ class ScreenSelectDialog extends Dialog { child: GridView.count( crossAxisSpacing: 8, crossAxisCount: 2, - children: _sources + children: _sources.entries .where((element) => - element.type == + element.value.type == SourceType.Screen) .map((e) => Column( children: [ @@ -131,7 +166,7 @@ class ScreenSelectDialog extends Dialog { null && _selected_source! .id == - e.id) + e.value.id) ? BoxDecoration( border: Border.all( width: 2, @@ -141,16 +176,18 @@ class ScreenSelectDialog extends Dialog { child: InkWell( onTap: () { print( - 'Selected screen id => ${e.id}'); + 'Selected screen id => ${e.value.id}'); setState(() { _selected_source = - e; + e.value; }); }, child: - e.thumbnail != null + e.value.thumbnail != + null ? Image.memory( - e.thumbnail!, + e.value + .thumbnail!, scale: 1.0, repeat: ImageRepeat .noRepeat, @@ -159,7 +196,7 @@ class ScreenSelectDialog extends Dialog { ), )), Text( - e.name, + e.value.name, style: TextStyle( fontSize: 12, color: Colors.black87, @@ -167,7 +204,8 @@ class ScreenSelectDialog extends Dialog { null && _selected_source! .id == - e.id) + e.value + .id) ? FontWeight.bold : FontWeight .normal), @@ -183,9 +221,9 @@ class ScreenSelectDialog extends Dialog { child: GridView.count( crossAxisSpacing: 8, crossAxisCount: 3, - children: _sources + children: _sources.entries .where((element) => - element.type == + element.value.type == SourceType.Window) .map((e) => Column( children: [ @@ -195,7 +233,7 @@ class ScreenSelectDialog extends Dialog { null && _selected_source! .id == - e.id) + e.value.id) ? BoxDecoration( border: Border.all( width: 2, @@ -205,25 +243,29 @@ class ScreenSelectDialog extends Dialog { child: InkWell( onTap: () { print( - 'Selected window id => ${e.id}'); + 'Selected window id => ${e.value.id}'); setState(() { _selected_source = - e; + e.value; }); }, - child: - e.thumbnail != null - ? Image.memory( - e.thumbnail!, - scale: 1.0, - repeat: ImageRepeat + child: e + .value + .thumbnail! + .isNotEmpty + ? Image.memory( + e.value + .thumbnail!, + scale: 1.0, + repeat: + ImageRepeat .noRepeat, - ) - : Container(), + ) + : Container(), ), )), Text( - e.name, + e.value.name, style: TextStyle( fontSize: 12, color: Colors.black87, @@ -231,7 +273,8 @@ class ScreenSelectDialog extends Dialog { null && _selected_source! .id == - e.id) + e.value + .id) ? FontWeight.bold : FontWeight .normal), @@ -261,7 +304,7 @@ class ScreenSelectDialog extends Dialog { style: TextStyle(color: Colors.black54), ), onPressed: () { - _pop(context); + _cancel(context); }, ), MaterialButton( @@ -270,7 +313,7 @@ class ScreenSelectDialog extends Dialog { 'Share', ), onPressed: () { - _pop(context); + _ok(context); }, ), ], diff --git a/pubspec.yaml b/pubspec.yaml index 6b2a06b..e43dae6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,11 @@ dependencies: sdk: flutter cupertino_icons: ^1.0.3 - flutter_webrtc: ^0.8.12 + flutter_webrtc: + git: + url: https://github.com/flutter-webrtc/flutter-webrtc.git + ref: feat/screen-capture-event-listener-for-win + shared_preferences: ^2.0.7 http: ^0.13.3 path_provider: ^2.0.2 From 198ccba6f5909e23fe8eaef27d8a64db4f08da24 Mon Sep 17 00:00:00 2001 From: cloudwebrtc Date: Sun, 31 Jul 2022 22:53:35 +0800 Subject: [PATCH 3/6] update. --- android/app/build.gradle | 1 + pubspec.yaml | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 57cc757..568b1ac 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -26,6 +26,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 28 + ndkVersion "21.4.7075529" lintOptions { disable 'InvalidPackage' diff --git a/pubspec.yaml b/pubspec.yaml index e43dae6..e6e3ac6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,11 +11,7 @@ dependencies: sdk: flutter cupertino_icons: ^1.0.3 - flutter_webrtc: - git: - url: https://github.com/flutter-webrtc/flutter-webrtc.git - ref: feat/screen-capture-event-listener-for-win - + flutter_webrtc: ^0.9.0 shared_preferences: ^2.0.7 http: ^0.13.3 path_provider: ^2.0.2 From 0cc2747f49bf9a2f0c0c70458648f05c1a9a528e Mon Sep 17 00:00:00 2001 From: cloudwebrtc Date: Thu, 20 Oct 2022 17:36:58 +0800 Subject: [PATCH 4/6] update. --- lib/src/widgets/screen_select_dialog.dart | 218 ++++++++++------------ 1 file changed, 99 insertions(+), 119 deletions(-) diff --git a/lib/src/widgets/screen_select_dialog.dart b/lib/src/widgets/screen_select_dialog.dart index 496c9b0..45a4f2a 100644 --- a/lib/src/widgets/screen_select_dialog.dart +++ b/lib/src/widgets/screen_select_dialog.dart @@ -3,6 +3,80 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; +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() { @@ -19,14 +93,8 @@ class ScreenSelectDialog extends Dialog { _stateSetter?.call(() {}); })); - _subscriptions.add(desktopCapturer.onNameChanged.stream.listen((source) { - _sources[source.id] = source; - _stateSetter?.call(() {}); - })); - _subscriptions .add(desktopCapturer.onThumbnailChanged.stream.listen((source) { - _sources[source.id] = source; _stateSetter?.call(() {}); })); } @@ -60,15 +128,15 @@ class ScreenSelectDialog extends Dialog { print( 'name: ${element.name}, id: ${element.id}, type: ${element.type}'); }); - _stateSetter?.call(() { - sources.forEach((element) { - _sources[element.id] = element; - }); - }); _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()); @@ -158,59 +226,16 @@ class ScreenSelectDialog extends Dialog { .where((element) => element.value.type == SourceType.Screen) - .map((e) => Column( - children: [ - Expanded( - child: Container( - decoration: (_selected_source != - null && - _selected_source! - .id == - e.value.id) - ? BoxDecoration( - border: Border.all( - width: 2, - color: Colors - .blueAccent)) - : null, - child: InkWell( - onTap: () { - print( - 'Selected screen id => ${e.value.id}'); - setState(() { - _selected_source = - e.value; - }); - }, - child: - e.value.thumbnail != - null - ? Image.memory( - e.value - .thumbnail!, - scale: 1.0, - repeat: ImageRepeat - .noRepeat, - ) - : Container(), - ), - )), - Text( - e.value.name, - style: TextStyle( - fontSize: 12, - color: Colors.black87, - fontWeight: (_selected_source != - null && - _selected_source! - .id == - e.value - .id) - ? FontWeight.bold - : FontWeight - .normal), - ), - ], + .map((e) => ThumbnailWidget( + onTap: (source) { + setState(() { + _selected_source = source; + }); + }, + source: e.value, + selected: + _selected_source?.id == + e.value.id, )) .toList(), ), @@ -225,61 +250,16 @@ class ScreenSelectDialog extends Dialog { .where((element) => element.value.type == SourceType.Window) - .map((e) => Column( - children: [ - Expanded( - child: Container( - decoration: (_selected_source != - null && - _selected_source! - .id == - e.value.id) - ? BoxDecoration( - border: Border.all( - width: 2, - color: Colors - .blueAccent)) - : null, - child: InkWell( - onTap: () { - print( - 'Selected window id => ${e.value.id}'); - setState(() { - _selected_source = - e.value; - }); - }, - child: e - .value - .thumbnail! - .isNotEmpty - ? Image.memory( - e.value - .thumbnail!, - scale: 1.0, - repeat: - ImageRepeat - .noRepeat, - ) - : Container(), - ), - )), - Text( - e.value.name, - style: TextStyle( - fontSize: 12, - color: Colors.black87, - fontWeight: (_selected_source != - null && - _selected_source! - .id == - e.value - .id) - ? FontWeight.bold - : FontWeight - .normal), - ), - ], + .map((e) => ThumbnailWidget( + onTap: (source) { + setState(() { + _selected_source = source; + }); + }, + source: e.value, + selected: + _selected_source?.id == + e.value.id, )) .toList(), ), From 48b6c64be4a560e6ea84e0a62c6f5ef93cd898e6 Mon Sep 17 00:00:00 2001 From: cloudwebrtc Date: Thu, 20 Oct 2022 22:14:38 +0800 Subject: [PATCH 5/6] change profile-level-id to fix issue for screen sharing on macOS. --- lib/src/call_sample/signaling.dart | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/src/call_sample/signaling.dart b/lib/src/call_sample/signaling.dart index 14599ae..ff59b7c 100644 --- a/lib/src/call_sample/signaling.dart +++ b/lib/src/call_sample/signaling.dart @@ -494,7 +494,7 @@ class Signaling { try { RTCSessionDescription s = await session.pc!.createOffer(media == 'data' ? _dcConstraints : {}); - await session.pc!.setLocalDescription(s); + await session.pc!.setLocalDescription(_fixSdp(s)); _send('offer', { 'to': session.pid, 'from': _selfId, @@ -507,11 +507,18 @@ class Signaling { } } + RTCSessionDescription _fixSdp(RTCSessionDescription s) { + var sdp = s.sdp; + s.sdp = + sdp!.replaceAll('profile-level-id=640c1f', 'profile-level-id=42e032'); + return s; + } + Future _createAnswer(Session session, String media) async { try { RTCSessionDescription s = await session.pc!.createAnswer(media == 'data' ? _dcConstraints : {}); - await session.pc!.setLocalDescription(s); + await session.pc!.setLocalDescription(_fixSdp(s)); _send('answer', { 'to': session.pid, 'from': _selfId, From 420495f55ff6345d8e28d622dbc77d1cab09d327 Mon Sep 17 00:00:00 2001 From: "duanweiwei1982@gmail.com" Date: Thu, 20 Oct 2022 22:38:11 +0800 Subject: [PATCH 6/6] bump version for flutter-webrtc. --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index e6e3ac6..360f7d8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: sdk: flutter cupertino_icons: ^1.0.3 - flutter_webrtc: ^0.9.0 + flutter_webrtc: ^0.9.11 shared_preferences: ^2.0.7 http: ^0.13.3 path_provider: ^2.0.2