diff --git a/lib/installer-exclude/ice4j.jar b/lib/installer-exclude/ice4j.jar index 6893ed708..e29fa550a 100644 Binary files a/lib/installer-exclude/ice4j.jar and b/lib/installer-exclude/ice4j.jar differ diff --git a/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java b/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java index e4eb36890..acbb3a17e 100644 --- a/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java @@ -724,6 +724,66 @@ private void deviceSessionSsrcListChanged(PropertyChangeEvent evt) this.localContributingSourceIDList = csrcArray; } + /** + * Sets the target of this MediaStream to which it is to send and + * from which it is to receive data (e.g. RTP) and control data (e.g. RTCP). + * In contrast to {@link #setTarget(MediaStreamTarget)}, sets the specified + * target on this MediaStreamImpl even if its current + * target is equal to the specified one. + * + * @param target the MediaStreamTarget describing the data + * (e.g. RTP) and the control data (e.g. RTCP) locations to which this + * MediaStream is to send and from which it is to receive + * @see MediaStreamImpl#setTarget(MediaStreamTarget) + */ + private void doSetTarget(MediaStreamTarget target) + { + rtpConnector.removeTargets(); + rtpConnectorTarget = null; + + boolean targetIsSet; + + if (target != null) + { + InetSocketAddress dataAddr = target.getDataAddress(); + InetSocketAddress controlAddr = target.getControlAddress(); + + try + { + rtpConnector + .addTarget( + new SessionAddress( + dataAddr.getAddress(), + dataAddr.getPort(), + controlAddr.getAddress(), + controlAddr.getPort())); + targetIsSet = true; + } + catch (IOException ioe) + { + // TODO + targetIsSet = false; + logger.error("Failed to set target " + target, ioe); + } + } + else + targetIsSet = true; + + if (targetIsSet) + { + rtpConnectorTarget = target; + + if (logger.isTraceEnabled()) + logger + .trace( + "Set target of " + + getClass().getSimpleName() + + " with hashCode " + + hashCode() + + " to " + + target); + } + } /** * Gets the MediaDevice that this stream uses to play back and * capture media. @@ -1091,8 +1151,17 @@ protected void rtpConnectorChanged( { zrtpControl.setConnector(newValue); - // Register the transform engines that we will be using in this stream. - rtpConnector.setEngine(createTransformEngineChain()); + if (newValue != null) + { + /* + * Register the transform engines that we will be using in this + * stream. + */ + newValue.setEngine(createTransformEngineChain()); + + if (rtpConnectorTarget != null) + doSetTarget(rtpConnectorTarget); + } } /** @@ -1354,51 +1423,7 @@ public void setTarget(MediaStreamTarget target) else if (target.equals(rtpConnectorTarget)) return; - rtpConnector.removeTargets(); - rtpConnectorTarget = null; - - boolean targetIsSet; - - if (target != null) - { - InetSocketAddress dataAddr = target.getDataAddress(); - InetSocketAddress controlAddr = target.getControlAddress(); - - try - { - rtpConnector - .addTarget( - new SessionAddress( - dataAddr.getAddress(), - dataAddr.getPort(), - controlAddr.getAddress(), - controlAddr.getPort())); - targetIsSet = true; - } - catch (IOException ioe) - { - // TODO - targetIsSet = false; - logger.error("Failed to set target " + target, ioe); - } - } - else - targetIsSet = true; - - if (targetIsSet) - { - rtpConnectorTarget = target; - - if (logger.isTraceEnabled()) - logger - .trace( - "Set target of " - + getClass().getSimpleName() - + " with hashCode " - + hashCode() - + " to " - + target); - } + doSetTarget(target); } /** diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java index eaf84af36..0c20f135a 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java @@ -15,26 +15,23 @@ import net.java.sip.communicator.util.*; /** - * A Jabber implementation of the Call abstract class encapsulating Jabber - * jingle sessions. + * A Jabber implementation of the Call abstract class encapsulating + * Jabber jingle sessions. * * @author Emil Ivov */ -public class CallJabberImpl extends MediaAwareCall< - CallPeerJabberImpl, - OperationSetBasicTelephonyJabberImpl, - ProtocolProviderServiceJabberImpl> +public class CallJabberImpl + extends MediaAwareCall< + CallPeerJabberImpl, + OperationSetBasicTelephonyJabberImpl, + ProtocolProviderServiceJabberImpl> { /** - * Logger of this class + * The Logger used by the CallJabberImpl class and its + * instances for logging output. */ private static final Logger logger = Logger.getLogger(CallJabberImpl.class); - /** - * The operation set that created us. - */ - private final OperationSetBasicTelephonyJabberImpl parentOpSet; - /** * Indicates if the CallPeer will support inputevt * extension (i.e. will be able to be remote-controlled). @@ -42,11 +39,11 @@ public class CallJabberImpl extends MediaAwareCall< private boolean localInputEvtAware = false; /** - * Crates a CallJabberImpl instance belonging to sourceProvider and - * associated with the jingle session with the specified jingleSID. - * If this call corresponds to an incoming jingle session then the jingleSID - * would come from there. Otherwise one could generate one using {@link - * JingleIQ#generateSID()} + * Initializes a new CallJabberImpl instance belonging to + * sourceProvider and associated with the jingle session with the + * specified jingleSID. If the new instance corresponds to an + * incoming jingle session, then the jingleSID would come from there. + * Otherwise, one could generate one using {@link JingleIQ#generateSID()}. * * @param parentOpSet the {@link OperationSetBasicTelephonyJabberImpl} * instance in the context of which this call has been created. @@ -55,7 +52,6 @@ protected CallJabberImpl( OperationSetBasicTelephonyJabberImpl parentOpSet) { super(parentOpSet); - this.parentOpSet = parentOpSet; //let's add ourselves to the calls repo. we are doing it ourselves just //to make sure that no one ever forgets. @@ -113,9 +109,7 @@ public CallPeerJabberImpl processSessionInitiate(JingleIQ jingleIQ) // 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; } @@ -142,6 +136,7 @@ public CallPeerJabberImpl initiateSession(String calleeJID, { // create the session-initiate IQ CallPeerJabberImpl callPeer = new CallPeerJabberImpl(calleeJID, this); + callPeer.setDiscoverInfo(discoverInfo); addCallPeer(callPeer); @@ -151,9 +146,7 @@ public CallPeerJabberImpl initiateSession(String calleeJID, // 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_INITIATED), this); - } /* enable video if it is a videocall */ callPeer.getMediaHandler().setLocalVideoTransmissionEnabled( @@ -163,7 +156,7 @@ public CallPeerJabberImpl initiateSession(String calleeJID, //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.. + //STUN/TURN addresses in initiateSession() which would take a while. callPeer.setState( CallPeerState.CONNECTING); callPeer.initiateSession(); @@ -187,9 +180,7 @@ public void modifyVideoContent(boolean allowed) "Stop local video streaming"); for(CallPeerJabberImpl peer : getCallPeersVector()) - { peer.sendModifyVideoContent(allowed); - } } /** @@ -203,13 +194,7 @@ public void modifyVideoContent(boolean allowed) */ public boolean containsJingleSID(String sid) { - for(CallPeerJabberImpl peer : getCallPeersVector()) - { - if (peer.getJingleSID().equals(sid)) - return true; - } - - return false; + return (getPeer(sid) != null); } /** @@ -228,7 +213,6 @@ public CallPeerJabberImpl getPeer(String sid) if (peer.getJingleSID().equals(sid)) return peer; } - return null; } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java index cffe4e3b9..1ce5af63d 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java @@ -19,7 +19,7 @@ import net.java.sip.communicator.util.*; /** - * Our Jabber implementation of the default CallPeer; + * Implements a Jabber CallPeer. * * @author Emil Ivov */ @@ -29,11 +29,11 @@ public class CallPeerJabberImpl ProtocolProviderServiceJabberImpl> { /** - * The Logger used by the CallPeerJabberImpl - * class and its instances for logging output. + * The Logger used by the CallPeerJabberImpl class and its + * instances for logging output. */ - private static final Logger logger = Logger - .getLogger(CallPeerJabberImpl.class.getName()); + private static final Logger logger + = Logger.getLogger(CallPeerJabberImpl.class); /** * The jabber address of this peer @@ -99,7 +99,7 @@ public void setAddress(String address) fireCallPeerChangeEvent( CallPeerChangeEvent.CALL_PEER_ADDRESS_CHANGE, oldAddress, - address.toString()); + address); } /** @@ -111,15 +111,10 @@ public String getDisplayName() { if (getCall() != null) { - ProtocolProviderService pps = getCall().getProtocolProvider(); - OperationSetPresence opSetPresence - = pps.getOperationSet(OperationSetPresence.class); + Contact contact = getContact(); - Contact cont = opSetPresence.findContactByID(getAddress()); - if (cont != null) - { - return cont.getDisplayName(); - } + if (contact != null) + return contact.getDisplayName(); } return peerJID; } @@ -153,26 +148,30 @@ protected synchronized void processSessionInitiate(JingleIQ sessionInitIQ) this.sessionInitIQ = sessionInitIQ; this.isInitiator = true; - // This is the SDP offer that came from the initial session-initiate, - //contrary to sip we we are guaranteed to have content because XEP-0166 - //says: "A session consists of at least one content type at a time." + // This is the SDP offer that came from the initial session-initiate. + // Contrary to SIP, we are guaranteed to have content because XEP-0166 + // says: "A session consists of at least one content type at a time." List offer = sessionInitIQ.getContentList(); try { getMediaHandler().processOffer(offer); } - catch(Exception exc) + catch(Exception ex) { - logger.info("Failed to process an incoming session initiate", exc); + logger.info("Failed to process an incoming session initiate", ex); //send an error response; - JingleIQ errResp = JinglePacketFactory.createSessionTerminate( - sessionInitIQ.getTo(), sessionInitIQ.getFrom(), - sessionInitIQ.getSID(), Reason.INCOMPATIBLE_PARAMETERS, - "Error: " + exc.getMessage()); - - setState(CallPeerState.FAILED, "Error: " + exc.getMessage()); + String reasonText = "Error: " + ex.getMessage(); + JingleIQ errResp + = JinglePacketFactory.createSessionTerminate( + sessionInitIQ.getTo(), + sessionInitIQ.getFrom(), + sessionInitIQ.getSID(), + Reason.INCOMPATIBLE_PARAMETERS, + reasonText); + + setState(CallPeerState.FAILED, reasonText); getProtocolProvider().getConnection().sendPacket(errResp); return; } @@ -196,19 +195,25 @@ protected synchronized void initiateSession() throws OperationFailedException { isInitiator = false; + //Create the media description that we'd like to send to the other side. - List offer = getMediaHandler() - .createContentList(); + List offer + = getMediaHandler().createContentList(); //send a ringing response if (logger.isTraceEnabled()) logger.trace("will send ringing response: "); - this.sessionInitIQ = JinglePacketFactory - .createSessionInitiate( getProtocolProvider().getOurJID(), - this.peerJID, JingleIQ.generateSID(), offer); + ProtocolProviderServiceJabberImpl protocolProvider + = getProtocolProvider(); - getProtocolProvider().getConnection().sendPacket(sessionInitIQ); + this.sessionInitIQ + = JinglePacketFactory.createSessionInitiate( + protocolProvider.getOurJID(), + this.peerJID, + JingleIQ.generateSID(), + offer); + protocolProvider.getConnection().sendPacket(sessionInitIQ); } /** @@ -362,11 +367,11 @@ public void processSessionAccept(JingleIQ sessionInitIQ) { this.sessionInitIQ = sessionInitIQ; - List offer = sessionInitIQ.getContentList(); + List answer = sessionInitIQ.getContentList(); try { - getMediaHandler().processAnswer(offer); + getMediaHandler().processAnswer(answer); } catch(Exception exc) { @@ -495,8 +500,7 @@ private void sendAddVideoContent() try { - contents = getMediaHandler(). - createContentList(MediaType.VIDEO); + contents = getMediaHandler().createContentList(MediaType.VIDEO); } catch(Exception exc) { @@ -504,11 +508,16 @@ private void sendAddVideoContent() return; } - JingleIQ contentIQ = JinglePacketFactory - .createContentAdd(getProtocolProvider().getOurJID(), - this.peerJID, getJingleSID(), contents); + ProtocolProviderServiceJabberImpl protocolProvider + = getProtocolProvider(); + JingleIQ contentIQ + = JinglePacketFactory.createContentAdd( + protocolProvider.getOurJID(), + this.peerJID, + getJingleSID(), + contents); - getProtocolProvider().getConnection().sendPacket(contentIQ); + protocolProvider.getConnection().sendPacket(contentIQ); } /** @@ -730,9 +739,7 @@ public void processContentRemove(JingleIQ content) List contents = content.getContentList(); for(ContentPacketExtension ext : contents) - { getMediaHandler().removeRemoteContent(ext.getName()); - } } /** diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerJabberImpl.java index 1ab2ae38c..f81bc2511 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerJabberImpl.java @@ -6,8 +6,11 @@ */ package net.java.sip.communicator.impl.protocol.jabber; +import java.lang.reflect.*; import java.util.*; +import org.jivesoftware.smackx.packet.*; + import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*; import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.ContentPacketExtension.*; import net.java.sip.communicator.impl.protocol.jabber.jinglesdp.*; @@ -22,6 +25,7 @@ * An XMPP specific extension of the generic media handler. * * @author Emil Ivov + * @author Lyubomir Marinov */ public class CallPeerMediaHandlerJabberImpl extends CallPeerMediaHandler @@ -34,10 +38,10 @@ public class CallPeerMediaHandlerJabberImpl = Logger.getLogger(CallPeerMediaHandlerJabberImpl.class); /** - * A temporarily single transport manager that we use for generating - * addresses until we properly implement both ICE and Raw UDP managers. + * The TransportManager implementation handling our address + * management. */ - private final TransportManagerJabberImpl transportManager; + private TransportManagerJabberImpl transportManager; /** * The current description of the streams that we have going toward the @@ -76,8 +80,6 @@ public class CallPeerMediaHandlerJabberImpl public CallPeerMediaHandlerJabberImpl(CallPeerJabberImpl peer) { super(peer, peer); - - transportManager = new IceUdpTransportManager(peer); } /** @@ -94,12 +96,17 @@ public CallPeerMediaHandlerJabberImpl(CallPeerJabberImpl peer) * @throws OperationFailedException the exception that we wanted this method * to throw. */ - @Override - protected void throwOperationFailedException(String message, int errorCode, - Throwable cause) throws OperationFailedException + protected void throwOperationFailedException( + String message, + int errorCode, + Throwable cause) + throws OperationFailedException { ProtocolProviderServiceJabberImpl.throwOperationFailedException( - message, errorCode, cause, logger); + message, + errorCode, + cause, + logger); } /** @@ -188,8 +195,14 @@ protected MediaStream initStream(String streamName, List rtpExtensions) throws OperationFailedException { - MediaStream stream = super.initStream(connector, device, format, - target, direction, rtpExtensions); + MediaStream stream + = super.initStream( + connector, + device, + format, + target, + direction, + rtpExtensions); if(stream != null) stream.setName(streamName); @@ -218,8 +231,7 @@ public void processOffer(List offer) { // prepare to generate answers to all the incoming descriptions List answerContentList - = new ArrayList(offer.size()); - + = new ArrayList(offer.size()); boolean atLeastOneValidDescription = false; for (ContentPacketExtension content : offer) @@ -227,18 +239,19 @@ public void processOffer(List offer) remoteContentMap.put(content.getName(), content); RtpDescriptionPacketExtension description - = JingleUtils.getRtpDescription(content); + = JingleUtils.getRtpDescription(content); MediaType mediaType - = MediaType.parseString( description.getMedia() ); + = MediaType.parseString( description.getMedia() ); - List remoteFormats = JingleUtils.extractFormats( - description, getDynamicPayloadTypes()); + List remoteFormats + = JingleUtils.extractFormats( + description, + getDynamicPayloadTypes()); MediaDevice dev = getDefaultDevice(mediaType); - MediaDirection devDirection = (dev == null) - ? MediaDirection.INACTIVE - : dev.getDirection(); + MediaDirection devDirection + = (dev == null) ? MediaDirection.INACTIVE : dev.getDirection(); // Take the preference of the user with respect to streaming // mediaType into account. @@ -272,6 +285,22 @@ public void processOffer(List offer) int targetDataPort = target.getDataAddress().getPort(); + // transport + /* + * RawUdpTransportPacketExtension extends + * IceUdpTransportPacketExtension so getting + * IceUdpTransportPacketExtension should suffice. + */ + IceUdpTransportPacketExtension transport + = content.getFirstChildOfType( + IceUdpTransportPacketExtension.class); + + /* + * TODO If the offered transport is not supported, attempt to + * fall back to a supported one using transport-replace. + */ + setTransportManager(transport.getNamespace()); + if (mutuallySupportedFormats.isEmpty() || (devDirection == MediaDirection.INACTIVE) || (targetDataPort == 0)) @@ -331,10 +360,14 @@ public void processOffer(List offer) } if (!atLeastOneValidDescription) - ProtocolProviderServiceJabberImpl - .throwOperationFailedException("Offer contained no media " - + " formats or no valid media descriptions.", - OperationFailedException.ILLEGAL_ARGUMENT, null, logger); + { + ProtocolProviderServiceJabberImpl.throwOperationFailedException( + "Offer contained no media formats" + + " or no valid media descriptions.", + OperationFailedException.ILLEGAL_ARGUMENT, + null, + logger); + } //now, before we go, tell the transport manager to start our candidate //harvest @@ -354,20 +387,22 @@ public void processOffer(List offer) protected List generateSessionAccept() throws OperationFailedException { + TransportManagerJabberImpl transportManager = getTransportManager(); List sessAccept - = getTransportManager().wrapupHarvest(); + = transportManager.wrapupHarvest(); + CallPeerJabberImpl peer = getPeer(); //user answered an incoming call so we go through whatever content //entries we are initializing and init their corresponding streams for(ContentPacketExtension ourContent : sessAccept) { RtpDescriptionPacketExtension description - = JingleUtils.getRtpDescription(ourContent); + = JingleUtils.getRtpDescription(ourContent); MediaType type = MediaType.parseString(description.getMedia()); // StreamConnector connector - = getTransportManager().getStreamConnector(type); + = transportManager.getStreamConnector(type); //the device this stream would be reading from and writing to. MediaDevice dev = getDefaultDevice(type); @@ -382,7 +417,7 @@ protected List generateSessionAccept() //stream direction MediaDirection direction = JingleUtils.getDirection( - ourContent, !getPeer().isInitiator()); + ourContent, !peer.isInitiator()); //let's now see what was the format we announced as first and //configure the stream with it. @@ -425,15 +460,13 @@ protected List generateSessionAccept() if(ourContent.getChildExtensionsOfType( InputEvtPacketExtension.class) != null) { - OperationSetDesktopSharingClientJabberImpl client = - (OperationSetDesktopSharingClientJabberImpl) - this.getPeer().getProtocolProvider().getOperationSet( - OperationSetDesktopSharingClient.class); + OperationSetDesktopSharingClientJabberImpl client + = (OperationSetDesktopSharingClientJabberImpl) + peer.getProtocolProvider().getOperationSet( + OperationSetDesktopSharingClient.class); - if(client != null) - { + if (client != null) client.fireRemoteControlGranted(); - } } } return sessAccept; @@ -447,14 +480,14 @@ protected List generateSessionAccept() * @return the {@link ContentPacketExtension}s of stream that this * handler is prepared to initiate. * @throws OperationFailedException if we fail to create the descriptions - * for reasons like - problems with device interaction, allocating ports, - * etc. + * for reasons like problems with device interaction, allocating ports, etc. */ private ContentPacketExtension createContent(MediaDevice dev) + throws OperationFailedException { - MediaDirection direction = dev.getDirection().and( - getDirectionUserPreference( - dev.getMediaType())); + MediaDirection direction + = dev.getDirection().and( + getDirectionUserPreference(dev.getMediaType())); if(isLocallyOnHold()) direction = direction.and(MediaDirection.SENDONLY); @@ -469,10 +502,11 @@ private ContentPacketExtension createContent(MediaDevice dev) if(getPeer().getCall().isSipZrtpAttribute()) { ZrtpControl control = getZrtpControls().get(dev.getMediaType()); + if(control == null) { - control = JabberActivator.getMediaService() - .createZrtpControl(); + control + = JabberActivator.getMediaService().createZrtpControl(); getZrtpControls().put(dev.getMediaType(), control); } @@ -482,6 +516,7 @@ private ContentPacketExtension createContent(MediaDevice dev) { ZrtpHashPacketExtension hash = new ZrtpHashPacketExtension(); + hash.setVersion(helloHash[0]); hash.setValue(helloHash[1]); @@ -534,10 +569,15 @@ public List createContentList(MediaType mediaType) } //now add the transport elements - getTransportManager().startCandidateHarvest(mediaDescs); + TransportManagerJabberImpl transportManager = getTransportManager(); + + transportManager.startCandidateHarvest(mediaDescs); - //XXX ideally we wouldn't wrapup that quickly. we need to revisit this - return getTransportManager().wrapupHarvest(); + /* + * XXX Ideally, we wouldn't wrap up that quickly. We need to revisit + * this. + */ + return transportManager.wrapupHarvest(); } /** @@ -550,15 +590,14 @@ public List createContentList(MediaType mediaType) * streams that this handler is prepared to initiate. * * @throws OperationFailedException if we fail to create the descriptions - * for reasons like - problems with device interaction, allocating ports, - * etc. + * for reasons like problems with device interaction, allocating ports, etc. */ public List createContentList() throws OperationFailedException { //Audio Media Description List mediaDescs - = new ArrayList(); + = new ArrayList(); for (MediaType mediaType : MediaType.values()) { @@ -574,9 +613,11 @@ public List createContentList() if(direction != MediaDirection.INACTIVE) { - ContentPacketExtension content = createContentForOffer( - dev.getSupportedFormats(), direction, - dev.getSupportedExtensions()); + ContentPacketExtension content + = createContentForOffer( + dev.getSupportedFormats(), + direction, + dev.getSupportedExtensions()); //ZRTP if(getPeer().getCall().isSipZrtpAttribute()) @@ -607,8 +648,8 @@ public List createContentList() */ RtpDescriptionPacketExtension description = JingleUtils.getRtpDescription(content); - if(description.getMedia().equals( - MediaType.VIDEO.toString()) && localInputEvtAware) + if(description.getMedia().equals(MediaType.VIDEO.toString()) + && localInputEvtAware) { content.addChildExtension( new InputEvtPacketExtension()); @@ -622,18 +663,24 @@ public List createContentList() //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); + ProtocolProviderServiceJabberImpl.throwOperationFailedException( + "We couldn't find any active Audio/Video devices" + + " and couldn't create a call", + OperationFailedException.GENERAL_ERROR, + null, + logger); } //now add the transport elements - getTransportManager().startCandidateHarvest(mediaDescs); + TransportManagerJabberImpl transportManager = getTransportManager(); + + transportManager.startCandidateHarvest(mediaDescs); - //XXX ideally we wouldn't wrapup that quickly. we need to revisit this - return getTransportManager().wrapupHarvest(); + /* + * XXX Ideally, we wouldn't wrap up that quickly. We need to revisit + * this. + */ + return transportManager.wrapupHarvest(); } /** @@ -656,14 +703,15 @@ private ContentPacketExtension createContentForOffer( MediaDirection direction, List supportedExtensions) { - ContentPacketExtension content = JingleUtils.createDescription( - CreatorEnum.initiator, - supportedFormats.get(0).getMediaType().toString(), - JingleUtils.getSenders(direction, !getPeer().isInitiator()), - supportedFormats, - supportedExtensions, - getDynamicPayloadTypes(), - getRtpExtensionsRegistry()); + ContentPacketExtension content + = JingleUtils.createDescription( + CreatorEnum.initiator, + supportedFormats.get(0).getMediaType().toString(), + JingleUtils.getSenders(direction, !getPeer().isInitiator()), + supportedFormats, + supportedExtensions, + getDynamicPayloadTypes(), + getRtpExtensionsRegistry()); this.localContentMap.put(content.getName(), content); return content; @@ -708,10 +756,11 @@ public void reinitAllContents() * in this operation can synchronize to the mediaHandler instance to wait * processing to stop (method setState in CallPeer). */ - public void reinitContent(String name, + public void reinitContent( + String name, ContentPacketExtension.SendersEnum senders) - throws OperationFailedException, - IllegalArgumentException + throws OperationFailedException, + IllegalArgumentException { ContentPacketExtension ext = remoteContentMap.get(name); @@ -790,13 +839,13 @@ public void removeRemoteContent(String name) */ private void processContent(ContentPacketExtension content) throws OperationFailedException, - IllegalArgumentException + IllegalArgumentException { RtpDescriptionPacketExtension description - = JingleUtils.getRtpDescription(content); + = JingleUtils.getRtpDescription(content); MediaType mediaType - = MediaType.parseString( description.getMedia() ); + = MediaType.parseString( description.getMedia() ); //stream target MediaStreamTarget target @@ -882,24 +931,130 @@ public void processAnswer(List answer) throws OperationFailedException, IllegalArgumentException { - for ( ContentPacketExtension content : answer) + for (ContentPacketExtension content : answer) { remoteContentMap.put(content.getName(), content); processContent(content); } + + /* + * Since we've received (the) remote candidates from the peer, we can + * start checking them for connectivity. + */ + startConnectivityEstablishment(answer); } /** - * Returns the transport manager that is handling our address management. + * Gets the TransportManager implementation handling our address + * management. * - * @return the transport manager that is handling our address management. + * @return the TransportManager implementation handling our address + * management + * @see CallPeerMediaHandler#getTransportManager() */ public TransportManagerJabberImpl getTransportManager() { + if (transportManager == null) + { + CallPeerJabberImpl peer = getPeer(); + + if (peer.isInitiator()) + { + throw new IllegalStateException( + "The initiator is expected to specify the transport" + + " in their offer."); + } + else + { + ScServiceDiscoveryManager discoveryManager + = peer.getProtocolProvider().getDiscoveryManager(); + DiscoverInfo peerDiscoverInfo = peer.getDiscoverInfo(); + + if (discoveryManager.includesFeature( + ProtocolProviderServiceJabberImpl + .URN_XMPP_JINGLE_ICE_UDP_1) + && ((peerDiscoverInfo == null) + || peerDiscoverInfo.containsFeature( + ProtocolProviderServiceJabberImpl + .URN_XMPP_JINGLE_ICE_UDP_1))) + { + transportManager = new IceUdpTransportManager(peer); + } + else if (discoveryManager.includesFeature( + ProtocolProviderServiceJabberImpl + .URN_XMPP_JINGLE_RAW_UDP_0) + && ((peerDiscoverInfo == null) + || peerDiscoverInfo.containsFeature( + ProtocolProviderServiceJabberImpl + .URN_XMPP_JINGLE_RAW_UDP_0))) + { + transportManager = new RawUdpTransportManager(peer); + } + else if (logger.isDebugEnabled()) + { + logger.debug( + "No known Jingle transport supported" + + " by Jabber call peer " + + peer); + } + } + } return transportManager; } + /** + * Sets the TransportManager implementation to handle our address + * management by Jingle transport XML namespace. + * + * @param xmlns the Jingle transport XML namespace specifying the + * TransportManager implementation type to be set on this instance + * to handle our address management + * @throws IllegalArgumentException if the specified xmlns does not + * specify a (supported) TransportManager implementation type + */ + private void setTransportManager(String xmlns) + throws IllegalArgumentException + { + // Is this really going to be an actual change? + if ((transportManager != null) + && transportManager.getXmlNamespace().equals(xmlns)) + { + return; + } + + CallPeerJabberImpl peer = getPeer(); + + if (!peer + .getProtocolProvider() + .getDiscoveryManager().includesFeature(xmlns)) + { + throw new IllegalArgumentException( + "Unsupported Jingle transport " + xmlns); + } + + /* + * TODO The transportManager is going to be changed so it may need to be + * disposed prior to the change. + */ + + if (xmlns.equals( + ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_ICE_UDP_1)) + { + transportManager = new IceUdpTransportManager(peer); + } + else if (xmlns.equals( + ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RAW_UDP_0)) + { + transportManager = new RawUdpTransportManager(peer); + } + else + { + throw new IllegalArgumentException( + "Unsupported Jingle transport " + xmlns); + } + } + /** * Acts upon a notification received from the remote party indicating that * they've put us on/off hold. @@ -910,6 +1065,7 @@ public TransportManagerJabberImpl getTransportManager() public void setRemotelyOnHold(boolean onHold) { this.remotelyOnHold = onHold; + MediaStream audioStream = getStream(MediaType.AUDIO); MediaStream videoStream = getStream(MediaType.VIDEO); @@ -969,8 +1125,9 @@ private MediaDirection calculatePostHoldDirection(MediaStream stream) //2. check the user preference. MediaDevice device = stream.getDevice(); - postHoldDir = postHoldDir - .and(getDirectionUserPreference(device.getMediaType())); + postHoldDir + = postHoldDir.and( + getDirectionUserPreference(device.getMediaType())); //3. check our local hold status. if(isLocallyOnHold()) @@ -983,4 +1140,69 @@ private MediaDirection calculatePostHoldDirection(MediaStream stream) return postHoldDir; } + + /** + * Overrides {@link CallPeerMediaHandler#start()}. Prior to starting this + * CallPeerMediaHandler, makes sure connectivity establishment + * through the associated TransportManager has been started in + * order to determine the StreamConnectors and the + * MediaStreamTargets of the MediaStreams managed by this + * instance. + * + * @throws IllegalStateException if this CallPeerMediaHandler has + * not first seen a media description or has not generated an offer + * @see CallPeerMediaHandler#start() + */ + @Override + public void start() + throws IllegalStateException + { + if (getPeer().isInitiator()) + { + try + { + startConnectivityEstablishment(remoteContentMap.values()); + } + catch (OperationFailedException ofe) + { + throw new UndeclaredThrowableException(ofe); + } + } + + super.start(); + } + + /** + * Starts the connectivity establishment of the associated + * TransportManagerJabberImpl i.e. checks the connectivity between + * the local and the remote peers given the remote counterpart of the + * negotiation between them and sets the respective connectors and + * targets of the associated MediaStreams. + * + * @param remote the collection of ContentPacketExtensions which + * represents the remote counterpart of the negotiation between the local + * and the remote peers + * @throws OperationFailedException if anything goes wrong while starting + * the connectivity establishment or setting the connectors or + * targets of the associated MediaStreams + */ + private void startConnectivityEstablishment( + Collection remote) + throws OperationFailedException + { + TransportManagerJabberImpl transportManager = getTransportManager(); + + transportManager.startConnectivityEstablishment(remote); + for (MediaType mediaType : MediaType.values()) + { + MediaStream stream = getStream(mediaType); + + if (stream != null) + { + stream.setConnector( + transportManager.getStreamConnector(mediaType)); + stream.setTarget(transportManager.getStreamTarget(mediaType)); + } + } + } } 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 e8b357c19..04ca8cebb 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java @@ -6,10 +6,13 @@ */ package net.java.sip.communicator.impl.protocol.jabber; +import java.beans.*; +import java.net.*; import java.util.*; 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.netaddr.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.util.*; @@ -24,6 +27,7 @@ * candidate management. * * @author Emil Ivov + * @author Lyubomir Marinov */ public class IceUdpTransportManager extends TransportManagerJabberImpl @@ -32,12 +36,19 @@ public class IceUdpTransportManager * The Logger used by the IceUdpTransportManager * class and its instances for logging output. */ - private static final Logger logger = Logger - .getLogger(IceUdpTransportManager.class.getName()); + private static final Logger logger + = Logger.getLogger(IceUdpTransportManager.class); + + /** + * The ICE Component IDs in their common order used, for example, + * by DefaultStreamConnector, MediaStreamTarget. + */ + private static final int[] COMPONENT_IDS + = new int[] { Component.RTP, Component.RTCP }; /** * This is where we keep our answer between the time we get the offer and - * are ready with the answer; + * are ready with the answer. */ private List cpeList; @@ -54,7 +65,7 @@ public class IceUdpTransportManager * @param callPeer the {@link CallPeer} whose traffic we will be taking * care of. */ - protected IceUdpTransportManager(CallPeerJabberImpl callPeer) + public IceUdpTransportManager(CallPeerJabberImpl callPeer) { super(callPeer); @@ -70,12 +81,18 @@ protected IceUdpTransportManager(CallPeerJabberImpl callPeer) */ private Agent createIceAgent() { - ProtocolProviderServiceJabberImpl provider - = getCallPeer().getProtocolProvider(); + CallPeerJabberImpl peer = getCallPeer(); + ProtocolProviderServiceJabberImpl provider = peer.getProtocolProvider(); NetworkAddressManagerService namSer = getNetAddrMgr(); Agent agent = namSer.createIceAgent(); + /* + * XEP-0176: the initiator MUST include the ICE-CONTROLLING attribute, + * the responder MUST include the ICE-CONTROLLED attribute. + */ + agent.setControlling(!peer.isInitiator()); + //we will now create the harvesters JabberAccountID accID = (JabberAccountID)provider.getAccountID(); @@ -84,15 +101,18 @@ private Agent createIceAgent() //the default server is supposed to use the same user name and //password as the account itself. String username = provider.getOurJID(); - String password = JabberActivator - .getProtocolProviderFactory().loadPassword(accID); + String password + = JabberActivator.getProtocolProviderFactory().loadPassword( + accID); - StunCandidateHarvester autoHarvester = namSer.discoverStunServer( - accID.getService(), - StringUtils.getUTF8Bytes(username), - StringUtils.getUTF8Bytes(password) ); + StunCandidateHarvester autoHarvester + = namSer.discoverStunServer( + accID.getService(), + StringUtils.getUTF8Bytes(username), + StringUtils.getUTF8Bytes(password)); - logger.info("Auto discovered harvester is " + autoHarvester); + if (logger.isInfoEnabled()) + logger.info("Auto discovered harvester is " + autoHarvester); if (autoHarvester != null) agent.addCandidateHarvester(autoHarvester); @@ -110,9 +130,12 @@ private Agent createIceAgent() if(desc.isTurnSupported()) { //Yay! a TURN server - harvester = new TurnCandidateHarvester( - addr, new LongTermCredential( - desc.getUsername(), desc.getPassword())); + harvester + = new TurnCandidateHarvester( + addr, + new LongTermCredential( + desc.getUsername(), + desc.getPassword())); } else { @@ -120,7 +143,8 @@ addr, new LongTermCredential( harvester = new StunCandidateHarvester(addr); } - logger.info("Adding pre-conficugred harvester " + harvester); + if (logger.isInfoEnabled()) + logger.info("Adding pre-configured harvester " + harvester); agent.addCandidateHarvester(harvester); } @@ -128,6 +152,206 @@ addr, new LongTermCredential( return agent; } + /** + * Initializes a new StreamConnector to be used as the + * connector of the MediaStream with a specific + * MediaType. + * + * @param mediaType the MediaType of the MediaStream which + * is to have its connector set to the returned + * StreamConnector + * @return a new StreamConnector to be used as the + * connector of the MediaStream with the specified + * MediaType + * @throws OperationFailedException if anything goes wrong while + * initializing the new StreamConnector + */ + @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 StreamConnector to be used as the connector of + * the MediaStream with a specific MediaType. + * + * @param mediaType the MediaType of the MediaStream which + * is to have its connector set to the returned + * StreamConnector + * @return the StreamConnector to be used as the connector + * of the MediaStream with the specified MediaType + * @throws OperationFailedException if anything goes wrong while + * initializing the requested StreamConnector + * @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 DatagramSockets which represents the sockets to + * be used by the StreamConnector with the specified + * MediaType in the order of {@link #COMPONENT_IDS} if + * {@link #iceAgent} has completed. + * + * @param mediaType the MediaType of the StreamConnector + * for which the DatagramSockets are to be returned + * @return an array of DatagramSockets which represents the sockets + * to be used by the StreamConnector which the specified + * MediaType in the order of {@link #COMPONENT_IDS} if + * {@link #iceAgent} has completed; otherwise, null + */ + private DatagramSocket[] getStreamConnectorSockets(MediaType mediaType) + { + IceMediaStream stream = iceAgent.getStream(mediaType.toString()); + + 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) + return streamConnectorSockets; + } + return null; + } + + /** + * Implements {@link TransportManagerJabberImpl#getStreamTarget(MediaType)}. + * Gets the MediaStreamTarget to be used as the target of + * the MediaStream with a specific MediaType. + * + * @param mediaType the MediaType of the MediaStream which + * is to have its target set to the returned + * MediaStreamTarget + * @return the MediaStreamTarget to be used as the target + * of the MediaStream with the specified MediaType + * @see TransportManagerJabberImpl#getStreamTarget(MediaType) + */ + public MediaStreamTarget getStreamTarget(MediaType mediaType) + { + IceMediaStream stream = iceAgent.getStream(mediaType.toString()); + MediaStreamTarget streamTarget = null; + + 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) + { + streamTarget + = new MediaStreamTarget( + streamTargetAddresses[0 /* RTP */], + streamTargetAddresses[1 /* RTCP */]); + } + } + return streamTarget; + } + + /** + * Implements {@link TransportManagerJabberImpl#getXmlNamespace()}. Gets the + * XML namespace of the Jingle transport implemented by this + * TransportManagerJabberImpl. + * + * @return the XML namespace of the Jingle transport implemented by this + * TransportManagerJabberImpl + * @see TransportManagerJabberImpl#getXmlNamespace() + */ + public String getXmlNamespace() + { + return ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_ICE_UDP_1; + } + /** * Starts transport candidate harvest. This method should complete rapidly * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests @@ -158,15 +382,13 @@ public void startCandidateHarvest(List theirOffer, continue; RtpDescriptionPacketExtension rtpDesc - = (RtpDescriptionPacketExtension)ourContent - .getFirstChildOfType(RtpDescriptionPacketExtension.class); + = ourContent.getFirstChildOfType( + RtpDescriptionPacketExtension.class); IceMediaStream stream = createIceStream(rtpDesc.getMedia()); //we now generate the XMPP code containing the candidates. ourContent.addChildExtension(JingleUtils.createTransport(stream)); - - } this.cpeList = ourAnswer; @@ -191,12 +413,11 @@ public void startCandidateHarvest( for(ContentPacketExtension ourContent : ourOffer) { RtpDescriptionPacketExtension rtpDesc - = (RtpDescriptionPacketExtension)ourContent - .getFirstChildOfType(RtpDescriptionPacketExtension.class); + = ourContent.getFirstChildOfType( + RtpDescriptionPacketExtension.class); IceMediaStream stream = createIceStream(rtpDesc.getMedia()); - //we now generate the XMPP code containing the candidates. ourContent.addChildExtension(JingleUtils.createTransport(stream)); } @@ -225,11 +446,12 @@ private IceMediaStream createIceStream(String media) stream = getNetAddrMgr().createIceStream( nextMediaPortToTry, media, iceAgent); } - catch (Exception exc) + catch (Exception ex) { throw new OperationFailedException( "Failed to initialize stream " + media, - OperationFailedException.INTERNAL_ERROR); + OperationFailedException.INTERNAL_ERROR, + ex); } //let's now update the next port var as best we can: we would assume @@ -252,8 +474,6 @@ private IceMediaStream createIceStream(String media) return stream; } - - /** * Simply returns the list of local candidates that we gathered during the * harvest. This is a raw udp transport manager so there's no real wrapping @@ -267,29 +487,6 @@ public List wrapupHarvest() return cpeList; } - /** - * Looks through the cpExtList and returns the {@link - * ContentPacketExtension} with the specified name. - * - * @param cpExtList the list that we will be searching for a specific - * content. - * @param name the name of the content element we are looking for. - * @return the {@link ContentPacketExtension} with the specified name or - * null if no such content element exists. - */ - private ContentPacketExtension findContentByName( - List cpExtList, - String name) - { - for(ContentPacketExtension cpExt : cpExtList) - { - if(cpExt.getName().equals(name)) - return cpExt; - } - - return null; - } - /** * Returns a reference to the {@link NetworkAddressManagerService}. The only * reason this method exists is that {@link JabberActivator @@ -302,4 +499,127 @@ private static NetworkAddressManagerService getNetAddrMgr() { return JabberActivator.getNetworkAddressManagerService(); } + + /** + * Implements + * {@link TransportManagerJabberImpl#startConnectivityEstablishment(Collection)}. + * Starts the connectivity establishment of the associated ICE + * Agent and waits for it to complete. + * + * @param remote the collection of ContentPacketExtensions which + * represents the remote counterpart of the negotiation between the local + * and the remote peers + * @see TransportManagerJabberImpl#startConnectivityEstablishment(Collection) + */ + public void startConnectivityEstablishment( + Collection remote) + { + int generation = iceAgent.getGeneration(); + + for (ContentPacketExtension content : remote) + { + IceUdpTransportPacketExtension transport + = content.getFirstChildOfType( + IceUdpTransportPacketExtension.class); + + String ufrag = transport.getUfrag(); + + if (ufrag != null) + iceAgent.setRemoteUfrag(ufrag); + + String password = transport.getPassword(); + + if (password != null) + iceAgent.setRemotePassword(password); + + List candidates + = transport.getChildExtensionsOfType( + CandidatePacketExtension.class); + + RtpDescriptionPacketExtension description + = content.getFirstChildOfType( + RtpDescriptionPacketExtension.class); + IceMediaStream stream = iceAgent.getStream(description.getMedia()); + + for (CandidatePacketExtension candidate : candidates) + { + /* + * Is the remote candidate from the current generation of the + * iceAgent? + */ + if (candidate.getGeneration() != generation) + continue; + + Component component + = stream.getComponent(candidate.getComponent()); + + component.addRemoteCandidate( + new RemoteCandidate( + new TransportAddress( + candidate.getIP(), + candidate.getPort(), + Transport.parse( + candidate.getProtocol())), + component, + org.ice4j.ice.CandidateType.parse( + candidate.getType().toString()), + Integer.toString(candidate.getFoundation()), + candidate.getPriority())); + } + } + + final Object[] iceProcessingState = new Object[1]; + 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 == IceUdpTransportManager.this.iceAgent) + { + synchronized (iceProcessingState) + { + iceProcessingState[0] = newValue; + iceProcessingState.notify(); + } + } + } + } + }; + + iceAgent.addStateChangeListener(stateChangeListener); + iceAgent.startConnectivityEstablishment(); + + // Wait for the connectivity checks to finish. + boolean interrupted = false; + + synchronized (iceProcessingState) + { + while (iceProcessingState[0] == null) + { + try + { + iceProcessingState.wait(); + } + catch (InterruptedException ie) + { + interrupted = true; + } + } + } + if (interrupted) + Thread.currentThread().interrupt(); + } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java b/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java index 1e889f103..a91fc7b74 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java @@ -9,6 +9,7 @@ import java.util.*; 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.protocol.*; @@ -17,15 +18,23 @@ * single candidate pair (i.e. RTP and RTCP). * * @author Emil Ivov + * @author Lyubomir Marinov */ public class RawUdpTransportManager extends TransportManagerJabberImpl { /** - * This is where we keep our answer between the time we get the offer and - * are ready with the answer; + * The list of ContentPacketExtensions which represents the local + * counterpart of the negotiation between the local and the remote peers. */ - private List cpeList; + private List local; + + /** + * The collection of ContentPacketExtensions which represents the + * remote counterpart of the negotiation between the local and the remote + * peers. + */ + private Collection remote; /** * Creates a new instance of this transport manager, binding it to the @@ -34,11 +43,61 @@ public class RawUdpTransportManager * @param callPeer the {@link CallPeer} whose traffic we will be taking * care of. */ - protected RawUdpTransportManager(CallPeerJabberImpl callPeer) + public RawUdpTransportManager(CallPeerJabberImpl callPeer) { super(callPeer); } + /** + * Implements {@link TransportManagerJabberImpl#getStreamTarget(MediaType)}. + * Gets the MediaStreamTarget to be used as the target of + * the MediaStream with a specific MediaType. + * + * @param mediaType the MediaType of the MediaStream which + * is to have its target set to the returned + * MediaStreamTarget + * @return the MediaStreamTarget to be used as the target + * of the MediaStream with the specified MediaType + * @see TransportManagerJabberImpl#getStreamTarget(MediaType) + */ + public MediaStreamTarget getStreamTarget(MediaType mediaType) + { + MediaStreamTarget streamTarget = null; + + if (remote != null) + { + for (ContentPacketExtension content : remote) + { + RtpDescriptionPacketExtension rtpDescription + = content.getFirstChildOfType( + RtpDescriptionPacketExtension.class); + MediaType contentMediaType + = MediaType.parseString(rtpDescription.getMedia()); + + if (mediaType.equals(contentMediaType)) + { + streamTarget = JingleUtils.extractDefaultTarget(content); + break; + } + } + } + return streamTarget; + } + + /** + * Implements {@link TransportManagerJabberImpl#getXmlNamespace()}. Gets the + * XML namespace of the Jingle transport implemented by this + * TransportManagerJabberImpl. + * + * @return the XML namespace of the Jingle transport implemented by this + * TransportManagerJabberImpl + * @see TransportManagerJabberImpl#getXmlNamespace() + */ + public String getXmlNamespace() + { + return ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RAW_UDP_0; + } + /** * Starts transport candidate harvest. This method should complete rapidly * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests @@ -61,8 +120,8 @@ public void startCandidateHarvest(List theirOffer, for(ContentPacketExtension content : theirOffer) { RtpDescriptionPacketExtension rtpDesc - = (RtpDescriptionPacketExtension)content - .getFirstChildOfType(RtpDescriptionPacketExtension.class); + = content.getFirstChildOfType( + RtpDescriptionPacketExtension.class); StreamConnector connector = getStreamConnector( MediaType.parseString( rtpDesc.getMedia())); @@ -81,7 +140,7 @@ public void startCandidateHarvest(List theirOffer, cpExt.addChildExtension(ourTransport); } - this.cpeList = ourAnswer; + this.local = ourAnswer; } /** @@ -91,39 +150,40 @@ public void startCandidateHarvest(List theirOffer, * harvest would then need to be concluded in the {@link #wrapupHarvest()} * method which would be called once we absolutely need the candidates. * - * @param ourAnswer the content list that should tell us how many stream + * @param ourOffer the content list that should tell us how many stream * connectors we actually need. * * @throws OperationFailedException in case we fail allocating ports */ - public void startCandidateHarvest( - List ourAnswer) + public void startCandidateHarvest(List ourOffer) throws OperationFailedException { - for(ContentPacketExtension content : ourAnswer) + for(ContentPacketExtension content : ourOffer) { RtpDescriptionPacketExtension rtpDesc - = (RtpDescriptionPacketExtension)content - .getFirstChildOfType(RtpDescriptionPacketExtension.class); + = content.getFirstChildOfType( + RtpDescriptionPacketExtension.class); - StreamConnector connector = getStreamConnector( - MediaType.parseString( rtpDesc.getMedia())); + StreamConnector connector + = getStreamConnector( + MediaType.parseString( rtpDesc.getMedia())); RawUdpTransportPacketExtension ourTransport - = createTransport(connector); + = createTransport(connector); - //now add our transport to our answer + //now add our transport to our offer ContentPacketExtension cpExt - = findContentByName(ourAnswer, content.getName()); + = findContentByName(ourOffer, content.getName()); cpExt.addChildExtension(ourTransport); } - this.cpeList = ourAnswer; + + this.local = ourOffer; } /** - * Creates a raw udp transport element according to the specified stream - * connector + * Creates a raw UDP transport element according to the specified stream + * connector. * * @param connector the connector that we'd like to describe within the * transport element. @@ -138,7 +198,7 @@ private RawUdpTransportPacketExtension createTransport( = new RawUdpTransportPacketExtension(); // create and add candidates that correspond to the stream connector - // rtp + // RTP CandidatePacketExtension rtpCand = new CandidatePacketExtension(); rtpCand.setComponent(CandidatePacketExtension.RTP_COMPONENT_ID); rtpCand.setGeneration(getCurrentGeneration()); @@ -150,7 +210,7 @@ private RawUdpTransportPacketExtension createTransport( ourTransport.addCandidate(rtpCand); - // rtcp + // RTCP CandidatePacketExtension rtcpCand = new CandidatePacketExtension(); rtcpCand.setComponent(CandidatePacketExtension.RTCP_COMPONENT_ID); rtcpCand.setGeneration(getCurrentGeneration()); @@ -167,7 +227,7 @@ private RawUdpTransportPacketExtension createTransport( /** * Simply returns the list of local candidates that we gathered during the - * harvest. This is a raw udp transport manager so there's no real wraping + * harvest. This is a raw UDP transport manager so there's no real wrapping * up to do. * * @return the list of local candidates that we gathered during the @@ -175,29 +235,25 @@ private RawUdpTransportPacketExtension createTransport( */ public List wrapupHarvest() { - return cpeList; + return local; } /** - * Looks through the cpExtList and returns the {@link - * ContentPacketExtension} with the specified name. + * Implements + * {@link TransportManagerJabberImpl#startConnectivityEstablishment(Collection)}. + * Since this represents a raw UDP transport, performs no connectivity + * checks and just remembers the remote counterpart of the negotiation + * between the local and the remote peers in order to be able to report the + * MediaStreamTargets upon request. * - * @param cpExtList the list that we will be searching for a specific - * content. - * @param name the name of the content element we are looking for. - * @return the {@link ContentPacketExtension} with the specified name or - * null if no such content element exists. + * @param remote the collection of ContentPacketExtensions which + * represent the remote counterpart of the negotiation between the local and + * the remote peers + * @see TransportManagerJabberImpl#startConnectivityEstablishment(Collection) */ - private ContentPacketExtension findContentByName( - List cpExtList, - String name) + public void startConnectivityEstablishment( + Collection remote) { - for(ContentPacketExtension cpExt : cpExtList) - { - if(cpExt.getName().equals(name)) - return cpExt; - } - - return null; + this.remote = remote; } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/TransportManagerJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/TransportManagerJabberImpl.java index 1fd8f5179..5d7e5c33f 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/TransportManagerJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/TransportManagerJabberImpl.java @@ -10,6 +10,7 @@ import java.util.*; import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*; +import net.java.sip.communicator.service.neomedia.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.media.*; @@ -21,6 +22,7 @@ * it has not yet completed. * * @author Emil Ivov + * @author Lyubomir Marinov */ public abstract class TransportManagerJabberImpl extends TransportManager @@ -82,6 +84,27 @@ protected String getNextID() return Integer.toString(nextID++); } + /** + * Gets the MediaStreamTarget to be used as the target of + * the MediaStream with a specific MediaType. + * + * @param mediaType the MediaType of the MediaStream which + * is to have its target set to the returned + * MediaStreamTarget + * @return the MediaStreamTarget to be used as the target + * of the MediaStream with the specified MediaType + */ + public abstract MediaStreamTarget getStreamTarget(MediaType mediaType); + + /** + * Gets the XML namespace of the Jingle transport implemented by this + * TransportManagerJabberImpl. + * + * @return the XML namespace of the Jingle transport implemented by this + * TransportManagerJabberImpl + */ + public abstract String getXmlNamespace(); + /** * Returns the generation that our current candidates belong to. * @@ -134,7 +157,7 @@ public abstract void startCandidateHarvest( * @throws OperationFailedException if we fail to allocate a port number. */ public abstract void startCandidateHarvest( - List ourOffer) + List ourOffer) throws OperationFailedException; /** @@ -146,4 +169,39 @@ public abstract void startCandidateHarvest( * a new instance) and that we have updated with transport lists. */ public abstract List wrapupHarvest(); + + /** + * Looks through the cpExtList and returns the {@link + * ContentPacketExtension} with the specified name. + * + * @param cpExtList the list that we will be searching for a specific + * content. + * @param name the name of the content element we are looking for. + * @return the {@link ContentPacketExtension} with the specified name or + * null if no such content element exists. + */ + protected ContentPacketExtension findContentByName( + List cpExtList, + String name) + { + for(ContentPacketExtension cpExt : cpExtList) + { + if(cpExt.getName().equals(name)) + return cpExt; + } + return null; + } + + /** + * Starts the connectivity establishment of this + * TransportManagerJabberImpl 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 ContentPacketExtensions which + * represents the remote counterpart of the negotiation between the local + * and the remote peer + */ + public abstract void startConnectivityEstablishment( + Collection remote); } diff --git a/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java b/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java index b7578309c..8c16603d5 100644 --- a/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java +++ b/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java @@ -173,7 +173,7 @@ public abstract class CallPeerMediaHandler< * Indicates whether this handler has already started at least one of its * streams, at least once. */ - private boolean isStarted = false; + private boolean started = false; /** * Contains all dynamic payload type mappings that have been made for this @@ -387,19 +387,15 @@ public synchronized void close() protected void closeStream(MediaType type) { if( type == MediaType.AUDIO) - { setAudioStream(null); - - } else - { setVideoStream(null); - } - this.getTransportManager().closeStreamConnector(type); + getTransportManager().closeStreamConnector(type); - // clears the zrtp controls used for current call. + // Clear the ZRTP controls used for the associated Call. ZrtpControl zrtpCtrl = zrtpControls.get(type); + if (zrtpCtrl != null) { zrtpCtrl.cleanup(); @@ -1126,34 +1122,35 @@ protected MediaStream initStream(StreamConnector connector, List rtpExtensions) throws OperationFailedException { - MediaStream stream = null; - - if (device.getMediaType() == MediaType.AUDIO) - stream = this.audioStream; - else - { - stream = this.videoStream; - } + MediaType mediaType = device.getMediaType(); + MediaStream stream = getStream(mediaType); if (stream == null) { + if (logger.isTraceEnabled() && (mediaType != format.getMediaType())) + logger.trace("The media types of device and format differ."); + // check whether a control already exists - ZrtpControl control = zrtpControls.get(format.getMediaType()); + ZrtpControl control = zrtpControls.get(mediaType); MediaService mediaService - = ProtocolMediaActivator.getMediaService(); + = ProtocolMediaActivator.getMediaService(); if(control == null) stream = mediaService.createMediaStream(connector, device); else - stream = mediaService.createMediaStream( - connector, device, control); + { + stream + = mediaService.createMediaStream( + connector, device, control); + } } else { //this is a reinit } - return configureStream( + return + configureStream( device, format, target, direction, rtpExtensions, stream); } @@ -1209,7 +1206,7 @@ protected MediaStream configureStream( MediaDevice device, if(peer.getCall().isDefaultEncrypted()) { // we use the audio stream for master stream - // when using zrtp multistreams + // when using ZRTP multistreams. ZrtpControl zrtpControl = stream.getZrtpControl(); zrtpControl.setZrtpListener(zrtpController); @@ -1220,9 +1217,9 @@ protected MediaStream configureStream( MediaDevice device, } /** - * Send empty UDP packet to target destination data/control ports - * in order to open port on NAT or RTP proxy if any. In order to be really - * efficient, this method should be called after we send our offer or answer + * Sends empty UDP packets to target destination data/control ports in order + * to open port on NAT or RTP proxy if any. In order to be really efficient, + * this method should be called after we send our offer or answer. * * @param target MediaStreamTarget */ @@ -1536,16 +1533,15 @@ protected DynamicRTPExtensionsRegistry getRtpExtensionsRegistry() */ public boolean isStarted() { - return isStarted; + return started; } /** - * Returns true if this handler has already started at least one - * of its streams, at least once, and false otherwise. If the - * handler is already started, this method has no effect. + * Starts this CallPeerMediaHandler. If it has already been + * started, does nothing. * * @throws IllegalStateException if this method is called without this - * handler having first seen a media description or having generate an + * handler having first seen a media description or having generated an * offer. */ public void start() @@ -1555,21 +1551,22 @@ public void start() return; MediaStream stream = getStream(MediaType.AUDIO); - if ( stream != null && !stream.isStarted() - && isLocalAudioTransmissionEnabled()) + if ((stream != null) + && !stream.isStarted() + && isLocalAudioTransmissionEnabled()) { stream.start(); } stream = getStream(MediaType.VIDEO); - if ( stream != null && !stream.isStarted()) + if ((stream != null) && !stream.isStarted()) { stream.start(); // send empty packet to deblock some kind of RTP proxy to let just // one user sends its video - if(stream instanceof VideoMediaStream - && !isLocalVideoTransmissionEnabled()) + if ((stream instanceof VideoMediaStream) + && !isLocalVideoTransmissionEnabled()) { sendHolePunchPacket(stream.getTarget()); } @@ -1606,9 +1603,11 @@ protected abstract void throwOperationFailedException( String message, throws OperationFailedException; /** - * Returns the transport manager that is handling our address management. + * Gets the TransportManager implementation handling our address + * management. * - * @return the transport manager that is handling our address management. + * @return the TransportManager implementation handling our address + * management */ public abstract TransportManager getTransportManager(); } diff --git a/src/net/java/sip/communicator/service/protocol/media/TransportManager.java b/src/net/java/sip/communicator/service/protocol/media/TransportManager.java index c6986a34a..ede43c69b 100644 --- a/src/net/java/sip/communicator/service/protocol/media/TransportManager.java +++ b/src/net/java/sip/communicator/service/protocol/media/TransportManager.java @@ -8,6 +8,7 @@ import java.net.*; +import net.java.sip.communicator.service.configuration.*; import net.java.sip.communicator.service.neomedia.*; import net.java.sip.communicator.service.netaddr.*; import net.java.sip.communicator.service.protocol.*; @@ -21,6 +22,7 @@ * or CallPeerJabberImpl * * @author Emil Ivov + * @author Lyubomir Marinov */ public abstract class TransportManager> { @@ -28,8 +30,8 @@ public abstract class TransportManager> * The Logger used by the TransportManager * class and its instances for logging output. */ - private static final Logger logger = Logger - .getLogger(TransportManager.class.getName()); + private static final Logger logger + = Logger.getLogger(TransportManager.class); /** * The minimum port number that we'd like our RTP sockets to bind upon. @@ -48,21 +50,17 @@ public abstract class TransportManager> protected static int nextMediaPortToTry = minMediaPort; /** - * The RTP/RTCP socket couple that this media handler should use to send - * and receive audio flows through. - */ - private StreamConnector audioStreamConnector = null; - - /** - * The RTP/RTCP socket couple that this media handler should use to send - * and receive video flows through. + * The {@link MediaAwareCallPeer} whose traffic we will be taking care of. */ - private StreamConnector videoStreamConnector = null; + private U callPeer; /** - * The {@link MediaAwareCallPeer} whose traffic we will be taking care of. + * The RTP/RTCP socket couples that this TransportManager uses to + * send and receive media flows through indexed by MediaType + * (ordinal). */ - private U callPeer; + private final StreamConnector[] streamConnectors + = new StreamConnector[MediaType.values().length]; /** * Creates a new instance of this transport manager, binding it to the @@ -83,8 +81,8 @@ protected TransportManager(U callPeer) * been initialized for this mediaType yet or in case one * of its underlying sockets has been closed. * - * @param mediaType the MediaType that we'd like to create a connector for. - * + * @param mediaType the MediaType that we'd like to create a + * connector for. * @return this media handler's StreamConnector for the specified * mediaType. * @@ -94,28 +92,18 @@ protected TransportManager(U callPeer) public StreamConnector getStreamConnector(MediaType mediaType) throws OperationFailedException { - if (mediaType == MediaType.AUDIO) - { - if ( audioStreamConnector == null - || audioStreamConnector.getDataSocket().isClosed() - || audioStreamConnector.getControlSocket().isClosed()) - { - audioStreamConnector = createStreamConnector(); - } + StreamConnector streamConnector = streamConnectors[mediaType.ordinal()]; - return audioStreamConnector; - } - else + if ((streamConnector == null) + || streamConnector.getDataSocket().isClosed() + || streamConnector.getControlSocket().isClosed()) { - if ( videoStreamConnector == null - || videoStreamConnector.getDataSocket().isClosed() - || videoStreamConnector.getControlSocket().isClosed()) - { - videoStreamConnector = createStreamConnector(); - } - - return videoStreamConnector; + streamConnectors[mediaType.ordinal()] + = streamConnector + = createStreamConnector(mediaType); } + + return streamConnector; } /** @@ -127,31 +115,30 @@ public StreamConnector getStreamConnector(MediaType mediaType) public void closeStreamConnector(MediaType mediaType) { StreamConnector connector - = (mediaType == MediaType.VIDEO) - ? videoStreamConnector - : audioStreamConnector; - - if(connector == null) - return; + = streamConnectors[mediaType.ordinal()]; - synchronized(connector) + if(connector != null) { - connector.getDataSocket().close(); - connector.getControlSocket().close(); - connector = null; + synchronized(connector) + { + connector.getDataSocket().close(); + connector.getControlSocket().close(); + streamConnectors[mediaType.ordinal()] = null; + } } - } /** * Creates a media StreamConnector. The method takes into account * the minimum and maximum media port boundaries. * + * @param mediaType the MediaType of the stream for which a new + * StreamConnector is to be created * @return a new StreamConnector. * * @throws OperationFailedException if we fail binding the the sockets. */ - protected StreamConnector createStreamConnector() + protected StreamConnector createStreamConnector(MediaType mediaType) throws OperationFailedException { NetworkAddressManagerService nam @@ -192,7 +179,8 @@ protected StreamConnector createStreamConnector() { throw new OperationFailedException( "Failed to allocate the network ports necessary for the call.", - OperationFailedException.INTERNAL_ERROR, exc); + OperationFailedException.INTERNAL_ERROR, + exc); } //make sure that next time we don't try to bind on occupied ports @@ -201,11 +189,7 @@ protected StreamConnector createStreamConnector() if (nextMediaPortToTry > maxMediaPort -1)// take RTCP into account. nextMediaPortToTry = minMediaPort; - //create the RTCP socket - DefaultStreamConnector connector = new DefaultStreamConnector( - rtpSocket, rtcpSocket); - - return connector; + return new DefaultStreamConnector(rtpSocket, rtcpSocket); } /** @@ -219,10 +203,12 @@ protected void initializePortNumbers() maxMediaPort = 6000; //then set to anything the user might have specified. + ConfigurationService configuration + = ProtocolMediaActivator.getConfigurationService(); String minPortNumberStr - = ProtocolMediaActivator.getConfigurationService() - .getString(OperationSetBasicTelephony - .MIN_MEDIA_PORT_NUMBER_PROPERTY_NAME); + = configuration.getString( + OperationSetBasicTelephony + .MIN_MEDIA_PORT_NUMBER_PROPERTY_NAME); if (minPortNumberStr != null) { @@ -239,8 +225,8 @@ protected void initializePortNumbers() } String maxPortNumberStr - = ProtocolMediaActivator.getConfigurationService() - .getString(OperationSetBasicTelephony + = configuration.getString( + OperationSetBasicTelephony .MAX_MEDIA_PORT_NUMBER_PROPERTY_NAME); if (maxPortNumberStr != null) @@ -270,7 +256,7 @@ protected void initializePortNumbers() * descriptions so we already have a specific local address assigned to them * at the time we get ready to create the c= and o= fields. It is therefore * better to try and return one of these addresses before trying the net - * address manager again ang running the slight risk of getting a different + * address manager again and running the slight risk of getting a different * address. * * @return an InetAddress that we use in one of the @@ -278,14 +264,17 @@ protected void initializePortNumbers() */ public InetAddress getLastUsedLocalHost() { - if (audioStreamConnector != null) - return audioStreamConnector.getDataSocket().getLocalAddress(); + for (MediaType mediaType : MediaType.values()) + { + StreamConnector streamConnector + = streamConnectors[mediaType.ordinal()]; - if (videoStreamConnector != null) - return videoStreamConnector.getDataSocket().getLocalAddress(); + if (streamConnector != null) + return streamConnector.getDataSocket().getLocalAddress(); + } NetworkAddressManagerService nam - = ProtocolMediaActivator.getNetworkAddressManagerService(); + = ProtocolMediaActivator.getNetworkAddressManagerService(); InetAddress intendedDestination = getIntendedDestination(getCallPeer()); return nam.getLocalHost(intendedDestination);