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
Create a Project on Stringee Dashboard

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.
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.
android/app/src/main/AndroidManifest.xml<!-- 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.
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
From the ios directory run following command:
pod install --repo-update
After running CocoaPods, open the generated .xcworkspace file.
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>
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.
import 'dart:async';
import 'package:stringee_plugin/stringee_plugin.dart';
...
final StringeeClient _client = StringeeClient();
StreamSubscription? _clientSubscription;
StreamSubscription? _roomSubscription;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) {}
...
}
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.
After the client connects to Stringee server, follow these steps to connect to a room:
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.
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:
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;
}
/// 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
}
});
}
});
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
}
});
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,
],
),
);
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
}
});
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
}
});
To leave the room, call leave(...) on the StringeeVideoRoom object:
_room!.leave(allClient: false).then((result) {
if (result['status']) {
// Success
}
});
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
}
});
Switch the local camera:
final localTrack = _localTrack;
if (localTrack == null) {
return;
}
localTrack.switchCamera().then((result) {
if (result['status']) {
///success
}
});
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
}
});
You can view a completed version of this sample app on GitHub: Stringee Flutter plugin example