/**
   * Gets an <tt>Endpoint</tt> participating in this <tt>Conference</tt> which has a specific
   * identifier/ID. If an <tt>Endpoint</tt> participating in this <tt>Conference</tt> with the
   * specified <tt>id</tt> does not exist at the time the method is invoked, the method optionally
   * initializes a new <tt>Endpoint</tt> instance with the specified <tt>id</tt> and adds it to the
   * list of <tt>Endpoint</tt>s participating in this <tt>Conference</tt>.
   *
   * @param id the identifier/ID of the <tt>Endpoint</tt> which is to be returned
   * @return an <tt>Endpoint</tt> participating in this <tt>Conference</tt> which has the specified
   *     <tt>id</tt> or <tt>null</tt> if there is no such <tt>Endpoint</tt> and <tt>create</tt>
   *     equals <tt>false</tt>
   */
  private Endpoint getEndpoint(String id, boolean create) {
    Endpoint endpoint = null;
    boolean changed = false;

    synchronized (endpoints) {
      for (Iterator<WeakReference<Endpoint>> i = endpoints.iterator(); i.hasNext(); ) {
        Endpoint e = i.next().get();

        if (e == null) {
          i.remove();
          changed = true;
        } else if (e.getID().equals(id)) {
          endpoint = e;
        }
      }

      if (create && endpoint == null) {
        endpoint = new Endpoint(id, this);
        // The propertyChangeListener will weakly reference this
        // Conference and will unregister itself from the endpoint
        // sooner or later.
        endpoint.addPropertyChangeListener(propertyChangeListener);
        endpoints.add(new WeakReference<>(endpoint));
        changed = true;

        EventAdmin eventAdmin = videobridge.getEventAdmin();
        if (eventAdmin != null) eventAdmin.sendEvent(EventFactory.endpointCreated(endpoint));
      }
    }

    if (changed) firePropertyChange(ENDPOINTS_PROPERTY_NAME, null, null);

    return endpoint;
  }
  /**
   * Notifies this instance that there was a change in the value of a property of an
   * <tt>Endpoint</tt> participating in this multipoint conference.
   *
   * @param endpoint the <tt>Endpoint</tt> which is the source of the event/notification and is
   *     participating in this multipoint conference
   * @param ev a <tt>PropertyChangeEvent</tt> which specifies the source of the event/notification,
   *     the name of the property and the old and new values of that property
   */
  private void endpointPropertyChange(Endpoint endpoint, PropertyChangeEvent ev) {
    String propertyName = ev.getPropertyName();
    boolean maybeRemoveEndpoint;

    if (Endpoint.SCTP_CONNECTION_PROPERTY_NAME.equals(propertyName)) {
      // The SctpConnection of/associated with an Endpoint has changed. We
      // may want to fire initial events over that SctpConnection (as soon
      // as it is ready).
      SctpConnection oldValue = (SctpConnection) ev.getOldValue();
      SctpConnection newValue = (SctpConnection) ev.getNewValue();

      endpointSctpConnectionChanged(endpoint, oldValue, newValue);

      // The SctpConnection may have expired.
      maybeRemoveEndpoint = (newValue == null);
    } else if (Endpoint.CHANNELS_PROPERTY_NAME.equals(propertyName)) {
      // An RtpChannel may have expired.
      maybeRemoveEndpoint = true;
    } else {
      maybeRemoveEndpoint = false;
    }
    if (maybeRemoveEndpoint) {
      // It looks like there is a chance that the Endpoint may have
      // expired. Endpoints are held by this Conference via WeakReferences
      // but WeakReferences are unpredictable. We have functionality
      // though which could benefit from discovering that an Endpoint has
      // expired as quickly as possible (e.g. ConferenceSpeechActivity).
      // Consequently, try to expedite the removal of expired Endpoints.
      if (endpoint.getSctpConnection() == null && endpoint.getChannelCount(null) == 0) {
        removeEndpoint(endpoint);
      }
    }
  }
  /**
   * Checks whether RTP packets from {@code sourceChannel} should be forwarded to {@link #channel}.
   *
   * @param sourceChannel the channel.
   * @return {@code true} iff RTP packets from {@code sourceChannel} should be forwarded to {@link
   *     #channel}.
   */
  public boolean isForwarded(Channel sourceChannel) {
    if (lastN < 0 && currentLastN < 0) {
      // If Last-N is disabled, we forward everything.
      return true;
    }

    if (sourceChannel == null) {
      logger.warn("Invalid sourceChannel: null.");
      return false;
    }

    Endpoint channelEndpoint = sourceChannel.getEndpoint();
    if (channelEndpoint == null) {
      logger.warn("sourceChannel has no endpoint.");
      return false;
    }

    if (forwardedEndpoints == INITIAL_EMPTY_LIST) {
      // LastN is enabled, but we haven't yet initialized the list of
      // endpoints in the conference.
      initializeConferenceEndpoints();
    }

    // This may look like a place to optimize, because we query an unordered
    // list (in O(n)) and it executes on each video packet if lastN is
    // enabled. However, the size of  forwardedEndpoints is restricted to
    // lastN and so small enough that it is not worth optimizing.
    return forwardedEndpoints.contains(channelEndpoint.getID());
  }
  @Override
  public Endpoint createEndpoint(String endpointUri, TestContext context) {
    try {
      URI uri = new URI(endpointUri);
      String path = uri.getSchemeSpecificPart();

      if (path.startsWith("//")) {
        path = path.substring(2);
      }

      if (path.contains("?")) {
        path = path.substring(0, path.indexOf('?'));
      }

      Map<String, String> parameters = getParameters(endpointUri);
      String endpointName = null;
      if (parameters.containsKey(ENDPOINT_NAME)) {
        endpointName = parameters.remove(ENDPOINT_NAME);
      }

      Endpoint endpoint = createEndpoint(path, parameters, context);

      if (StringUtils.hasText(endpointName)) {
        endpoint.setName(endpointName);
      }

      return endpoint;
    } catch (URISyntaxException e) {
      throw new CitrusRuntimeException(
          String.format("Unable to parse endpoint uri '%s'", endpointUri), e);
    }
  }
 /** @return the ID of the endpoint of our channel. */
 private String getEndpointId() {
   if (endpointId == null) {
     Endpoint endpoint = channel.getEndpoint();
     if (endpoint != null) {
       endpointId = endpoint.getID();
     }
   }
   return endpointId;
 }
 /**
  * Used to send a message to a subset of endpoints in the call, primary use case being a message
  * that has originated from an endpoint (as opposed to a message originating from the bridge and
  * being sent to all endpoints in the call, for that see broadcastMessageOnDataChannels below
  *
  * @param msg
  * @param endpoints
  */
 public void sendMessageOnDataChannels(String msg, List<Endpoint> endpoints) {
   for (Endpoint endpoint : endpoints) {
     try {
       endpoint.sendMessageOnDataChannel(msg);
     } catch (IOException e) {
       logger.error("Failed to send message on data channel.", e);
     }
   }
 }
 private Endpoint getEndpoint(String name, String component) {
   Endpoint endpoint = endpointRepository.findOneByComponent(component);
   if (endpoint == null) {
     endpoint = new Endpoint();
     endpoint.setComponent(component);
     endpoint.setName(name);
     return endpointRepository.save(endpoint);
   }
   return endpoint;
 }
  /**
   * Extracts a list of endpoint IDs from a list of {@link Endpoint}s.
   *
   * @param endpoints the list of {@link Endpoint}s.
   * @return the list of IDs of endpoints in {@code endpoints}.
   */
  private List<String> getIDs(List<Endpoint> endpoints) {
    if (endpoints != null && !endpoints.isEmpty()) {
      List<String> endpointIds = new LinkedList<>();
      for (Endpoint endpoint : endpoints) {
        endpointIds.add(endpoint.getID());
      }
      return endpointIds;
    }

    return null;
  }
  /** {@inheritDoc} */
  public void mapLocalToNtp(long ssrc, long localTime, double ntpTime) {
    SSRCDesc ssrcDesc = getSSRCDesc(ssrc);

    if (localTime != -1 && ntpTime != -1.0 && ssrcDesc.endpointId != null) {
      Endpoint endpoint = getEndpoint(ssrcDesc.endpointId);
      if (endpoint.localTime == -1 || endpoint.ntpTime == -1.0) {
        synchronized (endpoint) {
          if (endpoint.localTime == -1 || endpoint.ntpTime == -1.0) {
            endpoint.localTime = localTime;
            endpoint.ntpTime = ntpTime;
          }
        }
      }
    }
  }
Exemple #10
0
 public void testEntity() throws Exception {
   int port = PortAllocator.getFreePort();
   String address = "http://localhost:" + port + "/entity";
   Endpoint endpoint = Endpoint.create(new MyEndpoint());
   endpoint.publish(address);
   try {
     HTTPResponseInfo rInfo = sendEntity(address);
     String resp = rInfo.getResponseBody();
     if (resp.contains("x1y1")) {
       fail("Entity is getting resolved");
     }
     int code = rInfo.getResponseCode();
     assertEquals(HttpURLConnection.HTTP_INTERNAL_ERROR, code);
   } finally {
     endpoint.stop();
   }
 }
  public boolean equals(Object obj) {
    if (obj != null && obj instanceof LiveRef) {
      LiveRef ref = (LiveRef) obj;

      return (ep.equals(ref.ep) && id.equals(ref.id) && isLocal == ref.isLocal);
    } else {
      return false;
    }
  }
  /**
   * Notifies this instance that {@link #speechActivity} has identified a speaker switch event in
   * this multipoint conference and there is now a new dominant speaker.
   */
  private void dominantSpeakerChanged() {
    Endpoint dominantSpeaker = speechActivity.getDominantEndpoint();

    if (logger.isTraceEnabled()) {
      logger.trace(
          "The dominant speaker in conference "
              + getID()
              + " is now the endpoint "
              + ((dominantSpeaker == null) ? "(null)" : dominantSpeaker.getID())
              + ".");
    }

    if (dominantSpeaker != null) {
      broadcastMessageOnDataChannels(createDominantSpeakerEndpointChangeEvent(dominantSpeaker));

      if (isRecording() && (recorderEventHandler != null))
        recorderEventHandler.dominantSpeakerChanged(dominantSpeaker);
    }
  }
  /**
   * Initializes a new <tt>SctpConnection</tt> instance.
   *
   * @param id the string identifier of this connection instance
   * @param content the <tt>Content</tt> which is initializing the new instance
   * @param endpoint the <tt>Endpoint</tt> of newly created instance
   * @param remoteSctpPort the SCTP port used by remote peer
   * @param channelBundleId the ID of the channel-bundle this <tt>SctpConnection</tt> is to be a
   *     part of (or <tt>null</tt> if no it is not to be a part of a channel-bundle).
   * @throws Exception if an error occurs while initializing the new instance
   */
  public SctpConnection(
      String id, Content content, Endpoint endpoint, int remoteSctpPort, String channelBundleId)
      throws Exception {
    super(content, id, channelBundleId);

    setEndpoint(endpoint.getID());

    this.remoteSctpPort = remoteSctpPort;
    this.debugId = generateDebugId();
  }
  /**
   * Notifies this instance that a specific <tt>SctpConnection</tt> has become ready i.e. connected
   * to a/the remote peer and operational.
   *
   * @param sctpConnection the <tt>SctpConnection</tt> which has become ready and is the cause of
   *     the method invocation
   */
  private void sctpConnectionReady(SctpConnection sctpConnection) {
    /*
     * We want to fire initial events over the SctpConnection as soon as it
     * is ready, we do not want to fire them multiple times i.e. every time
     * the SctpConnection becomes ready.
     */
    sctpConnection.removeChannelListener(webRtcDataStreamListener);

    if (!isExpired() && !sctpConnection.isExpired() && sctpConnection.isReady()) {
      Endpoint endpoint = sctpConnection.getEndpoint();

      if (endpoint != null) endpoint = getEndpoint(endpoint.getID());
      if (endpoint != null) {
        /*
         * It appears that this Conference, the SctpConnection and the
         * Endpoint are in states which allow them to fire the initial
         * events.
         */
        Endpoint dominantSpeaker = speechActivity.getDominantEndpoint();

        if (dominantSpeaker != null) {
          try {
            endpoint.sendMessageOnDataChannel(
                createDominantSpeakerEndpointChangeEvent(dominantSpeaker));
          } catch (IOException e) {
            logger.error("Failed to send message on data channel.", e);
          }
        }

        /*
         * Determining the instant at which an SctpConnection associated
         * with an Endpoint becomes ready (i.e. connected to the remote
         * peer and operational) is a multi-step ordeal. The Conference
         * class implements the procedure so do not make other classes
         * implement it as well.
         */
        endpoint.sctpConnectionReady(sctpConnection);
      }
    }
  }
  /**
   * Notifies this instance that the ordered list of endpoints in the conference has changed.
   *
   * @param endpoints the new ordered list of endpoints in the conference.
   * @return the list of endpoints which were added to the list of forwarded endpoints as a result
   *     of the call, or {@code null} if none were added.
   */
  public List<Endpoint> speechActivityEndpointsChanged(List<Endpoint> endpoints) {
    List<String> newEndpointIdList = getIDs(endpoints);
    List<String> enteringEndpointIds = speechActivityEndpointIdsChanged(newEndpointIdList);

    if (logger.isDebugEnabled()) {
      logger.debug(
          "New list of conference endpoints: "
              + newEndpointIdList.toString()
              + "; entering endpoints: "
              + (enteringEndpointIds == null ? "none" : enteringEndpointIds.toString()));
    }

    List<Endpoint> ret = new LinkedList<>();
    if (enteringEndpointIds != null) {
      for (Endpoint endpoint : endpoints) {
        if (enteringEndpointIds.contains(endpoint.getID())) {
          ret.add(endpoint);
        }
      }
    }

    return ret;
  }
  /**
   * Updates an <tt>Endpoint</tt> of this <tt>Conference</tt> with the information contained in
   * <tt>colibriEndpoint</tt>. The ID of <tt>colibriEndpoint</tt> is used to select the
   * <tt>Endpoint</tt> to update.
   *
   * @param colibriEndpoint a <tt>ColibriConferenceIQ.Endpoint</tt> instance that contains
   *     information to be set on an <tt>Endpoint</tt> instance of this <tt>Conference</tt>.
   */
  void updateEndpoint(ColibriConferenceIQ.Endpoint colibriEndpoint) {
    String id = colibriEndpoint.getId();

    if (id != null) {
      Endpoint endpoint = getEndpoint(id);

      if (endpoint != null) {
        String oldDisplayName = endpoint.getDisplayName();
        String newDisplayName = colibriEndpoint.getDisplayName();

        if ((oldDisplayName == null && newDisplayName != null)
            || (oldDisplayName != null && !oldDisplayName.equals(newDisplayName))) {
          endpoint.setDisplayName(newDisplayName);

          if (isRecording() && endpointRecorder != null) endpointRecorder.updateEndpoint(endpoint);

          EventAdmin eventAdmin = getVideobridge().getEventAdmin();
          if (eventAdmin != null) {
            eventAdmin.sendEvent(EventFactory.endpointDisplayNameChanged(endpoint));
          }
        }
      }
    }
  }
  /**
   * Removes a specific <tt>Endpoint</tt> instance from this list of <tt>Endpoint</tt>s
   * participating in this multipoint conference.
   *
   * @param endpoint the <tt>Endpoint</tt> to remove
   * @return <tt>true</tt> if the list of <tt>Endpoint</tt>s participating in this multipoint
   *     conference changed as a result of the execution of the method; otherwise, <tt>false</tt>
   */
  private boolean removeEndpoint(Endpoint endpoint) {
    boolean removed = false;

    synchronized (endpoints) {
      for (Iterator<WeakReference<Endpoint>> i = endpoints.iterator(); i.hasNext(); ) {
        Endpoint e = i.next().get();

        if (e == null || e == endpoint) {
          i.remove();
          removed = true;
        }
      }

      if (endpoint != null) {
        endpoint.expire();
      }
    }

    if (removed) firePropertyChange(ENDPOINTS_PROPERTY_NAME, null, null);

    return removed;
  }
 /** {@inheritDoc} */
 @Override
 protected void onEndpointChanged(Endpoint oldValue, Endpoint newValue) {
   if (oldValue != null) oldValue.setSctpConnection(null);
   if (newValue != null) newValue.setSctpConnection(this);
 }
 /** Export the object to accept incoming calls. */
 public void exportObject(Target target) throws RemoteException {
   ep.exportObject(target);
 }
 public Channel getChannel() throws RemoteException {
   if (ch == null) {
     ch = ep.getChannel();
   }
   return ch;
 }
  /**
   * Maybe send a data channel command to he associated simulcast sender to make it stop streaming
   * its hq stream, if it's not being watched by any participant.
   */
  public void maybeSendStopHighQualityStreamCommand() {
    if (nativeSimulcast || !hasLayers()) {
      // In native simulcast the client adjusts its layers autonomously so
      // we don't need (nor we can) to control it with data channel
      // messages.
      return;
    }

    Endpoint oldEndpoint = getSimulcastEngine().getVideoChannel().getEndpoint();

    SimulcastLayer[] oldSimulcastLayers = getSimulcastLayers();

    SctpConnection sctpConnection;
    if (oldSimulcastLayers != null
        && oldSimulcastLayers.length > 1
        /* oldEndpoint != null is implied*/
        && (sctpConnection = oldEndpoint.getSctpConnection()) != null
        && sctpConnection.isReady()
        && !sctpConnection.isExpired()) {
      // we have an old endpoint and it has an SCTP connection that is
      // ready and not expired. if nobody else is watching the old
      // endpoint, stop its hq stream.

      boolean stopHighQualityStream = true;
      for (Endpoint e :
          getSimulcastEngine().getVideoChannel().getContent().getConference().getEndpoints()) {
        // TODO(gp) need some synchronization here. What if the selected
        // endpoint changes while we're in the loop?

        if (oldEndpoint != e && (oldEndpoint == e.getEffectivelySelectedEndpoint())
            || e.getEffectivelySelectedEndpoint() == null) {
          // somebody is watching the old endpoint or somebody has not
          // yet signaled its selected endpoint to the bridge, don't
          // stop the hq stream.
          stopHighQualityStream = false;
          break;
        }
      }

      if (stopHighQualityStream) {
        // TODO(gp) this assumes only a single hq stream.

        logDebug(
            getSimulcastEngine().getVideoChannel().getEndpoint().getID()
                + " notifies "
                + oldEndpoint.getID()
                + " to stop "
                + "its HQ stream.");

        SimulcastLayer hqLayer = oldSimulcastLayers[oldSimulcastLayers.length - 1];

        StopSimulcastLayerCommand command = new StopSimulcastLayerCommand(hqLayer);

        String json = mapper.toJson(command);

        try {
          oldEndpoint.sendMessageOnDataChannel(json);
        } catch (IOException e1) {
          logError(oldEndpoint.getID() + " failed to send " + "message on data channel.", e1);
        }
      }
    }
  }
  /**
   * Maybe send a data channel command to the associated <tt>Endpoint</tt> to make it start
   * streaming its hq stream, if it's being watched by some receiver.
   */
  public void maybeSendStartHighQualityStreamCommand() {
    if (nativeSimulcast || !hasLayers()) {
      // In native simulcast the client adjusts its layers autonomously so
      // we don't need (nor we can) to control it with data channel
      // messages.
      return;
    }

    Endpoint newEndpoint = getSimulcastEngine().getVideoChannel().getEndpoint();
    SimulcastLayer[] newSimulcastLayers = getSimulcastLayers();

    SctpConnection sctpConnection;
    if (newSimulcastLayers == null
        || newSimulcastLayers.length <= 1
        /* newEndpoint != null is implied */
        || (sctpConnection = newEndpoint.getSctpConnection()) == null
        || !sctpConnection.isReady()
        || sctpConnection.isExpired()) {
      return;
    }

    // we have a new endpoint and it has an SCTP connection that is
    // ready and not expired. if somebody else is watching the new
    // endpoint, start its hq stream.

    boolean startHighQualityStream = false;

    for (Endpoint e :
        getSimulcastEngine().getVideoChannel().getContent().getConference().getEndpoints()) {
      // TODO(gp) need some synchronization here. What if the
      // selected endpoint changes while we're in the loop?

      if (e == newEndpoint) continue;

      Endpoint eSelectedEndpoint = e.getEffectivelySelectedEndpoint();

      if (newEndpoint == eSelectedEndpoint) {
        // somebody is watching the new endpoint or somebody has not
        // yet signaled its selected endpoint to the bridge, start
        // the hq stream.

        if (logger.isDebugEnabled()) {
          Map<String, Object> map = new HashMap<String, Object>(3);

          map.put("e", e);
          map.put("newEndpoint", newEndpoint);
          map.put("maybe", eSelectedEndpoint == null ? "(maybe) " : "");

          StringCompiler sc =
              new StringCompiler(map).c("{e.id} is {maybe} watching {newEndpoint.id}.");

          logDebug(sc.toString().replaceAll("\\s+", " "));
        }

        startHighQualityStream = true;
        break;
      }
    }

    if (startHighQualityStream) {
      // TODO(gp) this assumes only a single hq stream.

      logDebug(
          getSimulcastEngine().getVideoChannel().getEndpoint().getID()
              + " notifies "
              + newEndpoint.getID()
              + " to start its HQ stream.");

      SimulcastLayer hqLayer = newSimulcastLayers[newSimulcastLayers.length - 1];
      ;
      StartSimulcastLayerCommand command = new StartSimulcastLayerCommand(hqLayer);
      String json = mapper.toJson(command);

      try {
        newEndpoint.sendMessageOnDataChannel(json);
      } catch (IOException e) {
        logError(newEndpoint.getID() + " failed to send message on data channel.", e);
      }
    }
  }
 /**
  * Initializes a new <tt>String</tt> to be sent over an <tt>SctpConnection</tt> in order to notify
  * an <tt>Endpoint</tt> that the dominant speaker in this multipoint conference has changed to a
  * specific <tt>Endpoint</tt>.
  *
  * @param dominantSpeaker the dominant speaker in this multipoint conference
  * @return a new <tt>String</tt> to be sent over an <tt>SctpConnection</tt> in order to notify an
  *     <tt>Endpoint</tt> that the dominant speaker in this multipoint conference has changed to
  *     <tt>dominantSpeaker</tt>
  */
 private String createDominantSpeakerEndpointChangeEvent(Endpoint dominantSpeaker) {
   return "{\"colibriClass\":\"DominantSpeakerEndpointChangeEvent\","
       + "\"dominantSpeakerEndpoint\":\""
       + JSONValue.escape(dominantSpeaker.getID())
       + "\"}";
 }