[TOC]
1.概述
提供云课堂基础SDK功能,包括推流,拉流等。为用户提供快速,简便的方法开展自己的实时互动课堂。
1.1 功能特性
功能特性 | 描述 | 备注 |
推流 | 支持推流到服务器 | |
拉流 | 支持从服务器订阅流 | |
获取流状态 | 支持获取流的状态(发报数、收报数、丢包数、延时) | |
前后摄像头切换 | 支持手机前后摄像头切换 | |
后台播放 | 支持直播退到后台只播放音频 | |
支持https协议 | 支持接口https请求 |
1.2 阅读对象
本文档为技术文档,需要阅读者:
- 具备基本的Android开发能力
- 准备接入CC视频的基础SDK相关功能
- CC基础版本SDK,为了使用硬件媒体编解码器,建议API级别19以上。
2.开发准备
2.1 开发环境
- Android Studio : Android 开发IDE
- Android SDK : Android 官方SDK
2.2 混淆配置
ccclassroom-base.jar已经混淆过,如果需要对应用进行混淆,需要在混淆的配置文件增加如下代码,以防止SDK的二次混淆:
-keep public class com.bokecc.sskt.base.**{*;}
-keep public interface com.bokecc.sskt.base.**{*;}
-keep public class com.intel.webrtc.base.**{*;}
-keep public interface com.intel.webrtc.base.**{*;}
-keep public class com.intel.webrtc.conference.**{*;}
-keep public interface com.intel.webrtc.conference.**{*;}
-keep public class org.webrtc.**{*;}
-keep public interface org.webrtc.**{*;}
3.快速集成
注:快速集成主要提供的是推流和拉流的功能(核心功能)。
首先,需要下载最新版本的SDK,下载地址为:
3.1 导入jar
名称 | 描述 |
---|---|
ccclassroom-base.jar | CC基础SDK版本核心jar包 |
3.2 导入so
名称 | 描述 |
---|---|
libjingle_peerconnection.so | CC连麦依赖native库 |
3.3 配置依赖库
修改 build.gradle,打开您的工程目录下的 build.gradle,确保已经添加了如下依赖:
compile('io.socket:socket.io-client:0.8.3') {
exclude group: 'org.json', module: 'json'
}
compile 'com.squareup.okhttp3:okhttp:3.8.1'
compile files('libs/ccclassroom-base.jar')
3.4初始化渲染器以及布局控件
预览展示控件和订阅展示控件:
<android.support.constraint.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="ccsskt.bokecc.base.example.MainActivity">
<LinearLayout
android:id="@+id/id_local_container"
android:layout_width="0dp"
android:layout_height="270dp"
android:orientation="vertical"
android:gravity="center"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_weight="1"/>
<LinearLayout
android:id="@+id/id_remote_mix_container"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/id_local_container"
app:layout_constraintVertical_weight="1"/>
<android.support.constraint.ConstraintLayout
初始化渲染器:
private void initRenderer() {
mLocalRenderer = new CCSurfaceRenderer(this);
mLocalRenderer.init(mAtlasClient.getEglBase().getEglBaseContext(), null);
mLocalRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
mLocalContainer.addView(mLocalRenderer);
mRemoteMixRenderer = new CCSurfaceRenderer(this);
mRemoteMixRenderer.init(mAtlasClient.getEglBase().getEglBaseContext(), null);
mRemoteMixRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
mRemoteMixContainer.addView(mRemoteMixRenderer);
}
3.5 创建SDK实例
创建SDK实例:
mAtlasClient = new CCAtlasClient(this, Config.PUBLIC_KEY);
mAtlasClient.addAtlasObserver(mClientObserver);
系统代理回调:
private CCAtlasClient.AtlasClientObserver mClientObserver = new CCAtlasClient.AtlasClientObserver() {
@Override
public void onServerDisconnected() {
android.os.Process.killProcess(android.os.Process.myPid());
}
@Override
public void onStreamAdded(CCStream stream) {
if (stream.isRemoteIsLocal()) { // 不订阅自己的本地流
return;
}
Log.e(TAG, "onStreamAdded: [ " + stream.getStreamId() + " ]");
if (stream.getStreamType() == CCStream.REMOTE_MIX) {
// 订阅
mStream = stream;
}
}
@Override
public void onStreamRemoved(CCStream stream) {
Log.e(TAG, "onStreamRemoved: [ " + stream.getStreamId() + " ]");
if (stream.getStreamType() == CCStream.REMOTE_MIX) {
mStream = null;
mAtlasClient.unsubcribe(stream, null);
}
}
@Override
public void onStreamError(String streamid, String errorMsg) {
}
};
创建本地流:
private void createLocalStream() {
LocalStreamConfig config = new LocalStreamConfig.LocalStreamConfigBuilder().build();
isFront = config.cameraType == LocalStreamConfig.CAMERA_FRONT;
try {
mLocalStream = mAtlasClient.createLocalStream(config);
} catch (StreamException e) {
showToast(e.getMessage());
}
}
3.6 加入直播间和直播间开始结束的接口
加入直播间的接口:
mAtlasClient.join(sessionid, userAccount, new CCAtlasCallBack<CCBaseBean>() {
@Override
public void onSuccess(CCBaseBean ccBaseBean) {
dismissProgress();
showToast("join room success");
}
@Override
public void onFailure(int errCode, String errMsg) {
dismissProgress();
showToast(errMsg);
}
});
开始直播:
@OnClick(R.id.id_start)
void start() {
mAtlasClient.startLive(new CCAtlasCallBack<Void>() {
@Override
public void onSuccess(Void aVoid) {
showToast("start live success");
}
@Override
public void onFailure(int errCode, String errMsg) {
showToast("start live failed [ " + errMsg + " ]");
}
});
}
结束直播:
@OnClick(R.id.id_stop)
void stop() {
mAtlasClient.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 + " ]");
}
});
3.7 推流调用接口
下面是推流端代码:
mAtlasClient.publish(new CCAtlasCallBack<Void>() {
@Override
public void onSuccess(Void aVoid) {
isPublish = true;
dismissProgress();
showToast("publish success");
mPublishBtn.setSelected(!mPublishBtn.isSelected());
mPublishBtn.setText(mPublishBtn.isSelected() ? "停止发布" : "发布");
}
@Override
public void onFailure(int errCode, String errMsg) {
dismissProgress();
showToast("publish failed [ " + errMsg + " ]");
}
});
3.8 取消推流接口调用
下面取消推流接口调用代码:
mAtlasClient.unpublish(new CCAtlasCallBack<Void>() {
@Override
public void onSuccess(Void aVoid) {
isPublish = false;
dismissProgress();
showToast("unpublish success");
mPublishBtn.setSelected(!mPublishBtn.isSelected());
mPublishBtn.setText(mPublishBtn.isSelected() ? "停止发布" : "发布");
}
@Override
public void onFailure(int errCode, String errMsg) {
dismissProgress();
showToast("unpublish failed [ " + errMsg + " ]");
}
});
3.9 订阅流接口调用
下面代码是订阅流接口:
mAtlasClient.subscribe(mStream, new CCAtlasCallBack<CCStream>() {
@Override
public void onSuccess(CCStream stream) {
dismissProgress();
showToast("subscribe success");
mSubscribeBtn.setSelected(!mSubscribeBtn.isSelected());
mSubscribeBtn.setText(mSubscribeBtn.isSelected() ? "取消订阅" : "订阅");
try {
mStream.attach(mRemoteMixRenderer);
} catch (StreamException ignored) {
}
}
@Override
public void onFailure(int errCode, String errMsg) {
dismissProgress();
showToast("subscribe failed [ " + errMsg + " ]");
}
});
3.10 取消订阅流接口
下面是取消订阅流接口代码:
mAtlasClient.unsubcribe(mStream, new CCAtlasCallBack<Void>() {
@Override
public void onSuccess(Void aVoid) {
dismissProgress();
showToast("unsubscribe success");
mSubscribeBtn.setSelected(!mSubscribeBtn.isSelected());
mSubscribeBtn.setText(mSubscribeBtn.isSelected() ? "取消订阅" : "订阅");
try {
mStream.detach(mRemoteMixRenderer);
} catch (StreamException ignored) {
} finally {
mRemoteMixRenderer.cleanFrame();
}
}
@Override
public void onFailure(int errCode, String errMsg) {
dismissProgress();
showToast("unsubscribe failed [ " + errMsg + " ]");
}
});
3.11 切换摄像头
下面是切换摄像头代码:
@OnClick(R.id.id_switch)
void switchCamera() {
mAtlasClient.switchCamera(new CCAtlasCallBack<Boolean>() {
@Override
public void onSuccess(Boolean aBoolean) {
isFront = aBoolean;
mLocalRenderer.setMirror(isFront);
}
@Override
public void onFailure(int errCode, String errMsg) {
}
});
}
4.功能使用
功能使用时相关的核心类:CCAtlasClient(小班课核心类)。
4.1 预览
预览是将初始化相机的流渲染出来:
- 预览方法void preview();
- 渲染的方法mLocalStream.attach(mLocalRenderer);
4.2 开始直播
点击开始直播,成功以后,才会进行推流和拉流操作:
- 开始直播方法void start()
- 开始直播接口mAtlasClient.startLive(new CCAtlasCallBack())
- 回调实例new CCAtlasCallBack
4.3 结束直播
结束当前直播:
- 结束直播方法void stop()
- 结束直播接口mAtlasClient.stopLive(new CCAtlasCallBack())
- 回调实例new CCAtlasCallBack
4.4 推流/取消推流
推本地相机的流到atlas服务器:
- 推流的方法void publish()
- 推流接口mAtlasClient.publish(new CCAtlasCallBack())
取消推相机本地流到atlas服务器:
- 取消推流的方法void publish()
- 取消推流接口mAtlasClient.unpublish(new CCAtlasCallBack())
4.5 拉流/取消拉流
从atlas服务端拉流:
- 拉流方法 void subscribe()
- 拉流接口 mAtlasClient.subscribe(mStream, new CCAtlasCallBack())
取消从atlas服务端拉流:
- 取消拉流方法 void subscribe()
- 取消拉流接口 mAtlasClient.unsubcribe(mStream, new CCAtlasCallBack())
4.6 添加RTMP推流/取消RTMP推流
添加RTMP流到atlas服务端:
- 添加RTMP流方法 void pushRtmp()
- 添加RTMP流接口 mAtlasClient.addExternalOutput(rtmp, new CCAtlasCallBack())
取消RTMP流到atlas服务端:
- 取消添加RTMP流方法 void pushRtmp()
- 取消添加RTMP流接口 mAtlasClient.removeExternalOutput(rtmp, new CCAtlasCallBack())
4.7 切换摄像头
切换摄像头,前置摄像头和后置摄像头:
- 切换摄像头方法 void switchCamera()
- 切换摄像头接口 mAtlasClient.switchCamera(new CCAtlasCallBack())
4.8 开启本地视频/关闭本地视频
开启本地视频流,也就是相机采集的视频:
- 开启相机视频方法void disableLocalVideo()
- 开启相机视频的接口mLocalStream.enableVideo();
关闭本地视频,也就是关闭相机采集的视频:
- 关闭相机视频方法void disableLocalVideo()
- 关闭相机视频的接口 mLocalStream.disableVideo();
4.9 开启本地音频/关闭本地音频
开启本地视频流的音频,也就是相机采集的音频流:
- 开启本地音频方法 void disableLocalAduio()
- 开启本地音频的接口 mLocalStream.enableAudio();
关闭本地视频流的音频,也就是关闭相机采集的音频流:
- 关闭本地音频方法 void disableLocalAduio()
- 关闭本地音频的接口 mLocalStream.disableAudio();
4.10 开启远程视频/关闭远程视频
订阅流视频的开启:
- 开启远程视频方法void disableRemoteVideo()
- 开启远程视频的接口mStream.enableVideo();
订阅流视频的关闭:
- 关闭远程视频方法void disableRemoteVideo()
- 关闭远程视频的接口 mStream.disableVideo();
4.11 开启远程音频/关闭远程音频
订阅流音频的开启:
- 开启远程音频方法 void disableRemoteAudio()
- 开启远程音频的接口 mStream.enableAudio();
订阅流音频的关闭:
- 关闭远程音频方法 void disableRemoteAudio()
- 关闭远程音频的接口 mStream.disableAudio();
4.12 拍照
拍照功能:
- 拍照方法void takePic()
- 拍照接口mLocalRenderer.getBitmap(new CCSurfaceRenderer.OnShotCallback())
5.API查询
Doc目录打开index.html文件
6.Q&A
6.1 无法拉流
- 首先开始预览,开始直播
- 然后发布(推流),在去订阅流。
6.2 推荐权限声明
在AndroidManifest.xml声明权限:
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<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_LOGS"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.FLASHLIGHT"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus"/>
<uses-feature
android:glEsVersion="0x00020000"
android:required="true">
</uses-feature>