diff options
| author | Jeremy Lainé <jeremy.laine@m4x.org> | 2012-02-08 12:33:41 +0000 |
|---|---|---|
| committer | Jeremy Lainé <jeremy.laine@m4x.org> | 2012-02-08 12:33:41 +0000 |
| commit | 21acd67e9b65bea87902032b12709675905aa922 (patch) | |
| tree | ed5ae9066b10400c4fe6e67dfaf2f4c37a09c32e /src/client/QXmppCallManager.cpp | |
| parent | cea7ae1e702b82d2d0d0a851de1aae58270b14f6 (diff) | |
| download | qxmpp-21acd67e9b65bea87902032b12709675905aa922.tar.gz | |
start moving client-specific code
Diffstat (limited to 'src/client/QXmppCallManager.cpp')
| -rw-r--r-- | src/client/QXmppCallManager.cpp | 1005 |
1 files changed, 1005 insertions, 0 deletions
diff --git a/src/client/QXmppCallManager.cpp b/src/client/QXmppCallManager.cpp new file mode 100644 index 00000000..363881af --- /dev/null +++ b/src/client/QXmppCallManager.cpp @@ -0,0 +1,1005 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#include <QDomElement> +#include <QTimer> + +#include "QXmppCallManager.h" +#include "QXmppClient.h" +#include "QXmppCodec.h" +#include "QXmppConstants.h" +#include "QXmppJingleIq.h" +#include "QXmppRtpChannel.h" +#include "QXmppStun.h" +#include "QXmppUtils.h" + +static int typeId = qRegisterMetaType<QXmppCall::State>(); + +static const int RTP_COMPONENT = 1; +static const int RTCP_COMPONENT = 2; + +static const QLatin1String AUDIO_MEDIA("audio"); +static const QLatin1String VIDEO_MEDIA("video"); + +class QXmppCallPrivate +{ +public: + class Stream { + public: + QXmppRtpChannel *channel; + QXmppIceConnection *connection; + QString creator; + QString media; + QString name; + }; + + QXmppCallPrivate(QXmppCall *qq); + Stream *createStream(const QString &media); + Stream *findStreamByMedia(const QString &media); + Stream *findStreamByName(const QString &name); + void handleAck(const QXmppIq &iq); + bool handleDescription(QXmppCallPrivate::Stream *stream, const QXmppJingleIq::Content &content); + void handleRequest(const QXmppJingleIq &iq); + bool handleTransport(QXmppCallPrivate::Stream *stream, const QXmppJingleIq::Content &content); + void setState(QXmppCall::State state); + bool sendAck(const QXmppJingleIq &iq); + bool sendInvite(); + bool sendRequest(const QXmppJingleIq &iq); + void terminate(QXmppJingleIq::Reason::Type reasonType); + + QXmppCall::Direction direction; + QString jid; + QString ownJid; + QXmppCallManager *manager; + QList<QXmppJingleIq> requests; + QString sid; + QXmppCall::State state; + + // Media streams + bool sendVideo; + QList<Stream*> streams; + QIODevice::OpenMode audioMode; + QIODevice::OpenMode videoMode; + +private: + QXmppCall *q; +}; + +class QXmppCallManagerPrivate +{ +public: + QXmppCallManagerPrivate(QXmppCallManager *qq); + QXmppCall *findCall(const QString &sid) const; + QXmppCall *findCall(const QString &sid, QXmppCall::Direction direction) const; + + QList<QXmppCall*> calls; + QHostAddress stunHost; + quint16 stunPort; + QHostAddress turnHost; + quint16 turnPort; + QString turnUser; + QString turnPassword; + +private: + QXmppCallManager *q; +}; + +QXmppCallPrivate::QXmppCallPrivate(QXmppCall *qq) + : state(QXmppCall::ConnectingState), + sendVideo(false), + audioMode(QIODevice::NotOpen), + videoMode(QIODevice::NotOpen), + q(qq) +{ +} + +QXmppCallPrivate::Stream *QXmppCallPrivate::findStreamByMedia(const QString &media) +{ + foreach (Stream *stream, streams) + if (stream->media == media) + return stream; + return 0; +} + +QXmppCallPrivate::Stream *QXmppCallPrivate::findStreamByName(const QString &name) +{ + foreach (Stream *stream, streams) + if (stream->name == name) + return stream; + return 0; +} + +void QXmppCallPrivate::handleAck(const QXmppIq &ack) +{ + const QString id = ack.id(); + for (int i = 0; i < requests.size(); ++i) { + if (id == requests[i].id()) { + // process acknowledgement + const QXmppJingleIq request = requests.takeAt(i); + q->debug(QString("Received ACK for packet %1").arg(id)); + + // handle termination + if (request.action() == QXmppJingleIq::SessionTerminate) + q->terminated(); + return; + } + } +} + +bool QXmppCallPrivate::handleDescription(QXmppCallPrivate::Stream *stream, const QXmppJingleIq::Content &content) +{ + stream->channel->setRemotePayloadTypes(content.payloadTypes()); + if (!(stream->channel->openMode() & QIODevice::ReadWrite)) { + q->warning(QString("Remote party %1 did not provide any known %2 payloads for call %3").arg(jid, stream->media, sid)); + return false; + } + q->updateOpenMode(); + return true; +} + +bool QXmppCallPrivate::handleTransport(QXmppCallPrivate::Stream *stream, const QXmppJingleIq::Content &content) +{ + stream->connection->setRemoteUser(content.transportUser()); + stream->connection->setRemotePassword(content.transportPassword()); + foreach (const QXmppJingleCandidate &candidate, content.transportCandidates()) + stream->connection->addRemoteCandidate(candidate); + + // perform ICE negotiation + if (!content.transportCandidates().isEmpty()) + stream->connection->connectToHost(); + return true; +} + +void QXmppCallPrivate::handleRequest(const QXmppJingleIq &iq) +{ + if (iq.action() == QXmppJingleIq::SessionAccept) { + + if (direction == QXmppCall::IncomingDirection) { + q->warning("Ignoring Session-Accept for an incoming call"); + return; + } + + // send ack + sendAck(iq); + + // check content description and transport + QXmppCallPrivate::Stream *stream = findStreamByName(iq.content().name()); + if (!stream || + !handleDescription(stream, iq.content()) || + !handleTransport(stream, iq.content())) { + + // terminate call + terminate(QXmppJingleIq::Reason::FailedApplication); + return; + } + + // check for call establishment + setState(QXmppCall::ActiveState); + + } else if (iq.action() == QXmppJingleIq::SessionInfo) { + + // notify user + QTimer::singleShot(0, q, SIGNAL(ringing())); + + } else if (iq.action() == QXmppJingleIq::SessionTerminate) { + + // send ack + sendAck(iq); + + // terminate + q->info(QString("Remote party %1 terminated call %2").arg(iq.from(), iq.sid())); + q->terminated(); + + } else if (iq.action() == QXmppJingleIq::ContentAccept) { + + // send ack + sendAck(iq); + + // check content description and transport + QXmppCallPrivate::Stream *stream = findStreamByName(iq.content().name()); + if (!stream || + !handleDescription(stream, iq.content()) || + !handleTransport(stream, iq.content())) { + + // FIXME: what action? + return; + } + + } else if (iq.action() == QXmppJingleIq::ContentAdd) { + + // send ack + sendAck(iq); + + // check media stream does not exist yet + QXmppCallPrivate::Stream *stream = findStreamByName(iq.content().name()); + if (stream) + return; + + // create media stream + stream = createStream(iq.content().descriptionMedia()); + if (!stream) + return; + stream->creator = iq.content().creator(); + stream->name = iq.content().name(); + + // check content description + if (!handleDescription(stream, iq.content()) || + !handleTransport(stream, iq.content())) { + + QXmppJingleIq iq; + iq.setTo(q->jid()); + iq.setType(QXmppIq::Set); + iq.setAction(QXmppJingleIq::ContentReject); + iq.setSid(q->sid()); + iq.reason().setType(QXmppJingleIq::Reason::FailedApplication); + sendRequest(iq); + delete stream; + return; + } + streams << stream; + + // accept content + QXmppJingleIq iq; + iq.setTo(q->jid()); + iq.setType(QXmppIq::Set); + iq.setAction(QXmppJingleIq::ContentAccept); + iq.setSid(q->sid()); + iq.content().setCreator(stream->creator); + iq.content().setName(stream->name); + + // description + iq.content().setDescriptionMedia(stream->media); + foreach (const QXmppJinglePayloadType &payload, stream->channel->localPayloadTypes()) + iq.content().addPayloadType(payload); + + // transport + iq.content().setTransportUser(stream->connection->localUser()); + iq.content().setTransportPassword(stream->connection->localPassword()); + foreach (const QXmppJingleCandidate &candidate, stream->connection->localCandidates()) + iq.content().addTransportCandidate(candidate); + + sendRequest(iq); + + } else if (iq.action() == QXmppJingleIq::TransportInfo) { + + // send ack + sendAck(iq); + + // check content transport + QXmppCallPrivate::Stream *stream = findStreamByName(iq.content().name()); + if (!stream || + !handleTransport(stream, iq.content())) { + // FIXME: what action? + return; + } + + } +} + +QXmppCallPrivate::Stream *QXmppCallPrivate::createStream(const QString &media) +{ + bool check; + Q_UNUSED(check); + Q_ASSERT(manager); + + Stream *stream = new Stream; + stream->media = media; + + // RTP channel + QObject *channelObject = 0; + if (media == AUDIO_MEDIA) { + QXmppRtpAudioChannel *audioChannel = new QXmppRtpAudioChannel(q); + stream->channel = audioChannel; + channelObject = audioChannel; + } else if (media == VIDEO_MEDIA) { + QXmppRtpVideoChannel *videoChannel = new QXmppRtpVideoChannel(q); + stream->channel = videoChannel; + channelObject = videoChannel; + } else { + q->warning(QString("Unsupported media type %1").arg(media)); + delete stream; + return 0; + } + + // ICE connection + stream->connection = new QXmppIceConnection(q); + stream->connection->setIceControlling(direction == QXmppCall::OutgoingDirection); + stream->connection->setStunServer(manager->d->stunHost, manager->d->stunPort); + stream->connection->setTurnServer(manager->d->turnHost, manager->d->turnPort); + stream->connection->setTurnUser(manager->d->turnUser); + stream->connection->setTurnPassword(manager->d->turnPassword); + stream->connection->addComponent(RTP_COMPONENT); + stream->connection->addComponent(RTCP_COMPONENT); + stream->connection->bind(QXmppIceComponent::discoverAddresses()); + + // connect signals + check = QObject::connect(stream->connection, SIGNAL(localCandidatesChanged()), + q, SLOT(localCandidatesChanged())); + Q_ASSERT(check); + + check = QObject::connect(stream->connection, SIGNAL(connected()), + q, SLOT(updateOpenMode())); + Q_ASSERT(check); + + check = QObject::connect(q, SIGNAL(stateChanged(QXmppCall::State)), + q, SLOT(updateOpenMode())); + Q_ASSERT(check); + + check = QObject::connect(stream->connection, SIGNAL(disconnected()), + q, SLOT(hangup())); + Q_ASSERT(check); + + if (channelObject) { + QXmppIceComponent *rtpComponent = stream->connection->component(RTP_COMPONENT); + + check = QObject::connect(rtpComponent, SIGNAL(datagramReceived(QByteArray)), + channelObject, SLOT(datagramReceived(QByteArray))); + Q_ASSERT(check); + + check = QObject::connect(channelObject, SIGNAL(sendDatagram(QByteArray)), + rtpComponent, SLOT(sendDatagram(QByteArray))); + Q_ASSERT(check); + } + return stream; +} + +/// Sends an acknowledgement for a Jingle IQ. +/// + +bool QXmppCallPrivate::sendAck(const QXmppJingleIq &iq) +{ + QXmppIq ack; + ack.setId(iq.id()); + ack.setTo(iq.from()); + ack.setType(QXmppIq::Result); + return manager->client()->sendPacket(ack); +} + +bool QXmppCallPrivate::sendInvite() +{ + QXmppJingleIq iq; + iq.setTo(jid); + iq.setType(QXmppIq::Set); + iq.setAction(QXmppJingleIq::SessionInitiate); + iq.setInitiator(ownJid); + iq.setSid(sid); + + // create audio stream + QXmppCallPrivate::Stream *stream = findStreamByMedia(AUDIO_MEDIA); + Q_ASSERT(stream); + iq.content().setCreator(stream->creator); + iq.content().setName(stream->name); + iq.content().setSenders("both"); + + // description + iq.content().setDescriptionMedia(stream->media); + foreach (const QXmppJinglePayloadType &payload, stream->channel->localPayloadTypes()) + iq.content().addPayloadType(payload); + + // transport + iq.content().setTransportUser(stream->connection->localUser()); + iq.content().setTransportPassword(stream->connection->localPassword()); + foreach (const QXmppJingleCandidate &candidate, stream->connection->localCandidates()) + iq.content().addTransportCandidate(candidate); + + return sendRequest(iq); +} + +/// Sends a Jingle IQ and adds it to outstanding requests. +/// + +bool QXmppCallPrivate::sendRequest(const QXmppJingleIq &iq) +{ + requests << iq; + return manager->client()->sendPacket(iq); +} + +void QXmppCallPrivate::setState(QXmppCall::State newState) +{ + if (state != newState) + { + state = newState; + emit q->stateChanged(state); + + if (state == QXmppCall::ActiveState) + emit q->connected(); + else if (state == QXmppCall::FinishedState) + emit q->finished(); + } +} + +/// Request graceful call termination + +void QXmppCallPrivate::terminate(QXmppJingleIq::Reason::Type reasonType) +{ + if (state == QXmppCall::DisconnectingState || + state == QXmppCall::FinishedState) + return; + + // hangup call + QXmppJingleIq iq; + iq.setTo(jid); + iq.setType(QXmppIq::Set); + iq.setAction(QXmppJingleIq::SessionTerminate); + iq.setSid(sid); + iq.reason().setType(reasonType); + sendRequest(iq); + setState(QXmppCall::DisconnectingState); + + // schedule forceful termination in 5s + QTimer::singleShot(5000, q, SLOT(terminated())); +} + +QXmppCall::QXmppCall(const QString &jid, QXmppCall::Direction direction, QXmppCallManager *parent) + : QXmppLoggable(parent) +{ + d = new QXmppCallPrivate(this); + d->direction = direction; + d->jid = jid; + d->ownJid = parent->client()->configuration().jid(); + d->manager = parent; + + // create audio stream + QXmppCallPrivate::Stream *stream = d->createStream(AUDIO_MEDIA); + stream->creator = QLatin1String("initiator"); + stream->name = QLatin1String("voice"); + d->streams << stream; +} + +QXmppCall::~QXmppCall() +{ + foreach (QXmppCallPrivate::Stream *stream, d->streams) + delete stream; + delete d; +} + +/// Call this method if you wish to accept an incoming call. +/// + +void QXmppCall::accept() +{ + if (d->direction == IncomingDirection && d->state == ConnectingState) + { + Q_ASSERT(d->streams.size() == 1); + QXmppCallPrivate::Stream *stream = d->streams.first(); + + // accept incoming call + QXmppJingleIq iq; + iq.setTo(d->jid); + iq.setType(QXmppIq::Set); + iq.setAction(QXmppJingleIq::SessionAccept); + iq.setResponder(d->ownJid); + iq.setSid(d->sid); + iq.content().setCreator(stream->creator); + iq.content().setName(stream->name); + + // description + iq.content().setDescriptionMedia(stream->media); + foreach (const QXmppJinglePayloadType &payload, stream->channel->localPayloadTypes()) + iq.content().addPayloadType(payload); + + // transport + iq.content().setTransportUser(stream->connection->localUser()); + iq.content().setTransportPassword(stream->connection->localPassword()); + foreach (const QXmppJingleCandidate &candidate, stream->connection->localCandidates()) + iq.content().addTransportCandidate(candidate); + + d->sendRequest(iq); + + // notify user + d->manager->callStarted(this); + + // check for call establishment + d->setState(QXmppCall::ActiveState); + } +} + +/// Returns the RTP channel for the audio data. +/// +/// It acts as a QIODevice so that you can read / write audio samples, for +/// instance using a QAudioOutput and a QAudioInput. +/// + +QXmppRtpAudioChannel *QXmppCall::audioChannel() const +{ + QXmppCallPrivate::Stream *stream = d->findStreamByMedia(AUDIO_MEDIA); + if (stream) + return (QXmppRtpAudioChannel*)stream->channel; + else + return 0; +} + +/// Returns the audio mode. + +QIODevice::OpenMode QXmppCall::audioMode() const +{ + return d->audioMode; +} + +/// Returns the RTP channel for the video data. +/// + +QXmppRtpVideoChannel *QXmppCall::videoChannel() const +{ + QXmppCallPrivate::Stream *stream = d->findStreamByMedia(VIDEO_MEDIA); + if (stream) + return (QXmppRtpVideoChannel*)stream->channel; + else + return 0; +} + +/// Returns the video mode. + +QIODevice::OpenMode QXmppCall::videoMode() const +{ + return d->videoMode; +} + +void QXmppCall::terminated() +{ + // close streams + foreach (QXmppCallPrivate::Stream *stream, d->streams) { + stream->channel->close(); + stream->connection->close(); + } + + // update state + d->setState(QXmppCall::FinishedState); +} + +/// Returns the call's direction. +/// + +QXmppCall::Direction QXmppCall::direction() const +{ + return d->direction; +} + +/// Hangs up the call. +/// + +void QXmppCall::hangup() +{ + d->terminate(QXmppJingleIq::Reason::None); +} + +/// Sends a transport-info to inform the remote party of new local candidates. +/// + +void QXmppCall::localCandidatesChanged() +{ + // find the stream + QXmppIceConnection *conn = qobject_cast<QXmppIceConnection*>(sender()); + QXmppCallPrivate::Stream *stream = 0; + foreach (QXmppCallPrivate::Stream *ptr, d->streams) { + if (ptr->connection == conn) { + stream = ptr; + break; + } + } + if (!stream) + return; + + QXmppJingleIq iq; + iq.setTo(d->jid); + iq.setType(QXmppIq::Set); + iq.setAction(QXmppJingleIq::TransportInfo); + iq.setSid(d->sid); + + iq.content().setCreator(stream->creator); + iq.content().setName(stream->name); + + // transport + iq.content().setTransportUser(stream->connection->localUser()); + iq.content().setTransportPassword(stream->connection->localPassword()); + foreach (const QXmppJingleCandidate &candidate, stream->connection->localCandidates()) + iq.content().addTransportCandidate(candidate); + + d->sendRequest(iq); +} + +/// Returns the remote party's JID. +/// + +QString QXmppCall::jid() const +{ + return d->jid; +} + +void QXmppCall::updateOpenMode() +{ + QXmppCallPrivate::Stream *stream; + QIODevice::OpenMode mode; + + // determine audio mode + mode = QIODevice::NotOpen; + stream = d->findStreamByMedia(AUDIO_MEDIA); + if (d->state == QXmppCall::ActiveState && stream && stream->connection->isConnected()) + mode = stream->channel->openMode() & QIODevice::ReadWrite; + if (mode != d->audioMode) { + d->audioMode = mode; + emit audioModeChanged(mode); + } + + // determine video mode + mode = QIODevice::NotOpen; + stream = d->findStreamByMedia(VIDEO_MEDIA); + if (d->state == QXmppCall::ActiveState && stream && stream->connection->isConnected()) { + mode |= (stream->channel->openMode() & QIODevice::ReadOnly); + if (d->sendVideo) + mode |= (stream->channel->openMode() & QIODevice::WriteOnly); + } + if (mode != d->videoMode) { + d->videoMode = mode; + emit videoModeChanged(mode); + } +} + +/// Returns the call's session identifier. +/// + +QString QXmppCall::sid() const +{ + return d->sid; +} + +/// Returns the call's state. +/// +/// \sa stateChanged() + +QXmppCall::State QXmppCall::state() const +{ + return d->state; +} + +/// Starts sending video to the remote party. + +void QXmppCall::startVideo() +{ + if (d->state != QXmppCall::ActiveState) { + warning("Cannot start video, call is not active"); + return; + } + + d->sendVideo = true; + QXmppCallPrivate::Stream *stream = d->findStreamByMedia(VIDEO_MEDIA); + if (stream) { + updateOpenMode(); + return; + } + + // create video stream + stream = d->createStream(VIDEO_MEDIA); + stream->creator = (d->direction == QXmppCall::OutgoingDirection) ? QLatin1String("initiator") : QLatin1String("responder"); + stream->name = QLatin1String("webcam"); + d->streams << stream; + + // build request + QXmppJingleIq iq; + iq.setTo(d->jid); + iq.setType(QXmppIq::Set); + iq.setAction(QXmppJingleIq::ContentAdd); + iq.setSid(d->sid); + iq.content().setCreator(stream->creator); + iq.content().setName(stream->name); + iq.content().setSenders("both"); + + // description + iq.content().setDescriptionMedia(stream->media); + foreach (const QXmppJinglePayloadType &payload, stream->channel->localPayloadTypes()) + iq.content().addPayloadType(payload); + + // transport + iq.content().setTransportUser(stream->connection->localUser()); + iq.content().setTransportPassword(stream->connection->localPassword()); + foreach (const QXmppJingleCandidate &candidate, stream->connection->localCandidates()) + iq.content().addTransportCandidate(candidate); + + d->sendRequest(iq); +} + +/// Stops sending video to the remote party. + +void QXmppCall::stopVideo() +{ + if (!d->sendVideo) + return; + + d->sendVideo = false; + QXmppCallPrivate::Stream *stream = d->findStreamByMedia(VIDEO_MEDIA); + if (stream) + updateOpenMode(); +} + +QXmppCallManagerPrivate::QXmppCallManagerPrivate(QXmppCallManager *qq) + : stunPort(0), + turnPort(0), + q(qq) +{ +} + +QXmppCall *QXmppCallManagerPrivate::findCall(const QString &sid) const +{ + foreach (QXmppCall *call, calls) + if (call->sid() == sid) + return call; + return 0; +} + +QXmppCall *QXmppCallManagerPrivate::findCall(const QString &sid, QXmppCall::Direction direction) const +{ + foreach (QXmppCall *call, calls) + if (call->sid() == sid && call->direction() == direction) + return call; + return 0; +} + +/// Constructs a QXmppCallManager object to handle incoming and outgoing +/// Voice-Over-IP calls. +/// + +QXmppCallManager::QXmppCallManager() +{ + d = new QXmppCallManagerPrivate(this); +} + +/// Destroys the QXmppCallManager object. + +QXmppCallManager::~QXmppCallManager() +{ + delete d; +} + +QStringList QXmppCallManager::discoveryFeatures() const +{ + return QStringList() + << ns_jingle // XEP-0166 : Jingle + << ns_jingle_rtp // XEP-0167 : Jingle RTP Sessions + << ns_jingle_rtp_audio + << ns_jingle_rtp_video + << ns_jingle_ice_udp; // XEP-0176 : Jingle ICE-UDP Transport Method +} + +bool QXmppCallManager::handleStanza(const QDomElement &element) +{ + if(element.tagName() == "iq") + { + // XEP-0166: Jingle + if (QXmppJingleIq::isJingleIq(element)) + { + QXmppJingleIq jingleIq; + jingleIq.parse(element); + _q_jingleIqReceived(jingleIq); + return true; + } + } + + return false; +} + +void QXmppCallManager::setClient(QXmppClient *client) +{ + bool check; + Q_UNUSED(check); + + QXmppClientExtension::setClient(client); + + check = connect(client, SIGNAL(disconnected()), + this, SLOT(_q_disconnected())); + Q_ASSERT(check); + + check = connect(client, SIGNAL(iqReceived(QXmppIq)), + this, SLOT(_q_iqReceived(QXmppIq))); + Q_ASSERT(check); + + check = connect(client, SIGNAL(presenceReceived(QXmppPresence)), + this, SLOT(_q_presenceReceived(QXmppPresence))); + Q_ASSERT(check); +} + +/// Initiates a new outgoing call to the specified recipient. +/// +/// \param jid + +QXmppCall *QXmppCallManager::call(const QString &jid) +{ + bool check; + Q_UNUSED(check); + + if (jid.isEmpty()) { + warning("Refusing to call an empty jid"); + return 0; + } + + if (jid == client()->configuration().jid()) { + warning("Refusing to call self"); + return 0; + } + + QXmppCall *call = new QXmppCall(jid, QXmppCall::OutgoingDirection, this); + call->d->sid = generateStanzaHash(); + + // register call + d->calls << call; + check = connect(call, SIGNAL(destroyed(QObject*)), + this, SLOT(_q_callDestroyed(QObject*))); + Q_ASSERT(check); + emit callStarted(call); + + call->d->sendInvite(); + + return call; +} + +/// Sets the STUN server to use to determine server-reflexive addresses +/// and ports. +/// +/// \param host The address of the STUN server. +/// \param port The port of the STUN server. + +void QXmppCallManager::setStunServer(const QHostAddress &host, quint16 port) +{ + d->stunHost = host; + d->stunPort = port; +} + +/// Sets the TURN server to use to relay packets in double-NAT configurations. +/// +/// \param host The address of the TURN server. +/// \param port The port of the TURN server. + +void QXmppCallManager::setTurnServer(const QHostAddress &host, quint16 port) +{ + d->turnHost = host; + d->turnPort = port; +} + +/// Sets the \a user used for authentication with the TURN server. +/// +/// \param user + +void QXmppCallManager::setTurnUser(const QString &user) +{ + d->turnUser = user; +} + +/// Sets the \a password used for authentication with the TURN server. +/// +/// \param password + +void QXmppCallManager::setTurnPassword(const QString &password) +{ + d->turnPassword = password; +} + +/// Handles call destruction. + +void QXmppCallManager::_q_callDestroyed(QObject *object) +{ + d->calls.removeAll(static_cast<QXmppCall*>(object)); +} + +/// Handles disconnection from server. + +void QXmppCallManager::_q_disconnected() +{ + foreach (QXmppCall *call, d->calls) + call->d->terminate(QXmppJingleIq::Reason::Gone); +} + +/// Handles acknowledgements. +/// + +void QXmppCallManager::_q_iqReceived(const QXmppIq &ack) +{ + if (ack.type() != QXmppIq::Result) + return; + + // find request + foreach (QXmppCall *call, d->calls) + call->d->handleAck(ack); +} + +/// Handles a Jingle IQ. +/// + +void QXmppCallManager::_q_jingleIqReceived(const QXmppJingleIq &iq) +{ + bool check; + Q_UNUSED(check); + + if (iq.type() != QXmppIq::Set) + return; + + if (iq.action() == QXmppJingleIq::SessionInitiate) + { + // build call + QXmppCall *call = new QXmppCall(iq.from(), QXmppCall::IncomingDirection, this); + call->d->sid = iq.sid(); + + QXmppCallPrivate::Stream *stream = call->d->findStreamByMedia(iq.content().descriptionMedia()); + if (!stream) + return; + stream->creator = iq.content().creator(); + stream->name = iq.content().name(); + + // send ack + call->d->sendAck(iq); + + // check content description and transport + if (!call->d->handleDescription(stream, iq.content()) || + !call->d->handleTransport(stream, iq.content())) { + + // terminate call + call->d->terminate(QXmppJingleIq::Reason::FailedApplication); + call->terminated(); + delete call; + return; + } + + // register call + d->calls << call; + check = connect(call, SIGNAL(destroyed(QObject*)), + this, SLOT(_q_callDestroyed(QObject*))); + Q_ASSERT(check); + + // send ringing indication + QXmppJingleIq ringing; + ringing.setTo(call->jid()); + ringing.setType(QXmppIq::Set); + ringing.setAction(QXmppJingleIq::SessionInfo); + ringing.setSid(call->sid()); + ringing.setRinging(true); + call->d->sendRequest(ringing); + + // notify user + emit callReceived(call); + return; + + } else { + + // for all other requests, require a valid call + QXmppCall *call = d->findCall(iq.sid()); + if (!call) { + warning(QString("Remote party %1 sent a request for an unknown call %2").arg(iq.from(), iq.sid())); + return; + } + call->d->handleRequest(iq); + } +} + +/// Handles a presence. + +void QXmppCallManager::_q_presenceReceived(const QXmppPresence &presence) +{ + if (presence.type() != QXmppPresence::Unavailable) + return; + + foreach (QXmppCall *call, d->calls) { + if (presence.from() == call->jid()) { + // the remote party has gone away, terminate call + call->d->terminate(QXmppJingleIq::Reason::Gone); + } + } +} + |
