From 055882473aa085ae0c1480cc8ced0a2d82fff1b2 Mon Sep 17 00:00:00 2001 From: Lyubomir Marinov Date: Wed, 17 Oct 2012 09:13:13 +0000 Subject: [PATCH] Commits work in progress on refactorying the call-related user interface for the purposes of audio and video telephony conferencing over single or multiple protocols and Jitsi VideoBridge. --- lib/installer-exclude/libjitsi.jar | Bin 995054 -> 996167 bytes .../impl/gui/main/call/CallPanel.java | 38 +++- .../gui/main/call/OneToOneCallPeerPanel.java | 3 - .../impl/gui/main/call/UIVideoHandler2.java | 22 +- .../conference/BasicConferenceCallPanel.java | 8 +- .../conference/VideoConferenceCallPanel.java | 121 ++++++++++- ...ionSetTelephonyConferencingJabberImpl.java | 9 +- ...rationSetTelephonyConferencingSipImpl.java | 7 +- .../protocol/AbstractConferenceMember.java | 188 +++++++++++++++- .../service/protocol/ConferenceMember.java | 43 ++++ ...ractOperationSetTelephonyConferencing.java | 205 +++++++++++++----- 11 files changed, 558 insertions(+), 86 deletions(-) diff --git a/lib/installer-exclude/libjitsi.jar b/lib/installer-exclude/libjitsi.jar index ec632cdba2537dc3ea87ad517b2c2eb41b0093f9..0ea822b6cdbed7f15df290835942d23e2a7ab4d7 100644 GIT binary patch delta 27680 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 delta 26982 zcmZWR2V9Nc``y`H?|a*OqBN9JijlJt8SxD`b>iHYrzTOH7Gpo7pUFt{>2EwZS-Gclq}v>(mMgp_^u-dqHH9kLzC7N-s$h8)Rw z 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 KCB35HU;hW&J&`T| 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);