Room demo tutorial - Kurento Room 6.1.1 documentation

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。甚是得朕心






































最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,924评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,781评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,813评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,264评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,273评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,383评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,800评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,482评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,673评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,497评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,545评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,240评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,802评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,866评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,101评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,673评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,245评论 2 341