From 59b312c2462033084868c3f4674e533e17807434 Mon Sep 17 00:00:00 2001 From: shivanshtalwar0 Date: Fri, 12 May 2023 15:08:30 +0530 Subject: [PATCH 1/4] support for screenshare windows and macos --- example/lib/typed_examples/audio_bridge.dart | 2 +- example/lib/typed_examples/google_meet.dart | 120 ++++++-- example/lib/typed_examples/sip.dart | 111 ++++---- example/lib/typed_examples/video_call.dart | 65 ++--- example/windows/runner/CMakeLists.txt | 7 + example/windows/runner/Runner.rc | 10 +- lib/janus_client.dart | 5 +- lib/janus_plugin.dart | 40 ++- lib/widgets/screen_select_dialog.dart | 279 +++++++++++++++++++ 9 files changed, 505 insertions(+), 134 deletions(-) create mode 100644 lib/widgets/screen_select_dialog.dart diff --git a/example/lib/typed_examples/audio_bridge.dart b/example/lib/typed_examples/audio_bridge.dart index 5e48cd00..8c6511ba 100644 --- a/example/lib/typed_examples/audio_bridge.dart +++ b/example/lib/typed_examples/audio_bridge.dart @@ -62,7 +62,7 @@ class _AudioRoomState extends State { // await navigator.mediaDevices.enumerateDevices(); // MediaDeviceInfo microphone = // devices.firstWhere((element) => element.kind == "audioinput"); - await pluginHandle?.initializeMediaDevices( + await pluginHandle?.initializeMediaDevices(context: context, mediaConstraints: {"audio": true, "video": false}); pluginHandle?.joinRoom(myRoom, display: "Shivansh"); diff --git a/example/lib/typed_examples/google_meet.dart b/example/lib/typed_examples/google_meet.dart index 87271718..781a71c0 100644 --- a/example/lib/typed_examples/google_meet.dart +++ b/example/lib/typed_examples/google_meet.dart @@ -87,7 +87,7 @@ class _VideoRoomState extends State { } initialize() async { - ws = WebSocketJanusTransport(url: servermap['servercheap']); + ws = WebSocketJanusTransport(url: servermap['janus_ws']); j = JanusClient( transport: ws!, isUnifiedPlan: true, @@ -150,6 +150,8 @@ class _VideoRoomState extends State { 'kind': event.track?.kind }); int? feedId = videoState.subStreamsToFeedIdMap[event.mid]?['feed_id']; + String? displayName = + videoState.feedIdToDisplayStreamsMap[feedId]?['display']; if (feedId != null) { if (videoState.streamsToBeRendered.containsKey(feedId.toString()) && event.track?.kind == "audio") { @@ -325,8 +327,14 @@ class _VideoRoomState extends State { videoPlugin = await attachPlugin(pop: true); eventMessagesHandler(); await localVideoRenderer.init(); - localVideoRenderer.mediaStream = await videoPlugin?.initializeMediaDevices( - mediaConstraints: {'video': true, 'audio': true}); + + localVideoRenderer.mediaStream = await videoPlugin + ?.initializeMediaDevices(context: context, mediaConstraints: { + 'audio': { + 'deviceId': {'exact': selectedAudioInputDeviceId}, + }, + }); + await Helper.selectAudioInput(selectedAudioInputDeviceId!); localVideoRenderer.videoRenderer.srcObject = localVideoRenderer.mediaStream; setState(() { videoState.streamsToBeRendered @@ -363,18 +371,20 @@ class _VideoRoomState extends State { screenPlugin?.handleRemoteJsep(event.jsep); }); await localScreenSharingRenderer.init(); - localScreenSharingRenderer.mediaStream = await screenPlugin - ?.initializeMediaDevices( + localScreenSharingRenderer.mediaStream = + await screenPlugin?.initializeMediaDevices( + context: context, mediaConstraints: {'video': true, 'audio': true}, useDisplayMediaDevices: true); localScreenSharingRenderer.videoRenderer.srcObject = localScreenSharingRenderer.mediaStream; + localScreenSharingRenderer.publisherName = "Your Screenshare"; setState(() { videoState.streamsToBeRendered.putIfAbsent( localScreenSharingRenderer.id, () => localScreenSharingRenderer); }); await screenPlugin?.joinPublisher(myRoom, - displayName: username.text + "screenshare", + displayName: username.text + "_screenshare", id: screenShareId, pin: myPin); } @@ -403,6 +413,7 @@ class _VideoRoomState extends State { await localVideoRenderer.init(); localVideoRenderer.videoRenderer.srcObject = videoPlugin?.webRTCHandle!.localStream; + localVideoRenderer.publisherName = "My Camera"; setState(() { videoState.streamsToBeRendered['local'] = localVideoRenderer; }); @@ -445,26 +456,45 @@ class _VideoRoomState extends State { remotePlugin = null; } + Future> audioInputDevices = + navigator.mediaDevices.enumerateDevices(); + String? selectedAudioInputDeviceId; + String? selectedAudioOutputDeviceId; Future showJoiningDialog() async { joiningDialog = 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('Join Room'), + IconButton( + onPressed: () { + Navigator.of(context).pop(this); + }, + icon: Icon(Icons.close)) + ]), + actionsAlignment: MainAxisAlignment.start, 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(); - } - }, - child: Text('Join')) + 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, @@ -489,6 +519,54 @@ class _VideoRoomState extends State { controller: pin, obscureText: true, decoration: InputDecoration(label: Text('Pin')), + ), + 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 { + print(value); + setState(() { + selectedAudioInputDeviceId = value; + }); + }); + } + return CircularProgressIndicator(); + }, + future: Helper.enumerateDevices('audioinput'), + ), + 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; + }); + await Helper.selectAudioOutput( + selectedAudioOutputDeviceId!); + }); + } + return CircularProgressIndicator(); + }, + future: Helper.enumerateDevices('audiooutput'), ) ]), ), @@ -590,7 +668,8 @@ class _VideoRoomState extends State { visible: remoteStream.isVideoMuted == false, replacement: Container( child: Center( - child: Text("Video Paused By Owner", + child: Text( + "Video Paused By " + remoteStream.publisherName!, style: TextStyle(color: Colors.black)), ), ), @@ -607,6 +686,7 @@ class _VideoRoomState extends State { mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.end, children: [ + Text(remoteStream.publisherName!), Icon(remoteStream.isAudioMuted == true ? Icons.mic_off : Icons.mic), diff --git a/example/lib/typed_examples/sip.dart b/example/lib/typed_examples/sip.dart index 673225ad..255e286e 100644 --- a/example/lib/typed_examples/sip.dart +++ b/example/lib/typed_examples/sip.dart @@ -14,14 +14,14 @@ class _SipExampleState extends State { late WebSocketJanusTransport ws; late JanusSession session; JanusSipPlugin? sip; - TextEditingController proxyController = - TextEditingController(text: "sip:sip.linphone.org"); - TextEditingController usernameController = - TextEditingController(text: "sip:maksim11111@sip.linphone.org"); - TextEditingController secretController = - TextEditingController(text: "1234567q"); - TextEditingController callUriController = - TextEditingController(text: "sip:00918744849050@sip.theansr.com"); + Map credentials = { + 'creds1': {'proxy': 'sip:sip.linphone.org', 'username': 'sip:maksim11111@sip.linphone.org', 'secret': '1234567q'}, + 'creds2': {'proxy': 'sip:sip.linphone.org', 'username': 'sip:educampus@sip.linphone.org', 'secret': '1234567q'} + }; + TextEditingController proxyController = TextEditingController(text: ""); + TextEditingController usernameController = TextEditingController(text: ""); + TextEditingController secretController = TextEditingController(text: ""); + TextEditingController callUriController = TextEditingController(text: ""); RTCVideoRenderer _remoteVideoRenderer = RTCVideoRenderer(); MediaStream? remoteVideoStream; MediaStream? remoteAudioStream; @@ -36,8 +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; } @@ -48,10 +47,8 @@ class _SipExampleState extends State { await sip?.initializeWebRTCStack(); await localMediaSetup(); - var offer = await sip?.createOffer( - videoSend: false, videoRecv: false, audioSend: true, audioRecv: true); - await sip?.call(callUriController.text, - offer: offer, autoAcceptReInvites: false); + var offer = await sip?.createOffer(videoSend: false, videoRecv: false, audioSend: true, audioRecv: true); + await sip?.call(callUriController.text, offer: offer, autoAcceptReInvites: false); } openRegisterDialog() async { @@ -69,10 +66,24 @@ class _SipExampleState extends State { child: Column( mainAxisSize: MainAxisSize.max, children: [ + DropdownButtonFormField( + decoration: InputDecoration(label: Text("Default credentials")), + items: credentials.values.map((e) { + return DropdownMenuItem( + child: Text('${e['username']}'), + value: e, + ); + }).toList(), + onChanged: (value) { + if (value == null) { + return; + } + proxyController.text = value['proxy']; + usernameController.text = value['username']; + secretController.text = value['secret']; + }), TextFormField( - decoration: InputDecoration( - labelText: "Sip Server URI", - hintText: "sip:host:port"), + decoration: InputDecoration(labelText: "Sip Server URI", hintText: "sip:host:port"), controller: proxyController, validator: (val) { if (val == '') { @@ -81,9 +92,7 @@ class _SipExampleState extends State { }, ), TextFormField( - decoration: InputDecoration( - labelText: "Sip username", - hintText: "sip:test@host:port"), + decoration: InputDecoration(labelText: "Sip username", hintText: "sip:test@host:port"), controller: usernameController, validator: (val) { if (val == '') { @@ -130,6 +139,23 @@ class _SipExampleState extends State { content: Column( mainAxisSize: MainAxisSize.min, children: [ + DropdownButtonFormField( + decoration: InputDecoration(label: Text("Default call URI")), + items: credentials.values + .where((e) { + return e['username'] != usernameController.text; + }) + .map((e) => DropdownMenuItem( + child: Text('${e['username']}'), + value: e, + )) + .toList(), + onChanged: (value) { + if (value == null) { + return; + } + callUriController.text = value['username']; + }), TextFormField( decoration: InputDecoration(labelText: "sip URI to call"), controller: callUriController, @@ -142,9 +168,7 @@ class _SipExampleState extends State { : null, child: Text("Call"), ), - Visibility( - visible: !enableCallButton, - child: Text("status:$statusMessage")), + Visibility(visible: !enableCallButton, child: Text("status:$statusMessage")), Visibility( visible: !enableCallButton, child: ElevatedButton( @@ -155,9 +179,7 @@ class _SipExampleState extends State { statusMessage = ""; }); }, - style: ButtonStyle( - backgroundColor: - MaterialStateProperty.all(Colors.red)), + style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Colors.red)), child: Text("Hangup"), ), ) @@ -176,15 +198,7 @@ class _SipExampleState extends State { initJanusClient() async { ws = WebSocketJanusTransport(url: servermap['janus_ws']); - j = JanusClient( - transport: ws, - iceServers: [ - RTCIceServer( - urls: "stun:stun.voip.eutelia.it:3478", - username: "", - credential: "") - ], - isUnifiedPlan: true); + j = JanusClient(transport: ws, iceServers: [RTCIceServer(urls: "stun:stun.voip.eutelia.it:3478", username: "", credential: "")], isUnifiedPlan: true); session = await j.createSession(); sip = await session.attach(); await _remoteVideoRenderer.initialize(); @@ -213,8 +227,7 @@ class _SipExampleState extends State { setState(() { isIncomingCall = true; }); - var dialog = - await showIncomingCallDialog(data.result?.callee, even.jsep); + var dialog = await showIncomingCallDialog(data.result?.callee, even.jsep); setState(() { incomingDialog = dialog; }); @@ -318,11 +331,7 @@ class _SipExampleState extends State { Future registerUser() async { if (formKey.currentState?.validate() == true) { print('registering user...'); - await sip?.register(usernameController.text, - forceUdp: true, - rfc2543Cancel: true, - proxy: proxyController.text, - secret: secretController.text); + await sip?.register(usernameController.text, forceUdp: true, rfc2543Cancel: true, proxy: proxyController.text, secret: secretController.text); } } @@ -332,8 +341,7 @@ class _SipExampleState extends State { Navigator.of(context).pop(); } - Future showIncomingCallDialog( - String? caller, RTCSessionDescription? remoteOffer) async { + Future showIncomingCallDialog(String? caller, RTCSessionDescription? remoteOffer) async { await sip?.handleRemoteJsep(remoteOffer); return showDialog( context: context, @@ -344,22 +352,16 @@ class _SipExampleState extends State { ElevatedButton( onPressed: () async { await localMediaSetup(); - Navigator.of(context, rootNavigator: true) - .pop(incomingDialog); + Navigator.of(context, rootNavigator: true).pop(incomingDialog); Navigator.of(context, rootNavigator: true).pop(callDialog); // since in this example for calling we are using offer so we have to send answer to complete the circle - var answer = await sip?.createAnswer( - audioRecv: true, - audioSend: true, - videoRecv: false, - videoSend: false); + var answer = await sip?.createAnswer(audioRecv: true, audioSend: true, videoRecv: false, videoSend: false); await sip?.accept(sessionDescription: answer); }, child: Text('Accept')), ElevatedButton( onPressed: () async { - Navigator.of(context, rootNavigator: true) - .pop(incomingDialog); + Navigator.of(context, rootNavigator: true).pop(incomingDialog); Navigator.of(context, rootNavigator: true).pop(callDialog); await sip?.decline(); }, @@ -381,8 +383,7 @@ class _SipExampleState extends State { RTCVideoView( _remoteVideoRenderer, mirror: true, - objectFit: - RTCVideoViewObjectFit.RTCVideoViewObjectFitContain, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitContain, ) ], ), diff --git a/example/lib/typed_examples/video_call.dart b/example/lib/typed_examples/video_call.dart index 7fcc3daa..b50957b1 100644 --- a/example/lib/typed_examples/video_call.dart +++ b/example/lib/typed_examples/video_call.dart @@ -38,16 +38,14 @@ 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; } makeCall() async { await localMediaSetup(); await publishVideo.initDataChannel(); - var offer = await publishVideo.createOffer( - audioSend: true, audioRecv: true, videoRecv: true, videoSend: true); + var offer = await publishVideo.createOffer(audioSend: true, audioRecv: true, videoRecv: true, videoSend: true); await publishVideo.call(nameController.text, offer: offer); nameController.text = ""; } @@ -105,8 +103,7 @@ class _VideoCallV2ExampleState extends State { mainAxisSize: MainAxisSize.min, children: [ TextFormField( - decoration: InputDecoration( - labelText: "Name Of Registered User to call"), + decoration: InputDecoration(labelText: "Name Of Registered User to call"), controller: nameController, ), ElevatedButton( @@ -130,15 +127,7 @@ class _VideoCallV2ExampleState extends State { initJanusClient() async { ws = WebSocketJanusTransport(url: servermap['janus_ws']); - j = JanusClient( - transport: ws, - iceServers: [ - RTCIceServer( - urls: "stun:stun.voip.eutelia.it:3478", - username: "", - credential: "") - ], - isUnifiedPlan: true); + j = JanusClient(transport: ws, iceServers: [RTCIceServer(urls: "stun:stun.voip.eutelia.it:3478", username: "", credential: "")], isUnifiedPlan: true); session = await j.createSession(); publishVideo = await session.attach(); await _remoteVideoRenderer.initialize(); @@ -151,10 +140,8 @@ class _VideoCallV2ExampleState extends State { messages.add(event.text); }); }); - publishVideo.webRTCHandle?.peerConnection?.onConnectionState = - (connectionState) async { - if (connectionState == - RTCPeerConnectionState.RTCPeerConnectionStateConnected) { + publishVideo.webRTCHandle?.peerConnection?.onConnectionState = (connectionState) async { + if (connectionState == RTCPeerConnectionState.RTCPeerConnectionStateConnected) { print('connection established'); } }; @@ -177,8 +164,7 @@ class _VideoCallV2ExampleState extends State { await makeCallDialog(); } if (data is VideoCallIncomingCallEvent) { - incomingDialog = - await showIncomingCallDialog(data.result!.username!, even.jsep); + incomingDialog = await showIncomingCallDialog(data.result!.username!, even.jsep); } if (data is VideoCallAcceptedEvent) { setState(() { @@ -247,8 +233,7 @@ class _VideoCallV2ExampleState extends State { Navigator.of(context).pop(); } - Future showIncomingCallDialog( - String caller, RTCSessionDescription? jsep) async { + Future showIncomingCallDialog(String caller, RTCSessionDescription? jsep) async { return showDialog( context: context, builder: (context) { @@ -259,11 +244,7 @@ class _VideoCallV2ExampleState extends State { onPressed: () async { await localMediaSetup(); await publishVideo.handleRemoteJsep(jsep); - var answer = await publishVideo.createAnswer( - audioRecv: true, - audioSend: true, - videoRecv: true, - videoSend: true); + var answer = await publishVideo.createAnswer(audioRecv: true, audioSend: true, videoRecv: true, videoSend: true); await publishVideo.acceptCall(answer: answer); Navigator.of(context).pop(incomingDialog); Navigator.of(context).pop(callDialog); @@ -271,8 +252,7 @@ class _VideoCallV2ExampleState extends State { child: Text('Accept')), ElevatedButton( onPressed: () async { - Navigator.of(context, rootNavigator: true) - .pop(incomingDialog); + Navigator.of(context, rootNavigator: true).pop(incomingDialog); Navigator.of(context, rootNavigator: true).pop(callDialog); await publishVideo.hangup(); }, @@ -296,9 +276,7 @@ class _VideoCallV2ExampleState extends State { icon: Icon(Icons.input), itemBuilder: (BuildContext context) { if (_mediaDevicesList != null) { - return _mediaDevicesList! - .where((device) => device.kind == 'audioinput') - .map((device) { + return _mediaDevicesList!.where((device) => device.kind == 'audioinput').map((device) { return PopupMenuItem( value: device.deviceId, child: Text(device.label), @@ -337,13 +315,11 @@ class _VideoCallV2ExampleState extends State { front = !front; }); // note:- deviceId is important for web browsers - await publishVideo.switchCamera( - deviceId: await getCameraDeviceId(front)); + await publishVideo.switchCamera(deviceId: await getCameraDeviceId(front)); // everytime we make changes in stream we update in ui and renderer like this. setState(() { - _localRenderer.srcObject = - publishVideo.webRTCHandle?.localStream; + _localRenderer.srcObject = publishVideo.webRTCHandle?.localStream; }); }) ]), @@ -359,8 +335,7 @@ class _VideoCallV2ExampleState extends State { RTCVideoView( _remoteVideoRenderer, mirror: true, - objectFit: - RTCVideoViewObjectFit.RTCVideoViewObjectFitContain, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitContain, ) ], ), @@ -371,8 +346,7 @@ class _VideoCallV2ExampleState extends State { child: RTCVideoView( _localRenderer, mirror: true, - objectFit: - RTCVideoViewObjectFit.RTCVideoViewObjectFitContain, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitContain, ), )) ], @@ -384,8 +358,7 @@ class _VideoCallV2ExampleState extends State { Expanded( flex: 2, child: ListView( - children: List.generate( - messages.length, (index) => Text('${messages[index]}')), + children: List.generate(messages.length, (index) => Text('${messages[index]}')), )), Flexible( child: Row( @@ -393,8 +366,7 @@ class _VideoCallV2ExampleState extends State { Flexible( child: TextFormField( controller: messageController, - decoration: - InputDecoration(label: Text('Data channel message')), + decoration: InputDecoration(label: Text('Data channel message')), )), Flexible( child: TextButton( @@ -416,8 +388,7 @@ class _VideoCallV2ExampleState extends State { child: Container( width: MediaQuery.of(context).size.width, height: 60, - decoration: BoxDecoration( - color: ringing ? Colors.green : Colors.grey.withOpacity(0.3)), + decoration: BoxDecoration(color: ringing ? Colors.green : Colors.grey.withOpacity(0.3)), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ 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 7eb26698..b5cc6a99 100644 --- a/lib/janus_client.dart +++ b/lib/janus_client.dart @@ -1,7 +1,8 @@ /// 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:collection/collection.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; import 'dart:async'; @@ -129,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 b933f94d..04693db8 100644 --- a/lib/janus_plugin.dart +++ b/lib/janus_plugin.dart @@ -469,15 +469,46 @@ class JanusPlugin { await webRTCHandle?.peerConnection?.setRemoteDescription(data); } } + /// 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; + } /// 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.
/// [mediaConstraints] : using this map you can specify media contraits such as resolution and fps etc. /// you can use this method to get the stream and show live preview of your camera to RTCVideoRendererView - Future initializeMediaDevices( - {bool? useDisplayMediaDevices = false, - Map? mediaConstraints}) async { + Future initializeMediaDevices({bool? useDisplayMediaDevices = false, required BuildContext context, Map? mediaConstraints}) async { await _disposeMediaStreams(ignoreRemote: true); List videoDevices = await getVideoInputDevices(); List audioDevices = await getAudioInputDevices(); @@ -501,8 +532,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); + }, + ), + ], + ), + ), + ], + ), + )), + ); + } +} From ac0d0e8214ef16848a316691db39937d401e2d24 Mon Sep 17 00:00:00 2001 From: shivanshtalwar0 Date: Fri, 12 May 2023 15:14:19 +0530 Subject: [PATCH 2/4] formatting --- example/lib/Helper.dart | 11 +- example/lib/Home.dart | 42 +--- example/lib/main.dart | 1 - example/lib/typed_examples/audio_bridge.dart | 32 +-- example/lib/typed_examples/google_meet.dart | 238 +++++-------------- example/lib/typed_examples/streaming.dart | 60 ++--- example/lib/typed_examples/text_room.dart | 43 +--- example/lib/util.dart | 98 ++------ lib/janus_plugin.dart | 3 +- lib/utils.dart | 2 - test/janus_audio_bridge_plugin_test.dart | 6 +- test/janus_client_test.dart | 6 +- test/janus_session_test.dart | 3 +- test/janus_text_room_plugin_test.dart | 3 +- test/janus_transport_test.dart | 21 +- test/janus_video_call_plugin_test.dart | 3 +- 16 files changed, 148 insertions(+), 424 deletions(-) diff --git a/example/lib/Helper.dart b/example/lib/Helper.dart index a6e3935a..4f4c99a1 100644 --- a/example/lib/Helper.dart +++ b/example/lib/Helper.dart @@ -6,12 +6,9 @@ import 'dart:convert'; import 'package:flutter_webrtc/flutter_webrtc.dart'; import 'package:janus_client/janus_client.dart'; -List streamingItemFromMap(String str) => - List.from( - json.decode(str).map((x) => StreamingItem.fromMap(x))); +List streamingItemFromMap(String str) => List.from(json.decode(str).map((x) => StreamingItem.fromMap(x))); -String streamingItemToMap(List data) => - json.encode(List.from(data.map((x) => x.toMap()))); +String streamingItemToMap(List data) => json.encode(List.from(data.map((x) => x.toMap()))); class StreamingItem { StreamingItem({ @@ -53,9 +50,7 @@ class StreamingItem { description: json["description"], metadata: json["metadata"], enabled: json["enabled"], - media: json["media"] == null - ? [] - : (json["media"] as List).map((x) => Media.fromMap(x)).toList(), + media: json["media"] == null ? [] : (json["media"] as List).map((x) => Media.fromMap(x)).toList(), ); Map toMap() => { diff --git a/example/lib/Home.dart b/example/lib/Home.dart index b30a0b0e..ea2a9c0b 100644 --- a/example/lib/Home.dart +++ b/example/lib/Home.dart @@ -19,12 +19,7 @@ class _HomeState extends State { children: [ ListTile( title: Text.rich( - TextSpan(children: [ - TextSpan(text: "Google meet"), - TextSpan( - text: " New", - style: TextStyle(color: Colors.green)) - ]), + TextSpan(children: [TextSpan(text: "Google meet"), TextSpan(text: " New", style: TextStyle(color: Colors.green))]), ), onTap: () { Navigator.of(context).pushNamed("/google-meet"); @@ -32,12 +27,7 @@ class _HomeState extends State { ), ListTile( title: Text.rich( - TextSpan(children: [ - TextSpan(text: "Typed streaming Unified"), - TextSpan( - text: " New", - style: TextStyle(color: Colors.green)) - ]), + TextSpan(children: [TextSpan(text: "Typed streaming Unified"), TextSpan(text: " New", style: TextStyle(color: Colors.green))]), ), onTap: () { Navigator.of(context).pushNamed("/typed_streaming"); @@ -45,12 +35,7 @@ class _HomeState extends State { ), ListTile( title: Text.rich( - TextSpan(children: [ - TextSpan(text: "Typed Video Call Unified"), - TextSpan( - text: " New", - style: TextStyle(color: Colors.green)) - ]), + TextSpan(children: [TextSpan(text: "Typed Video Call Unified"), TextSpan(text: " New", style: TextStyle(color: Colors.green))]), ), onTap: () { Navigator.of(context).pushNamed("/typed_video_call"); @@ -58,12 +43,7 @@ class _HomeState extends State { ), ListTile( title: Text.rich( - TextSpan(children: [ - TextSpan(text: "Typed Audio Bridge Unified"), - TextSpan( - text: " New", - style: TextStyle(color: Colors.green)) - ]), + TextSpan(children: [TextSpan(text: "Typed Audio Bridge Unified"), TextSpan(text: " New", style: TextStyle(color: Colors.green))]), ), onTap: () { Navigator.of(context).pushNamed("/typed_audio_bridge"); @@ -71,12 +51,7 @@ class _HomeState extends State { ), ListTile( title: Text.rich( - TextSpan(children: [ - TextSpan(text: "Typed Text Room"), - TextSpan( - text: " New", - style: TextStyle(color: Colors.green)) - ]), + TextSpan(children: [TextSpan(text: "Typed Text Room"), TextSpan(text: " New", style: TextStyle(color: Colors.green))]), ), onTap: () { Navigator.of(context).pushNamed("/typed_text_room"); @@ -84,12 +59,7 @@ class _HomeState extends State { ), ListTile( title: Text.rich( - TextSpan(children: [ - TextSpan(text: "Typed Sip Example"), - TextSpan( - text: " New", - style: TextStyle(color: Colors.green)) - ]), + TextSpan(children: [TextSpan(text: "Typed Sip Example"), TextSpan(text: " New", style: TextStyle(color: Colors.green))]), ), onTap: () { Navigator.of(context).pushNamed("/typed_sip"); diff --git a/example/lib/main.dart b/example/lib/main.dart index 4c299309..56544ee3 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -7,7 +7,6 @@ import 'package:janus_client_example/typed_examples/streaming.dart'; import 'package:janus_client_example/typed_examples/video_call.dart'; import 'typed_examples/text_room.dart'; - void main() { runApp(MaterialApp( initialRoute: '/', diff --git a/example/lib/typed_examples/audio_bridge.dart b/example/lib/typed_examples/audio_bridge.dart index 8c6511ba..c489c95e 100644 --- a/example/lib/typed_examples/audio_bridge.dart +++ b/example/lib/typed_examples/audio_bridge.dart @@ -50,20 +50,14 @@ class _AudioRoomState extends State { stringIds: false, apiSecret: "SecureIt", transport: ws, - iceServers: [ - RTCIceServer( - urls: "stun:stun1.l.google.com:19302", - username: "", - credential: "") - ]); + iceServers: [RTCIceServer(urls: "stun:stun1.l.google.com:19302", username: "", credential: "")]); session = await j?.createSession(); pluginHandle = await session?.attach(); // List devices = // await navigator.mediaDevices.enumerateDevices(); // MediaDeviceInfo microphone = // devices.firstWhere((element) => element.kind == "audioinput"); - await pluginHandle?.initializeMediaDevices(context: context, - 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); @@ -115,8 +109,7 @@ class _AudioRoomState extends State { participants.update(data.userId.toString(), (value) { return value?.copyWith(talking: data.isTalking); }, ifAbsent: () { - return participants[data.userId.toString()] - ?.copyWith(talking: data.isTalking); + return participants[data.userId.toString()]?.copyWith(talking: data.isTalking); }); }); } @@ -168,9 +161,7 @@ class _AudioRoomState extends State { icon: Icon(Icons.input), itemBuilder: (BuildContext context) { if (_mediaDevicesList != null) { - return _mediaDevicesList! - .where((device) => device.kind == 'audioinput') - .map((device) { + return _mediaDevicesList!.where((device) => device.kind == 'audioinput').map((device) { return PopupMenuItem( value: device.deviceId, child: Text(device.label), @@ -220,9 +211,7 @@ class _AudioRoomState extends State { ), onPressed: callStarted ? () async { - if (pluginHandle - ?.webRTCHandle?.peerConnection?.signalingState != - RTCSignalingState.RTCSignalingStateClosed) { + if (pluginHandle?.webRTCHandle?.peerConnection?.signalingState != RTCSignalingState.RTCSignalingStateClosed) { setState(() { muted = !muted; }); @@ -252,17 +241,14 @@ class _AudioRoomState extends State { child: ListView.builder( itemCount: remoteRenderers.entries.map((e) => e.value).length, itemBuilder: (context, index) { - var renderer = remoteRenderers.entries - .map((e) => e.value) - .toList()[index]; + var renderer = remoteRenderers.entries.map((e) => e.value).toList()[index]; return Container( color: Colors.red, width: 50, height: 50, child: RTCVideoView( renderer, - objectFit: RTCVideoViewObjectFit - .RTCVideoViewObjectFitContain, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitContain, )); })), ), @@ -283,9 +269,7 @@ class _AudioRoomState extends State { color: Colors.white, ), Icon( - e?.talking == true - ? Icons.volume_up_sharp - : Icons.volume_mute_sharp, + e?.talking == true ? Icons.volume_up_sharp : Icons.volume_mute_sharp, color: Colors.white, ) ], diff --git a/example/lib/typed_examples/google_meet.dart b/example/lib/typed_examples/google_meet.dart index 757b9e7b..72b626fc 100644 --- a/example/lib/typed_examples/google_meet.dart +++ b/example/lib/typed_examples/google_meet.dart @@ -58,8 +58,7 @@ class _VideoRoomState extends State { } attachPlugin({bool pop = false}) async { - JanusVideoRoomPlugin? videoPlugin = - await session?.attach(); + JanusVideoRoomPlugin? videoPlugin = await session?.attach(); videoPlugin?.typedMessages?.listen((event) async { Object data = event.event.plugindata?.data; if (data is VideoRoomJoinedEvent) { @@ -67,13 +66,7 @@ class _VideoRoomState extends State { if (pop) { Navigator.of(context).pop(joiningDialog); } - (await videoPlugin.configure( - bitrate: 3000000, - sessionDescription: await videoPlugin.createOffer( - audioRecv: false, - audioSend: true, - videoSend: true, - videoRecv: false))); + (await videoPlugin.configure(bitrate: 3000000, sessionDescription: await videoPlugin.createOffer(audioRecv: false, audioSend: true, videoSend: true, videoRecv: false))); } if (data is VideoRoomLeavingEvent) { unSubscribeTo(data.leaving!); @@ -88,16 +81,7 @@ class _VideoRoomState extends State { initialize() async { ws = WebSocketJanusTransport(url: servermap['janus_ws']); - j = JanusClient( - transport: ws!, - isUnifiedPlan: true, - iceServers: [ - RTCIceServer( - urls: "stun:stun1.l.google.com:19302", - username: "", - credential: "") - ], - loggerLevel: Level.FINE); + j = JanusClient(transport: ws!, isUnifiedPlan: true, iceServers: [RTCIceServer(urls: "stun:stun1.l.google.com:19302", username: "", credential: "")], loggerLevel: Level.FINE); session = await j?.createSession(); initLocalMediaRenderer(); } @@ -111,11 +95,9 @@ class _VideoRoomState extends State { videoState.feedIdToDisplayStreamsMap.remove(id.toString()); await videoState.streamsToBeRendered[id]?.dispose(); var unsubscribeStreams = (feed['streams'] as List).map((stream) { - return SubscriberUpdateStream( - feed: id, mid: stream['mid'], crossrefid: null); + return SubscriberUpdateStream(feed: id, mid: stream['mid'], crossrefid: null); }).toList(); - if (remotePlugin != null) - await remotePlugin?.update(unsubscribe: unsubscribeStreams); + if (remotePlugin != null) await remotePlugin?.update(unsubscribe: unsubscribeStreams); videoState.feedIdToMidSubscriptionMap.remove(id); } @@ -131,10 +113,8 @@ class _VideoRoomState extends State { streams?.forEach((element) { videoState.subStreamsToFeedIdMap[element['mid']] = element; // to avoid duplicate subscriptions - if (videoState.feedIdToMidSubscriptionMap[element['feed_id']] == null) - videoState.feedIdToMidSubscriptionMap[element['feed_id']] = {}; - videoState.feedIdToMidSubscriptionMap[element['feed_id']] - [element['mid']] = true; + if (videoState.feedIdToMidSubscriptionMap[element['feed_id']] == null) videoState.feedIdToMidSubscriptionMap[element['feed_id']] = {}; + videoState.feedIdToMidSubscriptionMap[element['feed_id']][element['mid']] = true; }); if (payload.jsep != null) { await remotePlugin?.handleRemoteJsep(payload.jsep); @@ -143,48 +123,33 @@ class _VideoRoomState extends State { }); remotePlugin?.remoteTrack?.listen((event) async { - print({ - 'mid': event.mid, - 'flowing': event.flowing, - 'id': event.track?.id, - 'kind': event.track?.kind - }); + print({'mid': event.mid, 'flowing': event.flowing, 'id': event.track?.id, 'kind': event.track?.kind}); int? feedId = videoState.subStreamsToFeedIdMap[event.mid]?['feed_id']; - String? displayName = - videoState.feedIdToDisplayStreamsMap[feedId]?['display']; + String? displayName = videoState.feedIdToDisplayStreamsMap[feedId]?['display']; if (feedId != null) { - if (videoState.streamsToBeRendered.containsKey(feedId.toString()) && - event.track?.kind == "audio") { - var existingRenderer = - videoState.streamsToBeRendered[feedId.toString()]; + if (videoState.streamsToBeRendered.containsKey(feedId.toString()) && event.track?.kind == "audio") { + var existingRenderer = videoState.streamsToBeRendered[feedId.toString()]; existingRenderer?.mediaStream?.addTrack(event.track!); - existingRenderer?.videoRenderer.srcObject = - existingRenderer.mediaStream; + existingRenderer?.videoRenderer.srcObject = existingRenderer.mediaStream; existingRenderer?.videoRenderer.muted = false; setState(() {}); } - if (!videoState.streamsToBeRendered.containsKey(feedId.toString()) && - event.track?.kind == "video") { + if (!videoState.streamsToBeRendered.containsKey(feedId.toString()) && event.track?.kind == "video") { var localStream = StreamRenderer(feedId.toString()); await localStream.init(); - localStream.mediaStream = - await createLocalMediaStream(feedId.toString()); + localStream.mediaStream = await createLocalMediaStream(feedId.toString()); localStream.mediaStream?.addTrack(event.track!); localStream.videoRenderer.srcObject = localStream.mediaStream; localStream.publisherName = displayName; localStream.publisherId = feedId.toString(); setState(() { - videoState.streamsToBeRendered - .putIfAbsent(feedId.toString(), () => localStream); + videoState.streamsToBeRendered.putIfAbsent(feedId.toString(), () => localStream); }); } } }); - List streams = sources - .map((e) => e.map((e) => PublisherStream( - feed: e['id'], mid: e['mid'], simulcast: e['simulcast']))) - .expand((element) => element) - .toList(); + List streams = + sources.map((e) => e.map((e) => PublisherStream(feed: e['id'], mid: e['mid'], simulcast: e['simulcast']))).expand((element) => element).toList(); await remotePlugin?.joinSubscriber(myRoom, streams: streams, pin: myPin); return; } @@ -200,15 +165,11 @@ class _VideoRoomState extends State { 'feed': stream['id'], // This is mandatory 'mid': stream['mid'] // This is optional (all streams, if missing) }); - videoState.feedIdToMidSubscriptionMap[stream['id']] - ?.remove(stream['mid']); + videoState.feedIdToMidSubscriptionMap[stream['id']]?.remove(stream['mid']); videoState.feedIdToMidSubscriptionMap.remove(stream['id']); continue; } - if (videoState.feedIdToMidSubscriptionMap[stream['id']] != null && - videoState.feedIdToMidSubscriptionMap[stream['id']] - [stream['mid']] == - true) { + if (videoState.feedIdToMidSubscriptionMap[stream['id']] != null && videoState.feedIdToMidSubscriptionMap[stream['id']][stream['mid']] == true) { print("Already subscribed to stream, skipping:"); continue; } @@ -219,26 +180,17 @@ class _VideoRoomState extends State { 'feed': stream['id'], // This is mandatory 'mid': stream['mid'] // This is optional (all streams, if missing) }); - if (videoState.feedIdToMidSubscriptionMap[stream['id']] == null) - videoState.feedIdToMidSubscriptionMap[stream['id']] = {}; - videoState.feedIdToMidSubscriptionMap[stream['id']][stream['mid']] = - true; + if (videoState.feedIdToMidSubscriptionMap[stream['id']] == null) videoState.feedIdToMidSubscriptionMap[stream['id']] = {}; + videoState.feedIdToMidSubscriptionMap[stream['id']][stream['mid']] = true; } } - if ((added == null || added.length == 0) && - (removed == null || removed.length == 0)) { + if ((added == null || added.length == 0) && (removed == null || removed.length == 0)) { // Nothing to do return; } await remotePlugin?.update( - subscribe: added - ?.map((e) => SubscriberUpdateStream( - feed: e['feed'], mid: e['mid'], crossrefid: null)) - .toList(), - unsubscribe: removed - ?.map((e) => SubscriberUpdateStream( - feed: e['feed'], mid: e['mid'], crossrefid: null)) - .toList()); + subscribe: added?.map((e) => SubscriberUpdateStream(feed: e['feed'], mid: e['mid'], crossrefid: null)).toList(), + unsubscribe: removed?.map((e) => SubscriberUpdateStream(feed: e['feed'], mid: e['mid'], crossrefid: null)).toList()); } manageMuteUIEvents(String mid, String kind, bool muted) async { @@ -246,8 +198,7 @@ class _VideoRoomState extends State { if (feedId == null) { return; } - StreamRenderer renderer = - videoState.streamsToBeRendered[feedId.toString()]!; + StreamRenderer renderer = videoState.streamsToBeRendered[feedId.toString()]!; setState(() { if (kind == 'audio') { renderer.isAudioMuted = muted; @@ -264,11 +215,7 @@ class _VideoRoomState extends State { if ([myId, screenShareId].contains(publisher['id'])) { continue; } - videoState.feedIdToDisplayStreamsMap[publisher['id']] = { - 'id': publisher['id'], - 'display': publisher['display'], - 'streams': publisher['streams'] - }; + videoState.feedIdToDisplayStreamsMap[publisher['id']] = {'id': publisher['id'], 'display': publisher['display'], 'streams': publisher['streams']}; List mappedStreams = []; for (Map stream in publisher['streams'] ?? []) { if (stream['disabled'] == true) { @@ -276,10 +223,7 @@ class _VideoRoomState extends State { } else { manageMuteUIEvents(stream['mid'], stream['type'], false); } - if (videoState.feedIdToMidSubscriptionMap[publisher['id']] != null && - videoState.feedIdToMidSubscriptionMap[publisher['id']] - ?[stream['mid']] == - true) { + if (videoState.feedIdToMidSubscriptionMap[publisher['id']] != null && videoState.feedIdToMidSubscriptionMap[publisher['id']]?[stream['mid']] == true) { continue; } stream['id'] = publisher['id']; @@ -306,19 +250,15 @@ class _VideoRoomState extends State { }); videoPlugin?.renegotiationNeeded?.listen((event) async { - if (videoPlugin?.webRTCHandle?.peerConnection?.signalingState != - RTCSignalingState.RTCSignalingStateStable) return; + if (videoPlugin?.webRTCHandle?.peerConnection?.signalingState != RTCSignalingState.RTCSignalingStateStable) return; print('retrying to connect publisher'); - var offer = await videoPlugin?.createOffer( - audioRecv: false, audioSend: true, videoRecv: false, videoSend: true); + var offer = await videoPlugin?.createOffer(audioRecv: false, audioSend: true, videoRecv: false, videoSend: true); await videoPlugin?.configure(sessionDescription: offer); }); screenPlugin?.renegotiationNeeded?.listen((event) async { - if (screenPlugin?.webRTCHandle?.peerConnection?.signalingState != - RTCSignalingState.RTCSignalingStateStable) return; + if (screenPlugin?.webRTCHandle?.peerConnection?.signalingState != RTCSignalingState.RTCSignalingStateStable) return; print('retrying to connect publisher'); - var offer = await screenPlugin?.createOffer( - audioRecv: false, audioSend: true, videoRecv: false, videoSend: true); + var offer = await screenPlugin?.createOffer(audioRecv: false, audioSend: true, videoRecv: false, videoSend: true); await screenPlugin?.configure(sessionDescription: offer); }); } @@ -330,8 +270,7 @@ class _VideoRoomState extends State { eventMessagesHandler(); await localVideoRenderer.init(); - localVideoRenderer.mediaStream = await videoPlugin - ?.initializeMediaDevices(context: context, mediaConstraints: { + localVideoRenderer.mediaStream = await videoPlugin?.initializeMediaDevices(context: context, mediaConstraints: { 'audio': { 'deviceId': {'exact': selectedAudioInputDeviceId}, }, @@ -340,11 +279,9 @@ class _VideoRoomState extends State { localVideoRenderer.videoRenderer.srcObject = localVideoRenderer.mediaStream; localVideoRenderer.publisherName = "You"; setState(() { - videoState.streamsToBeRendered - .putIfAbsent('local', () => localVideoRenderer); + videoState.streamsToBeRendered.putIfAbsent('local', () => localVideoRenderer); }); - await videoPlugin?.joinPublisher(myRoom, - displayName: username.text, id: myId, pin: myPin); + await videoPlugin?.joinPublisher(myRoom, displayName: username.text, id: myId, pin: myPin); } screenShare() async { @@ -358,12 +295,7 @@ class _VideoRoomState extends State { if (data is VideoRoomJoinedEvent) { myPvtId = data.privateId; (await screenPlugin?.configure( - bitrate: 3000000, - sessionDescription: await screenPlugin?.createOffer( - audioRecv: false, - audioSend: true, - videoSend: true, - videoRecv: false))); + bitrate: 3000000, sessionDescription: await screenPlugin?.createOffer(audioRecv: false, audioSend: true, videoSend: true, videoRecv: false))); } if (data is VideoRoomLeavingEvent) { unSubscribeTo(data.leaving!); @@ -375,21 +307,13 @@ class _VideoRoomState extends State { }); await localScreenSharingRenderer.init(); localScreenSharingRenderer.mediaStream = - await screenPlugin?.initializeMediaDevices( - context: context, - mediaConstraints: {'video': true, 'audio': true}, - useDisplayMediaDevices: true); - localScreenSharingRenderer.videoRenderer.srcObject = - localScreenSharingRenderer.mediaStream; + await screenPlugin?.initializeMediaDevices(context: context, mediaConstraints: {'video': true, 'audio': true}, useDisplayMediaDevices: true); + localScreenSharingRenderer.videoRenderer.srcObject = localScreenSharingRenderer.mediaStream; localScreenSharingRenderer.publisherName = "Your Screenshare"; setState(() { - videoState.streamsToBeRendered.putIfAbsent( - localScreenSharingRenderer.id, () => localScreenSharingRenderer); + videoState.streamsToBeRendered.putIfAbsent(localScreenSharingRenderer.id, () => localScreenSharingRenderer); }); - await screenPlugin?.joinPublisher(myRoom, - displayName: username.text + "_screenshare", - id: screenShareId, - pin: myPin); + await screenPlugin?.joinPublisher(myRoom, displayName: username.text + "_screenshare", id: screenShareId, pin: myPin); } disposeScreenSharing() async { @@ -399,8 +323,7 @@ class _VideoRoomState extends State { await screenPlugin?.unpublish(); StreamRenderer? rendererRemoved; setState(() { - rendererRemoved = - videoState.streamsToBeRendered.remove(localScreenSharingRenderer.id); + rendererRemoved = videoState.streamsToBeRendered.remove(localScreenSharingRenderer.id); }); await rendererRemoved?.dispose(); await screenPlugin?.hangup(); @@ -414,8 +337,7 @@ class _VideoRoomState extends State { await videoPlugin?.switchCamera(deviceId: await getCameraDeviceId(front)); localVideoRenderer = StreamRenderer('local'); await localVideoRenderer.init(); - localVideoRenderer.videoRenderer.srcObject = - videoPlugin?.webRTCHandle!.localStream; + localVideoRenderer.videoRenderer.srcObject = videoPlugin?.webRTCHandle!.localStream; localVideoRenderer.publisherName = "My Camera"; setState(() { videoState.streamsToBeRendered['local'] = localVideoRenderer; @@ -423,15 +345,11 @@ class _VideoRoomState extends State { } mute(RTCPeerConnection? peerConnection, String kind, bool enabled) async { - var transreciever = (await peerConnection?.getTransceivers()) - ?.where((element) => element.sender.track?.kind == kind) - .toList(); + var transreciever = (await peerConnection?.getTransceivers())?.where((element) => element.sender.track?.kind == kind).toList(); if (transreciever?.isEmpty == true) { return; } - await transreciever?.first.setDirection(enabled - ? TransceiverDirection.SendOnly - : TransceiverDirection.Inactive); + await transreciever?.first.setDirection(enabled ? TransceiverDirection.SendOnly : TransceiverDirection.Inactive); } callEnd() async { @@ -459,8 +377,7 @@ class _VideoRoomState extends State { remotePlugin = null; } - Future> audioInputDevices = - navigator.mediaDevices.enumerateDevices(); + Future> audioInputDevices = navigator.mediaDevices.enumerateDevices(); String? selectedAudioInputDeviceId; String? selectedAudioOutputDeviceId; Future showJoiningDialog() async { @@ -470,17 +387,14 @@ class _VideoRoomState extends State { builder: (context) { return StatefulBuilder(builder: ((context, setState) { return AlertDialog( - title: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('Join Room'), - IconButton( - onPressed: () { - Navigator.of(context).pop(this); - }, - icon: Icon(Icons.close)) - ]), + title: Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Text('Join Room'), + IconButton( + onPressed: () { + Navigator.of(context).pop(this); + }, + icon: Icon(Icons.close)) + ]), actionsAlignment: MainAxisAlignment.start, actions: [ Center( @@ -527,14 +441,9 @@ class _VideoRoomState extends State { builder: (context, snapshot) { if (snapshot.data != null) { return DropdownButtonFormField( - decoration: InputDecoration( - label: Text('Audio Input Device')), + decoration: InputDecoration(label: Text('Audio Input Device')), value: selectedAudioInputDeviceId, - items: snapshot.data - ?.map((e) => DropdownMenuItem( - value: e.deviceId, - child: Text('${e.label}'))) - .toList(), + items: snapshot.data?.map((e) => DropdownMenuItem(value: e.deviceId, child: Text('${e.label}'))).toList(), onChanged: (value) async { print(value); setState(() { @@ -550,21 +459,15 @@ class _VideoRoomState extends State { builder: (context, snapshot) { if (snapshot.data != null) { return DropdownButtonFormField( - decoration: InputDecoration( - label: Text('Audio Output Device')), + decoration: InputDecoration(label: Text('Audio Output Device')), value: selectedAudioOutputDeviceId, - items: snapshot.data - ?.map((e) => DropdownMenuItem( - value: e.deviceId, - child: Text('${e.label}'))) - .toList(), + items: snapshot.data?.map((e) => DropdownMenuItem(value: e.deviceId, child: Text('${e.label}'))).toList(), onChanged: (value) async { print(value); setState(() { selectedAudioOutputDeviceId = value; }); - await Helper.selectAudioOutput( - selectedAudioOutputDeviceId!); + await Helper.selectAudioOutput(selectedAudioOutputDeviceId!); }); } return CircularProgressIndicator(); @@ -625,8 +528,7 @@ class _VideoRoomState extends State { setState(() { audioEnabled = !audioEnabled; }); - await mute(videoPlugin?.webRTCHandle?.peerConnection, - 'audio', audioEnabled); + await mute(videoPlugin?.webRTCHandle?.peerConnection, 'audio', audioEnabled); setState(() { localVideoRenderer.isAudioMuted = !audioEnabled; }); @@ -642,8 +544,7 @@ class _VideoRoomState extends State { setState(() { videoEnabled = !videoEnabled; }); - await mute(videoPlugin?.webRTCHandle?.peerConnection, - 'video', videoEnabled); + await mute(videoPlugin?.webRTCHandle?.peerConnection, 'video', videoEnabled); } : null), IconButton( @@ -657,13 +558,10 @@ class _VideoRoomState extends State { ), body: GridView.builder( shrinkWrap: true, - gridDelegate: - SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3), itemCount: videoState.streamsToBeRendered.entries.length, itemBuilder: (context, index) { - List items = videoState.streamsToBeRendered.entries - .map((e) => e.value) - .toList(); + List items = videoState.streamsToBeRendered.entries.map((e) => e.value).toList(); StreamRenderer remoteStream = items[index]; return Stack( children: [ @@ -671,16 +569,13 @@ class _VideoRoomState extends State { visible: remoteStream.isVideoMuted == false, replacement: Container( child: Center( - child: Text( - "Video Paused By " + remoteStream.publisherName!, - style: TextStyle(color: Colors.black)), + child: Text("Video Paused By " + remoteStream.publisherName!, style: TextStyle(color: Colors.black)), ), ), child: RTCVideoView( remoteStream.videoRenderer, filterQuality: FilterQuality.none, - objectFit: - RTCVideoViewObjectFit.RTCVideoViewObjectFitContain, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitContain, ), ), Align( @@ -690,9 +585,7 @@ class _VideoRoomState extends State { mainAxisAlignment: MainAxisAlignment.end, children: [ Text(remoteStream.publisherName!), - Icon(remoteStream.isAudioMuted == true - ? Icons.mic_off - : Icons.mic), + Icon(remoteStream.isAudioMuted == true ? Icons.mic_off : Icons.mic), IconButton( onPressed: () async { fullScreenDialog = await showDialog( @@ -717,8 +610,7 @@ class _VideoRoomState extends State { alignment: Alignment.topRight, child: IconButton( onPressed: () { - Navigator.of(context) - .pop(fullScreenDialog); + Navigator.of(context).pop(fullScreenDialog); }, icon: Icon( Icons.close, diff --git a/example/lib/typed_examples/streaming.dart b/example/lib/typed_examples/streaming.dart index 1615d673..902ba8a3 100644 --- a/example/lib/typed_examples/streaming.dart +++ b/example/lib/typed_examples/streaming.dart @@ -40,11 +40,7 @@ class _StreamingState extends State { DropdownButtonFormField( isExpanded: true, value: selectedStreamId, - items: List.generate( - streams.length, - (index) => DropdownMenuItem( - value: streams[index].id, - child: Text(streams[index].description ?? ''))), + items: List.generate(streams.length, (index) => DropdownMenuItem(value: streams[index].id, child: Text(streams[index].description ?? ''))), onChanged: (v) async { print(v); if (v != null) { @@ -74,10 +70,7 @@ class _StreamingState extends State { j = JanusClient( transport: ws, iceServers: [ - RTCIceServer( - username: '', - credential: '', - urls: 'stun:stun.l.google.com:19302'), + RTCIceServer(username: '', credential: '', urls: 'stun:stun.l.google.com:19302'), ], isUnifiedPlan: true, ); @@ -90,37 +83,29 @@ class _StreamingState extends State { }); showStreamSelectionDialog(); plugin.remoteTrack?.listen((event) async { - if (event.track != null && - event.flowing == true && - event.track?.kind == 'audio') { + if (event.track != null && event.flowing == true && event.track?.kind == 'audio') { MediaStream temp = await createLocalMediaStream(event.track!.id!); setState(() { - remoteAudioRenderers.putIfAbsent( - event.track!.id!, () => RTCVideoRenderer()); + remoteAudioRenderers.putIfAbsent(event.track!.id!, () => RTCVideoRenderer()); remoteAudioStreams.putIfAbsent(event.track!.id!, () => temp); }); await remoteAudioRenderers[event.track!.id!]?.initialize(); await remoteAudioStreams[event.track!.id!]?.addTrack(event.track!); - remoteAudioRenderers[event.track!.id!]?.srcObject = - remoteAudioStreams[event.track!.id!]; + remoteAudioRenderers[event.track!.id!]?.srcObject = remoteAudioStreams[event.track!.id!]; if (kIsWeb) { remoteAudioRenderers[event.track!.id!]?.muted = false; } } - if (event.track != null && - event.flowing == true && - event.track?.kind == 'video') { + if (event.track != null && event.flowing == true && event.track?.kind == 'video') { MediaStream temp = await createLocalMediaStream(event.track!.id!); setState(() { - remoteVideoRenderers.putIfAbsent( - event.track!.id!, () => RTCVideoRenderer()); + remoteVideoRenderers.putIfAbsent(event.track!.id!, () => RTCVideoRenderer()); remoteVideoStreams.putIfAbsent(event.track!.id!, () => temp); }); await remoteVideoRenderers[event.track!.id!]?.initialize(); await remoteVideoStreams[event.track!.id!]?.addTrack(event.track!); - remoteVideoRenderers[event.track!.id!]?.srcObject = - remoteVideoStreams[event.track!.id!]; + remoteVideoRenderers[event.track!.id!]?.srcObject = remoteVideoStreams[event.track!.id!]; if (kIsWeb) { remoteVideoRenderers[event.track!.id!]?.muted = false; } @@ -177,20 +162,13 @@ class _StreamingState extends State { Widget build(BuildContext context) { return Scaffold( body: Stack(children: [ - ...remoteAudioRenderers.entries - .map((e) => e.value) - .map((e) => RTCVideoView(e)) - .toList(), + ...remoteAudioRenderers.entries.map((e) => e.value).map((e) => RTCVideoView(e)).toList(), Column( children: [ Expanded( child: GridView.count( crossAxisCount: 1, - childAspectRatio: MediaQuery.of(context).size.width / - (MediaQuery.of(context).size.height / - (remoteVideoRenderers.length > 0 - ? remoteVideoRenderers.length - : 1)), + childAspectRatio: MediaQuery.of(context).size.width / (MediaQuery.of(context).size.height / (remoteVideoRenderers.length > 0 ? remoteVideoRenderers.length : 1)), mainAxisSpacing: 0, crossAxisSpacing: 5, shrinkWrap: true, @@ -198,8 +176,7 @@ class _StreamingState extends State { .map((e) => e.value) .map((e) => RTCVideoView( e, - objectFit: - RTCVideoViewObjectFit.RTCVideoViewObjectFitContain, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitContain, )) .toList(), )), @@ -229,8 +206,7 @@ class _StreamingState extends State { backgroundColor: Colors.grey, radius: 30, child: IconButton( - icon: Icon( - isMuted ? Icons.volume_off : Icons.volume_up), + icon: Icon(isMuted ? Icons.volume_off : Icons.volume_up), color: Colors.white, onPressed: () async { if (isMuted) { @@ -242,8 +218,7 @@ class _StreamingState extends State { isMuted = true; }); } - var transrecievers = await plugin - .webRTCHandle?.peerConnection?.transceivers; + var transrecievers = await plugin.webRTCHandle?.peerConnection?.transceivers; transrecievers?.forEach((element) { if (element.receiver.track?.kind == 'audio') { element.receiver.track?.enabled = !isMuted; @@ -257,8 +232,7 @@ class _StreamingState extends State { backgroundColor: Colors.green, radius: 30, child: IconButton( - icon: Icon( - isPlaying ? Icons.pause : Icons.play_arrow), + icon: Icon(isPlaying ? Icons.pause : Icons.play_arrow), color: Colors.white, onPressed: () async { if (isPlaying) { @@ -286,11 +260,7 @@ class _StreamingState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, - children: [ - CircularProgressIndicator(), - Padding(padding: EdgeInsets.all(10)), - Text("Fetching Available Streams..") - ], + children: [CircularProgressIndicator(), Padding(padding: EdgeInsets.all(10)), Text("Fetching Available Streams..")], ), ) : Padding(padding: EdgeInsets.zero), diff --git a/example/lib/typed_examples/text_room.dart b/example/lib/typed_examples/text_room.dart index 72f9bde1..73fa62ad 100644 --- a/example/lib/typed_examples/text_room.dart +++ b/example/lib/typed_examples/text_room.dart @@ -25,16 +25,8 @@ class _TextRoomExampleState extends State { initializeClient() async { rest = RestJanusTransport(url: servermap['janus_rest']); ws = WebSocketJanusTransport(url: servermap['janus_ws']); - janusClient = JanusClient( - withCredentials: true, - apiSecret: "SecureIt", - transport: ws, - iceServers: [ - RTCIceServer( - urls: "stun:stun1.l.google.com:19302", - username: "", - credential: "") - ]); + janusClient = + JanusClient(withCredentials: true, apiSecret: "SecureIt", transport: ws, iceServers: [RTCIceServer(urls: "stun:stun1.l.google.com:19302", username: "", credential: "")]); session = await janusClient.createSession(); textRoom = await session.attach(); } @@ -73,9 +65,7 @@ class _TextRoomExampleState extends State { padding: const EdgeInsets.all(8.0), child: ElevatedButton( onPressed: () async { - await textRoom.joinRoom( - myRoom, userNameController.text, - display: userNameController.text); + await textRoom.joinRoom(myRoom, userNameController.text, display: userNameController.text); Navigator.of(context).pop(dialog); }, child: Text('Join')), @@ -90,8 +80,7 @@ class _TextRoomExampleState extends State { Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( - decoration: - InputDecoration.collapsed(hintText: "Username"), + decoration: InputDecoration.collapsed(hintText: "Username"), controller: userNameController, ), ), @@ -117,8 +106,7 @@ class _TextRoomExampleState extends State { } if (data['textroom'] == 'leave') { setState(() { - textMessages - .add({'from': data['username'], 'text': 'Left The Chat!'}); + textMessages.add({'from': data['username'], 'text': 'Left The Chat!'}); Future.delayed(Duration(seconds: 1)).then((value) { userNameDisplayMap.remove(data['username']); }); @@ -127,18 +115,15 @@ class _TextRoomExampleState extends State { } if (data['textroom'] == 'join') { setState(() { - userNameDisplayMap.putIfAbsent( - data['username'], () => data['display']); - textMessages - .add({'from': data['username'], 'text': 'Joined The Chat!'}); + userNameDisplayMap.putIfAbsent(data['username'], () => data['display']); + textMessages.add({'from': data['username'], 'text': 'Joined The Chat!'}); }); scrollToBottom(); } if (data['participants'] != null) { (data['participants'] as List).forEach((element) { setState(() { - userNameDisplayMap.putIfAbsent( - element['username'], () => element['display']); + userNameDisplayMap.putIfAbsent(element['username'], () => element['display']); }); }); } @@ -212,13 +197,8 @@ class _TextRoomExampleState extends State { controller: controller, itemBuilder: (context, index) { return ListTile( - title: Text( - userNameDisplayMap[textMessages[index]['from']] != null - ? userNameDisplayMap[textMessages[index]['from']]! - : ''), - subtitle: Text(textMessages[index]['text'] != null - ? textMessages[index]['text'] - : ''), + title: Text(userNameDisplayMap[textMessages[index]['from']] != null ? userNameDisplayMap[textMessages[index]['from']]! : ''), + subtitle: Text(textMessages[index]['text'] != null ? textMessages[index]['text'] : ''), ); }, itemCount: textMessages.length, @@ -246,8 +226,7 @@ class _TextRoomExampleState extends State { }, controller: nameController, cursorHeight: 24, - decoration: InputDecoration.collapsed( - hintText: "Type Your Message"), + decoration: InputDecoration.collapsed(hintText: "Type Your Message"), focusNode: focusNode, ), fit: FlexFit.tight, diff --git a/example/lib/util.dart b/example/lib/util.dart index 34050017..ef11f85f 100644 --- a/example/lib/util.dart +++ b/example/lib/util.dart @@ -58,22 +58,16 @@ class GenericVideoRoomManagedPlugin { void Function(void Function()) setState = (func) {}; GenericVideoRoomManagedPlugin({this.myRoom, this.myPin}); - Future unSubscribeStreamMedia( - VideoRoomPluginStateManager mediaState, int id, - {Map? subscriptionMap}) async { + Future unSubscribeStreamMedia(VideoRoomPluginStateManager mediaState, int id, {Map? subscriptionMap}) async { var feed = mediaState.feedIdToDisplayStreamsMap[id]; if (feed == null) return; mediaState.feedIdToDisplayStreamsMap.remove(id); await mediaState.streamsToBeRendered[id]?.dispose(); mediaState.streamsToBeRendered.remove(id); - mediaState.unSubscribeStreams = - (feed['streams'] as List).map((stream) { - return SubscriberUpdateStream( - feed: id, mid: stream.mid, crossrefid: null); + mediaState.unSubscribeStreams = (feed['streams'] as List).map((stream) { + return SubscriberUpdateStream(feed: id, mid: stream.mid, crossrefid: null); }).toList(); - if (remoteMediaHandle != null) - await remoteMediaHandle?.update( - unsubscribe: mediaState.unSubscribeStreams); + if (remoteMediaHandle != null) await remoteMediaHandle?.update(unsubscribe: mediaState.unSubscribeStreams); mediaState.unSubscribeStreams = []; if (subscriptionMap != null) { subscriptionMap.remove(id); @@ -82,47 +76,32 @@ class GenericVideoRoomManagedPlugin { mediaState.feedIdToMidSubscriptionMap.remove(id); } - subscribeToMedia(VideoRoomPluginStateManager mediaState, - List> sources) async { + subscribeToMedia(VideoRoomPluginStateManager mediaState, List> sources) async { if (sources.length == 0) return; - var streams = (sources) - .map((e) => PublisherStream(mid: e['mid'], feed: e['feed'])) - .toList(); + var streams = (sources).map((e) => PublisherStream(mid: e['mid'], feed: e['feed'])).toList(); if (remoteMediaHandle != null) { - await remoteMediaHandle?.update( - subscribe: mediaState.subscribeStreams, - unsubscribe: mediaState.unSubscribeStreams); + await remoteMediaHandle?.update(subscribe: mediaState.subscribeStreams, unsubscribe: mediaState.unSubscribeStreams); mediaState.subscribeStreams = []; mediaState.unSubscribeStreams = []; return; } remoteMediaHandle = await this.session?.attach(); remoteMediaHandle?.renegotiationNeeded?.listen((event) async { - if (remoteMediaHandle?.webRTCHandle?.peerConnection?.signalingState != - RTCSignalingState.RTCSignalingStateHaveRemoteOffer || - remoteMediaHandle?.webRTCHandle?.peerConnection?.signalingState != - RTCSignalingState.RTCSignalingStateHaveRemotePrAnswer) return; + if (remoteMediaHandle?.webRTCHandle?.peerConnection?.signalingState != RTCSignalingState.RTCSignalingStateHaveRemoteOffer || + remoteMediaHandle?.webRTCHandle?.peerConnection?.signalingState != RTCSignalingState.RTCSignalingStateHaveRemotePrAnswer) return; print('retrying to connect subscribers'); - await remoteMediaHandle?.start(myRoom, - answer: await remoteMediaHandle?.createAnswer( - audioRecv: true, - audioSend: false, - videoRecv: true, - videoSend: false)); + await remoteMediaHandle?.start(myRoom, answer: await remoteMediaHandle?.createAnswer(audioRecv: true, audioSend: false, videoRecv: true, videoSend: false)); }); - await remoteMediaHandle?.joinSubscriber(myRoom, - streams: streams, pin: myPin); + await remoteMediaHandle?.joinSubscriber(myRoom, streams: streams, pin: myPin); remoteMediaHandle?.typedMessages?.listen((event) async { Object data = event.event.plugindata?.data; await remoteMediaHandle?.handleRemoteJsep(event.jsep); if (data is VideoRoomUpdatedEvent) { data.streams?.forEach((element) { // to avoid duplicate subscriptions - if (mediaState.feedIdToMidSubscriptionMap[element.feedId] == null) - mediaState.feedIdToMidSubscriptionMap[element.feedId] = {}; - mediaState.feedIdToMidSubscriptionMap[element.feedId][element.mid] = - true; + if (mediaState.feedIdToMidSubscriptionMap[element.feedId] == null) mediaState.feedIdToMidSubscriptionMap[element.feedId] = {}; + mediaState.feedIdToMidSubscriptionMap[element.feedId][element.mid] = true; }); print('videoroom updated event triggered'); @@ -131,16 +110,10 @@ class GenericVideoRoomManagedPlugin { if (data is VideoRoomAttachedEvent) { data.streams?.forEach((element) { // to avoid duplicate subscriptions - if (mediaState.feedIdToMidSubscriptionMap[element.feedId] == null) - mediaState.feedIdToMidSubscriptionMap[element.feedId] = {}; - mediaState.feedIdToMidSubscriptionMap[element.feedId][element.mid] = - true; + if (mediaState.feedIdToMidSubscriptionMap[element.feedId] == null) mediaState.feedIdToMidSubscriptionMap[element.feedId] = {}; + mediaState.feedIdToMidSubscriptionMap[element.feedId][element.mid] = true; }); - var answer = await remoteMediaHandle?.createAnswer( - audioRecv: true, - audioSend: false, - videoRecv: true, - videoSend: false); + var answer = await remoteMediaHandle?.createAnswer(audioRecv: true, audioSend: false, videoRecv: true, videoSend: false); await remoteMediaHandle?.start(myRoom, answer: answer); } remoteMediaHandle?.remoteTrack?.listen((event) async { @@ -181,36 +154,27 @@ class GenericVideoRoomManagedPlugin { return; } - updateStateWithPublisherMediaInfo(VideoRoomPluginStateManager mediaState, - dynamic data, List ownIds) { + updateStateWithPublisherMediaInfo(VideoRoomPluginStateManager mediaState, dynamic data, List ownIds) { List> publisherStreams = []; for (Publishers publisher in data.publishers ?? []) { if (ownIds.contains(publisher.id)) { continue; } - mediaState.feedIdToDisplayStreamsMap[publisher.id!] = { - "id": publisher.id, - "display": publisher.display, - "streams": publisher.streams - }; + mediaState.feedIdToDisplayStreamsMap[publisher.id!] = {"id": publisher.id, "display": publisher.display, "streams": publisher.streams}; for (Streams stream in publisher.streams ?? []) { - if (mediaState.feedIdToMidSubscriptionMap[publisher.id] != null && - mediaState.feedIdToMidSubscriptionMap[publisher.id]?[stream.mid] == - true) { + if (mediaState.feedIdToMidSubscriptionMap[publisher.id] != null && mediaState.feedIdToMidSubscriptionMap[publisher.id]?[stream.mid] == true) { continue; } publisherStreams.add({"feed": publisher.id, ...stream.toMap()}); if (publisher.id != null && stream.mid != null) { - mediaState.subStreamsToFeedIdMap - .putIfAbsent(stream.mid, () => publisher.id); + mediaState.subStreamsToFeedIdMap.putIfAbsent(stream.mid, () => publisher.id); } } } return publisherStreams; } - init(VideoRoomPluginStateManager mediaState, JanusSession? session, - List ownIds, void Function(void Function()) setState) async { + init(VideoRoomPluginStateManager mediaState, JanusSession? session, List ownIds, void Function(void Function()) setState) async { this.session = session; this.setState = setState; mediaHandle = await this.session?.attach(); @@ -221,16 +185,9 @@ class GenericVideoRoomManagedPlugin { // if (afterJoined != null) { // await afterJoined(); // } else { - (await mediaHandle?.configure( - bitrate: 3000000, - sessionDescription: await mediaHandle?.createOffer( - audioRecv: false, - audioSend: false, - videoSend: true, - videoRecv: false))); + (await mediaHandle?.configure(bitrate: 3000000, sessionDescription: await mediaHandle?.createOffer(audioRecv: false, audioSend: false, videoSend: true, videoRecv: false))); // } - List> publisherStreams = - updateStateWithPublisherMediaInfo(mediaState, data, ownIds); + List> publisherStreams = updateStateWithPublisherMediaInfo(mediaState, data, ownIds); subscribeToMedia( mediaState, publisherStreams, @@ -238,8 +195,7 @@ class GenericVideoRoomManagedPlugin { } if (data is VideoRoomNewPublisherEvent) { print(data.publishers); - List> publisherStreams = - updateStateWithPublisherMediaInfo(mediaState, data, ownIds); + List> publisherStreams = updateStateWithPublisherMediaInfo(mediaState, data, ownIds); subscribeToMedia( mediaState, publisherStreams, @@ -262,11 +218,9 @@ class GenericVideoRoomManagedPlugin { } }); mediaHandle?.renegotiationNeeded?.listen((event) async { - if (mediaHandle?.webRTCHandle?.peerConnection?.signalingState != - RTCSignalingState.RTCSignalingStateStable) return; + if (mediaHandle?.webRTCHandle?.peerConnection?.signalingState != RTCSignalingState.RTCSignalingStateStable) return; print('retrying to connect publisher'); - var offer = await mediaHandle?.createOffer( - audioRecv: false, audioSend: true, videoRecv: false, videoSend: true); + var offer = await mediaHandle?.createOffer(audioRecv: false, audioSend: true, videoRecv: false, videoSend: true); await mediaHandle?.configure(sessionDescription: offer); }); } diff --git a/lib/janus_plugin.dart b/lib/janus_plugin.dart index 6f8c84f8..cf88e5f7 100644 --- a/lib/janus_plugin.dart +++ b/lib/janus_plugin.dart @@ -411,6 +411,7 @@ class JanusPlugin { await webRTCHandle?.peerConnection?.setRemoteDescription(data); } } + /// helper method for managing screen capture on web and desktop platforms Future getDisplayMediaStream(BuildContext context) async { MediaStream? screenStream; @@ -426,7 +427,7 @@ class JanusPlugin { 'deviceId': {'exact': source.id}, 'mandatory': {'frameRate': 30.0} }, - 'audio':true + 'audio': true }); stream.getVideoTracks()[0].onEnded = () { print('By adding a listener on onEnded you can: 1) catch stop video sharing on Web'); diff --git a/lib/utils.dart b/lib/utils.dart index 509fb5b0..baa59092 100644 --- a/lib/utils.dart +++ b/lib/utils.dart @@ -59,7 +59,6 @@ class EventMessage { } // - } class RTCIceServer { @@ -180,7 +179,6 @@ class RemoteTrack { } // - } Uuid getUuid() { diff --git a/test/janus_audio_bridge_plugin_test.dart b/test/janus_audio_bridge_plugin_test.dart index bdd2f913..29a4e16f 100644 --- a/test/janus_audio_bridge_plugin_test.dart +++ b/test/janus_audio_bridge_plugin_test.dart @@ -7,8 +7,7 @@ class _MyHttpOverrides extends HttpOverrides {} void main() async { TestWidgetsFlutterBinding.ensureInitialized(); HttpOverrides.global = _MyHttpOverrides(); - WebSocketJanusTransport ws = - WebSocketJanusTransport(url: 'wss://janus.conf.meetecho.com/ws'); + WebSocketJanusTransport ws = WebSocketJanusTransport(url: 'wss://janus.conf.meetecho.com/ws'); JanusClient client = JanusClient(transport: ws); JanusSession session = await client.createSession(); late JanusAudioBridgePlugin audioBridgePlugin; @@ -17,8 +16,7 @@ void main() async { audioBridgePlugin = await session.attach(); }); test('Test RtpForward', () async { - print(await audioBridgePlugin.rtpForward( - "1234", "https://janus.conf.meetecho.com", 9084)); + print(await audioBridgePlugin.rtpForward("1234", "https://janus.conf.meetecho.com", 9084)); }); test('mute a Participant', () async { print(await audioBridgePlugin.muteParticipant("1234", 3465765, true)); diff --git a/test/janus_client_test.dart b/test/janus_client_test.dart index 842b54c4..215dcbcd 100644 --- a/test/janus_client_test.dart +++ b/test/janus_client_test.dart @@ -8,10 +8,8 @@ class _MyHttpOverrides extends HttpOverrides {} void main() async { TestWidgetsFlutterBinding.ensureInitialized(); HttpOverrides.global = _MyHttpOverrides(); - WebSocketJanusTransport ws = - WebSocketJanusTransport(url: 'wss://janus.conf.meetecho.com/ws'); - RestJanusTransport rest = - RestJanusTransport(url: 'https://janus.conf.meetecho.com/janus'); + WebSocketJanusTransport ws = WebSocketJanusTransport(url: 'wss://janus.conf.meetecho.com/ws'); + RestJanusTransport rest = RestJanusTransport(url: 'https://janus.conf.meetecho.com/janus'); JanusClient client = JanusClient(transport: ws); group('WebSocketJanusTransport', () { test('info', () async { diff --git a/test/janus_session_test.dart b/test/janus_session_test.dart index 294e3bfa..b17cb197 100644 --- a/test/janus_session_test.dart +++ b/test/janus_session_test.dart @@ -3,8 +3,7 @@ import 'package:janus_client/janus_client.dart'; void main() async { - WebSocketJanusTransport ws = WebSocketJanusTransport( - url: 'wss://master-janus.onemandev.tech/websocket'); + WebSocketJanusTransport ws = WebSocketJanusTransport(url: 'wss://master-janus.onemandev.tech/websocket'); JanusClient j = JanusClient(transport: ws); // RestJanusTransport rest = // RestJanusTransport(url: 'https://master-janus.onemandev.tech/rest'); diff --git a/test/janus_text_room_plugin_test.dart b/test/janus_text_room_plugin_test.dart index 7e1730f0..870bb2cd 100644 --- a/test/janus_text_room_plugin_test.dart +++ b/test/janus_text_room_plugin_test.dart @@ -7,8 +7,7 @@ class _MyHttpOverrides extends HttpOverrides {} void main() async { TestWidgetsFlutterBinding.ensureInitialized(); HttpOverrides.global = _MyHttpOverrides(); - WebSocketJanusTransport ws = - WebSocketJanusTransport(url: 'wss://janus.conf.meetecho.com/ws'); + WebSocketJanusTransport ws = WebSocketJanusTransport(url: 'wss://janus.conf.meetecho.com/ws'); JanusClient client = JanusClient(transport: ws); JanusSession session = await client.createSession(); late JanusTextRoomPlugin textRoomPlugin; diff --git a/test/janus_transport_test.dart b/test/janus_transport_test.dart index e606c39b..5ff96930 100644 --- a/test/janus_transport_test.dart +++ b/test/janus_transport_test.dart @@ -6,26 +6,19 @@ import 'dart:io'; import 'package:janus_client/janus_client.dart'; void main() { - RestJanusTransport rest = - RestJanusTransport(url: 'https://master-janus.onemandev.tech/rest'); + RestJanusTransport rest = RestJanusTransport(url: 'https://master-janus.onemandev.tech/rest'); - WebSocketJanusTransport ws = WebSocketJanusTransport( - url: 'wss://master-janus.onemandev.tech/websocket'); + WebSocketJanusTransport ws = WebSocketJanusTransport(url: 'wss://master-janus.onemandev.tech/websocket'); ws.connect(); group('RestJanusTransport', () { test('Create a new Session', () async { - var response = - await rest.post({"janus": "create", "transaction": "sdhbds"}); + var response = await rest.post({"janus": "create", "transaction": "sdhbds"}); rest.sessionId = response['data']['id']; expect(response['janus'], 'success'); }); test('Attach A Plugin', () async { - Map request = { - "janus": "attach", - "plugin": "janus.plugin.videoroom", - "transaction": "random for attaching plugin" - }; + Map request = {"janus": "attach", "plugin": "janus.plugin.videoroom", "transaction": "random for attaching plugin"}; var response = await rest.post(request); expect(response['janus'], 'success'); @@ -47,11 +40,7 @@ void main() { }); group('WebSocketJanusTransport', () { test('Attach A Plugin', () async { - Map request = { - "janus": "attach", - "plugin": "janus.plugin.videoroom", - "transaction": "random for attaching plugin" - }; + Map request = {"janus": "attach", "plugin": "janus.plugin.videoroom", "transaction": "random for attaching plugin"}; ws.sink!.add(request); ws.stream.listen((event) { if (event['transaction'] == request['transaction']) { diff --git a/test/janus_video_call_plugin_test.dart b/test/janus_video_call_plugin_test.dart index cc9e112b..1ba065e8 100644 --- a/test/janus_video_call_plugin_test.dart +++ b/test/janus_video_call_plugin_test.dart @@ -8,8 +8,7 @@ class _MyHttpOverrides extends HttpOverrides {} void main() async { TestWidgetsFlutterBinding.ensureInitialized(); HttpOverrides.global = _MyHttpOverrides(); - WebSocketJanusTransport ws = - WebSocketJanusTransport(url: 'wss://janus.conf.meetecho.com/ws'); + WebSocketJanusTransport ws = WebSocketJanusTransport(url: 'wss://janus.conf.meetecho.com/ws'); // ws.connect(); JanusClient client = JanusClient(transport: ws); JanusSession session = await client.createSession(); From 951e310de2647b84f2670118221d1a2d0c3db6a9 Mon Sep 17 00:00:00 2001 From: shivanshtalwar Date: Sat, 13 May 2023 02:39:16 +0530 Subject: [PATCH 3/4] mac os tested screen share audio not working --- example/lib/typed_examples/google_meet.dart | 196 ++++++++++++++------ lib/janus_plugin.dart | 128 ++++++------- 2 files changed, 207 insertions(+), 117 deletions(-) diff --git a/example/lib/typed_examples/google_meet.dart b/example/lib/typed_examples/google_meet.dart index 72b626fc..33433d87 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; } } @@ -127,11 +130,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.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.track?.kind == "video") { @@ -194,7 +200,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; } @@ -271,11 +277,19 @@ class _VideoRoomState extends State { await localVideoRenderer.init(); localVideoRenderer.mediaStream = await videoPlugin?.initializeMediaDevices(context: context, mediaConstraints: { - 'audio': { - 'deviceId': {'exact': selectedAudioInputDeviceId}, - }, + if (selectedAudioInputDeviceId != null) + 'audio': { + 'deviceId': {'exact': selectedAudioInputDeviceId}, + }, + if (selectedVideoInputDeviceId != null) + 'video': { + 'deviceId': {'exact': selectedVideoInputDeviceId}, + } }); - await Helper.selectAudioInput(selectedAudioInputDeviceId!); + if (!WebRTC.platformIsWeb) { + await Helper.selectAudioInput(selectedAudioInputDeviceId!); + // await Helper. + } localVideoRenderer.videoRenderer.srcObject = localVideoRenderer.mediaStream; localVideoRenderer.publisherName = "You"; setState(() { @@ -345,11 +359,20 @@ class _VideoRoomState extends State { } mute(RTCPeerConnection? peerConnection, String kind, bool enabled) async { - var transreciever = (await peerConnection?.getTransceivers())?.where((element) => element.sender.track?.kind == kind).toList(); - if (transreciever?.isEmpty == true) { - return; - } - await transreciever?.first.setDirection(enabled ? TransceiverDirection.SendOnly : TransceiverDirection.Inactive); + // var transreciever = (await peerConnection?.getTransceivers())?.where((element) => element.sender.track?.kind == kind).toList(); + // if (transreciever == null || transreciever.isEmpty == true) { + // return; + // } + (await peerConnection?.senders)?.forEach((element) { + if (element.track?.kind == kind) { + element.track?.enabled = enabled; + } + }); + // if (!WebRTC.platformIsWeb&&kind=='audio') { + // Helper.setMicrophoneMute(!enabled, transreciever.first.sender.track!); + // return; + // } + // await transreciever?.first.setDirection(enabled ? TransceiverDirection.SendOnly : TransceiverDirection.Inactive); } callEnd() async { @@ -377,9 +400,105 @@ class _VideoRoomState extends State { remotePlugin = null; } - Future> audioInputDevices = navigator.mediaDevices.enumerateDevices(); + 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, @@ -437,43 +556,6 @@ class _VideoRoomState extends State { obscureText: true, decoration: InputDecoration(label: Text('Pin')), ), - 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 { - print(value); - setState(() { - selectedAudioInputDeviceId = value; - }); - }); - } - return CircularProgressIndicator(); - }, - future: Helper.enumerateDevices('audioinput'), - ), - 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; - }); - await Helper.selectAudioOutput(selectedAudioOutputDeviceId!); - }); - } - return CircularProgressIndicator(); - }, - future: Helper.enumerateDevices('audiooutput'), - ) ]), ), ); @@ -552,7 +634,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/lib/janus_plugin.dart b/lib/janus_plugin.dart index cf88e5f7..c3fdb9c5 100644 --- a/lib/janus_plugin.dart +++ b/lib/janus_plugin.dart @@ -619,80 +619,82 @@ class JanusPlugin { this._context._logger.finest('using transrecievers in prepare transrecievers'); RTCRtpTransceiver? audioTransceiver; RTCRtpTransceiver? videoTransceiver; - List transceivers = await webRTCHandle!.peerConnection!.transceivers; - if (transceivers.length > 0) { - transceivers.forEach((t) { - if ((t.sender.track != null && t.sender.track!.kind == "audio") || (t.receiver.track != null && t.receiver.track!.kind == "audio")) { - if (audioTransceiver == null) { - audioTransceiver = t; + List? transceivers; + try { + transceivers = await webRTCHandle?.peerConnection?.transceivers; + if (transceivers?.isNotEmpty == true) { + transceivers?.forEach((t) { + if ((t.sender.track != null && t.sender.track!.kind == "audio") || (t.receiver.track != null && t.receiver.track!.kind == "audio")) { + if (audioTransceiver == null) { + audioTransceiver = t; + } } - } - if ((t.sender.track != null && t.sender.track!.kind == "video") || (t.receiver.track != null && t.receiver.track!.kind == "video")) { - if (videoTransceiver == null) { - videoTransceiver = t; + if ((t.sender.track != null && t.sender.track!.kind == "video") || (t.receiver.track != null && t.receiver.track!.kind == "video")) { + if (videoTransceiver == null) { + videoTransceiver = t; + } } - } - }); - } - - if (!audioSend && !audioRecv) { - // Audio disabled: have we removed it? - if (audioTransceiver != null) { - audioTransceiver!.setDirection(TransceiverDirection.Inactive); - this._context._logger.finest("Setting audio transceiver to inactive:" + audioTransceiver.toString()); + }); } - } else { - // Take care of audio m-line - if (audioSend && audioRecv) { - if (audioTransceiver != null) { - audioTransceiver!.setDirection(TransceiverDirection.SendRecv); - this._context._logger.finest("Setting audio transceiver to sendrecv:" + audioTransceiver.toString()); - } - } else if (audioSend && !audioRecv) { + + if (!audioSend && !audioRecv) { + // Audio disabled: have we removed it? if (audioTransceiver != null) { - audioTransceiver!.setDirection(TransceiverDirection.SendOnly); - this._context._logger.finest("Setting audio transceiver to sendonly:" + audioTransceiver.toString()); + await audioTransceiver?.setDirection(TransceiverDirection.Inactive); + this._context._logger.finest("Setting audio transceiver to inactive:" + audioTransceiver.toString()); } - } else if (!audioSend && audioRecv) { - if (audioTransceiver != null) { - audioTransceiver!.setDirection(TransceiverDirection.RecvOnly); - this._context._logger.finest("Setting audio transceiver to recvonly:" + audioTransceiver.toString()); - } else { - // In theory, this is the only case where we might not have a transceiver yet - audioTransceiver = - await webRTCHandle!.peerConnection!.addTransceiver(kind: RTCRtpMediaType.RTCRtpMediaTypeAudio, init: RTCRtpTransceiverInit(direction: TransceiverDirection.RecvOnly)); - this._context._logger.finest("Adding recvonly audio transceiver:" + audioTransceiver.toString()); + } else { + // Take care of audio m-line + if (audioSend && audioRecv) { + if (audioTransceiver != null) { + await audioTransceiver?.setDirection(TransceiverDirection.SendRecv); + this._context._logger.finest("Setting audio transceiver to sendrecv:" + audioTransceiver.toString()); + } + } else if (audioSend && !audioRecv) { + if (audioTransceiver != null) { + await audioTransceiver?.setDirection(TransceiverDirection.SendOnly); + this._context._logger.finest("Setting audio transceiver to sendonly:" + audioTransceiver.toString()); + } + } else if (!audioSend && audioRecv) { + if (audioTransceiver != null) { + await audioTransceiver?.setDirection(TransceiverDirection.RecvOnly); + this._context._logger.finest("Setting audio transceiver to recvonly:" + audioTransceiver.toString()); + } else { + // In theory, this is the only case where we might not have a transceiver yet + audioTransceiver = await webRTCHandle!.peerConnection + ?.addTransceiver(kind: RTCRtpMediaType.RTCRtpMediaTypeAudio, init: RTCRtpTransceiverInit(direction: TransceiverDirection.RecvOnly)); + this._context._logger.finest("Adding recvonly audio transceiver:" + audioTransceiver.toString()); + } } } - } - if (!videoSend && !videoRecv) { - // Video disabled: have we removed it? - if (videoTransceiver != null) { - videoTransceiver!.setDirection(TransceiverDirection.Inactive); - // Janus.log("Setting video transceiver to inactive:", videoTransceiver); - } - } else { - // Take care of video m-line - if (videoSend && videoRecv) { - if (videoTransceiver != null) { - videoTransceiver!.setDirection(TransceiverDirection.SendRecv); - // Janus.log("Setting video transceiver to sendrecv:", videoTransceiver); - } - } else if (videoSend && !videoRecv) { + if (!videoSend && !videoRecv) { + // Video disabled: have we removed it? if (videoTransceiver != null) { - videoTransceiver!.setDirection(TransceiverDirection.SendOnly); - // Janus.log("Setting video transceiver to sendonly:", videoTransceiver); + await videoTransceiver?.setDirection(TransceiverDirection.Inactive); + // Janus.log("Setting video transceiver to inactive:", videoTransceiver); } - } else if (!videoSend && videoRecv) { - if (videoTransceiver != null) { - videoTransceiver!.setDirection(TransceiverDirection.RecvOnly); - // Janus.log("Setting video transceiver to recvonly:", videoTransceiver); - } else { - // In theory, this is the only case where we might not have a transceiver yet - videoTransceiver = - await webRTCHandle!.peerConnection!.addTransceiver(kind: RTCRtpMediaType.RTCRtpMediaTypeAudio, init: RTCRtpTransceiverInit(direction: TransceiverDirection.RecvOnly)); + } else { + // Take care of video m-line + if (videoSend && videoRecv) { + if (videoTransceiver != null) { + await videoTransceiver?.setDirection(TransceiverDirection.SendRecv); + } + } else if (videoSend && !videoRecv) { + if (videoTransceiver != null) { + await videoTransceiver?.setDirection(TransceiverDirection.SendOnly); + } + } else if (!videoSend && videoRecv) { + if (videoTransceiver != null) { + await videoTransceiver?.setDirection(TransceiverDirection.RecvOnly); + } else { + // In theory, this is the only case where we might not have a transceiver yet + videoTransceiver = await webRTCHandle!.peerConnection + ?.addTransceiver(kind: RTCRtpMediaType.RTCRtpMediaTypeVideo, init: RTCRtpTransceiverInit(direction: TransceiverDirection.RecvOnly)); + } } } + } catch (err) { + this._context._logger.finest('transrecievers not found', err); } } } From f2f0ef08fc41bda4364a13fdad457d3e13d97f8e Mon Sep 17 00:00:00 2001 From: shivanshtalwar Date: Wed, 31 Jan 2024 16:08:09 +0200 Subject: [PATCH 4/4] ios test --- .flutter-plugins | 2 +- .flutter-plugins-dependencies | 2 +- example/ios/Podfile | 2 +- example/ios/Podfile.lock | 48 +++++++++++++---- example/ios/Runner.xcodeproj/project.pbxproj | 7 ++- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- example/ios/Runner/Info.plist | 2 + example/pubspec.lock | 54 +++++++++++-------- pubspec.lock | 54 +++++++++++-------- pubspec.yaml | 2 +- 10 files changed, 111 insertions(+), 64 deletions(-) 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/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/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