DEV Community

Cover image for Building a Livestream App with Flutter 3
Braden Riggs for Dolby.io

Posted on • Originally published at dolby.io

Building a Livestream App with Flutter 3

Originally published here.

Streaming, especially the low latency kind, has become a popular medium to engage with an audience, host live events, and connect people virtually. For developers building streaming apps, however, there is just one issue. If we are interested in connecting to a wide audience we need to develop for a wide range of platforms such as Android, iOS, Web, and even desktop native apps, which can quickly become a heavy lift for any team. This is where Flutter 3 comes in, released in May of 2022, Flutter 3 takes cross-platform to the next level allowing users to "build for any screen" from a single code base. Hence, rather than building 3 separate apps for iOS, Android, and Web, you can build just one. To further sweeten the deal, Dolby.io has recently released their WebRTC real-time streaming SDK for Flutter, allowing users to build cross-platform streaming apps that combine scalability and ultra-low delay. 

In this guide, we'll be exploring how to build a cross-platform real-time streaming app that works on Android, iOS, Desktop Native, and Web using theDolby.io Streaming SDK for Flutter.

An example of the Flutter real-time streaming app in action, streaming out to a chrome tab.

Getting Started with the Real-Time Streaming SDK

Before we begin you need to make sure you have the latest version of Flutter installed and set up on your machine. To get started with building a streaming app we need to install the Dolby.io Streaming SDK for Flutter 3 via the terminal.

 

flutter pub add millicast_flutter_sdk
Enter fullscreen mode Exit fullscreen mode

Then run the following command in terminal to download the dependencies:

flutter pub get
Enter fullscreen mode Exit fullscreen mode

With the Flutter Streaming SDK installed, you can start by creating a vanilla Flutter app and add the most recent version of flutter_webrtc to your project's pubspec.yaml. You should also see that the Dolby.io Millicast flutter SDK has been automatically added.

flutter_webrtc: ^x.x.x
millicast_flutter_sdk: ^x.x.x
Enter fullscreen mode Exit fullscreen mode

Then inside  main.dart you just import flutter_webrtc alongside any other dependencies your project may have.

import 'package:flutter_webrtc/flutter_webrtc.dart';
Enter fullscreen mode Exit fullscreen mode

In addition to installing the SDK, you'll also need to create a free Dolby.io Account. The free account offers 50 Gigabytes of data transfer a month, which will be plenty for building and testing out the real-time streaming app.

Interested in following along with a project that already has the SDK installed and set up? Check out this GitHub repository which contains a completed version of this app.

Building the Real-Time Streaming App with Flutter

Building a WebRTC Flutter streaming app can be complicated, so to get started we first need to divide the app into a series of features that come together to support a real-time streaming experience. In order for the app to connect to the Dolby.io servers, we must include a way for the user to input their streaming credentials and tokens in order to authenticate and use the Dolby.io servers.

Taking in the WebRTC Stream Credentials

To publish and view a WebRTC stream with the Dolby.io Flutter SDK we need three things: an account ID, a stream name, and a publishing tokenThese credentials can be found on your Dolby.io dashboard and need to be input by the user which we can capture with the TextFormField widget, where the widget, on change, updates a TextEditingController variable.

 

Container(
    width: MediaQuery.of(context).size.width,
    constraints: const BoxConstraints(
        minWidth: 100, maxWidth: 400),
    child: TextFormField(
      maxLength: 20,
      controller: accID,
      decoration: const InputDecoration(
        labelText: 'Enter Account ID',
      ),
      onChanged: (v) => accID.text = v,
    )),
Enter fullscreen mode Exit fullscreen mode

Note: In production, you don't need to have users input these credentials, instead you could use a custom login and serve the users a temporary login token. For learning more about Dolby.io tokens check out this blog on creating and securing tokens.

Because we need three inputs to publish a WebRTC stream to the Dolby.io server, we can repeat this code for each input.

Container(
     width: MediaQuery.of(context).size.width,
     constraints: const BoxConstraints(
         minWidth: 100, maxWidth: 400),
     child: TextFormField(
       maxLength: 20,
       controller: accID,
       decoration: const InputDecoration(
         labelText: 'Enter Account ID',
       ),
       onChanged: (v) => accID.text = v,
     )),
Container(
     width: MediaQuery.of(context).size.width,
     constraints: const BoxConstraints(
         minWidth: 100, maxWidth: 400),
     child: TextFormField(
       maxLength: 20,
       controller: streamName,
       onChanged: (v) => streamName.text = v,
       decoration: const InputDecoration(
         labelText: 'Enter Stream Name',
       ),
     )),
 // Publishing Token Input
 Container(
     width: MediaQuery.of(context).size.width,
     constraints: const BoxConstraints(
         minWidth: 100, maxWidth: 400),
     child: TextFormField(
       controller: pubTok,
       maxLength: 100,
       onChanged: (v) => pubTok.text = v,
       decoration: const InputDecoration(
         labelText: 'Enter Publishing Token',
       ),
     )),
Enter fullscreen mode Exit fullscreen mode

Additionally, we can add an ElevatedButton for the user to press once they have added their credentials.

 

ElevatedButton(
  style: ElevatedButton.styleFrom(
    primary: Colors.deepPurple,
  ),
  onPressed: publishExample,
  child: const Text('Start Stream'),
),
Enter fullscreen mode Exit fullscreen mode

The sample app, on launch, captures the user’s credentials to start the stream.

Authentication and Publishing Streams from Flutter

You'll notice that the Elevated button triggers a function via its onPressed parameter. This function, called publishExample, checks if the credentials are valid and authenticates the stream. First, the function checks that a user has input a value for each input.

void publishExample() async {
    if (pubTok.text.isEmpty || streamName.text.isEmpty || accID.text.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
        backgroundColor: Colors.grey,
        content: Text(
            'Make sure Account ID, Stream Name, and Publishing Token all include values.'),
      ));
    }
Enter fullscreen mode Exit fullscreen mode

Then the function calls publishConnect, an asynchronous function that takes in streamName, pubTok, and a third object called localRendererlocalRendered is a RTCVideoRenderer object included with the flutter.webrtc package.

final RTCVideoRenderer localRenderer = RTCVideoRenderer();
publish = await publishConnect(localRenderer, streamName.text, pubTok.text);
Enter fullscreen mode Exit fullscreen mode

Using these three parameters we have everything we need to authenticate and begin publishing a stream. Inside of the publishConnect function, we need to generate a temporary publishing token using the streamName and pubTok:

Future publishConnect(RTCVideoRenderer localRenderer, String streamName, String pubTok) async {
  // Setting subscriber options
  DirectorPublisherOptions directorPublisherOptions =
      DirectorPublisherOptions(token: pubTok, streamName: streamName);

  /// Define callback for generate new token
  tokenGenerator() => Director.getPublisher(directorPublisherOptions);

...
}
Enter fullscreen mode Exit fullscreen mode

With the temporary publishing token created, we then can use it to create a publish object. Using this publish object we could start the stream, however, we wouldn't be able to see or hear anything, this is because we haven't specified what kind of stream we are creating or which devices we will connect to. To do this we need to specify if the stream will include audio, video, or audio and video, then we need to pass these constraints into the getUserMedia function which will map the constraints to the default audio capture device and the default video capture device.

 

{
...
Publish publish =
      Publish(streamName: 'your-streamname', tokenGenerator: tokenGenerator);

  final Map<String, dynamic> constraints = <String, bool>{
    'audio': true,
    'video': true
  };

  MediaStream stream = await navigator.mediaDevices.getUserMedia(constraints);

...
}
Enter fullscreen mode Exit fullscreen mode

Using this stream object, we can also provide a feed to the user in the form of a viewer. To do this we need to assign our input devices to localRender as sources.

{
...

localRenderer.srcObject = stream;

...
}
Enter fullscreen mode Exit fullscreen mode

Finally, we can map the stream object and pass it as an option to the connect function, which is inherited from publish.

{
...
//Publishing Options
  Map<String, dynamic> broadcastOptions = {'mediaStream': stream};

  /// Start connection to publisher
  await publish.connect(options: broadcastOptions);
  return publish;
}
Enter fullscreen mode Exit fullscreen mode

With our stream connected, we can now look at setting up the viewer using localRender.

In-App WebRTC Stream Viewer

Now that our stream is authenticated and publishing we need to add a viewer object so the streamer can see themselves streaming. This can be done with RTCVideoView object which takes in our localRender object and is constrained by a container.

 

Container(
  margin: const EdgeInsets.all(30),
  constraints: const BoxConstraints(
      minWidth: 100, maxWidth: 1000, maxHeight: 500),
  width: MediaQuery.of(context).size.width,
  height: MediaQuery.of(context).size.height / 1.7,
  decoration:
      const BoxDecoration(color: Colors.black54),
  child: RTCVideoView(localRenderer, mirror: true),
)
Enter fullscreen mode Exit fullscreen mode

Sharing the Real-time Stream

With the stream authenticated and live, we want to share our content with the world. We can do this via a URL formatted with our streamName and our accountID which we collected as inputs. Using the example app as a template we can create a function called shareStream which formats the URL to share and copies it to the clipboard.

void shareStream() {
    Clipboard.setData(ClipboardData(
        text:
            "https://viewer.millicast.com/?streamId=${accID.text}/${streamName.text}"));
    ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
      backgroundColor: Colors.grey,
      content: Text('Stream link copied to clipboard.'),
    ));
  }
Enter fullscreen mode Exit fullscreen mode

Unpublishing a WebRTC Stream

To unpublish the stream we can call the publish object returned from our asynchronous publishConnect function to stop, killing the connection with the Dolby.io server.

publish.stop();
Enter fullscreen mode Exit fullscreen mode

Flutter 3 is Truly Cross Platform  

The power of Flutter is taking one code base and having it work across multiple platforms. Here we can see examples of the app working for Android, Windows, and Web:

An example of the Flutter real-time streaming app launching on an Android emulator.

An example of the Flutter real-time streaming app launching as a web app.

An example of the Flutter real-time streaming app launching as a Windows Native app.

Building in this cross-platform framework saves both time and resources, allowing you to get started building real-time streaming apps without having to worry about which platform works for your users. These apps are perfect for streaming live events and virtual events to the widest range of audiences allowing for high-quality interactive experiences. If you are interested in learning more about our Flutter streaming SDK check out our documentation and play around with the full project on this GitHub repository

Feedback or Questions? Reach out to the team on TwitterLinkedIn, or via our support desk.

Top comments (2)

Collapse
 
bradenriggs profile image
Braden Riggs

I'm interested in what everyone is building with Flutter, leave a comment with your ideas!

Collapse
 
ytskk profile image
Andrey

Hola! Nice article.
Currently I’m building dev to flutter app (some kind of alternative to this site)