mirror of
https://bitbucket.org/chromiumembedded/cef
synced 2025-06-05 21:39:12 +02:00
Add support for media device discovery and messaging (fixes issue #2900)
Chromium supports communication with media devices on the local network via the Cast and DIAL protocols. This takes two primary forms: 1. Messaging, where strings representing state information are passed between the client and a dedicated receiver app on the media device. The receiver app communicates directly with an app-specific backend service to retrieve and possibly control media playback. 2. Tab/desktop mirroring, where the media contents are streamed directly from the browser to a generic streaming app on the media device and playback is controlled by the browser. This change adds support for device discovery and messaging (but not mirroring) with functionality exposed via the new CefMediaRouter interface. To test: Navigate to http://tests/media_router in cefclient and follow the on-screen instructions.
This commit is contained in:
83
libcef/browser/media_router/media_route_impl.cc
Normal file
83
libcef/browser/media_router/media_route_impl.cc
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright (c) 2020 The Chromium Embedded Framework Authors. All rights
|
||||
// reserved. Use of this source code is governed by a BSD-style license that
|
||||
// can be found in the LICENSE file.
|
||||
|
||||
#include "libcef/browser/media_router/media_route_impl.h"
|
||||
|
||||
#include "libcef/browser/media_router/media_router_manager.h"
|
||||
#include "libcef/browser/media_router/media_sink_impl.h"
|
||||
#include "libcef/browser/media_router/media_source_impl.h"
|
||||
#include "libcef/browser/thread_util.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Do not keep a reference to the object returned by this method.
|
||||
CefBrowserContext* GetBrowserContext(const CefBrowserContext::Getter& getter) {
|
||||
CEF_REQUIRE_UIT();
|
||||
DCHECK(!getter.is_null());
|
||||
|
||||
// Will return nullptr if the BrowserContext has been destroyed.
|
||||
return getter.Run();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CefMediaRouteImpl::CefMediaRouteImpl(
|
||||
const media_router::MediaRoute& route,
|
||||
const CefBrowserContext::Getter& browser_context_getter)
|
||||
: route_(route), browser_context_getter_(browser_context_getter) {
|
||||
CEF_REQUIRE_UIT();
|
||||
}
|
||||
|
||||
CefString CefMediaRouteImpl::GetId() {
|
||||
return route_.media_route_id();
|
||||
}
|
||||
|
||||
CefRefPtr<CefMediaSource> CefMediaRouteImpl::GetSource() {
|
||||
return new CefMediaSourceImpl(route_.media_source().id());
|
||||
}
|
||||
|
||||
CefRefPtr<CefMediaSink> CefMediaRouteImpl::GetSink() {
|
||||
return new CefMediaSinkImpl(route_.media_sink_id(), route_.media_sink_name());
|
||||
}
|
||||
|
||||
void CefMediaRouteImpl::SendRouteMessage(const void* message,
|
||||
size_t message_size) {
|
||||
std::string message_str(reinterpret_cast<const char*>(message), message_size);
|
||||
|
||||
if (!CEF_CURRENTLY_ON_UIT()) {
|
||||
CEF_POST_TASK(
|
||||
CEF_UIT,
|
||||
base::BindOnce(
|
||||
[](CefRefPtr<CefMediaRouteImpl> self, std::string message_str) {
|
||||
self->SendRouteMessageInternal(std::move(message_str));
|
||||
},
|
||||
CefRefPtr<CefMediaRouteImpl>(this), std::move(message_str)));
|
||||
return;
|
||||
}
|
||||
|
||||
SendRouteMessageInternal(std::move(message_str));
|
||||
}
|
||||
|
||||
void CefMediaRouteImpl::Terminate() {
|
||||
if (!CEF_CURRENTLY_ON_UIT()) {
|
||||
CEF_POST_TASK(CEF_UIT, base::BindOnce(&CefMediaRouteImpl::Terminate, this));
|
||||
return;
|
||||
}
|
||||
|
||||
auto browser_context = GetBrowserContext(browser_context_getter_);
|
||||
if (!browser_context)
|
||||
return;
|
||||
|
||||
browser_context->GetMediaRouterManager()->TerminateRoute(
|
||||
route_.media_route_id());
|
||||
}
|
||||
|
||||
void CefMediaRouteImpl::SendRouteMessageInternal(std::string message) {
|
||||
auto browser_context = GetBrowserContext(browser_context_getter_);
|
||||
if (!browser_context)
|
||||
return;
|
||||
|
||||
browser_context->GetMediaRouterManager()->SendRouteMessage(
|
||||
route_.media_route_id(), message);
|
||||
}
|
40
libcef/browser/media_router/media_route_impl.h
Normal file
40
libcef/browser/media_router/media_route_impl.h
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2020 The Chromium Embedded Framework Authors. All rights
|
||||
// reserved. Use of this source code is governed by a BSD-style license that
|
||||
// can be found in the LICENSE file.
|
||||
|
||||
#ifndef CEF_LIBCEF_BROWSER_MEDIA_ROUTER_MEDIA_ROUTE_IMPL_H_
|
||||
#define CEF_LIBCEF_BROWSER_MEDIA_ROUTER_MEDIA_ROUTE_IMPL_H_
|
||||
#pragma once
|
||||
|
||||
#include "include/cef_media_router.h"
|
||||
#include "libcef/browser/browser_context.h"
|
||||
|
||||
#include "chrome/common/media_router/media_route.h"
|
||||
|
||||
// Implementation of the CefMediaRoute interface. Only created on the UI thread.
|
||||
class CefMediaRouteImpl : public CefMediaRoute {
|
||||
public:
|
||||
CefMediaRouteImpl(const media_router::MediaRoute& route,
|
||||
const CefBrowserContext::Getter& browser_context_getter);
|
||||
|
||||
// CefMediaRoute methods.
|
||||
CefString GetId() override;
|
||||
CefRefPtr<CefMediaSource> GetSource() override;
|
||||
CefRefPtr<CefMediaSink> GetSink() override;
|
||||
void SendRouteMessage(const void* message, size_t message_size) override;
|
||||
void Terminate() override;
|
||||
|
||||
const media_router::MediaRoute& route() const { return route_; }
|
||||
|
||||
private:
|
||||
void SendRouteMessageInternal(std::string message);
|
||||
|
||||
// Read-only after creation.
|
||||
const media_router::MediaRoute route_;
|
||||
const CefBrowserContext::Getter browser_context_getter_;
|
||||
|
||||
IMPLEMENT_REFCOUNTING(CefMediaRouteImpl);
|
||||
DISALLOW_COPY_AND_ASSIGN(CefMediaRouteImpl);
|
||||
};
|
||||
|
||||
#endif // CEF_LIBCEF_BROWSER_MEDIA_ROUTER_MEDIA_ROUTE_IMPL_H_
|
290
libcef/browser/media_router/media_router_impl.cc
Normal file
290
libcef/browser/media_router/media_router_impl.cc
Normal file
@ -0,0 +1,290 @@
|
||||
// Copyright (c) 2020 The Chromium Embedded Framework Authors. All rights
|
||||
// reserved. Use of this source code is governed by a BSD-style license that
|
||||
// can be found in the LICENSE file.
|
||||
|
||||
#include "libcef/browser/media_router/media_router_impl.h"
|
||||
|
||||
#include "libcef/browser/media_router/media_route_impl.h"
|
||||
#include "libcef/browser/media_router/media_router_manager.h"
|
||||
#include "libcef/browser/media_router/media_sink_impl.h"
|
||||
#include "libcef/browser/media_router/media_source_impl.h"
|
||||
#include "libcef/browser/thread_util.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Do not keep a reference to the object returned by this method.
|
||||
CefBrowserContext* GetBrowserContext(const CefBrowserContext::Getter& getter) {
|
||||
CEF_REQUIRE_UIT();
|
||||
DCHECK(!getter.is_null());
|
||||
|
||||
// Will return nullptr if the BrowserContext has been destroyed.
|
||||
return getter.Run();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class CefRegistrationImpl : public CefRegistration,
|
||||
public CefMediaRouterManager::Observer {
|
||||
public:
|
||||
explicit CefRegistrationImpl(CefRefPtr<CefMediaObserver> observer)
|
||||
: observer_(observer) {
|
||||
DCHECK(observer_);
|
||||
}
|
||||
|
||||
~CefRegistrationImpl() override {
|
||||
CEF_REQUIRE_UIT();
|
||||
|
||||
// May be null if OnMediaRouterDestroyed was called.
|
||||
if (browser_context_getter_.is_null())
|
||||
return;
|
||||
|
||||
auto browser_context = GetBrowserContext(browser_context_getter_);
|
||||
if (!browser_context)
|
||||
return;
|
||||
|
||||
browser_context->GetMediaRouterManager()->RemoveObserver(this);
|
||||
}
|
||||
|
||||
void Initialize(const CefBrowserContext::Getter& browser_context_getter) {
|
||||
CEF_REQUIRE_UIT();
|
||||
DCHECK(!browser_context_getter.is_null());
|
||||
DCHECK(browser_context_getter_.is_null());
|
||||
browser_context_getter_ = browser_context_getter;
|
||||
|
||||
auto browser_context = GetBrowserContext(browser_context_getter_);
|
||||
if (!browser_context)
|
||||
return;
|
||||
|
||||
browser_context->GetMediaRouterManager()->AddObserver(this);
|
||||
}
|
||||
|
||||
private:
|
||||
// CefMediaRouterManager::Observer methods:
|
||||
void OnMediaRouterDestroyed() override { browser_context_getter_.Reset(); }
|
||||
|
||||
void OnMediaSinks(
|
||||
const CefMediaRouterManager::MediaSinkVector& sinks) override {
|
||||
std::vector<CefRefPtr<CefMediaSink>> cef_sinks;
|
||||
for (const auto& sink : sinks) {
|
||||
cef_sinks.push_back(new CefMediaSinkImpl(sink.sink));
|
||||
}
|
||||
observer_->OnSinks(cef_sinks);
|
||||
}
|
||||
|
||||
void OnMediaRoutes(
|
||||
const CefMediaRouterManager::MediaRouteVector& routes) override {
|
||||
std::vector<CefRefPtr<CefMediaRoute>> cef_routes;
|
||||
for (const auto& route : routes) {
|
||||
cef_routes.push_back(MakeCefRoute(route));
|
||||
}
|
||||
observer_->OnRoutes(cef_routes);
|
||||
}
|
||||
|
||||
void OnMediaRouteMessages(
|
||||
const media_router::MediaRoute& route,
|
||||
const CefMediaRouterManager::MediaMessageVector& messages) override {
|
||||
CefRefPtr<CefMediaRoute> cef_route = MakeCefRoute(route);
|
||||
for (const auto& message : messages) {
|
||||
if (message->type == media_router::mojom::RouteMessage::Type::TEXT) {
|
||||
if (message->message.has_value()) {
|
||||
const std::string& str = *(message->message);
|
||||
observer_->OnRouteMessageReceived(cef_route, str.c_str(), str.size());
|
||||
}
|
||||
} else if (message->type ==
|
||||
media_router::mojom::RouteMessage::Type::BINARY) {
|
||||
if (message->data.has_value()) {
|
||||
const std::vector<uint8_t>& data = *(message->data);
|
||||
observer_->OnRouteMessageReceived(cef_route, data.data(),
|
||||
data.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnMediaRouteStateChange(
|
||||
const media_router::MediaRoute& route,
|
||||
const content::PresentationConnectionStateChangeInfo& info) override {
|
||||
observer_->OnRouteStateChanged(MakeCefRoute(route),
|
||||
ToConnectionState(info.state));
|
||||
}
|
||||
|
||||
CefRefPtr<CefMediaRoute> MakeCefRoute(const media_router::MediaRoute& route) {
|
||||
return new CefMediaRouteImpl(route, browser_context_getter_);
|
||||
}
|
||||
|
||||
static CefMediaObserver::ConnectionState ToConnectionState(
|
||||
blink::mojom::PresentationConnectionState state) {
|
||||
switch (state) {
|
||||
case blink::mojom::PresentationConnectionState::CONNECTING:
|
||||
return CEF_MRCS_CONNECTING;
|
||||
case blink::mojom::PresentationConnectionState::CONNECTED:
|
||||
return CEF_MRCS_CONNECTED;
|
||||
case blink::mojom::PresentationConnectionState::CLOSED:
|
||||
return CEF_MRCS_CLOSED;
|
||||
case blink::mojom::PresentationConnectionState::TERMINATED:
|
||||
return CEF_MRCS_TERMINATED;
|
||||
}
|
||||
NOTREACHED();
|
||||
return CEF_MRCS_UNKNOWN;
|
||||
}
|
||||
|
||||
CefRefPtr<CefMediaObserver> observer_;
|
||||
CefBrowserContext::Getter browser_context_getter_;
|
||||
|
||||
IMPLEMENT_REFCOUNTING_DELETE_ON_UIT(CefRegistrationImpl);
|
||||
DISALLOW_COPY_AND_ASSIGN(CefRegistrationImpl);
|
||||
};
|
||||
|
||||
CefMediaRouterImpl::CefMediaRouterImpl() {
|
||||
// Verify that our enum matches Chromium's values.
|
||||
static_assert(
|
||||
static_cast<int>(CEF_MRCR_TOTAL_COUNT) ==
|
||||
static_cast<int>(media_router::RouteRequestResult::TOTAL_COUNT),
|
||||
"enum mismatch");
|
||||
}
|
||||
|
||||
void CefMediaRouterImpl::Initialize(
|
||||
const CefBrowserContext::Getter& browser_context_getter) {
|
||||
CEF_REQUIRE_UIT();
|
||||
DCHECK(!browser_context_getter.is_null());
|
||||
DCHECK(browser_context_getter_.is_null());
|
||||
browser_context_getter_ = browser_context_getter;
|
||||
}
|
||||
|
||||
CefRefPtr<CefRegistration> CefMediaRouterImpl::AddObserver(
|
||||
CefRefPtr<CefMediaObserver> observer) {
|
||||
if (!observer)
|
||||
return nullptr;
|
||||
CefRefPtr<CefRegistrationImpl> registration =
|
||||
new CefRegistrationImpl(observer);
|
||||
InitializeRegistrationOnUIThread(registration);
|
||||
return registration.get();
|
||||
}
|
||||
|
||||
CefRefPtr<CefMediaSource> CefMediaRouterImpl::GetSource(const CefString& urn) {
|
||||
if (urn.empty())
|
||||
return nullptr;
|
||||
|
||||
// Check for a valid URL and supported Cast/DIAL schemes.
|
||||
GURL presentation_url(urn.ToString());
|
||||
if (!media_router::IsValidPresentationUrl(presentation_url)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (presentation_url.SchemeIsHTTPOrHTTPS()) {
|
||||
// We don't support tab/desktop mirroring, which is what Cast uses for
|
||||
// arbitrary HTTP/HTTPS URLs (see CastMediaSource).
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new CefMediaSourceImpl(presentation_url);
|
||||
}
|
||||
|
||||
void CefMediaRouterImpl::NotifyCurrentSinks() {
|
||||
if (!CEF_CURRENTLY_ON_UIT()) {
|
||||
CEF_POST_TASK(
|
||||
CEF_UIT, base::BindOnce(&CefMediaRouterImpl::NotifyCurrentSinks, this));
|
||||
return;
|
||||
}
|
||||
|
||||
auto browser_context = GetBrowserContext(browser_context_getter_);
|
||||
if (!browser_context)
|
||||
return;
|
||||
|
||||
browser_context->GetMediaRouterManager()->NotifyCurrentSinks();
|
||||
}
|
||||
|
||||
void CefMediaRouterImpl::CreateRoute(
|
||||
CefRefPtr<CefMediaSource> source,
|
||||
CefRefPtr<CefMediaSink> sink,
|
||||
CefRefPtr<CefMediaRouteCreateCallback> callback) {
|
||||
if (!CEF_CURRENTLY_ON_UIT()) {
|
||||
CEF_POST_TASK(CEF_UIT, base::BindOnce(&CefMediaRouterImpl::CreateRoute,
|
||||
this, source, sink, callback));
|
||||
return;
|
||||
}
|
||||
|
||||
std::string error;
|
||||
|
||||
auto browser_context = GetBrowserContext(browser_context_getter_);
|
||||
if (!browser_context) {
|
||||
error = "Context has already been destroyed";
|
||||
} else if (!source || !source->IsValid()) {
|
||||
error = "Source is empty or invalid";
|
||||
} else if (!sink || !sink->IsValid()) {
|
||||
error = "Sink is empty or invalid";
|
||||
} else if (!sink->IsCompatibleWith(source)) {
|
||||
error = "Sink is not compatible with source";
|
||||
}
|
||||
|
||||
if (!error.empty()) {
|
||||
LOG(WARNING) << "Media route creation failed: " << error;
|
||||
if (callback) {
|
||||
callback->OnMediaRouteCreateFinished(CEF_MRCR_UNKNOWN_ERROR, error,
|
||||
nullptr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto source_impl = static_cast<CefMediaSourceImpl*>(source.get());
|
||||
auto sink_impl = static_cast<CefMediaSinkImpl*>(sink.get());
|
||||
|
||||
browser_context->GetMediaRouterManager()->CreateRoute(
|
||||
source_impl->source().id(), sink_impl->sink().id(), url::Origin(),
|
||||
base::BindOnce(&CefMediaRouterImpl::CreateRouteCallback, this, callback));
|
||||
}
|
||||
|
||||
void CefMediaRouterImpl::NotifyCurrentRoutes() {
|
||||
if (!CEF_CURRENTLY_ON_UIT()) {
|
||||
CEF_POST_TASK(CEF_UIT, base::BindOnce(
|
||||
&CefMediaRouterImpl::NotifyCurrentRoutes, this));
|
||||
return;
|
||||
}
|
||||
|
||||
auto browser_context = GetBrowserContext(browser_context_getter_);
|
||||
if (!browser_context)
|
||||
return;
|
||||
|
||||
browser_context->GetMediaRouterManager()->NotifyCurrentRoutes();
|
||||
}
|
||||
|
||||
void CefMediaRouterImpl::InitializeRegistrationOnUIThread(
|
||||
CefRefPtr<CefRegistrationImpl> registration) {
|
||||
if (!CEF_CURRENTLY_ON_UIT()) {
|
||||
CEF_POST_TASK(
|
||||
CEF_UIT,
|
||||
base::BindOnce(&CefMediaRouterImpl::InitializeRegistrationOnUIThread,
|
||||
this, registration));
|
||||
return;
|
||||
}
|
||||
registration->Initialize(browser_context_getter_);
|
||||
}
|
||||
|
||||
void CefMediaRouterImpl::CreateRouteCallback(
|
||||
CefRefPtr<CefMediaRouteCreateCallback> callback,
|
||||
const media_router::RouteRequestResult& result) {
|
||||
CEF_REQUIRE_UIT();
|
||||
|
||||
if (result.result_code() != media_router::RouteRequestResult::OK) {
|
||||
LOG(WARNING) << "Media route creation failed: " << result.error() << " ("
|
||||
<< result.result_code() << ")";
|
||||
}
|
||||
|
||||
if (!callback)
|
||||
return;
|
||||
|
||||
CefRefPtr<CefMediaRoute> route;
|
||||
if (result.result_code() == media_router::RouteRequestResult::OK &&
|
||||
result.route()) {
|
||||
route = new CefMediaRouteImpl(*result.route(), browser_context_getter_);
|
||||
}
|
||||
|
||||
callback->OnMediaRouteCreateFinished(
|
||||
static_cast<cef_media_route_create_result_t>(result.result_code()),
|
||||
result.error(), route);
|
||||
}
|
||||
|
||||
// static
|
||||
CefRefPtr<CefMediaRouter> CefMediaRouter::GetGlobalMediaRouter() {
|
||||
return CefRequestContext::GetGlobalContext()->GetMediaRouter();
|
||||
}
|
49
libcef/browser/media_router/media_router_impl.h
Normal file
49
libcef/browser/media_router/media_router_impl.h
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2020 The Chromium Embedded Framework Authors. All rights
|
||||
// reserved. Use of this source code is governed by a BSD-style license that
|
||||
// can be found in the LICENSE file.
|
||||
|
||||
#ifndef CEF_LIBCEF_BROWSER_MEDIA_ROUTER_MEDIA_ROUTER_IMPL_H_
|
||||
#define CEF_LIBCEF_BROWSER_MEDIA_ROUTER_MEDIA_ROUTER_IMPL_H_
|
||||
#pragma once
|
||||
|
||||
#include "include/cef_media_router.h"
|
||||
#include "libcef/browser/browser_context.h"
|
||||
|
||||
#include "chrome/common/media_router/mojom/media_router.mojom.h"
|
||||
|
||||
class CefRegistrationImpl;
|
||||
|
||||
// Implementation of the CefMediaRouter interface. May be created on any thread.
|
||||
class CefMediaRouterImpl : public CefMediaRouter {
|
||||
public:
|
||||
CefMediaRouterImpl();
|
||||
|
||||
// Called on the UI thread after object creation and before any other object
|
||||
// methods are executed on the UI thread.
|
||||
void Initialize(const CefBrowserContext::Getter& browser_context_getter);
|
||||
|
||||
// CefMediaRouter methods.
|
||||
CefRefPtr<CefRegistration> AddObserver(
|
||||
CefRefPtr<CefMediaObserver> observer) override;
|
||||
CefRefPtr<CefMediaSource> GetSource(const CefString& urn) override;
|
||||
void NotifyCurrentSinks() override;
|
||||
void CreateRoute(CefRefPtr<CefMediaSource> source,
|
||||
CefRefPtr<CefMediaSink> sink,
|
||||
CefRefPtr<CefMediaRouteCreateCallback> callback) override;
|
||||
void NotifyCurrentRoutes() override;
|
||||
|
||||
private:
|
||||
void InitializeRegistrationOnUIThread(
|
||||
CefRefPtr<CefRegistrationImpl> registration);
|
||||
|
||||
void CreateRouteCallback(CefRefPtr<CefMediaRouteCreateCallback> callback,
|
||||
const media_router::RouteRequestResult& result);
|
||||
|
||||
// Only accessed on the UI thread. Will be non-null after Initialize().
|
||||
CefBrowserContext::Getter browser_context_getter_;
|
||||
|
||||
IMPLEMENT_REFCOUNTING(CefMediaRouterImpl);
|
||||
DISALLOW_COPY_AND_ASSIGN(CefMediaRouterImpl);
|
||||
};
|
||||
|
||||
#endif // CEF_LIBCEF_BROWSER_MEDIA_ROUTER_MEDIA_ROUTER_IMPL_H_
|
292
libcef/browser/media_router/media_router_manager.cc
Normal file
292
libcef/browser/media_router/media_router_manager.cc
Normal file
@ -0,0 +1,292 @@
|
||||
// Copyright (c) 2020 The Chromium Embedded Framework Authors. All rights
|
||||
// reserved. Use of this source code is governed by a BSD-style license that
|
||||
// can be found in the LICENSE file.
|
||||
|
||||
#include "libcef/browser/media_router/media_router_manager.h"
|
||||
|
||||
#include "libcef/browser/browser_context.h"
|
||||
#include "libcef/browser/thread_util.h"
|
||||
|
||||
#include "chrome/browser/media/router/media_router_factory.h"
|
||||
#include "chrome/browser/media/router/media_routes_observer.h"
|
||||
#include "chrome/browser/media/router/route_message_observer.h"
|
||||
#include "chrome/browser/media/router/route_message_util.h"
|
||||
|
||||
namespace {
|
||||
|
||||
const int kTimeoutMs = 5 * 1000;
|
||||
const char kDefaultPresentationUrl[] = "https://google.com";
|
||||
|
||||
} // namespace
|
||||
|
||||
class CefMediaRoutesObserver : public media_router::MediaRoutesObserver {
|
||||
public:
|
||||
explicit CefMediaRoutesObserver(CefMediaRouterManager* manager)
|
||||
: media_router::MediaRoutesObserver(manager->GetMediaRouter()),
|
||||
manager_(manager) {}
|
||||
|
||||
void OnRoutesUpdated(const std::vector<media_router::MediaRoute>& routes,
|
||||
const std::vector<media_router::MediaRoute::Id>&
|
||||
joinable_route_ids) override {
|
||||
manager_->routes_ = routes;
|
||||
manager_->NotifyCurrentRoutes();
|
||||
}
|
||||
|
||||
private:
|
||||
CefMediaRouterManager* const manager_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CefMediaRoutesObserver);
|
||||
};
|
||||
|
||||
// Used to receive messages if PresentationConnection is not supported.
|
||||
class CefRouteMessageObserver : public media_router::RouteMessageObserver {
|
||||
public:
|
||||
CefRouteMessageObserver(CefMediaRouterManager* manager,
|
||||
const media_router::MediaRoute& route)
|
||||
: media_router::RouteMessageObserver(manager->GetMediaRouter(),
|
||||
route.media_route_id()),
|
||||
manager_(manager),
|
||||
route_(route) {}
|
||||
|
||||
void OnMessagesReceived(
|
||||
CefMediaRouterManager::MediaMessageVector messages) override {
|
||||
manager_->OnMessagesReceived(route_, messages);
|
||||
}
|
||||
|
||||
private:
|
||||
CefMediaRouterManager* const manager_;
|
||||
const media_router::MediaRoute route_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CefRouteMessageObserver);
|
||||
};
|
||||
|
||||
// Used for messaging and route status notifications with Cast.
|
||||
class CefPresentationConnection : public blink::mojom::PresentationConnection {
|
||||
public:
|
||||
explicit CefPresentationConnection(
|
||||
CefMediaRouterManager* manager,
|
||||
const media_router::MediaRoute& route,
|
||||
media_router::mojom::RoutePresentationConnectionPtr connections)
|
||||
: manager_(manager),
|
||||
route_(route),
|
||||
connection_receiver_(this, std::move(connections->connection_receiver)),
|
||||
connection_remote_(std::move(connections->connection_remote)) {}
|
||||
|
||||
void OnMessage(
|
||||
blink::mojom::PresentationConnectionMessagePtr message) override {
|
||||
CefMediaRouterManager::MediaMessageVector messages;
|
||||
if (message->is_message()) {
|
||||
messages.push_back(media_router::message_util::RouteMessageFromString(
|
||||
message->get_message()));
|
||||
} else if (message->is_data()) {
|
||||
messages.push_back(media_router::message_util::RouteMessageFromData(
|
||||
message->get_data()));
|
||||
}
|
||||
if (!messages.empty()) {
|
||||
manager_->OnMessagesReceived(route_, messages);
|
||||
}
|
||||
}
|
||||
|
||||
void DidChangeState(
|
||||
blink::mojom::PresentationConnectionState state) override {
|
||||
// May result in |this| being deleted, so post async and allow the call
|
||||
// stack to unwind.
|
||||
CEF_POST_TASK(
|
||||
CEF_UIT,
|
||||
base::BindOnce(&CefMediaRouterManager::OnRouteStateChange,
|
||||
manager_->weak_ptr_factory_.GetWeakPtr(), route_,
|
||||
content::PresentationConnectionStateChangeInfo(state)));
|
||||
}
|
||||
|
||||
void DidClose(
|
||||
blink::mojom::PresentationConnectionCloseReason reason) override {
|
||||
DidChangeState(blink::mojom::PresentationConnectionState::CLOSED);
|
||||
}
|
||||
|
||||
void SendRouteMessage(const std::string& message) {
|
||||
connection_remote_->OnMessage(
|
||||
blink::mojom::PresentationConnectionMessage::NewMessage(message));
|
||||
}
|
||||
|
||||
private:
|
||||
CefMediaRouterManager* const manager_;
|
||||
const media_router::MediaRoute route_;
|
||||
|
||||
// Used to receive messages from the MRP.
|
||||
mojo::Receiver<blink::mojom::PresentationConnection> connection_receiver_;
|
||||
|
||||
// Used to send messages to the MRP.
|
||||
mojo::Remote<blink::mojom::PresentationConnection> connection_remote_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CefPresentationConnection);
|
||||
};
|
||||
|
||||
CefMediaRouterManager::CefMediaRouterManager(CefBrowserContext* browser_context)
|
||||
: browser_context_(browser_context),
|
||||
query_result_manager_(GetMediaRouter()),
|
||||
weak_ptr_factory_(this) {
|
||||
// Perform initialization.
|
||||
GetMediaRouter()->OnUserGesture();
|
||||
|
||||
query_result_manager_.AddObserver(this);
|
||||
|
||||
// A non-empty presentation URL to required for discovery of Cast devices.
|
||||
query_result_manager_.SetSourcesForCastMode(
|
||||
media_router::MediaCastMode::PRESENTATION,
|
||||
{media_router::MediaSource::ForPresentationUrl(
|
||||
GURL(kDefaultPresentationUrl))},
|
||||
url::Origin());
|
||||
|
||||
routes_observer_ = std::make_unique<CefMediaRoutesObserver>(this);
|
||||
}
|
||||
|
||||
CefMediaRouterManager::~CefMediaRouterManager() {
|
||||
CEF_REQUIRE_UIT();
|
||||
for (auto& observer : observers_) {
|
||||
observers_.RemoveObserver(&observer);
|
||||
observer.OnMediaRouterDestroyed();
|
||||
}
|
||||
|
||||
query_result_manager_.RemoveObserver(this);
|
||||
}
|
||||
|
||||
void CefMediaRouterManager::AddObserver(Observer* observer) {
|
||||
CEF_REQUIRE_UIT();
|
||||
observers_.AddObserver(observer);
|
||||
}
|
||||
|
||||
void CefMediaRouterManager::RemoveObserver(Observer* observer) {
|
||||
CEF_REQUIRE_UIT();
|
||||
observers_.RemoveObserver(observer);
|
||||
}
|
||||
|
||||
void CefMediaRouterManager::NotifyCurrentSinks() {
|
||||
CEF_REQUIRE_UIT();
|
||||
for (auto& observer : observers_) {
|
||||
observer.OnMediaSinks(sinks_);
|
||||
}
|
||||
}
|
||||
|
||||
void CefMediaRouterManager::NotifyCurrentRoutes() {
|
||||
CEF_REQUIRE_UIT();
|
||||
for (auto& observer : observers_) {
|
||||
observer.OnMediaRoutes(routes_);
|
||||
}
|
||||
}
|
||||
|
||||
void CefMediaRouterManager::CreateRoute(
|
||||
const media_router::MediaSource::Id& source_id,
|
||||
const media_router::MediaSink::Id& sink_id,
|
||||
const url::Origin& origin,
|
||||
CreateRouteResultCallback callback) {
|
||||
GetMediaRouter()->CreateRoute(
|
||||
source_id, sink_id, origin, nullptr /* web_contents */,
|
||||
base::BindOnce(&CefMediaRouterManager::OnCreateRoute,
|
||||
weak_ptr_factory_.GetWeakPtr(), std::move(callback)),
|
||||
base::TimeDelta::FromMilliseconds(kTimeoutMs), false /* incognito */);
|
||||
}
|
||||
|
||||
void CefMediaRouterManager::SendRouteMessage(
|
||||
const media_router::MediaRoute::Id& route_id,
|
||||
const std::string& message) {
|
||||
// Must use PresentationConnection to send messages if it exists.
|
||||
auto state = GetRouteState(route_id);
|
||||
if (state && state->presentation_connection_) {
|
||||
state->presentation_connection_->SendRouteMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
GetMediaRouter()->SendRouteMessage(route_id, message);
|
||||
}
|
||||
|
||||
void CefMediaRouterManager::TerminateRoute(
|
||||
const media_router::MediaRoute::Id& route_id) {
|
||||
GetMediaRouter()->TerminateRoute(route_id);
|
||||
}
|
||||
|
||||
void CefMediaRouterManager::OnResultsUpdated(const MediaSinkVector& sinks) {
|
||||
sinks_ = sinks;
|
||||
NotifyCurrentSinks();
|
||||
}
|
||||
|
||||
media_router::MediaRouter* CefMediaRouterManager::GetMediaRouter() const {
|
||||
CEF_REQUIRE_UIT();
|
||||
return media_router::MediaRouterFactory::GetApiForBrowserContext(
|
||||
browser_context_);
|
||||
}
|
||||
|
||||
void CefMediaRouterManager::OnCreateRoute(
|
||||
CreateRouteResultCallback callback,
|
||||
media_router::mojom::RoutePresentationConnectionPtr connection,
|
||||
const media_router::RouteRequestResult& result) {
|
||||
CEF_REQUIRE_UIT();
|
||||
if (result.route()) {
|
||||
CreateRouteState(*result.route(), std::move(connection));
|
||||
}
|
||||
|
||||
std::move(callback).Run(result);
|
||||
}
|
||||
|
||||
void CefMediaRouterManager::OnRouteStateChange(
|
||||
const media_router::MediaRoute& route,
|
||||
const content::PresentationConnectionStateChangeInfo& info) {
|
||||
CEF_REQUIRE_UIT();
|
||||
if (info.state == blink::mojom::PresentationConnectionState::CLOSED ||
|
||||
info.state == blink::mojom::PresentationConnectionState::TERMINATED) {
|
||||
RemoveRouteState(route.media_route_id());
|
||||
}
|
||||
|
||||
for (auto& observer : observers_) {
|
||||
observer.OnMediaRouteStateChange(route, info);
|
||||
}
|
||||
}
|
||||
|
||||
void CefMediaRouterManager::OnMessagesReceived(
|
||||
const media_router::MediaRoute& route,
|
||||
const MediaMessageVector& messages) {
|
||||
CEF_REQUIRE_UIT();
|
||||
for (auto& observer : observers_) {
|
||||
observer.OnMediaRouteMessages(route, messages);
|
||||
}
|
||||
}
|
||||
|
||||
void CefMediaRouterManager::CreateRouteState(
|
||||
const media_router::MediaRoute& route,
|
||||
media_router::mojom::RoutePresentationConnectionPtr connection) {
|
||||
const auto route_id = route.media_route_id();
|
||||
auto state = std::make_unique<RouteState>();
|
||||
|
||||
if (!connection.is_null()) {
|
||||
// PresentationConnection must be used for messaging and status
|
||||
// notifications if it exists.
|
||||
state->presentation_connection_ =
|
||||
std::make_unique<CefPresentationConnection>(this, route,
|
||||
std::move(connection));
|
||||
} else {
|
||||
// Fallback if PresentationConnection is not supported.
|
||||
state->message_observer_ =
|
||||
std::make_unique<CefRouteMessageObserver>(this, route);
|
||||
state->state_subscription_ =
|
||||
GetMediaRouter()->AddPresentationConnectionStateChangedCallback(
|
||||
route_id,
|
||||
base::BindRepeating(&CefMediaRouterManager::OnRouteStateChange,
|
||||
weak_ptr_factory_.GetWeakPtr(), route));
|
||||
}
|
||||
|
||||
route_state_map_.insert(std::make_pair(route_id, std::move(state)));
|
||||
}
|
||||
|
||||
CefMediaRouterManager::RouteState* CefMediaRouterManager::GetRouteState(
|
||||
const media_router::MediaRoute::Id& route_id) {
|
||||
const auto it = route_state_map_.find(route_id);
|
||||
if (it != route_state_map_.end())
|
||||
return it->second.get();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CefMediaRouterManager::RemoveRouteState(
|
||||
const media_router::MediaRoute::Id& route_id) {
|
||||
auto it = route_state_map_.find(route_id);
|
||||
if (it != route_state_map_.end())
|
||||
route_state_map_.erase(it);
|
||||
}
|
125
libcef/browser/media_router/media_router_manager.h
Normal file
125
libcef/browser/media_router/media_router_manager.h
Normal file
@ -0,0 +1,125 @@
|
||||
// Copyright (c) 2020 The Chromium Embedded Framework Authors. All rights
|
||||
// reserved. Use of this source code is governed by a BSD-style license that
|
||||
// can be found in the LICENSE file.
|
||||
|
||||
#ifndef CEF_LIBCEF_BROWSER_MEDIA_ROUTER_MEDIA_ROUTER_MANAGER_H_
|
||||
#define CEF_LIBCEF_BROWSER_MEDIA_ROUTER_MEDIA_ROUTER_MANAGER_H_
|
||||
#pragma once
|
||||
|
||||
#include "include/cef_media_router.h"
|
||||
#include "libcef/browser/browser_context.h"
|
||||
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/observer_list.h"
|
||||
#include "chrome/browser/media/router/media_router.h"
|
||||
#include "chrome/browser/ui/media_router/query_result_manager.h"
|
||||
#include "chrome/common/media_router/mojom/media_router.mojom.h"
|
||||
|
||||
class CefBrowserContext;
|
||||
class CefMediaRoutesObserver;
|
||||
class CefPresentationConnection;
|
||||
class CefRouteMessageObserver;
|
||||
|
||||
// Manages CEF usage of MediaRouter. Owned by CefBrowserContext and only
|
||||
// accessed on the UI thread.
|
||||
class CefMediaRouterManager
|
||||
: public media_router::QueryResultManager::Observer {
|
||||
public:
|
||||
using MediaRouteVector = std::vector<media_router::MediaRoute>;
|
||||
using MediaSinkVector = std::vector<media_router::MediaSinkWithCastModes>;
|
||||
using MediaMessageVector = std::vector<media_router::mojom::RouteMessagePtr>;
|
||||
|
||||
class Observer : public base::CheckedObserver {
|
||||
public:
|
||||
virtual void OnMediaRouterDestroyed() = 0;
|
||||
|
||||
virtual void OnMediaSinks(const MediaSinkVector& sinks) = 0;
|
||||
virtual void OnMediaRoutes(const MediaRouteVector& routes) = 0;
|
||||
|
||||
virtual void OnMediaRouteMessages(const media_router::MediaRoute& route,
|
||||
const MediaMessageVector& messages) = 0;
|
||||
virtual void OnMediaRouteStateChange(
|
||||
const media_router::MediaRoute& route,
|
||||
const content::PresentationConnectionStateChangeInfo& info) = 0;
|
||||
|
||||
protected:
|
||||
~Observer() override {}
|
||||
};
|
||||
|
||||
explicit CefMediaRouterManager(CefBrowserContext* browser_context);
|
||||
~CefMediaRouterManager() override;
|
||||
|
||||
// |observer| must outlive this object or be removed.
|
||||
void AddObserver(Observer* observer);
|
||||
void RemoveObserver(Observer* observer);
|
||||
|
||||
void NotifyCurrentSinks();
|
||||
void NotifyCurrentRoutes();
|
||||
|
||||
using CreateRouteResultCallback =
|
||||
base::OnceCallback<void(const media_router::RouteRequestResult& result)>;
|
||||
|
||||
void CreateRoute(const media_router::MediaSource::Id& source_id,
|
||||
const media_router::MediaSink::Id& sink_id,
|
||||
const url::Origin& origin,
|
||||
CreateRouteResultCallback callback);
|
||||
|
||||
void SendRouteMessage(const media_router::MediaRoute::Id& route_id,
|
||||
const std::string& message);
|
||||
void TerminateRoute(const media_router::MediaRoute::Id& route_id);
|
||||
|
||||
// QueryResultManager::Observer methods.
|
||||
void OnResultsUpdated(const MediaSinkVector& sinks) override;
|
||||
|
||||
private:
|
||||
friend class CefMediaRoutesObserver;
|
||||
friend class CefPresentationConnection;
|
||||
friend class CefRouteMessageObserver;
|
||||
|
||||
// Do not keep a reference to the object returned by this method.
|
||||
media_router::MediaRouter* GetMediaRouter() const;
|
||||
|
||||
void OnCreateRoute(
|
||||
CreateRouteResultCallback callback,
|
||||
media_router::mojom::RoutePresentationConnectionPtr connection,
|
||||
const media_router::RouteRequestResult& result);
|
||||
void OnRouteStateChange(
|
||||
const media_router::MediaRoute& route,
|
||||
const content::PresentationConnectionStateChangeInfo& info);
|
||||
void OnMessagesReceived(const media_router::MediaRoute& route,
|
||||
const MediaMessageVector& messages);
|
||||
|
||||
struct RouteState {
|
||||
std::unique_ptr<CefPresentationConnection> presentation_connection_;
|
||||
|
||||
// Used if there is no RoutePresentationConnectionPtr.
|
||||
std::unique_ptr<CefRouteMessageObserver> message_observer_;
|
||||
std::unique_ptr<media_router::PresentationConnectionStateSubscription>
|
||||
state_subscription_;
|
||||
};
|
||||
void CreateRouteState(
|
||||
const media_router::MediaRoute& route,
|
||||
media_router::mojom::RoutePresentationConnectionPtr connection);
|
||||
RouteState* GetRouteState(const media_router::MediaRoute::Id& route_id);
|
||||
void RemoveRouteState(const media_router::MediaRoute::Id& route_id);
|
||||
|
||||
CefBrowserContext* const browser_context_;
|
||||
|
||||
base::ObserverList<Observer> observers_;
|
||||
|
||||
media_router::QueryResultManager query_result_manager_;
|
||||
std::unique_ptr<CefMediaRoutesObserver> routes_observer_;
|
||||
|
||||
MediaRouteVector routes_;
|
||||
MediaSinkVector sinks_;
|
||||
|
||||
using RouteStateMap =
|
||||
std::map<media_router::MediaRoute::Id, std::unique_ptr<RouteState>>;
|
||||
RouteStateMap route_state_map_;
|
||||
|
||||
base::WeakPtrFactory<CefMediaRouterManager> weak_ptr_factory_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CefMediaRouterManager);
|
||||
};
|
||||
|
||||
#endif // CEF_LIBCEF_BROWSER_MEDIA_ROUTER_MEDIA_ROUTER_MANAGER_H_
|
46
libcef/browser/media_router/media_sink_impl.cc
Normal file
46
libcef/browser/media_router/media_sink_impl.cc
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright (c) 2020 The Chromium Embedded Framework Authors. All rights
|
||||
// reserved. Use of this source code is governed by a BSD-style license that
|
||||
// can be found in the LICENSE file.
|
||||
|
||||
#include "libcef/browser/media_router/media_sink_impl.h"
|
||||
|
||||
CefMediaSinkImpl::CefMediaSinkImpl(const media_router::MediaSink& sink)
|
||||
: sink_(sink) {}
|
||||
|
||||
CefMediaSinkImpl::CefMediaSinkImpl(const media_router::MediaSink::Id& sink_id,
|
||||
const std::string& sink_name)
|
||||
: sink_(sink_id, sink_name, media_router::SinkIconType::GENERIC) {}
|
||||
|
||||
CefString CefMediaSinkImpl::GetId() {
|
||||
return sink_.id();
|
||||
}
|
||||
|
||||
bool CefMediaSinkImpl::IsValid() {
|
||||
return true;
|
||||
}
|
||||
|
||||
CefString CefMediaSinkImpl::GetName() {
|
||||
return sink_.name();
|
||||
}
|
||||
|
||||
CefString CefMediaSinkImpl::GetDescription() {
|
||||
return sink_.description().value_or("");
|
||||
}
|
||||
|
||||
bool CefMediaSinkImpl::IsCastSink() {
|
||||
return sink_.provider_id() == media_router::CAST;
|
||||
}
|
||||
|
||||
bool CefMediaSinkImpl::IsDialSink() {
|
||||
return sink_.provider_id() == media_router::DIAL;
|
||||
}
|
||||
|
||||
bool CefMediaSinkImpl::IsCompatibleWith(CefRefPtr<CefMediaSource> source) {
|
||||
if (source) {
|
||||
if (IsCastSink())
|
||||
return source->IsCastSource();
|
||||
if (IsDialSink())
|
||||
return source->IsDialSource();
|
||||
}
|
||||
return false;
|
||||
}
|
39
libcef/browser/media_router/media_sink_impl.h
Normal file
39
libcef/browser/media_router/media_sink_impl.h
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2020 The Chromium Embedded Framework Authors. All rights
|
||||
// reserved. Use of this source code is governed by a BSD-style license that
|
||||
// can be found in the LICENSE file.
|
||||
|
||||
#ifndef CEF_LIBCEF_BROWSER_MEDIA_ROUTER_MEDIA_SINK_IMPL_H_
|
||||
#define CEF_LIBCEF_BROWSER_MEDIA_ROUTER_MEDIA_SINK_IMPL_H_
|
||||
#pragma once
|
||||
|
||||
#include "include/cef_media_router.h"
|
||||
|
||||
#include "chrome/common/media_router/media_sink.h"
|
||||
|
||||
// Implementation of the CefMediaSink interface. May be created on any thread.
|
||||
class CefMediaSinkImpl : public CefMediaSink {
|
||||
public:
|
||||
explicit CefMediaSinkImpl(const media_router::MediaSink& sink);
|
||||
CefMediaSinkImpl(const media_router::MediaSink::Id& sink_id,
|
||||
const std::string& sink_name);
|
||||
|
||||
// CefMediaSink methods.
|
||||
CefString GetId() override;
|
||||
bool IsValid() override;
|
||||
CefString GetName() override;
|
||||
CefString GetDescription() override;
|
||||
bool IsCastSink() override;
|
||||
bool IsDialSink() override;
|
||||
bool IsCompatibleWith(CefRefPtr<CefMediaSource> source) override;
|
||||
|
||||
const media_router::MediaSink& sink() const { return sink_; }
|
||||
|
||||
private:
|
||||
// Read-only after creation.
|
||||
const media_router::MediaSink sink_;
|
||||
|
||||
IMPLEMENT_REFCOUNTING(CefMediaSinkImpl);
|
||||
DISALLOW_COPY_AND_ASSIGN(CefMediaSinkImpl);
|
||||
};
|
||||
|
||||
#endif // CEF_LIBCEF_BROWSER_MEDIA_ROUTER_MEDIA_SINK_IMPL_H_
|
28
libcef/browser/media_router/media_source_impl.cc
Normal file
28
libcef/browser/media_router/media_source_impl.cc
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2020 The Chromium Embedded Framework Authors. All rights
|
||||
// reserved. Use of this source code is governed by a BSD-style license that
|
||||
// can be found in the LICENSE file.
|
||||
|
||||
#include "libcef/browser/media_router/media_source_impl.h"
|
||||
|
||||
CefMediaSourceImpl::CefMediaSourceImpl(
|
||||
const media_router::MediaSource::Id& source_id)
|
||||
: source_(source_id) {}
|
||||
|
||||
CefMediaSourceImpl::CefMediaSourceImpl(const GURL& presentation_url)
|
||||
: source_(presentation_url) {}
|
||||
|
||||
CefString CefMediaSourceImpl::GetId() {
|
||||
return source_.id();
|
||||
}
|
||||
|
||||
bool CefMediaSourceImpl::IsValid() {
|
||||
return source_.IsValid();
|
||||
}
|
||||
|
||||
bool CefMediaSourceImpl::IsCastSource() {
|
||||
return !IsDialSource();
|
||||
}
|
||||
|
||||
bool CefMediaSourceImpl::IsDialSource() {
|
||||
return source_.IsDialSource();
|
||||
}
|
35
libcef/browser/media_router/media_source_impl.h
Normal file
35
libcef/browser/media_router/media_source_impl.h
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2020 The Chromium Embedded Framework Authors. All rights
|
||||
// reserved. Use of this source code is governed by a BSD-style license that
|
||||
// can be found in the LICENSE file.
|
||||
|
||||
#ifndef CEF_LIBCEF_BROWSER_MEDIA_ROUTER_MEDIA_SOURCE_IMPL_H_
|
||||
#define CEF_LIBCEF_BROWSER_MEDIA_ROUTER_MEDIA_SOURCE_IMPL_H_
|
||||
#pragma once
|
||||
|
||||
#include "include/cef_media_router.h"
|
||||
|
||||
#include "chrome/common/media_router/media_source.h"
|
||||
|
||||
// Implementation of the CefMediaSource interface. May be created on any thread.
|
||||
class CefMediaSourceImpl : public CefMediaSource {
|
||||
public:
|
||||
explicit CefMediaSourceImpl(const media_router::MediaSource::Id& source_id);
|
||||
explicit CefMediaSourceImpl(const GURL& presentation_url);
|
||||
|
||||
// CefMediaSource methods.
|
||||
CefString GetId() override;
|
||||
bool IsValid() override;
|
||||
bool IsCastSource() override;
|
||||
bool IsDialSource() override;
|
||||
|
||||
const media_router::MediaSource& source() const { return source_; }
|
||||
|
||||
private:
|
||||
// Read-only after creation.
|
||||
const media_router::MediaSource source_;
|
||||
|
||||
IMPLEMENT_REFCOUNTING(CefMediaSourceImpl);
|
||||
DISALLOW_COPY_AND_ASSIGN(CefMediaSourceImpl);
|
||||
};
|
||||
|
||||
#endif // CEF_LIBCEF_BROWSER_MEDIA_ROUTER_MEDIA_SOURCE_IMPL_H_
|
Reference in New Issue
Block a user