Compare commits

..

No commits in common. "b95baa67996b276597ffde63b9601194cfa52b9b" and "3669312329bd8156d218aa7dbb770c96de8ebe3e" have entirely different histories.

4 changed files with 41 additions and 328 deletions

22
LICENSE
View file

@ -1,21 +1 @@
MIT License
Copyright (c) 2025-2026 Andrew 'nuark' G.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
TODO: Add your license here.

235
README.md
View file

@ -1,237 +1,8 @@
# liblinphone_flutter
A Flutter plugin that provides integration with the Linphone library for VoIP calls (audio and video) on Android and iOS platforms.
libLinPhone integration library for Flutter apps
## Third-Party Licenses
## Getting Started
This project depends on the Linphone SDK, developed by Belledonne Communications, which is dual-licensed under the GNU Affero General Public License v3 (AGPLv3) and a proprietary commercial license.
Better docs will be available some day
This wrapper library itself is licensed under the MIT License. However, it is designed to work with the Linphone SDK, and therefore **does not remove or replace the licensing requirements of Linphone**.
### Important
Any application using this plugin and linking against the Linphone SDK is subject to the terms of the AGPLv3, unless a commercial license for Linphone is obtained.
In practice, this means that applications using this plugin must comply with AGPLv3 requirements, which may include releasing the application's source code under a compatible license.
For proprietary or closed-source applications, you must obtain a commercial license for the Linphone SDK from Belledonne Communications.
## Description
This plugin wraps the native Linphone SDK (liblinphone) and exposes a Dart API for making SIP-based voice and video calls in Flutter applications.
## Features
- SIP account registration
- Audio and video calls
- Video streaming with local preview and remote view
- Call management (answer, hangup, toggle video/microphone)
- Registration and call state event streams
## Platform Support
| Platform | Status |
| -------- | --------- |
| Android | Supported |
| iOS | Supported |
## Requirements
- Flutter SDK >= 3.3.0
- Dart SDK >= 3.9.0-333.2.beta
- Linphone SDK (native dependency)
## Installation
Add this to your `pubspec.yaml`:
```yaml
dependencies:
liblinphone_flutter: ^0.0.3
```
Or if you want to go with git:
```yaml
dependencies:
liblinphone_flutter:
git:
url: https://git.nuark.xyz/nuark/liblinphone_flutter.git
ref: commit-hash
```
Then run:
```bash
flutter pub get
```
### Platform-Specific Setup
#### Android
Ensure you have the necessary permissions in your `AndroidManifest.xml`:
```xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
```
#### iOS
The plugin requires access to the camera and microphone. Add the following to your `Info.plist`:
```xml
<key>NSCameraUsageDescription</key>
<string>Camera access is required for video calls</string>
<key>NSMicrophoneUsageDescription</key>
<string>Microphone access is required for audio and video calls</string>
```
## Usage
### Basic Example
```dart
import 'package:liblinphone_flutter/liblinphone_flutter.dart';
final liblinphone = LiblinphoneFlutter();
// Initialize
await liblinphone.checkPermissions();
await liblinphone.initialize();
// Register to SIP server
await liblinphone.register(
'username',
'password',
'sip.server.com',
5060,
);
// Listen to registration events
liblinphone.registrationEvents.listen((state) {
print('Registration state: $state');
});
// Listen to call events
liblinphone.callEvents.listen((state) {
print('Call state: $state');
});
// Make a video call
await liblinphone.makeCall('sip:recipient@sip.server.com', true);
// Answer incoming call
await liblinphone.answerCall();
// Toggle video during call
await liblinphone.toggleVideo();
// Toggle microphone
await liblinphone.toggleMicrophone();
// Hangup
await liblinphone.hangupCall();
// Unregister and cleanup
await liblinphone.unregister();
await liblinphone.stop();
```
### Video Views
For video calls, use the provided widgets to display local and remote video streams:
```dart
import 'package:liblinphone_flutter/widgets/local_view.dart';
import 'package:liblinphone_flutter/widgets/remote_view.dart';
// In your widget tree
Column(
children: [
// Remote video (full screen or large area)
Expanded(
child: RemoteView(),
),
// Local video preview (picture-in-picture)
SizedBox(
height: 150,
width: 100,
child: LocalView(),
),
],
)
```
## API Reference
### Main Class: `LiblinphoneFlutter`
#### Methods
| Method | Description |
| ----------------------------------------------------------------- | -------------------------------------------------- |
| `Future<bool> checkPermissions()` | Checks and requests camera/microphone permissions |
| `Future<bool> initialize()` | Initializes the Linphone core |
| `Future<bool> register(username, password, serverIp, serverPort)` | Registers to a SIP server |
| `Future<bool> unregister()` | Unregisters from the SIP server |
| `Future<bool> makeCall(callTo, isVideoEnabled)` | Makes an outgoing call |
| `Future<bool> answerCall()` | Answers an incoming call |
| `Future<bool> hangupCall()` | Hangs up the current call |
| `Future<bool> inCall()` | Returns true if there is an active call |
| `Future<CallType> callType()` | Returns the type of the current call (audio/video) |
| `Future<bool> toggleVideo()` | Toggles video enabled state during a call |
| `Future<bool> toggleMicrophone()` | Toggles microphone muted state |
| `Future<bool> stop()` | Stops the Linphone core |
| `Future<void> syncCurrentState()` | Forces synchronization of current state |
#### Event Streams
| Stream | Type | Description |
| -------------------- | --------------------------- | -------------------------------- |
| `registrationEvents` | `Stream<RegistrationState>` | Emits registration state changes |
| `callEvents` | `Stream<CallState>` | Emits call state changes |
### Enums
#### `RegistrationState`
- `None` - Not registered
- `Progress` - Registration in progress
- `Ok` - Successfully registered
- `Cleared` - Registration cleared
- `Failed` - Registration failed
#### `CallState`
- `Idle` - No active call
- `IncomingReceived` - Incoming call received
- `OutgoingInit` - Outgoing call initialized
- `OutgoingProgress` - Outgoing call in progress
- `OutgoingRinging` - Remote party ringing
- `Connected` - Call connected
- `StreamsRunning` - Media streams running
- `Pausing` - Call pausing
- `Paused` - Call paused
- `Resuming` - Call resuming
- `Error` - Call error
- `End` - Call ended
- And other states...
#### `CallType`
- `Audio` - Audio-only call
- `Video` - Video call
- `Unknown` - Call type unknown
## License
This project is licensed under the MIT License.
## Links
- [Homepage](https://git.nuark.xyz/nuark/liblinphone_flutter)
- [Linphone Project](https://www.linphone.org/)

View file

@ -1,10 +1,27 @@
// import 'package:flutter/services.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
// import 'package:liblinphone_flutter/liblinphone_flutter_method_channel.dart';
import 'package:liblinphone_flutter/liblinphone_flutter_method_channel.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
// MethodChannelLiblinphoneFlutter platform = MethodChannelLiblinphoneFlutter();
// const MethodChannel channel = MethodChannel('liblinphone_flutter');
MethodChannelLiblinphoneFlutter platform = MethodChannelLiblinphoneFlutter();
const MethodChannel channel = MethodChannel('liblinphone_flutter');
setUp(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
channel,
(MethodCall methodCall) async {
return '42';
},
);
});
tearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null);
});
test('getPlatformVersion', () async {
expect(await platform.getPlatformVersion(), '42');
});
}

View file

@ -1,84 +1,29 @@
import 'package:flutter_test/flutter_test.dart';
// import 'package:liblinphone_flutter/liblinphone_flutter.dart';
import 'package:liblinphone_flutter/liblinphone_flutter.dart';
import 'package:liblinphone_flutter/liblinphone_flutter_platform_interface.dart';
// import 'package:liblinphone_flutter/liblinphone_flutter_method_channel.dart';
import 'package:liblinphone_flutter/models/call_type.dart';
import 'package:liblinphone_flutter/liblinphone_flutter_method_channel.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
class MockLiblinphoneFlutterPlatform
with MockPlatformInterfaceMixin
implements LiblinphoneFlutterPlatform {
@override
Future<bool> answerCall() {
throw UnimplementedError();
}
@override
Future<CallType> callType() {
throw UnimplementedError();
}
@override
Future<bool> checkPermissions() {
throw UnimplementedError();
}
@override
Future<bool> hangupCall() {
throw UnimplementedError();
}
@override
Future<bool> inCall() {
throw UnimplementedError();
}
@override
Future<bool> initialize() {
throw UnimplementedError();
}
@override
Future<bool> makeCall(String callTo, bool isVideoEnabled) {
throw UnimplementedError();
}
@override
Future<bool> register(
String username,
String password,
String serverIp,
int serverPort,
) {
throw UnimplementedError();
}
@override
Future<bool> stop() {
throw UnimplementedError();
}
@override
Future<bool> syncCurrentState() {
throw UnimplementedError();
}
@override
Future<bool> toggleMicrophone() {
throw UnimplementedError();
}
@override
Future<bool> toggleVideo() {
throw UnimplementedError();
}
@override
Future<bool> unregister() {
throw UnimplementedError();
}
Future<String?> getPlatformVersion() => Future.value('42');
}
void main() {
// final LiblinphoneFlutterPlatform initialPlatform = LiblinphoneFlutterPlatform.instance;
final LiblinphoneFlutterPlatform initialPlatform = LiblinphoneFlutterPlatform.instance;
test('$MethodChannelLiblinphoneFlutter is the default instance', () {
expect(initialPlatform, isInstanceOf<MethodChannelLiblinphoneFlutter>());
});
test('getPlatformVersion', () async {
LiblinphoneFlutter liblinphoneFlutterPlugin = LiblinphoneFlutter();
MockLiblinphoneFlutterPlatform fakePlatform = MockLiblinphoneFlutterPlatform();
LiblinphoneFlutterPlatform.instance = fakePlatform;
expect(await liblinphoneFlutterPlugin.getPlatformVersion(), '42');
});
}