feat: Working VoIP calling implementation (Flutter + Android)

Working video and audio calls, as well as android integration
This commit is contained in:
Andrew 2025-08-30 18:46:02 +07:00
commit 96a7e211a0
60 changed files with 2445 additions and 0 deletions

View 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();
}

View 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'))!;
}
}

View 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.');
}
}

View 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];
}
}

View file

@ -0,0 +1,9 @@
enum CallType {
Audio,
Video,
Unknown;
static CallType fromOrdinal(int ordinal) {
return values[ordinal];
}
}

View file

@ -0,0 +1,11 @@
enum RegistrationState {
None,
Progress,
Ok,
Cleared,
Failed;
static RegistrationState fromOrdinal(int ordinal) {
return values[ordinal];
}
}

View 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");
},
);
}
}

View 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");
},
);
}
}