diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index c0d76fa5..054053b0 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -3,9 +3,9 @@ name: Build on: push: - branches: [ master ] + branches: [ main ] pull_request: - branches: [ master ] + branches: [ main ] jobs: test: diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 5c9d8574..c19c1aee 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -23,8 +23,8 @@ jobs: run: flutter analyze - name: Dart Test Check run: flutter test - #- name: Check Publish Warnings - # run: dart pub publish --dry-run + - name: Check Publish Warnings + run: dart pub publish --dry-run - name: Publish uses: k-paxian/dart-package-publisher@v1.5.1 with: diff --git a/.github/workflows/pushMaster.yaml b/.github/workflows/pushMaster.yaml new file mode 100644 index 00000000..4a80bff8 --- /dev/null +++ b/.github/workflows/pushMaster.yaml @@ -0,0 +1,27 @@ +name: Push To Master + +on: + push: + branches: + - main + +jobs: + build: + name: Build Checks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install Flutter + uses: subosito/flutter-action@v2 + with: + channel: "stable" + - name: Install project dependencies + run: flutter pub get + - name: Dart Format Check + run: dart format lib/ test/ --set-exit-if-changed + - name: Import Sorter Check + run: flutter pub run import_sorter:main --no-comments --exit-if-changed + - name: Dart Analyze Check + run: flutter analyze + - name: Dart Test Check + run: flutter test diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e5fe8c4..0719b820 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ # Changelog -------------------------------------------- +[1.0.1] -2024.12.19 + +* make sure the session terminates by @victortive in https://github.com/flutter-webrtc/dart-sip-ua/pull/485 +* Hold and video upgrade fixed by @mikaelwills in https://github.com/flutter-webrtc/dart-sip-ua/pull/503 +* sip_ua_helper: add sendOptions by @eschmidbauer in https://github.com/flutter-webrtc/dart-sip-ua/pull/476 +* Update callscreen.dart by @HVaidehi in https://github.com/flutter-webrtc/dart-sip-ua/pull/427 + + [1.0.0] - 2024.08.24 * allow to change UA uri in runtime (#425) diff --git a/analysis_options.yaml b/analysis_options.yaml index 3a1f08c9..9b708ede 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -53,7 +53,6 @@ analyzer: avoid_types_as_parameter_names: ignore empty_catches: ignore unawaited_futures: ignore - use_rethrow_when_possible: ignore unused_import: ignore must_be_immutable: ignore todo: ignore diff --git a/example/ios/Podfile b/example/ios/Podfile index c9339a03..ed164703 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, '12.0' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/lib/src/callscreen.dart b/example/lib/src/callscreen.dart index 976c0307..a6b6ebe9 100644 --- a/example/lib/src/callscreen.dart +++ b/example/lib/src/callscreen.dart @@ -181,7 +181,10 @@ class _MyCallScreenWidget extends State if (_localRenderer != null) { _localRenderer!.srcObject = stream; } - if (!kIsWeb && !WebRTC.platformIsDesktop) { + + if (!kIsWeb && + !WebRTC.platformIsDesktop && + event.stream?.getAudioTracks().isNotEmpty == true) { event.stream?.getAudioTracks().first.enableSpeakerphone(false); } _localStream = stream; @@ -227,6 +230,7 @@ class _MyCallScreenWidget extends State 'minFrameRate': '30', }, 'facingMode': 'user', + 'optional': [], } : false }; @@ -239,6 +243,9 @@ class _MyCallScreenWidget extends State await navigator.mediaDevices.getUserMedia(mediaConstraints); mediaStream.addTrack(userStream.getAudioTracks()[0], addToNative: true); } else { + if (!remoteHasVideo) { + mediaConstraints['video'] = false; + } mediaStream = await navigator.mediaDevices.getUserMedia(mediaConstraints); } @@ -334,10 +341,14 @@ class _MyCallScreenWidget extends State call!.voiceOnly = false; }); helper!.renegotiate( - call: call!, voiceOnly: false, done: (incomingMessage) {}); + call: call!, + voiceOnly: false, + done: (IncomingMessage? incomingMessage) {}); } else { helper!.renegotiate( - call: call!, voiceOnly: true, done: (incomingMessage) {}); + call: call!, + voiceOnly: true, + done: (IncomingMessage? incomingMessage) {}); } } diff --git a/example/lib/src/dialpad.dart b/example/lib/src/dialpad.dart index 79db8747..6ec93de6 100644 --- a/example/lib/src/dialpad.dart +++ b/example/lib/src/dialpad.dart @@ -172,7 +172,7 @@ class _MyDialPadWidget extends State List _buildDialPad() { Color? textFieldColor = - Theme.of(context).textTheme.bodyMedium?.color?.withOpacity(0.5); + Theme.of(context).textTheme.bodyMedium?.color?.withValues(alpha: 0.5); Color? textFieldFill = Theme.of(context).buttonTheme.colorScheme?.surfaceContainerLowest; return [ @@ -190,15 +190,15 @@ class _MyDialPadWidget extends State filled: true, fillColor: textFieldFill, border: OutlineInputBorder( - borderSide: BorderSide(color: Colors.blue.withOpacity(0.5)), + borderSide: BorderSide(color: Colors.blue.withValues(alpha: 0.5)), borderRadius: BorderRadius.circular(5), ), enabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.blue.withOpacity(0.5)), + borderSide: BorderSide(color: Colors.blue.withValues(alpha: 0.5)), borderRadius: BorderRadius.circular(5), ), focusedBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.blue.withOpacity(0.5)), + borderSide: BorderSide(color: Colors.blue.withValues(alpha: 0.5)), borderRadius: BorderRadius.circular(5), ), ), diff --git a/example/lib/src/register.dart b/example/lib/src/register.dart index a9f0bc98..fecc2471 100644 --- a/example/lib/src/register.dart +++ b/example/lib/src/register.dart @@ -151,11 +151,32 @@ class _MyRegisterWidget extends State borderRadius: BorderRadius.circular(5), ); Color? textLabelColor = - Theme.of(context).textTheme.bodyMedium?.color?.withOpacity(0.5); + Theme.of(context).textTheme.bodyMedium?.color?.withValues(alpha: 0.5); return Scaffold( appBar: AppBar( title: Text("SIP Account"), ), + bottomNavigationBar: Padding( + padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Expanded( + child: SizedBox( + height: 40, + child: ElevatedButton( + child: Text('Register'), + onPressed: () => _handleSave(context), + ), + ), + ), + ], + ), + ], + ), + ), body: ListView( padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 20), children: [ @@ -281,11 +302,6 @@ class _MyRegisterWidget extends State ], ), ], - const SizedBox(height: 20), - ElevatedButton( - child: Text('Register'), - onPressed: () => _handleSave(context), - ), ], ), ); diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 1e834d69..6c4c2372 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -25,14 +25,14 @@ dependencies: path: ../ shared_preferences: ^2.2.0 permission_handler: ^11.1.0 - flutter_webrtc: ^0.10.4 + flutter_webrtc: ^0.12.4 provider: 6.1.2 logger: ^2.4.0 dev_dependencies: flutter_test: sdk: flutter - lints: ^3.0.0 + lints: ^4.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart index 4e8e53f4..900418a1 100644 --- a/example/test/widget_test.dart +++ b/example/test/widget_test.dart @@ -13,7 +13,7 @@ import 'package:dart_sip_ua_example/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); + await tester.pumpWidget(MyApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); diff --git a/lib/sip_ua.dart b/lib/sip_ua.dart index c7f2efe7..397defee 100644 --- a/lib/sip_ua.dart +++ b/lib/sip_ua.dart @@ -3,5 +3,3 @@ export 'src/sip_message.dart'; export 'src/sip_ua_helper.dart'; export 'src/transport_type.dart'; export 'src/uri.dart'; - - diff --git a/lib/src/config.dart b/lib/src/config.dart index d9849d85..102e7ef5 100644 --- a/lib/src/config.dart +++ b/lib/src/config.dart @@ -7,7 +7,6 @@ import 'exceptions.dart' as Exceptions; import 'grammar.dart'; import 'logger.dart'; import 'transports/web_socket.dart'; -import 'uri.dart'; import 'utils.dart' as Utils; // Default settings. @@ -287,6 +286,6 @@ void load(Settings src, Settings? dst) { }); } catch (e) { logger.e('Failed to load config: ${e.toString()}'); - throw e; + rethrow; } } diff --git a/lib/src/rtc_session.dart b/lib/src/rtc_session.dart index 9fc9da52..b8dfa379 100644 --- a/lib/src/rtc_session.dart +++ b/lib/src/rtc_session.dart @@ -22,11 +22,9 @@ import 'rtc_session/info.dart' as RTCSession_Info; import 'rtc_session/info.dart'; import 'rtc_session/refer_notifier.dart'; import 'rtc_session/refer_subscriber.dart'; -import 'sip_message.dart'; import 'timers.dart'; import 'transactions/transaction_base.dart'; import 'ua.dart'; -import 'uri.dart'; import 'utils.dart' as utils; class C { @@ -301,7 +299,8 @@ class RTCSession extends EventManager implements Owner { // Set anonymous property. bool anonymous = options['anonymous'] ?? false; Map requestParams = { - 'from_tag': _from_tag + 'from_tag': _from_tag, + 'to_display_name': options['to_display_name'] ?? '', }; _ua.contact!.anonymous = anonymous; _ua.contact!.outbound = true; @@ -314,7 +313,7 @@ class RTCSession extends EventManager implements Owner { requestParams['from_display_name'] = options['from_display_name'] ?? ''; requestParams['from_uri'] = URI.parse(options['from_uri']); extraHeaders - .add('P-Preferred-Identity: ${_ua.configuration.uri.toString()}'); + .add('P-Preferred-Identity: ${_ua.configuration.uri.toString()}'); } if (anonymous) { @@ -1032,7 +1031,7 @@ class RTCSession extends EventManager implements Owner { /** * Hold */ - bool hold([Map? options, Function? done]) { + bool hold([Map? options, Function(IncomingMessage?)? done]) { logger.d('hold()'); options = options ?? {}; @@ -1056,7 +1055,7 @@ class RTCSession extends EventManager implements Owner { handlers.on(EventSucceeded(), (EventSucceeded event) { if (done != null) { - done(); + done(event.response); } }); handlers.on(EventCallFailed(), (EventCallFailed event) { @@ -1083,7 +1082,8 @@ class RTCSession extends EventManager implements Owner { return true; } - bool unhold([Map? options, Function? done]) { + bool unhold( + [Map? options, Function(IncomingMessage?)? done]) { logger.d('unhold()'); options = options ?? {}; @@ -1106,7 +1106,7 @@ class RTCSession extends EventManager implements Owner { EventManager handlers = EventManager(); handlers.on(EventSucceeded(), (EventSucceeded event) { if (done != null) { - done(); + done(event.response); } }); handlers.on(EventCallFailed(), (EventCallFailed event) { @@ -1135,8 +1135,8 @@ class RTCSession extends EventManager implements Owner { bool renegotiate( {Map? options, - Function(IncomingMessage)? done, - bool useUpdate = false}) { + bool useUpdate = false, + Function(IncomingMessage?)? done}) { logger.d('renegotiate()'); options = options ?? {}; @@ -1155,6 +1155,15 @@ class RTCSession extends EventManager implements Owner { return false; } + bool? upgradeToVideo; + try { + upgradeToVideo = (options['mediaConstraints']?['video'] != false || + options['mediaConstraints']?['mandatory']?['video'] != null) && + rtcOfferConstraints?['offerToReceiveVideo'] == null; + } catch (e) { + print('Failed to determine upgrade to video: $e'); + } + if (!_isReadyToReOffer()) { return false; } @@ -1175,7 +1184,7 @@ class RTCSession extends EventManager implements Owner { _setLocalMediaStatus(); - if (useUpdate) { + if (options['useUpdate'] != null) { _sendUpdate({ 'sdpOffer': true, 'eventHandlers': handlers, @@ -1183,13 +1192,21 @@ class RTCSession extends EventManager implements Owner { 'extraHeaders': options['extraHeaders'] }); } else { - _sendReinvite({ - 'eventHandlers': handlers, - 'sdpSemantics': sdpSemantics, - 'rtcOfferConstraints': rtcOfferConstraints, - 'mediaConstraints': mediaConstraints, - 'extraHeaders': options['extraHeaders'] - }); + if (upgradeToVideo ?? false) { + _sendVideoUpgradeReinvite({ + 'eventHandlers': handlers, + 'sdpSemantics': sdpSemantics, + 'rtcOfferConstraints': rtcOfferConstraints, + 'mediaConstraints': mediaConstraints, + 'extraHeaders': options['extraHeaders'] + }); + } else { + _sendReinvite({ + 'eventHandlers': handlers, + 'rtcOfferConstraints': rtcOfferConstraints, + 'extraHeaders': options['extraHeaders'] + }); + } } return true; @@ -1592,19 +1609,6 @@ class RTCSession extends EventManager implements Owner { 'optional': [], }; offerConstraints['mandatory']['IceRestart'] = true; - - EventManager handlers = EventManager(); - handlers.on(EventSucceeded(), (EventSucceeded event) async { - logger.d('ICE Restart was successful'); - }); - handlers.on(EventCallFailed(), (EventCallFailed event) { - terminate({ - 'cause': DartSIP_C.CausesType.WEBRTC_ERROR, - 'status_code': 500, - 'reason_phrase': 'Media Renegotiation Failed' - }); - }); - offerConstraints['eventHandlers'] = handlers; renegotiate(options: offerConstraints); } @@ -1670,7 +1674,7 @@ class RTCSession extends EventManager implements Owner { modifiers = constraints['offerModifiers'] ?? Function(RTCSessionDescription)>[]; - constraints.remove('offerModifiers'); + constraints['offerModifiers'] = null; if (type != 'offer' && type != 'answer') { completer.completeError(Exceptions.TypeError( @@ -2013,17 +2017,16 @@ class RTCSession extends EventManager implements Owner { bool upgradeToVideo = false; if (sdp != null) { List mediaList = sdp['media']; - for (Map m in mediaList) { - if (holdMediaTypes.indexOf(m['type']) == -1) { - continue; - } - - if (m['type'] == 'video') { - upgradeToVideo = true; - } - - String direction = m['direction'] ?? sdp['direction'] ?? 'sendrecv'; + // Loop media list items for video upgrade + for (Map media in mediaList) { + if (holdMediaTypes.indexOf(media['type']) == -1) continue; + if (media['type'] == 'video') upgradeToVideo = true; + } + // Loop media list items for hold + for (Map media in mediaList) { + if (holdMediaTypes.indexOf(media['type']) == -1) continue; + String direction = media['direction'] ?? sdp['direction'] ?? 'sendrecv'; if (direction == 'sendonly' || direction == 'inactive') { hold = true; } @@ -2046,19 +2049,34 @@ class RTCSession extends EventManager implements Owner { 'facingMode': 'user', } }; - MediaStream localStream = - await navigator.mediaDevices.getUserMedia(mediaConstraints); - if (localStream.getVideoTracks().isEmpty) { - logger.w( - 'Remote wants to upgrade to video but failed to get local video'); - } - for (MediaStreamTrack track in localStream.getTracks()) { - if (track.kind == 'video') { - _connection!.addTrack(track, localStream); + bool hasCamera = false; + try { + List devices = + await navigator.mediaDevices.enumerateDevices(); + for (MediaDeviceInfo device in devices) { + if (device.kind == 'videoinput') hasCamera = true; } + } catch (e) { + logger.w('Failed to enumerate devices: $e'); + } + if (hasCamera) { + MediaStream localStream = + await navigator.mediaDevices.getUserMedia(mediaConstraints); + if (localStream.getVideoTracks().isEmpty) { + logger.w( + 'Remote wants to upgrade to video but failed to get local video'); + } + for (MediaStreamTrack track in localStream.getTracks()) { + if (track.kind == 'video') { + _connection!.addTrack(track, localStream); + } + } + emit(EventStream( + session: this, originator: 'local', stream: localStream)); + } else { + logger.w( + 'Remote wants to upgrade to video but no camera available to send'); } - emit( - EventStream(session: this, originator: 'local', stream: localStream)); } logger.d('emit "sdp"'); @@ -2336,7 +2354,7 @@ class RTCSession extends EventManager implements Owner { 'User Denied Media Access'); logger.e('emit "getusermediafailed" [error:${error.toString()}]'); emit(EventGetUserMediaFailed(exception: error)); - throw error; + rethrow; } } @@ -2388,7 +2406,7 @@ class RTCSession extends EventManager implements Owner { return; } logger.e('Failed to _sendInitialRequest: ${error.toString()}'); - throw error; + rethrow; } } @@ -2485,6 +2503,9 @@ class RTCSession extends EventManager implements Owner { emit(EventSetRemoteDescriptionFailed(exception: error)); } } else if (utils.test2XX(status_code)) { + // 2XX + _status = C.STATUS_CONFIRMED; + if (response.body == null || response.body!.isEmpty) { _acceptAndTerminate(response, 400, DartSIP_C.CausesType.MISSING_SDP); _failed('remote', null, null, response, 400, @@ -2506,8 +2527,6 @@ class RTCSession extends EventManager implements Owner { return; } - _status = C.STATUS_CONFIRMED; - logger.d('emit "sdp"'); emit(EventSdp(originator: 'remote', type: 'answer', sdp: response.body)); @@ -2567,6 +2586,113 @@ class RTCSession extends EventManager implements Owner { options = options ?? {}; + List extraHeaders = options['extraHeaders'] != null + ? utils.cloneArray(options['extraHeaders']) + : []; + EventManager eventHandlers = options['eventHandlers'] ?? EventManager(); + Map? rtcOfferConstraints = + options['rtcOfferConstraints'] ?? _rtcOfferConstraints; + + bool succeeded = false; + + extraHeaders.add('Contact: $_contact'); + extraHeaders.add('Content-Type: application/sdp'); + + // Session Timers. + if (_sessionTimers.running) { + extraHeaders.add( + 'Session-Expires: ${_sessionTimers.currentExpires};refresher=${_sessionTimers.refresher ? 'uac' : 'uas'}'); + } + + void onFailed([dynamic response]) { + eventHandlers.emit(EventCallFailed(session: this, response: response)); + } + + void onSucceeded(IncomingResponse? response) async { + if (_status == C.STATUS_TERMINATED) { + return; + } + + sendRequest(SipMethod.ACK); + + // If it is a 2XX retransmission exit now. + if (succeeded != null) { + return; + } + + // Handle Session Timers. + _handleSessionTimersInIncomingResponse(response); + + // Must have SDP answer. + if (response!.body == null || response.body!.isEmpty) { + onFailed(); + return; + } else if (response.getHeader('Content-Type') != 'application/sdp') { + onFailed(); + return; + } + + logger.d('emit "sdp"'); + emit(EventSdp(originator: 'remote', type: 'answer', sdp: response.body)); + + RTCSessionDescription answer = + RTCSessionDescription(response.body, 'answer'); + + try { + await _connection!.setRemoteDescription(answer); + eventHandlers.emit(EventSucceeded(response: response)); + } catch (error) { + onFailed(); + logger.e( + 'emit "peerconnection:setremotedescriptionfailed" [error:${error.toString()}]'); + emit(EventSetRemoteDescriptionFailed(exception: error)); + } + } + + try { + RTCSessionDescription desc = + await _createLocalDescription('offer', rtcOfferConstraints); + String? sdp = _mangleOffer(desc.sdp); + logger.d('emit "sdp"'); + emit(EventSdp(originator: 'local', type: 'offer', sdp: sdp)); + + EventManager handlers = EventManager(); + handlers.on(EventOnSuccessResponse(), (EventOnSuccessResponse event) { + onSucceeded(event.response as IncomingResponse?); + succeeded = true; + }); + handlers.on(EventOnErrorResponse(), (EventOnErrorResponse event) { + onFailed(event.response); + }); + handlers.on(EventOnTransportError(), (EventOnTransportError event) { + onTransportError(); // Do nothing because session ends. + }); + handlers.on(EventOnRequestTimeout(), (EventOnRequestTimeout event) { + onRequestTimeout(); // Do nothing because session ends. + }); + handlers.on(EventOnDialogError(), (EventOnDialogError event) { + onDialogError(); // Do nothing because session ends. + }); + + sendRequest(SipMethod.INVITE, { + 'extraHeaders': extraHeaders, + 'body': sdp, + 'eventHandlers': handlers + }); + } catch (e, s) { + logger.e(e.toString(), error: e, stackTrace: s); + onFailed(); + } + } + + /** + * Send Re-INVITE + */ + void _sendVideoUpgradeReinvite([Map? options]) async { + logger.d('sendVideoUpgradeReinvite()'); + + options = options ?? {}; + List extraHeaders = options['extraHeaders'] != null ? utils.cloneArray(options['extraHeaders']) : []; @@ -2577,26 +2703,22 @@ class RTCSession extends EventManager implements Owner { Map mediaConstraints = options['mediaConstraints'] ?? {}; + mediaConstraints['audio'] = false; + dynamic sdpSemantics = options['pcConfig']?['sdpSemantics'] ?? 'unified-plan'; - bool hasVideo = (options['mediaConstraints']?['video'] ?? false) != false; - try { MediaStream localStream = await navigator.mediaDevices.getUserMedia(mediaConstraints); _localMediaStreamLocallyGenerated = true; - _localMediaStream = localStream; - - emit( - EventStream(session: this, originator: 'local', stream: localStream)); switch (sdpSemantics) { case 'unified-plan': localStream.getTracks().forEach((MediaStreamTrack track) { - if (track.kind == 'video' && hasVideo) { + if (track.kind == 'video') _connection!.addTrack(track, localStream); - } + _localMediaStream?.addTrack(track); }); break; case 'plan-b': @@ -2606,6 +2728,9 @@ class RTCSession extends EventManager implements Owner { logger.e('Unkown sdp semantics $sdpSemantics'); throw Exceptions.NotReadyError('Unkown sdp semantics $sdpSemantics'); } + + emit(EventStream( + session: this, originator: 'local', stream: _localMediaStream)); } catch (error) { if (_status == C.STATUS_TERMINATED) { throw Exceptions.InvalidStateError('terminated'); @@ -2937,7 +3062,7 @@ class RTCSession extends EventManager implements Owner { } /// SDP offers may contain text media channels. e.g. Older clients using linphone. - /// + /// /// WebRTC does not support text media channels, so remove them. String? _sdpOfferToWebRTC(String? sdpInput) { if (sdpInput == null) { diff --git a/lib/src/rtc_session/dtmf.dart b/lib/src/rtc_session/dtmf.dart index df8269e3..e1fe8f16 100644 --- a/lib/src/rtc_session/dtmf.dart +++ b/lib/src/rtc_session/dtmf.dart @@ -7,7 +7,6 @@ import '../event_manager/internal_events.dart'; import '../exceptions.dart' as Exceptions; import '../logger.dart'; import '../rtc_session.dart' as rtc; -import '../sip_message.dart'; import '../utils.dart' as Utils; class C { diff --git a/lib/src/sip_ua_helper.dart b/lib/src/sip_ua_helper.dart index 71de9291..f2562b07 100644 --- a/lib/src/sip_ua_helper.dart +++ b/lib/src/sip_ua_helper.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter_webrtc/flutter_webrtc.dart'; import 'package:logger/logger.dart'; import 'package:sdp_transform/sdp_transform.dart' as sdp_transform; + import 'package:sip_ua/sip_ua.dart'; import 'package:sip_ua/src/event_manager/internal_events.dart'; import 'package:sip_ua/src/map_helper.dart'; @@ -14,9 +15,9 @@ import 'event_manager/event_manager.dart'; import 'event_manager/subscriber_events.dart'; import 'logger.dart'; import 'message.dart'; +import 'options.dart'; import 'rtc_session.dart'; import 'rtc_session/refer_subscriber.dart'; -import 'sip_message.dart'; import 'stack_trace_nj.dart'; import 'subscriber.dart'; import 'transports/web_socket.dart'; @@ -123,7 +124,7 @@ class SIPUAHelper extends EventManager { required bool voiceOnly, Map? options, bool useUpdate = false, - Function(IncomingMessage)? done, + Function(IncomingMessage?)? done, }) async { Map finalOptions = options ?? buildCallOptions(voiceOnly); call.renegotiate(options: finalOptions, useUpdate: useUpdate, done: done); @@ -179,9 +180,9 @@ class SIPUAHelper extends EventManager { _settings.instance_id = uaSettings.instanceId; _settings.registrar_server = uaSettings.registrarServer; _settings.contact_uri = uaSettings.contact_uri; - _settings.connection_recovery_max_interval = + _settings.connection_recovery_max_interval = uaSettings.connectionRecoveryMaxInterval; - _settings.connection_recovery_min_interval = + _settings.connection_recovery_min_interval = uaSettings.connectionRecoveryMinInterval; _settings.terminateOnAudioMediaPortZero = uaSettings.terminateOnMediaPortZero; @@ -413,6 +414,11 @@ class SIPUAHelper extends EventManager { return _ua!.sendMessage(target, body, options, params); } + Options sendOptions( + String target, String body, Map? params) { + return _ua!.sendOptions(target, body, params); + } + void subscribe(String target, String event, String contentType) { Subscriber s = _ua!.subscribe(target, event, contentType); @@ -554,7 +560,7 @@ class Call { assert(_session != null, 'ERROR(hangup): rtc session is invalid!'); if (peerConnection != null) { for (MediaStream? stream in peerConnection!.getLocalStreams()) { - if (stream == null) return; + if (stream == null) continue; logger.d( 'Stopping local stream with tracks: ${stream.getTracks().length}'); for (MediaStreamTrack track in stream.getTracks()) { @@ -563,7 +569,7 @@ class Call { } } for (MediaStream? stream in peerConnection!.getRemoteStreams()) { - if (stream == null) return; + if (stream == null) continue; logger.d( 'Stopping remote stream with tracks: ${stream.getTracks().length}'); for (MediaStreamTrack track in stream.getTracks()) { @@ -600,7 +606,7 @@ class Call { void renegotiate({ required Map? options, bool useUpdate = false, - Function(IncomingMessage)? done, + Function(IncomingMessage?)? done, }) { assert(_session != null, 'ERROR(renegotiate): rtc session is invalid!'); _session.renegotiate(options: options, useUpdate: useUpdate, done: done); diff --git a/lib/src/transports/tcp_socket.dart b/lib/src/transports/tcp_socket.dart index 1b5b1e6e..ce3832c7 100644 --- a/lib/src/transports/tcp_socket.dart +++ b/lib/src/transports/tcp_socket.dart @@ -140,7 +140,7 @@ class SIPUATcpSocket extends SIPUASocketInterface { return true; } catch (error) { logger.e('send() | error sending message: $error'); - throw error; + rethrow; } } @@ -179,7 +179,7 @@ class SIPUATcpSocket extends SIPUASocketInterface { @override String? get url { - if (_host == null || _port == null) { + if (_host == null || _port == null) { return null; } return '$_host:$_port'; diff --git a/lib/src/transports/web_socket.dart b/lib/src/transports/web_socket.dart index 66123a00..57d610c8 100644 --- a/lib/src/transports/web_socket.dart +++ b/lib/src/transports/web_socket.dart @@ -142,7 +142,7 @@ class SIPUAWebSocket extends SIPUASocketInterface { return true; } catch (error) { logger.e('send() | error sending message: $error'); - throw error; + rethrow; } } diff --git a/lib/src/transports/websocket_dart_impl.dart b/lib/src/transports/websocket_dart_impl.dart index 6516515c..1ec30b21 100644 --- a/lib/src/transports/websocket_dart_impl.dart +++ b/lib/src/transports/websocket_dart_impl.dart @@ -118,7 +118,7 @@ class SIPUAWebSocketImpl { return webSocket; } catch (e) { logger.e('error $e'); - throw e; + rethrow; } } } diff --git a/lib/src/ua.dart b/lib/src/ua.dart index 92e581bb..6d0ed965 100644 --- a/lib/src/ua.dart +++ b/lib/src/ua.dart @@ -95,7 +95,7 @@ class UA extends EventManager { } catch (e) { _status = C.STATUS_NOT_READY; _error = C.CONFIGURATION_ERROR; - throw e; + rethrow; } // Initialize registrator. @@ -821,7 +821,7 @@ class UA extends EventManager { try { config.load(configuration, _configuration); } catch (e) { - throw e; + rethrow; } // Post Configuration Process. diff --git a/pubspec.yaml b/pubspec.yaml index 1ebd9cd5..47237cb6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sip_ua -version: 1.0.0 +version: 1.0.1 description: A SIP UA stack for Flutter/Dart, based on flutter-webrtc, support iOS/Android/Destkop/Web. homepage: https://github.com/cloudwebrtc/dart-sip-ua environment: @@ -9,8 +9,8 @@ environment: dependencies: collection: ^1.18.0 crypto: ^3.0.3 - flutter_webrtc: ^0.10.4 - intl: ^0.19.0 + flutter_webrtc: ^0.12.4 + intl: ^0.20.1 logger: ^2.0.2+1 path: ^1.6.4 random_string: ^2.3.1 @@ -22,5 +22,5 @@ dependencies: dev_dependencies: import_sorter: ^4.6.0 - lints: ^3.0.0 + lints: ^4.0.0 test: any