Suggestions

close search

Getting started with Stringee Call2 API using Flutter Plugin

Step 1: Prepare

  1. 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

  2. Create a Project on Stringee Dashboard

    Stringee create Project

  3. 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. Stringee buy Number

  4. 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

    • Configure Project's answer_url: To make an app-to-app, app-to-phone call, configure your Project's answer_url

    Stringee Project 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.

    • Configure Number's answer_url: To receive a phone-to-app call, configure your Number's answer_url

    Stringee Number 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.

Step 2: Install stringee-plugin

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.

Step 3: Setup

Android

  1. Permissions The Stringee Android SDK requires some permissions from your AndroidManifest
    • Open up android/app/src/main/AndroidManifest.xml
    • Add the following lines:
      // 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" />
  2. Proguard If your project uses ProGuard, you may have to add the following settings to the ProGuard configuration file to ensure Stringee builds correctly:
    • Create file proguard-rules.pro in your app/ dir and insert inside:
      #Flutter Wrapper
      -dontwarn org.webrtc.**
      -keep class org.webrtc.** { *; }
      -keep class com.stringee.** { *; }
    • Add the following lines to /app/buidl.gradle :
      android {
      ...
         buildTypes {
         ...
             release {
             ...
             useProguard true
             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
             }
         }
      }

iOS

  1. From the command line run following command:

    pod install --repo-update
  2. After running cocoapods command, open project file .xcworkspace

  3. In the "Build Settings" tab → "Other linker flags" add "$(inherited)" flag

  4. In the "Build Settings" tab → "Enable bitcode" select "NO"

  5. 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>
  6. In the "Build Settings" tab → "Allow Non-modular includes in Framework Modules" select "YES"

Step 4: Connect to Stringee Server

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

  1. Initialize StringeeClient:
    import 'package:stringee_plugin/stringee_plugin.dart';
    ...
    StringeeClient _client = StringeeClient();
  2. 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) {}
        ...
    }
  3. Connect
        @override
            Future<void> initState() {
                super.initState();
                ...
                String token = 'PUT YOUR TOKEN HERE'
                _client.connect(token);
                ...
            }

Step 5: Make a call

After the client connects to Stringee server, follow these steps to make a call:

  1. Initialize StringeeCall2
    import 'package:stringee_plugin/stringee_plugin.dart';
    ...
    StringeeCall2 _call2;
  2. 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');
        }
        ...
    }
  3. 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}');
    });
    ...
    • 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'
    };
    
    _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}');
    });
    ...

Step 6: Answer a call

When the client receives an incoming call from handleIncomingCallEvent(), initialize a StringeeCall2 as described in 4.1, 4.2. Then follow these steps:

  1. Initialize the answer

    _call2.initAnswer().then((event) {
        bool status = event['status'];
        if (status) {
            ///success
        }else{
            ///false
        }
    });
  2. Answer

    _call2.answer().then((result) {
        bool status = result['status'];
        if (status) {
            ///success
        }else{
            ///false
        }
    });

Step 7: Make a video call

  1. A Stringee call2 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
  2. 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,
        ],
      ),
    );
    ...
  3. 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,
        ],
      ),
    );
    ...

Step 8: Hangup

Hangup a call:

```
_call2.hangup().then((result) {
    bool status = result['status'];
    if (status) {
        ///success
    }else{
        ///false
    }
    });
```

Step 9: Reject

Reject a call:

```
_call2.reject().then((result) {
    bool status = result['status'];
    if (status) {
        ///success
    }else{
        ///false
    }
});
```

Step 10: Mute

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
    }
});
```

Step 11: Switch speaker

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
    }
});
```

Step 12: Switch camera

Switch the local camera:

```
_call2.switchCamera().then((result) {
    bool status = result['status'];
    if (status) {
        ///success
    }else{
        ///false
    }
});
```

Step 13: Turn on/off video

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
    }
});
```

Step 14: Share the screen (require android API >= 21)

  1. 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
        }
    });
  2. 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
        ],
      ),
    );
    ...
  3. 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;
        });
    }

Sample

You can view a full version of this sample app on GitHub: https://github.com/stringeecom/stringee_flutter_plugin/tree/master/example