Reactions during Live Stream - Flutter
To enhance interaction between viewers and speakers during a livestream, a creative approach is to display viewers' reactions to everyone. This can be achieved by creating a lively atmosphere with flying emojis, similar to the experience seen in livestreams on platforms like Instagram.
This guide explains how to implement this engaging feature using the VideoSDK PubSub mechanism.
Step 1: Creating a button to send reaction
To implement this functionality, start by creating a button that sends reactions to all users. When this button is clicked, publish a message with the emoji to all participants using the VideoSDK PubSub mechanism.
publish() needs a joined room. It returns a Future that throws a VideoSDKError when the SDK refuses the message, for example 3035 when the room is not joined yet.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
class ReactionSender extends StatelessWidget {
final Room room;
const ReactionSender({required this.room});
void sendReaction(BuildContext context, String reaction) async {
// Show local emoji immediately
showEmoji(context, reaction);
// Send reaction to others
try {
await room.pubSub.publish(
"REACTION",
reaction,
const PubSubPublishOptions(persist: false),
);
} on VideoSDKError catch (e) {
// Nobody else saw it. A reaction is cheap, so this is only logged.
debugPrint("Reaction failed: ${e.code} ${e.name}");
} catch (e) {
// Anything else that went wrong.
debugPrint("Reaction failed: $e");
}
}
void showEmoji(BuildContext context, String reaction) {
final overlay = Overlay.of(context);
final emojiEntry = OverlayEntry(
builder: (context) => FlyingEmojiWidget(emoji: reaction),
);
overlay.insert(emojiEntry);
Future.delayed(const Duration(seconds: 3), () => emojiEntry.remove());
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => sendReaction(context, "😍"),
child: const Text("Send Reaction"),
);
}
}
class FlyingEmojiWidget extends StatefulWidget {
final String emoji;
const FlyingEmojiWidget({required this.emoji});
@override
State<FlyingEmojiWidget> createState() => _FlyingEmojiWidgetState();
}
class _FlyingEmojiWidgetState extends State<FlyingEmojiWidget>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation<Offset> _animation;
final Random random = Random();
@override
void initState() {
super.initState();
final dx = random.nextDouble() * 2 - 1; // random horizontal direction
final startOffset = Offset(dx, 1.2); // start off bottom
final endOffset = Offset(dx, -0.2); // move to top
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 3000),
);
_animation = Tween<Offset>(
begin: startOffset,
end: endOffset,
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Positioned.fill(
child: SlideTransition(
position: _animation,
child: Align(
alignment: Alignment.center,
child: Text(widget.emoji, style: const TextStyle(fontSize: 40)),
),
),
);
}
}
Step 2: Displaying the Reactions to all the users
FlyingEmojiWidget will be utilized to display the reactions on the screen. Subscribe to the REACTION topic to show the reactions that other participants send. Your own reactions also arrive on the topic, so skip them: the sender already showed them.
subscribe() needs a joined room, so show ReactionListener after Events.roomJoined.
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
class ReactionListener extends StatefulWidget {
final Room room;
const ReactionListener({required this.room});
@override
State<ReactionListener> createState() => _ReactionListenerState();
}
class _ReactionListenerState extends State<ReactionListener> {
// Pass this same object to unsubscribe()
late final PubSubMessageListener reactionListener =
PubSubMessageListener(onMessageReceived: handleReaction);
@override
void initState() {
super.initState();
// initState() cannot await, so failures are caught here.
widget.room.pubSub
.subscribe("REACTION", reactionListener)
.catchError((Object e) => debugPrint("Subscribe failed: $e"));
}
@override
void dispose() {
widget.room.pubSub
.unsubscribe("REACTION", reactionListener)
.catchError((Object e) => debugPrint("Unsubscribe failed: $e"));
super.dispose();
}
void handleReaction(PubSubMessage message) {
// ReactionSender already showed your own reaction.
if (message.senderId == widget.room.localParticipant.id) return;
final reaction = message.message;
showEmoji(context, reaction);
}
void showEmoji(BuildContext context, String emoji) {
final overlay = Overlay.of(context);
final emojiEntry = OverlayEntry(
builder: (context) => FlyingEmojiWidget(emoji: emoji),
);
overlay.insert(emojiEntry);
Future.delayed(const Duration(seconds: 3), () => emojiEntry.remove());
}
@override
Widget build(BuildContext context) {
return const SizedBox();
}
}
API Reference
The API references for all the methods and events utilized in this guide are provided below.
Got a Question? Ask us on discord

