// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/media/session/media_session_impl.h"

#include <algorithm>
#include <memory>
#include <utility>

#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "components/url_formatter/url_formatter.h"
#include "content/browser/media/session/audio_focus_delegate.h"
#include "content/browser/media/session/media_session_controller.h"
#include "content/browser/media/session/media_session_player_observer.h"
#include "content/browser/media/session/media_session_service_impl.h"
#include "content/browser/picture_in_picture/video_picture_in_picture_window_controller_impl.h"
#include "content/browser/renderer_host/back_forward_cache_disable.h"
#include "content/browser/renderer_host/back_forward_cache_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/media_session.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "media/audio/audio_device_description.h"
#include "media/base/media_content_type.h"
#include "media/base/media_switches.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "services/media_session/public/cpp/media_image_manager.h"
#include "services/media_session/public/mojom/audio_focus.mojom.h"
#include "third_party/blink/public/mojom/favicon/favicon_url.mojom.h"
#include "third_party/blink/public/strings/grit/blink_strings.h"
#include "ui/gfx/favicon_size.h"

#if BUILDFLAG(IS_ANDROID)
#include "content/browser/media/session/media_session_android.h"
#endif  // BUILDFLAG(IS_ANDROID)

#if defined(OHOS_MEDIA_AVSESSION)
#include "content/browser/media/session/media_session_ohos.h"
#endif  // defined(OHOS_MEDIA_AVSESSION)

namespace content {

using blink::mojom::MediaSessionPlaybackState;
using MediaSessionUserAction = MediaSessionUmaHelper::MediaSessionUserAction;
using media_session::mojom::MediaAudioVideoState;
using media_session::mojom::MediaPlaybackState;
using media_session::mojom::MediaSessionImageType;
using media_session::mojom::MediaSessionInfo;

namespace {

const double kUnduckedVolumeMultiplier = 1.0;
const double kDefaultDuckingVolumeMultiplier = 0.2;

const char kDebugInfoOwnerSeparator[] = " - ";

using MapRenderFrameHostToDepth = std::map<RenderFrameHost*, size_t>;

using media_session::mojom::AudioFocusType;

using MediaSessionSuspendedSource =
    MediaSessionUmaHelper::MediaSessionSuspendedSource;

const char kMediaSessionDataName[] = "MediaSessionDataName";

class MediaSessionData : public base::SupportsUserData::Data {
 public:
  MediaSessionData() = default;

  MediaSessionData(const MediaSessionData&) = delete;
  MediaSessionData& operator=(const MediaSessionData&) = delete;

  static MediaSessionData* GetOrCreate(BrowserContext* context) {
    auto* data = static_cast<MediaSessionData*>(
        context->GetUserData(kMediaSessionDataName));

    if (!data) {
      auto new_data = std::make_unique<MediaSessionData>();
      data = new_data.get();
      context->SetUserData(kMediaSessionDataName, std::move(new_data));
    }

    return data;
  }

  const base::UnguessableToken& source_id() const { return source_id_; }

 private:
  base::UnguessableToken source_id_ = base::UnguessableToken::Create();
};

size_t ComputeFrameDepth(RenderFrameHost* rfh,
                         MapRenderFrameHostToDepth* map_rfh_to_depth) {
  DCHECK(rfh);
  size_t depth = 0;
  RenderFrameHost* current_frame = rfh;
  while (current_frame) {
    auto it = map_rfh_to_depth->find(current_frame);
    if (it != map_rfh_to_depth->end()) {
      depth += it->second;
      break;
    }
    ++depth;
    current_frame = current_frame->GetParentOrOuterDocument();
  }
  (*map_rfh_to_depth)[rfh] = depth;
  return depth;
}

MediaSessionUserAction MediaSessionActionToUserAction(
    media_session::mojom::MediaSessionAction action) {
  switch (action) {
    case media_session::mojom::MediaSessionAction::kPlay:
      return MediaSessionUserAction::kPlay;
    case media_session::mojom::MediaSessionAction::kPause:
      return MediaSessionUserAction::kPause;
    case media_session::mojom::MediaSessionAction::kPreviousTrack:
      return MediaSessionUserAction::kPreviousTrack;
    case media_session::mojom::MediaSessionAction::kNextTrack:
      return MediaSessionUserAction::kNextTrack;
    case media_session::mojom::MediaSessionAction::kSeekBackward:
      return MediaSessionUserAction::kSeekBackward;
    case media_session::mojom::MediaSessionAction::kSeekForward:
      return MediaSessionUserAction::kSeekForward;
    case media_session::mojom::MediaSessionAction::kSkipAd:
      return MediaSessionUserAction::kSkipAd;
    case media_session::mojom::MediaSessionAction::kStop:
      return MediaSessionUserAction::kStop;
    case media_session::mojom::MediaSessionAction::kSeekTo:
      return MediaSessionUserAction::kSeekTo;
    case media_session::mojom::MediaSessionAction::kScrubTo:
      return MediaSessionUserAction::kScrubTo;
    case media_session::mojom::MediaSessionAction::kEnterPictureInPicture:
      return MediaSessionUserAction::kEnterPictureInPicture;
    case media_session::mojom::MediaSessionAction::kExitPictureInPicture:
      return MediaSessionUserAction::kExitPictureInPicture;
    case media_session::mojom::MediaSessionAction::kSwitchAudioDevice:
      return MediaSessionUserAction::kSwitchAudioDevice;
    case media_session::mojom::MediaSessionAction::kToggleMicrophone:
      return MediaSessionUserAction::kToggleMicrophone;
    case media_session::mojom::MediaSessionAction::kToggleCamera:
      return MediaSessionUserAction::kToggleCamera;
    case media_session::mojom::MediaSessionAction::kHangUp:
      return MediaSessionUserAction::kHangUp;
    case media_session::mojom::MediaSessionAction::kRaise:
      return MediaSessionUserAction::kRaise;
    case media_session::mojom::MediaSessionAction::kSetMute:
      return MediaSessionUserAction::kSetMute;
    case media_session::mojom::MediaSessionAction::kPreviousSlide:
      return MediaSessionUserAction::kPreviousSlide;
    case media_session::mojom::MediaSessionAction::kNextSlide:
      return MediaSessionUserAction::kNextSlide;
  }
  NOTREACHED();
  return MediaSessionUserAction::kPlay;
}

// If the string is not empty then push it to the back of a vector.
void MaybePushBackString(std::vector<std::string>& vector,
                         const std::string& str) {
  if (!str.empty())
    vector.push_back(str);
}

bool IsSizeAtLeast(const gfx::Size& size, int min_size) {
  return size.width() >= min_size || size.height() >= min_size;
}

bool IsSizesAtLeast(const std::vector<gfx::Size>& sizes, int min_size) {
  // If we haven't found an image based on size then we should check if there
  // are any images that have no size data or have an "any" size which is
  // denoted by a single empty gfx::Size value.
  if (sizes.size() == 0 || (sizes.size() == 1 && sizes[0].IsEmpty()))
    return true;

  bool check_size = false;
  for (auto& size : sizes)
    check_size = check_size || IsSizeAtLeast(size, min_size);
  return check_size;
}

std::u16string SanitizeMediaTitle(const std::u16string title) {
  std::u16string out;
  base::TrimString(title, u" ", &out);
  return out;
}

}  // anonymous namespace

constexpr int MediaSessionImpl::kDurationUpdateMaxAllowance;
constexpr base::TimeDelta
    MediaSessionImpl::kDurationUpdateAllowanceIncreaseInterval;

MediaSessionImpl::PlayerIdentifier::PlayerIdentifier(
    MediaSessionPlayerObserver* observer,
    int player_id)
    : observer(observer), player_id(player_id) {}

bool MediaSessionImpl::PlayerIdentifier::operator==(
    const PlayerIdentifier& other) const {
  return this->observer == other.observer && this->player_id == other.player_id;
}

bool MediaSessionImpl::PlayerIdentifier::operator!=(
    const PlayerIdentifier& other) const {
  return this->observer != other.observer || this->player_id != other.player_id;
}

bool MediaSessionImpl::PlayerIdentifier::operator<(
    const PlayerIdentifier& other) const {
  return observer != other.observer ? observer < other.observer
                                    : player_id < other.player_id;
}

// static
MediaSession* MediaSession::Get(WebContents* web_contents) {
  return MediaSessionImpl::Get(web_contents);
}

// static
const base::UnguessableToken& MediaSession::GetSourceId(
    BrowserContext* browser_context) {
  return MediaSessionData::GetOrCreate(browser_context)->source_id();
}

// static
WebContents* MediaSession::GetWebContentsFromRequestId(
    const base::UnguessableToken& request_id) {
  DCHECK_NE(base::UnguessableToken::Null(), request_id);
  for (WebContentsImpl* web_contents : WebContentsImpl::GetAllWebContents()) {
    MediaSessionImpl* session = MediaSessionImpl::FromWebContents(web_contents);
    if (!session)
      continue;
    if (session->GetRequestId() == request_id)
      return web_contents;
  }
  return nullptr;
}

// static
WebContents* MediaSession::GetWebContentsFromRequestId(
    const std::string& request_id) {
  for (WebContentsImpl* web_contents : WebContentsImpl::GetAllWebContents()) {
    MediaSessionImpl* session = MediaSessionImpl::FromWebContents(web_contents);
    if (!session)
      continue;
    if (session->GetRequestId().ToString() == request_id)
      return web_contents;
  }
  return nullptr;
}

// static
const base::UnguessableToken& MediaSession::GetRequestIdFromWebContents(
    WebContents* web_contents) {
  DCHECK(web_contents);
  MediaSessionImpl* session = MediaSessionImpl::FromWebContents(web_contents);
  return session ? session->GetRequestId() : base::UnguessableToken::Null();
}

// static
MediaSessionImpl* MediaSessionImpl::Get(WebContents* web_contents) {
  MediaSessionImpl* session = FromWebContents(web_contents);
  if (!session) {
    CreateForWebContents(web_contents);
    session = FromWebContents(web_contents);
    session->Initialize();
  }
  return session;
}

MediaSessionImpl::~MediaSessionImpl() {
  DCHECK(normal_players_.empty());
  DCHECK(pepper_players_.empty());
  DCHECK(one_shot_players_.empty());
  DCHECK(audio_focus_state_ == State::INACTIVE);
}

#if BUILDFLAG(IS_ANDROID)
void MediaSessionImpl::ClearMediaSessionAndroid() {
  session_android_.reset();
}

MediaSessionAndroid* MediaSessionImpl::GetMediaSessionAndroid() {
  return session_android_.get();
}
#endif

void MediaSessionImpl::WebContentsDestroyed() {
  delegate_->ReleaseRequestId();

  // This should only work for tests. In production, all the players should have
  // already been removed before WebContents is destroyed.

  // TODO(zqzhang): refactor MediaSessionImpl, maybe move the interface used to
  // talk with AudioFocusManager out to a seperate class. The AudioFocusManager
  // unit tests then could mock the interface and abandon audio focus when
  // WebContents is destroyed. See https://crbug.com/651069
  normal_players_.clear();
  pepper_players_.clear();
  one_shot_players_.clear();

  AbandonSystemAudioFocusIfNeeded();

  content::GetContentClient()->browser()->RemovePresentationObserver(
      this, web_contents());
}

void MediaSessionImpl::RenderFrameDeleted(RenderFrameHost* rfh) {
  const auto rfh_id = rfh->GetGlobalId();
  if (services_.count(rfh_id))
    OnServiceDestroyed(services_[rfh_id]);
}

void MediaSessionImpl::DidFinishNavigation(
    NavigationHandle* navigation_handle) {
  if (!navigation_handle->HasCommitted() ||
      navigation_handle->IsSameDocument()) {
    return;
  }

  auto new_origin = url::Origin::Create(navigation_handle->GetURL());
  if (navigation_handle->IsInPrimaryMainFrame() &&
      !new_origin.IsSameOriginWith(origin_)) {
    audio_device_id_for_origin_.reset();
    origin_ = new_origin;
  }

  const auto rfh_id = navigation_handle->GetRenderFrameHost()->GetGlobalId();
  if (services_.count(rfh_id))
    services_[rfh_id]->DidFinishNavigation();

  RebuildAndNotifyMetadataChanged();
}

void MediaSessionImpl::OnWebContentsFocused(RenderWidgetHost*) {
  focused_ = true;

#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_MAC)
  // If we have just gained focus and we have audio focus we should re-request
  // system audio focus. This will ensure this media session is towards the top
  // of the stack if we have multiple sessions active at the same time.
  if (audio_focus_state_ == State::ACTIVE)
    RequestSystemAudioFocus(desired_audio_focus_type_);
#endif
}

void MediaSessionImpl::OnWebContentsLostFocus(RenderWidgetHost*) {
  focused_ = false;
}

void MediaSessionImpl::TitleWasSet(NavigationEntry* entry) {
  RebuildAndNotifyMetadataChanged();
}

void MediaSessionImpl::DidUpdateFaviconURL(
    RenderFrameHost* rfh,
    const std::vector<blink::mojom::FaviconURLPtr>& candidates) {
  std::vector<media_session::MediaImage> icons;

  for (auto& icon : candidates) {
    // We only want either favicons or the touch icons. There is another type of
    // touch icon which is "precomposed". This means it might have rounded
    // corners, etc. but it is not predictable so we cannot show them in the UI.
    if (icon->icon_type != blink::mojom::FaviconIconType::kFavicon &&
        icon->icon_type != blink::mojom::FaviconIconType::kTouchIcon) {
      continue;
    }

    std::vector<gfx::Size> sizes = icon->icon_sizes;

    // If we are a favicon and we do not have a size then we should assume the
    // default size for favicons.
    if (icon->icon_type == blink::mojom::FaviconIconType::kFavicon &&
        sizes.empty())
      sizes.push_back(gfx::Size(gfx::kFaviconSize, gfx::kFaviconSize));

    if (sizes.empty() || !icon->icon_url.is_valid())
      continue;

    media_session::MediaImage image;
    image.src = icon->icon_url;
    image.sizes = sizes;
    icons.push_back(image);
  }

  auto it = images_.find(MediaSessionImageType::kSourceIcon);
  if (it != images_.end() && it->second == icons)
    return;

  images_.insert_or_assign(MediaSessionImageType::kSourceIcon, icons);

  for (auto& observer : observers_)
    observer->MediaSessionImagesChanged(this->images_);
}

void MediaSessionImpl::MediaPictureInPictureChanged(
    bool is_picture_in_picture) {
  RebuildAndNotifyMediaSessionInfoChanged();
}

void MediaSessionImpl::RenderFrameHostStateChanged(
    RenderFrameHost* host,
    RenderFrameHost::LifecycleState old_state,
    RenderFrameHost::LifecycleState new_state) {
  // If the page goes to back-forward cache, hide the players.
  if (new_state == RenderFrameHost::LifecycleState::kInBackForwardCache) {
    // Checking the normal players is enough. One shot players and pepper
    // players are not related to media control UIs.
    auto players = normal_players_;
    for (auto player : players) {
      if (player.first.observer->render_frame_host() != host) {
        continue;
      }
      // RemovePlayer removes the player from not only |normal_players_| but
      // also |hidden_players_|. Call RemovePlayer first.
      RemovePlayer(player.first.observer, player.first.player_id);
      hidden_players_.insert(player.first);
    }
    return;
  }

  // If the page is restored from back-forward cache, show the players.
  if (new_state == RenderFrameHost::LifecycleState::kActive) {
    auto players = hidden_players_;
    bool added_players = false;
    for (auto player : players) {
      if (player.observer->render_frame_host() != host)
        continue;
      hidden_players_.erase(player);
      AddPlayer(player.observer, player.player_id);
      added_players = true;
    }

    // Just after adding a player, the state might be 'play'. Make sure that the
    // state is 'pause'.
    if (added_players)
      OnSuspendInternal(SuspendType::kSystem, State::SUSPENDED);

    return;
  }
}

bool MediaSessionImpl::AddPlayer(MediaSessionPlayerObserver* observer,
                                 int player_id) {
  media::MediaContentType media_content_type = observer->GetMediaContentType();

  if (media_content_type == media::MediaContentType::OneShot)
    return AddOneShotPlayer(observer, player_id);
  if (media_content_type == media::MediaContentType::Pepper)
    return AddPepperPlayer(observer, player_id);

  observer->OnSetVolumeMultiplier(player_id, GetVolumeMultiplier());
  if (audio_device_id_for_origin_)
    observer->OnSetAudioSinkId(player_id, audio_device_id_for_origin_.value());

  AudioFocusType required_audio_focus_type;
  if (media_content_type == media::MediaContentType::Persistent)
    required_audio_focus_type = AudioFocusType::kGain;
  else
    required_audio_focus_type = AudioFocusType::kGainTransientMayDuck;

  PlayerIdentifier key(observer, player_id);

  // If the audio focus is already granted and is of type Content, there is
  // nothing to do. If it is granted of type Transient the requested type is
  // also transient, there is also nothing to do. Otherwise, the session needs
  // to request audio focus again.
  if (audio_focus_state_ == State::ACTIVE) {
    absl::optional<AudioFocusType> current_focus_type =
        delegate_->GetCurrentFocusType();
    if (current_focus_type == AudioFocusType::kGain ||
        current_focus_type == required_audio_focus_type) {
      auto iter = normal_players_.find(key);
      if (iter == normal_players_.end())
        normal_players_.emplace(std::move(key), required_audio_focus_type);
      else
        iter->second = required_audio_focus_type;

      UpdateRoutedService();
      RebuildAndNotifyMediaSessionInfoChanged();
      RebuildAndNotifyActionsChanged();
      RebuildAndNotifyMediaPositionChanged();
      return true;
    }
  }

  State old_audio_focus_state = audio_focus_state_;
  RequestSystemAudioFocus(required_audio_focus_type);

  if (audio_focus_state_ != State::ACTIVE)
    return false;

  // The session should be reset if a player is starting while all players are
  // suspended.
  if (old_audio_focus_state != State::ACTIVE)
    normal_players_.clear();

  auto iter = normal_players_.find(key);
  if (iter == normal_players_.end())
    normal_players_.emplace(std::move(key), required_audio_focus_type);
  else
    iter->second = required_audio_focus_type;

  UpdateRoutedService();
  RebuildAndNotifyMediaSessionInfoChanged();
  RebuildAndNotifyActionsChanged();
  RebuildAndNotifyMediaPositionChanged();

  return true;
}

void MediaSessionImpl::RemovePlayer(MediaSessionPlayerObserver* observer,
                                    int player_id) {
#if defined(OHOS_MEDIA_AVSESSION)
  bool has_normal_player = normal_players_.size() > 0;
#endif // OHOS_MEDIA_AVSESSION

  const PlayerIdentifier identifier(observer, player_id);
  normal_players_.erase(identifier);
  pepper_players_.erase(identifier);
  one_shot_players_.erase(identifier);
  hidden_players_.erase(identifier);

#if defined(OHOS_MEDIA_AVSESSION)
  if (has_normal_player && (normal_players_.size() == 0)) {
    SetWebviewShow(false, false);
  }
#endif // OHOS_MEDIA_AVSESSION

  if (guarding_player_id_ && *guarding_player_id_ == identifier)
    ResetDurationUpdateGuard();

  AbandonSystemAudioFocusIfNeeded();
  UpdateRoutedService();

  RebuildAndNotifyMediaSessionInfoChanged();
  RebuildAndNotifyActionsChanged();
  RebuildAndNotifyMediaPositionChanged();
}

void MediaSessionImpl::RemovePlayers(MediaSessionPlayerObserver* observer) {
#if defined(OHOS_MEDIA_AVSESSION)
  bool has_normal_player = normal_players_.size() > 0;
#endif // OHOS_MEDIA_AVSESSION
  for (auto it = normal_players_.begin(); it != normal_players_.end();) {
    if (it->first.observer == observer)
      normal_players_.erase(it++);
    else
      ++it;
  }

  for (auto it = pepper_players_.begin(); it != pepper_players_.end();) {
    if (it->observer == observer)
      pepper_players_.erase(it++);
    else
      ++it;
  }

  for (auto it = one_shot_players_.begin(); it != one_shot_players_.end();) {
    if (it->observer == observer)
      one_shot_players_.erase(it++);
    else
      ++it;
  }

#if defined(OHOS_MEDIA_AVSESSION)
  if (has_normal_player && (normal_players_.size() == 0)) {
    SetWebviewShow(false, false);
  }
#endif // OHOS_MEDIA_AVSESSION
  if (guarding_player_id_ && guarding_player_id_->observer == observer)
    ResetDurationUpdateGuard();

  AbandonSystemAudioFocusIfNeeded();
  UpdateRoutedService();

  RebuildAndNotifyMediaSessionInfoChanged();
  RebuildAndNotifyActionsChanged();
  RebuildAndNotifyMediaPositionChanged();
}

void MediaSessionImpl::RecordSessionDuck() {
  uma_helper_.RecordSessionSuspended(
      MediaSessionSuspendedSource::kSystemTransientDuck);
}

void MediaSessionImpl::OnPlayerPaused(MediaSessionPlayerObserver* observer,
                                      int player_id) {
  // If a playback is completed, BrowserMediaPlayerManager will call
  // OnPlayerPaused() after RemovePlayer(). This is a workaround.
  // Also, this method may be called when a player that is not added
  // to this session (e.g. a silent video) is paused. MediaSessionImpl
  // should ignore the paused player for this case.
  PlayerIdentifier identifier(observer, player_id);
  if (!normal_players_.count(identifier) &&
      !pepper_players_.count(identifier) &&
      !one_shot_players_.count(identifier)) {
    return;
  }

  // If the player to be removed is a pepper player, or there is more than one
  // observer, remove the paused one from the session.
  if (pepper_players_.count(identifier) || normal_players_.size() != 1) {
    RemovePlayer(observer, player_id);
    return;
  }

  // If the player is a one-shot player, just remove it since it is not expected
  // to resume a one-shot player via resuming MediaSession.
  if (one_shot_players_.count(identifier)) {
    RemovePlayer(observer, player_id);
    return;
  }

  // Otherwise, suspend the session.
  DCHECK(IsActive());
 #if defined(OHOS_MEDIA_AVSESSION)
  if (!IsPauseByAvsession()) {
    EndSessionWhenHide();
  }
  if (IsSuspended()) {
    return;
  }
#endif // OHOS_MEDIA_AVSESSION
  OnSuspendInternal(SuspendType::kContent, State::SUSPENDED);
}

#if defined(OHOS_MEDIA_AVSESSION)
void MediaSessionImpl::EndSessionWhenHide() {
  auto states = MediaSessionImpl::GetMediaAudioVideoStates();
  if (!states.empty() && states.back()!= MediaAudioVideoState::kAudioOnly && !focused_) {
     SetWebviewShow(false, false);
  }
}
#endif // OHOS_MEDIA_AVSESSION

void MediaSessionImpl::RebuildAndNotifyMediaPositionChanged() {
  absl::optional<media_session::MediaPosition> position;

  // If there was a position specified from Blink then we should use that.
  if (routed_service_ && routed_service_->position()) {
    position = routed_service_->position();

    // We do not throttle updates from media session API because there's
    // no effective way to disdinguish updates from single player or
    // different players.
    ResetDurationUpdateGuard();
  }

  // If we only have a single player then we should use the position from that.
  if (!position && normal_players_.size() == 1 && one_shot_players_.empty() &&
      pepper_players_.empty()) {
    auto& first = normal_players_.begin()->first;
    position = first.observer->GetPosition(first.player_id);

    if (should_throttle_duration_update_) {
      if (!guarding_player_id_ || *guarding_player_id_ != first) {
        ResetDurationUpdateGuard();
        guarding_player_id_ = first;
      }

      position = MaybeGuardDurationUpdate(position);
    }
  }

  if (position == position_) {
  #if defined(OHOS_MEDIA_AVSESSION)
    if (session_ohos_) {
      session_ohos_->MediaSessionPositionChanged(position);
    }
  #endif  // defined(OHOS_MEDIA_AVSESSION)
    return;
  }

  position_ = position;

  if (auto* pip_window_controller_ =
          VideoPictureInPictureWindowControllerImpl::FromWebContents(
              web_contents())) {
    pip_window_controller_->MediaSessionPositionChanged(position_);
  }

  for (auto& observer : observers_)
    observer->MediaSessionPositionChanged(position_);
}

void MediaSessionImpl::Resume(SuspendType suspend_type) {
  if (!IsSuspended())
    return;

  if (suspend_type == SuspendType::kUI) {
    // If the site has registered an action handler for play then we should
    // pass it to the site and let them handle it.
    if (ShouldRouteAction(media_session::mojom::MediaSessionAction::kPlay)) {
      DidReceiveAction(media_session::mojom::MediaSessionAction::kPlay);
      return;
    }

    MediaSessionUmaHelper::RecordMediaSessionUserAction(
        MediaSessionUmaHelper::MediaSessionUserAction::kPlayDefault, focused_);
  }

  // When the resume requests comes from another source than system, audio focus
  // must be requested.
  if (suspend_type != SuspendType::kSystem) {
    // Request audio focus again in case we lost it because another app started
    // playing while the playback was paused. If the audio focus request is
    // delayed we will resume the player when the request completes.
    AudioFocusDelegate::AudioFocusResult result =
        RequestSystemAudioFocus(desired_audio_focus_type_);

    SetAudioFocusState(result != AudioFocusDelegate::AudioFocusResult::kFailed
                           ? State::ACTIVE
                           : State::INACTIVE);

    if (audio_focus_state_ != State::ACTIVE)
      return;
  }

  OnResumeInternal(suspend_type);
}

void MediaSessionImpl::Suspend(SuspendType suspend_type) {
  if (!IsActive())
    return;

  if (suspend_type == SuspendType::kUI) {
    // If the site has registered an action handler for pause then we should
    // pass it to the site and let them handle it.
    if (ShouldRouteAction(media_session::mojom::MediaSessionAction::kPause)) {
      DidReceiveAction(media_session::mojom::MediaSessionAction::kPause);
      return;
    }

    MediaSessionUmaHelper::RecordMediaSessionUserAction(
        MediaSessionUserAction::kPauseDefault, focused_);
  }

  OnSuspendInternal(suspend_type, State::SUSPENDED);
}

void MediaSessionImpl::Stop(SuspendType suspend_type) {
  DCHECK(audio_focus_state_ != State::INACTIVE);
  DCHECK(suspend_type != SuspendType::kContent);
  DCHECK(!HasPepper());

  if (suspend_type == SuspendType::kUI) {
    // If the site has registered an action handle for stop then we should
    // notify the site but continue stopping the media session.
    if (ShouldRouteAction(media_session::mojom::MediaSessionAction::kStop)) {
      DidReceiveAction(media_session::mojom::MediaSessionAction::kStop);
    } else {
      MediaSessionUmaHelper::RecordMediaSessionUserAction(
          MediaSessionUmaHelper::MediaSessionUserAction::kStopDefault,
          focused_);
    }
  }

  if (auto* pip_window_controller_ =
          VideoPictureInPictureWindowControllerImpl::FromWebContents(
              web_contents())) {
    pip_window_controller_->Close(false /* should_pause_video */);
  }

  // TODO(mlamouri): merge the logic between UI and SYSTEM.
  if (suspend_type == SuspendType::kSystem) {
    OnSuspendInternal(suspend_type, State::INACTIVE);
    return;
  }

  if (audio_focus_state_ != State::SUSPENDED)
    OnSuspendInternal(suspend_type, State::SUSPENDED);

  DCHECK(audio_focus_state_ == State::SUSPENDED);
  normal_players_.clear();

  AbandonSystemAudioFocusIfNeeded();
  RebuildAndNotifyMediaPositionChanged();
}

void MediaSessionImpl::Seek(base::TimeDelta seek_time) {
  DCHECK(!seek_time.is_zero());

  if (seek_time.is_positive()) {
    // If the site has registered an action handler for seek forward then we
    // should pass it to the site and let them handle it.
    if (ShouldRouteAction(
            media_session::mojom::MediaSessionAction::kSeekForward)) {
      DidReceiveAction(media_session::mojom::MediaSessionAction::kSeekForward);
      return;
    }

    for (const auto& it : normal_players_)
      it.first.observer->OnSeekForward(it.first.player_id, seek_time);
  } else if (seek_time.is_negative()) {
    // If the site has registered an action handler for seek backward then we
    // should pass it to the site and let them handle it.
    if (ShouldRouteAction(
            media_session::mojom::MediaSessionAction::kSeekBackward)) {
      DidReceiveAction(media_session::mojom::MediaSessionAction::kSeekBackward);
      return;
    }

    for (const auto& it : normal_players_)
      it.first.observer->OnSeekBackward(it.first.player_id, seek_time * -1);
  }
}

bool MediaSessionImpl::IsControllable() const {
  if (audio_focus_state_ == State::INACTIVE || HasOnlyOneShotPlayers())
    return false;

#if !BUILDFLAG(IS_ANDROID)
  if (routed_service_ && routed_service_->playback_state() !=
                             blink::mojom::MediaSessionPlaybackState::NONE) {
    return true;
  }
#endif

  return desired_audio_focus_type_ == AudioFocusType::kGain;
}

void MediaSessionImpl::SetDuckingVolumeMultiplier(double multiplier) {
  ducking_volume_multiplier_ = std::clamp(multiplier, 0.0, 1.0);
}

void MediaSessionImpl::SetAudioFocusGroupId(
    const base::UnguessableToken& group_id) {
  audio_focus_group_id_ = group_id;
}

void MediaSessionImpl::StartDucking() {
  if (is_ducking_)
    return;
  is_ducking_ = true;
  UpdateVolumeMultiplier();
  RebuildAndNotifyMediaSessionInfoChanged();
}

void MediaSessionImpl::StopDucking() {
  if (!is_ducking_)
    return;
  is_ducking_ = false;
  UpdateVolumeMultiplier();
  RebuildAndNotifyMediaSessionInfoChanged();
}

void MediaSessionImpl::UpdateVolumeMultiplier() {
  for (const auto& it : normal_players_) {
    it.first.observer->OnSetVolumeMultiplier(it.first.player_id,
                                             GetVolumeMultiplier());
  }

  for (const auto& it : pepper_players_)
    it.observer->OnSetVolumeMultiplier(it.player_id, GetVolumeMultiplier());
}

double MediaSessionImpl::GetVolumeMultiplier() const {
  return is_ducking_ ? ducking_volume_multiplier_ : kUnduckedVolumeMultiplier;
}

bool MediaSessionImpl::IsActive() const {
  return audio_focus_state_ == State::ACTIVE;
}

bool MediaSessionImpl::IsSuspended() const {
  return audio_focus_state_ == State::SUSPENDED;
}

bool MediaSessionImpl::HasPepper() const {
  return !pepper_players_.empty();
}

#if BUILDFLAG(IS_OHOS)
bool MediaSessionImpl::HasOnlyOneShotPlayersPublic() const {
  return HasOnlyOneShotPlayers();
}
#endif //BUILDFLAG(IS_OHOS)

bool MediaSessionImpl::HasOnlyOneShotPlayers() const {
  return !one_shot_players_.empty() && normal_players_.empty() &&
         pepper_players_.empty();
}

void MediaSessionImpl::SetDelegateForTests(
    std::unique_ptr<AudioFocusDelegate> delegate) {
  delegate_ = std::move(delegate);
}

MediaSessionUmaHelper* MediaSessionImpl::uma_helper_for_test() {
  return &uma_helper_;
}

void MediaSessionImpl::RemoveAllPlayersForTest() {
  normal_players_.clear();
  pepper_players_.clear();
  one_shot_players_.clear();
  AbandonSystemAudioFocusIfNeeded();
}

void MediaSessionImpl::OnImageDownloadComplete(
    GetMediaImageBitmapCallback callback,
    int minimum_size_px,
    int desired_size_px,
    bool source_icon,
    int id,
    int http_status_code,
    const GURL& image_url,
    const std::vector<SkBitmap>& bitmaps,
    const std::vector<gfx::Size>& sizes) {
  DCHECK(bitmaps.size() == sizes.size());
  SkBitmap image;
  double best_image_score = 0.0;

  // Rank |sizes| and |bitmaps| using MediaImageManager.
  for (size_t i = 0; i < bitmaps.size(); i++) {
    double image_score = media_session::MediaImageManager::GetImageSizeScore(
        minimum_size_px, desired_size_px, sizes.at(i));

    if (image_score > best_image_score)
      image = bitmaps.at(i);
  }

  // If the image is the wrong color type then we should convert it.
  SkBitmap bitmap;
  if (!image.isNull()) {
    if (image.colorType() == kRGBA_8888_SkColorType) {
      bitmap = image;
    } else {
      SkImageInfo info = image.info().makeColorType(kRGBA_8888_SkColorType);
      if (bitmap.tryAllocPixels(info))
        image.readPixels(info, bitmap.getPixels(), bitmap.rowBytes(), 0, 0);
    }
  }

  if (source_icon) {
    GetPageData(web_contents()->GetPrimaryPage())
        .AddImageCache(image_url, bitmap);
  }

  std::move(callback).Run(bitmap);
}

void MediaSessionImpl::OnSystemAudioFocusRequested(bool result) {
  uma_helper_.RecordRequestAudioFocusResult(result);
  if (result)
    StopDucking();
}

void MediaSessionImpl::OnSuspendInternal(SuspendType suspend_type,
                                         State new_state) {
  LOG(INFO) << "MediaSessionImpl::OnSuspendInternal";
  DCHECK(!HasPepper());

  DCHECK(new_state == State::SUSPENDED || new_state == State::INACTIVE);
  // UI suspend cannot use State::INACTIVE.
  DCHECK(suspend_type == SuspendType::kSystem || new_state == State::SUSPENDED);

  if (HasOnlyOneShotPlayers()) {
    LOG(INFO) << "MediaSessionImpl::OnSuspendInternal, HasOnlyOneShotPlayers";
    return;
  }

  if (audio_focus_state_ != State::ACTIVE) {
    LOG(INFO) << "MediaSessionImpl::OnSuspendInternal, audio_focus_state_: " << static_cast<int> (audio_focus_state_);
    return;
  }

  switch (suspend_type) {
    case SuspendType::kUI:
      uma_helper_.RecordSessionSuspended(MediaSessionSuspendedSource::kUI);
      break;
    case SuspendType::kSystem:
      switch (new_state) {
        case State::SUSPENDED:
          uma_helper_.RecordSessionSuspended(
              MediaSessionSuspendedSource::kSystemTransient);
          break;
        case State::INACTIVE:
          uma_helper_.RecordSessionSuspended(
              MediaSessionSuspendedSource::kSystemPermanent);
          break;
        case State::ACTIVE:
          NOTREACHED();
          break;
      }
      break;
    case SuspendType::kContent:
      uma_helper_.RecordSessionSuspended(MediaSessionSuspendedSource::kCONTENT);
      break;
  }

  SetAudioFocusState(new_state);
  suspend_type_ = suspend_type;

  if (suspend_type != SuspendType::kContent) {
    // SuspendType::CONTENT happens when the suspend action came from
    // the page in which case the player is already paused.
    // Otherwise, the players need to be paused.
    for (const auto& it : normal_players_)
      it.first.observer->OnSuspend(it.first.player_id);
  }

  for (const auto& it : pepper_players_)
    it.observer->OnSetVolumeMultiplier(it.player_id,
                                       ducking_volume_multiplier_);

  RebuildAndNotifyMediaSessionInfoChanged();
}

void MediaSessionImpl::OnResumeInternal(SuspendType suspend_type) {
  if (suspend_type == SuspendType::kSystem && suspend_type_ != suspend_type)
    return;

  SetAudioFocusState(State::ACTIVE);

  for (const auto& it : normal_players_)
    it.first.observer->OnResume(it.first.player_id);

  for (const auto& it : pepper_players_)
    it.observer->OnSetVolumeMultiplier(it.player_id, GetVolumeMultiplier());

  RebuildAndNotifyMediaSessionInfoChanged();
}

MediaSessionImpl::MediaSessionImpl(WebContents* web_contents)
    : WebContentsObserver(web_contents),
      WebContentsUserData<MediaSessionImpl>(*web_contents),
      audio_focus_state_(State::INACTIVE),
      desired_audio_focus_type_(AudioFocusType::kGainTransientMayDuck),
      is_ducking_(false),
      ducking_volume_multiplier_(kDefaultDuckingVolumeMultiplier),
      routed_service_(nullptr)
#ifdef OHOS_MEDIA_POLICY
      , weakMediaSessionFactory_(this)
#endif // OHOS_MEDIA_POLICY
{
#if BUILDFLAG(IS_ANDROID)
  session_android_ = std::make_unique<MediaSessionAndroid>(this);
  should_throttle_duration_update_ = true;
#endif  // BUILDFLAG(IS_ANDROID)
#if defined(OHOS_MEDIA_AVSESSION)
  auto currentProcess = base::CommandLine::ForCurrentProcess();
  if (currentProcess && !currentProcess->HasSwitch(switches::kEnableMediaAvsession)) {
    session_ohos_ = std::make_unique<MediaSessionOHOS>(this);
  }
#endif  // defined(OHOS_MEDIA_AVSESSION)
  if (web_contents && web_contents->GetPrimaryMainFrame() &&
      web_contents->GetPrimaryMainFrame()->GetView()) {
    focused_ = web_contents->GetPrimaryMainFrame()->GetView()->HasFocus();
  }

  RebuildAndNotifyMetadataChanged();
}

void MediaSessionImpl::Initialize() {
  delegate_ = AudioFocusDelegate::Create(this);
  delegate_->MediaSessionInfoChanged(GetMediaSessionInfoSync());

  DCHECK(web_contents());
  DidUpdateFaviconURL(web_contents()->GetPrimaryMainFrame(),
                      web_contents()->GetFaviconURLs());

  content::GetContentClient()->browser()->AddPresentationObserver(
      this, web_contents());
}

void MediaSessionImpl::OnPresentationsChanged(bool has_presentation) {
  has_presentation_ = has_presentation;
  RebuildAndNotifyMediaSessionInfoChanged();
}

AudioFocusDelegate::AudioFocusResult MediaSessionImpl::RequestSystemAudioFocus(
    AudioFocusType audio_focus_type) {
  // |kGainTransient| is not used in MediaSessionImpl.
  DCHECK_NE(media_session::mojom::AudioFocusType::kGainTransient,
            audio_focus_type);

  AudioFocusDelegate::AudioFocusResult result =
      delegate_->RequestAudioFocus(audio_focus_type);
  desired_audio_focus_type_ = audio_focus_type;

  bool success = result != AudioFocusDelegate::AudioFocusResult::kFailed;
  SetAudioFocusState(success ? State::ACTIVE : State::INACTIVE);

  // If we are delayed then we should return now and wait for the response from
  // the audio focus delegate.
  if (result == AudioFocusDelegate::AudioFocusResult::kDelayed)
    return result;

  OnSystemAudioFocusRequested(success);
  return result;
}

mojo::PendingRemote<media_session::mojom::MediaSession>
MediaSessionImpl::AddRemote() {
  mojo::PendingRemote<media_session::mojom::MediaSession> remote;
  receivers_.Add(this, remote.InitWithNewPipeAndPassReceiver());
  return remote;
}

void MediaSessionImpl::GetDebugInfo(GetDebugInfoCallback callback) {
  media_session::mojom::MediaSessionDebugInfoPtr info(
      media_session::mojom::MediaSessionDebugInfo::New());

  // Add the title and the url to the owner.
  std::vector<std::string> owner_parts;
  MaybePushBackString(owner_parts,
                      base::UTF16ToUTF8(web_contents()->GetTitle()));
  MaybePushBackString(owner_parts,
                      web_contents()->GetLastCommittedURL().spec());
  info->owner = base::JoinString(owner_parts, kDebugInfoOwnerSeparator);

  std::move(callback).Run(std::move(info));
}

media_session::mojom::MediaSessionInfoPtr
MediaSessionImpl::GetMediaSessionInfoSync() {
  media_session::mojom::MediaSessionInfoPtr info(
      media_session::mojom::MediaSessionInfo::New());

  switch (audio_focus_state_) {
    case State::ACTIVE:
      info->state = MediaSessionInfo::SessionState::kActive;
      break;
    case State::SUSPENDED:
      info->state = MediaSessionInfo::SessionState::kSuspended;
      break;
    case State::INACTIVE:
      info->state = MediaSessionInfo::SessionState::kInactive;
      break;
  }

  // The state should always be kDucked if we are ducked.
  if (is_ducking_)
    info->state = MediaSessionInfo::SessionState::kDucking;

  // If we have Pepper players then we should force ducking.
  info->force_duck = HasPepper();

  // The playback state should use |IsActive| to determine whether we are
  // playing or not. However, if there is a |routed_service_| which is playing
  // then we should force the playback state to be playing.
  info->playback_state =
      IsActive() ? MediaPlaybackState::kPlaying : MediaPlaybackState::kPaused;
  if (routed_service_ &&
      routed_service_->playback_state() == MediaSessionPlaybackState::PLAYING) {
    info->playback_state = MediaPlaybackState::kPlaying;
  }

  info->audio_video_states = GetMediaAudioVideoStates();
  info->is_controllable = IsControllable();

  // If the browser context is off the record then it should be sensitive.
  info->is_sensitive = web_contents()->GetBrowserContext()->IsOffTheRecord();

  info->picture_in_picture_state =
      web_contents()->HasPictureInPictureVideo()
          ? media_session::mojom::MediaPictureInPictureState::
                kInPictureInPicture
          : media_session::mojom::MediaPictureInPictureState::
                kNotInPictureInPicture;

  auto shared_audio_device_id = GetSharedAudioOutputDeviceId();
  // When the default audio device is in use, or this session's players are
  // using different devices, the |audio_sink_id| attribute should remain unset.
  if (shared_audio_device_id != media::AudioDeviceDescription::kDefaultDeviceId)
    info->audio_sink_id = shared_audio_device_id;

  if (routed_service_) {
    info->microphone_state = routed_service_->microphone_state();
    info->camera_state = routed_service_->camera_state();
  } else {
    info->microphone_state = media_session::mojom::MicrophoneState::kUnknown;
    info->camera_state = media_session::mojom::CameraState::kUnknown;
  }

  info->muted = is_muted_;
  info->has_presentation = has_presentation_;

  // Disable Remote Playback by passing empty RemotePlaybackMetadata when there
  // are multiple media players.
  if (normal_players_.size() == 1u) {
    info->remote_playback_metadata = remote_playback_metadata_.Clone();
  }
  return info;
}

void MediaSessionImpl::GetMediaSessionInfo(
    GetMediaSessionInfoCallback callback) {
  std::move(callback).Run(GetMediaSessionInfoSync());
}

void MediaSessionImpl::AddObserver(
    mojo::PendingRemote<media_session::mojom::MediaSessionObserver> observer) {
  mojo::Remote<media_session::mojom::MediaSessionObserver>
      media_session_observer(std::move(observer));
  media_session_observer->MediaSessionInfoChanged(GetMediaSessionInfoSync());
  media_session_observer->MediaSessionMetadataChanged(metadata_);
  media_session_observer->MediaSessionImagesChanged(images_);
  media_session_observer->MediaSessionPositionChanged(position_);

  std::vector<media_session::mojom::MediaSessionAction> actions(
      actions_.begin(), actions_.end());
  media_session_observer->MediaSessionActionsChanged(actions);

  observers_.Add(std::move(media_session_observer));
}

void MediaSessionImpl::FinishSystemAudioFocusRequest(
    AudioFocusType audio_focus_type,
    bool result) {
  // If the media session is not active then we do not need to enforce the
  // result of the audio focus request.
  if (audio_focus_state_ != State::ACTIVE) {
    AbandonSystemAudioFocusIfNeeded();
    return;
  }

  OnSystemAudioFocusRequested(result);

  if (!result && !HasPepper()) {
    switch (audio_focus_type) {
      case AudioFocusType::kGain:
        // If the gain audio focus request failed then we should suspend the
        // media session.
        OnSuspendInternal(SuspendType::kSystem, State::SUSPENDED);
        break;
      case AudioFocusType::kAmbient:
      case AudioFocusType::kGainTransient:
        // MediaSessionImpl does not use |kGainTransient| or |kAmbient|.
        NOTREACHED();
        break;
      case AudioFocusType::kGainTransientMayDuck:
        // The focus request failed, we should suspend any players that have
        // the same audio focus type.
        for (auto& player : normal_players_) {
          if (audio_focus_type == player.second)
            player.first.observer->OnSuspend(player.first.player_id);
        }
        break;
    }
  }
}

void MediaSessionImpl::PreviousTrack() {
  DidReceiveAction(media_session::mojom::MediaSessionAction::kPreviousTrack);
}

void MediaSessionImpl::NextTrack() {
  DidReceiveAction(media_session::mojom::MediaSessionAction::kNextTrack);
}

void MediaSessionImpl::SkipAd() {
  DidReceiveAction(media_session::mojom::MediaSessionAction::kSkipAd);
}

void MediaSessionImpl::PreviousSlide() {
  DidReceiveAction(media_session::mojom::MediaSessionAction::kPreviousSlide);
}

void MediaSessionImpl::NextSlide() {
  DidReceiveAction(media_session::mojom::MediaSessionAction::kNextSlide);
}

void MediaSessionImpl::SeekTo(base::TimeDelta seek_time) {
  // If the site has registered an action handler for seek to then we
  // should pass it to the site and let them handle it.
  if (ShouldRouteAction(media_session::mojom::MediaSessionAction::kSeekTo)) {
    DidReceiveAction(media_session::mojom::MediaSessionAction::kSeekTo,
                     blink::mojom::MediaSessionActionDetails::NewSeekTo(
                         blink::mojom::MediaSessionSeekToDetails::New(
                             seek_time, /*fast_seek=*/false)));
    return;
  }

  for (const auto& it : normal_players_)
    it.first.observer->OnSeekTo(it.first.player_id, seek_time);
}

void MediaSessionImpl::ScrubTo(base::TimeDelta seek_time) {
  // If the site has registered an action handler for seek to then we
  // should pass it to the site and let them handle it.
  if (ShouldRouteAction(media_session::mojom::MediaSessionAction::kSeekTo)) {
    DidReceiveAction(media_session::mojom::MediaSessionAction::kSeekTo,
                     blink::mojom::MediaSessionActionDetails::NewSeekTo(
                         blink::mojom::MediaSessionSeekToDetails::New(
                             seek_time, /*fast_seek=*/true)));
    return;
  }

  for (const auto& it : normal_players_)
    it.first.observer->OnSeekTo(it.first.player_id, seek_time);
}

void MediaSessionImpl::EnterPictureInPicture() {
  if (ShouldRouteAction(
          media_session::mojom::MediaSessionAction::kEnterPictureInPicture)) {
    DidReceiveAction(
        media_session::mojom::MediaSessionAction::kEnterPictureInPicture);
    return;
  }

  // There should be one and only one player when we enter picture-in-picture.
  DCHECK_EQ(normal_players_.size(), 1u);
  normal_players_.begin()->first.observer->OnEnterPictureInPicture(
      normal_players_.begin()->first.player_id);
}

void MediaSessionImpl::ExitPictureInPicture() {
  if (ShouldRouteAction(
          media_session::mojom::MediaSessionAction::kExitPictureInPicture)) {
    DidReceiveAction(
        media_session::mojom::MediaSessionAction::kExitPictureInPicture);
    return;
  }

  // There should be one and only one player when we exit picture-in-picture.
  DCHECK_EQ(normal_players_.size(), 1u);
  normal_players_.begin()->first.observer->OnExitPictureInPicture(
      normal_players_.begin()->first.player_id);
}

#ifdef OHOS_MEDIA_POLICY
MediaSessionImpl::NWebPlaybackState MediaSessionImpl::NWebGetState() {
  if (GetMediaAudioVideoStates().empty()) {
    return NWebPlaybackState::NONE;
  }
  if (IsSuspended()) {
    return NWebPlaybackState::PAUSED;
  }
  if (IsActive()) {
    return NWebPlaybackState::PLAYING;
  } else {
    return NWebPlaybackState::STOP;
  }
}

bool MediaSessionImpl::IsEndOfMedia() {
  bool ret = true;
  if (position_) {
    ret = position_->end_of_media();
    if (ret) {
      return ret;
    }
  }
  if (session_ohos_) {
    ret = session_ohos_->IsEndOfMedia();
    if (ret) {
      return ret;
    }
  }
  return ret;
}

void MediaSessionImpl::SetEndOfMedia(bool end_of_media)
{
  if (session_ohos_) {
    session_ohos_->SetEndOfMedia(end_of_media);
  }
}

bool MediaSessionImpl::GetPlayingState()
{
  return isPlayingState_;
}

void MediaSessionImpl::SetPlayingState(bool playingState)
{
#if defined(OHOS_MEDIA_AVSESSION)
  RebuildAndNotifyMetadataChanged();
#endif // OHOS_MEDIA_AVSESSION
  isPlayingState_ = playingState;
}

bool MediaSessionImpl::GetMuteState()
{
  return is_muted_;
}

bool MediaSessionImpl::IsPauseByAvsession() {
    bool ret = false;
    if (session_ohos_) {
        ret = session_ohos_->IsPauseByAvsession();
    }
    return ret;
}

void MediaSessionImpl::SetPauseByAvsession(bool is_pause) {
    if (session_ohos_) {
        session_ohos_->SetPauseByAvsession(is_pause);
    }
}

void MediaSessionImpl::SetWebviewShow(bool show, bool is_special_for_audio) {
    if (session_ohos_) {
        session_ohos_->SetWebviewShow(show, is_special_for_audio);
    }
}

void MediaSessionImpl::SetSessionState(MediaSessionImpl::NWebMediaSessionState sessionState)
{
  sessionState_ = sessionState;
}

MediaSessionImpl::NWebMediaSessionState MediaSessionImpl::GetSessionState()
{
  return sessionState_;
}

#endif // OHOS_MEDIA_POLICY

void MediaSessionImpl::SetAudioSinkId(const absl::optional<std::string>& id) {
  audio_device_id_for_origin_ = id;

  for (const auto& it : normal_players_) {
    it.first.observer->OnSetAudioSinkId(
        it.first.player_id,
        id.value_or(media::AudioDeviceDescription::kDefaultDeviceId));
  }
}

void MediaSessionImpl::ToggleMicrophone() {
  DidReceiveAction(media_session::mojom::MediaSessionAction::kToggleMicrophone);
}

void MediaSessionImpl::ToggleCamera() {
  DidReceiveAction(media_session::mojom::MediaSessionAction::kToggleCamera);
}

void MediaSessionImpl::HangUp() {
  DidReceiveAction(media_session::mojom::MediaSessionAction::kHangUp);
}

void MediaSessionImpl::Raise() {
  content::WebContentsDelegate* delegate = web_contents()->GetDelegate();
  if (!delegate)
    return;

  delegate->ActivateContents(web_contents());
}

void MediaSessionImpl::SetMute(bool mute) {
  DCHECK_EQ(normal_players_.size(), 1u);
  normal_players_.begin()->first.observer->OnSetMute(
      normal_players_.begin()->first.player_id, mute);
}

void MediaSessionImpl::RequestMediaRemoting() {
  DCHECK_EQ(normal_players_.size(), 1u);
  normal_players_.begin()->first.observer->OnRequestMediaRemoting(
      normal_players_.begin()->first.player_id);
}

void MediaSessionImpl::GetMediaImageBitmap(
    const media_session::MediaImage& image,
    int minimum_size_px,
    int desired_size_px,
    GetMediaImageBitmapCallback callback) {
  // We should make sure |image| is in |images_|.
  bool found = false;
  bool source_icon = false;
  for (auto& image_type : images_) {
    if (base::Contains(image_type.second, image)) {
      found = true;

      if (image_type.first ==
          media_session::mojom::MediaSessionImageType::kSourceIcon) {
        source_icon = true;
      }
    }
  }

  if (!found || !IsSizesAtLeast(image.sizes, minimum_size_px)) {
    std::move(callback).Run(SkBitmap());
    return;
  }

  // Check the cache.
  PageData& page_data = GetPageData(web_contents()->GetPrimaryPage());
  if (source_icon) {
    if (auto* bitmap = page_data.GetImageCache(image.src)) {
      std::move(callback).Run(*bitmap);
      return;
    }
  }

  const gfx::Size preferred_size(desired_size_px, desired_size_px);
  web_contents()->DownloadImage(
      image.src, false /* is_favicon */, preferred_size,
      desired_size_px /* max_bitmap_size */, false /* bypass_cache */,
      base::BindOnce(&MediaSessionImpl::OnImageDownloadComplete,
                     base::Unretained(this),
                     mojo::WrapCallbackWithDefaultInvokeIfNotRun(
                         std::move(callback), SkBitmap()),
                     minimum_size_px, desired_size_px, source_icon));
}

void MediaSessionImpl::AbandonSystemAudioFocusIfNeeded() {
  if (audio_focus_state_ == State::INACTIVE || !normal_players_.empty() ||
      !pepper_players_.empty() || !one_shot_players_.empty()) {
    return;
  }
  delegate_->AbandonAudioFocus();
  is_ducking_ = false;

  SetAudioFocusState(State::INACTIVE);
  RebuildAndNotifyMediaSessionInfoChanged();
  RebuildAndNotifyActionsChanged();
}

void MediaSessionImpl::SetAudioFocusState(State audio_focus_state) {
  if (audio_focus_state == audio_focus_state_)
    return;

  audio_focus_state_ = audio_focus_state;
  switch (audio_focus_state_) {
    case State::ACTIVE:
      uma_helper_.OnSessionActive();
      break;
    case State::SUSPENDED:
      uma_helper_.OnSessionSuspended();
      break;
    case State::INACTIVE:
      uma_helper_.OnSessionInactive();
      break;
  }

  RebuildAndNotifyMediaSessionInfoChanged();
}

void MediaSessionImpl::FlushForTesting() {
  observers_.FlushForTesting();
}

void MediaSessionImpl::RebuildAndNotifyMediaSessionInfoChanged() {
  media_session::mojom::MediaSessionInfoPtr current_info =
      GetMediaSessionInfoSync();

  if (current_info == session_info_)
    return;

  // Picture-in-Picture window controller needs to be updated on current media
  // session info.
  if (auto* pip_window_controller_ =
          VideoPictureInPictureWindowControllerImpl::FromWebContents(
              web_contents())) {
    pip_window_controller_->MediaSessionInfoChanged(current_info);
  }

  for (auto& observer : observers_)
    observer->MediaSessionInfoChanged(current_info.Clone());

  delegate_->MediaSessionInfoChanged(current_info);

  session_info_ = std::move(current_info);
}

bool MediaSessionImpl::AddPepperPlayer(MediaSessionPlayerObserver* observer,
                                       int player_id) {
  AudioFocusDelegate::AudioFocusResult result =
      RequestSystemAudioFocus(AudioFocusType::kGain);

  if (result == AudioFocusDelegate::AudioFocusResult::kFailed)
    return false;

  pepper_players_.insert(PlayerIdentifier(observer, player_id));

  observer->OnSetVolumeMultiplier(player_id, GetVolumeMultiplier());

  UpdateRoutedService();
  RebuildAndNotifyMediaSessionInfoChanged();
  RebuildAndNotifyMediaPositionChanged();

  return result != AudioFocusDelegate::AudioFocusResult::kFailed;
}

bool MediaSessionImpl::AddOneShotPlayer(MediaSessionPlayerObserver* observer,
                                        int player_id) {
  AudioFocusDelegate::AudioFocusResult result =
      RequestSystemAudioFocus(AudioFocusType::kGain);

  if (result == AudioFocusDelegate::AudioFocusResult::kFailed)
    return false;

  one_shot_players_.insert(PlayerIdentifier(observer, player_id));

  UpdateRoutedService();
  RebuildAndNotifyMediaSessionInfoChanged();
  RebuildAndNotifyMediaPositionChanged();

  return true;
}

// MediaSessionService-related methods

void MediaSessionImpl::OnServiceCreated(MediaSessionServiceImpl* service) {
  const auto rfh_id = service->GetRenderFrameHostId();

  services_[rfh_id] = service;
  UpdateRoutedService();
}

void MediaSessionImpl::OnServiceDestroyed(MediaSessionServiceImpl* service) {
  services_.erase(service->GetRenderFrameHostId());

  if (routed_service_ == service)
    UpdateRoutedService();
}

void MediaSessionImpl::OnMediaSessionPlaybackStateChanged(
    MediaSessionServiceImpl* service) {
  if (service != routed_service_)
    return;

  RebuildAndNotifyMediaSessionInfoChanged();
  RebuildAndNotifyActionsChanged();
}

void MediaSessionImpl::OnMediaSessionMetadataChanged(
    MediaSessionServiceImpl* service) {
  if (service != routed_service_)
    return;

  RebuildAndNotifyMetadataChanged();
}

void MediaSessionImpl::OnMediaSessionActionsChanged(
    MediaSessionServiceImpl* service) {
  if (service != routed_service_)
    return;

  RebuildAndNotifyActionsChanged();
}

void MediaSessionImpl::OnMediaSessionInfoChanged(
    MediaSessionServiceImpl* service) {
  if (service != routed_service_)
    return;

  RebuildAndNotifyMediaSessionInfoChanged();
}

void MediaSessionImpl::DidReceiveAction(
    media_session::mojom::MediaSessionAction action) {
  DidReceiveAction(action, nullptr /* details */);
}

void MediaSessionImpl::DidReceiveAction(
    media_session::mojom::MediaSessionAction action,
    blink::mojom::MediaSessionActionDetailsPtr details) {
  MediaSessionUmaHelper::RecordMediaSessionUserAction(
      MediaSessionActionToUserAction(action), focused_);

  // Pause all players in non-routed frames if the action is PAUSE.
  //
  // This is the default PAUSE action handler per Media Session API spec. The
  // reason for pausing all players in all other sessions is to avoid the
  // players in other frames keep the session active so that the UI will always
  // show the pause button but it does not pause anything (as the routed frame
  // already pauses when responding to the PAUSE action while other frames does
  // not).
  //
  // TODO(zqzhang): Currently, this might not work well on desktop as Pepper and
  // OneShot players are not really suspended, so that the session is still
  // active after this. See https://crbug.com/619084 and
  // https://crbug.com/596516.
  if (media_session::mojom::MediaSessionAction::kPause == action) {
    RenderFrameHost* rfh_of_routed_service =
        routed_service_ ? routed_service_->GetRenderFrameHost() : nullptr;
    for (const auto& player : normal_players_) {
      if (player.first.observer->render_frame_host() != rfh_of_routed_service)
        player.first.observer->OnSuspend(player.first.player_id);
    }
    for (const auto& player : pepper_players_) {
      if (player.observer->render_frame_host() != rfh_of_routed_service) {
        player.observer->OnSetVolumeMultiplier(player.player_id,
                                               ducking_volume_multiplier_);
      }
    }
    for (const auto& player : one_shot_players_) {
      if (player.observer->render_frame_host() != rfh_of_routed_service)
        player.observer->OnSuspend(player.player_id);
    }
  }

  if (!routed_service_)
    return;

  routed_service_->GetClient()->DidReceiveAction(action, std::move(details));
}

bool MediaSessionImpl::IsServiceActiveForRenderFrameHost(RenderFrameHost* rfh) {
  return services_.find(rfh->GetGlobalId()) != services_.end();
}

void MediaSessionImpl::UpdateRoutedService() {
  MediaSessionServiceImpl* new_service = ComputeServiceForRouting();

  if (new_service == routed_service_)
    return;

  routed_service_ = new_service;

  RebuildAndNotifyMetadataChanged();
  RebuildAndNotifyActionsChanged();
  RebuildAndNotifyMediaSessionInfoChanged();
  RebuildAndNotifyMediaPositionChanged();

  if (routed_service_ &&
      !BackForwardCacheImpl::IsMediaSessionServiceAllowed()) {
    // A page in the back-forward cache may affect the media control UI
    // displayed to users. So it is marked as ineligible as soon as a
    // MediaSession service is associated with it.
    BackForwardCache::DisableForRenderFrameHost(
        routed_service_->GetRenderFrameHostId(),
        BackForwardCacheDisable::DisabledReason(
            BackForwardCacheDisable::DisabledReasonId::kMediaSessionService));
  }
}

MediaSessionServiceImpl* MediaSessionImpl::ComputeServiceForRouting() {
  // The service selection strategy is: select a frame that has a playing/paused
  // player and has a corresponding MediaSessionService and return the
  // corresponding MediaSessionService. If multiple frames satisfy the criteria,
  // prefer the top-most frame.
  std::set<RenderFrameHost*> frames;
  for (const auto& player : normal_players_) {
    RenderFrameHost* frame = player.first.observer->render_frame_host();
    if (frame)
      frames.insert(frame);
  }

  for (const auto& player : one_shot_players_) {
    RenderFrameHost* frame = player.observer->render_frame_host();
    if (frame)
      frames.insert(frame);
  }

  for (const auto& player : pepper_players_) {
    RenderFrameHost* frame = player.observer->render_frame_host();
    if (frame)
      frames.insert(frame);
  }

  RenderFrameHost* best_frame = nullptr;
  size_t min_depth = std::numeric_limits<size_t>::max();
  std::map<RenderFrameHost*, size_t> map_rfh_to_depth;

  for (RenderFrameHost* frame : frames) {
    size_t depth = ComputeFrameDepth(frame, &map_rfh_to_depth);
    if (depth >= min_depth)
      continue;
    if (!IsServiceActiveForRenderFrameHost(frame))
      continue;
    best_frame = frame;
    min_depth = depth;
  }

  return best_frame ? services_[best_frame->GetGlobalId()] : nullptr;
}

void MediaSessionImpl::OnMediaMutedStatusChanged(bool mute) {
  is_muted_ = mute;
  RebuildAndNotifyMediaSessionInfoChanged();
}

void MediaSessionImpl::OnPictureInPictureAvailabilityChanged() {
  if (normal_players_.size() != 1)
    return;

  RebuildAndNotifyActionsChanged();
}

void MediaSessionImpl::OnAudioOutputSinkIdChanged() {
  if (audio_device_id_for_origin_ &&
      audio_device_id_for_origin_ != GetSharedAudioOutputDeviceId()) {
    audio_device_id_for_origin_.reset();
  }

  RebuildAndNotifyMediaSessionInfoChanged();
}

void MediaSessionImpl::OnAudioOutputSinkChangingDisabled() {
  RebuildAndNotifyMediaSessionInfoChanged();
}

void MediaSessionImpl::SetRemotePlaybackMetadata(
    media_session::mojom::RemotePlaybackMetadataPtr metadata) {
  remote_playback_metadata_ = std::move(metadata);
  RebuildAndNotifyMediaSessionInfoChanged();
}

bool MediaSessionImpl::ShouldRouteAction(
    media_session::mojom::MediaSessionAction action) const {
  return routed_service_ && base::Contains(routed_service_->actions(), action);
}

const base::UnguessableToken& MediaSessionImpl::GetSourceId() const {
  return MediaSessionData::GetOrCreate(web_contents()->GetBrowserContext())
      ->source_id();
}

const base::UnguessableToken& MediaSessionImpl::GetRequestId() const {
  return delegate_->request_id();
}

base::WeakPtr<MediaSessionImpl> MediaSessionImpl::GetWeakPtr() {
  return weak_factory_.GetWeakPtr();
}

void MediaSessionImpl::RebuildAndNotifyActionsChanged() {
  std::set<media_session::mojom::MediaSessionAction> actions =
      routed_service_ ? routed_service_->actions()
                      : std::set<media_session::mojom::MediaSessionAction>();

  // Picture-in-Picture window controller needs to know only actions that are
  // handled by the website.
  if (auto* pip_window_controller_ =
          VideoPictureInPictureWindowControllerImpl::FromWebContents(
              web_contents())) {
    pip_window_controller_->MediaSessionActionsChanged(actions);
  }

  // If we are controllable then we should always add these actions as we can
  // support them by directly interacting with the players underneath.
  if (IsControllable()) {
    actions.insert(media_session::mojom::MediaSessionAction::kPlay);
    actions.insert(media_session::mojom::MediaSessionAction::kPause);
    actions.insert(media_session::mojom::MediaSessionAction::kStop);
    actions.insert(media_session::mojom::MediaSessionAction::kSeekTo);
    actions.insert(media_session::mojom::MediaSessionAction::kScrubTo);
  }

  if (base::FeatureList::IsEnabled(
          media::kGlobalMediaControlsPictureInPicture) &&
      IsPictureInPictureAvailable()) {
    actions.insert(
        media_session::mojom::MediaSessionAction::kEnterPictureInPicture);
    actions.insert(
        media_session::mojom::MediaSessionAction::kExitPictureInPicture);
  }

  if (base::FeatureList::IsEnabled(
          media::kGlobalMediaControlsSeamlessTransfer) &&
      IsAudioOutputDeviceSwitchingSupported()) {
    actions.insert(
        media_session::mojom::MediaSessionAction::kSwitchAudioDevice);
  }

  if (actions_ == actions)
    return;

  actions_ = actions;

  std::vector<media_session::mojom::MediaSessionAction> actions_vec(
      actions.begin(), actions.end());
  for (auto& observer : observers_)
    observer->MediaSessionActionsChanged(actions_vec);
}

void MediaSessionImpl::RebuildAndNotifyMetadataChanged() {
  std::vector<media_session::MediaImage> artwork;
  media_session::MediaMetadata metadata;

  if (routed_service_ && routed_service_->metadata()) {
    metadata.title = routed_service_->metadata()->title;
    metadata.artist = routed_service_->metadata()->artist;
    metadata.album = routed_service_->metadata()->album;
    artwork = routed_service_->metadata()->artwork;
  }

  if (metadata.title.empty())
    metadata.title = SanitizeMediaTitle(web_contents()->GetTitle());

#if defined(OHOS_MEDIA_AVSESSION)
  if (web_contents()) {
    std::u16string u16title =
        base::UTF8ToUTF16(web_contents()->GetMediaTitle());
    if (!u16title.empty()) {
      metadata.title = u16title;
    }
  }
#endif // OHOS_MEDIA_AVSESSION

  ContentClient* content_client = content::GetContentClient();
  const GURL& url = web_contents()->GetLastCommittedURL();

  // If |url| wraps a chrome extension ID or System Web App, we can display the
  // extension or app name instead, which is more human-readable.
  std::u16string source_title;
  WebContentsDelegate* delegate = web_contents()->GetDelegate();
  if (delegate) {
    source_title =
        base::UTF8ToUTF16(delegate->GetTitleForMediaControls(web_contents()));
  }

  if (source_title.empty()) {
    // If the url is a file then we should display a placeholder.
    source_title =
        url.SchemeIsFile()
            ? content_client->GetLocalizedString(IDS_MEDIA_SESSION_FILE_SOURCE)
            : url_formatter::FormatUrl(
                  url::Origin::Create(url).GetURL(),
                  url_formatter::kFormatUrlOmitDefaults |
                      url_formatter::kFormatUrlOmitHTTPS |
                      url_formatter::kFormatUrlOmitTrivialSubdomains,
                  base::UnescapeRule::SPACES, nullptr, nullptr, nullptr);
  }

  metadata.source_title = source_title;

#if defined(OHOS_MEDIA_AVSESSION)
  std::string attrib_image_url = web_contents()->GetVideoPoster();
  media_session::MediaImage mediaImage;
  mediaImage.src = GURL(attrib_image_url);
  artwork.push_back(mediaImage);
#endif // OHOS_MEDIA_AVSESSION

  // If we have no artwork in |images_| or the arwork has changed then we should
  // update it with the latest artwork from the routed service.
  auto it = images_.find(MediaSessionImageType::kArtwork);
  bool images_changed = it == images_.end() || it->second != artwork;
  if (images_changed)
    images_.insert_or_assign(MediaSessionImageType::kArtwork, artwork);

  bool metadata_changed = metadata_ != metadata;
  if (metadata_changed)
    metadata_ = metadata;
#ifndef OHOS_MEDIA_AVSESSION
  if (!images_changed && !metadata_changed)
    return;
#endif
  for (auto& observer : observers_) {
#ifndef OHOS_MEDIA_AVSESSION
    if (metadata_changed)
      observer->MediaSessionMetadataChanged(this->metadata_);
#else
    if (metadata_changed) {
      if (observer) {
        observer->MediaSessionMetadataChanged(this->metadata_);
      }
    }
#endif
    if (images_changed)
      observer->MediaSessionImagesChanged(this->images_);
  }
}

bool MediaSessionImpl::IsPictureInPictureAvailable() const {
  if (normal_players_.size() != 1)
    return false;

  auto& first = normal_players_.begin()->first;
  return first.observer->IsPictureInPictureAvailable(first.player_id);
}

std::string MediaSessionImpl::GetSharedAudioOutputDeviceId() const {
  if (normal_players_.empty())
    return media::AudioDeviceDescription::kDefaultDeviceId;

  auto& first = normal_players_.begin()->first;
  const auto& first_id = first.observer->GetAudioOutputSinkId(first.player_id);
  if (base::ranges::all_of(normal_players_, [&first_id](const auto& player) {
        return player.first.observer->GetAudioOutputSinkId(
                   player.first.player_id) == first_id;
      })) {
    return first_id;
  }

  return media::AudioDeviceDescription::kDefaultDeviceId;
}

bool MediaSessionImpl::IsAudioOutputDeviceSwitchingSupported() const {
  if (normal_players_.empty())
    return false;

  return base::ranges::all_of(normal_players_, [](const auto& player) {
    return player.first.observer->SupportsAudioOutputDeviceSwitching(
        player.first.player_id);
  });
}

std::vector<MediaAudioVideoState> MediaSessionImpl::GetMediaAudioVideoStates() {
  RenderFrameHost* routed_rfh =
      routed_service_ ? routed_service_->GetRenderFrameHost() : nullptr;
  std::vector<MediaAudioVideoState> states;

  ForAllPlayers(base::BindRepeating(
      [](RenderFrameHost* routed_rfh, std::vector<MediaAudioVideoState>* states,
         const PlayerIdentifier& player) {
        // If we have a routed frame then we should limit the players to the
        // frame so it is aligned with the media metadata.
#ifdef OHOS_BUGFIX_CRASH
        if (!player.observer || (routed_rfh && player.observer->render_frame_host() != routed_rfh))
          return;
#else
        if (routed_rfh && player.observer->render_frame_host() != routed_rfh)
          return;
#endif

        const bool has_audio = player.observer->HasAudio(player.player_id);
        const bool has_video = player.observer->HasVideo(player.player_id);
        if (has_audio && has_video) {
          states->push_back(MediaAudioVideoState::kAudioVideo);
        } else if (has_audio) {
          states->push_back(MediaAudioVideoState::kAudioOnly);
        } else if (has_video) {
          states->push_back(MediaAudioVideoState::kVideoOnly);
        }
      },
      routed_rfh, &states));

  return states;
}

void MediaSessionImpl::ForAllPlayers(
    base::RepeatingCallback<void(const PlayerIdentifier&)> callback) {
  for (const auto& player : normal_players_)
    callback.Run(player.first);

  for (const auto& player : one_shot_players_)
    callback.Run(player);

  for (const auto& player : pepper_players_)
    callback.Run(player);
}

absl::optional<media_session::MediaPosition>
MediaSessionImpl::MaybeGuardDurationUpdate(
    absl::optional<media_session::MediaPosition> position) {
  if (!position) {
    // |position| should never go back to unset state once it's
    // set. Therefore it's safe to return it here when it's unset.
    DCHECK(!is_throttling_);
    return position;
  }

  if (position_ && position_->duration() == position->duration())
    return position;

  if (duration_update_allowance_ == 0) {
    is_throttling_ = true;
    DCHECK(duration_update_allowance_timer_.IsRunning());

    // Reset the timer so that we can keep the media as livestream
    // until the time difference between two updates is greater
    // than |kDurationUpdateAllowanceIncreaseInterval|.
    duration_update_allowance_timer_.Reset();

    return media_session::MediaPosition(
        position->playback_rate(), base::TimeDelta::Max(),
        position->GetPosition(), position->end_of_media());
  }

  --duration_update_allowance_;
  DCHECK_GE(duration_update_allowance_, 0);
  if (!duration_update_allowance_timer_.IsRunning()) {
    duration_update_allowance_timer_.Start(
        FROM_HERE, kDurationUpdateAllowanceIncreaseInterval, this,
        &MediaSessionImpl::IncreaseDurationUpdateAllowance);
  }

  return position;
}

void MediaSessionImpl::IncreaseDurationUpdateAllowance() {
  ++duration_update_allowance_;

  if (duration_update_allowance_ == kDurationUpdateMaxAllowance)
    duration_update_allowance_timer_.Stop();

  if (is_throttling_) {
    is_throttling_ = false;
    RebuildAndNotifyMediaPositionChanged();
  }
}

void MediaSessionImpl::ResetDurationUpdateGuard() {
  duration_update_allowance_timer_.Stop();
  duration_update_allowance_ = kDurationUpdateMaxAllowance;
  is_throttling_ = false;
  guarding_player_id_.reset();
}

void MediaSessionImpl::SetShouldThrottleDurationUpdateForTest(
    bool should_throttle) {
  should_throttle_duration_update_ = should_throttle;
}

bool MediaSessionImpl::HasImageCacheForTest(const GURL& image_url) const {
  return GetPageData(web_contents()->GetPrimaryPage()).GetImageCache(image_url);
}

#if defined(OHOS_MEDIA_AVSESSION)
void MediaSessionImpl::PutWebMediaAVSessionEnabled(bool enable) {
  LOG(INFO) << "media avsession MediaSessionImpl::PutWebMediaAVSessionEnabled enable is: " << enable;
  bool isWebMediaAVSessionSwitch = false;
  auto currentProcess = base::CommandLine::ForCurrentProcess();
  if (currentProcess && !currentProcess->HasSwitch(switches::kEnableMediaAvsession)) {
    isWebMediaAVSessionSwitch = true;
  }
  if (enable) {
    if (isWebMediaAVSessionSwitch && !session_ohos_) {
      session_ohos_ = std::make_unique<MediaSessionOHOS>(this);
      if (web_contents() && web_contents()->GetPrimaryMainFrame() &&
        web_contents()->GetPrimaryMainFrame()->GetView()) {
        focused_ = web_contents()->GetPrimaryMainFrame()->GetView()->HasFocus();
      }
    }
  } else {
    if (session_ohos_) {
      session_ohos_.reset();
    }
  }
  RebuildAndNotifyMetadataChanged();
}
#endif // OHOS_MEDIA_AVSESSION

MediaSessionImpl::PageData::PageData(content::Page& page)
    : PageUserData(page) {}

MediaSessionImpl::PageData::~PageData() = default;

MediaSessionImpl::PageData& MediaSessionImpl::GetPageData(
    content::Page& page) const {
  return *PageData::GetOrCreateForPage(page);
}

PAGE_USER_DATA_KEY_IMPL(MediaSessionImpl::PageData);

WEB_CONTENTS_USER_DATA_KEY_IMPL(MediaSessionImpl);

}  // namespace content