# Quickstart (/en/realtime-media/interactive-live-streaming/quickstart/flutter)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

This page provides a step-by-step guide on how to create a basic Interactive Live Streaming app using the Agora Video SDK.

    ## Understand the tech [#understand-the-tech-6]

    To start a Interactive Live Streaming session, implement the following steps in your app:

    * **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.

    * **Join a channel**: Call methods to create and join a channel.

      * **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.

      * **Join as audience**: Audience members can only subscribe to streams published by hosts.

    * **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

    ![Streaming workflow](https://assets-docs.agora.io/images/video-sdk/get-started-ils-bs.svg)

    ## Prerequisites [#prerequisites-6]

    * [Flutter](https://docs.flutter.dev/get-started/install) 2.10.5 or higher with Dart 2.14.0 or higher.
    * [Android Studio](https://developer.android.com/studio), IntelliJ, VS Code, or any other IDE that supports Flutter. See [Set up an editor](https://docs.flutter.dev/get-started/editor).
    * Prepare your development and testing environment according to your [target platform](reference/supported-platforms.md).

    Run the `flutter doctor` command to confirm that your development environment is set up correctly for Flutter development.

    * A camera and a microphone

    * A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.

    ## Set up your project [#set-up-your-project-6]

    This section shows you how to set up your Flutter project and install the Agora Video SDK.

    **Create a new project**
    From the Terminal, run the following commands to create a new project named `agora_project`, or follow the [steps for your IDE](https://docs.flutter.dev/get-started/test-drive?tab=vscode#choose-your-ide):

    ```bash
    flutter create agora_project
    cd agora_project
    ```

    **Add to an existing project**
    To add Interactive Live Streaming to your existing project:

    1. Open your Flutter project and navigate to the `lib` folder.
    2. Add a new file to the `lib` folder and name it `agora_logic.dart`.

    ### Install the SDK [#install-the-sdk-6]

    Install the Agora Video SDK and other dependencies.

    1. Add the [latest version](https://pub.dev/packages/agora_rtc_engine) of Agora Video SDK to your Flutter project:

       ```bash
       flutter pub add agora_rtc_engine
       ```

    2. Add the permission processing package:

       ```bash
       flutter pub add permission_handler
       ```

    The `dependencies` in your `pubspec.yaml` file should look like the following:

    ```yaml
    dependencies:
     flutter:
      sdk: flutter
     agora_rtc_engine: ^6.5.0 # Agora Flutter SDK, please use the latest version
     permission_handler: ^11.3.1 # Package for managing runtime permissions
     cupertino_icons: ^1.0.8
    ```

    1. Install the dependencies.

       Execute the following command in the project path:

       ```bash
       flutter pub get
       ```

    ## Implement Interactive Live Streaming [#implement-interactive-live-streaming-6]

    This section guides you through the implementation of basic real-time audio and video interaction in your app.

    The following figure illustrates the essential steps:

    <Accordions>
      <Accordion title="Quick start sequence">
        ![Quick start sequence](https://assets-docs.agora.io/images/video-sdk/quick-start-sequence.svg)
      </Accordion>
    </Accordions>

    This guide includes [complete sample code](#complete-sample-code) that demonstrates implementing basic real-time interaction. To understand the core API calls in the sample code, review the following implementation steps.

    ### Import packages [#import-packages]

    Import the following packages in your dart file.

    ```dart
    import 'dart:async';

    import 'package:agora_rtc_engine/agora_rtc_engine.dart';
    import 'package:flutter/material.dart';
    import 'package:permission_handler/permission_handler.dart';
    ```

    ### Initialize the engine [#initialize-the-engine-5]

    For real-time communication, initialize an `RtcEngine` instance. Use `RtcEngineContext` to specify the [App ID](manage-agora-account.md), and other configuration parameters. In your dart file, add the following code:

    ```dart
    // Set up the Agora RTC engine instance
    Future<void> _initializeAgoraVideoSDK() async {
     _engine = createAgoraRtcEngine();
     await _engine.initialize(const RtcEngineContext(
      appId: appId,
      channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
     ));
    }
    ```

    ### Join a channel [#join-a-channel-6]

    To join a channel, call `joinChannel` with the following parameters:

    * **Channel name**: The name of the channel to join. Clients that pass the same channel name join the same channel. If a channel with the specified name does not exist, it is created when the first user joins.

    * **Authentication token**: A dynamic key that authenticates a user when the client joins a channel. In a production environment, you obtain a token from a [token server](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).

    * **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID and returns the value in the `onJoinChannelSuccess` callback.

    * **Channel media options**: Configure `ChannelMediaOptions` to define publishing and subscription settings, optimize performance for your specific use-case, and set optional parameters.

    For Interactive Live Streaming, set the `clientRoleType` to `clientRoleBroadcaster` (host) or `clientRoleAudience`, and the `audienceLatencyLevel` to `audienceLatencyLevelUltraLowLatency`.

    ```dart
    // Join a channel
    Future<void> _joinChannel() async {
     await _engine.joinChannel(
      token: token,
      channelId: channel,
      options: const ChannelMediaOptions(
       autoSubscribeVideo: true, // Automatically subscribe to all video streams
       autoSubscribeAudio: true, // Automatically subscribe to all audio streams
       publishCameraTrack: true, // Publish camera-captured video
       publishMicrophoneTrack: true, // Publish microphone-captured audio
       // Use clientRoleBroadcaster to act as a host or clientRoleAudience for audience
       clientRoleType: ClientRoleType.clientRoleBroadcaster,
       // Set the audience latency level
       audienceLatencyLevel: AudienceLatencyLevelType.audienceLatencyLevelUltraLowLatency

      ),
      uid: 0,
     );
    }
    ```

    ### Subscribe to Video SDK events [#subscribe-to-video-sdk-events-4]

    The SDK provides the `RtcEngineEventHandler` for subscribing to channel events. To use it, pass an instance of `RtcEngineEventHandler` to `registerEventHandler` and implement the event methods you want to handle.

    Call `registerEventHandler` to bind the event handler to the SDK.

    ```dart
    // Register an event handler for Agora RTC
    void _setupEventHandlers() {
     _engine.registerEventHandler(
      RtcEngineEventHandler(
       onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
        debugPrint("Local user ${connection.localUid} joined");
        setState(() => _localUserJoined = true);
       },
       onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
        debugPrint("Remote user $remoteUid joined");
        setState(() => _remoteUid = remoteUid);
       },
       onUserOffline: (RtcConnection connection, int remoteUid, UserOfflineReasonType reason) {
        debugPrint("Remote user $remoteUid left");
        setState(() => _remoteUid = null);
       },
      ),
     );
    }
    ```

    When a remote user joins the channel, the `onUserJoined` callback is triggered. Use the remote user's `uid` returned in the callback, to create an `AgoraVideoView` control for displaying the video stream from the remote user.

    <CalloutContainer type="info">
      <CalloutDescription>
        To ensure that you receive all Video SDK events, register the event handler before joining a channel.
      </CalloutDescription>
    </CalloutContainer>

    ### Display the local video [#display-the-local-video-6]

    To display the local video, enable the video module by calling `enableVideo`, then start the local video preview with `startPreview`.

    ```dart
    Future<void> _setupLocalVideo() async {
     // The video module and preview are disabled by default.
     await _engine.enableVideo();
     await _engine.startPreview();
    }
    ```

    To render the local video, add the following widget inside your UI’s widget tree, such as in the build method of your `StatefulWidget`:

    ```dart
    // Displays the local user's video view using the Agora engine.
    Widget _localVideo() {
     return AgoraVideoView(
      controller: VideoViewController(
       rtcEngine: _engine, // Uses the Agora engine instance
       canvas: const VideoCanvas(
        uid: 0, // Specifies the local user
        renderMode: RenderModeType.renderModeHidden, // Sets the video rendering mode
       ),
      ),
     );
    }
    ```

    ### Display remote video [#display-remote-video-6]

    To render a remote video, add the following widget inside your UI’s widget tree, such as in the build method of your `StatefulWidget`:

    ```dart
    // If a remote user has joined, render their video, else display a waiting message
    Widget _remoteVideo() {
     if (_remoteUid != null) {
      return AgoraVideoView(
       controller: VideoViewController.remote(
        rtcEngine: _engine, // Uses the Agora engine instance
        canvas: VideoCanvas(uid: _remoteUid), // Binds the remote user's video
        connection: const RtcConnection(channelId: channel), // Specifies the channel
       ),
      );
     } else {
      return const Text(
       'Waiting for remote user to join...',
       textAlign: TextAlign.center,
      );
     }
    }
    ```

    ### Handle permissions [#handle-permissions-4]

    Request microphone and camera permissions for Interactive Live Streaming.

    ```dart
    Future<void> _requestPermissions() async {
     await [Permission.microphone, Permission.camera].request();
    }
    ```

    <CalloutContainer type="info">
      <CalloutDescription>
        If your target platform is iOS or macOS, add the microphone and camera permission declarations required for real-time interaction to [`Info.plist`](https://help.apple.com/xcode/mac/current/#/dev3f399a2a6).

        | **Device** | **Key**                                | **Value**       |
        | :--------- | :------------------------------------- | :-------------- |
        | Microphone | Privacy - Microphone Usage Description | for audio calls |
        | Camera     | Privacy - Camera Usage Description     | for video calls |
      </CalloutDescription>
    </CalloutContainer>

    ### Start and close the app [#start-and-close-the-app-4]

    To start Interactive Live Streaming, request microphone and camera permissions, initialize the Agora SDK instance, set up event handlers, join a channel, and display the local video.

    ```dart
    await _requestPermissions();
    await _initializeAgoraVideoSDK();
    await _setupLocalVideo();
    _setupEventHandlers();
    await _joinChannel();
    ```

    To stop Interactive Live Streaming, leave the channel and release the engine instance.

    ```dart
    // Leaves the channel and releases resources
    Future<void> _cleanupAgoraEngine() async {
     await _engine.leaveChannel();
     await _engine.release();
    }
    ```

    <CalloutContainer type="warning">
      <CalloutDescription>
        After you call `release`, you no longer have access to the methods and callbacks of the SDK. To use Interactive Live Streaming features again, create a new engine instance.
      </CalloutDescription>
    </CalloutContainer>

    ### Complete sample code [#complete-sample-code-6]

    A complete code sample demonstrating the basic process of real-time interaction is provided for your reference. To use the sample code, copy the following code to replace the entire contents of the `.dart` file in your project.

    <Accordions>
      <Accordion title="Complete sample code for real-time Interactive Live Streaming">
        ```dart
        import 'dart:async';
        import 'package:agora_rtc_engine/agora_rtc_engine.dart';
        import 'package:flutter/material.dart';
        import 'package:permission_handler/permission_handler.dart';

        void main() => runApp(const MyApp());

        // Fill in the app ID obtained from Agora console
        const appId = "<-- Insert app Id -->";
        // Fill in the temporary token generated from Agora Console
        const token = "<-- Insert token -->";
        // Fill in the channel name you used to generate the token
        const channel = "<-- Insert channel name -->";

        // Main App Widget
        class MyApp extends StatelessWidget {
         const MyApp({Key? key}) : super(key: key);

         @override
         Widget build(BuildContext context) {
          return const MaterialApp(
           home: _MainScreen(),
          );
         }
        }

        // Video Call Screen Widget
        class _MainScreen extends StatefulWidget {
         const _MainScreen({Key? key}) : super(key: key);

         @override
         _MainScreenScreenState createState() => _MainScreenScreenState();
        }

        class _MainScreenScreenState extends State<_MainScreen> {
         int? _remoteUid; // Stores remote user ID
         bool _localUserJoined = false; // Indicates if local user has joined the channel
         late RtcEngine _engine; // Stores Agora RTC Engine instance

         @override
         void initState() {
          super.initState();
          _startLiveStreaming();
         }

         // Initializes Agora SDK
         Future<void> _startLiveStreaming() async {
          await _requestPermissions();
          await _initializeAgoraVideoSDK();
          await _setupLocalVideo();
          _setupEventHandlers();
          await _joinChannel();
         }

         // Requests microphone and camera permissions
         Future<void> _requestPermissions() async {
          await [Permission.microphone, Permission.camera].request();
         }

         // Set up the Agora RTC engine instance
         Future<void> _initializeAgoraVideoSDK() async {
          _engine = createAgoraRtcEngine();
          await _engine.initialize(const RtcEngineContext(
           appId: appId,
           channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
          ));
         }

         // Enables and starts local video preview
         Future<void> _setupLocalVideo() async {
          await _engine.enableVideo();
          await _engine.startPreview();
         }

         // Register an event handler for Agora RTC
         void _setupEventHandlers() {
          _engine.registerEventHandler(
           RtcEngineEventHandler(
            onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
             debugPrint("Local user \${connection.localUid} joined");
             setState(() => _localUserJoined = true);
            },
            onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
             debugPrint("Remote user \$remoteUid joined");
             setState(() => _remoteUid = remoteUid);
            },
            onUserOffline: (RtcConnection connection, int remoteUid, UserOfflineReasonType reason) {
             debugPrint("Remote user \$remoteUid left");
             setState(() => _remoteUid = null);
            },
           ),
          );
         }

         // Join a channel
         Future<void> _joinChannel() async {
          await _engine.joinChannel(
           token: token,
           channelId: channel,
           options: const ChannelMediaOptions(
            autoSubscribeVideo: true,
            autoSubscribeAudio: true,
            publishCameraTrack: true,
            publishMicrophoneTrack: true,
            clientRoleType: ClientRoleType.clientRoleBroadcaster,
            audienceLatencyLevel: AudienceLatencyLevelType.audienceLatencyLevelUltraLowLatency

           ),
           uid: 0,
          );
         }

         @override
         void dispose() {
          _cleanupAgoraEngine();
          super.dispose();
         }

         // Leaves the channel and releases resources
         Future<void> _cleanupAgoraEngine() async {
          await _engine.leaveChannel();
          await _engine.release();
         }

         @override
         Widget build(BuildContext context) {
          return Scaffold(
           appBar: AppBar(title: const Text('Agora Interactive Live Streaming')),
           body: Stack(
            children: [
             Center(child: _remoteVideo()),
             Align(
              alignment: Alignment.topLeft,
              child: SizedBox(
               width: 100,
               height: 150,
               child: Center(
                child: _localUserJoined
                  ? _localVideo()
                  : const CircularProgressIndicator(),
               ),
              ),
             ),
            ],
           ),
          );
         }

          // Displays remote video view
         Widget _localVideo() {
          return AgoraVideoView(
           controller: VideoViewController(
            rtcEngine: _engine,
            canvas: const VideoCanvas(
             uid: 0,
             renderMode: RenderModeType.renderModeHidden,
            ),
           ),
          );
         }

         // Displays remote video view
         Widget _remoteVideo() {
          if (_remoteUid != null) {
           return AgoraVideoView(
            controller: VideoViewController.remote(
             rtcEngine: _engine,
             canvas: VideoCanvas(uid: _remoteUid),
             connection: const RtcConnection(channelId: channel),
            ),
           );
          } else {
           return const Text(
            'Waiting for remote user to join...',
            textAlign: TextAlign.center,
           );
          }
         }
        }
        ```
      </Accordion>
    </Accordions>

    <CalloutContainer type="info">
      <CalloutDescription>
        In the `appId` and `token` fields, enter the corresponding values you obtained from Agora Console. Use the same `channel` name you filled in when generating the temporary token.
      </CalloutDescription>
    </CalloutContainer>

    ### Create a user interface [#create-a-user-interface-6]

    To connect the sample code to your existing UI, ensure that your widget tree includes the `_remoteVideo` and `_localVideo` widgets used to [Display the local video](#display-the-local-video) and [Display remote video](#display-remote-video).

    Alternatively, use the following sample code to generate a basic user interface:

    **Sample code to create the user interface**

    ```dart
    // Build UI to display local video and remote video
    @override
    Widget build(BuildContext context) {
     return Scaffold(
      appBar: AppBar(title: const Text('Agora Video Call')),
      body: Stack(
       children: [
        Center(child: _remoteVideo()),
        Align(
         alignment: Alignment.topLeft,
         child: SizedBox(
          width: 100,
          height: 150,
          child: Center(
           child: _localUserJoined
             ? _localVideo()
             : const CircularProgressIndicator(),
          ),
         ),
        ),
       ],
      ),
     );
    }
    ```

    ## Test the sample code [#test-the-sample-code-6]

    Take the following steps to test the sample code:

    1. In `main.dart` update the values for `appId`, and `token` with values from Agora Console. Fill in the same `channel` name you used to generate the token.

    2. Connect a target device to your development computer.

    3. Open Terminal and execute the following command in the project folder to run the sample project:

       ```bash
       flutter run
       ```

    4. Launch the App, grant microphone and camera permissions. If you set the user role to host, you see yourself in the local view.

    5. On a second target device, repeat the previous steps to install and launch the app. Alternatively, use the [Web demo](https://webdemo.agora.io/basicVideoCall/index.html) to join the same channel and test the following use-cases:

       * If users on both devices join the channel as hosts, they can see and hear each other.
       * If one user joins as host and the other as audience, the host can see themselves in the local video window; the audience can see the host in the remote video window and hear the host.

       On an Android device, the app UI appears similar to the following:

       ![quickstart-ui-flutter.png](https://assets-docs.agora.io/images/video-sdk/quickstart-ui-flutter.png)

    ## Reference [#reference-6]

    This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.

    * If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.

    ### Next steps [#next-steps-6]

    After implementing the quickstart sample, read the following documents to learn more:

    * To ensure communication security in a test or production environment, best practice is to obtain and use a token from an authentication server. For details, see [Secure authentication with tokens](build/authenticate-users/use-tokens.mdx).

    ### Sample project [#sample-project-6]

    Agora provides open source sample projects on [GitHub](https://github.com/AgoraIO-Extensions/Agora-Flutter-SDK/tree/main/example/lib/examples) for your reference. Download or view [join\_channel\_video.dart](https://github.com/AgoraIO-Extensions/Agora-Flutter-SDK/tree/main/example/lib/examples/basic/join_channel_video) for a more detailed example.

    ### API reference [#api-reference-6]

    * [`enableVideo`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengine.html#api_irtcengine_enablevideo)

    * [`registerEventHandler`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengine.html#api_irtcengine_addhandler)

    * [`setClientRole`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengine.html#api_irtcengine_setclientrole2)

    * [`setChannelProfile`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengine.html#api_irtcengine_setchannelprofile)

    * [`startPreview`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengine.html#api_irtcengine_startpreview2)

    * [`AgoraVideoView`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_agoravideoview.html)

    * [`joinChannel`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengine.html#api_irtcengine_joinchannel2)

    * [`leaveChannel`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengine.html#api_irtcengine_leavechannel2)

    ### Frequently asked questions [#frequently-asked-questions-5]

    * [How can I fix black screen issues?](/en/api-reference/faq/quality/video_blank)

    * [Why can't I turn on the camera?](/en/api-reference/faq/quality/video_camera)

    * [How can I listen for audience joining or leaving a channel?](/en/api-reference/faq/integration/audience_event)

    * [How can I solve channel-related issues?](/en/api-reference/faq/integration/channel)

    * [How can I set the log file?](/en/api-reference/faq/integration/set_log_file)

    ### See also [#see-also-6]

    * [Error codes](/en/realtime-media/video/reference/error-codes)

    * [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)

    
  
      
  
      
  
      
  
      
  
      
  
      
  
