Before using Stringee Call API for the first time, you must have a Stringee account.
If you do not have a Stringee account, sign up for free here: https://developer.stringee.com/account/register
Create a Project on Stringee Dashboard

Buy a Number (optional)
For app-to-phone, phone-to-app calling, buy a Number from Dashboard. If you only need app-to-app calling, skip this step.

Configure answer_url
For more information about answer_url, read Stringee Call API Overview. You can view the answer_url sample code.

If you do not have answer_url, you can use the following Project's answer_url to speed up the process:
Project's answer_url for App-to-App call:
https://developer.stringee.com/scco_helper/simple_project_answer_url?record=false&appToPhone=false
Project's answer_url for App-to-Phone call:
https://developer.stringee.com/scco_helper/simple_project_answer_url?record=false&appToPhone=true
(Source code: https://github.com/stringeecom/server-samples/blob/master/answer_url/php/project_answer_url.php)
When building an application, you should use your own answer_url.

If you do not have answer_url, you can use the following Number's answer_url to speed up the process:
Number's answer_url for Phone-to-App call (The call is routed to Your App, which is authenticated by USER_ID):
https://developer.stringee.com/scco_helper/simple_number_answer_url?record=true&phoneToPhone=false&to_number=USER_ID
Number's answer_url for Phone-to-Phone call (The call is routed to TO_NUMBER):
https://developer.stringee.com/scco_helper/simple_number_answer_url?record=true&phoneToPhone=true&stringeeNumber=STRINGEE_NUMBER&to_number=TO_NUMBER
(Source code: https://github.com/stringeecom/server-samples/blob/master/answer_url/php/number_answer_url.php)
When building an application, you should use your own answer_url.
Install stringee-plugin from pub.dev by running the following command from the project root:
$ flutter pub add stringee_plugin:^1.3.2
Check out plugin's documentation for more information.
The latest plugin requires JDK 17, Android minSdk 21 or later, and is compiled with Android API 36. If your application declares its own compileSdk, set it to 36 or later:
android {
compileSdk = 36
defaultConfig {
minSdk = 21
}
}
Permissions
The Stringee Android SDK requires some permissions from your AndroidManifest. Open up android/app/src/main/AndroidManifest.xml then add the following lines:
<!--Internet-->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!--Record-->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!--Audio-->
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<!--Camera-->
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-feature
android:name="android.hardware.camera.autofocus"
android:required="false" />
<!--Bluetooth-->
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <!--Require for android 12 or higher-->
<uses-feature
android:name="android.hardware.bluetooth"
android:required="false" />
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="false" />
<!-- Graphic -->
<uses-feature
android:glEsVersion="0x00020000"
android:required="false" />
Request camera, microphone, and BLUETOOTH_CONNECT at runtime on Android versions where they are dangerous permissions.
ProGuard/R8 rules for Stringee and WebRTC are included in the plugin. Keep your application's existing minification configuration; you do not need to enable minification specifically for Stringee.
The latest plugin requires iOS 13.0 or later.
From the command line run following command:
pod install --repo-update
After running CocoaPods, open the generated .xcworkspace file.
Right-click the information property list file (Info.plist) and select Open As → Source Code. Then insert the following XML snippet into the body of your file just before the final element:
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) uses Camera</string>
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) uses Microphone</string>
In the "Build Settings" tab → "Allow Non-modular includes in Framework Modules" select "YES"
To connect to Stringee Server, 3-party authentication is required as described here: Client authentication
For testing, go to Dashboard -> Tools -> Generate Access token and generate an access_token. In production, your server should generate the access_token. See the access token examples.
Initialize StringeeClient:
import 'dart:async';
import 'package:stringee_plugin/stringee_plugin.dart';
...
final StringeeClient _client = StringeeClient();
StreamSubscription? _clientSubscription;
StreamSubscription? _callSubscription;
Register the client's events in your State
// Listen for the StringeeClient event
_clientSubscription = _client.eventStreamController.stream.listen((event) {
Map<dynamic, dynamic> map = event;
switch (map['eventType']) {
case StringeeClientEvents.didConnect:
handleDidConnectEvent();
break;
case StringeeClientEvents.didDisconnect:
handleDiddisconnectEvent();
break;
case StringeeClientEvents.didFailWithError:
int code = map['body']['code'];
String msg = map['body']['message'];
handleDidFailWithErrorEvent(code,msg);
break;
case StringeeClientEvents.requestAccessToken:
handleRequestAccessTokenEvent();
break;
case StringeeClientEvents.didReceiveCustomMessage:
handleDidReceiveCustomMessageEvent(map['body']);
break;
case StringeeClientEvents.incomingCall:
StringeeCall call = map['body'];
handleIncomingCallEvent(call);
break;
default:
break;
}
});
...
/// Invoked when the StringeeClient is connected
void handleDidConnectEvent() {}
/// Invoked when the StringeeClient is disconnected
void handleDiddisconnectEvent() {}
/// Invoked when the StringeeClient connection fails
void handleDidFailWithErrorEvent(int code, String message) {}
/// Invoked when your token is expired
void handleRequestAccessTokenEvent() {}
/// Invoked when get Custom message
void handleDidReceiveCustomMessageEvent(Map<dynamic, dynamic> map) {}
/// Invoked when an incoming StringeeCall is received
void handleIncomingCallEvent(StringeeCall call) {}
Connect
String token = 'PUT YOUR TOKEN HERE';
_client.connect(token);
After the client connects to Stringee server, follow these steps to make a call:
Initialize StringeeCall
import 'package:stringee_plugin/stringee_plugin.dart';
...
late StringeeCall _call;
...
_call = StringeeCall(_client);
Register the call's events in your State
// Register this listener for every outgoing or incoming StringeeCall object.
void registerCallEvents(StringeeCall call) {
_callSubscription?.cancel();
_callSubscription = call.eventStreamController.stream.listen((event) {
Map<dynamic, dynamic> map = event;
switch (map['eventType']) {
case StringeeCallEvents.didChangeSignalingState:
handleSignalingStateChangeEvent(map['body']);
break;
case StringeeCallEvents.didChangeMediaState:
handleMediaStateChangeEvent(map['body']);
break;
case StringeeCallEvents.didReceiveCallInfo:
handleReceiveCallInfoEvent(map['body']);
break;
case StringeeCallEvents.didHandleOnAnotherDevice:
handleHandleOnAnotherDeviceEvent(map['body']);
break;
case StringeeCallEvents.didReceiveLocalStream:
handleReceiveLocalStreamEvent(map['body']);
break;
case StringeeCallEvents.didReceiveRemoteStream:
handleReceiveRemoteStreamEvent(map['body']);
break;
default:
break;
}
});
}
registerCallEvents(_call);
...
/// Invoked when get Signaling state
void handleSignalingStateChangeEvent(StringeeSignalingState state) {}
/// Invoked when get Media state
void handleMediaStateChangeEvent(StringeeMediaState state) {}
/// Invoked when get Call info
void handleReceiveCallInfoEvent(Map<dynamic, dynamic> info) {}
/// Invoked when an incoming call is handle on another device
void handleHandleOnAnotherDeviceEvent(StringeeSignalingState state) {}
/// Invoked when get Local stream in video call
void handleReceiveLocalStreamEvent(String callId) {}
/// Invoked when get Remote stream in video call
void handleReceiveRemoteStreamEvent(String callId) {}
Make a call
Option 1: Using our MakeCallParams for easily to make a call
MakeCallParams params = MakeCallParams(
'caller_userId', /// caller id
'callee_userId', /// callee id
isVideoCall: false, /// true - video call, false - not video call, default is 'false'
videoQuality: VideoQuality.normal, /// video quality in video call, default is 'normal'
);
_call.makeCallFromParams(params).then((result) {
bool status = result['status'];
int code = result['code'];
String message = result['message'];
print('MakeCall CallBack --- $status - $code - $message - ${_call.id} - ${_call.from} - ${_call.to}');
});
Option 2: Using custom parameters to make a call
final parameters = {
'from': 'caller_userId',
'to': 'callee_userId',
'isVideoCall': false, /// true - video call, false - not video call, default is 'false'
'videoQuality': VideoQuality.normal, /// video quality in video call, default is 'normal'
};
_call.makeCall(parameters).then((result) {
bool status = result['status'];
int code = result['code'];
String message = result['message'];
print('MakeCall CallBack --- $status - $code - $message - ${_call.id} - ${_call.from} - ${_call.to}');
});
When the client receives an incoming call from handleIncomingCallEvent(), use a StringeeCall received from handleIncomingCallEvent. Then follow these steps:
Initialize the call
/// Invoked when an incoming StringeeCall is received
void handleIncomingCallEvent(StringeeCall call) {
_call = call;
registerCallEvents(_call);
// Must initAnswer before answer a call
_call.initAnswer();
}
Answer a call
_call.answer();
A StringeeCall is a voice call by default. If you want to make a video call, you must change value of field isVideoCall in your parameters to true
Receive and display the local video
Using our StringeeVideoView to display the local video
void handleReceiveLocalStreamEvent(String callId) {
setState(() {
widget.hasLocalStream = true;
widget.callId = callId;
});
}
...
Widget localView = widget.hasLocalStream
? StringeeVideoView(
callId, /// callId of StringeeCall
true, /// true - local video, false - remote video
)
: Placeholder();
...
return Scaffold(
body: Stack(
children: [
remoteView,
localView,
],
),
);
Receive and display the remote video
Using our StringeeVideoView to display the remote video
...
void handleReceiveRemoteStreamEvent(String callId) {
setState(() {
widget.hasRemoteStream = true;
widget.callId = callId;
});
}
...
Widget remoteView = widget.hasRemoteStream
? StringeeVideoView(
callId, /// callId of StringeeCall
false, /// true - local video, false - remote video
)
: Placeholder();
...
return Scaffold(
body: Stack(
children: [
remoteView,
localView,
],
),
);
Hang up a call:
_call.hangup();
Reject a call:
_call.reject();
Mute the local sound:
bool mute = true; // true: mute, false: unmute
_call.mute(mute);
Manager output audio device by using singleton class StringeeAudioManager:
Listening for audio device change events:
AudioDevice? selectedAudioDevice; // Default audio device
List<AudioDevice> availableAudioDevices = []; // List of available audio devices
...
// Create StringeeAudioEvent
StringeeAudioEvent audioEvent = StringeeAudioEvent(onChangeAudioDevice: (selectedAudioDevice, availableAudioDevices) {
// Save list of available audio devices and selected audio device
this.availableAudioDevices = availableAudioDevices;
this.selectedAudioDevice = selectedAudioDevice;
// For the first time, you need choose an audio device that you want to use in your call.
// E.g: Video call get AudioDevice with type is AudioType.speakerPhone from availableAudioDevices or which device you want to use then call selectDevice(AudioDevice device) method
});
// Register the audioEvent to StringeeAudioManager
StringeeAudioManager().addListener(audioEvent);
Start audio manager:
You need to start the audio manager before using it.
StringeeAudioManager().start();
Select audio device:
To select an audio device, you can use the following code:
// Select audio device
StringeeAudioManager().selectDevice(audioDevice);
Release audio manager:
Before your app is closed, you should release the audio manager to avoid bugs when the call is ended or the app is closed:
// Stop listening for audio events
StringeeAudioManager().removeListener(audioEvent);
// Stop the audio manager
StringeeAudioManager().stop();
Switch the local camera:
_call.switchCamera();
Turn on/off video:
bool enableVideo = true; // true: turn on, false: turn off
_call.enableVideo(enableVideo);
In case your app is in the background or terminated, you need to handle call notification by following the Flutter push notification guide.
You can view a full version of this sample app on GitHub: call_sample