Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(79)

Side by Side Diff: chrome/browser/media/cast_remoting_connector.cc

Issue 2310753002: Media Remoting: Data/Control plumbing between renderer and Media Router. (Closed)
Patch Set: xjz's PS4 comments addressed. Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 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 "chrome/browser/media/cast_remoting_connector.h"
6
7 #include <stdio.h>
8
9 #include "base/bind.h"
10 #include "base/bind_helpers.h"
11 #include "base/callback.h"
12 #include "base/logging.h"
13 #include "base/memory/ptr_util.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_piece.h"
16 #include "base/strings/stringprintf.h"
17 #include "chrome/browser/media/cast_remoting_sender.h"
18 #include "chrome/browser/media/router/media_router.h"
19 #include "chrome/browser/media/router/media_router_factory.h"
20 #include "chrome/browser/media/router/media_source_helper.h"
21 #include "chrome/browser/media/router/route_message.h"
22 #include "chrome/browser/media/router/route_message_observer.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "mojo/public/cpp/bindings/strong_binding.h"
25
26 DEFINE_WEB_CONTENTS_USER_DATA_KEY(CastRemotingConnector);
27
28 using content::BrowserThread;
29 using media::mojom::RemotingStartFailReason;
30 using media::mojom::RemotingStopReason;
31
32 namespace {
33
34 // Simple command messages sent from/to the connector to/from the Media Router
35 // Cast Provider to start/stop media remoting to a Cast device.
36 //
37 // Field separator (for tokenizing parts of messages).
38 constexpr char kMessageFieldSeparator = ':';
39 // Message sent by CastRemotingConnector to Cast provider to start remoting.
40 // Example:
41 // "START_CAST_REMOTING:session=1f"
42 constexpr char kStartRemotingMessageFormat[] =
43 "START_CAST_REMOTING:session=%x";
44 // Message sent by CastRemotingConnector to Cast provider to start the remoting
45 // RTP stream(s). Example:
46 // "START_CAST_REMOTING_STREAMS:session=1f:audio=N:video=Y"
47 constexpr char kStartStreamsMessageFormat[] =
48 "START_CAST_REMOTING_STREAMS:session=%x:audio=%c:video=%c";
49 // Start acknowledgement message sent by Cast provider to CastRemotingConnector
50 // once remoting RTP streams have been set up. Examples:
51 // "STARTED_CAST_REMOTING_STREAMS:session=1f:audio_stream_id=2e:"
52 // "video_stream_id=3d"
53 // "STARTED_CAST_REMOTING_STREAMS:session=1f:video_stream_id=b33f"
54 constexpr char kStartedStreamsMessageFormatPartial[] =
55 "STARTED_CAST_REMOTING_STREAMS:session=%x";
56 constexpr char kStartedStreamsMessageAudioIdSpecifier[] = ":audio_stream_id=";
57 constexpr char kStartedStreamsMessageVideoIdSpecifier[] = ":video_stream_id=";
58 // Stop message sent by CastRemotingConnector to Cast provider. Example:
59 // "STOP_CAST_REMOTING:session=1f"
60 constexpr char kStopRemotingMessageFormat[] =
61 "STOP_CAST_REMOTING:session=%x";
62 // Stop acknowledgement message sent by Cast provider to CastRemotingConnector
63 // once remoting is available again after the last session ended. Example:
64 // "STOPPED_CAST_REMOTING:session=1f"
65 constexpr char kStoppedMessageFormat[] =
66 "STOPPED_CAST_REMOTING:session=%x";
67 // Failure message sent by Cast provider to CastRemotingConnector any time there
68 // was a fatal error (e.g., the Cast provider failed to set up the RTP streams,
69 // or there was some unexpected external event). Example:
70 // "FAILED_CAST_REMOTING:session=1f"
71 constexpr char kFailedMessageFormat[] = "FAILED_CAST_REMOTING:session=%x";
72
73 // Returns true if the given |message| matches the given |format| and the
74 // session ID in the |message| is equal to the |expected_session_id|.
75 bool IsMessageForSession(const std::string& message, const char* format,
76 unsigned int expected_session_id) {
77 unsigned int session_id;
78 if (sscanf(message.c_str(), format, &session_id) == 1)
79 return session_id == expected_session_id;
80 return false;
81 }
82
83 // Scans |message| for |specifier| and extracts the remoting stream ID that
84 // follows the specifier. Returns a negative value on error.
85 int32_t GetStreamIdFromStartedMessage(const std::string& message,
dcheng 2016/09/20 08:20:12 Nit: StringPiece here will net slightly more effic
miu 2016/09/21 03:15:50 Done.
86 const char* specifier,
87 size_t specifier_length) {
88 auto start = message.find(specifier, 0, specifier_length);
89 if (start == std::string::npos)
90 return -1;
91 start += specifier_length;
92 if (start + 1 >= message.size())
93 return -1; // Must be at least one hex digit following the specifier.
94 int parsed_value;
95 if (!base::HexStringToInt(
96 message.substr(start, message.find(kMessageFieldSeparator, start)),
97 &parsed_value) ||
98 parsed_value < 0 ||
99 parsed_value > std::numeric_limits<int32_t>::max()) {
100 return -1; // Non-hex digits, or outside valid range.
101 }
102 return static_cast<int32_t>(parsed_value);
103 }
104
105 } // namespace
106
107 class CastRemotingConnector::FrameRemoterFactory
108 : public media::mojom::RemoterFactory {
109 public:
110 // |render_frame_host| represents the source render frame.
111 explicit FrameRemoterFactory(content::RenderFrameHost* render_frame_host)
112 : host_(render_frame_host) {
113 DCHECK(host_);
114 }
115 ~FrameRemoterFactory() final {}
116
117 void Create(media::mojom::RemotingSourcePtr source,
118 media::mojom::RemoterRequest request) final {
119 CastRemotingConnector::Get(content::WebContents::FromRenderFrameHost(host_))
120 ->CreateBridge(std::move(source), std::move(request));
121 }
122
123 private:
124 content::RenderFrameHost* const host_;
125
126 DISALLOW_COPY_AND_ASSIGN(FrameRemoterFactory);
127 };
128
129 class CastRemotingConnector::RemotingBridge : public media::mojom::Remoter {
130 public:
131 // Constructs a "bridge" to delegate calls between the given |source| and
132 // |connector|. |connector| must outlive this instance.
133 RemotingBridge(media::mojom::RemotingSourcePtr source,
134 CastRemotingConnector* connector)
135 : source_(std::move(source)), connector_(connector) {
136 DCHECK(connector_);
137 source_.set_connection_error_handler(base::Bind(
138 &RemotingBridge::Stop, base::Unretained(this),
139 RemotingStopReason::SOURCE_GONE));
140 connector_->RegisterBridge(this);
141 }
142
143 ~RemotingBridge() final {
144 connector_->DeregisterBridge(this, RemotingStopReason::SOURCE_GONE);
145 }
146
147 // The CastRemotingConnector calls these to call back to the RemotingSource.
148 void OnSinkAvailable() { source_->OnSinkAvailable(); }
149 void OnSinkGone() { source_->OnSinkGone(); }
150 void OnStarted() { source_->OnStarted(); }
151 void OnStartFailed(RemotingStartFailReason reason) {
152 source_->OnStartFailed(reason);
153 }
154 void OnMessageFromSink(const std::vector<uint8_t>& message) {
155 source_->OnMessageFromSink(message);
156 }
157 void OnStopped(RemotingStopReason reason) { source_->OnStopped(reason); }
158
159 // media::mojom::Remoter implementation. The source calls these to start/stop
160 // media remoting and send messages to the sink. These simply delegate to the
161 // CastRemotingConnector, which mediates to establish only one remoting
162 // session among possibly multiple requests. The connector will respond to
163 // this request by calling one of: OnStarted() or OnStartFailed().
164 void Start() final {
165 connector_->StartRemoting(this);
166 }
167 void StartDataStreams(
168 mojo::ScopedDataPipeConsumerHandle audio_pipe,
169 mojo::ScopedDataPipeConsumerHandle video_pipe,
170 media::mojom::RemotingDataStreamSenderRequest audio_sender_request,
171 media::mojom::RemotingDataStreamSenderRequest video_sender_request)
172 final {
173 connector_->StartRemotingDataStreams(
174 this, std::move(audio_pipe), std::move(video_pipe),
175 std::move(audio_sender_request), std::move(video_sender_request));
176 }
177 void Stop(RemotingStopReason reason) final {
178 connector_->StopRemoting(this, reason);
179 }
180 void SendMessageToSink(const std::vector<uint8_t>& message) final {
181 connector_->SendMessageToSink(this, message);
182 }
183
184 private:
185 media::mojom::RemotingSourcePtr source_;
186 CastRemotingConnector* const connector_;
187
188 DISALLOW_COPY_AND_ASSIGN(RemotingBridge);
189 };
190
191 class CastRemotingConnector::MessageObserver
192 : public media_router::RouteMessageObserver {
193 public:
194 MessageObserver(media_router::MediaRouter* router,
195 const media_router::MediaRoute::Id& route_id,
196 CastRemotingConnector* connector)
197 : RouteMessageObserver(router, route_id), connector_(connector) {}
198 ~MessageObserver() final {}
199
200 private:
201 void OnMessagesReceived(
202 const std::vector<media_router::RouteMessage>& messages) final {
203 connector_->ProcessMessagesFromRoute(messages);
204 }
205
206 CastRemotingConnector* const connector_;
207 };
208
209 // static
210 CastRemotingConnector* CastRemotingConnector::Get(
211 content::WebContents* contents) {
212 CastRemotingConnector* connector = FromWebContents(contents);
213 if (connector)
214 return connector;
215 connector = new CastRemotingConnector(contents);
216 // The following transfers ownership of |connector| to WebContents.
217 contents->SetUserData(UserDataKey(), connector);
218 return connector;
219 }
220
221 // static
222 void CastRemotingConnector::CreateRemoterFactory(
223 content::RenderFrameHost* render_frame_host,
224 media::mojom::RemoterFactoryRequest request) {
225 mojo::MakeStrongBinding(
226 base::MakeUnique<FrameRemoterFactory>(render_frame_host),
227 std::move(request));
228 }
229
230 CastRemotingConnector::CastRemotingConnector(content::WebContents* contents)
231 : CastRemotingConnector(
232 media_router::MediaRouterFactory::GetApiForBrowserContext(
233 contents->GetBrowserContext()),
234 media_router::MediaSourceForTabContentRemoting(contents).id()) {}
235
236 CastRemotingConnector::CastRemotingConnector(
237 media_router::MediaRouter* router,
238 const media_router::MediaSource::Id& route_source_id)
239 : media_router::MediaRoutesObserver(router, route_source_id),
240 session_counter_(0),
241 active_bridge_(nullptr),
242 weak_factory_(this) {}
243
244 CastRemotingConnector::~CastRemotingConnector() {
245 // Remoting should not be active at this point, and this instance is expected
246 // to outlive all bridges. See comment in CreateBridge().
247 DCHECK(!active_bridge_);
248 DCHECK(bridges_.empty());
249 }
250
251 void CastRemotingConnector::CreateBridge(media::mojom::RemotingSourcePtr source,
252 media::mojom::RemoterRequest request) {
253 // Create a new RemotingBridge, which will become owned by the message pipe
254 // associated with |request|. |this| CastRemotingConnector should be valid
255 // for the full lifetime of the bridge because it can be deduced that the
256 // connector will always outlive the mojo message pipe: A single WebContents
257 // will destroy the render frame tree (which destroys all associated mojo
258 // message pipes) before CastRemotingConnector. To ensure this assumption is
259 // not broken by future design changes in external modules, a DCHECK() has
260 // been placed in the CastRemotingConnector destructor as a sanity-check.
261 mojo::MakeStrongBinding(
262 base::MakeUnique<RemotingBridge>(std::move(source), this),
263 std::move(request));
264 }
265
266 void CastRemotingConnector::RegisterBridge(RemotingBridge* bridge) {
267 DCHECK_CURRENTLY_ON(BrowserThread::UI);
268 DCHECK(bridges_.find(bridge) == bridges_.end());
269
270 bridges_.insert(bridge);
271 if (message_observer_ && !active_bridge_)
272 bridge->OnSinkAvailable();
273 }
274
275 void CastRemotingConnector::DeregisterBridge(RemotingBridge* bridge,
276 RemotingStopReason reason) {
277 DCHECK_CURRENTLY_ON(BrowserThread::UI);
278 DCHECK(bridges_.find(bridge) != bridges_.end());
279
280 if (bridge == active_bridge_)
281 StopRemoting(bridge, reason);
282 bridges_.erase(bridge);
283 }
284
285 void CastRemotingConnector::StartRemoting(RemotingBridge* bridge) {
286 DCHECK_CURRENTLY_ON(BrowserThread::UI);
287 DCHECK(bridges_.find(bridge) != bridges_.end());
288
289 // Refuse to start if there is no remoting route available, or if remoting is
290 // already active.
291 if (!message_observer_) {
292 bridge->OnStartFailed(RemotingStartFailReason::ROUTE_TERMINATED);
293 return;
294 }
295 if (active_bridge_) {
296 bridge->OnStartFailed(RemotingStartFailReason::CANNOT_START_MULTIPLE);
297 return;
298 }
299
300 // Notify all other sources that the sink is no longer available for remoting.
301 // A race condition is possible, where one of the other sources will try to
302 // start remoting before receiving this notification; but that attempt will
303 // just fail later on.
304 for (RemotingBridge* notifyee : bridges_) {
305 if (notifyee == bridge)
306 continue;
307 notifyee->OnSinkGone();
308 }
309
310 active_bridge_ = bridge;
311
312 // Send a start message to the Cast Provider.
313 ++session_counter_; // New remoting session ID.
314 SendMessageToProvider(
315 base::StringPrintf(kStartRemotingMessageFormat, session_counter_));
316
317 bridge->OnStarted();
318 }
319
320 void CastRemotingConnector::StartRemotingDataStreams(
321 RemotingBridge* bridge,
322 mojo::ScopedDataPipeConsumerHandle audio_pipe,
323 mojo::ScopedDataPipeConsumerHandle video_pipe,
324 media::mojom::RemotingDataStreamSenderRequest audio_sender_request,
325 media::mojom::RemotingDataStreamSenderRequest video_sender_request) {
326 DCHECK_CURRENTLY_ON(BrowserThread::UI);
327
328 // Refuse to start if there is no remoting route available, or if remoting is
329 // not active for this |bridge|.
330 if (!message_observer_ || active_bridge_ != bridge)
331 return;
332 // Also, if neither audio nor video pipe was provided, or if a request for a
333 // RemotingDataStreamSender was not provided for a data pipe, error-out early.
334 if ((!audio_pipe.is_valid() && !video_pipe.is_valid()) ||
335 (audio_pipe.is_valid() && !audio_sender_request.is_pending()) ||
336 (video_pipe.is_valid() && !video_sender_request.is_pending())) {
337 StopRemoting(active_bridge_, RemotingStopReason::DATA_SEND_FAILED);
338 return;
339 }
340
341 // Hold on to the data pipe handles and interface requests until one/both
342 // CastRemotingSenders are created and ready for use.
343 pending_audio_pipe_ = std::move(audio_pipe);
344 pending_video_pipe_ = std::move(video_pipe);
345 pending_audio_sender_request_ = std::move(audio_sender_request);
346 pending_video_sender_request_ = std::move(video_sender_request);
347
348 // Send a "start streams" message to the Cast Provider. The provider is
349 // responsible for creating and setting up a remoting Cast Streaming session
350 // that will result in new CastRemotingSender instances being created here in
351 // the browser process.
352 SendMessageToProvider(base::StringPrintf(
353 kStartStreamsMessageFormat, session_counter_,
354 pending_audio_sender_request_.is_pending() ? 'Y' : 'N',
355 pending_video_sender_request_.is_pending() ? 'Y' : 'N'));
356 }
357
358 void CastRemotingConnector::StopRemoting(RemotingBridge* bridge,
359 RemotingStopReason reason) {
360 DCHECK_CURRENTLY_ON(BrowserThread::UI);
361
362 if (active_bridge_ != bridge)
363 return;
364
365 active_bridge_ = nullptr;
366
367 // Explicitly close the data pipes (and related requests) just in case the
368 // "start streams" operation was interrupted.
369 pending_audio_pipe_.reset();
370 pending_video_pipe_.reset();
371 pending_audio_sender_request_.PassMessagePipe().reset();
dcheng 2016/09/20 08:20:12 Would it make sense to call ResetWithReason here?
miu 2016/09/21 03:15:50 This is brand-new, so I hadn't considered it. Havi
372 pending_video_sender_request_.PassMessagePipe().reset();
373
374 // Cancel all outstanding callbacks related to the remoting session.
375 weak_factory_.InvalidateWeakPtrs();
376
377 // Prevent the source from trying to start again until the Cast Provider has
378 // indicated the stop operation has completed.
379 bridge->OnSinkGone();
380 // Note: At this point, all sources should think the sink is gone.
381
382 SendMessageToProvider(
383 base::StringPrintf(kStopRemotingMessageFormat, session_counter_));
384 // Note: Once the Cast Provider sends back an acknowledgement message, all
385 // sources will be notified that the remoting sink is available again.
386
387 bridge->OnStopped(reason);
388 }
389
390 void CastRemotingConnector::SendMessageToSink(
391 RemotingBridge* bridge, const std::vector<uint8_t>& message) {
392 DCHECK_CURRENTLY_ON(BrowserThread::UI);
393
394 // During an active remoting session, simply pass all binary messages through
395 // to the sink.
396 if (!message_observer_ || active_bridge_ != bridge)
397 return;
398 media_router::MediaRoutesObserver::router()->SendRouteBinaryMessage(
399 message_observer_->route_id(),
400 base::MakeUnique<std::vector<uint8_t>>(message),
401 base::Bind(&CastRemotingConnector::HandleSendMessageResult,
402 weak_factory_.GetWeakPtr()));
403 }
404
405 void CastRemotingConnector::SendMessageToProvider(const std::string& message) {
406 DCHECK_CURRENTLY_ON(BrowserThread::UI);
407
408 if (!message_observer_)
409 return;
410
411 if (active_bridge_) {
412 media_router::MediaRoutesObserver::router()->SendRouteMessage(
413 message_observer_->route_id(), message,
414 base::Bind(&CastRemotingConnector::HandleSendMessageResult,
415 weak_factory_.GetWeakPtr()));
416 } else {
417 struct Helper {
418 static void IgnoreSendMessageResult(bool ignored) {}
419 };
420 media_router::MediaRoutesObserver::router()->SendRouteMessage(
421 message_observer_->route_id(), message,
422 base::Bind(&Helper::IgnoreSendMessageResult));
423 }
424 }
425
426 void CastRemotingConnector::ProcessMessagesFromRoute(
427 const std::vector<media_router::RouteMessage>& messages) {
dcheng 2016/09/20 08:20:12 For fuzzing, I think this is one of the interfaces
miu 2016/09/21 03:15:50 Acknowledged.
428 DCHECK_CURRENTLY_ON(BrowserThread::UI);
429
430 for (const media_router::RouteMessage& message : messages) {
431 switch (message.type) {
432 case media_router::RouteMessage::TEXT:
433 // This is a notification message from the Cast Provider, about the
434 // execution state of the media remoting session between Chrome and the
435 // remote device.
436 DCHECK(message.text);
dcheng 2016/09/20 08:20:12 Not for this CL, but we should consider making the
miu 2016/09/21 03:15:50 Agreed.
437
438 // If this is a "start streams" acknowledgement message, the
439 // CastRemotingSenders should now be available to begin consuming from
440 // the data pipes.
441 if (active_bridge_ &&
442 IsMessageForSession(*message.text,
443 kStartedStreamsMessageFormatPartial,
444 session_counter_)) {
445 if (pending_audio_sender_request_.is_pending()) {
446 cast::CastRemotingSender::FindAndBind(
447 GetStreamIdFromStartedMessage(
448 *message.text, kStartedStreamsMessageAudioIdSpecifier,
449 sizeof(kStartedStreamsMessageAudioIdSpecifier) - 1),
dcheng 2016/09/20 08:20:12 Nit: use strlen here and below, our compilers shou
miu 2016/09/21 03:15:50 Done.
450 std::move(pending_audio_pipe_),
451 std::move(pending_audio_sender_request_),
452 base::Bind(&CastRemotingConnector::OnDataSendFailed,
453 weak_factory_.GetWeakPtr()));
454 }
455 if (pending_video_sender_request_.is_pending()) {
456 cast::CastRemotingSender::FindAndBind(
457 GetStreamIdFromStartedMessage(
458 *message.text, kStartedStreamsMessageVideoIdSpecifier,
459 sizeof(kStartedStreamsMessageVideoIdSpecifier) - 1),
460 std::move(pending_video_pipe_),
461 std::move(pending_video_sender_request_),
462 base::Bind(&CastRemotingConnector::OnDataSendFailed,
463 weak_factory_.GetWeakPtr()));
464 }
465 break;
466 }
467
468 // If this is a failure message, call StopRemoting().
469 if (active_bridge_ &&
470 IsMessageForSession(*message.text, kFailedMessageFormat,
471 session_counter_)) {
472 StopRemoting(active_bridge_, RemotingStopReason::UNEXPECTED_FAILURE);
473 break;
474 }
475
476 // If this is a stop acknowledgement message, indicating that the last
477 // session was stopped, notify all sources that the sink is once again
478 // available.
479 if (IsMessageForSession(*message.text, kStoppedMessageFormat,
480 session_counter_)) {
481 if (active_bridge_) {
482 // Hmm...The Cast Provider was in a state that disagrees with this
483 // connector. Attempt to resolve this by shutting everything down to
484 // effectively reset to a known state.
485 LOG(WARNING) << "BUG: Cast Provider sent 'stopped' message during "
486 "an active remoting session.";
487 StopRemoting(active_bridge_,
488 RemotingStopReason::UNEXPECTED_FAILURE);
489 }
490 for (RemotingBridge* notifyee : bridges_)
491 notifyee->OnSinkAvailable();
492 break;
493 }
494
495 LOG(WARNING) << "BUG: Unexpected message from Cast Provider: "
496 << *message.text;
497 break;
498
499 case media_router::RouteMessage::BINARY: // This is for the source.
500 DCHECK(message.binary);
501
502 // All binary messages are passed through to the source during an active
503 // remoting session.
504 if (active_bridge_)
505 active_bridge_->OnMessageFromSink(*message.binary);
506 break;
507 }
508 }
509 }
510
511 void CastRemotingConnector::HandleSendMessageResult(bool success) {
512 DCHECK_CURRENTLY_ON(BrowserThread::UI);
513 // A single message send failure is treated as fatal to an active remoting
514 // session.
515 if (!success && active_bridge_)
516 StopRemoting(active_bridge_, RemotingStopReason::MESSAGE_SEND_FAILED);
517 }
518
519 void CastRemotingConnector::OnDataSendFailed() {
520 DCHECK_CURRENTLY_ON(BrowserThread::UI);
521 // A single data send failure is treated as fatal to an active remoting
522 // session.
523 if (active_bridge_)
524 StopRemoting(active_bridge_, RemotingStopReason::DATA_SEND_FAILED);
525 }
526
527 void CastRemotingConnector::OnRoutesUpdated(
528 const std::vector<media_router::MediaRoute>& routes,
529 const std::vector<media_router::MediaRoute::Id>& joinable_route_ids) {
530 DCHECK_CURRENTLY_ON(BrowserThread::UI);
531
532 // If a remoting route has already been identified, check that it still
533 // exists. Otherwise, shut down messaging and any active remoting, and notify
534 // the sources that remoting is no longer available.
535 if (message_observer_) {
536 for (const media_router::MediaRoute& route : routes) {
537 if (message_observer_->route_id() == route.media_route_id())
538 return; // Remoting route still exists. Take no further action.
539 }
540 message_observer_.reset();
541 if (active_bridge_)
542 StopRemoting(active_bridge_, RemotingStopReason::ROUTE_TERMINATED);
543 for (RemotingBridge* notifyee : bridges_)
544 notifyee->OnSinkGone();
545 }
546
547 // There shouldn't be an active RemotingBridge at this point, since there is
548 // currently no known remoting route.
549 DCHECK(!active_bridge_);
550
551 // Scan |routes| for a new remoting route. If one is found, begin processing
552 // messages on the route, and notify the sources that remoting is now
553 // available.
554 if (!routes.empty()) {
555 const media_router::MediaRoute& route = routes.front();
556 message_observer_.reset(new MessageObserver(
557 media_router::MediaRoutesObserver::router(), route.media_route_id(),
558 this));
559 // TODO(miu): In the future, scan the route ID for sink capabilities
560 // properties and pass these to the source in the OnSinkAvailable()
561 // notification.
562 for (RemotingBridge* notifyee : bridges_)
563 notifyee->OnSinkAvailable();
564 }
565 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698