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

> 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

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

* A device running Windows 7 or higher.

* Microsoft Visual Studio 2017 or higher with [C++ desktop development](https://devblogs.microsoft.com/cppblog/windows-desktop-development-with-c-in-visual-studio/) support. C++ 11 or above.

* If you use C# for development, you also need the [.NET framework](https://learn.microsoft.com/en-us/dotnet/framework/install/guide-for-developers).

* 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

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

**Create a new project**
The following steps outline the process to set up a new Visual Studio 2022 project on Windows 11 for implementing real-time audio and video interaction functions.

1. In Visual Studio, select **File > New > Project** to create a new project. In the pop-up window, select **MFC application** as the project template, click **Next**, update the project name to `AgoraQuickStart`, set the project storage location, and then click **Create**.

2. In the pop-up MFC application window, set the application type to **Dialog-based** , and set **Use MFC** to **Use MFC in a shared DLL**. Enter the generated class, set the generated class to **Dlg**, set the base class to **CDialog**, and click **Finish**.

**Add to an existing project**
To integrate real-time audio and video interaction into your project:

3. Launch Visual Studio 2022 and open your existing project by selecting **File > Open > Project/Solution**.

4. Navigate to your project directory and open the `.sln` file.

5. Create a user-interface for your app based on your application use-case.

A basic UI consists of the following controls:

* A Picture Control for displaying local video
* A Picture Control for displaying remote video
* An Edit Control for entering a channel name
* Buttons to join and leave a channel

Refer to [Create a user interface](#create-a-user-interface) to get a bare bones sample layout.

### Install the SDK

Install the Agora Video SDK:

1. Download the latest Windows [SDK](/en/api-reference/sdks?product=video\&platform=windows).

2. Unzip and open the downloaded SDK. Copy all subfolders in `sdk/` to your solution folder. Make sure these subfolders are in the same directory as your `.sln` file.

#### Configure the project

In the Solution Explorer window, right-click the project name and click **Properties** to configure the following:

1. Go to the **C/C++ > General > Additional Include Directory** menu, click **Edit**, and in the pop-up window enter `$(SolutionDir)sdk\high_level_api\include`.
2. Go to the **Linker > General > Additional Library Directory** menu, click **Edit**, and in the pop-up window:
   * for 64 bit Windows, enter `$(SolutionDir)sdk\x86_64`.
   * for x86 Windows, enter `$(SolutionDir)sdk\x86`.
3. Go to the **Linker > Input > Additional Dependencies** menu, click **Edit**, and in the pop-up window:
   * for 64 bit Windows, enter `$(SolutionDir)sdk\x86_64\agora_rtc_sdk.dll.lib`.
   * for x86 Windows, enter `$(SolutionDir)sdk\x86\agora_rtc_sdk.dll.lib`.
4. Enter the **Advanced** menu, and in **Advanced Properties**, set **Copy contents to OutDir** and **Copy C++ runtime to output directory** to `Yes`.
5. Go to the **Build Events > Post-Build Events > Command Line** menu and enter `copy $(SolutionDir)sdk\x86_64\*.dll $(SolutionDir)$(Platform)\$(Configuration)`.
6. Click **Apply** to save the configuration.

## Implement Interactive Live Streaming

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.

### Initialize the engine

For real-time communication, initialize an `IRtcEngine` instance and set up event handlers to manage user interactions within the channel. Use `RtcEngineContext` to specify the [App ID](manage-agora-account.md), and custom [event handler](#subscribe-to--events), then call `initialize` to initialize the engine, enabling further channel operations. Add the following code to your header and `.cpp` files:

```cpp
// Declare the required variables
IRtcEngine* m_rtcEngine = nullptr; // RTC engine instance
CAgoraQuickStartRtcEngineEventHandler m_eventHandler; // IRtcEngineEventHandler
```

```cpp
void CAgoraQuickStartDlg::initializeAgoraEngine() {
  // Create IRtcEngine object
  m_rtcEngine = createAgoraRtcEngine();
  // Create IRtcEngine context object
  RtcEngineContext context;
  // Input your App ID. You can obtain your project's App ID from the Agora Console
  context.appId = APP_ID;
  // Add event handler for callbacks and events
  context.eventHandler = &m_eventHandler;
  // Initialize
  int ret = m_rtcEngine->initialize(context);
  m_initialize = (ret == 0);

  if (m_initialize) {
    // Enable the video module
    m_rtcEngine->enableVideo();
  } else {
    AfxMessageBox(_T("Failed to initialize Agora RTC engine"));
  }
}
```

### Join a channel

To join a channel, call `joinChannel`\[2/2] 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 `channelProfile` to `CHANNEL_PROFILE_LIVE_BROADCASTING`, the `clientRoleType` to `CLIENT_ROLE_BROADCASTER` (host) or `CLIENT_ROLE_AUDIENCE`, and the `audienceLatencyLevel` to `AUDIENCE_LATENCY_LEVEL_ULTRA_LOW_LATENCY`.

```cpp
void CAgoraQuickStartDlg::joinChannel(const char* token, const char* channelName) {
  ChannelMediaOptions options;
  // Set channel profile to live broadcasting
  options.channelProfile = CHANNEL_PROFILE_LIVE_BROADCASTING;
  // Set user role to broadcaster; keep default value if setting user role to audience
  options.clientRoleType = CLIENT_ROLE_BROADCASTER;
  // Publish the audio stream captured by the microphone
  options.publishMicrophoneTrack = true;
  // Publish the camera track
  options.publishCameraTrack = true;
  // Automatically subscribe to all audio streams
  options.autoSubscribeAudio = true;
  // Automatically subscribe to all video streams
  options.autoSubscribeVideo = true;
  // Specify the audio latency level
  options.audienceLatencyLevel = AUDIENCE_LATENCY_LEVEL_ULTRA_LOW_LATENCY;
  // Join the channel using the token and channel name (both are const char*)
  m_rtcEngine->joinChannel(token, channelName, 0, options);
}
```

### Subscribe to Video SDK events

The Video SDK provides an interface for subscribing to channel events. To use it, create a custom event handler class by inheriting from `IRtcEngineEventHandler` and override its methods to handle real-time interaction events. Add callbacks to receive notification of users joining and leaving the channel.

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

```cpp
// Define the CAgoraQuickStartRtcEngineEventHandler class to handle callback events such as users joining and leaving the channel
class CAgoraQuickStartRtcEngineEventHandler
  : public IRtcEngineEventHandler {
  public:
    CAgoraQuickStartRtcEngineEventHandler()
      : m_hMsgHandler(nullptr) {
    }

    // Set the handle of the message receiving window
    void SetMsgReceiver(HWND hWnd) {
      m_hMsgHandler = hWnd;
    }

    // Register onJoinChannelSuccess callback
    // This callback is triggered when a local user successfully joins a channel
    virtual void onJoinChannelSuccess(const char* channel, uid_t uid, int elapsed) {
      if (m_hMsgHandler) {
        ::PostMessage(m_hMsgHandler, WM_MSGID(EID_JOIN_CHANNEL_SUCCESS), uid, 0);
      }
    }

    // Register onUserJoined callback
    // This callback is triggered when the remote host successfully joins the channel
    virtual void onUserJoined(uid_t uid, int elapsed) {
      if (m_hMsgHandler) {
        ::PostMessage(m_hMsgHandler, WM_MSGID(EID_USER_JOINED), uid, 0);
      }
    }

    // Register onUserOffline callback
    // This callback is triggered when the remote host leaves the channel or is offline
    virtual void onUserOffline(uid_t uid, USER_OFFLINE_REASON_TYPE reason) {
      if (m_hMsgHandler) {
        ::PostMessage(m_hMsgHandler, WM_MSGID(EID_USER_OFFLINE), uid, 0);
      }
    }

  private:
    HWND m_hMsgHandler;
};
```

Implement the callback functions.

```cpp
LRESULT CAgoraQuickStartDlg::OnEIDJoinChannelSuccess(WPARAM wParam, LPARAM lParam) {
  // Join channel success callback
  uid_t localUid = wParam;
  return 0;
}

LRESULT CAgoraQuickStartDlg::OnEIDUserJoined(WPARAM wParam, LPARAM lParam) {
  // Remote user joined callback
  uid_t remoteUid = wParam;
  if (m_remoteRender) {
    return 0;
  }
  // Render remote view
  VideoCanvas canvas;
  canvas.renderMode = RENDER_MODE_TYPE::RENDER_MODE_HIDDEN;
  canvas.uid = remoteUid;
  canvas.view = m_staRemote.GetSafeHwnd();
  m_rtcEngine->setupRemoteVideo(canvas);
  m_remoteRender = true;
  return 0;
}

LRESULT CAgoraQuickStartDlg::OnEIDUserOffline(WPARAM wParam, LPARAM lParam) {
  // Remote user left callback
  uid_t remoteUid = wParam;
  if (!m_remoteRender) {
    return 0;
  }
  // Clear remote view
  VideoCanvas canvas;
  canvas.uid = remoteUid;
  m_rtcEngine->setupRemoteVideo(canvas);
  m_remoteRender = false;
  return 0;
}
```

### Enable the video module

Call the `enableVideo` method to enable the video module.

```cpp
// Enable the video module
m_rtcEngine->enableVideo();
```

### Display the local video

Follow these steps to set up and start the local video preview:

1. Create a `VideoCanvas` instance and configure its properties:
2. Set the video rendering mode.
3. Specify the user ID (`uid`).
4. Define the display window.
5. Call the `setupLocalVideo` method to apply the `VideoCanvas` configuration.
6. Call the `startPreview` method to start the local video preview.

   ```cpp
   void CAgoraQuickStartDlg::setupLocalVideo() {
     // Set local video display properties
     VideoCanvas canvas;
     // Set video to be scaled proportionally
     canvas.renderMode = RENDER_MODE_TYPE::RENDER_MODE_HIDDEN;
     // User ID
     canvas.uid = 0;
     // Video display window
     canvas.view = m_staLocal.GetSafeHwnd();
     m_rtcEngine->setupLocalVideo(canvas);
     // Preview the local video
     m_rtcEngine->startPreview();
   }
   ```

### Display remote video

To display the remote user's video:

1. Define the video display properties using `VideoCanvas`.
2. Call `setupRemoteVideo` to render the video.

   ```cpp
   void CAgoraQuickStartDlg::setupRemoteVideo(uid_t remoteUid) {
     // Set remote video display properties
     VideoCanvas canvas;
     // Set video size to be proportionally scaled
     canvas.renderMode = RENDER_MODE_TYPE::RENDER_MODE_HIDDEN;
     // Remote user ID
     canvas.uid = remoteUid;
     // You can only choose to set either view or surfaceTexture. If both are set, only the settings in view take effect.
     canvas.view = m_staRemote.GetSafeHwnd();
     m_rtcEngine->setupRemoteVideo(canvas);
     m_remoteRender = true;
     return 0;
   }
   ```

### Leave the channel

When a user ends a call, or closes the app call `leaveChannel` to exit the current channel.

To stop the local video preview and clear the view:

1. Call `stopPreview` to stop playing the local video.
2. Call `setupLocalVideo`, passing an empty `VideoCanvas` to reset the view.

   ```cpp
   void CAgoraQuickStartDlg::LeaveChannel() {
     if (m_rtcEngine) {
       // Stop local video preview
       m_rtcEngine->stopPreview();
       // Leave the channel
       m_rtcEngine->leaveChannel();
       // Clear local view
       VideoCanvas canvas;
       canvas.uid = 0;
       m_rtcEngine->setupLocalVideo(canvas);
       m_remoteRender = false;
     }
   }
   ```

### Release resources

To destroy the engine instance, call `release` :

```cpp
// Release resources when the object is destroyed
m_rtcEngine->release(true);
m_rtcEngine = NULL;
```

<CalloutContainer type="warning">
  <CalloutDescription>
    After you call `Dispose`, you can no longer use any SDK methods or callbacks. To use real-time Interactive Live Streaming again, you must create a new engine. For more information, see [Initialize the Engine](#initialize-the-engine).
  </CalloutDescription>
</CalloutContainer>

### Complete sample code

A complete code sample that implements the basic process of real-time interaction is presented here for your reference. Copy the sample code into your project to quickly implement the basic functions of real-time interaction.

**AgoraQuickStartDlg.h**

<Accordions>
  <Accordion title="Complete sample code for real-time Interactive Live Streaming">
    ```cpp
    #pragma once
    #include
    // Import related header files
    #include
    using namespace now;
    using namespace agora::rtc;
    using namespace agora::media;
    using namespace agora::media::base;

    // Define the message ID
    #define WM_MSGID(code) (WM_USER+0x200+code)
    #define EID_JOIN_CHANNEL_SUCCESS    0x00000002
    #define EID_USER_JOINED    0x00000004
    #define EID_USER_OFFLINE    0x00000004

    // Define the CAgoraQuickStartRtcEngineEventHandler class to handle callback events such as users joining and leaving the channel
    class CAgoraQuickStartRtcEngineEventHandler
      : public IRtcEngineEventHandler {
      public:
        CAgoraQuickStartRtcEngineEventHandler()
          : m_hMsgHandler(nullptr) {
        }

        // Set the handle of the message receiving window
        void SetMsgReceiver(HWND hWnd) {
          m_hMsgHandler = hWnd;
        }

        // Register onJoinChannelSuccess callback
        // This callback is triggered when a local user successfully joins a channel
        virtual void onJoinChannelSuccess(const char* channel, uid_t uid, int elapsed) {
          if (m_hMsgHandler) {
            ::PostMessage(m_hMsgHandler, WM_MSGID(EID_JOIN_CHANNEL_SUCCESS), uid, 0);
          }
        }

        // Register onUserJoined callback
        // This callback is triggered when the remote host successfully joins the channel
        virtual void onUserJoined(uid_t uid, int elapsed) {
          if (m_hMsgHandler) {
            ::PostMessage(m_hMsgHandler, WM_MSGID(EID_USER_JOINED), uid, 0);
          }
        }

        // Register onUserOffline callback
        // This callback is triggered when the remote host leaves the channel or is offline
        virtual void onUserOffline(uid_t uid, USER_OFFLINE_REASON_TYPE reason) {
          if (m_hMsgHandler) {
            ::PostMessage(m_hMsgHandler, WM_MSGID(EID_USER_OFFLINE), uid, 0);
          }
        }

      private:
        HWND m_hMsgHandler;
    };

    // CAgoraQuickStartDlg dialog box
    class CAgoraQuickStartDlg : public CDialog
    {
      // Construction
      public:
        CAgoraQuickStartDlg(CWnd* pParent = nullptr); // Standard constructor
        virtual ~CAgoraQuickStartDlg();

        // Dialog box data
      #ifdef AFX_DESIGN_TIME
        enum { IDD = IDD_AGORAQUICKSTART_DIALOG };
      #endif

        // Handle the join/leave button click event
        afx_msg void OnBnClickedBtnJoin();
        afx_msg void OnBnClickedBtnLeave();
        // Handle callback events such as user joining/user leaving
        afx_msg LRESULT OnEIDJoinChannelSuccess(WPARAM wParam, LPARAM lParam);
        afx_msg LRESULT OnEIDUserJoined(WPARAM wParam, LPARAM lParam);
        afx_msg LRESULT OnEIDUserOffline(WPARAM wParam, LPARAM lParam);

      protected:
        HICON m_hIcon;

        CEdit m_edtChannelName;

        // DDX/DDV support
        virtual void DoDataExchange(CDataExchange* pDX);
        virtual BOOL OnInitDialog();
        afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
        afx_msg void OnPaint();
        afx_msg HCURSOR OnQueryDragIcon();
        DECLARE_MESSAGE_MAP()

        std::string cs2utf8(CString str);

      private:
        IRtcEngine* m_rtcEngine = nullptr;
        CAgoraQuickStartRtcEngineEventHandler m_eventHandler;
        bool m_initialize = false;
        bool m_remoteRender = false;
    };
    ```

    **AgoraQuickStartDlg.cpp**

    ```cpp
    #include "pch.h"
    #include "framework.h"
    #include "AgoraQuickStart.h"
    #include "AgoraQuickStartDlg.h"
    #include "afxdialogex.h"

    #ifdef _DEBUG
    #define new DEBUG_NEW
    #endif

    // CAboutDlg dialog used for App "About" menu item
    class CAboutDlg : public CDialogEx {
    public:
      CAboutDlg();

      // Dialog Data
    #ifdef AFX_DESIGN_TIME
      enum { IDD = IDD_ABOUTBOX };
    #endif

    protected:
      virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support

      // Implementation
    protected:
      DECLARE_MESSAGE_MAP()
    };

    CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX) {}

    void CAboutDlg::DoDataExchange(CDataExchange* pDX) {
      CDialogEx::DoDataExchange(pDX);
    }

    BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
    END_MESSAGE_MAP()

    // CAgoraQuickStartDlg dialog for handling main user interactions and callback events
    CAgoraQuickStartDlg::CAgoraQuickStartDlg(CWnd* pParent /*=nullptr*/)
      : CDialog(IDD_AGORAQUICKSTART_DIALOG, pParent) {
      m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
    }

    CAgoraQuickStartDlg::~CAgoraQuickStartDlg() {
      if (m_rtcEngine) {
        m_rtcEngine->release(true);
        m_rtcEngine = NULL;
      }
    }

    void CAgoraQuickStartDlg::DoDataExchange(CDataExchange* pDX) {
      CDialog::DoDataExchange(pDX);
      DDX_Control(pDX, IDC_EDIT_CHANNEL, m_edtChannelName);
      DDX_Control(pDX, IDC_STATIC_REMOTE, m_staRemote);
      DDX_Control(pDX, IDC_STATIC_LOCAL, m_staLocal);
    }

    BEGIN_MESSAGE_MAP(CAgoraQuickStartDlg, CDialog)
      ON_WM_SYSCOMMAND()
      ON_WM_PAINT()
      ON_WM_QUERYDRAGICON()
      ON_BN_CLICKED(ID_BTN_JOIN, &CAgoraQuickStartDlg::OnBnClickedBtnJoin)
      ON_BN_CLICKED(ID_BTN_LEAVE, &CAgoraQuickStartDlg::OnBnClickedBtnLeave)
      ON_MESSAGE(WM_MSGID(EID_JOIN_CHANNEL_SUCCESS), &CAgoraQuickStartDlg::OnEIDJoinChannelSuccess)
      ON_MESSAGE(WM_MSGID(EID_USER_JOINED), &CAgoraQuickStartDlg::OnEIDUserJoined)
      ON_MESSAGE(WM_MSGID(EID_USER_OFFLINE), &CAgoraQuickStartDlg::OnEIDUserOffline)
    END_MESSAGE_MAP()

    // Insert your project's App ID obtained from the Agora Console
    #define APP_ID "<YOUR_APP_ID>"

    // Insert the temporary token obtained from the Agora Console
    #define token "<YOUR_TOKEN>"

    void CAgoraQuickStartDlg::initializeAgoraEngine() {
      m_rtcEngine = createAgoraRtcEngine();
      RtcEngineContext context;
      context.appId = APP_ID;
      context.eventHandler = &m_eventHandler;

      int ret = m_rtcEngine->initialize(context);
      m_initialize = (ret == 0);

      if (m_initialize) {
        // Enable the video module
        m_rtcEngine->enableVideo();
      } else {
        AfxMessageBox(_T("Failed to initialize Agora RTC engine"));
      }
    }

    BOOL CAgoraQuickStartDlg::OnInitDialog() {
      CDialog::OnInitDialog();

      // Add "About..." menu item to the system menu
      CMenu* pSysMenu = GetSystemMenu(FALSE);
      if (pSysMenu != nullptr) {
        CString strAboutMenu;
        strAboutMenu.LoadString(IDS_ABOUTBOX);
        if (!strAboutMenu.IsEmpty()) {
          pSysMenu->AppendMenu(MF_SEPARATOR);
          pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
        }
      }

      // Set the dialog icons
      SetIcon(m_hIcon, TRUE); // Set big icon
      SetIcon(m_hIcon, FALSE); // Set small icon

      // Set the message receiver
      m_eventHandler.SetMsgReceiver(m_hWnd);

      // Initialize Agora engine
      initializeAgoraEngine();

      return TRUE; // Unless focus is set to a control, return TRUE
    }

    void CAgoraQuickStartDlg::OnSysCommand(UINT nID, LPARAM lParam) {
      if ((nID & 0xFFF0) == IDM_ABOUTBOX) {
        CAboutDlg dlgAbout;
        dlgAbout.DoModal();
      } else {
        CDialog::OnSysCommand(nID, lParam);
      }
    }

    void CAgoraQuickStartDlg::OnPaint() {
      if (IsIconic()) {
        CPaintDC dc(this);
        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);

        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        dc.DrawIcon(x, y, m_hIcon);
      } else {
        CDialog::OnPaint();
      }
    }

    HCURSOR CAgoraQuickStartDlg::OnQueryDragIcon() {
      return static_cast<HCURSOR>(m_hIcon);
    }

    std::string CAgoraQuickStartDlg::cs2utf8(CString str) {
      char szBuf[2 * MAX_PATH] = { 0 };
      WideCharToMultiByte(CP_UTF8, 0, str.GetBuffer(0), str.GetLength(), szBuf, 2 * MAX_PATH, NULL, NULL);
      return szBuf;
    }

    void CAgoraQuickStartDlg::joinChannel(const char* token, const char* channelName) {
      ChannelMediaOptions options;
      options.channelProfile = CHANNEL_PROFILE_LIVE_BROADCASTING;
      options.clientRoleType = CLIENT_ROLE_BROADCASTER;
      options.publishMicrophoneTrack = true;
      options.publishCameraTrack = true;
      options.autoSubscribeAudio = true;
      options.autoSubscribeVideo = true;
      options.audienceLatencyLevel = AUDIENCE_LATENCY_LEVEL_ULTRA_LOW_LATENCY;

      m_rtcEngine->joinChannel(token, channelName, 0, options);
    }

    void CAgoraQuickStartDlg::setupRemoteVideo() {
      VideoCanvas canvas;
      canvas.renderMode = RENDER_MODE_TYPE::RENDER_MODE_HIDDEN;
      canvas.uid = remoteUid;
      canvas.view = m_staRemote.GetSafeHwnd();
      m_rtcEngine->setupRemoteVideo(canvas);
      m_remoteRender = true;
    }

    void CAgoraQuickStartDlg::setupLocalVideo() {
      VideoCanvas canvas;
      canvas.renderMode = RENDER_MODE_TYPE::RENDER_MODE_HIDDEN;
      canvas.uid = 0;
      canvas.view = m_staLocal.GetSafeHwnd();
      m_rtcEngine->setupLocalVideo(canvas);
      m_rtcEngine->startPreview();
    }

    void CAgoraQuickStartDlg::OnBnClickedBtnJoin() {
      CString strChannelName;
      m_edtChannelName.GetWindowText(strChannelName);

      if (strChannelName.IsEmpty()) {
        AfxMessageBox(_T("Fill channel name first"));
        return;
      }

      joinChannel(token, CW2A(strChannelName));
      setupLocalVideo();
    }

    void CAgoraQuickStartDlg::OnBnClickedBtnLeave() {
      m_rtcEngine->leaveChannel();

      VideoCanvas canvas;
      canvas.uid = 0;
      m_rtcEngine->setupLocalVideo(canvas);
      m_rtcEngine->stopPreview();
      m_remoteRender = false;
    }

    LRESULT CAgoraQuickStartDlg::OnEIDJoinChannelSuccess(WPARAM wParam, LPARAM lParam) {
      uid_t localUid = wParam;
      return 0;
    }

    LRESULT CAgoraQuickStartDlg::OnEIDUserJoined(WPARAM wParam, LPARAM lParam) {
      uid_t remoteUid = wParam;

      if (m_remoteRender) {
        return 0;
      }

      setupRemoteVideo();
      return 0;
    }

    LRESULT CAgoraQuickStartDlg::OnEIDUserOffline(WPARAM wParam, LPARAM lParam) {
      uid_t remoteUid = wParam;

      if (!m_remoteRender) {
        return 0;
      }

      VideoCanvas canvas;
      canvas.uid = remoteUid;
      m_rtcEngine->setupRemoteVideo(canvas);
      m_remoteRender = false;
      return 0;
    }
    ```
  </Accordion>
</Accordions>

### Create a user interface

To connect the sample code to your existing user interface, ensure that your UI includes the controls used to [Display the local video](#display-the-local-video) and [Display remote video](#display-remote-video).

Alternatively, follow these steps to create a bare-bones UI for your project.

**Steps to create a minimalistic UI**

1. Switch the project to resource view in the right menu bar, and then open the `.Dialog` file.

2. From **View > Toolbox**, select Add **Picture Control**, and in **Properties > Miscellaneous**, set the ID of the control to `IDC_STATIC_REMOTE`.

3. From **View > Toolbox** , select Add **Picture Control** , and in **Properties > Miscellaneous** , set the control's ID to `IDC_STATIC_LOCAL`.

4. To set up an input box for entering the channel name, from **View > Toolbox** , select add **Static Text** control, and change the description text to `Channel name` in the properties. Add an **Edit Control** as an input box, and in **Properties > Miscellaneous**, set the control's ID to `IDC_EDIT_CHANNEL`.

5. To add join and leave channel buttons, open **View > Toolbox** and add two **Button** controls. In **Properties > Miscellaneous** , set the IDs to `ID_BTN_JOIN` and `ID_BTN_LEAVE`, and set the description text to **Join** and **Leave** respectively. Your user interface looks similar to the following:

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

Follow these steps to create a bare-bones UI for your project.

## Test the sample code

To test your app, follow these steps:

1. In Visual Studio, select local Windows debugger to start compiling the application.

2. Enter the name of the channel you want to join in the input box and click the **Join** button to join the channel.

You see yourself in the local view.

3. 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.

## Reference

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

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

Agora provides open source sample projects on [GitHub](https://github.com/AgoraIO/API-Examples/tree/main/windows/APIExample/) for your reference.

* Download or view the [sample project](https://github.com/AgoraIO/API-Examples/tree/main/windows/APIExample/APIExample/Basic/LiveBroadcasting) for a more detailed example.
* For a Windows C# implementation, see [this sample project](https://github.com/AgoraIO-Extensions/Agora-C_Sharp-SDK/tree/master/APIExample/src/Basic/JoinChannelVideo).

### API Reference

* [`createAgoraRtcEngine`](https://api-ref.agora.io/en/video-sdk/cpp/4.x/API/class_irtcengine.html#api_createagorartcengine)

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

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

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

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

* [`release`](https://api-ref.agora.io/en/video-sdk/cpp/4.x/API/class_irtcengine.html#api_irtcengine_release)

### Frequently asked questions

* [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

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

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

    
  
      
  
      
  
      
  
      
  
      
  
      
  
      
  
      
  
