diff --git a/bin/dbus/cx.ring.Ring.CallManager.xml b/bin/dbus/cx.ring.Ring.CallManager.xml index 76c1cf8427bddbfb73705238baa0ff7a893bfe80..7ae32d13dcdb6e43d723bac6e2429ddfde2ad350 100644 --- a/bin/dbus/cx.ring.Ring.CallManager.xml +++ b/bin/dbus/cx.ring.Ring.CallManager.xml @@ -842,5 +842,12 @@ <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="VectorMapStringString"/> <arg type="aa{ss}" name="infos" /> </signal> + + <signal name="remoteRecordingChanged" tp:name-for-bindings="remoteRecordingChanged"> + <tp:added version="1.0.0"/> + <arg type="s" name="callID" /> + <arg type="s" name="peerNumber" /> + <arg type="b" name="remoteRecordingState" /> + </signal> </interface> </node> diff --git a/bin/dbus/dbusclient.cpp b/bin/dbus/dbusclient.cpp index a848a2fa71333161e6e04b34029d0fd101314b7f..c4057bfef0eca8aea1f76172092ae41786695256 100644 --- a/bin/dbus/dbusclient.cpp +++ b/bin/dbus/dbusclient.cpp @@ -177,7 +177,8 @@ DBusClient::initLibrary(int flags) exportable_callback<CallSignal::PeerHold>(bind(&DBusCallManager::peerHold, callM, _1, _2)), exportable_callback<CallSignal::AudioMuted>(bind(&DBusCallManager::audioMuted, callM, _1, _2)), exportable_callback<CallSignal::VideoMuted>(bind(&DBusCallManager::videoMuted, callM, _1, _2)), - exportable_callback<CallSignal::SmartInfo>(bind(&DBusCallManager::SmartInfo, callM, _1)) + exportable_callback<CallSignal::SmartInfo>(bind(&DBusCallManager::SmartInfo, callM, _1)), + exportable_callback<CallSignal::RemoteRecordingChanged>(bind(&DBusCallManager::remoteRecordingChanged, callM, _1, _2, _3)) }; // Configuration event handlers diff --git a/bin/jni/callmanager.i b/bin/jni/callmanager.i index b0d5f1e9c61bb2edd475016e5d004dad1d55effc..077e6b4b8d212b2cb9fce6b1bb9e1b444c18167a 100644 --- a/bin/jni/callmanager.i +++ b/bin/jni/callmanager.i @@ -48,6 +48,7 @@ public: virtual void onConferenceInfosUpdated(const std::string& confId, const std::vector<std::map<std::string, std::string>>& infos) {} virtual void peerHold(const std::string& call_id, bool holding){} virtual void connectionUpdate(const std::string& id, int state){} + virtual void remoteRecordingChanged(const std::string& call_id, const std::string& peer_number, bool state){} }; @@ -137,4 +138,6 @@ public: virtual void onConferenceInfosUpdated(const std::string& confId, const std::vector<std::map<std::string, std::string>>& infos) {} virtual void peerHold(const std::string& call_id, bool holding){} virtual void connectionUpdate(const std::string& id, int state){} + virtual void remoteRecordingChanged(const std::string& call_id, const std::string& peer_number, bool state){} + }; diff --git a/bin/jni/jni_interface.i b/bin/jni/jni_interface.i index 13c1005fd933536d7a1d9d00ff4af2cd8f6d8d65..e68d64747d252d0dd503957d369414eeef511e25 100644 --- a/bin/jni/jni_interface.i +++ b/bin/jni/jni_interface.i @@ -249,7 +249,8 @@ void init(ConfigurationCallback* confM, Callback* callM, PresenceCallback* presM exportable_callback<CallSignal::RtcpReportReceived>(bind(&Callback::onRtcpReportReceived, callM, _1, _2)), exportable_callback<CallSignal::OnConferenceInfosUpdated>(bind(&Callback::onConferenceInfosUpdated, callM, _1, _2)), exportable_callback<CallSignal::PeerHold>(bind(&Callback::peerHold, callM, _1, _2)), - exportable_callback<CallSignal::ConnectionUpdate>(bind(&Callback::connectionUpdate, callM, _1, _2)) + exportable_callback<CallSignal::ConnectionUpdate>(bind(&Callback::connectionUpdate, callM, _1, _2)), + exportable_callback<CallSignal::RemoteRecordingChanged>(bind(&Callback::remoteRecordingChanged, callM, _1, _2, _3)) }; // Configuration event handlers diff --git a/configure.ac b/configure.ac index 455f6256a2a27faa3f2e8eed05e9129c0f6e6ad9..2efeab8971d40ead044420c0b2bb10da01c19941 100644 --- a/configure.ac +++ b/configure.ac @@ -4,7 +4,7 @@ dnl Process this file with autoconf to produce a configure script. AC_PREREQ([2.65]) AC_INIT([Jami Daemon],[9.5.0],[ring@gnu.org],[jami]) -AC_COPYRIGHT([[Copyright (c) Savoir-faire Linux 2004-2019]]) +AC_COPYRIGHT([[Copyright (c) Savoir-faire Linux 2004-2020]]) AC_REVISION([$Revision$]) dnl Where to find configure files diff --git a/src/client/ring_signal.cpp b/src/client/ring_signal.cpp index dbbb657a468db61fdee3c38b6fe9dbb9b486d1e6..c63eada36fb22ea3b130c3bd53a40bd9deef5e3d 100644 --- a/src/client/ring_signal.cpp +++ b/src/client/ring_signal.cpp @@ -49,6 +49,7 @@ getSignalHandlers() exported_callback<DRing::CallSignal::SmartInfo>(), exported_callback<DRing::CallSignal::ConnectionUpdate>(), exported_callback<DRing::CallSignal::OnConferenceInfosUpdated>(), + exported_callback<DRing::CallSignal::RemoteRecordingChanged>(), /* Configuration */ exported_callback<DRing::ConfigurationSignal::VolumeChanged>(), diff --git a/src/dring/callmanager_interface.h b/src/dring/callmanager_interface.h index 60820a97f192d18fe121954c2961b93c22784999..41baed45c64bdb9aee50eccec466b296c337f7dc 100644 --- a/src/dring/callmanager_interface.h +++ b/src/dring/callmanager_interface.h @@ -228,6 +228,10 @@ struct DRING_PUBLIC CallSignal using cb_type = void(const std::string&, const std::vector<std::map<std::string, std::string>>&); }; + struct DRING_PUBLIC RemoteRecordingChanged { + constexpr static const char* name = "RemoteRecordingChanged"; + using cb_type = void(const std::string&, const std::string&, bool); + }; }; } // namespace DRing diff --git a/src/media/video/video_rtp_session.cpp b/src/media/video/video_rtp_session.cpp index e7fa759d8f3ea85fe9c7a7abedcf0563358126f3..54837382a84ffbede64818c2f0b30f2b334c9d06 100644 --- a/src/media/video/video_rtp_session.cpp +++ b/src/media/video/video_rtp_session.cpp @@ -649,6 +649,8 @@ VideoRtpSession::initRecorder(std::shared_ptr<MediaRecorder>& rec) } } } + if (recordingStateCallback_) + recordingStateCallback_(true); } void @@ -664,6 +666,9 @@ VideoRtpSession::deinitRecorder(std::shared_ptr<MediaRecorder>& rec) input->detach(ob); } } + if (recordingStateCallback_) + recordingStateCallback_(false); + } void diff --git a/src/media/video/video_rtp_session.h b/src/media/video/video_rtp_session.h index 35dc6807b4d827232b020ce8c48d513e66dcf97f..d7f404c7f3f5ffc5c727e9ce22ee576ad30aade2 100644 --- a/src/media/video/video_rtp_session.h +++ b/src/media/video/video_rtp_session.h @@ -97,6 +97,9 @@ public: const std::string& getInput() const { return input_; } void setChangeOrientationCallback(std::function<void(int)> cb); + void setRecStateCallback(std::function<void(bool)> cb) { + recordingStateCallback_ = std::move(cb); + } void initRecorder(std::shared_ptr<MediaRecorder>& rec) override; void deinitRecorder(std::shared_ptr<MediaRecorder>& rec) override; @@ -161,6 +164,8 @@ private: std::function<void(int)> changeOrientationCallback_; + std::function<void(bool)> recordingStateCallback_; + // interval in seconds between RTCP checkings std::chrono::seconds rtcp_checking_interval {4}; diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp index e606e9f14a6e5ca691cf1bccc0291d741f7dd1fb..222aa60a8388c2cd66984f2e739f123790c0f932 100644 --- a/src/sip/sipcall.cpp +++ b/src/sip/sipcall.cpp @@ -335,6 +335,25 @@ SIPCall::sendSIPInfo(const char* const body, const char* const subtype) pjsip_dlg_send_request(inv->dlg, tdata, Manager::instance().sipVoIPLink().getModId(), NULL); } +void +SIPCall::updateRecState(bool state) +{ + std::string BODY = + "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + "<media_control><vc_primitive><to_encoder>" + "<recording_state=" + std::to_string(state) + "/>" + "</to_encoder></vc_primitive></media_control>"; + // see https://tools.ietf.org/html/rfc5168 for XML Schema for Media Control details + + JAMI_DBG("Sending recording state via SIP INFO"); + + try { + sendSIPInfo(BODY.c_str(), "media_control+xml"); + } catch (const std::exception& e) { + JAMI_ERR("Error sending recording state: %s", e.what()); + } +} + void SIPCall::requestKeyframe() { @@ -1037,6 +1056,12 @@ SIPCall::startAllMedia() this_->setVideoOrientation(angle); }); }); + videortp_->setRecStateCallback([wthis = weak()] (bool state) { + runOnMainThread([wthis, state] { + if (auto this_ = wthis.lock()) + this_->updateRecState(state); + }); + }); #endif for (const auto& slot : slots) { @@ -1599,4 +1624,16 @@ SIPCall::rtpSetupSuccess(MediaType type) toggleRecording(); } +void +SIPCall::setRemoteRecording(bool state) +{ + if (state) { + JAMI_WARN("SIP remote recording enabled"); + emitSignal<DRing::CallSignal::RemoteRecordingChanged>(getCallId(), getPeerNumber(), true); + } else { + JAMI_WARN("SIP remote recording disabled"); + emitSignal<DRing::CallSignal::RemoteRecordingChanged>(getCallId(), getPeerNumber(), false); + } +} + } // namespace jami diff --git a/src/sip/sipcall.h b/src/sip/sipcall.h index 750b54083b697b148cf247b5adcfec181f5ae936..72478f81087d86fac4edb155760f3ab980013651 100644 --- a/src/sip/sipcall.h +++ b/src/sip/sipcall.h @@ -174,6 +174,8 @@ public: // SIP related void requestKeyframe(); + void updateRecState(bool state); + SIPAccountBase& getSIPAccount() const; void updateSDPFromSTUN(); @@ -233,6 +235,8 @@ public: // NOT SIP RELATED (good candidates to be moved elsewhere) void rtpSetupSuccess(MediaType type); + void setRemoteRecording(bool state); + private: using clock = std::chrono::steady_clock; using time_point = clock::time_point; diff --git a/src/sip/sipvoiplink.cpp b/src/sip/sipvoiplink.cpp index 72cac8deaee8b257be2b91dc0b646942d6f63e94..8c0dc4ed1759040b1e783ad7fcfbdc2d4f7a343f 100644 --- a/src/sip/sipvoiplink.cpp +++ b/src/sip/sipvoiplink.cpp @@ -1052,6 +1052,7 @@ handleMediaControl(SIPCall& call, pjsip_msg_body* body) pj_strset(&control_st, (char*) body->data, body->len); static constexpr pj_str_t PICT_FAST_UPDATE = CONST_PJ_STR("picture_fast_update"); static constexpr pj_str_t DEVICE_ORIENTATION = CONST_PJ_STR("device_orientation"); + static constexpr pj_str_t RECORDING_STATE = CONST_PJ_STR("recording_state"); if (pj_strstr(&control_st, &PICT_FAST_UPDATE)) { call.sendKeyframe(); @@ -1078,6 +1079,22 @@ handleMediaControl(SIPCall& call, pjsip_msg_body* body) return true; } } + else if (pj_strstr(&control_st, &RECORDING_STATE)) { + static const std::regex REC_REGEX("recording_state=([0-1])"); + std::string body_msg(control_st.ptr, control_st.slen); + std::smatch matched_pattern; + std::regex_search(body_msg, matched_pattern, REC_REGEX); + + if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) { + try { + bool state = std::stoi(matched_pattern[1]); + call.setRemoteRecording(state); + } catch (const std::exception& e) { + JAMI_WARN("Error parsing state remote recording: %s", e.what()); + } + return true; + } + } } return false;