Skip to main content
Version: 1.1.x

Display Video - Flutter

In this guide, we will take a look at how to render the participant's audio and video on the screen.

Rendering Participant

The two steps are involve to achieve this process.

  1. Get Mic and Webcam Status
  2. Rendering Video

1. Get Mic and Webcam Status

We must determine whether the participant's audio or video is on or off before rendering him or her. Hence, if the webcam is not turned on, we will begin by rendering the participant's frames with their name in them; otherwise, we will render the video.

Step 1: Let's get every participants from Room by listenting to the participantJoined as well as participantLeft event.

meeting_screen.dart
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
import './participant_tile.dart';

class MeetingScreen extends StatefulWidget {
final String meetingId;
final String token;

const MeetingScreen(
{super.key, required this.meetingId, required this.token});

@override
State<MeetingScreen> createState() => _MeetingScreenState();
}

class _MeetingScreenState extends State<MeetingScreen> {
late Room _room;
var micEnabled = true;
var camEnabled = true;
String? _presenterId;

Map<String, Participant> participants = {};

@override
void initState() {
// create room
_room = VideoSDK.createRoom(
roomId: widget.meetingId,
token: widget.token,
displayName: "John Doe",
micEnabled: micEnabled,
camEnabled: camEnabled,
defaultCameraIndex:
1, // Index of MediaDevices will be used to set default camera
);

setMeetingEventListener();

// Join room
_room.join();

super.initState();
}

// listening to meeting events
void setMeetingEventListener() {
_room.on(Events.roomJoined, () {
setState(() {
participants.putIfAbsent(
_room.localParticipant.id, () => _room.localParticipant);
});
});

_room.on(
Events.participantJoined,
(Participant participant) {
setState(
() => participants.putIfAbsent(participant.id, () => participant),
);
},
);

_room.on(Events.participantLeft, (String participantId) {
if (participants.containsKey(participantId)) {
setState(
() => participants.remove(participantId),
);
}
});

_room.on(Events.roomLeft, () {
participants.clear();
Navigator.popUntil(context, ModalRoute.withName('/'));
});
}

// onbackButton pressed leave the room
Future<bool> _onWillPop() async {
_room.leave();
return true;
}

@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _onWillPop(),
child: Scaffold(
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [],
),
),
),
);
}
}

Step 2: WIth participant list ready, let render a list of participants with create a straightforward box with each person's name.

meeting_screen.dart
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
import './participant_tile.dart';

class MeetingScreen extends StatefulWidget {
...
}

class _MeetingScreenState extends State<MeetingScreen> {
late Room _room;
var micEnabled = true;
var camEnabled = true;

Map<String, Participant> participants = {};

@override
void initState() {
// create room
...
}

// listening to meeting events
void setMeetingEventListener() {
...
}

// onbackButton pressed leave the room
Future<bool> _onWillPop() async {
_room.leave();
return true;
}

@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _onWillPop(),
child: Scaffold(
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
//render all participant
Expanded(
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 1,
),
itemBuilder: (context, index) {
return ParticipantTile(
participant: participants.values.elementAt(index));
},
itemCount: participants.length,
),
),
],
),
),
),
);
}
}
participant_tile.dart
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';

class ParticipantTile extends StatefulWidget {
final Participant participant;
const ParticipantTile({super.key, required this.participant});

@override
State<ParticipantTile> createState() => _ParticipantTileState();
}

class _ParticipantTileState extends State<ParticipantTile> {

@override
void initState() {
super.initState();
}

@override
Widget build(BuildContext context) {
return Container(
color: Colors.black54,
child: Stack(
children: [
Positioned(
top: 10,
left: 10,
child: Text(
"Name: ${widget.participant.displayName}",
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.white,
fontSize: 16.0,
),
),
),
],
),
);
}
}

Step 3: To display the status of each participant's microphone and webcam in the list, we will create the local state of audio and video streams of the participant fromt he streams property and also add the event listener to get notified on status change.

Here's a code code snippet of rendering mic and webcam status:

import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';

class ParticipantTile extends StatefulWidget {
final Participant participant;
const ParticipantTile({super.key, required this.participant});

@override
State<ParticipantTile> createState() => _ParticipantTileState();
}

class _ParticipantTileState extends State<ParticipantTile> {
//declare stream state
Stream? videoStream;
Stream? audioStream;

@override
void initState() {
// initial video stream for the participant
widget.participant.streams.forEach((key, Stream stream) {
setState(() {
if (stream.kind == 'video') {
videoStream = stream;
}
});
});
_initStreamListeners();
super.initState();
}

_initStreamListeners() {
widget.participant.on(Events.streamEnabled, (Stream stream) {
if (stream.kind == 'video') {
setState(() => videoStream = stream);
} else if (stream.kind == 'audio') {
setState(() => audioStream = stream);
}
});

widget.participant.on(Events.streamDisabled, (Stream stream) {
if (stream.kind == 'video') {
setState(() => videoStream = null);
} else if (stream.kind == 'audio') {
setState(() => audioStream = null);
}
});
}

@override
Widget build(BuildContext context) {
return Container(
color: Colors.black54,
child: Stack(
children: [
Positioned(
top: 10,
left: 10,
//Update the text
child: Text(
"Name : ${widget.participant.displayName}" +
"\nMic :${audioStream == null ? "Off" : "On"}" +
"\nCam :${audioStream == null ? "Off" : "On"}",
style: const TextStyle(
color: Colors.white,
fontSize: 16.0,
),
),
),
],
),
);
}
}

2. Rendering Video

The status of the webcam and mic is now displayed. If the webcam is turned on, we will use the videoStream we declared earlier and display the participant's video.

import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';

class ParticipantTile extends StatefulWidget {
final Participant participant;
...
}

class _ParticipantTileState extends State<ParticipantTile> {
Stream? videoStream;
Stream? audioStream;

@override
void initState() {
...
}

_initStreamListeners() {
...
}

@override
Widget build(BuildContext context) {
return Container(
color: Colors.black54,
child: Stack(
children: [
if (videoStream != null)
RTCVideoView(
videoStream?.renderer as RTCVideoRenderer,
objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover,
),
Positioned(
top: 10,
left: 10,
child: Text(
"Name : ${widget.participant.displayName}" +
"\nWebcam :${videoStream == null ? "Off" : "On"}",
" Mic :${audioStream == null ? "Off" : "On"}" +
style: const TextStyle(
color: Colors.black,
fontSize: 16.0,
),
),
),
],
),
);
}
}
caution

Unlike before, you don't need to render audio separately because RTCView is a component that handles audio stream automatically.

2.1 Mirror Local Video View

If you wish to show the mirror view of the local participant, you can pass boolean value to mirror property of RTCView.

import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';

class ParticipantTile extends StatefulWidget {
final Participant participant;
...
}

class _ParticipantTileState extends State<ParticipantTile> {
Stream? videoStream;
Stream? audioStream;

@override
void initState() {
...
}

_initStreamListeners() {
...
}

@override
Widget build(BuildContext context) {
return Container(
color: Colors.black54,
child: Stack(
children: [
if (videoStream != null)
RTCVideoView(
videoStream?.renderer as RTCVideoRenderer,
objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover,
mirror: true,
),
],
),
);
}
}
Sample of mirror view video

mirror view

API Reference

The API references for all the methods and events utilised in this guide are provided below.

Got a Question? Ask us on discord