blob: 4ca9a016cb5db1d668f3e2b16887df9c12f0383e [file] [log] [blame]
// 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