All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.lastbamboo.common.ice.IceOfferAnswerFactory Maven / Gradle / Ivy

package org.lastbamboo.common.ice;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Collection;
import java.util.HashSet;

import javax.net.SocketFactory;

import org.lastbamboo.common.ice.candidate.IceCandidate;
import org.lastbamboo.common.ice.sdp.IceCandidateSdpEncoder;
import org.lastbamboo.common.offer.answer.IceMediaStreamDesc;
import org.lastbamboo.common.offer.answer.OfferAnswer;
import org.lastbamboo.common.offer.answer.OfferAnswerConnectException;
import org.lastbamboo.common.offer.answer.OfferAnswerFactory;
import org.lastbamboo.common.offer.answer.OfferAnswerListener;
import org.lastbamboo.common.turn.client.TurnClientListener;
import org.littleshoot.mina.common.ByteBuffer;
import org.littleshoot.util.CandidateProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Class for creating ICE agents that process ICE offers and answers.
 */
public class IceOfferAnswerFactory implements OfferAnswerFactory {

    private final Logger m_log = LoggerFactory.getLogger(getClass());
    
    private final IceMediaStreamFactory m_mediaStreamFactory;
    private final UdpSocketFactory m_udpSocketFactory;
    private final CandidateProvider m_turnCandidateProvider;
    private final MappedServerSocket m_answererServer;

    private final TurnClientListener m_turnClientListener;

    private final CandidateProvider m_stunCandidateProvider;

    private final MappedTcpOffererServerPool m_offererServer;

    private final SocketFactory m_socketFactory;

    /**
     * Creates a new ICE agent factory. The factory maintains a reference to
     * the TCP TURN client because the client holds a persistent connection
     * to the TURN server and is used across all ICE sessions.
     * 
     * @param mediaStreamFactory The factory for creating ICE media streams.
     * @param udpSocketFactory Factory for creating reliable UDP sockets.
     * @param streamDesc Data for the type of stream to create, such as TCP,
     * UPD, or either.
     * @param answererServer The single router port-mapped server socket for
     * when we're the answerer.
     * @param stunCandidateProvider Provider for STUN servers.
     * @param offererServer The pool of mapped servers to send from the
     * offering side.
     */
    public IceOfferAnswerFactory(
            final IceMediaStreamFactory mediaStreamFactory,
            final UdpSocketFactory udpSocketFactory,
            final CandidateProvider turnCandidateProvider,
            final MappedServerSocket answererServer,
            final TurnClientListener turnClientListener, 
            final CandidateProvider stunCandidateProvider, 
            final MappedTcpOffererServerPool offererServer,
            final SocketFactory socketFactory) {
        this.m_mediaStreamFactory = mediaStreamFactory;
        this.m_udpSocketFactory = udpSocketFactory;
        this.m_turnCandidateProvider = turnCandidateProvider;
        this.m_answererServer = answererServer;
        this.m_turnClientListener = turnClientListener;
        this.m_stunCandidateProvider = stunCandidateProvider;
        this.m_offererServer = offererServer;
        this.m_socketFactory = socketFactory;
    }

    @Override
    public OfferAnswer createAnswerer(
            final OfferAnswerListener offerAnswerListener, 
            final boolean useRelay)
            throws OfferAnswerConnectException {
        return createOfferAnswer(false, offerAnswerListener, 
            new IceMediaStreamDesc(true, true, "message", "http", 1, useRelay, 
                true));
    }

    @Override
    public OfferAnswer createOfferer(
            final OfferAnswerListener offerAnswerListener,
            final IceMediaStreamDesc desc)
            throws OfferAnswerConnectException {
        return createOfferAnswer(true, offerAnswerListener, desc);
    }

    private OfferAnswer createOfferAnswer(final boolean controlling,
            final OfferAnswerListener offerAnswerListener,
            final IceMediaStreamDesc mediaDesc)
            throws OfferAnswerConnectException {
        final IceOfferAnswer turnOfferAnswer = newTurnOfferAnswer(controlling,
                offerAnswerListener, mediaDesc);
        final IceOfferAnswer udp = newUdpOfferAnswer(controlling,
                offerAnswerListener, mediaDesc);

        final IceOfferAnswer tcp = 
            newTcpOfferAnswer(offerAnswerListener, controlling,mediaDesc);

        // We create a high-level class that starts a race between the TCP
        // and UDP connections. The TCP approach does not use ICE, instead
        // simplifying things significantly through using straight sockets,
        // either via UPnP, directly over an internal network, or when one of
        // the peers is on the public Internet.
        return new OfferAnswer() {
            @Override
            public byte[] generateOffer() {
                return encodeCandidates(controlling, tcp, udp, turnOfferAnswer, 
                    mediaDesc);
            }

            @Override
            public byte[] generateAnswer() {
                return encodeCandidates(controlling, tcp, udp, turnOfferAnswer,
                        mediaDesc);
            }

            @Override
            public void close() {
                if (tcp != null)
                    tcp.close();
                if (turnOfferAnswer != null)
                    turnOfferAnswer.close();
                if (udp != null)
                    udp.close();
            }

            @Override
            public void processAnswer(final ByteBuffer answer) {
                m_log.info("Processing answer...");
                m_log.info("Turn offer answer: {}", turnOfferAnswer);
                if (mediaDesc.isUseRelay() && turnOfferAnswer != null) {
                    turnOfferAnswer.processAnswer(answer.duplicate());
                }
                if (mediaDesc.isTcp() && tcp != null) {
                    tcp.processAnswer(answer.duplicate());
                }
                if (mediaDesc.isUdp() && udp != null) {
                    udp.processAnswer(answer.duplicate());
                }
            }

            @Override
            public void processOffer(final ByteBuffer offer) {
                m_log.info("Processing offer...");
                if (mediaDesc.isTcp() && tcp != null) {
                    tcp.processOffer(offer);
                }
                if (mediaDesc.isUdp() && udp != null) {
                    udp.processOffer(offer);
                }
                if (mediaDesc.isUseRelay() && turnOfferAnswer != null) {
                    turnOfferAnswer.processOffer(offer);
                }
                m_log.info("Done processing offer...");
            }

            @Override
            public void closeTcp() {
                if (tcp != null)
                    tcp.close();
                if (turnOfferAnswer != null)
                    turnOfferAnswer.close();
            }

            @Override
            public void closeUdp() {
                if (udp != null)
                    udp.close();
            }

            @Override
            public void useRelay() {
                m_log.info("Sending use relay notification.");
                if (tcp != null)
                    tcp.useRelay();
                if (turnOfferAnswer != null)
                    turnOfferAnswer.useRelay();
            }
        };
    }
    
    private IceOfferAnswer newTcpOfferAnswer(
            final OfferAnswerListener offerAnswerListener,
            final boolean controlling, final IceMediaStreamDesc mediaDesc) {
        if (mediaDesc.isTcp()) {
            m_log.info("Creating new TCP offer answer");
            return new TcpOfferAnswer(offerAnswerListener,
                controlling, m_answererServer, this.m_stunCandidateProvider, 
                this.m_offererServer, m_socketFactory);
        } else {
            return null;
        }
    }

    private IceOfferAnswer newUdpOfferAnswer(final boolean controlling,
            final OfferAnswerListener offerAnswerListener,
            final IceMediaStreamDesc mediaDesc)
            throws OfferAnswerConnectException {
        if (mediaDesc.isUdp()) {
            try {
                m_log.info("Creating UDP offer answer...");
                return new IceAgentImpl(this.m_mediaStreamFactory, controlling,
                        offerAnswerListener, this.m_udpSocketFactory,
                        new RawUdpSocketFactory(), mediaDesc);
            } catch (final IceUdpConnectException e) {
                throw new OfferAnswerConnectException(
                        "Could not create UDP connection", e);
            }
        }
        return null;
    }

    private byte[] encodeCandidates(final boolean controlling,
            final IceOfferAnswer tcp, final IceOfferAnswer udp,
            final IceOfferAnswer tcpTurn,
            final IceMediaStreamDesc mediaDesc) {
        final IceCandidateSdpEncoder encoder = new IceCandidateSdpEncoder(
                mediaDesc.getMimeContentType(),
                mediaDesc.getMimeContentSubtype());

        final Collection localCandidates = 
            new HashSet();
        if (tcp != null) {
            localCandidates.addAll(tcp.gatherCandidates());
        }
        if (udp != null) {
            localCandidates.addAll(udp.gatherCandidates());
        }
        if (!controlling && mediaDesc.isUseRelay() && tcpTurn != null) {
            localCandidates.addAll(tcpTurn.gatherCandidates());
        }
        encoder.visitCandidates(localCandidates);
        return encoder.getSdp();
    }

    /**
     * Creates a new TURN offer/answer.
     * 
     * @param controlling Whether or not this is the controlling ICE agent.
     * @param offerAnswerListener The listener for socket resolution.
     * @return The offer/answer for TURN.
     */
    private IceOfferAnswer newTurnOfferAnswer(final boolean controlling,
            final OfferAnswerListener offerAnswerListener,
            final IceMediaStreamDesc mediaDesc) {
        if (!mediaDesc.isUseRelay()) {
            return null;
        }

        try {
            final TcpTurnOfferAnswer turn = new TcpTurnOfferAnswer(
                m_turnCandidateProvider, controlling, offerAnswerListener,
                m_turnClientListener);

            // We only actually connect to the TURN server on the answerer/
            // non-controlling client.
            if (!controlling) {
                turn.connect();
            }
            return turn;
        } catch (final IOException e) {
            m_log.error("Could not connect to TURN server!!", e);
            return null;
        }
    }

    @Override
    public boolean isAnswererPortMapped() {
        return this.m_answererServer.isPortMapped();
    }
    
    @Override
    public int getMappedPort() {
        return this.m_answererServer.getMappedPort();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy