Twilio Flutter Video SDK
A Flutter plugin for integrating Twilio Programmable Video SDK, providing video conferencing capabilities with features like mute/unmute, camera switching, video toggle, and meeting management.
Features
- ✅ Join/Leave video rooms
- ✅ Mute/Unmute audio
- ✅ Toggle video on/off
- ✅ Switch between front and back camera
- ✅ End meeting/disconnect
- ✅ Real-time participant events
- ✅ Connection status monitoring
- ✅ Error handling
- ✅ Native video rendering via PlatformView
- ✅ Multiple remote participant support
- ✅ Video track enabled/disabled handling
- ✅ Automatic handling of existing participants when joining rooms
Platform Support
- ✅ Android (API 24+, Twilio Video SDK 7.9.1)
- ✅ iOS (13.0+, Twilio Video SDK 5.3.0)
Installation
Add this to your package's pubspec.yaml file:
dependencies:
twilio_flutter_video_sdk:
path: ../twilio_flutter_video_sdk
Or if published:
dependencies:
twilio_flutter_video_sdk: ^1.0.0
Android Setup
The plugin requires the following permissions in your AndroidManifest.xml:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.BLUETOOTH" />
The Twilio Video SDK dependency (version 7.9.1) is automatically included via Maven Central.
iOS Setup
Add the following permissions to your Info.plist:
<key>NSCameraUsageDescription</key>
<string>This app needs access to the camera for video calls</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app needs access to the microphone for video calls</string>
<key>io.flutter.embedded_views_preview</key>
<true/>
The Twilio Video SDK dependency (version 5.3.0) is automatically included via CocoaPods.
Run pod install in the ios/ directory after adding the plugin.
Usage
Quick Start with VideoRoomScreen (Recommended)
The easiest way to use the plugin is with the built-in VideoRoomScreen widget:
import 'package:twilio_flutter_video_sdk/twilio_flutter_video_sdk.dart';
import 'package:permission_handler/permission_handler.dart';
// Request permissions first
await [Permission.camera, Permission.microphone].request();
// Navigate to video room screen
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => VideoRoomScreen(
options: VideoRoomScreenOptions(
accessToken: 'YOUR_ACCESS_TOKEN',
roomName: 'my-room',
enableAudio: true,
enableVideo: true,
enableFrontCamera: true,
onConnected: () {
print('Connected to room!');
},
onDisconnected: () {
print('Disconnected from room');
},
onConnectionFailure: (error) {
print('Connection failed: $error');
},
),
),
),
);
Custom VideoRoomScreen
You can customize the video room screen with your own widgets:
VideoRoomScreen(
options: VideoRoomScreenOptions(
accessToken: 'YOUR_ACCESS_TOKEN',
roomName: 'my-room',
// Custom local video widget
localVideoBuilder: (context) => Container(
height: 200,
child: TwilioVideoView(viewId: "0"),
),
// Custom remote video widget
remoteVideoBuilder: (context, participantSid) => Container(
height: 150,
child: TwilioVideoView(viewId: participantSid),
),
// Custom controls
controlsBuilder: (context, controller) => Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
icon: Icon(controller.isMuted ? Icons.mic_off : Icons.mic),
onPressed: controller.toggleMute,
),
IconButton(
icon: Icon(controller.isVideoEnabled ? Icons.videocam : Icons.videocam_off),
onPressed: controller.toggleVideo,
),
IconButton(
icon: Icon(Icons.cameraswitch),
onPressed: controller.switchCamera,
),
IconButton(
icon: Icon(Icons.call_end),
onPressed: controller.disconnect,
),
],
),
),
)
Basic Example (Manual Implementation)
import 'package:twilio_flutter_video_sdk/twilio_flutter_video_sdk.dart';
import 'package:permission_handler/permission_handler.dart';
// Request permissions
await [Permission.camera, Permission.microphone].request();
// Create video controller
final videoController = TwilioVideoController();
// Create a room instance
final room = videoController.createRoom();
// Listen to events
room.events.listen((event) {
print('Event: $event');
if (event == TwilioVideoEvent.connected) {
print('Connected to room');
}
});
room.errors.listen((error) {
print('Error: $error');
});
// Listen to participant events
room.participantEvents.listen((participant) {
print('Participant: ${participant.identity} connected');
});
// Listen to video track events
room.videoTrackEvents.listen((track) {
print('Video track: ${track.participantSid}, enabled: ${track.isEnabled}');
});
// Join a room
await room.joinRoom(
RoomOptions(
accessToken: 'YOUR_ACCESS_TOKEN',
roomName: 'my-room',
enableAudio: true,
enableVideo: true,
enableFrontCamera: true,
),
);
// Mute/Unmute
await room.toggleMute();
// Toggle video
await room.toggleVideo();
// Switch camera
await room.switchCamera();
// Disconnect
await room.disconnect();
Displaying Video Views
The plugin provides TwilioVideoView widget for rendering native video:
import 'package:twilio_flutter_video_sdk/twilio_flutter_video_sdk.dart';
// Local video view (viewId: 0)
TwilioVideoView(viewId: 0)
// Remote participant video view (viewId: participantSid)
TwilioVideoView(viewId: participantSid)
Complete Example with Video Views
class VideoRoomScreen extends StatefulWidget {
@override
_VideoRoomScreenState createState() => _VideoRoomScreenState();
}
class _VideoRoomScreenState extends State<VideoRoomScreen> {
TwilioVideoController? _controller;
TwilioVideoRoom? _room;
Set<String> _remoteParticipantSids = {};
bool _isConnected = false;
@override
void initState() {
super.initState();
_controller = TwilioVideoController();
_room = _controller!.createRoom();
// Listen to video track events
_room!.videoTrackEvents.listen((track) {
setState(() {
if (track.isEnabled && track.nativeViewReady) {
_remoteParticipantSids.add(track.participantSid);
} else {
_remoteParticipantSids.remove(track.participantSid);
}
});
});
// Listen to connection events
_room!.events.listen((event) {
if (event == TwilioVideoEvent.connected) {
setState(() => _isConnected = true);
} else if (event == TwilioVideoEvent.disconnected) {
setState(() {
_isConnected = false;
_remoteParticipantSids.clear();
});
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
// Local video
SizedBox(
height: 200,
child: TwilioVideoView(viewId: 0),
),
// Remote videos
Expanded(
child: _remoteParticipantSids.isEmpty
? Center(child: Text('Waiting for participants...'))
: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemCount: _remoteParticipantSids.length,
itemBuilder: (context, index) {
final participantSid = _remoteParticipantSids.elementAt(index);
return TwilioVideoView(viewId: participantSid);
},
),
),
],
),
);
}
@override
void dispose() {
_room?.disconnect();
_controller?.dispose();
super.dispose();
}
}
Complete Example
See the example/ directory for a complete working example with UI.
API Reference
TwilioVideoController
Main controller for managing video rooms.
final controller = TwilioVideoController();
final room = controller.createRoom();
controller.dispose(); // Clean up resources
TwilioVideoRoom
Represents a video room connection.
Methods
Future<void> joinRoom(RoomOptions options)- Join a video roomFuture<void> disconnect()- Disconnect from the roomFuture<void> setMuted(bool muted)- Mute or unmute audioFuture<void> toggleMute()- Toggle mute stateFuture<void> setVideoEnabled(bool enabled)- Enable or disable videoFuture<void> toggleVideo()- Toggle video on/offFuture<void> switchCamera()- Switch between front and back cameravoid dispose()- Dispose resources
Properties
bool isConnected- Whether currently connectedbool isMuted- Whether audio is mutedbool isVideoEnabled- Whether video is enabledbool isFrontCamera- Whether using front camera
Streams
Stream<TwilioVideoEvent> events- Video events streamStream<ParticipantInfo> participantEvents- Participant updatesStream<VideoTrackInfo> videoTrackEvents- Video track updatesStream<String> errors- Error messages
VideoRoomScreen
Ready-to-use video room screen widget with built-in UI and controls.
VideoRoomScreen({
required VideoRoomScreenOptions options,
VideoRoomScreenController? controller,
})
Features:
- Complete UI with local and remote video views
- Built-in controls (mute, video toggle, camera switch, disconnect)
- Automatic handling of video tracks and participants
- Customizable with builder functions
- Optional controller for programmatic control
VideoRoomScreenOptions
Configuration for VideoRoomScreen.
VideoRoomScreenOptions({
required String accessToken, // Twilio access token
required String roomName, // Room name to join
bool enableAudio = true, // Enable audio by default
bool enableVideo = true, // Enable video by default
bool enableFrontCamera = true, // Use front camera by default
bool showInputFields = false, // Show token/room input fields
String? appBarTitle, // Custom app bar title
VoidCallback? onConnected, // Called when connected
VoidCallback? onDisconnected, // Called when disconnected
Function(String)? onConnectionFailure, // Called on connection failure
Widget Function(BuildContext)? localVideoBuilder, // Custom local video widget
Widget Function(BuildContext, String)? remoteVideoBuilder, // Custom remote video widget
Widget Function(BuildContext, VideoRoomScreenController)? controlsBuilder, // Custom controls
})
VideoRoomScreenController
Controller for programmatically controlling VideoRoomScreen.
final controller = VideoRoomScreenController();
// Properties
controller.isConnected // Whether connected to room
controller.isMuted // Whether audio is muted
controller.isVideoEnabled // Whether video is enabled
controller.isFrontCamera // Whether using front camera
controller.room // Access to TwilioVideoRoom instance
// Methods
await controller.toggleMute(); // Toggle mute state
await controller.toggleVideo(); // Toggle video on/off
await controller.switchCamera(); // Switch camera
await controller.disconnect(); // Disconnect from room
TwilioVideoView
Widget for displaying native video views.
TwilioVideoView({
required String viewId, // "0" for local, participantSid for remote
})
Important Notes:
- Local video always uses
viewId: "0" - Remote participant videos use
viewId: participantSid - Only create
TwilioVideoViewwidgets whentrack.isEnabled && track.nativeViewReadyis true - The widget automatically handles platform-specific rendering (AndroidView on Android, UiKitView on iOS)
RoomOptions
Configuration for joining a room.
RoomOptions(
accessToken: 'YOUR_ACCESS_TOKEN', // Required
roomName: 'room-name', // Required
enableAudio: true, // Optional, default: true
enableVideo: true, // Optional, default: true
enableFrontCamera: true, // Optional, default: true
)
TwilioVideoEvent
Video events emitted by the room.
class TwilioVideoEvent {
final String event;
final Map<String, dynamic> data;
}
Event types:
connected- Successfully connected to roomdisconnected- Disconnected from roomconnectionFailure- Failed to connectparticipantConnected- Remote participant joinedparticipantDisconnected- Remote participant leftvideoTrackAdded- Video track added (local or remote)videoTrackRemoved- Video track removedaudioTrackAdded- Audio track addedaudioTrackRemoved- Audio track removed
VideoTrackInfo
Information about a video track.
class VideoTrackInfo {
final String trackSid;
final String participantSid;
final bool isEnabled;
final bool nativeViewReady; // Whether native VideoView is ready
}
Important: Only create TwilioVideoView widgets when both isEnabled and nativeViewReady are true. This ensures the native view exists before Flutter tries to access it.
ParticipantInfo
Information about a participant.
class ParticipantInfo {
final String sid;
final String identity;
final bool isAudioEnabled;
final bool isVideoEnabled;
final bool isConnected;
}
Video Track Lifecycle
The plugin handles video tracks as follows:
-
When a participant joins with video:
videoTrackAddedevent is sent withisEnabled: trueandnativeViewReady: true- Create
TwilioVideoViewwidget with the participant's SID
-
When a participant disables video:
videoTrackAddedevent is sent withisEnabled: false- Remove the participant from your UI or show a placeholder
- The native view is kept for reuse when video is enabled again
-
When a participant enables video again:
videoTrackAddedevent is sent withisEnabled: trueandnativeViewReady: true- Re-add the participant to your UI
-
When a participant leaves:
videoTrackRemovedevent is sent- Remove the participant from your UI
Handling Existing Participants
When you join a room where other participants are already present:
- The plugin automatically detects existing participants
- Video tracks for existing participants are handled automatically
videoTrackAddedevents are sent for all existing subscribed tracks- No special handling needed - just listen to
videoTrackEventsand create views as tracks are added
Getting Access Tokens
To use this plugin, you need Twilio access tokens. You can generate them using the Twilio Video Access Token Generator or by creating a backend service.
Important: Never embed your Twilio credentials in your mobile app. Always use a backend service to generate access tokens.
Example backend endpoint (Node.js):
const express = require('express');
const twilio = require('twilio');
const app = express();
const AccessToken = twilio.jwt.AccessToken;
const VideoGrant = AccessToken.VideoGrant;
app.post('/token', (req, res) => {
const { identity, roomName } = req.body;
const token = new AccessToken(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_API_KEY,
process.env.TWILIO_API_SECRET,
{ identity }
);
const videoGrant = new VideoGrant({ room: roomName });
token.addGrant(videoGrant);
res.json({ token: token.toJwt() });
});
Permissions
The plugin requires camera and microphone permissions. Make sure to request these permissions before joining a room:
import 'package:permission_handler/permission_handler.dart';
final statuses = await [
Permission.camera,
Permission.microphone,
].request();
if (statuses[Permission.camera]?.isGranted == true &&
statuses[Permission.microphone]?.isGranted == true) {
// Permissions granted, proceed to join room
} else {
// Handle permission denial
}
Troubleshooting
Android Issues
-
Build errors with Twilio SDK:
- Ensure
mavenCentral()is in yoursettings.gradle.ktsrepositories - Verify Twilio Video SDK version 7.9.1 is being used
- Clean and rebuild:
./gradlew clean build
- Ensure
-
Camera not working:
- Ensure minSdk is 24 or higher
- Check that all permissions are declared in AndroidManifest.xml
- Verify camera permission is granted before joining room
-
Video views not showing:
- Ensure you're only creating
TwilioVideoViewwhennativeViewReadyis true - Check that
viewIdmatches the participant SID for remote videos - Verify PlatformView is properly registered
- Ensure you're only creating
iOS Issues
-
Build errors:
- Ensure iOS deployment target is 13.0 or higher
- Run
pod installin the iOS directory after adding the plugin - Clean build folder in Xcode: Product → Clean Build Folder
-
Camera/Microphone not working:
- Check that camera and microphone permissions are in Info.plist
- Verify
io.flutter.embedded_views_previewis set totruein Info.plist - Request permissions before joining room
-
Video views not showing:
- Ensure you're only creating
TwilioVideoViewwhennativeViewReadyis true - Check that
viewIdmatches the participant SID for remote videos - Verify PlatformView factory is properly registered
- Ensure you're only creating
Common Issues
-
"Waiting for video..." message:
- This appears when Flutter tries to create a PlatformView before the native view is ready
- Solution: Only create
TwilioVideoViewwhentrack.nativeViewReadyistrue
-
Frozen video when participant turns off video:
- Fixed in version 1.0.0: The plugin now properly removes renderers when video is disabled
- Ensure you're handling
isEnabled: falseevents and hiding/showing placeholders
-
Remote videos not appearing when joining active room:
- Fixed in version 1.0.0: The plugin automatically detects and handles existing participants
- Just listen to
videoTrackEventsand create views as tracks are added
Version History
1.0.0+1
- Initial release
- Support for Android (SDK 7.9.1) and iOS (SDK 5.3.0)
- Native video rendering via PlatformView
- Multiple remote participant support
- Video track enabled/disabled handling
- Automatic handling of existing participants
- Proper cleanup when video is disabled (prevents frozen frames)
- VideoRoomScreen widget - Ready-to-use video room screen with built-in UI and controls
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
- Twilio Video SDK for the native SDKs
- Flutter team for the plugin architecture