diff options
author | Emil Ivov <emcho@jitsi.org> | 2007-08-27 17:28:46 +0000 |
---|---|---|
committer | Emil Ivov <emcho@jitsi.org> | 2007-08-27 17:28:46 +0000 |
commit | 4809ba5d41cba22e291343d493f05cf00d958d8f (patch) | |
tree | c9d9c2020cce91664809a757edd3fa173fdd84e6 | |
parent | 241c038fe69e7617369fd16173a4fab9f362af1e (diff) | |
download | jitsi-4809ba5d41cba22e291343d493f05cf00d958d8f.zip jitsi-4809ba5d41cba22e291343d493f05cf00d958d8f.tar.gz jitsi-4809ba5d41cba22e291343d493f05cf00d958d8f.tar.bz2 |
Committing support for Jingle. (By Symphorien Wanko)
17 files changed, 2870 insertions, 71 deletions
diff --git a/ide/nbproject/project.xml b/ide/nbproject/project.xml index fe195ef..cc3ce68 100644 --- a/ide/nbproject/project.xml +++ b/ide/nbproject/project.xml @@ -3,15 +3,13 @@ <type>org.netbeans.modules.ant.freeform</type> <configuration> <general-data xmlns="http://www.netbeans.org/ns/freeform-project/1"> - <!-- Created by Brian Burch on September 27, 2006 - Do NOT use the Netbeans Project Properties customizer - because this file is maintained manually! The master - version is held in CVS, as /ide/nbproject/project.xml. - You should copy it to /nbproject/project.xml (when - NetBeans is not running!) to make it active on your - own system. - --> - <name>sip-communicator-1.0</name> + <!-- Created by Brian Burch on September 27, 2006 + The master version is held in CVS, as + /ide/nbproject/project.xml. You should copy it to + /nbproject/project.xml (when NetBeans is not running!) + to make it active on your own system. + --> + <name>SIP Communicator</name> <properties/> <folders> <source-folder> @@ -27,7 +25,7 @@ </folders> <ide-actions> <action name="build"> - <target>make</target> + <target>rebuild</target> </action> <action name="clean"> <target>clean</target> @@ -36,40 +34,29 @@ <target>javadoc</target> </action> <action name="run"> - <!-- run has NO dependents, but we have to protect - users from not picking up the latest changes - by doing an explicit incremental make. - --> - <target>make</target> <target>run</target> </action> <action name="test"> <target>test</target> </action> <action name="rebuild"> + <target>clean</target> <target>rebuild</target> </action> - <action name="run.single"> - <target>test</target> - <context> - <property>test.name</property> - <folder>test</folder> - <pattern>\.java$</pattern> - <format>relative-path</format> - <arity> - <one-file-only/> - </arity> - </context> + <action name="debug"> + <script>nbproject/ide-targets.xml</script> + <target>debug-nb</target> </action> - <action name="debug.single"> - <target>debug-selected-file</target> + <action name="compile.single"> + <script>nbproject/ide-file-targets.xml</script> + <target>compile-selected-files-in-src</target> <context> - <property>test.name</property> - <folder>test</folder> + <property>files</property> + <folder>src</folder> <pattern>\.java$</pattern> <format>relative-path</format> <arity> - <one-file-only/> + <separated-files>,</separated-files> </arity> </context> </action> @@ -95,10 +82,7 @@ <ide-action name="run"/> <ide-action name="test"/> <ide-action name="rebuild"/> - <action> - <label>Safe clean/build of all targets</label> - <target>rebuild</target> - </action> + <ide-action name="debug"/> </context-menu> </view> <subprojects/> @@ -106,17 +90,12 @@ <java-data xmlns="http://www.netbeans.org/ns/freeform-project-java/2"> <compilation-unit> <package-root>src</package-root> - <classpath mode="compile"> -lib/bundle/org.apache.felix.servicebinder-0.8.0-SNAPSHOT.jar:lib/installer-exclude/bcprov-jdk14-130.jar:lib/installer-exclude/cindy.jar:lib/installer-exclude/commons-logging.jar:lib/installer-exclude/concurrent.jar:lib/installer-exclude/JainSipApi1.2.jar:lib/installer-exclude/JainSipRi1.2.jar:lib/installer-exclude/jmf.jar:lib/installer-exclude/jml-1.0b1.jar:lib/installer-exclude/joscar-client.jar:lib/installer-exclude/joscar-common.jar:lib/installer-exclude/joscar-protocol.jar:lib/installer-exclude/jsocks-klea.jar:lib/installer-exclude/jspeex.jar:lib/installer-exclude/log4j-1.2.8.jar:lib/installer-exclude/nist-sdp-1.0.jar:lib/installer-exclude/retroweaver-rt-2.0Beta2.jar:lib/installer-exclude/smack.jar:lib/installer-exclude/smackx.jar:lib/installer-exclude/Stun4J.jar:lib/installer-exclude/ymsg_network_v0_61.jar:lib/servicebinder.jar:lib/felix.jar:lib/jdic-all.jar:lib/kxml-min.jar:lib/os-specific/linux/installer-exclude/jmf.jar:lib/os-specific/linux/jdic_stub.jar:lib/os-specific/mac/installer-exclude/jmf.jar:lib/os-specific/mac/AppleJavaExtensions.jar:lib/os-specific/mac/growl.jar:lib/os-specific/mac/jdic_stub.jar:lib/os-specific/solaris/installer-exclude/jmf.jar:lib/os-specific/solaris/jdic_stub.jar:lib/os-specific/windows/installer-exclude/jmf.jar:lib/os-specific/windows/installer-exclude/sound.jar:lib/os-specific/windows/jdic_stub.jar - </classpath> + <classpath mode="compile">lib/felix.jar:lib/jdic-all.jar:lib/kxml-min.jar:lib/servicebinder.jar:lib/bundle/junit.jar:lib/bundle/log4j.jar:lib/bundle/org.apache.felix.servicebinder-0.8.0-SNAPSHOT.jar:lib/installer-exclude/aclibico-2.1.jar:lib/installer-exclude/backport-util-concurrent.jar:lib/installer-exclude/bcprov-jdk14-130.jar:lib/installer-exclude/commons-logging.jar:lib/installer-exclude/concurrent.jar:lib/installer-exclude/dnsjava-2.0.3.jar:lib/installer-exclude/JainSipApi1.2.jar:lib/installer-exclude/JainSipRi1.2.jar:lib/installer-exclude/jcalendar-1.3.2.jar:lib/installer-exclude/jdom.jar:lib/installer-exclude/jmf.jar:lib/installer-exclude/jml-1.0b1.jar:lib/installer-exclude/joscar-client.jar:lib/installer-exclude/joscar-common.jar:lib/installer-exclude/joscar-protocol.jar:lib/installer-exclude/jsocks-klea.jar:lib/installer-exclude/jspeex.jar:lib/installer-exclude/junit.jar:lib/installer-exclude/log4j-1.2.8.jar:lib/installer-exclude/nist-sdp-1.0.jar:lib/installer-exclude/retroweaver-rt-2.0.jar:lib/installer-exclude/rome-0.9.jar:lib/installer-exclude/smack.jar:lib/installer-exclude/smackx-jingle.jar:lib/installer-exclude/smackx.jar:lib/installer-exclude/Stun4J.jar:lib/installer-exclude/xalan-2.6.0.jar.ant:lib/installer-exclude/ymsg_network_v0_61.jar:lib/os-specific/linux/installer-exclude/jmf.jar:lib/os-specific/linux/jdic_stub.jar:lib/os-specific/mac/AppleJavaExtensions.jar:lib/os-specific/mac/growl.jar:lib/os-specific/mac/jdic_stub.jar:lib/os-specific/mac/installer-exclude/jmf.jar:lib/os-specific/windows/installer-exclude/jmf.jar:lib/os-specific/windows/installer-exclude/sound.jar</classpath> <source-level>1.4</source-level> </compilation-unit> <compilation-unit> <package-root>test</package-root> <unit-tests/> - <classpath mode="compile"> -lib/bundle/org.apache.felix.servicebinder-0.8.0-SNAPSHOT.jar:lib/installer-exclude/bcprov-jdk14-130.jar:lib/installer-exclude/cindy.jar:lib/installer-exclude/commons-logging.jar:lib/installer-exclude/concurrent.jar:lib/installer-exclude/JainSipApi1.2.jar:lib/installer-exclude/JainSipRi1.2.jar:lib/installer-exclude/jmf.jar:lib/installer-exclude/jml-1.0b1.jar:lib/installer-exclude/joscar-client.jar:lib/installer-exclude/joscar-common.jar:lib/installer-exclude/joscar-protocol.jar:lib/installer-exclude/jsocks-klea.jar:lib/installer-exclude/jspeex.jar:lib/installer-exclude/log4j-1.2.8.jar:lib/installer-exclude/nist-sdp-1.0.jar:lib/installer-exclude/retroweaver-rt-2.0Beta2.jar:lib/installer-exclude/smack.jar:lib/installer-exclude/smackx.jar:lib/installer-exclude/Stun4J.jar:lib/installer-exclude/ymsg_network_v0_61.jar:lib/servicebinder.jar:lib/felix.jar:lib/jdic-all.jar:lib/kxml-min.jar:lib/os-specific/linux/installer-exclude/jmf.jar:lib/os-specific/linux/jdic_stub.jar:lib/os-specific/mac/installer-exclude/jmf.jar:lib/os-specific/mac/AppleJavaExtensions.jar:lib/os-specific/mac/growl.jar:lib/os-specific/mac/jdic_stub.jar:lib/os-specific/solaris/installer-exclude/jmf.jar:lib/os-specific/solaris/jdic_stub.jar:lib/os-specific/windows/installer-exclude/jmf.jar:lib/os-specific/windows/installer-exclude/sound.jar:lib/os-specific/windows/jdic_stub.jar:classes:lib/bundle/junit.jar - </classpath> <source-level>1.4</source-level> </compilation-unit> </java-data> diff --git a/lib/installer-exclude/smack.manifest.mf b/lib/installer-exclude/smack.manifest.mf index bf85252..c35d8b9 100644 --- a/lib/installer-exclude/smack.manifest.mf +++ b/lib/installer-exclude/smack.manifest.mf @@ -19,5 +19,11 @@ Export-Package: org.jivesoftware.smack, org.jivesoftware.smackx, org.jivesoftware.smackx.muc, org.jivesoftware.smackx.packet, + org.jivesoftware.smackx.jingle, + org.jivesoftware.smackx.jingle.nat, + org.jivesoftware.smackx.jingle.media, + org.jivesoftware.smackx.jingle.packet, + org.jivesoftware.smackx.jingle.provider, + org.jivesoftware.smackx.jingle.listeners, org.xmlpull.v1, org.xmlpull.mxp1 diff --git a/src/net/java/sip/communicator/impl/media/MediaServiceImpl.java b/src/net/java/sip/communicator/impl/media/MediaServiceImpl.java index 7bc3f84..05aa1e3 100644 --- a/src/net/java/sip/communicator/impl/media/MediaServiceImpl.java +++ b/src/net/java/sip/communicator/impl/media/MediaServiceImpl.java @@ -29,10 +29,14 @@ import net.java.sip.communicator.util.*; * @author Emil Ivov * @author Martin Andre * @author Ryan Ricard + * @author Symphorien Wanko */ public class MediaServiceImpl implements MediaService { + /** + * Our logger. + */ private Logger logger = Logger.getLogger(MediaServiceImpl.class); /** @@ -74,7 +78,7 @@ public class MediaServiceImpl * control mapping. */ private MediaControl defaultMediaControl = new MediaControl(); - + /** * Mappings of calls to instances of <tt>MediaControl</tt>. In case a call * has been mapped to a media control instance, it is going to be used for @@ -84,18 +88,18 @@ public class MediaServiceImpl * as their sound source. */ private Map callMediaControlMappings = new Hashtable(); - + /** * Mappings of calls to custom data sinks. Used by mailbox plug-ins for * sending audio/video flows to a file instead of the sound card or the * screen. */ - private Hashtable callDataSinkMappings = new Hashtable(); + private Map callDataSinkMappings = new Hashtable(); /** * Currently open call sessions. */ - private Hashtable activeCallSessions = new Hashtable(); + private Map activeCallSessions = new Hashtable(); /** * Default constructor @@ -105,6 +109,31 @@ public class MediaServiceImpl } /** + * Implements <tt>getSupportedAudioEncodings</tt> from interface + * <tt>MediaService</tt> + * + * @return an array of Strings containing audio formats in the order of + * preference. + */ + public String[] getSupportedAudioEncodings() + { + return defaultMediaControl.getSupportedAudioEncodings(); + } + + /** + * Implements <tt>getSupportedVideoEncodings</tt> from interface + * <tt>MediaService</tt> + * + * @return an array of Strings containing video formats in the order of + * preference. + */ + public String[] getSupportedVideoEncodings() + { + return defaultMediaControl.getSupportedAudioEncodings(); + } + + + /** * Creates a call session for <tt>call</tt>. The method allocates audio * and video ports which won't be released until the corresponding call * gets into a DISCONNECTED state. If a session already exists for call, @@ -134,6 +163,36 @@ public class MediaServiceImpl } /** + * A <tt>RtpFlow</tt> is an object which role is to handle media data + * transfer, capture and playback. It's build between two points, a local + * and a remote. The media transfered will be in a format specified by the + * <tt>mediaEncodings</tt> parameter. + * + * @param localIP local address of this RtpFlow + * @param localPort local port of this RtpFlow + * @param remoteIP remote address of this RtpFlow + * @param remotePort remote port of this RtpFlow + * @param mediaEncodings format used to encode data on this flow + * @return rtpFlow the newly created <tt>RtpFlow</tt> + * @throws MediaException if operation fails + */ + public RtpFlow createRtpFlow(String localIP, + int localPort, + String remoteIP, + int remotePort, + Map mediaEncodings) + throws MediaException + { + waitUntilStarted(); + assertStarted(); + + RtpFlowImpl rtpFlow = new RtpFlowImpl(this, localIP, remoteIP, + localPort, remotePort, new Hashtable(mediaEncodings)); + return rtpFlow; + } + + + /** * Adds a listener that will be listening for incoming media and changes * in the state of the media listener. * @@ -191,8 +250,11 @@ public class MediaServiceImpl } /** + * Verifies whether the media service is started and ready for use and + * throws an exception otherwise. * - * @throws MediaException + * @throws MediaException if the media service is not started and ready for + * use. */ protected void assertStarted() throws MediaException diff --git a/src/net/java/sip/communicator/impl/media/RtpFlowImpl.java b/src/net/java/sip/communicator/impl/media/RtpFlowImpl.java new file mode 100644 index 0000000..44e15b7 --- /dev/null +++ b/src/net/java/sip/communicator/impl/media/RtpFlowImpl.java @@ -0,0 +1,435 @@ +/* + * SIP Communicator, 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.media; + +import java.io.*; +import java.net.*; +import java.util.*; + +import javax.media.*; +import javax.media.rtp.*; +import javax.media.control.*; +import javax.media.protocol.*; +import javax.media.rtp.event.*; + +import net.java.sip.communicator.service.media.*; +import net.java.sip.communicator.service.media.MediaException; +import net.java.sip.communicator.util.*; + +/** + * Implementation of a <tt>RtpFlow</tt> which is a bridge for data transmission + * between two end points. Data which transit on this flow are encoded with + * a specified format. + * + * @author Symphorien Wanko + */ +public class RtpFlowImpl + implements RtpFlow, + ReceiveStreamListener, + SessionListener, + ControllerListener +{ + /** + * Logger for this class + */ + private static final Logger logger + = Logger.getLogger(RtpFlowImpl.class); + + /** + * Our IP address + */ + private String localAddress = null; + + /** + * IP to which this <tt>flow send media</tt> + */ + private String remoteAddress = null; + + /** + * The local port used by this <tt>RtpFlow</tt> + */ + private int localPort = -1; + + /** + * Port fort the remote endpoint + */ + private int remotePort = -1; + + /** + * Collection of <tt>RtpManager</tt>s used by this <tt>RtpFlow</tt> + */ + private RTPManager rtpMgrs[] = null; + + /** + * Data source used for media capture + */ + private DataSource dataSource = null; + + /** + * Collection of send streams used by this <tt>RtpFlow</tt> + */ + private List sendStreams = new ArrayList(); + + /** + * The media on which this <tt>RtpFlow</tt> depend, + * the one which created us + */ + private MediaServiceImpl mediaService = null; + + /** + * Used for controlling media + */ + private MediaControl mediaControl = null; + + /** + * Media encoding passed to JMF via the media control + */ + private Hashtable mediaEncoding = new Hashtable(); + + /** + * Creates an instance of <tt>RtpFlowImpl</tt> for media transmission. + * A flow do not care about session or anything other than transmitting + * media data between two end points, using the given media encoding. + * + * @param mediaServie the media service which created us + * @param localAddress local IP address + * @param localPort local port number + * @param remoteAddress remote IP address + * @param remotePort remote port number + * @param mediaEncoding media encoding used for data + * + * @throws MediaException if initializing the flow fails. + */ + public RtpFlowImpl(MediaServiceImpl mediaServie, + String localIpAddress, + String remoteIpAddress, + int localPort, + int remotePort, + Hashtable mediaEncoding) + throws MediaException + { + this.localAddress = localIpAddress; + this.remoteAddress = remoteIpAddress; + this.localPort = localPort; + this.remotePort = remotePort; + this.mediaService = mediaServie; + this.mediaControl = mediaService.getMediaControl(); + this.mediaEncoding.putAll( mediaEncoding ); + + initialize(); + } + + /** + * Returns the local port used by this flow. + * + * @return localPort the local port used by this flow. + */ + public int getLocalPort() + { + return localPort; + } + + /** + * Returns the local address used by this flow. + * + * @return localAddress the local address port used by this flow. + */ + public String getLocalAddress() + { + return localAddress; + } + + /** + * Returns the remote port used by this flow. + * + * @return remotePort the remote port used by this flow. + */ + public int getRemotePort() + { + return remotePort; + } + + /** + * Returns the remote address used by this flow. + * + * @return remoteAddress the remote address port used by this flow. + */ + public String getRemoteAddress() + { + return remoteAddress; + } + + /** + * This method initializes the <tt>RtpFlow</tt> by creating + * datasource and associated transmitter. We also create session for + * each JMF track here. + * + * @throws MediaException if initialization fails + */ + private void initialize() throws MediaException + { + dataSource = mediaControl.createDataSourceForEncodings(mediaEncoding); + + PushBufferDataSource pbds = (PushBufferDataSource) dataSource; + PushBufferStream pbss[] = pbds.getStreams(); + + rtpMgrs = new RTPManager[pbss.length]; + SessionAddress localAddr, destAddr; + InetAddress ipAddr; + SendStream sendStream; + + int port; + + for (int i = 0; i < pbss.length; i++) + { + try + { + rtpMgrs[i] = RTPManager.newInstance(); + + port = remotePort + 2 * i; + ipAddr = InetAddress.getByName(remoteAddress); + + localAddr = new SessionAddress(InetAddress. + getByName(this.localAddress), localPort); + + destAddr = new SessionAddress(ipAddr, port); + + rtpMgrs[i].addReceiveStreamListener(this); + rtpMgrs[i].addSessionListener(this); + + BufferControl bc = (BufferControl) rtpMgrs[i] + .getControl("javax.media.control.BufferControl"); + if (bc != null) + { + int bl = 160; + bc.setBufferLength(bl); + } + + try + { + rtpMgrs[i].initialize(localAddr); + } + catch (InvalidSessionAddressException e) + { + // In case the local address is not allowed to read, + // we user another local address + SessionAddress sessAddr = new SessionAddress(); + localAddr = new SessionAddress(sessAddr.getDataAddress(), + localPort); + rtpMgrs[i].initialize(localAddr); + } + + rtpMgrs[i].addTarget(destAddr); + logger.info("Created RTP session at " + localPort + + " to: " + remoteAddress + " " + port); + sendStream = rtpMgrs[i].createSendStream(dataSource, i); + sendStreams.add(sendStream); + sendStream.start(); + } + catch (Exception e) + { + throw new MediaException("Failed to create transmitter" + , MediaException.INTERNAL_ERROR, e); + } + } + } + + /** + * Implementation of <tt>startTransmission</tt> start to send media data. + */ + public void start() + { + mediaControl.startProcessingMedia(this); + } + + /** + * Stops the transmission if already started. + * Stops receiving also. + */ + public void stop() + { + RTPManager rtpMgr; + for (int i = 0; i < rtpMgrs.length; i++) + { + rtpMgr = rtpMgrs[i]; + rtpMgr.removeReceiveStreamListener(this); + rtpMgr.removeSessionListener(this); + rtpMgr.removeTargets("Session ended."); + rtpMgr.dispose(); + } + sendStreams.clear(); + mediaControl.stopProcessingMedia(this); + } + + /** + * Resume media transmission on this flow + */ + public void resume() + { + Iterator it = sendStreams.iterator(); + SendStream sendStream; + + logger.info("pausing transmission... "); + + while (it.hasNext()) + { + sendStream = (SendStream) it.next(); + try + { + sendStream.start(); + } + catch (IOException ex) + { + logger.warn("Exception when pausing transmission ", ex); + } + } + } + + /** + * Pause media transmission on this flow + */ + public void pause() + { + Iterator it = sendStreams.iterator(); + SendStream sendStream; + + logger.info("pausing transmission... "); + + while (it.hasNext()) + { + sendStream = (SendStream) it.next(); + try + { + sendStream.stop(); + } + catch (IOException ex) + { + logger.warn("Exception when pausing transmission ", ex); + } + } + } + + /** + * Implements update from javax.media.rtp.SessionListener + * + * @param evt received event + */ + public synchronized void update(SessionEvent evt) + { + if (evt instanceof NewParticipantEvent) + { + Participant p = ((NewParticipantEvent) evt).getParticipant(); + logger.info("A new participant had just joined: " + p.getCNAME()); + } + } + + /** + * Implements update from javax.media.rtp.ReceiveStreamListener + * + * @param evt received event + */ + public synchronized void update(ReceiveStreamEvent evt) + { + Participant participant = evt.getParticipant(); // could be null. + ReceiveStream stream = evt.getReceiveStream(); // could be null. + + if (evt instanceof RemotePayloadChangeEvent) + { + logger.warn("Received an RTP PayloadChangeEvent," + + " not supported cannot handle payload change."); + } + else if (evt instanceof NewReceiveStreamEvent) + { + + try + { + stream = evt.getReceiveStream(); + DataSource ds = stream.getDataSource(); + + // Find out the formats. + RTPControl ctl = + (RTPControl) ds.getControl("javax.jmf.rtp.RTPControl"); + if (ctl != null) + { + logger.info("Recevied new RTP stream: " + ctl.getFormat()); + } + else + logger.info("Recevied new RTP stream"); + + if (participant == null) + logger.info("The sender of this stream" + + "had yet to be identified."); + else + { + logger.info("The stream comes from: " + + participant.getCNAME()); + } + + // create a player by passing datasource to the Media Manager + Player p = javax.media.Manager.createPlayer(ds); + if (p == null) + return; + + p.addControllerListener(this); + p.realize(); + } + catch (Exception e) + { + logger.warn("NewReceiveStreamEvent exception ", e); + return; + } + + } + else if (evt instanceof StreamMappedEvent) + { + + if (stream != null && stream.getDataSource() != null) + { + DataSource ds = stream.getDataSource(); + // Find out the formats. + RTPControl ctl = + (RTPControl) ds.getControl("javax.jmf.rtp.RTPControl"); + logger.info("The previously unidentified stream "); + if (ctl != null) + logger.info(": " + ctl.getFormat()); + logger.info(" had now been identified as sent by: " + + participant.getCNAME()); + } + } + else if (evt instanceof ByeEvent) + { + logger.info("Got \"bye\" from: " + participant.getCNAME()); + } + + } + + /** + * Implements controllerUpdate from javax.media.rtp.ControllerListener + * + * @param ce received event + */ + public synchronized void controllerUpdate(ControllerEvent ce) + { + + Player p = (Player) ce.getSourceController(); + + if (p == null) + return; + + // Get this when the internal players are realized. + if (ce instanceof RealizeCompleteEvent) + { + p.start(); + } + + if (ce instanceof ControllerErrorEvent) + { + p.removeControllerListener(this); + logger.warn("Receiver internal error " + ce); + } + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ActiveCallsRepository.java b/src/net/java/sip/communicator/impl/protocol/jabber/ActiveCallsRepository.java new file mode 100644 index 0000000..5d74b35 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ActiveCallsRepository.java @@ -0,0 +1,187 @@ +/* + * SIP Communicator, 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; + +import net.java.sip.communicator.util.*; +import java.util.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.service.protocol.*; +import org.jivesoftware.smackx.jingle.*; + +/** + * Keeps a list of all calls currently active and maintained by this protocol + * provider. Offers methods for finding a call by its ID, participant session + * and others. + * + * @author Emil Ivov + */ +public class ActiveCallsRepository + implements CallChangeListener +{ + /** + * logger of this class + */ + private static final Logger logger + = Logger.getLogger(ActiveCallsRepository.class); + + /** + * The operation set that created us. Instance is mainly used for firing + * events when necessary. + */ + private OperationSetBasicTelephonyJabberImpl parentOperationSet = null; + + /** + * A table mapping call ids against call instances. + */ + private Hashtable activeCalls = new Hashtable(); + + /** + * It's where we store all active calls + * @param opSet the <tt>OperationSetBasicTelphony</tt> instance which has + * been used to create calls in this repository + */ + public ActiveCallsRepository(OperationSetBasicTelephonyJabberImpl opSet) + { + this.parentOperationSet = opSet; + } + + /** + * Adds the specified call to the list of calls tracked by this repository. + * @param call CallJabberImpl + */ + public void addCall(CallJabberImpl call) + { + activeCalls.put(call.getCallID(), call); + call.addCallChangeListener(this); + } + + /** + * A dummy implementation of the CallChangeListener method that we don't + * use. + * @param evt unused. + */ + public void callParticipantAdded(CallParticipantEvent evt) + {} + + /** + * A dummy implementation of the CallChangeListener method that we don't + * use. + * @param evt unused. + */ + public void callParticipantRemoved(CallParticipantEvent evt) + {} + + /** + * If <tt>evt</tt> indicates that the call has been ended we remove it from + * the repository. + * @param evt the <tt>CallChangeEvent</tt> instance containing the source + * calls and its old and new state. + */ + public void callStateChanged(CallChangeEvent evt) + { + if(evt.getEventType().equals(CallChangeEvent.CALL_STATE_CHANGE) + && ((CallState)evt.getNewValue()).equals(CallState.CALL_ENDED)) + { + CallJabberImpl sourceCall = (CallJabberImpl)this.activeCalls + .remove(evt.getSourceCall().getCallID()); + + logger.trace( "Removing call " + sourceCall + " from the list of " + + "active calls because it entered an ENDED state"); + + this.parentOperationSet.fireCallEvent( + CallEvent.CALL_ENDED, sourceCall); + } + } + + /** + * Returns an iterator over all currently active (non-ended) calls. + * + * @return an iterator over all currently active (non-ended) calls. + */ + public Iterator getActiveCalls() + { + return new LinkedList(activeCalls.values()).iterator(); + } + + /** + * Returns the call that contains the specified session (i.e. it is + * established between us and one of the other call participants). + * <p> + * @param session the <tt>jingleSession</tt> whose containing call we're + * looking for. + * @return the <tt>CallJabberImpl</tt> containing <tt>session</tt> or null + * if no call contains the specified session. + */ + public CallJabberImpl findCall(JingleSession session) + { + Iterator activeCalls = getActiveCalls(); + + if(session == null) + { + logger.debug("Cannot find a participant with a null session. " + +"Returning null"); + return null; + } + + if(logger.isTraceEnabled()) + { + logger.trace("Looking for participant with session: " + session + + " among " + this.activeCalls.size() + " calls"); + } + + + while(activeCalls.hasNext()) + { + CallJabberImpl call = (CallJabberImpl)activeCalls.next(); + if(call.contains(session)) + return call; + } + + return null; + } + + /** + * Returns the call participant whose associated jingle session matches + * <tt>session</tt>. + * + * @param session the jingle session whose corresponding participant we're + * looking for. + * @return the call participant whose jingle session is the same as the + * specified or null if no such call participant was found. + */ + public CallParticipantJabberImpl findCallParticipant(JingleSession session) + { + Iterator activeCalls = getActiveCalls(); + + if(session == null) + { + logger.debug("Cannot find a participant with a null session. " + +"Returning null"); + return null; + } + + if(logger.isTraceEnabled()) + { + logger.trace("Looking for participant with session: " + session + + " among " + this.activeCalls.size() + " calls"); + } + + while(activeCalls.hasNext()) + { + CallJabberImpl call = (CallJabberImpl)activeCalls.next(); + CallParticipantJabberImpl callParticipant + = call.findCallParticipant(session); + if(callParticipant != null) + { + logger.trace("Returning participant " + callParticipant); + return callParticipant; + } + } + + return null; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java new file mode 100644 index 0000000..9f8e33a --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java @@ -0,0 +1,292 @@ +/* + * SIP Communicator, 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; + +import java.util.*; + +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.service.media.*; +import org.jivesoftware.smackx.jingle.*; + +/** + * A Jabber implementation of the Call abstract class encapsulating Jabber + * jingle sessions. + * + * @author Emil Ivov + */ +public class CallJabberImpl + extends Call + implements CallParticipantListener +{ + /** + * Logger of this class + */ + private static final Logger logger = Logger.getLogger(CallJabberImpl.class); + /** + * A list containing all <tt>CallParticipant</tt>s of this call. + */ + private Vector callParticipants = new Vector(); + + /** + * The state that this call is currently in. + */ + private CallState callState = CallState.CALL_INITIALIZATION; + + /** + * The <tt>CallSession</tt> that the media service has created for this + * call. + */ + private CallSession mediaCallSession = null; + + /** + * Crates a CallJabberImpl instance belonging to <tt>sourceProvider</tt> and + * initiated by <tt>CallCreator</tt>. + * + * @param sourceProvider the ProtocolProviderServiceJabberImpl instance in the + * context of which this call has been created. + */ + protected CallJabberImpl(ProtocolProviderServiceJabberImpl sourceProvider) + { + super(sourceProvider); + } + + /** + * Adds <tt>callParticipant</tt> to the list of participants in this call. + * If the call participant is already included in the call, the method has + * no effect. + * + * @param callParticipant the new <tt>CallParticipant</tt> + */ + public void addCallParticipant(CallParticipantJabberImpl callParticipant) + { + if(callParticipants.contains(callParticipant)) + return; + + callParticipant.addCallParticipantListener(this); + + this.callParticipants.add(callParticipant); + fireCallParticipantEvent( + callParticipant, CallParticipantEvent.CALL_PARTICIPANT_ADDED); + } + + /** + * Removes <tt>callParticipant</tt> from the list of participants in this + * call. The method has no effect if there was no such participant in the + * call. + * + * @param callParticipant the <tt>CallParticipant</tt> leaving the call; + */ + public void removeCallParticipant(CallParticipantJabberImpl callParticipant) + { + if(!callParticipants.contains(callParticipant)) + return; + + this.callParticipants.remove(callParticipant); + callParticipant.setCall(null); + callParticipant.removeCallParticipantListener(this); + + fireCallParticipantEvent( + callParticipant, CallParticipantEvent.CALL_PARTICIPANT_REMVOVED); + + if(callParticipants.size() == 0) + setCallState(CallState.CALL_ENDED); + } + + /** + * Sets the state of this call and fires a call change event notifying + * registered listenres for the change. + * + * @param newState a reference to the <tt>CallState</tt> instance that + * the call is to enter. + */ + public void setCallState(CallState newState) + { + CallState oldState = getCallState(); + + if(oldState == newState) + return; + + this.callState = newState; + + fireCallChangeEvent( + CallChangeEvent.CALL_STATE_CHANGE, oldState, newState); + } + + /** + * Returns the state that this call is currently in. + * + * @return a reference to the <tt>CallState</tt> instance that the call is + * currently in. + */ + public CallState getCallState() + { + return callState; + } + + /** + * Returns an iterator over all call participants. + * @return an Iterator over all participants currently involved in the call. + */ + public Iterator getCallParticipants() + { + return new LinkedList(callParticipants).iterator(); + } + + /** + * Returns the number of participants currently associated with this call. + * @return an <tt>int</tt> indicating the number of participants currently + * associated with this call. + */ + public int getCallParticipantsCount() + { + return callParticipants.size(); + } + + /** + * Dummy implementation of a method (inherited from CallParticipantListener) + * that we don't need. + * + * @param evt unused. + */ + public void participantImageChanged(CallParticipantChangeEvent evt) + {} + + /** + * Dummy implementation of a method (inherited from CallParticipantListener) + * that we don't need. + * + * @param evt unused. + */ + public void participantAddressChanged(CallParticipantChangeEvent evt) + {} + + /** + * Dummy implementation of a method (inherited from CallParticipantListener) + * that we don't need. + * + * @param evt unused. + */ + public void participantTransportAddressChanged( + CallParticipantChangeEvent evt) + {} + + + /** + * Dummy implementation of a method (inherited from CallParticipantListener) + * that we don't need. + * + * @param evt unused. + */ + public void participantDisplayNameChanged(CallParticipantChangeEvent evt) + {} + + /** + * Verifies whether the call participant has entered a state. + * + * @param evt The <tt>CallParticipantChangeEvent</tt> instance containing + * the source event as well as its previous and its new status. + */ + public void participantStateChanged(CallParticipantChangeEvent evt) + { + if(((CallParticipantState)evt.getNewValue()) + == CallParticipantState.DISCONNECTED + || ((CallParticipantState)evt.getNewValue()) + == CallParticipantState.FAILED) + { + removeCallParticipant( + (CallParticipantJabberImpl)evt.getSourceCallParticipant()); + } + else if (((CallParticipantState)evt.getNewValue()) + == CallParticipantState.CONNECTED + && getCallState().equals(CallState.CALL_INITIALIZATION)) + { + setCallState(CallState.CALL_IN_PROGRESS); + } + } + + /** + * Returns <tt>true</tt> if <tt>session</tt> matches the jingle session + * established with one of the participants in this call. + * + * @param session the session whose corresponding participant we're looking + * for. + * @return true if this call contains a call participant whose jingleSession + * session is the same as the specified and false otherwise. + */ + public boolean contains(JingleSession session) + { + return findCallParticipant(session) != null; + } + + /** + * Returns the call participant whose associated jingle session matches + * <tt>session</tt>. + * + * @param session the jingle session whose corresponding participant we're + * looking for. + * @return the call participant whose jingle session is the same as the + * specified or null if no such call participant was found. + */ + public CallParticipantJabberImpl findCallParticipant(JingleSession session) + { + Iterator callParticipants = this.getCallParticipants(); + + if(logger.isTraceEnabled()) + { + logger.trace("Looking for participant with session: " + session + + "among " + this.callParticipants.size() + " calls"); + } + + + while (callParticipants.hasNext()) + { + CallParticipantJabberImpl cp + = (CallParticipantJabberImpl)callParticipants.next(); + + if( cp.getJingleSession() == session) + { + logger.trace("Returing cp="+cp); + return cp; + } + else + { + logger.trace("Ignoring cp="+cp + + " because cp.jingleSession="+cp.getJingleSession() + + " while session="+session); + } + } + + return null; + } + + /** + * Sets the <tt>CallSession</tt> that the media service has created for this + * call. + * + * @param callSession the <tt>CallSession</tt> that the media service has + * created for this call. + */ + public void setMediaCallSession(CallSession callSession) + { + this.mediaCallSession = callSession; + } + + /** + * Sets the <tt>CallSession</tt> that the media service has created for this + * call. + * + * @return the <tt>CallSession</tt> that the media service has + * created for this call or null if no call session has been created so + * far. + */ + public CallSession getMediaCallSession() + { + return this.mediaCallSession; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallParticipantJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallParticipantJabberImpl.java new file mode 100644 index 0000000..ed1669a --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallParticipantJabberImpl.java @@ -0,0 +1,346 @@ +/* + * SIP Communicator, 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; + +//import java.text.*; +import java.util.*; + +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; +import org.jivesoftware.smackx.jingle.*; + +/** + * Our Jabber implementation of the default CallParticipant; + * + * @author Emil Ivov + */ +public class CallParticipantJabberImpl + extends AbstractCallParticipant +{ + /** + * logger of this class + */ + private static final Logger logger + = Logger.getLogger(CallParticipantJabberImpl.class); + + /** + * The jabber address of this participant + */ + private String participantAddress = null; + + /** + * The state of the call participant. + */ + protected CallParticipantState callParticipantState = + CallParticipantState.UNKNOWN; + /** + * Indicates the date when is call participant passed into its current state. + */ + protected Date currentStateStartDate = new Date(); + + /** + * A byte array containing the image/photo representing the call participant. + */ + private byte[] image; + + /** + * A string uniquely identifying the participant. + */ + private String participantID; + + /** + * The call this participant belongs to. + */ + private CallJabberImpl call; + + /** + * The jingle session that has been created by the application for + * communication with this call participant. + */ + private JingleSession jingleSession = null; + + /** + * Creates a new call participant with address <tt>participantAddress</tt>. + * + * @param participantAddress the Jabber address of the new call + * participant. + * @param owningCall the call that contains this call participant. + */ + public CallParticipantJabberImpl(String participantAddress, + CallJabberImpl owningCall) + { + this.participantAddress = participantAddress; + this.call = owningCall; + call.addCallParticipant(this); + + //create the uid + this.participantID = String.valueOf( System.currentTimeMillis()) + + String.valueOf(hashCode()); + } + + /** + * Returns a String locator for that participant. + * + * @return the participant's address or phone number. + */ + public String getAddress() + { + return participantAddress; + } + + /** + * Specifies the address, phone number, or other protocol specific + * identifier that represents this call participant. This method is to be + * used by service users and MUST NOT be called by the implementation. + * + * @param address The address of this call participant. + */ + public void setAddress(String address) + { + String oldAddress = getAddress(); + + if(participantAddress.equals(address)) + return; + + this.participantAddress = address; + //Fire the Event + fireCallParticipantChangeEvent( + CallParticipantChangeEvent.CALL_PARTICIPANT_ADDRESS_CHANGE, + oldAddress, + address.toString()); + } + + /** + * Returns an object representing the current state of that participant. + * + * @return a CallParticipantState instance representing the participant's + * state. + */ + public CallParticipantState getState() + { + return callParticipantState; + } + + /** + * Causes this CallParticipant to enter the specified state. The method also + * sets the currentStateStartDate field and fires a + * CallParticipantChangeEvent. + * + * @param newState the state this call participant should enter. + * @param reason a string that could be set to contain a human readable + * explanation for the transition (particularly handy when moving into a + * FAILED state). + */ + protected void setState(CallParticipantState newState, String reason) + { + CallParticipantState oldState = getState(); + + if(oldState == newState) + return; + + this.callParticipantState = newState; + this.currentStateStartDate = new Date(); + fireCallParticipantChangeEvent( + CallParticipantChangeEvent.CALL_PARTICIPANT_STATE_CHANGE, + oldState, + newState); + } + + /** + * Causes this CallParticipant to enter the specified state. The method also + * sets the currentStateStartDate field and fires a + * CallParticipantChangeEvent. + * + * @param newState the state this call participant should enter. + */ + protected void setState(CallParticipantState newState) + { + setState(newState, null); + } + + + + /** + * Returns the date (time) when this call participant acquired its + * current status. + * + * @return a java.util.Date object containing the date when this call + * participant entered its current state. + */ + public Date getCurrentStateStartDate() + { + return currentStateStartDate; + } + + /** + * Returns a human readable name representing this participant. + * + * @return a String containing a name for that participant. + */ + public String getDisplayName() + { + int atIndex = participantAddress.indexOf("@"); + if (atIndex > 0) { + return participantAddress.substring(0, atIndex); + } else { + return participantAddress; + } + } + + /** + * Sets a human readable name representing this participant. + * + * @param displayName the participant's display name + */ + protected void setDisplayName(String displayName) + { + String oldName = getDisplayName(); + /*try + { + //this.participantAddress.setDisplayName(displayName); + } + catch (ParseException ex) + { + //couldn't happen + logger.error(ex.getMessage(), ex); + throw new IllegalArgumentException(ex.getMessage()); + }*/ + + //Fire the Event + fireCallParticipantChangeEvent( + CallParticipantChangeEvent.CALL_PARTICIPANT_DISPLAY_NAME_CHANGE, + oldName, + displayName); + } + + /** + * The method returns an image representation of the call participant + * (e.g. + * + * @return byte[] a byte array containing the image or null if no image + * is available. + */ + public byte[] getImage() + { + return image; + } + + /** + * Sets the byte array containing an image representation (photo or picture) + * of the call participant. + * + * @param image a byte array containing the image + */ + protected void setImage(byte[] image) + { + byte[] oldImage = getImage(); + this.image = image; + + //Fire the Event + fireCallParticipantChangeEvent( + CallParticipantChangeEvent.CALL_PARTICIPANT_IMAGE_CHANGE, + oldImage, + image); + } + + /** + * Returns a unique identifier representing this participant. + * + * @return an identifier representing this call participant. + */ + public String getParticipantID() + { + return participantID; + } + + /** + * Returns the latest sdp description that this participant sent us. + * @return the latest sdp description that this participant sent us. + */ + /*public String getSdpDescription() + { + return sdpDescription; + }*/ + + /** + * Sets the String that serves as a unique identifier of this + * CallParticipant. + * @param participantID the ID of this call participant. + */ + protected void setParticipantID(String participantID) + { + this.participantID = participantID; + } + + /** + * Returns a reference to the call that this participant belongs to. Calls + * are created by underlying telephony protocol implementations. + * + * @return a reference to the call containing this participant. + */ + public Call getCall() + { + return call; + } + + /** + * Sets the call containing this participant. + * @param call the call that this call participant is + * partdicipating in. + */ + protected void setCall(CallJabberImpl call) + { + this.call = call; + } + + /** + * Sets the jingle session that has been created by the application for + * communication with this call participant. + * @param session the jingle session that has been created by the + * application for this call. + */ + public void setJingleSession(JingleSession session) + { + this.jingleSession = session; + } + + /** + * Returns the jingle session that has been created by the application for + * communication with this call participant. + * + * @return the jingle session that has been created by the application for + * communication with this call participant. + */ + + public JingleSession getJingleSession() + { + return jingleSession; + } + + /** + * Returns the protocol provider that this participant belongs to. + * @return a reference to the ProtocolProviderService that this participant + * belongs to. + */ + public ProtocolProviderService getProtocolProvider() + { + return this.getCall().getProtocolProvider(); + } + + /** + * Returns the contact corresponding to this participant or null if no + * particular contact has been associated. + * <p> + * @return the <tt>Contact</tt> corresponding to this participant or null + * if no particular contact has been associated. + */ + public Contact getContact() + { + return null; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/JabberActivator.java b/src/net/java/sip/communicator/impl/protocol/jabber/JabberActivator.java index 0928a22..e42404b 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/JabberActivator.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/JabberActivator.java @@ -1,3 +1,9 @@ +/* + * SIP Communicator, 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; import java.util.*; @@ -5,20 +11,41 @@ import java.util.*; import org.osgi.framework.*; import net.java.sip.communicator.service.configuration.*; import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.media.*; /** * Loads the Jabber provider factory and registers it with service in the OSGI * bundle context. * * @author Damian Minkov + * @author Symphorien Wanko */ public class JabberActivator implements BundleActivator { - private ServiceRegistration jabberPpFactoryServReg = null; - private static BundleContext bundleContext = null; - private static ConfigurationService configurationService = null; + /** + * Service reference for the currently valid Jabber provider factory. + */ + private ServiceRegistration jabberPpFactoryServReg = null; + + /** + * Bundle context from OSGi. + */ + private static BundleContext bundleContext = null; + + /** + * Configuration service. + */ + private static ConfigurationService configurationService = null; + + /** + * Media service. + */ + private static MediaService mediaService = null; + /** + * The jabber protocol provider factory. + */ private static ProtocolProviderFactoryJabberImpl jabberProviderFactory = null; /** @@ -92,6 +119,26 @@ public class JabberActivator } /** + * Returns a reference to a MediaService implementation currently registered + * in the bundle context or null if no such implementation was found. + * + * @return a reference to a MediaService implementation currently registered + * in the bundle context or null if no such implementation was found. + */ + public static MediaService getMediaService() + { + if(mediaService == null) + { + ServiceReference mediaServiceReference + = bundleContext.getServiceReference( + MediaService.class.getName()); + mediaService = (MediaService)bundleContext + .getService(mediaServiceReference); + } + return mediaService; + } + + /** * Called when this bundle is stopped so the Framework can perform the * bundle-specific activities necessary to stop the bundle. * diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java new file mode 100644 index 0000000..775d69a --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java @@ -0,0 +1,720 @@ +/* + * SIP Communicator, 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; + +import java.text.*; +import java.util.*; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smackx.jingle.*; +import org.jivesoftware.smackx.jingle.media.*; +import org.jivesoftware.smackx.jingle.listeners.*; +import org.jivesoftware.smackx.jingle.nat.*; + +import net.java.sip.communicator.service.media.*; +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.impl.protocol.jabber.mediamgr.*; +import org.jivesoftware.smackx.packet.DiscoverInfo; + +/** + * Implements all call management logic and exports basic telephony support by + * implementing OperationSetBasicTelephony. + * + * @author Symphorien Wanko + */ +public class OperationSetBasicTelephonyJabberImpl + implements OperationSetBasicTelephony, + RegistrationStateChangeListener, + JingleMediaListener, + JingleTransportListener, + JingleSessionRequestListener, + CreatedJingleSessionListener, + JingleSessionStateListener, + JingleSessionListener +{ + + /** + * the logger used by this class + */ + private static final Logger logger + = Logger.getLogger(OperationSetBasicTelephonyJabberImpl.class); + + /** + * A reference to the <tt>ProtocolProviderServiceJabberImpl</tt> instance + * that created us. + */ + private ProtocolProviderServiceJabberImpl protocolProvider = null; + + /** + * A list of listeners registered for call events. + */ + private Vector callListeners = new Vector(); + + /** + * Contains references for all currently active (non ended) calls. + */ + private ActiveCallsRepository activeCallsRepository + = new ActiveCallsRepository(this); + + /** + * The manager we use to initiate, receive and ... manage jingle session. + */ + private JingleManager jingleManager = null; + + /** + * The transport manager is used by the <tt>jingleManager</tt> to handle transport + * method. + */ + private JingleTransportManager transportManager = null; + + /** + * The media manager is used by the <tt>jingleManager</tt> to handle media + * session. + */ + private JingleMediaManager mediaManager = null; + + /** + * Creates a new instance. + * + * @param protocolProvider a reference to the + * <tt>ProtocolProviderServiceJabberImpl</tt> instance that created us. + */ + public OperationSetBasicTelephonyJabberImpl( + ProtocolProviderServiceJabberImpl protocolProvider) + { + + this.protocolProvider = protocolProvider; + protocolProvider.addRegistrationStateChangeListener(this); + transportManager = new BasicTransportManager(); + mediaManager = new JingleScMediaManager(); + } + + /** + * Implementation of method <tt>registrationStateChange</tt> from + * interface RegistrationStateChangeListener for setting up (or down) + * our <tt>JingleManager</tt> when an <tt>XMPPConnection</tt> is available + * + * @param evt the event received + */ + public void registrationStateChanged(RegistrationStateChangeEvent evt) + { + if ((evt.getNewState() == RegistrationState.REGISTERED)) + { + // NOTE: a null stun server is provided here _intentionally_ + // it may cause some exceptions, it's temporary + logger.warn("warning: we will use a null stun server ***"); + transportManager = new ICETransportManager( + protocolProvider.getConnection(), + null, 3478); + + jingleManager = new JingleManager( + protocolProvider.getConnection(), + transportManager, + mediaManager); + + jingleManager.addCreationListener(this); + jingleManager.addJingleSessionRequestListener(this); + logger.info("Jingle : ON "); + } + else if ((evt.getNewState() == RegistrationState.UNREGISTERED)) + { + if (jingleManager != null) + { + jingleManager.removeCreationListener(this); + jingleManager.removeJingleSessionRequestListener(this); + jingleManager = null; + logger.info("Jingle : OFF "); + } + } + } + + /** + * Registers <tt>listener</tt> with this provider so that it + * could be notified when incoming calls are received. + * + * @param listener the listener to register with this provider. + */ + public void addCallListener(CallListener listener) + { + synchronized(callListeners) + { + if (!callListeners.contains(listener)) + callListeners.add(listener); + } + } + + /** + * Create a new call and invite the specified CallParticipant to it. + * + * @param callee the jabber address of the callee that we should invite to a + * new call. + * @return CallParticipant the CallParticipant that will represented by + * the specified uri. All following state change events will be + * delivered through that call participant. The Call that this + * participant is a member of could be retrieved from the + * CallParticipatn instance with the use of the corresponding method. + * @throws OperationFailedException with the corresponding code if we fail + * to create the call. + */ + public Call createCall(String callee) + throws OperationFailedException + { + + return createOutgoingCall(callee); + } + + /** + * Create a new call and invite the specified CallParticipant to it. + * + * @param callee the address of the callee that we should invite to a + * new call. + * @return CallParticipant the CallParticipant that will represented by + * the specified uri. All following state change events will be + * delivered through that call participant. The Call that this + * participant is a member of could be retrieved from the + * CallParticipatn instance with the use of the corresponding method. + * @throws OperationFailedException with the corresponding code if we fail + * to create the call. + */ + public Call createCall(Contact callee) + throws OperationFailedException + { + + return createOutgoingCall(callee.getAddress()); + } + + /** + * Init and establish the specified call. + * + * @param calleeAddress the address of the callee that we'd like to connect + * with. + * + * @return CallParticipant the CallParticipant that represented by + * the specified uri. All following state change events will be + * delivered through that call participant. The Call that this + * participant is a member of could be retrieved from the + * CallParticipatn instance with the use of the corresponding method. + * + * @throws OperationFailedException with the corresponding code if we fail + * to create the call. + */ + private CallJabberImpl createOutgoingCall(String calleeAddress) + throws OperationFailedException + { + OutgoingJingleSession outJS; + + logger.info("creating outgoing call..."); + if (protocolProvider.getConnection() == null) { + throw new OperationFailedException( + "Failed to create OutgoingJingleSession.\n" + + "we don't have a valid XMPPConnection." + , OperationFailedException.INTERNAL_ERROR); + } + String resource = protocolProvider.getConnection(). + getRoster().getPresence(calleeAddress).getFrom(); + String[] parts = resource.split("/"); + if (parts.length != 2) + { + throw new OperationFailedException( + "Failed to create OutgoingJingleSession.\n" + + "user " + calleeAddress + " is unknwon to us." + , OperationFailedException.INTERNAL_ERROR); + } + resource = "/" + parts[1]; + try + { + outJS = jingleManager.createOutgoingJingleSession(calleeAddress. + concat(resource)); + } + catch (XMPPException ex) + { + throw new OperationFailedException( + "Failed to create OutgoingJingleSession.\n" + + "This is most probably a network connection error." + , OperationFailedException.INTERNAL_ERROR + , ex); + } + CallJabberImpl call = new CallJabberImpl(protocolProvider); + CallParticipantJabberImpl callParticipant = + new CallParticipantJabberImpl(calleeAddress, call); + callParticipant.setJingleSession(outJS); + + callParticipant.setState(CallParticipantState.INITIATING_CALL); + fireCallEvent(CallEvent.CALL_INITIATED, call); + + activeCallsRepository.addCall(call); + outJS.start(); + return (CallJabberImpl) callParticipant.getCall(); + } + + /** + * Creates and dispatches a <tt>CallEvent</tt> notifying registered + * listeners that an event with id <tt>eventID</tt> has occurred on + * <tt>sourceCall</tt>. + * + * @param eventID the ID of the event to dispatch + * @param sourceCall the call on which the event has occurred. + */ + protected void fireCallEvent( int eventID, + CallJabberImpl sourceCall) + { + CallEvent cEvent = new CallEvent(sourceCall, eventID); + + logger.debug("Dispatching a CallEvent to " + + callListeners.size() + +" listeners. event is: " + cEvent.toString()); + Iterator listeners = null; + synchronized(callListeners) + { + listeners = new ArrayList(callListeners).iterator(); + } + + while(listeners.hasNext()) + { + CallListener listener = (CallListener)listeners.next(); + if(eventID == CallEvent.CALL_INITIATED) + listener.outgoingCallCreated(cEvent); + else if(eventID == CallEvent.CALL_RECEIVED) + listener.incomingCallReceived(cEvent); + else if(eventID == CallEvent.CALL_ENDED) + listener.callEnded(cEvent); + } + } + + /** + * Returns an iterator over all currently active calls. + * + * @return an iterator over all currently active calls. + */ + public Iterator getActiveCalls() + { + return activeCallsRepository.getActiveCalls(); + } + + /** + * Resumes communication with a call participant previously put on hold. + * + * @param participant the call participant to put on hold. + */ + public void putOffHold(CallParticipant participant) + { + /** @todo implement putOffHold() */ + ((CallParticipantJabberImpl) participant).getJingleSession(). + getJingleMediaSession().setTrasmit(true); + } + + /** + * Puts the specified CallParticipant "on hold". + * + * @param participant the participant that we'd like to put on hold. + */ + public void putOnHold(CallParticipant participant) + { + /** @todo implement putOnHold() */ + ((CallParticipantJabberImpl) participant).getJingleSession(). + getJingleMediaSession().setTrasmit(false); + } + + /** + * Removes the <tt>listener</tt> from the list of call listeners. + * + * @param listener the listener to unregister. + */ + public void removeCallListener(CallListener listener) + { + synchronized(callListeners) + { + callListeners.remove(listener); + } + } + + /** + * Implements method <tt>hangupCallParticipant</tt> + * from <tt>OperationSetBasicTelephony</tt>. + * + * @param participant the participant that we'd like to hang up on. + * @throws ClassCastException if participant is not an instance of + * CallParticipantJabberImpl. + * + * @throws OperationFailedException if we fail to terminate the call. + * + * // TODO: ask for suppression of OperationFailedException from the interface. + * // what happens if hangup fails ? are we forced to continue to talk ? :o) + */ + public void hangupCallParticipant(CallParticipant participant) + throws ClassCastException, OperationFailedException + { + CallParticipantJabberImpl callParticipant + = (CallParticipantJabberImpl)participant; + try + { + callParticipant.getJingleSession().terminate(); + } + catch (XMPPException ex) + { + ex.printStackTrace(); + } + finally + { + callParticipant.setState(CallParticipantState.DISCONNECTED); + } + } + + + + /** + * Implements method <tt>answerCallParticipant</tt> + * from <tt>OperationSetBasicTelephony</tt>. + * + * @param participant the call participant that we want to answer + * @throws OperationFailedException if we fails to answer + */ + public void answerCallParticipant(CallParticipant participant) + throws OperationFailedException + { + CallParticipantJabberImpl callParticipant + = (CallParticipantJabberImpl)participant; + try + { + ((IncomingJingleSession)callParticipant.getJingleSession()). + start(); + } + catch (XMPPException ex) + { + throw new OperationFailedException( + "Failed to answer an incoming call" + , OperationFailedException.INTERNAL_ERROR); + } + } + + /** + * Closes all active calls. And releases resources. + */ + public void shutdown() + { + logger.trace("Ending all active calls."); + Iterator activeCalls = this.activeCallsRepository.getActiveCalls(); + + // this is fast, but events aren't triggered ... + //jingleManager.disconnectAllSessions(); + + //go through all active calls. + while(activeCalls.hasNext()) + { + CallJabberImpl call = (CallJabberImpl)activeCalls.next(); + + Iterator callParticipants = call.getCallParticipants(); + + //go through all call participants and say bye to every one. + while(callParticipants.hasNext()) + { + CallParticipant participant + = (CallParticipant) callParticipants.next(); + try + { + this.hangupCallParticipant(participant); + } + catch (Exception ex) + { + logger.warn("Failed to properly hangup participant " + + participant + , ex); + } + } + } + } + + /** + * Implements method sessionRequested from JingleSessionRequestListener. + * + * @param jingleSessionRequest the session requested + */ + public void sessionRequested(JingleSessionRequest jingleSessionRequest) + { + IncomingJingleSession inJS; + logger.info("session requested "); + try + { + inJS = jingleSessionRequest.accept(); + } + catch (XMPPException ex) + { + logger.error("Failed to accept inoming jingle request : " + ex); + return; + } + CallJabberImpl call = new CallJabberImpl(protocolProvider); + String from = jingleSessionRequest.getFrom(); + if (from.indexOf("/") > 0) + { + from = from.substring(0, from.indexOf("/")); + } + CallParticipantJabberImpl callParticipant = + new CallParticipantJabberImpl(from, call); + callParticipant.setJingleSession(inJS); + callParticipant.setState(CallParticipantState.INCOMING_CALL); + activeCallsRepository.addCall(call); + fireCallEvent(CallEvent.CALL_RECEIVED, call); + } + + /** + * Implements method sessionCreated from CreatedJingleSessionListener. + * + * @param jingleSession the newly created jingle session + */ + public void sessionCreated(JingleSession jingleSession) + { + logger.info("session created : " + jingleSession); + jingleSession.addListener(this); + jingleSession.addMediaListener(this); + jingleSession.addStateListener(this); + jingleSession.addTransportListener(this); + } + + /** + * Implements method mediaClosed from JingleMediaListener. + * + * @param payloadType payload supported by the closed media + */ + public void mediaClosed(PayloadType payloadType) + { + logger.info(" media losed "); + } + + /** + * Implements method <tt>mediaEstablished</tt> from JingleMediaListener. + * + * @param payloadType payload used by the established media + */ + public void mediaEstablished(PayloadType payloadType) + { + logger.info("media established "); + } + + /** + * Implements method <tt>transportClosed</tt> from JingleTransportListener. + * + * @param transportCandidate <tt>transportCandiate</tt> with which + * we were dealing + */ + public void transportClosed(TransportCandidate transportCandidate) + { + logger.info("transport closed\n"); + } + + /** + * Implements method <tt>transportClosedOnError</tt> from JingleTransportListener. + * + * @param ex the exception accompagning this error + */ + public void transportClosedOnError(XMPPException ex) + { + logger.error("transport closed on error ", ex); + } + + /** + * Implements method <tt>transportEstablished</tt> from JingleTransportListener. + * + * @param local local <tt>TransportCandidate</tt> for this transport link + * @param remote remote <tt>TransportCandidate</tt> for this transport link + */ + public void transportEstablished(TransportCandidate local, + TransportCandidate remote) + { + logger.info("transport established " + local + " -:- " + remote); + } + + /** + * Implements method <tt>beforeChange</tt> from JingleSessionStateListener. + * This method is called before the change occurs in the session. + * We can cancel the change by throwing a <tt>JingleException</tt> + * + * @param oldState old state of the session + * @param newState state in which we will go + * + * @throws JingleException we have the ability to cancel a state change by + * throwing a <tt>JingleException</tt> + */ + public void beforeChange(JingleNegotiator.State oldState + , JingleNegotiator.State newState) + throws JingleNegotiator.JingleException + { + if (newState instanceof IncomingJingleSession.Accepting) + {} + else if (newState instanceof IncomingJingleSession.Pending) + {} + else if (newState instanceof IncomingJingleSession.Active) + { + JingleSession session = (JingleSession) newState.getNegotiator(); + CallParticipantJabberImpl callParticipant = + activeCallsRepository.findCallParticipant(session); + if (callParticipant == null) + { + return; + } + callParticipant.setState(CallParticipantState.CONNECTED); + } + else if (newState instanceof OutgoingJingleSession.Inviting) + { + JingleSession session = (JingleSession) newState.getNegotiator(); + CallParticipantJabberImpl callParticipant = + activeCallsRepository.findCallParticipant(session); + if (callParticipant == null) + { + return; + } + callParticipant.setState(CallParticipantState.CONNECTING); + } + else if (newState instanceof OutgoingJingleSession.Pending) + { + JingleSession session = (JingleSession) newState.getNegotiator(); + CallParticipantJabberImpl callParticipant = + activeCallsRepository.findCallParticipant(session); + if (callParticipant == null) + { + return; + } + callParticipant.setState(CallParticipantState.ALERTING_REMOTE_SIDE); + } + else if (newState instanceof OutgoingJingleSession.Active) + { + JingleSession session = (JingleSession) newState.getNegotiator(); + CallParticipantJabberImpl callParticipant = + activeCallsRepository.findCallParticipant(session); + if (callParticipant == null) + { + return; + } + callParticipant.setState(CallParticipantState.CONNECTED); + } +// else if (newState instanceof MediaNegotiator.Inviting) +// {} +// else if (newState instanceof MediaNegotiator.Pending) +// {} +// else if (newState instanceof MediaNegotiator.Accepting) +// {} +// else if (newState instanceof MediaNegotiator.Active) +// {} +// else if (newState instanceof TransportNegotiator.Inviting) +// {} +// else if (newState instanceof TransportNegotiator.Pending) +// {} +// else if (newState instanceof TransportNegotiator.Accepting) +// {} +// else if (newState instanceof TransportNegotiator.Active) +// {} + + if ((newState == null) && (oldState != null)) + { //hanging + JingleSession session = (JingleSession) oldState.getNegotiator(); + CallParticipantJabberImpl callParticipant = + activeCallsRepository.findCallParticipant(session); + if (callParticipant == null) + { + logger.debug("Received a stray trying response."); + return; + } + try + { + hangupCallParticipant(callParticipant); + } + catch (Exception ex) + { + ex.printStackTrace(); + } + } + } + + /** + * Implements method <tt>afterChanged</tt> from JingleSessionStateListener. + * called when we are effectivly in the <tt>newState</tt> + * + * @param oldState old session state + * @param newState new session state + */ + public void afterChanged(JingleNegotiator.State oldState, + JingleNegotiator.State newState) + { + logger.info("session state changed : " + oldState + " => " + newState); + } + + /** + * Implements <tt>sessionEstablished</tt> from <tt>JingleSessionListener</tt> + * + * + * @param payloadType the <tt>payloadType</tt> used for media in thi session + * @param remoteCandidate the remote end point of thi session + * @param localCandidate the local end point of this session + * @param jingleSession the session which is now fully established + */ + public void sessionEstablished(PayloadType payloadType, + TransportCandidate remoteCandidate, + TransportCandidate localCandidate, JingleSession jingleSession) + { + logger.info("session established "); + } + + /** + * Implements <tt>sessionDeclined</tt> from <tt>JingleSessionListener</tt> + * + * @param reason why the session has been declined + * @param jingleSession the declined session + */ + public void sessionDeclined(String reason, JingleSession jingleSession) + { + logger.info("session declined : " + reason); + } + + /** + * Implements <tt>sessionRedirected</tt> from <tt>JingleSessionListener</tt> + * + * @param redirection redirection information + * @param jingleSession the session which redirected + */ + public void sessionRedirected(String redirection, + JingleSession jingleSession) + { + logger.info("session redirected : " + redirection); + } + + /** + * Implements <tt>sessionClosed</tt> from <tt>JingleSessionListener</tt> + * + * @param reason why the session has been closed + * @param jingleSession the session which is closed + */ + public void sessionClosed(String reason, JingleSession jingleSession) + { + logger.info("session closed : " + reason); + } + + /** + * Implements <tt>sessionClosedOnError</tt> from <tt>JingleSessionListener</tt> + * + * @param ex execption which caused the error + * @param jingleSession the session which is closed + */ + public void sessionClosedOnError(XMPPException ex, + JingleSession jingleSession) + { + logger.error("session closed on error ", ex); + } + + /** + * Implements <tt>sessionMediaReceived</tt> from <tt>JingleSessionListener</tt> + * + * @param jingleSession the session where the media is established + * @param participant the participant for this media session + */ + public void sessionMediaReceived(JingleSession jingleSession, + String participant) + { + logger.info("session media received "); + } +} + diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java index 2dae79d..5f51215 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java @@ -18,14 +18,22 @@ import net.java.sip.communicator.util.*; import org.jivesoftware.smack.*; import org.jivesoftware.smack.util.*; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smackx.packet.*; + /** * An implementation of the protocol provider service over the Jabber protocol * * @author Damian Minkov + * @author Symphorien Wanko */ public class ProtocolProviderServiceJabberImpl implements ProtocolProviderService { + /** + * Logger of this class + */ private static final Logger logger = Logger.getLogger(ProtocolProviderServiceJabberImpl.class); @@ -34,6 +42,9 @@ public class ProtocolProviderServiceJabberImpl */ private Hashtable supportedOperationSets = new Hashtable(); + /** + * Used to connect to a XMPP server. + */ private XMPPConnection connection = null; /** @@ -62,6 +73,9 @@ public class ProtocolProviderServiceJabberImpl */ private SecurityAuthority authority = null; + /** + * True if we are reconnecting, false otherwise. + */ private boolean reconnecting = false; /** @@ -71,6 +85,26 @@ public class ProtocolProviderServiceJabberImpl = new ProtocolIconJabberImpl(); /** + * A set of features supported by our Jabber implementation. + * In general, we add new feature(s) when we add new operation sets. + * (see xep-0030 : http://www.xmpp.org/extensions/xep-0030.html#info). + * Example : to tell the world that we support jingle, we simply have + * to do : + * supportedFeatures.add("http://www.xmpp.org/extensions/xep-0166.html#ns"); + * Beware there is no canonical mapping between op set and jabber features + * (op set is a SC "concept"). This means that one op set in SC can + * correspond to many jabber features. It is also possible that there is no + * jabber feature corresponding to a SC op set or again, + * we can currently support some features wich do not have a specific + * op set in SC (the mandatory feature : + * http://jabber.org/protocol/disco#info is one example). + * We can find features corresponding to op set in the xep(s) related + * to implemented functionality. + */ + private List supportedFeatures = new ArrayList(); + + + /** * Returns the state of the registration of this protocol provider * @return the <tt>RegistrationState</tt> that this provider is * currently in or null in case it is in a unknown state. @@ -351,6 +385,55 @@ public class ProtocolProviderServiceJabberImpl OperationFailedException.INVALID_ACCOUNT_PROPERTIES, ex); } } + + // we listen for discovery info request and send an answer + // wich advetise all features supported by our jabber implementation + if (getRegistrationState() == RegistrationState.REGISTERED) + { + connection.addPacketListener( new PacketListener() + { + public void processPacket(Packet packet) + { + DiscoverInfo infos = new DiscoverInfo(); + + infos.setType(IQ.Type.RESULT); + infos.setFrom(packet.getTo()); + infos.setTo(packet.getFrom()); + infos.setPacketID(packet.getPacketID()); + + DiscoverInfo.Identity id + = new DiscoverInfo.Identity("account", "sc"); + id.setType("registered"); + infos.addIdentity(id); + + Iterator it = supportedFeatures.iterator(); + while (it.hasNext()) + { + infos.addFeature((String) it.next()); + } + connection.sendPacket(infos); + } + }, new PacketFilter() + { + public boolean accept(Packet packet) + { + // we are interested only for packets related to + // disovery info here. + String s = packet.toXML(); + if (s.indexOf("type=\"get\"") != -1 + && s.indexOf( + "http://jabber.org/protocol/disco#info") != -1) + { + return true; + } + else + { + return false; + } + } + }); + } + } /** @@ -423,7 +506,7 @@ public class ProtocolProviderServiceJabberImpl * @param opsetClass the <tt>Class</tt> of the operation set that we're * looking for. * @return returns an OperationSet of the specified <tt>Class</tt> if the - * undelying implementation supports it or null otherwise. + * underlying implementation supports it or null otherwise. */ public OperationSet getOperationSet(Class opsetClass) { @@ -433,7 +516,7 @@ public class ProtocolProviderServiceJabberImpl /** * Initialized the service implementation, and puts it in a sate where it - * could interoperate with other services. It is strongly recomended that + * could interoperate with other services. It is strongly recommended that * properties in this Map be mapped to property names as specified by * <tt>AccountProperties</tt>. * @@ -451,6 +534,10 @@ public class ProtocolProviderServiceJabberImpl { this.accountID = accountID; + //this feature is mandatory to be compliant with Service Discovery + supportedFeatures.add("http://jabber.org/protocol/disco#info"); + + String keepAliveStrValue = (String)accountID.getAccountProperties(). get("SEND_KEEP_ALIVE"); @@ -465,11 +552,21 @@ public class ProtocolProviderServiceJabberImpl { persistentPresence.setResourcePriority( new Integer(resourcePriority).intValue()); + // TODO : is this resource priority related to xep-0168 + // (Resource Application Priority) ? + // see http://www.xmpp.org/extensions/xep-0168.html + // If the answer is no, comment the following lines please + supportedFeatures.add( + "http://www.xmpp.org/extensions/xep-0168.html#ns"); } - + supportedOperationSets.put( OperationSetPersistentPresence.class.getName(), persistentPresence); + // TODO: add the feature, if any, corresponding to persistent + // presence, if someone knows + // supportedFeatures.add(_PRESENCE_); + //register it once again for those that simply need presence supportedOperationSets.put( OperationSetPresence.class.getName(), @@ -488,6 +585,11 @@ public class ProtocolProviderServiceJabberImpl OperationSetBasicInstantMessaging.class.getName(), basicInstantMessaging); + // TODO: add the feature, if any, corresponding to IM if someone + // knows + // supportedFeatures.add(_IM_); + + //initialize the typing notifications operation set OperationSetTypingNotifications typingNotifications = new OperationSetTypingNotificationsJabberImpl(this); @@ -495,9 +597,10 @@ public class ProtocolProviderServiceJabberImpl supportedOperationSets.put( OperationSetTypingNotifications.class.getName(), typingNotifications); + supportedFeatures.add("http://jabber.org/protocol/chatstates"); - //initialize the multi user chat operation set + //initialize the multi user chat operation set OperationSetMultiUserChat multiUserChat = new OperationSetMultiUserChatJabberImpl(this); @@ -505,6 +608,29 @@ public class ProtocolProviderServiceJabberImpl OperationSetMultiUserChat.class.getName(), multiUserChat); + // TODO: this is the "main" feature to advertise when a client + // support muc. We have to add some features for + // specific functionnality we support in muc. + // see http://www.xmpp.org/extensions/xep-0045.html + supportedFeatures.add("http://jabber.org/protocol/muc"); + + //initialize the telephony opset + OperationSetBasicTelephony opSetBasicTelephony + = new OperationSetBasicTelephonyJabberImpl(this); + + supportedOperationSets.put( + OperationSetBasicTelephony.class.getName(), + opSetBasicTelephony); + + supportedFeatures.add( + "http://www.xmpp.org/extensions/xep-0166.html#ns"); + // TODO: we have commented out this line since it breaks + // compatibility with spark and since spark is the only other + // client supporting jingle ... it's worth it. let's hope + // they fix it soon.' + //supportedFeatures.add( + // "http://www.xmpp.org/extensions/xep-0167.html#ns"); + isInitialized = true; } } @@ -515,9 +641,17 @@ public class ProtocolProviderServiceJabberImpl * shutdown/garbage collection. */ public void shutdown() - { + { synchronized(initializationLock) { + logger.trace("Killing the Jabber Protocol Provider."); + + //kill all active calls + OperationSetBasicTelephonyJabberImpl telephony + = (OperationSetBasicTelephonyJabberImpl)getOperationSet( + OperationSetBasicTelephony.class); + telephony.shutdown(); + if(connection != null) { connection.disconnect(); @@ -635,9 +769,15 @@ public class ProtocolProviderServiceJabberImpl logger.trace("Done."); } + /** + * Enable to listen for jabber connection events + */ private class JabberConnectionListener implements ConnectionListener { + /** + * Implements <tt>connectionClosed</tt> from <tt>ConnectionListener</tt> + */ public void connectionClosed() { OperationSetPersistentPresenceJabberImpl opSetPersPresence = @@ -650,6 +790,12 @@ public class ProtocolProviderServiceJabberImpl JabberStatusEnum.OFFLINE); } + /** + * Implements <tt>connectionClosedOnError</tt> from + * <tt>ConnectionListener</tt>. + * + * @param exception contains information on the error. + */ public void connectionClosedOnError(Exception exception) { logger.error("connectionClosedOnError " + @@ -661,16 +807,30 @@ public class ProtocolProviderServiceJabberImpl reconnecting = false; } + /** + * Implements <tt>reconnectingIn</tt> from <tt>ConnectionListener</tt> + * + * @param i delay in seconds for reconnection. + */ public void reconnectingIn(int i) { logger.info("reconnectingIn " + i); } + /** + * Implements <tt>reconnectingIn</tt> from <tt>ConnectionListener</tt> + */ public void reconnectionSuccessful() { logger.info("reconnectionSuccessful"); } + /** + * Implements <tt>reconnectionFailed</tt> from + * <tt>ConnectionListener</tt>. + * + * @param exception description of the failure + */ public void reconnectionFailed(Exception exception) { logger.info("reconnectionFailed ", exception); diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf index 90f7d67..eae402c 100755 --- a/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf +++ b/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf @@ -12,11 +12,18 @@ Import-Package: org.osgi.framework, org.jivesoftware.smackx, org.jivesoftware.smackx.muc, org.jivesoftware.smackx.packet, + org.jivesoftware.smackx.jingle, + org.jivesoftware.smackx.jingle.listeners, + org.jivesoftware.smackx.jingle.media, + org.jivesoftware.smackx.jingle.nat, + org.jivesoftware.smackx.jingle.packet, + org.jivesoftware.smackx.jingle.provider, net.java.sip.communicator.service.configuration, net.java.sip.communicator.util, net.java.sip.communicator.service.configuration.event, net.java.sip.communicator.service.protocol, net.java.sip.communicator.service.protocol.jabberconstants, net.java.sip.communicator.service.protocol.event, + net.java.sip.communicator.service.media, org.xmlpull.v1, org.xmlpull.mxp1 diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/mediamgr/AudioMediaSession.java b/src/net/java/sip/communicator/impl/protocol/jabber/mediamgr/AudioMediaSession.java new file mode 100644 index 0000000..b1857b1 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/mediamgr/AudioMediaSession.java @@ -0,0 +1,218 @@ +/* + * SIP Communicator, 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.mediamgr; + +import net.java.sip.communicator.util.*; +import org.jivesoftware.smackx.jingle.*; +import org.jivesoftware.smackx.jingle.media.*; +import org.jivesoftware.smackx.jingle.nat.*; + +import net.java.sip.communicator.service.media.*; +import net.java.sip.communicator.impl.protocol.jabber.*; + +import java.io.*; +import java.net.*; +import java.util.*; + +/** + * This Class implements a JingleMediaSession. which serve as a wrapper + * for a RtpFlow, allowing us to receive and transmit audio. + * + * based on Thiago Camargo's JingleScMediaManager from jingle + * + * @author Symphorien Wanko + */ +public class AudioMediaSession + extends JingleMediaSession +{ + /** + * the logger used by this class + */ + private static final Logger logger + = Logger.getLogger(AudioMediaSession.class); + + /** + * An audio media session encapsulate a RtpFlow which handle media transfer + */ + private RtpFlow rtpFlow = null; + + /** + * Creates an AudioMediaSession with defined payload type, + * remote and local candidates + * + * @param payloadType Payload used by jmf + * @param remote the remote information. + * @param local the local information. + * @param locator media locator + */ + public AudioMediaSession(PayloadType payloadType, TransportCandidate remote + , TransportCandidate local) + { + this(payloadType, remote, local, null); + initialize(); + } + + /** + * This constructor is used only to match the one of our super class. + * In SC, An audioSession don't need the media locator asked by + * <tt>jingleMediaSession</tt> because the locator is handled + * by the media service. + * + * @param payloadType Payload used by jmf + * @param remote the remote information. + * @param local the local information. + * @param locator media locator + */ + private AudioMediaSession(PayloadType payloadType, TransportCandidate remote + , TransportCandidate local, String locator) + { + //super(payloadType, remote, local, locator==null?"dsound://":locator, jingleSession); + super(payloadType, remote, local, locator); + } + + /** + * Initialize the RtpFlow to make us able to send and receive audio media + */ + public void initialize() + { + String remoteIp; + String localIp; + int localPort; + int remotePort; + +// if (getLocal().getSymmetric() != null) +// { +// remoteIp = getLocal().getIp(); +// localIp = getLocal().getLocalIp(); +// localPort = getFreePort(); +// remotePort = getLocal().getSymmetric().getPort(); +// } +// else +// { + if (getLocal().getSymmetric() != null) + { + logger.warn("oops : unexpected situation ... "); + // TODO : if this situation happens only one + // un comment the above code wich is meant to handle this + } + + remoteIp = getRemote().getIp(); + localIp = getLocal().getLocalIp(); + localPort = getLocal().getPort(); + remotePort = getRemote().getPort(); + logger.info("AudioMediaSession : " + localIp + ":" + localPort + + " <-> " + remoteIp + ":" + remotePort); +// } + + Hashtable mediaEncoding = MediaUtils.getAudioEncoding( + getPayloadType().getId()); + try + { + rtpFlow = JabberActivator.getMediaService(). + createRtpFlow( + localIp, localPort, + remoteIp, remotePort, + mediaEncoding); + } + catch (MediaException ex) + { + logger.error("failed to create a RtpFlow between " + + localIp + ":" + localPort + " and " + + remoteIp + ":" + remotePort, ex); + rtpFlow = null; + throw new RuntimeException("failed to create a RtpFlow between " + + localIp + ":" + localPort + " and " + + remoteIp + ":" + remotePort, + ex); + } + } + + /** + * Implements <tt>startTransmit</tt> from <tt>JingleMediaSession.</tt> + */ + public void startTrasmit() + { + rtpFlow.start(); + } + + /** + * Implements <tt>startReceive</tt> from <tt>JingleMediaSession.</tt> + */ + public void startReceive() + {} + + /** + * Implements <tt>setTrasmit</tt> from <tt>JingleMediaSession.</tt> + * + * @param active pause the transmission if false, resume otherwise + */ + public void setTrasmit(boolean active) + { + if (active) + rtpFlow.resume(); + else + rtpFlow.pause(); + } + + /** + * Implements <tt>stopTrasmit</tt> from <tt>JingleMediaSession.</tt> + */ + public void stopTrasmit() + { + //if (rtpFlow != null) + rtpFlow.stop(); + } + + /** + * Implements <tt>stopReceive</tt> from <tt>JingleMediaSession.</tt> + */ + public void stopReceive() + {} + +// /** +// * Obtain a free port we can use. +// * +// * @return A free port number. +// */ +// protected int getFreePort() +// { +// //perhaps this method will be better in an utily class +// +// ServerSocket ss; +// int freePort = 0; +// +// for (int i = 0; i < 10; i++) +// { +// freePort = (int) (10000 + Math.round(Math.random() * 10000)); +// freePort = freePort % 2 == 0 ? freePort : freePort + 1; +// try +// { +// ss = new ServerSocket(freePort); +// freePort = ss.getLocalPort(); +// ss.close(); +// return freePort; +// } +// catch (IOException e) +// { +// e.printStackTrace(); +// } +// } +// try +// { +// ss = new ServerSocket(0); +// freePort = ss.getLocalPort(); +// ss.close(); +// } +// catch (IOException e) +// { +// e.printStackTrace(); +// } +// +// return freePort; +// } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/mediamgr/JingleScMediaManager.java b/src/net/java/sip/communicator/impl/protocol/jabber/mediamgr/JingleScMediaManager.java new file mode 100644 index 0000000..85b360f --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/mediamgr/JingleScMediaManager.java @@ -0,0 +1,100 @@ +/* + * SIP Communicator, 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.mediamgr; + +import java.util.*; +import net.java.sip.communicator.impl.protocol.jabber.*; +import net.java.sip.communicator.util.*; + +import org.jivesoftware.smackx.jingle.*; +import org.jivesoftware.smackx.jingle.media.*; +import org.jivesoftware.smackx.jingle.nat.*; + +/** + * Implements a JingleMediaManager backed by JMF. + * + * based on Thiago Camargo's JingleScMediaManager from jingle + * + * @author Symphorien Wanko + */ +public class JingleScMediaManager extends JingleMediaManager +{ + /** + * Logger for this class + */ + private Logger logger = Logger.getLogger(JingleScMediaManager.class); + + /** + * List of payload that this media manager supports. + * This list is based on the one reported by the media service. + */ + private List payloads = new ArrayList(); + + /** + * Creates a new instance of JingleScMediaManager + */ + public JingleScMediaManager() + { + setupPayloads(); + } + + /** + * Returns a new jingleMediaSession + * + * @param payloadType payloadType + * @param remote remote Candidate + * @param local local Candidate + * @return JingleMediaSession + */ + public JingleMediaSession createMediaSession( + PayloadType payloadType, + TransportCandidate remote, + TransportCandidate local) + { + return new AudioMediaSession(payloadType, remote, local); + } + + /** + * Setup API supported Payloads + * + * http://tools.ietf.org/html/rfc3551#page-32 to view the + *corresponsdance between PayloadType and codec + */ + private void setupPayloads() { + + String[] audioEnc = JabberActivator.getMediaService(). + getSupportedAudioEncodings(); + for (int i = 0; i < audioEnc.length; i++) + { + int payloadType = Integer.parseInt(audioEnc[i]); + if (MediaUtils.getPayloadName(payloadType) != null) + { + payloads.add(new PayloadType.Audio(payloadType + , MediaUtils.getPayloadName(payloadType))); + } + else + { + payloads.add(new PayloadType.Audio(payloadType, audioEnc[i])); + } + } + if (payloads.isEmpty()) + { + logger.warn("The list of payloads supported" + + " by JmfMediaManager is empty "); + } + } + + /** + * Return all supported Payloads for this Manager + * + * @return The Payload List + */ + public List getPayloads() + { + return payloads; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/mediamgr/MediaUtils.java b/src/net/java/sip/communicator/impl/protocol/jabber/mediamgr/MediaUtils.java new file mode 100644 index 0000000..9b17aac --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/mediamgr/MediaUtils.java @@ -0,0 +1,168 @@ +/* + * SIP Communicator, 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.mediamgr; + +import java.util.*; +import javax.media.format.*; +import javax.sdp.*; +import net.java.sip.communicator.util.*; + +// +// TODO: merge this MediaUtils with the one in impl/media +// There is a MediaUtils class in package impl.media +// which isn't exposed by the media service. It will +// be a good idea to find a mean to merge this two +// class somewhere +// +/** + * Media utils + * A collection of static methods related to media. + */ +public abstract class MediaUtils +{ + + /** + * logger of this class + */ + private static final Logger logger = + Logger.getLogger(MediaUtils.class); + + /** Creates a new instance of MediaUtils */ + public MediaUtils() + { + } + + /** + * <tt>getAudioEncoding</tt> return an <tt>Hashtable</tt> + * with one entry in a format wich can be directly passed + * to the media service for creating a <tt>RtpFlow</tt> + * based on this encoding. + * + * @param payloadType the payload type + * @return an hashtable ready to be passed to media service + */ + public static Hashtable getAudioEncoding(int payloadType) + { + Hashtable ht = new Hashtable(); + List list = new ArrayList(); + ht.put("audio", list); + // most of the format aren't handled by SC now + // but itsn't a problem. it will facilitates + // updates when more codecs will become available + // in SC. + switch (payloadType) + { + case SdpConstants.PCMU: + list.add(AudioFormat.ULAW_RTP); + return ht; + case SdpConstants.GSM: + list.add(AudioFormat.GSM_RTP); + return ht; + case SdpConstants.G723: + list.add(AudioFormat.G723_RTP); + return ht; + case SdpConstants.DVI4_8000: + list.add(AudioFormat.DVI_RTP); + return ht; + case SdpConstants.DVI4_16000: + list.add(AudioFormat.DVI_RTP); + return ht; + //case SdpConstants.LPC: + // list.add(AudioFormat...); + // return ht; + case SdpConstants.PCMA: + list.add(AudioFormat.ALAW); + return ht; + //case SdpConstants.G722: + // list.add(AudioFormat...); + // return ht; + //case SdpConstants.L16_1CH: + // list.add(AudioFormat...); + // return ht; + //case SdpConstants.L16_2CH: + // list.add(AudioFormat...); + // return ht; + //case SdpConstants.QCELP: + // list.add(AudioFormat...); + // return ht; + //case SdpConstants.CN: + // list.add(AudioFormat...); + // return ht; + //case SdpConstants.MPA: + // list.add(AudioFormat...); + // return ht; + case SdpConstants.G728: + list.add(AudioFormat.G728_RTP); + return ht; + case SdpConstants.DVI4_11025: + list.add(AudioFormat.DVI_RTP); + return ht; + case SdpConstants.DVI4_22050: + list.add(AudioFormat.DVI_RTP); + return ht; + case SdpConstants.G729: + list.add(AudioFormat.G729_RTP); + return ht; + default: + //throw new IllegalStateException("Unknown payload type"); + logger.warn("unknown payload type : " + payloadType); + return null; + } + } + + /** + * This method gives the name wich correspond to a payload type + * + * @param payloadType the type of the payload + * @return the string corresponding to the payload + */ + public static String getPayloadName(int payloadType) + { + // for update, seee http://tools.ietf.org/html/rfc3551#page-32 + switch (payloadType) + { + case SdpConstants.PCMU: + return "PCMU"; + case SdpConstants.GSM: + return "GSM"; + case SdpConstants.G723: + return "G723"; + case SdpConstants.DVI4_8000: + return "DVI4_8000"; + case SdpConstants.DVI4_16000: + return "DVI4_16000"; + case SdpConstants.LPC: + return "LPC"; + case SdpConstants.PCMA: + return "PCMA"; + case SdpConstants.G722: + return "G722"; + case SdpConstants.L16_1CH: + return "L16_1CH"; + case SdpConstants.L16_2CH: + return "L16_2CH"; + case SdpConstants.QCELP: + return "QCELP"; + case SdpConstants.CN: + return "CN"; + case SdpConstants.MPA: + return "MPA"; + case SdpConstants.G728: + return "G728"; + case SdpConstants.DVI4_11025: + return "DVI4_11025"; + case SdpConstants.DVI4_22050: + return "DVI4_22050"; + case SdpConstants.G729: + return "G729"; + default: + //throw new IllegalStateException("Unknown payload type"); + logger.warn("unknown payload type : " + payloadType); + return null; + } + } +} diff --git a/src/net/java/sip/communicator/impl/systray/jdic/SystrayServiceJdicImpl.java b/src/net/java/sip/communicator/impl/systray/jdic/SystrayServiceJdicImpl.java index e24c20f..b656d22 100644 --- a/src/net/java/sip/communicator/impl/systray/jdic/SystrayServiceJdicImpl.java +++ b/src/net/java/sip/communicator/impl/systray/jdic/SystrayServiceJdicImpl.java @@ -118,7 +118,7 @@ public class SystrayServiceJdicImpl public void actionPerformed(ActionEvent e) { UIService uiService = SystrayActivator.getUIService(); - + boolean isVisible; isVisible = ! uiService.isVisible(); @@ -141,7 +141,7 @@ public class SystrayServiceJdicImpl public void actionPerformed(ActionEvent e) { UIService uiService = SystrayActivator.getUIService(); - + firePopupMessageEvent(e.getSource()); ExportedWindow chatWindow diff --git a/src/net/java/sip/communicator/service/media/MediaService.java b/src/net/java/sip/communicator/service/media/MediaService.java index 2183951..0be0478 100644 --- a/src/net/java/sip/communicator/service/media/MediaService.java +++ b/src/net/java/sip/communicator/service/media/MediaService.java @@ -6,6 +6,7 @@ */ package net.java.sip.communicator.service.media; +import java.util.*; import net.java.sip.communicator.service.media.event.*; import net.java.sip.communicator.service.protocol.*; import java.net.*; @@ -15,12 +16,13 @@ import java.net.*; * (J)FFMPEG, JMFPAPI, and others. It takes care of all media play and capture * as well as media transport (e.g. over RTP). * - * Before being able to use this service calles would have to make sure that + * Before being able to use this service calls would have to make sure that * it is initialized (i.e. consult the isInitialized() method). * * @author Emil Ivov * @author Martin Andre * @author Ryan Ricard + * @author Symphorien Wanko */ public interface MediaService { @@ -34,14 +36,14 @@ public interface MediaService /** * The name of the property that contains the minimum port number that we'd - * like our rtp managers to bind upon. + * like our RTP managers to bind upon. */ public static final String MIN_PORT_NUMBER_PROPERTY_NAME = "net.java.sip.communicator.service.media.MIN_PORT_NUMBER"; /** * The name of the property that contains the maximum port number that we'd - * like our rtp managers to bind upon. + * like our RTP managers to bind upon. */ public static final String MAX_PORT_NUMBER_PROPERTY_NAME = "net.java.sip.communicator.service.media.MAX_PORT_NUMBER"; @@ -54,13 +56,31 @@ public interface MediaService public static final int BIND_RETRIES_DEFAULT_VALUE = 50; /** + * Give an array of Strings containing audio formats in the order of + * preference. + * + * @return an array of Strings containing audio formats in the order of + * preference. + */ + public String[] getSupportedAudioEncodings(); + + /** + * Give an array of Strings containing video formats in the order of + * preference. + * + * @return an array of Strings containing video formats in the order of + * preference. + */ + public String[] getSupportedVideoEncodings(); + + /** * Creates a call session for <tt>call</tt>. The method allocates audio * and video ports which won't be released until the corresponding call * gets into a DISCONNECTED state. If a session already exists for call, * it is returned and no new session is created. Once created a session * follows the state changes of the call it encapsulates and automatically * adapts to them by starting or stopping transmission and/or reception of - * data. A CallSession would autodestroy when the <tt>Call</tt> it + * data. A CallSession would auto destroy when the <tt>Call</tt> it * encapsulates enters the CALL_ENDED <tt>CallState</tt>. * <p> * @@ -74,8 +94,29 @@ public interface MediaService throws MediaException; /** + * Create a RtpFlow which will manage media data transfer on the specified + * addresses and ports, using the specified codec. + * + * @param localIP local IP of this flow + * @param localPort local port of for this flow + * @param remoteIP remote IP of this flow + * @param remotePort remote port of for this flow + * @param mediaEncodings encoding used for media on this flow + * @return a <tt>RtpFlow</tt> with the corresponding parameters + * @throws MediaException throw a media exception if we fail to create the + * flow + */ + public RtpFlow createRtpFlow(String localIP, + int localPort, + String remoteIP, + int remotePort, + Map mediaEncodings) + throws MediaException; + + /** * Adds a listener that will be listening for incoming media and changes - * in the state of the media listener + * in the state of the media listener. + * * @param listener the listener to register */ public void addMediaListener(MediaListener listener); @@ -116,7 +157,7 @@ public interface MediaService * it on the speakers/screen * * @param call the call whose data destination will be changed - * @param dataSinkURL the URL of the new data Desintation + * @param dataSinkURL the URL of the new data sink. * * @throws MediaException if we fail to initialize the data sink */ @@ -136,7 +177,7 @@ public interface MediaService * the given call. If the data source is not time-based, i.e. a microphone, * the method returns -1. * - * @param call the call whose data source duration will be retreived + * @param call the call whose data source duration will be retrieved * @return the duration of the data currently available in the <tt>call</tt> * specific data source or -1 if we are using a microphone/webcam. **/ @@ -144,8 +185,8 @@ public interface MediaService /** - * Returns true if the media service implementation is initialized and ready - * for use by other services, and false otherwise. + * Returns true if the media service implementation is initialized and + * ready for use by other services, and false otherwise. * * @return true if the service implementation is initialized and ready for * use and false otherwise. diff --git a/src/net/java/sip/communicator/service/media/RtpFlow.java b/src/net/java/sip/communicator/service/media/RtpFlow.java index 050dc48..6d56b84 100644 --- a/src/net/java/sip/communicator/service/media/RtpFlow.java +++ b/src/net/java/sip/communicator/service/media/RtpFlow.java @@ -9,10 +9,10 @@ package net.java.sip.communicator.service.media; /** * RtpFlow Interface. * - * The role of a RtpFlow is simply to handle media data - * transfert between two end points + * The role of a RtpFlow is simply to handle media data transfer between two + * end points as well as playback and capture. * - * @author Symphorien Wanko-Tchuente + * @author Symphorien Wanko */ public interface RtpFlow { @@ -27,9 +27,40 @@ public interface RtpFlow public void stop(); /** - * Allow to pause or resume media data transmission + * Gives the local port used by this flow * - * @param active pause transmission if false. Otherwise, resume. + * @return the local port used by this flow */ - public void setTransmit(boolean active); + public int getLocalPort(); + + /** + * Gives the local address used by this flow + * + * @return the local address used by this flow + */ + public String getLocalAddress(); + + /** + * Gives the remote port used by this flow + * + * @return the remote port used by this flow + */ + public int getRemotePort(); + + /** + * Gives the remote address used by this flow + * + * @return the remote address used by this flow + */ + public String getRemoteAddress(); + + /** + * Pause transmission on this flow + */ + public void pause(); + + /** + * Resume transmission on this flow + */ + public void resume(); } |