diff --git a/src/conference.cpp b/src/conference.cpp index 3d6774eb8a77c3987758f33e85d73539b4cf8c3d..8828fee81687a100bd5ead202e1c79a8f5cc1ce3 100644 --- a/src/conference.cpp +++ b/src/conference.cpp @@ -115,7 +115,7 @@ Conference::Conference(const std::shared_ptr<Account>& account, bool attachHost) // Handle participants showing their video for (const auto& info : infos) { std::string uri {}; - bool isLocalMuted = false; + bool isLocalMuted = false, isPeerRecording = false; std::string deviceId {}; auto active = false; if (!info.callId.empty()) { @@ -123,6 +123,7 @@ Conference::Conference(const std::shared_ptr<Account>& account, bool attachHost) if (auto call = std::dynamic_pointer_cast<SIPCall>(getCall(callId))) { uri = call->getPeerNumber(); isLocalMuted = call->isPeerMuted(); + isPeerRecording = call->isPeerRecording(); if (auto* transport = call->getTransport()) deviceId = transport->deviceId(); } @@ -146,7 +147,8 @@ Conference::Conference(const std::shared_ptr<Account>& account, bool attachHost) isModeratorMuted, isModerator, isHandRaised, - isVoiceActive}); + isVoiceActive, + isPeerRecording}); } else { auto isModeratorMuted = false; // If not local @@ -165,6 +167,7 @@ Conference::Conference(const std::shared_ptr<Account>& account, bool attachHost) getCall(streamInfo.callId))) { uri = call->getPeerNumber(); isLocalMuted = call->isPeerMuted(); + isPeerRecording = call->isPeerRecording(); if (auto* transport = call->getTransport()) deviceId = transport->deviceId(); } @@ -180,6 +183,7 @@ Conference::Conference(const std::shared_ptr<Account>& account, bool attachHost) peerId = "host"sv; deviceId = acc->currentDeviceId(); isLocalMuted = shared->isMediaSourceMuted(MediaType::MEDIA_AUDIO); + isPeerRecording = shared->isRecording(); } auto isHandRaised = shared->isHandRaised(deviceId); auto isVoiceActive = shared->isVoiceActive(streamId); @@ -196,7 +200,8 @@ Conference::Conference(const std::shared_ptr<Account>& account, bool attachHost) isModeratorMuted, isModerator, isHandRaised, - isVoiceActive}); + isVoiceActive, + isPeerRecording}); } } if (auto videoMixer = shared->videoMixer_) { @@ -659,9 +664,7 @@ Conference::addParticipant(const std::string& participant_id) { JAMI_DBG("Adding call %s to conference %s", participant_id.c_str(), id_.c_str()); - jami_tracepoint(conference_add_participant, - id_.c_str(), - participant_id.c_str()); + jami_tracepoint(conference_add_participant, id_.c_str(), participant_id.c_str()); { std::lock_guard<std::mutex> lk(participantsMtx_); @@ -1036,7 +1039,9 @@ Conference::toggleRecording() // Notify each participant foreachCall([&](auto call) { call->updateRecState(newState); }); - return Recordable::toggleRecording(); + auto res = Recordable::toggleRecording(); + updateRecording(); + return res; } std::string @@ -1453,6 +1458,21 @@ Conference::muteParticipant(const std::string& participant_id, const bool& state muteCall(call->getCallId(), state); } +void +Conference::updateRecording() +{ + std::lock_guard<std::mutex> lk(confInfoMutex_); + for (auto& info : confInfo_) { + if (info.uri.empty()) { + info.recording = isRecording(); + } else if (auto call = getCallWith(std::string(string_remove_suffix(info.uri, '@')), + info.device)) { + info.recording = call->isPeerRecording(); + } + } + sendConferenceInfos(); +} + void Conference::updateMuted() { @@ -1763,4 +1783,19 @@ Conference::getRemoteId(const std::shared_ptr<jami::Call>& call) const return {}; } +void +Conference::stopRecording() +{ + Recordable::stopRecording(); + updateRecording(); +} + +bool +Conference::startRecording(const std::string& path) +{ + auto res = Recordable::startRecording(path); + updateRecording(); + return res; +} + } // namespace jami diff --git a/src/conference.h b/src/conference.h index 481ceb7ff8bafcd0d32008412acb0090333935c3..d50f6bc12d211671ae8e20e04e7bf0360bb4d745 100644 --- a/src/conference.h +++ b/src/conference.h @@ -76,6 +76,7 @@ struct ParticipantInfo bool isModerator {false}; bool handRaised {false}; bool voiceActivity {false}; + bool recording {false}; void fromJson(const Json::Value& v) { @@ -93,6 +94,7 @@ struct ParticipantInfo isModerator = v["isModerator"].asBool(); handRaised = v["handRaised"].asBool(); voiceActivity = v["voiceActivity"].asBool(); + recording = v["recording"].asBool(); } Json::Value toJson() const @@ -112,6 +114,7 @@ struct ParticipantInfo val["isModerator"] = isModerator; val["handRaised"] = handRaised; val["voiceActivity"] = voiceActivity; + val["recording"] = recording; return val; } @@ -130,7 +133,8 @@ struct ParticipantInfo {"audioModeratorMuted", audioModeratorMuted ? "true" : "false"}, {"isModerator", isModerator ? "true" : "false"}, {"handRaised", handRaised ? "true" : "false"}, - {"voiceActivity", voiceActivity ? "true" : "false"}}; + {"voiceActivity", voiceActivity ? "true" : "false"}, + {"recording", recording ? "true" : "false"}}; } friend bool operator==(const ParticipantInfo& p1, const ParticipantInfo& p2) @@ -141,7 +145,7 @@ struct ParticipantInfo and p1.audioLocalMuted == p2.audioLocalMuted and p1.audioModeratorMuted == p2.audioModeratorMuted and p1.isModerator == p2.isModerator and p1.handRaised == p2.handRaised - and p1.voiceActivity == p2.voiceActivity; + and p1.voiceActivity == p2.voiceActivity and p1.recording == p2.recording; } friend bool operator!=(const ParticipantInfo& p1, const ParticipantInfo& p2) @@ -374,6 +378,7 @@ public: const std::string& streamId, const bool& state); void updateMuted(); + void updateRecording(); void updateVoiceActivity(); @@ -390,6 +395,10 @@ public: */ std::vector<DRing::MediaMap> currentMediaList() const; + // Update layout if recording changes + void stopRecording() override; + bool startRecording(const std::string& path) override; + private: std::weak_ptr<Conference> weak() { diff --git a/src/media/media_encoder.cpp b/src/media/media_encoder.cpp index 0e865d2b97296fe5d454e4a5702ce087bc7ef0c2..3742b6bb2b7e4af553e1f115575e47ff03ce93d3 100644 --- a/src/media/media_encoder.cpp +++ b/src/media/media_encoder.cpp @@ -505,6 +505,9 @@ MediaEncoder::encode(AVFrame* frame, int streamIdx) pkt.data = nullptr; // packet data will be allocated by the encoder pkt.size = 0; + if (!encoderCtx) + return -1; + ret = avcodec_send_frame(encoderCtx, frame); if (ret < 0) return -1; diff --git a/src/media/recordable.h b/src/media/recordable.h index 8fa1a32b11c9956ef2bd757af6f3c8b459038a7c..1665c8f2cfefca41d69a4137b37306bd39219962 100644 --- a/src/media/recordable.h +++ b/src/media/recordable.h @@ -60,7 +60,7 @@ public: /** * Start recording */ - bool startRecording(const std::string& path); + virtual bool startRecording(const std::string& path); /** * Return the file path for this recording diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp index 0aa586832c1f79d637e9e9f59f04864dab2c09c6..ea12dea0c148b00a86f93dae1b4bfaf0f0bdfbce 100644 --- a/src/sip/sipcall.cpp +++ b/src/sip/sipcall.cpp @@ -3489,6 +3489,8 @@ SIPCall::peerRecording(bool state) emitSignal<DRing::CallSignal::RemoteRecordingChanged>(id, getPeerNumber(), false); } peerRecording_ = state; + if (auto conf = conf_.lock()) + conf->updateRecording(); } void diff --git a/test/unitTest/call/conference.cpp b/test/unitTest/call/conference.cpp index a8fad525f3f94d442ad1bb46d5185965953837a5..f35b6b137fc38022d0377f6c9ad2ab15bacad228 100644 --- a/test/unitTest/call/conference.cpp +++ b/test/unitTest/call/conference.cpp @@ -49,6 +49,7 @@ struct CallData std::atomic_bool moderatorMuted {false}; std::atomic_bool raisedHand {false}; std::atomic_bool active {false}; + std::atomic_bool recording {false}; void reset() { @@ -60,6 +61,7 @@ struct CallData moderatorMuted = false; active = false; raisedHand = false; + recording = false; } }; @@ -96,6 +98,7 @@ private: void testAudioConferenceConfInfo(); void testHostAddRmSecondVideo(); void testParticipantAddRmSecondVideo(); + void testPropagateRecording(); CPPUNIT_TEST_SUITE(ConferenceTest); CPPUNIT_TEST(testGetConference); @@ -115,10 +118,12 @@ private: CPPUNIT_TEST(testAudioConferenceConfInfo); CPPUNIT_TEST(testHostAddRmSecondVideo); CPPUNIT_TEST(testParticipantAddRmSecondVideo); + CPPUNIT_TEST(testPropagateRecording); CPPUNIT_TEST_SUITE_END(); // Common parts std::string aliceId; + std::atomic_bool hostRecording {false}; std::string bobId; std::string carlaId; std::string daviId; @@ -155,6 +160,7 @@ ConferenceTest::setUp() daviCall.reset(); confId = {}; confChanged = false; + hostRecording = false; } void @@ -236,22 +242,27 @@ ConferenceTest::registerSignalHandlers() for (const auto& infos : participantsInfos) { if (infos.at("uri").find(bobUri) != std::string::npos) { bobCall.active = infos.at("active") == "true"; + bobCall.recording = infos.at("recording") == "true"; bobCall.moderatorMuted = infos.at("audioModeratorMuted") == "true"; bobCall.raisedHand = infos.at("handRaised") == "true"; bobCall.device = infos.at("device"); bobCall.streamId = infos.at("sinkId"); } else if (infos.at("uri").find(carlaUri) != std::string::npos) { carlaCall.active = infos.at("active") == "true"; + carlaCall.recording = infos.at("recording") == "true"; carlaCall.moderatorMuted = infos.at("audioModeratorMuted") == "true"; carlaCall.raisedHand = infos.at("handRaised") == "true"; carlaCall.device = infos.at("device"); carlaCall.streamId = infos.at("sinkId"); } else if (infos.at("uri").find(daviUri) != std::string::npos) { daviCall.active = infos.at("active") == "true"; + daviCall.recording = infos.at("recording") == "true"; daviCall.moderatorMuted = infos.at("audioModeratorMuted") == "true"; daviCall.raisedHand = infos.at("handRaised") == "true"; daviCall.device = infos.at("device"); daviCall.streamId = infos.at("sinkId"); + } else if (infos.at("uri").find(aliceUri) != std::string::npos) { + hostRecording = infos.at("recording") == "true"; } } cv.notify_one(); @@ -883,6 +894,31 @@ ConferenceTest::testParticipantAddRmSecondVideo() DRing::unregisterSignalHandlers(); } +void +ConferenceTest::testPropagateRecording() +{ + registerSignalHandlers(); + + startConference(); + + JAMI_INFO("Play with recording state"); + DRing::toggleRecording(bobId, bobCall.callId); + CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return bobCall.recording.load(); })); + + DRing::toggleRecording(bobId, bobCall.callId); + CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return !bobCall.recording.load(); })); + + DRing::toggleRecording(aliceId, confId); + CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return hostRecording.load(); })); + + DRing::toggleRecording(aliceId, confId); + CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return !hostRecording.load(); })); + + hangupConference(); + + DRing::unregisterSignalHandlers(); +} + } // namespace test } // namespace jami