菊风云平台
菊风云平台一对一视频通话快速开始

快速开始

参考接入教程,快速了解开发流程

1. 前提条件

支持 macOS 10.10 或以上版本的 macOS 设备

有效的菊风云开发者账号 免费注册

2. 准备工作

开始之前,请先做好如下准备工作。

2.1 SDK 下载

点击 macOS SDK 进行下载。如果已经下载了 SDK,请直接进行 SDK 配置。

2.2 AppKey 获取

AppKey 是应用在 菊风云平台 中的唯一标识。需要在 SDK 初始化的时候使用,AppKey 获取请参考 创建应用

2.3 SDK 配置

只有完成 SDK 的配置,才可以集成 JC SDK 提供的功能,mac 端使用的是动态库,因此下面介绍动态库的配置方法。

如果您已经集成了多家音视频引擎,则推荐使用动态库。

在 Mac 环境下解压下载的压缩包,解压后的文件夹内有 JCSDK 文件夹。JCSDK 文件夹里包含了 JCSDKOC.framework。

2.3.1 拷贝文件

将 JC SDK 文件夹拷贝到您工程所在的目录下,如下图(仅供参考)

2.3.2 工程设置

2.3.2.1 导入文件和库

打开 Xcode(以 Xcode 11.0 为例),进入 TARGETS > Project Name > Build Phases > Link Binary with Libraries 菜单,点击 + 添加如下依赖的库。

进入 TARGETS > Project Name > General > Frameworks, Libraries, and Embedded Content 菜单,点击 + ,再点击 Add Other…,找到 JCSDKOC.framework、libmtc.dylib 和 libzmf.dylib 文件并添加,并将这些文件的状态改为 Embed & Sign。

2.3.2.2 设置 Framework Search Paths 路径

点击 ‘Build Settings’,找到 Framework Search Paths、Header Search Paths 和 Library Search Paths,在右侧输入路径。如下图:

在设置 Framework Search Paths 时,一般在完成第1步导入 JCSDKOC.framework 后,Xcode 会自动生成该路径 如果 Xcode 没有自动生成路径,用户要根据 JCSDKOC.framework、lib 和 include 文件所在目录,手动设置路径。
2.3.2.3 设置预处理宏定义

点击 ‘Build Settings’,找到 Preprocessor Macros,在右侧输入 ZPLATFORM=ZPLATFORM_OSX,如下图

2.3.2.4 设置 Other Linker Flags 的参数为 -ObjC

点击 ‘Build Settings’,找到 Other Linker Flags 并添加参数 -ObjC,如下图

若你的项目已启用 App Sandbox 或 Hardened Runtime 设置,则需勾选如下内容,获取相应的设备权限

2.3.3 权限设置

2.3.3.1 设置麦克风和摄像头的权限

点击 ‘Info’,然后添加麦克风和摄像头权限,如下图:

KeyTypeValue
Privacy - Microphone Usage DescriptionString使用麦克风的目的,如语音通话。
Privacy - Camera Usage DescriptionString使用摄像头的目的,如视频通话。
2.3.3.2 编译运行

以上步骤进行完后,编译工程,如果没有报错,恭喜您,您已经成功配置 SDK,可以进行下一步了。

2.4 SDK 初始化

在使用 SDK 之前,需要进行 SDK 的初始化。

在需要使用 JC SDK 的地方 #import <JCSDKOC/JCSDKOC.h> 。

初始化 SDK,具体接口如下

在初始化的时候还可以设置 SDK 信息存储目录,日志路径以及日志打印的等级,具体通过 createParam 参数设置,如果不设置则使用默认值。

/**
 * @brief 创建 JCClient 实例
 * @param appKey 用户从 Juphoon Cloud 平台上申请的 AppKey 字符串
 * @param callback 回调接口,用于接收 JCClient 相关通知
 * @param createParam 创建参数,nil 则按默认值创建
 * @return JCClient 对象
 */
+(JCClient* __nullable)create:(NSString* __nonnull)appKey callback:(id<JCClientCallback> __nonnull)callback creatParam:(JCClientCreateParam* __nullable)createParam;
appKey 为准备工作中“获取 AppKey”步骤中取得的 AppKey。如果还未获取 AppKey,请参考 创建应用 来获取。

示例代码

// 初始化各模块,因为这些模块实例将被频繁使用,建议声明在单例中
JCClientCreateParam *param = [[JCClientCreateParam alloc] init];
param.sdkLogLevel = JCLogLevelInfo;
param.sdkInfoDir = @"SDK 信息存放路径";
param.sdkLogDir = @"日志存放路径";
JCClient *client = [JCClient create:@"your appkey" callback:self creatParam:param];

SDK 初始化之后,即可进行登录的集成。

3. 登录

登录涉及 JCClient 类及其回调 JCClientCallback 类,其主要作用是负责登录、登出管理及帐号信息存储。

登录之前,可以通过 loginParam 登录参数进行登录的相关配置,如服务器地址的设置或者使用代理服务器登录,如不设置则按照默认值登录,具体如下:

JCClientLoginParam* loginParam = [[JCClientLoginParam alloc] init];
//默认国内环境 http:cn.router.justalkcloud.com:8080
loginParam.serverAddress = @"服务器地址";
//如果使用代理服务器登录
loginParam.httpsProxy = @"代理服务器地址";

其中,服务器地址包括国际环境服务器地址和国内环境服务器地址:

国际环境 服务器地址为 http:intl.router.justalkcloud.com:8080
国内环境 服务器地址为 http:cn.router.justalkcloud.com:8080

3.1 发起登录

调用下面的接口发起登录

/**
 * @brief 登陆 Juphoon Cloud 平台,只有登陆成功后才能进行平台上的各种业务
 * 服务器分为鉴权模式和非鉴权模式
 *
 *     - 鉴权模式: 服务器会检查用户名和密码
 *
 *     - 免鉴权模式: 只要用户保证用户标识唯一即可, 服务器不校验
 *
 * 登陆结果通过 JCClientCallback 通知
 *
 * @param userId 用户名
 * @param password 密码,免鉴权模式密码可以随意输入,但不能为空
 * @param loginParam 登录参数,nil则按照默认值登录
 * @return 返回 true 表示正常执行调用流程,false 表示调用异常,异常错误通过 JCClientCallback 通知
 * @warning 目前只支持免鉴权模式,免鉴权模式下当账号不存在时会自动去创建该账号
 * @warning 用户名为英文数字和'+' '-' '_' '.',长度不要超过64字符,'-' '_' '.'不能作为第一个字符
 */
-(bool)login:(NSString* __nonnull)userId password:(NSString* __nonnull)password loginParam:(JCClientLoginParam* __nullable)loginParam;
用户名大小写不敏感,用户名为英文、数字和’+’ ‘-‘ ‘_’ ‘.’,长度不要超过64字符,’-‘ ‘_’ ‘.’不能作为第一个字符。

示例代码

JCClientLoginParam* loginParam = [[JCClientLoginParam alloc] init];
//默认国内环境 http:cn.router.justalkcloud.com:8080
loginParam.serverAddress = @"服务器地址";
[JCManager.shared.client login:@"账号" password:@"123" loginParam:loginParam];

登录的结果通过 onlogin 回调上报

/**
 *  @brief 登陆结果回调
 *  @param result true 表示登陆成功,false 表示登陆失败
 *  @param reason 当 result 为 false 时该值有效
 *  @see JCClientReason
 */
-(void)onLogin:(bool)result reason:(JCClientReason)reason;

其中,JCClientReason 有

/// 正常
JCClientReasonNone,
/// sdk 未初始化
JCClientReasonSDKNotInit,
/// 无效的参数
JCClientReasonInvalidParam,
/// 函数调用失败
JCClientReasonCallFunctionError,
/// 当前状态无法再次登录
JCClientReasonStateCannotLogin,
/// 超时
JCClientReasonTimeOut,
/// 网络异常
JCClientReasonNetWork,
/// appkey 错误
JCClientReasonAppKey,
/// 账号密码错误
JCClientReasonAuth,
/// 无该用户
JCClientReasonNoUser,
/// 被强制登出
JCClientReasonServerLogout,
/// 其他错误
JCClientReasonOther,

登录成功之后,SDK 会自动保持与服务器的连接状态,直到用户主动调用登出接口,或者因为帐号在其他设备登录导致该设备登出。

3.2 登出

登出调用下面的接口,登出后不能进行平台上的各种业务操作

/**
 *  登出 Juphoon Cloud 平台,登出后不能进行平台上的各种业务
 *  @return 返回 true 表示正常执行调用流程,false 表示调用异常,异常错误通过 JCClientCallback 通知
 */
-(bool)logout;

登出结果通过 onlogout 回调上报

/**
 *  @brief 登出回调
 *  @param reason 登出原因
 *  @see JCClientReason
 */
-(void)onLogout:(JCClientReason)reason;

当登录状态发生改变时,会通过 onClientStateChange 回调上报

/**
 *  @brief 登录状态变化通知
 *  @param state    当前状态值
 *  @param oldState 之前状态值
 */
-(void)onClientStateChange:(JCClientState)state oldState:(JCClientState)oldState;

JCClientState 有

// 未初始化
JCClientStateNotInit,
// 未登录
JCClientStateIdle,
// 登录中
JCClientStateLogining,
// 登录成功
JCClientStateLogined,
// 登出中
JCClientStateLogouting,

示例代码

-(void)onClientStateChange:(JCClientState)state oldState:(JCClientState)oldState
{
    if (state == JCClientStateIdle) { // 未登录
       ...
    } else if (state == JCClientStateLogining) { // 登录中
       ...
    } else if (state == JCClientStateLogined) {  // 登录成功
       ...
    } else if (state == JCClientStateLogouting) {  // 登出中
       ...
    }
}

集成登录后,即可进行相关业务的集成。

4. 业务集成

一对一视频通话涉及以下类

名称 描述
JCCall 一对一通话类,包含一对一语音和视频通话功能
JCCallItem 通话对象类,此类主要记录通话的一些状态,UI 可以根据其中的状态进行显示逻辑
JCCallCallback 通话模块回调代理
JCMediaDevice 设备模块,主要用于视频、音频设备的管理
JCMediaDeviceVideoCanvas 视频对象,主要用于 UI 层视频显示、渲染的控制
JCMediaDeviceCallback 设备模块回调代理

更多接口的详细信息请参考 API 说明文档


接口调用逻辑和相关状态

说明:蓝色与黄色字表示接口,红色字表示通话状态。

通话方向(呼入或呼出)及通话状态(振铃、连接中、通话中等)可通过 JCCallItem 对象中的 directionstate 获得。

开始集成通话功能前,请先进行 模块的初始化

创建 JCMediaDevice 实例

/**
 *  @brief 创建 JCMediaDevice 对象
 *  @param client JCClient 对象
 *  @param callback JCMediaDeviceCallback 回调接口,用于接收 JCMediaDevice 相关通知
 *  @return 返回 JCMediaDevice 对象
 */
+(JCMediaDevice* __nullable)create:(JCClient* __nonnull)client callback:(id<JCMediaDeviceCallback> __nonnull)callback;

创建 JCCall 实例

/**
 *  @brief                  创建 JCCall 实例
 *  @param client           JCClient 实例
 *  @param mediaDevice      JCMediaDevice 实例
 *  @param callback         JCCallCallback 回调接口,用于接收 JCCall 相关回调事件
 *  @return                 返回 JCCall 实例
 */
+(JCCall* __nullable)create:(JCClient* __nonnull)client mediaDevice:(JCMediaDevice* __nonnull)mediaDevice callback:(id<JCCallCallback> __nonnull)callback;

示例代码

// 初始化各模块,因为这些模块实例将被频繁使用,建议声明在单例中
JCMediaDevice *mediaDevice = [JCMediaDevice create:client callback:self];
JCCall *call = [JCCall create:client mediaDevice:mediaDevice callback:self];

4.1 拨打通话

主叫调用下面的接口发起视频通话,此时 video 传入值为 true

/**
 *  @brief                  一对一呼叫
 *  @param userId           用户标识
 *  @param video            是否为视频呼叫
 *  @param extraParam       透传参数,被叫方可获取透传参数
 *  @return                 返回 true 表示正常执行调用流程,false 表示调用异常
 */
-(bool)call:(NSString* __nonnull)userId video:(bool)video extraParam:(NSString * __nullable)extraParam;
调用此接口会自动打开音频设备。
extraParam 为自定义透传字符串,被叫可通过 JCCallItem 对象中的 extraParam 属性获得。

通话发起后,主叫和被叫均会收到新增通话的回调,通话状态变为 JCCallStatePending

/**
 *  @brief 新增通话回调
 *  @param item JCCallItem 对象
 */
-(void)onCallItemAdd:(JCCallItem* __nonnull)item;

示例代码

-(void)onCallItemAdd:(JCCallItem* __nonnull)item {
    // 收到新增通话回调
}
如果主叫想取消通话,可以直接转到第4步,调用第4步中的挂断通话的接口。这种情况下调用挂断后,通话状态变为 JCCallStateCancel。

4.1.1 创建本地视频画面

通话发起后,即可调用 JCMediaDevice 类中的 startCameraVideo 方法打开本地视频预览,调用此方法会打开摄像头

/**
 *  @brief 获得预览视频对象,通过此对象能获得视图用于UI显示
 *  @param type 渲染模式,@ref JCMediaDeviceRender
 *  @return JCMediaDeviceVideoCanvas 对象
 */
-(JCMediaDeviceVideoCanvas* __nullable)startCameraVideo:(int)type;

其中,type(渲染模式)可以参考 JCMediaDeviceRender 的枚举值,具体如下

/// 视频图像按比例填充整个渲染区域(裁剪掉超出渲染区域的部分区域)
JCMediaDeviceRenderFullScreen = 0,
/// 视频图像的内容完全呈现到渲染区域(可能会出现黑边,类似放电影的荧幕)
JCMediaDeviceRenderFullContent,
/// 自动
JCMediaDeviceRenderFullAuto,
调用该方法后,在挂断通话或者关闭摄像头时需要对应调用 stopVideo 方法停止视频。

该方法采集分辨率默认值为 640*360,帧率为 30。默认打开的是前置摄像头。

如果想自定义摄像头采集参数,如采集的高度、宽度和帧速率,请参考 视频采集和渲染

示例代码

// 发起视频呼叫
[call call:@"peer number" video:true extraParam:@"自定义透传字符串"];
// 本地视频渲染
JCMediaDeviceVideoCanvas *localCanvas = [mediaDevice startCameraVideo:JCMediaDeviceRenderFullContent];
localCanvas.videoView.frame = CGRectMake(20, 20, 90, 160);
[self.view addSubview:localCanvas.videoView];

4.2 应答通话

被叫收到 onCallItemAdd 回调事件,并通过 JCCallItem 中的 video 属性以及 direction 属性值 JCCallDirectionIn 判断是视频呼入还是语音呼入,此时可以调用以下接口选择视频应答或者语音应答

/**
 *  @brief                  接听
 *  @param item             JCCallItem 对象
 *  @param video            针对视频呼入可以选择以视频接听还是音频接听
 *  @return                 返回 true 表示正常执行调用流程,false 表示调用异常
 */
-(bool)answer:(JCCallItem* __nonnull)item video:(bool)video;

如果被叫应答通话成功,双方都会收到 onCallItemUpdate 的回调。

示例代码:

-(void)onCallItemAdd:(JCCallItem*)item {
    // 如果是视频呼入且在振铃中
    if(item && item.state == JCCallStatePending) {
        if (item.direction == JCCallDirectionIn && item.video) {
             // 应答通话
             [call answer:item video:true];
        }
    }
}

通话应答后,通话状态变为 JCCallStateConnecting。

如果要拒绝通话,可以直接转到第4步,调用第4步中的挂断通话的接口。这种情况下调用挂断后,通话状态变为 JCCallStateCanceled。

4.3 通话建立

被叫接听通话后,双方将建立连接,此时,主叫和被叫都将会收到通话更新的回调(onCallItemUpdate)。连接成功之后,可以进行远端视频的渲染。

4.3.1 创建远端视频画面

远端视频画面的获取通过调用 JCMediaDevice 类中的 startVideo 方法实现

/**
 *  @brief 获得预览视频对象,通过此对象能获得视图用于UI显示
 *  @param videoSource 渲染标识串,比如 JCMediaChannelParticipant JCCallItem 中的 renderId,当videoSource 为 videoFileId 时,内部会调用 startVideoFile
 *  @param type        渲染模式,@ref JCMediaDeviceRender
 *  @return JCMediaDeviceVideoCanvas 对象
 */
-(JCMediaDeviceVideoCanvas* __nullable)startVideo:(NSString* __nonnull)videoSource renderType:(int)type;

其中,type(渲染模式)可以参考 JCMediaDeviceRender 的枚举值,具体如下

/// 视频图像按比例填充整个渲染区域(裁剪掉超出渲染区域的部分区域)
JCMediaDeviceRenderFullScreen = 0,
/// 视频图像的内容完全呈现到渲染区域(可能会出现黑边,类似放电影的荧幕)
JCMediaDeviceRenderFullContent,
/// 自动
JCMediaDeviceRenderFullAuto,
调用该方法后,在挂断通话或者关闭摄像头时需要对应调用 stopVideo 方法停止视频。

现在您可以进行一对一视频通话了。

示例代码

-(void)onCallItemUpdate:(JCCallItem*)item {
    // 如果对端在上传视频流(uploadVideoStreamOther)
    if (item.state == JCCallStateTalking && remoteCanvas == nil && item.uploadVideoStreamOther) {
        // 获取远端视频画面,renderId来源JCCallItem对象
        JCMediaDeviceVideoCanvas *remoteCanvas = [mediaDevice startVideo:item.renderId renderType:JCMediaDeviceRenderFullContent];
        remoteCanvas.videoView.frame = self.view.frame;
        [self.view addSubview:remoteCanvas.videoView];
    }
}

4.4 挂断通话

主叫或者被叫均可以挂断通话,首先调用下面的接口获取当前活跃的通话对象

/**
 * @brief 获得当前活跃通话
 *
 * @return 当前活跃通话,没有则返回nil
 */
-(JCCallItem* __nullable)getActiveCallItem;

当前活跃通话对象获取后,调用下面的方法挂断通话

/**
 *  @brief                  挂断
 *  @param item             JCCallItem 对象
 *  @param reason           挂断原因
 *  @param description      挂断描述
 *  @return                 返回 true 表示正常执行调用流程,false 表示调用异常
 *  @see JCCallReason
 */
-(bool)term:(JCCallItem* __nonnull)item reason:(JCCallReason)reason description:(NSString* __nullable)description;

示例代码

// 挂断通话
JCCallItem *item = [call getActiveCallItem];
[call term:item reason:JCCallReasonNone description:@"主叫挂断"];

4.4.1 销毁本地和远端视频画面

通话挂断后,还需要调用 stopVideo 接口移除视频画面

/**
 *  @brief 停止视频
 *  @param canvas JCMediaDeviceVideoCanvas 对象,由 startVideo 获得
 */
-(void)stopVideo:(JCMediaDeviceVideoCanvas* __nonnull)canvas;

通话挂断后,UI 会收到移除通话的回调,通话状态变为 JCCallStateOk

/**
 *  @brief 移除通话
 *  @param item JCCallItem 对象
 *  @param reason 通话结束原因
 *  @param description 通话结束原因的描述,只有被动挂断的时候,才会收到这个值,其他情况下则返回空字符串
 *  @see JCCallReason
 */
-(void)onCallItemRemove:(JCCallItem* __nonnull)item reason:(JCCallReason)reason description:(NSString * __nullable)description;

其中,reason 有以下几种

名称 描述
JCCallReasonNone 无异常
JCCallReasonNotLogin 未登录
JCCallReasonCallFunctionError 函数调用错误
JCCallReasonTimeOut 超时
JCCallReasonNetWork 网络错误
JCCallReasonCallOverLimit 超出通话上限
JCCallReasonTermBySelf 自己挂断
JCCallReasonAnswerFail 应答失败
JCCallReasonBusy
JCCallReasonDecline 拒接
JCCallReasonUserOffline 用户不在线
JCCallReasonNotFound 无此用户
JCCallReasonRejectVideoWhenHasCall 已有通话拒绝视频来电
JCCallReasonRejectCallWhenHasVideoCall 已有视频通话拒绝来电
JCCallReasonOther 其他错误

示例代码

-(void)onCallItemRemove:(JCCallItem* __nonnull)item reason:(JCCallReason)reason description:(NSString * __nullable)description { //移除通话回调
    // 界面处理
    if (_localCanvas) { // 本端视频销毁
        [mediaDevice stopVideo:_localCanvas];
        [_localCanvas.videoView removeFromSuperview];
        _localCanvas = nil;
    }
    if (_remoteCanvas) { // 远端视频销毁
        [mediaDevice stopVideo:_remoteCanvas];
        [_remoteCanvas.videoView removeFromSuperview];
        _remoteCanvas = nil;
    }
}

通话挂断的其他情况:

如果拨打通话时,对方未在线,或者主叫呼叫后立即挂断,则对方再次上线时会收到未接来电的回调

/**
 * @brief上报服务器拉取的未接来电
 * @param item JCCallItem 对象
 */
-(void)onMissedCallItem:(JCCallItem * __nonnull)item;

此时通话状态变为 JCCallStateMissed。