// 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 shut down. return getter.Run(); } } // namespace class CefRegistrationImpl : public CefRegistration, public CefMediaRouterManager::Observer { public: explicit CefRegistrationImpl(CefRefPtr observer) : observer_(observer) { DCHECK(observer_); } CefRegistrationImpl(const CefRegistrationImpl&) = delete; CefRegistrationImpl& operator=(const CefRegistrationImpl&) = delete; ~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> 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> 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 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& 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 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; } DCHECK(false); return CEF_MRCS_UNKNOWN; } CefRefPtr observer_; CefBrowserContext::Getter browser_context_getter_; IMPLEMENT_REFCOUNTING_DELETE_ON_UIT(CefRegistrationImpl); }; CefMediaRouterImpl::CefMediaRouterImpl() = default; void CefMediaRouterImpl::Initialize( const CefBrowserContext::Getter& browser_context_getter, CefRefPtr callback) { CEF_REQUIRE_UIT(); DCHECK(!initialized_); DCHECK(!browser_context_getter.is_null()); DCHECK(browser_context_getter_.is_null()); browser_context_getter_ = browser_context_getter; initialized_ = true; if (!init_callbacks_.empty()) { for (auto& init_callback : init_callbacks_) { std::move(init_callback).Run(); } init_callbacks_.clear(); } if (callback) { // Execute client callback asynchronously for consistency. CEF_POST_TASK(CEF_UIT, base::BindOnce(&CefCompletionCallback::OnComplete, callback.get())); } } CefRefPtr CefMediaRouterImpl::AddObserver( CefRefPtr observer) { if (!observer) { return nullptr; } CefRefPtr registration = new CefRegistrationImpl(observer); StoreOrTriggerInitCallback(base::BindOnce( &CefMediaRouterImpl::InitializeRegistrationInternal, this, registration)); return registration.get(); } CefRefPtr 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() { StoreOrTriggerInitCallback( base::BindOnce(&CefMediaRouterImpl::NotifyCurrentSinksInternal, this)); } void CefMediaRouterImpl::CreateRoute( CefRefPtr source, CefRefPtr sink, CefRefPtr callback) { StoreOrTriggerInitCallback(base::BindOnce( &CefMediaRouterImpl::CreateRouteInternal, this, source, sink, callback)); } void CefMediaRouterImpl::NotifyCurrentRoutes() { StoreOrTriggerInitCallback( base::BindOnce(&CefMediaRouterImpl::NotifyCurrentRoutesInternal, this)); } void CefMediaRouterImpl::InitializeRegistrationInternal( CefRefPtr registration) { DCHECK(ValidContext()); registration->Initialize(browser_context_getter_); } void CefMediaRouterImpl::NotifyCurrentSinksInternal() { DCHECK(ValidContext()); auto browser_context = GetBrowserContext(browser_context_getter_); if (!browser_context) { return; } browser_context->GetMediaRouterManager()->NotifyCurrentSinks(); } void CefMediaRouterImpl::CreateRouteInternal( CefRefPtr source, CefRefPtr sink, CefRefPtr callback) { DCHECK(ValidContext()); std::string error; auto browser_context = GetBrowserContext(browser_context_getter_); if (!browser_context) { error = "Context is not valid"; } else if (!source) { error = "Source is empty or invalid"; } else if (!sink) { 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(source.get()); auto sink_impl = static_cast(sink.get()); browser_context->GetMediaRouterManager()->CreateRoute( source_impl->source().id(), sink_impl->sink().id(), url::Origin(), base::BindOnce(&CefMediaRouterImpl::CreateRouteCallback, this, callback)); } void CefMediaRouterImpl::NotifyCurrentRoutesInternal() { DCHECK(ValidContext()); auto browser_context = GetBrowserContext(browser_context_getter_); if (!browser_context) { return; } browser_context->GetMediaRouterManager()->NotifyCurrentRoutes(); } void CefMediaRouterImpl::CreateRouteCallback( CefRefPtr callback, const media_router::RouteRequestResult& result) { DCHECK(ValidContext()); if (result.result_code() != media_router::mojom::RouteRequestResultCode::OK) { LOG(WARNING) << "Media route creation failed: " << result.error() << " (" << result.result_code() << ")"; } if (!callback) { return; } CefRefPtr route; if (result.result_code() == media_router::mojom::RouteRequestResultCode::OK && result.route()) { route = new CefMediaRouteImpl(*result.route(), browser_context_getter_); } callback->OnMediaRouteCreateFinished( static_cast(result.result_code()), result.error(), route); } void CefMediaRouterImpl::StoreOrTriggerInitCallback( base::OnceClosure callback) { if (!CEF_CURRENTLY_ON_UIT()) { CEF_POST_TASK( CEF_UIT, base::BindOnce(&CefMediaRouterImpl::StoreOrTriggerInitCallback, this, std::move(callback))); return; } if (initialized_) { std::move(callback).Run(); } else { init_callbacks_.emplace_back(std::move(callback)); } } bool CefMediaRouterImpl::ValidContext() const { return CEF_CURRENTLY_ON_UIT() && initialized_; } // CefMediaRouter methods ------------------------------------------------------ // static CefRefPtr CefMediaRouter::GetGlobalMediaRouter( CefRefPtr callback) { return CefRequestContext::GetGlobalContext()->GetMediaRouter(callback); }