1 // Copyright 2015 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.
5 #include "chrome/browser/media/router/media_router_mojo_impl.h"
9 #include "base/logging.h"
10 #include "base/memory/scoped_vector.h"
11 #include "base/observer_list.h"
12 #include "base/strings/stringprintf.h"
13 #include "chrome/browser/media/router/media_router_mojo_impl_factory.h"
14 #include "chrome/browser/media/router/media_router_type_converters.h"
15 #include "chrome/browser/media/router/media_routes_observer.h"
16 #include "chrome/browser/media/router/media_sinks_observer.h"
17 #include "extensions/browser/process_manager.h"
19 #define DVLOG_WITH_INSTANCE(level) \
20 DVLOG(level) << "MR #" << instance_id_ << ": "
22 #define DLOG_WITH_INSTANCE(level) DLOG(level) << "MR #" << instance_id_ << ": "
24 namespace media_router
{
27 // Converts the callback result of calling Mojo CreateRoute()/JoinRoute()
28 // into a local callback.
29 void RouteResponseReceived(const MediaRouteResponseCallback
& callback
,
30 interfaces::MediaRoutePtr media_route
,
31 const mojo::String
& error_text
) {
32 if (media_route
.is_null()) {
34 DCHECK(!error_text
.is_null());
35 callback
.Run(nullptr, !error_text
.get().empty() ? error_text
.get()
39 callback
.Run(media_route
.To
<scoped_ptr
<MediaRoute
>>(), "");
42 // TODO(imcheng): We should handle failure in this case. One way is to invoke
43 // all pending requests with failure. (crbug.com/490787)
44 void EventPageWakeComplete(bool success
) {
46 LOG(ERROR
) << "An error encountered while waking the event page.";
49 scoped_ptr
<content::PresentationSessionMessage
>
50 ConvertToPresentationSessionMessage(interfaces::RouteMessagePtr input
) {
51 DCHECK(!input
.is_null());
52 // TODO(haibinlu): get presentation_url&id from route_id
53 std::string presentation_url
;
54 std::string presentation_id
;
55 scoped_ptr
<content::PresentationSessionMessage
> output
;
56 switch (input
->type
) {
57 case interfaces::RouteMessage::Type::TYPE_TEXT
: {
58 DCHECK(!input
->message
.is_null());
59 DCHECK(input
->data
.is_null());
60 output
= content::PresentationSessionMessage::CreateStringMessage(
61 presentation_url
, presentation_id
, make_scoped_ptr(new std::string
));
62 input
->message
.Swap(output
->message
.get());
65 case interfaces::RouteMessage::Type::TYPE_BINARY
: {
66 DCHECK(!input
->data
.is_null());
67 DCHECK(input
->message
.is_null());
68 output
= content::PresentationSessionMessage::CreateArrayBufferMessage(
69 presentation_url
, presentation_id
,
70 make_scoped_ptr(new std::vector
<uint8_t>));
71 input
->data
.Swap(output
->data
.get());
76 NOTREACHED() << "Invalid route message type " << input
->type
;
82 MediaRouterMojoImpl::MediaRouterMojoImpl(
83 extensions::EventPageTracker
* event_page_tracker
)
84 : event_page_tracker_(event_page_tracker
),
85 instance_id_(base::GenerateGUID()) {
86 DCHECK(event_page_tracker_
);
89 MediaRouterMojoImpl::~MediaRouterMojoImpl() {
90 DCHECK(thread_checker_
.CalledOnValidThread());
94 void MediaRouterMojoImpl::BindToRequest(
95 const std::string
& extension_id
,
96 content::BrowserContext
* context
,
97 mojo::InterfaceRequest
<interfaces::MediaRouterObserver
> request
) {
98 MediaRouterMojoImpl
* impl
=
99 MediaRouterMojoImplFactory::GetApiForBrowserContext(context
);
102 impl
->BindToMojoRequest(request
.Pass(), extension_id
);
105 void MediaRouterMojoImpl::BindToMojoRequest(
106 mojo::InterfaceRequest
<interfaces::MediaRouterObserver
> request
,
107 const std::string
& extension_id
) {
108 DCHECK(thread_checker_
.CalledOnValidThread());
111 new mojo::Binding
<interfaces::MediaRouterObserver
>(this, request
.Pass()));
112 binding_
->set_error_handler(this);
114 mojo_media_router_extension_id_
= extension_id
;
117 // TODO(imcheng): If this occurs while there are pending requests, we should
118 // probably invoke them with failure. (crbug.com/490787)
119 void MediaRouterMojoImpl::OnConnectionError() {
120 DCHECK(thread_checker_
.CalledOnValidThread());
122 mojo_media_router_
.reset();
126 void MediaRouterMojoImpl::ProvideMediaRouter(
127 interfaces::MediaRouterPtr media_router_ptr
,
128 const interfaces::MediaRouterObserver::ProvideMediaRouterCallback
&
130 DCHECK(thread_checker_
.CalledOnValidThread());
132 mojo_media_router_
= media_router_ptr
.Pass();
133 mojo_media_router_
.set_error_handler(this);
134 callback
.Run(instance_id_
);
135 ExecutePendingRequests();
138 void MediaRouterMojoImpl::OnIssue(const interfaces::IssuePtr issue
) {
139 // TODO(imcheng): Implement. (crbug.com/461815)
140 DCHECK(thread_checker_
.CalledOnValidThread());
144 void MediaRouterMojoImpl::OnSinksReceived(
145 const mojo::String
& media_source
,
146 mojo::Array
<interfaces::MediaSinkPtr
> sinks
) {
147 DCHECK(thread_checker_
.CalledOnValidThread());
149 DVLOG_WITH_INSTANCE(1) << "OnSinksReceived";
150 std::vector
<MediaSink
> sinks_converted
;
151 sinks_converted
.reserve(sinks
.size());
153 for (size_t i
= 0; i
< sinks
.size(); ++i
) {
154 sinks_converted
.push_back(sinks
[i
].To
<MediaSink
>());
157 auto it
= sinks_observers_
.find(media_source
);
158 if (it
== sinks_observers_
.end()) {
159 DVLOG_WITH_INSTANCE(1)
160 << "Received sink list without any active observers: " << media_source
;
162 FOR_EACH_OBSERVER(MediaSinksObserver
, *it
->second
,
163 OnSinksReceived(sinks_converted
));
167 void MediaRouterMojoImpl::OnRoutesUpdated(
168 mojo::Array
<interfaces::MediaRoutePtr
> routes
) {
169 DCHECK(thread_checker_
.CalledOnValidThread());
171 DVLOG_WITH_INSTANCE(1) << "OnRoutesUpdated";
173 std::vector
<MediaRoute
> routes_converted
;
174 routes_converted
.reserve(routes
.size());
176 for (size_t i
= 0; i
< routes
.size(); ++i
) {
177 routes_converted
.push_back(routes
[i
].To
<MediaRoute
>());
180 FOR_EACH_OBSERVER(MediaRoutesObserver
, routes_observers_
,
181 OnRoutesUpdated(routes_converted
));
184 void MediaRouterMojoImpl::CreateRoute(
185 const MediaSource::Id
& source_id
,
186 const MediaSink::Id
& sink_id
,
189 const MediaRouteResponseCallback
& callback
) {
190 DCHECK(thread_checker_
.CalledOnValidThread());
192 if (!origin
.is_valid()) {
193 DVLOG_WITH_INSTANCE(1) << "Invalid origin: " << origin
;
194 callback
.Run(nullptr, "Invalid origin");
197 RunOrDefer(base::Bind(
198 &MediaRouterMojoImpl::DoCreateRoute
, base::Unretained(this), source_id
,
199 sink_id
, origin
.is_empty() ? "" : origin
.spec(), tab_id
, callback
));
202 void MediaRouterMojoImpl::JoinRoute(
203 const MediaSource::Id
& source_id
,
204 const std::string
& presentation_id
,
207 const MediaRouteResponseCallback
& callback
) {
208 DCHECK(thread_checker_
.CalledOnValidThread());
210 if (!origin
.is_valid()) {
211 DVLOG_WITH_INSTANCE(1) << "Invalid origin: " << origin
;
212 callback
.Run(nullptr, "Invalid origin");
215 RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoJoinRoute
,
216 base::Unretained(this), source_id
, presentation_id
,
217 origin
.is_empty() ? "" : origin
.spec(), tab_id
,
221 void MediaRouterMojoImpl::CloseRoute(const MediaRoute::Id
& route_id
) {
222 DCHECK(thread_checker_
.CalledOnValidThread());
224 RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoCloseRoute
,
225 base::Unretained(this), route_id
));
228 void MediaRouterMojoImpl::SendRouteMessage(
229 const MediaRoute::Id
& route_id
,
230 const std::string
& message
,
231 const SendRouteMessageCallback
& callback
) {
232 DCHECK(thread_checker_
.CalledOnValidThread());
234 RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoSendSessionMessage
,
235 base::Unretained(this), route_id
, message
, callback
));
238 void MediaRouterMojoImpl::ListenForRouteMessages(
239 const std::vector
<MediaRoute::Id
>& route_ids
,
240 const PresentationSessionMessageCallback
& message_cb
) {
241 DCHECK(thread_checker_
.CalledOnValidThread());
242 RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoListenForRouteMessages
,
243 base::Unretained(this), route_ids
, message_cb
));
246 void MediaRouterMojoImpl::ClearIssue(const Issue::Id
& issue_id
) {
247 DCHECK(thread_checker_
.CalledOnValidThread());
249 RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoClearIssue
,
250 base::Unretained(this), issue_id
));
253 void MediaRouterMojoImpl::RegisterMediaSinksObserver(
254 MediaSinksObserver
* observer
) {
255 DCHECK(thread_checker_
.CalledOnValidThread());
257 // Create an observer list for the media source and add |observer|
258 // to it. Fail if |observer| is already registered.
259 const std::string
& source_id
= observer
->source().id();
260 base::ObserverList
<MediaSinksObserver
>* observer_list
=
261 sinks_observers_
.get(source_id
);
262 if (!observer_list
) {
263 observer_list
= new base::ObserverList
<MediaSinksObserver
>;
264 sinks_observers_
.add(source_id
, make_scoped_ptr(observer_list
));
266 DCHECK(!observer_list
->HasObserver(observer
));
269 // We need to call DoStartObservingMediaSinks every time an observer is
270 // added to ensure the observer will be notified with a fresh set of results.
271 // TODO(imcheng): Implement caching. (crbug.com/492451)
272 observer_list
->AddObserver(observer
);
273 RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoStartObservingMediaSinks
,
274 base::Unretained(this), source_id
));
277 void MediaRouterMojoImpl::UnregisterMediaSinksObserver(
278 MediaSinksObserver
* observer
) {
279 DCHECK(thread_checker_
.CalledOnValidThread());
281 const std::string
& source_id
= observer
->source().id();
282 auto* observer_list
= sinks_observers_
.get(source_id
);
283 if (!observer_list
|| !observer_list
->HasObserver(observer
)) {
287 // If we are removing the final observer for the source, then stop
288 // observing sinks for it.
289 // might_have_observers() is reliable here on the assumption that this call
290 // is not inside the ObserverList iteration.
291 observer_list
->RemoveObserver(observer
);
292 if (!observer_list
->might_have_observers()) {
293 RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoStopObservingMediaSinks
,
294 base::Unretained(this), source_id
));
295 sinks_observers_
.erase(source_id
);
299 void MediaRouterMojoImpl::RegisterMediaRoutesObserver(
300 MediaRoutesObserver
* observer
) {
301 DCHECK(thread_checker_
.CalledOnValidThread());
302 DCHECK(!routes_observers_
.HasObserver(observer
));
304 RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoStartObservingMediaRoutes
,
305 base::Unretained(this)));
306 routes_observers_
.AddObserver(observer
);
309 void MediaRouterMojoImpl::UnregisterMediaRoutesObserver(
310 MediaRoutesObserver
* observer
) {
311 if (!routes_observers_
.HasObserver(observer
))
314 routes_observers_
.RemoveObserver(observer
);
315 if (!routes_observers_
.might_have_observers()) {
316 RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoStopObservingMediaRoutes
,
317 base::Unretained(this)));
321 void MediaRouterMojoImpl::RegisterIssuesObserver(IssuesObserver
* observer
) {
322 // TODO(imcheng): Implement. (crbug.com/461815)
326 void MediaRouterMojoImpl::UnregisterIssuesObserver(IssuesObserver
* observer
) {
327 // TODO(imcheng): Implement. (crbug.com/461815)
331 void MediaRouterMojoImpl::DoCreateRoute(
332 const MediaSource::Id
& source_id
,
333 const MediaSink::Id
& sink_id
,
334 const std::string
& origin
,
336 const MediaRouteResponseCallback
& callback
) {
337 std::string
presentation_id("mr_");
338 presentation_id
+= base::GenerateGUID();
339 DVLOG_WITH_INSTANCE(1) << "DoCreateRoute " << source_id
<< "=>" << sink_id
340 << ", presentation ID: " << presentation_id
;
341 mojo_media_router_
->CreateRoute(source_id
, sink_id
, presentation_id
, origin
,
343 base::Bind(&RouteResponseReceived
, callback
));
346 void MediaRouterMojoImpl::DoJoinRoute(
347 const MediaSource::Id
& source_id
,
348 const std::string
& presentation_id
,
349 const std::string
& origin
,
351 const MediaRouteResponseCallback
& callback
) {
352 DVLOG_WITH_INSTANCE(1) << "DoJoinRoute " << source_id
353 << ", presentation ID: " << presentation_id
;
354 mojo_media_router_
->JoinRoute(source_id
, presentation_id
, origin
, tab_id
,
355 base::Bind(&RouteResponseReceived
, callback
));
358 void MediaRouterMojoImpl::DoCloseRoute(const MediaRoute::Id
& route_id
) {
359 DVLOG_WITH_INSTANCE(1) << "DoCloseRoute " << route_id
;
360 mojo_media_router_
->CloseRoute(route_id
);
363 void MediaRouterMojoImpl::DoSendSessionMessage(
364 const MediaRoute::Id
& route_id
,
365 const std::string
& message
,
366 const SendRouteMessageCallback
& callback
) {
367 DVLOG_WITH_INSTANCE(1) << "SendRouteMessage " << route_id
;
368 mojo_media_router_
->SendRouteMessage(route_id
, message
, callback
);
371 void MediaRouterMojoImpl::DoListenForRouteMessages(
372 const std::vector
<MediaRoute::Id
>& route_ids
,
373 const PresentationSessionMessageCallback
& message_cb
) {
374 DVLOG_WITH_INSTANCE(1) << "ListenForRouteMessages";
375 mojo_media_router_
->ListenForRouteMessages(
376 mojo::Array
<mojo::String
>::From(route_ids
),
377 base::Bind(&MediaRouterMojoImpl::OnRouteMessageReceived
,
378 base::Unretained(this), message_cb
));
381 void MediaRouterMojoImpl::OnRouteMessageReceived(
382 const PresentationSessionMessageCallback
& message_cb
,
383 mojo::Array
<interfaces::RouteMessagePtr
> messages
) {
384 scoped_ptr
<ScopedVector
<content::PresentationSessionMessage
>>
385 session_messages(new ScopedVector
<content::PresentationSessionMessage
>());
386 for (size_t i
= 0; i
< messages
.size(); ++i
) {
387 session_messages
->push_back(
388 ConvertToPresentationSessionMessage(messages
[i
].Pass()).Pass());
390 message_cb
.Run(session_messages
.Pass());
393 void MediaRouterMojoImpl::DoClearIssue(const Issue::Id
& issue_id
) {
394 DVLOG_WITH_INSTANCE(1) << "DoClearIssue " << issue_id
;
395 mojo_media_router_
->ClearIssue(issue_id
);
398 void MediaRouterMojoImpl::DoStartObservingMediaSinks(
399 const MediaSource::Id
& source_id
) {
400 DVLOG_WITH_INSTANCE(1) << "DoStartObservingMediaSinks: " << source_id
;
401 mojo_media_router_
->StartObservingMediaSinks(source_id
);
404 void MediaRouterMojoImpl::DoStopObservingMediaSinks(
405 const MediaSource::Id
& source_id
) {
406 DVLOG_WITH_INSTANCE(1) << "DoStopObservingMediaSinks: " << source_id
;
407 mojo_media_router_
->StopObservingMediaSinks(source_id
);
410 void MediaRouterMojoImpl::DoStartObservingMediaRoutes() {
411 DVLOG_WITH_INSTANCE(1) << "DoStartObservingMediaRoutes";
412 mojo_media_router_
->StartObservingMediaRoutes();
415 void MediaRouterMojoImpl::DoStopObservingMediaRoutes() {
416 DVLOG_WITH_INSTANCE(1) << "DoStopObservingMediaRoutes";
417 mojo_media_router_
->StopObservingMediaRoutes();
420 void MediaRouterMojoImpl::EnqueueTask(const base::Closure
& closure
) {
421 pending_requests_
.push_back(closure
);
422 DVLOG_WITH_INSTANCE(2) << "EnqueueTask (queue-length="
423 << pending_requests_
.size() << ")";
426 void MediaRouterMojoImpl::RunOrDefer(const base::Closure
& request
) {
427 DCHECK(event_page_tracker_
);
429 if (mojo_media_router_extension_id_
.empty()) {
430 DVLOG_WITH_INSTANCE(1) << "Extension ID not known yet.";
431 EnqueueTask(request
);
432 } else if (event_page_tracker_
->IsEventPageSuspended(
433 mojo_media_router_extension_id_
)) {
434 DVLOG_WITH_INSTANCE(1) << "Waking event page.";
435 EnqueueTask(request
);
436 if (!event_page_tracker_
->WakeEventPage(
437 mojo_media_router_extension_id_
,
438 base::Bind(&EventPageWakeComplete
))) {
439 LOG(ERROR
) << "An error encountered while waking the event page.";
441 mojo_media_router_
.reset();
442 } else if (!mojo_media_router_
) {
443 DVLOG_WITH_INSTANCE(1) << "Extension is awake, awaiting ProvideMediaRouter "
445 EnqueueTask(request
);
451 void MediaRouterMojoImpl::ExecutePendingRequests() {
452 DCHECK(thread_checker_
.CalledOnValidThread());
453 DCHECK(mojo_media_router_
);
454 DCHECK(event_page_tracker_
);
455 DCHECK(!mojo_media_router_extension_id_
.empty());
457 if (event_page_tracker_
->IsEventPageSuspended(
458 mojo_media_router_extension_id_
)) {
459 DVLOG_WITH_INSTANCE(1)
460 << "ExecutePendingRequests was called while extension is suspended.";
464 for (const auto& next_request
: pending_requests_
)
467 pending_requests_
.clear();
470 } // namespace media_router