aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java')
-rw-r--r--src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java2490
1 files changed, 1245 insertions, 1245 deletions
diff --git a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java
index 4e15038..23e42d4 100644
--- a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java
+++ b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java
@@ -1,4 +1,4 @@
-/*
+/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
@@ -15,1247 +15,1247 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package net.java.sip.communicator.service.protocol.media;
-
-import java.beans.*;
-import java.util.*;
-
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.service.protocol.event.*;
-import net.java.sip.communicator.util.*;
-
-import org.jitsi.service.neomedia.*;
-import org.jitsi.service.neomedia.event.*;
-
-/**
- * A utility class implementing media control code shared between current
- * telephony implementations. This class is only meant for use by protocol
- * implementations and should/could not be accessed by bundles that are simply
- * using the telephony functionalities.
- *
- * @param <T> the peer extension class like for example <tt>CallSipImpl</tt>
- * or <tt>CallJabberImpl</tt>
- * @param <U> the media handler extension class like for example
- * <tt>CallPeerMediaHandlerSipImpl</tt> or
- * <tt>CallPeerMediaHandlerJabberImpl</tt>
- * @param <V> the provider extension class like for example
- * <tt>ProtocolProviderServiceSipImpl</tt> or
- * <tt>ProtocolProviderServiceJabberImpl</tt>
- *
- * @author Emil Ivov
- * @author Lyubomir Marinov
- * @author Boris Grozev
- */
-public abstract class MediaAwareCallPeer
- <T extends MediaAwareCall<?, ?, V>,
- U extends CallPeerMediaHandler<?>,
- V extends ProtocolProviderService>
- extends AbstractCallPeer<T, V>
- implements SrtpListener,
- CallPeerConferenceListener,
- CsrcAudioLevelListener,
- SimpleAudioLevelListener
-{
- /**
- * The <tt>Logger</tt> used by the <tt>MediaAwareCallPeer</tt> class and its
- * instances for logging output.
- */
- private static final Logger logger
- = Logger.getLogger(MediaAwareCallPeer.class);
-
- /**
- * The call this peer belongs to.
- */
- private T call;
-
- /**
- * The listeners registered for level changes in the audio of participants
- * that this peer might be mixing and that we are not directly communicating
- * with.
- */
- private final List<ConferenceMembersSoundLevelListener>
- conferenceMembersSoundLevelListeners
- = new ArrayList<ConferenceMembersSoundLevelListener>();
-
- /**
- * A byte array containing the image/photo representing the call peer.
- */
- private byte[] image;
-
- /**
- * The media handler class handles all media management for a single
- * <tt>CallPeer</tt>. This includes initializing and configuring streams,
- * generating SDP, handling ICE, etc. One instance of <tt>CallPeer</tt>always
- * corresponds to exactly one instance of <tt>CallPeerMediaHandler</tt> and
- * both classes are only separated for reasons of readability.
- */
- private U mediaHandler;
-
- /**
- * The <tt>PropertyChangeListener</tt> which listens to
- * {@link CallPeerMediaHandler} for changes in the values of its properties.
- */
- private PropertyChangeListener mediaHandlerPropertyChangeListener;
-
- /**
- * A string uniquely identifying the peer.
- */
- private String peerID;
-
- /**
- * The protocol provider that this peer belongs to.
- */
- private final V protocolProvider;
-
- /**
- * The list of <tt>SoundLevelListener</tt>s interested in level changes in
- * the audio we are getting from the remote peer.
- * <p>
- * It is implemented as a copy-on-write storage because the number of
- * additions and removals of <tt>SoundLevelListener</tt>s is expected to be
- * far smaller than the number of audio level changes. The access to it is
- * to be synchronized using {@link #streamSoundLevelListenersSyncRoot}.
- * </p>
- */
- private List<SoundLevelListener> streamSoundLevelListeners;
-
- /**
- * The <tt>Object</tt> to synchronize the access to
- * {@link #streamSoundLevelListeners}.
- */
- private final Object streamSoundLevelListenersSyncRoot = new Object();
-
- /**
- * The <tt>List</tt> of <tt>PropertyChangeListener</tt>s listening to this
- * <tt>CallPeer</tt> for changes in the values of its properties related to
- * video.
- */
- private final List<PropertyChangeListener> videoPropertyChangeListeners
- = new LinkedList<PropertyChangeListener>();
-
- /**
- * Represents the last Conference Information (RFC4575) document sent to
- * this <tt>CallPeer</tt>. This is always a document with state "full", even
- * if the last document actually sent was a "partial"
- */
- private ConferenceInfoDocument lastConferenceInfoSent = null;
-
- /**
- * The time (as obtained by <tt>System.currentTimeMillis()</tt>) at which
- * a Conference Information (RFC4575) document was last sent to this
- * <tt>CallPeer</tt>.
- */
- private long lastConferenceInfoSentTimestamp = -1;
-
- /**
- * The last Conference Information (RFC4575) document sent to us by this
- * <tt>CallPeer</tt>. This is always a document with state "full", which is
- * only gets updated by "partial" or "deleted" documents.
- */
- private ConferenceInfoDocument lastConferenceInfoReceived = null;
-
- /**
- * Whether a conference-info document has been scheduled to be sent to this
- * <tt>CallPeer</tt>
- */
- private boolean confInfoScheduled = false;
-
- /**
- * Synchronization object for confInfoScheduled
- */
- private final Object confInfoScheduledSyncRoot = new Object();
-
- /**
- * Creates a new call peer with address <tt>peerAddress</tt>.
- *
- * @param owningCall the call that contains this call peer.
- */
- public MediaAwareCallPeer(T owningCall)
- {
- this.call = owningCall;
- this.protocolProvider = owningCall.getProtocolProvider();
-
- // create the uid
- this.peerID
- = String.valueOf(System.currentTimeMillis())
- + String.valueOf(hashCode());
-
- // we listen for events when the call will become focus or not
- // of a conference so we will add or remove our sound level listeners
- super.addCallPeerConferenceListener(this);
- }
-
- /**
- * Adds a specific <tt>ConferenceMembersSoundLevelListener</tt> to the list
- * of listeners interested in and notified about changes in conference
- * members sound level.
- *
- * @param listener the <tt>ConferenceMembersSoundLevelListener</tt> to add
- * @throws NullPointerException if <tt>listener</tt> is <tt>null</tt>
- */
- public void addConferenceMembersSoundLevelListener(
- ConferenceMembersSoundLevelListener listener)
- {
- /*
- * XXX The uses of the method at the time of this writing rely on being
- * able to add a null listener so make it a no-op here.
- */
- if (listener == null)
- return;
-
- synchronized (conferenceMembersSoundLevelListeners)
- {
- if (conferenceMembersSoundLevelListeners.size() == 0)
- {
- // if this is the first listener that's being registered with
- // us, we also need to register ourselves as a CSRC audio level
- // listener with the media handler.
- getMediaHandler().setCsrcAudioLevelListener(this);
- }
- conferenceMembersSoundLevelListeners.add(listener);
- }
- }
-
- /**
- * Adds a specific <tt>SoundLevelListener</tt> to the list of listeners
- * interested in and notified about changes in the sound level of the audio
- * sent by the remote party. When the first listener is being registered
- * the method also registers its single listener with the media handler so
- * that it would receive level change events and delegate them to the
- * listeners that have registered with us.
- *
- * @param listener the <tt>SoundLevelListener</tt> to add
- */
- public void addStreamSoundLevelListener(SoundLevelListener listener)
- {
- synchronized (streamSoundLevelListenersSyncRoot)
- {
- if ((streamSoundLevelListeners == null)
- || streamSoundLevelListeners.isEmpty())
- {
- CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
-
- if (isJitsiVideobridge())
- {
- /*
- * When the local user/peer has organized a telephony
- * conference utilizing the Jitsi Videobridge server-side
- * technology, the server will calculate the audio levels
- * and not the client.
- */
- mediaHandler.setCsrcAudioLevelListener(this);
- }
- else
- {
- /*
- * If this is the first listener that's being registered
- * with us, we also need to register ourselves as an audio
- * level listener with the media handler. We do this so that
- * audio levels would only be calculated if anyone is
- * interested in receiving them.
- */
- mediaHandler.setStreamAudioLevelListener(this);
- }
- }
-
- /*
- * Implement streamAudioLevelListeners as a copy-on-write storage so
- * that iterators over it can iterate without
- * ConcurrentModificationExceptions.
- */
- streamSoundLevelListeners
- = (streamSoundLevelListeners == null)
- ? new ArrayList<SoundLevelListener>()
- : new ArrayList<SoundLevelListener>(
- streamSoundLevelListeners);
- streamSoundLevelListeners.add(listener);
- }
- }
-
- /**
- * Adds a specific <tt>PropertyChangeListener</tt> to the list of
- * listeners which get notified when the properties (e.g.
- * LOCAL_VIDEO_STREAMING) associated with this <tt>CallPeer</tt> change
- * their values.
- *
- * @param listener the <tt>PropertyChangeListener</tt> to be notified
- * when the properties associated with the specified <tt>Call</tt> change
- * their values
- */
- public void addVideoPropertyChangeListener(PropertyChangeListener listener)
- {
- if (listener == null)
- throw new NullPointerException("listener");
-
- synchronized (videoPropertyChangeListeners)
- {
- /*
- * The video is part of the media-related functionality and thus it
- * is the responsibility of mediaHandler. So listen to mediaHandler
- * for video-related property changes and re-fire them as
- * originating from this instance.
- */
- if (!videoPropertyChangeListeners.contains(listener)
- && videoPropertyChangeListeners.add(listener)
- && (mediaHandlerPropertyChangeListener == null))
- {
- mediaHandlerPropertyChangeListener
- = new PropertyChangeListener()
- {
- public void propertyChange(PropertyChangeEvent event)
- {
- Iterable<PropertyChangeListener> listeners;
-
- synchronized (videoPropertyChangeListeners)
- {
- listeners
- = new LinkedList<PropertyChangeListener>(
- videoPropertyChangeListeners);
- }
-
- PropertyChangeEvent thisEvent
- = new PropertyChangeEvent(
- this,
- event.getPropertyName(),
- event.getOldValue(),
- event.getNewValue());
-
- for (PropertyChangeListener listener : listeners)
- listener.propertyChange(thisEvent);
- }
- };
- getMediaHandler()
- .addPropertyChangeListener(
- mediaHandlerPropertyChangeListener);
- }
- }
- }
-
- /**
- * Notified by its very majesty the media service about changes in the audio
- * level of the stream coming from this peer, the method generates the
- * corresponding events and delivers them to the listeners that have
- * registered here.
- *
- * @param newLevel the new audio level of the audio stream received from the
- * remote peer
- */
- public void audioLevelChanged(int newLevel)
- {
- /*
- * If we're in a conference in which this CallPeer is the focus and
- * we're the only member in it besides the focus, we will not receive
- * audio levels in the RTP and our media will instead measure the audio
- * levels of the received media. In order to make the UI oblivious of
- * the difference, we have to translate the event to the appropriate
- * type of listener.
- *
- * We may end up in a conference call with 0 members if the server
- * for some reason doesn't support sip conference (our subscribes
- * doesn't go to the focus of the conference) and so we must
- * pass the sound levels measured on the stream so we can see
- * the stream activity of the call.
- */
- int conferenceMemberCount = getConferenceMemberCount();
-
- if ((conferenceMemberCount > 0) && (conferenceMemberCount < 3))
- {
- long audioRemoteSSRC
- = getMediaHandler().getRemoteSSRC(MediaType.AUDIO);
-
- if (audioRemoteSSRC != CallPeerMediaHandler.SSRC_UNKNOWN)
- {
- audioLevelsReceived(new long[] { audioRemoteSSRC, newLevel });
- return;
- }
- }
-
- fireStreamSoundLevelChanged(newLevel);
- }
-
-
- /**
- * Implements {@link CsrcAudioLevelListener#audioLevelsReceived(long[])}.
- * Delivers the received audio levels to the
- * {@link ConferenceMembersSoundLevelListener}s registered with this
- * <tt>MediaAwareCallPeer</tt>..
- *
- * @param audioLevels the levels that we need to dispatch to all registered
- * <tt>ConferenceMemberSoundLevelListeners</tt>.
- */
- public void audioLevelsReceived(long[] audioLevels)
- {
- /*
- * When the local user/peer has organized a telephony conference
- * utilizing the Jitsi Videobridge server-side technology, the server
- * will calculate the audio levels and not the client.
- */
- if (isJitsiVideobridge())
- {
- long audioRemoteSSRC
- = getMediaHandler().getRemoteSSRC(MediaType.AUDIO);
-
- if (audioRemoteSSRC != CallPeerMediaHandler.SSRC_UNKNOWN)
- {
- for (int i = 0; i < audioLevels.length; i += 2)
- {
- if (audioLevels[i] == audioRemoteSSRC)
- {
- fireStreamSoundLevelChanged((int) audioLevels[i + 1]);
- break;
- }
- }
- }
- }
-
- if (getConferenceMemberCount() == 0)
- return;
-
- Map<ConferenceMember, Integer> levelsMap
- = new HashMap<ConferenceMember, Integer>();
-
- for (int i = 0; i < audioLevels.length; i += 2)
- {
- ConferenceMember mmbr = findConferenceMember(audioLevels[i]);
-
- if (mmbr != null)
- levelsMap.put(mmbr, (int) audioLevels[i + 1]);
- }
-
- synchronized (conferenceMembersSoundLevelListeners)
- {
- int conferenceMemberSoundLevelListenerCount
- = conferenceMembersSoundLevelListeners.size();
-
- if (conferenceMemberSoundLevelListenerCount > 0)
- {
- ConferenceMembersSoundLevelEvent ev
- = new ConferenceMembersSoundLevelEvent(this, levelsMap);
-
- for (int i = 0;
- i < conferenceMemberSoundLevelListenerCount;
- i++)
- {
- conferenceMembersSoundLevelListeners
- .get(i)
- .soundLevelChanged(ev);
- }
- }
- }
- }
-
- /**
- * Does nothing.
- * @param evt the event.
- */
- public void callPeerAdded(CallPeerEvent evt) {}
-
- /**
- * Does nothing.
- * @param evt the event.
- */
- public void callPeerRemoved(CallPeerEvent evt) {}
-
- /**
- * Dummy implementation of {@link CallPeerConferenceListener
- * #conferenceFocusChanged(CallPeerConferenceEvent)}.
- *
- * @param evt ignored
- */
- public void conferenceFocusChanged(CallPeerConferenceEvent evt)
- {
- }
-
- /**
- * Called when this peer becomes a mixer. The method add removes this class
- * as the stream audio level listener for the media coming from this peer
- * because the levels it delivers no longer represent the level of a
- * particular member. The method also adds this class as a member (CSRC)
- * audio level listener.
- *
- * @param conferenceEvent the event containing information (that we don't
- * really use) on the newly add member.
- */
- public void conferenceMemberAdded(CallPeerConferenceEvent conferenceEvent)
- {
- if (getConferenceMemberCount() > 2)
- {
- /*
- * This peer is now a conference focus with more than three
- * participants. It means that this peer is mixing and sending us
- * audio for at least two separate participants. We therefore need
- * to switch from stream to CSRC level listening.
- */
- CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
-
- mediaHandler.setStreamAudioLevelListener(null);
- mediaHandler.setCsrcAudioLevelListener(this);
- }
- }
-
- /**
- * Dummy implementation of {@link CallPeerConferenceListener
- * #conferenceMemberErrorReceived(CallPeerConferenceEvent)}.
- *
- * @param ev the event
- */
- public void conferenceMemberErrorReceived(CallPeerConferenceEvent ev) {};
-
- /**
- * Called when this peer stops being a mixer. The method add removes this
- * class as the stream audio level listener for the media coming from this
- * peer because the levels it delivers no longer represent the level of a
- * particular member. The method also adds this class as a member (CSRC)
- * audio level listener.
- *
- * @param conferenceEvent the event containing information (that we don't
- * really use) on the freshly removed member.
- */
- public void conferenceMemberRemoved(CallPeerConferenceEvent conferenceEvent)
- {
- if (getConferenceMemberCount() < 3)
- {
- /*
- * This call peer is no longer mixing audio from multiple sources
- * since there's only us and her in the call. We therefore need to
- * switch from CSRC to stream level listening.
- */
- CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
-
- mediaHandler.setStreamAudioLevelListener(this);
- mediaHandler.setCsrcAudioLevelListener(null);
- }
- }
-
- /**
- * Invokes {@link SoundLevelListener#soundLevelChanged(Object, int) on
- * the <tt>SoundLevelListener</tt>s interested in the changes of the audio
- * stream received from the remote peer i.e. in
- * {@link #streamSoundLevelListeners}.
- *
- * @param newLevel the new value of the sound level to notify
- * <tt>streamSoundLevelListeners</tt> about
- */
- private void fireStreamSoundLevelChanged(int newLevel)
- {
- List<SoundLevelListener> streamSoundLevelListeners;
-
- synchronized (streamSoundLevelListenersSyncRoot)
- {
- /*
- * Since the streamAudioLevelListeners field of this
- * MediaAwareCallPeer is implemented as a copy-on-write storage,
- * just get a reference to it and it should be safe to iterate over it
- * without ConcurrentModificationExceptions.
- */
- streamSoundLevelListeners = this.streamSoundLevelListeners;
- }
-
- if (streamSoundLevelListeners != null)
- {
- /*
- * Iterate over streamAudioLevelListeners using an index rather than
- * an Iterator in order to try to reduce the number of allocations
- * (as the number of audio level changes is expected to be very
- * large).
- */
- int streamSoundLevelListenerCount
- = streamSoundLevelListeners.size();
-
- for(int i = 0; i < streamSoundLevelListenerCount; i++)
- {
- streamSoundLevelListeners.get(i).soundLevelChanged(
- this,
- newLevel);
- }
- }
- }
-
- /**
- * Returns a reference to the call that this peer belongs to. Calls
- * are created by underlying telephony protocol implementations.
- *
- * @return a reference to the call containing this peer.
- */
- @Override
- public T getCall()
- {
- return call;
- }
-
- /**
- * The method returns an image representation of the call peer if one is
- * available.
- *
- * @return byte[] a byte array containing the image or null if no image is
- * available.
- */
- public byte[] getImage()
- {
- return image;
- }
-
- /**
- * Returns a reference to the <tt>CallPeerMediaHandler</tt> used by this
- * peer. The media handler class handles all media management for a single
- * <tt>CallPeer</tt>. This includes initializing and configuring streams,
- * generating SDP, handling ICE, etc. One instance of <tt>CallPeer</tt>
- * always corresponds to exactly one instance of
- * <tt>CallPeerMediaHandler</tt> and both classes are only separated for
- * reasons of readability.
- *
- * @return a reference to the <tt>CallPeerMediaHandler</tt> instance that
- * this peer uses for media related tips and tricks.
- */
- public U getMediaHandler()
- {
- return mediaHandler;
- }
-
- /**
- * Returns a unique identifier representing this peer.
- *
- * @return an identifier representing this call peer.
- */
- public String getPeerID()
- {
- return peerID;
- }
-
- /**
- * Returns the protocol provider that this peer belongs to.
- *
- * @return a reference to the <tt>ProtocolProviderService</tt> that this
- * peer belongs to.
- */
- @Override
- public V getProtocolProvider()
- {
- return protocolProvider;
- }
-
- /**
- * Determines whether this <tt>CallPeer</tt> is participating in a telephony
- * conference organized by the local user/peer utilizing the Jitsi
- * Videobridge server-side technology.
- *
- * @return <tt>true</tt> if this <tt>CallPeer</tt> is participating in a
- * telephony conference organized by the local user/peer utilizing the Jitsi
- * Videobridge server-side technology; otherwise, <tt>false</tt>
- */
- public final boolean isJitsiVideobridge()
- {
- Call call = getCall();
-
- if (call != null)
- {
- CallConference conference = call.getConference();
-
- if (conference != null)
- return conference.isJitsiVideobridge();
- }
- return false;
- }
-
- /**
- * Determines whether we are currently streaming video toward whoever this
- * <tt>MediaAwareCallPeer</tt> represents.
- *
- * @return <tt>true</tt> if we are currently streaming video toward this
- * <tt>CallPeer</tt> and <tt>false</tt> otherwise.
- */
- public boolean isLocalVideoStreaming()
- {
- return getMediaHandler().isLocalVideoTransmissionEnabled();
- }
-
- /**
- * Determines whether the audio stream (if any) being sent to this
- * peer is mute.
- *
- * @return <tt>true</tt> if an audio stream is being sent to this
- * peer and it is currently mute; <tt>false</tt>, otherwise
- */
- @Override
- public boolean isMute()
- {
- return getMediaHandler().isMute();
- }
-
- /**
- * Logs <tt>message</tt> and <tt>cause</tt> and sets this <tt>peer</tt>'s
- * state to <tt>CallPeerState.FAILED</tt>
- *
- * @param message a message to log and display to the user.
- * @param throwable the exception that cause the error we are logging
- */
- public void logAndFail(String message, Throwable throwable)
- {
- logger.error(message, throwable);
- setState(CallPeerState.FAILED, message);
- }
-
- /**
- * Updates the state of this <tt>CallPeer</tt> to match the locally-on-hold
- * status of our media handler.
- */
- public void reevalLocalHoldStatus()
- {
- CallPeerState state = getState();
- boolean locallyOnHold = getMediaHandler().isLocallyOnHold();
-
- if (CallPeerState.ON_HOLD_LOCALLY.equals(state))
- {
- if (!locallyOnHold)
- setState(CallPeerState.CONNECTED);
- }
- else if (CallPeerState.ON_HOLD_MUTUALLY.equals(state))
- {
- if (!locallyOnHold)
- setState(CallPeerState.ON_HOLD_REMOTELY);
- }
- else if (CallPeerState.ON_HOLD_REMOTELY.equals(state))
- {
- if (locallyOnHold)
- setState(CallPeerState.ON_HOLD_MUTUALLY);
- }
- else if (locallyOnHold)
- {
- setState(CallPeerState.ON_HOLD_LOCALLY);
- }
- }
-
- /**
- * Updates the state of this <tt>CallPeer</tt> to match the remotely-on-hold
- * status of our media handler.
- */
- public void reevalRemoteHoldStatus()
- {
- boolean remotelyOnHold = getMediaHandler().isRemotelyOnHold();
-
- CallPeerState state = getState();
- if (CallPeerState.ON_HOLD_LOCALLY.equals(state))
- {
- if (remotelyOnHold)
- setState(CallPeerState.ON_HOLD_MUTUALLY);
- }
- else if (CallPeerState.ON_HOLD_MUTUALLY.equals(state))
- {
- if (!remotelyOnHold)
- setState(CallPeerState.ON_HOLD_LOCALLY);
- }
- else if (CallPeerState.ON_HOLD_REMOTELY.equals(state))
- {
- if (!remotelyOnHold)
- setState(CallPeerState.CONNECTED);
- }
- else if (remotelyOnHold)
- {
- setState(CallPeerState.ON_HOLD_REMOTELY);
- }
- }
-
- /**
- * Removes a specific <tt>ConferenceMembersSoundLevelListener</tt> of the
- * list of listeners interested in and notified about changes in conference
- * members sound level.
- *
- * @param listener the <tt>ConferenceMembersSoundLevelListener</tt> to
- * remove
- */
- public void removeConferenceMembersSoundLevelListener(
- ConferenceMembersSoundLevelListener listener)
- {
- synchronized (conferenceMembersSoundLevelListeners)
- {
- if (conferenceMembersSoundLevelListeners.remove(listener)
- && (conferenceMembersSoundLevelListeners.size() == 0))
- {
- // if this was the last listener then we also remove ourselves
- // as a CSRC audio level listener from the handler so that we
- // don't have to create new events and maps for something no one
- // is interested in.
- getMediaHandler().setCsrcAudioLevelListener(null);
- }
- }
- }
-
- /**
- * Removes a specific <tt>SoundLevelListener</tt> of the list of
- * listeners interested in and notified about changes in stream sound level
- * related information.
- *
- * @param listener the <tt>SoundLevelListener</tt> to remove
- */
- public void removeStreamSoundLevelListener(SoundLevelListener listener)
- {
- synchronized (streamSoundLevelListenersSyncRoot)
- {
- /*
- * Implement streamAudioLevelListeners as a copy-on-write storage so
- * that iterators over it can iterate over it without
- * ConcurrentModificationExceptions.
- */
- if (streamSoundLevelListeners != null)
- {
- streamSoundLevelListeners
- = new ArrayList<SoundLevelListener>(
- streamSoundLevelListeners);
- if (streamSoundLevelListeners.remove(listener)
- && streamSoundLevelListeners.isEmpty())
- streamSoundLevelListeners = null;
- }
-
- if ((streamSoundLevelListeners == null)
- || streamSoundLevelListeners.isEmpty())
- {
- // if this was the last listener then we also need to remove
- // ourselves as an audio level so that audio levels would only
- // be calculated if anyone is interested in receiving them.
- getMediaHandler().setStreamAudioLevelListener(null);
- }
- }
- }
-
- /**
- * Removes a specific <tt>PropertyChangeListener</tt> from the list of
- * listeners which get notified when the properties (e.g.
- * LOCAL_VIDEO_STREAMING) associated with this <tt>CallPeer</tt> change
- * their values.
- *
- * @param listener the <tt>PropertyChangeListener</tt> to no longer be
- * notified when the properties associated with the specified <tt>Call</tt>
- * change their values
- */
- public void removeVideoPropertyChangeListener(
- PropertyChangeListener listener)
- {
- if (listener != null)
- synchronized (videoPropertyChangeListeners)
- {
- /*
- * The video is part of the media-related functionality and thus
- * it is the responsibility of mediaHandler. So we're listening
- * to mediaHandler for video-related property changes and w're
- * re-firing them as originating from this instance. Make sure
- * that we're not listening to mediaHandler if noone is
- * interested in video-related property changes originating from
- * this instance.
- */
- if (videoPropertyChangeListeners.remove(listener)
- && videoPropertyChangeListeners.isEmpty()
- && (mediaHandlerPropertyChangeListener != null))
- {
-// getMediaHandler()
-// .removePropertyChangeListener(
-// mediaHandlerPropertyChangeListener);
- mediaHandlerPropertyChangeListener = null;
- }
- }
- }
-
- /**
- * Sets the security message associated with a failure/warning or
- * information coming from the encryption protocol.
- *
- * @param messageType the type of the message.
- * @param i18nMessage the message
- * @param severity severity level
- */
- public void securityMessageReceived(
- String messageType,
- String i18nMessage,
- int severity)
- {
- fireCallPeerSecurityMessageEvent(messageType,
- i18nMessage,
- severity);
- }
-
- /**
- * Indicates that the other party has timeouted replying to our
- * offer to secure the connection.
- *
- * @param mediaType the <tt>MediaType</tt> of the call session
- * @param sender the security controller that caused the event
- */
- public void securityNegotiationStarted(
- MediaType mediaType,
- SrtpControl sender)
- {
- fireCallPeerSecurityNegotiationStartedEvent(
- new CallPeerSecurityNegotiationStartedEvent(
- this,
- toSessionType(mediaType),
- sender));
- }
-
- /**
- * Indicates that the other party has timeouted replying to our
- * offer to secure the connection.
- *
- * @param mediaType the <tt>MediaType</tt> of the call session
- */
- public void securityTimeout(MediaType mediaType)
- {
- fireCallPeerSecurityTimeoutEvent(
- new CallPeerSecurityTimeoutEvent(
- this,
- toSessionType(mediaType)));
- }
-
- /**
- * Sets the security status to OFF for this call peer.
- *
- * @param mediaType the <tt>MediaType</tt> of the call session
- */
- public void securityTurnedOff(MediaType mediaType)
- {
- // If this event has been triggered because of a call end event and the
- // call is already ended we don't need to alert the user for
- // security off.
- if((call != null) && !call.getCallState().equals(CallState.CALL_ENDED))
- {
- fireCallPeerSecurityOffEvent(
- new CallPeerSecurityOffEvent(
- this,
- toSessionType(mediaType)));
- }
- }
-
- /**
- * Sets the security status to ON for this call peer.
- *
- * @param mediaType the <tt>MediaType</tt> of the call session
- * @param cipher the cipher
- * @param sender the security controller that caused the event
- */
- public void securityTurnedOn(
- MediaType mediaType,
- String cipher,
- SrtpControl sender)
- {
- getMediaHandler().startSrtpMultistream(sender);
- fireCallPeerSecurityOnEvent(
- new CallPeerSecurityOnEvent(
- this,
- toSessionType(mediaType),
- cipher,
- sender));
- }
-
- /**
- * Sets the call containing this peer.
- *
- * @param call the call that this call peer is participating in.
- */
- public void setCall(T call)
- {
- this.call = call;
- }
-
- /**
- * Sets the byte array containing an image representation (photo or picture)
- * of the call peer.
- *
- * @param image a byte array containing the image
- */
- public void setImage(byte[] image)
- {
- byte[] oldImage = getImage();
- this.image = image;
-
- //Fire the Event
- fireCallPeerChangeEvent(
- CallPeerChangeEvent.CALL_PEER_IMAGE_CHANGE,
- oldImage,
- image);
- }
-
- /**
- * Modifies the local media setup to reflect the requested setting for the
- * streaming of the local video and then re-invites the peer represented by
- * this class using a corresponding SDP description..
- *
- * @param allowed <tt>true</tt> if local video transmission is allowed and
- * <tt>false</tt> otherwise.
- *
- * @throws OperationFailedException if video initialization fails.
- */
- public void setLocalVideoAllowed(boolean allowed)
- throws OperationFailedException
- {
- CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
-
- if(mediaHandler.isLocalVideoTransmissionEnabled() != allowed)
- {
- // Modify the local media setup to reflect the requested setting for
- // the streaming of the local video.
- mediaHandler.setLocalVideoTransmissionEnabled(allowed);
- }
- }
-
- /**
- * Sets a reference to the <tt>CallPeerMediaHandler</tt> used by this
- * peer. The media handler class handles all media management for a single
- * <tt>CallPeer</tt>. This includes initializing and configuring streams,
- * generating SDP, handling ICE, etc. One instance of <tt>CallPeer</tt>
- * always corresponds to exactly one instance of
- * <tt>CallPeerMediaHandler</tt> and both classes are only separated for
- * reasons of readability.
- *
- * @param mediaHandler a reference to the <tt>CallPeerMediaHandler</tt>
- * instance that this peer uses for media related tips and tricks.
- */
- protected void setMediaHandler(U mediaHandler)
- {
- this.mediaHandler = mediaHandler;
- }
-
- /**
- * Sets the mute property for this call peer.
- *
- * @param newMuteValue the new value of the mute property for this call peer
- */
- @Override
- public void setMute(boolean newMuteValue)
- {
- getMediaHandler().setMute(newMuteValue);
- super.setMute(newMuteValue);
- }
-
- /**
- * Sets the String that serves as a unique identifier of this
- * CallPeer.
- * @param peerID the ID of this call peer.
- */
- public void setPeerID(String peerID)
- {
- this.peerID = peerID;
- }
-
- /**
- * Overrides the parent set state method in order to make sure that we
- * close our media handler whenever we enter a disconnected state.
- *
- * @param newState the <tt>CallPeerState</tt> that we are about to enter and
- * that we pass to our predecessor.
- * @param reason a reason phrase explaining the state (e.g. if newState
- * indicates a failure) and that we pass to our predecessor.
- * @param reasonCode the code for the reason of the state change.
- */
- @Override
- public void setState(CallPeerState newState, String reason, int reasonCode)
- {
- // synchronized to mediaHandler if there are currently jobs of
- // initializing, configuring and starting streams (method processAnswer
- // of CallPeerMediaHandler) we won't set and fire the current state
- // to Disconnected. Before closing the mediaHandler is setting the state
- // in order to deliver states as quick as possible.
- CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
-
- synchronized(mediaHandler)
- {
- try
- {
- super.setState(newState, reason, reasonCode);
- }
- finally
- {
- // make sure whatever happens to close the media
- if (CallPeerState.DISCONNECTED.equals(newState)
- || CallPeerState.FAILED.equals(newState))
- mediaHandler.close();
- }
- }
- }
-
- /**
- * Returns the last <tt>ConferenceInfoDocument</tt> sent by us to this
- * <tt>CallPeer</tt>. It is a document with state <tt>full</tt>
- * @return the last <tt>ConferenceInfoDocument</tt> sent by us to this
- * <tt>CallPeer</tt>. It is a document with state <tt>full</tt>
- */
- public ConferenceInfoDocument getLastConferenceInfoSent()
- {
- return lastConferenceInfoSent;
- }
-
- /**
- * Sets the last <tt>ConferenceInfoDocument</tt> sent by us to this
- * <tt>CallPeer</tt>.
- * @param confInfo the document to set.
- */
- public void setLastConferenceInfoSent(ConferenceInfoDocument confInfo)
- {
- lastConferenceInfoSent = confInfo;
- }
-
- /**
- * Gets the time (as obtained by <tt>System.currentTimeMillis()</tt>)
- * at which we last sent a <tt>ConferenceInfoDocument</tt> to this
- * <tt>CallPeer</tt>.
- * @return the time (as obtained by <tt>System.currentTimeMillis()</tt>)
- * at which we last sent a <tt>ConferenceInfoDocument</tt> to this
- * <tt>CallPeer</tt>.
- */
- public long getLastConferenceInfoSentTimestamp()
- {
- return lastConferenceInfoSentTimestamp;
- }
-
- /**
- * Sets the time (as obtained by <tt>System.currentTimeMillis()</tt>)
- * at which we last sent a <tt>ConferenceInfoDocument</tt> to this
- * <tt>CallPeer</tt>.
- * @param newTimestamp the time to set
- */
- public void setLastConferenceInfoSentTimestamp(long newTimestamp)
- {
- lastConferenceInfoSentTimestamp = newTimestamp;
- }
-
- /**
- * Gets the last <tt>ConferenceInfoDocument</tt> sent to us by this
- * <tt>CallPeer</tt>.
- * @return the last <tt>ConferenceInfoDocument</tt> sent to us by this
- * <tt>CallPeer</tt>.
- */
- public ConferenceInfoDocument getLastConferenceInfoReceived()
- {
- return lastConferenceInfoReceived;
- }
-
- /**
- * Gets the last <tt>ConferenceInfoDocument</tt> sent to us by this
- * <tt>CallPeer</tt>.
- * @return the last <tt>ConferenceInfoDocument</tt> sent to us by this
- * <tt>CallPeer</tt>.
- */
- public void setLastConferenceInfoReceived(ConferenceInfoDocument confInfo)
- {
- lastConferenceInfoReceived = confInfo;
- }
-
- /**
- * Gets the <tt>version</tt> of the last <tt>ConferenceInfoDocument</tt>
- * sent to us by this <tt>CallPeer</tt>, or -1 if we haven't (yet) received
- * a <tt>ConferenceInformationDocument</tt> from this <tt>CallPeer</tt>.
- * @return
- */
- public int getLastConferenceInfoReceivedVersion()
- {
- return (lastConferenceInfoReceived == null)
- ? -1
- : lastConferenceInfoReceived.getVersion();
- }
-
- /**
- * Gets the <tt>String</tt> to be used for this <tt>CallPeer</tt> when
- * we describe it in a <tt>ConferenceInfoDocument</tt> (e.g. the
- * <tt>entity</tt> key attribute which to use for the <tt>user</tt>
- * element corresponding to this <tt>CallPeer</tt>)
- *
- * @return the <tt>String</tt> to be used for this <tt>CallPeer</tt> when
- * we describe it in a <tt>ConferenceInfoDocument</tt> (e.g. the
- * <tt>entity</tt> key attribute which to use for the <tt>user</tt>
- * element corresponding to this <tt>CallPeer</tt>)
- */
- public abstract String getEntity();
-
- /**
- * Check whether a conference-info document is scheduled to be sent to
- * this <tt>CallPeer</tt> (i.e. there is a thread which will eventually
- * (after sleeping a certain amount of time) trigger a document to be sent)
- * @return <tt>true</tt> if there is a conference-info document scheduled
- * to be sent to this <tt>CallPeer</tt> and <tt>false</tt> otherwise.
- */
- public boolean isConfInfoScheduled()
- {
- synchronized (confInfoScheduledSyncRoot)
- {
- return confInfoScheduled;
- }
- }
-
- /**
- * Sets the property which indicates whether a conference-info document
- * is scheduled to be sent to this <tt>CallPeer</tt>.
- * @param confInfoScheduled
- */
- public void setConfInfoScheduled(boolean confInfoScheduled)
- {
- synchronized (confInfoScheduledSyncRoot)
- {
- this.confInfoScheduled = confInfoScheduled;
- }
- }
-
- /**
- * Returns the direction of the session for media of type <tt>mediaType</tt>
- * that we have with this <tt>CallPeer</tt>. This is the direction of the
- * session negotiated in the signaling protocol, and it may or may not
- * coincide with the direction of the media stream.
- * For example, if we are the focus of a videobridge conference and another
- * peer is sending video to us, we have a <tt>RECVONLY</tt> video stream,
- * but <tt>SENDONLY</tt> or <tt>SENDRECV</tt> (Jingle) sessions with the
- * rest of the conference members.
- * Should always return non-null.
- *
- * @param mediaType the <tt>MediaType</tt> to use
- * @return Returns the direction of the session for media of type
- * <tt>mediaType</tt> that we have with this <tt>CallPeer</tt>.
- */
- public abstract MediaDirection getDirection(MediaType mediaType);
-
- /**
- * {@inheritDoc}
- *
- * When a <tt>ConferenceMember</tt> is removed from a conference with a
- * Jitsi-videobridge, an RTCP BYE packet is not always sent. Therefore,
- * if the <tt>ConferenceMember</tt> had an associated video SSRC, the stream
- * isn't be removed until it times out, leaving a blank video container in
- * the interface for a few seconds.
- * TODO: This works around the problem by removing the
- * <tt>ConferenceMember</tt>'s <tt>ReceiveStream</tt> when the
- * <tt>ConferenceMember</tt> is removed. The proper solution is to ensure
- * that RTCP BYEs are sent whenever necessary, and when it is deployed this
- * code should be removed.
- *
- * @param conferenceMember a <tt>ConferenceMember</tt> to be removed from
- * the list of <tt>ConferenceMember</tt> reported by this peer. If the
- * specified <tt>ConferenceMember</tt> is no contained in the list, no event
- */
- @Override
- public void removeConferenceMember(ConferenceMember conferenceMember)
- {
- MediaStream videoStream = getMediaHandler().getStream(MediaType.VIDEO);
- if (videoStream != null)
- videoStream.removeReceiveStreamForSsrc(
- conferenceMember.getVideoSsrc());
-
- super.removeConferenceMember(conferenceMember);
- }
-
- /**
- * Converts a specific <tt>MediaType</tt> into a <tt>sessionType</tt> value
- * in the terms of the <tt>CallPeerSecurityStatusEvent</tt> class.
- *
- * @param mediaType the <tt>MediaType</tt> to be converted
- * @return the <tt>sessionType</tt> value in the terms of the
- * <tt>CallPeerSecurityStatusEvent</tt> class that is equivalent to the
- * specified <tt>mediaType</tt>
- */
- private static int toSessionType(MediaType mediaType)
- {
- switch (mediaType)
- {
- case AUDIO:
- return CallPeerSecurityStatusEvent.AUDIO_SESSION;
- case VIDEO:
- return CallPeerSecurityStatusEvent.VIDEO_SESSION;
- default:
- throw new IllegalArgumentException("mediaType");
- }
- }
-}
+package net.java.sip.communicator.service.protocol.media;
+
+import java.beans.*;
+import java.util.*;
+
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.util.*;
+
+import org.jitsi.service.neomedia.*;
+import org.jitsi.service.neomedia.event.*;
+
+/**
+ * A utility class implementing media control code shared between current
+ * telephony implementations. This class is only meant for use by protocol
+ * implementations and should/could not be accessed by bundles that are simply
+ * using the telephony functionalities.
+ *
+ * @param <T> the peer extension class like for example <tt>CallSipImpl</tt>
+ * or <tt>CallJabberImpl</tt>
+ * @param <U> the media handler extension class like for example
+ * <tt>CallPeerMediaHandlerSipImpl</tt> or
+ * <tt>CallPeerMediaHandlerJabberImpl</tt>
+ * @param <V> the provider extension class like for example
+ * <tt>ProtocolProviderServiceSipImpl</tt> or
+ * <tt>ProtocolProviderServiceJabberImpl</tt>
+ *
+ * @author Emil Ivov
+ * @author Lyubomir Marinov
+ * @author Boris Grozev
+ */
+public abstract class MediaAwareCallPeer
+ <T extends MediaAwareCall<?, ?, V>,
+ U extends CallPeerMediaHandler<?>,
+ V extends ProtocolProviderService>
+ extends AbstractCallPeer<T, V>
+ implements SrtpListener,
+ CallPeerConferenceListener,
+ CsrcAudioLevelListener,
+ SimpleAudioLevelListener
+{
+ /**
+ * The <tt>Logger</tt> used by the <tt>MediaAwareCallPeer</tt> class and its
+ * instances for logging output.
+ */
+ private static final Logger logger
+ = Logger.getLogger(MediaAwareCallPeer.class);
+
+ /**
+ * The call this peer belongs to.
+ */
+ private T call;
+
+ /**
+ * The listeners registered for level changes in the audio of participants
+ * that this peer might be mixing and that we are not directly communicating
+ * with.
+ */
+ private final List<ConferenceMembersSoundLevelListener>
+ conferenceMembersSoundLevelListeners
+ = new ArrayList<ConferenceMembersSoundLevelListener>();
+
+ /**
+ * A byte array containing the image/photo representing the call peer.
+ */
+ private byte[] image;
+
+ /**
+ * The media handler class handles all media management for a single
+ * <tt>CallPeer</tt>. This includes initializing and configuring streams,
+ * generating SDP, handling ICE, etc. One instance of <tt>CallPeer</tt>always
+ * corresponds to exactly one instance of <tt>CallPeerMediaHandler</tt> and
+ * both classes are only separated for reasons of readability.
+ */
+ private U mediaHandler;
+
+ /**
+ * The <tt>PropertyChangeListener</tt> which listens to
+ * {@link CallPeerMediaHandler} for changes in the values of its properties.
+ */
+ private PropertyChangeListener mediaHandlerPropertyChangeListener;
+
+ /**
+ * A string uniquely identifying the peer.
+ */
+ private String peerID;
+
+ /**
+ * The protocol provider that this peer belongs to.
+ */
+ private final V protocolProvider;
+
+ /**
+ * The list of <tt>SoundLevelListener</tt>s interested in level changes in
+ * the audio we are getting from the remote peer.
+ * <p>
+ * It is implemented as a copy-on-write storage because the number of
+ * additions and removals of <tt>SoundLevelListener</tt>s is expected to be
+ * far smaller than the number of audio level changes. The access to it is
+ * to be synchronized using {@link #streamSoundLevelListenersSyncRoot}.
+ * </p>
+ */
+ private List<SoundLevelListener> streamSoundLevelListeners;
+
+ /**
+ * The <tt>Object</tt> to synchronize the access to
+ * {@link #streamSoundLevelListeners}.
+ */
+ private final Object streamSoundLevelListenersSyncRoot = new Object();
+
+ /**
+ * The <tt>List</tt> of <tt>PropertyChangeListener</tt>s listening to this
+ * <tt>CallPeer</tt> for changes in the values of its properties related to
+ * video.
+ */
+ private final List<PropertyChangeListener> videoPropertyChangeListeners
+ = new LinkedList<PropertyChangeListener>();
+
+ /**
+ * Represents the last Conference Information (RFC4575) document sent to
+ * this <tt>CallPeer</tt>. This is always a document with state "full", even
+ * if the last document actually sent was a "partial"
+ */
+ private ConferenceInfoDocument lastConferenceInfoSent = null;
+
+ /**
+ * The time (as obtained by <tt>System.currentTimeMillis()</tt>) at which
+ * a Conference Information (RFC4575) document was last sent to this
+ * <tt>CallPeer</tt>.
+ */
+ private long lastConferenceInfoSentTimestamp = -1;
+
+ /**
+ * The last Conference Information (RFC4575) document sent to us by this
+ * <tt>CallPeer</tt>. This is always a document with state "full", which is
+ * only gets updated by "partial" or "deleted" documents.
+ */
+ private ConferenceInfoDocument lastConferenceInfoReceived = null;
+
+ /**
+ * Whether a conference-info document has been scheduled to be sent to this
+ * <tt>CallPeer</tt>
+ */
+ private boolean confInfoScheduled = false;
+
+ /**
+ * Synchronization object for confInfoScheduled
+ */
+ private final Object confInfoScheduledSyncRoot = new Object();
+
+ /**
+ * Creates a new call peer with address <tt>peerAddress</tt>.
+ *
+ * @param owningCall the call that contains this call peer.
+ */
+ public MediaAwareCallPeer(T owningCall)
+ {
+ this.call = owningCall;
+ this.protocolProvider = owningCall.getProtocolProvider();
+
+ // create the uid
+ this.peerID
+ = String.valueOf(System.currentTimeMillis())
+ + String.valueOf(hashCode());
+
+ // we listen for events when the call will become focus or not
+ // of a conference so we will add or remove our sound level listeners
+ super.addCallPeerConferenceListener(this);
+ }
+
+ /**
+ * Adds a specific <tt>ConferenceMembersSoundLevelListener</tt> to the list
+ * of listeners interested in and notified about changes in conference
+ * members sound level.
+ *
+ * @param listener the <tt>ConferenceMembersSoundLevelListener</tt> to add
+ * @throws NullPointerException if <tt>listener</tt> is <tt>null</tt>
+ */
+ public void addConferenceMembersSoundLevelListener(
+ ConferenceMembersSoundLevelListener listener)
+ {
+ /*
+ * XXX The uses of the method at the time of this writing rely on being
+ * able to add a null listener so make it a no-op here.
+ */
+ if (listener == null)
+ return;
+
+ synchronized (conferenceMembersSoundLevelListeners)
+ {
+ if (conferenceMembersSoundLevelListeners.size() == 0)
+ {
+ // if this is the first listener that's being registered with
+ // us, we also need to register ourselves as a CSRC audio level
+ // listener with the media handler.
+ getMediaHandler().setCsrcAudioLevelListener(this);
+ }
+ conferenceMembersSoundLevelListeners.add(listener);
+ }
+ }
+
+ /**
+ * Adds a specific <tt>SoundLevelListener</tt> to the list of listeners
+ * interested in and notified about changes in the sound level of the audio
+ * sent by the remote party. When the first listener is being registered
+ * the method also registers its single listener with the media handler so
+ * that it would receive level change events and delegate them to the
+ * listeners that have registered with us.
+ *
+ * @param listener the <tt>SoundLevelListener</tt> to add
+ */
+ public void addStreamSoundLevelListener(SoundLevelListener listener)
+ {
+ synchronized (streamSoundLevelListenersSyncRoot)
+ {
+ if ((streamSoundLevelListeners == null)
+ || streamSoundLevelListeners.isEmpty())
+ {
+ CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
+
+ if (isJitsiVideobridge())
+ {
+ /*
+ * When the local user/peer has organized a telephony
+ * conference utilizing the Jitsi Videobridge server-side
+ * technology, the server will calculate the audio levels
+ * and not the client.
+ */
+ mediaHandler.setCsrcAudioLevelListener(this);
+ }
+ else
+ {
+ /*
+ * If this is the first listener that's being registered
+ * with us, we also need to register ourselves as an audio
+ * level listener with the media handler. We do this so that
+ * audio levels would only be calculated if anyone is
+ * interested in receiving them.
+ */
+ mediaHandler.setStreamAudioLevelListener(this);
+ }
+ }
+
+ /*
+ * Implement streamAudioLevelListeners as a copy-on-write storage so
+ * that iterators over it can iterate without
+ * ConcurrentModificationExceptions.
+ */
+ streamSoundLevelListeners
+ = (streamSoundLevelListeners == null)
+ ? new ArrayList<SoundLevelListener>()
+ : new ArrayList<SoundLevelListener>(
+ streamSoundLevelListeners);
+ streamSoundLevelListeners.add(listener);
+ }
+ }
+
+ /**
+ * Adds a specific <tt>PropertyChangeListener</tt> to the list of
+ * listeners which get notified when the properties (e.g.
+ * LOCAL_VIDEO_STREAMING) associated with this <tt>CallPeer</tt> change
+ * their values.
+ *
+ * @param listener the <tt>PropertyChangeListener</tt> to be notified
+ * when the properties associated with the specified <tt>Call</tt> change
+ * their values
+ */
+ public void addVideoPropertyChangeListener(PropertyChangeListener listener)
+ {
+ if (listener == null)
+ throw new NullPointerException("listener");
+
+ synchronized (videoPropertyChangeListeners)
+ {
+ /*
+ * The video is part of the media-related functionality and thus it
+ * is the responsibility of mediaHandler. So listen to mediaHandler
+ * for video-related property changes and re-fire them as
+ * originating from this instance.
+ */
+ if (!videoPropertyChangeListeners.contains(listener)
+ && videoPropertyChangeListeners.add(listener)
+ && (mediaHandlerPropertyChangeListener == null))
+ {
+ mediaHandlerPropertyChangeListener
+ = new PropertyChangeListener()
+ {
+ public void propertyChange(PropertyChangeEvent event)
+ {
+ Iterable<PropertyChangeListener> listeners;
+
+ synchronized (videoPropertyChangeListeners)
+ {
+ listeners
+ = new LinkedList<PropertyChangeListener>(
+ videoPropertyChangeListeners);
+ }
+
+ PropertyChangeEvent thisEvent
+ = new PropertyChangeEvent(
+ this,
+ event.getPropertyName(),
+ event.getOldValue(),
+ event.getNewValue());
+
+ for (PropertyChangeListener listener : listeners)
+ listener.propertyChange(thisEvent);
+ }
+ };
+ getMediaHandler()
+ .addPropertyChangeListener(
+ mediaHandlerPropertyChangeListener);
+ }
+ }
+ }
+
+ /**
+ * Notified by its very majesty the media service about changes in the audio
+ * level of the stream coming from this peer, the method generates the
+ * corresponding events and delivers them to the listeners that have
+ * registered here.
+ *
+ * @param newLevel the new audio level of the audio stream received from the
+ * remote peer
+ */
+ public void audioLevelChanged(int newLevel)
+ {
+ /*
+ * If we're in a conference in which this CallPeer is the focus and
+ * we're the only member in it besides the focus, we will not receive
+ * audio levels in the RTP and our media will instead measure the audio
+ * levels of the received media. In order to make the UI oblivious of
+ * the difference, we have to translate the event to the appropriate
+ * type of listener.
+ *
+ * We may end up in a conference call with 0 members if the server
+ * for some reason doesn't support sip conference (our subscribes
+ * doesn't go to the focus of the conference) and so we must
+ * pass the sound levels measured on the stream so we can see
+ * the stream activity of the call.
+ */
+ int conferenceMemberCount = getConferenceMemberCount();
+
+ if ((conferenceMemberCount > 0) && (conferenceMemberCount < 3))
+ {
+ long audioRemoteSSRC
+ = getMediaHandler().getRemoteSSRC(MediaType.AUDIO);
+
+ if (audioRemoteSSRC != CallPeerMediaHandler.SSRC_UNKNOWN)
+ {
+ audioLevelsReceived(new long[] { audioRemoteSSRC, newLevel });
+ return;
+ }
+ }
+
+ fireStreamSoundLevelChanged(newLevel);
+ }
+
+
+ /**
+ * Implements {@link CsrcAudioLevelListener#audioLevelsReceived(long[])}.
+ * Delivers the received audio levels to the
+ * {@link ConferenceMembersSoundLevelListener}s registered with this
+ * <tt>MediaAwareCallPeer</tt>..
+ *
+ * @param audioLevels the levels that we need to dispatch to all registered
+ * <tt>ConferenceMemberSoundLevelListeners</tt>.
+ */
+ public void audioLevelsReceived(long[] audioLevels)
+ {
+ /*
+ * When the local user/peer has organized a telephony conference
+ * utilizing the Jitsi Videobridge server-side technology, the server
+ * will calculate the audio levels and not the client.
+ */
+ if (isJitsiVideobridge())
+ {
+ long audioRemoteSSRC
+ = getMediaHandler().getRemoteSSRC(MediaType.AUDIO);
+
+ if (audioRemoteSSRC != CallPeerMediaHandler.SSRC_UNKNOWN)
+ {
+ for (int i = 0; i < audioLevels.length; i += 2)
+ {
+ if (audioLevels[i] == audioRemoteSSRC)
+ {
+ fireStreamSoundLevelChanged((int) audioLevels[i + 1]);
+ break;
+ }
+ }
+ }
+ }
+
+ if (getConferenceMemberCount() == 0)
+ return;
+
+ Map<ConferenceMember, Integer> levelsMap
+ = new HashMap<ConferenceMember, Integer>();
+
+ for (int i = 0; i < audioLevels.length; i += 2)
+ {
+ ConferenceMember mmbr = findConferenceMember(audioLevels[i]);
+
+ if (mmbr != null)
+ levelsMap.put(mmbr, (int) audioLevels[i + 1]);
+ }
+
+ synchronized (conferenceMembersSoundLevelListeners)
+ {
+ int conferenceMemberSoundLevelListenerCount
+ = conferenceMembersSoundLevelListeners.size();
+
+ if (conferenceMemberSoundLevelListenerCount > 0)
+ {
+ ConferenceMembersSoundLevelEvent ev
+ = new ConferenceMembersSoundLevelEvent(this, levelsMap);
+
+ for (int i = 0;
+ i < conferenceMemberSoundLevelListenerCount;
+ i++)
+ {
+ conferenceMembersSoundLevelListeners
+ .get(i)
+ .soundLevelChanged(ev);
+ }
+ }
+ }
+ }
+
+ /**
+ * Does nothing.
+ * @param evt the event.
+ */
+ public void callPeerAdded(CallPeerEvent evt) {}
+
+ /**
+ * Does nothing.
+ * @param evt the event.
+ */
+ public void callPeerRemoved(CallPeerEvent evt) {}
+
+ /**
+ * Dummy implementation of {@link CallPeerConferenceListener
+ * #conferenceFocusChanged(CallPeerConferenceEvent)}.
+ *
+ * @param evt ignored
+ */
+ public void conferenceFocusChanged(CallPeerConferenceEvent evt)
+ {
+ }
+
+ /**
+ * Called when this peer becomes a mixer. The method add removes this class
+ * as the stream audio level listener for the media coming from this peer
+ * because the levels it delivers no longer represent the level of a
+ * particular member. The method also adds this class as a member (CSRC)
+ * audio level listener.
+ *
+ * @param conferenceEvent the event containing information (that we don't
+ * really use) on the newly add member.
+ */
+ public void conferenceMemberAdded(CallPeerConferenceEvent conferenceEvent)
+ {
+ if (getConferenceMemberCount() > 2)
+ {
+ /*
+ * This peer is now a conference focus with more than three
+ * participants. It means that this peer is mixing and sending us
+ * audio for at least two separate participants. We therefore need
+ * to switch from stream to CSRC level listening.
+ */
+ CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
+
+ mediaHandler.setStreamAudioLevelListener(null);
+ mediaHandler.setCsrcAudioLevelListener(this);
+ }
+ }
+
+ /**
+ * Dummy implementation of {@link CallPeerConferenceListener
+ * #conferenceMemberErrorReceived(CallPeerConferenceEvent)}.
+ *
+ * @param ev the event
+ */
+ public void conferenceMemberErrorReceived(CallPeerConferenceEvent ev) {};
+
+ /**
+ * Called when this peer stops being a mixer. The method add removes this
+ * class as the stream audio level listener for the media coming from this
+ * peer because the levels it delivers no longer represent the level of a
+ * particular member. The method also adds this class as a member (CSRC)
+ * audio level listener.
+ *
+ * @param conferenceEvent the event containing information (that we don't
+ * really use) on the freshly removed member.
+ */
+ public void conferenceMemberRemoved(CallPeerConferenceEvent conferenceEvent)
+ {
+ if (getConferenceMemberCount() < 3)
+ {
+ /*
+ * This call peer is no longer mixing audio from multiple sources
+ * since there's only us and her in the call. We therefore need to
+ * switch from CSRC to stream level listening.
+ */
+ CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
+
+ mediaHandler.setStreamAudioLevelListener(this);
+ mediaHandler.setCsrcAudioLevelListener(null);
+ }
+ }
+
+ /**
+ * Invokes {@link SoundLevelListener#soundLevelChanged(Object, int) on
+ * the <tt>SoundLevelListener</tt>s interested in the changes of the audio
+ * stream received from the remote peer i.e. in
+ * {@link #streamSoundLevelListeners}.
+ *
+ * @param newLevel the new value of the sound level to notify
+ * <tt>streamSoundLevelListeners</tt> about
+ */
+ private void fireStreamSoundLevelChanged(int newLevel)
+ {
+ List<SoundLevelListener> streamSoundLevelListeners;
+
+ synchronized (streamSoundLevelListenersSyncRoot)
+ {
+ /*
+ * Since the streamAudioLevelListeners field of this
+ * MediaAwareCallPeer is implemented as a copy-on-write storage,
+ * just get a reference to it and it should be safe to iterate over it
+ * without ConcurrentModificationExceptions.
+ */
+ streamSoundLevelListeners = this.streamSoundLevelListeners;
+ }
+
+ if (streamSoundLevelListeners != null)
+ {
+ /*
+ * Iterate over streamAudioLevelListeners using an index rather than
+ * an Iterator in order to try to reduce the number of allocations
+ * (as the number of audio level changes is expected to be very
+ * large).
+ */
+ int streamSoundLevelListenerCount
+ = streamSoundLevelListeners.size();
+
+ for(int i = 0; i < streamSoundLevelListenerCount; i++)
+ {
+ streamSoundLevelListeners.get(i).soundLevelChanged(
+ this,
+ newLevel);
+ }
+ }
+ }
+
+ /**
+ * Returns a reference to the call that this peer belongs to. Calls
+ * are created by underlying telephony protocol implementations.
+ *
+ * @return a reference to the call containing this peer.
+ */
+ @Override
+ public T getCall()
+ {
+ return call;
+ }
+
+ /**
+ * The method returns an image representation of the call peer if one is
+ * available.
+ *
+ * @return byte[] a byte array containing the image or null if no image is
+ * available.
+ */
+ public byte[] getImage()
+ {
+ return image;
+ }
+
+ /**
+ * Returns a reference to the <tt>CallPeerMediaHandler</tt> used by this
+ * peer. The media handler class handles all media management for a single
+ * <tt>CallPeer</tt>. This includes initializing and configuring streams,
+ * generating SDP, handling ICE, etc. One instance of <tt>CallPeer</tt>
+ * always corresponds to exactly one instance of
+ * <tt>CallPeerMediaHandler</tt> and both classes are only separated for
+ * reasons of readability.
+ *
+ * @return a reference to the <tt>CallPeerMediaHandler</tt> instance that
+ * this peer uses for media related tips and tricks.
+ */
+ public U getMediaHandler()
+ {
+ return mediaHandler;
+ }
+
+ /**
+ * Returns a unique identifier representing this peer.
+ *
+ * @return an identifier representing this call peer.
+ */
+ public String getPeerID()
+ {
+ return peerID;
+ }
+
+ /**
+ * Returns the protocol provider that this peer belongs to.
+ *
+ * @return a reference to the <tt>ProtocolProviderService</tt> that this
+ * peer belongs to.
+ */
+ @Override
+ public V getProtocolProvider()
+ {
+ return protocolProvider;
+ }
+
+ /**
+ * Determines whether this <tt>CallPeer</tt> is participating in a telephony
+ * conference organized by the local user/peer utilizing the Jitsi
+ * Videobridge server-side technology.
+ *
+ * @return <tt>true</tt> if this <tt>CallPeer</tt> is participating in a
+ * telephony conference organized by the local user/peer utilizing the Jitsi
+ * Videobridge server-side technology; otherwise, <tt>false</tt>
+ */
+ public final boolean isJitsiVideobridge()
+ {
+ Call call = getCall();
+
+ if (call != null)
+ {
+ CallConference conference = call.getConference();
+
+ if (conference != null)
+ return conference.isJitsiVideobridge();
+ }
+ return false;
+ }
+
+ /**
+ * Determines whether we are currently streaming video toward whoever this
+ * <tt>MediaAwareCallPeer</tt> represents.
+ *
+ * @return <tt>true</tt> if we are currently streaming video toward this
+ * <tt>CallPeer</tt> and <tt>false</tt> otherwise.
+ */
+ public boolean isLocalVideoStreaming()
+ {
+ return getMediaHandler().isLocalVideoTransmissionEnabled();
+ }
+
+ /**
+ * Determines whether the audio stream (if any) being sent to this
+ * peer is mute.
+ *
+ * @return <tt>true</tt> if an audio stream is being sent to this
+ * peer and it is currently mute; <tt>false</tt>, otherwise
+ */
+ @Override
+ public boolean isMute()
+ {
+ return getMediaHandler().isMute();
+ }
+
+ /**
+ * Logs <tt>message</tt> and <tt>cause</tt> and sets this <tt>peer</tt>'s
+ * state to <tt>CallPeerState.FAILED</tt>
+ *
+ * @param message a message to log and display to the user.
+ * @param throwable the exception that cause the error we are logging
+ */
+ public void logAndFail(String message, Throwable throwable)
+ {
+ logger.error(message, throwable);
+ setState(CallPeerState.FAILED, message);
+ }
+
+ /**
+ * Updates the state of this <tt>CallPeer</tt> to match the locally-on-hold
+ * status of our media handler.
+ */
+ public void reevalLocalHoldStatus()
+ {
+ CallPeerState state = getState();
+ boolean locallyOnHold = getMediaHandler().isLocallyOnHold();
+
+ if (CallPeerState.ON_HOLD_LOCALLY.equals(state))
+ {
+ if (!locallyOnHold)
+ setState(CallPeerState.CONNECTED);
+ }
+ else if (CallPeerState.ON_HOLD_MUTUALLY.equals(state))
+ {
+ if (!locallyOnHold)
+ setState(CallPeerState.ON_HOLD_REMOTELY);
+ }
+ else if (CallPeerState.ON_HOLD_REMOTELY.equals(state))
+ {
+ if (locallyOnHold)
+ setState(CallPeerState.ON_HOLD_MUTUALLY);
+ }
+ else if (locallyOnHold)
+ {
+ setState(CallPeerState.ON_HOLD_LOCALLY);
+ }
+ }
+
+ /**
+ * Updates the state of this <tt>CallPeer</tt> to match the remotely-on-hold
+ * status of our media handler.
+ */
+ public void reevalRemoteHoldStatus()
+ {
+ boolean remotelyOnHold = getMediaHandler().isRemotelyOnHold();
+
+ CallPeerState state = getState();
+ if (CallPeerState.ON_HOLD_LOCALLY.equals(state))
+ {
+ if (remotelyOnHold)
+ setState(CallPeerState.ON_HOLD_MUTUALLY);
+ }
+ else if (CallPeerState.ON_HOLD_MUTUALLY.equals(state))
+ {
+ if (!remotelyOnHold)
+ setState(CallPeerState.ON_HOLD_LOCALLY);
+ }
+ else if (CallPeerState.ON_HOLD_REMOTELY.equals(state))
+ {
+ if (!remotelyOnHold)
+ setState(CallPeerState.CONNECTED);
+ }
+ else if (remotelyOnHold)
+ {
+ setState(CallPeerState.ON_HOLD_REMOTELY);
+ }
+ }
+
+ /**
+ * Removes a specific <tt>ConferenceMembersSoundLevelListener</tt> of the
+ * list of listeners interested in and notified about changes in conference
+ * members sound level.
+ *
+ * @param listener the <tt>ConferenceMembersSoundLevelListener</tt> to
+ * remove
+ */
+ public void removeConferenceMembersSoundLevelListener(
+ ConferenceMembersSoundLevelListener listener)
+ {
+ synchronized (conferenceMembersSoundLevelListeners)
+ {
+ if (conferenceMembersSoundLevelListeners.remove(listener)
+ && (conferenceMembersSoundLevelListeners.size() == 0))
+ {
+ // if this was the last listener then we also remove ourselves
+ // as a CSRC audio level listener from the handler so that we
+ // don't have to create new events and maps for something no one
+ // is interested in.
+ getMediaHandler().setCsrcAudioLevelListener(null);
+ }
+ }
+ }
+
+ /**
+ * Removes a specific <tt>SoundLevelListener</tt> of the list of
+ * listeners interested in and notified about changes in stream sound level
+ * related information.
+ *
+ * @param listener the <tt>SoundLevelListener</tt> to remove
+ */
+ public void removeStreamSoundLevelListener(SoundLevelListener listener)
+ {
+ synchronized (streamSoundLevelListenersSyncRoot)
+ {
+ /*
+ * Implement streamAudioLevelListeners as a copy-on-write storage so
+ * that iterators over it can iterate over it without
+ * ConcurrentModificationExceptions.
+ */
+ if (streamSoundLevelListeners != null)
+ {
+ streamSoundLevelListeners
+ = new ArrayList<SoundLevelListener>(
+ streamSoundLevelListeners);
+ if (streamSoundLevelListeners.remove(listener)
+ && streamSoundLevelListeners.isEmpty())
+ streamSoundLevelListeners = null;
+ }
+
+ if ((streamSoundLevelListeners == null)
+ || streamSoundLevelListeners.isEmpty())
+ {
+ // if this was the last listener then we also need to remove
+ // ourselves as an audio level so that audio levels would only
+ // be calculated if anyone is interested in receiving them.
+ getMediaHandler().setStreamAudioLevelListener(null);
+ }
+ }
+ }
+
+ /**
+ * Removes a specific <tt>PropertyChangeListener</tt> from the list of
+ * listeners which get notified when the properties (e.g.
+ * LOCAL_VIDEO_STREAMING) associated with this <tt>CallPeer</tt> change
+ * their values.
+ *
+ * @param listener the <tt>PropertyChangeListener</tt> to no longer be
+ * notified when the properties associated with the specified <tt>Call</tt>
+ * change their values
+ */
+ public void removeVideoPropertyChangeListener(
+ PropertyChangeListener listener)
+ {
+ if (listener != null)
+ synchronized (videoPropertyChangeListeners)
+ {
+ /*
+ * The video is part of the media-related functionality and thus
+ * it is the responsibility of mediaHandler. So we're listening
+ * to mediaHandler for video-related property changes and w're
+ * re-firing them as originating from this instance. Make sure
+ * that we're not listening to mediaHandler if noone is
+ * interested in video-related property changes originating from
+ * this instance.
+ */
+ if (videoPropertyChangeListeners.remove(listener)
+ && videoPropertyChangeListeners.isEmpty()
+ && (mediaHandlerPropertyChangeListener != null))
+ {
+// getMediaHandler()
+// .removePropertyChangeListener(
+// mediaHandlerPropertyChangeListener);
+ mediaHandlerPropertyChangeListener = null;
+ }
+ }
+ }
+
+ /**
+ * Sets the security message associated with a failure/warning or
+ * information coming from the encryption protocol.
+ *
+ * @param messageType the type of the message.
+ * @param i18nMessage the message
+ * @param severity severity level
+ */
+ public void securityMessageReceived(
+ String messageType,
+ String i18nMessage,
+ int severity)
+ {
+ fireCallPeerSecurityMessageEvent(messageType,
+ i18nMessage,
+ severity);
+ }
+
+ /**
+ * Indicates that the other party has timeouted replying to our
+ * offer to secure the connection.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the call session
+ * @param sender the security controller that caused the event
+ */
+ public void securityNegotiationStarted(
+ MediaType mediaType,
+ SrtpControl sender)
+ {
+ fireCallPeerSecurityNegotiationStartedEvent(
+ new CallPeerSecurityNegotiationStartedEvent(
+ this,
+ toSessionType(mediaType),
+ sender));
+ }
+
+ /**
+ * Indicates that the other party has timeouted replying to our
+ * offer to secure the connection.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the call session
+ */
+ public void securityTimeout(MediaType mediaType)
+ {
+ fireCallPeerSecurityTimeoutEvent(
+ new CallPeerSecurityTimeoutEvent(
+ this,
+ toSessionType(mediaType)));
+ }
+
+ /**
+ * Sets the security status to OFF for this call peer.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the call session
+ */
+ public void securityTurnedOff(MediaType mediaType)
+ {
+ // If this event has been triggered because of a call end event and the
+ // call is already ended we don't need to alert the user for
+ // security off.
+ if((call != null) && !call.getCallState().equals(CallState.CALL_ENDED))
+ {
+ fireCallPeerSecurityOffEvent(
+ new CallPeerSecurityOffEvent(
+ this,
+ toSessionType(mediaType)));
+ }
+ }
+
+ /**
+ * Sets the security status to ON for this call peer.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the call session
+ * @param cipher the cipher
+ * @param sender the security controller that caused the event
+ */
+ public void securityTurnedOn(
+ MediaType mediaType,
+ String cipher,
+ SrtpControl sender)
+ {
+ getMediaHandler().startSrtpMultistream(sender);
+ fireCallPeerSecurityOnEvent(
+ new CallPeerSecurityOnEvent(
+ this,
+ toSessionType(mediaType),
+ cipher,
+ sender));
+ }
+
+ /**
+ * Sets the call containing this peer.
+ *
+ * @param call the call that this call peer is participating in.
+ */
+ public void setCall(T call)
+ {
+ this.call = call;
+ }
+
+ /**
+ * Sets the byte array containing an image representation (photo or picture)
+ * of the call peer.
+ *
+ * @param image a byte array containing the image
+ */
+ public void setImage(byte[] image)
+ {
+ byte[] oldImage = getImage();
+ this.image = image;
+
+ //Fire the Event
+ fireCallPeerChangeEvent(
+ CallPeerChangeEvent.CALL_PEER_IMAGE_CHANGE,
+ oldImage,
+ image);
+ }
+
+ /**
+ * Modifies the local media setup to reflect the requested setting for the
+ * streaming of the local video and then re-invites the peer represented by
+ * this class using a corresponding SDP description..
+ *
+ * @param allowed <tt>true</tt> if local video transmission is allowed and
+ * <tt>false</tt> otherwise.
+ *
+ * @throws OperationFailedException if video initialization fails.
+ */
+ public void setLocalVideoAllowed(boolean allowed)
+ throws OperationFailedException
+ {
+ CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
+
+ if(mediaHandler.isLocalVideoTransmissionEnabled() != allowed)
+ {
+ // Modify the local media setup to reflect the requested setting for
+ // the streaming of the local video.
+ mediaHandler.setLocalVideoTransmissionEnabled(allowed);
+ }
+ }
+
+ /**
+ * Sets a reference to the <tt>CallPeerMediaHandler</tt> used by this
+ * peer. The media handler class handles all media management for a single
+ * <tt>CallPeer</tt>. This includes initializing and configuring streams,
+ * generating SDP, handling ICE, etc. One instance of <tt>CallPeer</tt>
+ * always corresponds to exactly one instance of
+ * <tt>CallPeerMediaHandler</tt> and both classes are only separated for
+ * reasons of readability.
+ *
+ * @param mediaHandler a reference to the <tt>CallPeerMediaHandler</tt>
+ * instance that this peer uses for media related tips and tricks.
+ */
+ protected void setMediaHandler(U mediaHandler)
+ {
+ this.mediaHandler = mediaHandler;
+ }
+
+ /**
+ * Sets the mute property for this call peer.
+ *
+ * @param newMuteValue the new value of the mute property for this call peer
+ */
+ @Override
+ public void setMute(boolean newMuteValue)
+ {
+ getMediaHandler().setMute(newMuteValue);
+ super.setMute(newMuteValue);
+ }
+
+ /**
+ * Sets the String that serves as a unique identifier of this
+ * CallPeer.
+ * @param peerID the ID of this call peer.
+ */
+ public void setPeerID(String peerID)
+ {
+ this.peerID = peerID;
+ }
+
+ /**
+ * Overrides the parent set state method in order to make sure that we
+ * close our media handler whenever we enter a disconnected state.
+ *
+ * @param newState the <tt>CallPeerState</tt> that we are about to enter and
+ * that we pass to our predecessor.
+ * @param reason a reason phrase explaining the state (e.g. if newState
+ * indicates a failure) and that we pass to our predecessor.
+ * @param reasonCode the code for the reason of the state change.
+ */
+ @Override
+ public void setState(CallPeerState newState, String reason, int reasonCode)
+ {
+ // synchronized to mediaHandler if there are currently jobs of
+ // initializing, configuring and starting streams (method processAnswer
+ // of CallPeerMediaHandler) we won't set and fire the current state
+ // to Disconnected. Before closing the mediaHandler is setting the state
+ // in order to deliver states as quick as possible.
+ CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
+
+ synchronized(mediaHandler)
+ {
+ try
+ {
+ super.setState(newState, reason, reasonCode);
+ }
+ finally
+ {
+ // make sure whatever happens to close the media
+ if (CallPeerState.DISCONNECTED.equals(newState)
+ || CallPeerState.FAILED.equals(newState))
+ mediaHandler.close();
+ }
+ }
+ }
+
+ /**
+ * Returns the last <tt>ConferenceInfoDocument</tt> sent by us to this
+ * <tt>CallPeer</tt>. It is a document with state <tt>full</tt>
+ * @return the last <tt>ConferenceInfoDocument</tt> sent by us to this
+ * <tt>CallPeer</tt>. It is a document with state <tt>full</tt>
+ */
+ public ConferenceInfoDocument getLastConferenceInfoSent()
+ {
+ return lastConferenceInfoSent;
+ }
+
+ /**
+ * Sets the last <tt>ConferenceInfoDocument</tt> sent by us to this
+ * <tt>CallPeer</tt>.
+ * @param confInfo the document to set.
+ */
+ public void setLastConferenceInfoSent(ConferenceInfoDocument confInfo)
+ {
+ lastConferenceInfoSent = confInfo;
+ }
+
+ /**
+ * Gets the time (as obtained by <tt>System.currentTimeMillis()</tt>)
+ * at which we last sent a <tt>ConferenceInfoDocument</tt> to this
+ * <tt>CallPeer</tt>.
+ * @return the time (as obtained by <tt>System.currentTimeMillis()</tt>)
+ * at which we last sent a <tt>ConferenceInfoDocument</tt> to this
+ * <tt>CallPeer</tt>.
+ */
+ public long getLastConferenceInfoSentTimestamp()
+ {
+ return lastConferenceInfoSentTimestamp;
+ }
+
+ /**
+ * Sets the time (as obtained by <tt>System.currentTimeMillis()</tt>)
+ * at which we last sent a <tt>ConferenceInfoDocument</tt> to this
+ * <tt>CallPeer</tt>.
+ * @param newTimestamp the time to set
+ */
+ public void setLastConferenceInfoSentTimestamp(long newTimestamp)
+ {
+ lastConferenceInfoSentTimestamp = newTimestamp;
+ }
+
+ /**
+ * Gets the last <tt>ConferenceInfoDocument</tt> sent to us by this
+ * <tt>CallPeer</tt>.
+ * @return the last <tt>ConferenceInfoDocument</tt> sent to us by this
+ * <tt>CallPeer</tt>.
+ */
+ public ConferenceInfoDocument getLastConferenceInfoReceived()
+ {
+ return lastConferenceInfoReceived;
+ }
+
+ /**
+ * Gets the last <tt>ConferenceInfoDocument</tt> sent to us by this
+ * <tt>CallPeer</tt>.
+ * @return the last <tt>ConferenceInfoDocument</tt> sent to us by this
+ * <tt>CallPeer</tt>.
+ */
+ public void setLastConferenceInfoReceived(ConferenceInfoDocument confInfo)
+ {
+ lastConferenceInfoReceived = confInfo;
+ }
+
+ /**
+ * Gets the <tt>version</tt> of the last <tt>ConferenceInfoDocument</tt>
+ * sent to us by this <tt>CallPeer</tt>, or -1 if we haven't (yet) received
+ * a <tt>ConferenceInformationDocument</tt> from this <tt>CallPeer</tt>.
+ * @return
+ */
+ public int getLastConferenceInfoReceivedVersion()
+ {
+ return (lastConferenceInfoReceived == null)
+ ? -1
+ : lastConferenceInfoReceived.getVersion();
+ }
+
+ /**
+ * Gets the <tt>String</tt> to be used for this <tt>CallPeer</tt> when
+ * we describe it in a <tt>ConferenceInfoDocument</tt> (e.g. the
+ * <tt>entity</tt> key attribute which to use for the <tt>user</tt>
+ * element corresponding to this <tt>CallPeer</tt>)
+ *
+ * @return the <tt>String</tt> to be used for this <tt>CallPeer</tt> when
+ * we describe it in a <tt>ConferenceInfoDocument</tt> (e.g. the
+ * <tt>entity</tt> key attribute which to use for the <tt>user</tt>
+ * element corresponding to this <tt>CallPeer</tt>)
+ */
+ public abstract String getEntity();
+
+ /**
+ * Check whether a conference-info document is scheduled to be sent to
+ * this <tt>CallPeer</tt> (i.e. there is a thread which will eventually
+ * (after sleeping a certain amount of time) trigger a document to be sent)
+ * @return <tt>true</tt> if there is a conference-info document scheduled
+ * to be sent to this <tt>CallPeer</tt> and <tt>false</tt> otherwise.
+ */
+ public boolean isConfInfoScheduled()
+ {
+ synchronized (confInfoScheduledSyncRoot)
+ {
+ return confInfoScheduled;
+ }
+ }
+
+ /**
+ * Sets the property which indicates whether a conference-info document
+ * is scheduled to be sent to this <tt>CallPeer</tt>.
+ * @param confInfoScheduled
+ */
+ public void setConfInfoScheduled(boolean confInfoScheduled)
+ {
+ synchronized (confInfoScheduledSyncRoot)
+ {
+ this.confInfoScheduled = confInfoScheduled;
+ }
+ }
+
+ /**
+ * Returns the direction of the session for media of type <tt>mediaType</tt>
+ * that we have with this <tt>CallPeer</tt>. This is the direction of the
+ * session negotiated in the signaling protocol, and it may or may not
+ * coincide with the direction of the media stream.
+ * For example, if we are the focus of a videobridge conference and another
+ * peer is sending video to us, we have a <tt>RECVONLY</tt> video stream,
+ * but <tt>SENDONLY</tt> or <tt>SENDRECV</tt> (Jingle) sessions with the
+ * rest of the conference members.
+ * Should always return non-null.
+ *
+ * @param mediaType the <tt>MediaType</tt> to use
+ * @return Returns the direction of the session for media of type
+ * <tt>mediaType</tt> that we have with this <tt>CallPeer</tt>.
+ */
+ public abstract MediaDirection getDirection(MediaType mediaType);
+
+ /**
+ * {@inheritDoc}
+ *
+ * When a <tt>ConferenceMember</tt> is removed from a conference with a
+ * Jitsi-videobridge, an RTCP BYE packet is not always sent. Therefore,
+ * if the <tt>ConferenceMember</tt> had an associated video SSRC, the stream
+ * isn't be removed until it times out, leaving a blank video container in
+ * the interface for a few seconds.
+ * TODO: This works around the problem by removing the
+ * <tt>ConferenceMember</tt>'s <tt>ReceiveStream</tt> when the
+ * <tt>ConferenceMember</tt> is removed. The proper solution is to ensure
+ * that RTCP BYEs are sent whenever necessary, and when it is deployed this
+ * code should be removed.
+ *
+ * @param conferenceMember a <tt>ConferenceMember</tt> to be removed from
+ * the list of <tt>ConferenceMember</tt> reported by this peer. If the
+ * specified <tt>ConferenceMember</tt> is no contained in the list, no event
+ */
+ @Override
+ public void removeConferenceMember(ConferenceMember conferenceMember)
+ {
+ MediaStream videoStream = getMediaHandler().getStream(MediaType.VIDEO);
+ if (videoStream != null)
+ videoStream.removeReceiveStreamForSsrc(
+ conferenceMember.getVideoSsrc());
+
+ super.removeConferenceMember(conferenceMember);
+ }
+
+ /**
+ * Converts a specific <tt>MediaType</tt> into a <tt>sessionType</tt> value
+ * in the terms of the <tt>CallPeerSecurityStatusEvent</tt> class.
+ *
+ * @param mediaType the <tt>MediaType</tt> to be converted
+ * @return the <tt>sessionType</tt> value in the terms of the
+ * <tt>CallPeerSecurityStatusEvent</tt> class that is equivalent to the
+ * specified <tt>mediaType</tt>
+ */
+ private static int toSessionType(MediaType mediaType)
+ {
+ switch (mediaType)
+ {
+ case AUDIO:
+ return CallPeerSecurityStatusEvent.AUDIO_SESSION;
+ case VIDEO:
+ return CallPeerSecurityStatusEvent.VIDEO_SESSION;
+ default:
+ throw new IllegalArgumentException("mediaType");
+ }
+ }
+}