From 6de2a751aa71257e96c95282b1771f6ffafb4a2c Mon Sep 17 00:00:00 2001 From: Lyubomir Marinov Date: Fri, 13 Nov 2009 14:06:51 +0000 Subject: [PATCH] Hopefully, fixes the display of the remote video received in SIP calls in the call dialog. --- .../gui/main/call/OneToOneCallPeerPanel.java | 8 +- .../impl/neomedia/VideoMediaStreamImpl.java | 6 +- .../protocol/sip/CallPeerMediaHandler.java | 232 +++++++++++++++++- .../impl/protocol/sip/CallPeerSipImpl.java | 97 +++++++- .../impl/protocol/sip/CallSipImpl.java | 1 + .../OperationSetVideoTelephonySipImpl.java | 106 ++++---- .../protocol/sip/sip.provider.manifest.mf | 1 + .../protocol/OperationSetVideoTelephony.java | 13 +- 8 files changed, 374 insertions(+), 90 deletions(-) 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 ac1c09619..2e9c956a6 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 @@ -527,13 +527,7 @@ public void run() } // REMOTE - Component[] videos = - videoTelephony - .getVisualComponents(callPeer); - - Component video = - ((videos == null) || (videos.length < 1)) ? null - : videos[0]; + Component video = videoTelephony.getVisualComponent(callPeer); if (video != null) videoContainer diff --git a/src/net/java/sip/communicator/impl/neomedia/VideoMediaStreamImpl.java b/src/net/java/sip/communicator/impl/neomedia/VideoMediaStreamImpl.java index ee0fdfea6..71b68c0cc 100644 --- a/src/net/java/sip/communicator/impl/neomedia/VideoMediaStreamImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/VideoMediaStreamImpl.java @@ -263,11 +263,7 @@ public void videoAdded(VideoEvent e) */ public void videoRemoved(VideoEvent e) { - if (fireVideoEvent( - e.getType(), - e.getVisualComponent(), - e.getOrigin())) - e.consume(); + videoAdded(e); } }; diff --git a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandler.java b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandler.java index cd941ec4d..8bb68e3cf 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandler.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandler.java @@ -6,6 +6,7 @@ */ package net.java.sip.communicator.impl.protocol.sip; +import java.awt.Component; import java.net.*; import java.util.*; @@ -17,6 +18,7 @@ import net.java.sip.communicator.service.neomedia.format.*; import net.java.sip.communicator.service.netaddr.*; import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; /** @@ -33,7 +35,8 @@ public class CallPeerMediaHandler /** * Our class logger. */ - private Logger logger = Logger.getLogger(CallPeerMediaHandler.class); + private static final Logger logger + = Logger.getLogger(CallPeerMediaHandler.class); /** * A reference to the CallPeerSipImpl instance that this handler is @@ -108,6 +111,57 @@ public class CallPeerMediaHandler */ private VideoMediaStream videoStream = null; + /** + * The List of VideoListeners interested in + * VideoEvents fired by this instance or rather its + * VideoMediaStream. + */ + private final List videoListeners + = new LinkedList(); + + /** + * The neomedia VideoListener which listens to {@link #videoStream} + * for changes in the availability of visual Components displaying + * remote video and re-fires them as + * net.java.sip.communicator.service.protocol.event.VideoEvents + * originating from this instance. + */ + private final net.java.sip.communicator.service.neomedia.event.VideoListener videoStreamVideoListener + = new net.java.sip.communicator.service.neomedia.event.VideoListener() + { + /** + * Notifies this neomedia VideoListener that a new visual + * Component displaying remote video has been added in + * {@link CallPeerMediaHandler#videoStream}. + * + * @param event the neomedia VideoEvent which specifies the + * newly-added visual Component displaying remote video + */ + public void videoAdded( + net.java.sip.communicator.service.neomedia.event.VideoEvent event) + { + if (fireVideoEvent( + event.getType(), + event.getVisualComponent(), + event.getOrigin())) + event.consume(); + } + + /** + * Notifies this neomedia VideoListener that a visual + * Component displaying remote video has been removed from + * {@link CallPeerMediaHandler#videoStream}. + * + * @param event the neomedia VideoEvent which specifies the + * removed visual Component displaying remote video + */ + public void videoRemoved( + net.java.sip.communicator.service.neomedia.event.VideoEvent event) + { + videoAdded(event); + } + }; + /** * A URL pointing to a location with call information or a call * control web interface related to the CallPeer that we are @@ -307,11 +361,7 @@ private void closeStream(MediaType type) } else { - if (this.videoStream != null) - { - videoStream.close(); - videoStream = null; - } + setVideoStream(null); if (this.videoStreamConnector != null) { @@ -613,7 +663,7 @@ private MediaStream configureAndStartStream( if( stream instanceof AudioMediaStream) this.audioStream = (AudioMediaStream)stream; else - this.videoStream = (VideoMediaStream)stream; + setVideoStream((VideoMediaStream)stream); if ( ! stream.isStarted()) stream.start(); @@ -1178,4 +1228,172 @@ private void setCallInfoURL(URL callInfolURL) { this.callInfoURL = callInfolURL; } + + /** + * Sets the RTP media stream that this instance uses to stream video to a + * specific VideoMediaStream. + * + * @param videoStream the VideoMediaStream to be set as the RTP + * media stream that this instance uses to stream video + */ + private void setVideoStream(VideoMediaStream videoStream) + { + if (this.videoStream != videoStream) + { + /* + * Make sure we will no longer notify the registered VideoListeners + * about changes in the availability of video in the old + * videoStream. + */ + Component oldVisualComponent = null; + + if (this.videoStream != null) + { + this.videoStream.removeVideoListener(videoStreamVideoListener); + oldVisualComponent = this.videoStream.getVisualComponent(); + + this.videoStream.close(); + } + + this.videoStream = videoStream; + + /* + * Make sure we will notify the registered VideoListeners about + * changes in the availability of video in the new videoStream. + */ + Component newVisualComponent = null; + + if (this.videoStream != null) + { + this.videoStream.addVideoListener(videoStreamVideoListener); + newVisualComponent = this.videoStream.getVisualComponent(); + } + + /* + * Notify the VideoListeners in case there was a change in the + * availability of the visual Components displaying remote + * video. + */ + if (oldVisualComponent != null) + fireVideoEvent( + VideoEvent.VIDEO_REMOVED, + oldVisualComponent, + VideoEvent.REMOTE); + if (newVisualComponent != null) + fireVideoEvent( + VideoEvent.VIDEO_REMOVED, + newVisualComponent, + VideoEvent.REMOTE); + } + } + + /** + * Registers a specific VideoListener with this instance so that it + * starts receiving notifications from it about changes in the availability + * of visual Components displaying video. + * + * @param listener the VideoListener to be registered with this + * instance and to start receiving notifications from it about changes in + * the availability of visual Components displaying video + */ + public void addVideoListener(VideoListener listener) + { + if (listener == null) + throw new NullPointerException("listener"); + + synchronized (videoListeners) + { + if (!videoListeners.contains(listener)) + videoListeners.add(listener); + } + } + + /** + * Notifies the VideoListeners registered with this + * CallPeerMediaHandler about a specific type of change in the + * availability of a specific visual Component depicting video. + * + * @param type the type of change as defined by VideoEvent in the + * availability of the specified visual Component depicting video + * @param visualComponent the visual Component depicting video + * which has been added or removed in this CallPeerMediaHandler + * @param origin {@link VideoEvent#LOCAL} if the origin of the video is + * local (e.g. it is being locally captured); {@link VideoEvent#REMOTE} if + * the origin of the video is remote (e.g. a remote peer is streaming it) + * @return true if this event and, more specifically, the visual + * Component it describes have been consumed and should be + * considered owned, referenced (which is important because + * Components belong to a single Container at a time); + * otherwise, false + */ + protected boolean fireVideoEvent( + int type, + Component visualComponent, + int origin) + { + VideoListener[] listeners; + + synchronized (videoListeners) + { + listeners + = videoListeners + .toArray(new VideoListener[videoListeners.size()]); + } + + boolean consumed; + + if (listeners.length > 0) + { + VideoEvent event + = new VideoEvent(this, type, visualComponent, origin); + + for (VideoListener listener : listeners) + switch (type) + { + case VideoEvent.VIDEO_ADDED: + listener.videoAdded(event); + break; + case VideoEvent.VIDEO_REMOVED: + listener.videoRemoved(event); + break; + } + + consumed = event.isConsumed(); + } + else + consumed = false; + return consumed; + } + + /** + * Gets the visual Component in which video from the remote peer is + * currently being rendered or null if there is currently no video + * streaming from the remote peer. + * + * @return the visual Component in which video from the remote peer + * is currently being rendered or null if there is currently no + * video streaming from the remote peer + */ + public Component getVisualComponent() + { + return (videoStream == null) ? null : videoStream.getVisualComponent(); + } + + /** + * Unregisters a specific VideoListener from this instance so that + * it stops receiving notifications from it about changes in the + * availability of visual Components displaying video. + * + * @param listener the VideoListener to be unregistered from this + * instance and to stop receiving notifications from it about changes in the + * availability of visual Components displaying video + */ + public void removeVideoListener(VideoListener listener) + { + if (listener != null) + synchronized (videoListeners) + { + videoListeners.remove(listener); + } + } } diff --git a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerSipImpl.java index 7d9ce2e40..2228984fd 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerSipImpl.java @@ -8,6 +8,7 @@ import java.net.*; import java.text.*; +import java.util.*; import javax.sip.*; import javax.sip.address.*; @@ -94,6 +95,20 @@ public class CallPeerSipImpl */ private final CallPeerMediaHandler mediaHandler; + /** + * The PropertyChangeListener which listens to + * {@link #mediaHandler} for changes in the values of its properties. + */ + private PropertyChangeListener mediaHandlerPropertyChangeListener; + + /** + * The List of PropertyChangeListeners listening to this + * CallPeer for changes in the values of its properties related to + * video. + */ + private final List videoPropertyChangeListeners + = new LinkedList(); + /** * Creates a new call peer with address peerAddress. * @@ -391,10 +406,7 @@ public Contact getContact() @Override public URL getCallInfoURL() { - if (mediaHandler == null) - return null; - - return mediaHandler.getCallInfoURL(); + return getMediaHandler().getCallInfoURL(); } /** @@ -1334,10 +1346,51 @@ public boolean isLocalVideoStreaming() */ public void addVideoPropertyChangeListener(PropertyChangeListener listener) { - /** - * @todo update to neomedia. - getMediaCallSession().addPropertyChangeListener(listener); - */ + if (listener == null) + throw new NullPointerException("listener"); + + synchronized (videoPropertyChangeListeners) + { + /* + * The video is part of the media-related functionality and thus it + * is the responsibility of mediaHandler. So listen to mediaHandler + * for video-related property changes and re-fire them as + * originating from this instance. + */ + if (!videoPropertyChangeListeners.contains(listener) + && videoPropertyChangeListeners.add(listener) + && (mediaHandlerPropertyChangeListener == null)) + { + mediaHandlerPropertyChangeListener + = new PropertyChangeListener() + { + public void propertyChange(PropertyChangeEvent event) + { + Iterable listeners; + + synchronized (videoPropertyChangeListeners) + { + listeners + = new LinkedList( + videoPropertyChangeListeners); + } + + PropertyChangeEvent thisEvent + = new PropertyChangeEvent( + this, + event.getPropertyName(), + event.getOldValue(), + event.getNewValue()); + + for (PropertyChangeListener listener : listeners) + listener.propertyChange(thisEvent); + } + }; +// getMediaHandler() +// .addPropertyChangeListener( +// mediaHandlerPropertyChangeListener); + } + } } /** @@ -1353,10 +1406,28 @@ public void addVideoPropertyChangeListener(PropertyChangeListener listener) public void removeVideoPropertyChangeListener( PropertyChangeListener listener) { - /** - * @todo update to neomedia. - getMediaCallSession().removePropertyChangeListener(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; + } + } } /** @@ -1389,7 +1460,7 @@ public void handleAuthenticationChallenge(ClientTransaction retryTran) * @return a reference to the CallPeerMediaHandler instance that * this peer uses for media related tips and tricks. */ - private CallPeerMediaHandler getMediaHandler() + CallPeerMediaHandler getMediaHandler() { return mediaHandler; } diff --git a/src/net/java/sip/communicator/impl/protocol/sip/CallSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/CallSipImpl.java index 631bee7a3..fb6972314 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/CallSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/CallSipImpl.java @@ -270,6 +270,7 @@ public CallPeerSipImpl findCallPeer(Dialog dialog) * @return a reference to the ProtocolProviderServiceSipImpl * instance that created this call. */ + @Override public ProtocolProviderServiceSipImpl getProtocolProvider() { return (ProtocolProviderServiceSipImpl)super.getProtocolProvider(); diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetVideoTelephonySipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetVideoTelephonySipImpl.java index e9e6f56cb..d45f6dfed 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetVideoTelephonySipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetVideoTelephonySipImpl.java @@ -51,12 +51,12 @@ public OperationSetVideoTelephonySipImpl( } /** - * Delegates to the CallSession of the Call of the specified CallPeer - * because the video is provided by the CallSession in the SIP protocol - * implementation. Because other OperationSetVideoTelephony implementations - * may not provide their video through the CallSession, this implementation - * promotes itself as the provider of the video by replacing the CallSession - * in the VideoEvents it fires. + * Delegates to the CallPeerMediaHandler of the specified + * CallPeer because the video is provided by it. Because other + * OperationSetVideoTelephony implementations may not provide their + * video through the CallPeerMediaHandler, this implementation + * promotes itself as the provider of the video by replacing the + * CallPeerMediaHandler in the VideoEvents it fires. * * @param peer the CallPeer that we will be registering * listener with. @@ -67,11 +67,10 @@ public void addVideoListener(CallPeer peer, VideoListener listener) if (listener == null) throw new NullPointerException("listener"); - /** - * @todo update to neomedia. - ((CallPeerSipImpl) peer).getMediaCallSession() - .addVideoListener(new InternalVideoListener(this, peer, listener)); - */ + ((CallPeerSipImpl) peer) + .getMediaHandler() + .addVideoListener( + new InternalVideoListener(this, peer, listener)); } /** @@ -136,34 +135,21 @@ public void disposeLocalVisualComponent(CallPeer peer, Component component) } /** - * Delegates to the CallSession of the Call of the specified CallPeer - * because the video is provided by the CallSession in the SIP protocol - * implementation. + * Gets the visual/video Component available in this telephony for + * a specific CallPeer. * - * @param peer the peer whose visual Components we'd like to - * retrieve. - * - * @return all visual Components for the specified peer. + * @param peer the CallPeer whose video is to be retrieved + * @return the visual/video Component available in this telephony + * for the specified peer if any; otherwise, null */ - public Component[] getVisualComponents(CallPeer peer) + public Component getVisualComponent(CallPeer peer) { - /** - * @todo update to neomedia. - CallSession callSession =((CallPeerSipImpl)peer).getMediaCallSession(); - - return (callSession != null) ? callSession.getVisualComponents() - : new Component[0]; - */ - return null; + return ((CallPeerSipImpl) peer).getMediaHandler().getVisualComponent(); } /** - * Delegates to the CallSession of the Call of the specified CallPeer - * because the video is provided by the CallSession in the SIP protocol - * implementation. Because other OperationSetVideoTelephony implementations - * may not provide their video through the CallSession, this implementation - * promotes itself as the provider of the video by replacing the CallSession - * in the VideoEvents it fires. + * Delegates to the CallPeerMediaHandler of the specified + * CallPeer because the video is provided by it. * * @param peer the CallPeer that we'd like to unregister our * VideoListener from. @@ -171,15 +157,11 @@ public Component[] getVisualComponents(CallPeer peer) */ public void removeVideoListener(CallPeer peer, VideoListener listener) { - /** - * @todo update to neomedia. if (listener != null) - { - ((CallPeerSipImpl) peer).getMediaCallSession() - .removeVideoListener( - new InternalVideoListener(this, peer, listener)); - } - */ + ((CallPeerSipImpl) peer) + .getMediaHandler() + .removeVideoListener( + new InternalVideoListener(this, peer, listener)); } /** @@ -246,8 +228,9 @@ public boolean isLocalVideoStreaming(Call call) * when the properties associated with the specified Call change * their values */ - public void addPropertyChangeListener(Call call, - PropertyChangeListener listener) + public void addPropertyChangeListener( + Call call, + PropertyChangeListener listener) { ((CallSipImpl) call).addVideoPropertyChangeListener(listener); } @@ -264,8 +247,9 @@ public void addPropertyChangeListener(Call call, * notified when the properties associated with the specified Call * change their values */ - public void removePropertyChangeListener(Call call, - PropertyChangeListener listener) + public void removePropertyChangeListener( + Call call, + PropertyChangeListener listener) { ((CallSipImpl) call).removeVideoPropertyChangeListener(listener); } @@ -317,8 +301,10 @@ private static class InternalVideoListener * that the videos in the SIP protocol implementation is managed by the * CallSession and not by the specified telephony */ - public InternalVideoListener(OperationSetVideoTelephony telephony, - CallPeer peer, VideoListener delegate) + public InternalVideoListener( + OperationSetVideoTelephony telephony, + CallPeer peer, + VideoListener delegate) { if (peer == null) throw new NullPointerException("peer cannot be null"); @@ -339,6 +325,7 @@ public InternalVideoListener(OperationSetVideoTelephony telephony, * @return true if the underlying peer, telephony operation set and * delegate are equal to those of the other instance. */ + @Override public boolean equals(Object other) { if (other == this) @@ -360,6 +347,7 @@ public boolean equals(Object other) * @return a hashcode based on the hash codes of the wrapped telephony * operation set and VideoListener delegate. */ + @Override public int hashCode() { return (telephony.hashCode() << 16) + (delegate.hashCode() >> 16); @@ -376,8 +364,16 @@ public int hashCode() */ public void videoAdded(VideoEvent event) { - delegate.videoAdded(new VideoEvent(this, event.getType(), event - .getVisualComponent(), event.getOrigin())); + VideoEvent delegateEvent + = new VideoEvent( + this, + event.getType(), + event.getVisualComponent(), + event.getOrigin()); + + delegate.videoAdded(delegateEvent); + if (delegateEvent.isConsumed()) + event.consume(); } /** @@ -392,8 +388,16 @@ public void videoAdded(VideoEvent event) */ public void videoRemoved(VideoEvent event) { - delegate.videoAdded(new VideoEvent(this, event.getType(), event - .getVisualComponent(), event.getOrigin())); + VideoEvent delegateEvent + = new VideoEvent( + this, + event.getType(), + event.getVisualComponent(), + event.getOrigin()); + + delegate.videoRemoved(delegateEvent); + if (delegateEvent.isConsumed()) + event.consume(); } } } diff --git a/src/net/java/sip/communicator/impl/protocol/sip/sip.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/sip/sip.provider.manifest.mf index e947dd4e8..10f0733a0 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/sip.provider.manifest.mf +++ b/src/net/java/sip/communicator/impl/protocol/sip/sip.provider.manifest.mf @@ -13,6 +13,7 @@ Import-Package: org.apache.log4j, net.java.sip.communicator.service.gui, net.java.sip.communicator.service.neomedia, net.java.sip.communicator.service.neomedia.device, + net.java.sip.communicator.service.neomedia.event, net.java.sip.communicator.service.neomedia.format, net.java.sip.communicator.service.netaddr, net.java.sip.communicator.service.protocol, diff --git a/src/net/java/sip/communicator/service/protocol/OperationSetVideoTelephony.java b/src/net/java/sip/communicator/service/protocol/OperationSetVideoTelephony.java index 7bfe45072..528f6507f 100644 --- a/src/net/java/sip/communicator/service/protocol/OperationSetVideoTelephony.java +++ b/src/net/java/sip/communicator/service/protocol/OperationSetVideoTelephony.java @@ -79,15 +79,14 @@ public Component createLocalVisualComponent(CallPeer peer, public void disposeLocalVisualComponent(CallPeer peer, Component component); /** - * Gets the visual/video Components available in this telephony - * for a specific CallPeer. + * Gets the visual/video Component available in this telephony for + * a specific CallPeer. * - * @param peer the CallPeer whose videos are to be retrieved - * - * @return an array of the visual Components available in this - * telephony for the specified peer + * @param peer the CallPeer whose video is to be retrieved + * @return the visual/video Component available in this telephony + * for the specified peer if any; otherwise, null */ - public Component[] getVisualComponents(CallPeer peer); + public Component getVisualComponent(CallPeer peer); /** * Removes a specific VideoListener from this telephony in