From 9501292fc931b46e8deb43ded110c80f8f068cff Mon Sep 17 00:00:00 2001 From: Green Sky Date: Tue, 1 Oct 2024 11:13:27 +0200 Subject: [PATCH] accept call --- src/chat_gui4.cpp | 76 +++++++--- src/frame_streams/voip_model.hpp | 11 +- src/tox_av_voip_model.cpp | 229 ++++++++++++++++++++----------- src/tox_av_voip_model.hpp | 16 ++- 4 files changed, 225 insertions(+), 107 deletions(-) diff --git a/src/chat_gui4.cpp b/src/chat_gui4.cpp index 8a25d86d..1cb34eec 100644 --- a/src/chat_gui4.cpp +++ b/src/chat_gui4.cpp @@ -14,6 +14,8 @@ // HACK: remove them #include +#include + #include #include @@ -23,7 +25,6 @@ #include "./media_meta_info_loader.hpp" #include "./sdl_clipboard_utils.hpp" -#include "entt/entity/entity.hpp" #include #include @@ -267,7 +268,8 @@ float ChatGui4::render(float time_delta) { if (ImGui::BeginMenu("VoIP")) { auto* voip_model = _cr.get(*_selected_contact); - std::vector contact_sessions; + std::vector contact_sessions; + std::vector acceptable_sessions; for (const auto& [ov, o_vm, sc] : _os.registry().view().each()) { if (o_vm != voip_model) { continue; @@ -275,7 +277,24 @@ float ChatGui4::render(float time_delta) { if (sc.c != *_selected_contact) { continue; } - contact_sessions.push_back(ov); + + auto o = _os.objectHandle(ov); + contact_sessions.push_back(o); + + if (!o.all_of()) { + continue; // not incoming + } + + // state is ringing/not yet accepted + const auto* session_state = o.try_get(); + if (session_state == nullptr) { + continue; + } + + if (session_state->state != Components::VoIP::SessionState::State::RINGING) { + continue; + } + acceptable_sessions.push_back(o); } static Components::VoIP::DefaultConfig g_default_connections{}; @@ -289,29 +308,46 @@ float ChatGui4::render(float time_delta) { ImGui::EndMenu(); } - // TODO: only list if >1 - if (ImGui::BeginMenu("accept call", false)) { - // list incomming here? - ImGui::EndMenu(); + if (acceptable_sessions.size() < 2) { + if (ImGui::MenuItem("accept call", nullptr, false, !acceptable_sessions.empty())) { + voip_model->accept(acceptable_sessions.front(), g_default_connections); + } + } else { + if (ImGui::BeginMenu("accept call", !acceptable_sessions.empty())) { + for (const auto o : acceptable_sessions) { + std::string label = "accept #"; + label += std::to_string(entt::to_integral(entt::to_entity(o.entity()))); + + if (ImGui::MenuItem(label.c_str())) { + voip_model->accept(o, g_default_connections); + } + } + ImGui::EndMenu(); + } } // TODO: disable if already in call? - if (ImGui::Button("call")) { - auto new_session = voip_model->enter(*_selected_contact, g_default_connections); + if (ImGui::Button(" call ")) { + voip_model->enter(*_selected_contact, g_default_connections); } - // TODO: only list if >1 - if (ImGui::BeginMenu("leave/reject call", !contact_sessions.empty())) { - // list - for (const auto ov : contact_sessions) { - std::string label = "end #"; - label += std::to_string(entt::to_integral(entt::to_entity(ov))); - - if (ImGui::MenuItem(label.c_str())) { - voip_model->leave({_os.registry(), ov}); - } + if (contact_sessions.size() < 2) { + if (ImGui::MenuItem("leave/reject call", nullptr, false, !contact_sessions.empty())) { + voip_model->leave(contact_sessions.front()); + } + } else { + if (ImGui::BeginMenu("leave/reject call")) { + // list + for (const auto o : contact_sessions) { + std::string label = "end #"; + label += std::to_string(entt::to_integral(entt::to_entity(o.entity()))); + + if (ImGui::MenuItem(label.c_str())) { + voip_model->leave(o); + } + } + ImGui::EndMenu(); } - ImGui::EndMenu(); } ImGui::EndMenu(); diff --git a/src/frame_streams/voip_model.hpp b/src/frame_streams/voip_model.hpp index ac5bf37d..573085f7 100644 --- a/src/frame_streams/voip_model.hpp +++ b/src/frame_streams/voip_model.hpp @@ -9,6 +9,11 @@ namespace Components::VoIP { struct TagVoIPSession {}; + // getting called or invited by + struct Incoming { + Contact3 c{entt::null}; + }; + struct DefaultConfig { bool incoming_audio {true}; bool incoming_video {true}; @@ -60,13 +65,13 @@ struct VoIPModelI { // - default stream sources/sinks ? // - enable a/v ? -> done on connecting to sources // returns object tieing together the VoIP session - virtual ObjectHandle enter(const Contact3 c, const Components::VoIP::DefaultConfig& defaults = {true, true, true, true}) { return {}; } + virtual ObjectHandle enter(const Contact3 c, const Components::VoIP::DefaultConfig& defaults = {true, true, true, true}) { (void)c,(void)defaults; return {}; } // accept/join an invite to a session - virtual bool accept(ObjectHandle session) { return false; } + virtual bool accept(ObjectHandle session, const Components::VoIP::DefaultConfig& defaults = {true, true, true, true}) { (void)session,(void)defaults; return false; } // leaves a call // - VoIP session object - virtual bool leave(ObjectHandle session) { return false; } + virtual bool leave(ObjectHandle session) { (void)session; return false; } }; diff --git a/src/tox_av_voip_model.cpp b/src/tox_av_voip_model.cpp index 7b00ced7..f25f4ec3 100644 --- a/src/tox_av_voip_model.cpp +++ b/src/tox_av_voip_model.cpp @@ -10,15 +10,12 @@ #include -namespace Contact::Components { - // session instead??? - struct ToxAVCall { +namespace Components { + struct ToxAVIncomingAV { bool incoming_audio {false}; bool incoming_video {false}; }; -} -namespace Components { struct ToxAVAudioSink { ObjectHandle o; // ptr? @@ -152,30 +149,56 @@ struct ToxAVCallAudioSink : public FrameStream2SinkI { } }; -ToxAVVoIPModel::ToxAVVoIPModel(ObjectStore2& os, ToxAVI& av, Contact3Registry& cr, ToxContactModel2& tcm) : - _os(os), _av(av), _cr(cr), _tcm(tcm) -{ - _av.subscribe(this, ToxAV_Event::friend_call); - _av.subscribe(this, ToxAV_Event::friend_call_state); - _av.subscribe(this, ToxAV_Event::friend_audio_bitrate); - _av.subscribe(this, ToxAV_Event::friend_video_bitrate); - _av.subscribe(this, ToxAV_Event::friend_audio_frame); - _av.subscribe(this, ToxAV_Event::friend_video_frame); +void ToxAVVoIPModel::addAudioSource(ObjectHandle session, uint32_t friend_number) { + auto& stream_source = session.get_or_emplace().streams; - // attach to all tox friend contacts + ObjectHandle incoming_audio {_os.registry(), _os.registry().create()}; - for (const auto& [cv, _] : _cr.view().each()) { - _cr.emplace(cv, this); + auto new_asrc = std::make_unique>(); + incoming_audio.emplace*>(new_asrc.get()); + incoming_audio.emplace>(std::move(new_asrc)); + incoming_audio.emplace(Components::StreamSource::create("ToxAV Friend Call Incoming Audio")); + + std::cout << "new incoming audio\n"; + if ( + const auto* defaults = session.try_get(); + defaults != nullptr && defaults->incoming_audio + ) { + incoming_audio.emplace(); // depends on what was specified in enter() + std::cout << "with default\n"; } - // TODO: events + + stream_source.push_back(incoming_audio); + session.emplace(incoming_audio); + // TODO: tie session to stream + + _audio_sources[friend_number] = incoming_audio; + + _os.throwEventConstruct(incoming_audio); } -ToxAVVoIPModel::~ToxAVVoIPModel(void) { - for (const auto& [ov, voipmodel] : _os.registry().view().each()) { - if (voipmodel == this) { - destroySession(_os.objectHandle(ov)); - } +void ToxAVVoIPModel::addAudioSink(ObjectHandle session, uint32_t friend_number) { + auto& stream_sinks = session.get_or_emplace().streams; + ObjectHandle outgoing_audio {_os.registry(), _os.registry().create()}; + + auto new_asink = std::make_unique(_av, friend_number); + outgoing_audio.emplace(new_asink.get()); + outgoing_audio.emplace(); + outgoing_audio.emplace>(std::move(new_asink)); + outgoing_audio.emplace(Components::StreamSink::create("ToxAV Friend Call Outgoing Audio")); + + if ( + const auto* defaults = session.try_get(); + defaults != nullptr && defaults->outgoing_audio + ) { + outgoing_audio.emplace(); // depends on what was specified in enter() } + + stream_sinks.push_back(outgoing_audio); + session.emplace(outgoing_audio); + // TODO: tie session to stream + + _os.throwEventConstruct(outgoing_audio); } void ToxAVVoIPModel::destroySession(ObjectHandle session) { @@ -220,6 +243,32 @@ void ToxAVVoIPModel::destroySession(ObjectHandle session) { _os.registry().destroy(session); } +ToxAVVoIPModel::ToxAVVoIPModel(ObjectStore2& os, ToxAVI& av, Contact3Registry& cr, ToxContactModel2& tcm) : + _os(os), _av(av), _cr(cr), _tcm(tcm) +{ + _av.subscribe(this, ToxAV_Event::friend_call); + _av.subscribe(this, ToxAV_Event::friend_call_state); + _av.subscribe(this, ToxAV_Event::friend_audio_bitrate); + _av.subscribe(this, ToxAV_Event::friend_video_bitrate); + _av.subscribe(this, ToxAV_Event::friend_audio_frame); + _av.subscribe(this, ToxAV_Event::friend_video_frame); + + // attach to all tox friend contacts + + for (const auto& [cv, _] : _cr.view().each()) { + _cr.emplace(cv, this); + } + // TODO: events +} + +ToxAVVoIPModel::~ToxAVVoIPModel(void) { + for (const auto& [ov, voipmodel] : _os.registry().view().each()) { + if (voipmodel == this) { + destroySession(_os.objectHandle(ov)); + } + } +} + void ToxAVVoIPModel::tick(void) { for (const auto& [oc, asink, asrf] : _os.registry().view().each()) { //for (const auto& [oc, asink] : _os.registry().view().each()) { @@ -276,19 +325,79 @@ ObjectHandle ToxAVVoIPModel::enter(const Contact3 c, const Components::VoIP::Def ObjectHandle new_session {_os.registry(), _os.registry().create()}; - new_session.emplace(c); - new_session.emplace(); // ?? - new_session.emplace().state = Components::VoIP::SessionState::State::RINGING; new_session.emplace(this); + new_session.emplace(); // ?? + new_session.emplace(c); + new_session.emplace().state = Components::VoIP::SessionState::State::RINGING; new_session.emplace(defaults); _os.throwEventConstruct(new_session); return new_session; } -bool ToxAVVoIPModel::accept(ObjectHandle session) { - //_av.toxavAnswer(, 0, 0); - return false; +bool ToxAVVoIPModel::accept(ObjectHandle session, const Components::VoIP::DefaultConfig& defaults) { + if (!static_cast(session)) { + return false; + } + + if (!session.all_of< + Components::VoIP::TagVoIPSession, + VoIPModelI*, + Components::VoIP::SessionContact, + Components::VoIP::Incoming + >()) { + return false; + } + + // check if self + if (session.get() != this) { + return false; + } + + const auto session_contact = session.get().c; + if (!_cr.all_of(session_contact)) { + return false; + } + + const auto friend_number = _cr.get(session_contact).friend_number; + auto err = _av.toxavAnswer(friend_number, 0, 0); + if (err != TOXAV_ERR_ANSWER_OK) { + std::cerr << "TOXAVVOIP error: ansering call failed: " << err << "\n"; + // we simply let it be for now, it apears we can try ansering later again + // we also get an error here when the call is already in progress (: + return false; + } + + session.emplace(defaults); + + // answer defaults to enabled receiving audio and video + // TODO: think about how we should handle this + // set to disabled? and enable on src connection? + // we already default disabled send and enabled on sink connection + //_av.toxavCallControl(friend_number, TOXAV_CALL_CONTROL_HIDE_VIDEO); + //_av.toxavCallControl(friend_number, TOXAV_CALL_CONTROL_MUTE_AUDIO); + + + // how do we know the other side is accepting audio + // bitrate cb or what? + assert(!session.all_of()); + addAudioSink(session, friend_number); + + if (const auto* i_av = session.try_get(); i_av != nullptr) { + // create audio src + if (i_av->incoming_audio) { + assert(!session.all_of()); + addAudioSource(session, friend_number); + } + + // create video src + if (i_av->incoming_video) { + } + } + + session.get_or_emplace().state = Components::VoIP::SessionState::State::CONNECTED; + _os.throwEventUpdate(session); + return true; } bool ToxAVVoIPModel::leave(ObjectHandle session) { @@ -298,7 +407,11 @@ bool ToxAVVoIPModel::leave(ObjectHandle session) { return false; } - if (!session.all_of()) { + if (!session.all_of< + Components::VoIP::TagVoIPSession, + VoIPModelI*, + Components::VoIP::SessionContact + >()) { return false; } @@ -331,10 +444,12 @@ bool ToxAVVoIPModel::onEvent(const Events::FriendCall& e) { ObjectHandle new_session {_os.registry(), _os.registry().create()}; - new_session.emplace(session_contact); - new_session.emplace(); // ?? - new_session.emplace().state = Components::VoIP::SessionState::State::RINGING; new_session.emplace(this); + new_session.emplace(); // ?? + new_session.emplace(session_contact); // in 1on1 its always the same contact, might leave blank + new_session.emplace(session_contact); + new_session.emplace().state = Components::VoIP::SessionState::State::RINGING; + new_session.emplace(e.audio_enabled, e.video_enabled); _os.throwEventConstruct(new_session); return true; @@ -368,28 +483,8 @@ bool ToxAVVoIPModel::onEvent(const Events::FriendCallState& e) { // remote accepted our call, or av send/recv conditions changed? o.get().state = Components::VoIP::SessionState::State::CONNECTED; // set to in call ?? - auto& stream_sinks = o.get_or_emplace().streams; if (s.is_accepting_a() && !o.all_of()) { - ObjectHandle outgoing_audio {_os.registry(), _os.registry().create()}; - - auto new_asink = std::make_unique(_av, e.friend_number); - outgoing_audio.emplace(new_asink.get()); - outgoing_audio.emplace(); - outgoing_audio.emplace>(std::move(new_asink)); - outgoing_audio.emplace(Components::StreamSink::create("ToxAV Friend Call Outgoing Audio")); - - if ( - const auto* defaults = o.try_get(); - defaults != nullptr && defaults->outgoing_audio - ) { - outgoing_audio.emplace(); // depends on what was specified in enter() - } - - stream_sinks.push_back(outgoing_audio); - o.emplace(outgoing_audio); - // TODO: tie session to stream - - _os.throwEventConstruct(outgoing_audio); + addAudioSink(o, e.friend_number); } else if (!s.is_accepting_a() && o.all_of()) { // remove asink? } @@ -397,33 +492,9 @@ bool ToxAVVoIPModel::onEvent(const Events::FriendCallState& e) { // video // add/update sources - auto& stream_source = o.get_or_emplace().streams; - // audio if (s.is_sending_a() && !o.all_of()) { - ObjectHandle incoming_audio {_os.registry(), _os.registry().create()}; - - auto new_asrc = std::make_unique>(); - incoming_audio.emplace*>(new_asrc.get()); - incoming_audio.emplace>(std::move(new_asrc)); - incoming_audio.emplace(Components::StreamSource::create("ToxAV Friend Call Incoming Audio")); - - std::cout << "new incoming audio\n"; - if ( - const auto* defaults = o.try_get(); - defaults != nullptr && defaults->incoming_audio - ) { - incoming_audio.emplace(); // depends on what was specified in enter() - std::cout << "with default\n"; - } - - stream_source.push_back(incoming_audio); - o.emplace(incoming_audio); - // TODO: tie session to stream - - _audio_sources[e.friend_number] = incoming_audio; - - _os.throwEventConstruct(incoming_audio); + addAudioSource(o, e.friend_number); } else if (!s.is_sending_a() && o.all_of()) { // remove asrc? } diff --git a/src/tox_av_voip_model.hpp b/src/tox_av_voip_model.hpp index 55ac8954..743e5952 100644 --- a/src/tox_av_voip_model.hpp +++ b/src/tox_av_voip_model.hpp @@ -8,7 +8,7 @@ #include -struct ToxAVVoIPModel : protected ToxAVEventI, public VoIPModelI { +class ToxAVVoIPModel : protected ToxAVEventI, public VoIPModelI { ObjectStore2& _os; ToxAVI& _av; Contact3Registry& _cr; @@ -17,16 +17,22 @@ struct ToxAVVoIPModel : protected ToxAVEventI, public VoIPModelI { // for faster lookup std::unordered_map _audio_sources; - ToxAVVoIPModel(ObjectStore2& os, ToxAVI& av, Contact3Registry& cr, ToxContactModel2& tcm); - ~ToxAVVoIPModel(void); + // TODO: virtual? strategy? protected? + virtual void addAudioSource(ObjectHandle session, uint32_t friend_number); + virtual void addAudioSink(ObjectHandle session, uint32_t friend_number); + // TODO: video void destroySession(ObjectHandle session); - void tick(void); + public: + ToxAVVoIPModel(ObjectStore2& os, ToxAVI& av, Contact3Registry& cr, ToxContactModel2& tcm); + ~ToxAVVoIPModel(void); + + void tick(void); public: // voip model ObjectHandle enter(const Contact3 c, const Components::VoIP::DefaultConfig& defaults) override; - bool accept(ObjectHandle session) override; + bool accept(ObjectHandle session, const Components::VoIP::DefaultConfig& defaults) override; bool leave(ObjectHandle session) override; protected: // toxav events