本文档将介绍如何在您的Harmony项目中集成 ARTC SDK, 快速实现一个简单的实时音视频互动App,适用于互动直播和视频通话等场景。
功能简介
在开始之前,了解以下几个关键概念会很有帮助:
ARTC SDK:这是阿里云的实时音视频产品,帮助开发者快速实现实时音视频互动的SDK。
GRTN:阿里云全球实时传输网络,提供超低延时、高音质、安全可靠的音视频通讯服务。
频道:相当于一个虚拟的房间,所有加入同一频道的用户都可以进行实时音视频互动。
主播:可在频道内发布音视频流,并可订阅其他主播发布的音视频流。
观众:可在频道内订阅音视频流,不能发布音视频流。
实现实时音视频互动的基本流程如下:
用户需要调用
setChannelProfile(设置频道场景),后调用joinChannel加入频道:视频通话场景:所有用户都是主播角色,可以进行推流和拉流。
互动直播场景:需要调用
setClientRole(设置角色),在频道内推流的用户设置主播角色;如果用户只需要拉流,不需要推流,则设置观众角色。
加入频道后,不同角色的用户有不同的推拉流行为:
所有加入频道内的用户都可以接收频道内的音视频流。
主播角色可以在频道内推音视频流。
观众如果需要推流,需要调用
setClientRole方法,将用户角色切换成主播,便可以推流。
示例项目
阿里云ARTC SDK提供了开源的实时音视频互动示例项目供客户参考,您可以前往下载或查看示例源码。
前提条件
获取 DevEco Studio 5.0.3.900 Release 或以上版本。
获取配套 API Version 12的 HarmonyOS NEXT SDK 或以上版本。
获取配套 API Version 12的 HarmonyOS NEXT 5.0.0.102 操作系统或以上版本,支持音视频的鸿蒙设备,且已开启“允许调试”选项。
如果需要使用真机调试,请参考 鸿蒙官网文档 进行配置。
鸿蒙设备已经连接到 Internet。
已 注册华为开发者账号 并完成实名认证。
创建项目(可选)
打开 DevEco-Studio,选择 Create Project。
选择 Application 并选择一个初始模板,在此以 Empty Ability 为例。

配置项目信息,包含项目名、包名、项目保存路径、SDK 版本等信息。

点击 Finish 完成创建,等待项目同步完成。
集成SDK
ohpm自动集成(推荐)
在entry下的oh-package.json文件中配置:
"dependencies": {
"@aliyun_video_cloud/alivcsdk_artc":"x.y.z",
}运行命令:
ohpm install @aliyun_video_cloud/alivcsdk_artc下载SDK手动集成
在SDK下载中下载最新版本的Harmony ARTC SDK ,放到工程libs中。在工程中配置引用:
"dependencies": {
"@aliyun_video_cloud/alivcsdk_artc":"file:./libs/AliVCSDK_ARTC-x.y.z.har",
}
实现步骤
本节介绍如何使用阿里云 ARTC SDK 快速实现一个基础的实时音视频互动应用。你可以先将完整示例代码复制到项目,快速体验功能,再通过以下步骤了解核心 API 的调用。
下图展示了实现音视频互动的基本流程:
1、申请权限请求
进入entry/src/main/ets/entryability目录,打开EntryAbiliy.ets文件,添加所需权限。
import { abilityAccessCtrl, AbilityConstant, common, Permissions, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
const permissions: Array<Permissions> = ['ohos.permission.MICROPHONE','ohos.permission.CAMERA','ohos.permission.KEEP_BACKGROUND_RUNNING'];
// 使用UIExtensionAbility:将common.UIAbilityContext 替换为common.UIExtensionContext
function reqPermissionsFromUser(permissions: Array<Permissions>, context: common.UIAbilityContext): void {
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
// requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
atManager.requestPermissionsFromUser(context, permissions).then((data) => {
let grantStatus: Array<number> = data.authResults;
let length: number = grantStatus.length;
for (let i = 0; i < length; i++) {
if (grantStatus[i] === 0) {
// 用户授权,可以继续访问目标操作
} else {
// 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
return;
}
}
// 授权成功
}).catch((err: BusinessError) => {
console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
})
}
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
}
onDestroy(): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
reqPermissionsFromUser(permissions, this.context);
windowStage.loadContent('pages/Login', (err) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
});
}
onWindowStageDestroy(): void {
// Main window is destroyed, release UI related resources
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground(): void {
// Ability has brought to foreground
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground(): void {
// Ability has back to background
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
}
}
2、鉴权Token
加入ARTC频道需要一个鉴权Token,用于鉴权用户的合法身份,其鉴权Token生成规则详情请参见:Token鉴权。Token 生成有两种方式:单参数方式和多参数方式,不同的Token生成方式需要调用SDK不同的加入频道(joinChannel)的接口。
上线发布阶段:
由于Token的生成需要使用AppKey,写死在客户端存在泄露的风险,因此强烈建议线上业务通过业务Server生成下发给客户端。
开发调试阶段:
开发调试阶段,如果业务Server还没有生成Token的逻辑,可以暂时参考APIExample上的Token生成逻辑,生成临时Token,其参考代码如下:
import util from '@ohos.util';
import { TokenParams } from 'configmanager/src/main/ets/common/Constants';
export class TokenJsonUtils {
/**
* 构建Token JSON对象
* @param params 参数对象
* @returns JSON字符串
*/
static buildTokenJson(params: TokenParams): string {
const jsonObj: TokenParams = {
appid: params.appid,
channelid: params.channelid,
userid: params.userid,
nonce: params.nonce,
timestamp: params.timestamp,
token: params.token
};
return JSON.stringify(jsonObj);
}
/**
* 将JSON字符串进行Base64编码
* @param jsonString JSON字符串
* @returns Base64编码字符串
*/
static async encodeJsonToBase64(jsonString: string): Promise<string> {
try {
const encoder = new util.TextEncoder();
const data = encoder.encodeInto(jsonString);
const base64 = new util.Base64();
const encoded = await base64.encodeToString(data);
return encoded.replace(/\n/g, '').replace(/\r/g, '');
} catch (error) {
console.error('Base64编码失败:', error);
return '';
}
}
/**
* 完整的Token生成流程
* @param params Token参数
* @returns Base64编码的Token JSON
*/
static generateCompleteToken(params: TokenParams): Promise<string> {
// 1. 构建JSON
const jsonString = TokenJsonUtils.buildTokenJson(params);
console.log('生成的JSON:', jsonString);
// 2. Base64编码
const base64String = TokenJsonUtils.encodeJsonToBase64(jsonString);
console.log('Base64编码结果:', base64String);
return base64String;
}
}3、导入ARTC SDK 相关类
导入 ARTC SDK 相关的类和接口:
import {
AliRtcEngine,
AliRtcVideoEncoderConfiguration,
AliRtcEngineAuthInfo,
AliRtcEngineEventListener,
AliRtcChannelProfile,
AliRtcClientRole,
AliRtcAudioProfile,
AliRtcAudioScenario,
AliRtcVideoMirrorMode,
AliRtcRotationMode,
AliRtcVideoEncoderOrientationMode,
AliRtcVideoTrack,
AliRtcVideoCanvas,
AliRtcRenderMode,
AliRtcRenderMirrorMode,
AliRtcXComponentController
} from '@aliyun_video_cloud/alivcsdk_artc';4、创建并初始化引擎
创建RTC引擎
调用getInstance接口创建引擎AliRTCEngine
private rtcEngine: AliRtcEngine | null | undefined;
this.rtcEngine = AliRtcEngine.getInstance('', this.context);初始化引擎
调用
setChannelProfile设置频道为AliRTCSdkInteractiveLive(互动模式)。根据具体的业务需求,可以选择适用于互动娱乐场景的互动模式,或者适合一对一或一对多广播的通信模式。正确的模式选择能够确保用户体验的流畅性并有效利用网络资源。您可以根据业务场景选择合适的模式。
模式
推流
拉流
模式介绍
互动模式
有角色限制,只能被赋予主播身份的用户可进行推流操作。
在整个过程中,参与者可以灵活地切换角色。
无角色限制所有参与者都拥有拉流的权限。
在互动模式中,主播加入或退出会议、以及开始推送直播流的事件都会实时通知给观众端,确保观众能够及时了解主播的动态。反之,观众的任何活动不会通告给主播,保持了主播的直播流程不受干扰。
在互动模式下,主播角色负责进行直播互动,而观众角色则主要接收内容,通常不参与直播的互动过程。若业务需求未来可能发生变化,导致不确定是否需要支持观众的互动参与,建议默认采用互动模式。这种模式具有较高的灵活性,可通过调整用户角色权限来适应不同的互动需求。
通信模式
无角色限制,所有参与者都拥有推流权限。
无角色限制,所有参与者都拥有拉流的权限。
在通信模式下,会议参与者能够相互察觉到彼此的存在。
该模式虽然没有区分用户角色,但实际上与互动模式中的主播角色相对应;目的是为了简化操作,让用户能够通过调用更少的API来实现所需的功能。
调用
setClientRole设置用户角色为AliRTCSdkInteractive(主播)或者AliRTCSdkLive(观众)。说明主播角色默认推拉流,观众角色默认关闭预览和推流,只拉流。
当用户在频道内切换角色时,系统会相应调整音视频流的推流状态:
从主播切换为观众(“下麦”):系统将停止推送本地音视频流,但已订阅的远端流不受影响,用户仍可继续观看其他人的音视频。
从观众切换为主播(“上麦”):系统将开始推送本地音视频流,同时已订阅的远端流保持不变,用户可以继续观看其他参与者的内容。
// 设置频道模式为互动模式,RTC下都使用AliRTCSdkInteractiveLive this.rtcEngine.setChannelProfile(AliRtcChannelProfile.AliEngineInteractiveLive); // 设置用户角色,既需要推流也需要拉流使用AliRTCSdkInteractive, 只拉流不推流使用AliRTCSdkLive this.rtcEngine.setClientRole(AliRtcClientRole.AliEngineClientRoleInteractive);
设置常用的回调
SDK 在运行过程中如遇到异常情况,会优先尝试内部重试机制以自动恢复。对于无法自行解决的错误,SDK 会通过预定义的回调接口通知您的应用程序。
以下是一些 SDK 无法处理、需由应用层监听和响应的关键回调:
异常发生原因
回调及参数
解决方案
说明
鉴权失败
onJoinChannel 回调中的result返回AliRtcErrJoinBadToken
发生错误时App需要检查Token是否正确。
在用户主动调用API时,若鉴权失败,系统将在调用API的回调中返回鉴权失败的错误信息。
鉴权将要过期
onWillAuthInfoExpire
发生该异常时App需要重新获取最新的鉴权信息后,再调用refreshAuthInfo刷新鉴权信息。
鉴权过期错误在两种情况下出现:用户调用API或程序执行期间。因此,错误反馈将通过API回调或通过独立的错误回调通知。
鉴权过期
onAuthInfoExpired
发生该异常时App需要重新入会。
鉴权过期错误在两种情况下出现:用户调用API或程序执行期间。因此,错误反馈将通过API回调或通过独立的错误回调通知。
网络连接异常
onConnectionStatusChange回调返回AliRtcConnectionStatusFailed。
发生该异常时APP需要重新入会。
SDK具备一定时间断网自动恢复能力,但若断线时间超出预设阈值,会触发超时并断开连接。此时,App应检查网络状态并指导用户重新加入会议。
被踢下线
onBye
AliRtcOnByeUserReplaced:当发生该异常时排查用户userid是否相同。AliRtcOnByeBeKickedOut:当发生该异常时,表示被业务踢下线,需要重新入会。AliRtcOnByeChannelTerminated:当发生该异常时,表示房间被销毁,需要重新入会。
RTC服务提供了管理员可以主动移除参与者的功能。
本地设备异常
onLocalDeviceException
发生该异常时App需要检测权限、设备硬件是否正常。
RTC服务支持设备检测和异常诊断的能力;当本地设备发生异常时,RTC服务会通过回调的方式通知客户本地设备异常,此时,若SDK无法自行解决问题,则App需要介入以查看设备是否正常。
const listener = new AliRtcEngineEventListener()
listener.onJoinChannel((resultCode: number, channel: string, elapsed: string) => {
console.info(`加入频道结果: result=${resultCode}, channel=${channel}, userId=${this.UserId}, elapsed=${elapsed}`);
const resultText = resultCode === 0
? `User ${this.UserId} Join ${channel} Success`
: `Join ${this.UserId} Join ${channel} Failed!,error: ${resultCode}`;
prompt.showToast({
message: resultText,
duration: 2000
});
})
.onLeaveChannel((resultCode: number) => {
console.info(`离开频道结果: result=${resultCode}`);
prompt.showToast({
message: 'Leave Channel',
duration: 2000
});
})
// 设置回调
this.rtcEngine.setRtcEngineEventListener(listener);5、设置音视频属性
设置音频相关属性
调用setAudioProfile设置音频的编码模式和音频场景
this.rtcEngine.setAudioProfile(
AliRtcAudioProfile.AliEngineHighQualityMode,
AliRtcAudioScenario.AliEngineSceneDefaultMode
);设置视频相关属性
可以设置推出去的视频流的分辨率、码率、帧率等信息。
// 设置视频编码配置
const videoConfig: AliRtcVideoEncoderConfiguration = new AliRtcVideoEncoderConfiguration();
videoConfig.dimensions.width = 640;
videoConfig.dimensions.height = 480;
videoConfig.frameRate = 20;
videoConfig.bitrate = 1200;
videoConfig.keyFrameInterval = 2000;
videoConfig.orientationMode = AliRtcVideoEncoderOrientationMode.AliEngineVideoEncoderOrientationModeAdaptive;
videoConfig.min_bitrate = 0;
videoConfig.forceStrictKeyFrameInterval = 0;
videoConfig.mirrorMode = AliRtcVideoMirrorMode.AliEngineVideoMirrorModeDisabled;
videoConfig.rotationMode = AliRtcRotationMode.AliEngineRotationMode_0;
this.rtcEngine.setVideoEncoderConfiguration(videoConfig);6、设置推拉流属性
设置推送音视频流及默认拉所有用户的流:
调用
publishLocalAudioStream推送音频流调用
publishLocalVideoStream推送视频流,如果是语音通话,可以设置成false
// 发布本地音频流
this.rtcEngine.publishLocalAudioStream(true);
// 发布本地视频流
this.rtcEngine.publishLocalVideoStream(true);
// 设置默认订阅所有远端音视频流
this.rtcEngine.setDefaultSubscribeAllRemoteAudioStreams(true);
this.rtcEngine.setDefaultSubscribeAllRemoteVideoStreams(true);
// 明确订阅所有远端音视频流
this.rtcEngine.subscribeAllRemoteAudioStreams(true);
this.rtcEngine.subscribeAllRemoteVideoStreams(true);说明
SDK默认是自动推拉流模式,默认会推送音视频流及订阅频道内所有用户的音视频流,可以通过调用上面的接口关闭自动推拉流模式。
7、开启本地预览
调用
setLocalViewConfig设置本地渲染视图,同时设置本地的视频显示属性。调用
startPreview方法,开启本地视频预览。
try {
// 设置本地视图配置
if (this.aliRtcVideoCanvas) {
this.rtcEngine.setLocalViewConfig(
this.aliRtcVideoCanvas,
this.componentController,
AliRtcVideoTrack.AliEngineVideoTrackCamera
);
}
// 开始本地视频预览
this.rtcEngine.startPreview();
this.ShowPreview = true;
console.info('本地预览已启动');
} catch (error) {
console.error('启动预览失败:', error);
}8、加入频道
调用joinChannel加入频道,如果token是单参数规则生成的,需要调用SDK单参数的joinChannelWithToken接口,如果是多参数规则生成的,需要调用SDK多参数的joinChannel接口。调用完加入频道后,可以在onJoinChannelResult回调中拿到加入频道结果,如果result为0,则表示加入频道成功,否则需要检查传进来的Token是否非法。
this.rtcEngine.joinChannelWithToken(token, null, null, 'username');说明
入会后会按照入会前设定的参数执行相应的推流和拉流。
SDK默认会自动推拉流,以减少客户端需要调用的API数量。
9、设置远端视图
在初始化引擎的时候设置对应回调mAliRtcEngine.setRtcEngineNotify,需要在onRemoteTrackAvailableNotify回调中,为远端用户设置远端视图,示例代码如下:
// 获取XComponent的surfaceId
stream.surfaceId = stream.xcomponentController.getXComponentSurfaceId();
// 配置视频画布
if (!stream.canvas) {
stream.canvas = new AliRtcVideoCanvas();
}
// 设置surfaceId
stream.canvas.surfaceId = stream.surfaceId;
stream.canvas.renderMode = AliRtcRenderMode.AliRtcRenderModeAuto;
stream.canvas.mirrorMode = AliRtcRenderMirrorMode.AliRtcRenderMirrorModeAllNoMirror;
// 设置远程视图配置
this.rtcEngine.setRemoteViewConfig(
stream.canvas,
this.componentController,
stream.uid,
AliRtcVideoTrack.AliRtcVideoTrackCamera
);10、离开房间并销毁引擎
音视频互动结束,需要离开房间并销毁引擎,按照下列步骤结束音视频互动
调用
stopPreview停止视频预览。调用
leaveChannel离会。
private void destroyRtcEngine() {
// 停止预览
this.rtcEngine.stopPreview();
// 离开频道
this.rtcEngine.leaveChannel();
// 销毁
AliRtcEngine.destroyInstance();
this.rtcEngine = null;
}