diff options
author | Lyubomir Marinov <lyubomir.marinov@jitsi.org> | 2011-07-26 19:09:24 +0000 |
---|---|---|
committer | Lyubomir Marinov <lyubomir.marinov@jitsi.org> | 2011-07-26 19:09:24 +0000 |
commit | e46f74f442136d50bb98e1f62041d10087b272e1 (patch) | |
tree | b0329164afddab6b3d16bb92f60769f1d1bb655b /src/net/java | |
parent | 95be6b19ee71f612aa53c30f07241ba3e19d0db0 (diff) | |
download | jitsi-e46f74f442136d50bb98e1f62041d10087b272e1.zip jitsi-e46f74f442136d50bb98e1f62041d10087b272e1.tar.gz jitsi-e46f74f442136d50bb98e1f62041d10087b272e1.tar.bz2 |
Addresses a possible deadlock in video calls.
Diffstat (limited to 'src/net/java')
12 files changed, 381 insertions, 185 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 1489881..7435ef1 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 @@ -787,26 +787,27 @@ public class OneToOneCallPeerPanel logger.trace("UI video event received originated in: " + event.getOrigin() + " and is of type: " + event.getType()); - synchronized (videoContainers) + if ((event != null) && !event.isConsumed()) { - if ((event != null) && !event.isConsumed()) - { - Component video = event.getVisualComponent(); + int origin = event.getOrigin(); + Component video = event.getVisualComponent(); + synchronized (videoContainers) + { switch (event.getType()) { case VideoEvent.VIDEO_ADDED: - if(event.getOrigin() == VideoEvent.LOCAL) + if (origin == VideoEvent.LOCAL) { this.localVideo = video; this.closeButton = new CloseButton(); } - else if(event.getOrigin() == VideoEvent.REMOTE) + else if (origin == VideoEvent.REMOTE) { this.remoteVideo = video; } - addMouseListeners(event.getOrigin()); + addMouseListeners(origin); /* * Let the creator of the local visual Component know it @@ -816,16 +817,18 @@ public class OneToOneCallPeerPanel break; case VideoEvent.VIDEO_REMOVED: - if (event.getOrigin() == VideoEvent.LOCAL && - localVideo == video) + if (origin == VideoEvent.LOCAL) { - this.localVideo = null; - this.closeButton = null; + if (localVideo == video) + { + this.localVideo = null; + this.closeButton = null; + } } - else if(event.getOrigin() == VideoEvent.REMOTE && - remoteVideo == video) + else if (origin == VideoEvent.REMOTE) { - this.remoteVideo = null; + if (remoteVideo == video) + this.remoteVideo = null; } break; } diff --git a/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java b/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java index 313e755..3f85d06 100644 --- a/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java @@ -272,7 +272,7 @@ public class MediaServiceImpl break; case VIDEO: captureDeviceInfo - = getDeviceConfiguration().getVideoCaptureDevice(useCase); + = getDeviceConfiguration().getVideoCaptureDevice(useCase); break; default: captureDeviceInfo = null; @@ -314,10 +314,12 @@ public class MediaServiceImpl } } - //Don't use the device in case the user has disabled all codecs for that - //kind of media. + /* + * Don't use the device in case the user has disabled all codecs for + * that kind of media. + */ if ((defaultDevice != null) - && (defaultDevice.getSupportedFormats().isEmpty())) + && (defaultDevice.getSupportedFormats().isEmpty())) { defaultDevice = null; } diff --git a/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java b/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java index 3fafa9e..8f51a76 100644 --- a/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java @@ -1392,7 +1392,7 @@ public class MediaStreamImpl deviceSessionPropertyChangeListener); // keep player active - deviceSession.setDisposePlayerWhenClose(false); + deviceSession.setDisposePlayerOnClose(false); deviceSession.close(); deviceSession = null; } diff --git a/src/net/java/sip/communicator/impl/neomedia/VideoMediaStreamImpl.java b/src/net/java/sip/communicator/impl/neomedia/VideoMediaStreamImpl.java index 8a1a0f7..a769354 100644 --- a/src/net/java/sip/communicator/impl/neomedia/VideoMediaStreamImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/VideoMediaStreamImpl.java @@ -236,9 +236,18 @@ public class VideoMediaStreamImpl /** * The facility which aids this instance in managing a list of * <tt>VideoListener</tt>s and firing <tt>VideoEvent</tt>s to them. + * <p> + * Since the <tt>videoNotifierSupport</tt> of this + * <tt>VideoMediaStreamImpl</tt> just forwards the <tt>VideoEvent</tt>s of + * the associated <tt>VideoMediaDeviceSession</tt> at the time of this + * writing, it does not make sense to have <tt>videoNotifierSupport</tt> + * executing asynchronously because it does not know whether it has to wait + * for the delivery of the <tt>VideoEvent</tt>s and thus it has to default + * to waiting anyway. + * </p> */ private final VideoNotifierSupport videoNotifierSupport - = new VideoNotifierSupport(this); + = new VideoNotifierSupport(this, true); /** * Initializes a new <tt>VideoMediaStreamImpl</tt> instance which will use @@ -408,7 +417,8 @@ public class VideoMediaStreamImpl if (fireVideoEvent( e.getType(), e.getVisualComponent(), - e.getOrigin())) + e.getOrigin(), + true)) e.consume(); } @@ -429,7 +439,7 @@ public class VideoMediaStreamImpl public void videoUpdate(VideoEvent e) { - fireVideoEvent(e); + fireVideoEvent(e, true); } }; } @@ -480,6 +490,9 @@ public class VideoMediaStreamImpl * @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) + * @param wait <tt>true</tt> if the call is to wait till the specified + * <tt>VideoEvent</tt> has been delivered to the <tt>VideoListener</tt>s; + * otherwise, <tt>false</tt> * @return <tt>true</tt> if this event and, more specifically, the visual * <tt>Component</tt> it describes have been consumed and should be * considered owned, referenced (which is important because @@ -487,9 +500,8 @@ public class VideoMediaStreamImpl * otherwise, <tt>false</tt> */ protected boolean fireVideoEvent( - int type, - Component visualComponent, - int origin) + int type, Component visualComponent, int origin, + boolean wait) { if (logger.isTraceEnabled()) logger @@ -500,7 +512,9 @@ public class VideoMediaStreamImpl + VideoEvent.originToString(origin)); return - videoNotifierSupport.fireVideoEvent(type, visualComponent, origin); + videoNotifierSupport.fireVideoEvent( + type, visualComponent, origin, + wait); } /** @@ -509,10 +523,13 @@ public class VideoMediaStreamImpl * * @param event the <tt>VideoEvent</tt> to be fired to the * <tt>VideoListener</tt>s registered with this instance + * @param wait <tt>true</tt> if the call is to wait till the specified + * <tt>VideoEvent</tt> has been delivered to the <tt>VideoListener</tt>s; + * otherwise, <tt>false</tt> */ - protected void fireVideoEvent(VideoEvent event) + protected void fireVideoEvent(VideoEvent event, boolean wait) { - videoNotifierSupport.fireVideoEvent(event); + videoNotifierSupport.fireVideoEvent(event, wait); } /** diff --git a/src/net/java/sip/communicator/impl/neomedia/device/ImageStreamingAuto.java b/src/net/java/sip/communicator/impl/neomedia/device/ImageStreamingAuto.java index 2765772..fb2db9b 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/ImageStreamingAuto.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/ImageStreamingAuto.java @@ -76,37 +76,33 @@ public class ImageStreamingAuto for(ScreenDevice screen : screens) { Dimension size = screenSize != null ? screenSize : screen.getSize(); - - Format formats[]= new Format[] - { - new AVFrameFormat( - size, - Format.NOT_SPECIFIED, - FFmpeg.PIX_FMT_ARGB, - Format.NOT_SPECIFIED), - new RGBFormat( - size, // size - Format.NOT_SPECIFIED, // maxDataLength - Format.byteArray, // dataType - Format.NOT_SPECIFIED, // frameRate - 32, // bitsPerPixel - 2 /* red */, 3 /* green */, 4 /* blue */) - }; - + Format formats[] + = new Format[] + { + new AVFrameFormat( + size, + Format.NOT_SPECIFIED, + FFmpeg.PIX_FMT_ARGB, + Format.NOT_SPECIFIED), + new RGBFormat( + size, // size + Format.NOT_SPECIFIED, // maxDataLength + Format.byteArray, // dataType + Format.NOT_SPECIFIED, // frameRate + 32, // bitsPerPixel + 2 /* red */, 3 /* green */, 4 /* blue */) + }; CaptureDeviceInfo devInfo = new CaptureDeviceInfo( name + " " + i, new MediaLocator(LOCATOR_PROTOCOL + ":" + i), formats); - /* add to JMF device manager */ CaptureDeviceManager.addDevice(devInfo); i++; if(multipleMonitorOneScreen) - { break; - } } CaptureDeviceManager.commit(); diff --git a/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceSession.java b/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceSession.java index 986d21e..d3301b9 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceSession.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceSession.java @@ -174,9 +174,9 @@ public class MediaDeviceSession private MediaDirection startedDirection = MediaDirection.INACTIVE; /** - * If the player have to be disposed when we #close() this instance. + * If the player have to be disposed when we {@link #close()} this instance. */ - private boolean disposePlayerWhenClose = true; + private boolean disposePlayerOnClose = true; /** * Whether output size has changed after latest processor config. @@ -204,9 +204,9 @@ public class MediaDeviceSession * * @param dispose value to set */ - public void setDisposePlayerWhenClose(boolean dispose) + public void setDisposePlayerOnClose(boolean dispose) { - disposePlayerWhenClose = dispose; + disposePlayerOnClose = dispose; } /** @@ -337,11 +337,9 @@ public class MediaDeviceSession disconnectCaptureDevice(); closeProcessor(); - if(disposePlayerWhenClose) - { - // playback + // playback + if (disposePlayerOnClose) disposePlayer(); - } } /** @@ -363,8 +361,16 @@ public class MediaDeviceSession if (processor.getState() == Processor.Realized) { - DataSource dataOutput = processor.getDataOutput(); + DataSource dataOutput; + try + { + dataOutput = processor.getDataOutput(); + } + catch (NotRealizedError nre) + { + dataOutput = null; + } if (dataOutput != null) dataOutput.disconnect(); } @@ -590,11 +596,23 @@ public class MediaDeviceSession */ private void disposePlayer() { + Player player; + synchronized (playbackSyncRoot) { - if (player != null) - disposePlayer(player); + /* + * If #disposePlayer(Player) is just executed inside the + * synchronized block protected by #playbackSyncRoot, it practically + * locks the rest of the state protected by the same synchronization + * root. But that is not necessary because #disposePlayer(Player) + * will protect #player when necessary. Anyway, the change from the + * described behavior to the current one has been made while solving + * a deadlock. + */ + player = this.player; } + if (player != null) + disposePlayer(player); } /** @@ -1815,7 +1833,7 @@ public class MediaDeviceSession */ protected void transferRenderingSession(MediaDeviceSession session) { - if(session.disposePlayerWhenClose) + if (session.disposePlayerOnClose) { logger.error("Cannot tranfer rendering session if " + "MediaDeviceSession has closed it"); diff --git a/src/net/java/sip/communicator/impl/neomedia/device/VideoMediaDeviceSession.java b/src/net/java/sip/communicator/impl/neomedia/device/VideoMediaDeviceSession.java index a20f1fd..af09bba 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/VideoMediaDeviceSession.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/VideoMediaDeviceSession.java @@ -115,7 +115,7 @@ public class VideoMediaDeviceSession * <tt>VideoListener</tt>s and firing <tt>VideoEvent</tt>s to them. */ private final VideoNotifierSupport videoNotifierSupport - = new VideoNotifierSupport(this); + = new VideoNotifierSupport(this, false); /** * Initializes a new <tt>VideoMediaDeviceSession</tt> instance which is to @@ -291,9 +291,8 @@ public class VideoMediaDeviceSession if (visualComponent != null) { fireVideoEvent( - VideoEvent.VIDEO_REMOVED, - visualComponent, - VideoEvent.REMOTE); + VideoEvent.VIDEO_REMOVED, visualComponent, VideoEvent.REMOTE, + false); } } @@ -309,6 +308,9 @@ public class VideoMediaDeviceSession * @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) + * @param wait <tt>true</tt> if the call is to wait till the specified + * <tt>VideoEvent</tt> has been delivered to the <tt>VideoListener</tt>s; + * otherwise, <tt>false</tt> * @return <tt>true</tt> if this event and, more specifically, the visual * <tt>Component</tt> it describes have been consumed and should be * considered owned, referenced (which is important because @@ -316,9 +318,8 @@ public class VideoMediaDeviceSession * otherwise, <tt>false</tt> */ protected boolean fireVideoEvent( - int type, - Component visualComponent, - int origin) + int type, Component visualComponent, int origin, + boolean wait) { if (logger.isTraceEnabled()) { @@ -330,7 +331,9 @@ public class VideoMediaDeviceSession } return - videoNotifierSupport.fireVideoEvent(type, visualComponent, origin); + videoNotifierSupport.fireVideoEvent( + type, visualComponent, origin, + wait); } /** @@ -339,10 +342,13 @@ public class VideoMediaDeviceSession * * @param videoEvent the <tt>VideoEvent</tt> to be fired to the * <tt>VideoListener</tt>s registered with this instance + * @param wait <tt>true</tt> if the call is to wait till the specified + * <tt>VideoEvent</tt> has been delivered to the <tt>VideoListener</tt>s; + * otherwise, <tt>false</tt> */ - protected void fireVideoEvent(VideoEvent videoEvent) + protected void fireVideoEvent(VideoEvent videoEvent, boolean wait) { - videoNotifierSupport.fireVideoEvent(videoEvent); + videoNotifierSupport.fireVideoEvent(videoEvent, wait); } /** @@ -498,7 +504,8 @@ public class VideoMediaDeviceSession if (fireVideoEvent( VideoEvent.VIDEO_ADDED, visualComponent, - VideoEvent.LOCAL)) + VideoEvent.LOCAL, + true)) { localVisualComponentConsumed(visualComponent, player); } @@ -648,7 +655,9 @@ public class VideoMediaDeviceSession */ canvas.setName(DESKTOP_STREAMING_ICON); - fireVideoEvent(VideoEvent.VIDEO_ADDED, canvas, VideoEvent.LOCAL); + fireVideoEvent( + VideoEvent.VIDEO_ADDED, canvas, VideoEvent.LOCAL, + false); } return canvas; } @@ -669,9 +678,8 @@ public class VideoMediaDeviceSession && DESKTOP_STREAMING_ICON.equals(component.getName())) { fireVideoEvent( - VideoEvent.VIDEO_REMOVED, - component, - VideoEvent.LOCAL); + VideoEvent.VIDEO_REMOVED, component, VideoEvent.LOCAL, + false); return; } @@ -709,9 +717,8 @@ public class VideoMediaDeviceSession if (visualComponent != null) fireVideoEvent( - VideoEvent.VIDEO_REMOVED, - visualComponent, - VideoEvent.LOCAL); + VideoEvent.VIDEO_REMOVED, visualComponent, VideoEvent.LOCAL, + false); } /** @@ -942,9 +949,8 @@ public class VideoMediaDeviceSession }); fireVideoEvent( - VideoEvent.VIDEO_ADDED, - visualComponent, - VideoEvent.REMOTE); + VideoEvent.VIDEO_ADDED, visualComponent, VideoEvent.REMOTE, + false); } } @@ -991,7 +997,8 @@ public class VideoMediaDeviceSession visualComponent, SizeChangeVideoEvent.REMOTE, width, - height)); + height), + false); } } @@ -1516,7 +1523,8 @@ public class VideoMediaDeviceSession fireVideoEvent( VideoEvent.VIDEO_ADDED, visualComponent, - VideoEvent.REMOTE); + VideoEvent.REMOTE, + false); } } } @@ -1531,7 +1539,8 @@ public class VideoMediaDeviceSession fireVideoEvent( VideoEvent.VIDEO_REMOVED, visualComponent, - VideoEvent.REMOTE); + VideoEvent.REMOTE, + false); } } } diff --git a/src/net/java/sip/communicator/impl/neomedia/neomedia.manifest.mf b/src/net/java/sip/communicator/impl/neomedia/neomedia.manifest.mf index b849ffc..61abd92 100644 --- a/src/net/java/sip/communicator/impl/neomedia/neomedia.manifest.mf +++ b/src/net/java/sip/communicator/impl/neomedia/neomedia.manifest.mf @@ -10,10 +10,11 @@ Import-Package: org.bouncycastle.crypto, org.bouncycastle.crypto.macs, org.bouncycastle.crypto.params, org.bouncycastle.crypto.prng, + org.ice4j.socket, org.json, org.osgi.framework, + org.w3c.dom, org.xml.sax, - org.ice4j.socket, javax.imageio, javax.sound.sampled, javax.swing, @@ -21,6 +22,7 @@ Import-Package: org.bouncycastle.crypto, javax.swing.event, javax.swing.table, javax.swing.text, + javax.xml.parsers, net.java.sip.communicator.service.configuration, net.java.sip.communicator.service.fileaccess, net.java.sip.communicator.service.gui, diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetDesktopSharingServerSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetDesktopSharingServerSipImpl.java index c677757..a95fabd 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetDesktopSharingServerSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetDesktopSharingServerSipImpl.java @@ -474,9 +474,7 @@ public class OperationSetDesktopSharingServerSipImpl byte[] rawContent) { if(requestEvent.getDialog() != callPeer.getDialog()) - { return; - } if (rawContent != null) { @@ -514,19 +512,14 @@ public class OperationSetDesktopSharingServerSipImpl List<ComponentEvent> events = null; Point p = getOrigin(); - events = DesktopSharingProtocolSipImpl.parse(root, size, - p); + events = DesktopSharingProtocolSipImpl.parse(root, size, p); for(ComponentEvent evt : events) { if(evt instanceof MouseEvent) - { processMouseEvent((MouseEvent)evt); - } else if(evt instanceof KeyEvent) - { processKeyboardEvent((KeyEvent)evt); - } } } } diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetDesktopStreamingSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetDesktopStreamingSipImpl.java index 8947bac..e8e1d84 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetDesktopStreamingSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetDesktopStreamingSipImpl.java @@ -83,10 +83,12 @@ public class OperationSetDesktopStreamingSipImpl Address toAddress = parentProvider.parseAddressString(uri); CallSipImpl call = basicTelephony.createOutgoingCall(); + call.setVideoDevice(mediaDevice); call.setLocalVideoAllowed(true, getMediaUseCase()); call.invite(toAddress, null); origin = getOriginForMediaDevice(mediaDevice); + return call; } @@ -119,6 +121,7 @@ public class OperationSetDesktopStreamingSipImpl } CallSipImpl call = basicTelephony.createOutgoingCall(); + call.setLocalVideoAllowed(true, getMediaUseCase()); call.setVideoDevice(mediaDevice); call.invite(toAddress, null); @@ -146,9 +149,9 @@ public class OperationSetDesktopStreamingSipImpl public Call createVideoCall(String uri) throws OperationFailedException, ParseException { - Call call = super.createVideoCall(uri); - MediaDevice device = ((CallSipImpl)call).getDefaultDevice( - MediaType.VIDEO); + CallSipImpl call = (CallSipImpl) super.createVideoCall(uri); + MediaDevice device = call.getDefaultDevice(MediaType.VIDEO); + size = (((VideoMediaFormat)device.getFormat()).getSize()); origin = getOriginForMediaDevice(device); return call; @@ -170,9 +173,9 @@ public class OperationSetDesktopStreamingSipImpl @Override public Call createVideoCall(Contact callee) throws OperationFailedException { - Call call = super.createVideoCall(callee); - MediaDevice device = ((CallSipImpl)call).getDefaultDevice( - MediaType.VIDEO); + CallSipImpl call = (CallSipImpl) super.createVideoCall(callee); + MediaDevice device = call.getDefaultDevice(MediaType.VIDEO); + size = (((VideoMediaFormat)device.getFormat()).getSize()); origin = getOriginForMediaDevice(device); return call; @@ -188,21 +191,24 @@ public class OperationSetDesktopStreamingSipImpl * @param allowed <tt>true</tt> if local video transmission is allowed and * <tt>false</tt> otherwise. * - * @throws OperationFailedException if video initialization fails. + * @throws OperationFailedException if video initialization fails. */ @Override public void setLocalVideoAllowed(Call call, boolean allowed) throws OperationFailedException { - ((CallSipImpl)call).setLocalVideoAllowed(allowed, MediaUseCase.DESKTOP); - ((CallSipImpl)call).setVideoDevice(null); - MediaDevice device = ((CallSipImpl)call).getDefaultDevice( - MediaType.VIDEO); - size = (((VideoMediaFormat)device.getFormat()).getSize()); + CallSipImpl callImpl = (CallSipImpl) call; + + callImpl.setLocalVideoAllowed(allowed, MediaUseCase.DESKTOP); + callImpl.setVideoDevice(null); + + MediaDevice device = callImpl.getDefaultDevice(MediaType.VIDEO); + + size = ((VideoMediaFormat)device.getFormat()).getSize(); origin = getOriginForMediaDevice(device); /* reinvite all peers */ - ((CallSipImpl)call).reInvite(); + callImpl.reInvite(); } /** @@ -267,13 +273,10 @@ public class OperationSetDesktopStreamingSipImpl CallSipImpl callImpl = (CallSipImpl)call; MediaDevice device = callImpl.getDefaultDevice(MediaType.VIDEO); - if(device != null) - { - MediaService mediaService = SipActivator.getMediaService(); - return mediaService.isPartialStreaming(device); - } - - return false; + return + (device == null) + ? false + : SipActivator.getMediaService().isPartialStreaming(device); } /** @@ -325,8 +328,8 @@ public class OperationSetDesktopStreamingSipImpl */ protected static Point getOriginForMediaDevice(MediaDevice device) { - MediaService mediaService = SipActivator.getMediaService(); - - return mediaService.getOriginForDesktopStreamingDevice(device); + return + SipActivator.getMediaService().getOriginForDesktopStreamingDevice( + device); } } diff --git a/src/net/java/sip/communicator/service/neomedia/event/VideoNotifierSupport.java b/src/net/java/sip/communicator/service/neomedia/event/VideoNotifierSupport.java index 4dffb14..70be427 100644 --- a/src/net/java/sip/communicator/service/neomedia/event/VideoNotifierSupport.java +++ b/src/net/java/sip/communicator/service/neomedia/event/VideoNotifierSupport.java @@ -6,18 +6,27 @@ */ package net.java.sip.communicator.service.neomedia.event; -import java.awt.Component; +import java.awt.*; import java.util.*; +import java.util.List; // disambiguation /** * Represents a mechanism to easily add to a specific <tt>Object</tt> by means * of composition support for firing <tt>VideoEvent</tt>s to * <tt>VideoListener</tt>s. * - * @author Lubomir Marinov + * @author Lyubomir Marinov */ public class VideoNotifierSupport { + private static final long THREAD_TIMEOUT = 5000; + + /** + * The list of <tt>VideoEvent</tt>s which are to be delivered to the + * {@link #listeners} registered with this instance when + * {@link #synchronous} is equal to <tt>false</tt>. + */ + private final List<VideoEvent> events; /** * The list of <tt>VideoListener</tt>s interested in changes in the @@ -33,6 +42,18 @@ public class VideoNotifierSupport private final Object source; /** + * The indicator which determines whether this instance delivers the + * <tt>VideoEvent</tt>s to the {@link #listeners} synchronously. + */ + private final boolean synchronous; + + /** + * The <tt>Thread</tt> in which {@link #events} are delivered to the + * {@link #listeners} when {@link #synchronous} is equal to <tt>false</tt>. + */ + private Thread thread; + + /** * Initializes a new <tt>VideoNotifierSupport</tt> instance which is to * facilitate the management of <tt>VideoListener</tt>s and firing * <tt>VideoEvent</tt>s to them for a specific <tt>Object</tt>. @@ -42,7 +63,25 @@ public class VideoNotifierSupport */ public VideoNotifierSupport(Object source) { + this(source, true); + } + + /** + * Initializes a new <tt>VideoNotifierSupport</tt> instance which is to + * facilitate the management of <tt>VideoListener</tt>s and firing + * <tt>VideoEvent</tt>s to them for a specific <tt>Object</tt>. + * + * @param source the <tt>Object</tt> which is to be reported as the source + * of the <tt>VideoEvent</tt>s fired by the new instance + * @param synchronous <tt>true</tt> if the new instance is to deliver the + * <tt>VideoEvent</tt>s synchronously; otherwise, <tt>false</tt> + */ + public VideoNotifierSupport(Object source, boolean synchronous) + { this.source = source; + this.synchronous = synchronous; + + events = this.synchronous ? null : new LinkedList<VideoEvent>(); } /** @@ -71,6 +110,32 @@ public class VideoNotifierSupport } } + protected void doFireVideoEvent(VideoEvent event) + { + VideoListener[] listeners; + + synchronized (this.listeners) + { + listeners + = this.listeners.toArray( + new VideoListener[this.listeners.size()]); + } + + for (VideoListener listener : listeners) + switch (event.getType()) + { + case VideoEvent.VIDEO_ADDED: + listener.videoAdded(event); + break; + case VideoEvent.VIDEO_REMOVED: + listener.videoRemoved(event); + break; + default: + listener.videoUpdate(event); + break; + } + } + /** * Notifies the <tt>VideoListener</tt>s registered with this * <tt>VideoMediaStream</tt> about a specific type of change in the @@ -83,6 +148,9 @@ public class VideoNotifierSupport * @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) + * @param wait <tt>true</tt> if the call is to wait till the specified + * <tt>VideoEvent</tt> has been delivered to the <tt>VideoListener</tt>s; + * otherwise, <tt>false</tt> * @return <tt>true</tt> if this event and, more specifically, the visual * <tt>Component</tt> it describes have been consumed and should be * considered owned, referenced (which is important because @@ -90,44 +158,14 @@ public class VideoNotifierSupport * otherwise, <tt>false</tt> */ public boolean fireVideoEvent( - int type, - Component visualComponent, - int origin) + int type, Component visualComponent, int origin, + boolean wait) { - VideoListener[] listeners; - - synchronized (this.listeners) - { - listeners - = this.listeners - .toArray(new VideoListener[this.listeners.size()]); - } - - boolean consumed; - - if (listeners.length > 0) - { - VideoEvent event - = new VideoEvent(source, 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; - default: - throw new IllegalArgumentException("type"); - } + VideoEvent event + = new VideoEvent(source, type, visualComponent, origin); - consumed = event.isConsumed(); - } - else - consumed = false; - return consumed; + fireVideoEvent(event, wait); + return event.isConsumed(); } /** @@ -136,31 +174,45 @@ public class VideoNotifierSupport * * @param event the <tt>VideoEvent</tt> to be fired to the * <tt>VideoListener</tt>s registered with this instance + * @param wait <tt>true</tt> if the call is to wait till the specified + * <tt>VideoEvent</tt> has been delivered to the <tt>VideoListener</tt>s; + * otherwise, <tt>false</tt> */ - public void fireVideoEvent(VideoEvent event) + public void fireVideoEvent(VideoEvent event, boolean wait) { - VideoListener[] listeners; - - synchronized (this.listeners) + if (synchronous) + doFireVideoEvent(event); + else { - listeners - = this.listeners - .toArray(new VideoListener[this.listeners.size()]); - } - - for (VideoListener listener : listeners) - switch (event.getType()) + synchronized (events) { - case VideoEvent.VIDEO_ADDED: - listener.videoAdded(event); - break; - case VideoEvent.VIDEO_REMOVED: - listener.videoRemoved(event); - break; - default: - listener.videoUpdate(event); - break; + events.add(event); + + if (thread == null) + startThread(); + else + events.notify(); + + if (wait) + { + boolean interrupted = false; + + while (events.contains(event) && (thread != null)) + { + try + { + events.wait(); + } + catch (InterruptedException ie) + { + interrupted = true; + } + } + if (interrupted) + Thread.currentThread().interrupt(); + } } + } } /** @@ -179,4 +231,97 @@ public class VideoNotifierSupport listeners.remove(listener); } } + + private void runInThread() + { + while (true) + { + VideoEvent event = null; + + synchronized (events) + { + long emptyTime = -1; + boolean interrupted = false; + + while (events.isEmpty()) + { + if (emptyTime == -1) + emptyTime = System.currentTimeMillis(); + else + { + long newEmptyTime = System.currentTimeMillis(); + + if ((newEmptyTime - emptyTime) >= THREAD_TIMEOUT) + { + events.notify(); + return; + } + } + + try + { + events.wait(THREAD_TIMEOUT); + } + catch (InterruptedException ie) + { + interrupted = true; + } + } + if (interrupted) + Thread.currentThread().interrupt(); + + event = events.remove(0); + } + + if (event != null) + { + try + { + doFireVideoEvent(event); + } + catch (Throwable t) + { + if (t instanceof ThreadDeath) + throw (ThreadDeath) t; + } + + synchronized (events) + { + events.notify(); + } + } + } + } + + private void startThread() + { + thread + = new Thread("VideoNotifierSupportThread") + { + @Override + public void run() + { + try + { + runInThread(); + } + finally + { + synchronized (events) + { + if (Thread.currentThread().equals(thread)) + { + thread = null; + if (events.isEmpty()) + events.notify(); + else + startThread(); + } + } + } + } + }; + thread.setDaemon(true); + thread.start(); + } } 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 e71e0b6..efcf238 100644 --- a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java +++ b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java @@ -375,12 +375,14 @@ public abstract class MediaAwareCallPeer public void setLocalVideoAllowed(boolean allowed) throws OperationFailedException { - if(getMediaHandler().isLocalVideoTransmissionEnabled() == allowed) - return; + CallPeerMediaHandler<?> mediaHandler = getMediaHandler(); - // Modify the local media setup to reflect the requested setting for - // the streaming of the local video. - getMediaHandler().setLocalVideoTransmissionEnabled(allowed); + if(mediaHandler.isLocalVideoTransmissionEnabled() != allowed) + { + // Modify the local media setup to reflect the requested setting for + // the streaming of the local video. + mediaHandler.setLocalVideoTransmissionEnabled(allowed); + } } /** @@ -509,14 +511,16 @@ public abstract class MediaAwareCallPeer // 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. - synchronized(getMediaHandler()) + CallPeerMediaHandler<?> mediaHandler = getMediaHandler(); + + synchronized(mediaHandler) { super.setState(newState, reason, reasonCode); if (CallPeerState.DISCONNECTED.equals(newState) || CallPeerState.FAILED.equals(newState)) { - getMediaHandler().close(); + mediaHandler.close(); } } } @@ -788,8 +792,10 @@ public abstract class MediaAwareCallPeer // us audio for at least two separate participants. We therefore // need to remove the stream level listeners and switch to CSRC // level listening - getMediaHandler().setStreamAudioLevelListener(null); - getMediaHandler().setCsrcAudioLevelListener(this); + CallPeerMediaHandler<?> mediaHandler = getMediaHandler(); + + mediaHandler.setStreamAudioLevelListener(null); + mediaHandler.setCsrcAudioLevelListener(this); } } @@ -811,8 +817,10 @@ public abstract class MediaAwareCallPeer // 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. - getMediaHandler().setStreamAudioLevelListener(this); - getMediaHandler().setCsrcAudioLevelListener(null); + CallPeerMediaHandler<?> mediaHandler = getMediaHandler(); + + mediaHandler.setStreamAudioLevelListener(this); + mediaHandler.setCsrcAudioLevelListener(null); } } |