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