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