diff options
author | Lyubomir Marinov <lyubomir.marinov@jitsi.org> | 2012-04-02 06:29:32 +0000 |
---|---|---|
committer | Lyubomir Marinov <lyubomir.marinov@jitsi.org> | 2012-04-02 06:29:32 +0000 |
commit | 6eae7b0183c65e16ccbbe097b726a9fee4c4e66a (patch) | |
tree | af778cef410f272b25bf32abfbc123f41fc1dea4 /src/net/java/sip | |
parent | dd3fc88660d5ff9b02d68f208c94b6ea69cfffdb (diff) | |
download | jitsi-6eae7b0183c65e16ccbbe097b726a9fee4c4e66a.zip jitsi-6eae7b0183c65e16ccbbe097b726a9fee4c4e66a.tar.gz jitsi-6eae7b0183c65e16ccbbe097b726a9fee4c4e66a.tar.bz2 |
Adds (experimental) support for the cobri Jabber extension.
Diffstat (limited to 'src/net/java/sip')
33 files changed, 2288 insertions, 1354 deletions
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 <tt>ZrtpControl</tt> used to control all zrtp options - * on particular stream. + * Initializes a new <tt>ZrtpControl</tt> instance which is to control all + * ZRTP options. * - * @return ZrtpControl instance. + * @return a new <tt>ZrtpControl</tt> instance which is to control all ZRTP + * options */ public ZrtpControl createZrtpControl() { @@ -721,9 +734,11 @@ public class MediaServiceImpl } /** - * Creates <tt>SDesControl</tt> used to control all SDes options. + * Initializes a new <tt>SDesControl</tt> instance which is to control all + * SDes options. * - * @return SDesControl instance. + * @return a new <tt>SDesControl</tt> instance which is to control all SDes + * options */ public SDesControl createSDesControl() { @@ -778,13 +793,10 @@ public class MediaServiceImpl ScreenDevice screens[] = ScreenDeviceImpl.getAvailableScreenDevice(); List<ScreenDevice> screenList; - if (screens != null) - { - screenList = new ArrayList<ScreenDevice>(screens.length); - screenList.addAll(Arrays.asList(screens)); - } + if ((screens != null) && (screens.length != 0)) + screenList = new ArrayList<ScreenDevice>(Arrays.asList(screens)); else - screenList = new ArrayList<ScreenDevice>(); + 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 <tt>MediaStreamImpl</tt> 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<CaptureDeviceInfo> videoCaptureDevices = CaptureDeviceManager.getDeviceList(format); CaptureDeviceInfo videoCaptureDevice = null; @@ -426,9 +427,10 @@ public class DeviceConfiguration */ public CaptureDeviceInfo[] getAvailableAudioCaptureDevices() { - Vector<CaptureDeviceInfo> audioCaptureDevices = - CaptureDeviceManager.getDeviceList(new AudioFormat( - AudioFormat.LINEAR, -1, 16, -1)); + @SuppressWarnings("unchecked") + Vector<CaptureDeviceInfo> 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<CaptureDeviceInfo> captureDeviceInfos = - CaptureDeviceManager.getDeviceList(format); + @SuppressWarnings("unchecked") + Vector<CaptureDeviceInfo> captureDeviceInfos + = CaptureDeviceManager.getDeviceList(format); if(useCase != MediaUseCase.ANY) { @@ -1080,6 +1083,7 @@ public class DeviceConfiguration */ private void registerCustomRenderers() { + @SuppressWarnings("unchecked") Vector<String> 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<String> 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 <tt>MediaDevice</tt> 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 <tt>MediaDeviceSession</tt> 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 <tt>Call</tt> 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 <tt>CallPeer</tt> will support </tt>inputevt</tt> + * Indicates if the <tt>CallPeer</tt> will support <tt>inputevt</tt> * 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 <tt>CallPeerMediaHandler</tt> state which is to be used by the + * <tt>CallPeer</tt>s of this <tt>Call</tt> which use {@link #cobri}. + */ + private MediaHandler cobriMediaHandler; + + private final List<WeakReference<CobriStreamConnector>> + cobriStreamConnectors + = new ArrayList<WeakReference<CobriStreamConnector>>( + MediaType.values().length); + + /** * Initializes a new <tt>CallJabberImpl</tt> instance belonging to * <tt>sourceProvider</tt> and associated with the jingle session with the * specified <tt>jingleSID</tt>. 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<ContentPacketExtension> offer = - callPeer.getSessionIQ().getContentList(); - Map<MediaType, MediaDirection> directions = new HashMap<MediaType, - MediaDirection>(); + List<ContentPacketExtension> offer + = callPeer.getSessionIQ().getContentList(); + Map<MediaType, MediaDirection> directions + = new HashMap<MediaType, MediaDirection>(); 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 <tt>CallGroup</tt>. + * Notifies this instance that a specific <tt>Call</tt> has been added to a + * <tt>CallGroup</tt>. * - * @param evt event + * @param evt a <tt>CallGroupEvent</tt> which specifies the <tt>Call</tt> + * which has been added to a <tt>CallGroup</tt> + * @see MediaAwareCall#callAdded(CallGroupEvent) */ + @Override public void callAdded(CallGroupEvent evt) { Iterator<CallPeerJabberImpl> 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 <tt>CobriStreamConnector</tt> which is associated with + * a <tt>MediaStream</tt> of a specific <tt>MediaType</tt> upon request from + * a specific <tt>CallPeer</tt>. + * + * @param peer the <tt>CallPeer</tt> which requests the closing of the + * specified <tt>cobriStreamConnector</tt> + * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> with + * which the specified <tt>cobriStreamConnector</tt> is associated + * @param cobriStreamConnector the <tt>CobriStreamConnector</tt> to close on + * behalf of the specified <tt>peer</tt> + */ + public void closeCobriStreamConnector( + CallPeerJabberImpl peer, + MediaType mediaType, + CobriStreamConnector cobriStreamConnector) + { + cobriStreamConnector.close(); + } + + /** + * Allocates cobri (conference) channels for asp ecific <tt>MediaType</tt> + * to be used by a specific <tt>CallPeer</tt>. + * + * @param peer the <tt>CallPeer</tt> which is to use the allocated cobri + * (conference) channels + * @param mediaTypes the <tt>MediaType</tt>s for which cobri (conference) + * channels are to be allocated + * @return a <tt>CobriConferenceIQ</tt> which describes the allocated cobri + * (conference) channels for the specified <tt>mediaTypes</tt> which are to + * be used by the specified <tt>peer</tt>; otherwise, <tt>null</tt> + */ + public CobriConferenceIQ createCobriChannels( + CallPeerJabberImpl peer, + Iterable<MediaType> 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 <tt>CobriStreamConnector</tt> on behalf of a specific + * <tt>CallPeer</tt> to be used in association with a specific + * <tt>CobriConferenceIQ.Channel</tt> of a specific <tt>MediaType</tt>. + * + * @param peer the <tt>CallPeer</tt> which requests the initialization of a + * <tt>CobriStreamConnector</tt> + * @param mediaType the <tt>MediaType</tt> of the stream which is to use the + * initialized <tt>CobriStreamConnector</tt> for RTP and RTCP traffic + * @param channel the <tt>CobriConferenceIQ.Channel</tt> to which RTP and + * RTCP traffic is to be sent and from which such traffic is to be received + * via the initialized <tt>CobriStreamConnector</tt> + * @param factory a <tt>StreamConnectorFactory</tt> implementation which is + * to allocate the sockets to be used for RTP and RTCP traffic + * @return a <tt>CobriStreamConnector</tt> to be used for RTP and RTCP + * traffic associated with the specified <tt>channel</tt> + */ + 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<CobriStreamConnector> 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>( + 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<Iterable<ContentPacketExtension>>(); /** + * 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 + * <tt>CallJabberImpl</tt> which provides information specific to this + * <tt>RawUdpTransportManager</tt> 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 <tt>StreamConnector</tt> associated with a specific + * <tt>MediaType</tt>. If this <tt>TransportManager</tt> has a reference to + * the specified <tt>streamConnector</tt>, it remains. + * + * @param mediaType the <tt>MediaType</tt> associated with the specified + * <tt>streamConnector</tt> + * @param streamConnector the <tt>StreamConnector</tt> 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 <tt>StreamConnector</tt> for a stream of a specific + * <tt>MediaType</tt>. + * + * @param mediaType the <tt>MediaType</tt> of the stream for which a + * <tt>StreamConnector</tt> is to be created + * @return a <tt>StreamConnector</tt> for the stream of the specified + * <tt>mediaType</tt> + * @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 * <tt>connector</tt>. * + * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> which + * uses the specified <tt>connector</tt> * @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 <tt>MediaType</tt> and is to be either locally + * or remotely used. + * + * @param mediaType the <tt>MediaType</tt> associated with the content which + * contains the <tt>CobriConferenceIQ.Channel</tt> to get + * @param local <tt>true</tt> if the <tt>CobriConferenceIQ.Channel</tt> + * which is to be used locally is to be returned or <tt>false</tt> for the + * one which is to be used remotely + * @return the <tt>CobriConferenceIQ.Channel</tt> which belongs to a content + * associated with the specified <tt>mediaType</tt> and which is to be used + * in accord with the specified <tt>local</tt> indicator if such a channel + * exists; otherwise, <tt>null</tt> + */ + 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<CobriConferenceIQ.Channel> 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 * <tt>TransportManagerJabberImpl</tt>. @@ -210,31 +386,12 @@ public class RawUdpTransportManager * {@link #wrapupCandidateHarvest()}. * @throws OperationFailedException in case we fail allocating ports */ - public void startCandidateHarvest(List<ContentPacketExtension> ourOffer, - TransportInfoSender transportInfoSender) + public void startCandidateHarvest( + List<ContentPacketExtension> 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<ContentPacketExtension> 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<MediaType> mediaTypes = new ArrayList<MediaType>(); + + 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<ContentPacketExtension>, 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 <tt>StreamConnector</tt> which allows sharing a specific + * <tt>StreamConnector</tt> instance among multiple <tt>TransportManager</tt>s + * for the purposes of the Jitsi VideoBridge. + * + * @author Lyubomir Marinov + */ +public class CobriStreamConnector + extends StreamConnectorDelegate<StreamConnector> +{ + /** + * Initializes a new <tt>CobriStreamConnector</tt> instance which is to + * share a specific <tt>StreamConnector</tt> instance among multiple + * <tt>TransportManager</tt>s for the purposes of the Jitsi VideoBridge. + * + * @param streamConnector the <tt>StreamConnector</tt> instance to be shared + * by the new instance among multiple <tt>TransportManager</tt>s 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 + * <tt>StreamConnector</tt> instance. + * + * @param <T> the very type of the <tt>StreamConnector</tt> wrapped by + * <tt>StreamConnectorDelegate</tt> + * + * @author Lyubomir Marinov + */ +public class StreamConnectorDelegate<T extends StreamConnector> + implements StreamConnector +{ + /** + * The <tt>StreamConnector</tt> wrapped by this instance. + */ + protected final T streamConnector; + + /** + * Initializes a new <tt>StreamConnectorDelegate</tt> which is to wrap a + * specific <tt>StreamConnector</tt>. + * + * @param streamConnector the <tt>StreamConnector</tt> 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 <tt>StreamConnector</tt> 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 <tt>StreamConnector</tt> instances. + * + * @author Lyubomir Marinov + */ +public interface StreamConnectorFactory +{ + /** + * Initializes a <tt>StreamConnector</tt> instance. + * + * @return a <tt>StreamConnector</tt> 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 <tt>MediaStream</tt> interface and adds methods specific to 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 <tt>LogMessageNotificationHandler</tt> interface.
- *
- * @author Yana Stamcheva
- */
-public class LogMessageNotificationAction
- extends NotificationAction
-{
- /**
- * Indicates that this log is of type trace. If this <tt>logType</tt> 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 <tt>logType</tt> 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 <tt>logType</tt> 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 <tt>LogMessageNotificationHandlerImpl</tt> 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 <tt>LogMessageNotificationHandler</tt> interface. + * + * @author Yana Stamcheva + */ +public class LogMessageNotificationAction + extends NotificationAction +{ + /** + * Indicates that this log is of type trace. If this <tt>logType</tt> 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 <tt>logType</tt> 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 <tt>logType</tt> 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 <tt>LogMessageNotificationHandlerImpl</tt> 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 <tt>CallPeer</tt> 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 <tt>ConferenceMember</tt> * 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 <tt>OperationSet</tt> 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 <tt>VideoEvent</tt> which notifies about an update to the size - * of a specific visual <tt>Component</tt> depicting video. - * - * @author Lubomir Marinov - */ -public class SizeChangeVideoEvent - extends VideoEvent -{ - /** - * Serial version UID. - */ - private static final long serialVersionUID = 0L; - - /** - * The type of a <tt>VideoEvent</tt> which notifies about an update to the - * size of a specific visual <tt>Component</tt> depicting video. - */ - public static final int VIDEO_SIZE_CHANGE = 3; - - /** - * The new height of the associated visual <tt>Component</tt>. - */ - private final int height; - - /** - * The new width of the associated visual <tt>Component</tt>. - */ - private final int width; - - /** - * Initializes a new <tt>SizeChangeVideoEvent</tt> which is to notify about - * an update to the size of a specific visual <tt>Component</tt> depicting - * video. - * - * @param source the source of the new <tt>SizeChangeVideoEvent</tt> - * @param visualComponent the visual <tt>Component</tt> depicting video - * with the updated size - * @param origin the origin of the video the new - * <tt>SizeChangeVideoEvent</tt> is to notify about - * @param width the new width of <tt>visualComponent</tt> - * @param height the new height of <tt>visualComponent</tt> - */ - 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 <tt>Component</tt>. - * - * @return the new height of the associated visual <tt>Component</tt> - */ - public int getHeight() - { - return height; - } - - /** - * Gets the new width of the associated visual <tt>Component</tt>. - * - * @return the new width of the associated visual <tt>Component</tt> - */ - 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 <code>Component</code>s
- * depicting video to notify about changes in the availability of such
- * <code>Component</code>s.
- *
- * @author Lubomir Marinov
- */
-public class VideoEvent
- extends EventObject
-{
- /**
- * Serial version UID.
- */
- private static final long serialVersionUID = 0L;
-
- /**
- * The video origin of a <code>VideoEvent</code> 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 <code>VideoEvent</code> which is remote to the
- * executing client such as a video being remotely streamed from a
- * <code>CallPeer</code>.
- */
- public static final int REMOTE = 2;
-
- /**
- * The type of a <code>VideoEvent</code> which notifies about a specific
- * visual <code>Component</code> depicting video being made available by the
- * firing provider.
- */
- public static final int VIDEO_ADDED = 1;
-
- /**
- * The type of a <code>VideoEvent</code> which notifies about a specific
- * visual <code>Component</code> 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 <code>Component</code> it describes have been consumed and
- * should be considered owned, referenced (which is important because
- * <code>Component</code>s belong to a single <code>Container</code> at a
- * time).
- */
- private boolean consumed;
-
- /**
- * The origin of the video this <code>VideoEvent</code> notifies about which
- * is one of {@link #LOCAL} and {@link #REMOTE}.
- */
- private final int origin;
-
- /**
- * The type of availability change this <code>VideoEvent</code> notifies
- * about which is one of {@link #VIDEO_ADDED} and {@link #VIDEO_REMOVED}.
- */
- private final int type;
-
- /**
- * The visual <code>Component</code> depicting video which had its
- * availability changed and which this <code>VideoEvent</code> notifies
- * about.
- */
- private final Component visualComponent;
-
- /**
- * Initializes a new <code>VideoEvent</code> which is to notify about a
- * specific change in the availability of a specific visual
- * <code>Component</code> depicting video and being provided by a specific
- * source.
- *
- * @param source the source of the new <code>VideoEvent</code> and the
- * provider of the visual <code>Component</code> depicting video
- * @param type the type of the availability change which has caused the new
- * <code>VideoEvent</code> to be fired
- * @param visualComponent the visual <code>Component</code> depicting video
- * which had its availability in the <code>source</code> provider
- * changed
- * @param origin the origin of the video the new <code>VideoEvent</code> 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
- * <code>Component</code> it describes as owned, referenced in order to let
- * other potential consumers know about its current ownership status (which
- * is important because <code>Component</code>s belong to a single
- * <code>Container</code> at a time).
- */
- public void consume()
- {
- consumed = true;
- }
-
- /**
- * Gets the origin of the video this <code>VideoEvent</code> 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 <code>VideoEvent</code> notifies about
- */
- public int getOrigin()
- {
- return origin;
- }
-
- /**
- * Gets the type of availability change this <code>VideoEvent</code>
- * 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
- * <code>VideoEvent</code> notifies about
- */
- public int getType()
- {
- return type;
- }
-
- /**
- * Gets the visual <code>Component</code> depicting video which had its
- * availability changed and which this <code>VideoEvent</code> notifies
- * about.
- *
- * @return the visual <code>Component</code> depicting video which had its
- * availability changed and which this <code>VideoEvent</code>
- * notifies about
- */
- public Component getVisualComponent()
- {
- return visualComponent;
- }
-
- /**
- * Determines whether this event and, more specifically, the visual
- * <code>Component</code> it describes have been consumed and should be
- * considered owned, referenced (which is important because
- * <code>Component</code>s belong to a single <code>Container</code> at a
- * time).
- *
- * @return <tt>true</tt> if this event and, more specifically, the visual
- * <code>Component</code> it describes have been consumed and should
- * be considered owned, referenced (which is important because
- * <code>Component</code>s belong to a single <code>Container</code>
- * at a time); otherwise, <tt>false</tt>
- */
- 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 <tt>Components</tt> representing video such as adding and removing.
- *
- * @author Lubomir Marinov
- */
-public interface VideoListener
- extends EventListener
-{
-
- /**
- * Notifies that a visual <tt>Component</tt> representing video has been
- * added to the provider this listener has been added to.
- *
- * @param event a <tt>VideoEvent</tt> describing the added visual
- * <tt>Component</tt> representing video and the provider it was added into
- */
- void videoAdded(VideoEvent event);
-
- /**
- * Notifies that a visual <tt>Component</tt> representing video has been
- * removed from the provider this listener has been added to.
- *
- * @param event a <tt>VideoEvent</tt> describing the removed visual
- * <tt>Component</tt> representing video and the provider it was removed
- * from
- */
- void videoRemoved(VideoEvent event);
-
- /**
- * Notifies about an update to a visual <tt>Component</tt> representing
- * video.
- *
- * @param event a <tt>VideoEvent</tt> describing the visual
- * <tt>Component</tt> 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 <tt>OperationSetVideoTelephony</tt> 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 <T> the peer extension class like for example <tt>CallPeerSipImpl</tt> * or <tt>CallPeerJabberImpl</tt> @@ -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 + <T extends MediaAwareCallPeer<?, ?, ?>> extends PropertyChangeNotifier { /** - * The <tt>Logger</tt> used by the <tt>CallPeerMediaHandler</tt> - * class and its instances for logging output. - */ - private static final Logger logger - = Logger.getLogger(CallPeerMediaHandler.class); - - /** * The name of the <tt>CallPeerMediaHandler</tt> property which specifies * the local SSRC of its audio <tt>MediaStream</tt>. */ @@ -83,12 +73,24 @@ public abstract class CallPeerMediaHandler< = MediaDirection.RECVONLY; /** + * The <tt>VideoMediaStream</tt> 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 <tt>AudioMediaStream</tt> 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 <tt>SrtpListener</tt> which is responsible for the SRTP control. Most + * often than not, it is the <tt>peer</tt> 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 <tt>CallPeer</tt> 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<MediaTypeSrtpControl, SrtpControl> srtpControls = - new TreeMap<MediaTypeSrtpControl, SrtpControl>(); - - /** - * The <tt>KeyFrameControl</tt> currently known to this - * <tt>CallPeerMediaHandlerSipImpl</tt> and made available by - * {@link #videoStream}. + * The <tt>PropertyChangeListener</tt> which listens to changes in the + * values of the properties of the <tt>Call</tt> of {@link #peer}. */ - private KeyFrameControl keyFrameControl; + private final CallPropertyChangeListener callPropertyChangeListener; /** * The <tt>KeyFrameRequester</tt> implemented by this - * <tt>CallPeerMediaHandlerSipImpl</tt> and provided to - * {@link #keyFrameControl}. + * <tt>CallPeerMediaHandler</tt>. */ private final KeyFrameControl.KeyFrameRequester keyFrameRequester = new KeyFrameControl.KeyFrameRequester() @@ -226,117 +184,75 @@ public abstract class CallPeerMediaHandler< }; /** - * The <tt>List</tt> of <tt>VideoListener</tt>s interested in - * <tt>VideoEvent</tt>s fired by this instance or rather its - * <tt>VideoMediaStream</tt>. + * The state of this instance which may be shared with multiple other + * <tt>CallPeerMediaHandler</tt>s. */ - private final List<VideoListener> videoListeners - = new LinkedList<VideoListener>(); + private MediaHandler mediaHandler; /** * The <tt>PropertyChangeListener</tt> which listens to changes in the - * values of the properties of {@link #audioStream} and - * {@link #videoStream}. + * values of the properties of the <tt>MediaStream</tt>s of this instance. */ private final PropertyChangeListener streamPropertyChangeListener = new PropertyChangeListener() - { - - /** - * Notifies this <tt>PropertyChangeListener</tt> that the value of - * a specific property of the notifier it is registered with has - * changed. - * - * @param evt a <tt>PropertyChangeEvent</tt> 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 <tt>PropertyChangeListener</tt> that the value of + * a specific property of the notifier it is registered with has + * changed. + * + * @param evt a <tt>PropertyChangeEvent</tt> 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 + * <tt>VideoListener</tt>s and firing <tt>VideoEvent</tt>s to them on behalf + * of this instance. + */ + private final VideoNotifierSupport videoNotifierSupport + = new VideoNotifierSupport(this, true); /** - * The neomedia <tt>VideoListener</tt> which listens to {@link #videoStream} - * for changes in the availability of visual <tt>Component</tt>s displaying - * remote video and re-fires them as - * <tt>net.java.sip.communicator.service.protocol.event.VideoEvent</tt>s + * The <tt>VideoListener</tt> which listens to the video + * <tt>MediaStream</tt> of this instance for changes in the availability of + * visual <tt>Component</tt>s 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 <tt>VideoListener</tt> that a new visual - * <tt>Component</tt> displaying remote video has been added in - * {@link CallPeerMediaHandler#videoStream}. - * - * @param event the neomedia <tt>VideoEvent</tt> which specifies the - * newly-added visual <tt>Component</tt> 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 <tt>VideoListener</tt> that a visual - * <tt>Component</tt> displaying remote video has been removed from - * {@link CallPeerMediaHandler#videoStream}. - * - * @param event the neomedia <tt>VideoEvent</tt> which specifies the - * removed visual <tt>Component</tt> 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 <tt>PropertyChangeListener</tt> which listens to changes in the - * values of the properties of the <tt>Call</tt> 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 <tt>MediaStream</tt> that this <tt>MediaHandler</tt> uses for - * specified media <tt>type</tt> and prepares it for garbage collection. + * Closes the <tt>MediaStream</tt> that this instance uses for a specific + * <tt>MediaType</tt> and prepares it for garbage collection. * * @param type the <tt>MediaType</tt> 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<MediaTypeSrtpControl> 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 - * <tt>net.java.sip.communicator.service.protocol.event.VideoEvent</tt> - * instance which represents the same notification/information as a specific - * <tt>net.java.sip.communicator.service.neomedia.event.VideoEvent</tt>. - * - * @param neomediaEvent the - * <tt>net.java.sip.communicator.service.neomedia.event.VideoEvent</tt> to - * represent as a - * <tt>net.java.sip.communicator.service.protocol.event.VideoEvent</tt> - * @param sender the <tt>Object</tt> to be reported as the source of the - * new <tt>VideoEvent</tt> - * @return a new - * <tt>net.java.sip.communicator.service.protocol.event.VideoEvent</tt> - * which represents the same notification/information as the specified - * <tt>neomediaEvent</tt> - */ - 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 <tt>KeyFrameControl</tt> currently known to this - * <tt>CallPeerMediaHandlerSipImpl</tt> made available by a specific - * <tt>VideoMediaStream</tt>. - * - * @param videoStream the <tt>VideoMediaStream</tt> the - * <tt>KeyFrameControl</tt> of which is to be set as the currently known to - * this <tt>CallPeerMediaHandlerSipImpl</tt> - */ - 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 <tt>AudioMediaStream</tt>. - * - * @param audioStream the <tt>AudioMediaStream</tt> 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 <tt>VideoMediaStream</tt>. - * - * @param videoStream the <tt>VideoMediaStream</tt> 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<Component> 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<Component> 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<Component> getVisualComponents() { + MediaStream videoStream = getStream(MediaType.VIDEO); List<Component> 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 <tt>SrtpControls</tt> map. + * Gets the <tt>SrtpControl</tt>s of the <tt>MediaStream</tt>s of this + * instance. * - * @return the currently valid <tt>SrtpControls</tt> map. + * @return the <tt>SrtpControl</tt>s of the <tt>MediaStream</tt>s of this + * instance */ protected Map<MediaTypeSrtpControl, SrtpControl> getSrtpControls() { - return this.srtpControls; + return mediaHandler.getSrtpControls(this); } /** @@ -1271,7 +889,7 @@ public abstract class CallPeerMediaHandler< * @return the newly created <tt>MediaStream</tt>. * * @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 <tt>stream</tt> to use the specified <tt>format</tt>, - * <tt>target</tt>, <tt>target</tt>, and <tt>direction</tt>. - * - * @param device the <tt>MediaDevice</tt> to be used by <tt>stream</tt> - * for capture and playback - * @param format the <tt>MediaFormat</tt> that we'd like the new stream - * to transmit in. - * @param target the <tt>MediaStreamTarget</tt> containing the RTP and - * RTCP address:port couples that the new stream would be sending - * packets to. - * @param direction the <tt>MediaDirection</tt> that we'd like the new - * stream to use (i.e. sendonly, sendrecv, recvonly, or inactive). - * @param rtpExtensions the list of <tt>RTPExtension</tt>s that should be - * enabled for this stream. - * @param stream the <tt>MediaStream</tt> that we'd like to configure. - * @param masterStream whether the stream to be used as master if secured - * - * @return the <tt>MediaStream</tt> that we received as a parameter (for - * convenience reasons). - * - * @throws OperationFailedException if setting the <tt>MediaFormat</tt> - * or connecting to the specified <tt>MediaDevice</tt> fails for some - * reason. - */ - protected MediaStream configureStream( MediaDevice device, - MediaFormat format, - MediaStreamTarget target, - MediaDirection direction, - List<RTPExtension> 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 - * <tt>MediaHandler</tt> with the specified <tt>MediaStream</tt>. - * - * @param stream the <tt>MediaStream</tt> that we'd like to register our - * dynamic payload mappings with. - */ - private void registerDynamicPTsWithStream(MediaStream stream) - { - for (Map.Entry<MediaFormat, Byte> mapEntry - : getDynamicPayloadTypes().getMappings().entrySet()) - { - byte pt = mapEntry.getValue(); - MediaFormat fmt = mapEntry.getKey(); - - stream.addDynamicRTPPayloadType(pt, fmt); - } - } - - /** - * Registers with the specified <tt>MediaStream</tt> all RTP extensions - * negotiated by this <tt>MediaHandler</tt>. - * - * @param stream the <tt>MediaStream</tt> that we'd like to register our - * <tt>RTPExtension</tt>s with. - * @param rtpExtensions the list of <tt>RTPExtension</tt>s that should be - * enabled for <tt>stream</tt>. - */ - private void registerRTPExtensionsWithStream( - List<RTPExtension> rtpExtensions, - MediaStream stream) - { - for ( RTPExtension rtpExtension : rtpExtensions) - { - byte extensionID - = rtpExtensionsRegistry.getExtensionMapping(rtpExtension); - - stream.addRTPExtension(extensionID, rtpExtension); - } - } - - /** * Gets the <tt>MediaStream</tt> of this <tt>CallPeerMediaHandler</tt> which * is of a specific <tt>MediaType</tt>. If this instance doesn't have such a * <tt>MediaStream</tt>, returns <tt>null</tt> @@ -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 <tt>true</tt> if this handler has already started at least one - * of its streams, at least once, and <tt>false</tt> otherwise. - * - * @return <tt>true</tt> if this handler has already started at least one - * of its streams, at least once, and <tt>false</tt> otherwise. - */ - public boolean isStarted() - { - return started; - } - - /** * Starts this <tt>CallPeerMediaHandler</tt>. 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 <tt>MediaType</tt> to get the SRTP control type for + * @return the SRTP control type (MIKEY, SDES, ZRTP) used for the given + * media type or <tt>null</tt> 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 + * <tt>CallPeerMediaHandler</tt>s. + * + * @author Lyubomir Marinov + */ +public class MediaHandler + extends PropertyChangeNotifier +{ + /** + * The <tt>Logger</tt> used by the <tt>MediaHandler</tt> class and its + * instances for logging output. + */ + private static final Logger logger = Logger.getLogger(MediaHandler.class); + + /** + * The <tt>AudioMediaStream</tt> which this instance uses to send and + * receive audio. + */ + private AudioMediaStream audioStream; + + /** + * The <tt>KeyFrameControl</tt> currently known to this + * <tt>MediaHandler</tt> and made available by {@link #videoStream}. + */ + private KeyFrameControl keyFrameControl; + + /** + * The <tt>KeyFrameRequester</tt> implemented by this + * <tt>MediaHandler</tt> and provided to {@link #keyFrameControl}. + */ + private final KeyFrameControl.KeyFrameRequester keyFrameRequester + = new KeyFrameControl.KeyFrameRequester() + { + public boolean requestKeyFrame() + { + return MediaHandler.this.requestKeyFrame(); + } + }; + + private final List<KeyFrameControl.KeyFrameRequester> keyFrameRequesters + = new LinkedList<KeyFrameControl.KeyFrameRequester>(); + + /** + * The last-known local SSRCs of the <tt>MediaStream</tt>s of this instance + * indexed by <tt>MediaType</tt> ordinal. + */ + private final long[] localSSRCs; + + /** + * The last-known remote SSRCs of the <tt>MediaStream</tt>s of this instance + * indexed by <tt>MediaType</tt> ordinal. + */ + private final long[] remoteSSRCs; + + /** + * The <tt>SrtpControl</tt>s of the <tt>MediaStream</tt>s of this instance. + */ + private final SortedMap<MediaTypeSrtpControl, SrtpControl> srtpControls + = new TreeMap<MediaTypeSrtpControl, SrtpControl>(); + + 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<SrtpListener> srtpListeners + = new LinkedList<SrtpListener>(); + + /** + * The <tt>PropertyChangeListener</tt> which listens to changes in the + * values of the properties of the <tt>MediaStream</tt>s of this instance. + */ + private final PropertyChangeListener streamPropertyChangeListener + = new PropertyChangeListener() + { + /** + * Notifies this <tt>PropertyChangeListener</tt> that the value of + * a specific property of the notifier it is registered with has + * changed. + * + * @param evt a <tt>PropertyChangeEvent</tt> 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 <tt>VideoMediaStream</tt> which this instance uses to send and + * receive video. + */ + private VideoMediaStream videoStream; + + /** + * The <tt>VideoListener</tt> which listens to {@link #videoStream} for + * changes in the availability of visual <tt>Component</tt>s 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 <tt>VideoListener</tt> with this instance so that it + * starts receiving notifications from it about changes in the availability + * of visual <tt>Component</tt>s displaying video. + * + * @param listener the <tt>VideoListener</tt> to be registered with this + * instance and to start receiving notifications from it about changes in + * the availability of visual <tt>Component</tt>s displaying video + */ + void addVideoListener(VideoListener listener) + { + videoNotifierSupport.addVideoListener(listener); + } + + /** + * Closes the <tt>MediaStream</tt> that this instance uses for a specific + * <tt>MediaType</tt> and prepares it for garbage collection. + * + * @param type the <tt>MediaType</tt> 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<Map.Entry<MediaTypeSrtpControl, SrtpControl>> iter + = srtpControls.entrySet().iterator(); + + while (iter.hasNext()) + { + Map.Entry<MediaTypeSrtpControl, SrtpControl> entry = iter.next(); + + if (entry.getKey().mediaType == type) + { + entry.getValue().cleanup(); + iter.remove(); + } + } + } + + /** + * Configures <tt>stream</tt> to use the specified <tt>device</tt>, + * <tt>format</tt>, <tt>target</tt>, <tt>direction</tt>, etc. + * + * @param device the <tt>MediaDevice</tt> to be used by <tt>stream</tt> + * for capture and playback + * @param format the <tt>MediaFormat</tt> that we'd like the new stream + * to transmit in. + * @param target the <tt>MediaStreamTarget</tt> containing the RTP and + * RTCP address:port couples that the new stream would be sending + * packets to. + * @param direction the <tt>MediaDirection</tt> that we'd like the new + * stream to use (i.e. sendonly, sendrecv, recvonly, or inactive). + * @param rtpExtensions the list of <tt>RTPExtension</tt>s that should be + * enabled for this stream. + * @param stream the <tt>MediaStream</tt> that we'd like to configure. + * @param masterStream whether the stream to be used as master if secured + * + * @return the <tt>MediaStream</tt> that we received as a parameter (for + * convenience reasons). + * + * @throws OperationFailedException if setting the <tt>MediaFormat</tt> + * or connecting to the specified <tt>MediaDevice</tt> fails for some + * reason. + */ + protected MediaStream configureStream( + CallPeerMediaHandler<?> callPeerMediaHandler, + MediaDevice device, + MediaFormat format, + MediaStreamTarget target, + MediaDirection direction, + List<RTPExtension> 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 <tt>VideoListener</tt>s registered with this + * <tt>MediaHandler</tt> about a specific type of change in the availability + * of a specific visual <tt>Component</tt> depicting video. + * + * @param type the type of change as defined by <tt>VideoEvent</tt> in the + * availability of the specified visual <tt>Component</tt> depicting video + * @param visualComponent the visual <tt>Component</tt> depicting video + * which has been added or removed in this <tt>MediaHandler</tt> + * @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 <tt>true</tt> if this event and, more specifically, the visual + * <tt>Component</tt> it describes have been consumed and should be + * considered owned, referenced (which is important because + * <tt>Component</tt>s belong to a single <tt>Container</tt> at a time); + * otherwise, <tt>false</tt> + */ + protected boolean fireVideoEvent( + int type, + Component visualComponent, + int origin) + { + return + videoNotifierSupport.fireVideoEvent( + type, visualComponent, origin, + true); + } + + /** + * Notifies the <tt>VideoListener</tt>s registered with this + * <tt>MediaHandler</tt> about a specific <tt>VideoEvent</tt>. + * + * @param event the <tt>VideoEvent</tt> to fire to the + * <tt>VideoListener</tt>s registered with this <tt>MediaHandler</tt> + */ + protected void fireVideoEvent(VideoEvent event) + { + videoNotifierSupport.fireVideoEvent(event, true); + } + + /** + * Gets the SRTP control type used for a given media type. + * + * @param mediaType the <tt>MediaType</tt> to get the SRTP control type for + * @return the SRTP control type (MIKEY, SDES, ZRTP) used for the given + * media type or <tt>null</tt> 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 <tt>SrtpControl</tt>s of the <tt>MediaStream</tt>s of this + * instance. + * + * @return the <tt>SrtpControl</tt>s of the <tt>MediaStream</tt>s of this + * instance + */ + Map<MediaTypeSrtpControl, SrtpControl> getSrtpControls( + CallPeerMediaHandler<?> callPeerMediaHandler) + { + return srtpControls; + } + + private SrtpListener[] getSrtpListeners() + { + synchronized (srtpListeners) + { + return + srtpListeners.toArray(new SrtpListener[srtpListeners.size()]); + } + } + + /** + * Gets the <tt>MediaStream</tt> of this instance which is of a specific + * <tt>MediaType</tt>. If this instance doesn't have such a + * <tt>MediaStream</tt>, returns <tt>null</tt> + * + * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> to + * retrieve + * @return the <tt>MediaStream</tt> of this <tt>CallPeerMediaHandler</tt> + * which is of the specified <tt>mediaType</tt> if this instance has such a + * <tt>MediaStream</tt>; otherwise, <tt>null</tt> + */ + 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 + * <tt>MediaHandler</tt> is using for the <tt>MediaType</tt> matching the + * one of the <tt>MediaDevice</tt>. + * + * @param connector the <tt>MediaConnector</tt> that we'd like to bind the + * newly created stream to. + * @param device the <tt>MediaDevice</tt> that we'd like to attach the newly + * created <tt>MediaStream</tt> to. + * @param format the <tt>MediaFormat</tt> that we'd like the new + * <tt>MediaStream</tt> to be set to transmit in. + * @param target the <tt>MediaStreamTarget</tt> containing the RTP and RTCP + * address:port couples that the new stream would be sending packets to. + * @param direction the <tt>MediaDirection</tt> that we'd like the new + * stream to use (i.e. sendonly, sendrecv, recvonly, or inactive). + * @param rtpExtensions the list of <tt>RTPExtension</tt>s that should be + * enabled for this stream. + * @param masterStream whether the stream to be used as master if secured + * + * @return the newly created <tt>MediaStream</tt>. + * + * @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<RTPExtension> 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 <tt>true</tt> if the request for a (video) key frame has been + * honored by the local peer; otherwise, <tt>false</tt> + */ + boolean processKeyFrameRequest(CallPeerMediaHandler<?> callPeerMediaHandler) + { + KeyFrameControl keyFrameControl = this.keyFrameControl; + + return + (keyFrameControl == null) + ? null + : keyFrameControl.keyFrameRequest(); + } + + /** + * Registers all dynamic payload mappings known to this + * <tt>MediaHandler</tt> with the specified <tt>MediaStream</tt>. + * + * @param stream the <tt>MediaStream</tt> that we'd like to register our + * dynamic payload mappings with. + */ + private void registerDynamicPTsWithStream( + CallPeerMediaHandler<?> callPeerMediaHandler, + MediaStream stream) + { + for (Map.Entry<MediaFormat, Byte> mapEntry + : callPeerMediaHandler.getDynamicPayloadTypes().getMappings() + .entrySet()) + { + byte pt = mapEntry.getValue(); + MediaFormat fmt = mapEntry.getKey(); + + stream.addDynamicRTPPayloadType(pt, fmt); + } + } + + /** + * Registers with the specified <tt>MediaStream</tt> all RTP extensions + * negotiated by this <tt>MediaHandler</tt>. + * + * @param stream the <tt>MediaStream</tt> that we'd like to register our + * <tt>RTPExtension</tt>s with. + * @param rtpExtensions the list of <tt>RTPExtension</tt>s that should be + * enabled for <tt>stream</tt>. + */ + private void registerRTPExtensionsWithStream( + CallPeerMediaHandler<?> callPeerMediaHandler, + List<RTPExtension> 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 <tt>VideoListener</tt> from this instance so that + * it stops receiving notifications from it about changes in the + * availability of visual <tt>Component</tt>s displaying video. + * + * @param listener the <tt>VideoListener</tt> to be unregistered from this + * instance and to stop receiving notifications from it about changes in the + * availability of visual <tt>Component</tt>s displaying video + */ + void removeVideoListener(VideoListener listener) + { + videoNotifierSupport.removeVideoListener(listener); + } + + /** + * Requests a key frame from the remote peer of the associated + * <tt>VideoMediaStream</tt> of this <tt>MediaHandler</tt>. + * + * @return <tt>true</tt> if this <tt>MediaHandler</tt> has indeed requested + * a key frame from the remote peer of its associated + * <tt>VideoMediaStream</tt> in response to the call; otherwise, + * <tt>false</tt> + */ + 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 <tt>AudioMediaStream</tt> which this instance is to use to send + * and receive audio. + * + * @param audioStream the <tt>AudioMediaStream</tt> 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 <tt>KeyFrameControl</tt> currently known to this + * <tt>MediaHandler</tt> made available by a specific + * <tt>VideoMediaStream</tt>. + * + * @param videoStream the <tt>VideoMediaStream</tt> the + * <tt>KeyFrameControl</tt> of which is to be set as the currently known to + * this <tt>MediaHandler</tt> + */ + 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 <tt>MediaStream</tt> of a specific + * <tt>MediaType</tt>. + * + * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> to + * set the last-known local SSRC of + * @param localSSRC the last-known local SSRC of the <tt>MediaStream</tt> of + * the specified <tt>mediaType</tt> + */ + 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 <tt>MediaStream</tt> of a specific + * <tt>MediaType</tt>. + * + * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> to + * set the last-known local SSRC of + * @param localSSRC the last-known local SSRC of the <tt>MediaStream</tt> of + * the specified <tt>mediaType</tt> + */ + 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 <tt>VideoMediaStream</tt> which this instance is to use to send + * and receive video. + * + * @param videoStream the <tt>VideoMediaStream</tt> 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<Component> 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<Component> 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<U extends MediaAwareCallPeer<?, ?, ?>> } /** - * 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 <tt>StreamConnector</tt>, if any, associated with a + * specific <tt>MediaType</tt> and removes its reference from this + * <tt>TransportManager</tt>. * - * @param mediaType the type of the connector we'd like to close. + * @param mediaType the <tt>MediaType</tt> associated with the + * <tt>StreamConnector</tt> 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 <tt>StreamConnector</tt> associated with a specific + * <tt>MediaType</tt>. If this <tt>TransportManager</tt> has a reference to + * the specified <tt>streamConnector</tt>, it remains. Allows extenders to + * override and perform additional customizations to the closing of the + * specified <tt>streamConnector</tt>. + * + * @param mediaType the <tt>MediaType</tt> associated with the specified + * <tt>streamConnector</tt> + * @param streamConnector the <tt>StreamConnector</tt> 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 <tt>StreamConnector</tt>. 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/event/PropertyChangeNotifier.java index 9039ea9..a5f683f 100644 --- a/src/net/java/sip/communicator/util/PropertyChangeNotifier.java +++ b/src/net/java/sip/communicator/util/event/PropertyChangeNotifier.java @@ -4,7 +4,7 @@ * Distributable under LGPL license.
* See terms of license at gnu.org.
*/
-package net.java.sip.communicator.util;
+package net.java.sip.communicator.util.event;
import java.util.*;
import java.beans.*;
@@ -13,7 +13,7 @@ import java.beans.*; * Represents a source of <tt>PropertyChangeEvent</tt>s which notifies
* <tt>PropertyChangeListener</tt>s about changes in the values of properties.
*
- * @author Lubomir Marinov
+ * @author Lyubomir Marinov
*/
public class PropertyChangeNotifier
{
diff --git a/src/net/java/sip/communicator/service/neomedia/event/SizeChangeVideoEvent.java b/src/net/java/sip/communicator/util/event/SizeChangeVideoEvent.java index 5db15b4..e4982bb 100644 --- a/src/net/java/sip/communicator/service/neomedia/event/SizeChangeVideoEvent.java +++ b/src/net/java/sip/communicator/util/event/SizeChangeVideoEvent.java @@ -4,7 +4,7 @@ * Distributable under LGPL license. * See terms of license at gnu.org. */ -package net.java.sip.communicator.service.neomedia.event; +package net.java.sip.communicator.util.event; import java.awt.*; @@ -12,7 +12,7 @@ import java.awt.*; * Represents a <tt>VideoEvent</tt> which notifies about an update to the size * of a specific visual <tt>Component</tt> depicting video. * - * @author Lubomir Marinov + * @author Lyubomir Marinov */ public class SizeChangeVideoEvent extends VideoEvent @@ -64,6 +64,16 @@ public class SizeChangeVideoEvent 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 <tt>Component</tt>. * diff --git a/src/net/java/sip/communicator/service/neomedia/event/VideoEvent.java b/src/net/java/sip/communicator/util/event/VideoEvent.java index 6135f47..695e004 100644 --- a/src/net/java/sip/communicator/service/neomedia/event/VideoEvent.java +++ b/src/net/java/sip/communicator/util/event/VideoEvent.java @@ -4,7 +4,7 @@ * Distributable under LGPL license. * See terms of license at gnu.org. */ -package net.java.sip.communicator.service.neomedia.event; +package net.java.sip.communicator.util.event; import java.awt.*; import java.util.*; @@ -14,7 +14,7 @@ import java.util.*; * depicting video to notify about changes in the availability of such * <tt>Component</tt>s. * - * @author Lubomir Marinov + * @author Lyubomir Marinov */ public class VideoEvent extends EventObject @@ -104,6 +104,14 @@ public class VideoEvent this.origin = origin; } + public VideoEvent clone(Object source) + { + return + new VideoEvent( + source, + getType(), getVisualComponent(), getOrigin()); + } + /** * Consumes this event and, more specifically, marks the <tt>Component</tt> * it describes as owned, referenced in order to let other potential diff --git a/src/net/java/sip/communicator/service/neomedia/event/VideoListener.java b/src/net/java/sip/communicator/util/event/VideoListener.java index 50c7089..25266a7 100644 --- a/src/net/java/sip/communicator/service/neomedia/event/VideoListener.java +++ b/src/net/java/sip/communicator/util/event/VideoListener.java @@ -4,7 +4,7 @@ * Distributable under LGPL license. * See terms of license at gnu.org. */ -package net.java.sip.communicator.service.neomedia.event; +package net.java.sip.communicator.util.event; import java.util.*; @@ -13,7 +13,7 @@ import java.util.*; * of visual <tt>Component</tt>s representing video such as adding and * removing. * - * @author Lubomir Marinov + * @author Lyubomir Marinov */ public interface VideoListener extends EventListener diff --git a/src/net/java/sip/communicator/service/neomedia/event/VideoNotifierSupport.java b/src/net/java/sip/communicator/util/event/VideoNotifierSupport.java index 6029b89..af30eaf 100644 --- a/src/net/java/sip/communicator/service/neomedia/event/VideoNotifierSupport.java +++ b/src/net/java/sip/communicator/util/event/VideoNotifierSupport.java @@ -4,7 +4,7 @@ * Distributable under LGPL license. * See terms of license at gnu.org. */ -package net.java.sip.communicator.service.neomedia.event; +package net.java.sip.communicator.util.event; import java.awt.*; import java.util.*; 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 |