Introduction
Video calling has become a staple form of communication, but it often struggles with scalability when user numbers climb into the millions. Interactive live streaming offers a robust solution, providing a way to engage with a vast audience in near real-time.
Interactive live streaming enhances user experience with features like real-time comments, reactions (Emojis ?), and hand-raising, making it indispensable across industries such as gaming, social platforms, and education.
Why Choose VideoSDK for Your Flutter Live Streaming App?
VideoSDK integrates seamlessly with Flutter, allowing developers to add real-time communication capabilities to their apps while scaling effortlessly to accommodate millions of users. It combines the intimacy of video calls with the broad reach of live streaming.
Key Features to Incorporate in Your Flutter Live Streaming App
Flutter is Google’s UI toolkit for building natively compiled applications for mobile, web, and desktop from a single codebase. It is particularly well-suited for developing high-performance apps. VideoSDK’s Flutter package comes loaded with over 15 features that are essential for a top-notch live streaming experience.
Step-by-Step Guide to Building Your App
Pre-requisites:
- Basic understanding of Flutter framework.
- Video SDK Developer Account (Not having one? Follow VideoSDK Dashboard).
- Video SDK Flutter Package.
So you are now ready to create a fresh flutter project
(a) Create and Configure Your Flutter Live Streaming Project
To create a new Flutter project, run the following command at your chosen location
flutter create videosdk_flutter_ils_app
Inside the terminal after navigating to the project directory, run the command.
flutter pub add videosdk
videosdk
package will now be added to the pubspec.yaml.
You'll need to add a few other packages
- HTTP -
http: ^0.13.5
- flutter_vlc_player -
flutter_vlc_player: ^7.2.0
(b) ⚙️ Configure Flutter Live Streaming Project
For Flutter Android
- You will need to update the
/android/app/src/main/AndroidManifest.xml
for the permissions you will be using to implement the audio and video features.
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.INTERNET"/>
- App level
build.gradle
file at/android/app/build.gradle
needs to be updated. You will need to increaseminSdkVersion
ofdefaultConfig
up to23
For Flutter iOS
Info.plist
file at/ios/Runner/Info.plist
needs to be updated to add permissions for iOS.
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) Camera Usage!</string>
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) Microphone Usage!</string>
- You will need to uncomment the following line to define a global platform for your project in
/ios/Podfile
# platform :ios, '11.0'
Project Structure for Flutter Live Streaming App
STEP 1: Get Started Flutter Live Streaming with api_call.dart
Before jumping to anything else, you will have to write a function to generate a unique meeting ID. To generate a meeting ID, you will need an Authentication Token. You can get an Authentication Token from VideoSDK Dashboard (For Development) or VideoSDK Authentication Server (For Production).
import 'dart:convert';
import 'package:http/http.dart' as http;
//Auth token we will use to generate a meeting and connect to it
String token = "<< Authentication Token >>";
// API call to create meeting
Future<String> createMeeting() async {
final http.Response httpResponse = await http.post(
Uri.parse("https://api.videosdk.live/v2/rooms"),
headers: {'Authorization': token},
);
//Destructuring the roomId from the response
return json.decode(httpResponse.body)['roomId'];
}
STEP 2: Develop the Join Screen Widget for Flutter Live Streaming App
Let's create join_screen.dart
file in lib
directory and create a JoinScreen StatelessWidget.
The JoinScreen will consist of:
- Create Meeting Button - This button will create a new meeting for the speaker and join it with the mode
CONFERENCE
. - Meeting ID TextField - This text field will contain the meeting ID, you want to join.
- Join Meeting as Host Button - This button will join the meeting in
CONFERENCE
mode for the speakers, which you have provided. - Join Meeting as Viewer Button - This button will join the meeting in
VIEWER
mode for the viewers, which you have provided.
- Update the app's home to JoinScreen in the
main.dart
file.
Output
STEP 3: Flutter Live Streaming Screen Widget
Let's create ils_screen.dart
file and create an ILSScreen StatefulWidget
which will take the meetingId
, token
and mode
of the participant in the constructor.
You will create a new room using the createRoom
method and render the ILSSpeakerView
or ILSViewerView
based on the past participant mode
.
STEP 4: SpeakerView Widget for Flutter Live Streaming App
Let's create the ILSSpeakerView
which will show the MeetingControls
and the ParticipantTile
in grid.
- Let us start off by creating the
StatefulWidget
namedParticipantTile
inparticipant_tile.dart
file. It will consist ofRTCVideoView
which will show the participant's video stream.
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> {
Stream? videoStream;
@override
void initState() {
// initialze video stream for the participant if they are present
widget.participant.streams.forEach((key, Stream stream) {
setState(() {
if (stream.kind == 'video') {
videoStream = stream;
}
});
});
_initStreamListeners();
super.initState();
}
//Event listener for the video stream of the participant
_initStreamListeners() {
widget.participant.on(Events.streamEnabled, (Stream stream) {
if (stream.kind == 'video') {
setState(() => videoStream = stream);
}
});
widget.participant.on(Events.streamDisabled, (Stream stream) {
if (stream.kind == 'video') {
setState(() => videoStream = null);
}
});
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
//Rending the Video Stream of the participant
child: videoStream != null
? RTCVideoView(
videoStream?.renderer as RTCVideoRenderer,
objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover,
)
: Container(
color: Colors.grey.shade800,
child: const Center(
child: Icon(
Icons.person,
size: 100,
),
),
),
);
}
}
- Next, let us add the
StatelessWidget
namedMeetingControls
in themeeting_controls.dart
file. This widget will accept the callback handlers for all the meeting control buttons and the current HLS state of the meeting.
import 'package:flutter/material.dart';
class MeetingControls extends StatelessWidget {
final String hlsState;
final void Function() onToggleMicButtonPressed;
final void Function() onToggleCameraButtonPressed;
final void Function() onHLSButtonPressed;
const MeetingControls(
{super.key,
required this.hlsState,
required this.onToggleMicButtonPressed,
required this.onToggleCameraButtonPressed,
required this.onHLSButtonPressed});
@override
Widget build(BuildContext context) {
return Wrap(
children: [
ElevatedButton(
onPressed: onToggleMicButtonPressed,
child: const Text('Toggle Mic')),
const SizedBox(width: 10),
ElevatedButton(
onPressed: onToggleCameraButtonPressed,
child: const Text('Toggle WebCam')),
const SizedBox(width: 10),
ElevatedButton(
onPressed: onHLSButtonPressed,
child: Text(hlsState == "HLS_STOPPED"
? 'Start HLS'
: hlsState == "HLS_STARTING"
? "Starting HLS"
: hlsState == "HLS_STARTED" || hlsState == "HLS_PLAYABLE"
? "Stop HLS"
: hlsState == "HLS_STOPPING"
? "Stopping HLS"
: "Start HLS")),
],
);
}
}
- Let's finally put all these widgets in the
StatefulWidget
namedILSSpeakerView
inils_speaker_view.dart
file. This widget will listen to thehlsStateChanged
,participantJoined
andparticipantLeft
events. It will render the participants and the meeting controls like leave, toggle mic and webcam as well as start and stop HLS.
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
import 'package:videosdk_flutter_quickstart/meeting_controls.dart';
import 'package:videosdk_flutter_quickstart/participant_tile.dart';
class ILSSpeakerView extends StatefulWidget {
final Room room;
const ILSSpeakerView({super.key, required this.room});
@override
State<ILSSpeakerView> createState() => _ILSSpeakerViewState();
}
class _ILSSpeakerViewState extends State<ILSSpeakerView> {
var micEnabled = true;
var camEnabled = true;
String hlsState = "HLS_STOPPED";
Map<String, Participant> participants = {};
@override
void initState() {
super.initState();
//Setting up the event listeners and initializing the participants and hls state
setMeetingEventListener();
participants.putIfAbsent(
widget.room.localParticipant.id, () => widget.room.localParticipant);
//filtering the CONFERENCE participants to be shown in the grid
widget.room.participants.values.forEach((participant) {
if (participant.mode == Mode.CONFERENCE) {
participants.putIfAbsent(participant.id, () => participant);
}
});
hlsState = widget.room.hlsState;
}
@override
void setState(VoidCallback fn) {
if (mounted) {
super.setState(fn);
}
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Row(
children: [
Expanded(
child: Text(
widget.room.id,
style: const TextStyle(color: Colors.white),
)),
ElevatedButton(
onPressed: () => {
Clipboard.setData(ClipboardData(text: widget.room.id)),
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text("Meeting Id Coppied"),
))
},
child: const Text("Copy Meeting Id"),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () => {widget.room.leave()},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
),
child: const Text("Leave"),
)
],
),
const SizedBox(
height: 20,
),
//Showing the current HLS state
Text(
"Current HLS State: ${hlsState == "HLS_STARTED" || hlsState == "HLS_PLAYABLE" ? "Livestream is Started" : hlsState == "HLS_STARTING" ? "Livestream is starting" : hlsState == "HLS_STOPPING" ? "Livestream is stopping" : "Livestream is stopped"}",
style: const TextStyle(color: Colors.white),
),
//render all participants in the room
Expanded(
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 1,
),
itemBuilder: (context, index) {
return ParticipantTile(
participant: participants.values.elementAt(index));
},
itemCount: participants.length,
),
),
//Rending the meeting Controls
MeetingControls(
hlsState: hlsState,
onToggleMicButtonPressed: () {
micEnabled ? widget.room.muteMic() : widget.room.unmuteMic();
micEnabled = !micEnabled;
},
onToggleCameraButtonPressed: () {
camEnabled ? widget.room.disableCam() : widget.room.enableCam();
camEnabled = !camEnabled;
},
//HLS handler which will start and stop HLS
onHLSButtonPressed: () => {
if (hlsState == "HLS_STOPPED")
{
widget.room.startHls(config: {
"layout": {
"type": "SPOTLIGHT",
"priority": "PIN",
"GRID": 20,
},
"mode": "video-and-audio",
"theme": "DARK",
"quality": "high",
"orientation": "portrait",
})
}
else if (hlsState == "HLS_STARTED" || hlsState == "HLS_PLAYABLE")
{widget.room.stopHls()}
},
),
],
),
);
}
// listening to room events for participants join, left and hls state changes
void setMeetingEventListener() {
widget.room.on(
Events.participantJoined,
(Participant participant) {
//Adding only Conference participant to show in grid
if (participant.mode == Mode.CONFERENCE) {
setState(
() => participants.putIfAbsent(participant.id, () => participant),
);
}
},
);
widget.room.on(Events.participantLeft, (String participantId) {
if (participants.containsKey(participantId)) {
setState(
() => participants.remove(participantId),
);
}
});
widget.room.on(
Events.hlsStateChanged,
(Map<String, dynamic> data) {
setState(
() => hlsState = data['status'],
);
},
);
}
}
Output
STEP 5: Implementing HLS for Flutter Live Streaming app
Let's create StatefulWidget
named ILSViewerView
in the ils_viewer_view.dart
file.
ILSViewerView will listen to the hlsStateChanged
event and if the HLS is started, it will show the live stream using the flutter_vlc_player
library.
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
import './livestream_player.dart';
class ILSViewerView extends StatefulWidget {
final Room room;
const ILSViewerView({super.key, required this.room});
@override
State<ILSViewerView> createState() => _ILSViewerViewState();
}
class _ILSViewerViewState extends State<ILSViewerView> {
String hlsState = "HLS_STOPPED";
String? downstreamUrl;
@override
void initState() {
super.initState();
//initialize the room's hls state and hls's downstreamUrl
hlsState = widget.room.hlsState;
downstreamUrl = widget.room.hlsDownstreamUrl;
//add the hlsStateChanged event listener
setMeetingEventListener();
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: Text(widget.room.id,
style: const TextStyle(color: Colors.white))),
ElevatedButton(
onPressed: () =>
{Clipboard.setData(ClipboardData(text: widget.room.id))},
child: const Text("Copy Meeting Id"),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () => {widget.room.leave()},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
),
child: const Text("Leave"),
)
],
),
),
//Show the livestream player if the downstreamUrl is present
downstreamUrl != null
? LivestreamPlayer(downstreamUrl: downstreamUrl!)
: const Text("Host has not started the HLS",
style: TextStyle(color: Colors.white)),
],
),
);
}
void setMeetingEventListener() {
// listening to hlsStateChanged events and updating the hlsState and downstremUrl
widget.room.on(
Events.hlsStateChanged,
(Map<String, dynamic> data) {
String status = data['status'];
if (mounted) {
setState(() {
hlsState = status;
if (status == "HLS_PLAYABLE" || status == "HLS_STOPPED") {
downstreamUrl = data['downstreamUrl'];
}
});
}
},
);
}
}
- Let us use the
flutter_vlc_player
library to play the HLS stream on the Viewer side. For these, you will create aStatefulWidget
namedLivestreamPlayer
in thelivestream_player.dart
file.
import 'package:flutter/material.dart';
import 'package:flutter_vlc_player/flutter_vlc_player.dart';
class LivestreamPlayer extends StatefulWidget {
final String downstreamUrl;
const LivestreamPlayer({
Key? key,
required this.downstreamUrl,
}) : super(key: key);
LivestreamPlayerState createState() => LivestreamPlayerState();
}
class LivestreamPlayerState extends State<LivestreamPlayer>
with AutomaticKeepAliveClientMixin {
late VlcPlayerController _controller;
bool get wantKeepAlive => true;
void initState() {
super.initState();
//initializing the player
_controller = VlcPlayerController.network(widget.downstreamUrl,
options: VlcPlayerOptions());
}
void dispose() async {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
super.build(context);
//rendering the VlcPlayer
return VlcPlayer(
controller: _controller,
aspectRatio: 9 / 16,
placeholder: const Center(child: CircularProgressIndicator()),
);
}
}
Output
Final Steps: Testing and Launching Your App
The app is all set to test. Make sure to update the token
in api_call.dart
Your app should look like this.
You can check out the Complete Code HERE.
Schedule a Demo with Our Live Video Expert!
Discover how VideoSDK can help you build a cutting-edge real-time video app.
Conclusion: Enhancing Your App with Additional Functionalities
With this, we successfully built the Flutter live streaming app using the video SDK in Flutter. If you wish to add functionalities like chat messaging, and screen sharing, you can always check out our Documentation. If you face any difficulty with the implementation, you can connect with us on our DISCORD Community.
More Flutter Resources
- Build a Flutter Video Calling App with Video SDK
- Video SDK Flutter SDK Integration Guide
- Flutter SDK QuickStart
- Flutter SDK Github Example
- Build a Flutter Video Calling App with Video SDK
- Video Call Flutter app with Video SDK (Android & IOS)
- Official Video SDK flutter plugin: (feel free to give it a star⭐)