网络会议Openmeetings下的openmeetings-screenshare文件分析4

2021SC@SDUSC

目录

ScreenShare下的类

Core

CaptureScreen

RTMPClientPublish

RTMPScreenShare

总结


ScreenShare下的类

网络会议openmeetings文件下的openmeetings-screenshare即将分析完毕,该文件下的其它类,上文已经分析完IScreenRncoder接口、IScreenShare接口、BaseScreenEncoder抽象类、AudioTone类以及ScreenV1Encoder类。本篇将对本文件的最后的内容Core、CaptureScreen、RTMPClientPublish以及四个RTMPScreenShare进行分析(四个文件代码几乎一致),则openmeetings-screenshare分析完毕。

Core

Core实现了IpendingServiceCallback和InetStreamEventHandler接口并实现了其中的抽象方法,IPendingServiceCallback接口中的ResultReceived ()方法专门用来处理调用的结果,InetStreamEventHandler接口中onStreamEvent()方法专门处理流事件。

Core类里面定义了一个不变的Java Logger日志记录器对象log,提供了日志记录的API ,可以往控制台/文件中写日志了。

然后定义了几个字符串常量值分别为“ScreenShare”、“RemoteJob”、“RemoteTrigger”、“NetConnection.Connect.Rejected”、“NetConnection.Connect.Failed”来分别对应不同的类和运行情况。

然后定义了一个枚举变量Protocol协议,其中的协议有rtmp、rtmpt、rtmpe、rtmps

再接着定义了IScreenShare接口类对象instance,这里会利用多态来实现并调用不同的方法。

定义了两个URI变量url和fallback分别表示当前url和回退到上一个页面的url。

定义布尔变量fallbackUsed=false表示是否能回退。

定义了字符串变量host、app以及int变量port表示主机号、应用、端口号。

定义了CaptureScreen类对象_capture、RTMPClientPublish类对象publishClient、以及ScreenSharerFrame类对象。

定义了默认quality为1,默认fps为10,是否显示fps为true,布尔变量allowRecording和allowPublishing为ture,表示是否网络允许记录和网络准许发布,布尔变量startSharing、startRecording、startPublishing、connected、readyToRecord、audioNotify均为false,来表示屏幕共享应用的对应状态,remoteEnabled为true(启用远程)、nativeSsl(本机ssl,ssl是一种安全协议,它提供使用TCP/IP的通信应用程序间的隐私与完整性)为false。

定义了SchedulerFactory类对象schdlrFactory和Scheduler对象schdlr,调度器Scheduler是Quartz框架的心脏,用来管理触发器和Job,并保证Job能被触发执行。程序员与框架内部之间的调用都是通过org.quartz.Scheduler接口来完成的。然而我们一般不使用构造方法去创建调度器,而是通过调度器工厂来创建。调度器工厂接口org.quartz.SchedulerFactory提供了两种不同类型的工厂实现,分别是org.quartz.impl.DirectSchedulerFactoryh和org.quartz.impl.StdSchedulerFactory。这里的SchedulerFactory类便是用来初始化创建Scheduler的。

定义了一个LinkedBlockingQueue类队列remoteEvent来表示远程事件。定义了ScreenDimensions类对象dim来表示屏幕的各种维度。

接下来看看构造方法:public Core(String[] args)

初始化dim,为屏幕共享的屏幕设置了不同的维度。

System.setProperty()方法设置键对值的系统属性,其中第一个参数是系统属性的名称,第二个参数是设置系统属性的值,这里设置"org.terracotta.quartz.skipUpdateCheck"为true,这里是设置quartz中的定时检查版本更新为true。然后打印日志,String args的参数。

然后为我的类属性设置初始值,url = URI(args[0]),fallbacl = URI(args[1]), sid = args[2],labelTexts = args[3];defaultQuality = Integer.parseInt(args[4]),defaultFps = Integer.parseInt(args[5]);showFps = bool(args[6]);remoteEnabled = bool(args[7]);allowRecording = bool(args[8]);allowPublishing = bool(args[9]);nativeSsl = bool(args[10])。

然后初始化调度器schdlr,设置JobDetail和Trigger类对象,当一个trigger被触发时,与之关联的JobDetail实例会被加载,JobDetail引用的job类通过配置在Scheduler上的JobFactory进行初始化。这里的JobDetail类remoteJob用JobBuilder类.build()方法来初始化,设置新的job,Trigger类对象cursorTrigger用TriggerBuilder类.build()方法来初始化,设置触发,然后加入调度器中等待调度。

下面的getCapture()方法即是判断我的类变量_capture是否为空,如果为空则new一个,返回该变量。

setInstance()方法,根据传进来的参数URI来获取uri中的协议,根据uri获取host、port和app的值,对应的方法直接调用即可getHost()、getPort()、getPath().substring(1)。然后根据不同的协议调用不同的类构造方法初始化instance。rtmp对应的是RTMPScreenShare,rtmpt对应的是RTMPTScreenShare,rtmps对应的是RTMPSScreenShare,否则抛出RuntimeException错误。然后log设置此处debug值,里面写的是对应的host、port、app以及sid的值用于调试。

接下来的createWindow()方法,则是初始化frame属性值,new一个ScreenSharerFrame对象,前文提到了这个界面是用javaswing写的,设置为可见,调用setVisible(true),然后设置允许记录标签和准许发布标签是否启用。

接下来的sendCursorStatus()方法,获取鼠标的实际位置x和y,判断是否连接,如果没有,则利用Red5类调用setConnectionLocal方法建立连接,然后调用invoke方法à查找并调用传入的参数中对应的方法,设置新的触发器位置,这个触发器指鼠标,位置即为鼠标的位置。如果设置失败,则frame设置状态err,log打印日志err。

接下来的setConnectionAsSharingClient()方法,首先打印debug日志,方便调试,首先判断是否连接,若没有则利用Red5类调用setConnectionLocal方法建立连接,然后定义一个Mao类对象,首先获取dim的X和Y大小,然后将属性一一建立Map映射放入map中,然后继续检查是否连接,然后调用invoke方法à查找并调用传入的参数中对应的方法,设置用户端的连接,第二个参数为map组,否则设置frame的状态为error,log打印日志err。

接下来的sharingStart()方法,即开始任务调度(屏幕共享),schdlr.start(),将startSharing设置为true。recordingStart()方法,将startRecording设置为true。publishingStart()方法,将startPublishing设置为true。上面三个方法均调用该类里面的captureScreenStart()方法,即开始屏幕捕捉。后文再继续分析该方法。

接下来的connect()方法则是instance调用connect()方法建立连接。handleException()方法则是处理异常,回调上一个网页,然后调用该Core类里面connect()即前面那个方法重新建立连接。然后captureScreenStart()方法,如果没有连接则重新建立连接,然后调用上文setConnectionAsSharingClient方法,调用invoke方法设置用户端的连接,开始对屏幕进行捕捉。invoke方法的使用需要导入Red5 的package包。

接下来的sharingStop()、recordingStop()、publishingStop()方法均是将对应的属性设置为false,然后调用captureScreenStop()方法。然后后面的captureScreenStop()方法则是即停止屏幕捕捉。调用invoke方法,根据传入的参数调用screenSharerAction方法。invoke方法的使用需要导入Red5 的package包。然后stopSharing()方法则是schdlr.standby(),设置Scheduler为standby模式会让调度器暂停寻找Job去执行。然后设置startSharing属性为false。后来的stopRecording()、stopPublishing()则是将状态设为false,断开连接。

后面的onCommand()方法,是对待处理的命令(传参)进行处理。接下来的stopStream()方法,会停止任何活动并断开连接,使用未使用的参数来执行调用。resultReceived()方法,根据传入的参数来得到调用的方法名字,建立连接,再根据该名字调用不同的方法来完成该操作,追踪并log记录,返回不同的结果。

其他方法即是一些get方法,即获取并返回某些属性值或对象。

public class Core implements IPendingServiceCallback, INetStreamEventHandler {
   private static final Logger log = getLogger(Core.class);
   static final String QUARTZ_GROUP_NAME = "ScreenShare";
   static final String QUARTZ_REMOTE_JOB_NAME = "RemoteJob";
   static final String QUARTZ_REMOTE_TRIGGER_NAME = "RemoteTrigger";
   private static final String CONNECT_REJECTED = "NetConnection.Connect.Rejected";
   private static final String CONNECT_FAILED = "NetConnection.Connect.Failed";

   enum Protocol {
      rtmp, rtmpt, rtmpe, rtmps
   }
   private IScreenShare instance = null;
   private URI url;
   private URI fallback;
   private boolean fallbackUsed = false;
   private String host;
   private String app;
   private int port;

   private String sid;
   private CaptureScreen _capture = null;
   private RTMPClientPublish publishClient = null;

   private ScreenSharerFrame frame;

   private int defaultQuality = 1;
   private int defaultFps = 10;
   private boolean showFps = true;

   private boolean allowRecording = true;
   private boolean allowPublishing = true;

   private boolean startSharing = false;
   private boolean startRecording = false;
   private boolean startPublishing = false;
   private boolean connected = false;
   private boolean readyToRecord = false;
   private boolean audioNotify = false;
   private boolean remoteEnabled = true;
   private boolean nativeSsl = false;
   private SchedulerFactory schdlrFactory;
   private Scheduler schdlr;
   private LinkedBlockingQueue<Map<String, Object>> remoteEvents = new LinkedBlockingQueue<>();
   private final ScreenDimensions dim;

   public Core(String[] args) {
      dim = new ScreenDimensions();
      try {
         System.setProperty("org.terracotta.quartz.skipUpdateCheck", "true");
         for (String arg : args) {
            log.debug("arg: " + arg);
         }
         String[] textArray = null;
         if (args.length > 8) {
            url = new URI(args[0]);
            fallback = new URI(args[1]);
            sid = args[2];
            String labelTexts = args[3];
            defaultQuality = Integer.parseInt(args[4]);
            defaultFps = Integer.parseInt(args[5]);
            showFps = bool(args[6]);
            remoteEnabled = bool(args[7]);
            allowRecording = bool(args[8]);
            allowPublishing = bool(args[9]);
            nativeSsl = bool(args[10]);

            if (labelTexts.length() > 0) {
               textArray = labelTexts.split(";");

               log.debug("labelTexts :: " + labelTexts);
               log.debug("textArray Length " + textArray.length);
               for (int i = 0; i < textArray.length; i++) {
                  log.debug(i + " :: " + textArray[i]);
               }
            }
         } else {
            System.exit(0);
         }
         schdlrFactory = new StdSchedulerFactory(getQurtzProps("CoreScreenShare"));
         schdlr = schdlrFactory.getScheduler();
         JobDetail remoteJob = JobBuilder.newJob(RemoteJob.class).withIdentity(QUARTZ_REMOTE_JOB_NAME, QUARTZ_GROUP_NAME).build();
         Trigger cursorTrigger = TriggerBuilder.newTrigger()
               .withIdentity(QUARTZ_REMOTE_TRIGGER_NAME, QUARTZ_GROUP_NAME)
               .withSchedule(simpleSchedule().withIntervalInMilliseconds(50).repeatForever())
               .build();
         remoteJob.getJobDataMap().put(RemoteJob.CORE_KEY, this);
         schdlr.scheduleJob(remoteJob, cursorTrigger);

         createWindow(textArray);
      } catch (Exception err) {
         log.error("", err);
      }
   }

   private CaptureScreen getCapture() {
      if (_capture == null) {
         _capture = new CaptureScreen(this, instance, host, app, port);
      }
      return _capture;
   }

   private void setInstance(URI uri) {
      Protocol protocol = Protocol.valueOf(uri.getScheme());
      host = uri.getHost();
      port = uri.getPort();
      app = uri.getPath().substring(1);

      switch (protocol) {
         case rtmp:
            instance = new RTMPScreenShare(this);
            break;
         case rtmpt:
            instance = new RTMPTScreenShare(this);
            break;
         case rtmps:
            if (nativeSsl) {
               RTMPSScreenShare client = new RTMPSScreenShare(this);
               instance = client;
            } else {
               instance = new RTMPTSScreenShare(this);
            }
            break;
         case rtmpe:
         default:
            throw new RuntimeException("Unsupported protocol");
      }
      instance.setServiceProvider(this);
      log.debug(String.format("host: %s, port: %s, app: %s, publish: %s", host, port, app, sid));
   }

   // ------------------------------------------------------------------------
   //
   // Main
   //
   // ------------------------------------------------------------------------
   public static void main(String[] args) {
      new Core(args);
   }

   // ------------------------------------------------------------------------
   //
   // GUI
   //
   // ------------------------------------------------------------------------
   public void createWindow(String[] textArray) {
      try {
         frame = new ScreenSharerFrame(this, textArray);
         frame.setVisible(true);
         frame.setRecordingTabEnabled(allowRecording);
         frame.setPublishingTabEnabled(allowPublishing);
         log.debug("initialized");
      } catch (Exception err) {
         log.error("createWindow Exception: ", err);
      }
   }

   public void sendCursorStatus() {
      try {
         Point mouseP = MouseInfo.getPointerInfo().getLocation();

         float scaleFactor = (1.0f * dim.getResizeX()) / dim.getSpinnerWidth();

         // Real size: Real mouse position = Resize : X
         int x = (int)((mouseP.getX() - dim.getSpinnerX()) * scaleFactor);
         int y = (int)((mouseP.getY() - dim.getSpinnerY()) * scaleFactor);

         if (instance.getConnection() != null) {
            if (Red5.getConnectionLocal() == null) {
               Red5.setConnectionLocal(instance.getConnection());
            }
            instance.invoke("setNewCursorPosition", new Object[] { x, y }, this);
         }
      } catch (NullPointerException npe) {
         //noop
      } catch (Exception err) {
         frame.setStatus("Exception: " + err);
         log.error("[sendCursorStatus]", err);
      }
   }

   /**
    * @param id The streamid sent by server
    */
   public void setId(String id) {
      //no-op
   }

   public void setConnectionAsSharingClient() {
      log.debug("########## setConnectionAsSharingClient");
      try {
         if (Red5.getConnectionLocal() == null) {
            Red5.setConnectionLocal(instance.getConnection());
         }
         Map<String, Object> map = new HashMap<>();

         int scaledWidth = dim.getResizeX();
         int scaledHeight = dim.getResizeY();

         map.put("screenWidth", scaledWidth);
         map.put("screenHeight", scaledHeight);
         map.put("startRecording", startRecording);
         map.put("startStreaming", startSharing);
         map.put("startPublishing", startPublishing);
         map.put("publishingHost", frame.getPublishHost());
         map.put("publishingApp", frame.getPublishApp());
         map.put("publishingId", frame.getPublishId());
         if (Red5.getConnectionLocal() == null) {
            Red5.setConnectionLocal(instance.getConnection());
         }
         instance.invoke("setConnectionAsSharingClient", new Object[] { map }, this);
      } catch (Exception err) {
         frame.setStatus("Error: " + err.getLocalizedMessage());
         log.error("[setConnectionAsSharingClient]", err);
      }
   }

   public void sharingStart() {
      try {
         schdlr.start();
      } catch (SchedulerException e) {
         log.error("[schdlr.start]", e);
      }
      startSharing = true;
      captureScreenStart();
   }

   public void recordingStart() {
      startRecording= true;
      captureScreenStart();
   }

   public void publishingStart() {
      startPublishing = true;
      captureScreenStart();
   }

   private void connect(String sid) {
      setInstance(fallbackUsed ? fallback : url);
      Map<String, Object> map = instance.makeDefaultConnectionParams(host, port, app);
      map.put("screenClient", true);
      Map<String, Object> params = new HashMap<>();
      params.put("sid", sid);
      instance.connect(host, port, map, this, new Object[]{params});
   }

   void handleException(Throwable e) {
      frame.setStatus("Exception: " + e);
      if (e instanceof ConnectException) {
         fallbackUsed = true;
         connect(sid);
      }
   }

   private void captureScreenStart() {
      try {
         log.debug("captureScreenStart");

         if (!connected) {
            connect(sid);
         } else {
            setConnectionAsSharingClient();
         }
      } catch (Exception err) {
         log.error("captureScreenStart Exception: ", err);
         frame.setStatus("Exception: " + err);
      }
   }

   public void sharingStop() {
      startSharing = false;
      captureScreenStop("stopStreaming");
   }

   public void recordingStop() {
      startRecording = false;
      captureScreenStop("stopRecording");
   }

   public void publishingStop() {
      startPublishing = false;
      captureScreenStop("stopPublishing");
   }

   private void captureScreenStop(String action) {
      try {
         log.debug("INVOKE screenSharerAction" );

         Map<String, Object> map = new HashMap<>();
         map.put(action, true);

         if (Red5.getConnectionLocal() == null) {
            Red5.setConnectionLocal(instance.getConnection());
         }
         instance.invoke("screenSharerAction", new Object[] { map }, this);
      } catch (Exception err) {
         log.error("captureScreenStop Exception: ", err);
         frame.setStatus("Exception: " + err);
      }
   }

   public void stopSharing() {
      try {
         schdlr.standby();
      } catch (SchedulerException e) {
         log.error("[schdlr.standby]", e);
      }
      frame.setSharingStatus(false, !startPublishing && !startRecording && !startSharing);
      startSharing = false;
   }

   public void stopRecording() {
      frame.setRecordingStatus(false, !startPublishing && !startRecording && !startSharing);
      startRecording = false;
   }

   public void stopPublishing() {
      frame.setPublishingStatus(false, !startPublishing && !startRecording && !startSharing);
      startPublishing = false;
      if (publishClient != null) {
         publishClient.disconnect();
         publishClient = null;
      }
   }

   public synchronized boolean isReadyToRecord() {
      return readyToRecord;
   }

   private synchronized void setReadyToRecord(boolean readyToRecord) {
      this.readyToRecord = readyToRecord;
   }

   /**
    * @param command - command to be processed
    */
   protected void onCommand(ICommand command) {
      if (!(command instanceof Notify)) {
         return;
      }
      Notify invoke = (Notify)command;
      if (invoke.getType() == IEvent.Type.STREAM_DATA) {
         return;
      }

      String method = invoke.getCall().getServiceMethodName();
      if ("screenSharerAction".equals(method)) {
         Object[] args = invoke.getCall().getArguments();
         if (args != null && args.length > 0) {
            @SuppressWarnings("unchecked")
            Map<String, Object> params = (Map<String, Object>)args[0];
            if (bool(params.get("stopPublishing"))) {
               stopPublishing();
            }
            if (params.containsKey("error")) {
               frame.setStatus("" + params.get("error"));
            }
         }
      }
   }

   /**
    * Will stop any activity and disconnect
    *
    * @param obj - dummy unused param to perform the call
    */
   public void stopStream(Object obj) {
      try {
         log.debug("ScreenShare stopStream");

         stopSharing();
         stopRecording();
         stopPublishing();
         connected = false;

         if (instance != null) {
            instance.disconnect();
         }
         setReadyToRecord(false);
         getCapture().setStartPublish(false);
         getCapture().release();
         _capture = null;
      } catch (Exception e) {
         log.error("ScreenShare stopStream exception " + e);
      }
   }

   @Override
   public void onStreamEvent(Notify notify) {
      log.debug( "onStreamEvent " + notify );

      @SuppressWarnings("rawtypes")
      ObjectMap map = (ObjectMap) notify.getCall().getArguments()[0];
      String code = (String) map.get("code");

      if (StatusCodes.NS_PUBLISH_START.equals(code)) {
         log.debug( "onStreamEvent Publish start" );
         getCapture().setStartPublish(true);
         setReadyToRecord(true);
      }
   }

   private static boolean bool(Object b) {
      return TRUE.equals(Boolean.valueOf("" + b));
   }

   public void sendRemoteCursorEvent(Map<String, Object> obj) {
      if (!remoteEnabled) {
         return;
      }
      log.trace("#### sendRemoteCursorEvent ");
      log.trace("Result Map Type "+ obj);

      if (obj != null) {
         remoteEvents.offer(obj);
         log.trace("Action offered:: {}, count: {}", obj, remoteEvents.size());
      }
   }

   @Override
   public void resultReceived(IPendingServiceCall call) {
      try {
         log.trace("service call result: " + call);
         if (call == null) {
            return;
         }

         String method = call.getServiceMethodName();
         Object o = call.getResult();
         if (log.isTraceEnabled()) {
            log.trace("Result Map Type " + (o == null ? null : o.getClass().getName()));
            log.trace("" + o);
         }
         @SuppressWarnings("unchecked")
         Map<String, Object> returnMap = (o != null && o instanceof Map) ? (Map<String, Object>) o : new HashMap<>();
         log.trace("call ### get Method Name " + method);
         if ("connect".equals(method)) {
            Object code = returnMap.get("code");
            if (CONNECT_FAILED.equals(code) && !fallbackUsed) {
               fallbackUsed = true;
               connect(sid);
               frame.setStatus("Re-connecting using fallback");
               return;
            }
            if (CONNECT_FAILED.equals(code) || CONNECT_REJECTED.equals(code)) {
               frame.setStatus(String.format("Error: %s %s", code, returnMap.get("description")));
               return;
            }
            connected = true;
            setConnectionAsSharingClient();
         } else if ("setConnectionAsSharingClient".equals(method)) {
            if (!bool(returnMap.get("alreadyPublished"))) {
               log.trace("Stream not yet started - do it ");

               instance.createStream(this);
            } else {
               log.trace("The Stream was already started ");
            }
            if (o != null) {
               Object modus = returnMap.get("modus");
               if ("startStreaming".equals(modus)) {
                  frame.setSharingStatus(true, false);
               } else if ("startRecording".equals(modus)) {
                  frame.setRecordingStatus(true, false);
               } else if ("startPublishing".equals(modus)) {
                  frame.setPublishingStatus(true, false);
                  publishClient = new RTMPClientPublish(
                     this
                     , frame.getPublishHost()
                     , frame.getPublishApp()
                     , frame.getPublishId());
                  publishClient.connect();
               }
            } else {
               String err = "Could not aquire modus for event setConnectionAsSharingClient";
               frame.setStatus(String.format("Error: %s", err));
               return;
            }
         } else if ("createStream".equals(method)) {
            if (startRecording || startSharing) {
               CaptureScreen capture = getCapture();
               if (o != null && o instanceof Number) {
                  if (capture.getStreamId() != null) {
                     instance.unpublish(capture.getStreamId());
                  }
                  capture.setStreamId((Number)o);
               }
               final String broadcastId = UUID.randomUUID().toString();
               log.debug("createPublishStream result stream id: {}; name: {}", capture.getStreamId(), broadcastId);
               instance.publish(capture.getStreamId(), broadcastId, "live", this);

               log.debug("setup capture thread spinnerWidth = {}; spinnerHeight = {};", dim.getSpinnerWidth(), dim.getSpinnerHeight());

               if (!capture.isStarted()) {
                  capture.setSendCursor(startSharing);
                  capture.start();
               }
            }
         } else if ("screenSharerAction".equals(method)) {
            Object result = returnMap.get("result");
            if ("stopAll".equals(result)) {
               log.trace("Stopping to stream, there is neither a Desktop Sharing nor Recording anymore");
               stopStream(null);
            } else if ("stopSharingOnly".equals(result)) {
               stopSharing();
            } else if ("stopRecordingOnly".equals(result)) {
               stopRecording();
            } else if ("stopPublishingOnly".equals(result)) {
               stopPublishing();
            }
         } else if ("setNewCursorPosition".equals(method)) {
            // Do not do anything
         } else {
            log.debug("Unknown method " + method);
         }

      } catch (Exception err) {
         log.error("[resultReceived]", err);
      }
   }

   public boolean isAudioNotify() {
      return audioNotify;
   }

   public void setAudioNotify(boolean audioNotify) {
      this.audioNotify = audioNotify;
   }

   public boolean isRemoteEnabled() {
      return remoteEnabled;
   }

   public void setRemoteEnabled(boolean remoteEnabled) {
      this.remoteEnabled = remoteEnabled;
   }

   public void setDeadlockGuard(RTMPConnection conn) {
      ThreadPoolTaskScheduler deadlockGuard = new ThreadPoolTaskScheduler();
      deadlockGuard.setPoolSize(16);
      deadlockGuard.setDaemon(false);
      deadlockGuard.setWaitForTasksToCompleteOnShutdown(true);
      deadlockGuard.setThreadNamePrefix("DeadlockGuardScheduler-");
      deadlockGuard.afterPropertiesSet();
      conn.setDeadlockGuardScheduler(deadlockGuard);
   }

   public IScreenShare getInstance() {
      return instance;
   }

   public LinkedBlockingQueue<Map<String, Object>> getRemoteEvents() {
      return remoteEvents;
   }

   public ScreenDimensions getDim() {
      return dim;
   }

   public int getDefaultQuality() {
      return defaultQuality;
   }

   public int getDefaultFps() {
      return defaultFps;
   }

   public boolean isShowFps() {
      return showFps;
   }
}

CaptureScreen

该CaptureScreen类继承了Thread线程类并重写了run方法。

首先看该类的构造方法,CaptureScreen(),初始化类属性core,连接客户client,主机host,应用app以及端口号port,cursorJob作业和cursorTrigger触发器。然后getScheduler()方法是通过抽象工厂构造方法的类SchedulerFactory来初始化任务调度器_scheduler并返回。release()方法是任务调度器调用shutdown方法来关闭任务调度,等待所有正在执行的任务执行完毕后关闭该任务调度器。

run()方法则是对Job任务调度的初始化以及调度开始,将EncodeJob类和SendJob类建立map映射加入任务调度序列,然后分别添加触发器,调用Scheduler类的start方法,开始任务调度。当该线程开始则自动调用run方法。

public class CaptureScreen extends Thread {
   private static final Logger log = getLogger(CaptureScreen.class);
   private static final String QUARTZ_CURSOR_TRIGGER_NAME = "CursorTrigger";
   private static final String QUARTZ_CURSOR_JOB_NAME = "CursorJob";
   private final Core core;
   private int timestampDelta;
   private volatile AtomicInteger timestamp = new AtomicInteger(0);
   private volatile AtomicBoolean sendFrameGuard = new AtomicBoolean(false);
   private long startTime = 0;
   private volatile boolean active = true;
   private IScreenEncoder se;
   private IScreenShare client;
   private Queue<VideoData> frames = new ArrayBlockingQueue<>(2);
   private String host = null;
   private String app = null;
   private int port = -1;
   private Number streamId;
   private boolean startPublish = false;
   private Scheduler _scheduler;
   private final JobDetail cursorJob;
   private final Trigger cursorTrigger;

   public CaptureScreen(Core coreScreenShare, IScreenShare client, String host, String app, int port) {
      core = coreScreenShare;
      this.client = client;
      this.host = host;
      this.app = app;
      this.port = port;
      cursorJob = JobBuilder.newJob(CursorJob.class).withIdentity(QUARTZ_CURSOR_JOB_NAME, QUARTZ_GROUP_NAME).build();
      cursorTrigger = TriggerBuilder.newTrigger()
            .withIdentity(QUARTZ_CURSOR_TRIGGER_NAME, QUARTZ_GROUP_NAME)
            .withSchedule(simpleSchedule().withIntervalInMilliseconds(1000 / Math.min(5, core.getDim().getFps())).repeatForever())
            .build();
      cursorJob.getJobDataMap().put(CursorJob.CAPTURE_KEY, this);
   }

   private Scheduler getScheduler() {
      if (_scheduler == null) {
         try {
            SchedulerFactory schdlrFactory = new StdSchedulerFactory(getQurtzProps("CaptureScreen"));
            _scheduler = schdlrFactory.getScheduler();
         } catch (SchedulerException e) {
            log.error("Unexpected error while creating scheduler", e);
         }
      }
      return _scheduler;
   }

   public void release() {
      try {
         if (_scheduler != null) {
            _scheduler.shutdown(true);
            _scheduler = null;
         }
      } catch (Exception e) {
         log.error("Unexpected error while shutting down scheduler", e);
      }
      active = false;
      timestamp = new AtomicInteger(0);
      startTime = 0;
   }

   @Override
   public void run() {
      try {
         while (active && !core.isReadyToRecord()) {
            Thread.sleep(60);
         }

         timestampDelta = 1000 / core.getDim().getFps();
         se = new ScreenV1Encoder(core.getDim()); //send keyframe every 3 seconds
         startTime = System.currentTimeMillis();

         JobDetail encodeJob = JobBuilder.newJob(EncodeJob.class).withIdentity("EncodeJob", QUARTZ_GROUP_NAME).build();
         encodeJob.getJobDataMap().put(EncodeJob.CAPTURE_KEY, this);
         Trigger encodeTrigger = TriggerBuilder.newTrigger()
               .withIdentity("EncodeTrigger", QUARTZ_GROUP_NAME)
               .withSchedule(simpleSchedule().withIntervalInMilliseconds(timestampDelta).repeatForever())
               .build();
         JobDetail sendJob = JobBuilder.newJob(SendJob.class).withIdentity("SendJob", QUARTZ_GROUP_NAME).build();
         Trigger sendTrigger = TriggerBuilder.newTrigger()
               .withIdentity("SendTrigger", QUARTZ_GROUP_NAME)
               .withSchedule(simpleSchedule().withIntervalInMilliseconds(timestampDelta).repeatForever())
               .build();
         sendJob.getJobDataMap().put(SendJob.CAPTURE_KEY, this);

         Scheduler s = getScheduler();
         s.scheduleJob(encodeJob, encodeTrigger);
         s.scheduleJob(sendJob, sendTrigger);
         s.start();
      } catch (Exception e) {
         log.error("Error while running: ", e);
      }
   }

   public void pushVideo(VideoData data, int ts) {
      if (startPublish) {
         if (Red5.getConnectionLocal() == null) {
            Red5.setConnectionLocal(client.getConnection());
         }
         RTMPMessage rtmpMsg = RTMPMessage.build(data, ts);
         client.publishStreamData(streamId, rtmpMsg);
      }
   }

   public String getHost() {
      return host;
   }

   public String getApp() {
      return app;
   }

   public int getPort() {
      return port;
   }

   public Number getStreamId() {
      return streamId;
   }

   public void setStreamId(Number streamId) {
      this.streamId = streamId;
   }

   public void setStartPublish(boolean startPublish) {
      this.startPublish = startPublish;
   }

   public IScreenEncoder getEncoder() {
      return se;
   }

   public Queue<VideoData> getFrames() {
      return frames;
   }

   public void setSendFrameGuard(boolean b) {
      sendFrameGuard.set(b);
   }

   public boolean getSendFrameGuard() {
      return sendFrameGuard.get();
   }

   public AtomicInteger getTimestamp() {
      return timestamp;
   }

   public long getStartTime() {
      return startTime;
   }

   public int getTimestampDelta() {
      return timestampDelta;
   }

   public void sendCursorStatus() {
      core.sendCursorStatus();
   }

   public boolean isStarted() throws SchedulerException {
      return active && _scheduler != null && _scheduler.isStarted() && !_scheduler.isShutdown();
   }

   public void setSendCursor(boolean sendCursor) {
      try {
         Scheduler s = getScheduler();
         if (sendCursor) {
            s.scheduleJob(cursorJob, cursorTrigger);
         } else {
            s.deleteJob(JobKey.jobKey(QUARTZ_CURSOR_JOB_NAME, QUARTZ_GROUP_NAME));
         }
      } catch (SchedulerException e) {
         log.error("Unexpected Error schedule/unschedule cursor job", e);
      }
   }

   public ScreenDimensions getDim() {
      return core.getDim();
   }
}

RTMPClientPublish

RTMPClientPublish类继承了RTMPClient类,实现了IPendingServiceCallback, INetStreamEventHandler, IScreenShare三个接口,构造方法即是对类属性id,core等的初始化,hanleException()方法则是log记录打印error错误,connectionOpened()方法是建立连接,创建流,connectionClosed()方法相反,关闭连接,释放关闭任务调度器进行的任务调度(进行完目前的任务)。resultReceived()方法是IPendingServiceCallback接口里的抽象方法的实现,获取参数里面的方法名,如果为createStream则创建流,设置流id和模式,然后调用start方法开始执行该线程。

class RTMPClientPublish extends RTMPClient implements IPendingServiceCallback, INetStreamEventHandler, IScreenShare {
   private static final Logger logger = LoggerFactory.getLogger(RTMPClientPublish.class);
   private final CaptureScreen publishScreen;
   private String id;
   private Core core;

   RTMPClientPublish(Core core, String host, String app, String id) {
      this.id = id;
      this.core = core;
      publishScreen = new CaptureScreen(core, this, host, app, 1935);
   }

   public void setCore(Core core) {
      this.core = core;
   }

   public void connect() {
      super.connect(publishScreen.getHost(), 1935, publishScreen.getApp(), this);
   }

   @Override
   public void handleException(Throwable throwable) {
      logger.error("ERROR", throwable);
   }

   @Override
   public void connectionOpened(RTMPConnection conn) {
      super.connectionOpened(conn);
      createStream(this);
   }

   @Override
   public void connectionClosed(RTMPConnection conn) {
      super.connectionClosed(conn);
      connectionClosed();
   }

   private void connectionClosed() {
      publishScreen.setStartPublish(false);
      publishScreen.release();
      core.publishingStop();
   }

   @Override
   public void resultReceived(IPendingServiceCall call) {
      String method = call == null ? null : call.getServiceMethodName();
      logger.trace("call ### get Method Name " + method);
      if ("createStream".equals(method)) {
         if (call.getResult() != null) {
            publishScreen.setStreamId((Integer)call.getResult());
            publish(publishScreen.getStreamId(), id, "live", this);
            publishScreen.setStartPublish(true);
            publishScreen.start();
         } else {
            connectionClosed();
         }
      }
   }

   @Override
   public void onStreamEvent(Notify notify) {
      //no-op
   }
}

RTMPScreenShare

下面的四个类代码几乎一致,RTMPTSScreenShare类继承了RTMPTSClient类,实现了IScreenShare接口。初始化类变量Core类对象core,connectionOpened方法则是建立连接,调用Core类的setDeadlockGuard方法,建立锁机制。connectionClosed方法关闭连接,关闭流。onCommand()方法处理待进行的命令。handleException方法调用core. handleException处理异常。

public class RTMPTSScreenShare extends RTMPTSClient implements IScreenShare {
   private static final Logger log = LoggerFactory.getLogger(RTMPTSScreenShare.class);

   private final Core core;

   public RTMPTSScreenShare(Core core) {
      this.core = core;
   };

   @Override
   public void connectionOpened(RTMPConnection conn) {
      log.debug("connection opened");
      super.connectionOpened(conn);
      core.setDeadlockGuard(conn);
   }

   @Override
   public void connectionClosed(RTMPConnection conn) {
      log.debug("connection closed");
      super.connectionClosed(conn);
      if (core.isAudioNotify()) {
         AudioTone.play();
      }
      core.stopStream(null);
   }

   @Override
   protected void onCommand(RTMPConnection conn, Channel channel, Header source, ICommand command) {
      super.onCommand(conn, channel, source, command);
      core.onCommand(command);
   }

   @Override
   public void handleException(Throwable throwable) {
      Throwable cause = throwable.getCause();
      log.error("{}", new Object[] { cause });
      core.handleException(cause);
   }
}

总结

openmeetings-screenshare文件下的所有文件已经分析完毕,该文件主要是对会议进行中屏幕共享这一功能的处理,可以让用户在会议过程中对自己屏幕进行分享,让会议更加的方便。可以查看往期内容。之后将对openmeetings-util文件进行分析。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值