美文网首页
Room demo tutorial - Kurento Roo

Room demo tutorial - Kurento Roo

作者: 你就不能换个昵称 | 来源:发表于2017-12-08 19:59 被阅读0次

    Room demo tutorial

    本教程是使用Room API SDK开发多协作应用程序的指南,共有三个基本结构,也就是三个文件夹,三个工程——kurento-room-server, kurento-room-client-jskurento-room-demo
    代码

    一、服务器端代码(server side code)

    主类是KurentoRoomServerApp.java,同样为一个springboot应用类,我们将会在这个类里用Spring bean的方式来实例化构成服务器端的各个组件。

    1.带通知的房间管理(room management)

    这里用了 Room SDK 来管理房间和用户,我们选择的API——NotificationRoomManager类是一个通知风格的API,把它定义为一个Spring bean,以便以后依赖的注入。
    但首先我们要给NotificationRoomManager的构造器提供一个 UserNotificationService 的实例作为参数,代码中为notificationService,它是一个 JsonRpcNotificationService 类型的对象,用于存储JSON-RPC会话,以支持向客户按发送响应和通知。另一个参数为kmsManager。下图代码先定义了两个参数,再用两个参数送到构造器来定义 NotificationRoomManager

      @Bean
      @ConditionalOnMissingBean
      public KurentoClientProvider kmsManager() {
    
        JsonArray kmsUris = getPropertyJson(KMSS_URIS_PROPERTY, KMSS_URIS_DEFAULT, JsonArray.class);
        List<String> kmsWsUris = JsonUtils.toStringList(kmsUris);
    
        if (kmsWsUris.isEmpty()) {
          throw new IllegalArgumentException(KMSS_URIS_PROPERTY
              + " should contain at least one kms url");
        }
    
        String firstKmsWsUri = kmsWsUris.get(0);
    
        if (firstKmsWsUri.equals("autodiscovery")) {
          log.info("Using autodiscovery rules to locate KMS on every pipeline");
          return new AutodiscoveryKurentoClientProvider();
        } else {
          log.info("Configuring Kurento Room Server to use first of the following kmss: " + kmsWsUris);
          return new FixedOneKmsManager(firstKmsWsUri);
        }
      }
    
      @Bean
      @ConditionalOnMissingBean
      public JsonRpcNotificationService notificationService() {
        return new JsonRpcNotificationService();
      }
    
      @Bean
      @ConditionalOnMissingBean
      public NotificationRoomManager roomManager() {
        return new NotificationRoomManager(notificationService(), kmsManager());
      }
    

    2.通信(signaling)

    我们的demo用Kureto 提供的 JSON-RPC 服务器库来实现与客户端的交互。
    我们为进来的信息注册了一个handler RoomJsonRpcHandler(整个RoomJsonRpcHandler.java就定义了这一个类)以便之后可以根据方法名字来处理请求。这个类 RoomJsonRpcHandler实现了之前提到的Websocket API。
    在向这个API的registerJsonRpcHandlers方法中(一看到register应该反应过来这个方法在主类中,即KurentoRoomServerApp.java)添加这个handler(RoomJsonRpcHandler类实例化而来的roomHandler)时,要指定路径。

      @Bean
      @ConditionalOnMissingBean
      public RoomJsonRpcHandler roomHandler() {
        return new RoomJsonRpcHandler(userControl(), notificationService());
      }
    
      @Override
      public void registerJsonRpcHandlers(JsonRpcHandlerRegistry registry) {
        registry.addHandler(roomHandler().withPingWatchdog(true), "/room");
      }
    

    回到RoomJsonRpcHandler.java类中,RoomJsonRpcHandler类的主方法handleRequest()会在每次收到客户端的请求时被触发,再出发的时候所有Websocket交流会在一个会话内完成,并且JSON-RPC库会给每个会话提供一个引用。一次请求-应答交换称为一次事务。
    应用会存储每个用户对应的绘画和事务,从而notificationService(主类中定义的)在从 Room SDK 中被调用的时候就可以向客户端发相应或服务器事件了。

      public final void handleRequest(Transaction transaction, Request<JsonObject> request)
          throws Exception {
    
        String sessionId = null;
        try {
          sessionId = transaction.getSession().getSessionId();
        } catch (Throwable e) {
          log.warn("Error getting session id from transaction {}", transaction, e);
          throw e;
        }
    
        updateThreadName(HANDLER_THREAD_NAME + "_" + sessionId);
    
        log.debug("Session #{} - request: {}", sessionId, request);
    
        notificationService.addTransaction(transaction, request);
    
        ParticipantRequest participantRequest = new ParticipantRequest(sessionId,
            Integer.toString(request.getId()));
    
        transaction.startAsync();
    
        switch (request.getMethod()) {
          case ProtocolElements.JOINROOM_METHOD :
            userControl.joinRoom(transaction, request, participantRequest);
            break;
          case ProtocolElements.PUBLISHVIDEO_METHOD :
            userControl.publishVideo(transaction, request, participantRequest);
            break;
          case ProtocolElements.UNPUBLISHVIDEO_METHOD :
            userControl.unpublishVideo(transaction, request, participantRequest);
            break;
          case ProtocolElements.RECEIVEVIDEO_METHOD :
            userControl.receiveVideoFrom(transaction, request, participantRequest);
            break;
          case ProtocolElements.UNSUBSCRIBEFROMVIDEO_METHOD :
            userControl.unsubscribeFromVideo(transaction, request, participantRequest);
            break;
          case ProtocolElements.ONICECANDIDATE_METHOD :
            userControl.onIceCandidate(transaction, request, participantRequest);
            break;
          case ProtocolElements.LEAVEROOM_METHOD :
            userControl.leaveRoom(transaction, request, participantRequest);
            break;
          case ProtocolElements.SENDMESSAGE_ROOM_METHOD :
            userControl.sendMessage(transaction, request, participantRequest);
            break;
          case ProtocolElements.CUSTOMREQUEST_METHOD :
            userControl.customRequest(transaction, request, participantRequest);
            break;
          default :
            log.error("Unrecognized request {}", request);
            break;
        }
    
        updateThreadName(HANDLER_THREAD_NAME);
      }
    

    3.管理用户请求(Manage user requests)

    handler把对用户请求的处理委托给了另一个组件,JsonRpcUserControl 类的一个实例,代码中是userControl,在主类中有定义

      @Bean
      @ConditionalOnMissingBean
      public JsonRpcUserControl userControl() {
        return new JsonRpcUserControl(roomManager());
      }
    

    这个对象(userControl)会从用户请求中提取出所需的参数,并且调用部分roomManager的必要部分。用的时候还是在handler里面用,只是具体实现封装到了userControl里面:

        switch (request.getMethod()) {
          case ProtocolElements.JOINROOM_METHOD :
            userControl.joinRoom(transaction, request, participantRequest);
            break;
          case ProtocolElements.PUBLISHVIDEO_METHOD :
            userControl.publishVideo(transaction, request, participantRequest);
            break;
          case ProtocolElements.UNPUBLISHVIDEO_METHOD :
            userControl.unpublishVideo(transaction, request, participantRequest);
            break;
          case ProtocolElements.RECEIVEVIDEO_METHOD :
            userControl.receiveVideoFrom(transaction, request, participantRequest);
            break;
          case ProtocolElements.UNSUBSCRIBEFROMVIDEO_METHOD :
            userControl.unsubscribeFromVideo(transaction, request, participantRequest);
            break;
          case ProtocolElements.ONICECANDIDATE_METHOD :
            userControl.onIceCandidate(transaction, request, participantRequest);
            break;
          case ProtocolElements.LEAVEROOM_METHOD :
            userControl.leaveRoom(transaction, request, participantRequest);
            break;
          case ProtocolElements.SENDMESSAGE_ROOM_METHOD :
            userControl.sendMessage(transaction, request, participantRequest);
            break;
          case ProtocolElements.CUSTOMREQUEST_METHOD :
            userControl.customRequest(transaction, request, participantRequest);
            break;
          default :
            log.error("Unrecognized request {}", request);
            break;
        }
    

    比如在joinRoom()请求中,它要干的事如下:

    public void joinRoom(Transaction transaction, Request<JsonObject> request,
          ParticipantRequest participantRequest) throws IOException, InterruptedException,
          ExecutionException {
        String roomName = getStringParam(request, ProtocolElements.JOINROOM_ROOM_PARAM);
        String userName = getStringParam(request, ProtocolElements.JOINROOM_USER_PARAM);
    
        boolean dataChannels = false;
        if (request.getParams().has(ProtocolElements.JOINROOM_DATACHANNELS_PARAM)) {
          dataChannels = request.getParams().get(ProtocolElements.JOINROOM_DATACHANNELS_PARAM)
              .getAsBoolean();
        }
    
        ParticipantSession participantSession = getParticipantSession(transaction);
        participantSession.setParticipantName(userName);
        participantSession.setRoomName(roomName);
        participantSession.setDataChannels(dataChannels);
    
        roomManager.joinRoom(userName, roomName, dataChannels, true, participantRequest);
      }
    

    4.用户响应与事件

    现在来到了notificationService,由上文知,他是一个JsonRpcNotificationService类型的对象,为RoomJsonRpcHandler类的私有成员。
    这个类将所有用户会话存储为映射,从中可以获取向一个房间请求做出响应所需的事务对象。发送通知用了session对象的功能。
    响应一个特定请求时,对应的事务对象被用完后会被移出内存(getAndRemoveTransaction()),再来相应就是新的事务了。发出错误指示回应也是一样。

      @Override
      public void sendResponse(ParticipantRequest participantRequest, Object result) {
        Transaction t = getAndRemoveTransaction(participantRequest);
        if (t == null) {
          log.error("No transaction found for {}, unable to send result {}", participantRequest, result);
          return;
        }
        try {
          t.sendResponse(result);
        } catch (Exception e) {
          log.error("Exception responding to user ({})", participantRequest, e);
        }
      }
    

    发服务器响应或者服务器事件时,我们需要用到session对象。session对象需要一直保留,直到close session()方法被调用。close session()被调用可以有两种来源,可以是因为用户的离开被roomhandler调用,也可以是因为网络错误被websocket调用。

    二、demo对服务器端的定制

    这个demo替换和修改了一些服务器端代码的spring bean来进行了一些特殊订制。全部在KurentoRoomDemoApp中完成,它先导入了原始server类然后做了修改:

    import org.kurento.room.KurentoRoomServerApp;
    ...
    public class KurentoRoomDemoApp {
       ...
       public static void main(String[] args) throws Exception {
          SpringApplication.run(KurentoRoomDemoApp.class, args);
       }
    }
    

    1.自定义KurentoClientProvider

    我们自定义了FixedNKmsManager作为默认的provider接口,得以管理一系列由在配置文件中指定的URI创建的KurentoClient

    2.自定义用户控制(user control)

    自定义了DemoJsonRpcUserControl来实现对于customRequest这一附加的websocket请求类型的支持。
    在这个类中我们重写了customRequest()方法,以实现切换FaceOverlayFilter,它可以在发布者的头上加一个帽子或者移除。他把滤镜对象当作websocket session的一个属性来存储,方便了滤镜的删除:

      @Override
      public void customRequest(Transaction transaction, Request<JsonObject> request,
          ParticipantRequest participantRequest) {
        try {
          if (request.getParams() == null
              || request.getParams().get(filterType.getCustomRequestParam()) == null) {
            throw new RuntimeException(
                "Request element '" + filterType.getCustomRequestParam() + "' is missing");
          }
          switch (filterType) {
            case MARKER:
              handleMarkerRequest(transaction, request, participantRequest);
              break;
            case HAT:
            default:
              handleHatRequest(transaction, request, participantRequest);
          }
        } catch (Exception e) {
          log.error("Unable to handle custom request", e);
          try {
            transaction.sendError(e);
          } catch (IOException e1) {
            log.warn("Unable to send error response", e1);
          }
        }
      }
    

    3.依赖

    手动删除了一些会造成冲突的依赖。

    三、客户端代码

    这部分描述一下kurento-room-demo中包含的AngularJS应用。

    1.库

    • 在这个时候,我突然脑子一抽觉得,我应该线跑一下demo再来看代码啊。所以我去跑demo,没想到,很惨,又是一堆别人从来没遇到过的错。改了,两天半吧,改了两天半的bug。你能想象官方demo还有bug吗!!!!而且,最主要的是,我也说了,这bug都是别人没遇到过的,所以我去搜解决方案就很少,仅有的比较契合的也都是英文,剩下的那些,中文的什么什么鬼的,完全看不下去。so,到这了,两天半,我终于找到了一个看似可以采用的解决方案,然后自定义得改了改,现在把电脑放着让它自己慢慢解决maven依赖吧,也不知道是不是有效。等之后回来再看吧。虽然还不能确定是否有效,但起码能做点什么了,也不枉我搜了这么多网页,看了那么多英文文档啊。哦,对,在这里表扬一下stack overflow。甚是得朕心






































    相关文章

      网友评论

          本文标题:Room demo tutorial - Kurento Roo

          本文链接:https://www.haomeiwen.com/subject/kvakixtx.html