/** * 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; } } } } }
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()) + "\"}"; }