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

快速开始

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

1. 前提条件

Android SDK API 等级 16 或以上;

支持 Android 4.1 或以上版本的移动设备。

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

2. 准备工作

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

2.1 SDK 下载

点击 Android SDK 进行下载。

2.2 AppKey 获取

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

2.3 SDK 配置

打开下载的 JC SDK,目录如下:

/style/images/document/instructions/androidlib.png

注解

从 2.1 版本开始,下载的 SDK 中包含 JCCloudWrapper.jar,该库是为IM(消息)服务的,如果需要集成 IM 消息,则需要在工程中添加 JCCloudWrapper.jar;如果不需要集成 IM 消息,则无需集成该库。

JuphoonCommon.aar 是为界面提供 http 账号管理功能的模块。

有两种方式集成 JC SDK:

方法一:使用 JCenter 自动集成

注解

该方法仅适用于 2.0 及以上版本的 JC SDK 集成。

在项目的 /app/build.gradle 文件中,添加如下行:

...
dependencies {
    ...
    // 可通过 SDK 发版说明取得最新版本号
    implementation 'com.JuphoonCloud:JC-SDK:2.1'
}

方法二:手动导入 JC SDK

  1. 下载 SDK,拷贝 libs 文件夹内的 armeabi-v7a、X86、mtc.jar 、JCSDK.jar 和 zmf.jar 到您工程目录中的 libs 目录下,并打开工程,如下图所示:
/style/images/document/instructions/quickstart_android1.png
  1. 为能连接到我们的 so 库,在您工程 build.gradle 文件中确保增加以下配置,如图:
/style/images/document/instructions/set_sdk_android2.png

3. 修改您工程中 Application 配置文件 AndroidManifest.xml,请确保已经加入以下特性和权限信息。具体信息可以参考 Android 权限说明

<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

注解

您在 AndroidManifest 中进行权限配置时,请确保您能够获得打开摄像头、音视频录制等相关权限。

  1. 设置混淆规则

在 proguard-rules.pro 文件,将 SDK 相关类加入不混淆名单:

-dontwarn com.juphoon.*
-keep class com.juphoon.**{*;}
//底层sdk
-dontwarn com.justalk.*
-keep class com.justalk.**{*;}
-keepattributes InnerClasses
-keep class **.R$* {*;}
			
  1. 配置完成后编译运行,如果没有报错,恭喜您,您已经成功配置 SDK,可以进行 SDK 初始化了。
SDK 不支持模拟器运行,请使用真机。

2.4 SDK 初始化

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

在实现初始化的文件中实现 JCClientCallback 回调,用于接收 JCClient 相关通知。

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

CreateParam 类有以下属性

/**
 * SDK 相关信息存放目录,包括账号信息,日志信息等,默认为 sdcard/Android/data/包名/files 下
 */
public String sdkInfoDir;
/**
 * SDK 日志目录,如果不设置则默认在 sSdkInfoDir 下的 log 目录中
 */
public String sdkLogDir;
/**
 * 是否内部自动加载so库,默认为 true,如果上层需要自己加载则设置为 false
 */
public boolean needLoadLibrary = true;
/**
 * sdk 日志等级
 */
public @LogLevel int sdkLogLevel;

日志等级 sdkLogLevel 有以下几种

/**
 * disable日志等级
 */
public static final int LOG_LEVEL_DISABLE = 0;
/**
 * error日志等级
 */
public static final int LOG_LEVEL_ERROR = 1;
/**
 * info日志等级
 */
public static final int LOG_LEVEL_INFO = 2;
/**
 * debug日志等级
 */
public static final int LOG_LEVEL_DEBUG = 3;

调用下面的接口初始化 SDK

/**
 * 创建 JCClient 实例
 *
 * @param context   上下文句柄
 * @param appKey    用户从 Juphoon Cloud 平台上申请的 AppKey 字符串
 * @param callback    回调接口,用于接收 JCClient 相关通知
 * @param createParam 创建参数,null 则按默认值创建
 * @return JCClient 对象
 */
public static JCClient create(@NonNull Context context, @NonNull String appKey, @NonNull JCClientCallback callback, CreateParam createParam) {
appKey 为准备工作中“获取 AppKey”步骤中取得的 AppKey。如果还未获取 AppKey,请参考 创建应用 来获取。

示例代码

public boolean initialize(Context context) {
    // 初始化各模块,因为这些模块实例将被频繁使用,建议声明在单例中
    JCClient.CreateParam createParam = new JCClient.CreateParam(this);
    createParam.sdkLogLevel = LOG_LEVEL_INFO;
    createParam.sdkInfoDir = "SDK 信息存放路径";
    createParam.sdkLogDir = "日志存放路径";
    JCClient client = JCClient.create(Context, "your appkey", this, createParam);
    return true;
}

SDK 初始化之后,可以调用下面的方法获取创建参数

/**
 * 创建参数
 * @return 创建参数
 */
public abstract CreateParam getCreateParam();

示例代码

JCClient.CreateParam createParam = client.getCreateParam();
			

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

3. 登录

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

登录之前,可以通过配置关键字进行登录的相关配置,如是否使用代理服务器登录以及服务器地址的设置,具体如下。

3.1 登录环境设置

服务器地址设置,包括国际环境服务器地址和国内环境服务器地址

JCClient.LoginParam loginParam = new JCClient.LoginParam();
//默认国内环境 http:cn.router.justalkcloud.com:8080
loginParam.serverAddress = "服务器地址";
//如果使用代理服务器登录
loginParam.httpsProxy = "代理服务器地址";
国际环境 服务器地址为 http:intl.router.justalkcloud.com:8080
国内环境 服务器地址为 http:cn.router.justalkcloud.com:8080

开发者可以使用自定义服务器地址。

还可以通过 displayName 属性设置昵称

/**
 * 设置昵称,用于通话,消息等,可以更直观的表明身份
 * @param displayName 昵称
 */
public abstract void setDisplayName(@NonNull String displayName);

示例代码

client.setDisplayName("小张");
			

3.2 发起登录

/**
 * 登陆 Juphoon Cloud 平台,只有登陆成功后才能进行平台上的各种业务<br>
 * 登陆结果通过 JCCallCallback 通知<br>
 * 注意:用户名为英文数字和'+' '-' '_' '.',长度不要超过64字符,'-' '.' '_'字符不能处于第一位<br>
 *
 * @param userId   用户名
 * @param password 密码,但不能为空
 * @param loginParam 登录参数,null 则按默认值
 * @return 返回 true 表示正常执行调用流程,false 表示调用异常,异常错误通过 JCClientCallback 通知
 */
public abstract boolean login(@NonNull String userId, @NonNull String password, LoginParam loginParam);
用户名大小写不敏感,用户名为英文、数字和’+’ ‘-‘ ‘_’ ‘.’,长度不要超过64字符,’-‘ ‘_’ ‘.’不能作为第一个字符。

其中,LoginParam 类有以下属性

/**
 * 登录服务器地址
 */
public String serverAddress = "http:cn.router.justalkcloud.com:8080";
/**
 * https代理地址,例如 192.168.1.100:3128
 */
public String httpsProxy;
/**
 * 设备id,一般模拟器使用,因为模拟器可能获得的设备id都一样
 */
public String deviceId;
/**
 * 登录账号不存在的情况下是否内部自动创建该账号,默认为 true
 */
public boolean autoCreateAccount = true;

示例代码

JCClient.LoginParam loginParam = new JCClient.LoginParam();
//默认国内环境 http:cn.router.justalkcloud.com:8080
loginParam.serverAddress = "http:cn.router.justalkcloud.com:8080";
client.login("账号", "123", loginParam);

登录成功之后,首先会触发登录状态改变(onClientStateChange)回调

/**
 * 登录状态变化通知
 *
 * @param state    当前状态值
 * @param oldState 之前状态值
 */
void onClientStateChange(@JCClient.ClientState int state, @JCClient.ClientState int oldState);

ClientState 有

// 未初始化
public static final int STATE_NOT_INIT = 0;
// 未登录
public static final int STATE_IDLE = 1;
// 登录中
public static final int STATE_LOGINING = 2;
// 登录成功
public static final int STATE_LOGINED = 3;
// 登出中
public static final int STATE_LOGOUTING = 4;

示例代码

public void onClientStateChange(@JCClient.ClientState int state, @JCClient.ClientState int oldState) {
     if (state == JCClient.STATE_IDLE) { // 未登录
       ...
    } else if (state == JCClient.STATE_LOGINING) { // 正在登录
       ...
    } else if (state == JCClient.STATE_LOGINED) { // 登录成功
       ...
    } else if (state == JCClient.STATE_LOGOUTING) { // 登出中
       ...
    }
}

之后通过 onLogin 回调上报登录结果

/**
 * 登陆结果回调
 *
 * @param result true 表示登陆成功,false 表示登陆失败
 * @param reason 当 result 为 false 时该值有效
 */
void onLogin(boolean result, @JCClient.ClientReason int reason);

其中,ClientReason 有

/**
 * 正常
 */
public static final int REASON_NONE = 0;
/**
 * sdk 未初始化
 */
public static final int REASON_SDK_NOT_INIT = 1;
/**
 * 无效参数
 */
public static final int REASON_INVALID_PARAM = 2;
/**
 * 函数调用失败
 */
public static final int REASON_CALL_FUNCTION_ERROR = 3;
/**
 * 当前状态无法再次登录
 */
public static final int REASON_STATE_CANNOT_LOGIN = 4;
/**
 * 超时
 */
public static final int REASON_TIMEOUT = 5;
/**
 * 网络异常
 */
public static final int REASON_NETWORK = 6;
/**
 * appkey 错误
 */
public static final int REASON_APPKEY = 7;
/**
 * 账号密码错误
 */
public static final int REASON_AUTH = 8;
/**
 * 无该用户
 */
public static final int REASON_NOUSER = 9;
/**
 * 强制登出
 */
public static final int REASON_SERVER_LOGOUT = 10;
/**
 * 其他错误
 */
public static final int REASON_OTHER = 100;

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

3.3 登出

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

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

登出同样会触发登录状态改变(onClientStateChange)回调。

之后将通过 onlogout 回调上报登出结果

/**
 * 登出回调
 *
 * @param reason 登出原因
 */
void onLogout(@JCClient.ClientReason int reason);

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

SDK 支持前后台模式,可以在应用进入前台或者后台时调用 JCClient 类中的 setForeground 方法进行设置。
/**
 * 设置是否为前台, 在有控制后台网络的手机上当进入前台时主动触发
 *
 * @param foreground 是否为前台
 */
public abstract void setForeground(boolean foreground);

完成以上步骤,就做好了基础工作,您可以开始集成业务了。

4. 业务集成

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

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

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


接口调用逻辑和相关状态

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

通话方向(direction)及通话状态(state)可通过 JCCallItem 对象中的 getDirection() 方法和 getState() 方法获得。

开始集成通话功能前,请先实现 JCMediaDeviceCallback, JCCallCallback 回调,用于接收 JCMediaDevice 和 JCCall 的相关通知

之后进行模块的初始化

创建 JCMediaDevice 实例

/**
 * 创建 JCMediaDevice 对象
 *
 * @param client   JCClient 对象
 * @param callback JCMediaDeviceCallback 回调接口,用于接收 JCMediaDevice 相关通知
 * @return 返回 JCMediaDevice 对象
 */
public static JCMediaDevice create(JCClient client, JCMediaDeviceCallback callback);

创建 JCCall 实例

/**
 * 创建JCCall实例
 *
 * @param client        JCClient实例
 * @param mediaDevice   JCMediaDevice实例
 * @param callback      回调接口,用于接收 JCCall 相关回调事件
 * @return JCCall       JCCall实例
 */
public static JCCall create(JCClient client, JCMediaDevice mediaDevice, JCCallCallback callback);

示例代码

// 初始化各模块,因为这些模块实例将被频繁使用,建议声明在单例中
JCMediaDevice mediaDevice = JCMediaDevice.create(client, this);
JCCall call = JCCall.create(client, mediaDevice, this);

开始集成

媒体参数配置

一对一视频通话支持智能硬件设备集成,需要在发起通话前设置媒体参数

// 根据模式生成配置参数
                    JCCallMediaConfig *mediaConfig = [JCCallMediaConfig generateByMode:JCCallMediaConfigModeIntelligentHardware];
                    // 设置媒体参数
                    JCManager.shared.call.mediaConfig = mediaConfig;
                    

菊风提供三种配置模式供开发者选择,并开放相关属性供开发者进行灵活的自定义配置,具体方法请查看 媒体参数设置

4.1 拨打通话

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

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

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

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

示例代码

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

4.1.1 创建本地视频画面

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

/**
 * 获得视频预览对象,通过此对象能获得视频用于UI显示
 *
 * @param renderType    渲染模式
 * @return              JCMediaDeviceVideoCanvas 对象
 * @see RenderType
 */
public abstract JCMediaDeviceVideoCanvas startCameraVideo(@RenderType int renderType);

其中,RenderType(渲染模式)有以下几种

/**
 * 铺满窗口
 */
public static final int RENDER_FULL_SCREEN = 0;
/**
 * 全图像显示,会有黑边,类似放电影的荧幕
 */
public static final int RENDER_FULL_CONTENT = 1;
/**
 * 自适应
 */
public static final int RENDER_FULL_AUTO = 2;
调用该方法后,在挂断通话或者关闭摄像头时需要对应调用 stopVideo 方法停止视频。

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

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

示例代码

// 发起视频呼叫
call.call("peer number", true, "自定义透传字符串");
// 打开本地视频预览
JCMediaDeviceVideoCanvas localCanvas = mediaDevice.startCameraVideo(JCMediaDevice.RENDER_FULL_CONTENT);
viewGroup.addView(localCanvas.getVideoView(), 0);

4.2 应答通话

被叫收到 onCallItemAdd 回调事件,此时可通过 JCCallItem 中的 getVideo() 方法以及 getDirection() 方法获取 video 和 direction 属性,并根据 video 属性的值以及 direction 属性的值 DIRECTION_IN 判断是视频呼入还是语音呼入,然后可以调用下面的接口选择视频应答或者语音应答

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

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

示例代码

public void onCallItemAdd(JCCallItem item) {
    // 如果是视频呼入且在振铃中
    if (item.getDirection() == JCCall.DIRECTION_IN && item.getVideo()) {
        // 应答通话
        call.answer(item, true);
    }
}

通话接听后,通话状态变为 STATE_CONNECTING。

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

4.3 通话建立

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

4.3.1 创建远端视频画面

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

/**
 * 获得视频对象,通过此对象能获得视频用于UI显示
 *
 * @param videoSource   渲染标识串,比如 JCMediaChannelParticipant JCCallItem 中的 renderId
 * @param renderType    渲染模式
 * @return              JCMediaDeviceVideoCanvas 对象
 * @see RenderType
 */
public abstract JCMediaDeviceVideoCanvas startVideo(String videoSource, @RenderType int renderType);
调用该方法后,在挂断通话或者关闭摄像头时需要对应调用 stopVideo 方法停止视频。

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

示例代码

public void onCallItemUpdate(JCCallItem item) {
    // 如果对端在上传视频流(uploadVideoStreamOther)
    if (item.getState() == JCCall.STATE_TALKING && remoteCanvas == null && item.getUploadVideoStreamOther()) {
        // 获取远端视频画面,renderId来源JCCallItem对象
        JCMediaDeviceVideoCanvas remoteCanvas = mediaDevice.startVideo(item.renderId, JCMediaDevice.RENDER_FULL_CONTENT);
        viewGroup.addView(remoteCanvas.getVideoView(), 0);
    }
}

4.4 挂断通话

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

/**
 * 获得当前活跃的通话
 *
 * @return 有返回 JCCallItem 对象,没有返回 null
 */
public abstract JCCallItem getActiveCallItem();

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

/**
 * 挂断
 *
 * @param item          JCCallItem 对象
 * @param reason        挂断原因
 * @param description   挂断描述
 * @return              返回 true 表示正常执行调用流程,false 表示调用异常
 * @see CallReason
 */
public abstract boolean term(JCCallItem item, @CallReason int reason, String description);

示例代码

JCCallItem item = call.getCallItems().get(0);
call.term(item, JCCall.REASON_NONE, null);

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

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

/**
 * 停止视频
 *
 * @param canvas JCMediaDeviceVideoCanvas 对象,由 startVideo 获得
 */
public abstract void stopVideo(JCMediaDeviceVideoCanvas canvas);

通话挂断后,UI 会收到移除通话的回调

/**
 * 移除通话回调
 *
 * @param item          JCCallItem 对象
 * @param reason        通话结束原因
 * @param description   通话结束原因的描述,只有被动挂断的时候,才会收到这个值,其他情况下则返回空字符串
 */
void onCallItemRemove(JCCallItem item, @JCCall.CallReason int reason, String description);

示例代码

public void onCallItemRemove(JCCallItem item, @JCCall.CallReason int reason, String description) {  // 移除通话回调
    if (mLocalCanvas != null) { // 本地视频销毁
        mContentView.removeView(mLocalCanvas.getVideoView());
        JCManager.getInstance().mediaDevice.stopVideo(mLocalCanvas);
        mLocalCanvas = null;
    }
    if (mRemoteCanvas != null) { // 远端视频销毁
        mContentView.removeView(mRemoteCanvas.getVideoView());
        JCManager.getInstance().mediaDevice.stopVideo(mRemoteCanvas);
        mRemoteCanvas = null;
    }
}

其中,reason 有以下几种

名称 描述
REASON_NONE = 0 无异常
REASON_NOT_LOGIN = 1 未登录
REASON_CALL_FUNCTION_ERROR = 2 函数调用错误
REASON_TIMEOUT = 3 超时
REASON_NETWORK = 4 网络错误
REASON_OVER_LIMIT = 5 超出通话上限
REASON_TERM_BY_SELF = 6 自己挂断
REASON_ANSWER_FAIL = 7 应答失败
REASON_BUSY = 8
REASON_DECLINE = 9 拒接
REASON_USER_OFFLINE = 10 用户不在线
REASON_NOT_FOUND = 11 无此用户
REASON_REJECT_VIDEO_WHEN_HAS_CALL 已有通话拒绝视频来电
REASON_REJECT_WHEN_HAS_VIDEO_CALL 已有视频通话拒绝来电
REASON_OTHER = 100 其他错误

通话挂断的其他情况

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

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

此时通话状态变为 STATE_MISSED。

待机功耗优化方案

针对手表端 JC SDK 待机功耗的问题,菊风提供对应的解决方案,详细请查看:

待机功耗优化方案