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)