Building a Real-Time Mobile Livestreaming App With Flutter, LiveKit and WebRTC

Calendar iconAugust 31, 20265 min read
Building a Real-Time Mobile Livestreaming App With Flutter, LiveKit and WebRTC

Getting video from one phone to another is close to a solved problem. Pick a real-time framework, publish a track, subscribe to it, and you have a livestream working in an afternoon.

Then someone walks into a lift. The host's app gets backgrounded. A network switches from Wi-Fi to cellular mid-broadcast. A viewer stares at a frozen frame for forty seconds because nothing in the system has noticed the stream is already over.

That gap, between "it works" and "it works when things go wrong," is what Zuzu was built to explore. It's the second project in NUS Labs, and the source is on GitHub.


What the app does

Zuzu combines hosting and viewing in a single iOS and Android app. Any signed-in user can broadcast or watch.

Hosts preview their stream before going live, switch between front and rear cameras, mute the microphone, and end the broadcast. Viewers browse a live-updating list of active streams, join one, send chat messages and heart reactions, and see the viewer count move in real time.

That's the surface. Underneath, the app is continuously managing livestream state, connectivity changes, recovery, and session cleanup for two different kinds of participant.


The architecture

A shared Flutter codebase across iOS and Android, a Node.js backend, and LiveKit as the real-time communication layer over WebRTC.

Layer Choice
Mobile Flutter & Dart
State management and DI GetX
Backend Node.js, Express, TypeScript
Data access Prisma
Real-time communication LiveKit
Media transport WebRTC
Platforms iOS, Android

The design decision worth pausing on: LiveKit doesn't only carry audio and video. It also powers stream discovery, participant presence, viewer counts, chat messages, and reactions.

The obvious alternative is to run media through WebRTC and everything else through a separate socket service. It works, and it's how a lot of apps are built. It also creates a permanent class of bug where two systems disagree about reality: the video is playing but the stream is missing from the list, the host has left but the viewer count still says twelve, a reaction arrives for a broadcast that already ended.

Keeping presence, discovery, and interaction inside the same real-time layer as the media means those states can't drift apart, because there's only one of them.


Four problems worth writing about

Hosts and viewers are different applications

The shortcut is to model a livestream as one connection with a role flag. Zuzu treats them as separate lifecycles, because they fail in completely different ways.

The host lifecycle covers preview, media setup, publishing, reconnection, stream termination, and cleanup. A host's failure modes are about their own device and uplink: the camera didn't initialise, publishing dropped, the app went to background, the broadcast needs to end cleanly so nothing is left dangling.

The viewer lifecycle covers stream discovery, joining, media subscription, host availability, interaction, reconnection, and cleanup. A viewer's failure modes are mostly about somebody else: the host vanished, the subscription failed, the stream ended while they were in it.

Collapsing these into one state machine is the kind of decision that looks efficient in week one and becomes the reason nobody wants to touch the streaming code in month six. Every new edge case has to be reasoned about twice, once for each role, inside the same tangle of conditionals.

Detecting a dead host quickly

When a host disappears, WebRTC will eventually notice. "Eventually" is the problem. Connection timeouts are tuned to avoid false positives on flaky networks, which means they're slow by design, and a viewer sitting on a frozen frame has no way to tell the difference between buffering and abandonment.

Zuzu runs a heartbeat over LiveKit Data Channels. The host emits a periodic signal; viewers watch for it. When it stops, the viewer interface can react without waiting for transport-level timeouts to expire.

The result is that a stream that has effectively ended looks ended. That's a small thing to describe and a large thing for perceived quality, because the alternative is users concluding the app is broken.

Recovery and device transfer

An interrupted broadcast raises a question the system has to answer correctly: is this person starting a new stream, or coming back to one that's still notionally alive?

Zuzu uses active stream state together with device identification to tell those apart. That makes recovery possible after an interruption instead of orphaning the old session and spawning a duplicate, and it opens the door to device-transfer scenarios where a broadcast continues from somewhere else.

Getting this wrong is how you end up with ghost streams in the discovery list that nobody is actually broadcasting to.

Mobile is a hostile environment

The rest of the resilience work is a list of things that are individually unremarkable and collectively the difference between a demo and a product:

  • reconnection after network interruptions
  • debounce handling so a brief offline blip doesn't trigger a full teardown
  • foreground and background transitions
  • state recovery when the app resumes
  • a wakelock while actively broadcasting, so the screen doesn't sleep mid-stream

None of this appears in a feature list. All of it appears in reviews when it's missing.


The honest trade-offs

Worth knowing before adopting this shape of architecture.

WebRTC economics don't scale like HLS. A peer-to-peer-derived architecture running through an SFU is excellent for low latency and interactive audiences. Costs rise with concurrent participants in a way that segmented HTTP streaming does not. For sub-second interactivity with modest audiences, WebRTC wins clearly. For one host broadcasting to a hundred thousand passive viewers, the calculation changes.

LiveKit is a real dependency. Using it as the unified real-time layer is what buys the state consistency described above, and it means the infrastructure decision, managed service or self-hosted, is now load-bearing.

Hosting is expensive on the device. Continuous capture, encode, and upload consumes battery and generates heat, and a wakelock guarantees the screen stays on throughout. Long broadcasts have real power implications.

The demo scope stops short of production concerns. Zuzu is a Labs project. Recording, transcoding, content moderation, abuse reporting, and adaptive bitrate ladders are not part of it. Any consumer-facing deployment needs most of that list.


Where this architecture belongs

Real-time video is not only an entertainment feature. The same patterns apply anywhere a live view has to be shared and acted on.

  • Field service and workforce: a technician streaming a fault to a remote specialist, on exactly the kind of connection this architecture is built to survive.
  • Education and learning: live sessions where chat, presence, and participation matter as much as the video itself.
  • Commerce and marketplace: live shopping, where reactions and viewer counts are the product and latency directly affects conversion.
  • Property and hospitality: remote viewings and inspections conducted live rather than scheduled and repeated.

In every case the interesting engineering is the same: not sending the video, but knowing what's true when the connection stops cooperating.


Read the code

Zuzu is open source: github.com/nustechnology/LiveStream-Zuzu

The full technical breakdown, architecture diagram, and demo video are on the Labs project page.

You can also read the companion post on ClearHear, which takes the opposite approach: real-time processing with the network removed entirely.

If real-time video, live collaboration, or resilient mobile is on your roadmap, talk to our team.

Written By

NUS Team

Share This Article

Copied!

Read More

The Web-Connected Cockpit: Engineering Low-Latency Drone Control Over the Cloud
Calendar iconAugust 27, 20265 min read

The Web-Connected Cockpit: Engineering Low-Latency Drone Control Over the Cloud

How to safely pilot a drone from a browser? Discover our cloud architecture with decoupled SDKs, split control streams & instant physical overrides.

Live Captions With the Internet Switched Off: Inside ClearHear
Calendar iconAugust 24, 20265 min read

Live Captions With the Internet Switched Off: Inside ClearHear

How we built real-time speech recognition, speaker identification, and AI summarization that never leave the phone. A full engineering breakdown, with open source code.

The Hidden Complexity of Building Cross-Platform Apps for Field Workers
Calendar iconJuly 14, 20267 min read

The Hidden Complexity of Building Cross-Platform Apps for Field Workers

Cross-platform field service apps promise efficiency, but offline sync, GPS integration, and device constraints create hidden complexity. Learn what it really takes.

Turn Insights into Action

Enjoying our articles?
Let’s have a strategic conversation about how these principles can
be applied to solve your specific business challenges.

Schedule a Strategy Session
CodeMonitorGrid with light