| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/media_router/media_router_ui.h" |
| |
| #include <utility> |
| |
| #include "base/atomic_sequence_num.h" |
| #include "base/containers/cxx20_erase.h" |
| #include "base/functional/bind.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider.h" |
| #include "chrome/browser/media/webrtc/desktop_media_picker_controller.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/browser_tabstrip.h" |
| #include "chrome/browser/ui/media_router/media_sink_with_cast_modes.h" |
| #include "chrome/browser/ui/media_router/ui_media_sink.h" |
| #include "chrome/common/url_constants.h" |
| #include "chrome/common/webui_url_constants.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/media_router/browser/issue_manager.h" |
| #include "components/media_router/browser/issues_observer.h" |
| #include "components/media_router/browser/log_util.h" |
| #include "components/media_router/browser/media_router.h" |
| #include "components/media_router/browser/media_router_factory.h" |
| #include "components/media_router/browser/media_routes_observer.h" |
| #include "components/media_router/browser/presentation/presentation_service_delegate_impl.h" |
| #include "components/media_router/common/media_route.h" |
| #include "components/media_router/common/media_sink.h" |
| #include "components/media_router/common/media_source.h" |
| #include "components/media_router/common/route_request_result.h" |
| #include "components/sessions/content/session_tab_helper.h" |
| #include "components/url_formatter/elide_url.h" |
| #include "third_party/icu/source/i18n/unicode/coll.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/display/display.h" |
| |
| #if BUILDFLAG(IS_MAC) |
| #include "base/mac/mac_util.h" |
| #include "ui/base/cocoa/permissions_utils.h" |
| #endif |
| |
| namespace media_router { |
| |
| namespace { |
| |
| constexpr char kLoggerComponent[] = "MediaRouterUI"; |
| |
| // Returns true if |issue| is associated with |ui_sink|. |
| bool IssueMatches(const Issue& issue, const UIMediaSink& ui_sink) { |
| return issue.info().sink_id == ui_sink.id || |
| (!issue.info().route_id.empty() && ui_sink.route && |
| issue.info().route_id == ui_sink.route->media_route_id()); |
| } |
| |
| std::u16string GetSinkFriendlyName(const MediaSink& sink) { |
| // Use U+2010 (HYPHEN) instead of ASCII hyphen to avoid problems with RTL |
| // languages. |
| const char* separator = " \u2010 "; |
| return base::UTF8ToUTF16(sink.description() ? sink.name() + separator + |
| sink.description().value() |
| : sink.name()); |
| } |
| |
| void MaybeReportCastingSource(MediaCastMode cast_mode, |
| const RouteRequestResult& result) { |
| if (result.result_code() == mojom::RouteRequestResultCode::OK) |
| base::UmaHistogramSparse("MediaRouter.Source.CastingSource", cast_mode); |
| } |
| |
| } // namespace |
| |
| MediaRouterUI::MediaRouterUI( |
| std::unique_ptr<MediaRouteStarter> media_route_starter) |
| : media_route_starter_(std::move(media_route_starter)), |
| router_(media_route_starter_->GetMediaRouter()), |
| logger_(router_->GetLogger()) { |
| DCHECK(media_route_starter_) << "Must have a media_route_starter!"; |
| media_route_starter_->SetLoggerComponent(kLoggerComponent); |
| Init(); |
| } |
| |
| MediaRouterUI::~MediaRouterUI() { |
| if (media_route_starter_) |
| DetachFromMediaRouteStarter(); |
| } |
| |
| // static |
| std::unique_ptr<MediaRouterUI> MediaRouterUI::CreateMediaRouterUI( |
| MediaRouterUIParameters params) { |
| DCHECK(params.initiator) << "Must have an initiator!"; |
| return std::make_unique<MediaRouterUI>( |
| std::make_unique<MediaRouteStarter>(std::move(params))); |
| } |
| |
| std::unique_ptr<MediaRouterUI> MediaRouterUI::CreateWithDefaultMediaSource( |
| content::WebContents* initiator) { |
| return CreateMediaRouterUI( |
| MediaRouterUIParameters({MediaCastMode::PRESENTATION}, initiator)); |
| } |
| |
| // static |
| std::unique_ptr<MediaRouterUI> |
| MediaRouterUI::CreateWithDefaultMediaSourceAndMirroring( |
| content::WebContents* initiator) { |
| return CreateMediaRouterUI(MediaRouterUIParameters( |
| {MediaCastMode::PRESENTATION, MediaCastMode::TAB_MIRROR, |
| MediaCastMode::DESKTOP_MIRROR}, |
| initiator)); |
| } |
| |
| // static |
| std::unique_ptr<MediaRouterUI> |
| MediaRouterUI::CreateWithStartPresentationContext( |
| content::WebContents* initiator, |
| std::unique_ptr<StartPresentationContext> context) { |
| DCHECK(context) << "context must not be null!"; |
| return CreateMediaRouterUI(MediaRouterUIParameters( |
| {MediaCastMode::PRESENTATION}, initiator, std::move(context))); |
| } |
| |
| // static |
| std::unique_ptr<MediaRouterUI> |
| MediaRouterUI::CreateWithStartPresentationContextAndMirroring( |
| content::WebContents* initiator, |
| std::unique_ptr<StartPresentationContext> context) { |
| DCHECK(context) << "context must not be null!"; |
| return CreateMediaRouterUI(MediaRouterUIParameters( |
| {MediaCastMode::PRESENTATION, MediaCastMode::TAB_MIRROR, |
| MediaCastMode::DESKTOP_MIRROR}, |
| initiator, std::move(context))); |
| } |
| |
| // static |
| std::unique_ptr<MediaRouterUI> |
| MediaRouterUI::CreateWithMediaSessionRemotePlayback( |
| content::WebContents* initiator, |
| media::VideoCodec video_codec, |
| media::AudioCodec audio_codec) { |
| DCHECK(video_codec != media::VideoCodec::kUnknown) << "Unknown video codec."; |
| DCHECK(audio_codec != media::AudioCodec::kUnknown) << "Unknown audio codec."; |
| return CreateMediaRouterUI( |
| MediaRouterUIParameters({MediaCastMode::REMOTE_PLAYBACK}, initiator, |
| nullptr, video_codec, audio_codec)); |
| } |
| |
| void MediaRouterUI::DetachFromMediaRouteStarter() { |
| for (CastDialogController::Observer& observer : observers_) |
| observer.OnControllerInvalidated(); |
| |
| media_route_starter()->RemovePresentationRequestSourceObserver(this); |
| media_route_starter()->RemoveMediaSinkWithCastModesObserver(this); |
| } |
| |
| void MediaRouterUI::AddObserver(CastDialogController::Observer* observer) { |
| observers_.AddObserver(observer); |
| // TODO(takumif): Update the header when this object is initialized instead. |
| UpdateModelHeader(media_route_starter()->GetPresentationRequestSourceName()); |
| } |
| |
| void MediaRouterUI::RemoveObserver(CastDialogController::Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void MediaRouterUI::StartCasting(const std::string& sink_id, |
| MediaCastMode cast_mode) { |
| CreateRoute(sink_id, cast_mode); |
| } |
| |
| void MediaRouterUI::StopCasting(const std::string& route_id) { |
| terminating_route_id_ = route_id; |
| // |route_id| may become invalid after UpdateSinks(), so we cannot refer to |
| // |route_id| below this line. |
| UpdateSinks(); |
| TerminateRoute(terminating_route_id_.value()); |
| } |
| |
| void MediaRouterUI::ClearIssue(const Issue::Id& issue_id) { |
| RemoveIssue(issue_id); |
| } |
| |
| std::unique_ptr<MediaRouteStarter> MediaRouterUI::TakeMediaRouteStarter() { |
| DCHECK(media_route_starter_) << "MediaRouteStarter already taken!"; |
| DetachFromMediaRouteStarter(); |
| return std::move(media_route_starter_); |
| } |
| |
| bool MediaRouterUI::CreateRoute(const MediaSink::Id& sink_id, |
| MediaCastMode cast_mode) { |
| logger_->LogInfo(mojom::LogCategory::kUi, kLoggerComponent, |
| "CreateRoute requested by MediaRouterViewsUI.", sink_id, "", |
| ""); |
| |
| auto params = |
| media_route_starter()->CreateRouteParameters(sink_id, cast_mode); |
| if (!params) { |
| SendIssueForUnableToCast(cast_mode, sink_id); |
| return false; |
| } |
| |
| if (!MediaRouteStarter::GetScreenCapturePermission(cast_mode)) { |
| SendIssueForScreenPermission(sink_id); |
| return false; |
| } |
| |
| GetIssueManager()->ClearAllIssues(); |
| |
| current_route_request_ = absl::make_optional(*params->request); |
| |
| params->route_result_callbacks.push_back( |
| base::BindOnce(&MaybeReportCastingSource, cast_mode)); |
| |
| params->route_result_callbacks.push_back(base::BindOnce( |
| &MediaRouterUI::OnRouteResponseReceived, weak_factory_.GetWeakPtr(), |
| current_route_request_->id, sink_id, cast_mode, |
| media_route_starter()->GetPresentationRequestSourceName())); |
| |
| media_route_starter()->StartRoute(std::move(params)); |
| |
| // TODO(crbug.com/1015203): This call to UpdateSinks() was originally in |
| // StartCasting(), but it causes Chrome to crash when the desktop picker |
| // dialog is shown, so for now we just don't call it in that case. Move it |
| // back once the problem is resolved. |
| if (cast_mode != MediaCastMode::DESKTOP_MIRROR) |
| UpdateSinks(); |
| |
| return true; |
| } |
| |
| void MediaRouterUI::TerminateRoute(const MediaRoute::Id& route_id) { |
| logger_->LogInfo(mojom::LogCategory::kUi, kLoggerComponent, |
| "TerminateRoute requested by MediaRouterUI.", |
| MediaRoute::GetSinkIdFromMediaRouteId(route_id), |
| MediaRoute::GetMediaSourceIdFromMediaRouteId(route_id), |
| MediaRoute::GetPresentationIdFromMediaRouteId(route_id)); |
| GetMediaRouter()->TerminateRoute(route_id); |
| } |
| |
| std::vector<MediaSinkWithCastModes> MediaRouterUI::GetEnabledSinks() const { |
| if (!display_observer_) |
| return sinks_; |
| |
| // Filter out the wired display sink for the display that the dialog is on. |
| // This is not the best place to do this because MRUI should not perform a |
| // provider-specific behavior, but we currently do not have a way to |
| // communicate dialog-specific information to/from the |
| // WiredDisplayMediaRouteProvider. |
| std::vector<MediaSinkWithCastModes> enabled_sinks(sinks_); |
| const std::string display_sink_id = |
| WiredDisplayMediaRouteProvider::GetSinkIdForDisplay( |
| display_observer_->GetCurrentDisplay()); |
| base::EraseIf(enabled_sinks, |
| [&display_sink_id](const MediaSinkWithCastModes& sink) { |
| return sink.sink.id() == display_sink_id; |
| }); |
| |
| return enabled_sinks; |
| } |
| |
| void MediaRouterUI::AddIssue(const IssueInfo& issue) { |
| GetIssueManager()->AddIssue(issue); |
| switch (issue.severity) { |
| case IssueInfo::Severity::NOTIFICATION: |
| logger_->LogInfo( |
| mojom::LogCategory::kUi, kLoggerComponent, |
| base::StrCat({"Sink button shows an issue in NOTIFICATION level: ", |
| issue.title}), |
| issue.sink_id, |
| MediaRoute::GetMediaSourceIdFromMediaRouteId(issue.route_id), |
| MediaRoute::GetPresentationIdFromMediaRouteId(issue.route_id)); |
| break; |
| default: |
| logger_->LogError( |
| mojom::LogCategory::kUi, kLoggerComponent, |
| base::StrCat( |
| {"Sink button shows an issue in WARNING or FATAL level: ", |
| issue.title}), |
| issue.sink_id, |
| MediaRoute::GetMediaSourceIdFromMediaRouteId(issue.route_id), |
| MediaRoute::GetPresentationIdFromMediaRouteId(issue.route_id)); |
| break; |
| } |
| } |
| |
| void MediaRouterUI::RemoveIssue(const Issue::Id& issue_id) { |
| GetIssueManager()->ClearIssue(issue_id); |
| } |
| |
| void MediaRouterUI::LogMediaSinkStatus() { |
| std::vector<std::string> sink_ids; |
| for (const auto& sink : GetEnabledSinks()) { |
| sink_ids.push_back(std::string(log_util::TruncateId(sink.sink.id()))); |
| } |
| |
| logger_->LogInfo( |
| mojom::LogCategory::kUi, kLoggerComponent, |
| base::StrCat( |
| {base::StringPrintf("%zu sinks shown on CastDialogView closed: ", |
| sink_ids.size()), |
| base::JoinString(sink_ids, ",")}), |
| "", "", ""); |
| } |
| |
| MediaRouterUI::UiIssuesObserver::UiIssuesObserver(IssueManager* issue_manager, |
| MediaRouterUI* ui) |
| : IssuesObserver(issue_manager), ui_(ui) { |
| DCHECK(ui); |
| } |
| |
| MediaRouterUI::UiIssuesObserver::~UiIssuesObserver() = default; |
| |
| void MediaRouterUI::UiIssuesObserver::OnIssue(const Issue& issue) { |
| ui_->OnIssue(issue); |
| } |
| |
| void MediaRouterUI::UiIssuesObserver::OnIssuesCleared() { |
| ui_->OnIssueCleared(); |
| } |
| |
| MediaRouterUI::UIMediaRoutesObserver::UIMediaRoutesObserver( |
| MediaRouter* router, |
| const RoutesUpdatedCallback& callback) |
| : MediaRoutesObserver(router), callback_(callback) { |
| DCHECK(!callback_.is_null()); |
| } |
| |
| MediaRouterUI::UIMediaRoutesObserver::~UIMediaRoutesObserver() = default; |
| |
| void MediaRouterUI::UIMediaRoutesObserver::OnRoutesUpdated( |
| const std::vector<MediaRoute>& routes) { |
| callback_.Run(routes); |
| } |
| |
| void MediaRouterUI::Init() { |
| DCHECK(!collator_) << "Init should only be called once!"; |
| |
| media_route_starter()->AddMediaSinkWithCastModesObserver(this); |
| media_route_starter()->AddPresentationRequestSourceObserver(this); |
| |
| GetMediaRouter()->OnUserGesture(); |
| |
| UErrorCode error = U_ZERO_ERROR; |
| const std::string& locale = g_browser_process->GetApplicationLocale(); |
| collator_.reset( |
| icu::Collator::createInstance(icu::Locale(locale.c_str()), error)); |
| if (U_FAILURE(error)) { |
| DLOG(ERROR) << "Failed to create collator for locale " << locale; |
| collator_.reset(); |
| } |
| |
| // Get the current list of media routes, so that the WebUI will have routes |
| // information at initialization. |
| OnRoutesUpdated(GetMediaRouter()->GetCurrentRoutes()); |
| display_observer_ = WebContentsDisplayObserver::Create( |
| initiator(), |
| base::BindRepeating(&MediaRouterUI::UpdateSinks, base::Unretained(this))); |
| |
| routes_observer_ = std::make_unique<UIMediaRoutesObserver>( |
| GetMediaRouter(), base::BindRepeating(&MediaRouterUI::OnRoutesUpdated, |
| base::Unretained(this))); |
| |
| StartObservingIssues(); |
| } |
| |
| void MediaRouterUI::OnSourceUpdated(std::u16string& source_name) { |
| UpdateModelHeader(source_name); |
| } |
| |
| void MediaRouterUI::UpdateSinks() { |
| std::vector<UIMediaSink> media_sinks; |
| for (const MediaSinkWithCastModes& sink : GetEnabledSinks()) { |
| auto route_it = base::ranges::find(routes(), sink.sink.id(), |
| &MediaRoute::media_sink_id); |
| const MediaRoute* route = route_it == routes().end() ? nullptr : &*route_it; |
| media_sinks.push_back(ConvertToUISink(sink, route, issue_)); |
| } |
| model_.set_media_sinks(std::move(media_sinks)); |
| for (CastDialogController::Observer& observer : observers_) |
| observer.OnModelUpdated(model_); |
| } |
| |
| void MediaRouterUI::SendIssueForRouteTimeout( |
| MediaCastMode cast_mode, |
| const MediaSink::Id& sink_id, |
| const std::u16string& presentation_request_source_name) { |
| std::string issue_title; |
| switch (cast_mode) { |
| case PRESENTATION: |
| DLOG_IF(ERROR, presentation_request_source_name.empty()) |
| << "Empty presentation request source name."; |
| issue_title = l10n_util::GetStringFUTF8( |
| IDS_MEDIA_ROUTER_ISSUE_CREATE_ROUTE_TIMEOUT_WITH_HOSTNAME, |
| presentation_request_source_name); |
| break; |
| case TAB_MIRROR: |
| issue_title = l10n_util::GetStringUTF8( |
| IDS_MEDIA_ROUTER_ISSUE_CREATE_ROUTE_TIMEOUT_FOR_TAB); |
| break; |
| case DESKTOP_MIRROR: |
| issue_title = l10n_util::GetStringUTF8( |
| IDS_MEDIA_ROUTER_ISSUE_CREATE_ROUTE_TIMEOUT_FOR_DESKTOP); |
| break; |
| case REMOTE_PLAYBACK: |
| issue_title = |
| l10n_util::GetStringUTF8(IDS_MEDIA_ROUTER_ISSUE_CREATE_ROUTE_TIMEOUT); |
| break; |
| } |
| |
| IssueInfo issue_info(issue_title, IssueInfo::Severity::NOTIFICATION); |
| issue_info.sink_id = sink_id; |
| AddIssue(issue_info); |
| } |
| |
| std::u16string MediaRouterUI::GetSinkFriendlyNameFromId( |
| const MediaSink::Id& sink_id) { |
| for (const MediaSinkWithCastModes& sink : GetEnabledSinks()) { |
| if (sink.sink.id() == sink_id) { |
| return GetSinkFriendlyName(sink.sink); |
| } |
| } |
| return std::u16string(u"Device"); |
| } |
| |
| void MediaRouterUI::SendIssueForUserNotAllowed(const MediaSink::Id& sink_id) { |
| std::string issue_title = l10n_util::GetStringFUTF8( |
| IDS_MEDIA_ROUTER_ISSUE_CREATE_ROUTE_USER_NOT_ALLOWED, |
| GetSinkFriendlyNameFromId(sink_id)); |
| IssueInfo issue_info(issue_title, IssueInfo::Severity::WARNING); |
| issue_info.sink_id = sink_id; |
| AddIssue(issue_info); |
| } |
| |
| void MediaRouterUI::SendIssueForNotificationDisabled( |
| const MediaSink::Id& sink_id) { |
| std::string issue_title = l10n_util::GetStringFUTF8( |
| IDS_MEDIA_ROUTER_ISSUE_CREATE_ROUTE_NOTIFICATION_DISABLED, |
| GetSinkFriendlyNameFromId(sink_id)); |
| IssueInfo issue_info(issue_title, IssueInfo::Severity::WARNING); |
| issue_info.sink_id = sink_id; |
| AddIssue(issue_info); |
| } |
| |
| void MediaRouterUI::SendIssueForScreenPermission(const MediaSink::Id& sink_id) { |
| #if BUILDFLAG(IS_MAC) |
| std::string issue_title = l10n_util::GetStringUTF8( |
| IDS_MEDIA_ROUTER_ISSUE_MAC_SCREEN_CAPTURE_PERMISSION_ERROR); |
| IssueInfo issue_info(issue_title, IssueInfo::Severity::WARNING); |
| issue_info.sink_id = sink_id; |
| AddIssue(issue_info); |
| #else |
| NOTREACHED() << "Only valid for MAC OS!"; |
| #endif |
| } |
| |
| void MediaRouterUI::SendIssueForUnableToCast(MediaCastMode cast_mode, |
| const MediaSink::Id& sink_id) { |
| // For a generic error, claim a tab error unless it was specifically desktop |
| // mirroring. |
| std::string issue_title = |
| (cast_mode == MediaCastMode::DESKTOP_MIRROR) |
| ? l10n_util::GetStringUTF8( |
| IDS_MEDIA_ROUTER_ISSUE_UNABLE_TO_CAST_DESKTOP) |
| : l10n_util::GetStringUTF8( |
| IDS_MEDIA_ROUTER_ISSUE_CREATE_ROUTE_TIMEOUT_FOR_TAB); |
| IssueInfo issue_info(issue_title, IssueInfo::Severity::WARNING); |
| issue_info.sink_id = sink_id; |
| AddIssue(issue_info); |
| } |
| |
| void MediaRouterUI::SendIssueForTabAudioNotSupported( |
| const MediaSink::Id& sink_id) { |
| IssueInfo issue_info( |
| l10n_util::GetStringUTF8(IDS_MEDIA_ROUTER_ISSUE_TAB_AUDIO_NOT_SUPPORTED), |
| IssueInfo::Severity::NOTIFICATION); |
| issue_info.sink_id = sink_id; |
| AddIssue(issue_info); |
| } |
| |
| IssueManager* MediaRouterUI::GetIssueManager() { |
| return GetMediaRouter()->GetIssueManager(); |
| } |
| |
| void MediaRouterUI::StartObservingIssues() { |
| issues_observer_ = |
| std::make_unique<UiIssuesObserver>(GetIssueManager(), this); |
| issues_observer_->Init(); |
| } |
| |
| void MediaRouterUI::OnIssue(const Issue& issue) { |
| issue_ = issue; |
| UpdateSinks(); |
| } |
| |
| void MediaRouterUI::OnIssueCleared() { |
| issue_ = absl::nullopt; |
| UpdateSinks(); |
| } |
| |
| void MediaRouterUI::OnRoutesUpdated(const std::vector<MediaRoute>& routes) { |
| routes_.clear(); |
| |
| for (const MediaRoute& route : routes) { |
| #ifndef NDEBUG |
| for (const MediaRoute& existing_route : routes_) { |
| if (existing_route.media_sink_id() == route.media_sink_id()) { |
| DVLOG(2) << "Received another route for display with the same sink" |
| << " id as an existing route. " << route.media_route_id() |
| << " has the same sink id as " |
| << existing_route.media_sink_id() << "."; |
| } |
| } |
| #endif |
| routes_.push_back(route); |
| } |
| |
| if (terminating_route_id_ && |
| !base::Contains(routes, terminating_route_id_.value(), |
| &MediaRoute::media_route_id)) { |
| terminating_route_id_.reset(); |
| } |
| UpdateSinks(); |
| } |
| |
| void MediaRouterUI::OnSinksUpdated( |
| const std::vector<MediaSinkWithCastModes>& sinks) { |
| sinks_ = sinks; |
| |
| const icu::Collator* collator_ptr = collator_.get(); |
| std::sort(sinks_.begin(), sinks_.end(), |
| [collator_ptr](const MediaSinkWithCastModes& sink1, |
| const MediaSinkWithCastModes& sink2) { |
| return sink1.sink.CompareUsingCollator(sink2.sink, collator_ptr); |
| }); |
| UpdateSinks(); |
| } |
| |
| void MediaRouterUI::OnRouteResponseReceived( |
| int route_request_id, |
| const MediaSink::Id& sink_id, |
| MediaCastMode cast_mode, |
| const std::u16string& presentation_request_source_name, |
| const RouteRequestResult& result) { |
| // If we receive a new route that we aren't expecting, do nothing. |
| if (!current_route_request_ || route_request_id != current_route_request_->id) |
| return; |
| |
| const MediaRoute* route = result.route(); |
| if (!route) { |
| // The provider will handle sending an issue for a failed route request. |
| logger_->LogError(mojom::LogCategory::kUi, kLoggerComponent, |
| "MediaRouteResponse returned error: " + result.error(), |
| sink_id, "", ""); |
| } |
| |
| current_route_request_.reset(); |
| if (result.result_code() == mojom::RouteRequestResultCode::OK && |
| cast_mode == TAB_MIRROR && !base::TimeTicks::IsHighResolution()) { |
| // When tab mirroring on a device without a high resolution clock, the audio |
| // is not mirrored. |
| SendIssueForTabAudioNotSupported(sink_id); |
| } else if (result.result_code() == mojom::RouteRequestResultCode::TIMED_OUT) { |
| SendIssueForRouteTimeout(cast_mode, sink_id, |
| presentation_request_source_name); |
| } else if (result.result_code() == |
| mojom::RouteRequestResultCode::USER_NOT_ALLOWED) { |
| SendIssueForUserNotAllowed(sink_id); |
| } else if (result.result_code() == |
| mojom::RouteRequestResultCode::NOTIFICATION_DISABLED) { |
| SendIssueForNotificationDisabled(sink_id); |
| } |
| } |
| |
| void MediaRouterUI::UpdateModelHeader(const std::u16string& source_name) { |
| const std::u16string header_text = |
| source_name.empty() |
| ? l10n_util::GetStringUTF16(IDS_MEDIA_ROUTER_TAB_MIRROR_CAST_MODE) |
| : l10n_util::GetStringFUTF16(IDS_MEDIA_ROUTER_PRESENTATION_CAST_MODE, |
| source_name); |
| model_.set_dialog_header(header_text); |
| for (CastDialogController::Observer& observer : observers_) |
| observer.OnModelUpdated(model_); |
| } |
| |
| UIMediaSink MediaRouterUI::ConvertToUISink(const MediaSinkWithCastModes& sink, |
| const MediaRoute* route, |
| const absl::optional<Issue>& issue) { |
| UIMediaSink ui_sink{sink.sink.provider_id()}; |
| ui_sink.id = sink.sink.id(); |
| ui_sink.friendly_name = GetSinkFriendlyName(sink.sink); |
| ui_sink.icon_type = sink.sink.icon_type(); |
| ui_sink.cast_modes = sink.cast_modes; |
| |
| if (route) { |
| ui_sink.status_text = base::UTF8ToUTF16(route->description()); |
| ui_sink.route = *route; |
| if (terminating_route_id_ && |
| route->media_route_id() == terminating_route_id_.value()) { |
| ui_sink.state = UIMediaSinkState::DISCONNECTING; |
| } else if (route->is_connecting()) { |
| ui_sink.state = UIMediaSinkState::CONNECTING; |
| } else { |
| ui_sink.state = UIMediaSinkState::CONNECTED; |
| } |
| } else { |
| ui_sink.state = current_route_request() && |
| sink.sink.id() == current_route_request()->sink_id |
| ? UIMediaSinkState::CONNECTING |
| : UIMediaSinkState::AVAILABLE; |
| } |
| if (issue && IssueMatches(*issue, ui_sink)) |
| ui_sink.issue = issue; |
| return ui_sink; |
| } |
| |
| MediaRouter* MediaRouterUI::GetMediaRouter() const { |
| return router_; |
| } |
| |
| } // namespace media_router |