Skip to content

Commit 4e8e03f

Browse files
author
Mikael Wills
committed
Added re-register mechanism in example app after call ends or fails for more quality of service
1 parent 903169a commit 4e8e03f

File tree

12 files changed

+209
-59
lines changed

12 files changed

+209
-59
lines changed

README.md

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ A dart-lang version of the SIP UA stack, ported from [JsSIP](https://github.com/
66

77
## Overview
88
- Use pure [dart-lang](https://dart.dev)
9-
- SIP over WebSocket (use real SIP in your flutter mobile, [desktop](https://flutter.dev/desktop), [web](https://flutter.dev/web) apps)
9+
- SIP over WebSocket && TCP (use real SIP in your flutter mobile, [desktop](https://flutter.dev/desktop), [web](https://flutter.dev/web) apps)
1010
- Audio/video calls ([flutter-webrtc](https://github.com/cloudwebrtc/flutter-webrtc)) and instant messaging
11-
- Support with standard SIP servers such as OpenSIPS, Kamailio, Asterisk and FreeSWITCH.
11+
- Support with standard SIP servers such as OpenSIPS, Kamailio, Asterisk, 3CX and FreeSWITCH.
1212
- Support RFC2833 or INFO to send DTMF.
1313

1414
## Currently supported platforms
@@ -50,9 +50,47 @@ Register with SIP server:
5050
- [Asterisk](https://github.com/flutter-webrtc/dockers/tree/main/asterisk)
5151
- FreeSWITCH
5252
- OpenSIPS
53+
- 3CX
5354
- Kamailio
5455
- or add your server example.
5556

57+
## FAQ's OR ISSUES
58+
<details>
59+
60+
<summary>expand</summary>
61+
62+
## Server not configured for DTLS/SRTP
63+
64+
WEBRTC_SET_REMOTE_DESCRIPTION_ERROR: Failed to set remote offer sdp: Called with SDP without DTLS fingerprint.
65+
66+
Your server is not sending a DTLS fingerprint inside the SDP when inviting the sip_ua client to start a call.
67+
68+
WebRTC uses encryption by Default, all WebRTC communications (audio, video, and data) are encrypted using DTLS and SRTP, ensuring secure communication. Your PBX must be configured to use DTLS/SRTP when calling sip_ua.
69+
70+
71+
## Why isn't there a UDP connection option?
72+
73+
This package uses a WS or TCP connection for the signalling processs to initiate or terminate a session (sip messages).
74+
Once the session is connected WebRTC transmits the actual media (audio/video) over UDP.
75+
76+
If anyone actually still wants to use UDP for the signalling process, feel free to submit a PR with the large amount of work needed to set it up, packet order checking, error checking, reliability timeouts, flow control, security etc etc.
77+
78+
## SIP/2.0 488 Not acceptable here
79+
80+
The codecs on your PBX server don't match the codecs used by WebRTC
81+
82+
- **opus** (payload type 111, 48kHz, 2 channels)
83+
- **red** (payload type 63, 48kHz, 2 channels)
84+
- **G722** (payload type 9, 8kHz, 1 channel)
85+
- **ILBC** (payload type 102, 8kHz, 1 channel)
86+
- **PCMU** (payload type 0, 8kHz, 1 channel)
87+
- **PCMA** (payload type 8, 8kHz, 1 channel)
88+
- **CN** (payload type 13, 8kHz, 1 channel)
89+
- **telephone-event** (payload type 110, 48kHz, 1 channel for wideband, 8000Hz, 1 channel for narrowband)
90+
91+
</details>
92+
93+
5694
## NOTE
5795
Thanks to the original authors of [JsSIP](https://github.com/versatica/JsSIP) for providing the JS version, which makes it possible to port the [dart-lang](https://dart.dev).
5896
- [José Luis Millán](https://github.com/jmillan)
@@ -69,6 +107,7 @@ The project is inseparable from the contributors of the community.
69107
- [Robert Sutton](https://github.com/rlsutton1) - Contributor
70108
- [Gavin Henry](https://github.com/ghenry) - Contributor
71109
- [Perondas](https://github.com/Perondas) - Contributor
110+
- [Mikael Wills](https://github.com/mikaelwills) - Contributor
72111

73112
## License
74113
dart-sip-ua is released under the [MIT license](https://github.com/cloudwebrtc/dart-sip-ua/blob/master/LICENSE).

example/android/gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
33
zipStoreBase=GRADLE_USER_HOME
44
zipStorePath=wrapper/dists
5-
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
5+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip

example/android/settings.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ pluginManagement {
1818

1919
plugins {
2020
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
21-
id "com.android.application" version "7.3.0" apply false
22-
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
21+
id "com.android.application" version "8.1.0" apply false
22+
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
2323
}
2424

2525
include ":app"

example/lib/main.dart

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:dart_sip_ua_example/src/theme_provider.dart';
2+
import 'package:dart_sip_ua_example/src/user_state/sip_user_cubit.dart';
23
import 'package:flutter/foundation.dart'
34
show debugDefaultTargetPlatformOverride;
45
import 'package:flutter/material.dart';
@@ -60,11 +61,18 @@ class MyApp extends StatelessWidget {
6061

6162
@override
6263
Widget build(BuildContext context) {
63-
return MaterialApp(
64-
title: 'Flutter Demo',
65-
theme: Provider.of<ThemeProvider>(context).currentTheme,
66-
initialRoute: '/',
67-
onGenerateRoute: _onGenerateRoute,
64+
return MultiProvider(
65+
providers: [
66+
Provider<SIPUAHelper>.value(value: _helper),
67+
Provider<SipUserCubit>(
68+
create: (context) => SipUserCubit(sipHelper: _helper)),
69+
],
70+
child: MaterialApp(
71+
title: 'Flutter Demo',
72+
theme: Provider.of<ThemeProvider>(context).currentTheme,
73+
initialRoute: '/',
74+
onGenerateRoute: _onGenerateRoute,
75+
),
6876
);
6977
}
7078
}

example/lib/src/dialpad.dart

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import 'package:dart_sip_ua_example/src/theme_provider.dart';
2+
import 'package:dart_sip_ua_example/src/user_state/sip_user_cubit.dart';
23
import 'package:flutter/foundation.dart';
34
import 'package:flutter/material.dart';
45
import 'package:flutter_webrtc/flutter_webrtc.dart';
6+
import 'package:logger/logger.dart';
57
import 'package:permission_handler/permission_handler.dart';
68
import 'package:provider/provider.dart';
79
import 'package:shared_preferences/shared_preferences.dart';
@@ -24,6 +26,9 @@ class _MyDialPadWidget extends State<DialPadWidget>
2426
SIPUAHelper? get helper => widget._helper;
2527
TextEditingController? _textController;
2628
late SharedPreferences _preferences;
29+
late SipUserCubit currentUserCubit;
30+
31+
final Logger _logger = Logger();
2732

2833
String? receivedMsg;
2934

@@ -241,6 +246,8 @@ class _MyDialPadWidget extends State<DialPadWidget>
241246
Color? textColor = Theme.of(context).textTheme.bodyMedium?.color;
242247
Color? iconColor = Theme.of(context).iconTheme.color;
243248
bool isDarkTheme = Theme.of(context).brightness == Brightness.dark;
249+
currentUserCubit = context.watch<SipUserCubit>();
250+
244251
return Scaffold(
245252
appBar: AppBar(
246253
title: Text("Dart SIP UA Demo"),
@@ -345,19 +352,37 @@ class _MyDialPadWidget extends State<DialPadWidget>
345352

346353
@override
347354
void registrationStateChanged(RegistrationState state) {
348-
setState(() {});
355+
setState(() {
356+
_logger.i("Registration state: ${state.state?.name}");
357+
});
349358
}
350359

351360
@override
352361
void transportStateChanged(TransportState state) {}
353362

354363
@override
355364
void callStateChanged(Call call, CallState callState) {
356-
if (callState.state == CallStateEnum.CALL_INITIATION) {
357-
Navigator.pushNamed(context, '/callscreen', arguments: call);
365+
switch (callState.state) {
366+
case CallStateEnum.CALL_INITIATION:
367+
Navigator.pushNamed(context, '/callscreen', arguments: call);
368+
break;
369+
case CallStateEnum.FAILED:
370+
reRegisterWithCurrentUser();
371+
break;
372+
case CallStateEnum.ENDED:
373+
reRegisterWithCurrentUser();
374+
break;
375+
default:
358376
}
359377
}
360378

379+
void reRegisterWithCurrentUser() async {
380+
if (currentUserCubit.state == null) return;
381+
if (helper!.registered) await helper!.unregister();
382+
_logger.i("Re-registering");
383+
currentUserCubit.register(currentUserCubit.state!);
384+
}
385+
361386
@override
362387
void onNewMessage(SIPMessageRequest msg) {
363388
//Save the incoming message to DB

example/lib/src/register.dart

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import 'package:dart_sip_ua_example/src/user_state/sip_user.dart';
2+
import 'package:dart_sip_ua_example/src/user_state/sip_user_cubit.dart';
13
import 'package:flutter/material.dart';
4+
import 'package:provider/provider.dart';
25
import 'package:shared_preferences/shared_preferences.dart';
36
import 'package:sip_ua/sip_ua.dart';
47
import 'package:flutter/foundation.dart' show kIsWeb;
@@ -32,6 +35,8 @@ class _MyRegisterWidget extends State<RegisterWidget>
3235

3336
SIPUAHelper? get helper => widget._helper;
3437

38+
late SipUserCubit currentUser;
39+
3540
@override
3641
void initState() {
3742
super.initState();
@@ -98,54 +103,46 @@ class _MyRegisterWidget extends State<RegisterWidget>
98103
barrierDismissible: false,
99104
builder: (BuildContext context) {
100105
return AlertDialog(
101-
title: Text('$alertFieldName is empty'),
102-
content: Text('Please enter $alertFieldName!'),
103-
actions: <Widget>[
104-
TextButton(
105-
child: Text('Ok'),
106-
onPressed: () {
107-
Navigator.of(context).pop();
108-
},
109-
),
110-
],
111-
);
106+
title: Text('$alertFieldName is empty'),
107+
content: Text('Please enter $alertFieldName!'),
108+
actions: <Widget>[
109+
TextButton(
110+
child: Text('Ok'),
111+
onPressed: () {
112+
Navigator.of(context).pop();
113+
},
114+
),
115+
]);
112116
},
113117
);
114118
}
115119

116-
void _handleSave(BuildContext context) {
120+
void _register(BuildContext context) {
117121
if (_wsUriController.text == '') {
118122
_alert(context, "WebSocket URL");
119123
} else if (_sipUriController.text == '') {
120124
_alert(context, "SIP URI");
121125
}
122126

123-
UaSettings settings = UaSettings();
124-
125-
settings.port = _portController.text;
126-
settings.webSocketSettings.extraHeaders = _wsExtraHeaders;
127-
settings.webSocketSettings.allowBadCertificate = true;
128-
//settings.webSocketSettings.userAgent = 'Dart/2.8 (dart:io) for OpenSIPS.';
129-
settings.tcpSocketSettings.allowBadCertificate = true;
130-
settings.transportType = _selectedTransport;
131-
settings.uri = _sipUriController.text;
132-
settings.webSocketUrl = _wsUriController.text;
133-
settings.host = _sipUriController.text.split('@')[1];
134-
settings.authorizationUser = _authorizationUserController.text;
135-
settings.password = _passwordController.text;
136-
settings.displayName = _displayNameController.text;
137-
settings.userAgent = 'Dart SIP Client v1.0.0';
138-
settings.dtmfMode = DtmfMode.RFC2833;
139-
settings.contact_uri = 'sip:${_sipUriController.text}';
127+
_saveSettings();
140128

141-
helper!.start(settings);
129+
currentUser.register(SipUser(
130+
selectedTransport: _selectedTransport,
131+
wsExtraHeaders: _wsExtraHeaders,
132+
sipUri: _sipUriController.text,
133+
port: _portController.text,
134+
displayName: _displayNameController.text,
135+
password: _passwordController.text,
136+
authUser: _authorizationUserController.text));
142137
}
143138

144139
@override
145140
Widget build(BuildContext context) {
146141
Color? textColor = Theme.of(context).textTheme.bodyMedium?.color;
147142
Color? textFieldFill =
148143
Theme.of(context).buttonTheme.colorScheme?.surfaceContainerLowest;
144+
currentUser = context.watch<SipUserCubit>();
145+
149146
OutlineInputBorder border = OutlineInputBorder(
150147
borderSide: BorderSide.none,
151148
borderRadius: BorderRadius.circular(5),
@@ -168,7 +165,7 @@ class _MyRegisterWidget extends State<RegisterWidget>
168165
height: 40,
169166
child: ElevatedButton(
170167
child: Text('Register'),
171-
onPressed: () => _handleSave(context),
168+
onPressed: () => _register(context),
172169
),
173170
),
174171
),
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import 'package:sip_ua/sip_ua.dart';
2+
3+
class SipUser {
4+
final String port;
5+
final String displayName;
6+
final String? wsUrl;
7+
final String? sipUri;
8+
final String password;
9+
final String authUser;
10+
final TransportType selectedTransport;
11+
final Map<String, String>? wsExtraHeaders;
12+
13+
SipUser({
14+
required this.port,
15+
required this.displayName,
16+
required this.password,
17+
required this.authUser,
18+
required this.selectedTransport,
19+
this.wsExtraHeaders,
20+
this.wsUrl,
21+
this.sipUri,
22+
});
23+
24+
@override
25+
bool operator ==(Object other) {
26+
if (identical(this, other)) return true;
27+
return other is SipUser &&
28+
other.port == port &&
29+
other.displayName == displayName &&
30+
other.wsUrl == wsUrl &&
31+
other.sipUri == sipUri &&
32+
other.selectedTransport == selectedTransport &&
33+
other.wsExtraHeaders == wsExtraHeaders &&
34+
other.password == password &&
35+
other.authUser == authUser;
36+
}
37+
38+
@override
39+
int get hashCode {
40+
return Object.hashAll([
41+
port,
42+
displayName,
43+
wsUrl,
44+
sipUri,
45+
password,
46+
wsExtraHeaders,
47+
selectedTransport,
48+
authUser,
49+
]);
50+
}
51+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import 'package:bloc/bloc.dart';
2+
import 'package:dart_sip_ua_example/src/user_state/sip_user.dart';
3+
import 'package:sip_ua/sip_ua.dart';
4+
5+
class SipUserCubit extends Cubit<SipUser?> {
6+
final SIPUAHelper sipHelper;
7+
SipUserCubit({required this.sipHelper}) : super(null);
8+
9+
10+
void register(SipUser user) {
11+
UaSettings settings = UaSettings();
12+
settings.port = user.port;
13+
settings.webSocketSettings.extraHeaders = user.wsExtraHeaders ?? {};
14+
settings.webSocketSettings.allowBadCertificate = true;
15+
//settings.webSocketSettings.userAgent = 'Dart/2.8 (dart:io) for OpenSIPS.';
16+
settings.tcpSocketSettings.allowBadCertificate = true;
17+
settings.transportType = user.selectedTransport;
18+
settings.uri = user.sipUri;
19+
settings.webSocketUrl = user.wsUrl;
20+
settings.host = user.sipUri?.split('@')[1];
21+
settings.authorizationUser = user.authUser;
22+
settings.password = user.password;
23+
settings.displayName = user.displayName;
24+
settings.userAgent = 'Dart SIP Client v1.0.0';
25+
settings.dtmfMode = DtmfMode.RFC2833;
26+
settings.contact_uri = 'sip:${user.sipUri}';
27+
28+
emit(user);
29+
sipHelper.start(settings);
30+
}
31+
}

example/pubspec.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,16 @@ environment:
1919
flutter: ">=1.10.0"
2020

2121
dependencies:
22+
bloc: ^9.0.0
2223
flutter:
2324
sdk: flutter
2425
sip_ua:
2526
path: ../
2627
shared_preferences: ^2.2.0
2728
permission_handler: ^11.1.0
28-
flutter_webrtc: ^0.12.4
29+
flutter_webrtc: ^0.12.6
2930
provider: 6.1.2
30-
logger: ^2.4.0
31+
logger: ^2.5.0
3132

3233
dev_dependencies:
3334
flutter_test:

0 commit comments

Comments
 (0)