Quick Start for Http Live Streaming(HLS) in Flutter
VideoSDK enables you to embed the livestreaming experience into your Flutter application in minutes.
In this quickstart, we are going to explore interactive livestreaming feature of Video SDK. We will go through step by step guide of integrating interactive livestreaming with Flutter Video SDK.
For low-latency interactive live streaming (under 100ms), follow this documentation.
Prerequisites
Before proceeding, ensure that your development environment meets the following requirements:
- Video SDK Developer Account (Not having one, follow Video SDK Dashboard)
- The basic understanding of Flutter.
- Flutter Video SDK
- Have Flutter 3.41 or later installed on your device.
One should have a VideoSDK account to generate token. Visit VideoSDK dashboard to generate token
Getting Started with the Code!
Follow the steps to create the environment necessary to add video calls into your app. Also you can find the code sample for quickstart here (this example uses an older SDK version).
Important Changes Flutter SDK in Version v1.3.0
- The following modes have been deprecated:
CONFERENCEhas been replaced bySEND_AND_RECVVIEWERhas been replaced bySIGNALLING_ONLY
Please update your implementation to use the new modes.
⚠️ Compatibility Notice:
To ensure a seamless meeting experience, all participants must use the same SDK version.
Do not mix version v1.3.0 + with older versions, as it may cause significant conflicts.
Create a new Flutter project.
Create a new Flutter App using the below command.
$ flutter create videosdk_flutter_quickstart_hls
Install Video SDK
Install the VideoSDK using the below-mentioned flutter command. Make sure you are in your flutter app directory before you run this command.
$ flutter pub add videosdk:^5.0.0
Then pin permission_handler, which the SDK uses to ask for camera and microphone permission, to version 12. Version 13 brings in permission_handler_android 14, which builds only with Android Gradle Plugin 9, so an app created with Flutter 3.41 fails to build for Android.
$ flutter pub add permission_handler:^12.0.1
Run the following command to add the http library, which is used to make the network call that generates a roomId.
$ flutter pub add http
Run the following command to add the flutter_vlc_player library, which is used to play the HLS stream in your app.
$ flutter pub add flutter_vlc_player
The Flutter SDK runs on Android and iOS. Web, macOS and Windows are not supported.
Structure of the project
Your project structure should look like this.
root
├── android
├── ios
├── lib
├── api_call.dart
├── hls_screen.dart
├── hls_speaker_view.dart
├── hls_viewer_view.dart
├── join_screen.dart
├── livestream_player.dart
├── main.dart
├── room_controls.dart
├── participant_tile.dart
We are going to create following flutter widgets JoinScreen, HLSScreen, HLSSpeakerView, RoomControls, ParticipantTile, HLSViewerView, LivestreamPlayer for the quickstart app which will cover both speaker and viewer part of the app.
App Structure
App widget will contain JoinScreen and HLSScreen screens. HLSScreen will render the HLSSpeakerView or HLSViewerView based on the participants mode. HLSSpeakerView will have RoomControls and ParticipantTile

Configure Project
For Android
- Update the
/android/app/src/main/AndroidManifest.xmlfor the permissions we 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"/>
- Also you will need to set your build settings to Java 17, because recent versions of the Android Gradle Plugin and the SDK's Android dependencies require it. Just add this to your app-level
/android/app/build.gradle.kts.
android {
//...
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
-
If necessary, in the same
build.gradle.ktsyou will need to increaseminSdkofdefaultConfigup to24(currently default Flutter generator set it to24). -
The SDK ships its native libraries for
arm64-v8aandarmeabi-v7a, so it does not run on x86 and x86_64 emulators. Test on an ARM device or on an emulator with anarm64-v8asystem image. To build your app forarm64-v8aandarmeabi-v7aonly, add an ABI filter todefaultConfigin the samebuild.gradle.kts.
android {
defaultConfig {
//...
ndk {
abiFilters += listOf("arm64-v8a", "armeabi-v7a")
}
}
}
- Then add this line to
/android/gradle.properties. Without it, Flutter adds its own ABIs to the filter and yours has no effect.
disable-abi-filtering=true
- For release builds, keep the SDK's classes. Flutter shrinks every Android release build with R8, which removes or renames the
videosdk.orgclasses that the SDK loads by name. Add these rules to/android/app/proguard-rules.pro, and create the file if it does not exist. Keep the-dontwarnline, because the keep rule alone fails the build.
-keep class videosdk.org.** { *; }
-dontwarn videosdk.org.**
If your project still uses the Groovy DSL (android/app/build.gradle), the same property assignments work there too. The older compileSdkVersion and minSdkVersion method syntax is deprecated as of Android Gradle Plugin 9 and will warn during the build.
For iOS
- Add the following entries which allow your app to access the camera and microphone to your
/ios/Runner/Info.plistfile :
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) Camera Usage!</string>
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) Microphone Usage!</string>
- The SDK's iOS plugin ships as a Swift Package, so it needs Flutter 3.41 or later with Swift Package Manager turned on. Turn it on for your project by adding
config:under the existingflutter:section of your app'spubspec.yaml, or runflutter config --enable-swift-package-manageron every machine that builds your app.
flutter:
config:
enable-swift-package-manager: true
Step 1: Get started with api_call.dart
Before jumping to anything else, you will write a function to generate a unique room ID. You will require auth token, you can generate it using either by using videosdk-rtc-api-server-examples or generate it from the Video SDK Dashboard for development.
import 'dart:convert';
import 'package:http/http.dart' as http;
//Auth token we will use to generate a room and connect to it
String token = "<Generated-from-dashboard>";
// API call to create room
Future<String> createRoomId() 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: Creating the JoinScreen
Let's create join_screen.dart file in lib directory and create JoinScreen StatelessWidget.
The JoinScreen will consist of:
- Create Room Button - This button will create a new room for speaker and join it with mode
SEND_AND_RECV. - Room ID TextField - This text field will contain the room ID, you want to join.
- Join Room as Host Button - This button will join the room in
SEND_AND_RECVmode for the speakers, which you have provided. - Join Room as Viewer Button - This button will join the room in
SIGNALLING_ONLYmode for the viewers, which you have provided.
import 'package:flutter/material.dart';
import 'api_call.dart';
import 'hls_screen.dart';
import 'package:videosdk/videosdk.dart';
class JoinScreen extends StatelessWidget {
final _roomIdController = TextEditingController();
JoinScreen({super.key});
//Creates new Room Id and joins it in SEND_AND_RECV mode.
void onCreateButtonPressed(BuildContext context) async {
// call api to create room and navigate to HLSScreen with roomId,token and mode
await createRoomId().then((roomId) {
if (!context.mounted) return;
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => HLSScreen(
roomId: roomId,
token: token,
mode: Mode.SEND_AND_RECV,
),
),
);
});
}
//Join the provided room with given Mode and roomId
void onJoinButtonPressed(BuildContext context, Mode mode) {
// check room id is not null or invalid
// if room id is valid then navigate to HLSScreen with roomId, token and mode
String roomId = _roomIdController.text;
var re = RegExp("\\w{4}\\-\\w{4}\\-\\w{4}");
if (roomId.isNotEmpty && re.hasMatch(roomId)) {
_roomIdController.clear();
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => HLSScreen(
roomId: roomId,
token: token,
mode: mode,
),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text("Please enter valid room id"),
));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('VideoSDK HLS QuickStart'),
),
body: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
//Creating a new room
ElevatedButton(
onPressed: () => onCreateButtonPressed(context),
child: const Text('Create Room'),
),
const SizedBox(height: 40),
TextField(
style: const TextStyle(color: Colors.white),
decoration: const InputDecoration(
hintText: 'Enter Room Id',
border: OutlineInputBorder(),
hintStyle: TextStyle(color: Colors.white),
),
controller: _roomIdController,
),
//Joining the room as host
ElevatedButton(
onPressed: () => onJoinButtonPressed(context, Mode.SEND_AND_RECV),
child: const Text('Join Room as Host'),
),
//Joining the room as viewer
ElevatedButton(
onPressed: () => onJoinButtonPressed(context, Mode.SIGNALLING_ONLY),
child: const Text('Join Room as Viewer'),
),
],
),
),
);
}
}
- Update the app entry to
JoinScreenfrom themain.dartfile.
import 'package:flutter/material.dart';
import 'join_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'VideoSDK QuickStart',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: JoinScreen(),
);
}
}
Output

Step 3: Creating the HLSScreen
Let's create hls_screen.dart file and create HLSScreen StatefulWidget which will take the roomId, token and mode of the participant in the constructor.
We will create a new room using the createRoom method and render the HLSSpeakerView or HLSViewerView based on the passed participant mode.
The SDK does not ask for camera and microphone permission on Android, so a speaker asks with VideoSDK.requestPermissions() before joining. join(), leave() and pin() return a Future that throws a VideoSDKError when the action fails, so the screen awaits them inside try / catch. The room releases itself after it leaves, so there is nothing to clean up when the screen closes.
import 'dart:async' show unawaited;
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
import './hls_speaker_view.dart';
import './hls_viewer_view.dart';
class HLSScreen extends StatefulWidget {
final String roomId;
final String token;
final Mode mode;
const HLSScreen(
{super.key,
required this.roomId,
required this.token,
required this.mode});
@override
State<HLSScreen> createState() => _HLSScreenState();
}
class _HLSScreenState extends State<HLSScreen> {
late Room _room;
bool isJoined = false;
@override
void initState() {
super.initState();
// create room when widget loads
_room = VideoSDK.createRoom(
roomId: widget.roomId,
token: widget.token,
participantName: "John Doe",
micEnabled: true,
camEnabled: true,
defaultCamera: FacingMode.FRONT, // camera used when the room starts
mode: widget.mode,
);
// setting the event listener for join and leave events
setRoomEventListener();
// Joining room
unawaited(joinRoom());
}
Future<void> joinRoom() async {
// On Android, the SDK does not ask for camera and microphone permission.
if (widget.mode == Mode.SEND_AND_RECV) {
await VideoSDK.requestPermissions();
}
try {
await _room.join();
} on VideoSDKError catch (error) {
log("Could not join the room: ${error.code} ${error.name}");
}
}
Future<void> pinLocalParticipant() async {
try {
await _room.localParticipant.pin();
} on VideoSDKError catch (error) {
log("Could not pin: ${error.code} ${error.name}");
}
}
// listening to room events
void setRoomEventListener() {
//Setting the joining flag to true when room is joined
_room.on(Events.roomJoined, () {
if (widget.mode == Mode.SEND_AND_RECV) {
unawaited(pinLocalParticipant());
}
setState(() {
isJoined = true;
});
});
//Handling navigation when room is left
_room.on(Events.roomLeft, () {
Navigator.popUntil(context, ModalRoute.withName('/'));
});
}
Future<void> leaveRoom() async {
try {
await _room.leave();
} on VideoSDKError catch (error) {
// 3054: the room has already left.
if (error.code != 3054) {
log("Could not leave the room: ${error.code} ${error.name}");
}
}
}
@override
Widget build(BuildContext context) {
return PopScope(
// leave the room when the back button closes the screen
onPopInvokedWithResult: (didPop, result) {
if (didPop) unawaited(leaveRoom());
},
child: Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('VideoSDK HLS QuickStart'),
),
//Showing the Speaker or Viewer View based on the mode
body: isJoined
? widget.mode == Mode.SEND_AND_RECV
? HLSSpeakerView(room: _room)
: widget.mode == Mode.SIGNALLING_ONLY
? HLSViewerView(room: _room)
: null
: const Center(
child: Text("Joining...",
style: TextStyle(color: Colors.white),
),
),
),
);
}
}
Step 4: Implementing Speaker View
Let's create the HLSSpeakerView which will show the RoomControls and the ParticipantTile in grid.
- Let us start off by creating the
StatefulWidgetnamedParticipantTileinparticipant_tile.dartfile. It will consist ofRTCVideoViewwhich will show participant's video stream. Stream events are delivered on theRoom, so the tile takes the room and keeps only the events for its own participant.
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
class ParticipantTile extends StatefulWidget {
final Room room;
final Participant participant;
const ParticipantTile(
{super.key, required this.room, required this.participant});
@override
State<ParticipantTile> createState() => _ParticipantTileState();
}
class _ParticipantTileState extends State<ParticipantTile> {
RTCStream? videoStream;
@override
void initState() {
super.initState();
// initialze video stream for the participant if they are present
videoStream = widget.participant.streams.values
.where((stream) => stream.kind == MediaType.VIDEO)
.firstOrNull;
_initStreamListeners();
}
//Event listener for the video stream of the participant.
//stream.participantId is the id of the participant the stream belongs to.
void _initStreamListeners() {
widget.room.on(Events.streamEnabled, _onStreamEnabled);
widget.room.on(Events.streamDisabled, _onStreamDisabled);
}
bool _isVideoOfThisParticipant(RTCStream stream) =>
stream.participantId == widget.participant.id &&
stream.kind == MediaType.VIDEO;
void _onStreamEnabled(RTCStream stream) {
if (_isVideoOfThisParticipant(stream)) {
setState(() => videoStream = stream);
}
}
void _onStreamDisabled(RTCStream stream) {
if (_isVideoOfThisParticipant(stream)) {
setState(() => videoStream = null);
}
}
@override
void dispose() {
// The room outlives the tile, so remove the handlers from it.
widget.room.off(Events.streamEnabled, _onStreamEnabled);
widget.room.off(Events.streamDisabled, _onStreamDisabled);
super.dispose();
}
@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
StatelessWidgetnamedRoomControlsin theroom_controls.dartfile. This widget will accept the callback handlers for all the room control buttons and the current HLS state of the room.
import 'package:flutter/material.dart';
class RoomControls extends StatelessWidget {
final String hlsState;
final void Function() onToggleMicButtonPressed;
final void Function() onToggleCameraButtonPressed;
final void Function() onHLSButtonPressed;
const RoomControls(
{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")),
],
);
}
}
- Lets finally put all these widget in the
StatefulWidgetnamedHLSSpeakerViewinhls_speaker_view.dartfile. This widget will listen to thehlsStateChanged,participantJoinedandparticipantLeftevents. It will render the participants and the room controls like leave, toggle mic and webcam as well as start and stop HLS.
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:videosdk/videosdk.dart';
import './room_controls.dart';
import './participant_tile.dart';
class HLSSpeakerView extends StatefulWidget {
final Room room;
const HLSSpeakerView({super.key, required this.room});
@override
State<HLSSpeakerView> createState() => _HLSSpeakerViewState();
}
class _HLSSpeakerViewState extends State<HLSSpeakerView> {
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
setRoomEventListener();
participants.putIfAbsent(
widget.room.localParticipant.id, () => widget.room.localParticipant);
//filtering the SEND_AND_RECV participants to be shown in the grid
widget.room.participants.values.forEach((participant) {
if (participant.mode == Mode.SEND_AND_RECV) {
participants.putIfAbsent(participant.id, () => participant);
}
});
hlsState = widget.room.hlsState;
}
@override
void setState(VoidCallback fn) {
if (mounted) {
super.setState(fn);
}
}
Future<void> leave() async {
try {
await widget.room.leave();
} on VideoSDKError catch (error) {
// 3054: the room has already left.
if (error.code != 3054) {
log("Could not leave the room: ${error.code} ${error.name}");
}
}
}
Future<void> toggleMic() async {
try {
micEnabled ? await widget.room.muteMic() : await widget.room.unmuteMic();
micEnabled = !micEnabled;
} on VideoSDKError catch (error) {
log("Could not toggle the mic: ${error.code} ${error.name}");
}
}
Future<void> toggleCamera() async {
try {
camEnabled ? await widget.room.disableCam() : await widget.room.enableCam();
camEnabled = !camEnabled;
} on VideoSDKError catch (error) {
log("Could not toggle the camera: ${error.code} ${error.name}");
}
}
//HLS handler which will start and stop HLS
Future<void> toggleHls() async {
try {
if (hlsState == "HLS_STOPPED") {
await widget.room.startHLS(
config: const HLSConfig(
layout: ConfigLayout(
type: ConfigLayoutGrid.SPOTLIGHT,
priority: ConfigLayoutPriority.PIN,
gridSize: 20,
),
mode: ConfigMode.VIDEO_AND_AUDIO,
theme: ConfigTheme.DARK,
quality: ConfigQuality.HIGH,
orientation: ConfigOrientation.PORTRAIT,
),
);
} else if (hlsState == "HLS_STARTED" || hlsState == "HLS_PLAYABLE") {
await widget.room.stopHLS();
}
} on VideoSDKError catch (error) {
log("Could not start or stop HLS: ${error.code} ${error.name}");
}
}
@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("Room Id Coppied"),
))
},
child: const Text("Copy Room Id"),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: 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(
key: Key(participants.values.elementAt(index).id),
room: widget.room,
participant: participants.values.elementAt(index));
},
itemCount: participants.length,
),
),
//Rending the room Controls
RoomControls(
hlsState: hlsState,
onToggleMicButtonPressed: toggleMic,
onToggleCameraButtonPressed: toggleCamera,
onHLSButtonPressed: toggleHls,
),
],
),
);
}
// listening to room events for participants join, left and hls state changes
void setRoomEventListener() {
widget.room.on(
Events.participantJoined,
(Participant participant) {
//Adding only SEND_AND_RECV participant to show in grid
if (participant.mode == Mode.SEND_AND_RECV) {
setState(
() => participants.putIfAbsent(participant.id, () => participant),
);
}
},
);
widget.room.on(Events.participantLeft,
(Participant participant, LeaveReason reason) {
if (participants.containsKey(participant.id)) {
setState(
() => participants.remove(participant.id),
);
}
});
widget.room.on(
Events.hlsStateChanged,
(String state, HLSUrl? hlsUrl) {
setState(
() => hlsState = state,
);
},
);
}
}
Output of Speaker View

Step 5: Implementing the Viewer View
Let's create StatefulWidget named HLSViewerView in the hls_viewer_view.dart file.
HLSViewerView will listen to the hlsStateChanged event and if the HLS is started, it will show the livestream using the flutter_vlc_player library.
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:videosdk/videosdk.dart';
import './livestream_player.dart';
class HLSViewerView extends StatefulWidget {
final Room room;
const HLSViewerView({super.key, required this.room});
@override
State<HLSViewerView> createState() => _HLSViewerViewState();
}
class _HLSViewerViewState extends State<HLSViewerView> {
String hlsState = "HLS_STOPPED";
String? playbackHlsUrl;
@override
void initState() {
super.initState();
//initialize the room's hls state and hls's playbackHlsUrl
hlsState = widget.room.hlsState;
playbackHlsUrl = widget.room.hlsUrl?.playbackHlsUrl;
//add the hlsStateChanged event listener
setRoomEventListener();
}
Future<void> leave() async {
try {
await widget.room.leave();
} on VideoSDKError catch (error) {
// 3054: the room has already left.
if (error.code != 3054) {
log("Could not leave the room: ${error.code} ${error.name}");
}
}
}
@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 Room Id"),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: leave,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
),
child: const Text("Leave"),
)
],
),
),
//Show the livestream player if the playbackHlsUrl is present
playbackHlsUrl != null
? LivestreamPlayer(playbackHlsUrl: playbackHlsUrl!)
: const Text("Host has not started the HLS",
style: TextStyle(color: Colors.white)),
],
),
);
}
void setRoomEventListener() {
// listening to hlsStateChanged events and updating the hlsState and playbackHlsUrl
widget.room.on(
Events.hlsStateChanged,
(String state, HLSUrl? hlsUrl) {
if (mounted) {
setState(() {
hlsState = state;
if (state == "HLS_PLAYABLE") {
playbackHlsUrl = hlsUrl?.playbackHlsUrl;
} else if (state == "HLS_STOPPED") {
playbackHlsUrl = null;
}
});
}
},
);
}
}
- Let us use the
flutter_vlc_playerlibrary to play the HLS stream on the Viewer side. For these we will create aStatefulWidgetnamedLivestreamPlayerin thelivestream_player.dartfile.
import 'package:flutter/material.dart';
import 'package:flutter_vlc_player/flutter_vlc_player.dart';
class LivestreamPlayer extends StatefulWidget {
final String playbackHlsUrl;
const LivestreamPlayer({
Key? key,
required this.playbackHlsUrl,
}) : super(key: key);
@override
LivestreamPlayerState createState() => LivestreamPlayerState();
}
class LivestreamPlayerState extends State<LivestreamPlayer>
with AutomaticKeepAliveClientMixin {
late VlcPlayerController _controller;
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
//initializing the player
_controller = VlcPlayerController.network(widget.playbackHlsUrl,
options: VlcPlayerOptions());
}
@override
void dispose() async {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
super.build(context);
//rendering the VlcPlayer
return VlcPlayer(
controller: _controller,
aspectRatio: 9 / 16,
placeholder: const Center(child: CircularProgressIndicator()),
);
}
}
Output of Viewer View

Run and Test
The app is all set to test. Make sure to update the token in api_call.dart
Your app should look like this after the implementation.
Speaker View Output
Viewer View Output
Run the app on an ARM Android device or an arm64-v8a emulator, and on an iPhone, an iPad or the iOS Simulator on a Mac with Apple silicon. x86 and x86_64 Android emulators and the iOS Simulator on an Intel Mac are not supported. See Known Issues.
You can checkout the complete quick start example here (this example uses an older SDK version).
Got a Question? Ask us on discord

