feat: Working VoIP calling implementation (Flutter + Android)
Working video and audio calls, as well as android integration
This commit is contained in:
commit
96a7e211a0
60 changed files with 2445 additions and 0 deletions
70
lib/liblinphone_flutter.dart
Normal file
70
lib/liblinphone_flutter.dart
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import 'package:flutter/services.dart' show EventChannel;
|
||||
|
||||
import 'liblinphone_flutter_platform_interface.dart';
|
||||
import 'models/call_type.dart';
|
||||
import 'models/registration_state.dart';
|
||||
import 'models/call_state.dart';
|
||||
|
||||
class LiblinphoneFlutter {
|
||||
final _registrationEventsStream = EventChannel(
|
||||
'liblinphone_flutter.nuark.xyz/registration_events',
|
||||
);
|
||||
|
||||
final _callEventsStream = EventChannel(
|
||||
'liblinphone_flutter.nuark.xyz/call_events',
|
||||
);
|
||||
|
||||
Stream<RegistrationState> get registrationEvents =>
|
||||
_registrationEventsStream.receiveBroadcastStream().map((dynamic event) {
|
||||
return RegistrationState.fromOrdinal(event);
|
||||
});
|
||||
|
||||
Stream<CallState> get callEvents =>
|
||||
_callEventsStream.receiveBroadcastStream().map((dynamic event) {
|
||||
print("call event: $event");
|
||||
return CallState.fromOrdinal(event);
|
||||
});
|
||||
|
||||
Future<bool> checkPermissions() async =>
|
||||
LiblinphoneFlutterPlatform.instance.checkPermissions();
|
||||
|
||||
Future<bool> initialize() async =>
|
||||
LiblinphoneFlutterPlatform.instance.initialize();
|
||||
|
||||
Future<bool> register(
|
||||
String username,
|
||||
String password,
|
||||
String serverIp,
|
||||
int serverPort,
|
||||
) async => LiblinphoneFlutterPlatform.instance.register(
|
||||
username,
|
||||
password,
|
||||
serverIp,
|
||||
serverPort,
|
||||
);
|
||||
|
||||
Future<bool> unregister() async =>
|
||||
LiblinphoneFlutterPlatform.instance.unregister();
|
||||
|
||||
Future<bool> makeCall(String callTo, bool isVideoEnabled) async =>
|
||||
LiblinphoneFlutterPlatform.instance.makeCall(callTo, isVideoEnabled);
|
||||
|
||||
Future<bool> answerCall() async =>
|
||||
LiblinphoneFlutterPlatform.instance.answerCall();
|
||||
|
||||
Future<bool> hangupCall() async =>
|
||||
LiblinphoneFlutterPlatform.instance.hangupCall();
|
||||
|
||||
Future<bool> inCall() async => LiblinphoneFlutterPlatform.instance.inCall();
|
||||
|
||||
Future<CallType> callType() async =>
|
||||
LiblinphoneFlutterPlatform.instance.callType();
|
||||
|
||||
Future<bool> toggleVideo() async =>
|
||||
LiblinphoneFlutterPlatform.instance.toggleVideo();
|
||||
|
||||
Future<bool> toggleMicrophone() async =>
|
||||
LiblinphoneFlutterPlatform.instance.toggleMicrophone();
|
||||
|
||||
Future<bool> stop() async => LiblinphoneFlutterPlatform.instance.stop();
|
||||
}
|
||||
87
lib/liblinphone_flutter_method_channel.dart
Normal file
87
lib/liblinphone_flutter_method_channel.dart
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'liblinphone_flutter_platform_interface.dart';
|
||||
import 'models/call_type.dart';
|
||||
|
||||
/// An implementation of [LiblinphoneFlutterPlatform] that uses method channels.
|
||||
class MethodChannelLiblinphoneFlutter extends LiblinphoneFlutterPlatform {
|
||||
/// The method channel used to interact with the native platform.
|
||||
@visibleForTesting
|
||||
final methodChannel = const MethodChannel('liblinphone_flutter');
|
||||
|
||||
@override
|
||||
Future<bool> checkPermissions() async {
|
||||
return (await methodChannel.invokeMethod<bool>('checkPermissions'))!;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> initialize() async {
|
||||
return (await methodChannel.invokeMethod<bool>('initialize'))!;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> register(
|
||||
String username,
|
||||
String password,
|
||||
String serverIp,
|
||||
int serverPort,
|
||||
) async {
|
||||
return (await methodChannel
|
||||
.invokeMethod<bool>('register', <String, dynamic>{
|
||||
'username': username,
|
||||
'password': password,
|
||||
'serverIp': serverIp,
|
||||
'serverPort': serverPort,
|
||||
}))!;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> unregister() async {
|
||||
return (await methodChannel.invokeMethod<bool>('unregister'))!;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> makeCall(String callTo, bool isVideoEnabled) async {
|
||||
return (await methodChannel.invokeMethod<bool>(
|
||||
'makeCall',
|
||||
<String, dynamic>{'callTo': callTo, 'isVideoEnabled': isVideoEnabled},
|
||||
))!;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> answerCall() async {
|
||||
return (await methodChannel.invokeMethod<bool>('answerCall'))!;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> hangupCall() async {
|
||||
return (await methodChannel.invokeMethod<bool>('hangupCall'))!;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> inCall() async {
|
||||
return (await methodChannel.invokeMethod<bool>('inCall'))!;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<CallType> callType() async {
|
||||
final callTypeOrdinal = await methodChannel.invokeMethod<int>('callType');
|
||||
return CallType.fromOrdinal(callTypeOrdinal!);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> toggleVideo() async {
|
||||
return (await methodChannel.invokeMethod<bool>('toggleVideo'))!;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> toggleMicrophone() async {
|
||||
return (await methodChannel.invokeMethod<bool>('toggleMicrophone'))!;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> stop() async {
|
||||
return (await methodChannel.invokeMethod<bool>('stop'))!;
|
||||
}
|
||||
}
|
||||
80
lib/liblinphone_flutter_platform_interface.dart
Normal file
80
lib/liblinphone_flutter_platform_interface.dart
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import 'package:liblinphone_flutter/models/call_type.dart';
|
||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||
|
||||
import 'liblinphone_flutter_method_channel.dart';
|
||||
|
||||
abstract class LiblinphoneFlutterPlatform extends PlatformInterface {
|
||||
/// Constructs a LiblinphoneFlutterPlatform.
|
||||
LiblinphoneFlutterPlatform() : super(token: _token);
|
||||
|
||||
static final Object _token = Object();
|
||||
|
||||
static LiblinphoneFlutterPlatform _instance =
|
||||
MethodChannelLiblinphoneFlutter();
|
||||
|
||||
/// The default instance of [LiblinphoneFlutterPlatform] to use.
|
||||
///
|
||||
/// Defaults to [MethodChannelLiblinphoneFlutter].
|
||||
static LiblinphoneFlutterPlatform get instance => _instance;
|
||||
|
||||
/// Platform-specific implementations should set this with their own
|
||||
/// platform-specific class that extends [LiblinphoneFlutterPlatform] when
|
||||
/// they register themselves.
|
||||
static set instance(LiblinphoneFlutterPlatform instance) {
|
||||
PlatformInterface.verifyToken(instance, _token);
|
||||
_instance = instance;
|
||||
}
|
||||
|
||||
Future<bool> checkPermissions() {
|
||||
throw UnimplementedError('checkPermissions() has not been implemented.');
|
||||
}
|
||||
|
||||
Future<bool> initialize() {
|
||||
throw UnimplementedError('initialize() has not been implemented.');
|
||||
}
|
||||
|
||||
Future<bool> register(
|
||||
String username,
|
||||
String password,
|
||||
String serverIp,
|
||||
int serverPort,
|
||||
) {
|
||||
throw UnimplementedError('register() has not been implemented.');
|
||||
}
|
||||
|
||||
Future<bool> unregister() {
|
||||
throw UnimplementedError('unregister() has not been implemented.');
|
||||
}
|
||||
|
||||
Future<bool> makeCall(String callTo, bool isVideoEnabled) {
|
||||
throw UnimplementedError('makeCall() has not been implemented.');
|
||||
}
|
||||
|
||||
Future<bool> answerCall() {
|
||||
throw UnimplementedError('answerCall() has not been implemented.');
|
||||
}
|
||||
|
||||
Future<bool> hangupCall() {
|
||||
throw UnimplementedError('hangupCall() has not been implemented.');
|
||||
}
|
||||
|
||||
Future<bool> inCall() {
|
||||
throw UnimplementedError('inCall() has not been implemented.');
|
||||
}
|
||||
|
||||
Future<CallType> callType() {
|
||||
throw UnimplementedError('callType() has not been implemented.');
|
||||
}
|
||||
|
||||
Future<bool> toggleVideo() {
|
||||
throw UnimplementedError('toggleVideo() has not been implemented.');
|
||||
}
|
||||
|
||||
Future<bool> toggleMicrophone() {
|
||||
throw UnimplementedError('toggleMicrophone() has not been implemented.');
|
||||
}
|
||||
|
||||
Future<bool> stop() {
|
||||
throw UnimplementedError('stop() has not been implemented.');
|
||||
}
|
||||
}
|
||||
28
lib/models/call_state.dart
Normal file
28
lib/models/call_state.dart
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
enum CallState {
|
||||
Idle,
|
||||
IncomingReceived,
|
||||
PushIncomingReceived,
|
||||
OutgoingInit,
|
||||
OutgoingProgress,
|
||||
OutgoingRinging,
|
||||
OutgoingEarlyMedia,
|
||||
Connected,
|
||||
StreamsRunning,
|
||||
Pausing,
|
||||
Paused,
|
||||
Resuming,
|
||||
Referred,
|
||||
Error,
|
||||
End,
|
||||
PausedByRemote,
|
||||
UpdatedByRemote,
|
||||
IncomingEarlyMedia,
|
||||
Updating,
|
||||
Released,
|
||||
EarlyUpdatedByRemote,
|
||||
EarlyUpdating;
|
||||
|
||||
static CallState fromOrdinal(int ordinal) {
|
||||
return values[ordinal];
|
||||
}
|
||||
}
|
||||
9
lib/models/call_type.dart
Normal file
9
lib/models/call_type.dart
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
enum CallType {
|
||||
Audio,
|
||||
Video,
|
||||
Unknown;
|
||||
|
||||
static CallType fromOrdinal(int ordinal) {
|
||||
return values[ordinal];
|
||||
}
|
||||
}
|
||||
11
lib/models/registration_state.dart
Normal file
11
lib/models/registration_state.dart
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
enum RegistrationState {
|
||||
None,
|
||||
Progress,
|
||||
Ok,
|
||||
Cleared,
|
||||
Failed;
|
||||
|
||||
static RegistrationState fromOrdinal(int ordinal) {
|
||||
return values[ordinal];
|
||||
}
|
||||
}
|
||||
20
lib/widgets/local_view.dart
Normal file
20
lib/widgets/local_view.dart
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class LocalView extends StatelessWidget {
|
||||
const LocalView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Map<String, dynamic> creationParams = <String, dynamic>{};
|
||||
|
||||
return AndroidView(
|
||||
viewType: "liblinphone_flutter.nuark.xyz/local_view",
|
||||
creationParams: creationParams,
|
||||
creationParamsCodec: const StandardMessageCodec(),
|
||||
onPlatformViewCreated: (int id) {
|
||||
print("onPlatformViewCreated: created LocalView with id $id");
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
20
lib/widgets/remote_view.dart
Normal file
20
lib/widgets/remote_view.dart
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class RemoteView extends StatelessWidget {
|
||||
const RemoteView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Map<String, dynamic> creationParams = <String, dynamic>{};
|
||||
|
||||
return AndroidView(
|
||||
viewType: "liblinphone_flutter.nuark.xyz/remote_view",
|
||||
creationParams: creationParams,
|
||||
creationParamsCodec: const StandardMessageCodec(),
|
||||
onPlatformViewCreated: (int id) {
|
||||
print("onPlatformViewCreated: created RemoteView with id $id");
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue