blob: 7830fd705115f44a2ab44bbddd1edef35a2a90cc [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/media/router/mojo/media_router_mojo_impl.h"
#include <stddef.h>
#include <utility>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/observer_list.h"
#include "base/ranges/algorithm.h"
#include "base/strings/stringprintf.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/media/cast_mirroring_service_host.h"
#include "chrome/browser/media/cast_remoting_connector.h"
#include "chrome/browser/media/router/mojo/media_router_mojo_metrics.h"
#include "chrome/browser/media/router/mojo/media_sink_service_status.h"
#include "chrome/grit/chromium_strings.h"
#include "components/media_router/browser/issues_observer.h"
#include "components/media_router/browser/media_router_metrics.h"
#include "components/media_router/browser/media_routes_observer.h"
#include "components/media_router/browser/media_sinks_observer.h"
#include "components/media_router/browser/presentation_connection_message_observer.h"
#include "components/media_router/common/media_source.h"
#include "components/media_router/common/providers/cast/cast_media_source.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "ui/base/l10n/l10n_util.h"
#if BUILDFLAG(IS_WIN)
#include "chrome/browser/media/router/mojo/media_route_provider_util_win.h"
#endif
#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "extensions/common/constants.h"
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
namespace media_router {
namespace {
const int kDefaultFrameTreeNodeId = -1;
DesktopMediaPickerController::Params MakeDesktopPickerParams(
content::WebContents* web_contents) {
#if !BUILDFLAG(IS_CHROMEOS_ASH)
DCHECK(web_contents);
#endif
DesktopMediaPickerController::Params params;
// Value of |web_contents| comes from the UI, and typically corresponds to
// the active tab.
params.web_contents = web_contents;
if (web_contents) {
params.context = web_contents->GetTopLevelNativeWindow();
}
params.app_name = l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME);
params.target_name = params.app_name;
params.select_only_screen = true;
params.request_audio = true;
params.force_audio_checkboxes_to_default_checked = true;
return params;
}
// Returns a vector of media routes that are in |routes_a| and not in
// |routes_b|. Compares routes only by route id, and returns the version of
// the routes from |routes_a|.
std::vector<MediaRoute> GetRouteSetDifference(
std::vector<MediaRoute> routes_a,
std::vector<MediaRoute> routes_b) {
std::vector<MediaRoute> routes;
for (auto route_a : routes_a) {
bool route_seen = false;
for (auto route_b : routes_b) {
if (route_a.media_route_id() == route_b.media_route_id()) {
route_seen = true;
}
}
if (!route_seen) {
routes.emplace_back(route_a);
}
}
return routes;
}
} // namespace
MediaRouterMojoImpl::MediaRoutesQuery::MediaRoutesQuery() = default;
MediaRouterMojoImpl::MediaRoutesQuery::~MediaRoutesQuery() = default;
MediaRouterMojoImpl::MediaSinksQuery::MediaSinksQuery() = default;
MediaRouterMojoImpl::MediaSinksQuery::~MediaSinksQuery() = default;
MediaRouterMojoImpl::InternalMediaRoutesObserver::InternalMediaRoutesObserver(
media_router::MediaRouter* router)
: MediaRoutesObserver(router) {}
MediaRouterMojoImpl::InternalMediaRoutesObserver::
~InternalMediaRoutesObserver() = default;
void MediaRouterMojoImpl::InternalMediaRoutesObserver::OnRoutesUpdated(
const std::vector<MediaRoute>& routes) {
current_routes_ = routes;
}
const std::vector<MediaRoute>&
MediaRouterMojoImpl::InternalMediaRoutesObserver::current_routes() const {
return current_routes_;
}
MediaRouterMojoImpl::MediaRouterMojoImpl(content::BrowserContext* context)
: context_(context) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}
MediaRouterMojoImpl::~MediaRouterMojoImpl() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}
void MediaRouterMojoImpl::Initialize() {
DCHECK(!internal_routes_observer_);
// Because observer calls virtual methods on MediaRouter, it must be created
// outside of the constructor.
internal_routes_observer_ =
std::make_unique<InternalMediaRoutesObserver>(this);
}
void MediaRouterMojoImpl::RegisterMediaRouteProvider(
mojom::MediaRouteProviderId provider_id,
mojo::PendingRemote<mojom::MediaRouteProvider>
media_route_provider_remote) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!base::Contains(media_route_providers_, provider_id));
mojo::Remote<mojom::MediaRouteProvider> bound_remote(
std::move(media_route_provider_remote));
bound_remote.set_disconnect_handler(
base::BindOnce(&MediaRouterMojoImpl::OnProviderConnectionError,
AsWeakPtr(), provider_id));
media_route_providers_[provider_id] = std::move(bound_remote);
}
void MediaRouterMojoImpl::OnIssue(const IssueInfo& issue) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
GetIssueManager()->AddIssue(issue);
}
void MediaRouterMojoImpl::ClearTopIssueForSink(const MediaSink::Id& sink_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
GetIssueManager()->ClearTopIssueForSink(sink_id);
}
void MediaRouterMojoImpl::OnSinksReceived(
mojom::MediaRouteProviderId provider_id,
const std::string& media_source,
const std::vector<MediaSinkInternal>& internal_sinks,
const std::vector<url::Origin>& origins) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto it = sinks_queries_.find(MediaSinksQuery::GetKey(media_source).id());
if (it == sinks_queries_.end()) {
return;
}
std::vector<MediaSink> sinks;
sinks.reserve(internal_sinks.size());
for (const auto& internal_sink : internal_sinks) {
sinks.push_back(internal_sink.sink());
}
auto* sinks_query = it->second.get();
sinks_query->SetSinksForProvider(provider_id, sinks);
sinks_query->set_origins(origins);
sinks_query->NotifyObservers();
}
void MediaRouterMojoImpl::OnRoutesUpdated(
mojom::MediaRouteProviderId provider_id,
const std::vector<MediaRoute>& routes) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto current_routes = GetCurrentRoutes();
std::vector<MediaRoute> added_routes =
GetRouteSetDifference(routes, current_routes);
std::vector<MediaRoute> removed_routes =
GetRouteSetDifference(current_routes, routes);
// Update the internal_routes_observer_, and SetRoutesForProvider before
// AddMirroringMediaControllerHost, since the latter relies on these to be up
// to date.
internal_routes_observer_->OnRoutesUpdated(routes);
routes_query_.SetRoutesForProvider(provider_id, routes);
for (const auto& route : added_routes) {
if (route.IsLocalMirroringRoute()) {
AddMirroringMediaControllerHost(route);
}
}
for (const auto& route : removed_routes) {
mirroring_media_controller_hosts_.erase(route.media_route_id());
}
routes_query_.NotifyObservers();
}
void MediaRouterMojoImpl::RouteResponseReceived(
const std::string& presentation_id,
mojom::MediaRouteProviderId provider_id,
bool is_off_the_record,
MediaRouteResponseCallback callback,
bool is_join,
const absl::optional<MediaRoute>& media_route,
mojom::RoutePresentationConnectionPtr connection,
const absl::optional<std::string>& error_text,
mojom::RouteRequestResultCode result_code) {
DCHECK(!connection ||
(connection->connection_remote && connection->connection_receiver));
std::unique_ptr<RouteRequestResult> result;
if (!media_route) {
// An error occurred.
const std::string& error = (error_text && !error_text->empty())
? *error_text
: std::string("Unknown error.");
result = RouteRequestResult::FromError(error, result_code);
} else if (media_route->is_off_the_record() != is_off_the_record) {
std::string error = base::StringPrintf(
"Mismatch in OffTheRecord status: request = %d, response = %d",
is_off_the_record, media_route->is_off_the_record());
result = RouteRequestResult::FromError(
error, mojom::RouteRequestResultCode::ROUTE_NOT_FOUND);
} else {
result = RouteRequestResult::FromSuccess(*media_route, presentation_id);
OnRouteAdded(provider_id, *media_route);
}
if (is_join) {
MediaRouterMetrics::RecordJoinRouteResultCode(result->result_code(),
provider_id);
} else {
MediaRouterMetrics::RecordCreateRouteResultCode(result->result_code(),
provider_id);
}
std::move(callback).Run(std::move(connection), *result);
}
void MediaRouterMojoImpl::CreateRoute(const MediaSource::Id& source_id,
const MediaSink::Id& sink_id,
const url::Origin& origin,
content::WebContents* web_contents,
MediaRouteResponseCallback callback,
base::TimeDelta timeout,
bool off_the_record) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
CHECK(callback);
const MediaSink* sink = GetSinkById(sink_id);
if (!sink) {
std::unique_ptr<RouteRequestResult> result = RouteRequestResult::FromError(
"Sink not found", mojom::RouteRequestResultCode::SINK_NOT_FOUND);
MediaRouterMetrics::RecordCreateRouteResultCode(result->result_code());
std::move(callback).Run(nullptr, *result);
return;
}
const MediaSource source(source_id);
if (source.IsTabMirroringSource()) {
// Ensure the CastRemotingConnector is created before mirroring starts.
CastRemotingConnector* const connector =
CastRemotingConnector::Get(web_contents);
connector->ResetRemotingPermission();
MediaRouterMojoMetrics::RecordTabMirroringMetrics(web_contents);
}
if (IsSiteInitiatedMirroringSource(source_id)) {
MediaRouterMojoMetrics::RecordSiteInitiatedMirroringStarted(web_contents,
source);
}
MediaRouterMetrics::RecordMediaSinkType(sink->icon_type());
// Record which of the possible ways the sink may render the source's
// presentation URL (if it has one).
if (source.url().is_valid()) {
RecordPresentationRequestUrlBySink(source, sink->provider_id());
}
const mojom::MediaRouteProviderId provider_id = sink->provider_id();
const std::string presentation_id = MediaRouterBase::CreatePresentationId();
auto mr_callback = base::BindOnce(&MediaRouterMojoImpl::RouteResponseReceived,
AsWeakPtr(), presentation_id, provider_id,
off_the_record, std::move(callback), false);
if (source.IsDesktopMirroringSource()) {
desktop_picker_.Show(
MakeDesktopPickerParams(web_contents),
{DesktopMediaList::Type::kScreen},
base::BindRepeating([](content::WebContents* wc) { return true; }),
base::BindOnce(&MediaRouterMojoImpl::CreateRouteWithSelectedDesktop,
AsWeakPtr(), provider_id, sink_id, presentation_id,
origin, web_contents, timeout, off_the_record,
std::move(mr_callback)));
} else {
const int frame_tree_node_id =
web_contents ? web_contents->GetPrimaryMainFrame()->GetFrameTreeNodeId()
: kDefaultFrameTreeNodeId;
media_route_providers_[provider_id]->CreateRoute(
source_id, sink_id, presentation_id, origin, frame_tree_node_id,
timeout, off_the_record, std::move(mr_callback));
}
}
// TODO(crbug.com/1418747): The auto-join and/or "mirroring to flinging"
// features result in multiple presentations with identical presentation IDs
// of "auto-join". This is a latent bug because the rest of the code assumes
// presentation IDs are unique.
void MediaRouterMojoImpl::JoinRoute(const MediaSource::Id& source_id,
const std::string& presentation_id,
const url::Origin& origin,
content::WebContents* web_contents,
MediaRouteResponseCallback callback,
base::TimeDelta timeout,
bool off_the_record) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
absl::optional<mojom::MediaRouteProviderId> provider_id =
GetProviderIdForPresentation(presentation_id);
if (!provider_id || !HasJoinableRoute()) {
std::unique_ptr<RouteRequestResult> result = RouteRequestResult::FromError(
"Route not found", mojom::RouteRequestResultCode::ROUTE_NOT_FOUND);
MediaRouterMetrics::RecordJoinRouteResultCode(result->result_code());
// TODO(btolsch): This should really move |result| now that there's only a
// single callback.
std::move(callback).Run(nullptr, *result);
return;
}
const int frame_tree_node_id =
web_contents ? web_contents->GetPrimaryMainFrame()->GetFrameTreeNodeId()
: kDefaultFrameTreeNodeId;
auto mr_callback = base::BindOnce(&MediaRouterMojoImpl::RouteResponseReceived,
AsWeakPtr(), presentation_id, *provider_id,
off_the_record, std::move(callback), true);
media_route_providers_[*provider_id]->JoinRoute(
source_id, presentation_id, origin, frame_tree_node_id, timeout,
off_the_record, std::move(mr_callback));
}
void MediaRouterMojoImpl::TerminateRoute(const MediaRoute::Id& route_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
absl::optional<mojom::MediaRouteProviderId> provider_id =
GetProviderIdForRoute(route_id);
if (!provider_id) {
MediaRouterMetrics::RecordJoinRouteResultCode(
mojom::RouteRequestResultCode::ROUTE_NOT_FOUND);
return;
}
auto callback = base::BindOnce(&MediaRouterMojoImpl::OnTerminateRouteResult,
AsWeakPtr(), route_id, *provider_id);
media_route_providers_[*provider_id]->TerminateRoute(route_id,
std::move(callback));
}
void MediaRouterMojoImpl::DetachRoute(MediaRoute::Id route_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
absl::optional<mojom::MediaRouteProviderId> provider_id =
GetProviderIdForRoute(route_id);
if (!provider_id) {
return;
}
media_route_providers_[*provider_id]->DetachRoute(route_id);
}
void MediaRouterMojoImpl::SendRouteMessage(const MediaRoute::Id& route_id,
const std::string& message) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
absl::optional<mojom::MediaRouteProviderId> provider_id =
GetProviderIdForRoute(route_id);
if (!provider_id) {
return;
}
media_route_providers_[*provider_id]->SendRouteMessage(route_id, message);
}
void MediaRouterMojoImpl::SendRouteBinaryMessage(
const MediaRoute::Id& route_id,
std::unique_ptr<std::vector<uint8_t>> data) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
absl::optional<mojom::MediaRouteProviderId> provider_id =
GetProviderIdForRoute(route_id);
if (!provider_id) {
return;
}
media_route_providers_[*provider_id]->SendRouteBinaryMessage(route_id, *data);
}
IssueManager* MediaRouterMojoImpl::GetIssueManager() {
return &issue_manager_;
}
void MediaRouterMojoImpl::OnUserGesture() {}
std::vector<MediaRoute> MediaRouterMojoImpl::GetCurrentRoutes() const {
return internal_routes_observer_->current_routes();
}
std::unique_ptr<media::FlingingController>
MediaRouterMojoImpl::GetFlingingController(const MediaRoute::Id& route_id) {
return nullptr;
}
MirroringMediaControllerHost*
MediaRouterMojoImpl::GetMirroringMediaControllerHost(
const MediaRoute::Id& route_id) {
auto it = mirroring_media_controller_hosts_.find(route_id);
if (it != mirroring_media_controller_hosts_.end()) {
return it->second.get();
} else {
return nullptr;
}
}
void MediaRouterMojoImpl::GetMediaController(
const MediaRoute::Id& route_id,
mojo::PendingReceiver<mojom::MediaController> controller,
mojo::PendingRemote<mojom::MediaStatusObserver> observer) {
auto* route = GetRoute(route_id);
absl::optional<mojom::MediaRouteProviderId> provider_id =
GetProviderIdForRoute(route_id);
if (!route || !provider_id ||
route->controller_type() == RouteControllerType::kNone) {
return;
}
auto callback = base::BindOnce(&MediaRouterMojoImpl::OnMediaControllerCreated,
AsWeakPtr(), route_id);
media_route_providers_[*provider_id]->CreateMediaRouteController(
route_id, std::move(controller), std::move(observer),
std::move(callback));
}
base::Value MediaRouterMojoImpl::GetLogs() const {
return logger_.GetLogsAsValue();
}
// static
MediaSource MediaRouterMojoImpl::MediaSinksQuery::GetKey(
const MediaSource::Id& id) {
MediaSource source(id);
if (source.IsTabMirroringSource()) {
return MediaSource::ForAnyTab();
}
return source;
}
// static
MediaSource MediaRouterMojoImpl::MediaSinksQuery::GetKey(
const MediaSinksObserver& observer) {
if (!observer.source()) {
return MediaSource{""};
}
return GetKey(observer.source()->id());
}
void MediaRouterMojoImpl::MediaSinksQuery::SetSinksForProvider(
mojom::MediaRouteProviderId provider_id,
const std::vector<MediaSink>& sinks) {
base::EraseIf(cached_sink_list_, [&provider_id](const MediaSink& sink) {
return sink.provider_id() == provider_id;
});
cached_sink_list_.insert(cached_sink_list_.end(), sinks.begin(), sinks.end());
}
void MediaRouterMojoImpl::MediaSinksQuery::Reset() {
cached_sink_list_.clear();
origins_.clear();
}
void MediaRouterMojoImpl::MediaSinksQuery::AddObserver(
MediaSinksObserver* observer) {
observers_.AddObserver(observer);
observer->OnSinksUpdated(cached_sink_list_, origins_);
}
void MediaRouterMojoImpl::MediaSinksQuery::RemoveObserver(
MediaSinksObserver* observer) {
observers_.RemoveObserver(observer);
}
void MediaRouterMojoImpl::MediaSinksQuery::NotifyObservers() {
for (auto& observer : observers_) {
observer.OnSinksUpdated(cached_sink_list_, origins_);
}
}
bool MediaRouterMojoImpl::MediaSinksQuery::HasObserver(
MediaSinksObserver* observer) const {
return observers_.HasObserver(observer);
}
bool MediaRouterMojoImpl::MediaSinksQuery::HasObservers() const {
return !observers_.empty();
}
void MediaRouterMojoImpl::MediaRoutesQuery::SetRoutesForProvider(
mojom::MediaRouteProviderId provider_id,
const std::vector<MediaRoute>& routes) {
providers_to_routes_[provider_id] = routes;
UpdateCachedRouteList();
}
bool MediaRouterMojoImpl::MediaRoutesQuery::AddRouteForProvider(
mojom::MediaRouteProviderId provider_id,
const MediaRoute& route) {
std::vector<MediaRoute>& routes = providers_to_routes_[provider_id];
if (!base::Contains(routes, route.media_route_id(),
&MediaRoute::media_route_id)) {
routes.push_back(route);
UpdateCachedRouteList();
return true;
}
return false;
}
void MediaRouterMojoImpl::MediaRoutesQuery::UpdateCachedRouteList() {
cached_route_list_.emplace();
for (const auto& provider_to_routes : providers_to_routes_) {
cached_route_list_->insert(cached_route_list_->end(),
provider_to_routes.second.begin(),
provider_to_routes.second.end());
}
}
void MediaRouterMojoImpl::MediaRoutesQuery::AddObserver(
MediaRoutesObserver* observer) {
observers_.AddObserver(observer);
observer->OnRoutesUpdated(
cached_route_list_.value_or(std::vector<MediaRoute>()));
}
void MediaRouterMojoImpl::MediaRoutesQuery::RemoveObserver(
MediaRoutesObserver* observer) {
observers_.RemoveObserver(observer);
}
void MediaRouterMojoImpl::MediaRoutesQuery::NotifyObservers() {
for (auto& observer : observers_) {
observer.OnRoutesUpdated(
cached_route_list_.value_or(std::vector<MediaRoute>()));
}
}
bool MediaRouterMojoImpl::MediaRoutesQuery::HasObserver(
MediaRoutesObserver* observer) const {
return observers_.HasObserver(observer);
}
bool MediaRouterMojoImpl::MediaRoutesQuery::HasObservers() const {
return !observers_.empty();
}
bool MediaRouterMojoImpl::RegisterMediaSinksObserver(
MediaSinksObserver* observer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Create an observer list for the media source and add |observer|
// to it. Fail if |observer| is already registered.
const MediaSource source = MediaSinksQuery::GetKey(*observer);
std::unique_ptr<MediaSinksQuery>& sinks_query = sinks_queries_[source.id()];
bool is_new_query = false;
if (!sinks_query) {
is_new_query = true;
sinks_query = std::make_unique<MediaSinksQuery>();
} else {
DCHECK(!sinks_query->HasObserver(observer));
}
sinks_query->AddObserver(observer);
// If the query isn't new, then there is no need to call MRPs.
if (is_new_query) {
for (const auto& provider : media_route_providers_) {
// TODO(crbug.com/1090890): Don't allow MediaSource::ForAnyTab().id() to
// be passed here.
provider.second->StartObservingMediaSinks(source.id());
}
}
return true;
}
void MediaRouterMojoImpl::UnregisterMediaSinksObserver(
MediaSinksObserver* observer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const MediaSource source = MediaSinksQuery::GetKey(*observer);
auto it = sinks_queries_.find(source.id());
if (it == sinks_queries_.end() || !it->second->HasObserver(observer)) {
return;
}
// If we are removing the final observer for the source, then stop
// observing sinks for it.
// HasObservers() is reliable here on the assumption that this call
// is not inside the ObserverList iteration.
it->second->RemoveObserver(observer);
// Since all tabs share the tab sinks query, we don't want to delete it
// here.
if (!it->second->HasObservers() && !source.IsTabMirroringSource()) {
for (const auto& provider : media_route_providers_) {
// TODO(crbug.com/1090890): Don't allow MediaSource::ForAnyTab().id() to
// be passed here.
provider.second->StopObservingMediaSinks(source.id());
}
sinks_queries_.erase(source.id());
}
}
void MediaRouterMojoImpl::RegisterMediaRoutesObserver(
MediaRoutesObserver* observer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const bool is_first_observer = !routes_query_.HasObservers();
if (!is_first_observer) {
DCHECK(!routes_query_.HasObserver(observer));
}
routes_query_.AddObserver(observer);
if (is_first_observer) {
for (const auto& provider : media_route_providers_) {
provider.second->StartObservingMediaRoutes();
// The MRPs will call MediaRouterMojoImpl::OnRoutesUpdated() soon, if
// there are any existing routes the new observer should be aware of.
}
} else {
// Return to the event loop before notifying of a cached route list because
// MediaRoutesObserver is calling this method from its constructor, and that
// must complete before invoking its virtual OnRoutesUpdated() method.
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&MediaRouterMojoImpl::NotifyOfExistingRoutes,
AsWeakPtr(), observer->AsWeakPtr()));
}
}
void MediaRouterMojoImpl::NotifyOfExistingRoutes(
base::WeakPtr<MediaRoutesObserver> observer) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (observer) {
observer->OnRoutesUpdated(
routes_query_.cached_route_list().value_or(std::vector<MediaRoute>{}));
}
}
void MediaRouterMojoImpl::UnregisterMediaRoutesObserver(
MediaRoutesObserver* observer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
routes_query_.RemoveObserver(observer);
}
void MediaRouterMojoImpl::RegisterPresentationConnectionMessageObserver(
PresentationConnectionMessageObserver* observer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(observer);
const MediaRoute::Id& route_id = observer->route_id();
auto& observer_list = message_observers_[route_id];
if (!observer_list) {
observer_list =
std::make_unique<PresentationConnectionMessageObserverList>();
} else {
DCHECK(!observer_list->HasObserver(observer));
}
bool should_listen = observer_list->empty();
observer_list->AddObserver(observer);
if (should_listen) {
absl::optional<mojom::MediaRouteProviderId> provider_id =
GetProviderIdForRoute(route_id);
if (provider_id) {
media_route_providers_[*provider_id]->StartListeningForRouteMessages(
route_id);
}
}
}
void MediaRouterMojoImpl::UnregisterPresentationConnectionMessageObserver(
PresentationConnectionMessageObserver* observer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(observer);
const MediaRoute::Id& route_id = observer->route_id();
auto it = message_observers_.find(route_id);
if (it == message_observers_.end() || !it->second->HasObserver(observer)) {
return;
}
it->second->RemoveObserver(observer);
if (it->second->empty()) {
message_observers_.erase(route_id);
absl::optional<mojom::MediaRouteProviderId> provider_id =
GetProviderIdForRoute(route_id);
if (provider_id) {
media_route_providers_[*provider_id]->StopListeningForRouteMessages(
route_id);
}
}
}
void MediaRouterMojoImpl::OnRouteMessagesReceived(
const std::string& route_id,
std::vector<mojom::RouteMessagePtr> messages) {
if (messages.empty()) {
return;
}
auto it = message_observers_.find(route_id);
if (it == message_observers_.end()) {
return;
}
for (auto& observer : *it->second) {
// TODO(mfoltz): We have to clone the messages here in case there are
// multiple observers. This can be removed once we stop passing messages
// through the MR and use the PresentationConnectionPtr directly.
std::vector<mojom::RouteMessagePtr> messages_copy;
for (auto& message : messages) {
messages_copy.emplace_back(message->Clone());
}
observer.OnMessagesReceived(std::move(messages_copy));
}
}
void MediaRouterMojoImpl::OnPresentationConnectionStateChanged(
const std::string& route_id,
blink::mojom::PresentationConnectionState state) {
NotifyPresentationConnectionStateChange(route_id, state);
}
void MediaRouterMojoImpl::OnPresentationConnectionClosed(
const std::string& route_id,
blink::mojom::PresentationConnectionCloseReason reason,
const std::string& message) {
NotifyPresentationConnectionClose(route_id, reason, message);
}
void MediaRouterMojoImpl::OnTerminateRouteResult(
const MediaRoute::Id& route_id,
mojom::MediaRouteProviderId provider_id,
const absl::optional<std::string>& error_text,
mojom::RouteRequestResultCode result_code) {
MediaRouterMetrics::RecordMediaRouteProviderTerminateRoute(result_code,
provider_id);
}
void MediaRouterMojoImpl::OnRouteAdded(mojom::MediaRouteProviderId provider_id,
const MediaRoute& route) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
routes_query_.AddRouteForProvider(provider_id, route);
routes_query_.NotifyObservers();
}
void MediaRouterMojoImpl::SyncStateToMediaRouteProvider(
mojom::MediaRouteProviderId provider_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const auto& provider = media_route_providers_[provider_id];
// Sink queries.
for (const auto& it : sinks_queries_) {
// TODO(crbug.com/1090890): Don't allow MediaSource::ForAnyTab().id() to
// be passed here.
provider->StartObservingMediaSinks(it.first);
}
// Route updates.
if (routes_query_.HasObservers()) {
provider->StartObservingMediaRoutes();
}
// Route messages.
for (const auto& it : message_observers_) {
provider->StartListeningForRouteMessages(it.first);
}
}
void MediaRouterMojoImpl::DiscoverSinksNow() {
for (const auto& provider : media_route_providers_) {
provider.second->DiscoverSinksNow();
}
}
void MediaRouterMojoImpl::OnMediaControllerCreated(
const MediaRoute::Id& route_id,
bool success) {
MediaRouterMojoMetrics::RecordMediaRouteControllerCreationResult(success);
}
void MediaRouterMojoImpl::AddMirroringMediaControllerHost(
const MediaRoute& route) {
mojo::Remote<media_router::mojom::MediaController> controller_remote;
mojo::PendingReceiver<media_router::mojom::MediaController>
controller_receiver = controller_remote.BindNewPipeAndPassReceiver();
auto host = std::make_unique<MirroringMediaControllerHost>(
std::move(controller_remote));
auto observer_remote = host->GetMediaStatusObserverPendingRemote();
GetMediaController(route.media_route_id(), std::move(controller_receiver),
std::move(observer_remote));
mirroring_media_controller_hosts_[route.media_route_id()] = std::move(host);
}
void MediaRouterMojoImpl::OnProviderConnectionError(
mojom::MediaRouteProviderId provider_id) {
media_route_providers_.erase(provider_id);
}
void MediaRouterMojoImpl::GetLogger(
mojo::PendingReceiver<mojom::Logger> receiver) {
logger_.BindReceiver(std::move(receiver));
}
LoggerImpl* MediaRouterMojoImpl::GetLogger() {
return &logger_;
}
void MediaRouterMojoImpl::GetDebugger(
mojo::PendingReceiver<mojom::Debugger> receiver) {
media_router_debugger_.BindReceiver(std::move(receiver));
}
MediaRouterDebugger& MediaRouterMojoImpl::GetDebugger() {
return media_router_debugger_;
}
void MediaRouterMojoImpl::GetLogsAsString(GetLogsAsStringCallback callback) {
std::move(callback).Run(logger_.GetLogsAsJson());
}
void MediaRouterMojoImpl::GetMediaSinkServiceStatus(
mojom::MediaRouter::GetMediaSinkServiceStatusCallback callback) {
MediaSinkServiceStatus status;
std::move(callback).Run(status.GetStatusAsJSONString());
}
void MediaRouterMojoImpl::BindToMojoReceiver(
mojo::PendingReceiver<mojom::MediaRouter> receiver) {
receivers_.Add(this, std::move(receiver));
}
absl::optional<mojom::MediaRouteProviderId>
MediaRouterMojoImpl::GetProviderIdForRoute(const MediaRoute::Id& route_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
for (const auto& provider_to_routes : routes_query_.providers_to_routes()) {
const mojom::MediaRouteProviderId provider_id = provider_to_routes.first;
const std::vector<MediaRoute>& routes = provider_to_routes.second;
if (base::Contains(routes, route_id, &MediaRoute::media_route_id)) {
return provider_id;
}
}
return absl::nullopt;
}
absl::optional<mojom::MediaRouteProviderId>
MediaRouterMojoImpl::GetProviderIdForSink(const MediaSink::Id& sink_id) {
const MediaSink* sink = GetSinkById(sink_id);
return sink ? absl::make_optional<mojom::MediaRouteProviderId>(
sink->provider_id())
: absl::nullopt;
}
absl::optional<mojom::MediaRouteProviderId>
MediaRouterMojoImpl::GetProviderIdForPresentation(
const std::string& presentation_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
for (const auto& provider_to_routes : routes_query_.providers_to_routes()) {
const mojom::MediaRouteProviderId provider_id = provider_to_routes.first;
const std::vector<MediaRoute>& routes = provider_to_routes.second;
DCHECK_LE(base::ranges::count(routes, presentation_id,
&MediaRoute::presentation_id),
1);
if (base::Contains(routes, presentation_id, &MediaRoute::presentation_id)) {
return provider_id;
}
}
return absl::nullopt;
}
const MediaSink* MediaRouterMojoImpl::GetSinkById(
const MediaSink::Id& sink_id) const {
// TODO(takumif): It is inefficient to iterate through all the sinks queries,
// so there should be one list containing all the sinks.
for (const auto& sinks_query : sinks_queries_) {
const std::vector<MediaSink>& sinks =
sinks_query.second->cached_sink_list();
DCHECK_LE(base::ranges::count(sinks, sink_id, &MediaSink::id), 1);
auto sink_it = base::ranges::find(sinks, sink_id, &MediaSink::id);
if (sink_it != sinks.end()) {
return &(*sink_it);
}
}
return nullptr;
}
// NOTE: To record this on Android, will need to move to
// //components/media_router and refactor to avoid the extensions dependency.
void MediaRouterMojoImpl::RecordPresentationRequestUrlBySink(
const MediaSource& source,
mojom::MediaRouteProviderId provider_id) {
PresentationUrlBySink value = PresentationUrlBySink::kUnknown;
// URLs that can be rendered in offscreen tabs (for cloud or Chromecast
// sinks), or on a wired display.
bool is_normal_url = source.url().SchemeIs(url::kHttpsScheme) ||
#if BUILDFLAG(ENABLE_EXTENSIONS)
source.url().SchemeIs(extensions::kExtensionScheme) ||
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
source.url().SchemeIs(url::kFileScheme);
switch (provider_id) {
case mojom::MediaRouteProviderId::WIRED_DISPLAY:
if (is_normal_url) {
value = PresentationUrlBySink::kNormalUrlToWiredDisplay;
}
break;
case mojom::MediaRouteProviderId::CAST:
if (source.IsCastPresentationUrl()) {
value = PresentationUrlBySink::kCastUrlToChromecast;
} else if (is_normal_url) {
value = PresentationUrlBySink::kNormalUrlToChromecast;
}
break;
case mojom::MediaRouteProviderId::DIAL:
if (source.IsDialSource()) {
value = PresentationUrlBySink::kDialUrlToDial;
}
break;
case mojom::MediaRouteProviderId::ANDROID_CAF:
case mojom::MediaRouteProviderId::TEST:
break;
}
base::UmaHistogramEnumeration("MediaRouter.PresentationRequest.UrlBySink",
value);
}
bool MediaRouterMojoImpl::HasJoinableRoute() const {
return !(internal_routes_observer_->current_routes().empty());
}
const MediaRoute* MediaRouterMojoImpl::GetRoute(
const MediaRoute::Id& route_id) const {
const auto& routes = internal_routes_observer_->current_routes();
auto it = base::ranges::find(routes, route_id, &MediaRoute::media_route_id);
return it == routes.end() ? nullptr : &*it;
}
void MediaRouterMojoImpl::Shutdown() {
// The observer calls virtual methods on MediaRouter; it must be destroyed
// outside of the dtor
internal_routes_observer_.reset();
}
void MediaRouterMojoImpl::CreateRouteWithSelectedDesktop(
mojom::MediaRouteProviderId provider_id,
const std::string& sink_id,
const std::string& presentation_id,
const url::Origin& origin,
content::WebContents* web_contents,
base::TimeDelta timeout,
bool off_the_record,
mojom::MediaRouteProvider::CreateRouteCallback mr_callback,
const std::string& err,
content::DesktopMediaID media_id) {
if (!err.empty()) {
std::move(mr_callback)
.Run(absl::nullopt, nullptr, err,
mojom::RouteRequestResultCode::DESKTOP_PICKER_FAILED);
return;
}
if (media_id.is_null()) {
std::move(mr_callback)
.Run(absl::nullopt, nullptr, "User canceled capture dialog",
mojom::RouteRequestResultCode::CANCELLED);
return;
}
media_route_providers_[provider_id]->CreateRoute(
MediaSource::ForDesktop(media_id.ToString(), media_id.audio_share).id(),
sink_id, presentation_id, origin, kDefaultFrameTreeNodeId, timeout,
off_the_record,
base::BindOnce(
[](mojom::MediaRouteProvider::CreateRouteCallback inner_callback,
const absl::optional<media_router::MediaRoute>& route,
mojom::RoutePresentationConnectionPtr connection,
const absl::optional<std::string>& error_text,
mojom::RouteRequestResultCode result_code) {
std::move(inner_callback)
.Run(route, std::move(connection), error_text, result_code);
},
std::move(mr_callback)));
}
} // namespace media_router