• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "cast/sender/cast_app_discovery_service_impl.h"
6 
7 #include <algorithm>
8 #include <chrono>
9 #include <utility>
10 
11 #include "cast/sender/public/cast_media_source.h"
12 #include "util/osp_logging.h"
13 
14 namespace openscreen {
15 namespace cast {
16 namespace {
17 
18 // The minimum time that must elapse before an app availability result can be
19 // force refreshed.
20 static constexpr std::chrono::minutes kRefreshThreshold =
21     std::chrono::minutes(1);
22 
23 }  // namespace
24 
CastAppDiscoveryServiceImpl(CastPlatformClient * platform_client,ClockNowFunctionPtr clock)25 CastAppDiscoveryServiceImpl::CastAppDiscoveryServiceImpl(
26     CastPlatformClient* platform_client,
27     ClockNowFunctionPtr clock)
28     : platform_client_(platform_client), clock_(clock), weak_factory_(this) {
29   OSP_DCHECK(platform_client_);
30   OSP_DCHECK(clock_);
31 }
32 
~CastAppDiscoveryServiceImpl()33 CastAppDiscoveryServiceImpl::~CastAppDiscoveryServiceImpl() {
34   OSP_CHECK_EQ(avail_queries_.size(), 0u);
35 }
36 
37 CastAppDiscoveryService::Subscription
StartObservingAvailability(const CastMediaSource & source,AvailabilityCallback callback)38 CastAppDiscoveryServiceImpl::StartObservingAvailability(
39     const CastMediaSource& source,
40     AvailabilityCallback callback) {
41   const std::string& source_id = source.source_id();
42 
43   // Return cached results immediately, if available.
44   std::vector<std::string> cached_device_ids =
45       availability_tracker_.GetAvailableDevices(source);
46   if (!cached_device_ids.empty()) {
47     callback(source, GetReceiversByIds(cached_device_ids));
48   }
49 
50   auto& callbacks = avail_queries_[source_id];
51   uint32_t query_id = next_avail_query_id_++;
52   callbacks.push_back({query_id, std::move(callback)});
53   if (callbacks.size() == 1) {
54     // NOTE: Even though we retain availability results for an app unregistered
55     // from the tracker, we will refresh the results when the app is
56     // re-registered.
57     std::vector<std::string> new_app_ids =
58         availability_tracker_.RegisterSource(source);
59     for (const auto& app_id : new_app_ids) {
60       for (const auto& entry : receivers_by_id_) {
61         RequestAppAvailability(entry.first, app_id);
62       }
63     }
64   }
65 
66   return Subscription(this, query_id);
67 }
68 
Refresh()69 void CastAppDiscoveryServiceImpl::Refresh() {
70   const auto app_ids = availability_tracker_.GetRegisteredApps();
71   for (const auto& entry : receivers_by_id_) {
72     for (const auto& app_id : app_ids) {
73       RequestAppAvailability(entry.first, app_id);
74     }
75   }
76 }
77 
AddOrUpdateReceiver(const ServiceInfo & receiver)78 void CastAppDiscoveryServiceImpl::AddOrUpdateReceiver(
79     const ServiceInfo& receiver) {
80   const std::string& device_id = receiver.unique_id;
81   receivers_by_id_[device_id] = receiver;
82 
83   // Any queries that currently contain this receiver should be updated.
84   UpdateAvailabilityQueries(
85       availability_tracker_.GetSupportedSources(device_id));
86 
87   for (const std::string& app_id : availability_tracker_.GetRegisteredApps()) {
88     RequestAppAvailability(device_id, app_id);
89   }
90 }
91 
RemoveReceiver(const ServiceInfo & receiver)92 void CastAppDiscoveryServiceImpl::RemoveReceiver(const ServiceInfo& receiver) {
93   const std::string& device_id = receiver.unique_id;
94   receivers_by_id_.erase(device_id);
95   UpdateAvailabilityQueries(
96       availability_tracker_.RemoveResultsForDevice(device_id));
97 }
98 
RequestAppAvailability(const std::string & device_id,const std::string & app_id)99 void CastAppDiscoveryServiceImpl::RequestAppAvailability(
100     const std::string& device_id,
101     const std::string& app_id) {
102   if (ShouldRefreshAppAvailability(device_id, app_id, clock_())) {
103     platform_client_->RequestAppAvailability(
104         device_id, app_id,
105         [self = weak_factory_.GetWeakPtr(), device_id](
106             const std::string& app_id, AppAvailabilityResult availability) {
107           if (self) {
108             self->UpdateAppAvailability(device_id, app_id, availability);
109           }
110         });
111   }
112 }
113 
UpdateAppAvailability(const std::string & device_id,const std::string & app_id,AppAvailabilityResult availability)114 void CastAppDiscoveryServiceImpl::UpdateAppAvailability(
115     const std::string& device_id,
116     const std::string& app_id,
117     AppAvailabilityResult availability) {
118   if (receivers_by_id_.find(device_id) == receivers_by_id_.end()) {
119     return;
120   }
121 
122   OSP_DVLOG << "App " << app_id << " on receiver " << device_id << " is "
123             << ToString(availability);
124 
125   UpdateAvailabilityQueries(availability_tracker_.UpdateAppAvailability(
126       device_id, app_id, {availability, clock_()}));
127 }
128 
UpdateAvailabilityQueries(const std::vector<CastMediaSource> & sources)129 void CastAppDiscoveryServiceImpl::UpdateAvailabilityQueries(
130     const std::vector<CastMediaSource>& sources) {
131   for (const auto& source : sources) {
132     const std::string& source_id = source.source_id();
133     auto it = avail_queries_.find(source_id);
134     if (it == avail_queries_.end())
135       continue;
136     std::vector<std::string> device_ids =
137         availability_tracker_.GetAvailableDevices(source);
138     std::vector<ServiceInfo> receivers = GetReceiversByIds(device_ids);
139     for (const auto& callback : it->second) {
140       callback.callback(source, receivers);
141     }
142   }
143 }
144 
GetReceiversByIds(const std::vector<std::string> & device_ids) const145 std::vector<ServiceInfo> CastAppDiscoveryServiceImpl::GetReceiversByIds(
146     const std::vector<std::string>& device_ids) const {
147   std::vector<ServiceInfo> receivers;
148   for (const std::string& device_id : device_ids) {
149     auto entry = receivers_by_id_.find(device_id);
150     if (entry != receivers_by_id_.end()) {
151       receivers.push_back(entry->second);
152     }
153   }
154   return receivers;
155 }
156 
ShouldRefreshAppAvailability(const std::string & device_id,const std::string & app_id,Clock::time_point now) const157 bool CastAppDiscoveryServiceImpl::ShouldRefreshAppAvailability(
158     const std::string& device_id,
159     const std::string& app_id,
160     Clock::time_point now) const {
161   // TODO(btolsch): Consider an exponential backoff mechanism instead.
162   // Receivers will typically respond with "unavailable" immediately after boot
163   // and then become available 10-30 seconds later.
164   auto availability = availability_tracker_.GetAvailability(device_id, app_id);
165   switch (availability.availability) {
166     case AppAvailabilityResult::kAvailable:
167       return false;
168     case AppAvailabilityResult::kUnavailable:
169       return (now - availability.time) > kRefreshThreshold;
170     // TODO(btolsch): Should there be a background task for periodically
171     // refreshing kUnknown (or even kUnavailable) results?
172     case AppAvailabilityResult::kUnknown:
173       return true;
174   }
175 
176   OSP_NOTREACHED();
177 }
178 
RemoveAvailabilityCallback(uint32_t id)179 void CastAppDiscoveryServiceImpl::RemoveAvailabilityCallback(uint32_t id) {
180   for (auto entry = avail_queries_.begin(); entry != avail_queries_.end();
181        ++entry) {
182     const std::string& source_id = entry->first;
183     auto& callbacks = entry->second;
184     auto it =
185         std::find_if(callbacks.begin(), callbacks.end(),
186                      [id](const AvailabilityCallbackEntry& callback_entry) {
187                        return callback_entry.id == id;
188                      });
189     if (it != callbacks.end()) {
190       callbacks.erase(it);
191       if (callbacks.empty()) {
192         availability_tracker_.UnregisterSource(source_id);
193         avail_queries_.erase(entry);
194       }
195       return;
196     }
197   }
198 }
199 
200 }  // namespace cast
201 }  // namespace openscreen
202