| // Copyright (c) 2020 The Chromium Embedded Framework Authors. All rights |
| // reserved. Use of this source code is governed by a BSD-style license that |
| // can be found in the LICENSE file. |
| |
| #include "libcef/browser/media_router/media_router_manager.h" |
| |
| #include "libcef/browser/browser_context.h" |
| #include "libcef/browser/thread_util.h" |
| |
| #include "chrome/browser/media/router/media_router_factory.h" |
| #include "chrome/browser/media/router/media_routes_observer.h" |
| #include "chrome/browser/media/router/route_message_observer.h" |
| #include "chrome/browser/media/router/route_message_util.h" |
| |
| namespace { |
| |
| const int kTimeoutMs = 5 * 1000; |
| const char kDefaultPresentationUrl[] = "https://google.com"; |
| |
| } // namespace |
| |
| class CefMediaRoutesObserver : public media_router::MediaRoutesObserver { |
| public: |
| explicit CefMediaRoutesObserver(CefMediaRouterManager* manager) |
| : media_router::MediaRoutesObserver(manager->GetMediaRouter()), |
| manager_(manager) {} |
| |
| void OnRoutesUpdated(const std::vector<media_router::MediaRoute>& routes, |
| const std::vector<media_router::MediaRoute::Id>& |
| joinable_route_ids) override { |
| manager_->routes_ = routes; |
| manager_->NotifyCurrentRoutes(); |
| } |
| |
| private: |
| CefMediaRouterManager* const manager_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CefMediaRoutesObserver); |
| }; |
| |
| // Used to receive messages if PresentationConnection is not supported. |
| class CefRouteMessageObserver : public media_router::RouteMessageObserver { |
| public: |
| CefRouteMessageObserver(CefMediaRouterManager* manager, |
| const media_router::MediaRoute& route) |
| : media_router::RouteMessageObserver(manager->GetMediaRouter(), |
| route.media_route_id()), |
| manager_(manager), |
| route_(route) {} |
| |
| void OnMessagesReceived( |
| CefMediaRouterManager::MediaMessageVector messages) override { |
| manager_->OnMessagesReceived(route_, messages); |
| } |
| |
| private: |
| CefMediaRouterManager* const manager_; |
| const media_router::MediaRoute route_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CefRouteMessageObserver); |
| }; |
| |
| // Used for messaging and route status notifications with Cast. |
| class CefPresentationConnection : public blink::mojom::PresentationConnection { |
| public: |
| explicit CefPresentationConnection( |
| CefMediaRouterManager* manager, |
| const media_router::MediaRoute& route, |
| media_router::mojom::RoutePresentationConnectionPtr connections) |
| : manager_(manager), |
| route_(route), |
| connection_receiver_(this, std::move(connections->connection_receiver)), |
| connection_remote_(std::move(connections->connection_remote)) {} |
| |
| void OnMessage( |
| blink::mojom::PresentationConnectionMessagePtr message) override { |
| CefMediaRouterManager::MediaMessageVector messages; |
| if (message->is_message()) { |
| messages.push_back(media_router::message_util::RouteMessageFromString( |
| message->get_message())); |
| } else if (message->is_data()) { |
| messages.push_back(media_router::message_util::RouteMessageFromData( |
| message->get_data())); |
| } |
| if (!messages.empty()) { |
| manager_->OnMessagesReceived(route_, messages); |
| } |
| } |
| |
| void DidChangeState( |
| blink::mojom::PresentationConnectionState state) override { |
| // May result in |this| being deleted, so post async and allow the call |
| // stack to unwind. |
| CEF_POST_TASK( |
| CEF_UIT, |
| base::BindOnce(&CefMediaRouterManager::OnRouteStateChange, |
| manager_->weak_ptr_factory_.GetWeakPtr(), route_, |
| content::PresentationConnectionStateChangeInfo(state))); |
| } |
| |
| void DidClose( |
| blink::mojom::PresentationConnectionCloseReason reason) override { |
| DidChangeState(blink::mojom::PresentationConnectionState::CLOSED); |
| } |
| |
| void SendRouteMessage(const std::string& message) { |
| connection_remote_->OnMessage( |
| blink::mojom::PresentationConnectionMessage::NewMessage(message)); |
| } |
| |
| private: |
| CefMediaRouterManager* const manager_; |
| const media_router::MediaRoute route_; |
| |
| // Used to receive messages from the MRP. |
| mojo::Receiver<blink::mojom::PresentationConnection> connection_receiver_; |
| |
| // Used to send messages to the MRP. |
| mojo::Remote<blink::mojom::PresentationConnection> connection_remote_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CefPresentationConnection); |
| }; |
| |
| CefMediaRouterManager::CefMediaRouterManager(CefBrowserContext* browser_context) |
| : browser_context_(browser_context), |
| query_result_manager_(GetMediaRouter()), |
| weak_ptr_factory_(this) { |
| // Perform initialization. |
| GetMediaRouter()->OnUserGesture(); |
| |
| query_result_manager_.AddObserver(this); |
| |
| // A non-empty presentation URL to required for discovery of Cast devices. |
| query_result_manager_.SetSourcesForCastMode( |
| media_router::MediaCastMode::PRESENTATION, |
| {media_router::MediaSource::ForPresentationUrl( |
| GURL(kDefaultPresentationUrl))}, |
| url::Origin()); |
| |
| routes_observer_ = std::make_unique<CefMediaRoutesObserver>(this); |
| } |
| |
| CefMediaRouterManager::~CefMediaRouterManager() { |
| CEF_REQUIRE_UIT(); |
| for (auto& observer : observers_) { |
| observers_.RemoveObserver(&observer); |
| observer.OnMediaRouterDestroyed(); |
| } |
| |
| query_result_manager_.RemoveObserver(this); |
| } |
| |
| void CefMediaRouterManager::AddObserver(Observer* observer) { |
| CEF_REQUIRE_UIT(); |
| observers_.AddObserver(observer); |
| } |
| |
| void CefMediaRouterManager::RemoveObserver(Observer* observer) { |
| CEF_REQUIRE_UIT(); |
| observers_.RemoveObserver(observer); |
| } |
| |
| void CefMediaRouterManager::NotifyCurrentSinks() { |
| CEF_REQUIRE_UIT(); |
| for (auto& observer : observers_) { |
| observer.OnMediaSinks(sinks_); |
| } |
| } |
| |
| void CefMediaRouterManager::NotifyCurrentRoutes() { |
| CEF_REQUIRE_UIT(); |
| for (auto& observer : observers_) { |
| observer.OnMediaRoutes(routes_); |
| } |
| } |
| |
| void CefMediaRouterManager::CreateRoute( |
| const media_router::MediaSource::Id& source_id, |
| const media_router::MediaSink::Id& sink_id, |
| const url::Origin& origin, |
| CreateRouteResultCallback callback) { |
| GetMediaRouter()->CreateRoute( |
| source_id, sink_id, origin, nullptr /* web_contents */, |
| base::BindOnce(&CefMediaRouterManager::OnCreateRoute, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback)), |
| base::TimeDelta::FromMilliseconds(kTimeoutMs), false /* incognito */); |
| } |
| |
| void CefMediaRouterManager::SendRouteMessage( |
| const media_router::MediaRoute::Id& route_id, |
| const std::string& message) { |
| // Must use PresentationConnection to send messages if it exists. |
| auto state = GetRouteState(route_id); |
| if (state && state->presentation_connection_) { |
| state->presentation_connection_->SendRouteMessage(message); |
| return; |
| } |
| |
| GetMediaRouter()->SendRouteMessage(route_id, message); |
| } |
| |
| void CefMediaRouterManager::TerminateRoute( |
| const media_router::MediaRoute::Id& route_id) { |
| GetMediaRouter()->TerminateRoute(route_id); |
| } |
| |
| void CefMediaRouterManager::OnResultsUpdated(const MediaSinkVector& sinks) { |
| sinks_ = sinks; |
| NotifyCurrentSinks(); |
| } |
| |
| media_router::MediaRouter* CefMediaRouterManager::GetMediaRouter() const { |
| CEF_REQUIRE_UIT(); |
| return media_router::MediaRouterFactory::GetApiForBrowserContext( |
| browser_context_); |
| } |
| |
| void CefMediaRouterManager::OnCreateRoute( |
| CreateRouteResultCallback callback, |
| media_router::mojom::RoutePresentationConnectionPtr connection, |
| const media_router::RouteRequestResult& result) { |
| CEF_REQUIRE_UIT(); |
| if (result.route()) { |
| CreateRouteState(*result.route(), std::move(connection)); |
| } |
| |
| std::move(callback).Run(result); |
| } |
| |
| void CefMediaRouterManager::OnRouteStateChange( |
| const media_router::MediaRoute& route, |
| const content::PresentationConnectionStateChangeInfo& info) { |
| CEF_REQUIRE_UIT(); |
| if (info.state == blink::mojom::PresentationConnectionState::CLOSED || |
| info.state == blink::mojom::PresentationConnectionState::TERMINATED) { |
| RemoveRouteState(route.media_route_id()); |
| } |
| |
| for (auto& observer : observers_) { |
| observer.OnMediaRouteStateChange(route, info); |
| } |
| } |
| |
| void CefMediaRouterManager::OnMessagesReceived( |
| const media_router::MediaRoute& route, |
| const MediaMessageVector& messages) { |
| CEF_REQUIRE_UIT(); |
| for (auto& observer : observers_) { |
| observer.OnMediaRouteMessages(route, messages); |
| } |
| } |
| |
| void CefMediaRouterManager::CreateRouteState( |
| const media_router::MediaRoute& route, |
| media_router::mojom::RoutePresentationConnectionPtr connection) { |
| const auto route_id = route.media_route_id(); |
| auto state = std::make_unique<RouteState>(); |
| |
| if (!connection.is_null()) { |
| // PresentationConnection must be used for messaging and status |
| // notifications if it exists. |
| state->presentation_connection_ = |
| std::make_unique<CefPresentationConnection>(this, route, |
| std::move(connection)); |
| } else { |
| // Fallback if PresentationConnection is not supported. |
| state->message_observer_ = |
| std::make_unique<CefRouteMessageObserver>(this, route); |
| state->state_subscription_ = |
| GetMediaRouter()->AddPresentationConnectionStateChangedCallback( |
| route_id, |
| base::BindRepeating(&CefMediaRouterManager::OnRouteStateChange, |
| weak_ptr_factory_.GetWeakPtr(), route)); |
| } |
| |
| route_state_map_.insert(std::make_pair(route_id, std::move(state))); |
| } |
| |
| CefMediaRouterManager::RouteState* CefMediaRouterManager::GetRouteState( |
| const media_router::MediaRoute::Id& route_id) { |
| const auto it = route_state_map_.find(route_id); |
| if (it != route_state_map_.end()) |
| return it->second.get(); |
| return nullptr; |
| } |
| |
| void CefMediaRouterManager::RemoveRouteState( |
| const media_router::MediaRoute::Id& route_id) { |
| auto it = route_state_map_.find(route_id); |
| if (it != route_state_map_.end()) |
| route_state_map_.erase(it); |
| } |