diff --git a/lib/installer-exclude/libjitsi.jar b/lib/installer-exclude/libjitsi.jar
index dcf32a645..da2b6ab3f 100644
Binary files a/lib/installer-exclude/libjitsi.jar and b/lib/installer-exclude/libjitsi.jar differ
diff --git a/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceFocusPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceFocusPanel.java
index c5c19c915..eb80d5a6d 100644
--- a/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceFocusPanel.java
+++ b/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceFocusPanel.java
@@ -105,8 +105,11 @@ public ConferenceFocusPanel(
addFocusPeerPanel();
this.focusPeer.addCallPeerConferenceListener(focusPeerListener);
- this.focusPeer.addConferenceMembersSoundLevelListener(
- focusPeerListener);
+ if (ConferencePeerPanel.isSoundLevelIndicatorEnabled())
+ {
+ this.focusPeer.addConferenceMembersSoundLevelListener(
+ focusPeerListener);
+ }
for (ConferenceMember conferenceMember
: this.focusPeer.getConferenceMembers())
@@ -565,11 +568,12 @@ public void soundLevelChanged(ConferenceMembersSoundLevelEvent ev)
Map levels = ev.getLevels();
// focusPeerPanel
+ String address = focusPeerPanel.getCallPeerContactAddress();
+
for(Map.Entry e : levels.entrySet())
{
ConferenceMember key = e.getKey();
Integer value = e.getValue();
- String address = focusPeerPanel.getCallPeerContactAddress();
if(CallManager.addressesAreEqual(key.getAddress(), address))
{
diff --git a/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferencePeerPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferencePeerPanel.java
index 7866c9793..d40f9d88a 100644
--- a/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferencePeerPanel.java
+++ b/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferencePeerPanel.java
@@ -8,6 +8,7 @@
import java.awt.*;
import java.awt.event.*;
+import java.util.*;
import javax.swing.*;
@@ -27,8 +28,9 @@
import org.jitsi.service.resources.*;
/**
- * The ConferencePeerPanel renders a single ConferencePeer,
- * which is not a conference focus.
+ * Depicts a single CallPeer who participates in a telephony conference
+ * and is not a focus or the local user/peer (identified by a specific
+ * Call instance).
*
* @author Yana Stamcheva
* @author Lyubomir Marinov
@@ -90,23 +92,17 @@ public class ConferencePeerPanel
/**
* The SoundLevelListener which listens to the changes in the
- * audio/sound level of the model of this instance. (If {@link #callPeer} is
- * non-null, callPeer is the model of this instance.)
+ * audio/sound level of the model of this instance. If {@link #callPeer} is
+ * non-null, callPeer is the model of this instance i.e.
+ * soundLevelListener will be added to the audio stream of
+ * callPeer and will listen to local calculations of the audio
+ * levels of the remote peer; otherwise, {@link #call} is the model of this
+ * instance i.e. soundLevelListener will be added to call
+ * and will listen to local calculations of the audio levels of the local
+ * peer.
*/
- private final SoundLevelListener soundLevelListener
- = new SoundLevelListener()
- {
- /**
- * Updates the sound level bar upon stream sound level changes.
- *
- * {@inheritDoc}
- */
- public void soundLevelChanged(Object source, int level)
- {
- if (source.equals(participant))
- updateSoundBar(level);
- }
- };
+ private final SoundLevelListenerImpl soundLevelListener
+ = new SoundLevelListenerImpl();
/**
* Initializes a new ConferencePeerPanel which is to depict the
@@ -164,13 +160,8 @@ public ConferencePeerPanel(
resources.getColor(
"service.gui.CALL_LOCAL_USER_BACKGROUND")));
- if(!GuiActivator.getConfigurationService().getBoolean(
- "net.java.sip.communicator.impl.gui.main.call."
- + "DISABLE_SOUND_LEVEL_INDICATORS",
- false))
- {
+ if(isSoundLevelIndicatorEnabled())
call.addLocalUserSoundLevelListener(soundLevelListener);
- }
}
/**
@@ -246,13 +237,8 @@ public ConferencePeerPanel(
callPeerAdapter = new CallPeerAdapter(this.callPeer, this);
- if(!GuiActivator.getConfigurationService().getBoolean(
- "net.java.sip.communicator.impl.gui.main.call."
- + "DISABLE_SOUND_LEVEL_INDICATORS",
- false))
- {
+ if(isSoundLevelIndicatorEnabled())
this.callPeer.addStreamSoundLevelListener(soundLevelListener);
- }
}
/**
@@ -263,7 +249,11 @@ public void dispose()
if (callPeerAdapter != null)
callPeerAdapter.dispose();
if (callPeer != null)
+ {
+ callPeer.removeConferenceMembersSoundLevelListener(
+ soundLevelListener);
callPeer.removeStreamSoundLevelListener(soundLevelListener);
+ }
if (call != null)
call.removeLocalUserSoundLevelListener(soundLevelListener);
}
@@ -362,6 +352,24 @@ public boolean isLocalVideoVisible()
return false;
}
+ /**
+ * Determines whether the indicator which depicts the sound/audio levels (of
+ * the local or remote peer in a call) is to be enabled. For example, the
+ * indicator may be disabled for performance-related reasons.
+ *
+ * @return true if the indicator which depicts the sound/audio
+ * levels (of the local or remote peer in a call) is to be enabled;
+ * otherwise, false
+ */
+ static boolean isSoundLevelIndicatorEnabled()
+ {
+ return
+ !GuiActivator.getConfigurationService().getBoolean(
+ "net.java.sip.communicator.impl.gui.main.call"
+ + ".DISABLE_SOUND_LEVEL_INDICATORS",
+ false);
+ }
+
/**
* Reloads style information.
*/
@@ -752,4 +760,47 @@ public void componentResized(ComponentEvent e)
});
}
}
+
+ /**
+ * Implements the various types of listeners which get notified about
+ * changes in the sound/audio levels of the model of this
+ * ConferencePeerPanel and updates its sound level indicator.
+ */
+ private class SoundLevelListenerImpl
+ implements ConferenceMembersSoundLevelListener,
+ SoundLevelListener
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public void soundLevelChanged(ConferenceMembersSoundLevelEvent ev)
+ {
+ /*
+ * If the callPeer depicted by this ConferencePeerPanel instance is
+ * represented as a ConferenceMember, update the sound level
+ * indicator of this ConferencePeerPanel instance with the specified
+ * sound level (value).
+ */
+ for (Map.Entry e
+ : ev.getLevels().entrySet())
+ {
+ if (CallManager.addressesAreEqual(
+ e.getKey().getAddress(),
+ callPeer.getAddress()))
+ {
+ updateSoundBar(e.getValue());
+ break;
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void soundLevelChanged(Object source, int level)
+ {
+ if (source.equals(participant))
+ updateSoundBar(level);
+ }
+ }
}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/cobri/CobriConferenceIQ.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/cobri/CobriConferenceIQ.java
index db23b3ae0..5831510ea 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/cobri/CobriConferenceIQ.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/cobri/CobriConferenceIQ.java
@@ -91,6 +91,11 @@ public boolean addContent(Content content)
return contents.contains(content) ? false : contents.add(content);
}
+ /**
+ * Returns an XML String representation of this IQ.
+ *
+ * @return an XML String representation of this IQ
+ */
public String getChildElementXML()
{
StringBuilder xml = new StringBuilder();
@@ -120,6 +125,16 @@ public String getChildElementXML()
return xml.toString();
}
+ /**
+ * Returns a Content from the list of Contents of this
+ * conference IQ which has a specific name. If no such
+ * Content exists, returns null.
+ *
+ * @param contentName the name of the Content to be returned
+ * @return a Content from the list of Contents of this
+ * conference IQ which has the specified contentName if
+ * such a Content exists; otherwise, null
+ */
public Content getContent(String contentName)
{
for (Content content : getContents())
@@ -128,6 +143,13 @@ public Content getContent(String contentName)
return null;
}
+ /**
+ * Returns a list of the Contents included into this
+ * conference IQ.
+ *
+ * @return an unmodifiable List of the Contents included
+ * into this conference IQ
+ */
public List getContents()
{
return Collections.unmodifiableList(contents);
@@ -143,6 +165,17 @@ public String getID()
return id;
}
+ /**
+ * Returns a Content from the list of Contents of this
+ * conference IQ which has a specific name. If no such
+ * Content exists at the time of the invocation of the method,
+ * initializes a new Content instance with the specified
+ * contentName and includes it into this conference IQ.
+ *
+ * @param contentName the name of the Content to be returned
+ * @return a Content from the list of Contents of this
+ * conference IQ which has the specified contentName
+ */
public Content getOrCreateContent(String contentName)
{
Content content = getContent(contentName);
@@ -349,6 +382,14 @@ public int getExpire()
return expire;
}
+ /**
+ * Gets the IP address (as a String value) of the host on which
+ * the channel represented by this instance has been allocated.
+ *
+ * @return a String value which represents the IP address of
+ * the host on which the channel represented by this instance
+ * has been allocated
+ */
public String getHost()
{
return host;
@@ -364,16 +405,38 @@ public String getID()
return id;
}
+ /**
+ * Gets a list of payload-type elements defined by XEP-0167:
+ * Jingle RTP Sessions added to this channel.
+ *
+ * @return an unmodifiable List of payload-type
+ * elements defined by XEP-0167: Jingle RTP Sessions added to this
+ * channel
+ */
public List getPayloadTypes()
{
return Collections.unmodifiableList(payloadTypes);
}
+ /**
+ * Gets the port which has been allocated to this channel for
+ * the purposes of transmitting RTCP packets.
+ *
+ * @return the port which has been allocated to this channel
+ * for the purposes of transmitting RTCP packets
+ */
public int getRTCPPort()
{
return rtcpPort;
}
+ /**
+ * Gets the port which has been allocated to this channel for
+ * the purposes of transmitting RTP packets.
+ *
+ * @return the port which has been allocated to this channel
+ * for the purposes of transmitting RTP packets
+ */
public int getRTPPort()
{
return rtpPort;
@@ -472,6 +535,14 @@ public void setExpire(int expire)
this.expire = expire;
}
+ /**
+ * Sets the IP address (as a String value) of the host on which
+ * the channel represented by this instance has been allocated.
+ *
+ * @param host a String value which represents the IP address
+ * of the host on which the channel represented by this
+ * instance has been allocated
+ */
public void setHost(String host)
{
this.host = host;
@@ -487,11 +558,25 @@ public void setID(String id)
this.id = id;
}
+ /**
+ * Sets the port which has been allocated to this channel for
+ * the purposes of transmitting RTCP packets.
+ *
+ * @param rtcpPort the port which has been allocated to this
+ * channel for the purposes of transmitting RTCP packets
+ */
public void setRTCPPort(int rtcpPort)
{
this.rtcpPort = rtcpPort;
}
+ /**
+ * Sets the port which has been allocated to this channel for
+ * the purposes of transmitting RTP packets.
+ *
+ * @param rtpPort the port which has been allocated to this
+ * channel for the purposes of transmitting RTP packets
+ */
public void setRTPPort(int rtpPort)
{
this.rtpPort = rtpPort;
@@ -515,6 +600,14 @@ public void setSSRCs(long[] ssrcs)
: ssrcs.clone();
}
+ /**
+ * Appends the XML String representation of this
+ * Channel to a specific StringBuilder.
+ *
+ * @param xml the StringBuilder to which the XML
+ * String representation of this Channel is to be
+ * appended
+ */
public void toXML(StringBuilder xml)
{
xml.append('<').append(ELEMENT_NAME);
@@ -639,6 +732,18 @@ public Content(String name)
setName(name);
}
+ /**
+ * Adds a specific Channel to the list of Channels
+ * included into this Content.
+ *
+ * @param channel the Channel to be included into this
+ * Content
+ * @return true if the list of Channels included into
+ * this Content was modified as a result of the execution of
+ * the method; otherwise, false
+ * @throws NullPointerException if the specified channel is
+ * null
+ */
public boolean addChannel(Channel channel)
{
if (channel == null)
@@ -647,11 +752,32 @@ public boolean addChannel(Channel channel)
return channels.contains(channel) ? false : channels.add(channel);
}
+ /**
+ * Gets the Channel at a specific index/position within the
+ * list of Channels included in this Content.
+ *
+ * @param channelIndex the index/position within the list of
+ * Channels included in this Content of the
+ * Channel to be returned
+ * @return the Channel at the specified channelIndex
+ * within the list of Channels included in this
+ * Content
+ */
public Channel getChannel(int channelIndex)
{
return getChannels().get(channelIndex);
}
+ /**
+ * Gets a Channel which is included into this Content
+ * and which has a specific ID.
+ *
+ * @param channelID the ID of the Channel included into this
+ * Content to be returned
+ * @return the Channel which is included into this
+ * Content and which has the specified channelID if
+ * such a Channel exists; otherwise, null
+ */
public Channel getChannel(String channelID)
{
for (Channel channel : getChannels())
@@ -660,11 +786,25 @@ public Channel getChannel(String channelID)
return null;
}
+ /**
+ * Gets the number of Channels included into/associated with
+ * this Content.
+ *
+ * @return the number of Channels included into/associated with
+ * this Content
+ */
public int getChannelCount()
{
return getChannels().size();
}
+ /**
+ * Gets a list of the Channel included into/associated with
+ * this Content.
+ *
+ * @return an unmodifiable List of the Channels
+ * included into/associated with this Content
+ */
public List getChannels()
{
return Collections.unmodifiableList(channels);
@@ -680,6 +820,16 @@ public String getName()
return name;
}
+ /**
+ * Removes a specific Channel from the list of
+ * Channels included into this Content.
+ *
+ * @param channel the Channel to be excluded from this
+ * Content
+ * @return true if the list of Channels included into
+ * this Content was modified as a result of the execution of
+ * the method; otherwise, false
+ */
public boolean removeChannel(Channel channel)
{
return channels.remove(channel);
@@ -701,6 +851,14 @@ public void setName(String name)
this.name = name;
}
+ /**
+ * Appends the XML String representation of this
+ * Content to a specific StringBuilder.
+ *
+ * @param xml the StringBuilder to which the XML
+ * String representation of this Content is to be
+ * appended
+ */
public void toXML(StringBuilder xml)
{
xml.append('<').append(ELEMENT_NAME);
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/cobri/CobriStreamConnector.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/cobri/CobriStreamConnector.java
index a682ea662..3181a5889 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/cobri/CobriStreamConnector.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/cobri/CobriStreamConnector.java
@@ -32,6 +32,14 @@ public CobriStreamConnector(StreamConnector streamConnector)
super(streamConnector);
}
+ /**
+ * {@inheritDoc}
+ *
+ * Overrides {@link StreamConnectorDelegate#close()} in order to prevent the
+ * closing of the StreamConnector wrapped by this instance because
+ * the latter is shared and it is not clear whether no
+ * TransportManager is using it.
+ */
@Override
public void close()
{
@@ -41,6 +49,14 @@ public void close()
*/
}
+ /**
+ * {@inheritDoc}
+ *
+ * Invokes {@link #close()} on this instance when it is clear that no
+ * TransportManager is using it in order to release the resources
+ * allocated by this instance throughout its life time (that need explicit
+ * disposal).
+ */
@Override
protected void finalize()
throws Throwable
diff --git a/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java b/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java
index 7702c420a..ab4a21112 100644
--- a/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java
+++ b/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java
@@ -235,25 +235,39 @@ public void propertyChange(PropertyChangeEvent evt)
private final VideoListener videoStreamVideoListener
= new VideoListener()
{
- public void videoAdded(VideoEvent event)
+ /**
+ * Notifies this VideoListener about a specific
+ * VideoEvent. Fires a new VideoEvent which has
+ * this CallPeerMediaHandler as its source and carries the
+ * same information as the specified ev i.e. translates the
+ * specified ev into a VideoEvent fired by this
+ * CallPeerMediaHandler.
+ *
+ * @param ev the VideoEvent to notify this
+ * VideoListener about
+ */
+ private void onVideoEvent(VideoEvent ev)
{
- VideoEvent clone = event.clone(CallPeerMediaHandler.this);
+ VideoEvent clone = ev.clone(CallPeerMediaHandler.this);
fireVideoEvent(clone);
if (clone.isConsumed())
- event.consume();
+ ev.consume();
+ }
+
+ public void videoAdded(VideoEvent ev)
+ {
+ onVideoEvent(ev);
}
- public void videoRemoved(VideoEvent event)
+ public void videoRemoved(VideoEvent ev)
{
- // Forwarded in the same way as VIDEO_ADDED.
- videoAdded(event);
+ onVideoEvent(ev);
}
- public void videoUpdate(VideoEvent event)
+ public void videoUpdate(VideoEvent ev)
{
- // Forwarded in the same way as VIDEO_ADDED.
- videoAdded(event);
+ onVideoEvent(ev);
}
};
@@ -779,7 +793,7 @@ public void removeVideoListener(VideoListener listener)
* null if we are trying to remove it.
*/
public void setLocalUserAudioLevelListener(
- SimpleAudioLevelListener listener)
+ SimpleAudioLevelListener listener)
{
synchronized(localAudioLevelListenerLock)
{
@@ -803,7 +817,6 @@ public void setLocalUserAudioLevelListener(
*
* @param listener the SimpleAudioLevelListener to add or
* null if we are trying to remove it.
- *
*/
public void setStreamAudioLevelListener(SimpleAudioLevelListener listener)
{
@@ -826,22 +839,21 @@ public void setStreamAudioLevelListener(SimpleAudioLevelListener listener)
* receiving notifications for changes in the audio levels of the remote
* participants that our peer is mixing.
*
- * @param csrcAudioLevelListener the CsrcAudioLevelListener to set
- * to our audio streams.
+ * @param listener the CsrcAudioLevelListener to set to our audio
+ * stream.
*/
- public void setCsrcAudioLevelListener(
- CsrcAudioLevelListener csrcAudioLevelListener)
+ public void setCsrcAudioLevelListener(CsrcAudioLevelListener listener)
{
synchronized(csrcAudioLevelListenerLock)
{
- this.csrcAudioLevelListener = csrcAudioLevelListener;
+ this.csrcAudioLevelListener = listener;
MediaStream audioStream = getStream(MediaType.AUDIO);
if (audioStream != null)
{
((AudioMediaStream) audioStream).setCsrcAudioLevelListener(
- csrcAudioLevelListener);
+ listener);
}
}
}
diff --git a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java
index 24feff228..fa228001a 100644
--- a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java
+++ b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java
@@ -134,9 +134,11 @@ protected void addCallPeer(T callPeer)
synchronized(localUserAudioLevelListenersSyncRoot)
{
- // if there's someone listening for audio level events then they'd
- // also like to know about the new peer. make sure always the first
- // element is the one to listen for local audio events
+ /*
+ * If there's someone listening for audio level events, then they'd
+ * also like to know about the new peer. Make sure the first element
+ * is always the one to listen for local audio events.
+ */
List callPeers = getCallPeerList();
if ((callPeers.size() == 1) && callPeers.get(0).equals(callPeer))
@@ -150,17 +152,16 @@ protected void addCallPeer(T callPeer)
}
/**
- * Removes callPeer from the list of peers in this
- * call. The method has no effect if there was no such peer in the
- * call.
+ * Removes callPeer from the list of peers in this call. The method
+ * has no effect if there was no such peer in the call.
*
- * @param evt the event containing the CallPeer leaving the call;
- * also we can obtain the reason for the CallPeerChangeEvent if
- * any. Use the event as cause for the call state change event..
+ * @param ev the event containing the CallPeer leaving the call and
+ * the reason (if any) for the CallPeerChangeEvent. Use the event
+ * as the cause for the call state change event.
*/
- @SuppressWarnings("unchecked")
private void removeCallPeer(CallPeerChangeEvent evt)
{
+ @SuppressWarnings("unchecked")
T callPeer = (T) evt.getSourceCallPeer();
if (!doRemoveCallPeer(callPeer))
@@ -180,9 +181,11 @@ private void removeCallPeer(CallPeerChangeEvent evt)
if(!callPeers.isEmpty())
{
- callPeers.get(0).getMediaHandler()
- .setLocalUserAudioLevelListener(
- localAudioLevelDelegator);
+ callPeers
+ .get(0)
+ .getMediaHandler()
+ .setLocalUserAudioLevelListener(
+ localAudioLevelDelegator);
}
}
@@ -356,19 +359,21 @@ public void addLocalUserSoundLevelListener(SoundLevelListener l)
if ((localUserAudioLevelListeners == null)
|| localUserAudioLevelListeners.isEmpty())
{
- //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
- //level would only be calculated if anyone is interested in
- //receiving them.
+ /*
+ * If this is the first listener that's being registered, we
+ * also need to register ourselves as an audio level listener
+ * with the MediaHandler. We do this so that audio level would
+ * only be calculated if anyone is interested in receiving them.
+ */
Iterator callPeerIter = getCallPeers();
while (callPeerIter.hasNext())
{
- callPeerIter.next()
- .getMediaHandler()
- .setLocalUserAudioLevelListener(
- localAudioLevelDelegator);
+ callPeerIter
+ .next()
+ .getMediaHandler()
+ .setLocalUserAudioLevelListener(
+ localAudioLevelDelegator);
}
}
@@ -425,9 +430,10 @@ public void removeLocalUserSoundLevelListener(SoundLevelListener l)
while (callPeerIter.hasNext())
{
- callPeerIter.next()
- .getMediaHandler()
- .setLocalUserAudioLevelListener(null);
+ callPeerIter
+ .next()
+ .getMediaHandler()
+ .setLocalUserAudioLevelListener(null);
}
}
}
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 55054eacf..3d56d05cf 100644
--- a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java
+++ b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java
@@ -32,6 +32,7 @@
* ProtocolProviderServiceJabberImpl
*
* @author Emil Ivov
+ * @author Lyubomir Marinov
*/
public abstract class MediaAwareCallPeer
,
@@ -50,15 +51,33 @@ public abstract class MediaAwareCallPeer
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
+ conferenceMembersSoundLevelListeners
+ = new ArrayList();
+
/**
* A byte array containing the image/photo representing the call peer.
*/
private byte[] image;
/**
- * A string uniquely identifying the peer.
+ * The media handler class handles all media management for a single
+ * CallPeer. This includes initializing and configuring streams,
+ * generating SDP, handling ICE, etc. One instance of CallPeeralways
+ * corresponds to exactly one instance of CallPeerMediaHandler and
+ * both classes are only separated for reasons of readability.
*/
- private String peerID;
+ private U mediaHandler;
/**
* The PropertyChangeListener which listens to
@@ -67,12 +86,14 @@ public abstract class MediaAwareCallPeer
private PropertyChangeListener mediaHandlerPropertyChangeListener;
/**
- * The List of PropertyChangeListeners listening to this
- * CallPeer for changes in the values of its properties related to
- * video.
+ * A string uniquely identifying the peer.
*/
- private final List videoPropertyChangeListeners
- = new LinkedList();
+ private String peerID;
+
+ /**
+ * The protocol provider that this peer belongs to.
+ */
+ private final V protocolProvider;
/**
* The list of SoundLevelListeners interested in level changes in
@@ -81,44 +102,24 @@ public abstract class MediaAwareCallPeer
* It is implemented as a copy-on-write storage because the number of
* additions and removals of SoundLevelListeners is expected to be
* far smaller than the number of audio level changes. The access to it is
- * to be synchronized using {@link #streamAudioLevelListenersSyncRoot}.
+ * to be synchronized using {@link #streamSoundLevelListenersSyncRoot}.
*
*/
- private List streamAudioLevelListeners;
+ private List streamSoundLevelListeners;
/**
* The Object to synchronize the access to
- * {@link #streamAudioLevelListeners}.
+ * {@link #streamSoundLevelListeners}.
*/
- private final Object streamAudioLevelListenersSyncRoot = new Object();
+ private final Object streamSoundLevelListenersSyncRoot = new Object();
/**
- * Holds 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
- conferenceMemberAudioLevelListeners
- = new ArrayList();
-
- /**
- * The call this peer belongs to.
- */
- private T call;
-
- /**
- * The protocol provider that this peer belongs to.
- */
- private final V protocolProvider;
-
- /**
- * The media handler class handles all media management for a single
- * CallPeer. This includes initializing and configuring streams,
- * generating SDP, handling ICE, etc. One instance of CallPeeralways
- * corresponds to exactly one instance of CallPeerMediaHandler and
- * both classes are only separated for reasons of readability.
+ * The List of PropertyChangeListeners listening to this
+ * CallPeer for changes in the values of its properties related to
+ * video.
*/
- private U mediaHandler;
+ private final List videoPropertyChangeListeners
+ = new LinkedList();
/**
* Creates a new call peer with address peerAddress.
@@ -141,458 +142,500 @@ public MediaAwareCallPeer(T owningCall)
}
/**
- * The method returns an image representation of the call peer if one is
- * available.
+ * Adds a specific ConferenceMembersSoundLevelListener to the list
+ * of listeners interested in and notified about changes in conference
+ * members sound level.
*
- * @return byte[] a byte array containing the image or null if no image is
- * available.
+ * @param listener the ConferenceMembersSoundLevelListener to add
+ * @throws NullPointerException if listener is null
*/
- public byte[] getImage()
+ public void addConferenceMembersSoundLevelListener(
+ ConferenceMembersSoundLevelListener listener)
{
- return image;
+ /*
+ * 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);
+ }
}
/**
- * Sets the byte array containing an image representation (photo or picture)
- * of the call peer.
+ * Adds a specific SoundLevelListener 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 image a byte array containing the image
+ * @param listener the SoundLevelListener to add
*/
- public void setImage(byte[] image)
+ public void addStreamSoundLevelListener(SoundLevelListener listener)
{
- byte[] oldImage = getImage();
- this.image = image;
+ synchronized (streamSoundLevelListenersSyncRoot)
+ {
+ if ((streamSoundLevelListeners == null)
+ || streamSoundLevelListeners.isEmpty())
+ {
+ // 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.
+ getMediaHandler().setStreamAudioLevelListener(this);
+ }
- //Fire the Event
- fireCallPeerChangeEvent(
- CallPeerChangeEvent.CALL_PEER_IMAGE_CHANGE,
- oldImage,
- image);
+ /*
+ * Implement streamAudioLevelListeners as a copy-on-write storage so
+ * that iterators over it can iterate without
+ * ConcurrentModificationExceptions.
+ */
+ streamSoundLevelListeners
+ = (streamSoundLevelListeners == null)
+ ? new ArrayList()
+ : new ArrayList(
+ streamSoundLevelListeners);
+ streamSoundLevelListeners.add(listener);
+ }
}
/**
- * Returns a unique identifier representing this peer.
+ * Adds a specific PropertyChangeListener to the list of
+ * listeners which get notified when the properties (e.g.
+ * LOCAL_VIDEO_STREAMING) associated with this CallPeer change
+ * their values.
*
- * @return an identifier representing this call peer.
+ * @param listener the PropertyChangeListener to be notified
+ * when the properties associated with the specified Call change
+ * their values
*/
- public String getPeerID()
+ public void addVideoPropertyChangeListener(PropertyChangeListener listener)
{
- return peerID;
- }
+ if (listener == null)
+ throw new NullPointerException("listener");
- /**
- * 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;
- }
+ 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 listeners;
+ synchronized (videoPropertyChangeListeners)
+ {
+ listeners
+ = new LinkedList(
+ videoPropertyChangeListeners);
+ }
- /**
- * 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.
- */
- public T getCall()
- {
- return call;
- }
+ PropertyChangeEvent thisEvent
+ = new PropertyChangeEvent(
+ this,
+ event.getPropertyName(),
+ event.getOldValue(),
+ event.getNewValue());
- /**
- * 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;
+ for (PropertyChangeListener listener : listeners)
+ listener.propertyChange(thisEvent);
+ }
+ };
+ getMediaHandler()
+ .addPropertyChangeListener(
+ mediaHandlerPropertyChangeListener);
+ }
+ }
}
/**
- * Returns the protocol provider that this peer belongs to.
+ * 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.
*
- * @return a reference to the ProtocolProviderService that this
- * peer belongs to.
+ * @param newLevel the new audio level of the audio stream received from the
+ * remote peer
*/
- public V getProtocolProvider()
+ public void audioLevelChanged(int newLevel)
{
- return protocolProvider;
+ /*
+ * 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;
+
+ if (((conferenceMemberCount = getConferenceMemberCount()) > 0)
+ && (conferenceMemberCount < 3))
+ {
+ long audioRemoteSSRC
+ = getMediaHandler().getRemoteSSRC(MediaType.AUDIO);
+
+ if (audioRemoteSSRC != CallPeerMediaHandler.SSRC_UNKNOWN)
+ {
+ audioLevelsReceived(new long[] { audioRemoteSSRC, newLevel });
+ return;
+ }
+ }
+
+ fireStreamSoundLevelChanged(newLevel);
}
+
/**
- * Determines whether the audio stream (if any) being sent to this
- * peer is mute.
+ * Implements {@link CsrcAudioLevelListener#audioLevelsReceived(long[])}.
+ * Delivers the received audio levels to the
+ * {@link ConferenceMembersSoundLevelListener}s registered with this
+ * MediaAwareCallPeer..
*
- * @return true if an audio stream is being sent to this
- * peer and it is currently mute; false, otherwise
+ * @param audioLevels the levels that we need to dispatch to all registered
+ * ConferenceMemberSoundLevelListeners.
*/
- @Override
- public boolean isMute()
+ public void audioLevelsReceived(long[] audioLevels)
{
- return getMediaHandler().isMute();
- }
-
- /**
- * Updates the state of this CallPeer to match the remotely-on-hold
- * status of our media handler.
- */
- public void reevalRemoteHoldStatus()
- {
- boolean remotelyOnHold = getMediaHandler().isRemotelyOnHold();
+ if (getConferenceMemberCount() == 0)
+ return;
- 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))
+ Map levelsMap
+ = new HashMap();
+
+ for (int i = 0; i < audioLevels.length; i += 2)
{
- if (!remotelyOnHold)
- setState(CallPeerState.CONNECTED);
+ ConferenceMember mmbr = findConferenceMember(audioLevels[i]);
+
+ if (mmbr != null)
+ levelsMap.put(mmbr, (int) audioLevels[i + 1]);
}
- else if (remotelyOnHold)
+
+ synchronized (conferenceMembersSoundLevelListeners)
{
- setState(CallPeerState.ON_HOLD_REMOTELY);
+ 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);
+ }
+ }
}
}
/**
- * Updates the state of this CallPeer to match the locally-on-hold
- * status of our media handler.
+ * Does nothing.
+ * @param evt the event.
*/
- 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);
- }
- }
+ public void callPeerAdded(CallPeerEvent evt) {}
/**
- * Sets the mute property for this call peer.
- *
- * @param newMuteValue the new value of the mute property for this call peer
+ * Does nothing.
+ * @param evt the event.
*/
- @Override
- public void setMute(boolean newMuteValue)
- {
- getMediaHandler().setMute(newMuteValue);
- super.setMute(newMuteValue);
- }
+ public void callPeerRemoved(CallPeerEvent evt) {}
/**
- * Logs message and cause and sets this peer's
- * state to CallPeerState.FAILED
+ * Dummy implementation of {@link CallPeerConferenceListener
+ * #conferenceFocusChanged(CallPeerConferenceEvent)}.
*
- * @param message a message to log and display to the user.
- * @param throwable the exception that cause the error we are logging
+ * @param evt ignored
*/
- public void logAndFail(String message, Throwable throwable)
+ public void conferenceFocusChanged(CallPeerConferenceEvent evt)
{
- logger.error(message, throwable);
- setState(CallPeerState.FAILED, message);
}
/**
- * 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 true if local video transmission is allowed and
- * false otherwise.
+ * 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.
*
- * @throws OperationFailedException if video initialization fails.
+ * @param conferenceEvent the event containing information (that we don't
+ * really use) on the newly add member.
*/
- public void setLocalVideoAllowed(boolean allowed)
- throws OperationFailedException
+ public void conferenceMemberAdded(CallPeerConferenceEvent conferenceEvent)
{
- CallPeerMediaHandler> mediaHandler = getMediaHandler();
-
- if(mediaHandler.isLocalVideoTransmissionEnabled() != allowed)
+ if (getConferenceMemberCount() > 2)
{
- // Modify the local media setup to reflect the requested setting for
- // the streaming of the local video.
- mediaHandler.setLocalVideoTransmissionEnabled(allowed);
+ /*
+ * 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);
}
}
/**
- * Determines whether we are currently streaming video toward whoever this
- * CallPeerSipImpl represents.
+ * 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.
*
- * @return true if we are currently streaming video toward this
- * CallPeer and false otherwise.
+ * @param conferenceEvent the event containing information (that we don't
+ * really use) on the freshly removed member.
*/
- public boolean isLocalVideoStreaming()
+ public void conferenceMemberRemoved(CallPeerConferenceEvent conferenceEvent)
{
- return getMediaHandler().isLocalVideoTransmissionEnabled();
+ 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);
+ }
}
/**
- * Adds a specific PropertyChangeListener to the list of
- * listeners which get notified when the properties (e.g.
- * LOCAL_VIDEO_STREAMING) associated with this CallPeer change
- * their values.
+ * Invokes {@link SoundLevelListener#soundLevelChanged(Object, int) on
+ * the SoundLevelListeners interested in the changes of the audio
+ * stream received from the remote peer i.e. in
+ * {@link #streamSoundLevelListeners}.
*
- * @param listener the PropertyChangeListener to be notified
- * when the properties associated with the specified Call change
- * their values
+ * @param newLevel the new value of the sound level to notify
+ * streamSoundLevelListeners about
*/
- public void addVideoPropertyChangeListener(PropertyChangeListener listener)
+ private void fireStreamSoundLevelChanged(int newLevel)
{
- if (listener == null)
- throw new NullPointerException("listener");
+ List streamSoundLevelListeners;
- synchronized (videoPropertyChangeListeners)
+ synchronized (streamSoundLevelListenersSyncRoot)
{
/*
- * 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.
+ * 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.
*/
- if (!videoPropertyChangeListeners.contains(listener)
- && videoPropertyChangeListeners.add(listener)
- && (mediaHandlerPropertyChangeListener == null))
- {
- mediaHandlerPropertyChangeListener
- = new PropertyChangeListener()
- {
- public void propertyChange(PropertyChangeEvent event)
- {
- Iterable listeners;
-
- synchronized (videoPropertyChangeListeners)
- {
- listeners
- = new LinkedList(
- videoPropertyChangeListeners);
- }
+ streamSoundLevelListeners = this.streamSoundLevelListeners;
+ }
- PropertyChangeEvent thisEvent
- = new PropertyChangeEvent(
- this,
- event.getPropertyName(),
- event.getOldValue(),
- event.getNewValue());
+ 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 (PropertyChangeListener listener : listeners)
- listener.propertyChange(thisEvent);
- }
- };
- getMediaHandler()
- .addPropertyChangeListener(
- mediaHandlerPropertyChangeListener);
+ for(int i = 0; i < streamSoundLevelListenerCount; i++)
+ {
+ streamSoundLevelListeners.get(i).soundLevelChanged(
+ this,
+ newLevel);
}
}
}
/**
- * Removes a specific PropertyChangeListener from the list of
- * listeners which get notified when the properties (e.g.
- * LOCAL_VIDEO_STREAMING) associated with this CallPeer change
- * their values.
+ * Returns a reference to the call that this peer belongs to. Calls
+ * are created by underlying telephony protocol implementations.
*
- * @param listener the PropertyChangeListener to no longer be
- * notified when the properties associated with the specified Call
- * change their values
+ * @return a reference to the call containing this peer.
*/
- public void removeVideoPropertyChangeListener(
- PropertyChangeListener listener)
+ public T getCall()
{
- 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;
- }
- }
+ 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 CallPeerMediaHandler used by this
+ * peer. The media handler class handles all media management for a single
+ * CallPeer. This includes initializing and configuring streams,
+ * generating SDP, handling ICE, etc. One instance of CallPeer
+ * always corresponds to exactly one instance of
+ * CallPeerMediaHandler and both classes are only separated for
+ * reasons of readability.
+ *
+ * @return a reference to the CallPeerMediaHandler instance that
+ * this peer uses for media related tips and tricks.
+ */
+ public U getMediaHandler()
+ {
+ return mediaHandler;
}
/**
- * Overrides the parent set state method in order to make sure that we
- * close our media handler whenever we enter a disconnected state.
+ * Returns a unique identifier representing this peer.
*
- * @param newState the CallPeerState 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.
+ * @return an identifier representing this call peer.
*/
- @Override
- public void setState(CallPeerState newState, String reason, int reasonCode)
+ public String getPeerID()
{
- // 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();
+ return peerID;
+ }
- 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 protocol provider that this peer belongs to.
+ *
+ * @return a reference to the ProtocolProviderService that this
+ * peer belongs to.
+ */
+ public V getProtocolProvider()
+ {
+ return protocolProvider;
}
/**
- * Adds a specific SoundLevelListener 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.
+ * Determines whether we are currently streaming video toward whoever this
+ * CallPeerSipImpl represents.
*
- * @param listener the SoundLevelListener to add
+ * @return true if we are currently streaming video toward this
+ * CallPeer and false otherwise.
*/
- public void addStreamSoundLevelListener(SoundLevelListener listener)
+ public boolean isLocalVideoStreaming()
{
- synchronized (streamAudioLevelListenersSyncRoot)
- {
- if ((streamAudioLevelListeners == null)
- || streamAudioLevelListeners.isEmpty())
- {
- // 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.
- getMediaHandler().setStreamAudioLevelListener(this);
- }
+ return getMediaHandler().isLocalVideoTransmissionEnabled();
+ }
- /*
- * Implement streamAudioLevelListeners as a copy-on-write storage so
- * that iterators over it can iterate without
- * ConcurrentModificationExceptions.
- */
- streamAudioLevelListeners
- = (streamAudioLevelListeners == null)
- ? new ArrayList()
- : new ArrayList(
- streamAudioLevelListeners);
- streamAudioLevelListeners.add(listener);
- }
+ /**
+ * Determines whether the audio stream (if any) being sent to this
+ * peer is mute.
+ *
+ * @return true if an audio stream is being sent to this
+ * peer and it is currently mute; false, otherwise
+ */
+ @Override
+ public boolean isMute()
+ {
+ return getMediaHandler().isMute();
}
/**
- * Removes a specific SoundLevelListener of the list of
- * listeners interested in and notified about changes in stream sound level
- * related information.
+ * Logs message and cause and sets this peer's
+ * state to CallPeerState.FAILED
*
- * @param listener the SoundLevelListener to remove
+ * @param message a message to log and display to the user.
+ * @param throwable the exception that cause the error we are logging
*/
- public void removeStreamSoundLevelListener(SoundLevelListener listener)
+ public void logAndFail(String message, Throwable throwable)
{
- synchronized (streamAudioLevelListenersSyncRoot)
- {
- /*
- * Implement streamAudioLevelListeners as a copy-on-write storage so
- * that iterators over it can iterate over it without
- * ConcurrentModificationExceptions.
- */
- if (streamAudioLevelListeners != null)
- {
- streamAudioLevelListeners
- = new ArrayList(
- streamAudioLevelListeners);
- if (streamAudioLevelListeners.remove(listener)
- && streamAudioLevelListeners.isEmpty())
- streamAudioLevelListeners = null;
- }
+ logger.error(message, throwable);
+ setState(CallPeerState.FAILED, message);
+ }
- if ((streamAudioLevelListeners == null)
- || streamAudioLevelListeners.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);
- }
+ /**
+ * Updates the state of this CallPeer 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);
}
}
/**
- * Adds a specific ConferenceMembersSoundLevelListener to the list
- * of listeners interested in and notified about changes in conference
- * members sound level.
- *
- * @param listener the ConferenceMembersSoundLevelListener to add
- * @throws NullPointerException if listener is null
+ * Updates the state of this CallPeer to match the remotely-on-hold
+ * status of our media handler.
*/
- public void addConferenceMembersSoundLevelListener(
- ConferenceMembersSoundLevelListener listener)
+ public void reevalRemoteHoldStatus()
{
- /*
- * 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;
+ boolean remotelyOnHold = getMediaHandler().isRemotelyOnHold();
- synchronized (conferenceMemberAudioLevelListeners)
+ CallPeerState state = getState();
+ if (CallPeerState.ON_HOLD_LOCALLY.equals(state))
{
- if (conferenceMemberAudioLevelListeners.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);
- }
-
- conferenceMemberAudioLevelListeners.add(listener);
+ 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);
}
}
@@ -607,10 +650,10 @@ public void addConferenceMembersSoundLevelListener(
public void removeConferenceMembersSoundLevelListener(
ConferenceMembersSoundLevelListener listener)
{
- synchronized (conferenceMemberAudioLevelListeners)
+ synchronized (conferenceMembersSoundLevelListeners)
{
- if (conferenceMemberAudioLevelListeners.remove(listener)
- && (conferenceMemberAudioLevelListeners.size() == 0))
+ 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
@@ -622,69 +665,93 @@ public void removeConferenceMembersSoundLevelListener(
}
/**
- * Implements {@link CsrcAudioLevelListener#audioLevelsReceived(long[])}.
- * Delivers the received audio levels to the
- * {@link ConferenceMembersSoundLevelListener}s registered with this
- * MediaAwareCallPeer..
+ * Removes a specific SoundLevelListener of the list of
+ * listeners interested in and notified about changes in stream sound level
+ * related information.
*
- * @param audioLevels the levels that we need to dispatch to all registered
- * ConferenceMemberSoundLevelListeners.
+ * @param listener the SoundLevelListener to remove
*/
- public void audioLevelsReceived(long[] audioLevels)
+ public void removeStreamSoundLevelListener(SoundLevelListener listener)
{
- if (getConferenceMemberCount() == 0)
- return;
-
- Map levelsMap
- = new HashMap();
-
- for (int i = 0; i < audioLevels.length; i += 2)
- {
- ConferenceMember mmbr = findConferenceMember(audioLevels[i]);
-
- if (mmbr != null)
- levelsMap.put(mmbr, (int)audioLevels[i + 1]);
- }
-
- ConferenceMembersSoundLevelEvent evt
- = new ConferenceMembersSoundLevelEvent(this, levelsMap);
-
- synchronized (conferenceMemberAudioLevelListeners)
+ synchronized (streamSoundLevelListenersSyncRoot)
{
- int conferenceMemberAudioLevelListenerCount
- = conferenceMemberAudioLevelListeners.size();
+ /*
+ * 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(
+ streamSoundLevelListeners);
+ if (streamSoundLevelListeners.remove(listener)
+ && streamSoundLevelListeners.isEmpty())
+ streamSoundLevelListeners = null;
+ }
- for (int i = 0; i < conferenceMemberAudioLevelListenerCount; i++)
- conferenceMemberAudioLevelListeners.get(i).soundLevelChanged(
- evt);
+ 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);
+ }
}
}
/**
- * Does nothing.
- * @param evt the event.
- */
- public void callPeerAdded(CallPeerEvent evt) {}
-
- /**
- * Does nothing.
- * @param evt the event.
+ * Removes a specific PropertyChangeListener from the list of
+ * listeners which get notified when the properties (e.g.
+ * LOCAL_VIDEO_STREAMING) associated with this CallPeer change
+ * their values.
+ *
+ * @param listener the PropertyChangeListener to no longer be
+ * notified when the properties associated with the specified Call
+ * change their values
*/
- public void callPeerRemoved(CallPeerEvent evt) {}
+ 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 status to ON for this call peer.
+ * Sets the security message associated with a failure/warning or
+ * information coming from the encryption protocol.
*
- * @param sessionType the type of the call session - audio or video.
- * @param cipher the cipher
- * @param sender the security controller that caused the event
+ * @param messageType the type of the message.
+ * @param i18nMessage the message
+ * @param severity severity level
*/
- public void securityTurnedOn(int sessionType, String cipher,
- SrtpControl sender)
+ public void securityMessageReceived(
+ String messageType, String i18nMessage, int severity)
{
- getMediaHandler().startSrtpMultistream(sender);
- fireCallPeerSecurityOnEvent(
- new CallPeerSecurityOnEvent(this, sessionType, cipher, sender));
+ fireCallPeerSecurityMessageEvent(messageType,
+ i18nMessage,
+ severity);
}
/**
@@ -692,12 +759,13 @@ public void securityTurnedOn(int sessionType, String cipher,
* offer to secure the connection.
*
* @param sessionType the type of the call session - audio or video.
+ * @param sender the security controller that caused the event
*/
- public void securityTimeout(int sessionType)
+ public void securityNegotiationStarted(int sessionType, SrtpControl sender)
{
- CallPeerSecurityTimeoutEvent evt =
- new CallPeerSecurityTimeoutEvent(this, sessionType);
- fireCallPeerSecurityTimeoutEvent(evt);
+ CallPeerSecurityNegotiationStartedEvent evt =
+ new CallPeerSecurityNegotiationStartedEvent(this, sessionType, sender);
+ fireCallPeerSecurityNegotiationStartedEvent(evt);
}
/**
@@ -705,13 +773,12 @@ public void securityTimeout(int sessionType)
* offer to secure the connection.
*
* @param sessionType the type of the call session - audio or video.
- * @param sender the security controller that caused the event
*/
- public void securityNegotiationStarted(int sessionType, SrtpControl sender)
+ public void securityTimeout(int sessionType)
{
- CallPeerSecurityNegotiationStartedEvent evt =
- new CallPeerSecurityNegotiationStartedEvent(this, sessionType, sender);
- fireCallPeerSecurityNegotiationStartedEvent(evt);
+ CallPeerSecurityTimeoutEvent evt =
+ new CallPeerSecurityTimeoutEvent(this, sessionType);
+ fireCallPeerSecurityTimeoutEvent(evt);
}
/**
@@ -733,157 +800,73 @@ public void securityTurnedOff(int sessionType)
}
/**
- * Sets the security message associated with a failure/warning or
- * information coming from the encryption protocol.
+ * Sets the security status to ON for this call peer.
*
- * @param messageType the type of the message.
- * @param i18nMessage the message
- * @param severity severity level
+ * @param sessionType the type of the call session - audio or video.
+ * @param cipher the cipher
+ * @param sender the security controller that caused the event
*/
- public void securityMessageReceived(
- String messageType, String i18nMessage, int severity)
+ public void securityTurnedOn(int sessionType, String cipher,
+ SrtpControl sender)
{
- fireCallPeerSecurityMessageEvent(messageType,
- i18nMessage,
- severity);
+ getMediaHandler().startSrtpMultistream(sender);
+ fireCallPeerSecurityOnEvent(
+ new CallPeerSecurityOnEvent(this, sessionType, cipher, sender));
}
/**
- * Dummy implementation of {@link CallPeerConferenceListener
- * #conferenceFocusChanged(CallPeerConferenceEvent)}.
+ * Sets the call containing this peer.
*
- * @param evt ignored
+ * @param call the call that this call peer is participating in.
*/
- public void conferenceFocusChanged(CallPeerConferenceEvent evt)
+ public void setCall(T call)
{
+ this.call = call;
}
/**
- * 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.
+ * Sets the byte array containing an image representation (photo or picture)
+ * of the call peer.
*
- * @param conferenceEvent the event containing information (that we don't
- * really use) on the newly add member.
+ * @param image a byte array containing the image
*/
- public void conferenceMemberAdded(CallPeerConferenceEvent conferenceEvent)
+ public void setImage(byte[] image)
{
- if (getConferenceMemberCount() > 2)
- {
- // this peer is now a conference focus with more than three
- // participants. This means that this peer is mixing and sending us
- // audio for at least two separate participants. We therefore need
- // to remove the stream level listeners and switch to CSRC level
- // listening
- CallPeerMediaHandler> mediaHandler = getMediaHandler();
+ byte[] oldImage = getImage();
+ this.image = image;
- mediaHandler.setStreamAudioLevelListener(null);
- mediaHandler.setCsrcAudioLevelListener(this);
- }
+ //Fire the Event
+ fireCallPeerChangeEvent(
+ CallPeerChangeEvent.CALL_PEER_IMAGE_CHANGE,
+ oldImage,
+ image);
}
/**
- * 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.
+ * 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 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. Lets stop being a CSRC
- // listener and move back to listening the audio level of the
- // stream itself.
- CallPeerMediaHandler> mediaHandler = getMediaHandler();
-
- mediaHandler.setStreamAudioLevelListener(this);
- mediaHandler.setCsrcAudioLevelListener(null);
- }
- }
-
- /**
- * Notified by its very majesty the media service about changes in the
- * audio level of the stream coming from this peer, this method generates
- * the corresponding events and delivers them to the listeners that have
- * registered here.
+ * @param allowed true if local video transmission is allowed and
+ * false otherwise.
*
- * @param newLevel the new audio level of the local user.
+ * @throws OperationFailedException if video initialization fails.
*/
- public void audioLevelChanged(int newLevel)
+ public void setLocalVideoAllowed(boolean allowed)
+ throws OperationFailedException
{
- /*
- * 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;
-
- if (isConferenceFocus()
- && ((conferenceMemberCount = getConferenceMemberCount()) > 0)
- && (conferenceMemberCount < 3))
- {
- long audioRemoteSSRC
- = getMediaHandler().getRemoteSSRC(MediaType.AUDIO);
-
- if (audioRemoteSSRC != CallPeerMediaHandler.SSRC_UNKNOWN)
- {
- audioLevelsReceived(new long[] { audioRemoteSSRC, newLevel });
- return;
- }
- }
-
- List streamAudioLevelListeners;
-
- synchronized (streamAudioLevelListenersSyncRoot)
- {
- /*
- * 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.
- */
- streamAudioLevelListeners = this.streamAudioLevelListeners;
- }
+ CallPeerMediaHandler> mediaHandler = getMediaHandler();
- if (streamAudioLevelListeners != null)
+ if(mediaHandler.isLocalVideoTransmissionEnabled() != allowed)
{
- /*
- * 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 streamAudioLevelListenerCount
- = streamAudioLevelListeners.size();
-
- for(int i = 0; i < streamAudioLevelListenerCount; i++)
- {
- streamAudioLevelListeners.get(i).soundLevelChanged(
- this,
- newLevel);
- }
+ // Modify the local media setup to reflect the requested setting for
+ // the streaming of the local video.
+ mediaHandler.setLocalVideoTransmissionEnabled(allowed);
}
}
/**
- * Returns a reference to the CallPeerMediaHandler used by this
+ * Sets a reference to the CallPeerMediaHandler used by this
* peer. The media handler class handles all media management for a single
* CallPeer. This includes initializing and configuring streams,
* generating SDP, handling ICE, etc. One instance of CallPeer
@@ -891,28 +874,69 @@ public void audioLevelChanged(int newLevel)
* CallPeerMediaHandler and both classes are only separated for
* reasons of readability.
*
- * @return a reference to the CallPeerMediaHandler instance that
- * this peer uses for media related tips and tricks.
+ * @param mediaHandler a reference to the CallPeerMediaHandler
+ * instance that this peer uses for media related tips and tricks.
*/
- public U getMediaHandler()
+ protected void setMediaHandler(U mediaHandler)
{
- return mediaHandler;
+ this.mediaHandler = mediaHandler;
}
/**
- * Sets a reference to the CallPeerMediaHandler used by this
- * peer. The media handler class handles all media management for a single
- * CallPeer. This includes initializing and configuring streams,
- * generating SDP, handling ICE, etc. One instance of CallPeer
- * always corresponds to exactly one instance of
- * CallPeerMediaHandler and both classes are only separated for
- * reasons of readability.
+ * Sets the mute property for this call peer.
*
- * @param mediaHandler a reference to the CallPeerMediaHandler
- * instance that this peer uses for media related tips and tricks.
+ * @param newMuteValue the new value of the mute property for this call peer
*/
- protected void setMediaHandler(U mediaHandler)
+ @Override
+ public void setMute(boolean newMuteValue)
{
- this.mediaHandler = mediaHandler;
+ 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 CallPeerState 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();
+ }
+ }
}
}
diff --git a/src/net/java/sip/communicator/service/protocol/media/MediaHandler.java b/src/net/java/sip/communicator/service/protocol/media/MediaHandler.java
index c5e26327b..e1a75b899 100644
--- a/src/net/java/sip/communicator/service/protocol/media/MediaHandler.java
+++ b/src/net/java/sip/communicator/service/protocol/media/MediaHandler.java
@@ -89,8 +89,10 @@ public void securityMessageReceived(
String message, String i18nMessage, int severity)
{
for (SrtpListener listener : getSrtpListeners())
+ {
listener.securityMessageReceived(
message, i18nMessage, severity);
+ }
}
public void securityTimeout(int sessionType)