- aligned state handling with avahi-browse tool: browser is started in client callback - browsing is limited to IPv4 services discovered on the specific daemon network interface only Additional minor changes.
201 lines
6.3 KiB
C++
201 lines
6.3 KiB
C++
//
|
|
// browser.cpp
|
|
//
|
|
// Copyright (c) 2019 2020 Andrea Bondavalli. All rights reserved.
|
|
//
|
|
// 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
|
|
// 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, see <http://www.gnu.org/licenses/>.
|
|
//
|
|
|
|
#include "browser.hpp"
|
|
|
|
using namespace std::chrono;
|
|
using second_t = duration<double, std::ratio<1> >;
|
|
|
|
std::shared_ptr<Browser> Browser::create(
|
|
std::shared_ptr<Config> config) {
|
|
// no need to be thread-safe here
|
|
static std::weak_ptr<Browser> instance;
|
|
if (auto ptr = instance.lock()) {
|
|
return ptr;
|
|
}
|
|
auto ptr =
|
|
std::shared_ptr<Browser>(new Browser(config));
|
|
instance = ptr;
|
|
return ptr;
|
|
}
|
|
|
|
std::list<RemoteSource> Browser::get_remote_sources() const {
|
|
std::list<RemoteSource> sources_list;
|
|
std::shared_lock sources_lock(sources_mutex_);
|
|
// return list of remote sources ordered by name
|
|
for (const auto& source: sources_.get<name_tag>()) {
|
|
sources_list.push_back(source);
|
|
}
|
|
return sources_list;
|
|
}
|
|
|
|
static std::string sdp_get_subject(const std::string& sdp) {
|
|
std::stringstream ssstrem(sdp);
|
|
std::string line;
|
|
while (getline(ssstrem, line, '\n')) {
|
|
if (line.substr(0, 2) == "s=") {
|
|
return line.substr(2);
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
bool Browser::worker() {
|
|
sap_.set_multicast_interface(config_->get_ip_addr_str());
|
|
// Join SAP muticast address
|
|
igmp_.join(config_->get_ip_addr_str(), config_->get_sap_mcast_addr());
|
|
auto sap_timepoint = steady_clock::now();
|
|
int sap_interval = 10;
|
|
auto mdns_timepoint = steady_clock::now();
|
|
int mdns_interval = 10;
|
|
|
|
while (running_) {
|
|
bool is_announce;
|
|
uint16_t msg_id_hash;
|
|
uint32_t addr;
|
|
std::string sdp;
|
|
|
|
if (sap_.receive(is_announce, msg_id_hash, addr, sdp)) {
|
|
std::stringstream ss;
|
|
ss << "sap:" << std::hex << addr << msg_id_hash;
|
|
std::string id(ss.str());
|
|
BOOST_LOG_TRIVIAL(debug) << "browser:: received SAP message for " << id;
|
|
|
|
std::unique_lock sources_lock(sources_mutex_);
|
|
|
|
auto it = sources_.get<id_tag>().find(id);
|
|
if (it == sources_.end()) {
|
|
// Source is not in the map
|
|
if (is_announce) {
|
|
BOOST_LOG_TRIVIAL(info) << "browser:: adding SAP source " << id;
|
|
// annoucement, add new source
|
|
RemoteSource source;
|
|
source.id = id;
|
|
source.sdp = sdp;
|
|
source.source = "SAP";
|
|
source.address = ip::address_v4(ntohl(addr)).to_string();
|
|
source.name = sdp_get_subject(sdp);
|
|
source.last_seen =
|
|
duration_cast<second_t>(steady_clock::now() - startup_).count();
|
|
source.announce_period = 360; //default period
|
|
sources_.insert(source);
|
|
}
|
|
} else {
|
|
// Source is already in the map
|
|
if (is_announce) {
|
|
BOOST_LOG_TRIVIAL(debug)
|
|
<< "browser:: refreshing SAP source " << it->id;
|
|
// annoucement, update last seen and announce period
|
|
auto upd_source{*it};
|
|
uint32_t last_seen =
|
|
duration_cast<second_t>(steady_clock::now() - startup_).count();
|
|
upd_source.announce_period = last_seen - upd_source.last_seen;
|
|
upd_source.last_seen = last_seen;
|
|
sources_.replace(it, upd_source);
|
|
} else {
|
|
BOOST_LOG_TRIVIAL(info)
|
|
<< "browser:: removing SAP source " << it->id;
|
|
// deletion, remove entry
|
|
sources_.erase(it);
|
|
}
|
|
}
|
|
}
|
|
|
|
// check if it's time to update the SAP remote sources
|
|
if ((duration_cast<second_t>(steady_clock::now() - sap_timepoint).count())
|
|
> sap_interval) {
|
|
sap_timepoint = steady_clock::now();
|
|
// remove all sessions no longer announced
|
|
auto offset = duration_cast<second_t>(steady_clock::now() - startup_).count();
|
|
|
|
std::unique_lock sources_lock(sources_mutex_);
|
|
for (auto it = sources_.begin(); it != sources_.end();) {
|
|
if (it->source == "SAP" &&
|
|
(offset - it->last_seen) > (it->announce_period * 10)) {
|
|
// remove from remote SAP sources
|
|
BOOST_LOG_TRIVIAL(info)
|
|
<< "browser:: SAP source " << it->id << " timeout";
|
|
it = sources_.erase(it);
|
|
} else {
|
|
it++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// check if it's time to process the mDNS RTSP sources
|
|
if ((duration_cast<second_t>(steady_clock::now() - mdns_timepoint).count())
|
|
> mdns_interval) {
|
|
mdns_timepoint = steady_clock::now();
|
|
process_results();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Browser::on_new_rtsp_source(const std::string& name,
|
|
const std::string& domain,
|
|
const RTSPSSource& s) {
|
|
uint32_t last_seen = duration_cast<second_t>(steady_clock::now() - startup_).count();
|
|
std::unique_lock sources_lock(sources_mutex_);
|
|
if (sources_.get<id_tag>().find(s.id) == sources_.end()) {
|
|
BOOST_LOG_TRIVIAL(info) << "browser:: adding RTSP source " << s.id;
|
|
sources_.insert({ s.id, s.source, s.address, name, s.sdp, last_seen, 0 });
|
|
}
|
|
}
|
|
|
|
void Browser::on_remove_rtsp_source(const std::string& name,
|
|
const std::string& domain) {
|
|
std::unique_lock sources_lock(sources_mutex_);
|
|
auto& name_idx = sources_.get<name_tag>();
|
|
for (auto it = name_idx.find(name); it != name_idx.end(); it++) {
|
|
if (it->source == "mDNS") {
|
|
BOOST_LOG_TRIVIAL(info) << "browser:: removing RTSP source " << it->id;
|
|
name_idx.erase(it);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Browser::init() {
|
|
if (!running_) {
|
|
/* init mDNS client */
|
|
if (config_->get_mdns_enabled() && !MDNSClient::init()) {
|
|
return false;
|
|
}
|
|
running_ = true;
|
|
res_ = std::async(std::launch::async, &Browser::worker, this);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Browser::terminate() {
|
|
if (running_) {
|
|
running_ = false;
|
|
/* wait for worker to exit */
|
|
res_.get();
|
|
/* terminate mDNS client */
|
|
if (config_->get_mdns_enabled()) {
|
|
MDNSClient::terminate();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|