Compare commits
No commits in common. "b95baa67996b276597ffde63b9601194cfa52b9b" and "3669312329bd8156d218aa7dbb770c96de8ebe3e" have entirely different histories.
b95baa6799
...
3669312329
4 changed files with 41 additions and 328 deletions
22
LICENSE
22
LICENSE
|
|
@ -1,21 +1 @@
|
||||||
MIT License
|
TODO: Add your license here.
|
||||||
|
|
||||||
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,237 +1,8 @@
|
||||||
# liblinphone_flutter
|
# 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/)
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,27 @@
|
||||||
// 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,84 +1,29 @@
|
||||||
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<CallType> callType() {
|
Future<String?> getPlatformVersion() => Future.value('42');
|
||||||
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