WinAv_zh - aopacloud/aopa-rtc GitHub Wiki

实现音视频互动

本文介绍如何集成奥帕实时互动 SDK,通过少量代码从 0 开始实现一个简单的实时互动 App,适用于互动直播和视频通话场景。

首先,你需要了解以下有关音视频实时互动的基础概念:

  • 奥帕实时互动 SDK:由奥帕开发的、帮助开发者在 App 中实现实时音视频互动的 SDK。
  • 频道:用于传输数据的通道,在同一个频道内的用户可以进行实时互动。
  • 主播:可以在频道内发布音视频,同时也可以订阅其他主播发布的音视频。
  • 观众:可以在频道内订阅音视频,不具备发布音视频权限。

下图展示在 App 中实现音视频互动的基本工作流程:

实现音视频互动

  1. 所有用户调用 joinChannel 方法加入频道,并根据需要设置用户角色:
    • 互动直播:如果用户需要在频道中发流,则设为主播;如果用户只需要收流,则设为观众。
    • 视频通话:将所有的用户角色都为主播。
  2. 加入频道后,不同角色的用户具备不同的行为:
    • 所有用户默认都可以接收频道中的音视频流。
    • 主播可以在频道内发布音视频流。
    • 观众如果需要发流,可在频道内调用 setClientRole 方法修改用户角色,使其具备发流权限。

前提条件​

在实现功能以前,请按照以下要求准备开发环境:

  • Windows 7 或以上版本的设备。

  • Microsoft Visual Studio 2017 或以上版本。

  • C++ 11 或以上版本。

  • 如果使用 C# 开发,还需要 .NET 桌面开发组件

  • 可以访问互联网的计算机。如果你的网络环境部署了防火墙,参考应对防火墙限制以正常使用奥帕服务。

  • 一个有效的奥帕账号以及奥帕项目。请参考[开通服务]从奥帕控制台获得以下信息:

    • App ID:奥帕随机生成的字符串,用于识别你的项目。
    • 临时 Token:Token 也称为动态密钥,在客户端加入频道时对用户鉴权。临时 Token 的有效期为 24 小时。
  • C++ 创建项目​


以 Windows 11 为例,参考以下操作在 Visual Studio 2019 中搭建一个 App 来实现实时音视频互动功能,如果你已有自己的项目,可跳过这一步骤。

创建 Windows 项目

  1. 在 Visual Studio 中,选择文件 > 新建 > 项目 来创建一个新项目。在弹出的窗口中,选择 MFC 应用作为项目模板,点击下一步将项目名称设为 AopaQuickStart 并设置项目储存位置,然后点击创建

  2. 在弹出的 MFC 应用程序窗口中,将应用程序类型设为基于对话框,将使用 MFC 设为在共享 DLL 中使用 MFC。进入生成的类,将生成的类设为 Dlg,将基类设为 CDialog,最后点击完成

集成 SDK​

  1. 从[下载]获取最新的 Windows SDK,解压并打开。
  2. 打开已下载的 SDK 文件,并将其中的 sdk 文件夹复制到你的项目路径下。保证 sdk 文件夹和你的 sln 文件处于同一目录。

配置项目属性​

解决方案资源管理器窗口中,右击项目名称并点击属性进行以下配置:

  • 进入 C/C++ > 常规 > 附加包含目录菜单,输入 $(SolutionDir)sdk\high_level_api\include

  • 进入链接器 > 常规 > 附加库目录菜单,输入 $(SolutionDir)sdk\x86_64

    信息

    如果你是 x86 的 Windows 操作系统,请输入 $(SolutionDir)sdk\x86

  • 进入链接器 > 输入 > 附加依赖项菜单,输入 $(SolutionDir)sdk\x86_64\aopa_rtc_sdk.dll.lib

    信息

    如果你是 x86 的 Windows 操作系统,请输入 $(SolutionDir)sdk\x86\aopa_rtc_sdk.dll.lib

  • 进入高级菜单,在高级属性中,把将内容复制到 OutDir将 C++ 运行时复制到输出目录设为

  • 进入生成事件 > 生成后事件 >命令行 菜单,输入 copy $(SolutionDir)sdk\x86_64\*.dll $(SolutionDir)$(Platform)\$(Configuration)

完成上述配置后点击应用

创建用户界面​

根据实时音视频互动的场景需要,你需要在你的应用中添加如下 UI:

  • 展示本地视频的视图框
  • 展示远端视频的视图框
  • 输入频道名称的输入框
  • 加入和离开频道按钮

你可以参考下列步骤来创建用户界面。

创建用户界面步骤

  1. 在右侧菜单栏将项目切换为资源视图,然后打开 .Dialog 文件,此时你的界面如下图所示: ui
  2. 添加展示远端视频的视图框。在视图 > 工具箱中,选择添加 Picture Control 控件,在属性 > 杂项中,将该控件的 ID 设为 IDC_STATIC_REMOTE,如下图所示: remote users
  3. 添加展示本地视频的视图框。在视图 > 工具箱中,选择添加 Picture Control 控件,在属性 > 杂项中,将该控件的 ID 设为 IDC_STATIC_LOCAL
  4. 添加输入频道名称的输入框。在视图 > 工具箱中,选择添加 Static Text 控件,在属性中将描述文字改为频道名。然后再添加一个 Edit Control 控件作为输入框,在属性 > 杂项中,将该控件的 ID 设为 IDC_EDIT_CHANNEL
  5. 添加加入和离开频道按钮。在视图 > 工具箱中,选择添加两个 Button 控件,在属性 > 杂项中,将控件的 ID 分设为 ID_BTN_JOINID_BTN_LEAVE,将描述文字分别设为加入离开。 此时的用户界面如下图所示: ui

实现流程​

本小节介绍如何实现一个实时音视频互动应用。你可以先复制完整的示例代码到你的项目中,快速体验实时音视频互动的基础功能,再按照实现步骤了解核心 API 调用。

下图展示了使用奥帕 RTC SDK 实现音视频互动的基本流程。

实现流程

以下展示实现实时互动基本流程的完整示例代码:

注意

复制完整示例代码到你的项目之后,你需要将代码中的 APP_IDtoken 替换成你在奥帕控制台获取的 App ID 和临时 Token。

AopaQuickStartDlg.h

C++

#pragma once  
#include <string>  
// 引入相关头文件  
#include <IBBRtcEngine.h>  
  
using namespace aopa;  
using namespace aopa::rtc;  
using namespace aopa::media;  
using namespace aopa::media::base;  

// 定义 CAopaQuickStartRtcEngineEventHandler 类,用于处理用户加入、离开频道等回调事件
class CAopaQuickStartRtcEngineEventHandler : public IRtcEngineEventHandler {
public:
    // 设置消息接收窗口的句柄
    void SetMsgReceiver(HWND hWnd) {
        m_hMsgHanlder = hWnd;
    }

    // 注册 onJoinChannelSuccess 回调,本地用户成功加入频道时,会触发该回调
    virtual void onJoinChannelSuccess(const char* channel, uid_t uid, int elapsed) {
        if (m_hMsgHanlder) {
            ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_JOIN_CHANNEL_SUCCESS), uid, 0);
        }
    }
    // 注册 onUserJoined 回调,远端主播成功加入频道时,会触发该回调
    virtual void onUserJoined(uid_t uid, int elapsed) {
        if (m_hMsgHanlder) {
            ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_USER_JOINED), uid, 0);
        }
    }
    // 注册 onUserOffline 回调,远端主播离开频道或掉线时,会触发该回调
    virtual void onUserOffline(uid_t uid, USER_OFFLINE_REASON_TYPE reason) {
        if (m_hMsgHanlder) {
            ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_USER_OFFLINE), uid, 0);
        }
    }

private:
    HWND m_hMsgHanlder;
};
// CAopaQuickStartDlg 对话框
class CAopaQuickStartDlg : public CDialog {
public:
    CAopaQuickStartDlg(CWnd* pParent = nullptr);
    virtual ~CAopaQuickStartDlg();

#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_AOPAQUICKSTART_DIALOG };
#endif
    // 处理加入/离开按钮点击事件
    afx_msg void OnBnClickedBtnJoin();
    afx_msg void OnBnClickedBtnLeave();
    // 处理用户加入频道、用户离开等回调事件
    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;
    CStatic m_staRemote;
    CStatic m_staLocal;
    CEdit m_edtChannelName;

    // DDX/DDV 支持
    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;
    CAopaQuickStartRtcEngineEventHandler m_eventHandler;
bool m_initialize = false;
bool m_remoteRender = false;
};

AopaQuickStartDlg.cpp

C++

// AopaQuickStartDlg.cpp: 实现文件
//
#include "pch.h"
#include "framework.h"
#include "AopaQuickStart.h"
#include "AopaQuickStartDlg.h"
#include "afxdialogex.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// 用于应用程序“关于”菜单项的 CAboutDlg 对话框
class CAboutDlg : public CDialogEx {
public:
    CAboutDlg();
    // 对话框数据
#ifdef AFX_DESIGN_TIME
    enum {
        IDD = IDD_ABOUTBOX
    }
    ;
#endif
protected:
    virtual void DoDataExchange(CDataExchange* pDX);
    // DDX/DDV 支持
    // 实现
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()
// CAopaQuickStartDlg 对话框,用于处理主要的用户交互和回调事件
CAopaQuickStartDlg::CAopaQuickStartDlg(CWnd* pParent
        /*=nullptr*/
                                        )
    : CDialog(IDD_AOPAQUICKSTART_DIALOG, pParent) {
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
CAopaQuickStartDlg::~CAopaQuickStartDlg() {
    CDialog::~CDialog();
    // 在删除 CAopaQuickStartDlg 对象时,释放引擎和相关资源
    if(m_rtcEngine) {
        m_rtcEngine->release(true);
        m_rtcEngine = NULL;
    }
}
void CAopaQuickStartDlg::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(CAopaQuickStartDlg, CDialog)
    // 声明消息映射,用于处理 Windows 消息和用户加入、离开频道等事件
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(ID_BTN_JOIN, &CAopaQuickStartDlg::OnBnClickedBtnJoin)
    ON_BN_CLICKED(ID_BTN_LEAVE, &CAopaQuickStartDlg::OnBnClickedBtnLeave)
    ON_MESSAGE(WM_MSGID(EID_JOIN_CHANNEL_SUCCESS), CAopaQuickStartDlg::OnEIDJoinChannelSuccess)
    ON_MESSAGE(WM_MSGID(EID_USER_JOINED), &CAopaQuickStartDlg::OnEIDUserJoined)
    ON_MESSAGE(WM_MSGID(EID_USER_OFFLINE), &CAopaQuickStartDlg::OnEIDUserOffline)
END_MESSAGE_MAP()
// CAopaQuickStartDlg 消息处理程序
// 填入你项目的 App ID,在声网控制台获取
#define "<APP_ID>"
// 填入你在声网控制台获取的临时 Token
#define "<token>"
BOOL CAopaQuickStartDlg::OnInitDialog() {
    CDialog::OnInitDialog();
    // 将“关于...”菜单项添加到系统菜单中
    // IDM_ABOUTBOX 必须在系统命令范围内
    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX < 0xF000);
    CMenu* pSysMenu = GetSystemMenu(FALSE);
    if (pSysMenu != nullptr) {
        BOOL bNameValid;
        CString strAboutMenu;
        bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
        ASSERT(bNameValid);
        if (!strAboutMenu.IsEmpty()) {
            pSysMenu->AppendMenu(MF_SEPARATOR);
            pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
        }
    }
    // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动执行此操作
    SetIcon(m_hIcon, TRUE);
    // 设置大图标
    SetIcon(m_hIcon, FALSE);
    // 设置小图
    m_eventHandler.SetMsgReceiver(m_hWnd);
    // 创建 rtcEngine 对象
    m_rtcEngine = createAopaRtcEngine();
    RtcEngineContext context;
    context.appId = APP_ID;
    context.eventHandler = &m_eventHandler;
    //初始化
    int ret = m_rtcEngine->initialize(context);
    if (ret == 0) {
        m_initialize = true;
    } else {
        m_initialize = false;
    }
    // 启用视频模块
    m_rtcEngine->enableVideo();
    return TRUE;
    // 除非将焦点设置到控件,否则返回 TRUE
}
void CAopaQuickStartDlg::OnSysCommand(UINT nID, LPARAM lParam) {
    if ((nID & 0xFFF0) == IDM_ABOUTBOX) {
        CAboutDlg dlgAbout;
        dlgAbout.DoModal();
    } else {
        CDialog::OnSysCommand(nID, lParam);
    }
}
// 如果向对话框添加最小化按钮,则需要下面的代码
// 来绘制该图标。对于使用文档/视图模型的 MFC 应用程序,这将由框架自动完成
void CAopaQuickStartDlg::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 CAopaQuickStartDlg::OnQueryDragIcon() {
    return static_cast<HCURSOR>(m_hIcon);
}
std::string CAopaQuickStartDlg::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 CAopaQuickStartDlg::OnBnClickedBtnJoin() {
    // 加入频道
    // 获取频道名
    CString strChannelName;
    m_edtChannelName.GetWindowText(strChannelName);
    if (strChannelName.IsEmpty()) {
        AfxMessageBox(_T("Fill channel name first"));
        return;
    }
    ChannelMediaOptions option;
    // 设置频道场景为直播
    option.channelProfile = CHANNEL_PROFILE_LIVE_BROADCASTING;
    // 设置用户角色为主播
    option.clientRoleType = CLIENT_ROLE_BROADCASTER;
    // 发布麦克风采集的音频流
    option.publishMicrophoneTrack = true;
    // 发布摄像头采集的视频流
    option.publishCameraTrack = true;
    // 自动订阅频道内的音频流
    option.autoSubscribeAudio = true;
    // 自动订阅频道内的视频流
    option.autoSubscribeVideo = true;
    // 填入你在控制台获取的临时 Token 加入频道
    int ret = m_rtcEngine->joinChannel(token, cs2utf8(strChannelName).c_str(), 0,option);
    // 渲染本地视图
    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 CAopaQuickStartDlg::OnBnClickedBtnLeave() {
    // 停止本地视频预览
    m_rtcEngine->stopPreview();
    // 离开频道
    m_rtcEngine->leaveChannel();
    // 清除本地视图
    VideoCanvas canvas;
    canvas.uid = 0;
    m_rtcEngine->setupLocalVideo(canvas);
    m_remoteRender = false;
}
LRESULT CAopaQuickStartDlg::OnEIDJoinChannelSuccess(WPARAM wParam, LPARAM lParam) {
    // 加入频道成功回调
    uid_t localUid = wParam;
    return 0;
}
LRESULT CAopaQuickStartDlg::OnEIDUserJoined(WPARAM wParam, LPARAM lParam) {
    // 远端用户加入回调
    uid_t remoteUid = wParam;
    if (m_remoteRender) {
        return 0;
    }
    // 渲染远端视图
    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 CAopaQuickStartDlg::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;
}

参考下列步骤来在你的应用中实现实时互动:

创建并初始化 IRtcEngine​

在调用其他奥帕 API 前,你需要调用 createAopaRtcEngine 创建一个IRtcEngine 对象,然后调用 initialize 并传入 App ID,初始化 IRtcEngine

C++

// 创建 IRtcEngine 对象  
m_rtcEngine = createAopaRtcEngine();  
  
// 创建 IRtcEngine 上下文对象  
RtcEngineContext context;  
  
// 输入你的 App ID。你可以在奥帕控制台获取你的项目的 App ID  
context.appId = APP_ID;  
  
// 添加注册回调和事件  
context.eventHandler = &m_eventHandler;  
  
// 初始化  
int ret = m_rtcEngine->initialize(context);  
if (ret == 0) {  
    m_initialize = true;} else {  
    m_initialize = false;}  

启用视频模块​

  1. 调用 enableVideo 方法,启用视频模块。
  2. 调用 setupLocalVideo 初始化本地视图,同时设置本地的视频显示属性。
  3. 调用 startPreview 方法,开启本地视频预览。

C++

// 启用视频模块  
m_rtcEngine->enableVideo();  
...  
  
// 设置本地视频显示属性  
VideoCanvas canvas;  
// 设置视频尺寸等比缩放  
canvas.renderMode = RENDER_MODE_TYPE::RENDER_MODE_HIDDEN;  
// 用户 ID  
canvas.uid = 0;  
// 视频显示窗口  
canvas.view = m_staLocal.GetSafeHwnd();  
m_rtcEngine->setupLocalVideo(canvas);  
// 开启本地视频预览  
m_rtcEngine->startPreview();  

加入频道并发布音视频流​

调用 joinChannel方法、填入你在控制台获取的临时 Token,以及获取 Token 时填入的频道名加入频道,并设置用户角色。

C++

void CAopaQuickStartDlg::OnBnClickedBtnJoin() {
    CString strChannelName;
    // 获取频道名
    m_edtChannelName.GetWindowText(strChannelName);
    if (strChannelName.IsEmpty()) {
        AfxMessageBox(_T("Fill channel name first"));
        return;
    }

    ChannelMediaOptions option;
    // 设置频道场景为直播场景
    option.channelProfile = CHANNEL_PROFILE_LIVE_BROADCASTING;
    // 设置用户角色为主播;如果要将用户角色设置为观众,保持默认值即可
    option.clientRoleType = CLIENT_ROLE_BROADCASTER;
    // 发布麦克风采集的音频流
    option.publishMicrophoneTrack = true;
    // 发布摄像头采集的视频流
    option.publishCameraTrack = true;
    // 自动订阅所有音频流
    option.autoSubscribeAudio = true;
    // 自动订阅所有视频流
    option.autoSubscribeVideo = true;
    // 填入你在控制台获取的临时 Token 加入频道
    m_rtcEngine->joinChannel(token, cs2utf8(strChannelName).c_str(), 0, option);
}

设置远端视图​

当远端用户加入频道时,从远端用户加入频道的回调中获取到的远端用户的 uid,然后调用 setupRemoteVideo 设置并渲染远端视图。

C++

void CAopaQuickStartDlg::OnBnClickedBtnJoin() {
    CString strChannelName;
    // 获取频道名
    m_edtChannelName.GetWindowText(strChannelName);
    if (strChannelName.IsEmpty()) {
        AfxMessageBox(_T("Fill channel name first"));
        return;
    }

    ChannelMediaOptions option;
    // 设置频道场景为直播场景
    option.channelProfile = CHANNEL_PROFILE_LIVE_BROADCASTING;
    // 设置用户角色为主播;如果要将用户角色设置为观众,保持默认值即可
    option.clientRoleType = CLIENT_ROLE_BROADCASTER;
    // 发布麦克风采集的音频流
    option.publishMicrophoneTrack = true;
    // 发布摄像头采集的视频流
    option.publishCameraTrack = true;
    // 自动订阅所有音频流
    option.autoSubscribeAudio = true;
    // 自动订阅所有视频流
    option.autoSubscribeVideo = true;
    // 填入你在控制台获取的临时 Token 加入频道
    m_rtcEngine->joinChannel(token, cs2utf8(strChannelName).c_str(), 0, option);
}

实现常用回调​

你可以根据需求,在初始化时实现其他功能,如注册用户加入、离开频道等回调。

C++

class CAopaQuickStartRtcEngineEventHandler : public IRtcEngineEventHandler {
public:
    // 注册 onJoinChannelSuccess 回调
    // 本地用户成功加入频道时,会触发该回调
    virtual void onJoinChannelSuccess(const char* channel, uid_t uid, int elapsed) {
        if (m_hMsgHanlder) {
            ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_JOIN_CHANNEL_SUCCESS), uid, 0);
        }
    }

    // 注册 onUserJoined 回调
    // 远端主播成功加入频道时,会触发该回调
    virtual void onUserJoined(uid_t uid, int elapsed) {
        if (m_hMsgHanlder) {
            ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_USER_JOINED), uid, 0);
        }
    }

    // 注册 onUserOffline 回调
    // 远端主播离开频道或掉线时,会触发该回调
    virtual void onUserOffline(uid_t uid, USER_OFFLINE_REASON_TYPE reason) {
        if (m_hMsgHanlder) {
            ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_USER_OFFLINE), uid, 0);
        }
    }

private:
    HWND m_hMsgHanlder;
};

离开频道​

根据场景需要,如关闭应用或应用切换至后台时,调用 leaveChannel 离开当前频道。

C++

class CAopaQuickStartRtcEngineEventHandler : public IRtcEngineEventHandler {
public:
    // 注册 onJoinChannelSuccess 回调
    // 本地用户成功加入频道时,会触发该回调
    virtual void onJoinChannelSuccess(const char* channel, uid_t uid, int elapsed) {
        if (m_hMsgHanlder) {
            ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_JOIN_CHANNEL_SUCCESS), uid, 0);
        }
    }

    // 注册 onUserJoined 回调
    // 远端主播成功加入频道时,会触发该回调
    virtual void onUserJoined(uid_t uid, int elapsed) {
        if (m_hMsgHanlder) {
            ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_USER_JOINED), uid, 0);
        }
    }

    // 注册 onUserOffline 回调
    // 远端主播离开频道或掉线时,会触发该回调
    virtual void onUserOffline(uid_t uid, USER_OFFLINE_REASON_TYPE reason) {
        if (m_hMsgHanlder) {
            ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_USER_OFFLINE), uid, 0);
        }
    }

private:
    HWND m_hMsgHanlder;
};

如果你不再需要互动,调用 release 方法释放引擎资源。

C++

CDialog::~CDialog() {
    // 在对象被销毁时释放资源
    if(m_rtcEngine){
       m_rtcEngine->release(true);
       m_rtcEngine = NULL;
    }
}

测试应用​

按照以下步骤来测试你的视频通话项目:

  1. 将你从奥帕控制台获取的 App ID 和临时 Token 分别填入到 Form1.cs 文件的 APP_IDAPP_TOKEN 中。
  2. 在 Visual Studio 中点击 Start 按钮运行你的项目,你将在本地视图中看到自己。
  3. 在输入框中输入你在奥帕控制台获取的频道名,并点击 Join 按钮加入频道。
  4. 邀请一位朋友通过另一台设备来使用相同的 App ID、频道名、Token 加入频道。你的朋友加入频道后,你们可以听见、看见对方。 图片 后续步骤​

  • 在测试或生产环境中,为确保通信安全,奥帕推荐使用 Token 服务器来生成 Token,详见[使用 Token 鉴权]
  • 如果你想要实现极速直播场景,可以在互动直播的基础上,通过修改观众端的延时级别为低延时 (AUDIENCE_LATENCY_LEVEL_LOW_LATENCY)实现。详见[实现极速直播]

相关信息​

示例项目​

奥帕提供了开源的音视频互动示例项目供你参考,你可以前往下载或查看其中的源代码。

C++

⚠️ **GitHub.com Fallback** ⚠️