Commit afb33054 authored by Takumi Fujimoto's avatar Takumi Fujimoto Committed by Commit Bot

Media Controller support in Cast Media Route Provider

Implement CastMediaController for the native Cast MRP, which will allow
controlling the media playback in flinging sessions.

Bug: 987474
Change-Id: I8a8909bb0f67efb71629a30ea9b206368b66482a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1625377Reviewed-by: default avatarBrandon Tolsch <btolsch@chromium.org>
Commit-Queue: Takumi Fujimoto <takumif@chromium.org>
Cr-Commit-Position: refs/heads/master@{#683023}
parent 152a4b66
...@@ -110,6 +110,8 @@ static_library("router") { ...@@ -110,6 +110,8 @@ static_library("router") {
"providers/cast/cast_app_discovery_service.h", "providers/cast/cast_app_discovery_service.h",
"providers/cast/cast_internal_message_util.cc", "providers/cast/cast_internal_message_util.cc",
"providers/cast/cast_internal_message_util.h", "providers/cast/cast_internal_message_util.h",
"providers/cast/cast_media_controller.cc",
"providers/cast/cast_media_controller.h",
"providers/cast/cast_media_route_provider.cc", "providers/cast/cast_media_route_provider.cc",
"providers/cast/cast_media_route_provider.h", "providers/cast/cast_media_route_provider.h",
"providers/cast/cast_media_route_provider_metrics.cc", "providers/cast/cast_media_route_provider_metrics.cc",
......
...@@ -166,11 +166,10 @@ void MediaRouterDesktop::RegisterExtensionMediaRouteProvider( ...@@ -166,11 +166,10 @@ void MediaRouterDesktop::RegisterExtensionMediaRouteProvider(
// executes commands to the MRP and its route controllers. Commands to the // executes commands to the MRP and its route controllers. Commands to the
// route controllers, once executed, will be queued in Mojo pipes until the // route controllers, once executed, will be queued in Mojo pipes until the
// Mojo requests are bound to implementations. // Mojo requests are bound to implementations.
// TODO(takumif): Once we have route controllers for MRPs other than the for (const auto& pair : route_controllers_) {
// extension MRP, we'll need to group them by MRP so that below is performed if (GetProviderIdForRoute(pair.first).value_or(UNKNOWN) == EXTENSION)
// only for extension route controllers. InitMediaRouteController(pair.second);
for (const auto& pair : route_controllers_) }
InitMediaRouteController(pair.second);
extension_provider_proxy_->RegisterMediaRouteProvider( extension_provider_proxy_->RegisterMediaRouteProvider(
std::move(extension_provider_ptr)); std::move(extension_provider_ptr));
} }
......
...@@ -117,10 +117,12 @@ class MediaRouterMojoImpl : public MediaRouterBase, public mojom::MediaRouter { ...@@ -117,10 +117,12 @@ class MediaRouterMojoImpl : public MediaRouterBase, public mojom::MediaRouter {
// Creates a binding between |this| and |request|. // Creates a binding between |this| and |request|.
void BindToMojoRequest(mojo::InterfaceRequest<mojom::MediaRouter> request); void BindToMojoRequest(mojo::InterfaceRequest<mojom::MediaRouter> request);
// Returns the ID of the provider associated with the presentation ID, or // Methods for obtaining a pointer to the provider associated with the given
// nullopt if not found. // object. They return a nullopt when such a provider is not found.
virtual base::Optional<MediaRouteProviderId> GetProviderIdForPresentation( virtual base::Optional<MediaRouteProviderId> GetProviderIdForPresentation(
const std::string& presentation_id); const std::string& presentation_id);
base::Optional<MediaRouteProviderId> GetProviderIdForRoute(
const MediaRoute::Id& route_id);
content::BrowserContext* context() const { return context_; } content::BrowserContext* context() const { return context_; }
...@@ -401,10 +403,8 @@ class MediaRouterMojoImpl : public MediaRouterBase, public mojom::MediaRouter { ...@@ -401,10 +403,8 @@ class MediaRouterMojoImpl : public MediaRouterBase, public mojom::MediaRouter {
// routes do not appear in |routes|. // routes do not appear in |routes|.
void RemoveInvalidRouteControllers(const std::vector<MediaRoute>& routes); void RemoveInvalidRouteControllers(const std::vector<MediaRoute>& routes);
// Methods for obtaining a pointer to the provider associated with the given // Method for obtaining a pointer to the provider associated with the given
// object. They return a nullopt when such a provider is not found. // object. Returns a nullopt when such a provider is not found.
base::Optional<MediaRouteProviderId> GetProviderIdForRoute(
const MediaRoute::Id& route_id);
base::Optional<MediaRouteProviderId> GetProviderIdForSink( base::Optional<MediaRouteProviderId> GetProviderIdForSink(
const MediaSink::Id& sink_id); const MediaSink::Id& sink_id);
......
...@@ -140,6 +140,10 @@ class ActivityRecord { ...@@ -140,6 +140,10 @@ class ActivityRecord {
blink::mojom::PresentationConnectionCloseReason close_reason) = 0; blink::mojom::PresentationConnectionCloseReason close_reason) = 0;
virtual void TerminatePresentationConnections() = 0; virtual void TerminatePresentationConnections() = 0;
virtual void CreateMediaController(
mojom::MediaControllerRequest media_controller,
mojom::MediaStatusObserverPtr observer) = 0;
protected: protected:
CastSession* GetSession() const; CastSession* GetSession() const;
......
...@@ -89,6 +89,7 @@ void CastActivityManager::LaunchSession( ...@@ -89,6 +89,7 @@ void CastActivityManager::LaunchSession(
MediaRoute route(route_id, source, sink_id, /* description */ std::string(), MediaRoute route(route_id, source, sink_id, /* description */ std::string(),
/* is_local */ true, /* for_display */ true); /* is_local */ true, /* for_display */ true);
route.set_incognito(incognito); route.set_incognito(incognito);
route.set_controller_type(RouteControllerType::kGeneric);
DVLOG(1) << "LaunchSession: source_id=" << cast_source.source_id() DVLOG(1) << "LaunchSession: source_id=" << cast_source.source_id()
<< ", route_id: " << route_id << ", sink_id=" << sink_id; << ", route_id: " << route_id << ", sink_id=" << sink_id;
DoLaunchSessionParams params(route, cast_source, sink, origin, tab_id, DoLaunchSessionParams params(route, cast_source, sink, origin, tab_id,
...@@ -387,6 +388,18 @@ void CastActivityManager::TerminateSession( ...@@ -387,6 +388,18 @@ void CastActivityManager::TerminateSession(
hash_token_, std::move(callback)); hash_token_, std::move(callback));
} }
bool CastActivityManager::CreateMediaController(
const std::string& route_id,
mojom::MediaControllerRequest media_controller,
mojom::MediaStatusObserverPtr observer) {
auto activity_it = activities_.find(route_id);
if (activity_it == activities_.end())
return false;
activity_it->second->CreateMediaController(std::move(media_controller),
std::move(observer));
return true;
}
CastActivityManager::ActivityMap::iterator CastActivityManager::ActivityMap::iterator
CastActivityManager::FindActivityByChannelId(int channel_id) { CastActivityManager::FindActivityByChannelId(int channel_id) {
return std::find_if( return std::find_if(
......
...@@ -105,6 +105,10 @@ class CastActivityManager : public CastActivityManagerBase, ...@@ -105,6 +105,10 @@ class CastActivityManager : public CastActivityManagerBase,
const MediaRoute::Id& route_id, const MediaRoute::Id& route_id,
mojom::MediaRouteProvider::TerminateRouteCallback callback); mojom::MediaRouteProvider::TerminateRouteCallback callback);
bool CreateMediaController(const std::string& route_id,
mojom::MediaControllerRequest media_controller,
mojom::MediaStatusObserverPtr observer);
const MediaRoute* GetRoute(const MediaRoute::Id& route_id) const; const MediaRoute* GetRoute(const MediaRoute::Id& route_id) const;
std::vector<MediaRoute> GetRoutes() const; std::vector<MediaRoute> GetRoutes() const;
......
...@@ -75,6 +75,8 @@ void CastActivityRecord::SetOrUpdateSession(const CastSession& session, ...@@ -75,6 +75,8 @@ void CastActivityRecord::SetOrUpdateSession(const CastSession& session,
client.second->SendMessageToClient( client.second->SendMessageToClient(
CreateUpdateSessionMessage(session, client.first, sink, hash_token)); CreateUpdateSessionMessage(session, client.first, sink, hash_token));
} }
if (media_controller_)
media_controller_->SetSession(session);
} }
cast_channel::Result CastActivityRecord::SendAppMessageToReceiver( cast_channel::Result CastActivityRecord::SendAppMessageToReceiver(
...@@ -185,6 +187,8 @@ void CastActivityRecord::SendMediaStatusToClients( ...@@ -185,6 +187,8 @@ void CastActivityRecord::SendMediaStatusToClients(
base::Optional<int> request_id) { base::Optional<int> request_id) {
for (auto& client : connected_clients()) for (auto& client : connected_clients())
client.second->SendMediaStatusToClient(media_status, request_id); client.second->SendMediaStatusToClient(media_status, request_id);
if (media_controller_)
media_controller_->SetMediaStatus(media_status);
} }
void CastActivityRecord::ClosePresentationConnections( void CastActivityRecord::ClosePresentationConnections(
...@@ -198,6 +202,25 @@ void CastActivityRecord::TerminatePresentationConnections() { ...@@ -198,6 +202,25 @@ void CastActivityRecord::TerminatePresentationConnections() {
client.second->TerminateConnection(); client.second->TerminateConnection();
} }
void CastActivityRecord::CreateMediaController(
mojom::MediaControllerRequest media_controller,
mojom::MediaStatusObserverPtr observer) {
media_controller_ = std::make_unique<CastMediaController>(
this, std::move(media_controller), std::move(observer));
if (session_id_) {
CastSession* session = GetSession();
if (session) {
media_controller_->SetSession(*session);
base::Value status_request(base::Value::Type::DICTIONARY);
status_request.SetKey("type", base::Value("MEDIA_GET_STATUS"));
message_handler_->SendMediaRequest(GetCastChannelId(), status_request,
media_controller_->sender_id(),
session->transport_id());
}
}
}
void CastActivityRecord::OnAppMessage( void CastActivityRecord::OnAppMessage(
const cast_channel::CastMessage& message) { const cast_channel::CastMessage& message) {
if (!session_id_) { if (!session_id_) {
...@@ -225,7 +248,7 @@ int CastActivityRecord::GetCastChannelId() { ...@@ -225,7 +248,7 @@ int CastActivityRecord::GetCastChannelId() {
if (!sink) { if (!sink) {
// TODO(crbug.com/905002): Add UMA metrics for this and other error // TODO(crbug.com/905002): Add UMA metrics for this and other error
// conditions. // conditions.
LOG(ERROR) << "Sink not found for route: " << route_; DLOG(ERROR) << "Sink not found for route: " << route_;
return -1; return -1;
} }
return sink->cast_data().cast_channel_id; return sink->cast_data().cast_channel_id;
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "base/containers/flat_map.h" #include "base/containers/flat_map.h"
#include "base/optional.h" #include "base/optional.h"
#include "chrome/browser/media/router/providers/cast/activity_record.h" #include "chrome/browser/media/router/providers/cast/activity_record.h"
#include "chrome/browser/media/router/providers/cast/cast_media_controller.h"
#include "chrome/common/media_router/mojom/media_router.mojom.h" #include "chrome/common/media_router/mojom/media_router.mojom.h"
#include "chrome/common/media_router/providers/cast/cast_media_source.h" #include "chrome/common/media_router/providers/cast/cast_media_source.h"
#include "components/cast_channel/cast_message_handler.h" #include "components/cast_channel/cast_message_handler.h"
...@@ -81,6 +82,8 @@ class CastActivityRecord : public ActivityRecord { ...@@ -81,6 +82,8 @@ class CastActivityRecord : public ActivityRecord {
void TerminatePresentationConnections() override; void TerminatePresentationConnections() override;
void OnAppMessage(const cast_channel::CastMessage& message) override; void OnAppMessage(const cast_channel::CastMessage& message) override;
void OnInternalMessage(const cast_channel::InternalMessage& message) override; void OnInternalMessage(const cast_channel::InternalMessage& message) override;
void CreateMediaController(mojom::MediaControllerRequest media_controller,
mojom::MediaStatusObserverPtr observer) override;
static void SetClientFactoryForTest( static void SetClientFactoryForTest(
CastSessionClientFactoryForTest* factory) { CastSessionClientFactoryForTest* factory) {
...@@ -103,6 +106,8 @@ class CastActivityRecord : public ActivityRecord { ...@@ -103,6 +106,8 @@ class CastActivityRecord : public ActivityRecord {
MediaSinkServiceBase* const media_sink_service_; MediaSinkServiceBase* const media_sink_service_;
CastActivityManagerBase* const activity_manager_; CastActivityManagerBase* const activity_manager_;
std::unique_ptr<CastMediaController> media_controller_;
}; };
} // namespace media_router } // namespace media_router
......
// Copyright 2019 The Chromium 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 "chrome/browser/media/router/providers/cast/cast_media_controller.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/values.h"
#include "chrome/browser/media/router/providers/cast/activity_record.h"
#include "chrome/browser/media/router/providers/cast/cast_internal_message_util.h"
#include "components/cast_channel/enum_table.h"
using cast_channel::V2MessageType;
namespace media_router {
namespace {
void SetIfValid(std::string* out, const base::Value* value) {
if (value && value->is_string())
*out = value->GetString();
}
void SetIfValid(float* out, const base::Value* value) {
if (!value)
return;
if (value->is_double()) {
*out = value->GetDouble();
} else if (value->is_int()) {
*out = value->GetInt();
}
}
void SetIfValid(int* out, const base::Value* value) {
if (value && value->is_int())
*out = value->GetInt();
}
void SetIfValid(bool* out, const base::Value* value) {
if (value && value->is_bool())
*out = value->GetBool();
}
void SetIfValid(base::TimeDelta* out, const base::Value* value) {
if (value && value->is_double())
*out = base::TimeDelta::FromSeconds(value->GetDouble());
}
} // namespace
CastMediaController::CastMediaController(ActivityRecord* activity,
mojom::MediaControllerRequest request,
mojom::MediaStatusObserverPtr observer)
: sender_id_("sender-" + base::NumberToString(base::RandUint64())),
activity_(activity),
binding_(this, std::move(request)),
observer_(std::move(observer)) {}
CastMediaController::~CastMediaController() {}
void CastMediaController::Play() {
if (session_id_.empty())
return;
activity_->SendMediaRequestToReceiver(
*CastInternalMessage::From(CreateMediaRequest(V2MessageType::kPlay)));
}
void CastMediaController::Pause() {
if (session_id_.empty())
return;
activity_->SendMediaRequestToReceiver(
*CastInternalMessage::From(CreateMediaRequest(V2MessageType::kPause)));
}
void CastMediaController::SetMute(bool mute) {
if (session_id_.empty())
return;
base::Value request = CreateVolumeRequest();
request.SetBoolPath("message.volume.muted", mute);
request.SetStringKey("type", "v2_message");
request.SetStringKey("clientId", sender_id_);
auto message = CastInternalMessage::From(std::move(request));
activity_->SendSetVolumeRequestToReceiver(*message, base::DoNothing());
}
void CastMediaController::SetVolume(float volume) {
if (session_id_.empty())
return;
base::Value request = CreateVolumeRequest();
request.SetDoublePath("message.volume.level", volume);
request.SetStringKey("type", "v2_message");
request.SetStringKey("clientId", sender_id_);
activity_->SendSetVolumeRequestToReceiver(
*CastInternalMessage::From(std::move(request)), base::DoNothing());
}
void CastMediaController::Seek(base::TimeDelta time) {
if (session_id_.empty())
return;
base::Value request = CreateMediaRequest(V2MessageType::kSeek);
request.SetDoublePath("message.currentTime", time.InSecondsF());
activity_->SendMediaRequestToReceiver(
*CastInternalMessage::From(std::move(request)));
}
void CastMediaController::SetSession(const CastSession& session) {
session_id_ = session.session_id();
if (!session.value().is_dict())
return;
const base::Value* volume = session.value().FindPath("receiver.volume");
if (!volume || !volume->is_dict())
return;
SetIfValid(&media_status_.volume, volume->FindKey("level"));
SetIfValid(&media_status_.is_muted, volume->FindKey("muted"));
const base::Value* volume_type = volume->FindKey("controlType");
if (volume_type && volume_type->is_string()) {
media_status_.can_set_volume = volume_type->GetString() != "fixed";
media_status_.can_mute = media_status_.can_set_volume;
}
observer_->OnMediaStatusUpdated(media_status_);
}
void CastMediaController::SetMediaStatus(const base::Value& status_value) {
UpdateMediaStatus(status_value);
observer_->OnMediaStatusUpdated(media_status_);
}
base::Value CastMediaController::CreateMediaRequest(V2MessageType type) {
base::Value message(base::Value::Type::DICTIONARY);
message.SetIntKey("mediaSessionId", media_session_id_);
message.SetStringKey("sessionId", session_id_);
message.SetStringKey("type", cast_util::EnumToString(type).value().data());
base::Value request(base::Value::Type::DICTIONARY);
request.SetKey("message", std::move(message));
request.SetStringKey("type", "v2_message");
request.SetStringKey("clientId", sender_id_);
return request;
}
base::Value CastMediaController::CreateVolumeRequest() {
base::Value message(base::Value::Type::DICTIONARY);
message.SetStringKey("sessionId", session_id_);
// Muting also uses the |kSetVolume| message type.
message.SetStringKey(
"type",
cast_util::EnumToString(V2MessageType::kSetVolume).value().data());
message.SetKey("volume", base::Value(base::Value::Type::DICTIONARY));
base::Value request(base::Value::Type::DICTIONARY);
request.SetKey("message", std::move(message));
return request;
}
void CastMediaController::UpdateMediaStatus(const base::Value& message_value) {
const base::Value* status_list_value = message_value.FindKey("status");
if (!status_list_value || !status_list_value->is_list())
return;
const base::Value::ListStorage& status_list = status_list_value->GetList();
if (status_list.empty())
return;
const base::Value& status_value = status_list[0];
if (!status_value.is_dict())
return;
SetIfValid(&media_session_id_, status_value.FindKey("mediaSessionId"));
SetIfValid(&media_status_.title,
status_value.FindPath("media.metadata.title"));
SetIfValid(&media_status_.current_time, status_value.FindKey("currentTime"));
SetIfValid(&media_status_.duration, status_value.FindPath("media.duration"));
const base::Value* commands = status_value.FindKey("supportedMediaCommands");
if (commands && commands->is_int()) {
// |can_set_volume| and |can_mute| are not used, because the receiver volume
// obtained in SetSession() is used instead.
media_status_.can_play_pause = commands->GetInt() & 1;
media_status_.can_seek = commands->GetInt() & 2;
}
const base::Value* player_state = status_value.FindKey("playerState");
if (player_state && player_state->is_string()) {
const std::string& state = player_state->GetString();
if (state == "PLAYING") {
media_status_.play_state = MediaStatus::PlayState::PLAYING;
} else if (state == "PAUSED") {
media_status_.play_state = MediaStatus::PlayState::PAUSED;
} else if (state == "BUFFERING") {
media_status_.play_state = MediaStatus::PlayState::BUFFERING;
}
}
}
} // namespace media_router
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_CAST_CAST_MEDIA_CONTROLLER_H_
#define CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_CAST_CAST_MEDIA_CONTROLLER_H_
#include "base/macros.h"
#include "chrome/common/media_router/media_status.h"
#include "chrome/common/media_router/mojom/media_controller.mojom.h"
#include "chrome/common/media_router/mojom/media_status.mojom.h"
#include "components/cast_channel/cast_message_util.h"
#include "mojo/public/cpp/bindings/binding.h"
namespace base {
class Value;
}
namespace media_router {
class ActivityRecord;
class CastSession;
// Per-session object for sending media control commands to a Cast receiver, and
// notifying an observer of updates on the session's media status.
class CastMediaController : public mojom::MediaController {
public:
CastMediaController(ActivityRecord* activity,
mojom::MediaControllerRequest request,
mojom::MediaStatusObserverPtr observer);
~CastMediaController() override;
// mojom::MediaController overrides:
void Play() override;
void Pause() override;
void SetMute(bool mute) override;
void SetVolume(float volume) override;
void Seek(base::TimeDelta time) override;
// These methods may notify the MediaStatusObserver that the status has been
// updated.
void SetSession(const CastSession& session);
void SetMediaStatus(const base::Value& media_status);
const std::string& sender_id() const { return sender_id_; }
private:
base::Value CreateMediaRequest(cast_channel::V2MessageType type);
base::Value CreateVolumeRequest();
void UpdateMediaStatus(const base::Value& message_value);
const std::string sender_id_;
ActivityRecord* const activity_;
MediaStatus media_status_;
std::string session_id_;
int media_session_id_;
mojo::Binding<mojom::MediaController> binding_;
mojom::MediaStatusObserverPtr observer_;
DISALLOW_COPY_AND_ASSIGN(CastMediaController);
};
} // namespace media_router
#endif // CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_CAST_CAST_MEDIA_CONTROLLER_H_
...@@ -264,8 +264,8 @@ void CastMediaRouteProvider::CreateMediaRouteController( ...@@ -264,8 +264,8 @@ void CastMediaRouteProvider::CreateMediaRouteController(
mojom::MediaControllerRequest media_controller, mojom::MediaControllerRequest media_controller,
mojom::MediaStatusObserverPtr observer, mojom::MediaStatusObserverPtr observer,
CreateMediaRouteControllerCallback callback) { CreateMediaRouteControllerCallback callback) {
NOTIMPLEMENTED(); std::move(callback).Run(activity_manager_->CreateMediaController(
std::move(callback).Run(false); route_id, std::move(media_controller), std::move(observer)));
} }
void CastMediaRouteProvider::OnSinkQueryUpdated( void CastMediaRouteProvider::OnSinkQueryUpdated(
......
...@@ -252,6 +252,10 @@ void MirroringActivityRecord::OnInternalMessage( ...@@ -252,6 +252,10 @@ void MirroringActivityRecord::OnInternalMessage(
channel_to_service_->Send(std::move(ptr)); channel_to_service_->Send(std::move(ptr));
} }
void MirroringActivityRecord::CreateMediaController(
mojom::MediaControllerRequest media_controller,
mojom::MediaStatusObserverPtr observer) {}
void MirroringActivityRecord::StopMirroring() { void MirroringActivityRecord::StopMirroring() {
// Running the callback will cause this object to be deleted. // Running the callback will cause this object to be deleted.
if (on_stop_) if (on_stop_)
......
...@@ -76,6 +76,10 @@ class MirroringActivityRecord : public ActivityRecord, ...@@ -76,6 +76,10 @@ class MirroringActivityRecord : public ActivityRecord,
void OnAppMessage(const cast_channel::CastMessage& message) override; void OnAppMessage(const cast_channel::CastMessage& message) override;
void OnInternalMessage(const cast_channel::InternalMessage& message) override; void OnInternalMessage(const cast_channel::InternalMessage& message) override;
protected:
void CreateMediaController(mojom::MediaControllerRequest media_controller,
mojom::MediaStatusObserverPtr observer) override;
private: private:
enum class MirroringType { enum class MirroringType {
kTab, // Mirror a single tab. kTab, // Mirror a single tab.
......
...@@ -65,6 +65,9 @@ class MockActivityRecord : public ActivityRecord { ...@@ -65,6 +65,9 @@ class MockActivityRecord : public ActivityRecord {
MOCK_METHOD1(OnAppMessage, void(const cast_channel::CastMessage& message)); MOCK_METHOD1(OnAppMessage, void(const cast_channel::CastMessage& message));
MOCK_METHOD1(OnInternalMessage, MOCK_METHOD1(OnInternalMessage,
void(const cast_channel::InternalMessage& message)); void(const cast_channel::InternalMessage& message));
MOCK_METHOD2(CreateMediaController,
void(mojom::MediaControllerRequest media_controller,
mojom::MediaStatusObserverPtr observer));
}; };
} // namespace media_router } // namespace media_router
......
...@@ -3879,6 +3879,7 @@ test("unit_tests") { ...@@ -3879,6 +3879,7 @@ test("unit_tests") {
"../browser/media/router/providers/cast/cast_app_availability_tracker_unittest.cc", "../browser/media/router/providers/cast/cast_app_availability_tracker_unittest.cc",
"../browser/media/router/providers/cast/cast_app_discovery_service_unittest.cc", "../browser/media/router/providers/cast/cast_app_discovery_service_unittest.cc",
"../browser/media/router/providers/cast/cast_internal_message_util_unittest.cc", "../browser/media/router/providers/cast/cast_internal_message_util_unittest.cc",
"../browser/media/router/providers/cast/cast_media_controller_unittest.cc",
"../browser/media/router/providers/cast/cast_media_route_provider_metrics_unittest.cc", "../browser/media/router/providers/cast/cast_media_route_provider_metrics_unittest.cc",
"../browser/media/router/providers/cast/cast_media_route_provider_unittest.cc", "../browser/media/router/providers/cast/cast_media_route_provider_unittest.cc",
"../browser/media/router/providers/cast/cast_session_client_unittest.cc", "../browser/media/router/providers/cast/cast_session_client_unittest.cc",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment