From b424a5d0bddd926dee92a9eab2d614be3b0f3b3e Mon Sep 17 00:00:00 2001
From: Pierre Lespagnol <pierre.lespagnol@savoirfairelinux.com>
Date: Mon, 31 Aug 2020 11:34:28 -0400
Subject: [PATCH] recorder: add remote recording indicator

Change-Id: I711a0b2f446f1620d7dff66945bf9295fd8372ac
---
 bin/dbus/cx.ring.Ring.CallManager.xml |  7 +++++
 bin/dbus/dbusclient.cpp               |  3 ++-
 bin/jni/callmanager.i                 |  3 +++
 bin/jni/jni_interface.i               |  3 ++-
 configure.ac                          |  2 +-
 src/client/ring_signal.cpp            |  1 +
 src/dring/callmanager_interface.h     |  4 +++
 src/media/video/video_rtp_session.cpp |  5 ++++
 src/media/video/video_rtp_session.h   |  5 ++++
 src/sip/sipcall.cpp                   | 37 +++++++++++++++++++++++++++
 src/sip/sipcall.h                     |  4 +++
 src/sip/sipvoiplink.cpp               | 17 ++++++++++++
 12 files changed, 88 insertions(+), 3 deletions(-)

diff --git a/bin/dbus/cx.ring.Ring.CallManager.xml b/bin/dbus/cx.ring.Ring.CallManager.xml
index 76c1cf8427..7ae32d13dc 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 a848a2fa71..c4057bfef0 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 b0d5f1e9c6..077e6b4b8d 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 13c1005fd9..e68d64747d 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 455f6256a2..2efeab8971 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 dbbb657a46..c63eada36f 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 60820a97f1..41baed45c6 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 e7fa759d8f..54837382a8 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 35dc6807b4..d7f404c7f3 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 e606e9f14a..222aa60a83 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 750b54083b..72478f8108 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 72cac8deae..8c0dc4ed17 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;
-- 
GitLab