// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ASH_SYSTEM_FOCUS_MODE_SOUNDS_FOCUS_MODE_SOUNDS_CONTROLLER_H_
#define ASH_SYSTEM_FOCUS_MODE_SOUNDS_FOCUS_MODE_SOUNDS_CONTROLLER_H_
#include <optional>
#include <utility>
#include <vector>
#include "ash/ash_export.h"
#include "ash/public/cpp/image_downloader.h"
#include "ash/system/focus_mode/focus_mode_util.h"
#include "ash/system/focus_mode/sounds/focus_mode_sounds_delegate.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "components/prefs/pref_change_registrar.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/media_session/public/mojom/audio_focus.mojom.h"
#include "services/media_session/public/mojom/media_controller.mojom.h"
#include "ui/gfx/image/image_skia.h"
namespace base {
class UnguessableToken;
} // namespace base
namespace ash {
namespace youtube_music {
struct PlaybackData;
} // namespace youtube_music
class FocusModeYouTubeMusicDelegate;
// This class is used to download images and record the info of playlists after
// getting the response data we need from Music API, which will be used to show
// on `FocusModeSoundsView`.
class ASH_EXPORT FocusModeSoundsController
: public media_session::mojom::AudioFocusObserver,
public media_session::mojom::MediaControllerObserver {
public:
using UpdateSoundsViewCallback = base::OnceCallback<void(bool)>;
// The data used to display on the focus panel. It will include a playlist id,
// a string of its title, and the downloaded thumbnail for the playlist cover
// currently. We will add the stream info in future.
struct Playlist {
// Playlist identifier.
std::string playlist_id;
// Title of the playlist.
std::string title;
// Playlist cover downloaded through its image url.
gfx::ImageSkia thumbnail;
};
class Observer : public base::CheckedObserver {
public:
// Called when a playlist is toggled by the user on the focus panel.
virtual void OnSelectedPlaylistChanged() = 0;
// Called when the state of `selected_playlist_` has been changed.
virtual void OnPlaylistStateChanged() {}
};
FocusModeSoundsController();
FocusModeSoundsController(const FocusModeSoundsController&) = delete;
FocusModeSoundsController& operator=(const FocusModeSoundsController&) =
delete;
~FocusModeSoundsController() override;
// Download the artwork for a track. Exposed here so that native portion of
// the focus mode web UI can download the artwork using the focus mode network
// traffic annotation.
static void DownloadTrackThumbnail(
const GURL& url,
ImageDownloader::DownloadCallback callback);
using GetNextTrackCallback = base::OnceCallback<void(
const std::optional<FocusModeSoundsDelegate::Track>&)>;
void GetNextTrack(GetNextTrackCallback callback);
const std::vector<std::unique_ptr<Playlist>>& soundscape_playlists() const {
return soundscape_playlists_;
}
const std::vector<std::unique_ptr<Playlist>>& youtube_music_playlists()
const {
return youtube_music_playlists_;
}
const focus_mode_util::SelectedPlaylist& selected_playlist() const {
return selected_playlist_;
}
focus_mode_util::SoundType sound_type() const { return sound_type_; }
void SoundsStarted() { sounds_started_time_ = base::Time::Now(); }
const base::flat_set<focus_mode_util::SoundType>& sound_sections() const {
return enabled_sound_sections_;
}
void reset_paused_event_count() { paused_event_count_ = 0; }
int paused_event_count() const { return paused_event_count_; }
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
// media_session::mojom::AudioFocusObserver:
void OnFocusGained(
media_session::mojom::AudioFocusRequestStatePtr session) override;
void OnFocusLost(
media_session::mojom::AudioFocusRequestStatePtr session) override;
void OnRequestIdReleased(const base::UnguessableToken& request_id) override;
// media_session::mojom::MediaControllerObserver:
void MediaSessionInfoChanged(
media_session::mojom::MediaSessionInfoPtr session_info) override;
void MediaSessionMetadataChanged(
const std::optional<media_session::MediaMetadata>& metadata) override {}
void MediaSessionActionsChanged(
const std::vector<media_session::mojom::MediaSessionAction>& actions)
override {}
void MediaSessionChanged(
const std::optional<base::UnguessableToken>& request_id) override {}
void MediaSessionPositionChanged(
const std::optional<media_session::MediaPosition>& position) override {}
// Toggles a playlist with the same id as the `playlist_data` to select or
// deselect based on its previous state.
void TogglePlaylist(const focus_mode_util::SelectedPlaylist& playlist_data);
void PausePlayback();
void ResumePlayingPlayback();
// Download images by providing urls. `update_sounds_view_callback` will be
// called only when finishing downloading all non-empty thumbnails for the
// Soundscape type or the YouTube Music type of playlists; however, if
// `ImageDownloader` doesn't exists or if there is an empty thumbnail
// downloaded, `update_sounds_view_callback` will be not triggered.
void DownloadPlaylistsForType(
const bool is_soundscape_type,
UpdateSoundsViewCallback update_sounds_view_callback);
void UpdateFromUserPrefs();
// Sets the no premium callback for all YouTube Music API requests. This
// callback is used to update the specific UIs that are dependent on the
// account premium status.
void SetYouTubeMusicNoPremiumCallback(base::RepeatingClosure callback);
// Reports playback to the media server. It's only used for YouTube Music at
// the moment.
void ReportYouTubeMusicPlayback(
const youtube_music::PlaybackData& playback_data);
void set_soundscape_playlists_for_testing(
std::vector<std::unique_ptr<Playlist>> soundscape_playlists) {
soundscape_playlists_.swap(soundscape_playlists);
}
void set_youtube_music_playlists_for_testing(
std::vector<std::unique_ptr<Playlist>> youtube_music_playlists) {
youtube_music_playlists_.swap(youtube_music_playlists);
}
void set_selected_playlist_for_testing(
const focus_mode_util::SelectedPlaylist& playlist) {
selected_playlist_ = playlist;
}
void update_selected_playlist_state_for_testing(
focus_mode_util::SoundState new_state) {
selected_playlist_.state = new_state;
}
void set_simulate_playback_for_testing() {
simulate_playback_for_testing_ = true;
}
private:
bool IsPlaylistAllowed(
const focus_mode_util::SelectedPlaylist& playlist) const;
void SaveUserPref();
void ResetSelectedPlaylist();
void SelectPlaylist(const focus_mode_util::SelectedPlaylist& playlist_data);
void OnAllThumbnailsDownloaded(
bool is_soundscape_type,
UpdateSoundsViewCallback update_sounds_view_callback,
std::vector<std::unique_ptr<Playlist>> sorted_playlists);
// Handler for changes in the FocusModeSoundsEnabled pref.
void OnPrefChanged();
std::unique_ptr<FocusModeSoundsDelegate> soundscape_delegate_;
std::unique_ptr<FocusModeYouTubeMusicDelegate> youtube_music_delegate_;
std::vector<std::unique_ptr<Playlist>> soundscape_playlists_;
std::vector<std::unique_ptr<Playlist>> youtube_music_playlists_;
focus_mode_util::SelectedPlaylist selected_playlist_;
focus_mode_util::SoundType sound_type_ =
focus_mode_util::SoundType::kSoundscape;
// Records the time when we requested to play a selected playlist.
base::Time sounds_started_time_;
// Records how many times the user paused `selected_playlist_` during a
// session.
int paused_event_count_ = 0;
PrefChangeRegistrar pref_registrar_;
base::flat_set<focus_mode_util::SoundType> enabled_sound_sections_;
// True if the request id of the focus mode media session has gained audio
// focus. Note that focus mode will only have a maximum of one media playing
// at any given time.
bool has_audio_focus_ = false;
base::UnguessableToken media_session_request_id_ =
base::UnguessableToken::Null();
bool simulate_playback_for_testing_ = false;
base::ObserverList<Observer> observers_;
// Used to control the media session.
mojo::Remote<media_session::mojom::MediaControllerManager>
media_controller_manager_remote_;
mojo::Remote<media_session::mojom::MediaController> media_controller_remote_;
// Connections with the media session service to listen for audio focus
// updates and control media sessions.
mojo::Receiver<media_session::mojom::AudioFocusObserver>
audio_focus_observer_receiver_{this};
mojo::Receiver<media_session::mojom::MediaControllerObserver>
media_controller_observer_receiver_{this};
base::WeakPtrFactory<FocusModeSoundsController> weak_factory_{this};
};
} // namespace ash
#endif // ASH_SYSTEM_FOCUS_MODE_SOUNDS_FOCUS_MODE_SOUNDS_CONTROLLER_H_