Suggestions

close search

Getting started with Stringee Video Conference API using Flutter Plugin

Step 1: Prepare

  1. Before using Stringee Video Conference 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

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:^1.3.4

Check out plugin's documentation for more information.

Step 3: Setup

Android

Stringee Plugin 1.3.4 uses Stringee Android SDK 2.1.18 and requires JDK 17 and Android API 21 or later. Set compileSdk to 36 and minSdk to 21 or later in android/app/build.gradle.

  1. Permissions The Stringee Android SDK requires some permissions from your AndroidManifest
    • Open up android/app/src/main/AndroidManifest.xml
    • Add the following lines:
      <!-- 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" />
      <!-- Audio and camera access -->
      <uses-permission android:name="android.permission.RECORD_AUDIO" />
      <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
      <uses-permission android:name="android.permission.CAMERA" />
      <!-- Bluetooth headset support -->
      <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
      <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
      <uses-feature android:name="android.hardware.camera" android:required="false" />

      Request RECORD_AUDIO, CAMERA, and, on Android 12 or later, BLUETOOTH_CONNECT at runtime before using the corresponding feature.

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

iOS

Stringee Plugin 1.3.4 requires iOS 15.0 or later and Stringee iOS SDK 2.2.0, which is not published to the CocoaPods trunk. In your ios/Podfile, set platform :ios, '15.0' and add the Stringee pod inside the Runner target:

platform :ios, '15.0'
...
target 'Runner' do
  use_frameworks!
  pod 'Stringee', :podspec => 'https://raw.githubusercontent.com/stringeecom/Stringee-iOS-SDK/2.2.0/Stringee.podspec'
  ...
end
  1. From the ios directory run following command:

    pod install --repo-update
  2. After running CocoaPods, open the generated .xcworkspace file.

  3. Add the following keys to ios/Runner/Info.plist before the final </dict> element:

    <key>NSCameraUsageDescription</key>
    <string>$(PRODUCT_NAME) uses Camera</string>
    <key>NSMicrophoneUsageDescription</key>
    <string>$(PRODUCT_NAME) uses Microphone</string>

Step 4: Connect to Stringee Server

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.

  1. Initialize StringeeClient:
    import 'dart:async';
    import 'package:stringee_plugin/stringee_plugin.dart';
    ...
    final StringeeClient _client = StringeeClient();
    StreamSubscription? _clientSubscription;
    StreamSubscription? _roomSubscription;
  2. Register the client's events in your State

    class _MyHomePageState extends State<MyHomePage> {
    ...
        @override
        void initState() {
            super.initState();
            ...
            /// 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.tokenWillExpire:
                        handleTokenWillExpireEvent(map['body']);
                        break;
                    case StringeeClientEvents.didReceiveCustomMessage:
                        handleDidReceiveCustomMessageEvent(map['body']);
                        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 or rejected: get a new token and call _client.connect(newToken)
        void handleRequestAccessTokenEvent() {}
    
        /// Invoked about 60 seconds before your token expires (plugin 1.3.4 or later)
        void handleTokenWillExpireEvent(Map<dynamic, dynamic> body) {
          // body['exp']: expiry time in epoch seconds, body['expireInSeconds']: seconds left
          // Get a new token from your server, then: _client.updateToken(newToken);
        }
    
        /// Invoked when get Custom message
        void handleDidReceiveCustomMessageEvent(Map<dynamic, dynamic> map) {}
        ...
    }
  3. Connect

        @override
            void initState() {
                super.initState();
                ...
                String token = 'PUT YOUR TOKEN HERE';
                _client.connect(token);
                ...
            }

    When the access token is about to expire (about 60 seconds before), the client emits StringeeClientEvents.tokenWillExpire. Get a new token for the same user from your server and call updateToken(newToken) to renew it without reconnecting. If the token is not renewed, the server closes the connection when it expires and the client emits StringeeClientEvents.requestAccessToken; call connect(newToken) on the same client to resume. See StringeeClient.updateToken for the result codes.

Step 5: Connect room

After the client connects to Stringee server, follow these steps to connect to a room:

  1. Initialize StringeeVideo

    import 'package:stringee_plugin/stringee_plugin.dart';
    ...
    late StringeeVideo _video;
    ...
    _video = StringeeVideo(_client);

    When creating StringeeVideo, you must pass StringeeClient, which you used to connect in step 4.

  2. Create and join room

    To create a room, use the Room Management REST API.

    To join the room, you need a room credential named room_token. In production, the room token should be generated by your server as described in Room token.

    StringeeVideoRoom? _room;
    StringeeVideoTrack? _localTrack;
    StringeeVideoTrackInfo? _remoteTrackInfo;
    StringeeVideoTrack? _remoteTrack;
    ...
    _video.joinRoom('YOUR_ROOM_TOKEN').then((value) {
        if (value['status']) {
            _room = value['body']['room'];
            registerRoomEvents(_room!);
            List<StringeeVideoTrackInfo> trackInfos = value['body']['videoTrackInfos'];
            List<StringeeRoomUser> users = value['body']['users'];
        }
    });

    In which:

    • trackInfos: list StringeeVideoTrackInfo available in room
    • users: list StringeeRoomUser available in room
  3. Register the room's events

    /// Register events only after joinRoom returns a StringeeVideoRoom object.
    void registerRoomEvents(StringeeVideoRoom room) {
        _roomSubscription = room.eventStreamController.stream.listen((event) {
            Map<dynamic, dynamic> map = event;
            switch (map['eventType']) {
            case StringeeRoomEvents.didJoinRoom:
                handleJoinRoomEvent(map['body']);
                break;
            case StringeeRoomEvents.didLeaveRoom:
                handleLeaveRoomEvent(map['body']);
                break;
            case StringeeRoomEvents.didAddVideoTrack:
                handleAddVideoTrackEvent(map['body']);
                break;
            case StringeeRoomEvents.didRemoveVideoTrack:
                handleRemoveVideoTrackEvent(map['body']);
                break;
            case StringeeRoomEvents.didReceiveRoomMessage:
                handleReceiveRoomMessageEvent(map['body']);
                break;
            case StringeeRoomEvents.trackReadyToPlay:
                handleTrackReadyToPlayEvent(map['body']);
                break;
            default:
                break;
            }
        });
    }
    ...
    
    /// Invoked when the another user join room
    void handleJoinRoomEvent(StringeeRoomUser joinUser) {}
    
    /// Invoked when the another user leave room
    void handleLeaveRoomEvent(StringeeRoomUser leaveUser) {}
    
    /// Invoked when the add track to room
    void handleAddVideoTrackEvent(StringeeVideoTrackInfo addTrackInfo) {
        _remoteTrackInfo = addTrackInfo;
    }
    
    /// Invoked when the remove track from room
    void handleRemoveVideoTrackEvent(StringeeVideoTrackInfo removeTrackInfo) {
        _remoteTrackInfo = null;
        _remoteTrack = null;
    }
    
    /// Invoked when receive message in room
    void handleReceiveRoomMessageEvent(Map<dynamic, dynamic> bodyMap) {}
    
    /// Invoked when track is ready to display video
    void handleTrackReadyToPlayEvent(StringeeVideoTrack track) {
        _remoteTrack = track;
    }

    Step 6: Create local video track and publish to room

/// Create video track options
StringeeVideoTrackOption options = StringeeVideoTrackOption(
    audio: true,
    video: true,
    screen: false,
);
/// Create local video track
_video.createLocalVideoTrack(options).then((value) {
    if (value['status']) {
        /// Save the local track, then publish it to the room.
        _localTrack = value['body'];
        _room!.publish(_localTrack!).then((value) {
            if (value['status']) {
                // Success
            }
        });
    }
});

Step 7: Subscribe to another video track

After receiving another video track information, you need to subscribe the track to display the video

final trackInfo = _remoteTrackInfo;
if (trackInfo == null) {
    return;
}
StringeeVideoTrackOption options = StringeeVideoTrackOption(
    audio: trackInfo.audioEnable,
    video: trackInfo.videoEnable,
    screen: trackInfo.isScreenCapture,
);

_room!.subscribe(trackInfo, options).then((value) {
    if (value['status']) {
        // Success
    }
});

Step 8: Display track

After subscribing another video track successfully or publishing your track successfully, you can display the video

final track = _remoteTrack;
if (track == null) {
    return const SizedBox.shrink();
}
StringeeVideoView videoView = track.attach(
    isMirror: track.isLocal,
    height: 200.0,
    width: 150.0,
    scalingType: ScalingType.fit,
);
...
return new Scaffold(
    backgroundColor: Colors.black,
    body: new Stack(
        children: <Widget>[
            ...
            videoView,
        ],
    ),
);

Step 9: Unpublish video track

If you want to stop publishing your track in the room, remove it by unpublishing it:

final localTrack = _localTrack;
if (localTrack == null) {
    return;
}
_room!.unpublish(localTrack).then((result) {
    if (result['status']) {
        // Success
    }
});

Step 10: Unsubscribe video track

If you don't want to receive another track's audio or video in the room, you can unsubscribe this track:

final trackInfo = _remoteTrackInfo;
if (trackInfo == null) {
    return;
}
_room!.unsubscribe(trackInfo).then((value) {
    if (value['status']) {
        // Success
    }
});

Step 11: Leave room

To leave the room, call leave(...) on the StringeeVideoRoom object:

_room!.leave(allClient: false).then((result) {
    if (result['status']) {
        // Success
    }
});

Step 12: Mute

Mute the local sound:

bool mute = true; // true: mute, false: unmute
final localTrack = _localTrack;
if (localTrack == null) {
    return;
}
localTrack.mute(mute).then((result) {
    if (result['status']) {
        ///success
    }
});

Step 13: Switch camera

Switch the local camera:

final localTrack = _localTrack;
if (localTrack == null) {
    return;
}
localTrack.switchCamera().then((result) {
    if (result['status']) {
        ///success
    }
});

Step 14: Turn on/off video

Turn on/off video:

bool enableVideo = true; // true: turn on, false: turn off
final localTrack = _localTrack;
if (localTrack == null) {
    return;
}
localTrack.enableVideo(enableVideo).then((result) {
    if (result['status']) {
        ///success
    }
});

Sample

You can view a completed version of this sample app on GitHub: Stringee Flutter plugin example