diff options
author | Lyubomir Marinov <lyubomir.marinov@jitsi.org> | 2012-08-21 17:56:38 +0000 |
---|---|---|
committer | Lyubomir Marinov <lyubomir.marinov@jitsi.org> | 2012-08-21 17:56:38 +0000 |
commit | 073d01525102b87c8a4c0b85c8636a264cc3dac2 (patch) | |
tree | 6907fe747e7ec10454417ba6e63a8f16f8c8f30e /src | |
parent | c3f189792fa30712a9516b226a135dac6f95691b (diff) | |
download | jitsi-073d01525102b87c8a4c0b85c8636a264cc3dac2.zip jitsi-073d01525102b87c8a4c0b85c8636a264cc3dac2.tar.gz jitsi-073d01525102b87c8a4c0b85c8636a264cc3dac2.tar.bz2 |
Fixes issues with video conferencing such as the display of a non-focus participant's video to other non-focus participants and the stopping of the streaming between non-focus participants upon stopping of the streaming of the focus' local video.
Diffstat (limited to 'src')
5 files changed, 1068 insertions, 1147 deletions
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/AbstractCallPeerJabberGTalkImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/AbstractCallPeerJabberGTalkImpl.java index 188482b..917c47b 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/AbstractCallPeerJabberGTalkImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/AbstractCallPeerJabberGTalkImpl.java @@ -6,6 +6,8 @@ */ package net.java.sip.communicator.impl.protocol.jabber; +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.*; @@ -17,14 +19,12 @@ import org.jivesoftware.smackx.packet.*; * of Jabber and Gtalk protocols. * * @author Vincent Lucas + * @author Lyubomir Marinov */ public abstract class AbstractCallPeerJabberGTalkImpl <T extends AbstractCallJabberGTalkImpl<?>, - U extends AbstractCallPeerMediaHandlerJabberGTalkImpl<?>> - extends MediaAwareCallPeer< - T, - U, - ProtocolProviderServiceJabberImpl> + U extends AbstractCallPeerMediaHandlerJabberGTalkImpl<?>> + extends MediaAwareCallPeer<T, U, ProtocolProviderServiceJabberImpl> { /** * The <tt>Logger</tt> used by the <tt>AbstractCallPeerJabberGTalkImpl</tt> @@ -34,14 +34,20 @@ public abstract class AbstractCallPeerJabberGTalkImpl = Logger.getLogger(AbstractCallPeerJabberGTalkImpl.class); /** - * The jabber address of this peer + * Any discovery information that we have for this peer. */ - protected String peerJID = null; + private DiscoverInfo discoverInfo; /** - * Any discovery information that we have for this peer. + * The indicator which determines whether this peer was initiated the + * session. */ - private DiscoverInfo discoverInfo; + protected boolean initiator = false; + + /** + * The jabber address of this peer + */ + protected String peerJID; /** * Creates a new call peer with address <tt>peerAddress</tt>. @@ -50,9 +56,7 @@ public abstract class AbstractCallPeerJabberGTalkImpl * peer. * @param owningCall the call that contains this call peer. */ - protected AbstractCallPeerJabberGTalkImpl( - String peerAddress, - T owningCall) + protected AbstractCallPeerJabberGTalkImpl(String peerAddress, T owningCall) { super(owningCall); @@ -60,14 +64,29 @@ public abstract class AbstractCallPeerJabberGTalkImpl } /** - * Sets the service discovery information that we have for this peer. + * Returns a String locator for that peer. * - * @param discoverInfo the discovery information that we have obtained for - * this peer. + * @return the peer's address or phone number. */ - public void setDiscoverInfo(DiscoverInfo discoverInfo) + public String getAddress() { - this.discoverInfo = discoverInfo; + return peerJID; + } + + /** + * 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()); } /** @@ -81,7 +100,46 @@ public abstract class AbstractCallPeerJabberGTalkImpl } /** - * Retrives the DiscoverInfo for a given peer identified by its URI. + * 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; + } + + /** + * Returns full URI of the address. + * + * @return full URI of the address + */ + public String getURI() + { + return "xmpp:" + peerJID; + } + + /** + * Determines whether this peer initiated the session. Note that if this + * peer is the initiator of the session, then we are the responder! + * + * @return <tt>true</tt> if this peer initiated the session; <tt>false</tt>, + * otherwise (i.e. if _we_ initiated the session). + */ + public boolean isInitiator() + { + return initiator; + } + + /** + * Retrieves the DiscoverInfo for a given peer identified by its URI. * * @param calleeURI The URI of the call peer. * @param ppsJabberImpl The call protocol provider service. @@ -105,4 +163,37 @@ public abstract class AbstractCallPeerJabberGTalkImpl logger.warn("could not retrieve info for " + calleeURI, ex); } } + + /** + * 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) + { + if (!peerJID.equals(address)) + { + String oldAddress = getAddress(); + + peerJID = address; + + fireCallPeerChangeEvent( + CallPeerChangeEvent.CALL_PEER_ADDRESS_CHANGE, + oldAddress, + address); + } + } + + /** + * Sets the service discovery information that we have for this peer. + * + * @param discoverInfo the discovery information that we have obtained for + * this peer. + */ + public void setDiscoverInfo(DiscoverInfo discoverInfo) + { + this.discoverInfo = discoverInfo; + } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerGTalkImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerGTalkImpl.java index 30e6644..a3329c3 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerGTalkImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerGTalkImpl.java @@ -12,7 +12,6 @@ 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.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; import org.jivesoftware.smack.packet.*; @@ -21,11 +20,11 @@ import org.jivesoftware.smack.packet.*; * Implements a Google Talk <tt>CallPeer</tt>. * * @author Sebastien Vincent + * @author Lyubomir Marinov */ public class CallPeerGTalkImpl extends AbstractCallPeerJabberGTalkImpl - <CallGTalkImpl, - CallPeerMediaHandlerGTalkImpl> + <CallGTalkImpl, CallPeerMediaHandlerGTalkImpl> { /** * The <tt>Logger</tt> used by the <tt>CallPeerGTalkImpl</tt> class and its @@ -35,25 +34,49 @@ public class CallPeerGTalkImpl = Logger.getLogger(CallPeerGTalkImpl.class); /** - * The {@link SessionIQ} that created the session that this call represents. + * Returns whether or not the <tt>CallPeer</tt> is an Android phone or + * a call pass throught Google Voice or uses Google Talk client. + * + * We base the detection of the JID's resource which in the case of Android + * is android/Vtok/Talk.vXXXXXXX (where XXXXXX is a suite of + * numbers/letters). */ - private SessionIQ sessionInitIQ = null; + private static boolean isAndroidOrVtokOrTalkClient(String fullJID) + { + int idx = fullJID.indexOf('/'); + + if(idx != -1) + { + String res = fullJID.substring(idx + 1); + if(res.startsWith("android") || res.startsWith("Vtok") || + res.startsWith("Talk.v")) + { + return true; + } + } + + if(fullJID.contains( + "@" + ProtocolProviderServiceJabberImpl.GOOGLE_VOICE_DOMAIN)) + return true; + + return false; + } /** - * Indicates whether this peer was the one that initiated the session. + * Temporary variable for handling client like FreeSwitch that sends + * "accept" message before sending candidates. */ - protected boolean isInitiator = false; + private SessionIQ sessAcceptedWithNoCands = null; /** - * Session ID. + * The {@link SessionIQ} that created the session that this call represents. */ - private String sid = null; + private SessionIQ sessionInitIQ = null; /** - * Temporary variable for handling client like FreeSwitch that sends - * "accept" message before sending candidates. + * Session ID. */ - private SessionIQ sessAcceptedWithNoCands = null; + private String sid = null; /** * Creates a new call peer with address <tt>peerAddress</tt>. @@ -69,131 +92,55 @@ public class CallPeerGTalkImpl } /** - * Returns a String locator for that peer. - * - * @return the peer's address or phone number. - */ - public String getAddress() - { - return peerJID; - } - - /** - * Returns full URI of the address. + * Indicates a user request to answer an incoming call from this + * <tt>CallPeer</tt>. * - * @return full URI of the address - */ - public String getURI() - { - return "xmpp:" + 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. + * Sends an OK response to <tt>callPeer</tt>. Make sure that the call + * peer contains an SDP description when you call this method. * - * @param address The address of this call peer. + * @throws OperationFailedException if we fail to create or send the + * response. */ - public void setAddress(String address) + public synchronized void answer() + throws OperationFailedException { - String oldAddress = getAddress(); - - if(peerJID.equals(address)) - return; - - this.peerJID = address; - //Fire the Event - fireCallPeerChangeEvent( - CallPeerChangeEvent.CALL_PEER_ADDRESS_CHANGE, - oldAddress, - address); - } + RtpDescriptionPacketExtension answer = null; - /** - * Returns a human readable name representing this peer. - * - * @return a String containing a name for that peer. - */ - public String getDisplayName() - { - if (getCall() != null) + try { - Contact contact = getContact(); - - if (contact != null) - return contact.getDisplayName(); + getMediaHandler().getTransportManager(). + wrapupConnectivityEstablishment(); + answer = getMediaHandler().generateSessionAccept(true); } - 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()); - } + catch(IllegalArgumentException e) + { + sessAcceptedWithNoCands = new SessionIQ(); - /** - * 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; + // HACK apparently FreeSwitch need to have accept before + answer = getMediaHandler().generateSessionAccept(false); - RtpDescriptionPacketExtension description = null; + SessionIQ response + = GTalkPacketFactory.createSessionAccept( + sessionInitIQ.getTo(), + sessionInitIQ.getFrom(), + getSessionID(), + answer); - for(PacketExtension ext : sessionInitIQ.getExtensions()) - { - if(ext.getElementName().equals( - RtpDescriptionPacketExtension.ELEMENT_NAME)) - { - description = (RtpDescriptionPacketExtension)ext; - break; - } + getProtocolProvider().getConnection().sendPacket(response); + return; } - - if(description == null) + catch(Exception exc) { - logger.info("No description in incoming session initiate"); + logger.info("Failed to answer an incoming call", exc); - //send an error response; - String reasonText = "Error: no description"; + //send an error response + String reasonText = "Error: " + exc.getMessage(); SessionIQ errResp = GTalkPacketFactory.createSessionTerminate( sessionInitIQ.getTo(), sessionInitIQ.getFrom(), sessionInitIQ.getID(), - Reason.INCOMPATIBLE_PARAMETERS, + Reason.FAILED_APPLICATION, reasonText); setState(CallPeerState.FAILED, reasonText); @@ -201,35 +148,165 @@ public class CallPeerGTalkImpl 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. + if(sessAcceptedWithNoCands == null) + getProtocolProvider().getConnection().sendPacket(response); + try { - getMediaHandler().processOffer(description); + getMediaHandler().start(); } - catch(Exception ex) + catch(UndeclaredThrowableException e) { - logger.info("Failed to process an incoming session initiate", ex); + Throwable exc = e.getUndeclaredThrowable(); - //send an error response; - String reasonText = "Error: " + ex.getMessage(); + 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.INCOMPATIBLE_PARAMETERS, + Reason.GENERAL_ERROR, reasonText); + getMediaHandler().getTransportManager().close(); setState(CallPeerState.FAILED, reasonText); getProtocolProvider().getConnection().sendPacket(errResp); return; } - // If we do not get the info about the remote peer yet. Get it right - // now. - if(this.getDiscoverInfo() == null) + //tell everyone we are connecting so that the audio notifications would + //stop + setState(CallPeerState.CONNECTED); + } + + /** + * 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; + } + + /** + * 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; + } + + /** + * 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 failed indicates if the hangup is following to a call failure or + * simply a disconnect + * @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(boolean failed, + String reasonText, + PacketExtension reasonOtherExtension) + { + // do nothing if the call is already ended + if (CallPeerState.DISCONNECTED.equals(getState()) + || CallPeerState.FAILED.equals(getState())) { - String calleeURI = sessionInitIQ.getFrom(); - retrieveDiscoverInfo(calleeURI); + if (logger.isDebugEnabled()) + logger.debug("Ignoring a request to hangup a call peer " + + "that is already DISCONNECTED"); + return; + } + + CallPeerState prevPeerState = getState(); + getMediaHandler().getTransportManager().close(); + + if (failed) + setState(CallPeerState.FAILED, reasonText); + else + setState(CallPeerState.DISCONNECTED, reasonText); + + SessionIQ responseIQ = null; + + if (prevPeerState.equals(CallPeerState.CONNECTED) + || CallPeerState.isOnHold(prevPeerState)) + { + responseIQ = GTalkPacketFactory.createBye( + getProtocolProvider().getOurJID(), peerJID, getSessionID()); + responseIQ.setInitiator(isInitiator() ? getAddress() : + getProtocolProvider().getOurJID()); + } + 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()); + responseIQ.setInitiator(isInitiator() ? getAddress() : + getProtocolProvider().getOurJID()); + } + else if (prevPeerState.equals(CallPeerState.INCOMING_CALL)) + { + responseIQ = GTalkPacketFactory.createBusy( + getProtocolProvider().getOurJID(), peerJID, getSessionID()); + responseIQ.setInitiator(isInitiator() ? getAddress() : + getProtocolProvider().getOurJID()); + } + 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); + } + else if(reason instanceof ReasonPacketExtension) + { + responseIQ.setReason( + (ReasonPacketExtension)reasonOtherExtension); + } + } + + getProtocolProvider().getConnection().sendPacket(responseIQ); } } @@ -247,7 +324,7 @@ public class CallPeerGTalkImpl throws OperationFailedException { sid = SessionIQ.generateSID(); - isInitiator = false; + initiator = false; //Create the media description that we'd like to send to the other side. RtpDescriptionPacketExtension offer @@ -294,112 +371,6 @@ public class CallPeerGTalkImpl } /** - * 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(IllegalArgumentException e) - { - // HACK for FreeSwitch that send accept message before sending - // candidates - sessAcceptedWithNoCands = sessionInitIQ; - return; - } - 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 @@ -450,7 +421,18 @@ public class CallPeerGTalkImpl // candidates if(sessAcceptedWithNoCands != null) { - if(!isInitiator) + if(isInitiator()) + { + try + { + answer(); + } + catch(OperationFailedException e) + { + logger.info("Failed to answer call (FreeSwitch hack)"); + } + } + else { final SessionIQ sess = sessAcceptedWithNoCands; sessAcceptedWithNoCands = null; @@ -465,190 +447,115 @@ public class CallPeerGTalkImpl } }.start(); } - else - { - try - { - answer(); - } - catch(OperationFailedException e) - { - logger.info("Failed to answer call (FreeSwitch hack)"); - } - } sessAcceptedWithNoCands = null; } } /** - * Returns the session ID of the Jingle session associated with this call. + * Processes the session initiation {@link SessionIQ} that we were created + * with, passing its content to the media handler. * - * @return the session ID of the Jingle session associated with this call. + * @param sessionInitIQ The {@link SessionIQ} that created the session that + * we are handling here. */ - public String getSessionID() + public void processSessionAccept(SessionIQ sessionInitIQ) { - return sessionInitIQ != null ? sessionInitIQ.getID() : sid; - } + this.sessionInitIQ = sessionInitIQ; - /** - * 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; - } + CallPeerMediaHandlerGTalkImpl mediaHandler = getMediaHandler(); + Collection<PacketExtension> extensions = + sessionInitIQ.getExtensions(); + RtpDescriptionPacketExtension answer = 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 failed indicates if the hangup is following to a call failure or - * simply a disconnect - * @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(boolean failed, - String reasonText, - PacketExtension reasonOtherExtension) - { - // do nothing if the call is already ended - if (CallPeerState.DISCONNECTED.equals(getState()) - || CallPeerState.FAILED.equals(getState())) + for(PacketExtension ext : extensions) { - if (logger.isDebugEnabled()) - logger.debug("Ignoring a request to hangup a call peer " - + "that is already DISCONNECTED"); - return; + if(ext.getElementName().equalsIgnoreCase( + RtpDescriptionPacketExtension.ELEMENT_NAME)) + { + answer = (RtpDescriptionPacketExtension)ext; + break; + } } - CallPeerState prevPeerState = getState(); - getMediaHandler().getTransportManager().close(); - - if (failed) - setState(CallPeerState.FAILED, reasonText); - else - setState(CallPeerState.DISCONNECTED, reasonText); - - SessionIQ responseIQ = null; - - if (prevPeerState.equals(CallPeerState.CONNECTED) - || CallPeerState.isOnHold(prevPeerState)) - { - responseIQ = GTalkPacketFactory.createBye( - getProtocolProvider().getOurJID(), peerJID, getSessionID()); - responseIQ.setInitiator(isInitiator() ? getAddress() : - getProtocolProvider().getOurJID()); - } - 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()); - responseIQ.setInitiator(isInitiator() ? getAddress() : - getProtocolProvider().getOurJID()); - } - else if (prevPeerState.equals(CallPeerState.INCOMING_CALL)) - { - responseIQ = GTalkPacketFactory.createBusy( - getProtocolProvider().getOurJID(), peerJID, getSessionID()); - responseIQ.setInitiator(isInitiator() ? getAddress() : - getProtocolProvider().getOurJID()); - } - else if (prevPeerState.equals(CallPeerState.BUSY) - || prevPeerState.equals(CallPeerState.FAILED)) + try { - // For FAILED and BUSY we only need to update CALL_STATUS - // as everything else has been done already. + mediaHandler.getTransportManager(). + wrapupConnectivityEstablishment(); + mediaHandler.processAnswer(answer); } - else + catch(IllegalArgumentException e) { - logger.info("Could not determine call peer state!"); + // HACK for FreeSwitch that send accept message before sending + // candidates + sessAcceptedWithNoCands = sessionInitIQ; + return; } - - if (responseIQ != null) + catch(Exception exc) { - if (reasonOtherExtension != null) - { - ReasonPacketExtension reason - = (ReasonPacketExtension) - responseIQ.getExtension( - ReasonPacketExtension.ELEMENT_NAME, - ReasonPacketExtension.NAMESPACE); + if (logger.isInfoEnabled()) + logger.info("Failed to process a session-accept", exc); - if (reason != null) - { - reason.setOtherExtension(reasonOtherExtension); - } - else if(reason instanceof ReasonPacketExtension) - { - responseIQ.setReason( - (ReasonPacketExtension)reasonOtherExtension); - } - } + //send an error response + String reasonText = "Error: " + exc.getMessage(); + SessionIQ errResp + = GTalkPacketFactory.createSessionTerminate( + sessionInitIQ.getTo(), + sessionInitIQ.getFrom(), + sessionInitIQ.getID(), + Reason.GENERAL_ERROR, + reasonText); - getProtocolProvider().getConnection().sendPacket(responseIQ); + 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(); } /** - * 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. + * 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. * - * @throws OperationFailedException if we fail to create or send the - * response. + * @param sessionInitIQ The {@link SessionIQ} that created the session that + * we are handling here. */ - public synchronized void answer() - throws OperationFailedException + protected synchronized void processSessionInitiate(SessionIQ sessionInitIQ) { - RtpDescriptionPacketExtension answer = null; + // Do initiate the session. + this.sessionInitIQ = sessionInitIQ; + this.initiator = true; - try + RtpDescriptionPacketExtension description = null; + + for(PacketExtension ext : sessionInitIQ.getExtensions()) { - getMediaHandler().getTransportManager(). - wrapupConnectivityEstablishment(); - answer = getMediaHandler().generateSessionAccept(true); + if(ext.getElementName().equals( + RtpDescriptionPacketExtension.ELEMENT_NAME)) + { + description = (RtpDescriptionPacketExtension)ext; + break; + } } - catch(IllegalArgumentException e) - { - sessAcceptedWithNoCands = new SessionIQ(); - - // HACK apparently FreeSwitch need to have accept before - answer = getMediaHandler().generateSessionAccept(false); - - SessionIQ response - = GTalkPacketFactory.createSessionAccept( - sessionInitIQ.getTo(), - sessionInitIQ.getFrom(), - getSessionID(), - answer); - getProtocolProvider().getConnection().sendPacket(response); - return; - } - catch(Exception exc) + if(description == null) { - logger.info("Failed to answer an incoming call", exc); + logger.info("No description in incoming session initiate"); - //send an error response - String reasonText = "Error: " + exc.getMessage(); + //send an error response; + String reasonText = "Error: no description"; SessionIQ errResp = GTalkPacketFactory.createSessionTerminate( sessionInitIQ.getTo(), sessionInitIQ.getFrom(), sessionInitIQ.getID(), - Reason.FAILED_APPLICATION, + Reason.INCOMPATIBLE_PARAMETERS, reasonText); setState(CallPeerState.FAILED, reasonText); @@ -656,76 +563,75 @@ public class CallPeerGTalkImpl 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. - if(sessAcceptedWithNoCands == null) - getProtocolProvider().getConnection().sendPacket(response); - try { - getMediaHandler().start(); + getMediaHandler().processOffer(description); } - catch(UndeclaredThrowableException e) + catch(Exception ex) { - Throwable exc = e.getUndeclaredThrowable(); - - logger.info("Failed to establish a connection", exc); + logger.info("Failed to process an incoming session initiate", ex); - //send an error response - String reasonText = "Error: " + exc.getMessage(); + //send an error response; + String reasonText = "Error: " + ex.getMessage(); SessionIQ errResp = GTalkPacketFactory.createSessionTerminate( sessionInitIQ.getTo(), sessionInitIQ.getFrom(), sessionInitIQ.getID(), - Reason.GENERAL_ERROR, + Reason.INCOMPATIBLE_PARAMETERS, 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); + // If we do not get the info about the remote peer yet. Get it right + // now. + if(this.getDiscoverInfo() == null) + { + String calleeURI = sessionInitIQ.getFrom(); + retrieveDiscoverInfo(calleeURI); + } } /** - * Returns whether or not the <tt>CallPeer</tt> is an Android phone or - * a call pass throught Google Voice or uses Google Talk client. + * Puts this peer into a {@link CallPeerState#DISCONNECTED}, indicating a + * reason to the user, if there is one. * - * We base the detection of the JID's resource which in the case of Android - * is android/Vtok/Talk.vXXXXXXX (where XXXXXX is a suite of - * numbers/letters). + * @param sessionIQ the {@link SessionIQ} that's terminating our session. */ - private static boolean isAndroidOrVtokOrTalkClient(String fullJID) + public void processSessionReject(SessionIQ sessionIQ) { - int idx = fullJID.indexOf('/'); + processSessionTerminate(sessionIQ); + } - if(idx != -1) + /** + * 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) { - String res = fullJID.substring(idx + 1); - if(res.startsWith("android") || res.startsWith("Vtok") || - res.startsWith("Talk.v")) - { - return true; - } - } + Reason reason = reasonExt.getReason(); - if(fullJID.contains( - "@" + ProtocolProviderServiceJabberImpl.GOOGLE_VOICE_DOMAIN)) - return true; + if(reason != null) + reasonStr += " Reason: " + reason.toString() + "."; - return false; + String text = reasonExt.getText(); + + if(text != null) + reasonStr += " " + text; + } + + getMediaHandler().getTransportManager().close(); + setState(CallPeerState.DISCONNECTED, reasonStr); } /** 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 e0eefe8..3053bf5 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java @@ -13,7 +13,6 @@ import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*; import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.ContentPacketExtension.SendersEnum; import net.java.sip.communicator.impl.protocol.jabber.jinglesdp.*; import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; import org.jitsi.service.neomedia.*; @@ -29,8 +28,7 @@ import org.jivesoftware.smack.packet.*; */ public class CallPeerJabberImpl extends AbstractCallPeerJabberGTalkImpl - <CallJabberImpl, - CallPeerMediaHandlerJabberImpl> + <CallJabberImpl, CallPeerMediaHandlerJabberImpl> { /** * The <tt>Logger</tt> used by the <tt>CallPeerJabberImpl</tt> class and its @@ -40,14 +38,9 @@ public class CallPeerJabberImpl = Logger.getLogger(CallPeerJabberImpl.class); /** - * The {@link JingleIQ} that created the session that this call represents. - */ - private JingleIQ sessionInitIQ; - - /** - * Indicates whether this peer was the one that initiated the session. + * If the call is cancelled before session-initiate is sent. */ - private boolean isInitiator = false; + private boolean cancelled = false; /** * Synchronization object for candidates available. @@ -60,24 +53,24 @@ public class CallPeerJabberImpl private boolean contentAddWithNoCands = false; /** - * Synchronization object for SID. + * If we have processed the session initiate. */ - private final Object sidSyncRoot = new Object(); + private boolean sessionInitiateProcessed = false; /** - * If the call is cancelled before session-initiate is sent. + * Synchronization object. */ - private boolean cancelled = false; + private final Object sessionInitiateSyncRoot = new Object(); /** - * Synchronization object. + * The {@link JingleIQ} that created the session that this call represents. */ - private final Object sessionInitiateSyncRoot = new Object(); + private JingleIQ sessionInitIQ; /** - * If we have processed the session initiate. + * Synchronization object for SID. */ - private boolean sessionInitiateProcessed = false; + private final Object sidSyncRoot = new Object(); /** * Creates a new call peer with address <tt>peerAddress</tt>. @@ -109,222 +102,6 @@ public class CallPeerJabberImpl } /** - * Returns a String locator for that peer. - * - * @return the peer's address or phone number. - */ - public String getAddress() - { - return peerJID; - } - - /** - * Returns full URI of the address. - * - * @return full URI of the address - */ - public String getURI() - { - return "xmpp:" + 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; - } - - /** - * 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 JingleIQ} that we were created - * with, passing its content to the media handler and then sends either a - * "session-info/ringing" or a "session-terminate" response. - * - * @param sessionInitIQ The {@link JingleIQ} that created the session that - * we are handling here. - */ - protected synchronized void processSessionInitiate(JingleIQ sessionInitIQ) - { - // Do initiate the session. - this.sessionInitIQ = sessionInitIQ; - this.isInitiator = true; - - // 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<ContentPacketExtension> offer = sessionInitIQ.getContentList(); - - try - { - getMediaHandler().processOffer(offer); - - CoinPacketExtension coin = null; - - for(PacketExtension ext : sessionInitIQ.getExtensions()) - { - if(ext.getElementName().equals( - CoinPacketExtension.ELEMENT_NAME)) - { - coin = (CoinPacketExtension)ext; - break; - } - } - - /* does the call peer acts as a conference focus ? */ - if(coin != null) - { - setConferenceFocus(Boolean.parseBoolean( - (String)coin.getAttribute("isfocus"))); - } - } - catch(Exception ex) - { - logger.info("Failed to process an incoming session initiate", ex); - - //send an error response; - 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; - } - - // If we do not get the info about the remote peer yet. Get it right - // now. - if(this.getDiscoverInfo() == null) - { - String calleeURI = sessionInitIQ.getFrom(); - retrieveDiscoverInfo(calleeURI); - } - - //send a ringing response - if (logger.isTraceEnabled()) - logger.trace("will send ringing response: "); - - getProtocolProvider().getConnection().sendPacket( - JinglePacketFactory.createRinging(sessionInitIQ)); - - synchronized(sessionInitiateSyncRoot) - { - sessionInitiateProcessed = true; - sessionInitiateSyncRoot.notify(); - } - } - - /** - * Processes the session initiation {@link JingleIQ} that we were created - * with, passing its content to the media handler and then sends either a - * "session-info/ringing" or a "session-terminate" response. - * - * @param sessionInitiateExtensions a collection of additional and optional - * <tt>PacketExtension</tt>s to be added to the <tt>session-initiate</tt> - * {@link JingleIQ} which is to initiate the session with this - * <tt>CallPeerJabberImpl</tt> - * @throws OperationFailedException exception - */ - protected synchronized void initiateSession( - Iterable<PacketExtension> sessionInitiateExtensions) - throws OperationFailedException - { - isInitiator = false; - - //Create the media description that we'd like to send to the other side. - List<ContentPacketExtension> offer - = getMediaHandler().createContentList(); - - //send a ringing response - if (logger.isTraceEnabled()) - logger.trace("will send ringing response: "); - - ProtocolProviderServiceJabberImpl protocolProvider - = getProtocolProvider(); - - synchronized(sidSyncRoot) - { - sessionInitIQ - = JinglePacketFactory.createSessionInitiate( - protocolProvider.getOurJID(), - this.peerJID, - JingleIQ.generateSID(), - offer); - - if(cancelled) - { - // we cancelled the call too early so no need to send the - // session-initiate to peer - getMediaHandler().getTransportManager().close(); - return; - } - } - - if (sessionInitiateExtensions != null) - { - for (PacketExtension sessionInitiateExtension - : sessionInitiateExtensions) - { - sessionInitIQ.addExtension(sessionInitiateExtension); - } - } - - protocolProvider.getConnection().sendPacket(sessionInitIQ); - } - - /** * Indicates a user request to answer an incoming call from this * <tt>CallPeer</tt>. * @@ -406,6 +183,40 @@ public class CallPeerJabberImpl } /** + * 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 getJingleSID() + { + return sessionInitIQ != null ? sessionInitIQ.getSID() : null; + } + + /** + * 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; + } + + /** + * 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 JingleIQ getSessionIQ() + { + return sessionInitIQ; + } + + /** * 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. @@ -509,64 +320,309 @@ public class CallPeerJabberImpl } /** - * Returns the session ID of the Jingle session associated with this call. + * Processes the session initiation {@link JingleIQ} that we were created + * with, passing its content to the media handler and then sends either a + * "session-info/ringing" or a "session-terminate" response. * - * @return the session ID of the Jingle session associated with this call. + * @param sessionInitiateExtensions a collection of additional and optional + * <tt>PacketExtension</tt>s to be added to the <tt>session-initiate</tt> + * {@link JingleIQ} which is to initiate the session with this + * <tt>CallPeerJabberImpl</tt> + * @throws OperationFailedException exception */ - public String getJingleSID() + protected synchronized void initiateSession( + Iterable<PacketExtension> sessionInitiateExtensions) + throws OperationFailedException { - return sessionInitIQ != null ? sessionInitIQ.getSID() : null; + initiator = false; + + //Create the media description that we'd like to send to the other side. + List<ContentPacketExtension> offer + = getMediaHandler().createContentList(); + + //send a ringing response + if (logger.isTraceEnabled()) + logger.trace("will send ringing response: "); + + ProtocolProviderServiceJabberImpl protocolProvider + = getProtocolProvider(); + + synchronized(sidSyncRoot) + { + sessionInitIQ + = JinglePacketFactory.createSessionInitiate( + protocolProvider.getOurJID(), + this.peerJID, + JingleIQ.generateSID(), + offer); + + if(cancelled) + { + // we cancelled the call too early so no need to send the + // session-initiate to peer + getMediaHandler().getTransportManager().close(); + return; + } + } + + if (sessionInitiateExtensions != null) + { + for (PacketExtension sessionInitiateExtension + : sessionInitiateExtensions) + { + sessionInitIQ.addExtension(sessionInitiateExtension); + } + } + + protocolProvider.getConnection().sendPacket(sessionInitIQ); } /** - * Returns the IQ ID of the Jingle session-initiate packet associated with - * this call. + * Processes the content-accept {@link JingleIQ}. * - * @return the IQ ID of the Jingle session-initiate packet associated with - * this call. + * @param content The {@link JingleIQ} that contains content that remote + * peer has accepted */ - public String getSessInitID() + public void processContentAccept(JingleIQ content) { - return sessionInitIQ != null ? sessionInitIQ.getPacketID() : null; + List<ContentPacketExtension> contents = content.getContentList(); + + try + { + getMediaHandler().getTransportManager(). + wrapupConnectivityEstablishment(); + getMediaHandler().processAnswer(contents); + } + catch(Exception exc) + { + logger.warn("Failed to process a content-accept", exc); + //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()); + getProtocolProvider().getConnection().sendPacket(errResp); + return; + } + + getMediaHandler().start(); } /** - * Returns the IQ ID of the Jingle session-initiate packet associated with - * this call. + * Processes the content-add {@link JingleIQ}. * - * @return the IQ ID of the Jingle session-initiate packet associated with - * this call. + * @param content The {@link JingleIQ} that contains content that remote + * peer wants to be added */ - public JingleIQ getSessionIQ() + public void processContentAdd(final JingleIQ content) { - return sessionInitIQ; + CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); + List<ContentPacketExtension> contents = content.getContentList(); + Iterable<ContentPacketExtension> answerContents; + JingleIQ contentIQ; + boolean noCands = false; + + logger.info("nocand " + noCands); + logger.info("run code"); + + /* + * If a remote peer turns her video on in a conference which is hosted + * by the local peer and the local peer is not streaming her local + * video, reinvite the other remote peers to enable RTP translation. + */ + MediaStream oldVideoStream = mediaHandler.getStream(MediaType.VIDEO); + + try + { + if(!contentAddWithNoCands) + { + mediaHandler.processOffer(contents); + + /* + * Gingle transport will not put candidate in session-initiate + * and content-add. + */ + for(ContentPacketExtension c : contents) + { + if(JingleUtils.getFirstCandidate(c, 1) == null) + { + contentAddWithNoCands = true; + noCands = true; + } + } + } + + // if no candidates are present, launch a new Thread which will + // process and wait for the connectivity establishment (otherwise + // the existing thread will be blocked and thus cannot receive + // transport-info with candidates + if(noCands) + { + new Thread() + { + public void run() + { + try + { + synchronized(candSyncRoot) + { + candSyncRoot.wait(); + } + } + catch(InterruptedException e) + { + } + + processContentAdd(content); + contentAddWithNoCands = false; + } + }.start(); + logger.info("start thread"); + return; + } + + mediaHandler.getTransportManager(). + wrapupConnectivityEstablishment(); + logger.info("wraping up"); + answerContents = mediaHandler.generateSessionAccept(); + contentIQ = null; + } + catch(Exception e) + { + logger.warn("Exception occurred", e); + + answerContents = null; + contentIQ + = JinglePacketFactory.createContentReject( + getProtocolProvider().getOurJID(), + this.peerJID, + getJingleSID(), + answerContents); + } + + if(contentIQ == null) + { + /* send content-accept */ + contentIQ + = JinglePacketFactory.createContentAccept( + getProtocolProvider().getOurJID(), + this.peerJID, + getJingleSID(), + answerContents); + } + + getProtocolProvider().getConnection().sendPacket(contentIQ); + mediaHandler.start(); + + /* + * If a remote peer turns her video on in a conference which is hosted + * by the local peer and the local peer is not streaming her local + * video, reinvite the other remote peers to enable RTP translation. + */ + if (oldVideoStream == null) + { + MediaStream newVideoStream + = mediaHandler.getStream(MediaType.VIDEO); + + if ((newVideoStream != null) + && mediaHandler.isRTPTranslationEnabled()) + { + try + { + getCall().modifyVideoContent(true); + } + catch (OperationFailedException ofe) + { + logger.error("Failed to enable RTP translation", ofe); + } + } + } } /** - * Puts this peer into a {@link CallPeerState#DISCONNECTED}, indicating a - * reason to the user, if there is one. + * Processes the content-modify {@link JingleIQ}. * - * @param jingleIQ the {@link JingleIQ} that's terminating our session. + * @param content The {@link JingleIQ} that contains content that remote + * peer wants to be modified */ - public void processSessionTerminate(JingleIQ jingleIQ) + public void processContentModify(JingleIQ content) { - String reasonStr = "Call ended by remote side."; - ReasonPacketExtension reasonExt = jingleIQ.getReason(); + ContentPacketExtension ext = content.getContentList().get(0); - if(reasonStr != null) + try { - Reason reason = reasonExt.getReason(); + boolean modify = false; + if(ext.getFirstChildOfType(RtpDescriptionPacketExtension.class) + != null) + { + modify = true; + } + getMediaHandler().reinitContent(ext.getName(), ext, modify); + } + catch(Exception exc) + { + logger.info("Failed to process an incoming content-modify", exc); - if(reason != null) - reasonStr += " Reason: " + reason.toString() + "."; + //send an error response; + JingleIQ errResp = JinglePacketFactory.createSessionTerminate( + sessionInitIQ.getTo(), sessionInitIQ.getFrom(), + sessionInitIQ.getSID(), Reason.INCOMPATIBLE_PARAMETERS, + "Error: " + exc.getMessage()); - String text = reasonExt.getText(); + setState(CallPeerState.FAILED, "Error: " + exc.getMessage()); + getProtocolProvider().getConnection().sendPacket(errResp); + return; + } + } - if(text != null) - reasonStr += " " + text; + /** + * Processes the content-reject {@link JingleIQ}. + * + * @param content The {@link JingleIQ} + */ + public void processContentReject(JingleIQ content) + { + if(content.getContentList().isEmpty()) + { + //send an error response; + JingleIQ errResp = JinglePacketFactory.createSessionTerminate( + sessionInitIQ.getTo(), sessionInitIQ.getFrom(), + sessionInitIQ.getSID(), Reason.INCOMPATIBLE_PARAMETERS, + "Error: content rejected"); + + setState(CallPeerState.FAILED, "Error: content rejected"); + getProtocolProvider().getConnection().sendPacket(errResp); + return; } + } - setState(CallPeerState.DISCONNECTED, reasonStr); + /** + * Processes the content-remove {@link JingleIQ}. + * + * @param content The {@link JingleIQ} that contains content that remote + * peer wants to be removed + */ + public void processContentRemove(JingleIQ content) + { + List<ContentPacketExtension> contents = content.getContentList(); + + if (!contents.isEmpty()) + { + CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); + + for(ContentPacketExtension c : contents) + mediaHandler.removeContent(c.getName()); + + /* + * TODO XEP-0166: Jingle says: If the content-remove results in zero + * content definitions for the session, the entity that receives the + * content-remove SHOULD send a session-terminate action to the + * other party (since a session with no content definitions is + * void). + */ + } } /** @@ -614,58 +670,6 @@ public class CallPeerJabberImpl } /** - * Puts the <tt>CallPeer</tt> represented by this instance on or off hold. - * - * @param onHold <tt>true</tt> to have the <tt>CallPeer</tt> put on hold; - * <tt>false</tt>, otherwise - * - * @throws OperationFailedException if we fail to construct or send the - * INVITE request putting the remote side on/off hold. - */ - public void putOnHold(boolean onHold) - throws OperationFailedException - { - CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); - - mediaHandler.setLocallyOnHold(onHold); - - SessionInfoType type; - - if(onHold) - type = SessionInfoType.hold; - else - { - type = SessionInfoType.unhold; - getMediaHandler().reinitAllContents(); - } - - //we are now on hold and need to realize this before potentially - //spoiling it all with an exception while sending the packet :). - reevalLocalHoldStatus(); - - JingleIQ onHoldIQ = JinglePacketFactory.createSessionInfo( - getProtocolProvider().getOurJID(), - peerJID, - getJingleSID(), - type); - - getProtocolProvider().getConnection().sendPacket(onHoldIQ); - } - - /** - * 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; - } - - /** * Handles the specified session <tt>info</tt> packet according to its * content. * @@ -693,6 +697,116 @@ public class CallPeerJabberImpl } /** + * Processes the session initiation {@link JingleIQ} that we were created + * with, passing its content to the media handler and then sends either a + * "session-info/ringing" or a "session-terminate" response. + * + * @param sessionInitIQ The {@link JingleIQ} that created the session that + * we are handling here. + */ + protected synchronized void processSessionInitiate(JingleIQ sessionInitIQ) + { + // Do initiate the session. + this.sessionInitIQ = sessionInitIQ; + this.initiator = true; + + // 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<ContentPacketExtension> offer = sessionInitIQ.getContentList(); + + try + { + getMediaHandler().processOffer(offer); + + CoinPacketExtension coin = null; + + for(PacketExtension ext : sessionInitIQ.getExtensions()) + { + if(ext.getElementName().equals( + CoinPacketExtension.ELEMENT_NAME)) + { + coin = (CoinPacketExtension)ext; + break; + } + } + + /* does the call peer acts as a conference focus ? */ + if(coin != null) + { + setConferenceFocus(Boolean.parseBoolean( + (String)coin.getAttribute("isfocus"))); + } + } + catch(Exception ex) + { + logger.info("Failed to process an incoming session initiate", ex); + + //send an error response; + 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; + } + + // If we do not get the info about the remote peer yet. Get it right + // now. + if(this.getDiscoverInfo() == null) + { + String calleeURI = sessionInitIQ.getFrom(); + retrieveDiscoverInfo(calleeURI); + } + + //send a ringing response + if (logger.isTraceEnabled()) + logger.trace("will send ringing response: "); + + getProtocolProvider().getConnection().sendPacket( + JinglePacketFactory.createRinging(sessionInitIQ)); + + synchronized(sessionInitiateSyncRoot) + { + sessionInitiateProcessed = true; + sessionInitiateSyncRoot.notify(); + } + } + + /** + * Puts this peer into a {@link CallPeerState#DISCONNECTED}, indicating a + * reason to the user, if there is one. + * + * @param jingleIQ the {@link JingleIQ} that's terminating our session. + */ + public void processSessionTerminate(JingleIQ jingleIQ) + { + String reasonStr = "Call ended by remote side."; + ReasonPacketExtension reasonExt = jingleIQ.getReason(); + + if(reasonStr != null) + { + Reason reason = reasonExt.getReason(); + + if(reason != null) + reasonStr += " Reason: " + reason.toString() + "."; + + String text = reasonExt.getText(); + + if(text != null) + reasonStr += " " + text; + } + + setState(CallPeerState.DISCONNECTED, reasonStr); + } + + /** * Processes a specific "XEP-0251: Jingle Session Transfer" * <tt>transfer</tt> packet (extension). * @@ -750,60 +864,130 @@ public class CallPeerJabberImpl } /** - * Send a <tt>content-add</tt> to add video setup. + * Processes the <tt>transport-info</tt> {@link JingleIQ}. + * + * @param jingleIQ the <tt>transport-info</tt> {@link JingleIQ} to process */ - private void sendAddVideoContent() + public void processTransportInfo(JingleIQ jingleIQ) { - List<ContentPacketExtension> contents; - + /* + * The transport-info action is used to exchange transport candidates so + * it only concerns the mediaHandler. + */ try { - contents = getMediaHandler().createContentList(MediaType.VIDEO); + if(isInitiator()) + { + synchronized(sessionInitiateSyncRoot) + { + if(!sessionInitiateProcessed) + { + try + { + sessionInitiateSyncRoot.wait(); + } + catch(InterruptedException e) + { + } + } + } + } + + getMediaHandler().processTransportInfo( + jingleIQ.getContentList()); } - catch(Exception exc) + catch (OperationFailedException ofe) { - logger.warn("Failed to gather content for video type", exc); + logger.warn("Failed to process an incoming transport-info", ofe); + + //send an error response + String reasonText = "Error: " + ofe.getMessage(); + JingleIQ errResp + = JinglePacketFactory.createSessionTerminate( + sessionInitIQ.getTo(), + sessionInitIQ.getFrom(), + sessionInitIQ.getSID(), + Reason.GENERAL_ERROR, + reasonText); + + setState(CallPeerState.FAILED, reasonText); + getProtocolProvider().getConnection().sendPacket(errResp); + return; } - ProtocolProviderServiceJabberImpl protocolProvider - = getProtocolProvider(); - JingleIQ contentIQ - = JinglePacketFactory.createContentAdd( - protocolProvider.getOurJID(), - this.peerJID, - getJingleSID(), - contents); - - protocolProvider.getConnection().sendPacket(contentIQ); + synchronized(candSyncRoot) + { + candSyncRoot.notify(); + } } /** - * Send a <tt>content-remove</tt> to remove video setup. + * Puts the <tt>CallPeer</tt> represented by this instance on or off hold. + * + * @param onHold <tt>true</tt> to have the <tt>CallPeer</tt> put on hold; + * <tt>false</tt>, otherwise + * + * @throws OperationFailedException if we fail to construct or send the + * INVITE request putting the remote side on/off hold. */ - private void sendRemoveVideoContent() + public void putOnHold(boolean onHold) + throws OperationFailedException { CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); - ContentPacketExtension content = new ContentPacketExtension(); - ContentPacketExtension remoteContent - = mediaHandler.getRemoteContent(MediaType.VIDEO.toString()); + mediaHandler.setLocallyOnHold(onHold); - content.setName(remoteContent.getName()); - content.setCreator(remoteContent.getCreator()); - content.setSenders(remoteContent.getSenders()); + SessionInfoType type; + + if(onHold) + type = SessionInfoType.hold; + else + { + type = SessionInfoType.unhold; + getMediaHandler().reinitAllContents(); + } + + //we are now on hold and need to realize this before potentially + //spoiling it all with an exception while sending the packet :). + reevalLocalHoldStatus(); + + JingleIQ onHoldIQ = JinglePacketFactory.createSessionInfo( + getProtocolProvider().getOurJID(), + peerJID, + getJingleSID(), + type); + + getProtocolProvider().getConnection().sendPacket(onHoldIQ); + } + + /** + * Send a <tt>content-add</tt> to add video setup. + */ + private void sendAddVideoContent() + { + List<ContentPacketExtension> contents; + + try + { + contents = getMediaHandler().createContentList(MediaType.VIDEO); + } + catch(Exception exc) + { + logger.warn("Failed to gather content for video type", exc); + return; + } ProtocolProviderServiceJabberImpl protocolProvider = getProtocolProvider(); JingleIQ contentIQ - = JinglePacketFactory.createContentRemove( + = JinglePacketFactory.createContentAdd( protocolProvider.getOurJID(), this.peerJID, getJingleSID(), - Arrays.asList(content)); + contents); protocolProvider.getConnection().sendPacket(contentIQ); - mediaHandler.removeContent(remoteContent.getName()); } /** @@ -825,54 +1009,6 @@ public class CallPeerJabberImpl /** * Send a <tt>content</tt> message to reflect change in video setup (start - * or stop). - */ - public void sendModifyVideoResolutionContent() - { - ContentPacketExtension remoteContent = getMediaHandler(). - getRemoteContent(MediaType.VIDEO.toString()); - ContentPacketExtension content; - - logger.info("send modify-content to change resolution"); - - // send content-modify with RTP description - SendersEnum senders = remoteContent.getSenders(); - - // create content list with resolution - try - { - content = getMediaHandler().createContentForMedia(MediaType.VIDEO); - } - catch(Exception exc) - { - logger.warn("Failed to gather content for video type", exc); - return; - } - - // if we are only receiving video senders is null - if(senders != null) - content.setSenders(senders); - - JingleIQ contentIQ = JinglePacketFactory - .createContentModify(getProtocolProvider().getOurJID(), - this.peerJID, getJingleSID(), content); - - getProtocolProvider().getConnection().sendPacket(contentIQ); - - try - { - getMediaHandler().reinitContent(remoteContent.getName(), content, - false); - getMediaHandler().start(); - } - catch(Exception e) - { - logger.warn("Exception occurred when media reinitialization", e); - } - } - - /** - * Send a <tt>content</tt> message to reflect change in video setup (start * or stop). Message can be content-modify if video content exists, * content-add if we start video but video is not enabled on the peer or * content-remove if we stop video and video is not enabled on the peer. @@ -881,53 +1017,63 @@ public class CallPeerJabberImpl */ public void sendModifyVideoContent(boolean allowed) { - ContentPacketExtension ext = new ContentPacketExtension(); + CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); + + /* + * If the local peer is the focus of a video conference, it should not + * remove content and should rather add/modify content in order to + * perform RTP translation. + */ + if (!allowed && mediaHandler.isRTPTranslationEnabled()) + allowed = true; + ContentPacketExtension remoteContent - = getMediaHandler().getRemoteContent(MediaType.VIDEO.toString()); + = mediaHandler.getRemoteContent(MediaType.VIDEO.toString()); - if(remoteContent == null) + if (remoteContent == null) { - if(allowed) + if (allowed) sendAddVideoContent(); return; } - else if(!allowed - && ((!isInitiator - && (remoteContent.getSenders() - == SendersEnum.initiator)) - || (isInitiator - && (remoteContent.getSenders() - == SendersEnum.responder)))) - { - sendRemoveVideoContent(); - return; - } SendersEnum senders = remoteContent.getSenders(); + if (!allowed) + { + boolean initiator = isInitiator(); + + if ((!initiator && (senders == SendersEnum.initiator)) + || (initiator && (senders == SendersEnum.responder))) + { + sendRemoveVideoContent(); + return; + } + } + /* adjust the senders attribute depending on current value and if we * allowed or not local video streaming */ - if(allowed) + if (allowed) { - if(senders != SendersEnum.none) - { - senders = SendersEnum.both; - } - else if(senders == SendersEnum.none) + if (senders == SendersEnum.none) { senders - = isInitiator + = isInitiator() ? SendersEnum.responder : SendersEnum.initiator; } + else + { + senders = SendersEnum.both; + } } else { - if(senders == SendersEnum.both || senders == null) + if ((senders == SendersEnum.both) || (senders == null)) { senders - = isInitiator + = isInitiator() ? SendersEnum.initiator : SendersEnum.responder; } @@ -937,9 +1083,12 @@ public class CallPeerJabberImpl } } + ContentPacketExtension ext = new ContentPacketExtension(); + String remoteContentName = remoteContent.getName(); + ext.setSenders(senders); ext.setCreator(remoteContent.getCreator()); - ext.setName(remoteContent.getName()); + ext.setName(remoteContentName); ProtocolProviderServiceJabberImpl protocolProvider = getProtocolProvider(); @@ -954,9 +1103,7 @@ public class CallPeerJabberImpl try { - CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); - - mediaHandler.reinitContent(remoteContent.getName(), ext, false); + mediaHandler.reinitContent(remoteContentName, ext, false); mediaHandler.start(); } catch(Exception e) @@ -966,310 +1113,86 @@ public class CallPeerJabberImpl } /** - * Processes the content-add {@link JingleIQ}. - * - * @param content The {@link JingleIQ} that contains content that remote - * peer wants to be added + * Send a <tt>content</tt> message to reflect change in video setup (start + * or stop). */ - public void processContentAdd(final JingleIQ content) + public void sendModifyVideoResolutionContent() { CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); - List<ContentPacketExtension> contents = content.getContentList(); - Iterable<ContentPacketExtension> answerContents; - JingleIQ contentIQ; - boolean noCands = false; - - logger.info("nocand " + noCands); - logger.info("run code"); - - /* - * If a remote peer turns her video on in a conference which is hosted - * by the local peer and the local peer is not streaming her local - * video, reinvite the other remote peers to enable RTP translation. - */ - MediaStream oldVideoStream = mediaHandler.getStream(MediaType.VIDEO); - - try - { - if(!contentAddWithNoCands) - mediaHandler.processOffer(contents); - - /* Gingle transport will not put candidate in session-initiate and - * content-add - */ - if(contentAddWithNoCands == false) - { - for(ContentPacketExtension c : contents) - { - if(JingleUtils.getFirstCandidate(c, 1) == null) - { - contentAddWithNoCands = true; - noCands = true; - } - } - } - - // if no candidates are present, launch a new Thread which will - // process and wait for the connectivity establishment (otherwise - // the existing thread will be blocked and thus cannot receive - // transport-info with candidates - if(noCands) - { - new Thread() - { - public void run() - { - try - { - synchronized(candSyncRoot) - { - candSyncRoot.wait(); - } - } - catch(InterruptedException e) - { - } - - processContentAdd(content); - contentAddWithNoCands = false; - } - }.start(); - logger.info("start thread"); - return; - } - - mediaHandler.getTransportManager(). - wrapupConnectivityEstablishment(); - logger.info("wraping up"); - answerContents = mediaHandler.generateSessionAccept(); - contentIQ = null; - } - catch(Exception e) - { - logger.warn("Exception occurred", e); - - answerContents = null; - contentIQ - = JinglePacketFactory.createContentReject( - getProtocolProvider().getOurJID(), - this.peerJID, - getJingleSID(), - answerContents); - } - - if(contentIQ == null) - { - /* send content-accept */ - contentIQ - = JinglePacketFactory.createContentAccept( - getProtocolProvider().getOurJID(), - this.peerJID, - getJingleSID(), - answerContents); - } - - getProtocolProvider().getConnection().sendPacket(contentIQ); - mediaHandler.start(); - - /* - * If a remote peer turns her video on in a conference which is hosted - * by the local peer and the local peer is not streaming her local - * video, reinvite the other remote peers to enable RTP translation. - */ - if (oldVideoStream == null) - { - MediaStream newVideoStream - = mediaHandler.getStream(MediaType.VIDEO); + ContentPacketExtension remoteContent + = mediaHandler.getRemoteContent(MediaType.VIDEO.toString()); + ContentPacketExtension content; - if ((newVideoStream != null) - && mediaHandler.isRTPTranslationEnabled()) - { - try - { - getCall().modifyVideoContent(true); - } - catch (OperationFailedException ofe) - { - logger.error("Failed to enable RTP translation", ofe); - } - } - } - } + logger.info("send modify-content to change resolution"); - /** - * Processes the content-accept {@link JingleIQ}. - * - * @param content The {@link JingleIQ} that contains content that remote - * peer has accepted - */ - public void processContentAccept(JingleIQ content) - { - List<ContentPacketExtension> contents = content.getContentList(); + // send content-modify with RTP description + // create content list with resolution try { - getMediaHandler().getTransportManager(). - wrapupConnectivityEstablishment(); - getMediaHandler().processAnswer(contents); + content = mediaHandler.createContentForMedia(MediaType.VIDEO); } - catch(Exception exc) + catch (Exception e) { - logger.warn("Failed to process a content-accept", exc); - //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()); - getProtocolProvider().getConnection().sendPacket(errResp); + logger.warn("Failed to gather content for video type", e); return; } - getMediaHandler().start(); - } - - /** - * Processes the content-modify {@link JingleIQ}. - * - * @param content The {@link JingleIQ} that contains content that remote - * peer wants to be modified - */ - public void processContentModify(JingleIQ content) - { - ContentPacketExtension ext = content.getContentList().get(0); - - try - { - boolean modify = false; - if(ext.getFirstChildOfType(RtpDescriptionPacketExtension.class) - != null) - { - modify = true; - } - getMediaHandler().reinitContent(ext.getName(), ext, modify); - } - catch(Exception exc) - { - logger.info("Failed to process an incoming content-modify", exc); + // if we are only receiving video senders is null + SendersEnum senders = remoteContent.getSenders(); - //send an error response; - JingleIQ errResp = JinglePacketFactory.createSessionTerminate( - sessionInitIQ.getTo(), sessionInitIQ.getFrom(), - sessionInitIQ.getSID(), Reason.INCOMPATIBLE_PARAMETERS, - "Error: " + exc.getMessage()); + if (senders != null) + content.setSenders(senders); - setState(CallPeerState.FAILED, "Error: " + exc.getMessage()); - getProtocolProvider().getConnection().sendPacket(errResp); - return; - } - } + ProtocolProviderServiceJabberImpl protocolProvider + = getProtocolProvider(); + JingleIQ contentIQ + = JinglePacketFactory.createContentModify( + protocolProvider.getOurJID(), + this.peerJID, + getJingleSID(), + content); - /** - * Processes the content-remove {@link JingleIQ}. - * - * @param content The {@link JingleIQ} that contains content that remote - * peer wants to be removed - */ - public void processContentRemove(JingleIQ content) - { - List<ContentPacketExtension> contents = content.getContentList(); + protocolProvider.getConnection().sendPacket(contentIQ); - if (!contents.isEmpty()) + try { - CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); - - for(ContentPacketExtension c : contents) - mediaHandler.removeContent(c.getName()); - - /* - * TODO XEP-0166: Jingle says: If the content-remove results in zero - * content definitions for the session, the entity that receives the - * content-remove SHOULD send a session-terminate action to the - * other party (since a session with no content definitions is - * void). - */ + mediaHandler.reinitContent(remoteContent.getName(), content, false); + mediaHandler.start(); } - } - - /** - * Processes the content-reject {@link JingleIQ}. - * - * @param content The {@link JingleIQ} - */ - public void processContentReject(JingleIQ content) - { - if(content.getContentList().isEmpty()) + catch(Exception e) { - //send an error response; - JingleIQ errResp = JinglePacketFactory.createSessionTerminate( - sessionInitIQ.getTo(), sessionInitIQ.getFrom(), - sessionInitIQ.getSID(), Reason.INCOMPATIBLE_PARAMETERS, - "Error: content rejected"); - - setState(CallPeerState.FAILED, "Error: content rejected"); - getProtocolProvider().getConnection().sendPacket(errResp); - return; + logger.warn("Exception occurred when media reinitialization", e); } } /** - * Processes the <tt>transport-info</tt> {@link JingleIQ}. - * - * @param jingleIQ the <tt>transport-info</tt> {@link JingleIQ} to process + * Send a <tt>content-remove</tt> to remove video setup. */ - public void processTransportInfo(JingleIQ jingleIQ) + private void sendRemoveVideoContent() { - /* - * The transport-info action is used to exchange transport candidates so - * it only concerns the mediaHandler. - */ - try - { - if(isInitiator) - { - synchronized(sessionInitiateSyncRoot) - { - if(!sessionInitiateProcessed) - { - try - { - sessionInitiateSyncRoot.wait(); - } - catch(InterruptedException e) - { - } - } - } - } - - getMediaHandler().processTransportInfo( - jingleIQ.getContentList()); - } - catch (OperationFailedException ofe) - { - logger.warn("Failed to process an incoming transport-info", ofe); + CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); - //send an error response - String reasonText = "Error: " + ofe.getMessage(); - JingleIQ errResp - = JinglePacketFactory.createSessionTerminate( - sessionInitIQ.getTo(), - sessionInitIQ.getFrom(), - sessionInitIQ.getSID(), - Reason.GENERAL_ERROR, - reasonText); + ContentPacketExtension content = new ContentPacketExtension(); + ContentPacketExtension remoteContent + = mediaHandler.getRemoteContent(MediaType.VIDEO.toString()); + String remoteContentName = remoteContent.getName(); - setState(CallPeerState.FAILED, reasonText); - getProtocolProvider().getConnection().sendPacket(errResp); + content.setName(remoteContentName); + content.setCreator(remoteContent.getCreator()); + content.setSenders(remoteContent.getSenders()); - return; - } + ProtocolProviderServiceJabberImpl protocolProvider + = getProtocolProvider(); + JingleIQ contentIQ + = JinglePacketFactory.createContentRemove( + protocolProvider.getOurJID(), + this.peerJID, + getJingleSID(), + Arrays.asList(content)); - synchronized(candSyncRoot) - { - candSyncRoot.notify(); - } + protocolProvider.getConnection().sendPacket(contentIQ); + mediaHandler.removeContent(remoteContentName); } /** 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 5469e92..726a883 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerJabberImpl.java @@ -496,8 +496,9 @@ public class CallPeerMediaHandlerJabberImpl //let's now see what was the format we announced as first and //configure the stream with it. + String contentName = ourContent.getName(); ContentPacketExtension theirContent - = this.remoteContentMap.get(ourContent.getName()); + = this.remoteContentMap.get(contentName); RtpDescriptionPacketExtension theirDescription = JingleUtils.getRtpDescription(theirContent); MediaFormat format = null; @@ -560,7 +561,7 @@ public class CallPeerMediaHandlerJabberImpl // create the corresponding stream... initStream( - ourContent.getName(), + contentName, connector, dev, format, diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoTelephonyJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoTelephonyJabberImpl.java index 76f6060..7d06db1 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoTelephonyJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoTelephonyJabberImpl.java @@ -66,7 +66,7 @@ public class OperationSetVideoTelephonyJabberImpl { super.setLocalVideoAllowed(call, allowed); - ((CallJabberImpl)call).modifyVideoContent(allowed); + ((AbstractCallJabberGTalkImpl<?>) call).modifyVideoContent(allowed); } /** @@ -171,10 +171,10 @@ public class OperationSetVideoTelephonyJabberImpl */ public QualityControl getQualityControl(CallPeer peer) { - if(peer instanceof CallPeerJabberImpl) - return ((CallPeerJabberImpl) peer).getMediaHandler(). - getQualityControl(); - else - return null; + return + (peer instanceof CallPeerJabberImpl) + ? ((CallPeerJabberImpl) peer).getMediaHandler() + .getQualityControl() + : null; } } |