1 // Copyright (c) 2020 The Chromium Embedded Framework Authors. All rights
2 // reserved. Use of this source code is governed by a BSD-style license that
3 // can be found in the LICENSE file.
4
5 #include "libcef/browser/media_router/media_router_impl.h"
6
7 #include "libcef/browser/media_router/media_route_impl.h"
8 #include "libcef/browser/media_router/media_router_manager.h"
9 #include "libcef/browser/media_router/media_sink_impl.h"
10 #include "libcef/browser/media_router/media_source_impl.h"
11 #include "libcef/browser/thread_util.h"
12
13 namespace {
14
15 // Do not keep a reference to the object returned by this method.
GetBrowserContext(const CefBrowserContext::Getter & getter)16 CefBrowserContext* GetBrowserContext(const CefBrowserContext::Getter& getter) {
17 CEF_REQUIRE_UIT();
18 DCHECK(!getter.is_null());
19
20 // Will return nullptr if the BrowserContext has been shut down.
21 return getter.Run();
22 }
23
24 } // namespace
25
26 class CefRegistrationImpl : public CefRegistration,
27 public CefMediaRouterManager::Observer {
28 public:
CefRegistrationImpl(CefRefPtr<CefMediaObserver> observer)29 explicit CefRegistrationImpl(CefRefPtr<CefMediaObserver> observer)
30 : observer_(observer) {
31 DCHECK(observer_);
32 }
33
34 CefRegistrationImpl(const CefRegistrationImpl&) = delete;
35 CefRegistrationImpl& operator=(const CefRegistrationImpl&) = delete;
36
~CefRegistrationImpl()37 ~CefRegistrationImpl() override {
38 CEF_REQUIRE_UIT();
39
40 // May be null if OnMediaRouterDestroyed was called.
41 if (browser_context_getter_.is_null())
42 return;
43
44 auto browser_context = GetBrowserContext(browser_context_getter_);
45 if (!browser_context)
46 return;
47
48 browser_context->GetMediaRouterManager()->RemoveObserver(this);
49 }
50
Initialize(const CefBrowserContext::Getter & browser_context_getter)51 void Initialize(const CefBrowserContext::Getter& browser_context_getter) {
52 CEF_REQUIRE_UIT();
53 DCHECK(!browser_context_getter.is_null());
54 DCHECK(browser_context_getter_.is_null());
55 browser_context_getter_ = browser_context_getter;
56
57 auto browser_context = GetBrowserContext(browser_context_getter_);
58 if (!browser_context)
59 return;
60
61 browser_context->GetMediaRouterManager()->AddObserver(this);
62 }
63
64 private:
65 // CefMediaRouterManager::Observer methods:
OnMediaRouterDestroyed()66 void OnMediaRouterDestroyed() override { browser_context_getter_.Reset(); }
67
OnMediaSinks(const CefMediaRouterManager::MediaSinkVector & sinks)68 void OnMediaSinks(
69 const CefMediaRouterManager::MediaSinkVector& sinks) override {
70 std::vector<CefRefPtr<CefMediaSink>> cef_sinks;
71 for (const auto& sink : sinks) {
72 cef_sinks.push_back(new CefMediaSinkImpl(sink.sink));
73 }
74 observer_->OnSinks(cef_sinks);
75 }
76
OnMediaRoutes(const CefMediaRouterManager::MediaRouteVector & routes)77 void OnMediaRoutes(
78 const CefMediaRouterManager::MediaRouteVector& routes) override {
79 std::vector<CefRefPtr<CefMediaRoute>> cef_routes;
80 for (const auto& route : routes) {
81 cef_routes.push_back(MakeCefRoute(route));
82 }
83 observer_->OnRoutes(cef_routes);
84 }
85
OnMediaRouteMessages(const media_router::MediaRoute & route,const CefMediaRouterManager::MediaMessageVector & messages)86 void OnMediaRouteMessages(
87 const media_router::MediaRoute& route,
88 const CefMediaRouterManager::MediaMessageVector& messages) override {
89 CefRefPtr<CefMediaRoute> cef_route = MakeCefRoute(route);
90 for (const auto& message : messages) {
91 if (message->type == media_router::mojom::RouteMessage::Type::TEXT) {
92 if (message->message.has_value()) {
93 const std::string& str = *(message->message);
94 observer_->OnRouteMessageReceived(cef_route, str.c_str(), str.size());
95 }
96 } else if (message->type ==
97 media_router::mojom::RouteMessage::Type::BINARY) {
98 if (message->data.has_value()) {
99 const std::vector<uint8_t>& data = *(message->data);
100 observer_->OnRouteMessageReceived(cef_route, data.data(),
101 data.size());
102 }
103 }
104 }
105 }
106
OnMediaRouteStateChange(const media_router::MediaRoute & route,const content::PresentationConnectionStateChangeInfo & info)107 void OnMediaRouteStateChange(
108 const media_router::MediaRoute& route,
109 const content::PresentationConnectionStateChangeInfo& info) override {
110 observer_->OnRouteStateChanged(MakeCefRoute(route),
111 ToConnectionState(info.state));
112 }
113
MakeCefRoute(const media_router::MediaRoute & route)114 CefRefPtr<CefMediaRoute> MakeCefRoute(const media_router::MediaRoute& route) {
115 return new CefMediaRouteImpl(route, browser_context_getter_);
116 }
117
ToConnectionState(blink::mojom::PresentationConnectionState state)118 static CefMediaObserver::ConnectionState ToConnectionState(
119 blink::mojom::PresentationConnectionState state) {
120 switch (state) {
121 case blink::mojom::PresentationConnectionState::CONNECTING:
122 return CEF_MRCS_CONNECTING;
123 case blink::mojom::PresentationConnectionState::CONNECTED:
124 return CEF_MRCS_CONNECTED;
125 case blink::mojom::PresentationConnectionState::CLOSED:
126 return CEF_MRCS_CLOSED;
127 case blink::mojom::PresentationConnectionState::TERMINATED:
128 return CEF_MRCS_TERMINATED;
129 }
130 NOTREACHED();
131 return CEF_MRCS_UNKNOWN;
132 }
133
134 CefRefPtr<CefMediaObserver> observer_;
135 CefBrowserContext::Getter browser_context_getter_;
136
137 IMPLEMENT_REFCOUNTING_DELETE_ON_UIT(CefRegistrationImpl);
138 };
139
CefMediaRouterImpl()140 CefMediaRouterImpl::CefMediaRouterImpl() {
141 // Verify that our enum matches Chromium's values.
142 static_assert(
143 static_cast<int>(CEF_MRCR_TOTAL_COUNT) ==
144 static_cast<int>(media_router::RouteRequestResult::TOTAL_COUNT),
145 "enum mismatch");
146 }
147
Initialize(const CefBrowserContext::Getter & browser_context_getter,CefRefPtr<CefCompletionCallback> callback)148 void CefMediaRouterImpl::Initialize(
149 const CefBrowserContext::Getter& browser_context_getter,
150 CefRefPtr<CefCompletionCallback> callback) {
151 CEF_REQUIRE_UIT();
152 DCHECK(!initialized_);
153 DCHECK(!browser_context_getter.is_null());
154 DCHECK(browser_context_getter_.is_null());
155 browser_context_getter_ = browser_context_getter;
156
157 initialized_ = true;
158 if (!init_callbacks_.empty()) {
159 for (auto& callback : init_callbacks_) {
160 std::move(callback).Run();
161 }
162 init_callbacks_.clear();
163 }
164
165 if (callback) {
166 // Execute client callback asynchronously for consistency.
167 CEF_POST_TASK(CEF_UIT, base::BindOnce(&CefCompletionCallback::OnComplete,
168 callback.get()));
169 }
170 }
171
AddObserver(CefRefPtr<CefMediaObserver> observer)172 CefRefPtr<CefRegistration> CefMediaRouterImpl::AddObserver(
173 CefRefPtr<CefMediaObserver> observer) {
174 if (!observer)
175 return nullptr;
176 CefRefPtr<CefRegistrationImpl> registration =
177 new CefRegistrationImpl(observer);
178 StoreOrTriggerInitCallback(base::BindOnce(
179 &CefMediaRouterImpl::InitializeRegistrationInternal, this, registration));
180 return registration.get();
181 }
182
GetSource(const CefString & urn)183 CefRefPtr<CefMediaSource> CefMediaRouterImpl::GetSource(const CefString& urn) {
184 if (urn.empty())
185 return nullptr;
186
187 // Check for a valid URL and supported Cast/DIAL schemes.
188 GURL presentation_url(urn.ToString());
189 if (!media_router::IsValidPresentationUrl(presentation_url)) {
190 return nullptr;
191 }
192
193 if (presentation_url.SchemeIsHTTPOrHTTPS()) {
194 // We don't support tab/desktop mirroring, which is what Cast uses for
195 // arbitrary HTTP/HTTPS URLs (see CastMediaSource).
196 return nullptr;
197 }
198
199 return new CefMediaSourceImpl(presentation_url);
200 }
201
NotifyCurrentSinks()202 void CefMediaRouterImpl::NotifyCurrentSinks() {
203 StoreOrTriggerInitCallback(
204 base::BindOnce(&CefMediaRouterImpl::NotifyCurrentSinksInternal, this));
205 }
206
CreateRoute(CefRefPtr<CefMediaSource> source,CefRefPtr<CefMediaSink> sink,CefRefPtr<CefMediaRouteCreateCallback> callback)207 void CefMediaRouterImpl::CreateRoute(
208 CefRefPtr<CefMediaSource> source,
209 CefRefPtr<CefMediaSink> sink,
210 CefRefPtr<CefMediaRouteCreateCallback> callback) {
211 StoreOrTriggerInitCallback(base::BindOnce(
212 &CefMediaRouterImpl::CreateRouteInternal, this, source, sink, callback));
213 }
214
NotifyCurrentRoutes()215 void CefMediaRouterImpl::NotifyCurrentRoutes() {
216 StoreOrTriggerInitCallback(
217 base::BindOnce(&CefMediaRouterImpl::NotifyCurrentRoutesInternal, this));
218 }
219
InitializeRegistrationInternal(CefRefPtr<CefRegistrationImpl> registration)220 void CefMediaRouterImpl::InitializeRegistrationInternal(
221 CefRefPtr<CefRegistrationImpl> registration) {
222 DCHECK(ValidContext());
223
224 registration->Initialize(browser_context_getter_);
225 }
226
NotifyCurrentSinksInternal()227 void CefMediaRouterImpl::NotifyCurrentSinksInternal() {
228 DCHECK(ValidContext());
229
230 auto browser_context = GetBrowserContext(browser_context_getter_);
231 if (!browser_context)
232 return;
233
234 browser_context->GetMediaRouterManager()->NotifyCurrentSinks();
235 }
236
CreateRouteInternal(CefRefPtr<CefMediaSource> source,CefRefPtr<CefMediaSink> sink,CefRefPtr<CefMediaRouteCreateCallback> callback)237 void CefMediaRouterImpl::CreateRouteInternal(
238 CefRefPtr<CefMediaSource> source,
239 CefRefPtr<CefMediaSink> sink,
240 CefRefPtr<CefMediaRouteCreateCallback> callback) {
241 DCHECK(ValidContext());
242
243 std::string error;
244
245 auto browser_context = GetBrowserContext(browser_context_getter_);
246 if (!browser_context) {
247 error = "Context is not valid";
248 } else if (!source) {
249 error = "Source is empty or invalid";
250 } else if (!sink) {
251 error = "Sink is empty or invalid";
252 } else if (!sink->IsCompatibleWith(source)) {
253 error = "Sink is not compatible with source";
254 }
255
256 if (!error.empty()) {
257 LOG(WARNING) << "Media route creation failed: " << error;
258 if (callback) {
259 callback->OnMediaRouteCreateFinished(CEF_MRCR_UNKNOWN_ERROR, error,
260 nullptr);
261 }
262 return;
263 }
264
265 auto source_impl = static_cast<CefMediaSourceImpl*>(source.get());
266 auto sink_impl = static_cast<CefMediaSinkImpl*>(sink.get());
267
268 browser_context->GetMediaRouterManager()->CreateRoute(
269 source_impl->source().id(), sink_impl->sink().id(), url::Origin(),
270 base::BindOnce(&CefMediaRouterImpl::CreateRouteCallback, this, callback));
271 }
272
NotifyCurrentRoutesInternal()273 void CefMediaRouterImpl::NotifyCurrentRoutesInternal() {
274 DCHECK(ValidContext());
275
276 auto browser_context = GetBrowserContext(browser_context_getter_);
277 if (!browser_context)
278 return;
279
280 browser_context->GetMediaRouterManager()->NotifyCurrentRoutes();
281 }
282
CreateRouteCallback(CefRefPtr<CefMediaRouteCreateCallback> callback,const media_router::RouteRequestResult & result)283 void CefMediaRouterImpl::CreateRouteCallback(
284 CefRefPtr<CefMediaRouteCreateCallback> callback,
285 const media_router::RouteRequestResult& result) {
286 DCHECK(ValidContext());
287
288 if (result.result_code() != media_router::RouteRequestResult::OK) {
289 LOG(WARNING) << "Media route creation failed: " << result.error() << " ("
290 << result.result_code() << ")";
291 }
292
293 if (!callback)
294 return;
295
296 CefRefPtr<CefMediaRoute> route;
297 if (result.result_code() == media_router::RouteRequestResult::OK &&
298 result.route()) {
299 route = new CefMediaRouteImpl(*result.route(), browser_context_getter_);
300 }
301
302 callback->OnMediaRouteCreateFinished(
303 static_cast<cef_media_route_create_result_t>(result.result_code()),
304 result.error(), route);
305 }
306
StoreOrTriggerInitCallback(base::OnceClosure callback)307 void CefMediaRouterImpl::StoreOrTriggerInitCallback(
308 base::OnceClosure callback) {
309 if (!CEF_CURRENTLY_ON_UIT()) {
310 CEF_POST_TASK(
311 CEF_UIT, base::BindOnce(&CefMediaRouterImpl::StoreOrTriggerInitCallback,
312 this, std::move(callback)));
313 return;
314 }
315
316 if (initialized_) {
317 std::move(callback).Run();
318 } else {
319 init_callbacks_.emplace_back(std::move(callback));
320 }
321 }
322
ValidContext() const323 bool CefMediaRouterImpl::ValidContext() const {
324 return CEF_CURRENTLY_ON_UIT() && initialized_;
325 }
326
327 // CefMediaRouter methods ------------------------------------------------------
328
329 // static
GetGlobalMediaRouter(CefRefPtr<CefCompletionCallback> callback)330 CefRefPtr<CefMediaRouter> CefMediaRouter::GetGlobalMediaRouter(
331 CefRefPtr<CefCompletionCallback> callback) {
332 return CefRequestContext::GetGlobalContext()->GetMediaRouter(callback);
333 }
334