Commit 3e0c55db authored by Bin Zhao's avatar Bin Zhao Committed by Commit Bot

[DIAL] added an in-browser DIAL mrp skeleton for DIAL sink query

dial_media_sink_service:
  - Added Register/UnregisterOnSinkQueryUpdatedCallback()
  - Start/StopObservingMediaSinks() takes std::string media_source as parameter

dial_media_sink_service_impl:
  - Do not check app availability for discovery only devices

media_router_desktop:
  - Initialize DialMediaRouteProvider if DialSinkQueryEnabled()

media_router_mojo_impl:
  - Use extension MRP to create/terminate route for DIAL sink

dial_media_route_provider:
  - Register OnSinkQueryUpdatedCallback with dial_media_sink_service

Bug: 779892
Change-Id: Iddc760c4fa035c00210480f28de7db96e42fe61c
Reviewed-on: https://chromium-review.googlesource.com/903419
Commit-Queue: Bin Zhao <zhaobin@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarDerek Cheng <imcheng@chromium.org>
Reviewed-by: default avatarmark a. foltz <mfoltz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#543872}
parent e4b36d18
......@@ -99,6 +99,8 @@ static_library("router") {
"providers/cast/chrome_cast_message_handler.h",
"providers/cast/dual_media_sink_service.cc",
"providers/cast/dual_media_sink_service.h",
"providers/dial/dial_media_route_provider.cc",
"providers/dial/dial_media_route_provider.h",
"providers/extension/extension_media_route_provider_proxy.cc",
"providers/extension/extension_media_route_provider_proxy.h",
"providers/wired_display/wired_display_media_route_provider.cc",
......
......@@ -14,14 +14,6 @@
namespace media_router {
namespace {
url::Origin CreateOrigin(const std::string& url) {
return url::Origin::Create(GURL(url));
}
} // namespace
DialMediaSinkService::DialMediaSinkService()
: impl_(nullptr, base::OnTaskRunnerDeleter(nullptr)),
weak_ptr_factory_(this) {}
......@@ -65,54 +57,41 @@ void DialMediaSinkService::OnUserGesture() {
base::Unretained(impl_.get())));
}
void DialMediaSinkService::RegisterMediaSinksObserver(
MediaSinksObserver* observer) {
DialMediaSinkService::SinkQueryByAppSubscription
DialMediaSinkService::StartMonitoringAvailableSinksForApp(
const std::string& app_name,
const SinkQueryByAppCallback& callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(observer);
if (!IsDialMediaSource(observer->source()))
return;
std::string app_name = AppNameFromDialMediaSource(observer->source());
auto& observers = sink_observers_[app_name];
if (!observers) {
auto& sink_list = sinks_by_app_name_[app_name];
if (!sink_list) {
// Register first observer for |app_name|.
observers = std::make_unique<base::ObserverList<MediaSinksObserver>>();
observers->AddObserver(observer);
sink_list = std::make_unique<SinkListByAppName>();
impl_->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(
&DialMediaSinkServiceImpl::StartMonitoringAvailableSinksForApp,
base::Unretained(impl_.get()), app_name));
} else {
if (observers->HasObserver(observer))
return;
observers->AddObserver(observer);
observer->OnSinksUpdated(cached_available_sinks_[app_name],
GetOrigins(app_name));
sink_list->callbacks.set_removal_callback(base::BindRepeating(
&DialMediaSinkService::OnAvailableSinksUpdatedCallbackRemoved,
base::Unretained(this), app_name));
}
}
void DialMediaSinkService::UnregisterMediaSinksObserver(
MediaSinksObserver* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!IsDialMediaSource(observer->source()))
return;
std::string app_name = AppNameFromDialMediaSource(observer->source());
auto& observers = sink_observers_[app_name];
if (!observers || !observers->HasObserver(observer))
return;
return sink_list->callbacks.Add(callback);
}
observers->RemoveObserver(observer);
std::vector<MediaSinkInternal> DialMediaSinkService::GetCachedAvailableSinks(
const std::string& app_name) {
const auto& sinks_it = sinks_by_app_name_.find(app_name);
if (sinks_it == sinks_by_app_name_.end())
return std::vector<MediaSinkInternal>();
impl_->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(
&DialMediaSinkServiceImpl::StopMonitoringAvailableSinksForApp,
base::Unretained(impl_.get()), app_name));
return sinks_it->second->cached_sinks;
}
DialMediaSinkService::SinkListByAppName::SinkListByAppName() = default;
DialMediaSinkService::SinkListByAppName::~SinkListByAppName() = default;
std::unique_ptr<DialMediaSinkServiceImpl, base::OnTaskRunnerDeleter>
DialMediaSinkService::CreateImpl(
const OnSinksDiscoveredCallback& sink_discovery_cb,
......@@ -145,45 +124,33 @@ void DialMediaSinkService::RunSinksDiscoveredCallback(
void DialMediaSinkService::RunAvailableSinksUpdatedCallback(
const std::string& app_name,
std::vector<MediaSinkInternal> available_sinks) {
auto& observers = sink_observers_[app_name];
if (!observers)
const auto& sinks_it = sinks_by_app_name_.find(app_name);
if (sinks_it == sinks_by_app_name_.end())
return;
const std::vector<url::Origin>& origins = GetOrigins(app_name);
std::vector<MediaSink> sinks;
for (const auto& sink_internal : available_sinks)
sinks.push_back(sink_internal.sink());
for (auto& observer : *observers)
observer.OnSinksUpdated(sinks, origins);
cached_available_sinks_[app_name] = sinks;
sinks_it->second->callbacks.Notify(app_name, available_sinks);
sinks_it->second->cached_sinks = std::move(available_sinks);
}
std::vector<url::Origin> DialMediaSinkService::GetOrigins(
void DialMediaSinkService::OnAvailableSinksUpdatedCallbackRemoved(
const std::string& app_name) {
static base::flat_map<std::string, std::vector<url::Origin>>
origin_white_list = {
{"YouTube",
{CreateOrigin("https://tv.youtube.com"),
CreateOrigin("https://tv-green-qa.youtube.com"),
CreateOrigin("https://tv-release-qa.youtube.com"),
CreateOrigin("https://web-green-qa.youtube.com"),
CreateOrigin("https://web-release-qa.youtube.com"),
CreateOrigin("https://www.youtube.com")}},
{"Netflix", {CreateOrigin("https://www.netflix.com")}},
{"Pandora", {CreateOrigin("https://www.pandora.com")}},
{"Radio", {CreateOrigin("https://www.pandora.com")}},
{"Hulu", {CreateOrigin("https://www.hulu.com")}},
{"Vimeo", {CreateOrigin("https://www.vimeo.com")}},
{"Dailymotion", {CreateOrigin("https://www.dailymotion.com")}},
{"com.dailymotion", {CreateOrigin("https://www.dailymotion.com")}}};
auto origins_it = origin_white_list.find(app_name);
if (origins_it == origin_white_list.end())
return std::vector<url::Origin>();
return origins_it->second;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const auto& sinks_it = sinks_by_app_name_.find(app_name);
if (sinks_it == sinks_by_app_name_.end())
return;
// Other profile is still monitoring |app_name|.
if (!sinks_it->second->callbacks.empty())
return;
impl_->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(
&DialMediaSinkServiceImpl::StopMonitoringAvailableSinksForApp,
base::Unretained(impl_.get()), app_name));
sinks_by_app_name_.erase(app_name);
}
} // namespace media_router
......@@ -7,15 +7,14 @@
#include <memory>
#include "base/callback_list.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/sequence_checker.h"
#include "base/sequenced_task_runner.h"
#include "chrome/browser/media/router/media_sinks_observer.h"
#include "chrome/common/media_router/discovery/media_sink_internal.h"
#include "chrome/common/media_router/discovery/media_sink_service_util.h"
#include "url/origin.h"
......@@ -32,6 +31,17 @@ using OnDialSinkAddedCallback =
// SequencedTaskRunner.
class DialMediaSinkService {
public:
// Callback is invoked when available sinks for |app_name| changes.
// |app_name|: app name, e.g. YouTube.
// |available_sinks|: media sinks on which app with |app_name| is available.
using SinkQueryByAppFunc =
void(const std::string& app_name,
const std::vector<MediaSinkInternal>& available_sinks);
using SinkQueryByAppCallback = base::RepeatingCallback<SinkQueryByAppFunc>;
using SinkQueryByAppCallbackList = base::CallbackList<SinkQueryByAppFunc>;
using SinkQueryByAppSubscription =
std::unique_ptr<SinkQueryByAppCallbackList::Subscription>;
DialMediaSinkService();
virtual ~DialMediaSinkService();
......@@ -52,12 +62,33 @@ class DialMediaSinkService {
// Marked virtual for tests.
virtual void OnUserGesture();
virtual void RegisterMediaSinksObserver(MediaSinksObserver* observer);
virtual void UnregisterMediaSinksObserver(MediaSinksObserver* observer);
// Registers |callback| to callback list entry in |sink_queries_|, with the
// key |app_name|. Returns a unique_ptr of callback list subscription. Caller
// owns the returned subscription and is responsible for destroying when it
// wants to unregister |callback|.
virtual SinkQueryByAppSubscription StartMonitoringAvailableSinksForApp(
const std::string& app_name,
const SinkQueryByAppCallback& callback);
// Returns cached available sinks for |app_name|.
virtual std::vector<MediaSinkInternal> GetCachedAvailableSinks(
const std::string& app_name);
private:
friend class DialMediaSinkServiceTest;
struct SinkListByAppName {
SinkListByAppName();
~SinkListByAppName();
// Keeps track of callbacks, which could come from different profiles.
SinkQueryByAppCallbackList callbacks;
std::vector<MediaSinkInternal> cached_sinks;
DISALLOW_COPY_AND_ASSIGN(SinkListByAppName);
};
// Marked virtual for tests.
virtual std::unique_ptr<DialMediaSinkServiceImpl, base::OnTaskRunnerDeleter>
CreateImpl(const OnSinksDiscoveredCallback& sink_discovery_cb,
......@@ -72,20 +103,16 @@ class DialMediaSinkService {
const std::string& app_name,
std::vector<MediaSinkInternal> available_sinks);
// Returns a list of valid origins for |app_name|. Returns an empty list if
// all origins are valid.
std::vector<url::Origin> GetOrigins(const std::string& app_name);
// Invoked when callback subscription returned by
// |StartMonitoringAvailableSinksForApp| is destroyed by the caller.
void OnAvailableSinksUpdatedCallbackRemoved(const std::string& app_name);
// Created on the UI thread, used and destroyed on its SequencedTaskRunner.
std::unique_ptr<DialMediaSinkServiceImpl, base::OnTaskRunnerDeleter> impl_;
// Map of media sink observers, keyed by app name
base::flat_map<std::string,
std::unique_ptr<base::ObserverList<MediaSinksObserver>>>
sink_observers_;
// Map of cached available media sinks, keyed by app name
base::flat_map<std::string, std::vector<MediaSink>> cached_available_sinks_;
// Map of available sinks and sink query callbacks, keyed by app name.
base::flat_map<std::string, std::unique_ptr<SinkListByAppName>>
sinks_by_app_name_;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<DialMediaSinkService> weak_ptr_factory_;
......
......@@ -6,12 +6,31 @@
#include <algorithm>
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/media/router/discovery/dial/dial_device_data.h"
#include "services/service_manager/public/cpp/connector.h"
namespace media_router {
namespace {
static constexpr const char* kDiscoveryOnlyModelNames[3] = {
"eureka dongle", "chromecast audio", "chromecast ultra"};
// Returns true if DIAL (SSDP) was only used to discover this sink, and it is
// not expected to support other DIAL features (app discovery, activity
// discovery, etc.)
// |model_name|: device model name.
bool IsDiscoveryOnly(const std::string& model_name) {
std::string lower_model_name = base::ToLowerASCII(model_name);
return std::find(std::begin(kDiscoveryOnlyModelNames),
std::end(kDiscoveryOnlyModelNames),
lower_model_name) != std::end(kDiscoveryOnlyModelNames);
}
} // namespace
DialMediaSinkServiceImpl::DialMediaSinkServiceImpl(
std::unique_ptr<service_manager::Connector> connector,
const OnSinksDiscoveredCallback& on_sinks_discovered_cb,
......@@ -88,6 +107,8 @@ void DialMediaSinkServiceImpl::StartMonitoringAvailableSinksForApp(
// Start checking if |app_name| is available on existing sinks.
for (const auto& dial_sink_it : current_sinks_)
FetchAppInfoForSink(dial_sink_it.second, app_name);
NotifySinkObservers(app_name);
}
void DialMediaSinkServiceImpl::StopMonitoringAvailableSinksForApp(
......@@ -115,7 +136,7 @@ void DialMediaSinkServiceImpl::SetAppDiscoveryServiceForTest(
void DialMediaSinkServiceImpl::OnDiscoveryComplete() {
MediaSinkServiceBase::OnDiscoveryComplete();
for (const auto& app_name : registered_apps_)
MaybeNotifySinkObservers(app_name);
NotifySinkObservers(app_name);
}
void DialMediaSinkServiceImpl::OnDialDeviceEvent(
......@@ -151,7 +172,7 @@ void DialMediaSinkServiceImpl::OnDeviceDescriptionAvailable(
MediaSinkInternal::ProcessDeviceUUID(description_data.unique_id);
std::string sink_id = base::StringPrintf("dial:<%s>", processed_uuid.c_str());
MediaSink sink(sink_id, description_data.friendly_name, SinkIconType::GENERIC,
MediaRouteProviderId::EXTENSION);
MediaRouteProviderId::DIAL);
DialSinkExtraData extra_data;
extra_data.app_url = description_data.app_url;
extra_data.model_name = description_data.model_name;
......@@ -166,9 +187,11 @@ void DialMediaSinkServiceImpl::OnDeviceDescriptionAvailable(
if (dial_sink_added_cb_)
dial_sink_added_cb_.Run(dial_sink);
// Start checking if all registered apps are available on |dial_sink|.
for (const auto& app_name : registered_apps_)
FetchAppInfoForSink(dial_sink, app_name);
if (!IsDiscoveryOnly(description_data.model_name)) {
// Start checking if all registered apps are available on |dial_sink|.
for (const auto& app_name : registered_apps_)
FetchAppInfoForSink(dial_sink, app_name);
}
// Start fetch timer again if device description comes back after
// |finish_timer_| fires.
......@@ -196,29 +219,29 @@ void DialMediaSinkServiceImpl::OnAppInfoParseCompleted(
SetAppStatus(sink_id, app_name, app_status);
if (old_status != app_status)
MaybeNotifySinkObservers(app_name);
NotifySinkObservers(app_name);
}
void DialMediaSinkServiceImpl::MaybeNotifySinkObservers(
void DialMediaSinkServiceImpl::NotifySinkObservers(
const std::string& app_name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::flat_set<MediaSinkInternal> sinks = GetAvailableSinks(app_name);
auto& last_known_sinks = last_known_available_sinks_[app_name];
if (sinks == last_known_sinks)
return;
DVLOG(2) << "NotifySinkObservers " << app_name << " has [" << sinks.size()
<< "] sinks";
available_sinks_updated_callback_.Run(
app_name, std::vector<MediaSinkInternal>(sinks.begin(), sinks.end()));
last_known_sinks.swap(sinks);
}
void DialMediaSinkServiceImpl::FetchAppInfoForSink(
const MediaSinkInternal& dial_sink,
const std::string& app_name) {
std::string model_name = dial_sink.dial_data().model_name;
if (IsDiscoveryOnly(model_name)) {
DVLOG(2) << "Model name does not support DIAL app availability: "
<< model_name;
return;
}
std::string sink_id = dial_sink.sink().id();
SinkAppStatus app_status = GetAppStatus(sink_id, app_name);
if (app_status != SinkAppStatus::kUnknown)
......@@ -229,6 +252,13 @@ void DialMediaSinkServiceImpl::FetchAppInfoForSink(
void DialMediaSinkServiceImpl::RescanAppInfo() {
for (const auto& dial_sink_it : current_sinks_) {
std::string model_name = dial_sink_it.second.dial_data().model_name;
if (IsDiscoveryOnly(model_name)) {
DVLOG(2) << "Model name does not support DIAL app availability: "
<< model_name;
continue;
}
for (const auto& app_name : registered_apps_) {
FetchAppInfoForSink(dial_sink_it.second, app_name);
}
......
......@@ -113,6 +113,10 @@ class DialMediaSinkServiceImpl : public MediaSinkServiceBase,
TestOnDialAppInfoAvailableWithAlreadyAvailableSinks);
FRIEND_TEST_ALL_PREFIXES(DialMediaSinkServiceImplTest,
TestFetchDialAppInfoWithCachedAppInfo);
FRIEND_TEST_ALL_PREFIXES(DialMediaSinkServiceImplTest,
TestStartAfterStopMonitoringForApp);
FRIEND_TEST_ALL_PREFIXES(DialMediaSinkServiceImplTest,
TestFetchDialAppInfoWithDiscoveryOnlySink);
// DialRegistry::Observer implementation
void OnDialDeviceEvent(const DialRegistry::DeviceList& devices) override;
......@@ -136,7 +140,7 @@ class DialMediaSinkServiceImpl : public MediaSinkServiceBase,
// Invokes |available_sinks_updated_callback_| with |app_name| and current
// available sinks for |app_name|.
void MaybeNotifySinkObservers(const std::string& app_name);
void NotifySinkObservers(const std::string& app_name);
// Queries app status of |app_name| on |dial_sink|.
void FetchAppInfoForSink(const MediaSinkInternal& dial_sink,
......@@ -185,10 +189,6 @@ class DialMediaSinkServiceImpl : public MediaSinkServiceBase,
// Map of app status, keyed by <sink id:app name>.
base::flat_map<std::string, SinkAppStatus> app_statuses_;
// Map of last known available sinks for a specific app, keyed by app name.
base::flat_map<std::string, base::flat_set<MediaSinkInternal>>
last_known_available_sinks_;
// Set of registered app names.
base::flat_set<std::string> registered_apps_;
......
......@@ -261,8 +261,9 @@ TEST_F(DialMediaSinkServiceImplTest,
TestStartStopMonitoringAvailableSinksForApp) {
MediaSinkInternal dial_sink = CreateDialSink(1);
EXPECT_CALL(*mock_app_discovery_service_,
FetchDialAppInfo(dial_sink, "YouTube"))
.Times(1);
FetchDialAppInfo(dial_sink, "YouTube"));
EXPECT_CALL(mock_available_sinks_updated_cb_,
Run("YouTube", std::vector<MediaSinkInternal>()));
media_sink_service_->current_sinks_.insert_or_assign(dial_sink.sink().id(),
dial_sink);
media_sink_service_->StartMonitoringAvailableSinksForApp("YouTube");
......@@ -290,7 +291,7 @@ TEST_F(DialMediaSinkServiceImplTest, TestOnDialAppInfoAvailableNoSink) {
EXPECT_CALL(mock_available_sinks_updated_cb_,
Run("YouTube", std::vector<MediaSinkInternal>()))
.Times(0);
.Times(2);
media_sink_service_->StartMonitoringAvailableSinksForApp("YouTube");
media_sink_service_->OnAppInfoParseCompleted(sink_id, "YouTube",
SinkAppStatus::kAvailable);
......@@ -306,6 +307,10 @@ TEST_F(DialMediaSinkServiceImplTest, TestOnDialAppInfoAvailableSinksAdded) {
media_sink_service_->current_sinks_.insert_or_assign(sink_id2, dial_sink2);
EXPECT_CALL(*mock_app_discovery_service_, FetchDialAppInfo(_, _)).Times(4);
EXPECT_CALL(mock_available_sinks_updated_cb_,
Run("YouTube", std::vector<MediaSinkInternal>()));
EXPECT_CALL(mock_available_sinks_updated_cb_,
Run("Netflix", std::vector<MediaSinkInternal>()));
media_sink_service_->StartMonitoringAvailableSinksForApp("YouTube");
media_sink_service_->StartMonitoringAvailableSinksForApp("Netflix");
......@@ -331,6 +336,7 @@ TEST_F(DialMediaSinkServiceImplTest, TestOnDialAppInfoAvailableSinksRemoved) {
std::string sink_id = dial_sink.sink().id();
EXPECT_CALL(*mock_app_discovery_service_, FetchDialAppInfo(_, _));
EXPECT_CALL(mock_available_sinks_updated_cb_, Run(_, _));
media_sink_service_->current_sinks_.insert_or_assign(sink_id, dial_sink);
media_sink_service_->StartMonitoringAvailableSinksForApp("YouTube");
......@@ -350,6 +356,7 @@ TEST_F(DialMediaSinkServiceImplTest,
MediaSinkInternal dial_sink = CreateDialSink(1);
EXPECT_CALL(*mock_app_discovery_service_, FetchDialAppInfo(_, _));
EXPECT_CALL(mock_available_sinks_updated_cb_, Run(_, _));
media_sink_service_->current_sinks_.insert_or_assign(dial_sink.sink().id(),
dial_sink);
media_sink_service_->StartMonitoringAvailableSinksForApp("YouTube");
......@@ -367,6 +374,7 @@ TEST_F(DialMediaSinkServiceImplTest, TestFetchDialAppInfoWithCachedAppInfo) {
MediaSinkInternal dial_sink = CreateDialSink(1);
EXPECT_CALL(*mock_app_discovery_service_, FetchDialAppInfo(_, _));
EXPECT_CALL(mock_available_sinks_updated_cb_, Run(_, _));
media_sink_service_->current_sinks_.insert_or_assign(dial_sink.sink().id(),
dial_sink);
media_sink_service_->StartMonitoringAvailableSinksForApp("YouTube");
......@@ -381,4 +389,39 @@ TEST_F(DialMediaSinkServiceImplTest, TestFetchDialAppInfoWithCachedAppInfo) {
media_sink_service_->StartMonitoringAvailableSinksForApp("YouTube");
}
TEST_F(DialMediaSinkServiceImplTest, TestStartAfterStopMonitoringForApp) {
MediaSinkInternal dial_sink = CreateDialSink(1);
EXPECT_CALL(*mock_app_discovery_service_, FetchDialAppInfo(_, _));
EXPECT_CALL(mock_available_sinks_updated_cb_, Run(_, _));
media_sink_service_->current_sinks_.insert_or_assign(dial_sink.sink().id(),
dial_sink);
media_sink_service_->StartMonitoringAvailableSinksForApp("YouTube");
EXPECT_CALL(mock_available_sinks_updated_cb_,
Run("YouTube", std::vector<MediaSinkInternal>({dial_sink})));
media_sink_service_->OnAppInfoParseCompleted(dial_sink.sink().id(), "YouTube",
SinkAppStatus::kAvailable);
media_sink_service_->StopMonitoringAvailableSinksForApp("YouTube");
EXPECT_CALL(mock_available_sinks_updated_cb_,
Run("YouTube", std::vector<MediaSinkInternal>({dial_sink})));
media_sink_service_->StartMonitoringAvailableSinksForApp("YouTube");
}
TEST_F(DialMediaSinkServiceImplTest,
TestFetchDialAppInfoWithDiscoveryOnlySink) {
MediaSinkInternal dial_sink = CreateDialSink(1);
media_router::DialSinkExtraData extra_data = dial_sink.dial_data();
extra_data.model_name = "Eureka Dongle";
dial_sink.set_dial_data(extra_data);
EXPECT_CALL(*mock_app_discovery_service_, FetchDialAppInfo(_, _)).Times(0);
EXPECT_CALL(mock_available_sinks_updated_cb_, Run(_, _));
media_sink_service_->current_sinks_.insert_or_assign(dial_sink.sink().id(),
dial_sink);
media_sink_service_->StartMonitoringAvailableSinksForApp("YouTube");
}
} // namespace media_router
......@@ -9,6 +9,7 @@
#include "chrome/browser/media/router/discovery/dial/dial_media_sink_service_impl.h"
#include "chrome/browser/media/router/test/mock_media_router.h"
#include "chrome/browser/media/router/test/test_helper.h"
#include "chrome/common/media_router/discovery/media_sink_service_util.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "services/service_manager/public/cpp/connector.h"
......@@ -21,20 +22,6 @@ using ::testing::Return;
namespace media_router {
class MockMediaSinksObserverInternal : public MediaSinksObserver {
public:
MockMediaSinksObserverInternal(MediaRouter* router,
const MediaSource& source,
const url::Origin& origin)
: MediaSinksObserver(router, source, origin) {}
~MockMediaSinksObserverInternal() override {}
MOCK_METHOD2(OnSinksUpdated,
void(const std::vector<MediaSink>& sinks,
const std::vector<url::Origin>& origins));
MOCK_METHOD1(OnSinksReceived, void(const std::vector<MediaSink>& sinks));
};
class MockDialMediaSinkServiceImpl : public DialMediaSinkServiceImpl {
public:
MockDialMediaSinkServiceImpl(
......@@ -103,6 +90,8 @@ class TestDialMediaSinkService : public DialMediaSinkService {
class DialMediaSinkServiceTest : public ::testing::Test {
public:
using SinkQueryByAppCallback = DialMediaSinkService::SinkQueryByAppCallback;
DialMediaSinkServiceTest()
: task_runner_(new base::TestSimpleTaskRunner()),
service_(new TestDialMediaSinkService(task_runner_)) {}
......@@ -150,68 +139,119 @@ TEST_F(DialMediaSinkServiceTest, OnDialSinkAddedCallback) {
}
TEST_F(DialMediaSinkServiceTest, TestAddRemoveSinkQuery) {
MediaSource youtube_source("cast-dial:YouTube");
MediaSource netflix_source("cast-dial:Netflix");
std::vector<url::Origin> youtube_origins = {
url::Origin::Create(GURL("https://tv.youtube.com")),
url::Origin::Create(GURL("https://tv-green-qa.youtube.com")),
url::Origin::Create(GURL("https://tv-release-qa.youtube.com")),
url::Origin::Create(GURL("https://web-green-qa.youtube.com")),
url::Origin::Create(GURL("https://web-release-qa.youtube.com")),
url::Origin::Create(GURL("https://www.youtube.com"))};
std::vector<url::Origin> netflix_origins = {
url::Origin::Create(GURL("https://www.netflix.com"))};
MockMediaRouter mock_router;
MockMediaSinksObserverInternal observer1(
&mock_router, youtube_source,
url::Origin::Create(GURL("https://tv.youtube.com")));
MockMediaSinksObserverInternal observer2(
&mock_router, youtube_source,
url::Origin::Create(GURL("https://www.youtube.com")));
MockMediaSinksObserverInternal observer3(
&mock_router, netflix_source,
url::Origin::Create(GURL("https://www.netflix.com")));
MockMediaSinksObserverInternal observer4(
&mock_router, netflix_source,
url::Origin::Create(GURL("https://www.netflix.com")));
EXPECT_CALL(*mock_impl_, StartMonitoringAvailableSinksForApp("YouTube"))
base::MockCallback<SinkQueryByAppCallback> mock_callback;
std::string app_name("YouTube");
EXPECT_CALL(*mock_impl_, StartMonitoringAvailableSinksForApp(app_name));
auto subscription = service_->StartMonitoringAvailableSinksForApp(
app_name, mock_callback.Get());
task_runner_->RunUntilIdle();
MediaSinkInternal sink_internal = CreateDialSink(1);
std::vector<MediaSinkInternal> sinks{sink_internal};
EXPECT_CALL(mock_callback, Run(app_name, sinks));
mock_impl_->available_sinks_updated_cb().Run(app_name, sinks);
base::RunLoop().RunUntilIdle();
subscription.reset();
EXPECT_CALL(*mock_impl_, StopMonitoringAvailableSinksForApp(app_name));
base::RunLoop().RunUntilIdle();
EXPECT_CALL(mock_callback, Run(_, _)).Times(0);
mock_impl_->available_sinks_updated_cb().Run(app_name, sinks);
base::RunLoop().RunUntilIdle();
}
TEST_F(DialMediaSinkServiceTest, TestAddSinkQuerySameAppSameObserver) {
base::MockCallback<SinkQueryByAppCallback> mock_callback;
std::string app_name("YouTube");
EXPECT_CALL(*mock_impl_, StartMonitoringAvailableSinksForApp(app_name))
.Times(1);
EXPECT_CALL(*mock_impl_, StartMonitoringAvailableSinksForApp("Netflix"))
auto subscription1 = service_->StartMonitoringAvailableSinksForApp(
app_name, mock_callback.Get());
auto subscription2 = service_->StartMonitoringAvailableSinksForApp(
app_name, mock_callback.Get());
task_runner_->RunUntilIdle();
MediaSinkInternal sink_internal = CreateDialSink(1);
std::vector<MediaSinkInternal> sinks{sink_internal};
EXPECT_CALL(mock_callback, Run(app_name, sinks)).Times(2);
mock_impl_->available_sinks_updated_cb().Run("YouTube", sinks);
base::RunLoop().RunUntilIdle();
subscription1.reset();
subscription2.reset();
EXPECT_CALL(*mock_impl_, StopMonitoringAvailableSinksForApp(app_name));
base::RunLoop().RunUntilIdle();
}
TEST_F(DialMediaSinkServiceTest, TestAddSinkQuerySameAppDifferentObservers) {
base::MockCallback<SinkQueryByAppCallback> mock_callback1;
base::MockCallback<SinkQueryByAppCallback> mock_callback2;
std::string app_name("YouTube");
EXPECT_CALL(*mock_impl_, StartMonitoringAvailableSinksForApp(app_name))
.Times(1);
service_->RegisterMediaSinksObserver(&observer1);
service_->RegisterMediaSinksObserver(&observer3);
EXPECT_CALL(observer4,
OnSinksUpdated(std::vector<MediaSink>(), netflix_origins));
service_->RegisterMediaSinksObserver(&observer4);
auto subscription1 = service_->StartMonitoringAvailableSinksForApp(
app_name, mock_callback1.Get());
auto subscription2 = service_->StartMonitoringAvailableSinksForApp(
app_name, mock_callback2.Get());
task_runner_->RunUntilIdle();
MediaSink sink("sink id 1", "sink name 1", SinkIconType::GENERIC);
MediaSinkInternal sink_internal(sink, DialSinkExtraData());
std::vector<MediaSink> sinks{sink};
MediaSinkInternal sink_internal = CreateDialSink(1);
std::vector<MediaSinkInternal> sinks{sink_internal};
EXPECT_CALL(observer1, OnSinksUpdated(sinks, youtube_origins));
mock_impl_->available_sinks_updated_cb().Run("YouTube", {sink_internal});
EXPECT_CALL(mock_callback1, Run(app_name, sinks));
EXPECT_CALL(mock_callback2, Run(app_name, sinks));
mock_impl_->available_sinks_updated_cb().Run(app_name, sinks);
base::RunLoop().RunUntilIdle();
EXPECT_CALL(observer2, OnSinksUpdated(sinks, youtube_origins));
service_->RegisterMediaSinksObserver(&observer2);
subscription1.reset();
EXPECT_CALL(*mock_impl_, StopMonitoringAvailableSinksForApp(app_name))
.Times(0);
base::RunLoop().RunUntilIdle();
EXPECT_CALL(observer3, OnSinksUpdated(sinks, netflix_origins));
EXPECT_CALL(observer4, OnSinksUpdated(sinks, netflix_origins));
mock_impl_->available_sinks_updated_cb().Run("Netflix", {sink_internal});
subscription2.reset();
EXPECT_CALL(*mock_impl_, StopMonitoringAvailableSinksForApp(app_name));
base::RunLoop().RunUntilIdle();
}
EXPECT_CALL(*mock_impl_, StopMonitoringAvailableSinksForApp("YouTube"))
.Times(2);
service_->UnregisterMediaSinksObserver(&observer1);
service_->UnregisterMediaSinksObserver(&observer2);
TEST_F(DialMediaSinkServiceTest, TestAddSinkQueryDifferentApps) {
base::MockCallback<SinkQueryByAppCallback> mock_callback1;
base::MockCallback<SinkQueryByAppCallback> mock_callback2;
std::string youtube_app_name("YouTube");
std::string netflix_app_name("Netflix");
EXPECT_CALL(*mock_impl_,
StartMonitoringAvailableSinksForApp(youtube_app_name));
EXPECT_CALL(*mock_impl_,
StartMonitoringAvailableSinksForApp(netflix_app_name));
auto subscription1 = service_->StartMonitoringAvailableSinksForApp(
youtube_app_name, mock_callback1.Get());
auto subscription2 = service_->StartMonitoringAvailableSinksForApp(
netflix_app_name, mock_callback2.Get());
task_runner_->RunUntilIdle();
EXPECT_CALL(observer1, OnSinksUpdated(_, _)).Times(0);
EXPECT_CALL(observer2, OnSinksUpdated(_, _)).Times(0);
mock_impl_->available_sinks_updated_cb().Run("YouTube", {sink_internal});
MediaSinkInternal sink_internal = CreateDialSink(1);
std::vector<MediaSinkInternal> sinks{sink_internal};
EXPECT_CALL(mock_callback1, Run(youtube_app_name, sinks));
mock_impl_->available_sinks_updated_cb().Run(youtube_app_name, sinks);
base::RunLoop().RunUntilIdle();
EXPECT_CALL(mock_callback2, Run(netflix_app_name, sinks));
mock_impl_->available_sinks_updated_cb().Run(netflix_app_name, sinks);
base::RunLoop().RunUntilIdle();
subscription1.reset();
subscription2.reset();
EXPECT_CALL(*mock_impl_,
StopMonitoringAvailableSinksForApp(youtube_app_name));
EXPECT_CALL(*mock_impl_,
StopMonitoringAvailableSinksForApp(netflix_app_name));
base::RunLoop().RunUntilIdle();
}
......
......@@ -4,6 +4,7 @@
#include "chrome/browser/media/router/mojo/media_router_desktop.h"
#include "base/bind_helpers.h"
#include "base/strings/string_util.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/media/router/media_router_factory.h"
......@@ -207,6 +208,8 @@ void MediaRouterDesktop::InitializeMediaRouteProviders() {
InitializeWiredDisplayMediaRouteProvider();
if (CastMediaRouteProviderEnabled())
InitializeCastMediaRouteProvider();
if (DialSinkQueryEnabled())
InitializeDialMediaRouteProvider();
}
void MediaRouterDesktop::InitializeExtensionMediaRouteProviderProxy() {
......@@ -247,6 +250,19 @@ void MediaRouterDesktop::InitializeCastMediaRouteProvider() {
std::move(cast_provider_ptr), base::DoNothing());
}
void MediaRouterDesktop::InitializeDialMediaRouteProvider() {
mojom::MediaRouterPtr media_router_ptr;
MediaRouterMojoImpl::BindToMojoRequest(mojo::MakeRequest(&media_router_ptr));
mojom::MediaRouteProviderPtr dial_provider_ptr;
DCHECK(media_sink_service_);
dial_provider_ = std::make_unique<DialMediaRouteProvider>(
mojo::MakeRequest(&dial_provider_ptr), std::move(media_router_ptr),
media_sink_service_->dial_media_sink_service());
RegisterMediaRouteProvider(MediaRouteProviderId::DIAL,
std::move(dial_provider_ptr), base::DoNothing());
}
#if defined(OS_WIN)
void MediaRouterDesktop::EnsureMdnsDiscoveryEnabled() {
if (media_router::CastDiscoveryEnabled()) {
......
......@@ -10,6 +10,7 @@
#include "chrome/browser/media/router/mojo/media_router_mojo_impl.h"
#include "chrome/browser/media/router/mojo/media_sink_service_status.h"
#include "chrome/browser/media/router/providers/cast/dual_media_sink_service.h"
#include "chrome/browser/media/router/providers/dial/dial_media_route_provider.h"
#include "chrome/browser/media/router/providers/extension/extension_media_route_provider_proxy.h"
namespace content {
......@@ -111,6 +112,7 @@ class MediaRouterDesktop : public MediaRouterMojoImpl {
void InitializeExtensionMediaRouteProviderProxy();
void InitializeWiredDisplayMediaRouteProvider();
void InitializeCastMediaRouteProvider();
void InitializeDialMediaRouteProvider();
#if defined(OS_WIN)
// Ensures that mDNS discovery is enabled in the MRPM extension. This can be
......@@ -135,6 +137,9 @@ class MediaRouterDesktop : public MediaRouterMojoImpl {
std::unique_ptr<CastMediaRouteProvider, base::OnTaskRunnerDeleter>
cast_provider_;
// MediaRouteProvider for DIAL.
std::unique_ptr<DialMediaRouteProvider> dial_provider_;
DualMediaSinkService* media_sink_service_;
DualMediaSinkService::Subscription media_sink_service_subscription_;
......
......@@ -201,12 +201,17 @@ void MediaRouterMojoImpl::CreateRoute(
MediaRouterMetrics::RecordMediaSinkType(sink->icon_type());
MediaRouteProviderId provider_id = sink->provider_id();
// This is a hack to ensure the extension handles the CreateRoute call until
// the CastMediaRouteProvider supports it.
// TODO(crbug.com/698940): Remove this hack when CastMediaRouteProvider
// TODO(crbug.com/698940): Remove check for Cast when CastMediaRouteProvider
// supports route management.
if (provider_id == MediaRouteProviderId::CAST)
// TODO(https://crbug.com/808720): Remove check for DIAL when in-browser DIAL
// MRP is fully implemented.
if (provider_id == MediaRouteProviderId::CAST ||
provider_id == MediaRouteProviderId::DIAL) {
provider_id = MediaRouteProviderId::EXTENSION;
}
int tab_id = SessionTabHelper::IdForTab(web_contents);
std::string presentation_id = MediaRouterBase::CreatePresentationId();
......@@ -613,8 +618,9 @@ bool MediaRouterMojoImpl::RegisterMediaSinksObserver(
// no need to call MRPs.
if (is_new_query) {
for (const auto& provider : media_route_providers_) {
if (sink_availability_.IsAvailableForProvider(provider.first))
if (sink_availability_.IsAvailableForProvider(provider.first)) {
provider.second->StartObservingMediaSinks(source_id);
}
}
}
return true;
......@@ -638,8 +644,9 @@ void MediaRouterMojoImpl::UnregisterMediaSinksObserver(
// Only ask MRPs to stop observing media sinks if there are sinks available.
// Otherwise, the MRPs would have discarded the queries already.
for (const auto& provider : media_route_providers_) {
if (sink_availability_.IsAvailableForProvider(provider.first))
if (sink_availability_.IsAvailableForProvider(provider.first)) {
provider.second->StopObservingMediaSinks(source_id);
}
}
sinks_queries_.erase(source_id);
}
......
......@@ -86,6 +86,8 @@ void MediaRouterMojoMetrics::RecordCreateRouteResultCode(
case MediaRouteProviderId::EXTENSION:
// TODO(crbug.com/809249): Implement Cast-specific metric.
case MediaRouteProviderId::CAST:
// TODO(crbug.com/808720): Implement DIAL-specific metric.
case MediaRouteProviderId::DIAL:
case MediaRouteProviderId::UNKNOWN:
UMA_HISTOGRAM_ENUMERATION(kHistogramProviderCreateRouteResult,
result_code, RouteRequestResult::TOTAL_COUNT);
......@@ -106,6 +108,8 @@ void MediaRouterMojoMetrics::RecordJoinRouteResultCode(
case MediaRouteProviderId::EXTENSION:
// TODO(crbug.com/809249): Implement Cast-specific metric.
case MediaRouteProviderId::CAST:
// TODO(crbug.com/808720): Implement DIAL-specific metric.
case MediaRouteProviderId::DIAL:
case MediaRouteProviderId::UNKNOWN:
UMA_HISTOGRAM_ENUMERATION(kHistogramProviderJoinRouteResult, result_code,
RouteRequestResult::TOTAL_COUNT);
......@@ -127,6 +131,8 @@ void MediaRouterMojoMetrics::RecordMediaRouteProviderTerminateRoute(
case MediaRouteProviderId::EXTENSION:
// TODO(crbug.com/809249): Implement Cast-specific metric.
case MediaRouteProviderId::CAST:
// TODO(crbug.com/808720): Implement DIAL-specific metric.
case MediaRouteProviderId::DIAL:
case MediaRouteProviderId::UNKNOWN:
UMA_HISTOGRAM_ENUMERATION(kHistogramProviderTerminateRouteResult,
result_code, RouteRequestResult::TOTAL_COUNT);
......
......@@ -54,18 +54,6 @@ void DualMediaSinkService::StartMdnsDiscovery() {
cast_media_sink_service_->StartMdnsDiscovery();
}
void DualMediaSinkService::RegisterMediaSinksObserver(
MediaSinksObserver* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
dial_media_sink_service_->RegisterMediaSinksObserver(observer);
}
void DualMediaSinkService::UnregisterMediaSinksObserver(
MediaSinksObserver* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
dial_media_sink_service_->UnregisterMediaSinksObserver(observer);
}
DualMediaSinkService::DualMediaSinkService() {
OnDialSinkAddedCallback dial_sink_added_cb;
if (CastDiscoveryEnabled()) {
......
......@@ -57,6 +57,10 @@ class DualMediaSinkService {
return current_sinks_;
}
DialMediaSinkService* dial_media_sink_service() {
return dial_media_sink_service_.get();
}
// Adds |callback| to be notified when the list of discovered sinks changes.
// The caller is responsible for destroying the returned Subscription when it
// no longer wishes to receive updates.
......@@ -68,8 +72,6 @@ class DualMediaSinkService {
// Starts mDNS discovery on |cast_media_sink_service_| if it is not already
// started.
virtual void StartMdnsDiscovery();
virtual void RegisterMediaSinksObserver(MediaSinksObserver* observer);
virtual void UnregisterMediaSinksObserver(MediaSinksObserver* observer);
protected:
// Used by tests.
......
// Copyright 2018 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/dial/dial_media_route_provider.h"
#include "base/no_destructor.h"
#include "base/stl_util.h"
#include "chrome/common/media_router/media_source_helper.h"
#include "url/origin.h"
namespace media_router {
namespace {
url::Origin CreateOrigin(const std::string& url) {
return url::Origin::Create(GURL(url));
}
} // namespace
DialMediaRouteProvider::DialMediaRouteProvider(
mojom::MediaRouteProviderRequest request,
mojom::MediaRouterPtr media_router,
DialMediaSinkService* dial_media_sink_service)
: binding_(this, std::move(request)),
media_router_(std::move(media_router)),
dial_media_sink_service_(dial_media_sink_service) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(dial_media_sink_service_);
// TODO(crbug.com/808720): This needs to be set properly according to sinks
// discovered.
media_router_->OnSinkAvailabilityUpdated(
MediaRouteProviderId::DIAL,
mojom::MediaRouter::SinkAvailability::PER_SOURCE);
}
DialMediaRouteProvider::~DialMediaRouteProvider() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(media_sink_queries_.empty());
}
void DialMediaRouteProvider::CreateRoute(const std::string& media_source,
const std::string& sink_id,
const std::string& presentation_id,
const url::Origin& origin,
int32_t tab_id,
base::TimeDelta timeout,
bool incognito,
CreateRouteCallback callback) {
NOTIMPLEMENTED();
std::move(callback).Run(
base::nullopt, std::string("Not implemented"),
RouteRequestResult::ResultCode::NO_SUPPORTED_PROVIDER);
}
void DialMediaRouteProvider::JoinRoute(const std::string& media_source,
const std::string& presentation_id,
const url::Origin& origin,
int32_t tab_id,
base::TimeDelta timeout,
bool incognito,
JoinRouteCallback callback) {
NOTIMPLEMENTED();
std::move(callback).Run(
base::nullopt, std::string("Not implemented"),
RouteRequestResult::ResultCode::NO_SUPPORTED_PROVIDER);
}
void DialMediaRouteProvider::ConnectRouteByRouteId(
const std::string& media_source,
const std::string& route_id,
const std::string& presentation_id,
const url::Origin& origin,
int32_t tab_id,
base::TimeDelta timeout,
bool incognito,
ConnectRouteByRouteIdCallback callback) {
NOTIMPLEMENTED();
std::move(callback).Run(
base::nullopt, std::string("Not implemented"),
RouteRequestResult::ResultCode::NO_SUPPORTED_PROVIDER);
}
void DialMediaRouteProvider::TerminateRoute(const std::string& route_id,
TerminateRouteCallback callback) {
NOTIMPLEMENTED();
std::move(callback).Run(
std::string("Not implemented"),
RouteRequestResult::ResultCode::NO_SUPPORTED_PROVIDER);
}
void DialMediaRouteProvider::SendRouteMessage(
const std::string& media_route_id,
const std::string& message,
SendRouteMessageCallback callback) {
NOTIMPLEMENTED();
std::move(callback).Run(false);
}
void DialMediaRouteProvider::SendRouteBinaryMessage(
const std::string& media_route_id,
const std::vector<uint8_t>& data,
SendRouteBinaryMessageCallback callback) {
NOTIMPLEMENTED();
std::move(callback).Run(false);
}
void DialMediaRouteProvider::StartObservingMediaSinks(
const std::string& media_source) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(dial_media_sink_service_);
MediaSource dial_source(media_source);
if (!IsDialMediaSource(dial_source))
return;
RegisterDialMediaSource(dial_source);
}
void DialMediaRouteProvider::StopObservingMediaSinks(
const std::string& media_source) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(dial_media_sink_service_);
MediaSource dial_source(media_source);
if (!IsDialMediaSource(dial_source))
return;
UnregisterDialMediaSource(dial_source);
}
void DialMediaRouteProvider::StartObservingMediaRoutes(
const std::string& media_source) {
NOTIMPLEMENTED();
}
void DialMediaRouteProvider::StopObservingMediaRoutes(
const std::string& media_source) {
NOTIMPLEMENTED();
}
void DialMediaRouteProvider::StartListeningForRouteMessages(
const std::string& route_id) {
NOTIMPLEMENTED();
}
void DialMediaRouteProvider::StopListeningForRouteMessages(
const std::string& route_id) {
NOTIMPLEMENTED();
}
void DialMediaRouteProvider::DetachRoute(const std::string& route_id) {
NOTIMPLEMENTED();
}
void DialMediaRouteProvider::EnableMdnsDiscovery() {
NOTIMPLEMENTED();
}
void DialMediaRouteProvider::UpdateMediaSinks(const std::string& media_source) {
NOTIMPLEMENTED();
}
void DialMediaRouteProvider::SearchSinks(
const std::string& sink_id,
const std::string& media_source,
mojom::SinkSearchCriteriaPtr search_criteria,
SearchSinksCallback callback) {
std::move(callback).Run(std::string());
}
void DialMediaRouteProvider::ProvideSinks(
const std::string& provider_name,
const std::vector<media_router::MediaSinkInternal>& sinks) {
NOTIMPLEMENTED();
}
void DialMediaRouteProvider::CreateMediaRouteController(
const std::string& route_id,
mojom::MediaControllerRequest media_controller,
mojom::MediaStatusObserverPtr observer,
CreateMediaRouteControllerCallback callback) {
NOTIMPLEMENTED();
std::move(callback).Run(false);
}
void DialMediaRouteProvider::OnAvailableSinksUpdated(
const std::string& app_name,
const std::vector<MediaSinkInternal>& sinks) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const auto& sink_query_it = media_sink_queries_.find(app_name);
if (sink_query_it == media_sink_queries_.end()) {
DVLOG(2) << "Not monitoring app " << app_name;
return;
}
const auto& media_sources = sink_query_it->second->media_sources;
std::vector<url::Origin> origins = GetOrigins(app_name);
for (const auto& media_source : media_sources) {
media_router_->OnSinksReceived(MediaRouteProviderId::DIAL,
media_source.id(), sinks, origins);
}
}
void DialMediaRouteProvider::RegisterDialMediaSource(
const MediaSource& dial_source) {
std::string app_name = AppNameFromDialMediaSource(dial_source);
auto& sink_query = media_sink_queries_[app_name];
if (!sink_query) {
sink_query = std::make_unique<MediaSinkQuery>();
sink_query->subscription =
dial_media_sink_service_->StartMonitoringAvailableSinksForApp(
app_name, base::BindRepeating(
&DialMediaRouteProvider::OnAvailableSinksUpdated,
base::Unretained(this)));
}
if (sink_query->media_sources.insert(dial_source).second)
MayNotifyMediaSinksObservers(dial_source.id(), app_name);
}
void DialMediaRouteProvider::MayNotifyMediaSinksObservers(
const MediaSource::Id& media_source_id,
const std::string& app_name) {
const auto& cached_sinks =
dial_media_sink_service_->GetCachedAvailableSinks(app_name);
if (cached_sinks.empty())
return;
media_router_->OnSinksReceived(MediaRouteProviderId::DIAL, media_source_id,
cached_sinks, GetOrigins(app_name));
}
void DialMediaRouteProvider::UnregisterDialMediaSource(
const MediaSource& dial_source) {
std::string app_name = AppNameFromDialMediaSource(dial_source);
const auto& sink_query_it = media_sink_queries_.find(app_name);
if (sink_query_it == media_sink_queries_.end())
return;
auto& media_sources = sink_query_it->second->media_sources;
media_sources.erase(dial_source);
if (!media_sources.empty())
return;
media_sink_queries_.erase(app_name);
}
std::vector<url::Origin> DialMediaRouteProvider::GetOrigins(
const std::string& app_name) {
static const base::NoDestructor<
base::flat_map<std::string, std::vector<url::Origin>>>
origin_white_list(
{{"YouTube",
{CreateOrigin("https://tv.youtube.com"),
CreateOrigin("https://tv-green-qa.youtube.com"),
CreateOrigin("https://tv-release-qa.youtube.com"),
CreateOrigin("https://web-green-qa.youtube.com"),
CreateOrigin("https://web-release-qa.youtube.com"),
CreateOrigin("https://www.youtube.com")}},
{"Netflix", {CreateOrigin("https://www.netflix.com")}},
{"Pandora", {CreateOrigin("https://www.pandora.com")}},
{"Radio", {CreateOrigin("https://www.pandora.com")}},
{"Hulu", {CreateOrigin("https://www.hulu.com")}},
{"Vimeo", {CreateOrigin("https://www.vimeo.com")}},
{"Dailymotion", {CreateOrigin("https://www.dailymotion.com")}},
{"com.dailymotion", {CreateOrigin("https://www.dailymotion.com")}}});
auto origins_it = origin_white_list->find(app_name);
if (origins_it == origin_white_list->end())
return std::vector<url::Origin>();
return origins_it->second;
}
DialMediaRouteProvider::MediaSinkQuery::MediaSinkQuery() = default;
DialMediaRouteProvider::MediaSinkQuery::~MediaSinkQuery() = default;
} // namespace media_router
// Copyright 2018 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_DIAL_DIAL_MEDIA_ROUTE_PROVIDER_H_
#define CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_DIAL_DIAL_MEDIA_ROUTE_PROVIDER_H_
#include <memory>
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/sequence_checker.h"
#include "chrome/browser/media/router/discovery/dial/dial_media_sink_service.h"
#include "chrome/common/media_router/mojo/media_router.mojom.h"
#include "mojo/public/cpp/bindings/binding.h"
namespace url {
class Origin;
}
namespace media_router {
// MediaRouteProvider for DIAL sinks.
class DialMediaRouteProvider : public mojom::MediaRouteProvider {
public:
DialMediaRouteProvider(mojom::MediaRouteProviderRequest request,
mojom::MediaRouterPtr media_router,
DialMediaSinkService* dial_media_sink_service);
~DialMediaRouteProvider() override;
// mojom::MediaRouteProvider:
void CreateRoute(const std::string& media_source,
const std::string& sink_id,
const std::string& presentation_id,
const url::Origin& origin,
int32_t tab_id,
base::TimeDelta timeout,
bool incognito,
CreateRouteCallback callback) override;
void JoinRoute(const std::string& media_source,
const std::string& presentation_id,
const url::Origin& origin,
int32_t tab_id,
base::TimeDelta timeout,
bool incognito,
JoinRouteCallback callback) override;
void ConnectRouteByRouteId(const std::string& media_source,
const std::string& route_id,
const std::string& presentation_id,
const url::Origin& origin,
int32_t tab_id,
base::TimeDelta timeout,
bool incognito,
ConnectRouteByRouteIdCallback callback) override;
void TerminateRoute(const std::string& route_id,
TerminateRouteCallback callback) override;
void SendRouteMessage(const std::string& media_route_id,
const std::string& message,
SendRouteMessageCallback callback) override;
void SendRouteBinaryMessage(const std::string& media_route_id,
const std::vector<uint8_t>& data,
SendRouteBinaryMessageCallback callback) override;
void StartObservingMediaSinks(const std::string& media_source) override;
void StopObservingMediaSinks(const std::string& media_source) override;
void StartObservingMediaRoutes(const std::string& media_source) override;
void StopObservingMediaRoutes(const std::string& media_source) override;
void StartListeningForRouteMessages(const std::string& route_id) override;
void StopListeningForRouteMessages(const std::string& route_id) override;
void DetachRoute(const std::string& route_id) override;
void EnableMdnsDiscovery() override;
void UpdateMediaSinks(const std::string& media_source) override;
void SearchSinks(const std::string& sink_id,
const std::string& media_source,
mojom::SinkSearchCriteriaPtr search_criteria,
SearchSinksCallback callback) override;
void ProvideSinks(
const std::string& provider_name,
const std::vector<media_router::MediaSinkInternal>& sinks) override;
void CreateMediaRouteController(
const std::string& route_id,
mojom::MediaControllerRequest media_controller,
mojom::MediaStatusObserverPtr observer,
CreateMediaRouteControllerCallback callback) override;
private:
FRIEND_TEST_ALL_PREFIXES(DialMediaRouteProviderTest, TestAddRemoveSinkQuery);
FRIEND_TEST_ALL_PREFIXES(DialMediaRouteProviderTest,
TestAddSinkQuerySameMediaSource);
FRIEND_TEST_ALL_PREFIXES(DialMediaRouteProviderTest,
TestAddSinkQuerySameAppDifferentMediaSources);
FRIEND_TEST_ALL_PREFIXES(DialMediaRouteProviderTest,
TestAddSinkQueryDifferentApps);
struct MediaSinkQuery {
MediaSinkQuery();
~MediaSinkQuery();
// Set of registered media sources for current sink query.
base::flat_set<MediaSource> media_sources;
DialMediaSinkService::SinkQueryByAppSubscription subscription;
DISALLOW_COPY_AND_ASSIGN(MediaSinkQuery);
};
void OnAvailableSinksUpdated(
const std::string& app_name,
const std::vector<MediaSinkInternal>& available_sinks);
void RegisterDialMediaSource(const MediaSource& dial_source);
void MayNotifyMediaSinksObservers(const MediaSource::Id& media_source_id,
const std::string& app_name);
void UnregisterDialMediaSource(const MediaSource& dial_source);
// Returns a list of valid origins for |app_name|. Returns an empty list if
// all origins are valid.
std::vector<url::Origin> GetOrigins(const std::string& app_name);
// Binds |this| to the Mojo request passed into the ctor.
mojo::Binding<mojom::MediaRouteProvider> binding_;
// Mojo pointer to the Media Router.
mojom::MediaRouterPtr media_router_;
// Non-owned pointer to the DialMediaSinkService instance.
DialMediaSinkService* const dial_media_sink_service_;
// Map of media sink queries, keyed by app name.
base::flat_map<std::string, std::unique_ptr<MediaSinkQuery>>
media_sink_queries_;
SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(DialMediaRouteProvider);
};
} // namespace media_router
#endif // CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_DIAL_DIAL_MEDIA_ROUTE_PROVIDER_H_
// Copyright 2018 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/dial/dial_media_route_provider.h"
#include "chrome/browser/media/router/test/mock_mojo_media_router.h"
#include "base/run_loop.h"
#include "chrome/browser/media/router/test/test_helper.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::InvokeWithoutArgs;
using ::testing::Return;
using ::testing::StrictMock;
namespace media_router {
class DialMediaRouteProviderTest : public ::testing::Test {
public:
DialMediaRouteProviderTest() : mock_service_() {}
void SetUp() override {
mojom::MediaRouterPtr router_ptr;
router_binding_ = std::make_unique<mojo::Binding<mojom::MediaRouter>>(
&mock_router_, mojo::MakeRequest(&router_ptr));
EXPECT_CALL(mock_router_, OnSinkAvailabilityUpdated(_, _));
provider_ = std::make_unique<DialMediaRouteProvider>(
mojo::MakeRequest(&provider_ptr_), std::move(router_ptr),
&mock_service_);
base::RunLoop().RunUntilIdle();
}
void TearDown() override { provider_.reset(); }
MOCK_METHOD1(OnSinksDiscovered, void(std::vector<MediaSinkInternal> sinks));
MOCK_METHOD1(OnDialSinkAdded, void(const MediaSinkInternal& sink));
protected:
content::TestBrowserThreadBundle thread_bundle_;
TestingProfile profile_;
mojom::MediaRouteProviderPtr provider_ptr_;
MockMojoMediaRouter mock_router_;
std::unique_ptr<mojo::Binding<mojom::MediaRouter>> router_binding_;
StrictMock<MockDialMediaSinkService> mock_service_;
std::unique_ptr<DialMediaRouteProvider> provider_;
std::vector<MediaSinkInternal> cached_sinks_;
DISALLOW_COPY_AND_ASSIGN(DialMediaRouteProviderTest);
};
TEST_F(DialMediaRouteProviderTest, TestAddRemoveSinkQuery) {
std::string youtube_source("cast-dial:YouTube");
EXPECT_CALL(mock_service_, StartMonitoringAvailableSinksForApp("YouTube", _));
EXPECT_CALL(mock_service_, GetCachedAvailableSinks("YouTube"))
.WillOnce(Return(cached_sinks_));
provider_->StartObservingMediaSinks(youtube_source);
MediaSinkInternal sink_internal = CreateDialSink(1);
std::vector<MediaSinkInternal> sinks{sink_internal};
std::vector<url::Origin> youtube_origins = {
url::Origin::Create(GURL("https://tv.youtube.com")),
url::Origin::Create(GURL("https://tv-green-qa.youtube.com")),
url::Origin::Create(GURL("https://tv-release-qa.youtube.com")),
url::Origin::Create(GURL("https://web-green-qa.youtube.com")),
url::Origin::Create(GURL("https://web-release-qa.youtube.com")),
url::Origin::Create(GURL("https://www.youtube.com"))};
EXPECT_CALL(mock_router_,
OnSinksReceived(MediaRouteProviderId::DIAL, youtube_source, sinks,
youtube_origins));
provider_->OnAvailableSinksUpdated("YouTube", sinks);
base::RunLoop().RunUntilIdle();
EXPECT_CALL(mock_router_, OnSinksReceived(_, _, _, _)).Times(0);
provider_->StopObservingMediaSinks(youtube_source);
provider_->OnAvailableSinksUpdated("YouTube", sinks);
base::RunLoop().RunUntilIdle();
}
TEST_F(DialMediaRouteProviderTest, TestAddSinkQuerySameMediaSource) {
std::string youtube_source("cast-dial:YouTube");
EXPECT_CALL(mock_service_, StartMonitoringAvailableSinksForApp("YouTube", _));
EXPECT_CALL(mock_service_, GetCachedAvailableSinks("YouTube"))
.WillOnce(Return(cached_sinks_));
provider_->StartObservingMediaSinks(youtube_source);
EXPECT_CALL(mock_service_, StartMonitoringAvailableSinksForApp(_, _))
.Times(0);
provider_->StartObservingMediaSinks(youtube_source);
EXPECT_CALL(mock_router_, OnSinksReceived(_, _, _, _)).Times(0);
provider_->StopObservingMediaSinks(youtube_source);
provider_->StopObservingMediaSinks(youtube_source);
provider_->OnAvailableSinksUpdated("YouTube",
std::vector<MediaSinkInternal>());
base::RunLoop().RunUntilIdle();
}
TEST_F(DialMediaRouteProviderTest,
TestAddSinkQuerySameAppDifferentMediaSources) {
std::string youtube_source1("cast-dial:YouTube");
std::string youtube_source2("cast-dial:YouTube?clientId=15178573373126446");
EXPECT_CALL(mock_service_, StartMonitoringAvailableSinksForApp("YouTube", _));
EXPECT_CALL(mock_service_, GetCachedAvailableSinks("YouTube"))
.WillOnce(Return(cached_sinks_));
provider_->StartObservingMediaSinks(youtube_source1);
MediaSinkInternal sink_internal = CreateDialSink(1);
std::vector<MediaSinkInternal> sinks{sink_internal};
EXPECT_CALL(mock_service_, GetCachedAvailableSinks("YouTube"))
.WillOnce(Return(sinks));
EXPECT_CALL(mock_service_, StartMonitoringAvailableSinksForApp(_, _))
.Times(0);
EXPECT_CALL(mock_router_, OnSinksReceived(MediaRouteProviderId::DIAL,
youtube_source2, sinks, _));
provider_->StartObservingMediaSinks(youtube_source2);
base::RunLoop().RunUntilIdle();
EXPECT_CALL(mock_router_, OnSinksReceived(MediaRouteProviderId::DIAL,
youtube_source1, sinks, _));
EXPECT_CALL(mock_router_, OnSinksReceived(MediaRouteProviderId::DIAL,
youtube_source2, sinks, _));
provider_->OnAvailableSinksUpdated("YouTube", sinks);
base::RunLoop().RunUntilIdle();
EXPECT_CALL(mock_router_, OnSinksReceived(MediaRouteProviderId::DIAL,
youtube_source2, sinks, _));
provider_->StopObservingMediaSinks(youtube_source1);
provider_->OnAvailableSinksUpdated("YouTube", sinks);
base::RunLoop().RunUntilIdle();
EXPECT_CALL(mock_router_, OnSinksReceived(_, _, _, _)).Times(0);
provider_->StopObservingMediaSinks(youtube_source2);
provider_->OnAvailableSinksUpdated("YouTube", sinks);
base::RunLoop().RunUntilIdle();
}
TEST_F(DialMediaRouteProviderTest, TestAddSinkQueryDifferentApps) {
std::string youtube_source("cast-dial:YouTube");
std::string netflix_source("cast-dial:Netflix");
EXPECT_CALL(mock_service_, StartMonitoringAvailableSinksForApp("YouTube", _));
EXPECT_CALL(mock_service_, GetCachedAvailableSinks("YouTube"))
.WillOnce(Return(cached_sinks_));
provider_->StartObservingMediaSinks(youtube_source);
EXPECT_CALL(mock_service_, StartMonitoringAvailableSinksForApp("Netflix", _));
EXPECT_CALL(mock_service_, GetCachedAvailableSinks("Netflix"))
.WillOnce(Return(cached_sinks_));
provider_->StartObservingMediaSinks(netflix_source);
MediaSinkInternal sink_internal = CreateDialSink(1);
std::vector<MediaSinkInternal> sinks{sink_internal};
EXPECT_CALL(mock_router_, OnSinksReceived(MediaRouteProviderId::DIAL,
youtube_source, sinks, _));
provider_->OnAvailableSinksUpdated("YouTube", sinks);
base::RunLoop().RunUntilIdle();
EXPECT_CALL(mock_router_, OnSinksReceived(MediaRouteProviderId::DIAL,
netflix_source, sinks, _));
provider_->OnAvailableSinksUpdated("Netflix", sinks);
base::RunLoop().RunUntilIdle();
provider_->StopObservingMediaSinks(youtube_source);
provider_->StopObservingMediaSinks(netflix_source);
EXPECT_CALL(mock_router_, OnSinksReceived(_, _, _, _)).Times(0);
provider_->OnAvailableSinksUpdated("YouTube", sinks);
provider_->OnAvailableSinksUpdated("Netflix", sinks);
base::RunLoop().RunUntilIdle();
}
} // namespace media_router
......@@ -17,8 +17,6 @@ class NoopDualMediaSinkService : public DualMediaSinkService {
// DualMediaSinkService
void OnUserGesture() override {}
void StartMdnsDiscovery() override {}
void RegisterMediaSinksObserver(MediaSinksObserver* observer) override {}
void UnregisterMediaSinksObserver(MediaSinksObserver* observer) override {}
private:
DISALLOW_COPY_AND_ASSIGN(NoopDualMediaSinkService);
......
......@@ -89,7 +89,8 @@ MediaSinkInternal CreateDialSink(int num) {
net::IPEndPoint ip_endpoint = CreateIPEndPoint(num);
media_router::MediaSink sink(unique_id, friendly_name,
media_router::SinkIconType::GENERIC);
media_router::SinkIconType::GENERIC,
MediaRouteProviderId::EXTENSION);
media_router::DialSinkExtraData extra_data;
extra_data.ip_address = ip_endpoint.address();
extra_data.model_name = base::StringPrintf("model name %d", num);
......
......@@ -126,6 +126,12 @@ class MockDialMediaSinkService : public DialMediaSinkService {
void(const OnSinksDiscoveredCallback&,
const OnDialSinkAddedCallback&));
MOCK_METHOD0(OnUserGesture, void());
MOCK_METHOD2(StartMonitoringAvailableSinksForApp,
DialMediaSinkService::SinkQueryByAppSubscription(
const std::string&,
const SinkQueryByAppCallback&));
MOCK_METHOD1(GetCachedAvailableSinks,
std::vector<MediaSinkInternal>(const std::string& app_name));
};
class MockCastMediaSinkService : public CastMediaSinkService {
......
......@@ -16,6 +16,8 @@ const char* ProviderIdToString(MediaRouteProviderId provider_id) {
return "WIRED_DISPLAY";
case CAST:
return "CAST";
case DIAL:
return "DIAL";
case UNKNOWN:
return "UNKNOWN";
}
......
......@@ -16,6 +16,7 @@ enum MediaRouteProviderId {
EXTENSION,
WIRED_DISPLAY,
CAST,
DIAL,
UNKNOWN // New values must be added above this value.
};
......
......@@ -33,6 +33,10 @@ bool MediaSource::operator==(const MediaSource& other) const {
return id_ == other.id();
}
bool MediaSource::operator<(const MediaSource& other) const {
return id_ < other.id();
}
std::string MediaSource::ToString() const {
return "MediaSource[" + id_ + "]";
}
......
......@@ -37,6 +37,8 @@ class MediaSource {
// Returns true if two MediaSource objects use the same media ID.
bool operator==(const MediaSource& other) const;
bool operator<(const MediaSource& other) const;
// Used for logging.
std::string ToString() const;
......
......@@ -247,7 +247,8 @@ interface MediaRouteProvider {
enum Id {
EXTENSION,
WIRED_DISPLAY,
CAST
CAST,
DIAL
};
// Creates a media route from |media_source| to the sink given by |sink_id|.
......
......@@ -628,6 +628,8 @@ struct EnumTraits<media_router::mojom::MediaRouteProvider::Id,
return media_router::mojom::MediaRouteProvider::Id::WIRED_DISPLAY;
case media_router::MediaRouteProviderId::CAST:
return media_router::mojom::MediaRouteProvider::Id::CAST;
case media_router::MediaRouteProviderId::DIAL:
return media_router::mojom::MediaRouteProvider::Id::DIAL;
case media_router::MediaRouteProviderId::UNKNOWN:
break;
}
......@@ -648,6 +650,9 @@ struct EnumTraits<media_router::mojom::MediaRouteProvider::Id,
case media_router::mojom::MediaRouteProvider::Id::CAST:
*provider_id = media_router::MediaRouteProviderId::CAST;
return true;
case media_router::mojom::MediaRouteProvider::Id::DIAL:
*provider_id = media_router::MediaRouteProviderId::DIAL;
return true;
}
return false;
}
......
......@@ -3070,6 +3070,7 @@ test("unit_tests") {
"../browser/media/router/providers/cast/cast_app_discovery_service_unittest.cc",
"../browser/media/router/providers/cast/cast_media_route_provider_metrics_unittest.cc",
"../browser/media/router/providers/cast/dual_media_sink_service_unittest.cc",
"../browser/media/router/providers/dial/dial_media_route_provider_unittest.cc",
"../browser/media/router/providers/extension/extension_media_route_provider_proxy_unittest.cc",
"../browser/media/router/providers/wired_display/wired_display_media_route_provider_unittest.cc",
"../browser/policy/local_sync_policy_handler_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