diff options
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.java | 2490 |
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"); + } + } +} |