之前做的WebGL加载速度慢,尝试做成App,需要Android中能够播放Kurento视频。
目录
一、调研资料
1.1、考察1
1.2、考察2
1.3、找别人的Demo
二、开发
2.1、播放本地视频
2.1.1 获取权限
2.1.2 界面
2.2.3 播放本地视频
2.2.4 生命周期相关
2.2.5 startLocalMedia
2.2、和服务端通信
2.3、获取远程视频
2.3.1 客户端发送部分
2.3.2 客户端接收部分
2.3.3 客户端显示视频
2.3.4 其他处理
参考1:Android端WebRtc+Kurento详解
参考2:Kurento WebRTC Peer For Android(Kurento官方)
下载参考1里面的https://github.com/nubomedia-vtt/kurento-room-client-android和https://github.com/BaeBae33/webrtc_android。
无法用androidstudio直接打包androidapp,只能看看代码。
kurento-room-client-android是一个library,感觉像是基于websocket的一层封装。
webrtc_android则是一个jar包(org.webrtc)的源代码,可能是后面会引用到的核心jar包的源代码(的一个老的版本),同时可能也是上面的kurento-room-client-android后续迭代版本。
参考2是Kurento官方的接教程,就3个部分,Overview,Installation Guide,Developer Guide。
从Overview里面下载https://github.com/nubomedia-vtt/webrtcpeer-android,感觉像是又对上面的webrtc_android的一层封装,
Installation Guide里面教你怎么导入jar包到项目中。
https://bintray.com/nubomedia-vtt/maven/webrtcpeer-android
compile 'fi.vtt.nubomedia:webrtcpeer-android:1.1.2'
实际我用的androidstudio版本是3.5.2现在已经不用compile了,用implementation,
Developer Guide教你怎么写功能了,但是我按着教程,加上个SurfaceViewRender后打包app,结果界面是黑屏了。
对了官方教程也有问题(笔误吧)
这里的localRender,应该是localView的。
这里卡住了.....
我的android知识不行,很久以前(Android2.*时代)学了一点,所以不知道怎么应变了。
要么先再学习一下android。
尝试用“NBMWebRTCPeer.Observer”在github上找demo。
找到几个能直接打包的
https://github.com/nubomedia-vtt/nubo-test/,3年前,好像是NUBOMEDIA官方的?加载有错误。
https://github.com/nhancv/nc-kurento-android,2年前,能打包,有界面。似乎是基于org.webrtc的另一套封装
这里提供了服务端的部分
Setup
Install server: https://www.youtube.com/watch?v=02X7HOyhAkA
Start server demo: https://www.youtube.com/watch?v=b44IKU2pl3U
Start app: https://www.youtube.com/watch?v=W407V5T_aW4
Demo server
https://github.com/nhancv/ot-kurento-node-webrtc
Kurento tutorial flow
http://doc-kurento.readthedocs.io/en/stable/
Android webrtc-peer lib
https://github.com/nhancv/nc-android-webrtcpeer
https://github.com/satriyaPhincon/CallVideo,3个月前,能打包,有界面。logo(OCBC NISP)是新加坡银行的??
https://github.com/gaopj/webrtcdemo,7个月前,能打包,有界面。在这个基础上学习一下吧。
-----------------------------------------
https://github.com/kries2333/nubo-android-base,11个月前,有界面,加载有错误,因为是NUBOMEDIA官方的,有点期待,尝试处理一下问题。
问题1.
不用管 ok下去
----------
问题2.
compile改成implementation
---------
问题3.
Execution failed for task ':app:compileDebugJavaWithJavac'
错误: -source 1.7 中不支持方法引用
(请使用 -source 8 或更高版本以启用方法引用)
错误: -source 1.7 中不支持 lambda 表达式
(请使用 -source 8 或更高版本以启用 lambda 表达式)
参考:https://blog.csdn.net/w1227976200/article/details/79542943
加上
出现更多错误(问题4)
不过应该是已经向前一步了。
为什么有些org.webrtc下面的类可以,有些不行呢?
org.webrtc实际上上libjingle包里面的.
implementation ('io.pristine:libjingle:11139@aar') { transitive=true }
把不行的import拷贝到其他可以打包app的项目中也是不行的。
怀疑是现在的libjingle和11个月前不一样了。
结论:暂时不行。看看代码好了。
-----------------------------------------
学习上面的项目代码的基础上,开发AndroidKurentoPlayer。
记得要加上:
implementation 'fi.vtt.nubomedia:webrtcpeer-android:1.1.2'
需要dexter,按我理解这是一个权限管理插件,参考:Android Dexter 分析。
原本不知道这个,本地摄像头视频无法显示,代码运行后日志显示
E/VideoCapturerAndroid: VideoCapturerAndroid: startCapture failed
VideoCapturerAndroid: java.lang.RuntimeException: Fail to connect to camera service
E/VideoCapturerAndroid: VideoCapturerAndroid: java.lang.RuntimeException: Fail to connect to camera service
首先AndroidManifest.xml里面要加上权限
在build.gradle里面加上dexter
implementation 'com.karumi:dexter:5.0.0'
然后在Activity的onStart中询问权限(不然要手动开启权限)
private void permission(){
Dexter.withActivity(MainActivity.this)
.withPermissions(
Manifest.permission.CALL_PHONE,
Manifest.permission.CAMERA,
Manifest.permission.ANSWER_PHONE_CALLS,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO
)
.withListener(new MultiplePermissionsListener() {
@Override
public void onPermissionsChecked(MultiplePermissionsReport report) {
Log.d("checkpermission", String.valueOf(report.areAllPermissionsGranted()));
if (report.areAllPermissionsGranted()){
Log.d("checkpermission", "granted");
} else if(report.isAnyPermissionPermanentlyDenied()) {
Log.d("checkpermission", "not granted");
}
}
@Override
public void onPermissionRationaleShouldBeShown(List permissions, PermissionToken token) {
}
}).onSameThread().check();
}
核心是需要在界面上添加
在onStart(或者onCreate)初始化WebRTCPeer
@Override
protected void onStart() {
Log.e(TAG,"onStart");
super.onStart();
initWebRtc();
}
private void initWebRtc(){
localView = findViewById(R.id.gl_surface_local);//界面上有个gl_surface_local的onInitialize
}
@Override
public void onInitialize() {
Log.d(TAG,"onInitialize");
nbmWebRTCPeer.generateOffer("local", true);//这样就能播放本地视频了
//nbmWebRTCPeer.startLocalMedia();//只有这个会崩溃啊,日志:JNI DETECTED ERROR IN APPLICATION: java_object == null
}
参考:Android Activity生命周期解析
测试时:onCreate->onStart->onResume->onPause->onResume
在onPause之前有个:E/LB: fail to open file: No such file or directory
启动过程中间都有失去焦点过?
加上nbmWebRTCPeer.initialize();的话,是 onCreate->onStart(initialize)->onResume->onPause->onInitialize->onResume
-------------------------------------------
切换程序:onPause->onStop
切回程序:onStart->OnResume
这里除了点问题,onStart里面的initWebRtc()被再次调用了。
那放到onCreate中吧,onCreate(initialize)->onStart->onResume->onPause->onInitialize->onResume
参考1:基于Kurento的WebRTC移动视频群聊解决方案0000
参考2:NUBOMEDIA 起步
我原本以为播放本地视频需要startLocalMedia,结果从代码上看是不需要的。
看代码,startLocalMedia是和stopLocalMedia成对使用
@Override
protected void onPause() {
Log.e(TAG,"onPause ");
nbmWebRTCPeer.stopLocalMedia();
//不加上这个会提示:I/SurfaceViewRenderer: SurfaceViewRenderer: gl_surface_local: No surface to draw on
super.onPause();
}
@Override
protected void onResume() {
Log.e(TAG,"onResume");
super.onResume();
nbmWebRTCPeer.startLocalMedia();
}
完整的生命周期:
onCreate(initialize)->onStart->onResume->onPause->onInitialize->onResume ->
切出 ->onPause(stopLocalMedia)->onStop
切回->onStart->OnResume(startLocalMedia)
但是我这样使用会出现程序闪退的情况,日志是
E/MainActivity: onPause
D/AndroidRuntime: Shutting down VM
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.myapplication, PID: 21412
java.lang.RuntimeException: Unable to pause activity {com.example.myapplication/com.example.myapplication.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void fi.vtt.nubomedia.webrtcpeerandroid.MediaResourceManager.stopVideoSource()' on a null object reference
......
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void fi.vtt.nubomedia.webrtcpeerandroid.MediaResourceManager.stopVideoSource()' on a null object reference
at fi.vtt.nubomedia.webrtcpeerandroid.NBMWebRTCPeer.stopLocalMedia(NBMWebRTCPeer.java:565)
at com.example.myapplication.MainActivity.onPause(MainActivity.java:164)
at android.app.Activity.performPause(Activity.java:7444)
at android.app.Instrumentation.callActivityOnPause(Instrumentation.java:1466)
at android.app.ActivityThread.performPauseActivityIfNeeded(ActivityThread.java:4068)
关键是:
Attempt to invoke virtual method 'void fi.vtt.nubomedia.webrtcpeerandroid.MediaResourceManager.stopVideoSource()' on a null
修改stopLocalMedia()调用代码
protected void onPause() {
Log.e(TAG,"onPause ");
if(nbmWebRTCPeer!=null && nbmWebRTCPeer.isInitialized()){
nbmWebRTCPeer.stopLocalMedia();
}
//不加上这个会提示:I/SurfaceViewRenderer: SurfaceViewRenderer: gl_surface_local: No surface to draw on
super.onPause();
}
startLocalMedia()又出错了,发现是为了测试把generateOffer注释掉了,看源代码,发现,startLocalMedia和generateOffer里面都调用了startLocalMediaSync。
--------------------
现在的问题是startLocalMedia没有效果,调用stopLocalMedia后再调用startLocalMedia本地视频也出不来了。
startLocalMedia的源码是:
public boolean startLocalMedia() {
if (mediaResourceManager != null && mediaResourceManager.getLocalMediaStream() == null) {
executor.execute(new Runnable() {
@Override
public void run() {
startLocalMediaSync();
}
});
return true;
} else {
return false;
}
}
发现不管那次的返回值都是false。
应该是后面的判断mediaResourceManager.getLocalMediaStream() == null进不去
stopLocalMedial的源码是:
public void stopLocalMedia() {
mediaResourceManager.stopVideoSource();
}
MediaResourceManager里面的close才是把localMediaStream设置为null的地方
void close(){
// Uncomment only if you know what you are doing
localMediaStream.dispose();
localMediaStream = null;
//videoCapturer.dispose();
//videoCapturer = null;
}
考虑修改源代码,源代码https://github.com/nubomedia-vtt/webrtcpeer-android。
不改源代码的情况下,不用startLocalMedia,用initialize。
现在暂时改成这样
@Override
protected void onPause() {
Log.e(TAG,"onPause ");
if(nbmWebRTCPeer!=null && nbmWebRTCPeer.isInitialized()){
nbmWebRTCPeer.stopLocalMedia();
isStop=true;
}
//不加上这个会提示:I/SurfaceViewRenderer: SurfaceViewRenderer: gl_surface_local: No surface to draw on
super.onPause();
}
private boolean isStop=false;
@Override
protected void onResume() {
super.onResume();
Log.e(TAG,"onResume");
if(nbmWebRTCPeer!=null && nbmWebRTCPeer.isInitialized() && isStop){
boolean b=nbmWebRTCPeer.startLocalMedia();
Log.e(TAG,"startLocalMedia:"+b);
if(b==false){//都是false
nbmWebRTCPeer.initialize();
}
}
}
从结果上,只是查看本地摄像头视频没什么问题,不知道对于传输视频信息会怎样。
通信是基于WebSocket的,也可以是Http的吧。现有的例子都是视频通话的例子,都是使用KurentoRoomAPI连接服务端的。
我是播放rtsp视频用的,需要自己改一下,先写个KurentoPlayerAPI继承自KurentoRoomAPI。
public class KurentoPlayerAPI extends KurentoRoomAPI {
/**
* Constructor that initializes required instances and parameters for the API calls.
* WebSocket connections are not established in the constructor. User is responsible
* for opening, closing and checking if the connection is open through the corresponding
* API calls.
*
* @param executor is the asynchronous UI-safe executor for tasks.
* @param uri is the web socket link to the room web services.
* @param listener interface handles the callbacks for responses, notifications and errors.
*/
public KurentoPlayerAPI(LooperExecutor executor, String uri, RoomListener listener) {
super(executor, uri, listener);
Log.e("KurentoPlayerAPI","uri:"+uri);
}
public void startPlayer(String videourl, String sdpOffer, int id){
HashMap namedParameters = new HashMap<>();
namedParameters.put("videourl", videourl);
namedParameters.put("sdpOffer", sdpOffer);
send("start", namedParameters, id);
}
}
在onCreate或者onStart里面初始化
private void initkurentoRoomAPI() {
executor = new LooperExecutor();
executor.requestStart();
//wsUri = mSharedPreferences.getString(Constants.SERVER_NAME, Constants.DEFAULT_SERVER);
kurentoRoomAPI = new KurentoPlayerAPI(executor, Constants.DEFAULT_SERVER, MainActivity.this);
kurentoRoomAPI.connectWebSocket();
findViewById(R.id.btnJoinRoom).setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Log.i(TAG,"onClick sendJoinRoom");
kurentoRoomAPI.sendJoinRoom("userId","roomId",true,roomId);
}
});
}
测试了一下,KurentoRoomPlayer发送过去的,服务端收到的是
{"method":"joinRoom","id":1,"params":{"dataChannels":true,"user":"userId","room":"roomId"},"jsonrpc":"2.0"}
我的服务端是原来的Kurento-Player-Java的,改一下,
public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
// JsonObject jsonMessage = gson.fromJson(message.getPayload(), JsonObject.class);
JsonObject jsonMessage=GetJsonMessage(message);
if(jsonMessage==null)return;
String sessionId = session.getId();
log.debug("Incoming message {} from sessionId", jsonMessage, sessionId);
JsonElement jsonrpcElement=jsonMessage.get("jsonrpc");
if(jsonrpcElement!=null){//KurentoRoomAPI发送过来的
String jsonrpc=jsonrpcElement.getAsString();
String method=jsonMessage.get("method").getAsString();
log.info(">>>>>>>>>>>>>>>>> PlayerHandler.handleTextMessage method="+method+",jsonrpc="+jsonrpc);
switch(method){
case "joinRoom":
break;
case "leaveRoom":
break;
case "publishVideo":
break;
case "unpublishVideo":
break;
case "receiveVideoFrom":
break;
case "unsubscribeFromVideo":
break;
case "onIceCandidate":
break;
case "sendMessage":
break;
case "customRequest":
break;
default:
doPlayerCommand(session, jsonMessage, sessionId, method);//
break;
}
}
else{ //原来的player
JsonElement idElement=jsonMessage.get("id");
if(idElement==null){
log.error("no id element:"+jsonMessage.toString());
return;
}
String id=idElement.getAsString();
//log.info("id="+id);
log.info(">>>>>>>>>>>>>>>>> PlayerHandler.handleTextMessage id="+id);
doPlayerCommand(session, jsonMessage, sessionId, id);//根据id,进行不同操作
}
}
------------------------------------------------------
https://github.com/Kurento/kurento-room,聊天室相关代码,3年前的,服务端后续参考改一下。先试着播放视频。
他们是不是3年前开始去做云服务了.....新的NUBOMEDIA项目(https://github.com/nubomedia)。
另外,找到一个博客:Kurento应用开发指南(以Kurento 6.0为模板) 之八: Kurento协议,介绍了一下JSON-RPC 消息格式。这里还有很多webrtc相关的博客,有空看看。
------------------------------------------------------
服务端收客户端消息,还是按照原来的JsonObject收,获取JsonElement,再获取具体参数,也是没问题的。
但是因为客户端接收处理的部分封装好了,KurentoRoomAPI->KurentoAPI->JsonRpcWebSocketClient->ExtendedWebSocketClient->onMessage
@Override
public void onMessage(final String message) {
executor.execute(new Runnable() {
@Override
public void run() {
if (connectionState == WebSocketConnectionState.CONNECTED) {
try {
JSONRPC2Message msg = JSONRPC2Message.parse(message);
if (msg instanceof JSONRPC2Request) {
JsonRpcRequest request = new JsonRpcRequest();
request.setId(((JSONRPC2Request) msg).getID());
request.setMethod(((JSONRPC2Request) msg).getMethod());
request.setNamedParams(((JSONRPC2Request) msg).getNamedParams());
request.setPositionalParams(((JSONRPC2Request) msg).getPositionalParams());
events.onRequest(request);
} else if (msg instanceof JSONRPC2Notification) {
JsonRpcNotification notification = new JsonRpcNotification();
notification.setMethod(((JSONRPC2Notification) msg).getMethod());
notification.setNamedParams(((JSONRPC2Notification) msg).getNamedParams());
notification.setPositionalParams(((JSONRPC2Notification) msg).getPositionalParams());
events.onNotification(notification);
} else if (msg instanceof JSONRPC2Response) {
JsonRpcResponse notification = new JsonRpcResponse(message);
events.onResponse(notification);
}
} catch (JSONRPC2ParseException e) {
// TODO: Handle exception
}
}
}
});
}
到KurentoRoomAPI转换成了RoomListener里面的OnRoomResponse(JSONRPC2Response)和OnRoomNotification(JSONRPC2Notification)。
也就是说服务端发回来的信息必须是JSONRPC2Response或者JSONRPC2Notification,不然到业务层代码就收不到消息了。
服务端加上引用
com.thetransactioncompany
jsonrpc2-base
1.38
最终服务端代码没找到具体能直接用的,前端参考的https://github.com/satriyaPhincon/CallVideo。如果要配合前端改后端很麻烦,要把不同的指令类型分成Response和Notification。麻烦,我改成全部信息都用JSONRPC2Notification发回。
另外还要兼容原来的js客户端的代码,创建了个JsonResponse类,把JsonObject发回的消息封装一下,添加用JSONRPC2Notification发回的接口。根据前端信息内容确定是否用JSONRPC返回。
package org.kurento.tutorial.player;
import com.google.gson.JsonObject;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Notification;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Response;
import org.kurento.client.IceCandidate;
import org.kurento.jsonrpc.JsonUtils;
import java.util.HashMap;
import java.util.Map;
public class JsonResponse {
private boolean isRpc;
private String id = "id";
private HashMap map;
public JsonResponse(boolean isRpc,String id) {
this.isRpc=isRpc;
this.id = id;
map = new HashMap();
}
public void addParam(String key, Object value) {
this.map.put(key, value);
}
public void add(String key, Object value) {
this.map.put(key, value);
}
public void addProperty(String key, Object value) {
this.map.put(key, value);
}
@Override
public String toString() {
return getJson(this.isRpc);
}
public String getJson(boolean isRpc) {
//System.out.println("getJson:"+isRpc);
if (isRpc) {
JSONRPC2Notification notification =getJSONRPC2Notification();
return notification.toString();
} else {
JsonObject response = getJsonObject();
return response.toString();
}
}
public JsonObject getJsonObject() {
JsonObject response = new JsonObject();
response.addProperty("id", id);
for (Map.Entry entry : map.entrySet()) {
String mapKey = entry.getKey();
Object mapValue = entry.getValue();
String className=mapValue.getClass().getName();
//System.out.println("className:"+className);
if(className=="String"){
System.out.println("className:"+className);
response.addProperty(mapKey, (String)mapValue);
}
// else if(className=="com.google.gson.JsonObject"){
// //System.out.println("className:"+className);
// response.add(mapKey, (JsonObject)mapValue);
// }
else if(className=="org.kurento.client.IceCandidate"){
IceCandidate candidate=(IceCandidate)mapValue;
response.add(mapKey, JsonUtils.toJsonObject(candidate));
}
else{
System.out.println("className4:"+className);
response.addProperty(mapKey, mapValue.toString());
}
}
return response;
}
public JSONRPC2Notification getJSONRPC2Notification() {
JSONRPC2Notification notification = new JSONRPC2Notification(id);
Map params = new HashMap<>();
for (Map.Entry entry : map.entrySet()) {
String mapKey = entry.getKey();
Object mapValue = entry.getValue();
String className=mapValue.getClass().getName();
if(className=="String"){
System.out.println("className:"+className);
params.put(mapKey, (String)mapValue);
}
else if(className=="org.kurento.client.IceCandidate"){
System.out.println("className1:"+className);
IceCandidate candidate=(IceCandidate)mapValue;
params.put("sdpMid", candidate.getSdpMid());
params.put("sdpMLineIndex", candidate.getSdpMLineIndex());
params.put("candidate", candidate.getCandidate());
}
else{
System.out.println("className4:"+className);
params.put(mapKey, mapValue.toString());
}
}
notification.setNamedParams(params);
return notification;
}
}
IceCandidate部分需要特殊处理一下。
既然添加了jsonrpc2-base,handleTextMessage部分实际上也能用JSONRPC2Notification解析出来处理。
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
JsonObject jsonMessage=GetJsonMessage(message);
if(jsonMessage==null)return;
String sessionId = session.getId();
log.debug("Incoming message {} from sessionId", jsonMessage, sessionId);
JsonElement jsonrpcElement=jsonMessage.get("jsonrpc");
if(jsonrpcElement!=null){//KurentoRoomAPI发送过来的
isRpc=true;
String jsonString=jsonMessage.toString();
log.info(">>>>>>>>>>>>>>>>> jsonString="+jsonString);
JSONRPC2Notification jsonRpc=JSONRPC2Notification.parse(jsonString);
log.info(">>>>>>>>>>>>>>>>> jsonRpc="+jsonRpc);
String method=jsonRpc.getMethod();
log.info(">>>>>>>>>>>>>>>>> method="+method);
String jsonrpc=jsonrpcElement.getAsString();
// String method=jsonMessage.get("method").getAsString();
JsonObject paramsObject=jsonMessage.getAsJsonObject("params");
log.info(">>>>>>>>>>>>>>>>> PlayerHandler.handleTextMessage method="+method+",jsonrpc="+jsonrpc);
switch(method){
case "joinRoom":
break;
case "leaveRoom":
break;
case "publishVideo":
break;
case "unpublishVideo":
break;
case "receiveVideoFrom":
break;
case "unsubscribeFromVideo":
break;
case "onIceCandidate":
onIceCandidate(sessionId,jsonRpc);//jsonRpc处理
break;
case "sendMessage":
break;
case "customRequest":
break;
default:
doPlayerCommand(session, paramsObject, sessionId, method);//
break;
}
}
else{ //原来的player
isRpc=false;
JsonElement idElement=jsonMessage.get("id");
if(idElement==null){
log.error("no id element:"+jsonMessage.toString());
return;
}
String id=idElement.getAsString();
log.info(">>>>>>>>>>>>>>>>> PlayerHandler.handleTextMessage id="+id);
doPlayerCommand(session, jsonMessage, sessionId, id);//根据id,进行不同操作
}
}
如果不是要兼容js客户端,可以整个改掉的,或者是修改js客户端发送JSONRPC信息。先这样吧。
本来立刻开始改前后端代码了,结果不成功,没办法,静下心来整理一下整个获取视频的过程。
另外参考:https://blog.csdn.net/fanhenghui/article/details/80229811
总之两边都收到candidate并addCandidate后,连接就建立的,就能获取视频了。
另外发现之前写的代码是漏了服务端添加candidate部分了,补上后视频就出来了。
webrtc是p2p的,这里的服务端其实也相当于p2p的一边。
-----------------
同时姑且整理了一下CallVideo的流程,虽然现在没用。
------------------
接下来就是参考https://github.com/satriyaPhincon/CallVideo,android播放webrtc视频的过程。
1.开始,生成offer
在onCreate或者onStart中初始化NBMWebRTCPeer
private void initWebRtc(){
rootEglBase = EglBase.create();
masterView = findViewById(R.id.gl_surface);
masterView.init(rootEglBase.getEglBaseContext(), null);
masterView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_BALANCED);
//masterView.setMirror(true);//镜像,左右颠倒
localView = findViewById(R.id.gl_surface_local);//界面上有个gl_surface_local的onInitialize
}
在onInitialize中generateOffer
@Override
public void onInitialize() {
Log.e(TAG,"onInitialize:"+false);
nbmWebRTCPeer.generateOffer("remote", false);
}
2. 发送sdpOffer
在onLocalSdpOfferGenerated中发送sdpOffer
@Override
public void onLocalSdpOfferGenerated(SessionDescription localSdpOffer, NBMPeerConnection connection) {
String sdpOffer=localSdpOffer.description;
connectionId = connection.getConnectionId();
int publishVideoRequestId = ++Constants.id;//感觉没什么用
String videoUrl="rtsp://admin:[email protected]:554/cam/realmonitor?channel=1&subtype=0";
kurentoRoomAPI.startPlay(videoUrl,sdpOffer,publishVideoRequestId);
}
3. 发送localCandidate
在onIceCandidate中发送candidate
@Override
public void onIceCandidate(IceCandidate iceCandidate, NBMPeerConnection connection) {
String endpointName=connection.getConnectionId();//暂时没用
int id=12;//不知道什么意思
kurentoRoomAPI.sendOnIceCandidate(endpointName, iceCandidate.sdp,
iceCandidate.sdpMid, Integer.toString(iceCandidate.sdpMLineIndex), id);
}
在onRoomNotification中处理服务端消息
@Override
public void onRoomNotification(RoomNotification notification) {
Log.e("RoomListener","onRoomNotification:"+notification);
String method=notification.getMethod();
switch (method) {
case "startResponse":
startResponse(notification);
break;
case "error":
break;
case "playEnd":
//playEnd();
break;
case "videoInfo":
//showVideoData(parsedMessage);
break;
case "iceCandidate":
addIceCandidate(notification);
break;
case "seek":
break;
case "position":
break;
default:
break;
}
}
1.processAnswer
private void startResponse(RoomNotification response){
String sdpAnswer=response.getParam("sdpAnswer").toString();
SessionDescription sd = new SessionDescription(SessionDescription.Type.ANSWER,sdpAnswer );
nbmWebRTCPeer.processAnswer(sd, connectionId);
}
2 添加remoteCandidate
public void addIceCandidate(RoomNotification notification) {
String sdpMid = notification.getParam("sdpMid").toString();
int sdpMLineIndex = Integer.valueOf(notification.getParam("sdpMLineIndex").toString());
String sdp = notification.getParam("candidate").toString();
IceCandidate ic = new IceCandidate(sdpMid, sdpMLineIndex, sdp);
nbmWebRTCPeer.addRemoteIceCandidate(ic, connectionId);
}
@Override
public void onRemoteStreamAdded(MediaStream stream, NBMPeerConnection connection) {
Log.e("NBMWebRTCPeerObserver", String.format("onRemoteStreamAdded:" + stream + "|" + stream.videoTracks.get(0)));
nbmWebRTCPeer.setActiveMasterStream(stream);
nbmWebRTCPeer.attachRendererToRemoteStream(masterView, stream);
}
因为我这个只是一个视频播放客户端,不需要本地视频。但是generateOffer("remote", false)还是会显示本地视频,要有个地方stopLocalMedia。
1.在generateOffer后面马上stopLocalMedia,会导致远程视频也看不到
2.手动按钮点击stopLocalMedia,则不影响远程视频的播放。
3.手动按钮点击nbmWebRTCPeer.initialize()则本地和远程都会出来。
最终在onResum和onLocalSdpOfferGenerated最后加上了stopLocalMedia。
后续还有封装成类库,给其他app调用;结合Unity播放视频。