[TOC]
## 1.版本说明

见发版记录

## 2.快速开始

如果你是第一次使用场景视频-云课堂的服务，我们建议您按照以下步骤，初步完成云课堂的使用，了解关于场景视频-云课堂服务的基本信息以及如何快速跑通demo；

### 前提条件

* Android Studio 3.0及以上；
* Android SDK API 18及以上；
* 支持Android 4.3或以上版本的移动设备；
* 支持 CPU 架构 armeabi-v7a、arm64-v8a
* 有效的场景视频账户；

### 准备开发环境

本节介绍如何创建项目，并将场景视频-云课堂集成进你的项目中。

#### 1.集成SDK

```markdown
//步骤一：项目根build.gradle增加私有库
repositories {
        // 配置私有仓库地址
        maven {url 'http://nexus-app.bokecc.com/repository/sdk-group/'}
        ...
}

allprojects {
    repositories {
        // 配置私有仓库地址
        maven {url 'http://nexus-app.bokecc.com/repository/sdk-group/'}
        ...
    }
}
```

```markdown
//步骤二：项目module的build.gradle
//方式一：集成新版直播房间
dependencies {
    api 'com.bokecc:SaasRoomUILib:x.x.x'//ui库，具体版本号看要集成的版本
}
//方式二：集成sdk,UI层自定义开发
dependencies {
    //云课堂sdk引用
    api 'com.bokecc:ClassBaseLib:x.x.x'//基础库，具体版本号看要集成的版本
    api 'com.bokecc:docLib:x.x.x'//文档库，具体版本号看要集成的版本
}
```

#### 2.添加项目权限

```markdown
<!-- 联网 -->
<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.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
<!-- 麦克风相关权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- 修改音频设置（单主播和连麦使用不同设置） -->
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

<!-- 蓝牙，获取蓝牙连接状态 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<!-- 允许程序发现和配对蓝牙设备 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

<!-- 文件读写权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

<!-- 防止锁屏 -->
<uses-permission android:name="android.permission.WAKE_LOCK" />

<!-- 读取设备型号，系统版本，判断是否为模拟器 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />

<!-- 获取信号强度及位置（获取粗略位置）（android APILevel < 29） -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 获取信号强度及定位（获取详细位置）（android APILevel >= 29） -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
```

#### 3.代码混淆配置

```markdown
-keep class com.bokecc.**{*;}
-keep interface com.bokecc.**{*;}
-keep class io.agora.**{*;}
-keep class com.zego.**{*;}
-keep class com.tencent.** {*;}
-keep class com.aliyun.ams.ipdetector.Inet64Util{*;}
-keep class com.alibaba.sdk.android.**{*;}
-keep class com.ut.**{*;}
-keep class com.ta.**{*;}
```



### 方式一：快速接入CCSaasRoomUI

#### 时序图![截屏2020-08-0514.12.22](/Users/wy/dev/9.cc/android/CloudClass_Android_Module_SDK/document/res/快速对接时序图一.png)

#### 1.初始化SDK

在Application实现类中调用

```
CCSaasRoomInteractSDK.getInstance().init(context);
```

#### 2.获取推荐节点

通过此接口获取当前用户地域推荐节点及所有节点列表
其中，accountId 为当前账号id

```
CCAtlasClient.getInstance().dispatch(accountId, new CCAtlasCallBack<CCCityListSet>() {
            @Override
            public void onSuccess(final CCCityListSet ccCityBean) {
                String mFisrtCityName = ccCityBean.getloc();//节点名称
                String mAreaCode = ccCityBean.getareacode();//节点编码
                mCityList = ccCityBean.getLiveListSet();//节点列表
            }

            @Override
            public void onFailure(int errCode, String errMsg) {}
        });
```

#### 3.获取房间信息

通过roomId获取当前房间的相关配置信息；
其中，mRoomId 为房间id

```
CCAtlasClient.getInstance().getRoomMsg(mRoomId, new CCAtlasCallBack<String>() {
	@Override
	public void onSuccess(String des) {
     //返回json格式，解析参考demo的使用
	}

	@Override
	public void onFailure(int errCode, String errMsg) {

	}
});
```

#### 4.登录接口

通过此接口获取用户的sessionId，sessionId为用户进入房间的唯一身份标识；
其中，mRoomId（房间id），accountId（账号id），mRole（登录角色），mNickName （昵称），mPwd （房间密码）

```
CCAtlasClient.getInstance().login(mRoomId,accountId, mRole, mNickName, mPwd, new CCAtlasCallBack<String>() {
	@Override
	public void onSuccess(String sessionid) {

	}

	@Override
	public void onFailure(int errCode, String errMsg) {

	}
});
```

或通过新接口获取用户的sessionId，sessionId为用户进入房间的唯一身份标识；
其中，mRoomId（房间id），accountId（账号id），mRole（登录角色），mNickName （昵称），mPwd （房间密码）custominfo(自定义字符串)

```
CCAtlasClient.getInstance().login(mRoomId,accountId, mRole, mNickName, mPwd, "custominfo"，new CCAtlasCallBack<String>() {
	@Override
	public void onSuccess(String sessionid) {

	}

	@Override
	public void onFailure(int errCode, String errMsg) {

	}
});
```



#### 5.进入房间

在用户完成登录后，就可以进入房间，根据角色，房间类型，当前场景视频云课堂移动端支持以下类型房间：

![截屏2020-08-0514.44.33](/Users/wy/dev/9.cc/android/CloudClass_Android_Module_SDK/document/res/快速对接图二.jpg)

进入房间，需要传递以下参数

```java
//跳转的界面，根据角色进行跳转
//学生
Intent intent = new Intent(ValidateActivity.this, SaasStudentRoomActivity.class);
//老师
Intent intent = new Intent(ValidateActivity.this, SaasTeacherRoomActivity.class);
//助教
Intent intent = new Intent(ValidateActivity.this, SaasAssistantRoomActivity.class);

//登录得到的sessionId（必填）
intent.putExtra(CCRoomActivity.SeesionidKey, mSeesionid);
//房间id（必填）
intent.putExtra(CCRoomActivity.RoomIdKey, mRoomId);
//账号id（必填）
intent.putExtra(CCRoomActivity.UserAccountKey, mAccountId);
//房间布局样式，房间信息接口返回（必填）
intent.putExtra(CCRoomActivity.KeyLayoutMode, mLayoutMode);
//选择的节点（可选）
intent.putExtra(CCRoomActivity.AreaCodeKey, mAreaCode);
//智能笔参数（可选）
//intent.putExtra(CCRoomActivity.BleLicenseKey, byte[](证书));//智能笔相关，证书请联系客服
//startActivityForResult(intent，Config.CITYSTATUS_RESULT_CODE);

startActivityForResult(intent,Config.REQUEST_UPDATE_FLAG);
```

注意：如果接入 SaasRoomUILib，推荐使用//startActivityForResult(intent，Config.CITYSTATUS_RESULT_CODE); 从 6.5.7开始支持版本控制，在 onActivityResult 的回调方法中会有点击更新的回调，resultCode 为CCAtlasClient.UPDATE_GENERAL_FLAG，是普通更新，可以关闭，resultCode 为CCAtlasClient.UPDATE_FORCE_FLAG是强制更新，不能关闭。两种方式点击“立即更新”会回调到onActivityResult中，根据具体情况处理。

```
/*
 * CCAtlasClient.UPDATE_GENERAL_FLAG 为提示性更新，可以关闭弹窗，点击立即更新则回调至此处个性化处理
 * CCAtlasClient.UPDATE_FORCE_FLAG 为强制性更新，弹窗不可关闭，点击立即更新则回调至此处个性化处理
 */
 if(resultCode == CCAtlasClient.UPDATE_GENERAL_FLAG||resultCode ==     CCAtlasClient.UPDATE_FORCE_FLAG){
                
      //处理app 的更新逻辑，比如调用浏览器更新等
       Tools.openSystemBrowser(this,Config.CC_CLASS_UPDATE_URL);
  }
```

### 方式二：快速接入CCBaseSdk

#### 1.进入房间

参考方式一  快速接入CCRoomUI，从链接获取必要参数，login 成功后执行 join 即可。

注意，如果采用接口验证方式中的OpenApi形式，请调用含有 roomId 的 join 方法即可

```java
public void join(String roomId,String sessionId, String appId, String areaCode, boolean isUpdateRtmpLayout, CCAtlasCallBack<CCInteractBean> callBack) 
```

注意：join 最好放到设置完监听之后，否则会出现丢流情况。

#### 2.直播间流服务功能介绍

##### 1.关联组件

mCCAtlasClient = CCAtlasClient.getInstance();//基础SDK 音视频相关
mCCBarLeyManager = CCBarLeyManager.getInstance();//排麦组件 

##### 2.SDK 事件监听

mCCAtlasClient 重要监听回调方法

```
//通知应用程序处理流事件监听
public void setOnNotifyStreamListener(OnNotifyStreamListener observer) 

//服务器连接状态回调通知
public void setOnServerListener(OnServerListener onServerListener)

//上课状态变化通知
public void setOnClassStatusListener(OnClassStatusListener onClassStatusListener)

// 开始直播() isRecord 0不开启录制/1开启录制
public void startLive(int isRecord, final CCAtlasCallBack<Void> callBack) 

//开始直播后调用开始推流接口
public synchronized void publish(final CCAtlasCallBack<Void> callBack)
//结束直播
public void stopLive(final CCAtlasCallBack<Void> callBack)

/**
     * 订阅远程流，在 setOnNotifyStreamListener 回调方法onStreamAllowSub 订阅远程流需要切换到
     * @param remoteStream 远程流
     * @param renderMode 全铺满 Config.RENDER_MODE_HIDDEN, 自适应 Config.RENDER_MODE_FIT
     * @param callBack 回调
     * @throws StreamException StreamException
     */
public synchronized void SubscribeStream(CCStream remoteStream, int renderMode, CCAtlasCallBack<CCStream> callBack)

//取消订阅远程流
public synchronized void unSubscribeStream(CCStream remoteStream, CCAtlasCallBack<Void> callBack) 
```

CCBarLeyManager 重要监听回调

```
//排麦状态回调
public void setOnNotifyMaiStatusLisnter(OnNotifyMaiStatusLisnter
onNotifyMaiStatusLisnter) 

//学生排麦麦序更新回调
public void setOnQueueMaiUpdateListener(OnQueueMaiUpdateListener onQueueMaiUpdateListener)
```

##### 3.推拉流

###### 1.预览

join 成功后，可以预览

```
//设置图像显示方向 ,true 竖屏
mCCAtlasClient.setAppOrientation(true);
//设置分辨率，取房间默认配置
mCCAtlasClient.setResolution(mCCAtlasClient.getDefaultResolution());
//或者指定分辨率       
//mCCAtlasClient.setResolution(CCAtlasClient.Resolution_480P);
//获取预览视图
SurfaceView surfaceView = mCCAtlasClient.startPreview(this,Config.RENDER_MODE_HIDDEN);
//添加到指定 view
mLocalContainer.addView(surfaceView);
            
```

注意，预览需要在流服务初始化成功后才能使用（OnNotifyMaiStatusLisnter 的 onServerInitSuccess 方法回调后）

###### 2.推流

老师端（角色为 PRESENTER ）推流需要先开播，开播成功后再推流

```
    /**
     * 开始直播
     */
    private void startLive(int isRecord) {
        // 发布本地流
        mCCAtlasClient.startLive(isRecord, new CCAtlasCallBack<Void>() {
            @Override
            public void onSuccess(Void aVoid) {
                publish();
            }

            @Override
            public void onFailure(int errCode, String errMsg) {
                showToast(errMsg);
            }
        });
    }

    /**
     * 发布本地流
     */
    private synchronized void publish() {
        mCCAtlasClient.publish(new CCAtlasCallBack<Void>() {
            @Override
            public void onSuccess(Void aVoid) {
                showToast("推流成功！");
            }

            @Override
            public void onFailure(int errCode, String errMsg) {
                showToast(errMsg);
            }
        });
    }
```

###### 3.拉流

需要监听上面排麦组件 setOnNotifyStreamListener 方法
此监听方法较多，简单推拉流功能需要关注onServerInitSuccess， onStreamAllowSub，onStreamRemoved 三个方法

```
 				//流服务监听
        mCCAtlasClient.setOnNotifyStreamListener(new OnNotifyStreamListener() {
            @Override
            public void onStreamAllowSub(final SubscribeRemoteStream mStream) {
                //收到流需要去订阅
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        //添加流，去订阅
                        subscribeStream(mStream);
                    }
                });

            }

            @Override
            public void onStreamRemoved(SubscribeRemoteStream stream) {
                //移除流，取消订阅
                try {
                    mCCAtlasClient.unSubscribeStream(stream.getRemoteStream(), null);
                } catch (StreamException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onServerInitSuccess() {
                //流服务初始化成功，之后才能进行流操作，比如预览等

            }

            @Override
            public void onServerInitFail() {

            }

            @Override
            public void onServerConnected() {

            }

            @Override
            public void onServerReconnect() {

            }

            @Override
            public void onStreamError() {
                //流错误
            }

            @Override
            public void onServerDisconnected() {
            }

            @Override
            public void onStartRouteOptimization() {

            }

            @Override
            public void onStopRouteOptimization() {

            }

            @Override
            public void onRouteOptimizationError(String s) {

            }

            @Override
            public void onReloadPreview() {

            }

            @Override
            public void onStudentDownMai() {

            }
        });
        
    /**
     * 订阅流，mCCAtlasClient.SubscribeStream 方法第二个参数为适应或者铺满
     * renderMode 全铺满 Config.RENDER_MODE_HIDDEN, 自适应 Config.RENDER_MODE_FIT
     * @param stream
     */
    private synchronized void subscribeStream(SubscribeRemoteStream stream) {
        try {
            mCCAtlasClient.SubscribeStream(stream.getRemoteStream(), Config.RENDER_MODE_HIDDEN, new CCAtlasCallBack<CCStream>() {
                @Override
                public void onSuccess(final CCStream ccstream) {
                    mRemoteContainer.addView(ccstream.getSurfaceView());
                }

                @Override
                public void onFailure(int errCode, String errMsg) {
                    Tools.showToast(errMsg);
                }
            });
        } catch (StreamException e) {
            e.printStackTrace();
        }
    }

```

###### 5.停止推流

SDK 中有一个停止直播的方法，老师端可以调用，另外有停止推流的方法，停止推流的方法内部会调用停止直播方法。

```
  			//停止直播
        mCCAtlasClient.stopLive(new CCAtlasCallBack<Void>() {
            @Override
            public void onSuccess(Void aVoid) {
                showToast("stop live success");
            }

            @Override
            public void onFailure(int errCode, String errMsg) {
                showToast("stop live failed [ " + errMsg + " ]");
            }
        });

```

通常调用停止推流即可

```
mCCAtlasClient.unpublish(new CCAtlasCallBack<Void>() {
                @Override
                public void onSuccess(Void aVoid) {
                    //比如离开房间
                }

                @Override
                public void onFailure(int errCode, String errMsg) {


                }
            });
```

###### 6.上下麦

不同的连麦模式需要用到此监听，根据监听来判断需要上麦或者下麦

```
CCBarLeyManager.getInstance().setOnNotifyMaiStatusLisnter(new CCBarLeyManager.OnNotifyMaiStatusLisnter() {
            @Override
            public void onUpMai(int oldStatus) {
            //这块监听是监听上麦状态事件，逻辑可以根据需要设置
               //比如去推流 publish();
            }

            @Override
            public void onDownMai() {
            //这块监听是监听下麦状态事件，逻辑可以根据需要设置
               //比如取消推流 unPublish();
            }
        });
 
```

 //上麦接口

```
 CCBarLeyManager.getInstance().handsup(new CCBarLeyCallBack<Void>() {
            @Override
            public void onSuccess(Void aVoid) {
                
            }

            @Override
            public void onFailure(String err) {
               
            }
        });
        

```

下麦接口

```
CCBarLeyManager.getInstance().handsDown(new CCBarLeyCallBack<Void>() {
            @Override
            public void onSuccess(Void aVoid) {
         
            }

            @Override
            public void onFailure(String err) {
           
            }
        });
```

#### 3.直播间文档组件

##### 1.基础文档控件

###### 1.布局

```
				<RelativeLayout
        android:id="@+id/id_lecture_doc_area"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#FFFFFF">

        <com.bokecc.ccdocview.DocWebView
            android:id="@+id/id_lecture_docppt_display"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        <com.bokecc.ccdocview.DocView
            android:id="@+id/id_lecture_doc_display"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
        </com.bokecc.ccdocview.DocWebView>
    </RelativeLayout>
```

###### 2. 初始化

```
        //1、初始化文档管理类
        mCCDocViewManager = CCDocViewManager.getInstance();
        //基础监听
        mCCDocViewManager.addInteractListeners();
        //2、设置画笔 noIntercept true可以用画笔，用户角色CCAtlasClient.PRESENTER(老师红色笔)、CCAtlasClient.TALKER（学生蓝色画笔）
        mDocView.setTouchInterceptor(false, CCAtlasClient.PRESENTER);
        //3.设置文档展示界面
        mCCDocViewManager.setDocHistory(mDocView, mDocWebView);
        mDocView.setDocWebViewSetVisibility(mDocWebView);
        //4.设置白板的宽高
        ViewGroup.LayoutParams params = mDocContainer.getLayoutParams();
        int width = DensityUtil.getWidth(this);
        int height = (int) (width / 16.0 * 9);
        mDocView.setWhiteboard(width, height,true);
        params.height = height;
        mDocContainer.setLayoutParams(params);//父布局
```

###### 3. 监听

1.基础监听 

```
CCDocViewManager.getInstance().addInteractListeners();
```

2.加载回调

```
mDocWebView.setOnDpCompleteListener(new DocWebView.OnDpCompleteListener() {
                @Override
                public void dpLoadComplete(int w, int h) {
                    //加载完成
                }

                @Override
                public void dpLoading() {
										//正在加载
                }

                @Override
                public void dpAnimationChange(int index) {

                }

                @Override
                public void dpLoadError() {

                }
            });
```

###### 4. 资源释放

```
CCDocViewManager.getInstance().release();
```

##### 2.使用 CCRoomUI 的文档控件 

```
CCDocView 文档组合视图（包含文档画笔显示、翻页按钮，文档工具条，全屏、退出全屏按钮、跳页按钮等）, 可以将文档整体当作一个控件使用，参考{@link TeacherRoomActivity} 和 {@link StudentRoomActivity}
```

CCRoomUI 中对文档控件进行了封装 CCDocView，此控件是 demo 中 UI 下的文档组合控件，如果和 demo 中的需求相同可以使用， 如果不同，请参考上一条处理
####4.离开房间
注意此方法离开房间必须调用

```
mCCAtlasClient.leave(CCAtlasCallBack<Void>);
```

#### 4.其他

##### 1.切换前后摄像头

```
mAtlasClient.switchCamera(new CCAtlasCallBack<Boolean>() {});
```

##### 2.是否开播

```
 mCCAtlasClient.isRoomLive()
```

##### 3.设置音频模式

```
 setAudioMode(boolean isAudioMode);
```

