视频服务器(11) Kurento[6] Android播放

 

之前做的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.1、考察1

下载参考1里面的https://github.com/nubomedia-vtt/kurento-room-client-android和https://github.com/BaeBae33/webrtc_android。

无法用androidstudio直接打包androidapp,只能看看代码。

kurento-room-client-android是一个library,感觉像是基于websocket的一层封装。

视频服务器(11) Kurento[6] Android播放_第1张图片

webrtc_android则是一个jar包(org.webrtc)的源代码,可能是后面会引用到的核心jar包的源代码(的一个老的版本),同时可能也是上面的kurento-room-client-android后续迭代版本。

视频服务器(11) Kurento[6] Android播放_第2张图片

1.2、考察2

参考2是Kurento官方的接教程,就3个部分,Overview,Installation Guide,Developer Guide。

从Overview里面下载https://github.com/nubomedia-vtt/webrtcpeer-android,感觉像是又对上面的webrtc_android的一层封装,

视频服务器(11) Kurento[6] Android播放_第3张图片

Installation Guide里面教你怎么导入jar包到项目中。

https://bintray.com/nubomedia-vtt/maven/webrtcpeer-android

视频服务器(11) Kurento[6] Android播放_第4张图片

compile 'fi.vtt.nubomedia:webrtcpeer-android:1.1.2'

视频服务器(11) Kurento[6] Android播放_第5张图片

实际我用的androidstudio版本是3.5.2现在已经不用compile了,用implementation,

视频服务器(11) Kurento[6] Android播放_第6张图片

Developer Guide教你怎么写功能了,但是我按着教程,加上个SurfaceViewRender后打包app,结果界面是黑屏了。

视频服务器(11) Kurento[6] Android播放_第7张图片

对了官方教程也有问题(笔误吧)

视频服务器(11) Kurento[6] Android播放_第8张图片

这里的localRender,应该是localView的。

这里卡住了.....

我的android知识不行,很久以前(Android2.*时代)学了一点,所以不知道怎么应变了。

要么先再学习一下android。

1.3、找别人的Demo

尝试用“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.

视频服务器(11) Kurento[6] Android播放_第9张图片

不用管 ok下去

----------

问题2.

视频服务器(11) Kurento[6] Android播放_第10张图片

compile改成implementation

---------

问题3.

Execution failed for task ':app:compileDebugJavaWithJavac'

视频服务器(11) Kurento[6] Android播放_第11张图片

 

视频服务器(11) Kurento[6] Android播放_第12张图片

错误: -source 1.7 中不支持方法引用
(请使用 -source 8 或更高版本以启用方法引用)

错误: -source 1.7 中不支持 lambda 表达式
(请使用 -source 8 或更高版本以启用 lambda 表达式)

参考:https://blog.csdn.net/w1227976200/article/details/79542943

加上

视频服务器(11) Kurento[6] Android播放_第13张图片

出现更多错误(问题4)

视频服务器(11) Kurento[6] Android播放_第14张图片

视频服务器(11) Kurento[6] Android播放_第15张图片

不过应该是已经向前一步了。

为什么有些org.webrtc下面的类可以,有些不行呢?

org.webrtc实际上上libjingle包里面的.

implementation ('io.pristine:libjingle:11139@aar') { transitive=true }

把不行的import拷贝到其他可以打包app的项目中也是不行的。

怀疑是现在的libjingle和11个月前不一样了。

结论:暂时不行。看看代码好了。

-----------------------------------------

二、开发

学习上面的项目代码的基础上,开发AndroidKurentoPlayer。

2.1、播放本地视频

记得要加上:

implementation 'fi.vtt.nubomedia:webrtcpeer-android:1.1.2'

2.1.1 获取权限

需要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();
    }

2.1.2 界面



    
        
        
        

核心是需要在界面上添加

2.2.3 播放本地视频

在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
    }

2.2.4 生命周期相关

参考:Android Activity生命周期解析

视频服务器(11) Kurento[6] Android播放_第16张图片

测试时:onCreate->onStart->onResume->onPause->onResume

在onPause之前有个:E/LB: fail to open file: No such file or directory

视频服务器(11) Kurento[6] Android播放_第17张图片

启动过程中间都有失去焦点过?

加上nbmWebRTCPeer.initialize();的话,是 onCreate->onStart(initialize)->onResume->onPause->onInitialize->onResume

-------------------------------------------

切换程序:onPause->onStop

切回程序:onStart->OnResume

这里除了点问题,onStart里面的initWebRtc()被再次调用了。

那放到onCreate中吧,onCreate(initialize)->onStart->onResume->onPause->onInitialize->onResume 

2.2.5 startLocalMedia

参考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();
            }
        }
    }

从结果上,只是查看本地摄像头视频没什么问题,不知道对于传输视频信息会怎样。

2.2、和服务端通信

通信是基于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信息。先这样吧。

2.3、获取远程视频

本来立刻开始改前后端代码了,结果不成功,没办法,静下心来整理一下整个获取视频的过程。

视频服务器(11) Kurento[6] Android播放_第18张图片

另外参考:https://blog.csdn.net/fanhenghui/article/details/80229811

视频服务器(11) Kurento[6] Android播放_第19张图片

总之两边都收到candidate并addCandidate后,连接就建立的,就能获取视频了。

另外发现之前写的代码是漏了服务端添加candidate部分了,补上后视频就出来了。

webrtc是p2p的,这里的服务端其实也相当于p2p的一边。

-----------------

同时姑且整理了一下CallVideo的流程,虽然现在没用。

视频服务器(11) Kurento[6] Android播放_第20张图片

------------------

接下来就是参考https://github.com/satriyaPhincon/CallVideo,android播放webrtc视频的过程。

2.3.1 客户端发送部分

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);
    }

视频服务器(11) Kurento[6] Android播放_第21张图片

2.3.2 客户端接收部分

在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);
    }

2.3.3 客户端显示视频

    @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);
    }

2.3.4 其他处理

因为我这个只是一个视频播放客户端,不需要本地视频。但是generateOffer("remote", false)还是会显示本地视频,要有个地方stopLocalMedia。

1.在generateOffer后面马上stopLocalMedia,会导致远程视频也看不到

2.手动按钮点击stopLocalMedia,则不影响远程视频的播放。

3.手动按钮点击nbmWebRTCPeer.initialize()则本地和远程都会出来。

最终在onResum和onLocalSdpOfferGenerated最后加上了stopLocalMedia。

后续还有封装成类库,给其他app调用;结合Unity播放视频。

你可能感兴趣的:(WebRTC,Kurento,RTSP)