diff --git a/lib/installer-exclude/libjitsi.jar b/lib/installer-exclude/libjitsi.jar index ec632cdba..0ea822b6c 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/CallPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/CallPanel.java index 9ee762b83..901b04893 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/CallPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/CallPanel.java @@ -722,6 +722,7 @@ private void doUpdateViewFromModelInEventDispatchThread() boolean isConference = isConference(); boolean isVideo = CallManager.isVideoStreaming(callConference); CallPeer callPeer = null; + boolean validateAndRepaint = false; if (callPanel != null) { @@ -770,6 +771,7 @@ private void doUpdateViewFromModelInEventDispatchThread() if (removeCallPanel) { remove(callPanel); + validateAndRepaint = true; try { ((CallRenderer) callPanel).dispose(); @@ -814,15 +816,39 @@ private void doUpdateViewFromModelInEventDispatchThread() } } if (callPanel != null) + { add(callPanel, BorderLayout.CENTER); + validateAndRepaint = true; + } } - /* - * The center of this view is occupied by callPanel and we have just - * updated it. The bottom of this view is dedicated to settingsPanel so - * we have to update it as well. - */ - updateSettingsPanelInEventDispatchThread(false); + try + { + /* + * The center of this view is occupied by callPanel and we have just + * updated it. The bottom of this view is dedicated to settingsPanel + * so we have to update it as well. + */ + updateSettingsPanelInEventDispatchThread(false); + } + finally + { + /* + * It seems that AWT/Swing does not validate and/or repaint this + * Container (enough) and, consequently, its display may not update + * itself with an up-to-date drawing of the current callPanel. + */ + if (validateAndRepaint) + { + if (isDisplayable()) + { + validate(); + repaint(); + } + else + doLayout(); + } + } } /** diff --git a/src/net/java/sip/communicator/impl/gui/main/call/OneToOneCallPeerPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/OneToOneCallPeerPanel.java index 60f418b0e..d8a2c3023 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/OneToOneCallPeerPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/OneToOneCallPeerPanel.java @@ -1161,9 +1161,6 @@ private void updateViewFromModelInEventDispatchThread() } this.localVideo = localVideo; } - - center.validate(); - center.repaint(); } } } diff --git a/src/net/java/sip/communicator/impl/gui/main/call/UIVideoHandler2.java b/src/net/java/sip/communicator/impl/gui/main/call/UIVideoHandler2.java index 8cbb1ed7d..1fc5bbdde 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/UIVideoHandler2.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/UIVideoHandler2.java @@ -96,17 +96,17 @@ protected void callConferenceCallsPropertyChange(PropertyChangeEvent ev) } /** - * Notifies this instance about a change in the value of the - * videoSsrc property of a ConferenceMember. Changing the - * value in question means that a visual Component displaying video - * may be associated or dissociated with the ConferenceMember. + * Notifies this instance about a change in the value of a video-related + * property of a ConferenceMember. Changing such a value means that + * a visual Component displaying video may be associated or + * dissociated with the ConferenceMember. * * @param ev a PropertyChangeEvent which specifies the - * ConferenceMember whose videoSsrc property value changed - * and the old and new values of the property in question + * ConferenceMember whose video-related property value changed, the + * name of the property whose value changed, and the old and new values of + * the property in question */ - protected void conferenceMemberVideoSsrcPropertyChange( - PropertyChangeEvent ev) + protected void conferenceMemberVideoPropertyChange(PropertyChangeEvent ev) { notifyObservers(ev); } @@ -488,10 +488,12 @@ public void propertyChange(PropertyChangeEvent ev) } } else if (ConferenceMember.VIDEO_SSRC_PROPERTY_NAME.equals( - propertyName)) + propertyName) + || ConferenceMember.VIDEO_STATUS_PROPERTY_NAME.equals( + propertyName)) { if (ev.getSource() instanceof ConferenceMember) - conferenceMemberVideoSsrcPropertyChange(ev); + conferenceMemberVideoPropertyChange(ev); } else if (OperationSetVideoTelephony.LOCAL_VIDEO_STREAMING.equals( propertyName)) diff --git a/src/net/java/sip/communicator/impl/gui/main/call/conference/BasicConferenceCallPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/conference/BasicConferenceCallPanel.java index d2f97bed2..ce249893a 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/conference/BasicConferenceCallPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/conference/BasicConferenceCallPanel.java @@ -326,14 +326,16 @@ protected void updateViewFromModel() /** * Updates the ConferenceCallPeerRenderer which is to depict a - * specific CallPeer. + * specific CallPeer. Invoked by + * {@link #updateViewFromModelInEventDispatchThread()} in the AWT event + * dispatching thread. * * @param callPeer the CallPeer whose depicting * ConferenceCallPeerPanel is to be updated. The null * value is used to indicate the local peer. * @see #updateViewFromModel(ConferenceCallPeerRenderer, CallPeer) */ - private void updateViewFromModel(CallPeer callPeer) + protected void updateViewFromModel(CallPeer callPeer) { ConferenceCallPeerRenderer oldCallPeerPanel = callPeerPanels.get(callPeer); @@ -399,7 +401,7 @@ protected abstract ConferenceCallPeerRenderer updateViewFromModel( */ protected void updateViewFromModelInEventDispatchThread() { - /* Update the view of the local peer. */ + /* Update the view of the local peer/user. */ updateViewFromModel(null); List callPeers = callConference.getCallPeers(); diff --git a/src/net/java/sip/communicator/impl/gui/main/call/conference/VideoConferenceCallPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/conference/VideoConferenceCallPanel.java index fedcb500c..c03cddae0 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/conference/VideoConferenceCallPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/conference/VideoConferenceCallPanel.java @@ -66,6 +66,13 @@ public void update(Observable o, Object arg) */ private final VideoContainer videoContainer; + /** + * The set of visual Components displaying video streaming between + * the local peer/user and the remote peers which are depicted by this + * instance. + */ + private final Set videos = new HashSet(); + /** * Initializes a new VideoConferenceCallPanel instance which is to * be used by a specific CallPanel to depict a specific @@ -573,7 +580,28 @@ else if (cmcParticipant == conferenceMember) { if (cmc.getParticipant() == conferenceMember) { - if (cmc.toBeRemoved) + /* + * It is possible to have a ConferenceMember who is + * sending video but we just do not have the SSRC of + * that video to associate the video with the + * ConferenceMember. In such a case, we may be depicting + * the ConferenceMember twice: once with video without a + * ConferenceMember and once with a ConferenceMember + * without video. This will surely be the case at the + * time of this writing with non-focus participants in a + * telephony conference hosted on a Jitsi VideoBridge. + * Such a display is undesirable. If the + * conferenceMember is known to send video, we will not + * display it until we associated it with a video. This + * way, if a ConferenceMember is not sending video, we + * will depict it and we can be sure that no video + * without a ConferenceMember association will be + * depicting it a second time. + */ + if (cmc.toBeRemoved + && !conferenceMember + .getVideoStatus() + .allowsSending()) { cmc.setVideo(null); cmc.toBeRemoved = false; @@ -613,7 +641,6 @@ else if (cmcParticipant == conferenceMember) } } - @Override protected ConferenceCallPeerRenderer updateViewFromModel( ConferenceCallPeerRenderer callPeerPanel, CallPeer callPeer) @@ -748,6 +775,96 @@ protected ConferenceCallPeerRenderer updateViewFromModel( return callPeerPanel; } + @Override + protected void updateViewFromModelInEventDispatchThread() + { + /* + * Determine the set of visual Components displaying video streaming + * between the local peer/user and the remote peers which are to be + * depicted by this instance. + */ + Component localVideo = null; + Set videos = new HashSet(); + + for (Call call : callConference.getCalls()) + { + OperationSetVideoTelephony videoTelephony + = call.getProtocolProvider().getOperationSet( + OperationSetVideoTelephony.class); + + if (videoTelephony == null) + continue; + + Iterator callPeerIter = call.getCallPeers(); + + while (callPeerIter.hasNext()) + { + CallPeer callPeer = callPeerIter.next(); + + if (uiVideoHandler.isLocalVideoVisible() + && (localVideo == null)) + { + try + { + localVideo + = videoTelephony.getLocalVisualComponent(callPeer); + } + catch (OperationFailedException ofe) + { + /* + * We'll just try to get the local video through another + * CallPeer then. + */ + } + if (localVideo != null) + videos.add(localVideo); + } + + List callPeerRemoteVideos + = videoTelephony.getVisualComponents(callPeer); + + videos.addAll(callPeerRemoteVideos); + } + } + + /* + * Remove the Components of this view which are no longer present in the + * model. + */ + Iterator thisVideoIter = this.videos.iterator(); + + while (thisVideoIter.hasNext()) + { + Component thisVideo = thisVideoIter.next(); + + if (!videos.contains(thisVideo)) + { + thisVideoIter.remove(); + videoContainer.remove(thisVideo); + } + + /* + * If a video is known to be depicted by this view and is still + * present in the model, then we could remove it from the set of + * videos present in the model in order to prevent going through the + * procedure of adding it to this view. However, we choose to play + * on the safe side. + */ + } + + /* + * Add the Components of the model which are not depicted by this view. + */ + for (Component video : videos) + { + if (!UIVideoHandler2.isAncestor(videoContainer, video)) + { + this.videos.add(video); + videoContainer.add(video, VideoLayout.CENTER_REMOTE); + } + } + } + @Override protected void viewForModelAdded( ConferenceCallPeerRenderer callPeerPanel, diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTelephonyConferencingJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTelephonyConferencingJabberImpl.java index df8d308de..511cc4399 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTelephonyConferencingJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTelephonyConferencingJabberImpl.java @@ -164,7 +164,7 @@ private List getMedia( = new MediaPacketExtension(Long.toString(i)); long srcId = remote - ? stream.getRemoteSourceID() + ? getRemoteSourceID(callPeer, mediaType) : stream.getLocalSourceID(); if (srcId != -1) @@ -172,7 +172,10 @@ private List getMedia( ext.setType(mediaType.toString()); - MediaDirection direction = stream.getDirection(); + MediaDirection direction + = remote + ? getRemoteDirection(callPeer, mediaType) + : stream.getDirection(); if (direction == null) direction = MediaDirection.INACTIVE; @@ -290,7 +293,7 @@ private IQ getConferenceInfo(CallPeerJabberImpl callPeer, int version) iq.setEntity(getBasicTelephony().getProtocolProvider().getOurJID()); iq.setVersion(version); iq.setState(StateType.full); - iq.setSID(callPeer.getSID()); + iq.setSID(callPeerSID); // conference-description iq.addExtension(new DescriptionPacketExtension()); diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetTelephonyConferencingSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetTelephonyConferencingSipImpl.java index bf562a160..39ec45cf4 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetTelephonyConferencingSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetTelephonyConferencingSipImpl.java @@ -453,7 +453,7 @@ private void getMediaXML( long srcId = remote - ? stream.getRemoteSourceID() + ? getRemoteSourceID(callPeer, mediaType) : stream.getLocalSourceID(); if (srcId != -1) @@ -465,7 +465,10 @@ private void getMediaXML( append(xml, ""); } - MediaDirection direction = stream.getDirection(); + MediaDirection direction + = remote + ? getRemoteDirection(callPeer, mediaType) + : stream.getDirection(); if (direction == null) direction = MediaDirection.INACTIVE; diff --git a/src/net/java/sip/communicator/service/protocol/AbstractConferenceMember.java b/src/net/java/sip/communicator/service/protocol/AbstractConferenceMember.java index a3e423ea4..072e2eb3c 100644 --- a/src/net/java/sip/communicator/service/protocol/AbstractConferenceMember.java +++ b/src/net/java/sip/communicator/service/protocol/AbstractConferenceMember.java @@ -6,6 +6,9 @@ */ package net.java.sip.communicator.service.protocol; +import java.util.*; + +import org.jitsi.service.neomedia.*; import org.jitsi.util.event.*; /** @@ -75,6 +78,12 @@ public class AbstractConferenceMember */ private long audioSsrc = -1; + /** + * The status in both directions of the audio RTP stream from the point of + * view of this ConferenceMember. + */ + private MediaDirection audioStatus = MediaDirection.INACTIVE; + /** * The CallPeer which is the conference focus of this * ConferenceMember. @@ -98,6 +107,12 @@ public class AbstractConferenceMember */ private long videoSsrc = -1; + /** + * The status in both directions of the video RTP stream from the point of + * view of this ConferenceMember. + */ + private MediaDirection videoStatus = MediaDirection.INACTIVE; + /** * Creates an instance of AbstractConferenceMember by specifying * the corresponding conferenceFocusCallPeer, to which this member @@ -142,6 +157,14 @@ public long getAudioSsrc() return audioSsrc; } + /** + * {@inheritDoc} + */ + public MediaDirection getAudioStatus() + { + return audioStatus; + } + /** * {@inheritDoc} * @@ -191,6 +214,56 @@ public long getVideoSsrc() return videoSsrc; } + /** + * {@inheritDoc} + */ + public MediaDirection getVideoStatus() + { + return videoStatus; + } + + private static long parseMediaSssrc(Object value) + { + long ssrc; + + if (value == null) + ssrc = -1; + else if (value instanceof Long) + ssrc = ((Long) value).longValue(); + else + { + String str = value.toString(); + + if ((str == null) || (str.length() == 0)) + ssrc = -1; + else + ssrc = Long.parseLong(str); + } + + return ssrc; + } + + private static MediaDirection parseMediaStatus(Object value) + { + MediaDirection status; + + if (value == null) + status = MediaDirection.INACTIVE; + else if (value instanceof MediaDirection) + status = (MediaDirection) value; + else + { + String str = value.toString(); + + if ((str == null) || (str.length() == 0)) + status = MediaDirection.INACTIVE; + else + status = MediaDirection.parseString(str); + } + + return status; + } + /** * Sets the audio SSRC identifier of this member. * @@ -198,7 +271,41 @@ public long getVideoSsrc() */ public void setAudioSsrc(long ssrc) { - this.audioSsrc = ssrc; + if (this.audioSsrc != ssrc) + { + long oldValue = this.audioSsrc; + + this.audioSsrc = ssrc; + + firePropertyChange( + AUDIO_SSRC_PROPERTY_NAME, + oldValue, this.audioSsrc); + } + } + + /** + * Sets the status in both directions of the audio RTP stream from the point + * of view of this ConferenceMember. + * + * @param status the status in both directions of the audio RTP stream from + * the point of view of this ConferenceMember. If null, + * the method executes as if {@link MediaDirection#INACTIVE}. was specified. + */ + public void setAudioStatus(MediaDirection status) + { + if (status == null) + status = MediaDirection.INACTIVE; + + if (this.audioStatus != status) + { + MediaDirection oldValue = this.audioStatus; + + this.audioStatus = status; + + firePropertyChange( + AUDIO_STATUS_PROPERTY_NAME, + oldValue, this.audioStatus); + } } /** @@ -258,6 +365,60 @@ else if (PENDING.equalsIgnoreCase(endpointStatus)) setState(state); } + public boolean setProperties(Map properties) + { + boolean changed = false; + + for (Map.Entry entry : properties.entrySet()) + { + String key = entry.getKey(); + Object value = entry.getValue(); + + if (AUDIO_SSRC_PROPERTY_NAME.equals(key)) + { + long ssrc = parseMediaSssrc(value); + + if (getAudioSsrc() != ssrc) + { + setAudioSsrc(ssrc); + changed = true; + } + } + else if (AUDIO_STATUS_PROPERTY_NAME.equals(key)) + { + MediaDirection status = parseMediaStatus(value); + + if (!getAudioStatus().equals(status)) + { + setAudioStatus(status); + changed = true; + } + } + else if (VIDEO_SSRC_PROPERTY_NAME.equals(key)) + { + long ssrc = parseMediaSssrc(value); + + if (getVideoSsrc() != ssrc) + { + setVideoSsrc(ssrc); + changed = true; + } + } + else if (VIDEO_STATUS_PROPERTY_NAME.equals(key)) + { + MediaDirection status = parseMediaStatus(value); + + if (!getVideoStatus().equals(status)) + { + setVideoStatus(status); + changed = true; + } + } + } + + return changed; + } + /** * Sets the state of the device and signaling session of this * ConferenceMember in the conference and fires a new @@ -298,4 +459,29 @@ public void setVideoSsrc(long ssrc) oldValue, this.videoSsrc); } } + + /** + * Sets the status in both directions of the video RTP stream from the point + * of view of this ConferenceMember. + * + * @param status the status in both directions of the video RTP stream from + * the point of view of this ConferenceMember. If null, + * the method executes as if {@link MediaDirection#INACTIVE}. was specified. + */ + public void setVideoStatus(MediaDirection status) + { + if (status == null) + status = MediaDirection.INACTIVE; + + if (this.videoStatus != status) + { + MediaDirection oldValue = this.videoStatus; + + this.videoStatus = status; + + firePropertyChange( + VIDEO_STATUS_PROPERTY_NAME, + oldValue, this.videoStatus); + } + } } diff --git a/src/net/java/sip/communicator/service/protocol/ConferenceMember.java b/src/net/java/sip/communicator/service/protocol/ConferenceMember.java index a5f22aafd..ad7cf67e4 100644 --- a/src/net/java/sip/communicator/service/protocol/ConferenceMember.java +++ b/src/net/java/sip/communicator/service/protocol/ConferenceMember.java @@ -8,6 +8,8 @@ import java.beans.*; +import org.jitsi.service.neomedia.*; + /** * Represents a member and its details in a telephony conference managed by a * CallPeer in its role as a conference focus. @@ -16,6 +18,20 @@ */ public interface ConferenceMember { + /** + * The name of the property of ConferenceMember which specifies the + * SSRC of the audio content/RTP stream sent by the respective + * ConferenceMember in the conference. + */ + public static final String AUDIO_SSRC_PROPERTY_NAME = "audioSsrc"; + + /** + * The name of the property of ConferenceMember which specifies the + * status of the audio RTP stream from the point of view of the + * ConferenceMember. + */ + public static final String AUDIO_STATUS_PROPERTY_NAME = "audioStatus"; + /** * The name of the property of ConferenceMember which specifies the * user-friendly display name of the respective ConferenceMember in @@ -37,6 +53,13 @@ public interface ConferenceMember */ public static final String VIDEO_SSRC_PROPERTY_NAME = "videoSsrc"; + /** + * The name of the property of ConferenceMember which specifies the + * status of the video RTP stream from the point of view of the + * ConferenceMember. + */ + public static final String VIDEO_STATUS_PROPERTY_NAME = "videoStatus"; + /** * Adds a specific PropertyChangeListener to the list of * listeners interested in and notified about changes in the values of the @@ -75,6 +98,16 @@ public interface ConferenceMember */ public long getAudioSsrc(); + /** + * Gets the status in both directions of the audio RTP stream from the point + * of view of this ConferenceMember. + * + * @return a MediaDIrection which represents the status in both + * directions of the audio RTP stream from the point of view of this + * ConferenceMember + */ + public MediaDirection getAudioStatus(); + /** * Gets the CallPeer which is the conference focus of this * ConferenceMember. @@ -115,6 +148,16 @@ public interface ConferenceMember */ public long getVideoSsrc(); + /** + * Gets the status in both directions of the video RTP stream from the point + * of view of this ConferenceMember. + * + * @return a MediaDIrection which represents the status in both + * directions of the video RTP stream from the point of view of this + * ConferenceMember + */ + public MediaDirection getVideoStatus(); + /** * Removes a specific PropertyChangeListener from the list of * listeners interested in and notified about changes in the values of the diff --git a/src/net/java/sip/communicator/service/protocol/media/AbstractOperationSetTelephonyConferencing.java b/src/net/java/sip/communicator/service/protocol/media/AbstractOperationSetTelephonyConferencing.java index b09a8249b..3b3114497 100644 --- a/src/net/java/sip/communicator/service/protocol/media/AbstractOperationSetTelephonyConferencing.java +++ b/src/net/java/sip/communicator/service/protocol/media/AbstractOperationSetTelephonyConferencing.java @@ -397,23 +397,12 @@ public OperationSetBasicTelephonyT getBasicTelephony() return basicTelephony; } - /** - * Reads the text content of the src-id XML element of a - * media XML element of a specific endpoint XML element. - * - * @param endpoint an XML Node which represents the - * endpoint XML element from which to get the text content of a - * src-id XML element of a media XML element - * @param mediaType the type of the media to get the src-id of - * @return the text content of the src-id XML element of the - * media XML element of the specified endpoint XML element - * if any; otherwise, null - */ - private String getEndpointMediaSrcId(Node endpoint, MediaType mediaType) + private void getEndpointMediaProperties( + Node endpoint, + Map properties) { NodeList endpointChildList = endpoint.getChildNodes(); int endpoingChildCount = endpointChildList.getLength(); - String mediaTypeStr = mediaType.toString(); for (int endpointChildIndex = 0; endpointChildIndex < endpoingChildCount; @@ -426,6 +415,7 @@ private String getEndpointMediaSrcId(Node endpoint, MediaType mediaType) NodeList mediaChildList = endpointChild.getChildNodes(); int mediaChildCount = mediaChildList.getLength(); String srcId = null; + String status = null; String type = null; for (int mediaChildIndex = 0; @@ -436,24 +426,33 @@ private String getEndpointMediaSrcId(Node endpoint, MediaType mediaType) String mediaChildName = mediaChild.getNodeName(); if (ELEMENT_SRC_ID.equals(mediaChildName)) - { srcId = mediaChild.getTextContent(); - if (mediaTypeStr.equalsIgnoreCase(type)) - return srcId; - } + else if (ELEMENT_STATUS.equals(mediaChildName)) + status = mediaChild.getTextContent(); else if (ELEMENT_TYPE.equals(mediaChildName)) - { type = mediaChild.getTextContent(); - if ((srcId != null) - && mediaTypeStr.equalsIgnoreCase(type)) - { - return srcId; - } - } + } + + if (MediaType.AUDIO.toString().equalsIgnoreCase(type)) + { + properties.put( + ConferenceMember.AUDIO_SSRC_PROPERTY_NAME, + srcId); + properties.put( + ConferenceMember.AUDIO_STATUS_PROPERTY_NAME, + status); + } + else if (MediaType.VIDEO.toString().equalsIgnoreCase(type)) + { + properties.put( + ConferenceMember.VIDEO_SSRC_PROPERTY_NAME, + srcId); + properties.put( + ConferenceMember.VIDEO_STATUS_PROPERTY_NAME, + status); } } } - return null; } /** @@ -483,6 +482,110 @@ private String getEndpointStatus(Node endpoint) return null; } + /** + * Gets the MediaDirection of the media RTP stream of a specific + * CallPeer with a specific MediaType from the point of + * view of the remote peer. + * + * @param callPeer + * @param mediaType + * @return the MediaDirection of the media RTP stream of a specific + * CallPeer with a specific MediaType from the point of + * view of the remote peer + */ + protected MediaDirection getRemoteDirection( + MediaAwareCallPeer callPeer, + MediaType mediaType) + { + MediaStream stream = callPeer.getMediaHandler().getStream(mediaType); + MediaDirection remoteDirection; + + if (stream != null) + { + remoteDirection = stream.getDirection(); + if (remoteDirection != null) + remoteDirection = remoteDirection.getReverseDirection(); + } + else + remoteDirection = null; + return null; + } + + /** + * Gets the remote SSRC to be reported in the conference-info XML for a + * specific CallPeer's media of a specific MediaType. + * + * @param callPeer the CallPeer whose remote SSRC for the media of + * the specified mediaType is to be returned + * @param mediaType the MediaType of the specified + * callPeer's media whose remote SSRC is to be returned + * @return the remote SSRC to be reported in the conference-info XML for the + * specified callPeer's media of the specified mediaType + */ + protected long getRemoteSourceID( + MediaAwareCallPeer callPeer, + MediaType mediaType) + { + MediaStream stream = callPeer.getMediaHandler().getStream(mediaType); + long remoteSourceID; + + if (stream != null) + { + remoteSourceID = stream.getRemoteSourceID(); + if (remoteSourceID != -1) + { + /* + * TODO Technically, we are detecting conflicts within a Call + * while we should be detecting them within the whole + * CallConference. + */ + MediaAwareCall call = callPeer.getCall(); + + if (call != null) + { + for (MediaAwareCallPeer aCallPeer + : call.getCallPeerList()) + { + if (aCallPeer != callPeer) + { + MediaStream aStream + = aCallPeer.getMediaHandler().getStream( + mediaType); + + /* + * When the Jitsi VideoBridge server-side technology + * is utilized by the local peer/user to host a + * telephony conference, one and the same + * MediaStream instance will be shared by the + * CallPeers. This will definitely lead to a + * conflict. + */ + if (aStream == stream) + { + remoteSourceID = -1; + break; + } + else + { + long aRemoteSourceID + = stream.getRemoteSourceID(); + + if (aRemoteSourceID == remoteSourceID) + { + remoteSourceID = -1; + break; + } + } + } + } + } + } + } + else + remoteSourceID = -1; + return remoteSourceID; + } + /** * Notifies this CallListener that a specific incoming * Call has been received. @@ -703,6 +806,8 @@ private void setConferenceInfoDocument( { NodeList userList = usersList.item(0).getChildNodes(); int userCount = userList.getLength(); + Map conferenceMemberProperties + = new HashMap(); for (int userIndex = 0; userIndex < userCount; userIndex++) { @@ -759,9 +864,19 @@ private void setConferenceInfoDocument( int userChildCount = userChildList.getLength(); String displayName = null; String endpointStatus = null; - String audioSsrc = null; - String videoSsrc = null; + conferenceMemberProperties.put( + ConferenceMember.AUDIO_SSRC_PROPERTY_NAME, + null); + conferenceMemberProperties.put( + ConferenceMember.AUDIO_STATUS_PROPERTY_NAME, + null); + conferenceMemberProperties.put( + ConferenceMember.VIDEO_SSRC_PROPERTY_NAME, + null); + conferenceMemberProperties.put( + ConferenceMember.VIDEO_STATUS_PROPERTY_NAME, + null); for (int userChildIndex = 0; userChildIndex < userChildCount; userChildIndex++) @@ -774,39 +889,17 @@ private void setConferenceInfoDocument( else if (ELEMENT_ENDPOINT.equals(userChildName)) { endpointStatus = getEndpointStatus(userChild); - audioSsrc - = getEndpointMediaSrcId( - userChild, - MediaType.AUDIO); - videoSsrc - = getEndpointMediaSrcId( - userChild, - MediaType.VIDEO); + getEndpointMediaProperties( + userChild, + conferenceMemberProperties); } } existingConferenceMember.setDisplayName(displayName); existingConferenceMember.setEndpointStatus(endpointStatus); - if (audioSsrc != null) - { - long newSsrc = Long.parseLong(audioSsrc); - - if (existingConferenceMember.getAudioSsrc() != newSsrc) - { - changed = true; - existingConferenceMember.setAudioSsrc(newSsrc); - } - } - if (videoSsrc != null) - { - long newSsrc = Long.parseLong(videoSsrc); - - if (existingConferenceMember.getVideoSsrc() != newSsrc) - { - changed = true; - existingConferenceMember.setVideoSsrc(newSsrc); - } - } + changed + = existingConferenceMember.setProperties( + conferenceMemberProperties); if (addConferenceMember) callPeer.addConferenceMember(existingConferenceMember);