Before using Stringee Call2 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 answer_url sample code here: https://github.com/stringeecom/server-samples/tree/master/answer_url
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
Check out plugin's documentation for more information.
android/app/src/main/AndroidManifest.xml
// for internet access
<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" />
// for audio access
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
// for camera access
<uses-permission android:name="android.permission.CAMERA" />
proguard-rules.pro
in your app/
dir and insert inside:
#Flutter Wrapper
-dontwarn org.webrtc.**
-keep class org.webrtc.** { *; }
-keep class com.stringee.** { *; }
/app/buidl.gradle
:
android {
...
buildTypes {
...
release {
...
useProguard true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
From the command line run following command:
pod install --repo-update
After running cocoapods command, open project file .xcworkspace
In the "Build Settings" tab → "Other linker flags" add "$(inherited)" flag
In the "Build Settings" tab → "Enable bitcode" select "NO"
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 purpose, go to Dashboard -> Tools -> Generate Access token and generate an access_token. In production, your server should generate the access_token. Sample code generates access token here: https://github.com/stringeecom/server-samples/tree/master/access_token
import 'package:stringee_plugin/stringee_plugin.dart';
...
StringeeClient _client = StringeeClient();
Register the client's events in your State
class _MyHomePageState extends State<MyHomePage> {
...
@override
Future<void> initState() {
super.initState();
...
// Listen for the StringeeClient event
_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;
case StringeeClientEvents.incomingCall2:
StringeeCall2 call = map['body'];
handleIncomingCall2Event(call);
break;
default:
break;
}
});
...
}
...
/// Invoked when the StringeeClient is connected
void handleDidConnectEvent() {}
/// Invoked when the StringeeClient is disconnected
void handleDiddisconnectEvent() {}
/// Invoked when StringeeClient connect false
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 receive an incoming of StringeeCall
void handleIncomingCallEvent(StringeeCall call) {}
/// Invoked when receive an incoming of StringeeCall2
void handleIncomingCall2Event(StringeeCall2 call) {}
...
}
@override
Future<void> initState() {
super.initState();
...
String token = 'PUT YOUR TOKEN HERE'
_client.connect(token);
...
}
After the client connects to Stringee server, follow these steps to make a call:
import 'package:stringee_plugin/stringee_plugin.dart';
...
StringeeCall2 _call2;
Register the call's events in your State
class _CallState extends State<Call> {
...
@override
Future<void> initState() {
super.initState();
...
// Listen for the StringeeClient event
_call2.eventStreamController.stream.listen((event) {
Map<dynamic, dynamic> map = event;
switch (map['eventType']) {
case StringeeCall2Events.didChangeSignalingState:
handleSignalingStateChangeEvent(map['body']);
break;
case StringeeCall2Events.didChangeMediaState:
handleMediaStateChangeEvent(map['body']);
break;
case StringeeCall2Events.didReceiveCallInfo:
handleReceiveCallInfoEvent(map['body']);
break;
case StringeeCall2Events.didHandleOnAnotherDevice:
handleHandleOnAnotherDeviceEvent(map['body']);
break;
case StringeeCall2Events.didReceiveLocalStream:
handleReceiveLocalStreamEvent(map['body']);
break;
case StringeeCall2Events.didReceiveRemoteStream:
handleReceiveRemoteStreamEvent(map['body']);
break;
case StringeeCall2Events.didAddVideoTrack:
handleAddVideoTrackEvent(map['body']);
break;
case StringeeCall2Events.didRemoveVideoTrack:
handleRemoveVideoTrackEvent(map['body']);
break;
/// This event only for android
case StringeeCall2Events.didChangeAudioDevice:
if (Platform.isAndroid) handleChangeAudioDeviceEvent(map['selectedAudioDevice'], map['availableAudioDevices']);
break;
default:
break;
}
});
...
}
...
/// Invoked when get Signaling state
void handleSignalingStateChangeEvent(StringeeSignalingState state) {
print('handleSignalingStateChangeEvent - $state');
}
/// Invoked when get Media state
void handleMediaStateChangeEvent(StringeeMediaState state) {
print('handleMediaStateChangeEvent - $state');
}
/// Invoked when get Call info
void handleReceiveCallInfoEvent(Map<dynamic, dynamic> info) {
print('handleReceiveCallInfoEvent - $info');
}
/// Invoked when an incoming call is handle on another device
void handleHandleOnAnotherDeviceEvent(StringeeSignalingState state) {
print('handleHandleOnAnotherDeviceEvent - $state');
}
/// Invoked when get Local stream in video call
void handleReceiveLocalStreamEvent(String callId) {
print('handleReceiveLocalStreamEvent - $callId');
}
/// Invoked when get Remote stream in video call
void handleReceiveRemoteStreamEvent(String callId) {
print('handleReceiveRemoteStreamEvent - $callId');
}
/// Invoked when add new video track to call in video call
void handleAddVideoTrackEvent(StringeeVideoTrack track) {
print('handleAddVideoTrackEvent - ${track.id}');
}
/// Invoked when remove video in call in video call
void handleRemoveVideoTrackEvent(StringeeVideoTrack track) {
print('handleRemoveVideoTrackEvent - ${track.id}');
}
/// Invoked when change Audio device in android
void handleChangeAudioDeviceEvent(AudioDevice audioDevice, List<AudioDevice> availableAudioDevices) {
print('handleChangeAudioDeviceEvent - $audioDevice');
}
...
}
Make a call
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'
);
_call2.makeCallFromParams(params).then((result) {
bool status = result['status'];
int code = result['code'];
String message = result['message'];
print('MakeCall CallBack --- $status - $code - $message - ${_call2.id} - ${_call2.from} - ${_call2.to}');
});
...
...
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'
};
_call2.makeCall(parameters).then((result) {
bool status = result['status'];
int code = result['code'];
String message = result['message'];
print('MakeCall CallBack --- $status - $code - $message - ${_call2.id} - ${_call2.from} - ${_call2.to}');
});
...
When the client receives an incoming call from handleIncomingCallEvent(), initialize a StringeeCall2 as described in 4.1, 4.2. Then follow these steps:
Initialize the answer
_call2.initAnswer().then((event) {
bool status = event['status'];
if (status) {
///success
}else{
///false
}
});
Answer
_call2.answer().then((result) {
bool status = result['status'];
if (status) {
///success
}else{
///false
}
});
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)
? new StringeeVideoView(
callId, /// callId of StringeeCall2
true, /// true - local video, false - remote video
alignment: Alignment.topRight,
isOverlay: true, /// required in android for make sure which overlaps the other
margin: EdgeInsets.only(top: 100.0, right: 25.0),
height: 200.0,
width: 150.0,
)
: Placeholder();
...
return new Scaffold(
backgroundColor: Colors.black,
body: new Stack(
children: <Widget>[
remoteView,
localView,
],
),
);
...
Receive and display the remote video
Using our StringeeVideoView
to display the remote video
...
void handleReceiveLocalStreamEvent(String callId) {
setState(() {
widget.hasRemoteStream = true;
widget.callId = callId;
});
}
...
Widget remoteView = (widget.hasRemoteStream)
? new StringeeVideoView(
callId, /// callId of StringeeCall2
false, /// true - local video, false - remote video
isOverlay: false, /// required in android for make sure which overlaps the other
)
: Placeholder();
...
return new Scaffold(
backgroundColor: Colors.black,
body: new Stack(
children: <Widget>[
remoteView,
localView,
],
),
);
...
Hangup a call:
```
_call2.hangup().then((result) {
bool status = result['status'];
if (status) {
///success
}else{
///false
}
});
```
Reject a call:
```
_call2.reject().then((result) {
bool status = result['status'];
if (status) {
///success
}else{
///false
}
});
```
Mute the local sound:
```
bool mute = true; // true: mute, false: unmute
_call2.mute(mute).then((result) {
bool status = result['status'];
if (status) {
///success
}else{
///false
}
});
```
Reject a call: Switch to speakerphone or internal speaker:
```
bool isSpeaker = true; // true: speakerphone, false: internal speaker
_call2.setSpeakerphoneOn(isSpeaker).then((result) {
bool status = result['status'];
if (status) {
///success
}else{
///false
}
});
```
Switch the local camera:
```
_call2.switchCamera().then((result) {
bool status = result['status'];
if (status) {
///success
}else{
///false
}
});
```
Turn on/off video:
```
bool enableVideo = true; // true: turn on, false: turn off
_call2.enableVideo(enableVideo).then((result) {
bool status = result['status'];
if (status) {
///success
}else{
///false
}
});
```
Start capture screen:
Before start capture screen, you need to create and start a Foreground service with foregroundServiceType
is mediaProjection
.
_call2.startCapture().then((result) {
bool status = result['status'];
if (status) {
///success
}else{
///false
}
});
Display capture video:
After start capture success, you will receive a StringeeVideoTrack which is your capture screen track or other screen track from didAddVideoTrack:
...
StringeeVideoTrack screenTrack;
bool hasScreenTrack = false;
...
void handleAddVideoTrackEvent(StringeeVideoTrack track) {
setState(() {
screenTrack = track;
hasScreenTrack = true;
});
}
...
Widget screenView = (hasScreenTrack)
? screenTrack.attach(
alignment: Alignment.topRight,
isOverlay: true, /// required in android for make sure which overlaps the other
margin: EdgeInsets.only(top: 100.0, right: 25.0),
height: 200.0,
width: 150.0,
)
: Placeholder();
...
return new Scaffold(
backgroundColor: Colors.black,
body: new Stack(
children: <Widget>[
...
screenView
],
),
);
...
Stop capture screen:
_call2.stopCapture().then((result) {
bool status = result['status'];
if (status) {
///success
}else{
///false
}
});
...
//After stopCaptureScreen success, you will receive event didRemoveVideoTrack, then you can remove this track view in your view
void handleRemoveVideoTrackEvent(StringeeVideoTrack track) {
setState(() {
hasScreenTrack = false;
});
}
You can view a full version of this sample app on GitHub: https://github.com/stringeecom/stringee_flutter_plugin/tree/master/example