aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/java/sip
diff options
context:
space:
mode:
authorSebastien Vincent <seb@jitsi.org>2011-05-20 15:26:33 +0000
committerSebastien Vincent <seb@jitsi.org>2011-05-20 15:26:33 +0000
commitc65785209f07c9dde145b3dfa305783821d043d6 (patch)
tree21f3658816fdf4bfdd4240f614889051b4f17a11 /src/net/java/sip
parent400f019e34c76aacfb0621cabe1959b0606e87b9 (diff)
downloadjitsi-c65785209f07c9dde145b3dfa305783821d043d6.zip
jitsi-c65785209f07c9dde145b3dfa305783821d043d6.tar.gz
jitsi-c65785209f07c9dde145b3dfa305783821d043d6.tar.bz2
Commits work in progress on Google Talk voice support.
Diffstat (limited to 'src/net/java/sip')
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/ActiveCallsRepositoryGTalkImpl.java123
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/ActiveCallsRepositoryJabberImpl.java8
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/CallGTalkImpl.java184
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/CallPeerGTalkImpl.java630
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerGTalkImpl.java685
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/CandidatesSender.java31
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java2
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/OperationSetAvatarJabberImpl.java14
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java29
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/OperationSetDesktopSharingServerJabberImpl.java2
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/TransportManagerGTalkImpl.java891
-rw-r--r--src/net/java/sip/communicator/service/protocol/media/AbstractOperationSetTelephonyConferencing.java2
-rw-r--r--src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java10
13 files changed, 2596 insertions, 15 deletions
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ActiveCallsRepositoryGTalkImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ActiveCallsRepositoryGTalkImpl.java
new file mode 100644
index 0000000..5c421b0
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/ActiveCallsRepositoryGTalkImpl.java
@@ -0,0 +1,123 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.impl.protocol.jabber;
+
+import java.util.*;
+
+import net.java.sip.communicator.service.protocol.*;
+
+/**
+ * Keeps a list of all calls currently active and maintained by this protocol
+ * provider. Offers methods for finding a call by its ID, peer session
+ * and others.
+ *
+ * @author Emil Ivov
+ * @author Symphorien Wanko
+ */
+public class ActiveCallsRepositoryGTalkImpl extends ActiveCallsRepository<
+ CallGTalkImpl,
+ OperationSetBasicTelephonyJabberImpl>
+{
+ /**
+ * 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 ActiveCallsRepositoryGTalkImpl(
+ OperationSetBasicTelephonyJabberImpl opSet)
+ {
+ super(opSet);
+ }
+
+ /**
+ * Returns the {@link CallGTalkImpl} containing a {@link
+ * CallPeerGTalkImpl} whose corresponding jingle session has the specified
+ * jingle <tt>sid</tt>.
+ *
+ * @param sid the jingle <tt>sid</tt> we're looking for.
+ *
+ * @return the {@link CallGTalkImpl} containing the peer with the
+ * specified <tt>sid</tt> or <tt>null</tt> if we couldn't find one matching
+ * it.
+ */
+ public CallGTalkImpl findSessionID(String sid)
+ {
+ Iterator<CallGTalkImpl> calls = getActiveCalls();
+
+ while (calls.hasNext())
+ {
+ CallGTalkImpl call = calls.next();
+ if (call.containsSessionID(sid))
+ return call;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the {@link CallPeerGTalkImpl} whose jingle session has the
+ * specified jingle <tt>sid</tt>.
+ *
+ * @param sid the jingle <tt>sid</tt> we're looking for.
+ *
+ * @return the {@link CallPeerGTalkImpl} with the specified <tt>sid</tt>
+ * or tt>null</tt> if we couldn't find one matching it.
+ */
+ public CallPeerGTalkImpl findCallPeer(String sid)
+ {
+ Iterator<CallGTalkImpl> calls = getActiveCalls();
+
+ while (calls.hasNext())
+ {
+ CallGTalkImpl call = calls.next();
+ CallPeerGTalkImpl peer = call.getPeer(sid);
+ if ( peer != null )
+ return peer;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the {@link CallPeerGTalkImpl} whose session-init's ID has
+ * the specified IQ <tt>id</tt>.
+ *
+ * @param id the IQ <tt>id</tt> we're looking for.
+ *
+ * @return the {@link CallPeerGTalkImpl} with the specified <tt>id</tt>
+ * or <tt>null</tt> if we couldn't find one matching it.
+ */
+ public CallPeerGTalkImpl findCallPeerBySessInitPacketID(String id)
+ {
+ Iterator<CallGTalkImpl> calls = getActiveCalls();
+
+ while (calls.hasNext())
+ {
+ CallGTalkImpl call = calls.next();
+ CallPeerGTalkImpl peer = call.getPeerBySessInitPacketID(id);
+ if ( peer != null )
+ return peer;
+ }
+
+ return null;
+ }
+
+ /**
+ * 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
+ * @see ActiveCallsRepository#fireCallEvent(int, Call)
+ */
+ protected void fireCallEvent(int eventID, Call sourceCall)
+ {
+ parentOperationSet.fireCallEvent(eventID, sourceCall);
+ }
+} \ No newline at end of file
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ActiveCallsRepositoryJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ActiveCallsRepositoryJabberImpl.java
index 9733d3e..099c213 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/ActiveCallsRepositoryJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/ActiveCallsRepositoryJabberImpl.java
@@ -9,7 +9,6 @@ package net.java.sip.communicator.impl.protocol.jabber;
import java.util.*;
import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.util.*;
/**
* Keeps a list of all calls currently active and maintained by this protocol
@@ -24,13 +23,6 @@ public class ActiveCallsRepositoryJabberImpl
OperationSetBasicTelephonyJabberImpl>
{
/**
- * The <tt>Logger</tt> used by the <tt>ActiveCallsRepositoryJabberImpl</tt>
- * class and its instances for logging output.
- */
- private static final Logger logger
- = Logger.getLogger(ActiveCallsRepositoryJabberImpl.class);
-
- /**
* It's where we store all active calls
*
* @param opSet the <tt>OperationSetBasicTelphony</tt> instance which has
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallGTalkImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallGTalkImpl.java
new file mode 100644
index 0000000..eb34d0a
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallGTalkImpl.java
@@ -0,0 +1,184 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.impl.protocol.jabber;
+
+import org.jivesoftware.smack.packet.*;
+
+import net.java.sip.communicator.impl.protocol.jabber.extensions.gtalk.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.service.protocol.media.*;
+
+/**
+ * A Google Talk implementation of the <tt>Call</tt> abstract class
+ * encapsulating Google Talk sessions.
+ *
+ * @author Sebastien Vincent
+ */
+public class CallGTalkImpl
+ extends MediaAwareCall<
+ CallPeerGTalkImpl,
+ OperationSetBasicTelephonyJabberImpl,
+ ProtocolProviderServiceJabberImpl>
+{
+ /**
+ * Initializes a new <tt>CallGTalkImpl</tt> instance belonging to
+ * <tt>sourceProvider</tt> and associated with the jingle session with the
+ * specified <tt>sessionID</tt>. If the new instance corresponds to an
+ * incoming Google Talk session, then the sessionID would come from there.
+ * Otherwise, one could generate one using {@link SessionIQ#generateSID()}.
+ *
+ * @param parentOpSet the {@link OperationSetBasicTelephonyJabberImpl}
+ * instance in the context of which this call has been created.
+ */
+ protected CallGTalkImpl(
+ OperationSetBasicTelephonyJabberImpl parentOpSet)
+ {
+ super(parentOpSet);
+
+ //let's add ourselves to the calls repo. we are doing it ourselves just
+ //to make sure that no one ever forgets.
+ parentOpSet.getGTalkActiveCallsRepository().addCall(this);
+ }
+
+ /**
+ * Determines if this call contains a peer whose corresponding session has
+ * the specified <tt>sid</tt>.
+ *
+ * @param sid the ID of the session whose peer we are looking for.
+ *
+ * @return <tt>true</tt> if this call contains a peer with the specified
+ * Google Talk <tt>sid</tt> and false otherwise.
+ */
+ public boolean containsSessionID(String sid)
+ {
+ return (getPeer(sid) != null);
+ }
+
+ /**
+ * Returns the peer whose corresponding session has the specified
+ * <tt>sid</tt>.
+ *
+ * @param sid the ID of the session whose peer we are looking for.
+ *
+ * @return the {@link CallPeerGTalkImpl} with the specified Google Talk
+ * <tt>sid</tt> and <tt>null</tt> if no such peer exists in this call.
+ */
+ public CallPeerGTalkImpl getPeer(String sid)
+ {
+ for(CallPeerGTalkImpl peer : getCallPeersVector())
+ {
+ if (peer.getSessionID().equals(sid))
+ return peer;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the peer whose corresponding session-init ID has the specified
+ * <tt>id</tt>.
+ *
+ * @param id the ID of the session-init IQ whose peer we are looking for.
+ *
+ * @return the {@link CallPeerGTalkImpl} with the specified IQ
+ * <tt>id</tt> and <tt>null</tt> if no such peer exists in this call.
+ */
+ public CallPeerGTalkImpl getPeerBySessInitPacketID(String id)
+ {
+ for(CallPeerGTalkImpl peer : getCallPeersVector())
+ {
+ if (peer.getSessInitID().equals(id))
+ return peer;
+ }
+ return null;
+ }
+
+ /**
+ * Creates a new Google Talk call peer and sends a RINGING response.
+ *
+ * @param sessionIQ the {@link SessionIQ} that created the session.
+ *
+ * @return the newly created {@link CallPeerGTalkImpl} (the one that sent
+ * the INVITE).
+ */
+ public CallPeerGTalkImpl processGTalkInitiate(SessionIQ sessionIQ)
+ {
+ String remoteParty = sessionIQ.getInitiator();
+
+ //according to the Jingle spec initiator may be null.
+ if (remoteParty == null)
+ remoteParty = sessionIQ.getFrom();
+
+ CallPeerGTalkImpl callPeer = new CallPeerGTalkImpl(remoteParty, this);
+
+ addCallPeer(callPeer);
+
+ //before notifying about this call, make sure that it looks alright
+ callPeer.processSessionInitiate(sessionIQ);
+
+ if( callPeer.getState() == CallPeerState.FAILED)
+ return null;
+
+ callPeer.setState( CallPeerState.INCOMING_CALL );
+
+ // if this was the first peer we added in this call then the call is
+ // new and we also need to notify everyone of its creation.
+ if(this.getCallPeerCount() == 1)
+ parentOpSet.fireCallEvent( CallEvent.CALL_RECEIVED, this);
+
+ return callPeer;
+ }
+
+ /**
+ * Creates a <tt>CallPeerGTalkImpl</tt> from <tt>calleeJID</tt> and sends
+ * them <tt>initiate</tt> IQ request.
+ *
+ * @param calleeJID the party that we would like to invite to this call.
+ * @param sessionInitiateExtensions a collection of additional and optional
+ * <tt>PacketExtension</tt>s to be added to the <tt>initiate</tt>
+ * {@link SessionIQ} which is to init this <tt>CallJabberImpl</tt>
+ *
+ * @return the newly created <tt>Call</tt> corresponding to
+ * <tt>calleeJID</tt>. All following state change events will be
+ * delivered through this call peer.
+ *
+ * @throws OperationFailedException with the corresponding code if we fail
+ * to create the call.
+ */
+ public CallPeerGTalkImpl initiateGTalkSession(
+ String calleeJID,
+ Iterable<PacketExtension> sessionInitiateExtensions)
+ throws OperationFailedException
+ {
+ // create the session-initiate IQ
+ CallPeerGTalkImpl callPeer = new CallPeerGTalkImpl(calleeJID, this);
+
+ addCallPeer(callPeer);
+
+ callPeer.setState(CallPeerState.INITIATING_CALL);
+
+ // if this was the first peer we added in this call then the call is
+ // new and we also need to notify everyone of its creation.
+ if(getCallPeerCount() == 1)
+ parentOpSet.fireCallEvent(CallEvent.CALL_INITIATED, this);
+
+ CallPeerMediaHandlerGTalkImpl mediaHandler
+ = callPeer.getMediaHandler();
+
+ /* enable video if it is a videocall */
+ mediaHandler.setLocalVideoTransmissionEnabled(localVideoAllowed);
+
+ //set call state to connecting so that the user interface would start
+ //playing the tones. we do that here because we may be harvesting
+ //STUN/TURN addresses in initiateSession() which would take a while.
+ callPeer.setState(CallPeerState.CONNECTING);
+
+ callPeer.initiateSession(sessionInitiateExtensions);
+
+ return callPeer;
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerGTalkImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerGTalkImpl.java
new file mode 100644
index 0000000..41083b3
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerGTalkImpl.java
@@ -0,0 +1,630 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.impl.protocol.jabber;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.jivesoftware.smack.packet.*;
+
+import net.java.sip.communicator.impl.protocol.jabber.extensions.gtalk.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.service.protocol.media.*;
+import net.java.sip.communicator.util.*;
+
+/**
+ * Implements a Google Talk <tt>CallPeer</tt>.
+ *
+ * @author Sebastien Vincent
+ */
+public class CallPeerGTalkImpl
+ extends MediaAwareCallPeer<CallGTalkImpl,
+ CallPeerMediaHandlerGTalkImpl,
+ ProtocolProviderServiceJabberImpl>
+{
+ /**
+ * The <tt>Logger</tt> used by the <tt>CallPeerGTalkImpl</tt> class and its
+ * instances for logging output.
+ */
+ private static final Logger logger
+ = Logger.getLogger(CallPeerGTalkImpl.class);
+
+ /**
+ * The {@link SessionIQ} that created the session that this call represents.
+ */
+ private SessionIQ sessionInitIQ = null;
+
+ /**
+ * The jabber address of this peer
+ */
+ private String peerJID = null;
+
+ /**
+ * Indicates whether this peer was the one that initiated the session.
+ */
+ protected boolean isInitiator = false;
+
+ /**
+ * Session ID.
+ */
+ private String sid = null;
+
+ /**
+ * Creates a new call peer with address <tt>peerAddress</tt>.
+ *
+ * @param peerAddress the Google Talk address of the new call peer.
+ * @param owningCall the call that contains this call peer.
+ */
+ public CallPeerGTalkImpl(String peerAddress, CallGTalkImpl owningCall)
+ {
+ super(owningCall);
+ this.peerJID = peerAddress;
+ setMediaHandler( new CallPeerMediaHandlerGTalkImpl(this) );
+ }
+
+ /**
+ * Returns a String locator for that peer.
+ *
+ * @return the peer's address or phone number.
+ */
+ public String getAddress()
+ {
+ return peerJID;
+ }
+
+ /**
+ * Specifies the address, phone number, or other protocol specific
+ * identifier that represents this call peer. This method is to be
+ * used by service users and MUST NOT be called by the implementation.
+ *
+ * @param address The address of this call peer.
+ */
+ public void setAddress(String address)
+ {
+ String oldAddress = getAddress();
+
+ if(peerJID.equals(address))
+ return;
+
+ this.peerJID = address;
+ //Fire the Event
+ fireCallPeerChangeEvent(
+ CallPeerChangeEvent.CALL_PEER_ADDRESS_CHANGE,
+ oldAddress,
+ address);
+ }
+
+ /**
+ * Returns a human readable name representing this peer.
+ *
+ * @return a String containing a name for that peer.
+ */
+ public String getDisplayName()
+ {
+ if (getCall() != null)
+ {
+ Contact contact = getContact();
+
+ if (contact != null)
+ return contact.getDisplayName();
+ }
+ return peerJID;
+ }
+
+ /**
+ * Determines whether this peer was the one that initiated the session. Note
+ * that if this peer is the initiator of the session then this means we are
+ * the responder!
+ *
+ * @return <tt>true</tt> if this peer is the one that initiated the session
+ * and <tt>false</tt> otherwise (i.e. if _we_ initiated the session).
+ */
+ public boolean isInitiator()
+ {
+ return isInitiator;
+ }
+
+ /**
+ * Returns the contact corresponding to this peer or null if no
+ * particular contact has been associated.
+ * <p>
+ * @return the <tt>Contact</tt> corresponding to this peer or null
+ * if no particular contact has been associated.
+ */
+ public Contact getContact()
+ {
+ ProtocolProviderService pps = getCall().getProtocolProvider();
+ OperationSetPresence opSetPresence
+ = pps.getOperationSet(OperationSetPresence.class);
+
+ return opSetPresence.findContactByID(getAddress());
+ }
+
+ /**
+ * Processes the session initiation {@link SessionIQ} that we were created
+ * with, passing its content to the media handler and then sends either a
+ * "session-info/ringing" or a "terminate" response.
+ *
+ * @param sessionInitIQ The {@link SessionIQ} that created the session that
+ * we are handling here.
+ */
+ protected synchronized void processSessionInitiate(SessionIQ sessionInitIQ)
+ {
+ // Do initiate the session.
+ this.sessionInitIQ = sessionInitIQ;
+ this.isInitiator = true;
+
+ RtpDescriptionPacketExtension description = null;
+
+ for(PacketExtension ext : sessionInitIQ.getExtensions())
+ {
+ if(ext.getElementName().equals(
+ RtpDescriptionPacketExtension.ELEMENT_NAME))
+ {
+ description = (RtpDescriptionPacketExtension)ext;
+ break;
+ }
+ }
+
+ if(description == null)
+ {
+ logger.info("No description in incoming session initiate");
+
+ //send an error response;
+ String reasonText = "Error: no description";
+ SessionIQ errResp
+ = GTalkPacketFactory.createSessionTerminate(
+ sessionInitIQ.getTo(),
+ sessionInitIQ.getFrom(),
+ sessionInitIQ.getID(),
+ Reason.INCOMPATIBLE_PARAMETERS,
+ reasonText);
+
+ setState(CallPeerState.FAILED, reasonText);
+ getProtocolProvider().getConnection().sendPacket(errResp);
+ return;
+ }
+
+ try
+ {
+ getMediaHandler().processOffer(description);
+ }
+ catch(Exception ex)
+ {
+ logger.info("Failed to process an incoming session initiate", ex);
+
+ //send an error response;
+ String reasonText = "Error: " + ex.getMessage();
+ SessionIQ errResp
+ = GTalkPacketFactory.createSessionTerminate(
+ sessionInitIQ.getTo(),
+ sessionInitIQ.getFrom(),
+ sessionInitIQ.getID(),
+ Reason.INCOMPATIBLE_PARAMETERS,
+ reasonText);
+
+ setState(CallPeerState.FAILED, reasonText);
+ getProtocolProvider().getConnection().sendPacket(errResp);
+ return;
+ }
+ }
+
+ /**
+ * Initiate a Google Talk session {@link SessionIQ}.
+ *
+ * @param sessionInitiateExtensions a collection of additional and optional
+ * <tt>PacketExtension</tt>s to be added to the <tt>initiate</tt>
+ * {@link SessionIQ} which is to initiate the session with this
+ * <tt>CallPeerGTalkImpl</tt>
+ * @throws OperationFailedException exception
+ */
+ protected synchronized void initiateSession(
+ Iterable<PacketExtension> sessionInitiateExtensions)
+ throws OperationFailedException
+ {
+ sid = SessionIQ.generateSID();
+ isInitiator = false;
+
+ //Create the media description that we'd like to send to the other side.
+ RtpDescriptionPacketExtension offer
+ = getMediaHandler().createDescription();
+
+ ProtocolProviderServiceJabberImpl protocolProvider
+ = getProtocolProvider();
+
+ sessionInitIQ
+ = GTalkPacketFactory.createSessionInitiate(
+ protocolProvider.getOurJID(),
+ this.peerJID,
+ sid,
+ offer);
+
+ if (sessionInitiateExtensions != null)
+ {
+ for (PacketExtension sessionInitiateExtension
+ : sessionInitiateExtensions)
+ {
+ sessionInitIQ.addExtension(sessionInitiateExtension);
+ }
+ }
+
+ protocolProvider.getConnection().sendPacket(sessionInitIQ);
+
+ getMediaHandler().harvestCandidates(offer.getPayloadTypes(),
+ new CandidatesSender()
+ {
+ public void sendCandidates(
+ Iterable<GTalkCandidatePacketExtension> candidates)
+ {
+ CallPeerGTalkImpl.this.sendCandidates(candidates);
+ }
+ });
+ }
+
+ /**
+ * Puts this peer into a {@link CallPeerState#DISCONNECTED}, indicating a
+ * reason to the user, if there is one.
+ *
+ * @param sessionIQ the {@link SessionIQ} that's terminating our session.
+ */
+ public void processSessionReject(SessionIQ sessionIQ)
+ {
+ processSessionTerminate(sessionIQ);
+ }
+
+ /**
+ * Puts this peer into a {@link CallPeerState#DISCONNECTED}, indicating a
+ * reason to the user, if there is one.
+ *
+ * @param sessionIQ the {@link SessionIQ} that's terminating our session.
+ */
+ public void processSessionTerminate(SessionIQ sessionIQ)
+ {
+ String reasonStr = "Call ended by remote side.";
+ ReasonPacketExtension reasonExt = sessionIQ.getReason();
+
+ if(reasonStr != null && reasonExt != null)
+ {
+ Reason reason = reasonExt.getReason();
+
+ if(reason != null)
+ reasonStr += " Reason: " + reason.toString() + ".";
+
+ String text = reasonExt.getText();
+
+ if(text != null)
+ reasonStr += " " + text;
+ }
+
+ getMediaHandler().getTransportManager().close();
+ setState(CallPeerState.DISCONNECTED, reasonStr);
+ }
+
+ /**
+ * Processes the session initiation {@link SessionIQ} that we were created
+ * with, passing its content to the media handler.
+ *
+ * @param sessionInitIQ The {@link SessionIQ} that created the session that
+ * we are handling here.
+ */
+ public void processSessionAccept(SessionIQ sessionInitIQ)
+ {
+ this.sessionInitIQ = sessionInitIQ;
+
+ CallPeerMediaHandlerGTalkImpl mediaHandler = getMediaHandler();
+ Collection<PacketExtension> extensions =
+ sessionInitIQ.getExtensions();
+ RtpDescriptionPacketExtension answer = null;
+
+ for(PacketExtension ext : extensions)
+ {
+ if(ext.getElementName().equalsIgnoreCase(
+ RtpDescriptionPacketExtension.ELEMENT_NAME))
+ {
+ answer = (RtpDescriptionPacketExtension)ext;
+ break;
+ }
+ }
+
+ try
+ {
+ mediaHandler.getTransportManager().
+ wrapupConnectivityEstablishment();
+ mediaHandler.processAnswer(answer);
+ }
+ catch(Exception exc)
+ {
+ if (logger.isInfoEnabled())
+ logger.info("Failed to process a session-accept", exc);
+
+ //send an error response
+ String reasonText = "Error: " + exc.getMessage();
+ SessionIQ errResp
+ = GTalkPacketFactory.createSessionTerminate(
+ sessionInitIQ.getTo(),
+ sessionInitIQ.getFrom(),
+ sessionInitIQ.getID(),
+ Reason.GENERAL_ERROR,
+ reasonText);
+
+ getMediaHandler().getTransportManager().close();
+ setState(CallPeerState.FAILED, reasonText);
+ getProtocolProvider().getConnection().sendPacket(errResp);
+ return;
+ }
+
+ //tell everyone we are connecting so that the audio notifications would
+ //stop
+ setState(CallPeerState.CONNECTED);
+
+ mediaHandler.start();
+ }
+
+ /**
+ * Process candidates received.
+ *
+ * @param sessionInitIQ The {@link SessionIQ} that created the session we
+ * are handling here
+ */
+ public void processCandidates(SessionIQ sessionInitIQ)
+ {
+ Collection<PacketExtension> extensions = sessionInitIQ.getExtensions();
+ List<GTalkCandidatePacketExtension> candidates =
+ new ArrayList<GTalkCandidatePacketExtension>();
+
+ for(PacketExtension ext : extensions)
+ {
+ if(ext.getElementName().equalsIgnoreCase(
+ GTalkCandidatePacketExtension.ELEMENT_NAME))
+ {
+ GTalkCandidatePacketExtension cand =
+ (GTalkCandidatePacketExtension)ext;
+ candidates.add(cand);
+ }
+ }
+
+ try
+ {
+ getMediaHandler().processCandidates(candidates);
+ }
+ catch (OperationFailedException ofe)
+ {
+ logger.warn("Failed to process an incoming candidates", ofe);
+
+ //send an error response
+ String reasonText = "Error: " + ofe.getMessage();
+ SessionIQ errResp
+ = GTalkPacketFactory.createSessionTerminate(
+ sessionInitIQ.getTo(),
+ sessionInitIQ.getFrom(),
+ sessionInitIQ.getID(),
+ Reason.GENERAL_ERROR,
+ reasonText);
+
+ getMediaHandler().getTransportManager().close();
+ setState(CallPeerState.FAILED, reasonText);
+ getProtocolProvider().getConnection().sendPacket(errResp);
+ return;
+ }
+ }
+
+ /**
+ * Returns the session ID of the Jingle session associated with this call.
+ *
+ * @return the session ID of the Jingle session associated with this call.
+ */
+ public String getSessionID()
+ {
+ return sessionInitIQ != null ? sessionInitIQ.getID() : sid;
+ }
+
+ /**
+ * Returns the IQ ID of the Jingle session-initiate packet associated with
+ * this call.
+ *
+ * @return the IQ ID of the Jingle session-initiate packet associated with
+ * this call.
+ */
+ public String getSessInitID()
+ {
+ return sessionInitIQ != null ? sessionInitIQ.getPacketID() : null;
+ }
+
+ /**
+ * Ends the call with for this <tt>CallPeer</tt>. Depending on the state
+ * of the peer the method would send a CANCEL, BYE, or BUSY_HERE message
+ * and set the new state to DISCONNECTED.
+ *
+ * @param reasonText the text, if any, to be set on the
+ * <tt>ReasonPacketExtension</tt> as the value of its
+ * @param reasonOtherExtension the <tt>PacketExtension</tt>, if any, to be
+ * set on the <tt>ReasonPacketExtension</tt> as the value of its
+ * <tt>otherExtension</tt> property
+ */
+ public void hangup(String reasonText, PacketExtension reasonOtherExtension)
+ {
+ // do nothing if the call is already ended
+ if (CallPeerState.DISCONNECTED.equals(getState())
+ || CallPeerState.FAILED.equals(getState()))
+ {
+ if (logger.isDebugEnabled())
+ logger.debug("Ignoring a request to hangup a call peer "
+ + "that is already DISCONNECTED");
+ return;
+ }
+
+ CallPeerState prevPeerState = getState();
+ getMediaHandler().getTransportManager().close();
+ setState(CallPeerState.DISCONNECTED);
+ SessionIQ responseIQ = null;
+
+ if (prevPeerState.equals(CallPeerState.CONNECTED)
+ || CallPeerState.isOnHold(prevPeerState))
+ {
+ responseIQ = GTalkPacketFactory.createBye(
+ getProtocolProvider().getOurJID(), peerJID, getSessionID());
+ }
+ else if (CallPeerState.CONNECTING.equals(prevPeerState)
+ || CallPeerState.CONNECTING_WITH_EARLY_MEDIA.equals(prevPeerState)
+ || CallPeerState.ALERTING_REMOTE_SIDE.equals(prevPeerState))
+ {
+ responseIQ = GTalkPacketFactory.createCancel(
+ getProtocolProvider().getOurJID(), peerJID, getSessionID());
+ }
+ else if (prevPeerState.equals(CallPeerState.INCOMING_CALL))
+ {
+ responseIQ = GTalkPacketFactory.createBusy(
+ getProtocolProvider().getOurJID(), peerJID, getSessionID());
+ }
+ else if (prevPeerState.equals(CallPeerState.BUSY)
+ || prevPeerState.equals(CallPeerState.FAILED))
+ {
+ // For FAILED and BUSY we only need to update CALL_STATUS
+ // as everything else has been done already.
+ }
+ else
+ {
+ logger.info("Could not determine call peer state!");
+ }
+
+ if (responseIQ != null)
+ {
+ if (reasonOtherExtension != null)
+ {
+ ReasonPacketExtension reason
+ = (ReasonPacketExtension)
+ responseIQ.getExtension(
+ ReasonPacketExtension.ELEMENT_NAME,
+ ReasonPacketExtension.NAMESPACE);
+
+ if (reason != null)
+ reason.setOtherExtension(reasonOtherExtension);
+ }
+
+ getProtocolProvider().getConnection().sendPacket(responseIQ);
+ }
+ }
+
+ /**
+ * Indicates a user request to answer an incoming call from this
+ * <tt>CallPeer</tt>.
+ *
+ * Sends an OK response to <tt>callPeer</tt>. Make sure that the call
+ * peer contains an SDP description when you call this method.
+ *
+ * @throws OperationFailedException if we fail to create or send the
+ * response.
+ */
+ public synchronized void answer()
+ throws OperationFailedException
+ {
+ System.out.println("answer");
+ RtpDescriptionPacketExtension answer = null;
+
+ try
+ {
+ getMediaHandler().getTransportManager().
+ wrapupConnectivityEstablishment();
+ answer = getMediaHandler().generateSessionAccept();
+ }
+ catch(Exception exc)
+ {
+ logger.info("Failed to answer an incoming call", exc);
+
+ //send an error response
+ String reasonText = "Error: " + exc.getMessage();
+ SessionIQ errResp
+ = GTalkPacketFactory.createSessionTerminate(
+ sessionInitIQ.getTo(),
+ sessionInitIQ.getFrom(),
+ sessionInitIQ.getID(),
+ Reason.FAILED_APPLICATION,
+ reasonText);
+
+ setState(CallPeerState.FAILED, reasonText);
+ getProtocolProvider().getConnection().sendPacket(errResp);
+ return;
+ }
+
+ SessionIQ response
+ = GTalkPacketFactory.createSessionAccept(
+ sessionInitIQ.getTo(),
+ sessionInitIQ.getFrom(),
+ getSessionID(),
+ answer);
+
+ //send the packet first and start the stream later in case the media
+ //relay needs to see it before letting hole punching techniques through.
+ getProtocolProvider().getConnection().sendPacket(response);
+
+ try
+ {
+ getMediaHandler().start();
+ }
+ catch(UndeclaredThrowableException e)
+ {
+ Throwable exc = e.getUndeclaredThrowable();
+
+ logger.info("Failed to establish a connection", exc);
+
+ //send an error response
+ String reasonText = "Error: " + exc.getMessage();
+ SessionIQ errResp
+ = GTalkPacketFactory.createSessionTerminate(
+ sessionInitIQ.getTo(),
+ sessionInitIQ.getFrom(),
+ sessionInitIQ.getID(),
+ Reason.GENERAL_ERROR,
+ reasonText);
+
+ getMediaHandler().getTransportManager().close();
+ setState(CallPeerState.FAILED, reasonText);
+ getProtocolProvider().getConnection().sendPacket(errResp);
+ return;
+ }
+
+ //tell everyone we are connecting so that the audio notifications would
+ //stop
+ setState(CallPeerState.CONNECTED);
+ }
+
+ /**
+ * Sends local candidate addresses from the local peer to the remote peer
+ * using the <tt>candidates</tt> {@link SessionIQ}.
+ *
+ * @param candidates the local candidate addresses to be sent from the local
+ * peer to the remote peer using the <tt>candidates</tt>
+ * {@link SessionIQ}
+ */
+ protected void sendCandidates(
+ Iterable<GTalkCandidatePacketExtension> candidates)
+ {
+
+ ProtocolProviderServiceJabberImpl protocolProvider
+ = getProtocolProvider();
+
+ SessionIQ candidatesIQ = new SessionIQ();
+
+ candidatesIQ.setGTalkType(GTalkType.CANDIDATES);
+ candidatesIQ.setFrom(protocolProvider.getOurJID());
+ candidatesIQ.setInitiator(isInitiator() ? getAddress() :
+ protocolProvider.getOurJID());
+ candidatesIQ.setID(getSessionID());
+ candidatesIQ.setTo(getAddress());
+ candidatesIQ.setType(IQ.Type.SET);
+
+ for (GTalkCandidatePacketExtension candidate : candidates)
+ {
+ candidatesIQ.addExtension(candidate);
+ }
+ System.out.println("IQ: " + candidatesIQ.toXML());
+
+ protocolProvider.getConnection().sendPacket(candidatesIQ);
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerGTalkImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerGTalkImpl.java
new file mode 100644
index 0000000..46885db
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerGTalkImpl.java
@@ -0,0 +1,685 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.impl.protocol.jabber;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import net.java.sip.communicator.impl.protocol.jabber.extensions.gtalk.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
+import net.java.sip.communicator.impl.protocol.jabber.jinglesdp.*;
+import net.java.sip.communicator.service.neomedia.*;
+import net.java.sip.communicator.service.neomedia.device.*;
+import net.java.sip.communicator.service.neomedia.format.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.media.*;
+import net.java.sip.communicator.util.*;
+
+/**
+ * An Google Talk specific extension of the generic media handler.
+ *
+ * @author Sebastien Vincent
+ */
+public class CallPeerMediaHandlerGTalkImpl
+ extends CallPeerMediaHandler<CallPeerGTalkImpl>
+{
+ /**
+ * The <tt>Logger</tt> used by the <tt>CallPeerMediaHandlerGTalkImpl</tt>
+ * class and its instances for logging output.
+ */
+ private static final Logger logger
+ = Logger.getLogger(CallPeerMediaHandlerGTalkImpl.class);
+
+ /**
+ * Google Talk name for the audio part.
+ */
+ public static final String AUDIO_RTP = "rtp";
+
+ /**
+ * Google Talk name for the video part.
+ */
+ public static final String VIDEO_RTP = "video_rtp";
+
+ /**
+ * The current description of the streams that we have going toward the
+ * remote side. We use {@link LinkedHashMap}s to make sure that we preserve
+ * the order of the individual content extensions.
+ */
+ private Map<String, List<PayloadTypePacketExtension>> localContentMap
+ = new LinkedHashMap<String, List<PayloadTypePacketExtension> >();
+
+ /**
+ * The current description of the streams that the remote side has with us.
+ * We use {@link LinkedHashMap}s to make sure that we preserve
+ * the order of the individual content extensions.
+ */
+ private Map<String, List<PayloadTypePacketExtension> > remoteContentMap
+ = new LinkedHashMap<String, List<PayloadTypePacketExtension> >();
+
+ /**
+ * The <tt>TransportManager</tt> implementation handling our address
+ * management.
+ */
+ private TransportManagerGTalkImpl transportManager;
+
+ /**
+ * Creates a new handler that will be managing media streams for
+ * <tt>peer</tt>.
+ *
+ * @param peer that <tt>CallPeerGTalkImpl</tt> instance that we will be
+ * managing media for.
+ */
+ public CallPeerMediaHandlerGTalkImpl(CallPeerGTalkImpl peer)
+ {
+ super(peer, peer);
+ }
+
+ /**
+ * Lets the underlying implementation take note of this error and only
+ * then throws it to the using bundles.
+ *
+ * @param message the message to be logged and then wrapped in a new
+ * <tt>OperationFailedException</tt>
+ * @param errorCode the error code to be assigned to the new
+ * <tt>OperationFailedException</tt>
+ * @param cause the <tt>Throwable</tt> that has caused the necessity to log
+ * an error and have a new <tt>OperationFailedException</tt> thrown
+ *
+ * @throws OperationFailedException the exception that we wanted this method
+ * to throw.
+ */
+ protected void throwOperationFailedException(
+ String message,
+ int errorCode,
+ Throwable cause)
+ throws OperationFailedException
+ {
+ ProtocolProviderServiceJabberImpl.throwOperationFailedException(
+ message,
+ errorCode,
+ cause,
+ logger);
+ }
+
+ /**
+ * Parses and handles the specified <tt>offer</tt> and returns a content
+ * extension representing the current state of this media handler. This
+ * method MUST only be called when <tt>offer</tt> is the first session
+ * description that this <tt>MediaHandler</tt> is seeing.
+ *
+ * @param offer the offer that we'd like to parse, handle and get an answer
+ * for.
+ *
+ * @throws OperationFailedException if we have a problem satisfying the
+ * description received in <tt>offer</tt> (e.g. failed to open a device or
+ * initialize a stream ...).
+ * @throws IllegalArgumentException if there's a problem with
+ * <tt>offer</tt>'s format or semantics.
+ */
+ public void processOffer(RtpDescriptionPacketExtension offer)
+ throws OperationFailedException,
+ IllegalArgumentException
+ {
+ List<PayloadTypePacketExtension> payloadTypes = offer.getPayloadTypes();
+ boolean atLeastOneValidDescription = false;
+ List<PayloadTypePacketExtension> answer =
+ new ArrayList<PayloadTypePacketExtension>();
+ List<MediaFormat> remoteFormats = JingleUtils.extractFormats(
+ offer,
+ getDynamicPayloadTypes());
+ boolean isAudio = false;
+ boolean isVideo = false;
+
+ for(PayloadTypePacketExtension ext : payloadTypes)
+ {
+ if(ext.getNamespace().equals(
+ SessionIQProvider.GTALK_AUDIO_NAMESPACE))
+ {
+ isAudio = true;
+ }
+ else if(ext.getNamespace().equals(
+ SessionIQProvider.GTALK_VIDEO_NAMESPACE))
+ {
+ isVideo = true;
+ }
+ }
+
+ for(MediaType mediaType : MediaType.values())
+ {
+ if(!(isAudio && mediaType == MediaType.AUDIO) &&
+ !(isVideo && mediaType == MediaType.VIDEO))
+ {
+ continue;
+ }
+
+ remoteContentMap.put(mediaType.toString(), payloadTypes);
+
+ MediaDevice dev = getDefaultDevice(mediaType);
+
+ MediaDirection devDirection
+ = (dev == null) ? MediaDirection.INACTIVE : dev.getDirection();
+
+ // Take the preference of the user with respect to streaming
+ // mediaType into account.
+ devDirection
+ = devDirection.and(getDirectionUserPreference(mediaType));
+
+ // intersect the MediaFormats of our device with remote ones
+ List<MediaFormat> mutuallySupportedFormats
+ = intersectFormats(remoteFormats, dev.getSupportedFormats());
+
+ List<PayloadTypePacketExtension> contents
+ = createPayloadTypesForOffer(
+ getNameForMediaType(mediaType),
+ mutuallySupportedFormats);
+ answer.addAll(contents);
+
+ localContentMap.put(mediaType.toString(), answer);
+
+ atLeastOneValidDescription = true;
+ }
+
+
+ if (!atLeastOneValidDescription)
+ {
+ ProtocolProviderServiceJabberImpl.throwOperationFailedException(
+ "Offer contained no media formats"
+ + " or no valid media descriptions.",
+ OperationFailedException.ILLEGAL_ARGUMENT,
+ null,
+ logger);
+ }
+
+ /*
+ * In order to minimize post-pickup delay, start establishing the
+ * connectivity prior to ringing.
+ */
+ harvestCandidates(
+ answer,
+ new CandidatesSender()
+ {
+ public void sendCandidates(
+ Iterable<GTalkCandidatePacketExtension> candidates)
+ {
+ getPeer().sendCandidates(candidates);
+ }
+ });
+ }
+
+ /**
+ * Wraps up any ongoing candidate harvests and returns our response to the
+ * last offer we've received, so that the peer could use it to send a
+ * <tt>accept</tt>.
+ *
+ * @return the last generated list of
+ * {@link RtpDescriptionPacketExtension}s that the call peer could use to
+ * send a <tt>accept</tt>.
+ *
+ * @throws OperationFailedException if we fail to configure the media stream
+ */
+ public RtpDescriptionPacketExtension generateSessionAccept()
+ throws OperationFailedException
+ {
+ RtpDescriptionPacketExtension description =
+ new RtpDescriptionPacketExtension();
+ List<PayloadTypePacketExtension> lst = localContentMap.get("audio");
+
+ description.setNamespace(SessionIQProvider.GTALK_AUDIO_NAMESPACE);
+
+ for(MediaType mediaType : MediaType.values())
+ {
+ MediaFormat format = null;
+ String ns = getNamespaceForMediaType(mediaType);
+ String mediaName = getNameForMediaType(mediaType);
+
+ for(PayloadTypePacketExtension ext : lst)
+ {
+ if(ext.getNamespace().equals(ns))
+ {
+ format = JingleUtils.payloadTypeToMediaFormat(
+ ext,
+ getDynamicPayloadTypes());
+ description.addPayloadType(ext);
+
+ if(format == null)
+ {
+ continue;
+ }
+
+ break;
+ }
+ }
+
+ if(format == null)
+ {
+ continue;
+ }
+
+ System.out.println("session format " + format);
+
+ // stream connector
+ StreamConnector connector
+ = transportManager.getStreamConnector(mediaType);
+
+ //the device this stream would be reading from and writing to.
+ MediaDevice dev = getDefaultDevice(mediaType);
+
+ // stream target
+ MediaStreamTarget target = transportManager.getStreamTarget(
+ mediaType);
+
+ List<RTPExtension> rtpExtensions =
+ new ArrayList<RTPExtension>();
+ MediaDirection direction = MediaDirection.SENDRECV;
+
+ initStream(mediaName, connector, dev, format, target,
+ direction, rtpExtensions);
+
+ if(mediaType == MediaType.VIDEO)
+ {
+ description.setNamespace(
+ SessionIQProvider.GTALK_VIDEO_NAMESPACE);
+ }
+ }
+
+ return description;
+ }
+
+ /**
+ * Handles the specified <tt>answer</tt> by creating and initializing the
+ * corresponding <tt>MediaStream</tt>s.
+ *
+ * @param answer the Google Talk answer
+ *
+ * @throws OperationFailedException if we fail to handle <tt>answer</tt> for
+ * reasons like failing to initialize media devices or streams.
+ * @throws IllegalArgumentException if there's a problem with the syntax or
+ * the semantics of <tt>answer</tt>. Method is synchronized in order to
+ * avoid closing mediaHandler when we are currently in process of
+ * initializing, configuring and starting streams and anybody interested
+ * in this operation can synchronize to the mediaHandler instance to wait
+ * processing to stop (method setState in CallPeer).
+ */
+ public void processAnswer(RtpDescriptionPacketExtension answer)
+ throws OperationFailedException,
+ IllegalArgumentException
+ {
+ List<PayloadTypePacketExtension> lst = answer.getPayloadTypes();
+
+ for(MediaType mediaType : MediaType.values())
+ {
+ String ns = getNamespaceForMediaType(mediaType);
+ String mediaName = getNameForMediaType(mediaType);
+ MediaFormat format = null;
+
+ for(PayloadTypePacketExtension ext : lst)
+ {
+ if(ext.getNamespace().equals(ns))
+ {
+ format = JingleUtils.payloadTypeToMediaFormat(
+ ext,
+ getDynamicPayloadTypes());
+
+ if(format == null)
+ {
+ continue;
+ }
+ break;
+ }
+ }
+
+ if(format == null)
+ {
+ continue;
+ }
+
+ System.out.println("format: " + format);
+
+ // stream connector
+ StreamConnector connector
+ = transportManager.getStreamConnector(mediaType);
+
+ //the device this stream would be reading from and writing to.
+ MediaDevice dev = getDefaultDevice(mediaType);
+
+ // stream target
+ MediaStreamTarget target = transportManager.getStreamTarget(
+ mediaType);
+
+ List<RTPExtension> rtpExtensions = new ArrayList<RTPExtension>();
+ MediaDirection direction = MediaDirection.SENDRECV;
+
+ initStream(mediaName, connector, dev, format, target,
+ direction, rtpExtensions);
+ }
+ }
+
+ /**
+ * Gets the <tt>TransportManager</tt> implementation handling our address
+ * management.
+ *
+ * @return the <tt>TransportManager</tt> implementation handling our address
+ * management
+ * @see CallPeerMediaHandler#getTransportManager()
+ */
+ protected TransportManagerGTalkImpl getTransportManager()
+ {
+ if (transportManager == null)
+ {
+ /* Google Talk assumes to use ICE */
+ CallPeerGTalkImpl peer = getPeer();
+
+ // support for Google Talk
+ transportManager = new TransportManagerGTalkImpl(peer);
+ }
+ return transportManager;
+ }
+
+ /**
+ * Processes the transport-related information provided by the remote
+ * <tt>peer</tt> in a specific set of <tt>CandidatePacketExtension</tt>s.
+ *
+ * @param candidates the <tt>CandidatePacketExtenion</tt>s provided by the
+ * remote <tt>peer</tt> and containing the candidate-related information to
+ * be processed
+ * @throws OperationFailedException if anything goes wrong while processing
+ * the candidate-related information provided by the remote <tt>peer</tt> in
+ * the specified set of <tt>CandidatePacketExtension</tt>s
+ */
+ public void processCandidates(
+ Iterable<GTalkCandidatePacketExtension> candidates)
+ throws OperationFailedException
+ {
+ getTransportManager().startConnectivityEstablishment(candidates);
+ }
+
+ /**
+ * Creates a <tt>List</tt> containing the {@link ContentPacketExtension}s of
+ * the streams that this handler is prepared to initiate depending on
+ * available <tt>MediaDevice</tt>s and local on-hold and video transmission
+ * preferences.
+ *
+ * @return a <tt>RtpDescriptionPacketExtension</tt> that contains
+ * list of <tt>PayloadTypePacketExtension</tt>
+ *
+ * @throws OperationFailedException if we fail to create the descriptions
+ * for reasons like problems with device interaction, allocating ports, etc.
+ */
+ public RtpDescriptionPacketExtension createDescription()
+ throws OperationFailedException
+ {
+ RtpDescriptionPacketExtension description =
+ new RtpDescriptionPacketExtension(
+ SessionIQProvider.GTALK_AUDIO_NAMESPACE);
+ List<PayloadTypePacketExtension> mediaDescs
+ = new ArrayList<PayloadTypePacketExtension>();
+ boolean isVideo = false;
+
+ for (MediaType mediaType : MediaType.values())
+ {
+ MediaDevice dev = getDefaultDevice(mediaType);
+
+ if (dev != null)
+ {
+ MediaDirection direction = dev.getDirection().and(
+ getDirectionUserPreference(mediaType));
+
+ if(isLocallyOnHold())
+ direction = direction.and(MediaDirection.SENDONLY);
+
+ /*
+ * If we're only able to receive, we don't have to offer it at
+ * all. For example, we have to offer audio and no video when we
+ * start an audio call.
+ */
+ if (MediaDirection.RECVONLY.equals(direction))
+ direction = MediaDirection.INACTIVE;
+
+ if(direction != MediaDirection.INACTIVE)
+ {
+ List<PayloadTypePacketExtension> contents
+ = createPayloadTypesForOffer(
+ getNameForMediaType(mediaType),
+ dev.getSupportedFormats());
+
+ for(PayloadTypePacketExtension ext : contents)
+ {
+ /* if we add one "video" payload type, we must
+ * advertise it in the description IQ
+ */
+ if(!isVideo && mediaType.equals(MediaType.VIDEO))
+ {
+ description.setNamespace(SessionIQProvider.
+ GTALK_VIDEO_NAMESPACE);
+
+ isVideo = true;
+ }
+ description.addChildExtension(ext);
+ mediaDescs.add(ext);
+ }
+ }
+ }
+ }
+
+ //fail if all devices were inactive
+ if(mediaDescs.isEmpty())
+ {
+ ProtocolProviderServiceJabberImpl.throwOperationFailedException(
+ "We couldn't find any active Audio/Video devices"
+ + " and couldn't create a call",
+ OperationFailedException.GENERAL_ERROR,
+ null,
+ logger);
+ }
+
+ return description;
+ }
+
+ /**
+ * Gathers local candidate addresses.
+ *
+ * @param local the media descriptions sent or to be sent from the local
+ * peer to the remote peer.
+ * @param candidatesSender the <tt>CandidatesSender</tt> to be used by
+ * this <tt>TransportManagerGTalkImpl</tt> to send <tt>candidates</tt>
+ * <tt>SessionIQ</tt>s from the local peer to the remote peer if this
+ * <tt>TransportManagerGTalkImpl</tt> wishes to utilize <tt>candidates</tt>
+ * @throws OperationFailedException if anything goes wrong while starting or
+ * wrapping up the gathering of local candidate addresses
+ */
+ protected void harvestCandidates(
+ List<PayloadTypePacketExtension> local,
+ CandidatesSender candidatesSender)
+ throws OperationFailedException
+ {
+ getTransportManager().startCandidateHarvest(
+ local,
+ candidatesSender);
+
+ transportManager.wrapupCandidateHarvest();
+ }
+
+ /**
+ * Get Google Talk name for the media type.
+ *
+ * @param mediaType media type
+ * @return name for the media type
+ * @throws IllegalArgumentException if media type is not audio or video
+ */
+ private static String getNameForMediaType(MediaType mediaType)
+ throws IllegalArgumentException
+ {
+ if(mediaType == MediaType.AUDIO)
+ {
+ return AUDIO_RTP;
+ }
+ else if(mediaType == MediaType.VIDEO)
+ {
+ return VIDEO_RTP;
+ }
+ else
+ {
+ throw new IllegalArgumentException("not a mediatype");
+ }
+ }
+
+ /**
+ * Get Google Talk namespace for the media type.
+ *
+ * @param mediaType media type
+ * @return namespace for the media type
+ * @throws IllegalArgumentException if media type is not audio or video
+ */
+ private static String getNamespaceForMediaType(MediaType mediaType)
+ {
+
+ if(mediaType == MediaType.AUDIO)
+ {
+ return SessionIQProvider.GTALK_AUDIO_NAMESPACE;
+ }
+ else if(mediaType == MediaType.VIDEO)
+ {
+ return SessionIQProvider.GTALK_VIDEO_NAMESPACE;
+ }
+ else
+ {
+ throw new IllegalArgumentException("not a mediatype");
+ }
+ }
+
+ /**
+ * Create list of payload types for device.
+ *
+ * @param supportedFormats supported formats of a device
+ * @param direction direction
+ * @param supportedExtensions supported RTP extensions
+ * @return list of payload types for this device
+ */
+ private List<PayloadTypePacketExtension> createPayloadTypesForOffer(
+ String name,
+ List<MediaFormat> supportedFormats)
+ {
+ List<PayloadTypePacketExtension> peList =
+ new ArrayList<PayloadTypePacketExtension>();
+
+ for(MediaFormat fmt : supportedFormats)
+ {
+ PayloadTypePacketExtension ext =
+ JingleUtils.formatToPayloadType(fmt, getDynamicPayloadTypes());
+ ext.setNamespace(name.equals(AUDIO_RTP) ?
+ SessionIQProvider.GTALK_AUDIO_NAMESPACE :
+ SessionIQProvider.GTALK_VIDEO_NAMESPACE);
+ peList.add(ext);
+ }
+
+ return peList;
+ }
+
+ /**
+ * Waits for the associated <tt>TransportManagerJabberImpl</tt> to conclude
+ * any started connectivity establishment and then starts this
+ * <tt>CallPeerMediaHandler</tt>.
+ *
+ * @throws IllegalStateException if no offer or answer has been provided or
+ * generated earlier
+ */
+ @Override
+ public void start()
+ throws IllegalStateException
+ {
+ try
+ {
+ wrapupConnectivityEstablishment();
+ }
+ catch (OperationFailedException ofe)
+ {
+ throw new UndeclaredThrowableException(ofe);
+ }
+ super.start();
+ }
+
+ /**
+ * Notifies the associated <tt>TransportManagerGTalkImpl</tt> that it
+ * should conclude any connectivity establishment, waits for it to actually
+ * do so and sets the <tt>connector</tt>s and <tt>target</tt>s of the
+ * <tt>MediaStream</tt>s managed by this <tt>CallPeerMediaHandler</tt>.
+ *
+ * @throws OperationFailedException if anything goes wrong while setting the
+ * <tt>connector</tt>s and/or <tt>target</tt>s of the <tt>MediaStream</tt>s
+ * managed by this <tt>CallPeerMediaHandler</tt>
+ */
+ private void wrapupConnectivityEstablishment()
+ throws OperationFailedException
+ {
+ TransportManagerGTalkImpl transportManager = getTransportManager();
+
+ transportManager.wrapupConnectivityEstablishment();
+
+ for (MediaType mediaType : MediaType.values())
+ {
+ MediaStream stream = getStream(mediaType);
+
+ if (stream != null)
+ {
+ stream.setConnector(
+ transportManager.getStreamConnector(mediaType));
+ stream.setTarget(transportManager.getStreamTarget(mediaType));
+ }
+ }
+ }
+
+ /**
+ * Creates if necessary, and configures the stream that this
+ * <tt>MediaHandler</tt> is using for the <tt>MediaType</tt> matching the
+ * one of the <tt>MediaDevice</tt>. This method extends the one already
+ * available by adding a stream name, corresponding to a stream's content
+ * name.
+ *
+ * @param streamName the name of the stream as indicated in the XMPP
+ * <tt>content</tt> element.
+ * @param connector the <tt>MediaConnector</tt> that we'd like to bind the
+ * newly created stream to.
+ * @param device the <tt>MediaDevice</tt> that we'd like to attach the newly
+ * created <tt>MediaStream</tt> to.
+ * @param format the <tt>MediaFormat</tt> that we'd like the new
+ * <tt>MediaStream</tt> to be set to transmit in.
+ * @param target the <tt>MediaStreamTarget</tt> containing the RTP and RTCP
+ * address:port couples that the new stream would be sending packets to.
+ * @param direction the <tt>MediaDirection</tt> that we'd like the new
+ * stream to use (i.e. sendonly, sendrecv, recvonly, or inactive).
+ * @param rtpExtensions the list of <tt>RTPExtension</tt>s that should be
+ * enabled for this stream.
+ *
+ * @return the newly created <tt>MediaStream</tt>.
+ *
+ * @throws OperationFailedException if creating the stream fails for any
+ * reason (like for example accessing the device or setting the format).
+ */
+ protected MediaStream initStream(String streamName,
+ StreamConnector connector,
+ MediaDevice device,
+ MediaFormat format,
+ MediaStreamTarget target,
+ MediaDirection direction,
+ List<RTPExtension> rtpExtensions)
+ throws OperationFailedException
+ {
+ MediaStream stream
+ = super.initStream(
+ connector,
+ device,
+ format,
+ target,
+ direction,
+ rtpExtensions);
+
+ if(stream != null)
+ stream.setName(streamName);
+
+ return stream;
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CandidatesSender.java b/src/net/java/sip/communicator/impl/protocol/jabber/CandidatesSender.java
new file mode 100644
index 0000000..1a87c84
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/CandidatesSender.java
@@ -0,0 +1,31 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.impl.protocol.jabber;
+
+import net.java.sip.communicator.impl.protocol.jabber.extensions.gtalk.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
+
+/**
+ * Represents functionality which allows a <tt>TransportManagerGTalkImpl</tt>
+ * implementation to send <tt>candidates</tt> {@link SessionIQ}s for the
+ * purposes of expediting candidate negotiation.
+ *
+ * @author Sebastien Vincent
+ */
+public interface CandidatesSender
+{
+ /**
+ * Sends specific {@link CandidatePacketExtension}s in a <tt>candidates</tt>
+ * {@link SessionIQ} from the local peer to the remote peer.
+ *
+ * @param candidates the <tt>CandidatePacketExtension</tt>s to be sent in a
+ * <tt>candidates</tt> <tt>SessionIQ</tt> from the local peer to the
+ * remote peer
+ */
+ public void sendCandidates(
+ Iterable<GTalkCandidatePacketExtension> candidates);
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java b/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java
index de0b49f..7d84794 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java
@@ -491,7 +491,7 @@ public class IceUdpTransportManager
throws OperationFailedException
{
Collection<ContentPacketExtension> transportInfoContents
- = (transportInfoSender ==null)
+ = (transportInfoSender == null)
? null
: new LinkedList<ContentPacketExtension>();
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetAvatarJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetAvatarJabberImpl.java
index f7d695a..a495343 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetAvatarJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetAvatarJabberImpl.java
@@ -1,6 +1,6 @@
/*
* 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;
@@ -10,16 +10,24 @@ import net.java.sip.communicator.service.protocol.*;
/**
* A simple implementation of the <tt>OperationSetAvatar</tt> interface for the
* jabber protocol.
- *
+ *
* Actually there isn't any maximum size for the jabber protocol but GoogleTalk
* fix it a 96x96.
- *
+ *
* @author Damien Roth
*/
public class OperationSetAvatarJabberImpl extends
AbstractOperationSetAvatar<ProtocolProviderServiceJabberImpl>
{
+ /**
+ * Creates a new instances of <tt>OperationSetAvatarJabberImpl</tt>.
+ *
+ * @param parentProvider a reference to the
+ * <tt>ProtocolProviderServiceJabberImpl</tt> instance that created us.
+ * @param accountInfoOpSet a reference to the
+ * <tt>OperationSetServerStoredAccountInfo</tt>.
+ */
public OperationSetAvatarJabberImpl(
ProtocolProviderServiceJabberImpl parentProvider,
OperationSetServerStoredAccountInfo accountInfoOpSet)
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java
index 2ce3529..a96f1e1 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java
@@ -59,6 +59,13 @@ public class OperationSetBasicTelephonyJabberImpl
= new ActiveCallsRepositoryJabberImpl(this);
/**
+ * Contains references for all currently active (non ended) Google Talk
+ * calls.
+ */
+ private ActiveCallsRepositoryGTalkImpl activeGTalkCallsRepository
+ = new ActiveCallsRepositoryGTalkImpl(this);
+
+ /**
* Creates a new instance.
*
* @param protocolProvider a reference to the
@@ -343,6 +350,16 @@ public class OperationSetBasicTelephonyJabberImpl
}
/**
+ * Returns an iterator over all currently Google Talk active calls.
+ *
+ * @return an iterator over all currently Google Talk active calls.
+ */
+ public Iterator<CallGTalkImpl> getGTalkActiveCalls()
+ {
+ return activeGTalkCallsRepository.getActiveCalls();
+ }
+
+ /**
* Resumes communication with a call peer previously put on hold.
*
* @param peer the call peer to put on hold.
@@ -776,6 +793,18 @@ public class OperationSetBasicTelephonyJabberImpl
}
/**
+ * Returns a reference to the {@link ActiveCallsRepositoryGTalkImpl} that
+ * we are currently using.
+ *
+ * @return a reference to the {@link ActiveCallsRepositoryGTalkImpl} that
+ * we are currently using.
+ */
+ protected ActiveCallsRepositoryGTalkImpl getGTalkActiveCallsRepository()
+ {
+ return activeGTalkCallsRepository;
+ }
+
+ /**
* Returns the protocol provider that this operation set belongs to.
*
* @return a reference to the <tt>ProtocolProviderService</tt> that created
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetDesktopSharingServerJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetDesktopSharingServerJabberImpl.java
index 4d1c043..445b3b4 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetDesktopSharingServerJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetDesktopSharingServerJabberImpl.java
@@ -265,8 +265,6 @@ public class OperationSetDesktopSharingServerJabberImpl
/* enable remote-control */
call.setLocalInputEvtAware(supported);
-
-
basicTelephony.createOutgoingCall(call, calleeAddress);
return call;
}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/TransportManagerGTalkImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/TransportManagerGTalkImpl.java
new file mode 100644
index 0000000..b43d82b
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/TransportManagerGTalkImpl.java
@@ -0,0 +1,891 @@
+/*
+ * 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.beans.*;
+import java.net.*;
+import java.util.*;
+
+import org.ice4j.*;
+import org.ice4j.ice.*;
+
+import net.java.sip.communicator.impl.protocol.jabber.extensions.gtalk.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
+import net.java.sip.communicator.service.neomedia.*;
+import net.java.sip.communicator.service.netaddr.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.media.*;
+import net.java.sip.communicator.util.*;
+
+/**
+ * <tt>TransportManager</tt>s gather local candidates for incoming and outgoing
+ * calls. Their work starts by calling a start method which, using the remote
+ * peer's session description, would start the harvest. Calling a second wrapup
+ * method would deliver the candidate harvest, possibly after blocking if it has
+ * not yet completed.
+ *
+ * @author Emil Ivov
+ * @author Lyubomir Marinov
+ * @author Sebastien Vincent
+ */
+public class TransportManagerGTalkImpl
+ extends TransportManager<CallPeerGTalkImpl>
+{
+ /**
+ * The <tt>Logger</tt> used by the <tt>IceUdpTransportManager</tt>
+ * class and its instances for logging output.
+ */
+ private static final Logger logger
+ = Logger.getLogger(TransportManagerGTalkImpl.class);
+
+ /**
+ * The generation of the candidates we are currently generating
+ */
+ private int currentGeneration = 0;
+
+ /**
+ * The ID that we will be assigning to our next candidate. We use
+ * <tt>int</tt>s for interoperability reasons (Emil: I believe that GTalk
+ * uses <tt>int</tt>s. If that turns out not to be the case we can stop
+ * using <tt>int</tt>s here if that's an issue).
+ */
+ private static int nextID = 1;
+
+ /**
+ * The ICE <tt>Component</tt> IDs in their common order used, for example,
+ * by <tt>DefaultStreamConnector</tt>, <tt>MediaStreamTarget</tt>.
+ */
+ private static final int[] COMPONENT_IDS
+ = new int[] { Component.RTP, Component.RTCP };
+
+ /**
+ * The ICE agent that this transport manager would be using for ICE
+ * negotiation.
+ */
+ private final Agent iceAgent;
+
+ /**
+ * Creates a new instance of this transport manager, binding it to the
+ * specified peer.
+ *
+ * @param callPeer the {@link CallPeer} whose traffic we will be taking
+ * care of.
+ */
+ public TransportManagerGTalkImpl(CallPeerGTalkImpl callPeer)
+ {
+ super(callPeer);
+
+ iceAgent = createIceAgent();
+ }
+
+ /**
+ * Returns the ID that we will be assigning to the next candidate we create.
+ *
+ * @return the next ID to use with a candidate.
+ */
+ protected String getNextID()
+ {
+ return Integer.toString(nextID++);
+ }
+
+ /**
+ * Returns the generation that our current candidates belong to.
+ *
+ * @return the generation that we should assign to candidates that we are
+ * currently advertising.
+ */
+ protected int getCurrentGeneration()
+ {
+ return currentGeneration;
+ }
+
+ /**
+ * Increments the generation that we are assigning candidates.
+ */
+ protected void incrementGeneration()
+ {
+ currentGeneration++;
+ }
+
+ /**
+ * Returns the <tt>InetAddress</tt> that is most likely to be to be used
+ * as a next hop when contacting the specified <tt>destination</tt>. This is
+ * an utility method that is used whenever we have to choose one of our
+ * local addresses to put in the Via, Contact or (in the case of no
+ * registrar accounts) From headers.
+ *
+ * @param peer the CallPeer that we would contact.
+ *
+ * @return the <tt>InetAddress</tt> that is most likely to be to be used
+ * as a next hop when contacting the specified <tt>destination</tt>.
+ *
+ * @throws IllegalArgumentException if <tt>destination</tt> is not a valid
+ * host/IP/FQDN
+ */
+ @Override
+ protected InetAddress getIntendedDestination(CallPeerGTalkImpl peer)
+ {
+ return peer.getProtocolProvider().getNextHop();
+ }
+
+ /**
+ * Creates the ICE agent that we would be using in this transport manager
+ * for all negotiation.
+ *
+ * @return the ICE agent to use for all the ICE negotiation that this
+ * transport manager would be going through
+ */
+ private Agent createIceAgent()
+ {
+ CallPeerGTalkImpl peer = getCallPeer();
+ Agent agent = null;
+
+ /* XXX wait changes from ice4j
+ agent = new Agent(CompatibilityMode.GTALK);
+ agent.setControlling(!peer.isInitiator());
+ */
+
+ /* XXX no configured STUN/TURN for the moment
+ * it should be discovered by a Google XMPP extension
+ for(StunServerDescriptor desc : accID.getStunServers())
+ {
+ TransportAddress addr = new TransportAddress(
+ desc.getAddress(), desc.getPort(), Transport.UDP);
+
+ StunCandidateHarvester harvester;
+
+
+ if(desc.isTurnSupported())
+ {
+ //Yay! a TURN server
+ harvester
+ = new TurnCandidateHarvester(
+ addr,
+ new LongTermCredential(
+ desc.getUsername(),
+ desc.getPassword()));
+ }
+ else
+ {
+ //this is a STUN only server
+ harvester = new StunCandidateHarvester(addr);
+ }
+
+ if (logger.isInfoEnabled())
+ logger.info("Adding pre-configured harvester " + harvester);
+
+ atLeastOneStunServer = true;
+ agent.addCandidateHarvester(harvester);
+ }
+
+ if(accID.isUPNPEnabled())
+ {
+ UPNPHarvester harvester = new UPNPHarvester();
+
+ if(harvester != null)
+ {
+ agent.addCandidateHarvester(harvester);
+ }
+ }
+ */
+
+ return agent;
+ }
+
+ /**
+ * Initializes a new <tt>StreamConnector</tt> to be used as the
+ * <tt>connector</tt> of the <tt>MediaStream</tt> with a specific
+ * <tt>MediaType</tt>.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> which
+ * is to have its <tt>connector</tt> set to the returned
+ * <tt>StreamConnector</tt>
+ * @return a new <tt>StreamConnector</tt> to be used as the
+ * <tt>connector</tt> of the <tt>MediaStream</tt> with the specified
+ * <tt>MediaType</tt>
+ * @throws OperationFailedException if anything goes wrong while
+ * initializing the new <tt>StreamConnector</tt>
+ */
+ @Override
+ protected StreamConnector createStreamConnector(MediaType mediaType)
+ throws OperationFailedException
+ {
+ DatagramSocket[] streamConnectorSockets
+ = getStreamConnectorSockets(mediaType);
+
+ /*
+ * XXX If the iceAgent has not completed (yet), go with a default
+ * StreamConnector (until it completes).
+ */
+ return
+ (streamConnectorSockets == null)
+ ? super.createStreamConnector(mediaType)
+ : new DefaultStreamConnector(
+ streamConnectorSockets[0 /* RTP */],
+ streamConnectorSockets[1 /* RTCP */]);
+ }
+
+ /**
+ * Gets the <tt>StreamConnector</tt> to be used as the <tt>connector</tt> of
+ * the <tt>MediaStream</tt> with a specific <tt>MediaType</tt>.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> which
+ * is to have its <tt>connector</tt> set to the returned
+ * <tt>StreamConnector</tt>
+ * @return the <tt>StreamConnector</tt> to be used as the <tt>connector</tt>
+ * of the <tt>MediaStream</tt> with the specified <tt>MediaType</tt>
+ * @throws OperationFailedException if anything goes wrong while
+ * initializing the requested <tt>StreamConnector</tt>
+ * @see net.java.sip.communicator.service.protocol.media.TransportManager#
+ * getStreamConnector(MediaType)
+ */
+ @Override
+ public StreamConnector getStreamConnector(MediaType mediaType)
+ throws OperationFailedException
+ {
+ StreamConnector streamConnector = super.getStreamConnector(mediaType);
+
+ /*
+ * Since the super caches the StreamConnectors, make sure that the
+ * returned one is up-to-date with the iceAgent.
+ */
+ if (streamConnector != null)
+ {
+ DatagramSocket[] streamConnectorSockets
+ = getStreamConnectorSockets(mediaType);
+
+ /*
+ * XXX If the iceAgent has not completed (yet), go with the default
+ * StreamConnector (until it completes).
+ */
+ if ((streamConnectorSockets != null)
+ && ((streamConnector.getDataSocket()
+ != streamConnectorSockets[0 /* RTP */])
+ ))
+ //|| (streamConnector.getControlSocket()
+ // != streamConnectorSockets[1 /* RTCP */])))
+ {
+ // Recreate the StreamConnector for the specified mediaType.
+ closeStreamConnector(mediaType);
+ streamConnector = super.getStreamConnector(mediaType);
+ }
+ }
+
+ return streamConnector;
+ }
+
+ /**
+ * Gets an array of <tt>DatagramSocket</tt>s which represents the sockets to
+ * be used by the <tt>StreamConnector</tt> with the specified
+ * <tt>MediaType</tt> in the order of {@link #COMPONENT_IDS} if
+ * {@link #iceAgent} has completed.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the <tt>StreamConnector</tt>
+ * for which the <tt>DatagramSocket</tt>s are to be returned
+ * @return an array of <tt>DatagramSocket</tt>s which represents the sockets
+ * to be used by the <tt>StreamConnector</tt> which the specified
+ * <tt>MediaType</tt> in the order of {@link #COMPONENT_IDS} if
+ * {@link #iceAgent} has completed; otherwise, <tt>null</tt>
+ */
+ private DatagramSocket[] getStreamConnectorSockets(MediaType mediaType)
+ {
+ String mediaName = null;
+
+ if(mediaType == MediaType.AUDIO)
+ {
+ mediaName = "rtp";
+ }
+ else if(mediaType == MediaType.VIDEO)
+ {
+ mediaName = "video_rtp";
+ }
+ else
+ {
+ logger.error("Not an audio/rtp mediatype");
+ return null;
+ }
+
+ IceMediaStream stream = iceAgent.getStream(mediaName);
+
+ if (stream != null)
+ {
+ DatagramSocket[] streamConnectorSockets
+ = new DatagramSocket[COMPONENT_IDS.length];
+ int streamConnectorSocketCount = 0;
+
+ for (int i = 0; i < COMPONENT_IDS.length; i++)
+ {
+ Component component = stream.getComponent(COMPONENT_IDS[i]);
+
+ if (component != null)
+ {
+ CandidatePair selectedPair = component.getSelectedPair();
+
+ if (selectedPair != null)
+ {
+ DatagramSocket streamConnectorSocket
+ = selectedPair.getLocalCandidate().getSocket();
+
+ if (streamConnectorSocket != null)
+ {
+ streamConnectorSockets[i] = streamConnectorSocket;
+ streamConnectorSocketCount++;
+ }
+ }
+ }
+ }
+ if (streamConnectorSocketCount > 0)
+ {
+ // XXX GTalk audio has not RTCP channel
+ if(mediaName.equals("rtp") && streamConnectorSocketCount == 1)
+ {
+ try
+ {
+ streamConnectorSockets[1] = new DatagramSocket();
+ }
+ catch(Exception e)
+ {
+ }
+ }
+
+ return streamConnectorSockets;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Implements {@link TransportManagerJabberImpl#getStreamTarget(MediaType)}.
+ * Gets the <tt>MediaStreamTarget</tt> to be used as the <tt>target</tt> of
+ * the <tt>MediaStream</tt> with a specific <tt>MediaType</tt>.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> which
+ * is to have its <tt>target</tt> set to the returned
+ * <tt>MediaStreamTarget</tt>
+ * @return the <tt>MediaStreamTarget</tt> to be used as the <tt>target</tt>
+ * of the <tt>MediaStream</tt> with the specified <tt>MediaType</tt>
+ * @see TransportManagerJabberImpl#getStreamTarget(MediaType)
+ */
+ public MediaStreamTarget getStreamTarget(MediaType mediaType)
+ {
+ IceMediaStream stream = null;
+ MediaStreamTarget streamTarget = null;
+ String mediaName = null;
+
+ if(mediaType == MediaType.AUDIO)
+ {
+ mediaName = "rtp";
+ }
+ else if(mediaType == MediaType.VIDEO)
+ {
+ mediaName = "video_rtp";
+ }
+ else
+ {
+ logger.error("Not an audio/rtp mediatype");
+ return null;
+ }
+
+ stream = iceAgent.getStream(mediaName);
+
+ if (stream != null)
+ {
+ InetSocketAddress[] streamTargetAddresses
+ = new InetSocketAddress[COMPONENT_IDS.length];
+ int streamTargetAddressCount = 0;
+
+ for (int i = 0; i < COMPONENT_IDS.length; i++)
+ {
+ Component component = stream.getComponent(COMPONENT_IDS[i]);
+
+ if (component != null)
+ {
+ CandidatePair selectedPair = component.getSelectedPair();
+
+ if (selectedPair != null)
+ {
+ InetSocketAddress streamTargetAddress
+ = selectedPair
+ .getRemoteCandidate()
+ .getTransportAddress();
+
+ if (streamTargetAddress != null)
+ {
+ streamTargetAddresses[i] = streamTargetAddress;
+ streamTargetAddressCount++;
+ }
+ }
+ }
+ }
+ if (streamTargetAddressCount > 0)
+ {
+ int rtcpIndex = 1;
+
+ // XXX GTalk audio has not RTCP channel
+ if(mediaName.equals("rtp") && streamTargetAddressCount == 1)
+ {
+ rtcpIndex = 0;
+ }
+
+ streamTarget
+ = new MediaStreamTarget(
+ streamTargetAddresses[0 /* RTP */],
+ streamTargetAddresses[rtcpIndex /* RTCP */]);
+ }
+ }
+ return streamTarget;
+ }
+
+ /**
+ * Creates an {@link IceMediaStream} with the specified <tt>media</tt>
+ * name.
+ *
+ * @param media the name of the stream we'd like to create.
+ *
+ * @return the newly created {@link IceMediaStream}
+ *
+ * @throws OperationFailedException if binding on the specified media stream
+ * fails for some reason.
+ */
+ private IceMediaStream createIceStream(String media)
+ throws OperationFailedException
+ {
+ IceMediaStream stream;
+
+ try
+ {
+ //the following call involves STUN processing so it may take a while
+ stream = getNetAddrMgr().createIceStream(
+ nextMediaPortToTry, media, iceAgent);
+ }
+ catch (Exception ex)
+ {
+ throw new OperationFailedException(
+ "Failed to initialize stream " + media,
+ OperationFailedException.INTERNAL_ERROR,
+ ex);
+ }
+
+ //let's now update the next port var as best we can: we would assume
+ //that all local candidates are bound on the same port and set it
+ //to the one just above. if the assumption is wrong the next bind
+ //would simply include one more bind retry.
+ try
+ {
+ nextMediaPortToTry = stream.getComponent(Component.RTCP)
+ .getLocalCandidates().get(0)
+ .getTransportAddress().getPort() + 1;
+ }
+ catch(Throwable t)
+ {
+ //hey, we were just trying to be nice. if that didn't work for
+ //some reason we really can't be held responsible!
+ logger.debug("Determining next port didn't work: ", t);
+ }
+
+ return stream;
+ }
+
+ /**
+ * Returns a reference to the {@link NetworkAddressManagerService}. The only
+ * reason this method exists is that {@link JabberActivator
+ * #getNetworkAddressManagerService()} is too long to write and makes code
+ * look clumsy.
+ *
+ * @return a reference to the {@link NetworkAddressManagerService}.
+ */
+ private static NetworkAddressManagerService getNetAddrMgr()
+ {
+ return JabberActivator.getNetworkAddressManagerService();
+ }
+
+ /**
+ * Starts transport candidate harvest. This method should complete rapidly
+ * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests
+ * are necessary, they should be executed in a separate thread.
+ *
+ * @param ourAnswer the content descriptions that we should be adding our
+ * transport lists to (although not necessarily in this very instance).
+ * @param candidatesSender the <tt>CandidatesSender</tt> to be used by
+ * this <tt>TransportManagerGTalkImpl</tt> to send <tt>candidates</tt>
+ * <tt>SessionIQ</tt>s from the local peer to the remote peer if this
+ * <tt>TransportManagerGTalkImpl</tt> wishes to utilize
+ * <tt>candidates</tt>.
+ *
+ * @throws OperationFailedException if we fail to allocate a port number.
+ */
+ public void startCandidateHarvest(
+ List<PayloadTypePacketExtension> ourAnswer,
+ CandidatesSender candidatesSender)
+ throws OperationFailedException
+ {
+ boolean audio = false;
+ boolean video = false;
+ List<GTalkCandidatePacketExtension> candidates
+ = new LinkedList<GTalkCandidatePacketExtension>();
+
+ for(PayloadTypePacketExtension ext : ourAnswer)
+ {
+ if(ext.getNamespace().equals(
+ SessionIQProvider.GTALK_AUDIO_NAMESPACE))
+ {
+ audio = true;
+ }
+ else if(ext.getNamespace().equals(
+ SessionIQProvider.GTALK_VIDEO_NAMESPACE))
+ {
+ video = true;
+ }
+ }
+
+ if(audio)
+ {
+ IceMediaStream stream = createIceStream("rtp");
+
+ /* remove RTCP component for the audio as it is not used and
+ * remote gmail peer does not send them
+ */
+ for(Component cmp : stream.getComponents())
+ {
+ if(cmp.getComponentID() == 2)
+ {
+ stream.removeComponent(cmp);
+ }
+ }
+
+ candidates.addAll(GTalkPacketFactory.createCandidates("rtp",
+ stream));
+ }
+
+ if(video)
+ {
+ IceMediaStream stream = createIceStream("video_rtp");
+ candidates.addAll(GTalkPacketFactory.createCandidates("video_rtp",
+ stream));
+ }
+
+ /* send candidates */
+ candidatesSender.sendCandidates(candidates);
+ }
+
+ /**
+ * Notifies the transport manager that it should conclude candidate
+ * harvesting as soon as possible.
+ */
+ public void wrapupCandidateHarvest()
+ {
+ }
+
+ /**
+ * Starts the connectivity establishment of this
+ * <tt>TransportManagerGTalkImpl</tt> i.e. checks the connectivity between
+ * the local and the remote peers given the remote counterpart of the
+ * negotiation between them.
+ *
+ * @param remote the collection of <tt>CandidatePacketExtension</tt>s which
+ * represents the remote counterpart of the negotiation between the local
+ * and the remote peer
+ * @return <tt>true</tt> if connectivity establishment has been started in
+ * response to the call; otherwise, <tt>false</tt>.
+ */
+ public boolean startConnectivityEstablishment(
+ Iterable<GTalkCandidatePacketExtension> remote)
+ {
+ /* If ICE is already running, we try to update the checklists with
+ * the candidates. Note that this is a best effort.
+ */
+ if (IceProcessingState.RUNNING.equals(iceAgent.getState()))
+ {
+ if(logger.isInfoEnabled())
+ {
+ logger.info("Update Google ICE remote candidates");
+ }
+
+ if(remote == null)
+ {
+ return false;
+ }
+
+ for(GTalkCandidatePacketExtension candidate : remote)
+ {
+ String name = candidate.getName();
+ int numComponent = 0;
+
+ // change name to retrieve properly the ICE media stream
+ if(name.equals("rtp"))
+ {
+ numComponent = 1;
+ }
+ else if(name.equals("rtcp"))
+ {
+ name = "rtp";
+ numComponent = 1;
+ }
+ else if(name.equals("video_rtp"))
+ {
+ numComponent = 1;
+ }
+ else if(name.equals("video_rtcp"))
+ {
+ name = "video_rtp";
+ numComponent = 2;
+ }
+
+ IceMediaStream stream = iceAgent.getStream(name);
+
+ if(stream == null)
+ {
+ continue;
+ }
+
+ /* Different candidates may have different ufrag/password */
+ String ufrag = candidate.getUsername();
+ //String password = candidate.getPassword();
+
+ /*
+ * Is the remote candidate from the current generation of
+ * the iceAgent?
+ */
+ if (candidate.getGeneration() != iceAgent.getGeneration())
+ continue;
+
+ // XXX UDP only for the moment as ice4j.org does not support
+ // TCP yet
+ if(!candidate.getProtocol().equalsIgnoreCase(
+ Transport.UDP.toString()))
+ continue;
+
+ Component component
+ = stream.getComponent(numComponent);
+ /* XXX wait changes from ice4j
+ RemoteCandidate remoteCandidate = new RemoteCandidate(
+ new TransportAddress(
+ candidate.getAddress(),
+ candidate.getPort(),
+ Transport.parse(
+ candidate.getProtocol())),
+ component,
+ org.ice4j.ice.CandidateType.parse(
+ candidate.getType().toString()),
+ "0",
+ (long)(candidate.getPreference() * 1000),
+ ufrag);
+ component.addUpdateRemoteCandidate(remoteCandidate);
+ */
+ }
+
+ /* update all components of all streams */
+ for(IceMediaStream stream : iceAgent.getStreams())
+ {
+ for(Component component : stream.getComponents())
+ {
+ component.updateRemoteCandidate();
+ }
+ }
+ return false;
+ }
+
+ int generation = iceAgent.getGeneration();
+ boolean startConnectivityEstablishment = false;
+
+ if(remote == null)
+ {
+ return false;
+ }
+
+ for(GTalkCandidatePacketExtension candidate : remote)
+ {
+ String name = candidate.getName();
+ int numComponent = 0;
+
+ // change name to retrieve properly the ICE media stream
+ if(name.equals("rtp"))
+ {
+ numComponent = 1;
+ }
+ else if(name.equals("rtcp"))
+ {
+ name = "rtp";
+ numComponent = 1;
+ }
+ else if(name.equals("video_rtp"))
+ {
+ numComponent = 1;
+ }
+ else if(name.equals("video_rtcp"))
+ {
+ name = "video_rtp";
+ numComponent = 2;
+ }
+
+ IceMediaStream stream = iceAgent.getStream(name);
+
+ if(stream == null)
+ {
+ continue;
+ }
+
+ /* Different candidates may have different ufrag/password */
+ String ufrag = candidate.getUsername();
+ //String password = candidate.getPassword();
+
+ /*
+ * Is the remote candidate from the current generation of
+ * the iceAgent?
+ */
+ if (candidate.getGeneration() != generation)
+ continue;
+
+ if(!candidate.getProtocol().equalsIgnoreCase(
+ Transport.UDP.toString()))
+ continue;
+
+ Component component
+ = stream.getComponent(numComponent);
+
+ /* XXX wait changes from ice4j
+ RemoteCandidate remoteCandidate = new RemoteCandidate(
+ new TransportAddress(
+ candidate.getAddress(),
+ candidate.getPort(),
+ Transport.parse(
+ candidate.getProtocol())),
+ component,
+ org.ice4j.ice.CandidateType.parse(
+ candidate.getType().toString()),
+ "0",
+ (long)(candidate.getPreference() * 1000),
+ ufrag);
+ component.addRemoteCandidate(remoteCandidate);
+ */
+ startConnectivityEstablishment = true;
+ }
+
+ if (startConnectivityEstablishment)
+ {
+ /*
+ * Once again because the ICE Agent does not support adding
+ * candidates after the connectivity establishment has been started
+ * and because multiple transport-info JingleIQs may be used to send
+ * the whole set of transport candidates from the remote peer to the
+ * local peer, do not really start the connectivity establishment
+ * until we have at least two remote candidates (i.e. local and
+ * stun) per ICE Component.
+ */
+ for (IceMediaStream stream : iceAgent.getStreams())
+ {
+ for (Component component : stream.getComponents())
+ {
+ if(component.getName().equals("RTCP"))
+ continue;
+ if (component.getRemoteCandidateCount() < 2)
+ {
+ startConnectivityEstablishment = false;
+ break;
+ }
+ }
+ if (!startConnectivityEstablishment)
+ break;
+ }
+
+ if (startConnectivityEstablishment)
+ {
+ iceAgent.startConnectivityEstablishment();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Waits for the associated ICE <tt>Agent</tt> to finish any started
+ * connectivity checks.
+ *
+ * @throws OperationFailedException if ICE processing has failed
+ */
+ public void wrapupConnectivityEstablishment()
+ throws OperationFailedException
+ {
+ final Object iceProcessingStateSyncRoot = new Object();
+ PropertyChangeListener stateChangeListener
+ = new PropertyChangeListener()
+ {
+ public void propertyChange(PropertyChangeEvent evt)
+ {
+ Object newValue = evt.getNewValue();
+
+ if (IceProcessingState.COMPLETED.equals(newValue)
+ || IceProcessingState.FAILED.equals(newValue)
+ || IceProcessingState.TERMINATED.equals(newValue))
+ {
+ if (logger.isTraceEnabled())
+ logger.trace("ICE " + newValue);
+
+ Agent iceAgent = (Agent) evt.getSource();
+
+ iceAgent.removeStateChangeListener(this);
+
+ if (iceAgent == TransportManagerGTalkImpl.this.iceAgent)
+ {
+ synchronized (iceProcessingStateSyncRoot)
+ {
+ iceProcessingStateSyncRoot.notify();
+ }
+ }
+ }
+ }
+ };
+
+ iceAgent.addStateChangeListener(stateChangeListener);
+
+ // Wait for the connectivity checks to finish if they have been started.
+ boolean interrupted = false;
+
+ synchronized (iceProcessingStateSyncRoot)
+ {
+ while (IceProcessingState.RUNNING.equals(iceAgent.getState()))
+ {
+ try
+ {
+ iceProcessingStateSyncRoot.wait();
+ }
+ catch (InterruptedException ie)
+ {
+ interrupted = true;
+ }
+ }
+ }
+
+ if (interrupted)
+ Thread.currentThread().interrupt();
+
+ /*
+ * Make sure stateChangeListener is removed from iceAgent in case its
+ * #propertyChange(PropertyChangeEvent) has never been executed.
+ */
+ iceAgent.removeStateChangeListener(stateChangeListener);
+
+ /* check the state of ICE processing and throw exception if failed */
+ if(iceAgent.getState().equals(IceProcessingState.FAILED))
+ {
+ throw new OperationFailedException(
+ "Could not establish connection (ICE failed)",
+ OperationFailedException.GENERAL_ERROR);
+ }
+ }
+
+ /**
+ * Close this transport manager and release resources.
+ */
+ public void close()
+ {
+ if(iceAgent != null)
+ {
+ iceAgent.free();
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/service/protocol/media/AbstractOperationSetTelephonyConferencing.java b/src/net/java/sip/communicator/service/protocol/media/AbstractOperationSetTelephonyConferencing.java
index 2095fee..4d6fc4e 100644
--- a/src/net/java/sip/communicator/service/protocol/media/AbstractOperationSetTelephonyConferencing.java
+++ b/src/net/java/sip/communicator/service/protocol/media/AbstractOperationSetTelephonyConferencing.java
@@ -245,7 +245,7 @@ public abstract class AbstractOperationSetTelephonyConferencing<
* @throws OperationFailedException if inviting the specified callee to the
* specified call fails
*/
- protected abstract MediaAwareCallPeerT inviteCalleeToCall(
+ protected abstract CallPeer inviteCalleeToCall(
CalleeAddressT calleeAddress,
MediaAwareCallT call,
boolean wasConferenceFocus)
diff --git a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java
index d5ac2c0..d3b1261 100644
--- a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java
+++ b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java
@@ -520,6 +520,16 @@ public abstract class MediaAwareCall<
}
/**
+ * Get the media use case.
+ *
+ * @return media use case
+ */
+ public MediaUseCase getMediaUseCase()
+ {
+ return mediaUseCase;
+ }
+
+ /**
* Determines whether the streaming of local video in this <tt>Call</tt>
* is currently allowed. The setting does not reflect the availability of
* actual video capture devices, it just expresses the local policy (or