mirror of
https://git.jami.net/savoirfairelinux/jami-daemon.git
synced 2025-08-12 22:09:25 +08:00
545 lines
16 KiB
C++
545 lines
16 KiB
C++
/*
|
|
* Copyright (C) 2004-2022 Savoir-faire Linux Inc.
|
|
*
|
|
* Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com>
|
|
* Author: Yan Morin <yan.morin@savoirfairelinux.com>
|
|
* Author: Laurielle Lea <laurielle.lea@savoirfairelinux.com>
|
|
* Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "logger.h"
|
|
|
|
#include "recordable.h"
|
|
#include "peerrecorder.h"
|
|
#include "ip_utils.h"
|
|
#include "conference.h"
|
|
#include "media_codec.h"
|
|
#include "media/media_attribute.h"
|
|
|
|
#include <atomic>
|
|
#include <mutex>
|
|
#include <map>
|
|
#include <sstream>
|
|
#include <memory>
|
|
#include <vector>
|
|
#include <condition_variable>
|
|
#include <set>
|
|
#include <list>
|
|
#include <functional>
|
|
|
|
template<typename T>
|
|
bool
|
|
is_uninitialized(std::weak_ptr<T> const& weak)
|
|
{
|
|
using wt = std::weak_ptr<T>;
|
|
return !weak.owner_before(wt {}) && !wt {}.owner_before(weak);
|
|
}
|
|
|
|
namespace jami {
|
|
|
|
class VoIPLink;
|
|
class Account;
|
|
struct AccountVideoCodecInfo;
|
|
class AudioDeviceGuard;
|
|
|
|
class Call;
|
|
class Conference;
|
|
|
|
using CallMap = std::map<std::string, std::shared_ptr<Call>>;
|
|
|
|
namespace video {
|
|
class VideoGenerator;
|
|
}
|
|
|
|
/*
|
|
* @file call.h
|
|
* @brief A call is the base class for protocol-based calls
|
|
*/
|
|
|
|
class Call : public Recordable, public PeerRecorder, public std::enable_shared_from_this<Call>
|
|
{
|
|
public:
|
|
/**
|
|
* Tell where we're at with the call. The call gets Connected when we know
|
|
* from the other end what happened with out call. A call can be 'Connected'
|
|
* even if the call state is Busy, or Error.
|
|
*
|
|
* Audio should be transmitted when ConnectionState = Connected AND
|
|
* CallState = Active.
|
|
*
|
|
* \note modify validStateTransition/getStateStr if this enum changes
|
|
*/
|
|
enum class ConnectionState : unsigned {
|
|
DISCONNECTED,
|
|
TRYING,
|
|
PROGRESSING,
|
|
RINGING,
|
|
CONNECTED,
|
|
COUNT__
|
|
};
|
|
|
|
/**
|
|
* The Call State.
|
|
*
|
|
* \note modify validStateTransition/getStateStr if this enum changes
|
|
*/
|
|
enum class CallState : unsigned {
|
|
INACTIVE,
|
|
ACTIVE,
|
|
HOLD,
|
|
BUSY,
|
|
PEER_BUSY,
|
|
MERROR,
|
|
OVER,
|
|
COUNT__
|
|
};
|
|
|
|
enum class LinkType { GENERIC, SIP };
|
|
|
|
using SubcallSet = std::set<std::shared_ptr<Call>, std::owner_less<std::shared_ptr<Call>>>;
|
|
using OnReadyCb = std::function<void(bool)>;
|
|
using StateListenerCb = std::function<bool(CallState, ConnectionState, int)>;
|
|
|
|
/**
|
|
* This determines if the call originated from the local user (OUTGOING)
|
|
* or from some remote peer (INCOMING, MISSED).
|
|
*/
|
|
enum class CallType : unsigned { INCOMING, OUTGOING, MISSED };
|
|
|
|
virtual ~Call();
|
|
|
|
std::weak_ptr<Call> weak() { return std::static_pointer_cast<Call>(shared_from_this()); }
|
|
|
|
virtual LinkType getLinkType() const { return LinkType::GENERIC; }
|
|
|
|
/**
|
|
* Return a reference on the call id
|
|
* @return call id
|
|
*/
|
|
const std::string& getCallId() const { return id_; }
|
|
|
|
/**
|
|
* Return a reference on the conference id
|
|
* @return call id
|
|
*/
|
|
std::shared_ptr<Conference> getConference() const { return conf_.lock(); }
|
|
bool isConferenceParticipant() const { return not is_uninitialized(conf_); }
|
|
|
|
std::weak_ptr<Account> getAccount() const { return account_; }
|
|
std::string getAccountId() const;
|
|
|
|
CallType getCallType() const { return type_; }
|
|
|
|
/**
|
|
* Set the peer number (destination on outgoing)
|
|
* not protected by mutex (when created)
|
|
* @param number peer number
|
|
*/
|
|
void setPeerNumber(const std::string& number) { peerNumber_ = number; }
|
|
|
|
/**
|
|
* Get the peer number (destination on outgoing)
|
|
* not protected by mutex (when created)
|
|
* @return std::string The peer number
|
|
*/
|
|
const std::string& getPeerNumber() const { return peerNumber_; }
|
|
/**
|
|
* Set the display name (caller in ingoing)
|
|
* not protected by mutex (when created)
|
|
* @return std::string The peer display name
|
|
*/
|
|
void setPeerDisplayName(const std::string& name) { peerDisplayName_ = name; }
|
|
|
|
/**
|
|
* Get the peer display name (caller in ingoing)
|
|
* not protected by mutex (when created)
|
|
* @return std::string The peer name
|
|
*/
|
|
const std::string& getPeerDisplayName() const { return peerDisplayName_; }
|
|
/**
|
|
* Tell if the call is incoming
|
|
* @return true if yes false otherwise
|
|
*/
|
|
bool isIncoming() const { return type_ == CallType::INCOMING; }
|
|
|
|
/**
|
|
* Set the state of the call (protected by mutex)
|
|
* @param call_state The call state
|
|
* @param cnx_state The call connection state
|
|
* @param code Optional error-dependent code (used to report more information)
|
|
* @return true if the requested state change was valid, false otherwise
|
|
*/
|
|
bool setState(CallState call_state, signed code = 0);
|
|
bool setState(CallState call_state, ConnectionState cnx_state, signed code = 0);
|
|
bool setState(ConnectionState cnx_state, signed code = 0);
|
|
|
|
/**
|
|
* Get the call state of the call (protected by mutex)
|
|
* @return CallState The call state
|
|
*/
|
|
CallState getState() const;
|
|
|
|
/**
|
|
* Get the connection state of the call (protected by mutex)
|
|
* @return ConnectionState The connection state
|
|
*/
|
|
ConnectionState getConnectionState() const;
|
|
|
|
std::string getStateStr() const;
|
|
|
|
void setIPToIP(bool IPToIP) { isIPToIP_ = IPToIP; }
|
|
|
|
virtual std::map<std::string, std::string> getDetails() const;
|
|
|
|
/**
|
|
* Answer the call
|
|
*/
|
|
virtual void answer() = 0;
|
|
|
|
/**
|
|
* Answer a call with a list of media attributes.
|
|
* @param mediaList The list of the media attributes.
|
|
* The media attributes set by the caller of this method will
|
|
* determine the response sent to the peer and the configuration
|
|
* of the local media.
|
|
* If the media list is empty, the current media set when the call
|
|
* was created will be used.
|
|
*/
|
|
virtual void answer(const std::vector<DRing::MediaMap>& mediaList) = 0;
|
|
|
|
/**
|
|
* Check the media of an incoming media change request.
|
|
* This method checks the new media against the current media. It
|
|
* determines if the differences are significant enough to require
|
|
* more processing.
|
|
* For instance, this can be used to check if the a change request
|
|
* must be reported to the client for confirmation or can be handled
|
|
* by the daemon.
|
|
* The conditions that cause this method to return true are implementation
|
|
* specific.
|
|
*
|
|
* @param the new media list from the remote
|
|
* @return true if the new media differs from the current media
|
|
**/
|
|
virtual bool checkMediaChangeRequest(const std::vector<DRing::MediaMap>& remoteMediaList) = 0;
|
|
|
|
/**
|
|
* Process incoming media change request.
|
|
*
|
|
* @param the new media list from the remote
|
|
*/
|
|
virtual void handleMediaChangeRequest(const std::vector<DRing::MediaMap>& remoteMediaList) = 0;
|
|
|
|
/**
|
|
* Answer to a media update request.
|
|
* The media attributes set by the caller of this method will
|
|
* determine the response to send to the peer and the configuration
|
|
* of the local media.
|
|
* @param mediaList The list of media attributes. An empty media
|
|
* list means the media update request was not accepted, meaning the
|
|
* call continue with the current media. It's up to the implementation
|
|
* to determine wether an answer will be sent to the peer.
|
|
*/
|
|
virtual void answerMediaChangeRequest(const std::vector<DRing::MediaMap>& mediaList) = 0;
|
|
/**
|
|
* Hang up the call
|
|
* @param reason
|
|
*/
|
|
virtual void hangup(int reason) = 0;
|
|
|
|
/**
|
|
* Refuse incoming call
|
|
*/
|
|
virtual void refuse() = 0;
|
|
|
|
/**
|
|
* Transfer a call to specified URI
|
|
* @param to The recipient of the call
|
|
*/
|
|
virtual void transfer(const std::string& to) = 0;
|
|
|
|
/**
|
|
* Attended transfer
|
|
* @param The target call id
|
|
* @return True on success
|
|
*/
|
|
virtual bool attendedTransfer(const std::string& to) = 0;
|
|
|
|
/**
|
|
* Put a call on hold
|
|
* @param cb On hold can be queued if waiting for ICE. This callback will be called when ready
|
|
* @return bool True on success, False if failed or pending
|
|
*/
|
|
virtual bool onhold(OnReadyCb&& cb) = 0;
|
|
|
|
/**
|
|
* Resume a call from hold state
|
|
* @param cb On hold can be queued if waiting for ICE. This callback will be called when ready
|
|
* @return bool True on success, False if failed or pending
|
|
*/
|
|
virtual bool offhold(OnReadyCb&& cb) = 0;
|
|
|
|
virtual void sendKeyframe() = 0;
|
|
|
|
/**
|
|
* Check wether ICE is enabled for media
|
|
*/
|
|
virtual bool isIceEnabled() const = 0;
|
|
|
|
/**
|
|
* Peer has hung up a call
|
|
*/
|
|
virtual void peerHungup();
|
|
|
|
virtual void removeCall();
|
|
|
|
/**
|
|
* Update recording state. Typically used to send notifications
|
|
* to peers about the local recording session state
|
|
*/
|
|
virtual void updateRecState(bool state) = 0;
|
|
|
|
void addStateListener(StateListenerCb&& listener)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lk {callMutex_};
|
|
stateChangedListeners_.emplace_back(std::move(listener));
|
|
}
|
|
|
|
/**
|
|
* Attach subcall to this instance.
|
|
* If this subcall is answered, this subcall and this instance will be merged using merge().
|
|
*/
|
|
void addSubCall(Call& call);
|
|
|
|
///
|
|
/// Return true if this call instance is a subcall (internal call for multi-device handling)
|
|
///
|
|
bool isSubcall() const
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lk {callMutex_};
|
|
return parent_ != nullptr;
|
|
}
|
|
|
|
/**
|
|
* @return Call duration in milliseconds
|
|
*/
|
|
std::chrono::milliseconds getCallDuration() const
|
|
{
|
|
return duration_start_ == time_point::min()
|
|
? std::chrono::milliseconds::zero()
|
|
: std::chrono::duration_cast<std::chrono::milliseconds>(clock::now()
|
|
- duration_start_);
|
|
}
|
|
|
|
// media management
|
|
virtual bool toggleRecording();
|
|
|
|
virtual std::vector<MediaAttribute> getMediaAttributeList() const = 0;
|
|
|
|
#ifdef ENABLE_VIDEO
|
|
/**
|
|
* Add a dummy video stream with the attached sink.
|
|
* Typically needed in conference to display infos for participants
|
|
* that have joined the conference without video (audio only).
|
|
*/
|
|
virtual bool addDummyVideoRtpSession() = 0;
|
|
|
|
/**
|
|
* Remove all dummy video streams.
|
|
*/
|
|
virtual void removeDummyVideoRtpSessions() = 0;
|
|
|
|
virtual std::shared_ptr<Observable<std::shared_ptr<MediaFrame>>>
|
|
getReceiveVideoFrameActiveWriter() = 0;
|
|
virtual void createSinks(const ConfInfo& infos) = 0;
|
|
#endif
|
|
|
|
virtual void switchInput(const std::string& = {}) {};
|
|
|
|
/**
|
|
* mute/unmute a media of a call
|
|
* @param mediaType type of media
|
|
* @param isMuted true for muting, false for unmuting
|
|
*/
|
|
virtual void muteMedia(const std::string& mediaType, bool isMuted) = 0;
|
|
|
|
/**
|
|
* Send DTMF
|
|
* @param code The char code
|
|
*/
|
|
virtual void carryingDTMFdigits(char code) = 0;
|
|
|
|
/**
|
|
* Make a change request of the current media with the provided media
|
|
* @param mediaList the new media list
|
|
* @return true on success
|
|
*/
|
|
virtual bool requestMediaChange(const std::vector<DRing::MediaMap>& mediaList) = 0;
|
|
|
|
/**
|
|
* Send a message to a call identified by its callid
|
|
*
|
|
* @param A list of mimetype/payload pairs
|
|
* @param The sender of this message (could be another participant of a conference)
|
|
*/
|
|
virtual void sendTextMessage(const std::map<std::string, std::string>& messages,
|
|
const std::string& from)
|
|
= 0;
|
|
|
|
void onTextMessage(std::map<std::string, std::string>&& messages);
|
|
|
|
virtual std::shared_ptr<AccountCodecInfo> getAudioCodec() const { return {}; }
|
|
virtual std::shared_ptr<AccountCodecInfo> getVideoCodec() const { return {}; }
|
|
|
|
virtual void restartMediaSender() = 0;
|
|
|
|
/**
|
|
* Update call details after creation.
|
|
* @param details to update
|
|
*
|
|
* \note No warranty to update any details, only some details can be modified.
|
|
* See the implementation for more ... details :-).
|
|
*/
|
|
void updateDetails(const std::map<std::string, std::string>& details);
|
|
|
|
// Media status methods
|
|
virtual bool hasVideo() const = 0;
|
|
virtual bool isCaptureDeviceMuted(const MediaType& mediaType) const = 0;
|
|
|
|
/**
|
|
* A Call can be in a conference. If this is the case, the other side
|
|
* will send conference informations describing the rendered image
|
|
* @msg A JSON object describing the conference
|
|
*/
|
|
void setConferenceInfo(const std::string& msg);
|
|
|
|
virtual void enterConference(std::shared_ptr<Conference> conference) = 0;
|
|
virtual void exitConference() = 0;
|
|
|
|
std::vector<std::map<std::string, std::string>> getConferenceInfos() const
|
|
{
|
|
return confInfo_.toVectorMapStringString();
|
|
}
|
|
|
|
std::unique_ptr<AudioDeviceGuard> audioGuard;
|
|
void sendConfOrder(const Json::Value& root);
|
|
void sendConfInfo(const std::string& json);
|
|
void resetConfInfo();
|
|
|
|
virtual void monitor() const = 0;
|
|
|
|
protected:
|
|
using clock = std::chrono::steady_clock;
|
|
using time_point = clock::time_point;
|
|
virtual void merge(Call& scall);
|
|
|
|
/**
|
|
* Constructor of a call
|
|
* @param id Unique identifier of the call
|
|
* @param type set definitely this call as incoming/outgoing
|
|
* @param details volatile details to customize the call creation
|
|
*/
|
|
Call(const std::shared_ptr<Account>& account,
|
|
const std::string& id,
|
|
Call::CallType type,
|
|
const std::map<std::string, std::string>& details = {});
|
|
|
|
// TODO all these members are not protected against multi-thread access
|
|
|
|
const std::string id_ {};
|
|
|
|
///< MultiDevice: parent call, nullptr otherwise. Access protected by callMutex_.
|
|
mutable std::shared_ptr<Call> parent_;
|
|
|
|
///< MultiDevice: list of attached subcall
|
|
SubcallSet subcalls_;
|
|
|
|
using MsgList = std::list<std::pair<std::map<std::string, std::string>, std::string>>;
|
|
|
|
///< MultiDevice: message waiting to be sent (need a valid subcall)
|
|
MsgList pendingOutMessages_;
|
|
|
|
/** Protect every attribute that can be changed by two threads */
|
|
mutable std::recursive_mutex callMutex_ {};
|
|
|
|
mutable std::mutex confInfoMutex_ {};
|
|
mutable ConfInfo confInfo_ {};
|
|
time_point duration_start_ {time_point::min()};
|
|
|
|
private:
|
|
bool validStateTransition(CallState newState);
|
|
|
|
void checkPendingIM();
|
|
|
|
void checkAudio();
|
|
|
|
void subcallStateChanged(Call&, Call::CallState, Call::ConnectionState);
|
|
|
|
SubcallSet safePopSubcalls();
|
|
|
|
std::vector<StateListenerCb> stateChangedListeners_ {};
|
|
|
|
protected:
|
|
/** Unique conference ID, used exclusively in case of a conference */
|
|
std::weak_ptr<Conference> conf_ {};
|
|
|
|
/** Type of the call */
|
|
CallType type_;
|
|
|
|
/** Associate account ID */
|
|
std::weak_ptr<Account> account_;
|
|
|
|
/** Disconnected/Progressing/Trying/Ringing/Connected */
|
|
ConnectionState connectionState_ {ConnectionState::DISCONNECTED};
|
|
|
|
/** Inactive/Active/Hold/Busy/Error */
|
|
CallState callState_ {CallState::INACTIVE};
|
|
|
|
/** Direct IP-to-IP or classic call */
|
|
bool isIPToIP_ {false};
|
|
|
|
/** Number of the peer */
|
|
std::string peerNumber_ {};
|
|
|
|
/** Peer Display Name */
|
|
std::string peerDisplayName_ {};
|
|
|
|
time_t timestamp_start_ {0};
|
|
|
|
///< MultiDevice: message received by subcall to merged yet
|
|
MsgList pendingInMessages_;
|
|
};
|
|
|
|
// Helpers
|
|
|
|
/**
|
|
* Obtain a shared smart pointer of instance
|
|
*/
|
|
inline std::shared_ptr<Call>
|
|
getPtr(Call& call)
|
|
{
|
|
return call.shared_from_this();
|
|
}
|
|
|
|
} // namespace jami
|