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

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

Powered by Google App Engine
This is Rietveld 408576698