Building a Livestream App with Flutter 3
Tags: flutter, webdev, api, android
Published: 2022-10-31 20:19:09

[Originally published here.](https://dolby.io/blog/building-a-real-time-streaming-app-with-webrtc-and-flutter-3/)

Streaming, especially the low latency kind, has become a popular medium to engage with an audience, [host live events](https://dolby.io/solutions/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](https://flutter.dev/?gclid=CjwKCAjw4c-ZBhAEEiwAZ105RYihY2PWVmum6IojgwCKgGWKZg9IOYmyhWlapji_zIYo_FpW-vW8tRoCoKcQAvD_BwE&gclsrc=aw.ds) 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](http://dolby.io/) has recently released their [WebRTC real-time streaming SDK for Flutter](https://docs.dolby.io/streaming-apis/docs/flutter), allowing users to [build cross-platform streaming apps](https://dolby.io/products/real-time-streaming/) 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 the[](http://dolby.io/)[Dolby.io Streaming SDK for Flutter](http://dolby.io/).

![An example of the Flutter real-time streaming app in action, streaming out to a chrome tab.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a6gjawzp26fcfved0pha.jpg)

## Getting Started with the Real-Time Streaming SDK
Before we begin you need to make sure you have the [latest version of Flutter installed](https://docs.flutter.dev/get-started/install) and set up on your machine. To get started with building a streaming app we need to install the [Dolby.io](http://dolby.io/) Streaming SDK for Flutter 3 via the terminal. 

```
flutter pub add millicast_flutter_sdk
```

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

```
flutter pub get
```

With the Flutter Streaming SDK installed, you can start by creating a [vanilla Flutter app](https://docs.flutter.dev/get-started/test-drive?tab=vscode) 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
```
Then inside  `main.dart` you just import `flutter_webrtc `alongside any other dependencies your project may have.
```
import 'package:flutter_webrtc/flutter_webrtc.dart';
```
In addition to installing the SDK, you'll also need to [create a free ](https://dashboard.dolby.io/signup/)[Dolby.io](http://dolby.io/)[ Account](https://dashboard.dolby.io/signup/). 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](https://github.com/dolbyio-samples/blog-streaming-flutter-app/tree/main/streaming_app) 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](http://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](http://dolby.io/) servers.

#### Taking in the WebRTC Stream Credentials

To publish and view a WebRTC stream with the[ Dolby.io](http://dolby.io/) Flutter SDK we need three things: an `account ID`, a `stream name`, and a `publishing token`. [These credentials can be found on your Dolby.io dashboard](https://docs.dolby.io/streaming-apis/docs/about-dash) 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,
    )),
```
*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](https://dolby.io/blog/secure-token-authentication-with-dolby-io-millicast-streaming-webrtc/).*

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',
       ),
     )),
```
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'),
),
```
![The sample app, on launch, captures the user’s credentials to start the stream.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wisdn2ehwr3bq0p269du.jpg)
#### 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.'),
      ));
    }
```

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

```
final RTCVideoRenderer localRenderer = RTCVideoRenderer();
publish = await publishConnect(localRenderer, streamName.text, pubTok.text);
```

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);

...
}
```

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);

...
}
```
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;

...
}
```
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;
}
```
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 [a `RTCVideoView` object](https://pub.dev/documentation/flutter_webrtc/latest/flutter_webrtc/RTCVideoView-class.html) 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),
)
```
#### 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.'),
    ));
  }
```
#### 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](http://dolby.io/) server.

```
publish.stop();
```
#### 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.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ysoly1b5pkvtndib0hoj.jpg)

![An example of the Flutter real-time streaming app launching as a web app.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sgvnej5uyreo2073efvp.jpg)

![An example of the Flutter real-time streaming app launching as a Windows Native app.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4a6reoog8tnjzbo6qzyh.jpg)

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](https://docs.dolby.io/streaming-apis/docs/flutter) and play around with the full project on[ this GitHub repository](https://github.com/dolbyio-samples/blog-streaming-flutter-app/tree/main/streaming_app). 

Feedback or Questions? Reach out to the team on [Twitter](https://twitter.com/DolbyIO?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor), [LinkedIn](https://www.linkedin.com/company/dolbyio/), or via our [support desk](https://www.millicast.com/contactus/).