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