001/**
002 *
003 * Copyright the original author or authors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smackx.jingleold;
018
019import java.util.ArrayList;
020import java.util.List;
021
022import org.jivesoftware.smack.SmackException;
023import org.jivesoftware.smack.SmackException.NotConnectedException;
024import org.jivesoftware.smack.XMPPException;
025import org.jivesoftware.smack.packet.IQ;
026
027import org.jivesoftware.smackx.jingleold.listeners.JingleListener;
028import org.jivesoftware.smackx.jingleold.listeners.JingleSessionListener;
029import org.jivesoftware.smackx.jingleold.media.JingleMediaManager;
030import org.jivesoftware.smackx.jingleold.media.JingleMediaSession;
031import org.jivesoftware.smackx.jingleold.media.MediaNegotiator;
032import org.jivesoftware.smackx.jingleold.media.PayloadType;
033import org.jivesoftware.smackx.jingleold.nat.JingleTransportManager;
034import org.jivesoftware.smackx.jingleold.nat.TransportCandidate;
035import org.jivesoftware.smackx.jingleold.nat.TransportNegotiator;
036import org.jivesoftware.smackx.jingleold.packet.Jingle;
037import org.jivesoftware.smackx.jingleold.packet.JingleContent;
038
039/**
040 * Content negotiator.
041 *
042 *  @author Jeff Williams
043 */
044public class ContentNegotiator extends JingleNegotiator {
045
046    public static final String INITIATOR = "initiator";
047    public static final String RESPONDER = "responder";
048
049    private final List<TransportNegotiator> transportNegotiators;
050    private MediaNegotiator mediaNeg; // The description...
051    private TransportNegotiator transNeg; // and transport negotiators
052    private JingleTransportManager jingleTransportManager;
053    private final String creator;
054    private final String name;
055    private JingleMediaSession jingleMediaSession = null;
056
057    public ContentNegotiator(JingleSession session, String inCreator, String inName) {
058        super(session);
059        creator = inCreator;
060        name = inName;
061        transportNegotiators = new ArrayList<>();
062    }
063
064    @Override
065    public List<IQ> dispatchIncomingPacket(IQ iq, String id) throws XMPPException, SmackException, InterruptedException {
066        List<IQ> responses = new ArrayList<>();
067
068        // First only process IQ packets that contain <content> stanzas that
069        // match this media manager.
070
071        if (iq != null) {
072            if (iq.getType().equals(IQ.Type.error)) {
073                // Process errors
074                // TODO getState().eventError(iq);
075            } else if (iq.getType().equals(IQ.Type.result)) {
076                // Process ACKs
077                if (isExpectedId(iq.getStanzaId())) {
078                    removeExpectedId(iq.getStanzaId());
079                }
080            } else if (iq instanceof Jingle) {
081                Jingle jingle = (Jingle) iq;
082
083                // There are 1 or more <content> sections in a Jingle packet.
084                // Find out which <content> section belongs to this content negotiator, and
085                // then dispatch the Jingle packet to the media and transport negotiators.
086
087                for (JingleContent jingleContent : jingle.getContentsList()) {
088                    if (jingleContent.getName().equals(name)) {
089                        if (mediaNeg != null) {
090                            responses.addAll(mediaNeg.dispatchIncomingPacket(iq, id));
091                        }
092
093                        if (transNeg != null) {
094                            responses.addAll(transNeg.dispatchIncomingPacket(iq, id));
095                        }
096                    }
097
098                }
099            }
100        }
101        return responses;
102    }
103
104    public String getCreator() {
105        return creator;
106    }
107
108    public String getName() {
109        return name;
110    }
111
112    /**
113     * Get the JingleMediaSession of this Jingle Session.
114     *
115     * @return the JingleMediaSession
116     */
117    public JingleMediaSession getJingleMediaSession() {
118        return jingleMediaSession;
119    }
120
121    public void addTransportNegotiator(TransportNegotiator transportNegotiator) {
122        transportNegotiators.add(transportNegotiator);
123    }
124
125    /**
126     * Set jingle transport manager.
127     * @param jingleTransportManager TODO javadoc me please
128     */
129    public void setJingleTransportManager(JingleTransportManager jingleTransportManager) {
130        this.jingleTransportManager = jingleTransportManager;
131    }
132
133    /**
134     * Get jingle transport manager.
135     * @return the JingleTransportManager
136     */
137    public JingleTransportManager getTransportManager() {
138        return jingleTransportManager;
139    }
140
141    /**
142     * Called from above when starting a new session.
143     */
144    @Override
145    protected void doStart() {
146        // JingleContent result = new JingleContent(creator, name);
147
148        //        result.setDescription(mediaNeg.start());
149        //        result.addJingleTransport(transNeg.start());
150        //
151        //        return result;
152
153        mediaNeg.start();
154        transNeg.start();
155    }
156
157    /**
158     * Prepare to close the media manager.
159     */
160    @Override
161    public void close() {
162        destroyMediaNegotiator();
163        destroyTransportNegotiator();
164    }
165
166    /**
167     * Obtain the description negotiator for this session.
168     *
169     * @return the description negotiator
170     */
171    public MediaNegotiator getMediaNegotiator() {
172        return mediaNeg;
173    }
174
175    /**
176     * Set the jmf negotiator.
177     *
178     * @param mediaNeg TODO javadoc me please
179     *            the description negotiator to set
180     */
181    protected void setMediaNegotiator(MediaNegotiator mediaNeg) {
182        destroyMediaNegotiator();
183        this.mediaNeg = mediaNeg;
184    }
185
186    /**
187     * Destroy the jmf negotiator.
188     */
189    protected void destroyMediaNegotiator() {
190        if (mediaNeg != null) {
191            mediaNeg.close();
192            mediaNeg = null;
193        }
194    }
195
196    /**
197     * Obtain the transport negotiator for this session.
198     *
199     * @return the transport negotiator instance
200     */
201    public TransportNegotiator getTransportNegotiator() {
202        return transNeg;
203    }
204
205    /**
206     * Set TransportNegotiator
207     *
208     * @param transNeg TODO javadoc me please
209     *            the transNeg to set
210     */
211    protected void setTransportNegotiator(TransportNegotiator transNeg) {
212        destroyTransportNegotiator();
213        this.transNeg = transNeg;
214    }
215
216    /**
217     * Destroy the transport negotiator.
218     */
219    protected void destroyTransportNegotiator() {
220        if (transNeg != null) {
221            transNeg.close();
222            transNeg = null;
223        }
224    }
225
226    /**
227     * Return true if the transport and content negotiators have finished.
228     *
229     * @return <code>true</code> if fully established.
230     */
231    public boolean isFullyEstablished() {
232        boolean result = true;
233
234        MediaNegotiator mediaNeg = getMediaNegotiator();
235        if ((mediaNeg == null) || !mediaNeg.isFullyEstablished()) {
236            result = false;
237        }
238
239        TransportNegotiator transNeg = getTransportNegotiator();
240        if ((transNeg == null) || !transNeg.isFullyEstablished()) {
241            result = false;
242        }
243
244        return result;
245    }
246
247    public JingleContent getJingleContent() {
248        JingleContent result = new JingleContent(creator, name);
249
250        //            PayloadType.Audio bestCommonAudioPt = getMediaNegotiator().getBestCommonAudioPt();
251        //            TransportCandidate bestRemoteCandidate = getTransportNegotiator().getBestRemoteCandidate();
252        //
253        //            // Ok, send a packet saying that we accept this session
254        //            // with the audio payload type and the transport
255        //            // candidate
256        //            result.setDescription(new JingleDescription.Audio(new PayloadType(bestCommonAudioPt)));
257        //            result.addJingleTransport(this.getTransportNegotiator().getJingleTransport(bestRemoteCandidate));
258        if (mediaNeg != null) {
259            result.setDescription(mediaNeg.getJingleDescription());
260        }
261        if (transNeg != null) {
262            result.addJingleTransport(transNeg.getJingleTransport());
263        }
264
265        return result;
266    }
267
268    public void triggerContentEstablished() throws NotConnectedException, InterruptedException {
269
270        PayloadType bestCommonAudioPt = getMediaNegotiator().getBestCommonAudioPt();
271        TransportCandidate bestRemoteCandidate = getTransportNegotiator().getBestRemoteCandidate();
272        TransportCandidate acceptedLocalCandidate = getTransportNegotiator().getAcceptedLocalCandidate();
273
274        // Trigger the session established flag
275        triggerContentEstablished(bestCommonAudioPt, bestRemoteCandidate, acceptedLocalCandidate);
276    }
277
278    /**
279     * Trigger a session established event.
280     * @throws NotConnectedException if the XMPP connection is not connected.
281     * @throws InterruptedException if the calling thread was interrupted.
282     */
283    private void triggerContentEstablished(PayloadType pt, TransportCandidate rc, TransportCandidate lc) throws NotConnectedException, InterruptedException {
284
285        // Let the session know that we've established a content/media segment.
286        JingleSession session = getSession();
287        if (session != null) {
288            List<JingleListener> listeners = session.getListenersList();
289            for (JingleListener li : listeners) {
290                if (li instanceof JingleSessionListener) {
291                    JingleSessionListener sli = (JingleSessionListener) li;
292                    sli.sessionEstablished(pt, rc, lc, session);
293                }
294            }
295        }
296
297        // Create a media session for each media manager in the session.
298        if (mediaNeg.getMediaManager() != null) {
299            rc.removeCandidateEcho();
300            lc.removeCandidateEcho();
301
302            jingleMediaSession = getMediaNegotiator().getMediaManager().createMediaSession(pt, rc, lc, session);
303            jingleMediaSession.addMediaReceivedListener(session);
304            if (jingleMediaSession != null) {
305
306                jingleMediaSession.startTransmit();
307                jingleMediaSession.startReceive();
308
309                for (TransportCandidate candidate : getTransportNegotiator().getOfferedCandidates())
310                    candidate.removeCandidateEcho();
311            }
312            JingleMediaManager mediaManager = getMediaNegotiator().getMediaManager();
313            getSession().addJingleMediaSession(mediaManager.getName(), jingleMediaSession);
314        }
315
316    }
317
318    /**
319     *  Stop a Jingle media session.
320     */
321    public void stopJingleMediaSession() {
322
323        if (jingleMediaSession != null) {
324            jingleMediaSession.stopTransmit();
325            jingleMediaSession.stopReceive();
326        }
327    }
328
329    /**
330     * The negotiator state for the ContentNegotiators is a special case.
331     * It is a roll-up of the sub-negotiator states.
332     */
333    @Override
334    public JingleNegotiatorState getNegotiatorState() {
335        JingleNegotiatorState result = JingleNegotiatorState.PENDING;
336
337        if ((mediaNeg != null) && (transNeg != null)) {
338
339            if ((mediaNeg.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED)
340                    || (transNeg.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED))
341                result = JingleNegotiatorState.SUCCEEDED;
342
343            if ((mediaNeg.getNegotiatorState() == JingleNegotiatorState.FAILED)
344                    || (transNeg.getNegotiatorState() == JingleNegotiatorState.FAILED))
345                result = JingleNegotiatorState.FAILED;
346        }
347
348        // Store the state (to make it easier to know when debugging.)
349        setNegotiatorState(result);
350
351        return result;
352    }
353}