// 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.

#ifndef CONTENT_BROWSER_MEDIA_SESSION_MEDIA_SESSION_IMPL_H_
#define CONTENT_BROWSER_MEDIA_SESSION_MEDIA_SESSION_IMPL_H_

#include <stddef.h>

#include <map>
#include <optional>
#include <set>
#include <vector>

#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/containers/id_map.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/memory/weak_ptr.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "content/browser/media/session/audio_focus_delegate.h"
#include "content/browser/media/session/media_session_uma_helper.h"
#include "content/common/content_export.h"
#include "content/public/browser/media_session.h"
#include "content/public/browser/page_user_data.h"
#include "content/public/browser/presentation_observer.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
#include "media/base/picture_in_picture_events_info.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/remote_set.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/mojom/mediasession/media_session.mojom.h"
#include "arkweb/build/features/features.h"
#if BUILDFLAG(ARKWEB_MEDIA_POLICY)
#include "media/base/media_content_type.h"
#endif //BUILDFLAG(ARKWEB_MEDIA_POLICY)
#if BUILDFLAG(ARKWEB_MEDIA_MEMORY_PRESSURE)
#include "base/memory/memory_pressure_listener.h"
#endif

#if BUILDFLAG(IS_ANDROID)
#include "base/android/scoped_java_ref.h"
#endif  // BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(ARKWEB_MEDIA_POLICY)
namespace media {
class OHOSAudioOutputStream;
}
#endif //BUILDFLAG(ARKWEB_MEDIA_POLICY)

namespace media_session {
struct MediaMetadata;
}  // namespace media_session

namespace content {

class AudioFocusManagerTest;
class MediaSessionImplServiceRoutingTest;
class MediaSessionImplServiceRoutingThrottleTest;
class MediaSessionImplStateObserver;
class MediaSessionImplVisibilityBrowserTest;
class MediaSessionPlayerObserver;
class MediaSessionServiceImpl;
class MediaSessionServiceImplBrowserTest;
class MediaSessionImplUtils;
class VideoPictureInPictureWindowControllerImpl;

#if BUILDFLAG(IS_ANDROID)
class MediaSessionAndroid;
#endif  // BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(ARKWEB_MEDIA_AVSESSION)
class MediaSessionOHOS;
#endif  // BUILDFLAG(ARKWEB_MEDIA_AVSESSION)

// MediaSessionImpl is the implementation of MediaSession. It manages the media
// session and audio focus for a given WebContents. It is requesting the audio
// focus, pausing when requested by the system and dropping it on demand. The
// audio focus can be of two types: Transient or Content. A Transient audio
// focus will allow other players to duck instead of pausing and will be
// declared as temporary to the system. A Content audio focus will not be
// declared as temporary and will not allow other players to duck. If a given
// WebContents can only have one audio focus at a time, it will be Content in
// case of Transient and Content audio focus are both requested.
// TODO(thakis,mlamouri): MediaSessionImpl isn't CONTENT_EXPORT'd because it
// creates complicated build issues with WebContentsUserData being a
// non-exported template, see https://crbug.com/589840. As a result, the class
// uses CONTENT_EXPORT for methods that are being used from tests.
// CONTENT_EXPORT should be moved back to the class when the Windows build will
// work with it.
class CONTENT_EXPORT MediaSessionImpl : public MediaSession,
                         public WebContentsObserver,
                         public WebContentsUserData<MediaSessionImpl>,
                         public PresentationObserver {
 public:
  enum class State { ACTIVE, SUSPENDED, INACTIVE };
  friend class MediaSessionImplUtils;

  // Returns the MediaSessionImpl associated to this WebContents. Creates one if
  // none is currently available.
  CONTENT_EXPORT static MediaSessionImpl* Get(WebContents* web_contents);

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

  ~MediaSessionImpl() override;

  CONTENT_EXPORT void SetDelegateForTests(
      std::unique_ptr<AudioFocusDelegate> delegate);

#if BUILDFLAG(IS_ANDROID)
  void ClearMediaSessionAndroid();
  MediaSessionAndroid* GetMediaSessionAndroid();
#endif  // BUILDFLAG(IS_ANDROID)

  void NotifyMediaSessionMetadataChange();

  // Adds the given player to the current media session. Returns whether the
  // player was successfully added. If it returns false, AddPlayer() should be
  // called again later.
  CONTENT_EXPORT bool AddPlayer(MediaSessionPlayerObserver* observer,
                                int player_id);

  // Removes the given player from the current media session. Abandons audio
  // focus if that was the last player in the session.
  CONTENT_EXPORT void RemovePlayer(MediaSessionPlayerObserver* observer,
                                   int player_id);

  // Removes all the players associated with |observer|. Abandons audio focus if
  // these were the last players in the session.
  CONTENT_EXPORT void RemovePlayers(MediaSessionPlayerObserver* observer);

  // Called when a player is paused in the content.
  // If the paused player is the last player, we suspend the MediaSession.
  // Otherwise, the paused player will be removed from the MediaSession.
  CONTENT_EXPORT void OnPlayerPaused(MediaSessionPlayerObserver* observer,
                                     int player_id);

  // Called when the position state of the session might have changed.
  CONTENT_EXPORT void RebuildAndNotifyMediaPositionChanged();

  // Returns if the session is currently active.
  CONTENT_EXPORT bool IsActive() const;

  // Returns if the session is currently suspended.
  CONTENT_EXPORT bool IsSuspended() const;

  // WebContentsObserver implementation
  void WebContentsDestroyed() override;
  void RenderFrameDeleted(RenderFrameHost* rfh) override;
  void PrimaryPageChanged(content::Page& page) override;
  void DidFinishNavigation(NavigationHandle* navigation_handle) override;
  void OnWebContentsFocused(RenderWidgetHost*) override;
  void OnWebContentsLostFocus(RenderWidgetHost*) override;
  void TitleWasSet(NavigationEntry* entry) override;
  void DidUpdateFaviconURL(
      RenderFrameHost* rfh,
      const std::vector<blink::mojom::FaviconURLPtr>& candidates) override;
  void MediaPictureInPictureChanged(bool is_picture_in_picture) override;
  void RenderFrameHostStateChanged(
      RenderFrameHost* host,
      RenderFrameHost::LifecycleState old_state,
      RenderFrameHost::LifecycleState new_state) override;

  // MediaSessionService-related methods

  // Called when a MediaSessionService is created, which registers itself to
  // this session.
  void OnServiceCreated(MediaSessionServiceImpl* service);
  // Called when a MediaSessionService is destroyed, which unregisters itself
  // from this session.
  void OnServiceDestroyed(MediaSessionServiceImpl* service);

  // Called when the playback state of a MediaSessionService has
  // changed. Will notify observers of media session state change.
  void OnMediaSessionPlaybackStateChanged(MediaSessionServiceImpl* service);

  // Called when the metadata of a MediaSessionService has changed. Will notify
  // observers if the service is currently routed.
  void OnMediaSessionMetadataChanged(MediaSessionServiceImpl* service);

  // Called when the actions of a MediaSessionService has changed. Will notify
  // observers if the service is currently routed.
  void OnMediaSessionActionsChanged(MediaSessionServiceImpl* service);

  // Called when the info of a MediaSessionService has changed. Will notify
  // observers if the service is currently routed.
  void OnMediaSessionInfoChanged(MediaSessionServiceImpl* service);

  // Requests audio focus to the AudioFocusDelegate.
  // Returns whether the request was granted.
  CONTENT_EXPORT AudioFocusDelegate::AudioFocusResult RequestSystemAudioFocus(
      media_session::mojom::AudioFocusType audio_focus_type);

  // Creates a binding between |this| and |request|.
  mojo::PendingRemote<media_session::mojom::MediaSession> AddRemote();

  // Returns if the session can be controlled by the user.
  CONTENT_EXPORT bool IsControllable() const;

  // MediaSession overrides ---------------------------------------------------

  // Resume the media session.
  // |type| represents the origin of the request.
  CONTENT_EXPORT void Resume(MediaSession::SuspendType suspend_type) override;

  // Stop the media session.
  // |type| represents the origin of the request.
  CONTENT_EXPORT void Stop(MediaSession::SuspendType suspend_type) override;

  // Seek the media session.
  CONTENT_EXPORT void Seek(base::TimeDelta seek_time) override;

  // Called when a MediaSessionAction is received. The action will be forwarded
  // to blink::MediaSession corresponding to the current routed service.
  void DidReceiveAction(
      media_session::mojom::MediaSessionAction action) override;

  // Set the volume multiplier applied during ducking.
  CONTENT_EXPORT void SetDuckingVolumeMultiplier(double multiplier) override;

  // Set the audio focus group id for this media session. Sessions in the same
  // group can share audio focus. Setting this to null will use the browser
  // default value.
  CONTENT_EXPORT void SetAudioFocusGroupId(
      const base::UnguessableToken& group_id) override;

  // Returns the `RenderFrameHost` for the currently MediaSession routed
  // service, if the routed service exists, otherwise returns the top most frame
  // with an active media player.
  RenderFrameHost* GetRoutedFrame() override;

  // Returns the current media session info synchronously for a one-off request.
  CONTENT_EXPORT media_session::mojom::MediaSessionInfoPtr
  GetMediaSessionInfoSync() override;

  // Returns the current media session position for a one-off request.
  CONTENT_EXPORT std::optional<media_session::MediaPosition>
  GetMediaSessionPosition() override;

  // Returns the current media session metadata for a one-off request.
  CONTENT_EXPORT const media_session::MediaMetadata& GetMediaSessionMetadata()
      override;

  // Suspend the media session.
  // |type| represents the origin of the request.
  CONTENT_EXPORT void Suspend(MediaSession::SuspendType suspend_type) override;

  // Let the media session start ducking such that the volume multiplier is
  // reduced.
  CONTENT_EXPORT void StartDucking() override;

  // Let the media session stop ducking such that the volume multiplier is
  // recovered.
  CONTENT_EXPORT void StopDucking() override;

  // Returns information about the MediaSession. The sync method is not actually
  // slower and should be used over the async one which is available over mojo.
  void GetMediaSessionInfo(GetMediaSessionInfoCallback callback) override;

  // Returns debugging information to be displayed on chrome://media-internals.
  void GetDebugInfo(GetDebugInfoCallback) override;

  // Adds a mojo based observer to listen to events related to this session.
  void AddObserver(
      mojo::PendingRemote<media_session::mojom::MediaSessionObserver> observer)
      override;

  // Called by |AudioFocusDelegate| when an async audio focus request is
  // completed.
  CONTENT_EXPORT void FinishSystemAudioFocusRequest(
      media_session::mojom::AudioFocusType type,
      bool result);

  // Skip to the previous track.
  CONTENT_EXPORT void PreviousTrack() override;

  // Skip to the next track.
  CONTENT_EXPORT void NextTrack() override;

  // Skip ad.
  CONTENT_EXPORT void SkipAd() override;

  // Go back to previous slide.
  CONTENT_EXPORT void PreviousSlide() override;

  // Go to next slide.
  CONTENT_EXPORT void NextSlide() override;

  // Seek the media session to a specific time.
  void SeekTo(base::TimeDelta seek_time) override;

  // Scrub ("fast seek") the media session to a specific time.
  void ScrubTo(base::TimeDelta seek_time) override;

  // Enter picture-in-picture.
  void EnterPictureInPicture() override;

  // Exit picture-in-picture.
  void ExitPictureInPicture() override;

  // Automatically enter picture-in-picture from a non-user source (e.g. in
  // reaction to content being hidden).
  void EnterAutoPictureInPicture() override;

  // Routes the audio from this Media Session to the given output device. If
  // |id| is null, we will route to the default output device.
  // Players created after this setting has been set will also have their audio
  // rerouted. This setting persists until cross-origin navigation occurs, the
  // renderer reports an audio sink change to a device different from |id|, or
  // this method is called again.
  void SetAudioSinkId(const std::optional<std::string>& id) override;

  // Mute/Unmute the microphone for a WebRTC session.
  void ToggleMicrophone() override;

  // Turn on or off the camera for a WebRTC session.
  void ToggleCamera() override;

  // Hang up a WebRTC session.
  void HangUp() override;

  // Brings the associated tab into focus.
  void Raise() override;

  // Mute or unmute the media player.
  void SetMute(bool mute) override;

  // Request the media player to start Media Remoting once there are available
  // sinks.
  void RequestMediaRemoting() override;

  // PresentationObserver:
  void OnPresentationsChanged(bool has_presentation) override;

  // Downloads the bitmap version of a MediaImage at least |minimum_size_px|
  // and closest to |desired_size_px|. If the download failed, was too small or
  // the image did not come from the media session then returns a null image.
  CONTENT_EXPORT void GetMediaImageBitmap(
      const media_session::MediaImage& image,
      int minimum_size_px,
      int desired_size_px,
      GetMediaImageBitmapCallback callback) override;

  // Called to report to all players that the auto picture in picture
  // information changed.
  void ReportAutoPictureInPictureInfoChanged() override;

  const base::UnguessableToken& audio_focus_group_id() const {
    return audio_focus_group_id_;
  }

  void OnMediaMutedStatusChanged(bool mute);

  void OnPictureInPictureAvailabilityChanged();

  // Called when any of the normal players have switched to a different audio
  // output device.
  void OnAudioOutputSinkIdChanged();

  // Called when any of the normal players can no longer support audio output
  // device switching.
  void OnAudioOutputSinkChangingDisabled();

  // Called when any of the normal players video visibility changes.
  CONTENT_EXPORT void OnVideoVisibilityChanged();

  // Update the value of `remote_playback_metadata_`.
  CONTENT_EXPORT void SetRemotePlaybackMetadata(
      media_session::mojom::RemotePlaybackMetadataPtr metadata);

  // Returns whether the action should be routed to |routed_service_|.
  CONTENT_EXPORT bool ShouldRouteAction(
      media_session::mojom::MediaSessionAction action) const;

  // Returns the source ID which links media sessions on the same browser
  // context together.
  CONTENT_EXPORT const base::UnguessableToken& GetSourceId() const;

  // Returns the Audio Focus request ID associated with this media session.
  const base::UnguessableToken& GetRequestId() const;

  // Flushes the VideoPictureInPictureWindowControllerImpl with the latest data.
  void UpdateVideoPictureInPictureWindowController(
      VideoPictureInPictureWindowControllerImpl* pip_controller) const;

  // Returns a WeakPtr to `this`.
  base::WeakPtr<MediaSessionImpl> GetWeakPtr();

  CONTENT_EXPORT bool HasImageCacheForTest(const GURL& image_url) const;

  // Make sure that all observers have received any pending callbacks from us,
  // that might otherwise be sitting in a message pipe somewhere.
  void flush_observers_for_testing() { observers_.FlushForTesting(); }

#if BUILDFLAG(ARKWEB_MEDIA_POLICY)
  bool HasOnlyOneShotPlayersPublic() const;
  bool HasOneShotPlayersWhenSetMetadataPublic() const;
#endif
#if BUILDFLAG(ARKWEB_MEDIA_AVSESSION)
  void PutWebMediaAVSessionEnabled(bool enable);
#endif // ARKWEB_MEDIA_AVSESSION

#if BUILDFLAG(ARKWEB_MEDIA_POLICY)
 public:
  enum NWebPlaybackState { NONE, PLAYING, PAUSED, STOP };
  enum NWebMediaSessionState { NOINITIAL, NONEED, NEED };
  NWebPlaybackState NWebGetState();

  bool IsEndOfMedia();
  void SetEndOfMedia(bool end_of_media);
  bool GetPlayingState();
  void SetPlayingState(bool playingState);
  bool GetMuteState();
  bool IsPlayingAudio();
  bool IsPauseByAvsession();
  void SetPauseByAvsession(bool is_pause);
  void SetWebviewShow(bool show, bool is_special_for_audio);
  void EndSessionWhenHide();
  void SetMediaContentType(media::MediaContentType media_content_type) { media_content_type_ = media_content_type; }
  media::MediaContentType getMediaContentType() { return media_content_type_; }

  void SetSessionState(NWebMediaSessionState sessionState);
  NWebMediaSessionState GetSessionState();

  std::unordered_set<media::OHOSAudioOutputStream*> activeAudioStream_;
  int audioResumeInterval_ = 0;
  bool audioExclusive_ = true;
  int audioSessionType_ = 0;
  bool isPlayingState_ = false;
  bool fileAccess_ = false;
  std::vector<std::string> grantMediaFileAccessDirs_;
  NWebMediaSessionState sessionState_ = NWebMediaSessionState::NOINITIAL;
  bool has_one_shot_players_ = false;
#endif // BUILDFLAG(ARKWEB_MEDIA_POLICY)
#if BUILDFLAG(ARKWEB_PIP)
  void OnPictureInPictureStateChanged(
      const MediaPlayerId& id, uint32_t state, int32_t width, int32_t height);
#endif
#if BUILDFLAG(ARKWEB_MEDIA_MEMORY_PRESSURE)
  void OnNotifyMemoryLevel(int32_t level);
  // Memory pressure handler, called by |memory_pressure_listener_|.
  void OnMemoryPressure(
      base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level);
  std::unique_ptr<base::MemoryPressureListener> memory_pressure_listener_;
  base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level_ =
      base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
#endif

#if BUILDFLAG(ARKWEB_MEDIA_POLICY)
  void UpdateMediaPlayersMuteState(int player_id, bool mute);
  bool GetMediaPlayerMuteState();
#endif

#if BUILDFLAG(ARKWEB_TEST)
 public:
#else
 private:
#endif  // ARKWEB_TEST
  friend class content::WebContentsUserData<MediaSessionImpl>;
  friend class MediaSessionImplBrowserTest;
  friend class content::MediaSessionImplVisibilityBrowserTest;
  friend class content::AudioFocusManagerTest;
  friend class content::MediaSessionImplServiceRoutingTest;
  friend class content::MediaSessionImplServiceRoutingThrottleTest;
  friend class content::MediaSessionImplStateObserver;
  friend class content::MediaSessionServiceImplBrowserTest;
  friend class MediaSessionImplTest;
  friend class MediaSessionImplDurationThrottleTest;
  friend class MediaInternalsAudioFocusTest;
  friend class WebAppSystemMediaControlsBrowserTest;
#if BUILDFLAG(ARKWEB_MEDIA_AVSESSION)
  friend class MediaSessionOHOS;
#endif  // BUILDFLAG(ARKWEB_MEDIA_AVSESSION)
  raw_ptr<MediaSessionImplUtils> implUtils_;

  CONTENT_EXPORT void RemoveAllPlayersForTest();
  CONTENT_EXPORT MediaSessionUmaHelper* uma_helper_for_test();

  // Representation of a player for the MediaSessionImpl.
  struct PlayerIdentifier {
    PlayerIdentifier(MediaSessionPlayerObserver* observer, int player_id);

    PlayerIdentifier(const PlayerIdentifier&) = default;
    PlayerIdentifier(PlayerIdentifier&&) = default;

    PlayerIdentifier& operator=(const PlayerIdentifier&) = default;
    PlayerIdentifier& operator=(PlayerIdentifier&&) = default;

    friend bool operator==(const PlayerIdentifier&,
                           const PlayerIdentifier&) = default;
    friend auto operator<=>(const PlayerIdentifier&,
                            const PlayerIdentifier&) = default;
    // RAW_PTR_EXCLUSION: #union
    RAW_PTR_EXCLUSION MediaSessionPlayerObserver* observer;
    int player_id;
  };

  CONTENT_EXPORT explicit MediaSessionImpl(WebContents* web_contents);

  void Initialize();

  // Called when we have finished downloading an image.
  void 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);

  // Called when system audio focus has been requested and whether the request
  // was granted.
  void OnSystemAudioFocusRequested(bool result);

  CONTENT_EXPORT void OnSuspendInternal(MediaSession::SuspendType suspend_type,
                                        State new_state);
  CONTENT_EXPORT void OnResumeInternal(MediaSession::SuspendType suspend_type);

  // To be called after a call to AbandonAudioFocus() in order request the
  // delegate to abandon the audio focus.
  CONTENT_EXPORT void AbandonSystemAudioFocusIfNeeded();

  // Internal method that should be used instead of setting audio_focus_state_.
  // It sets audio_focus_state_ and notifies observers about the state change.
  void SetAudioFocusState(State audio_focus_state);

  // Flushes any mojo bindings for testing.
  CONTENT_EXPORT void FlushForTesting();

  // Notifies |observers_| and |delegate_| that |MediaSessionInfo| has changed.
  void RebuildAndNotifyMediaSessionInfoChanged();

  // Update the volume multiplier when ducking state changes.
  void UpdateVolumeMultiplier();

  // Get the volume multiplier, which depends on whether the media session is
  // ducking.
  double GetVolumeMultiplier() const;

  CONTENT_EXPORT bool AddOneShotPlayer(MediaSessionPlayerObserver* observer,
                                       int player_id);

  CONTENT_EXPORT bool AddAmbientPlayer(MediaSessionPlayerObserver* observer,
                                       int player_id);

  // Returns true if there is at least one player and all the players are
  // one-shot.
  bool HasOnlyOneShotPlayers() const;

  // MediaSessionService-related methods

  // Called when the routed service may have changed.
  void UpdateRoutedService();

  // Returns whether the frame |rfh| uses MediaSession API.
  bool IsServiceActiveForRenderFrameHost(RenderFrameHost* rfh);

  // Compute the frame that should be routed for media session. If
  // |ensure_service| is true, the routed frame must have an active
  // MediaSessionService, otherwise it does not, e.g. when no MediaSession API
  // has been called but there is an active media player. This method can be
  // used to compute both the routed frame and routed service.
  CONTENT_EXPORT RenderFrameHost* ComputeFrameForRouting(bool ensure_service);

  // Rebuilds |actions_| and notifies observers if they have changed.
  void RebuildAndNotifyActionsChanged();

  // Rebuilds |metadata_| and |images_| and notifies observers if they have
  // changed.
  void RebuildAndNotifyMetadataChanged();

#if BUILDFLAG(IS_CHROMEOS)
  void BuildPlaceholderMetadata(
      media_session::MediaMetadata& metadata,
      std::vector<media_session::MediaImage>& artwork);
#endif

  void BuildMetadata(media_session::MediaMetadata& metadata,
                     std::vector<media_session::MediaImage>& artwork);

  bool IsPictureInPictureAvailable() const;

  // Iterates over all |normal_players_| and returns true if any of the players'
  // videos is sufficiently visible, false otherwise.
  CONTENT_EXPORT bool HasSufficientlyVisibleVideo() const;

  // Iterates over all |normal_players_| and returns true if any of the players'
  // videos is sufficiently visible, false otherwise.
  //
  // This is very similar to `HasSufficientlyVisibleVideo`, however this method
  // is used to get notifications on demand, while `HasSufficientlyVisibleVideo`
  // is constantly reporting visibility.
  void GetVisibility(GetVisibilityCallback get_visibility_callback) override;

  // Returns the device ID for the audio output device being used by all of the
  // normal players. If the players are not all using the same audio output
  // device, the id of the default device will be returned.
  std::string GetSharedAudioOutputDeviceId() const;

  bool IsAudioOutputDeviceSwitchingSupported() const;

  // Called when a MediaSessionAction is received. The action will be forwarded
  // to blink::MediaSession corresponding to the current routed service.
  void DidReceiveAction(media_session::mojom::MediaSessionAction action,
                        blink::mojom::MediaSessionActionDetailsPtr details);

  // Returns the media audio video state for each player. This is whether the
  // players associated with the media session are audio-only, video-only, or
  // have both audio and video. If we have a |routed_service_| then we limit to
  // players on that frame because this should align with the metadata.
  std::vector<media_session::mojom::MediaAudioVideoState>
  GetMediaAudioVideoStates();

  // Calls the callback with each |PlayerIdentifier| for every player associated
  // with this media session.
  void ForAllPlayers(base::RepeatingCallback<void(const PlayerIdentifier&)>);

  // Restrict duration update under certain frequency.
  std::optional<media_session::MediaPosition> MaybeGuardDurationUpdate(
      std::optional<media_session::MediaPosition> position);

  void IncreaseDurationUpdateAllowance();

  void ResetDurationUpdateGuard();

  CONTENT_EXPORT void SetShouldThrottleDurationUpdateForTest(
      bool should_throttle);

  // True if `routed_service_` exists and either the camera or microphone are
  // currently actively used, false otherwise.
  bool IsActivelyUsingCameraOrMicrophone() const;

  // Returns true if we can enter browser initiated automatic
  // picture-in-picture, false otherwise.
  bool CanEnterBrowserInitiatedAutomaticPictureInPicture() const;

  // Automatically enter picture-in-picture from a non-user source (e.g. in
  // reaction to content being hidden), if the EnterAutoPictureInPicture action
  // is registered by the browser (the user did not provide an
  // `enterpictureinpicture` action handler).
  void MaybeEnterBrowserInitiatedAutomaticPictureInPicture();

  // Notifies a player of the last known auto picture-in-picture information.
  // This is used to keep newly added players updated with the latest
  // information.
  void NotifyPlayerOfAutoPictureInPictureInfo(
      MediaSessionPlayerObserver* observer,
      int player_id);

  // Duration update allowance is inscreasing by 1 every 20 seconds, and
  // capped at 3. Every duration updates will consume 1 allowance, and
  // if updates happen when we have 0 allowance, we consider the media as
  // a livestream and stop instreasing allowance until the time difference
  // between two updates is greater than 20 seconds.
  CONTENT_EXPORT static constexpr int kDurationUpdateMaxAllowance = 3;
  CONTENT_EXPORT static constexpr base::TimeDelta
      kDurationUpdateAllowanceIncreaseInterval = base::Seconds(20);

  // A set of actions supported by |routed_service_| and the current media
  // session.
  std::set<media_session::mojom::MediaSessionAction> actions_;

  std::unique_ptr<AudioFocusDelegate> delegate_;

  // Standard video playback (e.g. WebMediaPlayerImpl players).
  std::map<PlayerIdentifier, media_session::mojom::AudioFocusType>
      normal_players_;

  // Players that are playing in the web contents but we cannot control (e.g.
  // MediaStream).
  base::flat_set<PlayerIdentifier> one_shot_players_;

  // Players that we can neither control nor should affect other players in the
  // audio focus stack (e.g. WebAudio).
  base::flat_set<PlayerIdentifier> ambient_players_;

  // Players that are removed from |normal_players_| temporarily when the page
  // goes to back-forward cache. When the page is restored from the cache, these
  // players are also restored to |normal_players_|.
  base::flat_set<PlayerIdentifier> hidden_players_;

  State audio_focus_state_ = State::INACTIVE;
  MediaSession::SuspendType suspend_type_;

  // The |desired_audio_focus_type_| is the AudioFocusType we will request when
  // we request system audio focus.
  media_session::mojom::AudioFocusType desired_audio_focus_type_;

  // The last updated |MediaSessionInfo| that was sent to |observers_|.
  media_session::mojom::MediaSessionInfoPtr session_info_;

  // The last updated |MediaPosition| that was sent to |observers_|.
  std::optional<media_session::MediaPosition> position_;

  MediaSessionUmaHelper uma_helper_;

  // The ducking state of this media session. The initial value is |false|, and
  // is set to |true| after StartDucking(), and will be set to |false| after
  // StopDucking().
  bool is_ducking_;

  // True if we should unduck when we gain audio focus. This is set to true each
  // time we request focus, and set to false if the AudioFocusManager tells us
  // to duck. If our request is granted and this is still true, we will unduck.
  // If false (because we were told to duck after our request began) we will
  // remain ducked, as that is the intended state the AudioFocusManager expects.
  bool should_unduck_on_focus_gained_ = true;

  base::UnguessableToken audio_focus_group_id_ = base::UnguessableToken::Null();

  double ducking_volume_multiplier_;

  // True if the WebContents associated with this MediaSessionImpl is focused.
  bool focused_ = false;

  bool is_muted_ = false;

  // Used to persist audio device selection between navigations on the same
  // origin.
  url::Origin origin_;
  std::optional<std::string> audio_device_id_for_origin_;

  class PageData : public content::PageUserData<PageData> {
   public:
    explicit PageData(content::Page& page);

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

    ~PageData() override;

    void AddImageCache(const GURL& image_url, const SkBitmap& bitmap) {
      image_cache_.emplace(image_url, bitmap);
    }

    const SkBitmap* GetImageCache(const GURL& image_url) const {
      auto it = image_cache_.find(image_url);
      if (it == image_cache_.end())
        return nullptr;

      return &it->second;
    }

    PAGE_USER_DATA_KEY_DECL();

   private:
    // Cache of images that have been requested by clients.
    base::flat_map<GURL, SkBitmap> image_cache_;
  };

#if BUILDFLAG(ARKWEB_MEDIA_POLICY)
  media::MediaContentType media_content_type_;
  std::unordered_map<int, bool> players_mute_state_;
#endif

  // Returns the PageData for the specified |page|.
  PageData& GetPageData(content::Page& page) const;

#if BUILDFLAG(IS_ANDROID)
  std::unique_ptr<MediaSessionAndroid> session_android_;
#endif  // BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(ARKWEB_MEDIA_AVSESSION)
  std::unique_ptr<MediaSessionOHOS> session_ohos_;
  void CreateSessionOhos();
#endif  // BUILDFLAG(ARKWEB_MEDIA_AVSESSION)

  // MediaSessionService-related fields
  using ServicesMap =
      std::map<GlobalRenderFrameHostId,
               raw_ptr<MediaSessionServiceImpl, CtnExperimental>>;

  // The current metadata and images associated with the current media session.
  media_session::MediaMetadata metadata_;
  base::flat_map<media_session::mojom::MediaSessionImageType,
                 std::vector<media_session::MediaImage>>
      images_;

  // The collection of all managed services (non-owned pointers). The services
  // are owned by RenderFrameHost and should be registered on creation and
  // unregistered on destroy.
  ServicesMap services_;
  // The currently routed service (non-owned pointer).
  raw_ptr<MediaSessionServiceImpl> routed_service_;

  // Bindings for Mojo pointers to |this| held by media route providers.
  mojo::ReceiverSet<media_session::mojom::MediaSession> receivers_;

  mojo::RemoteSet<media_session::mojom::MediaSessionObserver> observers_;

  base::RepeatingTimer duration_update_allowance_timer_;

  bool is_throttling_ = false;

  // This is guaranteed to be reset to |kDurationUpdateMaxAllowance| at
  // first update because |guarding_player_id_| is always a mismatch
  // at first, and will trigger a reset.
  int duration_update_allowance_ = 0;

  bool should_throttle_duration_update_ = false;

  // Whether the associated WebContents is connected to a presentation.
  bool has_presentation_ = false;

  std::optional<PlayerIdentifier> guarding_player_id_;

  media_session::mojom::RemotePlaybackMetadataPtr remote_playback_metadata_;

  // Used by tests to force media sessions to be ignored when finding a new
  // active session.
  bool always_ignore_for_active_session_for_testing_ = false;

  // True if the given media has infinite duration OR has a duration that
  // changes often enough to be considered live. See
  // `MaybeGuardDurationUpdate()` for details on duration changes.
  bool is_considered_live_ = false;

#if BUILDFLAG(ARKWEB_MEDIA_POLICY)
 public:
  base::WeakPtrFactory<content::MediaSessionImpl> weakMediaSessionFactory_;
#endif // BUILDFLAG(ARKWEB_MEDIA_POLICY)

#if BUILDFLAG(ARKWEB_MEDIA_CAST)
  void OnNotifyMeidaCastUri(const std::string& media_uri);
  void CreateAVCastAdapter();
  void HandleStopMediaCast();
  int32_t GetMediaCastCurrentTime();
  void PullUpCastBackGround(const std::string& device_name);
  void UpdateUiPlayState(bool is_playing);
  void UpdateUiPlayPosition(int64_t position);
  void UpdateRemotePlayState(bool is_playing);
  void UpdateRemotePlayPosition(int64_t position);
  void MediaCastStopped();
  bool IsPageBackground();
  void SetPauseByAvcast(bool pause_avcast);
  void NotifyCastControlShow(bool is_show);
#endif // BUILDFLAG(ARKWEB_MEDIA_CAST)

 private:
  // The last auto picture-in-picture information that was sent to players.
  std::optional<media::PictureInPictureEventsInfo::AutoPipInfo>
      last_auto_picture_in_picture_info_;
  bool pause_avcast_ = false;
  base::WeakPtrFactory<MediaSessionImpl> weak_factory_{this};

  WEB_CONTENTS_USER_DATA_KEY_DECL();
};

}  // namespace content

#include "arkweb/chromium_ext/content/browser/media/session/media_session_impl_utils.h"
#endif  // CONTENT_BROWSER_MEDIA_SESSION_MEDIA_SESSION_IMPL_H_