Compare commits
2 commits
3669312329
...
b95baa6799
| Author | SHA1 | Date | |
|---|---|---|---|
| b95baa6799 | |||
| c9429141ae |
4 changed files with 328 additions and 41 deletions
22
LICENSE
22
LICENSE
|
|
@ -1 +1,21 @@
|
||||||
TODO: Add your license here.
|
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.
|
||||||
|
|
|
||||||
235
README.md
235
README.md
|
|
@ -1,8 +1,237 @@
|
||||||
# liblinphone_flutter
|
# liblinphone_flutter
|
||||||
|
|
||||||
libLinPhone integration library for Flutter apps
|
A Flutter plugin that provides integration with the Linphone library for VoIP calls (audio and video) on Android and iOS platforms.
|
||||||
|
|
||||||
## Getting Started
|
## Third-Party Licenses
|
||||||
|
|
||||||
Better docs will be available some day
|
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.
|
||||||
|
|
||||||
|
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/)
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,10 @@
|
||||||
import 'package:flutter/services.dart';
|
// import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.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() {
|
void main() {
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
MethodChannelLiblinphoneFlutter platform = MethodChannelLiblinphoneFlutter();
|
// MethodChannelLiblinphoneFlutter platform = MethodChannelLiblinphoneFlutter();
|
||||||
const MethodChannel channel = MethodChannel('liblinphone_flutter');
|
// 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');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,84 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
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_platform_interface.dart';
|
||||||
import 'package:liblinphone_flutter/liblinphone_flutter_method_channel.dart';
|
// import 'package:liblinphone_flutter/liblinphone_flutter_method_channel.dart';
|
||||||
|
import 'package:liblinphone_flutter/models/call_type.dart';
|
||||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||||
|
|
||||||
class MockLiblinphoneFlutterPlatform
|
class MockLiblinphoneFlutterPlatform
|
||||||
with MockPlatformInterfaceMixin
|
with MockPlatformInterfaceMixin
|
||||||
implements LiblinphoneFlutterPlatform {
|
implements LiblinphoneFlutterPlatform {
|
||||||
|
@override
|
||||||
|
Future<bool> answerCall() {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String?> getPlatformVersion() => Future.value('42');
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
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');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue