/** * * Copyright the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.smackx.jingleold; import java.util.ArrayList; import java.util.List; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smackx.jingleold.listeners.JingleListener; import org.jivesoftware.smackx.jingleold.listeners.JingleSessionListener; import org.jivesoftware.smackx.jingleold.media.JingleMediaManager; import org.jivesoftware.smackx.jingleold.media.JingleMediaSession; import org.jivesoftware.smackx.jingleold.media.MediaNegotiator; import org.jivesoftware.smackx.jingleold.media.PayloadType; import org.jivesoftware.smackx.jingleold.nat.JingleTransportManager; import org.jivesoftware.smackx.jingleold.nat.TransportCandidate; import org.jivesoftware.smackx.jingleold.nat.TransportNegotiator; import org.jivesoftware.smackx.jingleold.packet.Jingle; import org.jivesoftware.smackx.jingleold.packet.JingleContent; /** * Content negotiator. * * @author Jeff Williams */ public class ContentNegotiator extends JingleNegotiator { public static final String INITIATOR = "initiator"; public static final String RESPONDER = "responder"; private final List transportNegotiators; private MediaNegotiator mediaNeg; // The description... private TransportNegotiator transNeg; // and transport negotiators private JingleTransportManager jingleTransportManager; private final String creator; private final String name; private JingleMediaSession jingleMediaSession = null; public ContentNegotiator(JingleSession session, String inCreator, String inName) { super(session); creator = inCreator; name = inName; transportNegotiators = new ArrayList<>(); } @Override public List dispatchIncomingPacket(IQ iq, String id) throws XMPPException, SmackException, InterruptedException { List responses = new ArrayList<>(); // First only process IQ packets that contain stanzas that // match this media manager. if (iq != null) { if (iq.getType().equals(IQ.Type.error)) { // Process errors // TODO getState().eventError(iq); } else if (iq.getType().equals(IQ.Type.result)) { // Process ACKs if (isExpectedId(iq.getStanzaId())) { removeExpectedId(iq.getStanzaId()); } } else if (iq instanceof Jingle) { Jingle jingle = (Jingle) iq; // There are 1 or more sections in a Jingle packet. // Find out which section belongs to this content negotiator, and // then dispatch the Jingle packet to the media and transport negotiators. for (JingleContent jingleContent : jingle.getContentsList()) { if (jingleContent.getName().equals(name)) { if (mediaNeg != null) { responses.addAll(mediaNeg.dispatchIncomingPacket(iq, id)); } if (transNeg != null) { responses.addAll(transNeg.dispatchIncomingPacket(iq, id)); } } } } } return responses; } public String getCreator() { return creator; } public String getName() { return name; } /** * Get the JingleMediaSession of this Jingle Session. * * @return the JingleMediaSession */ public JingleMediaSession getJingleMediaSession() { return jingleMediaSession; } public void addTransportNegotiator(TransportNegotiator transportNegotiator) { transportNegotiators.add(transportNegotiator); } /** * Set jingle transport manager. * @param jingleTransportManager TODO javadoc me please */ public void setJingleTransportManager(JingleTransportManager jingleTransportManager) { this.jingleTransportManager = jingleTransportManager; } /** * Get jingle transport manager. * @return the JingleTransportManager */ public JingleTransportManager getTransportManager() { return jingleTransportManager; } /** * Called from above when starting a new session. */ @Override protected void doStart() { // JingleContent result = new JingleContent(creator, name); // result.setDescription(mediaNeg.start()); // result.addJingleTransport(transNeg.start()); // // return result; mediaNeg.start(); transNeg.start(); } /** * Prepare to close the media manager. */ @Override public void close() { destroyMediaNegotiator(); destroyTransportNegotiator(); } /** * Obtain the description negotiator for this session. * * @return the description negotiator */ public MediaNegotiator getMediaNegotiator() { return mediaNeg; } /** * Set the jmf negotiator. * * @param mediaNeg TODO javadoc me please * the description negotiator to set */ protected void setMediaNegotiator(MediaNegotiator mediaNeg) { destroyMediaNegotiator(); this.mediaNeg = mediaNeg; } /** * Destroy the jmf negotiator. */ protected void destroyMediaNegotiator() { if (mediaNeg != null) { mediaNeg.close(); mediaNeg = null; } } /** * Obtain the transport negotiator for this session. * * @return the transport negotiator instance */ public TransportNegotiator getTransportNegotiator() { return transNeg; } /** * Set TransportNegotiator * * @param transNeg TODO javadoc me please * the transNeg to set */ protected void setTransportNegotiator(TransportNegotiator transNeg) { destroyTransportNegotiator(); this.transNeg = transNeg; } /** * Destroy the transport negotiator. */ protected void destroyTransportNegotiator() { if (transNeg != null) { transNeg.close(); transNeg = null; } } /** * Return true if the transport and content negotiators have finished. * * @return true if fully established. */ public boolean isFullyEstablished() { boolean result = true; MediaNegotiator mediaNeg = getMediaNegotiator(); if ((mediaNeg == null) || !mediaNeg.isFullyEstablished()) { result = false; } TransportNegotiator transNeg = getTransportNegotiator(); if ((transNeg == null) || !transNeg.isFullyEstablished()) { result = false; } return result; } public JingleContent getJingleContent() { JingleContent result = new JingleContent(creator, name); // PayloadType.Audio bestCommonAudioPt = getMediaNegotiator().getBestCommonAudioPt(); // TransportCandidate bestRemoteCandidate = getTransportNegotiator().getBestRemoteCandidate(); // // // Ok, send a packet saying that we accept this session // // with the audio payload type and the transport // // candidate // result.setDescription(new JingleDescription.Audio(new PayloadType(bestCommonAudioPt))); // result.addJingleTransport(this.getTransportNegotiator().getJingleTransport(bestRemoteCandidate)); if (mediaNeg != null) { result.setDescription(mediaNeg.getJingleDescription()); } if (transNeg != null) { result.addJingleTransport(transNeg.getJingleTransport()); } return result; } public void triggerContentEstablished() throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException { PayloadType bestCommonAudioPt = getMediaNegotiator().getBestCommonAudioPt(); TransportCandidate bestRemoteCandidate = getTransportNegotiator().getBestRemoteCandidate(); TransportCandidate acceptedLocalCandidate = getTransportNegotiator().getAcceptedLocalCandidate(); // Trigger the session established flag triggerContentEstablished(bestCommonAudioPt, bestRemoteCandidate, acceptedLocalCandidate); } /** * Trigger a session established event. * @throws NotConnectedException if the XMPP connection is not connected. * @throws InterruptedException if the calling thread was interrupted. * @throws XMPPErrorException if there was an XMPP error returned. * @throws NoResponseException if there was no response from the remote entity. */ private void triggerContentEstablished(PayloadType pt, TransportCandidate rc, TransportCandidate lc) throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException { // Let the session know that we've established a content/media segment. JingleSession session = getSession(); if (session != null) { List listeners = session.getListenersList(); for (JingleListener li : listeners) { if (li instanceof JingleSessionListener) { JingleSessionListener sli = (JingleSessionListener) li; sli.sessionEstablished(pt, rc, lc, session); } } } // Create a media session for each media manager in the session. if (mediaNeg.getMediaManager() != null) { rc.removeCandidateEcho(); lc.removeCandidateEcho(); jingleMediaSession = getMediaNegotiator().getMediaManager().createMediaSession(pt, rc, lc, session); jingleMediaSession.addMediaReceivedListener(session); if (jingleMediaSession != null) { jingleMediaSession.startTransmit(); jingleMediaSession.startReceive(); for (TransportCandidate candidate : getTransportNegotiator().getOfferedCandidates()) candidate.removeCandidateEcho(); } JingleMediaManager mediaManager = getMediaNegotiator().getMediaManager(); getSession().addJingleMediaSession(mediaManager.getName(), jingleMediaSession); } } /** * Stop a Jingle media session. */ public void stopJingleMediaSession() { if (jingleMediaSession != null) { jingleMediaSession.stopTransmit(); jingleMediaSession.stopReceive(); } } /** * The negotiator state for the ContentNegotiators is a special case. * It is a roll-up of the sub-negotiator states. */ @Override public JingleNegotiatorState getNegotiatorState() { JingleNegotiatorState result = JingleNegotiatorState.PENDING; if ((mediaNeg != null) && (transNeg != null)) { if ((mediaNeg.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED) || (transNeg.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED)) result = JingleNegotiatorState.SUCCEEDED; if ((mediaNeg.getNegotiatorState() == JingleNegotiatorState.FAILED) || (transNeg.getNegotiatorState() == JingleNegotiatorState.FAILED)) result = JingleNegotiatorState.FAILED; } // Store the state (to make it easier to know when debugging.) setNegotiatorState(result); return result; } }