From 6eae7b0183c65e16ccbbe097b726a9fee4c4e66a Mon Sep 17 00:00:00 2001 From: Lyubomir Marinov Date: Mon, 2 Apr 2012 06:29:32 +0000 Subject: Adds (experimental) support for the cobri Jabber extension. --- .../impl/gui/main/call/UIVideoHandler.java | 1 + .../sip/communicator/impl/gui/swing.ui.manifest.mf | 5 +- .../impl/neomedia/MediaServiceImpl.java | 124 ++- .../impl/neomedia/VideoMediaStreamImpl.java | 2 +- .../impl/neomedia/device/DeviceConfiguration.java | 17 +- .../impl/neomedia/device/MediaDeviceSession.java | 1 + .../neomedia/device/VideoMediaDeviceSession.java | 2 +- .../impl/neomedia/neomedia.manifest.mf | 1 + .../impl/protocol/jabber/CallJabberImpl.java | 393 ++++++- .../protocol/jabber/RawUdpTransportManager.java | 324 +++++- .../extensions/cobri/CobriStreamConnector.java | 61 ++ .../service/neomedia/StreamConnectorDelegate.java | 88 ++ .../service/neomedia/StreamConnectorFactory.java | 22 + .../service/neomedia/VideoMediaStream.java | 2 +- .../neomedia/event/SizeChangeVideoEvent.java | 86 -- .../service/neomedia/event/VideoEvent.java | 218 ---- .../service/neomedia/event/VideoListener.java | 50 - .../neomedia/event/VideoNotifierSupport.java | 327 ------ .../notification/LogMessageNotificationAction.java | 116 +-- .../service/protocol/AbstractCallPeer.java | 1 + .../service/protocol/AbstractConferenceMember.java | 4 +- .../protocol/OperationSetVideoTelephony.java | 2 +- .../protocol/event/SizeChangeVideoEvent.java | 86 -- .../service/protocol/event/VideoEvent.java | 177 ---- .../service/protocol/event/VideoListener.java | 49 - .../media/AbstractOperationSetVideoTelephony.java | 2 +- .../protocol/media/CallPeerMediaHandler.java | 1085 +++++--------------- .../service/protocol/media/MediaHandler.java | 984 ++++++++++++++++++ .../service/protocol/media/TransportManager.java | 44 +- .../protocol/media/protocol.media.manifest.mf | 1 + .../service/protocol/protocol.provider.manifest.mf | 3 +- .../communicator/util/PropertyChangeNotifier.java | 135 --- .../util/event/PropertyChangeNotifier.java | 135 +++ .../util/event/SizeChangeVideoEvent.java | 96 ++ .../sip/communicator/util/event/VideoEvent.java | 226 ++++ .../sip/communicator/util/event/VideoListener.java | 50 + .../util/event/VideoNotifierSupport.java | 327 ++++++ .../java/sip/communicator/util/util.manifest.mf | 9 +- 38 files changed, 3095 insertions(+), 2161 deletions(-) create mode 100644 src/net/java/sip/communicator/impl/protocol/jabber/extensions/cobri/CobriStreamConnector.java create mode 100644 src/net/java/sip/communicator/service/neomedia/StreamConnectorDelegate.java create mode 100644 src/net/java/sip/communicator/service/neomedia/StreamConnectorFactory.java delete mode 100644 src/net/java/sip/communicator/service/neomedia/event/SizeChangeVideoEvent.java delete mode 100644 src/net/java/sip/communicator/service/neomedia/event/VideoEvent.java delete mode 100644 src/net/java/sip/communicator/service/neomedia/event/VideoListener.java delete mode 100644 src/net/java/sip/communicator/service/neomedia/event/VideoNotifierSupport.java delete mode 100644 src/net/java/sip/communicator/service/protocol/event/SizeChangeVideoEvent.java delete mode 100644 src/net/java/sip/communicator/service/protocol/event/VideoEvent.java delete mode 100644 src/net/java/sip/communicator/service/protocol/event/VideoListener.java create mode 100644 src/net/java/sip/communicator/service/protocol/media/MediaHandler.java delete mode 100644 src/net/java/sip/communicator/util/PropertyChangeNotifier.java create mode 100644 src/net/java/sip/communicator/util/event/PropertyChangeNotifier.java create mode 100644 src/net/java/sip/communicator/util/event/SizeChangeVideoEvent.java create mode 100644 src/net/java/sip/communicator/util/event/VideoEvent.java create mode 100644 src/net/java/sip/communicator/util/event/VideoListener.java create mode 100644 src/net/java/sip/communicator/util/event/VideoNotifierSupport.java (limited to 'src/net/java/sip') diff --git a/src/net/java/sip/communicator/impl/gui/main/call/UIVideoHandler.java b/src/net/java/sip/communicator/impl/gui/main/call/UIVideoHandler.java index 1889c7d..d55d2b2 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/UIVideoHandler.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/UIVideoHandler.java @@ -19,6 +19,7 @@ import net.java.sip.communicator.service.neomedia.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; +import net.java.sip.communicator.util.event.*; import net.java.sip.communicator.util.swing.*; /** diff --git a/src/net/java/sip/communicator/impl/gui/swing.ui.manifest.mf b/src/net/java/sip/communicator/impl/gui/swing.ui.manifest.mf index 9bdaaca..bf871c6 100644 --- a/src/net/java/sip/communicator/impl/gui/swing.ui.manifest.mf +++ b/src/net/java/sip/communicator/impl/gui/swing.ui.manifest.mf @@ -37,12 +37,13 @@ Import-Package: org.osgi.framework, net.java.sip.communicator.service.replacement, net.java.sip.communicator.service.replacement.smilies, net.java.sip.communicator.util, + net.java.sip.communicator.util.event, + net.java.sip.communicator.util.skin, + net.java.sip.communicator.util.swing.transparent, net.java.sip.communicator.util.swing, net.java.sip.communicator.util.swing.border, net.java.sip.communicator.util.swing.event, net.java.sip.communicator.util.swing.plaf, - net.java.sip.communicator.util.skin, - net.java.sip.communicator.util.swing.transparent, javax.accessibility, javax.imageio, javax.swing, diff --git a/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java b/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java index 63da32a..0570b14 100644 --- a/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java @@ -30,7 +30,9 @@ import net.java.sip.communicator.service.configuration.*; import net.java.sip.communicator.service.neomedia.*; import net.java.sip.communicator.service.neomedia.device.*; import net.java.sip.communicator.service.neomedia.format.*; +import net.java.sip.communicator.service.resources.*; import net.java.sip.communicator.util.*; +import net.java.sip.communicator.util.event.*; import net.java.sip.communicator.util.swing.*; /** @@ -611,7 +613,11 @@ public class MediaServiceImpl encodingConfiguration.registerCustomPackages(); encodingConfiguration.registerCustomCodecs(); - if(!OSUtils.IS_ANDROID) + /* + * The neomedia bundle is generic enough to be used in a headless + * GraphicsEnvironment so prevent a HeadlessException. + */ + if(!OSUtils.IS_ANDROID && !GraphicsEnvironment.isHeadless()) { try { @@ -620,55 +626,55 @@ public class MediaServiceImpl DeviceConfigurationComboBoxModel.AUDIO, false); - audioConfigDialog = new SIPCommDialog() - { - /** - * Serial version UID. - */ - private static final long serialVersionUID = 0L; - - /** - * {@inheritDoc} - */ - @Override - protected void close(boolean isEscaped) + audioConfigDialog + = new SIPCommDialog() { - setVisible(false); - } - }; + /** Serial version UID. */ + private static final long serialVersionUID = 0L; + + /** {@inheritDoc} */ + @Override + protected void close(boolean escaped) + { + setVisible(false); + } + }; TransparentPanel mainPanel = new TransparentPanel(new BorderLayout(20, 5)); - TransparentPanel fieldsPanel = new TransparentPanel(new BorderLayout(10, 5)); mainPanel.setBorder( - BorderFactory.createEmptyBorder(20, 20, 20, 20)); + BorderFactory.createEmptyBorder(20, 20, 20, 20)); TransparentPanel btnPanel = new TransparentPanel(new FlowLayout(FlowLayout.RIGHT)); + ResourceManagementService resources + = NeomediaActivator.getResources(); + JButton btn + = new JButton(resources.getI18NString("service.gui.CLOSE")); - JButton btn = new JButton(NeomediaActivator.getResources(). - getI18NString("service.gui.CLOSE")); - - btn.addActionListener(new ActionListener() - { - public void actionPerformed(ActionEvent evt) - { - audioConfigDialog.setVisible(false); - } - }); + btn.addActionListener( + new ActionListener() + { + public void actionPerformed(ActionEvent evt) + { + audioConfigDialog.setVisible(false); + } + }); btnPanel.add(btn); JTextArea infoTextArea = new JTextArea(); + infoTextArea.setOpaque(false); infoTextArea.setEditable(false); infoTextArea.setWrapStyleWord(true); infoTextArea.setLineWrap(true); - infoTextArea.setText(NeomediaActivator.getResources() - .getI18NString( - "impl.media.configform.AUDIO_DEVICE_CONNECTED_REMOVED")); + infoTextArea.setText( + resources.getI18NString( + "impl.media.configform" + + ".AUDIO_DEVICE_CONNECTED_REMOVED")); fieldsPanel.add(infoTextArea, BorderLayout.NORTH); fieldsPanel.add(panel, BorderLayout.CENTER); @@ -676,24 +682,30 @@ public class MediaServiceImpl TransparentPanel iconPanel = new TransparentPanel(new BorderLayout()); - iconPanel.add(new JLabel(NeomediaActivator.getResources() - .getImage("plugin.mediaconfig.AUDIO_ICON_64x64")), - BorderLayout.NORTH); + + iconPanel.add( + new JLabel( + resources.getImage( + "plugin.mediaconfig.AUDIO_ICON_64x64")), + BorderLayout.NORTH); mainPanel.add(iconPanel,BorderLayout.WEST); mainPanel.add(fieldsPanel, BorderLayout.CENTER); - audioConfigDialog.setTitle(NeomediaActivator.getResources() - .getI18NString("impl.media.configform.AUDIO_DEVICE_CONFIG")); + audioConfigDialog.setTitle( + resources.getI18NString( + "impl.media.configform.AUDIO_DEVICE_CONFIG")); audioConfigDialog.add(mainPanel); audioConfigDialog.validate(); audioConfigDialog.pack(); PortAudioDeviceChangedCallbacks.addDeviceChangedCallback(this); } - catch(Throwable e) + catch(Throwable t) { - logger.info("Cannot create audio configuration panel", e); + logger.info("Failed to create audio configuration panel", t); + if (t instanceof ThreadDeath) + throw (ThreadDeath) t; } } } @@ -710,10 +722,11 @@ public class MediaServiceImpl } /** - * Creates ZrtpControl used to control all zrtp options - * on particular stream. + * Initializes a new ZrtpControl instance which is to control all + * ZRTP options. * - * @return ZrtpControl instance. + * @return a new ZrtpControl instance which is to control all ZRTP + * options */ public ZrtpControl createZrtpControl() { @@ -721,9 +734,11 @@ public class MediaServiceImpl } /** - * Creates SDesControl used to control all SDes options. + * Initializes a new SDesControl instance which is to control all + * SDes options. * - * @return SDesControl instance. + * @return a new SDesControl instance which is to control all SDes + * options */ public SDesControl createSDesControl() { @@ -778,13 +793,10 @@ public class MediaServiceImpl ScreenDevice screens[] = ScreenDeviceImpl.getAvailableScreenDevice(); List screenList; - if (screens != null) - { - screenList = new ArrayList(screens.length); - screenList.addAll(Arrays.asList(screens)); - } + if ((screens != null) && (screens.length != 0)) + screenList = new ArrayList(Arrays.asList(screens)); else - screenList = new ArrayList(); + screenList = Collections.emptyList(); return screenList; } @@ -800,16 +812,15 @@ public class MediaServiceImpl int height = 0; ScreenDevice best = null; - for (ScreenDevice sc : screens) + for (ScreenDevice screen : screens) { - java.awt.Dimension res = sc.getSize(); + java.awt.Dimension res = screen.getSize(); - if ((res != null) - && ((width < res.width) || (height < res.height))) + if ((res != null) && ((width < res.width) || (height < res.height))) { width = res.width; height = res.height; - best = sc; + best = screen; } } return best; @@ -1318,8 +1329,9 @@ public class MediaServiceImpl MediaDeviceImpl dev = (MediaDeviceImpl)mediaDevice; CaptureDeviceInfo devInfo = dev.getCaptureDeviceInfo(); - return (devInfo != null - && devInfo.getName().startsWith("Partial desktop streaming")); + return + (devInfo != null) + && devInfo.getName().startsWith("Partial desktop streaming"); } /** diff --git a/src/net/java/sip/communicator/impl/neomedia/VideoMediaStreamImpl.java b/src/net/java/sip/communicator/impl/neomedia/VideoMediaStreamImpl.java index cc961d8..ba63c1a 100644 --- a/src/net/java/sip/communicator/impl/neomedia/VideoMediaStreamImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/VideoMediaStreamImpl.java @@ -24,9 +24,9 @@ import net.java.sip.communicator.service.neomedia.control.*; import net.java.sip.communicator.service.neomedia.control.KeyFrameControl; // disambiguation import net.java.sip.communicator.service.neomedia.device.*; import net.java.sip.communicator.service.neomedia.format.*; -import net.java.sip.communicator.service.neomedia.event.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.util.*; +import net.java.sip.communicator.util.event.*; /** * Extends MediaStreamImpl in order to provide an implementation of diff --git a/src/net/java/sip/communicator/impl/neomedia/device/DeviceConfiguration.java b/src/net/java/sip/communicator/impl/neomedia/device/DeviceConfiguration.java index c3f51bd..2f63316 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/DeviceConfiguration.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/DeviceConfiguration.java @@ -23,6 +23,7 @@ import net.java.sip.communicator.impl.neomedia.portaudio.*; import net.java.sip.communicator.service.configuration.*; import net.java.sip.communicator.service.neomedia.*; import net.java.sip.communicator.util.*; +import net.java.sip.communicator.util.event.*; /** * This class aims to provide a simple configuration interface for JMF. It @@ -33,7 +34,6 @@ import net.java.sip.communicator.util.*; * @author Emil Ivov * @author Lyubomir Marinov */ -@SuppressWarnings("unchecked") public class DeviceConfiguration extends PropertyChangeNotifier implements PropertyChangeListener, @@ -365,6 +365,7 @@ public class DeviceConfiguration */ private CaptureDeviceInfo extractConfiguredVideoCaptureDevice(Format format) { + @SuppressWarnings("unchecked") List videoCaptureDevices = CaptureDeviceManager.getDeviceList(format); CaptureDeviceInfo videoCaptureDevice = null; @@ -426,9 +427,10 @@ public class DeviceConfiguration */ public CaptureDeviceInfo[] getAvailableAudioCaptureDevices() { - Vector audioCaptureDevices = - CaptureDeviceManager.getDeviceList(new AudioFormat( - AudioFormat.LINEAR, -1, 16, -1)); + @SuppressWarnings("unchecked") + Vector audioCaptureDevices + = CaptureDeviceManager.getDeviceList( + new AudioFormat(AudioFormat.LINEAR, -1, 16, -1)); return audioCaptureDevices.toArray(NO_CAPTURE_DEVICES); } @@ -508,8 +510,9 @@ public class DeviceConfiguration for (Format format : formats) { - Vector captureDeviceInfos = - CaptureDeviceManager.getDeviceList(format); + @SuppressWarnings("unchecked") + Vector captureDeviceInfos + = CaptureDeviceManager.getDeviceList(format); if(useCase != MediaUseCase.ANY) { @@ -1080,6 +1083,7 @@ public class DeviceConfiguration */ private void registerCustomRenderers() { + @SuppressWarnings("unchecked") Vector renderers = PlugInManager.getPlugInList(null, null, PlugInManager.RENDERER); boolean commit = false; @@ -1116,6 +1120,7 @@ public class DeviceConfiguration * are considered preferred. */ int pluginType = PlugInManager.RENDERER; + @SuppressWarnings("unchecked") Vector plugins = PlugInManager.getPlugInList(null, null, pluginType); 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 a95b4f4..2e67910 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceSession.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceSession.java @@ -23,6 +23,7 @@ import net.java.sip.communicator.service.neomedia.*; import net.java.sip.communicator.service.neomedia.device.*; import net.java.sip.communicator.service.neomedia.format.*; import net.java.sip.communicator.util.*; +import net.java.sip.communicator.util.event.*; /** * Represents the use of a specific MediaDevice by a 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 efd0e2a..4f95b7d 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/VideoMediaDeviceSession.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/VideoMediaDeviceSession.java @@ -26,10 +26,10 @@ import net.java.sip.communicator.impl.neomedia.transform.*; import net.java.sip.communicator.service.neomedia.*; import net.java.sip.communicator.service.neomedia.control.*; import net.java.sip.communicator.service.neomedia.control.KeyFrameControl; // disambiguation -import net.java.sip.communicator.service.neomedia.event.*; import net.java.sip.communicator.service.neomedia.format.*; import net.java.sip.communicator.service.resources.*; import net.java.sip.communicator.util.*; +import net.java.sip.communicator.util.event.*; /** * Extends MediaDeviceSession to add video-specific functionality. 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 116ba5e..6ffdc19 100644 --- a/src/net/java/sip/communicator/impl/neomedia/neomedia.manifest.mf +++ b/src/net/java/sip/communicator/impl/neomedia/neomedia.manifest.mf @@ -35,6 +35,7 @@ Import-Package: org.bouncycastle.crypto, net.java.sip.communicator.service.protocol.event, net.java.sip.communicator.service.resources, net.java.sip.communicator.util, + net.java.sip.communicator.util.event, net.java.sip.communicator.util.swing, quicktime, quicktime.qd, diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java index 62b4c86..8e12c1b 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java @@ -6,11 +6,10 @@ */ package net.java.sip.communicator.impl.protocol.jabber; +import java.lang.ref.*; import java.util.*; -import org.jivesoftware.smack.packet.*; -import org.jivesoftware.smackx.packet.*; - +import net.java.sip.communicator.impl.protocol.jabber.extensions.cobri.*; import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*; import net.java.sip.communicator.impl.protocol.jabber.jinglesdp.*; import net.java.sip.communicator.service.neomedia.*; @@ -19,11 +18,17 @@ import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.service.protocol.media.*; import net.java.sip.communicator.util.*; +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smackx.packet.*; + /** * A Jabber implementation of the Call abstract class encapsulating * Jabber jingle sessions. * * @author Emil Ivov + * @author Lyubomir Marinov */ public class CallJabberImpl extends MediaAwareCall< @@ -38,12 +43,29 @@ public class CallJabberImpl private static final Logger logger = Logger.getLogger(CallJabberImpl.class); /** - * Indicates if the CallPeer will support inputevt + * Indicates if the CallPeer will support inputevt * extension (i.e. will be able to be remote-controlled). */ private boolean localInputEvtAware = false; /** + * The Jitsi VideoBridge conference which the local peer represented by this + * instance is a focus of. + */ + private CobriConferenceIQ cobri; + + /** + * The shared CallPeerMediaHandler state which is to be used by the + * CallPeers of this Call which use {@link #cobri}. + */ + private MediaHandler cobriMediaHandler; + + private final List> + cobriStreamConnectors + = new ArrayList>( + MediaType.values().length); + + /** * Initializes a new CallJabberImpl instance belonging to * sourceProvider and associated with the jingle session with the * specified jingleSID. If the new instance corresponds to an @@ -54,7 +76,7 @@ public class CallJabberImpl * instance in the context of which this call has been created. */ protected CallJabberImpl( - OperationSetBasicTelephonyJabberImpl parentOpSet) + OperationSetBasicTelephonyJabberImpl parentOpSet) { super(parentOpSet); @@ -102,8 +124,8 @@ public class CallJabberImpl if (remoteParty == null) remoteParty = jingleIQ.getFrom(); - CallPeerJabberImpl callPeer = new CallPeerJabberImpl(remoteParty, this, - jingleIQ); + CallPeerJabberImpl callPeer + = new CallPeerJabberImpl(remoteParty, this, jingleIQ); addCallPeer(callPeer); @@ -130,9 +152,8 @@ public class CallJabberImpl = getProtocolProvider(); basicTelephony = (OperationSetBasicTelephonyJabberImpl) - protocolProvider - .getOperationSet( - OperationSetBasicTelephony.class); + protocolProvider.getOperationSet( + OperationSetBasicTelephony.class); CallJabberImpl attendantCall = basicTelephony .getActiveCallsRepository() @@ -140,9 +161,7 @@ public class CallJabberImpl if (attendantCall != null) { - attendant - = attendantCall.getPeer(sid); - + attendant = attendantCall.getPeer(sid); if ((attendant != null) && basicTelephony .getFullCalleeURI(attendant.getAddress()) @@ -170,14 +189,18 @@ public class CallJabberImpl CoinPacketExtension coin = (CoinPacketExtension) - jingleIQ.getExtension( - CoinPacketExtension.ELEMENT_NAME, - CoinPacketExtension.NAMESPACE); + jingleIQ.getExtension( + CoinPacketExtension.ELEMENT_NAME, + CoinPacketExtension.NAMESPACE); if(coin != null) { - boolean b = (Boolean.parseBoolean((String) - coin.getAttribute(CoinPacketExtension.ISFOCUS_ATTR_NAME))); + boolean b + = Boolean.parseBoolean( + (String) + coin.getAttribute( + CoinPacketExtension.ISFOCUS_ATTR_NAME)); + callPeer.setConferenceFocus(b); } @@ -223,7 +246,8 @@ public class CallJabberImpl } catch(Exception e) { - logger.info("Exception occurred while answer transferred call", + logger.info( + "Exception occurred while answer transferred call", e); callPeer = null; } @@ -235,8 +259,10 @@ public class CallJabberImpl } catch(OperationFailedException e) { - logger.error("Failed to hang up on attendant as part of " + - "session transfer", e); + logger.error( + "Failed to hang up on attendant as part of session" + + " transfer", + e); } return callPeer; @@ -245,27 +271,24 @@ public class CallJabberImpl /* see if offer contains audio and video so that we can propose * option to the user (i.e. answer with video if it is a video call...) */ - List offer = - callPeer.getSessionIQ().getContentList(); - Map directions = new HashMap(); + List offer + = callPeer.getSessionIQ().getContentList(); + Map directions + = new HashMap(); directions.put(MediaType.AUDIO, MediaDirection.INACTIVE); directions.put(MediaType.VIDEO, MediaDirection.INACTIVE); for(ContentPacketExtension c : offer) { - MediaDirection remoteDirection = JingleUtils.getDirection( - c, callPeer.isInitiator()); + String contentName = c.getName(); + MediaDirection remoteDirection + = JingleUtils.getDirection(c, callPeer.isInitiator()); - if(c.getName().equals(MediaType.AUDIO.toString())) - { + if(MediaType.AUDIO.toString().equals(contentName)) directions.put(MediaType.AUDIO, remoteDirection); - } - else if(c.getName().equals(MediaType.VIDEO.toString())) - { + else if(MediaType.VIDEO.toString().equals(contentName)) directions.put(MediaType.VIDEO, remoteDirection); - } } // if this was the first peer we added in this call then the call is @@ -325,9 +348,8 @@ public class CallJabberImpl CallEvent event = new CallEvent(this, CallEvent.CALL_INITIATED); AbstractOperationSetTelephonyConferencing opSet = (AbstractOperationSetTelephonyConferencing) - getProtocolProvider() - .getOperationSet( - OperationSetTelephonyConferencing.class); + getProtocolProvider().getOperationSet( + OperationSetTelephonyConferencing.class); if (opSet != null) opSet.outgoingCallCreated(event); @@ -435,10 +457,14 @@ public class CallJabberImpl } /** - * Notified when a call are added to a CallGroup. + * Notifies this instance that a specific Call has been added to a + * CallGroup. * - * @param evt event + * @param evt a CallGroupEvent which specifies the Call + * which has been added to a CallGroup + * @see MediaAwareCall#callAdded(CallGroupEvent) */ + @Override public void callAdded(CallGroupEvent evt) { Iterator peers = getCallPeers(); @@ -447,6 +473,7 @@ public class CallJabberImpl while(peers.hasNext()) { setConferenceFocus(true); + CallPeerJabberImpl callPeer = peers.next(); if(callPeer.getState() == CallPeerState.CONNECTED) @@ -455,4 +482,294 @@ public class CallJabberImpl super.callAdded(evt); } + + /** + * Closes a specific CobriStreamConnector which is associated with + * a MediaStream of a specific MediaType upon request from + * a specific CallPeer. + * + * @param peer the CallPeer which requests the closing of the + * specified cobriStreamConnector + * @param mediaType the MediaType of the MediaStream with + * which the specified cobriStreamConnector is associated + * @param cobriStreamConnector the CobriStreamConnector to close on + * behalf of the specified peer + */ + public void closeCobriStreamConnector( + CallPeerJabberImpl peer, + MediaType mediaType, + CobriStreamConnector cobriStreamConnector) + { + cobriStreamConnector.close(); + } + + /** + * Allocates cobri (conference) channels for asp ecific MediaType + * to be used by a specific CallPeer. + * + * @param peer the CallPeer which is to use the allocated cobri + * (conference) channels + * @param mediaTypes the MediaTypes for which cobri (conference) + * channels are to be allocated + * @return a CobriConferenceIQ which describes the allocated cobri + * (conference) channels for the specified mediaTypes which are to + * be used by the specified peer; otherwise, null + */ + public CobriConferenceIQ createCobriChannels( + CallPeerJabberImpl peer, + Iterable mediaTypes) + { + if (!isConferenceFocus()) + return null; + + /* + * For a cobri conference to work properly, all CallPeers in the + * conference must share one and the same CallPeerMediaHandler state + * i.e. they must use a single set of MediaStreams as if there was a + * single CallPeerMediaHandler. + */ + CallPeerMediaHandler peerMediaHandler = peer.getMediaHandler(); + + if (peerMediaHandler.getMediaHandler() != cobriMediaHandler) + { + for (MediaType mediaType : MediaType.values()) + if (peerMediaHandler.getStream(mediaType) != null) + return null; + } + + ProtocolProviderServiceJabberImpl protocolProvider + = getProtocolProvider(); + String jitsiVideoBridge + = (cobri == null) + ? protocolProvider.getJitsiVideoBridge() + : cobri.getFrom(); + + if ((jitsiVideoBridge == null) || (jitsiVideoBridge.length() == 0)) + return null; + + CobriConferenceIQ conferenceRequest = new CobriConferenceIQ(); + + if (cobri != null) + conferenceRequest.setID(cobri.getID()); + + for (MediaType mediaType : mediaTypes) + { + String contentName = mediaType.toString(); + CobriConferenceIQ.Content contentRequest + = new CobriConferenceIQ.Content(contentName); + + conferenceRequest.addContent(contentRequest); + + boolean requestLocalChannel = true; + + if (cobri != null) + { + CobriConferenceIQ.Content content + = cobri.getContent(contentName); + + if ((content != null) && (content.getChannelCount() > 0)) + requestLocalChannel = false; + } + if (requestLocalChannel) + { + CobriConferenceIQ.Channel localChannelRequest + = new CobriConferenceIQ.Channel(); + + contentRequest.addChannel(localChannelRequest); + } + + CobriConferenceIQ.Channel remoteChannelRequest + = new CobriConferenceIQ.Channel(); + + contentRequest.addChannel(remoteChannelRequest); + } + + XMPPConnection connection = protocolProvider.getConnection(); + PacketCollector packetCollector + = connection.createPacketCollector( + new PacketIDFilter(conferenceRequest.getPacketID())); + + conferenceRequest.setTo(jitsiVideoBridge); + conferenceRequest.setType(IQ.Type.GET); + connection.sendPacket(conferenceRequest); + + Packet response + = packetCollector.nextResult( + SmackConfiguration.getPacketReplyTimeout()); + + packetCollector.cancel(); + + if ((response == null) + || (response.getError() != null) + || !(response instanceof CobriConferenceIQ)) + return null; + + CobriConferenceIQ conferenceResponse = (CobriConferenceIQ) response; + String conferenceResponseID = conferenceResponse.getID(); + + /* + * Update the complete VideoBridgeConferenceIQ + * representation maintained by this instance with the + * information given by the (current) response. + */ + if (cobri == null) + { + cobri = conferenceResponse; + } + else + { + String cobriID = cobri.getID(); + + if (cobriID == null) + cobri.setID(conferenceResponseID); + else if (!cobriID.equals(conferenceResponseID)) + throw new IllegalStateException("conference.id"); + + for (CobriConferenceIQ.Content contentResponse + : conferenceResponse.getContents()) + { + CobriConferenceIQ.Content content + = cobri.getOrCreateContent(contentResponse.getName()); + + for (CobriConferenceIQ.Channel channelResponse + : contentResponse.getChannels()) + content.addChannel(channelResponse); + } + } + + /* + * Formulate the result to be returned to the caller which + * is a subset of the whole conference information kept by + * this CallJabberImpl and includes the remote channels + * explicitly requested by the method caller and their + * respective local channels. + */ + CobriConferenceIQ conferenceResult = new CobriConferenceIQ(); + + conferenceResult.setID(conferenceResponseID); + + for (MediaType mediaType : mediaTypes) + { + CobriConferenceIQ.Content contentResponse + = conferenceResponse.getContent(mediaType.toString()); + + if (contentResponse != null) + { + String contentName = contentResponse.getName(); + CobriConferenceIQ.Content contentResult + = new CobriConferenceIQ.Content(contentName); + + conferenceResult.addContent(contentResult); + + /* + * The local channel may have been allocated in a + * previous method call as part of the allocation of + * the first remote channel in the respective + * content. Anyway, the current method caller still + * needs to know about it. + */ + CobriConferenceIQ.Content content + = cobri.getContent(contentName); + CobriConferenceIQ.Channel localChannel = null; + + if ((content != null) && (content.getChannelCount() > 0)) + { + localChannel = content.getChannel(0); + contentResult.addChannel(localChannel); + } + + String localChannelID + = (localChannel == null) ? null : localChannel.getID(); + + for (CobriConferenceIQ.Channel channelResponse + : contentResponse.getChannels()) + { + if ((localChannelID == null) + || !localChannelID.equals(channelResponse.getID())) + contentResult.addChannel(channelResponse); + } + } + } + + /* + * The specified CallPeer will participate in the cobri conference + * organized by this Call so it must use the shared CallPeerMediaHandler + * state of all CallPeers in the same cobri conference. + */ + if (cobriMediaHandler == null) + cobriMediaHandler = new MediaHandler(); + peerMediaHandler.setMediaHandler(cobriMediaHandler); + + return conferenceResult; + } + + /** + * Initializes a CobriStreamConnector on behalf of a specific + * CallPeer to be used in association with a specific + * CobriConferenceIQ.Channel of a specific MediaType. + * + * @param peer the CallPeer which requests the initialization of a + * CobriStreamConnector + * @param mediaType the MediaType of the stream which is to use the + * initialized CobriStreamConnector for RTP and RTCP traffic + * @param channel the CobriConferenceIQ.Channel to which RTP and + * RTCP traffic is to be sent and from which such traffic is to be received + * via the initialized CobriStreamConnector + * @param factory a StreamConnectorFactory implementation which is + * to allocate the sockets to be used for RTP and RTCP traffic + * @return a CobriStreamConnector to be used for RTP and RTCP + * traffic associated with the specified channel + */ + public CobriStreamConnector createCobriStreamConnector( + CallPeerJabberImpl peer, + MediaType mediaType, + CobriConferenceIQ.Channel channel, + StreamConnectorFactory factory) + { + String channelID = channel.getID(); + + if (channelID == null) + throw new IllegalArgumentException("channel"); + + if (cobri == null) + throw new IllegalStateException("cobri"); + + CobriConferenceIQ.Content content + = cobri.getContent(mediaType.toString()); + + if (content == null) + throw new IllegalArgumentException("mediaType"); + if ((content.getChannelCount() < 1) + || !channelID.equals((channel = content.getChannel(0)).getID())) + throw new IllegalArgumentException("channel"); + + CobriStreamConnector cobriStreamConnector; + + synchronized (cobriStreamConnectors) + { + int index = mediaType.ordinal(); + WeakReference weakReference + = cobriStreamConnectors.get(index); + + cobriStreamConnector + = (weakReference == null) ? null : weakReference.get(); + if (cobriStreamConnector == null) + { + StreamConnector streamConnector + = factory.createStreamConnector(); + + if (streamConnector != null) + { + cobriStreamConnector + = new CobriStreamConnector(streamConnector); + cobriStreamConnectors.set( + index, + new WeakReference( + cobriStreamConnector)); + } + } + } + + return cobriStreamConnector; + } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java b/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java index 3c5d0c9..a21d9e1 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java @@ -6,8 +6,10 @@ */ package net.java.sip.communicator.impl.protocol.jabber; +import java.net.*; import java.util.*; +import net.java.sip.communicator.impl.protocol.jabber.extensions.cobri.*; import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*; import net.java.sip.communicator.impl.protocol.jabber.jinglesdp.*; import net.java.sip.communicator.service.neomedia.*; @@ -38,6 +40,15 @@ public class RawUdpTransportManager = new LinkedList>(); /** + * The information pertaining to the Jisti VideoBridge conference which the + * local peer represented by this instance is a focus of. It gives a view of + * the whole Jitsi VideoBridge conference managed by the associated + * CallJabberImpl which provides information specific to this + * RawUdpTransportManager only. + */ + private CobriConferenceIQ cobri; + + /** * Creates a new instance of this transport manager, binding it to the * specified peer. * @@ -50,9 +61,90 @@ public class RawUdpTransportManager } /** + * Closes a specific StreamConnector associated with a specific + * MediaType. If this TransportManager has a reference to + * the specified streamConnector, it remains. + * + * @param mediaType the MediaType associated with the specified + * streamConnector + * @param streamConnector the StreamConnector to be closed + */ + @Override + protected void closeStreamConnector( + MediaType mediaType, + StreamConnector streamConnector) + { + if (streamConnector instanceof CobriStreamConnector) + { + CallPeerJabberImpl peer = getCallPeer(); + CallJabberImpl call = peer.getCall(); + + call.closeCobriStreamConnector( + peer, + mediaType, + (CobriStreamConnector) streamConnector); + } + else + super.closeStreamConnector(mediaType, streamConnector); + } + + /** + * Creates a media StreamConnector for a stream of a specific + * MediaType. + * + * @param mediaType the MediaType of the stream for which a + * StreamConnector is to be created + * @return a StreamConnector for the stream of the specified + * mediaType + * @throws OperationFailedException if the binding of the sockets fails + */ + @Override + protected StreamConnector createStreamConnector(final MediaType mediaType) + throws OperationFailedException + { + CobriConferenceIQ.Channel channel = getCobriChannel(mediaType, true); + + if (channel != null) + { + CallPeerJabberImpl peer = getCallPeer(); + CallJabberImpl call = peer.getCall(); + StreamConnector streamConnector + = call.createCobriStreamConnector( + peer, + mediaType, + channel, + new StreamConnectorFactory() + { + public StreamConnector createStreamConnector() + { + try + { + return + RawUdpTransportManager + .super + .createStreamConnector( + mediaType); + } + catch (OperationFailedException ofe) + { + return null; + } + } + }); + + if (streamConnector != null) + return streamConnector; + } + + return super.createStreamConnector(mediaType); + } + + /** * Creates a raw UDP transport element according to the specified stream * connector. * + * @param mediaType the MediaType of the MediaStream which + * uses the specified connector * @param connector the connector that we'd like to describe within the * transport element. * @@ -60,34 +152,60 @@ public class RawUdpTransportManager * RTCP candidates of the specified {@link StreamConnector}. */ private RawUdpTransportPacketExtension createTransport( - StreamConnector connector) + MediaType mediaType, + StreamConnector connector) { + CobriConferenceIQ.Channel channel = getCobriChannel(mediaType, false); + RawUdpTransportPacketExtension ourTransport = new RawUdpTransportPacketExtension(); + int generation = getCurrentGeneration(); // create and add candidates that correspond to the stream connector // RTP CandidatePacketExtension rtpCand = new CandidatePacketExtension(); + rtpCand.setComponent(CandidatePacketExtension.RTP_COMPONENT_ID); - rtpCand.setGeneration(getCurrentGeneration()); + rtpCand.setGeneration(generation); rtpCand.setID(getNextID()); - rtpCand.setIP(connector.getDataSocket().getLocalAddress() - .getHostAddress()); - rtpCand.setPort(connector.getDataSocket().getLocalPort()); rtpCand.setType(CandidateType.host); + if (channel == null) + { + DatagramSocket dataSocket = connector.getDataSocket(); + + rtpCand.setIP(dataSocket.getLocalAddress().getHostAddress()); + rtpCand.setPort(dataSocket.getLocalPort()); + } + else + { + rtpCand.setIP(channel.getHost()); + rtpCand.setPort(channel.getRTPPort()); + } + ourTransport.addCandidate(rtpCand); // RTCP CandidatePacketExtension rtcpCand = new CandidatePacketExtension(); + rtcpCand.setComponent(CandidatePacketExtension.RTCP_COMPONENT_ID); - rtcpCand.setGeneration(getCurrentGeneration()); + rtcpCand.setGeneration(generation); rtcpCand.setID(getNextID()); - rtcpCand.setIP(connector.getControlSocket().getLocalAddress() - .getHostAddress()); - rtcpCand.setPort(connector.getControlSocket().getLocalPort()); rtcpCand.setType(CandidateType.host); + if (channel == null) + { + DatagramSocket controlSocket = connector.getControlSocket(); + + rtcpCand.setIP(controlSocket.getLocalAddress().getHostAddress()); + rtcpCand.setPort(controlSocket.getLocalPort()); + } + else + { + rtcpCand.setIP(channel.getHost()); + rtcpCand.setPort(channel.getRTCPPort()); + } + ourTransport.addCandidate(rtcpCand); return ourTransport; @@ -121,7 +239,26 @@ public class RawUdpTransportManager if (mediaType.equals(contentMediaType)) { - streamTarget = JingleUtils.extractDefaultTarget(content); + CobriConferenceIQ.Channel channel + = getCobriChannel(mediaType, true); + + if (channel == null) + { + streamTarget + = JingleUtils.extractDefaultTarget(content); + } + else + { + streamTarget + = new MediaStreamTarget( + new InetSocketAddress( + channel.getHost(), + channel.getRTPPort()), + new InetSocketAddress( + channel.getHost(), + channel.getRTPPort())); + } + break; } } @@ -130,6 +267,45 @@ public class RawUdpTransportManager } /** + * Gets the {@link CobriConferenceIQ.Channel} which belongs to a content + * associated with a specific MediaType and is to be either locally + * or remotely used. + * + * @param mediaType the MediaType associated with the content which + * contains the CobriConferenceIQ.Channel to get + * @param local true if the CobriConferenceIQ.Channel + * which is to be used locally is to be returned or false for the + * one which is to be used remotely + * @return the CobriConferenceIQ.Channel which belongs to a content + * associated with the specified mediaType and which is to be used + * in accord with the specified local indicator if such a channel + * exists; otherwise, null + */ + private CobriConferenceIQ.Channel getCobriChannel( + MediaType mediaType, + boolean local) + { + CobriConferenceIQ.Channel channel = null; + + if (cobri != null) + { + CobriConferenceIQ.Content content + = cobri.getContent(mediaType.toString()); + + if (content != null) + { + List channels + = content.getChannels(); + + if (channels.size() == 2) + channel = channels.get(local ? 0 : 1); + } + } + + return channel; + } + + /** * Implements {@link TransportManagerJabberImpl#getXmlNamespace()}. Gets the * XML namespace of the Jingle transport implemented by this * TransportManagerJabberImpl. @@ -210,31 +386,12 @@ public class RawUdpTransportManager * {@link #wrapupCandidateHarvest()}. * @throws OperationFailedException in case we fail allocating ports */ - public void startCandidateHarvest(List ourOffer, - TransportInfoSender transportInfoSender) + public void startCandidateHarvest( + List ourOffer, + TransportInfoSender transportInfoSender) throws OperationFailedException { - for(ContentPacketExtension content : ourOffer) - { - RtpDescriptionPacketExtension rtpDesc - = content.getFirstChildOfType( - RtpDescriptionPacketExtension.class); - - StreamConnector connector - = getStreamConnector( - MediaType.parseString( rtpDesc.getMedia())); - - RawUdpTransportPacketExtension ourTransport - = createTransport(connector); - - //now add our transport to our offer - ContentPacketExtension cpExt - = findContentByName(ourOffer, content.getName()); - - cpExt.addChildExtension(ourTransport); - } - - this.local = ourOffer; + startCandidateHarvest(null, ourOffer, transportInfoSender); } /** @@ -269,23 +426,106 @@ public class RawUdpTransportManager TransportInfoSender transportInfoSender) throws OperationFailedException { - for(ContentPacketExtension content : theirOffer) + CallPeerJabberImpl peer = getCallPeer(); + CallJabberImpl call = peer.getCall(); + List cpes + = (theirOffer == null) ? ourAnswer : theirOffer; + + /* + * If Jitsi VideoBridge is to be used, determine which channels are to + * be allocated and attempt to allocate them now. + */ + if (call.isConferenceFocus()) + { + List mediaTypes = new ArrayList(); + + for (ContentPacketExtension cpe : cpes) + { + RtpDescriptionPacketExtension rtpDesc + = cpe.getFirstChildOfType( + RtpDescriptionPacketExtension.class); + MediaType mediaType = MediaType.parseString(rtpDesc.getMedia()); + + /* + * The existence of a content for the mediaType and regardless + * of the existence of channels in it signals that a channel + * allocation request has already been sent for that mediaType. + */ + if ((cobri == null) + || (cobri.getContent(mediaType.toString()) == null)) + { + if (!mediaTypes.contains(mediaType)) + mediaTypes.add(mediaType); + } + } + if (mediaTypes.size() != 0) + { + /* + * We are about to request the channel allocations for + * mediaTypes. Regardless of the response, we do not want to + * repeat these requests. + */ + if (cobri == null) + cobri = new CobriConferenceIQ(); + for (MediaType mediaType : mediaTypes) + cobri.getOrCreateContent(mediaType.toString()); + + CobriConferenceIQ conferenceResult + = call.createCobriChannels(peer, mediaTypes); + + if (conferenceResult != null) + { + String videoBridgeID = cobri.getID(); + String conferenceResultID = conferenceResult.getID(); + + if (videoBridgeID == null) + cobri.setID(conferenceResultID); + else if (!videoBridgeID.equals(conferenceResultID)) + throw new IllegalStateException("conference.id"); + + for (CobriConferenceIQ.Content contentResult + : conferenceResult.getContents()) + { + CobriConferenceIQ.Content content + = cobri.getOrCreateContent( + contentResult.getName()); + + for (CobriConferenceIQ.Channel channelResult + : contentResult.getChannels()) + { + if (content.getChannel(channelResult.getID()) + == null) + content.addChannel(channelResult); + } + } + } + } + } + + /* + * RawUdpTransportManager#startCandidateHarvest( + * List, TransportInfoSender) delegates here + * because the implementations are pretty much identical and it's just + * that there's no theirOffer and ourAnswer is in fact our offer to + * which their answer is expected. + */ + for (ContentPacketExtension cpe : cpes) { RtpDescriptionPacketExtension rtpDesc - = content.getFirstChildOfType( + = cpe.getFirstChildOfType( RtpDescriptionPacketExtension.class); - StreamConnector connector - = getStreamConnector(MediaType.parseString(rtpDesc.getMedia())); + MediaType mediaType = MediaType.parseString(rtpDesc.getMedia()); + StreamConnector connector = getStreamConnector(mediaType); RawUdpTransportPacketExtension ourTransport - = createTransport(connector); + = createTransport(mediaType, connector); //now add our transport to our answer - ContentPacketExtension cpExt - = findContentByName(ourAnswer, content.getName()); + ContentPacketExtension ourCpe + = findContentByName(ourAnswer, cpe.getName()); //it might be that we decided not to reply to this content - if(cpExt != null) - cpExt.addChildExtension(ourTransport); + if (ourCpe != null) + ourCpe.addChildExtension(ourTransport); } this.local = ourAnswer; diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/cobri/CobriStreamConnector.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/cobri/CobriStreamConnector.java new file mode 100644 index 0000000..288da0e --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/cobri/CobriStreamConnector.java @@ -0,0 +1,61 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.cobri; + +import net.java.sip.communicator.service.neomedia.*; + +/** + * Implements a StreamConnector which allows sharing a specific + * StreamConnector instance among multiple TransportManagers + * for the purposes of the Jitsi VideoBridge. + * + * @author Lyubomir Marinov + */ +public class CobriStreamConnector + extends StreamConnectorDelegate +{ + /** + * Initializes a new CobriStreamConnector instance which is to + * share a specific StreamConnector instance among multiple + * TransportManagers for the purposes of the Jitsi VideoBridge. + * + * @param streamConnector the StreamConnector instance to be shared + * by the new instance among multiple TransportManagers for the + * purposes of the Jitsi VideoBridge + */ + public CobriStreamConnector(StreamConnector streamConnector) + { + super(streamConnector); + } + + @Override + public void close() + { + /* + * Do not close the shared StreamConnector because it is not clear + * whether no TransportManager is using it. + */ + } + + @Override + protected void finalize() + throws Throwable + { + try + { + /* + * Close the shared StreamConnector because it is clear that no + * TrasportManager is using it. + */ + super.close(); + } + finally + { + super.finalize(); + } + } +} diff --git a/src/net/java/sip/communicator/service/neomedia/StreamConnectorDelegate.java b/src/net/java/sip/communicator/service/neomedia/StreamConnectorDelegate.java new file mode 100644 index 0000000..1119231 --- /dev/null +++ b/src/net/java/sip/communicator/service/neomedia/StreamConnectorDelegate.java @@ -0,0 +1,88 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.service.neomedia; + +import java.net.*; + +/** + * Implements a {@link StreamConnector} which wraps a specific + * StreamConnector instance. + * + * @param the very type of the StreamConnector wrapped by + * StreamConnectorDelegate + * + * @author Lyubomir Marinov + */ +public class StreamConnectorDelegate + implements StreamConnector +{ + /** + * The StreamConnector wrapped by this instance. + */ + protected final T streamConnector; + + /** + * Initializes a new StreamConnectorDelegate which is to wrap a + * specific StreamConnector. + * + * @param streamConnector the StreamConnector to be wrapped by the + * new instance + */ + public StreamConnectorDelegate(T streamConnector) + { + if (streamConnector == null) + throw new NullPointerException("streamConnector"); + + this.streamConnector = streamConnector; + } + + /** + * Releases the resources allocated by this instance in the course of its + * execution and prepares it to be garbage collected. Calls + * {@link StreamConnector#close()} on the StreamConnector wrapped + * by this instance. + */ + public void close() + { + streamConnector.close(); + } + + public DatagramSocket getControlSocket() + { + return streamConnector.getControlSocket(); + } + + public Socket getControlTCPSocket() + { + return streamConnector.getControlTCPSocket(); + } + + public DatagramSocket getDataSocket() + { + return streamConnector.getDataSocket(); + } + + public Socket getDataTCPSocket() + { + return streamConnector.getDataTCPSocket(); + } + + public Protocol getProtocol() + { + return streamConnector.getProtocol(); + } + + public void started() + { + streamConnector.started(); + } + + public void stopped() + { + streamConnector.stopped(); + } +} diff --git a/src/net/java/sip/communicator/service/neomedia/StreamConnectorFactory.java b/src/net/java/sip/communicator/service/neomedia/StreamConnectorFactory.java new file mode 100644 index 0000000..3a662f9 --- /dev/null +++ b/src/net/java/sip/communicator/service/neomedia/StreamConnectorFactory.java @@ -0,0 +1,22 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.service.neomedia; + +/** + * Represents a factory of StreamConnector instances. + * + * @author Lyubomir Marinov + */ +public interface StreamConnectorFactory +{ + /** + * Initializes a StreamConnector instance. + * + * @return a StreamConnector instance + */ + public StreamConnector createStreamConnector(); +} diff --git a/src/net/java/sip/communicator/service/neomedia/VideoMediaStream.java b/src/net/java/sip/communicator/service/neomedia/VideoMediaStream.java index 980b619..f9bdcee 100644 --- a/src/net/java/sip/communicator/service/neomedia/VideoMediaStream.java +++ b/src/net/java/sip/communicator/service/neomedia/VideoMediaStream.java @@ -11,7 +11,7 @@ import java.util.*; import java.util.List; import net.java.sip.communicator.service.neomedia.control.*; -import net.java.sip.communicator.service.neomedia.event.*; +import net.java.sip.communicator.util.event.*; /** * Extends the MediaStream interface and adds methods specific to diff --git a/src/net/java/sip/communicator/service/neomedia/event/SizeChangeVideoEvent.java b/src/net/java/sip/communicator/service/neomedia/event/SizeChangeVideoEvent.java deleted file mode 100644 index 5db15b4..0000000 --- a/src/net/java/sip/communicator/service/neomedia/event/SizeChangeVideoEvent.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. - */ -package net.java.sip.communicator.service.neomedia.event; - -import java.awt.*; - -/** - * Represents a VideoEvent which notifies about an update to the size - * of a specific visual Component depicting video. - * - * @author Lubomir Marinov - */ -public class SizeChangeVideoEvent - extends VideoEvent -{ - /** - * Serial version UID. - */ - private static final long serialVersionUID = 0L; - - /** - * The type of a VideoEvent which notifies about an update to the - * size of a specific visual Component depicting video. - */ - public static final int VIDEO_SIZE_CHANGE = 3; - - /** - * The new height of the associated visual Component. - */ - private final int height; - - /** - * The new width of the associated visual Component. - */ - private final int width; - - /** - * Initializes a new SizeChangeVideoEvent which is to notify about - * an update to the size of a specific visual Component depicting - * video. - * - * @param source the source of the new SizeChangeVideoEvent - * @param visualComponent the visual Component depicting video - * with the updated size - * @param origin the origin of the video the new - * SizeChangeVideoEvent is to notify about - * @param width the new width of visualComponent - * @param height the new height of visualComponent - */ - public SizeChangeVideoEvent( - Object source, - Component visualComponent, - int origin, - int width, - int height) - { - super(source, VIDEO_SIZE_CHANGE, visualComponent, origin); - - this.width = width; - this.height = height; - } - - /** - * Gets the new height of the associated visual Component. - * - * @return the new height of the associated visual Component - */ - public int getHeight() - { - return height; - } - - /** - * Gets the new width of the associated visual Component. - * - * @return the new width of the associated visual Component - */ - public int getWidth() - { - return width; - } -} diff --git a/src/net/java/sip/communicator/service/neomedia/event/VideoEvent.java b/src/net/java/sip/communicator/service/neomedia/event/VideoEvent.java deleted file mode 100644 index 6135f47..0000000 --- a/src/net/java/sip/communicator/service/neomedia/event/VideoEvent.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. - */ -package net.java.sip.communicator.service.neomedia.event; - -import java.awt.*; -import java.util.*; - -/** - * Represents an event fired by providers of visual Components - * depicting video to notify about changes in the availability of such - * Components. - * - * @author Lubomir Marinov - */ -public class VideoEvent - extends EventObject -{ - /** - * Serial version UID. - */ - private static final long serialVersionUID = 0L; - - /** - * The video origin of a VideoEvent which is local to the executing - * client such as a local video capture device. - */ - public static final int LOCAL = 1; - - /** - * The video origin of a VideoEvent which is remote to the - * executing client such as a video being remotely streamed from a - * CallPeer. - */ - public static final int REMOTE = 2; - - /** - * The type of a VideoEvent which notifies about a specific visual - * Component depicting video being made available by the firing - * provider. - */ - public static final int VIDEO_ADDED = 1; - - /** - * The type of a VideoEvent which notifies about a specific visual - * Component depicting video no longer being made available by the - * firing provider. - */ - public static final int VIDEO_REMOVED = 2; - - /** - * The indicator which determines whether 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). - */ - private boolean consumed; - - /** - * The origin of the video this VideoEvent notifies about which is - * one of {@link #LOCAL} and {@link #REMOTE}. - */ - private final int origin; - - /** - * The type of availability change this VideoEvent notifies about - * which is one of {@link #VIDEO_ADDED} and {@link #VIDEO_REMOVED}. - */ - private final int type; - - /** - * The visual Component depicting video which had its availability - * changed and which this VideoEvent notifies about. - */ - private final Component visualComponent; - - /** - * Initializes a new VideoEvent which is to notify about a specific - * change in the availability of a specific visual Component - * depicting video and being provided by a specific source. - * - * @param source the source of the new VideoEvent and the provider - * of the visual Component depicting video - * @param type the type of the availability change which has caused the new - * VideoEvent to be fired - * @param visualComponent the visual Component depicting video - * which had its availability in the source provider changed - * @param origin the origin of the video the new VideoEvent is to - * notify about - */ - public VideoEvent( - Object source, - int type, - Component visualComponent, - int origin) - { - super(source); - - this.type = type; - this.visualComponent = visualComponent; - this.origin = origin; - } - - /** - * Consumes this event and, more specifically, marks the Component - * it describes as owned, referenced in order to let other potential - * consumers know about its current ownership status (which is important - * because Components belong to a single Container at a - * time). - */ - public void consume() - { - consumed = true; - } - - /** - * Gets the origin of the video this VideoEvent notifies about - * which is one of {@link #LOCAL} and {@link #REMOTE}. - * - * @return one of {@link #LOCAL} and {@link #REMOTE} which specifies the - * origin of the video this VideoEvent notifies about - */ - public int getOrigin() - { - return origin; - } - - /** - * Gets the type of availability change this VideoEvent notifies - * about which is one of {@link #VIDEO_ADDED} and {@link #VIDEO_REMOVED}. - * - * @return one of {@link #VIDEO_ADDED} and {@link #VIDEO_REMOVED} which - * describes the type of availability change this VideoEvent - * notifies about - */ - public int getType() - { - return type; - } - - /** - * Gets the visual Component depicting video which had its - * availability changed and which this VideoEvent notifies about. - * - * @return the visual Component depicting video which had its - * availability changed and which this VideoEvent notifies about - */ - public Component getVisualComponent() - { - return visualComponent; - } - - /** - * Determines whether 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). - * - * @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 - */ - public boolean isConsumed() - { - return consumed; - } - - /** - * Returns a human-readable representation of a specific VideoEvent - * origin constant in the form of a String value. - * - * @param origin one of the VideoEvent origin constants such as - * {@link #LOCAL} or {@link #REMOTE} - * @return a String value which gives a human-readable - * representation of the specified VideoEvent origin - * constant - */ - public static String originToString(int origin) - { - switch (origin) - { - case VideoEvent.LOCAL: - return "LOCAL"; - case VideoEvent.REMOTE: - return "REMOTE"; - default: - throw new IllegalArgumentException("origin"); - } - } - - /** - * Returns a human-readable representation of a specific VideoEvent - * type constant in the form of a String value. - * - * @param type one of the VideoEvent type constants such as - * {@link #VIDEO_ADDED} or {@link #VIDEO_REMOVED} - * @return a String value which gives a human-readable - * representation of the specified VideoEvent type - * constant - */ - public static String typeToString(int type) - { - switch (type) - { - case VideoEvent.VIDEO_ADDED: - return "VIDEO_ADDED"; - case VideoEvent.VIDEO_REMOVED: - return "VIDEO_REMOVED"; - default: - throw new IllegalArgumentException("type"); - } - } -} diff --git a/src/net/java/sip/communicator/service/neomedia/event/VideoListener.java b/src/net/java/sip/communicator/service/neomedia/event/VideoListener.java deleted file mode 100644 index 50c7089..0000000 --- a/src/net/java/sip/communicator/service/neomedia/event/VideoListener.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. - */ -package net.java.sip.communicator.service.neomedia.event; - -import java.util.*; - -/** - * Defines the notification support informing about changes in the availability - * of visual Components representing video such as adding and - * removing. - * - * @author Lubomir Marinov - */ -public interface VideoListener - extends EventListener -{ - - /** - * Notifies that a visual Component representing video has been - * added to the provider this listener has been added to. - * - * @param event a VideoEvent describing the added visual - * Component representing video and the provider it was added into - */ - void videoAdded(VideoEvent event); - - /** - * Notifies that a visual Component representing video has been - * removed from the provider this listener has been added to. - * - * @param event a VideoEvent describing the removed visual - * Component representing video and the provider it was removed - * from - */ - void videoRemoved(VideoEvent event); - - /** - * Notifies about an update to a visual Component representing - * video. - * - * @param event a VideoEvent describing the visual - * Component related to the update and the details of the specific - * update - */ - void videoUpdate(VideoEvent event); -} diff --git a/src/net/java/sip/communicator/service/neomedia/event/VideoNotifierSupport.java b/src/net/java/sip/communicator/service/neomedia/event/VideoNotifierSupport.java deleted file mode 100644 index 6029b89..0000000 --- a/src/net/java/sip/communicator/service/neomedia/event/VideoNotifierSupport.java +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. - */ -package net.java.sip.communicator.service.neomedia.event; - -import java.awt.*; -import java.util.*; -import java.util.List; // disambiguation - -/** - * Represents a mechanism to easily add to a specific Object by means - * of composition support for firing VideoEvents to - * VideoListeners. - * - * @author Lyubomir Marinov - */ -public class VideoNotifierSupport -{ - private static final long THREAD_TIMEOUT = 5000; - - /** - * The list of VideoEvents which are to be delivered to the - * {@link #listeners} registered with this instance when - * {@link #synchronous} is equal to false. - */ - private final List events; - - /** - * The list of VideoListeners interested in changes in the - * availability of visual Components depicting video. - */ - private final List listeners - = new ArrayList(); - - /** - * The Object which is to be reported as the source of the - * VideoEvents fired by this instance. - */ - private final Object source; - - /** - * The indicator which determines whether this instance delivers the - * VideoEvents to the {@link #listeners} synchronously. - */ - private final boolean synchronous; - - /** - * The Thread in which {@link #events} are delivered to the - * {@link #listeners} when {@link #synchronous} is equal to false. - */ - private Thread thread; - - /** - * Initializes a new VideoNotifierSupport instance which is to - * facilitate the management of VideoListeners and firing - * VideoEvents to them for a specific Object. - * - * @param source the Object which is to be reported as the source - * of the VideoEvents fired by the new instance - */ - public VideoNotifierSupport(Object source) - { - this(source, true); - } - - /** - * Initializes a new VideoNotifierSupport instance which is to - * facilitate the management of VideoListeners and firing - * VideoEvents to them for a specific Object. - * - * @param source the Object which is to be reported as the source - * of the VideoEvents fired by the new instance - * @param synchronous true if the new instance is to deliver the - * VideoEvents synchronously; otherwise, false - */ - public VideoNotifierSupport(Object source, boolean synchronous) - { - this.source = source; - this.synchronous = synchronous; - - events = this.synchronous ? null : new LinkedList(); - } - - /** - * Adds a specific VideoListener to this - * VideoNotifierSupport in order to receive notifications when - * visual/video Components are being added and removed. - *

- * Adding a listener which has already been added does nothing i.e. it is - * not added more than once and thus does not receive one and the same - * VideoEvent multiple times. - *

- * - * @param listener the VideoListener to be notified when - * visual/video Components are being added or removed in this - * VideoNotifierSupport - */ - public void addVideoListener(VideoListener listener) - { - if (listener == null) - throw new NullPointerException("listener"); - - synchronized (listeners) - { - if (!listeners.contains(listener)) - listeners.add(listener); - } - } - - 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 VideoListeners registered with this - * VideoMediaStream 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 - * @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 true if the call is to wait till the specified - * VideoEvent has been delivered to the VideoListeners; - * otherwise, false - * @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 - */ - public boolean fireVideoEvent( - int type, Component visualComponent, int origin, - boolean wait) - { - VideoEvent event - = new VideoEvent(source, type, visualComponent, origin); - - fireVideoEvent(event, wait); - return event.isConsumed(); - } - - /** - * Notifies the VideoListeners registered with this instance about - * a specific VideoEvent. - * - * @param event the VideoEvent to be fired to the - * VideoListeners registered with this instance - * @param wait true if the call is to wait till the specified - * VideoEvent has been delivered to the VideoListeners; - * otherwise, false - */ - public void fireVideoEvent(VideoEvent event, boolean wait) - { - if (synchronous) - doFireVideoEvent(event); - else - { - synchronized (events) - { - 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(); - } - } - } - } - - /** - * Removes a specific VideoListener from this - * VideoNotifierSupport in order to have to no longer receive - * notifications when visual/video Components are being added and - * removed. - * - * @param listener the VideoListener to no longer be notified when - * visual/video Components are being added or removed - */ - public void removeVideoListener(VideoListener listener) - { - synchronized (listeners) - { - 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/notification/LogMessageNotificationAction.java b/src/net/java/sip/communicator/service/notification/LogMessageNotificationAction.java index 6efdb31..1a3f795 100644 --- a/src/net/java/sip/communicator/service/notification/LogMessageNotificationAction.java +++ b/src/net/java/sip/communicator/service/notification/LogMessageNotificationAction.java @@ -1,58 +1,58 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. - */ -package net.java.sip.communicator.service.notification; - -/** - * An implementation of the LogMessageNotificationHandler interface. - * - * @author Yana Stamcheva - */ -public class LogMessageNotificationAction - extends NotificationAction -{ - /** - * Indicates that this log is of type trace. If this logType is set - * the messages would be logged as trace logs. - */ - public static final String TRACE_LOG_TYPE = "TraceLog"; - - /** - * Indicates that this log is of type info. If this logType is set - * the messages would be logged as info logs. - */ - public static final String INFO_LOG_TYPE = "InfoLog"; - - /** - * Indicates that this log is of type error. If this logType is set - * the messages would be logged as error logs. - */ - public static final String ERROR_LOG_TYPE = "ErrorLog"; - - private String logType; - - /** - * Creates an instance of LogMessageNotificationHandlerImpl by - * specifying the log type. - * - * @param logType the type of the log - */ - public LogMessageNotificationAction(String logType) - { - super(NotificationAction.ACTION_LOG_MESSAGE); - this.logType = logType; - } - - /** - * Returns the type of the log - * - * @return the type of the log - */ - public String getLogType() - { - return logType; - } -} +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.service.notification; + +/** + * An implementation of the LogMessageNotificationHandler interface. + * + * @author Yana Stamcheva + */ +public class LogMessageNotificationAction + extends NotificationAction +{ + /** + * Indicates that this log is of type trace. If this logType is set + * the messages would be logged as trace logs. + */ + public static final String TRACE_LOG_TYPE = "TraceLog"; + + /** + * Indicates that this log is of type info. If this logType is set + * the messages would be logged as info logs. + */ + public static final String INFO_LOG_TYPE = "InfoLog"; + + /** + * Indicates that this log is of type error. If this logType is set + * the messages would be logged as error logs. + */ + public static final String ERROR_LOG_TYPE = "ErrorLog"; + + private String logType; + + /** + * Creates an instance of LogMessageNotificationHandlerImpl by + * specifying the log type. + * + * @param logType the type of the log + */ + public LogMessageNotificationAction(String logType) + { + super(NotificationAction.ACTION_LOG_MESSAGE); + this.logType = logType; + } + + /** + * Returns the type of the log + * + * @return the type of the log + */ + public String getLogType() + { + return logType; + } +} diff --git a/src/net/java/sip/communicator/service/protocol/AbstractCallPeer.java b/src/net/java/sip/communicator/service/protocol/AbstractCallPeer.java index 797dea8..10a2f6c 100644 --- a/src/net/java/sip/communicator/service/protocol/AbstractCallPeer.java +++ b/src/net/java/sip/communicator/service/protocol/AbstractCallPeer.java @@ -11,6 +11,7 @@ import java.util.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; +import net.java.sip.communicator.util.event.*; /** * Provides a default implementation for most of the CallPeer methods diff --git a/src/net/java/sip/communicator/service/protocol/AbstractConferenceMember.java b/src/net/java/sip/communicator/service/protocol/AbstractConferenceMember.java index 9e7d29f..a3a2bc6 100644 --- a/src/net/java/sip/communicator/service/protocol/AbstractConferenceMember.java +++ b/src/net/java/sip/communicator/service/protocol/AbstractConferenceMember.java @@ -6,13 +6,13 @@ */ package net.java.sip.communicator.service.protocol; -import net.java.sip.communicator.util.*; +import net.java.sip.communicator.util.event.*; /** * Provides the default implementation of the ConferenceMember * interface. * - * @author Lubomir Marinov + * @author Lyubomir Marinov * @author Yana Stamcheva * @author Emil Ivov */ diff --git a/src/net/java/sip/communicator/service/protocol/OperationSetVideoTelephony.java b/src/net/java/sip/communicator/service/protocol/OperationSetVideoTelephony.java index 186030f..6870764 100644 --- a/src/net/java/sip/communicator/service/protocol/OperationSetVideoTelephony.java +++ b/src/net/java/sip/communicator/service/protocol/OperationSetVideoTelephony.java @@ -12,7 +12,7 @@ import java.text.*; import java.util.List; import net.java.sip.communicator.service.neomedia.*; -import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.event.*; /** * Represents an OperationSet giving access to video-specific diff --git a/src/net/java/sip/communicator/service/protocol/event/SizeChangeVideoEvent.java b/src/net/java/sip/communicator/service/protocol/event/SizeChangeVideoEvent.java deleted file mode 100644 index 38bbe3d..0000000 --- a/src/net/java/sip/communicator/service/protocol/event/SizeChangeVideoEvent.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. - */ -package net.java.sip.communicator.service.protocol.event; - -import java.awt.*; - -/** - * Represents a VideoEvent which notifies about an update to the size - * of a specific visual Component depicting video. - * - * @author Lubomir Marinov - */ -public class SizeChangeVideoEvent - extends VideoEvent -{ - /** - * Serial version UID. - */ - private static final long serialVersionUID = 0L; - - /** - * The type of a VideoEvent which notifies about an update to the - * size of a specific visual Component depicting video. - */ - public static final int VIDEO_SIZE_CHANGE = 3; - - /** - * The new height of the associated visual Component. - */ - private final int height; - - /** - * The new width of the associated visual Component. - */ - private final int width; - - /** - * Initializes a new SizeChangeVideoEvent which is to notify about - * an update to the size of a specific visual Component depicting - * video. - * - * @param source the source of the new SizeChangeVideoEvent - * @param visualComponent the visual Component depicting video - * with the updated size - * @param origin the origin of the video the new - * SizeChangeVideoEvent is to notify about - * @param width the new width of visualComponent - * @param height the new height of visualComponent - */ - public SizeChangeVideoEvent( - Object source, - Component visualComponent, - int origin, - int width, - int height) - { - super(source, VIDEO_SIZE_CHANGE, visualComponent, origin); - - this.width = width; - this.height = height; - } - - /** - * Gets the new height of the associated visual Component. - * - * @return the new height of the associated visual Component - */ - public int getHeight() - { - return height; - } - - /** - * Gets the new width of the associated visual Component. - * - * @return the new width of the associated visual Component - */ - public int getWidth() - { - return width; - } -} diff --git a/src/net/java/sip/communicator/service/protocol/event/VideoEvent.java b/src/net/java/sip/communicator/service/protocol/event/VideoEvent.java deleted file mode 100644 index 329a446..0000000 --- a/src/net/java/sip/communicator/service/protocol/event/VideoEvent.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. - */ -package net.java.sip.communicator.service.protocol.event; - -import java.awt.*; -import java.util.*; - -/** - * Represents an event fired by providers of visual Components - * depicting video to notify about changes in the availability of such - * Components. - * - * @author Lubomir Marinov - */ -public class VideoEvent - extends EventObject -{ - /** - * Serial version UID. - */ - private static final long serialVersionUID = 0L; - - /** - * The video origin of a VideoEvent which is local to the - * executing client such as a local video capture device. - */ - public static final int LOCAL = 1; - - /** - * The video origin of a VideoEvent which is remote to the - * executing client such as a video being remotely streamed from a - * CallPeer. - */ - public static final int REMOTE = 2; - - /** - * The type of a VideoEvent which notifies about a specific - * visual Component depicting video being made available by the - * firing provider. - */ - public static final int VIDEO_ADDED = 1; - - /** - * The type of a VideoEvent which notifies about a specific - * visual Component depicting video no longer being made - * available by the firing provider. - */ - public static final int VIDEO_REMOVED = 2; - - /** - * The indicator which determines whether 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). - */ - private boolean consumed; - - /** - * The origin of the video this VideoEvent notifies about which - * is one of {@link #LOCAL} and {@link #REMOTE}. - */ - private final int origin; - - /** - * The type of availability change this VideoEvent notifies - * about which is one of {@link #VIDEO_ADDED} and {@link #VIDEO_REMOVED}. - */ - private final int type; - - /** - * The visual Component depicting video which had its - * availability changed and which this VideoEvent notifies - * about. - */ - private final Component visualComponent; - - /** - * Initializes a new VideoEvent which is to notify about a - * specific change in the availability of a specific visual - * Component depicting video and being provided by a specific - * source. - * - * @param source the source of the new VideoEvent and the - * provider of the visual Component depicting video - * @param type the type of the availability change which has caused the new - * VideoEvent to be fired - * @param visualComponent the visual Component depicting video - * which had its availability in the source provider - * changed - * @param origin the origin of the video the new VideoEvent is - * to notify about - */ - public VideoEvent(Object source, int type, Component visualComponent, - int origin) - { - super(source); - - this.type = type; - this.visualComponent = visualComponent; - this.origin = origin; - } - - /** - * Consumes this event and, more specifically, marks the - * Component it describes as owned, referenced in order to let - * other potential consumers know about its current ownership status (which - * is important because Components belong to a single - * Container at a time). - */ - public void consume() - { - consumed = true; - } - - /** - * Gets the origin of the video this VideoEvent notifies about - * which is one of {@link #LOCAL} and {@link #REMOTE}. - * - * @return one of {@link #LOCAL} and {@link #REMOTE} which specifies the - * origin of the video this VideoEvent notifies about - */ - public int getOrigin() - { - return origin; - } - - /** - * Gets the type of availability change this VideoEvent - * notifies about which is one of {@link #VIDEO_ADDED} and - * {@link #VIDEO_REMOVED}. - * - * @return one of {@link #VIDEO_ADDED} and {@link #VIDEO_REMOVED} which - * describes the type of availability change this - * VideoEvent notifies about - */ - public int getType() - { - return type; - } - - /** - * Gets the visual Component depicting video which had its - * availability changed and which this VideoEvent notifies - * about. - * - * @return the visual Component depicting video which had its - * availability changed and which this VideoEvent - * notifies about - */ - public Component getVisualComponent() - { - return visualComponent; - } - - /** - * Determines whether 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). - * - * @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 - */ - public boolean isConsumed() - { - return consumed; - } -} diff --git a/src/net/java/sip/communicator/service/protocol/event/VideoListener.java b/src/net/java/sip/communicator/service/protocol/event/VideoListener.java deleted file mode 100644 index 0f46828..0000000 --- a/src/net/java/sip/communicator/service/protocol/event/VideoListener.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. - */ -package net.java.sip.communicator.service.protocol.event; - -import java.util.*; - -/** - * Defines the notification support informing about changes in the availability - * of visual Components representing video such as adding and removing. - * - * @author Lubomir Marinov - */ -public interface VideoListener - extends EventListener -{ - - /** - * Notifies that a visual Component representing video has been - * added to the provider this listener has been added to. - * - * @param event a VideoEvent describing the added visual - * Component representing video and the provider it was added into - */ - void videoAdded(VideoEvent event); - - /** - * Notifies that a visual Component representing video has been - * removed from the provider this listener has been added to. - * - * @param event a VideoEvent describing the removed visual - * Component representing video and the provider it was removed - * from - */ - void videoRemoved(VideoEvent event); - - /** - * Notifies about an update to a visual Component representing - * video. - * - * @param event a VideoEvent describing the visual - * Component related to the update and the details of the specific - * update - */ - void videoUpdate(VideoEvent event); -} diff --git a/src/net/java/sip/communicator/service/protocol/media/AbstractOperationSetVideoTelephony.java b/src/net/java/sip/communicator/service/protocol/media/AbstractOperationSetVideoTelephony.java index 39fb00a..43da709 100644 --- a/src/net/java/sip/communicator/service/protocol/media/AbstractOperationSetVideoTelephony.java +++ b/src/net/java/sip/communicator/service/protocol/media/AbstractOperationSetVideoTelephony.java @@ -13,7 +13,7 @@ import java.util.List; import net.java.sip.communicator.service.neomedia.*; import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.event.*; /** * Represents a default implementation of OperationSetVideoTelephony in diff --git a/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java b/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java index 1e8a2ba..cd50650 100644 --- a/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java +++ b/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java @@ -17,16 +17,13 @@ import net.java.sip.communicator.service.neomedia.device.*; import net.java.sip.communicator.service.neomedia.event.*; import net.java.sip.communicator.service.neomedia.format.*; import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.SizeChangeVideoEvent; -import net.java.sip.communicator.service.protocol.event.VideoEvent; -import net.java.sip.communicator.service.protocol.event.VideoListener; -import net.java.sip.communicator.util.*; +import net.java.sip.communicator.util.event.*; /** * A utility class implementing media control code shared between current * telephony implementations. This class is only meant for use by protocol - * implementations and should/could not be accessed by bundles that are simply - * using the telephony functionalities. + * implementations and should not be accessed by bundles that are simply using + * the telephony functionalities. * * @param the peer extension class like for example CallPeerSipImpl * or CallPeerJabberImpl @@ -34,18 +31,11 @@ import net.java.sip.communicator.util.*; * @author Emil Ivov * @author Lyubomir Marinov */ -public abstract class CallPeerMediaHandler< - T extends MediaAwareCallPeer> +public abstract class CallPeerMediaHandler + > extends PropertyChangeNotifier { /** - * The Logger used by the CallPeerMediaHandler - * class and its instances for logging output. - */ - private static final Logger logger - = Logger.getLogger(CallPeerMediaHandler.class); - - /** * The name of the CallPeerMediaHandler property which specifies * the local SSRC of its audio MediaStream. */ @@ -83,12 +73,24 @@ public abstract class CallPeerMediaHandler< = MediaDirection.RECVONLY; /** + * The VideoMediaStream which this instance uses to send and + * receive video. + */ + private VideoMediaStream videoStream; + + /** * Determines whether or not streaming local audio is currently enabled. */ private MediaDirection audioDirectionUserPreference = MediaDirection.SENDRECV; /** + * The AudioMediaStream which this instance uses to send and + * receive audio. + */ + private AudioMediaStream audioStream; + + /** * List of advertised encryption methods. Indicated before establishing the * call. */ @@ -102,42 +104,12 @@ public abstract class CallPeerMediaHandler< private final T peer; /** - * A reference to the object that would be responsible for SRTP control - * and which most often would be the peer itself. + * The SrtpListener which is responsible for the SRTP control. Most + * often than not, it is the peer itself. */ private final SrtpListener srtpListener; /** - * The RTP stream that this media handler uses to send audio. - */ - private AudioMediaStream audioStream = null; - - /** - * The last-known local SSRC of {@link #audioStream}. - */ - private long audioLocalSSRC = SSRC_UNKNOWN; - - /** - * The last-known remote SSRC of {@link #audioStream}. - */ - private long audioRemoteSSRC = SSRC_UNKNOWN; - - /** - * The RTP stream that this media handler uses to send video. - */ - private VideoMediaStream videoStream = null; - - /** - * The last-known local SSRC of {@link #videoStream}. - */ - private long videoLocalSSRC = SSRC_UNKNOWN; - - /** - * The last-known remote SSRC of {@link #videoStream}. - */ - private long videoRemoteSSRC = SSRC_UNKNOWN; - - /** * The listener that the CallPeer registered for local user audio * level events. */ @@ -179,12 +151,6 @@ public abstract class CallPeerMediaHandler< private boolean locallyOnHold = false; /** - * Indicates whether this handler has already started at least one of its - * streams, at least once. - */ - private boolean started = false; - - /** * Contains all dynamic payload type mappings that have been made for this * call. */ @@ -199,22 +165,14 @@ public abstract class CallPeerMediaHandler< = new DynamicRTPExtensionsRegistry(); /** - * Holds the SRTP controls used for the current call. - */ - private SortedMap srtpControls = - new TreeMap(); - - /** - * The KeyFrameControl currently known to this - * CallPeerMediaHandlerSipImpl and made available by - * {@link #videoStream}. + * The PropertyChangeListener which listens to changes in the + * values of the properties of the Call of {@link #peer}. */ - private KeyFrameControl keyFrameControl; + private final CallPropertyChangeListener callPropertyChangeListener; /** * The KeyFrameRequester implemented by this - * CallPeerMediaHandlerSipImpl and provided to - * {@link #keyFrameControl}. + * CallPeerMediaHandler. */ private final KeyFrameControl.KeyFrameRequester keyFrameRequester = new KeyFrameControl.KeyFrameRequester() @@ -226,117 +184,75 @@ public abstract class CallPeerMediaHandler< }; /** - * The List of VideoListeners interested in - * VideoEvents fired by this instance or rather its - * VideoMediaStream. + * The state of this instance which may be shared with multiple other + * CallPeerMediaHandlers. */ - private final List videoListeners - = new LinkedList(); + private MediaHandler mediaHandler; /** * The PropertyChangeListener which listens to changes in the - * values of the properties of {@link #audioStream} and - * {@link #videoStream}. + * values of the properties of the MediaStreams of this instance. */ private final PropertyChangeListener streamPropertyChangeListener = new PropertyChangeListener() - { - - /** - * Notifies this PropertyChangeListener that the value of - * a specific property of the notifier it is registered with has - * changed. - * - * @param evt a PropertyChangeEvent which describes the - * source of the event, the name of the property which has changed - * its value and the old and new values of the property - * @see PropertyChangeListener#propertyChange(PropertyChangeEvent) - */ - public void propertyChange(PropertyChangeEvent evt) { - String propertyName = evt.getPropertyName(); - - if (MediaStream.PNAME_LOCAL_SSRC.equals(propertyName)) + /** + * Notifies this PropertyChangeListener that the value of + * a specific property of the notifier it is registered with has + * changed. + * + * @param evt a PropertyChangeEvent which describes the + * source of the event, the name of the property which has changed + * its value and the old and new values of the property + * @see PropertyChangeListener#propertyChange(PropertyChangeEvent) + */ + public void propertyChange(PropertyChangeEvent evt) { - Object source = evt.getSource(); - - if (source == audioStream) - setAudioLocalSSRC(audioStream.getLocalSourceID()); - else if (source == videoStream) - setVideoLocalSSRC(videoStream.getLocalSourceID()); + firePropertyChange( + evt.getPropertyName(), + evt.getOldValue(), + evt.getNewValue()); } - else if (MediaStream.PNAME_REMOTE_SSRC.equals(propertyName)) - { - Object source = evt.getSource(); + }; - if (source == audioStream) - setAudioRemoteSSRC(audioStream.getRemoteSourceID()); - else if (source == videoStream) - setVideoRemoteSSRC(videoStream.getRemoteSourceID()); - } - } - }; + /** + * The aid which implements the boilerplate related to adding and removing + * VideoListeners and firing VideoEvents to them on behalf + * of this instance. + */ + private final VideoNotifierSupport videoNotifierSupport + = new VideoNotifierSupport(this, true); /** - * 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 + * The VideoListener which listens to the video + * MediaStream of this instance for changes in the availability of + * visual Components displaying remote video and re-fires them as * 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) + private final VideoListener videoStreamVideoListener + = new VideoListener() { - if (fireVideoEvent( - event.getType(), - event.getVisualComponent(), - event.getOrigin())) - event.consume(); - } + public void videoAdded(VideoEvent event) + { + VideoEvent clone = event.clone(CallPeerMediaHandler.this); - /** - * 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) - { - // VIDEO_REMOVED is forwarded the same way as VIDEO_ADDED is. - videoAdded(event); - } + fireVideoEvent(clone); + if (clone.isConsumed()) + event.consume(); + } - public void videoUpdate( - net.java.sip.communicator.service.neomedia.event.VideoEvent - event) - { - fireVideoEvent( - neomedia2protocol(event, CallPeerMediaHandler.this)); - } - }; + public void videoRemoved(VideoEvent event) + { + // Forwarded in the same way as VIDEO_ADDED. + videoAdded(event); + } - /** - * The PropertyChangeListener which listens to changes in the - * values of the properties of the Call of {@link #peer}. - */ - private final CallPropertyChangeListener callPropertyChangeListener; + public void videoUpdate(VideoEvent event) + { + // Forwarded in the same way as VIDEO_ADDED. + videoAdded(event); + } + }; /** * Creates a new handler that will be managing media streams for @@ -352,8 +268,10 @@ public abstract class CallPeerMediaHandler< this.peer = peer; this.srtpListener = srtpListener; + setMediaHandler(new MediaHandler()); + /* - * Listener to the call of peer in order to track the user's choice with + * Listen to the call of peer in order to track the user's choice with * respect to the default audio device. */ MediaAwareCall call = this.peer.getCall(); @@ -380,42 +298,51 @@ public abstract class CallPeerMediaHandler< { this.locallyOnHold = locallyOnHold; + // On hold. if(locallyOnHold) { + MediaStream audioStream = getStream(MediaType.AUDIO); + if(audioStream != null) { - audioStream.setDirection(audioStream.getDirection() - .and(MediaDirection.SENDONLY)); + audioStream.setDirection( + audioStream.getDirection().and( + MediaDirection.SENDONLY)); audioStream.setMute(locallyOnHold); } + + MediaStream videoStream = getStream(MediaType.VIDEO); + if(videoStream != null) { - videoStream.setDirection(videoStream.getDirection() - .and(MediaDirection.SENDONLY)); + videoStream.setDirection( + videoStream.getDirection().and( + MediaDirection.SENDONLY)); videoStream.setMute(locallyOnHold); } } - else + /* + * Off hold. Make sure that we re-enable sending only if other party is + * not on hold. + */ + else if (!CallPeerState.ON_HOLD_MUTUALLY.equals(getPeer().getState())) { - //off hold - make sure that we re-enable sending, only - // if other party is not on hold - if (CallPeerState.ON_HOLD_MUTUALLY.equals( - getPeer().getState())) - { - return; - } + MediaStream audioStream = getStream(MediaType.AUDIO); if(audioStream != null) { - audioStream.setDirection(audioStream.getDirection() - .or(MediaDirection.SENDONLY)); + audioStream.setDirection( + audioStream.getDirection().or(MediaDirection.SENDONLY)); audioStream.setMute(locallyOnHold); } - if(videoStream != null - && videoStream.getDirection() != MediaDirection.INACTIVE) + + MediaStream videoStream = getStream(MediaType.VIDEO); + + if((videoStream != null) + && (videoStream.getDirection() != MediaDirection.INACTIVE)) { - videoStream.setDirection(videoStream.getDirection() - .or(MediaDirection.SENDONLY)); + videoStream.setDirection( + videoStream.getDirection().or(MediaDirection.SENDONLY)); videoStream.setMute(locallyOnHold); } } @@ -436,34 +363,45 @@ public abstract class CallPeerMediaHandler< if (callPropertyChangeListener != null) callPropertyChangeListener.call.removePropertyChangeListener( callPropertyChangeListener); + + setMediaHandler(null); } /** - * Closes the MediaStream that this MediaHandler uses for - * specified media type and prepares it for garbage collection. + * Closes the MediaStream that this instance uses for a specific + * MediaType and prepares it for garbage collection. * * @param type the MediaType that we'd like to stop a stream for. */ protected void closeStream(MediaType type) { - if (type == MediaType.AUDIO) - setAudioStream(null); - else - setVideoStream(null); - - getTransportManager().closeStreamConnector(type); + /* + * This CallPeerMediaHandler releases its reference to the MediaStream + * it has initialized via #initStream(). + */ + boolean mediaHandlerCloseStream = false; - // Clear the SRTP controls used for the associated Call. - Iterator it = srtpControls.keySet().iterator(); - while (it.hasNext()) + switch (type) { - MediaTypeSrtpControl mct = it.next(); - if (mct.mediaType == type) + case AUDIO: + if (audioStream != null) + { + audioStream = null; + mediaHandlerCloseStream = true; + } + break; + case VIDEO: + if (videoStream != null) { - srtpControls.get(mct).cleanup(); - it.remove(); + videoStream = null; + mediaHandlerCloseStream = true; } + break; } + if (mediaHandlerCloseStream) + mediaHandler.closeStream(this, type); + + getTransportManager().closeStreamConnector(type); } /** @@ -491,53 +429,13 @@ public abstract class CallPeerMediaHandler< */ public void setMute(boolean mute) { + MediaStream audioStream = getStream(MediaType.AUDIO); + if (audioStream != null) audioStream.setMute(mute); } /** - * Creates a new - * net.java.sip.communicator.service.protocol.event.VideoEvent - * instance which represents the same notification/information as a specific - * net.java.sip.communicator.service.neomedia.event.VideoEvent. - * - * @param neomediaEvent the - * net.java.sip.communicator.service.neomedia.event.VideoEvent to - * represent as a - * net.java.sip.communicator.service.protocol.event.VideoEvent - * @param sender the Object to be reported as the source of the - * new VideoEvent - * @return a new - * net.java.sip.communicator.service.protocol.event.VideoEvent - * which represents the same notification/information as the specified - * neomediaEvent - */ - private static VideoEvent neomedia2protocol( - net.java.sip.communicator.service.neomedia.event.VideoEvent - neomediaEvent, - Object sender) - { - if (neomediaEvent instanceof net.java.sip.communicator.service - .neomedia.event.SizeChangeVideoEvent) - { - net.java.sip.communicator.service.neomedia.event.SizeChangeVideoEvent - neomediaSizeChangeEvent - = (net.java.sip.communicator.service.neomedia.event - .SizeChangeVideoEvent)neomediaEvent; - - return - new SizeChangeVideoEvent( - sender, - neomediaEvent.getVisualComponent(), - neomediaEvent.getOrigin(), - neomediaSizeChangeEvent.getWidth(), - neomediaSizeChangeEvent.getHeight()); - } - else - throw new IllegalArgumentException("neomediaEvent"); - } - - /** * Determines whether the audio stream of this media handler is currently * on mute. * @@ -546,6 +444,8 @@ public abstract class CallPeerMediaHandler< */ public boolean isMute() { + MediaStream audioStream = getStream(MediaType.AUDIO); + return (audioStream != null) && audioStream.isMute(); } @@ -614,32 +514,6 @@ public abstract class CallPeerMediaHandler< } /** - * Sets the KeyFrameControl currently known to this - * CallPeerMediaHandlerSipImpl made available by a specific - * VideoMediaStream. - * - * @param videoStream the VideoMediaStream the - * KeyFrameControl of which is to be set as the currently known to - * this CallPeerMediaHandlerSipImpl - */ - private void setKeyFrameControlFromVideoStream(VideoMediaStream videoStream) - { - KeyFrameControl keyFrameControl - = (videoStream == null) ? null : videoStream.getKeyFrameControl(); - - if (this.keyFrameControl != keyFrameControl) - { - if (this.keyFrameControl != null) - this.keyFrameControl.removeKeyFrameRequester(keyFrameRequester); - - this.keyFrameControl = keyFrameControl; - - if (this.keyFrameControl != null) - this.keyFrameControl.addKeyFrameRequester(-1, keyFrameRequester); - } - } - - /** * Specifies whether this media handler should be allowed to transmit * local audio. * @@ -665,67 +539,24 @@ public abstract class CallPeerMediaHandler< } /** - * Sets the last-known local SSRC of {@link #audioStream}. - * - * @param audioLocalSSRC the last-known local SSRC of {@link #audioStream} - */ - private void setAudioLocalSSRC(long audioLocalSSRC) - { - if (this.audioLocalSSRC != audioLocalSSRC) - { - long oldValue = this.audioLocalSSRC; - - this.audioLocalSSRC = audioLocalSSRC; - - firePropertyChange(AUDIO_LOCAL_SSRC, oldValue, this.audioLocalSSRC); - } - } - - /** - * Sets the last-known remote SSRC of {@link #audioStream}. - * - * @param audioRemoteSSRC the last-known remote SSRC of {@link #audioStream} - */ - private void setAudioRemoteSSRC(long audioRemoteSSRC) - { - if (this.audioRemoteSSRC != audioRemoteSSRC) - { - long oldValue = this.audioRemoteSSRC; - - this.audioRemoteSSRC = audioRemoteSSRC; - - firePropertyChange( - AUDIO_REMOTE_SSRC, - oldValue, - this.audioRemoteSSRC); - } - } - - /** * Returns the secure state of the call. If both audio and video is secured. * * @return the call secure state */ public boolean isSecure() { - /* - * If a stream for a specific MediaType does not exist, it's said to be - * secure. - */ - boolean isAudioSecured - = (audioStream == null) - || audioStream.getSrtpControl().getSecureCommunicationStatus(); - - if (!isAudioSecured) - return false; - - boolean isVideoSecured - = (videoStream == null) - || videoStream.getSrtpControl().getSecureCommunicationStatus(); - - if (!isVideoSecured) - return false; + for (MediaType mediaType : MediaType.values()) + { + MediaStream stream = getStream(mediaType); + /* + * If a stream for a specific MediaType does not exist, it's + * considered secure. + */ + if ((stream != null) + && !stream.getSrtpControl().getSecureCommunicationStatus()) + return false; + } return true; } @@ -738,8 +569,9 @@ public abstract class CallPeerMediaHandler< */ public SrtpControlType[] getAdvertisedEncryptionMethods() { - return advertisedEncryptionMethods.toArray( - new SrtpControlType[advertisedEncryptionMethods.size()]); + return + advertisedEncryptionMethods.toArray( + new SrtpControlType[advertisedEncryptionMethods.size()]); } /** @@ -762,7 +594,9 @@ public abstract class CallPeerMediaHandler< */ public void startSrtpMultistream(SrtpControl master) { - if(videoStream != null) + MediaStream videoStream = getStream(MediaType.VIDEO); + + if (videoStream != null) videoStream.getSrtpControl().setMultistream(master); } @@ -775,7 +609,7 @@ public abstract class CallPeerMediaHandler< */ public long getAudioRemoteSSRC() { - return audioRemoteSSRC; + return mediaHandler.getRemoteSSRC(this, MediaType.AUDIO); } /** @@ -814,196 +648,7 @@ public abstract class CallPeerMediaHandler< */ public void addVideoListener(VideoListener listener) { - if (listener == null) - throw new NullPointerException("listener"); - - synchronized (videoListeners) - { - if (!videoListeners.contains(listener)) - videoListeners.add(listener); - } - } - - /** - * Sets the RTP media stream that this instance uses to stream audio to a - * specific AudioMediaStream. - * - * @param audioStream the AudioMediaStream to be set as the RTP - * media stream that this instance uses to stream audio - */ - protected void setAudioStream(AudioMediaStream audioStream) - { - if (this.audioStream != audioStream) - { - if (this.audioStream != null) - { - this.audioStream - .removePropertyChangeListener( - streamPropertyChangeListener); - - this.audioStream.close(); - } - - this.audioStream = audioStream; - - long audioLocalSSRC; - long audioRemoteSSRC; - - if (this.audioStream != null) - { - this.audioStream - .addPropertyChangeListener( - streamPropertyChangeListener); - audioLocalSSRC = this.audioStream.getLocalSourceID(); - audioRemoteSSRC = this.audioStream.getRemoteSourceID(); - } - else - audioLocalSSRC = audioRemoteSSRC = SSRC_UNKNOWN; - - setAudioLocalSSRC(audioLocalSSRC); - setAudioRemoteSSRC(audioRemoteSSRC); - } - } - - /** - * Sets the last-known local SSRC of {@link #videoStream}. - * - * @param videoLocalSSRC the last-known local SSRC of {@link #videoStream} - */ - private void setVideoLocalSSRC(long videoLocalSSRC) - { - if (this.videoLocalSSRC != videoLocalSSRC) - { - long oldValue = this.videoLocalSSRC; - - this.videoLocalSSRC = videoLocalSSRC; - - firePropertyChange(VIDEO_LOCAL_SSRC, oldValue, this.videoLocalSSRC); - } - } - - /** - * Sets the last-known remote SSRC of {@link #videoStream}. - * - * @param videoRemoteSSRC the last-known remote SSRC of {@link #videoStream} - */ - private void setVideoRemoteSSRC(long videoRemoteSSRC) - { - if (this.videoRemoteSSRC != videoRemoteSSRC) - { - long oldValue = this.videoRemoteSSRC; - - this.videoRemoteSSRC = videoRemoteSSRC; - - firePropertyChange( - VIDEO_REMOTE_SSRC, - oldValue, - this.videoRemoteSSRC); - } - } - - /** - * 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. - */ - List oldVisualComponents = null; - - if (this.videoStream != null) - { - this.videoStream.removePropertyChangeListener( - streamPropertyChangeListener); - - this.videoStream.removeVideoListener(videoStreamVideoListener); - oldVisualComponents = this.videoStream.getVisualComponents(); - - /* - * The current videoStream is going away so this - * CallPeerMediaHandlerSipImpl should no longer use its - * KeyFrameControl. - */ - setKeyFrameControlFromVideoStream(null); - - this.videoStream.close(); - } - - this.videoStream = videoStream; - - /* - * The videoStream has just changed so this - * CallPeerMediaHandlerSipImpl should use its KeyFrameControl. - */ - setKeyFrameControlFromVideoStream(this.videoStream); - - long videoLocalSSRC; - long videoRemoteSSRC; - /* - * Make sure we will notify the registered VideoListeners about - * changes in the availability of video in the new videoStream. - */ - List newVisualComponents = null; - - if (this.videoStream != null) - { - this.videoStream.addPropertyChangeListener( - streamPropertyChangeListener); - videoLocalSSRC = this.videoStream.getLocalSourceID(); - videoRemoteSSRC = this.videoStream.getRemoteSourceID(); - - this.videoStream.addVideoListener(videoStreamVideoListener); - newVisualComponents = this.videoStream.getVisualComponents(); - } - else - videoLocalSSRC = videoRemoteSSRC = SSRC_UNKNOWN; - - setVideoLocalSSRC(videoLocalSSRC); - setVideoRemoteSSRC(videoRemoteSSRC); - - /* - * Notify the VideoListeners in case there was a change in the - * availability of the visual Components displaying remote video. - */ - if ((oldVisualComponents != null) && !oldVisualComponents.isEmpty()) - { - /* - * Discard Components which are present in the old and in the - * new Lists. - */ - if (newVisualComponents == null) - newVisualComponents = Collections.emptyList(); - for (Component oldVisualComponent : oldVisualComponents) - { - if (!newVisualComponents.remove(oldVisualComponent)) - { - fireVideoEvent( - VideoEvent.VIDEO_REMOVED, - oldVisualComponent, - VideoEvent.REMOTE); - } - } - } - if ((newVisualComponents != null) && !newVisualComponents.isEmpty()) - { - for (Component newVisualComponent : newVisualComponents) - { - fireVideoEvent( - VideoEvent.VIDEO_ADDED, - newVisualComponent, - VideoEvent.REMOTE); - } - } - } + videoNotifierSupport.addVideoListener(listener); } /** @@ -1029,40 +674,10 @@ public abstract class CallPeerMediaHandler< 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; - default: - throw new IllegalArgumentException("type"); - } - - consumed = event.isConsumed(); - } - else - consumed = false; - return consumed; + return + videoNotifierSupport.fireVideoEvent( + type, visualComponent, origin, + true); } /** @@ -1075,28 +690,7 @@ public abstract class CallPeerMediaHandler< */ public void fireVideoEvent(VideoEvent event) { - VideoListener[] listeners; - - synchronized (videoListeners) - { - listeners - = videoListeners - .toArray(new VideoListener[videoListeners.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; - } + videoNotifierSupport.fireVideoEvent(event, true); } /** @@ -1106,10 +700,12 @@ public abstract class CallPeerMediaHandler< */ public Component createLocalVisualComponent() { + MediaStream videoStream = getStream(MediaType.VIDEO); + return ((videoStream == null) || !isLocalVideoTransmissionEnabled()) ? null - : videoStream.createLocalVisualComponent(); + : ((VideoMediaStream) videoStream).createLocalVisualComponent(); } /** @@ -1120,8 +716,13 @@ public abstract class CallPeerMediaHandler< */ public void disposeLocalVisualComponent(Component component) { + MediaStream videoStream = getStream(MediaType.VIDEO); + if (videoStream != null) - videoStream.disposeLocalVisualComponent(component); + { + ((VideoMediaStream) videoStream).disposeLocalVisualComponent( + component); + } } /** @@ -1150,12 +751,16 @@ public abstract class CallPeerMediaHandler< */ public List getVisualComponents() { + MediaStream videoStream = getStream(MediaType.VIDEO); List visualComponents; if (videoStream == null) visualComponents = Collections.emptyList(); else - visualComponents = videoStream.getVisualComponents(); + { + visualComponents + = ((VideoMediaStream) videoStream).getVisualComponents(); + } return visualComponents; } @@ -1170,11 +775,7 @@ public abstract class CallPeerMediaHandler< */ public void removeVideoListener(VideoListener listener) { - if (listener != null) - synchronized (videoListeners) - { - videoListeners.remove(listener); - } + videoNotifierSupport.removeVideoListener(listener); } /** @@ -1193,8 +794,13 @@ public abstract class CallPeerMediaHandler< { this.localAudioLevelListener = listener; - if(audioStream != null) - audioStream.setLocalUserAudioLevelListener(listener); + MediaStream audioStream = getStream(MediaType.AUDIO); + + if (audioStream != null) + { + ((AudioMediaStream) audioStream).setLocalUserAudioLevelListener( + listener); + } } } @@ -1214,8 +820,13 @@ public abstract class CallPeerMediaHandler< { this.streamAudioLevelListener = listener; - if(audioStream != null) - audioStream.setStreamAudioLevelListener(listener); + MediaStream audioStream = getStream(MediaType.AUDIO); + + if (audioStream != null) + { + ((AudioMediaStream) audioStream).setStreamAudioLevelListener( + listener); + } } } @@ -1234,19 +845,26 @@ public abstract class CallPeerMediaHandler< { this.csrcAudioLevelListener = csrcAudioLevelListener; - if(audioStream != null) - audioStream.setCsrcAudioLevelListener(csrcAudioLevelListener); + MediaStream audioStream = getStream(MediaType.AUDIO); + + if (audioStream != null) + { + ((AudioMediaStream) audioStream).setCsrcAudioLevelListener( + csrcAudioLevelListener); + } } } /** - * Returns the currently valid SrtpControls map. + * Gets the SrtpControls of the MediaStreams of this + * instance. * - * @return the currently valid SrtpControls map. + * @return the SrtpControls of the MediaStreams of this + * instance */ protected Map getSrtpControls() { - return this.srtpControls; + return mediaHandler.getSrtpControls(this); } /** @@ -1271,7 +889,7 @@ public abstract class CallPeerMediaHandler< * @return the newly created MediaStream. * * @throws OperationFailedException if creating the stream fails for any - * reason (like for example accessing the device or setting the format). + * reason (like, for example, accessing the device or setting the format). */ protected MediaStream initStream(StreamConnector connector, MediaDevice device, @@ -1282,130 +900,27 @@ public abstract class CallPeerMediaHandler< boolean masterStream) throws OperationFailedException { - MediaType mediaType = device.getMediaType(); - MediaStream stream = getStream(mediaType); - - if (stream == null) - { - if (logger.isTraceEnabled() && (mediaType != format.getMediaType())) - logger.trace("The media types of device and format differ."); - - MediaService mediaService = - ProtocolMediaActivator.getMediaService(); - // By default the SrtpControlType is ZRTP. - SrtpControlType srtpControlType = SrtpControlType.ZRTP; - // But if a SrtpControl exists already, we switch to this - // SrtpControlType. - if(srtpControls.size() > 0) - { - srtpControlType = srtpControls.firstKey().srtpControlType; - } - MediaTypeSrtpControl mediaTypeSrtpControl = - new MediaTypeSrtpControl(mediaType, srtpControlType); - // check whether a control already exists - SrtpControl control = srtpControls.get(mediaTypeSrtpControl); - if(control == null) - { - // this creates the default control, currently ZRTP without - // the hello-hash - // The creation of the SrtpControl is done in the - // MediaStreamImpl (which creates a new ZrtpControlImpl()), but - // this was done without linking to the srtpControls Map. - stream = mediaService.createMediaStream(connector, device); - srtpControls.put(mediaTypeSrtpControl, stream.getSrtpControl()); - } - else - { - stream = mediaService.createMediaStream( - connector, device, control); - } - } - else - { - //this is a reinit - } - - return - configureStream( - device, format, target, direction, rtpExtensions, stream, + MediaStream stream + = mediaHandler.initStream( + this, + connector, + device, + format, + target, + direction, + rtpExtensions, masterStream); - } - - /** - * Configures stream to use the specified format, - * target, target, and direction. - * - * @param device the MediaDevice to be used by stream - * for capture and playback - * @param format the MediaFormat that we'd like the new stream - * to transmit in. - * @param target the MediaStreamTarget containing the RTP and - * RTCP address:port couples that the new stream would be sending - * packets to. - * @param direction the MediaDirection that we'd like the new - * stream to use (i.e. sendonly, sendrecv, recvonly, or inactive). - * @param rtpExtensions the list of RTPExtensions that should be - * enabled for this stream. - * @param stream the MediaStream that we'd like to configure. - * @param masterStream whether the stream to be used as master if secured - * - * @return the MediaStream that we received as a parameter (for - * convenience reasons). - * - * @throws OperationFailedException if setting the MediaFormat - * or connecting to the specified MediaDevice fails for some - * reason. - */ - protected MediaStream configureStream( MediaDevice device, - MediaFormat format, - MediaStreamTarget target, - MediaDirection direction, - List rtpExtensions, - MediaStream stream, - boolean masterStream) - throws OperationFailedException - { - registerDynamicPTsWithStream(stream); - registerRTPExtensionsWithStream(rtpExtensions, stream); - - stream.setDevice(device); - stream.setTarget(target); - stream.setDirection(direction); - stream.setFormat(format); - MediaAwareCall call = peer.getCall(); - MediaType mediaType - = (stream instanceof AudioMediaStream) - ? MediaType.AUDIO - : MediaType.VIDEO; - - stream.setRTPTranslator(call.getRTPTranslator(mediaType)); - - switch (mediaType) + switch (device.getMediaType()) { case AUDIO: - setAudioStream((AudioMediaStream) stream); - registerAudioLevelListeners(audioStream); + audioStream = (AudioMediaStream) stream; break; - case VIDEO: - setVideoStream((VideoMediaStream) stream); + videoStream = (VideoMediaStream) stream; break; } - if (call.isDefaultEncrypted()) - { - /* - * We'll use the audio stream as the master stream when using SRTP - * multistreams. - */ - SrtpControl srtpControl = stream.getSrtpControl(); - - srtpControl.setMasterSession(masterStream); - srtpControl.setSrtpListener(srtpListener); - srtpControl.start(mediaType); - } - return stream; } @@ -1446,12 +961,7 @@ public abstract class CallPeerMediaHandler< */ public boolean processKeyFrameRequest() { - KeyFrameControl keyFrameControl = this.keyFrameControl; - - return - (keyFrameControl == null) - ? null - : keyFrameControl.keyFrameRequest(); + return mediaHandler.processKeyFrameRequest(this); } /** @@ -1467,7 +977,7 @@ public abstract class CallPeerMediaHandler< if (MediaAwareCall.DEFAULT_DEVICE.equals(event.getPropertyName())) { /* - * XXX We only support changing the default audio device at the time + * XXX We support changing the default audio device only at the time * of this writing. */ MediaStream stream = getStream(MediaType.AUDIO); @@ -1495,23 +1005,18 @@ public abstract class CallPeerMediaHandler< */ void registerAudioLevelListeners(AudioMediaStream audioStream) { - // if we already have a local level listener - register it now. synchronized (localAudioLevelListenerLock) { if (localAudioLevelListener != null) - audioStream - .setLocalUserAudioLevelListener(localAudioLevelListener); + audioStream.setLocalUserAudioLevelListener( + localAudioLevelListener); } - - // if we already have a stream level listener - register it now. synchronized (streamAudioLevelListenerLock) { if (streamAudioLevelListener != null) audioStream .setStreamAudioLevelListener(streamAudioLevelListener); } - - // if we already have a csrc level listener - register it now. synchronized (csrcAudioLevelListenerLock) { if (csrcAudioLevelListener != null) @@ -1520,47 +1025,6 @@ public abstract class CallPeerMediaHandler< } /** - * Registers all dynamic payload mappings known to this - * MediaHandler with the specified MediaStream. - * - * @param stream the MediaStream that we'd like to register our - * dynamic payload mappings with. - */ - private void registerDynamicPTsWithStream(MediaStream stream) - { - for (Map.Entry mapEntry - : getDynamicPayloadTypes().getMappings().entrySet()) - { - byte pt = mapEntry.getValue(); - MediaFormat fmt = mapEntry.getKey(); - - stream.addDynamicRTPPayloadType(pt, fmt); - } - } - - /** - * Registers with the specified MediaStream all RTP extensions - * negotiated by this MediaHandler. - * - * @param stream the MediaStream that we'd like to register our - * RTPExtensions with. - * @param rtpExtensions the list of RTPExtensions that should be - * enabled for stream. - */ - private void registerRTPExtensionsWithStream( - List rtpExtensions, - MediaStream stream) - { - for ( RTPExtension rtpExtension : rtpExtensions) - { - byte extensionID - = rtpExtensionsRegistry.getExtensionMapping(rtpExtension); - - stream.addRTPExtension(extensionID, rtpExtension); - } - } - - /** * Gets the MediaStream of this CallPeerMediaHandler which * is of a specific MediaType. If this instance doesn't have such a * MediaStream, returns null @@ -1592,12 +1056,13 @@ public abstract class CallPeerMediaHandler< */ public boolean isRemotelyOnHold() { - if(audioStream != null && audioStream.getDirection().allowsSending()) - return false; - - if(videoStream != null && videoStream.getDirection().allowsSending()) - return false; + for (MediaType mediaType : MediaType.values()) + { + MediaStream stream = getStream(mediaType); + if ((stream != null) && stream.getDirection().allowsSending()) + return false; + } return true; } @@ -1783,18 +1248,6 @@ public abstract class CallPeerMediaHandler< } /** - * Returns true if this handler has already started at least one - * of its streams, at least once, and false otherwise. - * - * @return true if this handler has already started at least one - * of its streams, at least once, and false otherwise. - */ - public boolean isStarted() - { - return started; - } - - /** * Starts this CallPeerMediaHandler. If it has already been * started, does nothing. * @@ -1805,34 +1258,37 @@ public abstract class CallPeerMediaHandler< public void start() throws IllegalStateException { - if(isStarted()) - return; + MediaStream stream; - MediaStream stream = getStream(MediaType.AUDIO); + stream = getStream(MediaType.AUDIO); if ((stream != null) && !stream.isStarted() && isLocalAudioTransmissionEnabled()) { - getTransportManager().setTrafficClass(stream.getTarget(), - MediaType.AUDIO); + getTransportManager().setTrafficClass( + stream.getTarget(), + MediaType.AUDIO); stream.start(); } stream = getStream(MediaType.VIDEO); - if ((stream != null)) + if (stream != null) { - /* Inform listener of LOCAL_VIDEO_STREAMING only once the video - * starts, so that VideoMediaDeviceSession has correct MediaDevice + /* + * Inform listener of LOCAL_VIDEO_STREAMING only once the video + * starts so that VideoMediaDeviceSession has correct MediaDevice * set (switch from desktop streaming to webcam video or vice-versa * issue) */ - firePropertyChange(OperationSetVideoTelephony.LOCAL_VIDEO_STREAMING, + firePropertyChange( + OperationSetVideoTelephony.LOCAL_VIDEO_STREAMING, null, this.videoDirectionUserPreference); if(!stream.isStarted()) { - getTransportManager().setTrafficClass(stream.getTarget(), - MediaType.VIDEO); + getTransportManager().setTrafficClass( + stream.getTarget(), + MediaType.VIDEO); stream.start(); // send empty packet to deblock some kind of RTP proxy to let @@ -1927,35 +1383,16 @@ public abstract class CallPeerMediaHandler< } /** - * Returns the SRTP control type used for a given media type (AUDIO or - * VIDEO). + * Gets the SRTP control type used for a given media type. * - * @param mediaType The media type (AUDIO or VIDEO) which may use SRTP - * enabled thanks to a given SRTP control type. - * - * @return the SRTP control type used (MIKEY, SDES, ZRTP) for the given - * media, or null if SRTP is not enabled for this media type. + * @param mediaType the MediaType to get the SRTP control type for + * @return the SRTP control type (MIKEY, SDES, ZRTP) used for the given + * media type or null if SRTP is not enabled for the given media + * type */ public SrtpControlType getEncryptionMethod(MediaType mediaType) { - SrtpControl srtpControl = null; - - // Goes through the different SRTP control type and stops if we found - // the one used for this media stream. - for(SrtpControlType srtpControlType : SrtpControlType.values()) - { - // If this SRTP control type exists and is activate to secure the - // communication. - if((srtpControl = srtpControls.get(new MediaTypeSrtpControl( - mediaType, srtpControlType))) - != null - && srtpControl.getSecureCommunicationStatus()) - { - return srtpControlType; - } - } - - return null; + return mediaHandler.getEncryptionMethod(this, mediaType); } /** @@ -1967,10 +1404,44 @@ public abstract class CallPeerMediaHandler< */ public String getICECandidateExtendedType() { - if(getTransportManager() == null) + TransportManager transportManager = getTransportManager(); + + return + (transportManager == null) + ? null + : transportManager.getICECandidateExtendedType(); + } + + public MediaHandler getMediaHandler() + { + return mediaHandler; + } + + public void setMediaHandler(MediaHandler mediaHandler) + { + if (this.mediaHandler != mediaHandler) { - return null; + if (this.mediaHandler != null) + { + this.mediaHandler.removeKeyFrameRequester(keyFrameRequester); + this.mediaHandler.removePropertyChangeListener( + streamPropertyChangeListener); + if (srtpListener != null) + this.mediaHandler.removeSrtpListener(srtpListener); + this.mediaHandler.removeVideoListener(videoStreamVideoListener); + } + + this.mediaHandler = mediaHandler; + + if (this.mediaHandler != null) + { + this.mediaHandler.addKeyFrameRequester(-1, keyFrameRequester); + this.mediaHandler.addPropertyChangeListener( + streamPropertyChangeListener); + if (srtpListener != null) + this.mediaHandler.addSrtpListener(srtpListener); + this.mediaHandler.addVideoListener(videoStreamVideoListener); + } } - return getTransportManager().getICECandidateExtendedType(); } } diff --git a/src/net/java/sip/communicator/service/protocol/media/MediaHandler.java b/src/net/java/sip/communicator/service/protocol/media/MediaHandler.java new file mode 100644 index 0000000..1d739f0 --- /dev/null +++ b/src/net/java/sip/communicator/service/protocol/media/MediaHandler.java @@ -0,0 +1,984 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.service.protocol.media; + +import java.awt.*; +import java.beans.*; +import java.util.*; +import java.util.List; + +import net.java.sip.communicator.service.neomedia.*; +import net.java.sip.communicator.service.neomedia.control.*; +import net.java.sip.communicator.service.neomedia.device.*; +import net.java.sip.communicator.service.neomedia.event.*; +import net.java.sip.communicator.service.neomedia.format.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.*; +import net.java.sip.communicator.util.event.*; + +/** + * Implements media control code which allows state sharing among multiple + * CallPeerMediaHandlers. + * + * @author Lyubomir Marinov + */ +public class MediaHandler + extends PropertyChangeNotifier +{ + /** + * The Logger used by the MediaHandler class and its + * instances for logging output. + */ + private static final Logger logger = Logger.getLogger(MediaHandler.class); + + /** + * The AudioMediaStream which this instance uses to send and + * receive audio. + */ + private AudioMediaStream audioStream; + + /** + * The KeyFrameControl currently known to this + * MediaHandler and made available by {@link #videoStream}. + */ + private KeyFrameControl keyFrameControl; + + /** + * The KeyFrameRequester implemented by this + * MediaHandler and provided to {@link #keyFrameControl}. + */ + private final KeyFrameControl.KeyFrameRequester keyFrameRequester + = new KeyFrameControl.KeyFrameRequester() + { + public boolean requestKeyFrame() + { + return MediaHandler.this.requestKeyFrame(); + } + }; + + private final List keyFrameRequesters + = new LinkedList(); + + /** + * The last-known local SSRCs of the MediaStreams of this instance + * indexed by MediaType ordinal. + */ + private final long[] localSSRCs; + + /** + * The last-known remote SSRCs of the MediaStreams of this instance + * indexed by MediaType ordinal. + */ + private final long[] remoteSSRCs; + + /** + * The SrtpControls of the MediaStreams of this instance. + */ + private final SortedMap srtpControls + = new TreeMap(); + + private final SrtpListener srtpListener + = new SrtpListener() + { + public void securityMessageReceived( + String message, String i18nMessage, int severity) + { + for (SrtpListener listener : getSrtpListeners()) + listener.securityMessageReceived( + message, i18nMessage, severity); + } + + public void securityTimeout(int sessionType) + { + for (SrtpListener listener : getSrtpListeners()) + listener.securityTimeout(sessionType); + } + + public void securityTurnedOff(int sessionType) + { + for (SrtpListener listener : getSrtpListeners()) + listener.securityTurnedOff(sessionType); + } + + public void securityTurnedOn( + int sessionType, String cipher, SrtpControl sender) + { + for (SrtpListener listener : getSrtpListeners()) + listener.securityTurnedOn(sessionType, cipher, sender); + } + }; + + private final List srtpListeners + = new LinkedList(); + + /** + * The PropertyChangeListener which listens to changes in the + * values of the properties of the MediaStreams of this instance. + */ + private final PropertyChangeListener streamPropertyChangeListener + = new PropertyChangeListener() + { + /** + * Notifies this PropertyChangeListener that the value of + * a specific property of the notifier it is registered with has + * changed. + * + * @param evt a PropertyChangeEvent which describes the + * source of the event, the name of the property which has changed + * its value and the old and new values of the property + * @see PropertyChangeListener#propertyChange(PropertyChangeEvent) + */ + public void propertyChange(PropertyChangeEvent evt) + { + String propertyName = evt.getPropertyName(); + + if (MediaStream.PNAME_LOCAL_SSRC.equals(propertyName)) + { + Object source = evt.getSource(); + + if (source == audioStream) + setLocalSSRC( + MediaType.AUDIO, + audioStream.getLocalSourceID()); + else if (source == videoStream) + setLocalSSRC( + MediaType.VIDEO, + videoStream.getLocalSourceID()); + } + else if (MediaStream.PNAME_REMOTE_SSRC.equals(propertyName)) + { + Object source = evt.getSource(); + + if (source == audioStream) + setRemoteSSRC( + MediaType.AUDIO, + audioStream.getRemoteSourceID()); + else if (source == videoStream) + setRemoteSSRC( + MediaType.VIDEO, + videoStream.getRemoteSourceID()); + } + } + }; + + private final VideoNotifierSupport videoNotifierSupport + = new VideoNotifierSupport(this, true); + + /** + * The VideoMediaStream which this instance uses to send and + * receive video. + */ + private VideoMediaStream videoStream; + + /** + * The VideoListener which listens to {@link #videoStream} for + * changes in the availability of visual Components displaying + * remote video and re-fires them as originating from this instance. + */ + private final VideoListener videoStreamVideoListener + = new VideoListener() + { + public void videoAdded(VideoEvent event) + { + VideoEvent clone = event.clone(MediaHandler.this); + + fireVideoEvent(clone); + if (clone.isConsumed()) + event.consume(); + } + + public void videoRemoved(VideoEvent event) + { + // Forwarded in the same way as VIDEO_ADDED. + videoAdded(event); + } + + public void videoUpdate(VideoEvent event) + { + // Forwarded in the same way as VIDEO_ADDED. + videoAdded(event); + } + }; + + public MediaHandler() + { + int mediaTypeValueCount = MediaType.values().length; + + localSSRCs = new long[mediaTypeValueCount]; + Arrays.fill(localSSRCs, CallPeerMediaHandler.SSRC_UNKNOWN); + remoteSSRCs = new long[mediaTypeValueCount]; + Arrays.fill(remoteSSRCs, CallPeerMediaHandler.SSRC_UNKNOWN); + } + + boolean addKeyFrameRequester( + int index, + KeyFrameControl.KeyFrameRequester keyFrameRequester) + { + if (keyFrameRequester == null) + throw new NullPointerException("keyFrameRequester"); + else + { + synchronized (keyFrameRequesters) + { + if (keyFrameRequesters.contains(keyFrameRequester)) + return false; + else + { + keyFrameRequesters.add( + (index == -1) + ? keyFrameRequesters.size() + : index, + keyFrameRequester); + return true; + } + } + } + } + + void addSrtpListener(SrtpListener listener) + { + if (listener == null) + throw new NullPointerException("listener"); + else + { + synchronized (srtpListeners) + { + if (!srtpListeners.contains(listener)) + srtpListeners.add(listener); + } + } + } + + /** + * 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 + */ + void addVideoListener(VideoListener listener) + { + videoNotifierSupport.addVideoListener(listener); + } + + /** + * Closes the MediaStream that this instance uses for a specific + * MediaType and prepares it for garbage collection. + * + * @param type the MediaType that we'd like to stop a stream for. + */ + protected void closeStream( + CallPeerMediaHandler callPeerMediaHandler, + MediaType type) + { + if (type == MediaType.AUDIO) + setAudioStream(null); + else + setVideoStream(null); + + // Clean up the SRTP controls used for the associated Call. + Iterator> iter + = srtpControls.entrySet().iterator(); + + while (iter.hasNext()) + { + Map.Entry entry = iter.next(); + + if (entry.getKey().mediaType == type) + { + entry.getValue().cleanup(); + iter.remove(); + } + } + } + + /** + * Configures stream to use the specified device, + * format, target, direction, etc. + * + * @param device the MediaDevice to be used by stream + * for capture and playback + * @param format the MediaFormat that we'd like the new stream + * to transmit in. + * @param target the MediaStreamTarget containing the RTP and + * RTCP address:port couples that the new stream would be sending + * packets to. + * @param direction the MediaDirection that we'd like the new + * stream to use (i.e. sendonly, sendrecv, recvonly, or inactive). + * @param rtpExtensions the list of RTPExtensions that should be + * enabled for this stream. + * @param stream the MediaStream that we'd like to configure. + * @param masterStream whether the stream to be used as master if secured + * + * @return the MediaStream that we received as a parameter (for + * convenience reasons). + * + * @throws OperationFailedException if setting the MediaFormat + * or connecting to the specified MediaDevice fails for some + * reason. + */ + protected MediaStream configureStream( + CallPeerMediaHandler callPeerMediaHandler, + MediaDevice device, + MediaFormat format, + MediaStreamTarget target, + MediaDirection direction, + List rtpExtensions, + MediaStream stream, + boolean masterStream) + throws OperationFailedException + { + registerDynamicPTsWithStream(callPeerMediaHandler, stream); + registerRTPExtensionsWithStream( + callPeerMediaHandler, + rtpExtensions, stream); + + stream.setDevice(device); + stream.setTarget(target); + stream.setDirection(direction); + stream.setFormat(format); + + MediaAwareCall call = callPeerMediaHandler.getPeer().getCall(); + MediaType mediaType + = (stream instanceof AudioMediaStream) + ? MediaType.AUDIO + : MediaType.VIDEO; + + stream.setRTPTranslator(call.getRTPTranslator(mediaType)); + + switch (mediaType) + { + case AUDIO: + setAudioStream((AudioMediaStream) stream); + callPeerMediaHandler.registerAudioLevelListeners(audioStream); + break; + + case VIDEO: + setVideoStream((VideoMediaStream) stream); + break; + } + + if (call.isDefaultEncrypted()) + { + /* + * We'll use the audio stream as the master stream when using SRTP + * multistreams. + */ + SrtpControl srtpControl = stream.getSrtpControl(); + + srtpControl.setMasterSession(masterStream); + srtpControl.setSrtpListener(srtpListener); + srtpControl.start(mediaType); + } + + return stream; + } + + /** + * Notifies the VideoListeners registered with this + * MediaHandler 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 MediaHandler + * @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) + { + return + videoNotifierSupport.fireVideoEvent( + type, visualComponent, origin, + true); + } + + /** + * Notifies the VideoListeners registered with this + * MediaHandler about a specific VideoEvent. + * + * @param event the VideoEvent to fire to the + * VideoListeners registered with this MediaHandler + */ + protected void fireVideoEvent(VideoEvent event) + { + videoNotifierSupport.fireVideoEvent(event, true); + } + + /** + * Gets the SRTP control type used for a given media type. + * + * @param mediaType the MediaType to get the SRTP control type for + * @return the SRTP control type (MIKEY, SDES, ZRTP) used for the given + * media type or null if SRTP is not enabled for the given media + * type + */ + SrtpControlType getEncryptionMethod( + CallPeerMediaHandler callPeerMediaHandler, + MediaType mediaType) + { + /* + * Find the first existing SRTP control type for the specified media + * type which is active i.e. secures the communication. + */ + for(SrtpControlType srtpControlType : SrtpControlType.values()) + { + SrtpControl srtpControl + = getSrtpControls(callPeerMediaHandler).get( + new MediaTypeSrtpControl(mediaType, srtpControlType)); + + if((srtpControl != null) + && srtpControl.getSecureCommunicationStatus()) + { + return srtpControlType; + } + } + + return null; + } + + long getRemoteSSRC( + CallPeerMediaHandler callPeerMediaHandler, + MediaType mediaType) + { + return remoteSSRCs[mediaType.ordinal()]; + } + + /** + * Gets the SrtpControls of the MediaStreams of this + * instance. + * + * @return the SrtpControls of the MediaStreams of this + * instance + */ + Map getSrtpControls( + CallPeerMediaHandler callPeerMediaHandler) + { + return srtpControls; + } + + private SrtpListener[] getSrtpListeners() + { + synchronized (srtpListeners) + { + return + srtpListeners.toArray(new SrtpListener[srtpListeners.size()]); + } + } + + /** + * Gets the MediaStream of this instance which is of a specific + * MediaType. If this instance doesn't have such a + * MediaStream, returns null + * + * @param mediaType the MediaType of the MediaStream to + * retrieve + * @return the MediaStream of this CallPeerMediaHandler + * which is of the specified mediaType if this instance has such a + * MediaStream; otherwise, null + */ + MediaStream getStream( + CallPeerMediaHandler callPeerMediaHandler, + MediaType mediaType) + { + switch (mediaType) + { + case AUDIO: + return audioStream; + case VIDEO: + return videoStream; + default: + throw new IllegalArgumentException("mediaType"); + } + } + + /** + * Creates if necessary, and configures the stream that this + * MediaHandler is using for the MediaType matching the + * one of the MediaDevice. + * + * @param connector the MediaConnector that we'd like to bind the + * newly created stream to. + * @param device the MediaDevice that we'd like to attach the newly + * created MediaStream to. + * @param format the MediaFormat that we'd like the new + * MediaStream to be set to transmit in. + * @param target the MediaStreamTarget containing the RTP and RTCP + * address:port couples that the new stream would be sending packets to. + * @param direction the MediaDirection that we'd like the new + * stream to use (i.e. sendonly, sendrecv, recvonly, or inactive). + * @param rtpExtensions the list of RTPExtensions that should be + * enabled for this stream. + * @param masterStream whether the stream to be used as master if secured + * + * @return the newly created MediaStream. + * + * @throws OperationFailedException if creating the stream fails for any + * reason (like for example accessing the device or setting the format). + */ + MediaStream initStream( + CallPeerMediaHandler callPeerMediaHandler, + StreamConnector connector, + MediaDevice device, + MediaFormat format, + MediaStreamTarget target, + MediaDirection direction, + List rtpExtensions, + boolean masterStream) + throws OperationFailedException + { + MediaType mediaType = device.getMediaType(); + MediaStream stream = getStream(callPeerMediaHandler, mediaType); + + if (stream == null) + { + if (logger.isTraceEnabled() && (mediaType != format.getMediaType())) + logger.trace("The media types of device and format differ."); + + MediaService mediaService + = ProtocolMediaActivator.getMediaService(); + /* + * The default SrtpControlType is ZRTP. But if a SrtpControl exists + * already, it determines the SrtpControlType. + */ + SrtpControlType srtpControlType + = (srtpControls.size() > 0) + ? srtpControls.firstKey().srtpControlType + : SrtpControlType.ZRTP; + MediaTypeSrtpControl mediaTypeSrtpControl + = new MediaTypeSrtpControl(mediaType, srtpControlType); + SrtpControl srtpControl = srtpControls.get(mediaTypeSrtpControl); + + // If a SrtpControl does not exist yet, create a default one. + if (srtpControl == null) + { + /* + * The default SrtpControl is currently ZRTP without the + * hello-hash. It is created by the MediaStream implementation. + * Consequently, it needs to be linked to the srtpControls Map. + */ + stream = mediaService.createMediaStream(connector, device); + srtpControls.put(mediaTypeSrtpControl, stream.getSrtpControl()); + } + else + { + stream + = mediaService.createMediaStream( + connector, + device, + srtpControl); + } + } + else + { + // this is a reinit + } + + return + configureStream( + callPeerMediaHandler, + device, format, target, direction, rtpExtensions, stream, + masterStream); + } + + /** + * Processes a request for a (video) key frame from a remote peer to the + * local peer. + * + * @return true if the request for a (video) key frame has been + * honored by the local peer; otherwise, false + */ + boolean processKeyFrameRequest(CallPeerMediaHandler callPeerMediaHandler) + { + KeyFrameControl keyFrameControl = this.keyFrameControl; + + return + (keyFrameControl == null) + ? null + : keyFrameControl.keyFrameRequest(); + } + + /** + * Registers all dynamic payload mappings known to this + * MediaHandler with the specified MediaStream. + * + * @param stream the MediaStream that we'd like to register our + * dynamic payload mappings with. + */ + private void registerDynamicPTsWithStream( + CallPeerMediaHandler callPeerMediaHandler, + MediaStream stream) + { + for (Map.Entry mapEntry + : callPeerMediaHandler.getDynamicPayloadTypes().getMappings() + .entrySet()) + { + byte pt = mapEntry.getValue(); + MediaFormat fmt = mapEntry.getKey(); + + stream.addDynamicRTPPayloadType(pt, fmt); + } + } + + /** + * Registers with the specified MediaStream all RTP extensions + * negotiated by this MediaHandler. + * + * @param stream the MediaStream that we'd like to register our + * RTPExtensions with. + * @param rtpExtensions the list of RTPExtensions that should be + * enabled for stream. + */ + private void registerRTPExtensionsWithStream( + CallPeerMediaHandler callPeerMediaHandler, + List rtpExtensions, + MediaStream stream) + { + DynamicRTPExtensionsRegistry rtpExtensionsRegistry + = callPeerMediaHandler.getRtpExtensionsRegistry(); + + for (RTPExtension rtpExtension : rtpExtensions) + { + byte extensionID + = rtpExtensionsRegistry.getExtensionMapping(rtpExtension); + + stream.addRTPExtension(extensionID, rtpExtension); + } + } + + boolean removeKeyFrameRequester( + KeyFrameControl.KeyFrameRequester keyFrameRequester) + { + if (keyFrameRequester == null) + return false; + else + { + synchronized (keyFrameRequesters) + { + return keyFrameRequesters.remove(keyFrameRequester); + } + } + } + + void removeSrtpListener(SrtpListener listener) + { + if (listener != null) + { + synchronized (srtpListeners) + { + srtpListeners.remove(listener); + } + } + } + + /** + * 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 + */ + void removeVideoListener(VideoListener listener) + { + videoNotifierSupport.removeVideoListener(listener); + } + + /** + * Requests a key frame from the remote peer of the associated + * VideoMediaStream of this MediaHandler. + * + * @return true if this MediaHandler has indeed requested + * a key frame from the remote peer of its associated + * VideoMediaStream in response to the call; otherwise, + * false + */ + protected boolean requestKeyFrame() + { + KeyFrameControl.KeyFrameRequester[] keyFrameRequesters; + + synchronized (this.keyFrameRequesters) + { + keyFrameRequesters + = this.keyFrameRequesters.toArray( + new KeyFrameControl.KeyFrameRequester[ + this.keyFrameRequesters.size()]); + } + + for (KeyFrameControl.KeyFrameRequester keyFrameRequester + : keyFrameRequesters) + { + if (keyFrameRequester.requestKeyFrame()) + return true; + } + return false; + } + + /** + * Sets the AudioMediaStream which this instance is to use to send + * and receive audio. + * + * @param audioStream the AudioMediaStream which this instance is + * to use to send and receive audio + */ + private void setAudioStream(AudioMediaStream audioStream) + { + if (this.audioStream != audioStream) + { + if (this.audioStream != null) + { + this.audioStream + .removePropertyChangeListener( + streamPropertyChangeListener); + + this.audioStream.close(); + } + + this.audioStream = audioStream; + + long audioLocalSSRC; + long audioRemoteSSRC; + + if (this.audioStream != null) + { + this.audioStream + .addPropertyChangeListener( + streamPropertyChangeListener); + audioLocalSSRC = this.audioStream.getLocalSourceID(); + audioRemoteSSRC = this.audioStream.getRemoteSourceID(); + } + else + { + audioLocalSSRC + = audioRemoteSSRC + = CallPeerMediaHandler.SSRC_UNKNOWN; + } + + setLocalSSRC(MediaType.AUDIO, audioLocalSSRC); + setRemoteSSRC(MediaType.AUDIO, audioRemoteSSRC); + } + } + + /** + * Sets the KeyFrameControl currently known to this + * MediaHandler made available by a specific + * VideoMediaStream. + * + * @param videoStream the VideoMediaStream the + * KeyFrameControl of which is to be set as the currently known to + * this MediaHandler + */ + private void setKeyFrameControlFromVideoStream(VideoMediaStream videoStream) + { + KeyFrameControl keyFrameControl + = (videoStream == null) ? null : videoStream.getKeyFrameControl(); + + if (this.keyFrameControl != keyFrameControl) + { + if (this.keyFrameControl != null) + this.keyFrameControl.removeKeyFrameRequester(keyFrameRequester); + + this.keyFrameControl = keyFrameControl; + + if (this.keyFrameControl != null) + this.keyFrameControl.addKeyFrameRequester(-1, keyFrameRequester); + } + } + + /** + * Sets the last-known local SSRC of the MediaStream of a specific + * MediaType. + * + * @param mediaType the MediaType of the MediaStream to + * set the last-known local SSRC of + * @param localSSRC the last-known local SSRC of the MediaStream of + * the specified mediaType + */ + private void setLocalSSRC(MediaType mediaType, long localSSRC) + { + int index = mediaType.ordinal(); + long oldValue = localSSRCs[index]; + + if (oldValue != localSSRC) + { + localSSRCs[index] = localSSRC; + + String property; + + switch (mediaType) + { + case AUDIO: + property = CallPeerMediaHandler.AUDIO_LOCAL_SSRC; + break; + case VIDEO: + property = CallPeerMediaHandler.VIDEO_LOCAL_SSRC; + break; + default: + property = null; + } + if (property != null) + firePropertyChange(property, oldValue, localSSRC); + } + } + + /** + * Sets the last-known local SSRC of the MediaStream of a specific + * MediaType. + * + * @param mediaType the MediaType of the MediaStream to + * set the last-known local SSRC of + * @param localSSRC the last-known local SSRC of the MediaStream of + * the specified mediaType + */ + private void setRemoteSSRC(MediaType mediaType, long remoteSSRC) + { + int index = mediaType.ordinal(); + long oldValue = remoteSSRCs[index]; + + if (oldValue != remoteSSRC) + { + remoteSSRCs[index] = remoteSSRC; + + String property; + + switch (mediaType) + { + case AUDIO: + property = CallPeerMediaHandler.AUDIO_REMOTE_SSRC; + break; + case VIDEO: + property = CallPeerMediaHandler.VIDEO_REMOTE_SSRC; + break; + default: + property = null; + } + if (property != null) + firePropertyChange(property, oldValue, remoteSSRC); + } + } + + /** + * Sets the VideoMediaStream which this instance is to use to send + * and receive video. + * + * @param videoStream the VideoMediaStream which this instance is + * to use to send and receive 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. + */ + List oldVisualComponents = null; + + if (this.videoStream != null) + { + this.videoStream.removePropertyChangeListener( + streamPropertyChangeListener); + + this.videoStream.removeVideoListener(videoStreamVideoListener); + oldVisualComponents = this.videoStream.getVisualComponents(); + + /* + * The current videoStream is going away so this + * CallPeerMediaHandler should no longer use its + * KeyFrameControl. + */ + setKeyFrameControlFromVideoStream(null); + + this.videoStream.close(); + } + + this.videoStream = videoStream; + + /* + * The videoStream has just changed so this CallPeerMediaHandler + * should use its KeyFrameControl. + */ + setKeyFrameControlFromVideoStream(this.videoStream); + + long videoLocalSSRC; + long videoRemoteSSRC; + /* + * Make sure we will notify the registered VideoListeners about + * changes in the availability of video in the new videoStream. + */ + List newVisualComponents = null; + + if (this.videoStream != null) + { + this.videoStream.addPropertyChangeListener( + streamPropertyChangeListener); + videoLocalSSRC = this.videoStream.getLocalSourceID(); + videoRemoteSSRC = this.videoStream.getRemoteSourceID(); + + this.videoStream.addVideoListener(videoStreamVideoListener); + newVisualComponents = this.videoStream.getVisualComponents(); + } + else + { + videoLocalSSRC + = videoRemoteSSRC + = CallPeerMediaHandler.SSRC_UNKNOWN; + } + + setLocalSSRC(MediaType.VIDEO, videoLocalSSRC); + setRemoteSSRC(MediaType.VIDEO, videoRemoteSSRC); + + /* + * Notify the VideoListeners in case there was a change in the + * availability of the visual Components displaying remote video. + */ + if ((oldVisualComponents != null) && !oldVisualComponents.isEmpty()) + { + /* + * Discard Components which are present in the old and in the + * new Lists. + */ + if (newVisualComponents == null) + newVisualComponents = Collections.emptyList(); + for (Component oldVisualComponent : oldVisualComponents) + { + if (!newVisualComponents.remove(oldVisualComponent)) + { + fireVideoEvent( + VideoEvent.VIDEO_REMOVED, + oldVisualComponent, + VideoEvent.REMOTE); + } + } + } + if ((newVisualComponents != null) && !newVisualComponents.isEmpty()) + { + for (Component newVisualComponent : newVisualComponents) + { + fireVideoEvent( + VideoEvent.VIDEO_ADDED, + newVisualComponent, + VideoEvent.REMOTE); + } + } + } + } +} diff --git a/src/net/java/sip/communicator/service/protocol/media/TransportManager.java b/src/net/java/sip/communicator/service/protocol/media/TransportManager.java index 5758e84..5ee00ff 100644 --- a/src/net/java/sip/communicator/service/protocol/media/TransportManager.java +++ b/src/net/java/sip/communicator/service/protocol/media/TransportManager.java @@ -155,30 +155,50 @@ public abstract class TransportManager> } /** - * Closes both the control and the data socket of the specified connector - * and releases its reference (if it wasn't the case already). + * Closes the existing StreamConnector, if any, associated with a + * specific MediaType and removes its reference from this + * TransportManager. * - * @param mediaType the type of the connector we'd like to close. + * @param mediaType the MediaType associated with the + * StreamConnector to close */ public void closeStreamConnector(MediaType mediaType) { int index = mediaType.ordinal(); - StreamConnector connector = streamConnectors[index]; + StreamConnector streamConnector = streamConnectors[index]; - if (connector != null) + if (streamConnector != null) { - /* - * XXX The connected owns the sockets so it is important that it - * decides whether to close them i.e. this TransportManager is not - * allowed to explicitly close the sockets by itself. - */ - connector.close(); - + closeStreamConnector(mediaType, streamConnector); streamConnectors[index] = null; } } /** + * Closes a specific StreamConnector associated with a specific + * MediaType. If this TransportManager has a reference to + * the specified streamConnector, it remains. Allows extenders to + * override and perform additional customizations to the closing of the + * specified streamConnector. + * + * @param mediaType the MediaType associated with the specified + * streamConnector + * @param streamConnector the StreamConnector to be closed + * @see #closeStreamConnector(MediaType) + */ + protected void closeStreamConnector( + MediaType mediaType, + StreamConnector streamConnector) + { + /* + * XXX The connected owns the sockets so it is important that it + * decides whether to close them i.e. this TransportManager is not + * allowed to explicitly close the sockets by itself. + */ + streamConnector.close(); + } + + /** * Creates a media StreamConnector. The method takes into account * the minimum and maximum media port boundaries. * diff --git a/src/net/java/sip/communicator/service/protocol/media/protocol.media.manifest.mf b/src/net/java/sip/communicator/service/protocol/media/protocol.media.manifest.mf index 27715f1..c855c6f 100644 --- a/src/net/java/sip/communicator/service/protocol/media/protocol.media.manifest.mf +++ b/src/net/java/sip/communicator/service/protocol/media/protocol.media.manifest.mf @@ -15,5 +15,6 @@ Import-Package: org.osgi.framework, net.java.sip.communicator.service.protocol.event, net.java.sip.communicator.service.netaddr, net.java.sip.communicator.util, + net.java.sip.communicator.util.event, org.ice4j.ice Export-Package: net.java.sip.communicator.service.protocol.media diff --git a/src/net/java/sip/communicator/service/protocol/protocol.provider.manifest.mf b/src/net/java/sip/communicator/service/protocol/protocol.provider.manifest.mf index 153ca98..775a2ab 100644 --- a/src/net/java/sip/communicator/service/protocol/protocol.provider.manifest.mf +++ b/src/net/java/sip/communicator/service/protocol/protocol.provider.manifest.mf @@ -8,8 +8,9 @@ Import-Package: org.osgi.framework, net.java.sip.communicator.service.configuration, net.java.sip.communicator.service.credentialsstorage, net.java.sip.communicator.service.neomedia, + net.java.sip.communicator.service.resources, net.java.sip.communicator.util, - net.java.sip.communicator.service.resources + net.java.sip.communicator.util.event Export-Package: net.java.sip.communicator.service.protocol, net.java.sip.communicator.service.protocol.aimconstants, net.java.sip.communicator.service.protocol.event, diff --git a/src/net/java/sip/communicator/util/PropertyChangeNotifier.java b/src/net/java/sip/communicator/util/PropertyChangeNotifier.java deleted file mode 100644 index 9039ea9..0000000 --- a/src/net/java/sip/communicator/util/PropertyChangeNotifier.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. - */ -package net.java.sip.communicator.util; - -import java.util.*; -import java.beans.*; - -/** - * Represents a source of PropertyChangeEvents which notifies - * PropertyChangeListeners about changes in the values of properties. - * - * @author Lubomir Marinov - */ -public class PropertyChangeNotifier -{ - - /** - * The list of PropertyChangeListeners interested in and notified - * about changes in the values of the properties of this - * PropertyChangeNotifier. - */ - private final List listeners - = new Vector(); - - /** - * Adds a specific PropertyChangeListener to the list of listeners - * interested in and notified about changes in the values of the properties - * of this PropertyChangeNotifier. - * - * @param listener a PropertyChangeListener to be notified about - * changes in the values of the properties of this - * PropertyChangeNotifier. If the specified listener is already in - * the list of interested listeners (i.e. it has been previously added), it - * is not added again. - */ - public void addPropertyChangeListener(PropertyChangeListener listener) - { - if (listener != null) - synchronized (listeners) - { - if (!listeners.contains(listener)) - listeners.add(listener); - } - } - - /** - * Removes a specific PropertyChangeListener from the list of - * listeners interested in and notified about changes in the values of the - * properties of this PropertyChangeNotifer. - * - * @param listener a PropertyChangeListener to no longer be - * notified about changes in the values of the properties of this - * PropertyChangeNotifier - */ - public void removePropertyChangeListener(PropertyChangeListener listener) - { - if (listener != null) - synchronized (listeners) - { - listeners.remove(listener); - } - } - - /** - * Fires a new PropertyChangeEvent to the - * PropertyChangeListeners registered with this - * PropertyChangeNotifier in order to notify about a change in the - * value of a specific property which had its old value modified to a - * specific new value. - * - * @param property the name of the property of this - * PropertyChangeNotifier which had its value changed - * @param oldValue the value of the property with the specified name before - * the change - * @param newValue the value of the property with the specified name after - * the change - */ - protected void firePropertyChange( - String property, - Object oldValue, - Object newValue) - { - PropertyChangeListener[] listeners; - synchronized (this.listeners) - { - listeners - = this.listeners - .toArray( - new PropertyChangeListener[this.listeners.size()]); - } - - PropertyChangeEvent event - = new PropertyChangeEvent( - getPropertyChangeSource(property, oldValue, newValue), - property, - oldValue, - newValue); - - for (PropertyChangeListener listener : listeners) - listener.propertyChange(event); - } - - /** - * Gets the Object to be reported as the source of a new - * PropertyChangeEvent which is to notify the - * PropertyChangeListeners registered with this - * PropertyChangeNotifier about the change in the value of a - * property with a specific name from a specific old value to a specific new - * value. - * - * @param property the name of the property which had its value changed from - * the specified old value to the specified new value - * @param oldValue the value of the property with the specified name before - * the change - * @param newValue the value of the property with the specified name after - * the change - * @return the Object to be reported as the source of the new - * PropertyChangeEvent which is to notify the - * PropertyChangeListeners registered with this - * PropertyChangeNotifier about the change in the value of the - * property with the specified name from the specified old value to the - * specified new value - */ - protected Object getPropertyChangeSource( - String property, - Object oldValue, - Object newValue) - { - return this; - } -} diff --git a/src/net/java/sip/communicator/util/event/PropertyChangeNotifier.java b/src/net/java/sip/communicator/util/event/PropertyChangeNotifier.java new file mode 100644 index 0000000..a5f683f --- /dev/null +++ b/src/net/java/sip/communicator/util/event/PropertyChangeNotifier.java @@ -0,0 +1,135 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.util.event; + +import java.util.*; +import java.beans.*; + +/** + * Represents a source of PropertyChangeEvents which notifies + * PropertyChangeListeners about changes in the values of properties. + * + * @author Lyubomir Marinov + */ +public class PropertyChangeNotifier +{ + + /** + * The list of PropertyChangeListeners interested in and notified + * about changes in the values of the properties of this + * PropertyChangeNotifier. + */ + private final List listeners + = new Vector(); + + /** + * Adds a specific PropertyChangeListener to the list of listeners + * interested in and notified about changes in the values of the properties + * of this PropertyChangeNotifier. + * + * @param listener a PropertyChangeListener to be notified about + * changes in the values of the properties of this + * PropertyChangeNotifier. If the specified listener is already in + * the list of interested listeners (i.e. it has been previously added), it + * is not added again. + */ + public void addPropertyChangeListener(PropertyChangeListener listener) + { + if (listener != null) + synchronized (listeners) + { + if (!listeners.contains(listener)) + listeners.add(listener); + } + } + + /** + * Removes a specific PropertyChangeListener from the list of + * listeners interested in and notified about changes in the values of the + * properties of this PropertyChangeNotifer. + * + * @param listener a PropertyChangeListener to no longer be + * notified about changes in the values of the properties of this + * PropertyChangeNotifier + */ + public void removePropertyChangeListener(PropertyChangeListener listener) + { + if (listener != null) + synchronized (listeners) + { + listeners.remove(listener); + } + } + + /** + * Fires a new PropertyChangeEvent to the + * PropertyChangeListeners registered with this + * PropertyChangeNotifier in order to notify about a change in the + * value of a specific property which had its old value modified to a + * specific new value. + * + * @param property the name of the property of this + * PropertyChangeNotifier which had its value changed + * @param oldValue the value of the property with the specified name before + * the change + * @param newValue the value of the property with the specified name after + * the change + */ + protected void firePropertyChange( + String property, + Object oldValue, + Object newValue) + { + PropertyChangeListener[] listeners; + synchronized (this.listeners) + { + listeners + = this.listeners + .toArray( + new PropertyChangeListener[this.listeners.size()]); + } + + PropertyChangeEvent event + = new PropertyChangeEvent( + getPropertyChangeSource(property, oldValue, newValue), + property, + oldValue, + newValue); + + for (PropertyChangeListener listener : listeners) + listener.propertyChange(event); + } + + /** + * Gets the Object to be reported as the source of a new + * PropertyChangeEvent which is to notify the + * PropertyChangeListeners registered with this + * PropertyChangeNotifier about the change in the value of a + * property with a specific name from a specific old value to a specific new + * value. + * + * @param property the name of the property which had its value changed from + * the specified old value to the specified new value + * @param oldValue the value of the property with the specified name before + * the change + * @param newValue the value of the property with the specified name after + * the change + * @return the Object to be reported as the source of the new + * PropertyChangeEvent which is to notify the + * PropertyChangeListeners registered with this + * PropertyChangeNotifier about the change in the value of the + * property with the specified name from the specified old value to the + * specified new value + */ + protected Object getPropertyChangeSource( + String property, + Object oldValue, + Object newValue) + { + return this; + } +} diff --git a/src/net/java/sip/communicator/util/event/SizeChangeVideoEvent.java b/src/net/java/sip/communicator/util/event/SizeChangeVideoEvent.java new file mode 100644 index 0000000..e4982bb --- /dev/null +++ b/src/net/java/sip/communicator/util/event/SizeChangeVideoEvent.java @@ -0,0 +1,96 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.util.event; + +import java.awt.*; + +/** + * Represents a VideoEvent which notifies about an update to the size + * of a specific visual Component depicting video. + * + * @author Lyubomir Marinov + */ +public class SizeChangeVideoEvent + extends VideoEvent +{ + /** + * Serial version UID. + */ + private static final long serialVersionUID = 0L; + + /** + * The type of a VideoEvent which notifies about an update to the + * size of a specific visual Component depicting video. + */ + public static final int VIDEO_SIZE_CHANGE = 3; + + /** + * The new height of the associated visual Component. + */ + private final int height; + + /** + * The new width of the associated visual Component. + */ + private final int width; + + /** + * Initializes a new SizeChangeVideoEvent which is to notify about + * an update to the size of a specific visual Component depicting + * video. + * + * @param source the source of the new SizeChangeVideoEvent + * @param visualComponent the visual Component depicting video + * with the updated size + * @param origin the origin of the video the new + * SizeChangeVideoEvent is to notify about + * @param width the new width of visualComponent + * @param height the new height of visualComponent + */ + public SizeChangeVideoEvent( + Object source, + Component visualComponent, + int origin, + int width, + int height) + { + super(source, VIDEO_SIZE_CHANGE, visualComponent, origin); + + this.width = width; + this.height = height; + } + + @Override + public VideoEvent clone(Object source) + { + return + new SizeChangeVideoEvent( + source, + getVisualComponent(), getOrigin(), + getWidth(), getHeight()); + } + + /** + * Gets the new height of the associated visual Component. + * + * @return the new height of the associated visual Component + */ + public int getHeight() + { + return height; + } + + /** + * Gets the new width of the associated visual Component. + * + * @return the new width of the associated visual Component + */ + public int getWidth() + { + return width; + } +} diff --git a/src/net/java/sip/communicator/util/event/VideoEvent.java b/src/net/java/sip/communicator/util/event/VideoEvent.java new file mode 100644 index 0000000..695e004 --- /dev/null +++ b/src/net/java/sip/communicator/util/event/VideoEvent.java @@ -0,0 +1,226 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.util.event; + +import java.awt.*; +import java.util.*; + +/** + * Represents an event fired by providers of visual Components + * depicting video to notify about changes in the availability of such + * Components. + * + * @author Lyubomir Marinov + */ +public class VideoEvent + extends EventObject +{ + /** + * Serial version UID. + */ + private static final long serialVersionUID = 0L; + + /** + * The video origin of a VideoEvent which is local to the executing + * client such as a local video capture device. + */ + public static final int LOCAL = 1; + + /** + * The video origin of a VideoEvent which is remote to the + * executing client such as a video being remotely streamed from a + * CallPeer. + */ + public static final int REMOTE = 2; + + /** + * The type of a VideoEvent which notifies about a specific visual + * Component depicting video being made available by the firing + * provider. + */ + public static final int VIDEO_ADDED = 1; + + /** + * The type of a VideoEvent which notifies about a specific visual + * Component depicting video no longer being made available by the + * firing provider. + */ + public static final int VIDEO_REMOVED = 2; + + /** + * The indicator which determines whether 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). + */ + private boolean consumed; + + /** + * The origin of the video this VideoEvent notifies about which is + * one of {@link #LOCAL} and {@link #REMOTE}. + */ + private final int origin; + + /** + * The type of availability change this VideoEvent notifies about + * which is one of {@link #VIDEO_ADDED} and {@link #VIDEO_REMOVED}. + */ + private final int type; + + /** + * The visual Component depicting video which had its availability + * changed and which this VideoEvent notifies about. + */ + private final Component visualComponent; + + /** + * Initializes a new VideoEvent which is to notify about a specific + * change in the availability of a specific visual Component + * depicting video and being provided by a specific source. + * + * @param source the source of the new VideoEvent and the provider + * of the visual Component depicting video + * @param type the type of the availability change which has caused the new + * VideoEvent to be fired + * @param visualComponent the visual Component depicting video + * which had its availability in the source provider changed + * @param origin the origin of the video the new VideoEvent is to + * notify about + */ + public VideoEvent( + Object source, + int type, + Component visualComponent, + int origin) + { + super(source); + + this.type = type; + this.visualComponent = visualComponent; + this.origin = origin; + } + + public VideoEvent clone(Object source) + { + return + new VideoEvent( + source, + getType(), getVisualComponent(), getOrigin()); + } + + /** + * Consumes this event and, more specifically, marks the Component + * it describes as owned, referenced in order to let other potential + * consumers know about its current ownership status (which is important + * because Components belong to a single Container at a + * time). + */ + public void consume() + { + consumed = true; + } + + /** + * Gets the origin of the video this VideoEvent notifies about + * which is one of {@link #LOCAL} and {@link #REMOTE}. + * + * @return one of {@link #LOCAL} and {@link #REMOTE} which specifies the + * origin of the video this VideoEvent notifies about + */ + public int getOrigin() + { + return origin; + } + + /** + * Gets the type of availability change this VideoEvent notifies + * about which is one of {@link #VIDEO_ADDED} and {@link #VIDEO_REMOVED}. + * + * @return one of {@link #VIDEO_ADDED} and {@link #VIDEO_REMOVED} which + * describes the type of availability change this VideoEvent + * notifies about + */ + public int getType() + { + return type; + } + + /** + * Gets the visual Component depicting video which had its + * availability changed and which this VideoEvent notifies about. + * + * @return the visual Component depicting video which had its + * availability changed and which this VideoEvent notifies about + */ + public Component getVisualComponent() + { + return visualComponent; + } + + /** + * Determines whether 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). + * + * @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 + */ + public boolean isConsumed() + { + return consumed; + } + + /** + * Returns a human-readable representation of a specific VideoEvent + * origin constant in the form of a String value. + * + * @param origin one of the VideoEvent origin constants such as + * {@link #LOCAL} or {@link #REMOTE} + * @return a String value which gives a human-readable + * representation of the specified VideoEvent origin + * constant + */ + public static String originToString(int origin) + { + switch (origin) + { + case VideoEvent.LOCAL: + return "LOCAL"; + case VideoEvent.REMOTE: + return "REMOTE"; + default: + throw new IllegalArgumentException("origin"); + } + } + + /** + * Returns a human-readable representation of a specific VideoEvent + * type constant in the form of a String value. + * + * @param type one of the VideoEvent type constants such as + * {@link #VIDEO_ADDED} or {@link #VIDEO_REMOVED} + * @return a String value which gives a human-readable + * representation of the specified VideoEvent type + * constant + */ + public static String typeToString(int type) + { + switch (type) + { + case VideoEvent.VIDEO_ADDED: + return "VIDEO_ADDED"; + case VideoEvent.VIDEO_REMOVED: + return "VIDEO_REMOVED"; + default: + throw new IllegalArgumentException("type"); + } + } +} diff --git a/src/net/java/sip/communicator/util/event/VideoListener.java b/src/net/java/sip/communicator/util/event/VideoListener.java new file mode 100644 index 0000000..25266a7 --- /dev/null +++ b/src/net/java/sip/communicator/util/event/VideoListener.java @@ -0,0 +1,50 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.util.event; + +import java.util.*; + +/** + * Defines the notification support informing about changes in the availability + * of visual Components representing video such as adding and + * removing. + * + * @author Lyubomir Marinov + */ +public interface VideoListener + extends EventListener +{ + + /** + * Notifies that a visual Component representing video has been + * added to the provider this listener has been added to. + * + * @param event a VideoEvent describing the added visual + * Component representing video and the provider it was added into + */ + void videoAdded(VideoEvent event); + + /** + * Notifies that a visual Component representing video has been + * removed from the provider this listener has been added to. + * + * @param event a VideoEvent describing the removed visual + * Component representing video and the provider it was removed + * from + */ + void videoRemoved(VideoEvent event); + + /** + * Notifies about an update to a visual Component representing + * video. + * + * @param event a VideoEvent describing the visual + * Component related to the update and the details of the specific + * update + */ + void videoUpdate(VideoEvent event); +} diff --git a/src/net/java/sip/communicator/util/event/VideoNotifierSupport.java b/src/net/java/sip/communicator/util/event/VideoNotifierSupport.java new file mode 100644 index 0000000..af30eaf --- /dev/null +++ b/src/net/java/sip/communicator/util/event/VideoNotifierSupport.java @@ -0,0 +1,327 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.util.event; + +import java.awt.*; +import java.util.*; +import java.util.List; // disambiguation + +/** + * Represents a mechanism to easily add to a specific Object by means + * of composition support for firing VideoEvents to + * VideoListeners. + * + * @author Lyubomir Marinov + */ +public class VideoNotifierSupport +{ + private static final long THREAD_TIMEOUT = 5000; + + /** + * The list of VideoEvents which are to be delivered to the + * {@link #listeners} registered with this instance when + * {@link #synchronous} is equal to false. + */ + private final List events; + + /** + * The list of VideoListeners interested in changes in the + * availability of visual Components depicting video. + */ + private final List listeners + = new ArrayList(); + + /** + * The Object which is to be reported as the source of the + * VideoEvents fired by this instance. + */ + private final Object source; + + /** + * The indicator which determines whether this instance delivers the + * VideoEvents to the {@link #listeners} synchronously. + */ + private final boolean synchronous; + + /** + * The Thread in which {@link #events} are delivered to the + * {@link #listeners} when {@link #synchronous} is equal to false. + */ + private Thread thread; + + /** + * Initializes a new VideoNotifierSupport instance which is to + * facilitate the management of VideoListeners and firing + * VideoEvents to them for a specific Object. + * + * @param source the Object which is to be reported as the source + * of the VideoEvents fired by the new instance + */ + public VideoNotifierSupport(Object source) + { + this(source, true); + } + + /** + * Initializes a new VideoNotifierSupport instance which is to + * facilitate the management of VideoListeners and firing + * VideoEvents to them for a specific Object. + * + * @param source the Object which is to be reported as the source + * of the VideoEvents fired by the new instance + * @param synchronous true if the new instance is to deliver the + * VideoEvents synchronously; otherwise, false + */ + public VideoNotifierSupport(Object source, boolean synchronous) + { + this.source = source; + this.synchronous = synchronous; + + events = this.synchronous ? null : new LinkedList(); + } + + /** + * Adds a specific VideoListener to this + * VideoNotifierSupport in order to receive notifications when + * visual/video Components are being added and removed. + *

+ * Adding a listener which has already been added does nothing i.e. it is + * not added more than once and thus does not receive one and the same + * VideoEvent multiple times. + *

+ * + * @param listener the VideoListener to be notified when + * visual/video Components are being added or removed in this + * VideoNotifierSupport + */ + public void addVideoListener(VideoListener listener) + { + if (listener == null) + throw new NullPointerException("listener"); + + synchronized (listeners) + { + if (!listeners.contains(listener)) + listeners.add(listener); + } + } + + 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 VideoListeners registered with this + * VideoMediaStream 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 + * @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 true if the call is to wait till the specified + * VideoEvent has been delivered to the VideoListeners; + * otherwise, false + * @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 + */ + public boolean fireVideoEvent( + int type, Component visualComponent, int origin, + boolean wait) + { + VideoEvent event + = new VideoEvent(source, type, visualComponent, origin); + + fireVideoEvent(event, wait); + return event.isConsumed(); + } + + /** + * Notifies the VideoListeners registered with this instance about + * a specific VideoEvent. + * + * @param event the VideoEvent to be fired to the + * VideoListeners registered with this instance + * @param wait true if the call is to wait till the specified + * VideoEvent has been delivered to the VideoListeners; + * otherwise, false + */ + public void fireVideoEvent(VideoEvent event, boolean wait) + { + if (synchronous) + doFireVideoEvent(event); + else + { + synchronized (events) + { + 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(); + } + } + } + } + + /** + * Removes a specific VideoListener from this + * VideoNotifierSupport in order to have to no longer receive + * notifications when visual/video Components are being added and + * removed. + * + * @param listener the VideoListener to no longer be notified when + * visual/video Components are being added or removed + */ + public void removeVideoListener(VideoListener listener) + { + synchronized (listeners) + { + 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/util/util.manifest.mf b/src/net/java/sip/communicator/util/util.manifest.mf index 1c73e3e..8e980fc 100644 --- a/src/net/java/sip/communicator/util/util.manifest.mf +++ b/src/net/java/sip/communicator/util/util.manifest.mf @@ -42,11 +42,12 @@ Import-Package: org.xml.sax, com.sun.awt, org.xbill.DNS Export-Package: net.java.sip.communicator.util.xml, + net.java.sip.communicator.util.swing.transparent, net.java.sip.communicator.util.swing.plaf, net.java.sip.communicator.util.swing.event, - net.java.sip.communicator.util.swing, - net.java.sip.communicator.util.swing.transparent, net.java.sip.communicator.util.swing.border, - net.java.sip.communicator.util, + net.java.sip.communicator.util.swing, net.java.sip.communicator.util.skin, - net.java.sip.communicator.util.launchutils + net.java.sip.communicator.util.launchutils, + net.java.sip.communicator.util.event, + net.java.sip.communicator.util -- cgit v1.1