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