You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
202 lines
6.6 KiB
202 lines
6.6 KiB
// Copyright 2020 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "cast/sender/cast_app_discovery_service_impl.h"
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <utility>
|
|
|
|
#include "cast/sender/public/cast_media_source.h"
|
|
#include "util/osp_logging.h"
|
|
|
|
namespace openscreen {
|
|
namespace cast {
|
|
namespace {
|
|
|
|
// The minimum time that must elapse before an app availability result can be
|
|
// force refreshed.
|
|
static constexpr std::chrono::minutes kRefreshThreshold =
|
|
std::chrono::minutes(1);
|
|
|
|
} // namespace
|
|
|
|
CastAppDiscoveryServiceImpl::CastAppDiscoveryServiceImpl(
|
|
CastPlatformClient* platform_client,
|
|
ClockNowFunctionPtr clock)
|
|
: platform_client_(platform_client), clock_(clock), weak_factory_(this) {
|
|
OSP_DCHECK(platform_client_);
|
|
OSP_DCHECK(clock_);
|
|
}
|
|
|
|
CastAppDiscoveryServiceImpl::~CastAppDiscoveryServiceImpl() {
|
|
OSP_CHECK_EQ(avail_queries_.size(), 0u);
|
|
}
|
|
|
|
CastAppDiscoveryService::Subscription
|
|
CastAppDiscoveryServiceImpl::StartObservingAvailability(
|
|
const CastMediaSource& source,
|
|
AvailabilityCallback callback) {
|
|
const std::string& source_id = source.source_id();
|
|
|
|
// Return cached results immediately, if available.
|
|
std::vector<std::string> cached_device_ids =
|
|
availability_tracker_.GetAvailableDevices(source);
|
|
if (!cached_device_ids.empty()) {
|
|
callback(source, GetReceiversByIds(cached_device_ids));
|
|
}
|
|
|
|
auto& callbacks = avail_queries_[source_id];
|
|
uint32_t query_id = next_avail_query_id_++;
|
|
callbacks.push_back({query_id, std::move(callback)});
|
|
if (callbacks.size() == 1) {
|
|
// NOTE: Even though we retain availability results for an app unregistered
|
|
// from the tracker, we will refresh the results when the app is
|
|
// re-registered.
|
|
std::vector<std::string> new_app_ids =
|
|
availability_tracker_.RegisterSource(source);
|
|
for (const auto& app_id : new_app_ids) {
|
|
for (const auto& entry : receivers_by_id_) {
|
|
RequestAppAvailability(entry.first, app_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Subscription(this, query_id);
|
|
}
|
|
|
|
void CastAppDiscoveryServiceImpl::Refresh() {
|
|
const auto app_ids = availability_tracker_.GetRegisteredApps();
|
|
for (const auto& entry : receivers_by_id_) {
|
|
for (const auto& app_id : app_ids) {
|
|
RequestAppAvailability(entry.first, app_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CastAppDiscoveryServiceImpl::AddOrUpdateReceiver(
|
|
const ServiceInfo& receiver) {
|
|
const std::string& device_id = receiver.unique_id;
|
|
receivers_by_id_[device_id] = receiver;
|
|
|
|
// Any queries that currently contain this receiver should be updated.
|
|
UpdateAvailabilityQueries(
|
|
availability_tracker_.GetSupportedSources(device_id));
|
|
|
|
for (const std::string& app_id : availability_tracker_.GetRegisteredApps()) {
|
|
RequestAppAvailability(device_id, app_id);
|
|
}
|
|
}
|
|
|
|
void CastAppDiscoveryServiceImpl::RemoveReceiver(const ServiceInfo& receiver) {
|
|
const std::string& device_id = receiver.unique_id;
|
|
receivers_by_id_.erase(device_id);
|
|
UpdateAvailabilityQueries(
|
|
availability_tracker_.RemoveResultsForDevice(device_id));
|
|
}
|
|
|
|
void CastAppDiscoveryServiceImpl::RequestAppAvailability(
|
|
const std::string& device_id,
|
|
const std::string& app_id) {
|
|
if (ShouldRefreshAppAvailability(device_id, app_id, clock_())) {
|
|
platform_client_->RequestAppAvailability(
|
|
device_id, app_id,
|
|
[self = weak_factory_.GetWeakPtr(), device_id](
|
|
const std::string& app_id, AppAvailabilityResult availability) {
|
|
if (self) {
|
|
self->UpdateAppAvailability(device_id, app_id, availability);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void CastAppDiscoveryServiceImpl::UpdateAppAvailability(
|
|
const std::string& device_id,
|
|
const std::string& app_id,
|
|
AppAvailabilityResult availability) {
|
|
if (receivers_by_id_.find(device_id) == receivers_by_id_.end()) {
|
|
return;
|
|
}
|
|
|
|
OSP_DVLOG << "App " << app_id << " on receiver " << device_id << " is "
|
|
<< ToString(availability);
|
|
|
|
UpdateAvailabilityQueries(availability_tracker_.UpdateAppAvailability(
|
|
device_id, app_id, {availability, clock_()}));
|
|
}
|
|
|
|
void CastAppDiscoveryServiceImpl::UpdateAvailabilityQueries(
|
|
const std::vector<CastMediaSource>& sources) {
|
|
for (const auto& source : sources) {
|
|
const std::string& source_id = source.source_id();
|
|
auto it = avail_queries_.find(source_id);
|
|
if (it == avail_queries_.end())
|
|
continue;
|
|
std::vector<std::string> device_ids =
|
|
availability_tracker_.GetAvailableDevices(source);
|
|
std::vector<ServiceInfo> receivers = GetReceiversByIds(device_ids);
|
|
for (const auto& callback : it->second) {
|
|
callback.callback(source, receivers);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<ServiceInfo> CastAppDiscoveryServiceImpl::GetReceiversByIds(
|
|
const std::vector<std::string>& device_ids) const {
|
|
std::vector<ServiceInfo> receivers;
|
|
for (const std::string& device_id : device_ids) {
|
|
auto entry = receivers_by_id_.find(device_id);
|
|
if (entry != receivers_by_id_.end()) {
|
|
receivers.push_back(entry->second);
|
|
}
|
|
}
|
|
return receivers;
|
|
}
|
|
|
|
bool CastAppDiscoveryServiceImpl::ShouldRefreshAppAvailability(
|
|
const std::string& device_id,
|
|
const std::string& app_id,
|
|
Clock::time_point now) const {
|
|
// TODO(btolsch): Consider an exponential backoff mechanism instead.
|
|
// Receivers will typically respond with "unavailable" immediately after boot
|
|
// and then become available 10-30 seconds later.
|
|
auto availability = availability_tracker_.GetAvailability(device_id, app_id);
|
|
switch (availability.availability) {
|
|
case AppAvailabilityResult::kAvailable:
|
|
return false;
|
|
case AppAvailabilityResult::kUnavailable:
|
|
return (now - availability.time) > kRefreshThreshold;
|
|
// TODO(btolsch): Should there be a background task for periodically
|
|
// refreshing kUnknown (or even kUnavailable) results?
|
|
case AppAvailabilityResult::kUnknown:
|
|
return true;
|
|
}
|
|
|
|
OSP_NOTREACHED();
|
|
}
|
|
|
|
void CastAppDiscoveryServiceImpl::RemoveAvailabilityCallback(uint32_t id) {
|
|
for (auto entry = avail_queries_.begin(); entry != avail_queries_.end();
|
|
++entry) {
|
|
const std::string& source_id = entry->first;
|
|
auto& callbacks = entry->second;
|
|
auto it =
|
|
std::find_if(callbacks.begin(), callbacks.end(),
|
|
[id](const AvailabilityCallbackEntry& callback_entry) {
|
|
return callback_entry.id == id;
|
|
});
|
|
if (it != callbacks.end()) {
|
|
callbacks.erase(it);
|
|
if (callbacks.empty()) {
|
|
availability_tracker_.UnregisterSource(source_id);
|
|
avail_queries_.erase(entry);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace cast
|
|
} // namespace openscreen
|