blob: f1a3464545cd480e8fac4332aa1e1cabdd1952f2 [file] [log] [blame]
/*
* Copyright (C) 2004-2021 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.
*/
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;
/**
* 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 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;
virtual std::shared_ptr<Observable<std::shared_ptr<MediaFrame>>>
getReceiveVideoFrameActiveWriter() = 0;
virtual void createSinks(const ConfInfo& infos) = 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