0

focus_mode: Add YouTube Music support to Google APIs

This adds YouTube Music support to Google APIs. ChromeOS focus mode
integrates with YouTube Music API during focus time for the following
functionalities:
  - Get playlists.
  - Prepare the playback queue.
  - Play the next in the queue.

Bug: b/331643640
Test: N/A
Change-Id: Ie796dd61c07e90c2ebae23bcc744a2a7f527147b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5527963
Commit-Queue: Yongshun Liu <yongshun@chromium.org>
Reviewed-by: Alex Ilin <alexilin@chromium.org>
Reviewed-by: Sean Kau <skau@chromium.org>
Reviewed-by: Richard Chui <richui@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1304029}
This commit is contained in:
Yongshun Liu
2024-05-21 20:42:40 +00:00
committed by Chromium LUCI CQ
parent 5ccb6e05a4
commit 18acfd6e54
9 changed files with 1364 additions and 0 deletions

@ -4024,6 +4024,7 @@ static_library("ui") {
"//google_apis/common",
"//google_apis/drive",
"//google_apis/tasks",
"//google_apis/youtube_music",
"//media/capture:capture_lib",
"//mojo/public/js:resources_grit",
"//remoting/host/chromeos:features",

@ -0,0 +1,24 @@
# 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.
assert(is_chromeos)
static_library("youtube_music") {
sources = [
"youtube_music_api_request_types.cc",
"youtube_music_api_request_types.h",
"youtube_music_api_requests.cc",
"youtube_music_api_requests.h",
"youtube_music_api_response_types.cc",
"youtube_music_api_response_types.h",
]
deps = [
"//base",
"//google_apis:google_apis",
"//google_apis/common:common",
"//net",
"//url",
]
}

@ -0,0 +1 @@
file://ash/system/focus_mode/youtube_music/OWNERS

@ -0,0 +1,86 @@
// 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.
#include "google_apis/youtube_music/youtube_music_api_request_types.h"
#include <string>
#include "base/check.h"
#include "base/json/json_writer.h"
#include "base/notreached.h"
namespace google_apis::youtube_music {
namespace {
constexpr char kPlayableIdKey[] = "playableId";
constexpr char kExplicitFilterKey[] = "explicitFilter";
constexpr char kShuffleModeKey[] = "shuffleMode";
const char kExplicitFilterNone[] = "none";
const char kExplicitFilterBestEffort[] = "besteffort";
const char kShuffleModeUnspecified[] = "SHUFFLE_MODE_UNSPECIFIED";
const char kShuffleModeOff[] = "OFF";
const char kShuffleModeOn[] = "ON";
const char* GetExplicitFilterValue(
const PlaybackQueuePrepareRequestPayload::ExplicitFilter& explicit_filter) {
switch (explicit_filter) {
case PlaybackQueuePrepareRequestPayload::ExplicitFilter::kNone:
return kExplicitFilterNone;
case PlaybackQueuePrepareRequestPayload::ExplicitFilter::kBestEffort:
return kExplicitFilterBestEffort;
}
NOTREACHED_NORETURN();
}
const char* GetShuffleModeValue(
const PlaybackQueuePrepareRequestPayload::ShuffleMode& shuffle_mode) {
switch (shuffle_mode) {
case PlaybackQueuePrepareRequestPayload::ShuffleMode::kUnspecified:
return kShuffleModeUnspecified;
case PlaybackQueuePrepareRequestPayload::ShuffleMode::kOff:
return kShuffleModeOff;
case PlaybackQueuePrepareRequestPayload::ShuffleMode::kOn:
return kShuffleModeOn;
}
NOTREACHED_NORETURN();
}
} // namespace
PlaybackQueuePrepareRequestPayload::PlaybackQueuePrepareRequestPayload(
std::string playable_id,
std::optional<ExplicitFilter> explicit_filter,
std::optional<ShuffleMode> shuffle_mode)
: playable_id(playable_id),
explicit_filter(explicit_filter),
shuffle_mode(shuffle_mode) {}
PlaybackQueuePrepareRequestPayload::PlaybackQueuePrepareRequestPayload(
const PlaybackQueuePrepareRequestPayload&) = default;
PlaybackQueuePrepareRequestPayload&
PlaybackQueuePrepareRequestPayload::operator=(
const PlaybackQueuePrepareRequestPayload&) = default;
PlaybackQueuePrepareRequestPayload::~PlaybackQueuePrepareRequestPayload() =
default;
std::string PlaybackQueuePrepareRequestPayload::ToJson() const {
base::Value::Dict root;
CHECK(!playable_id.empty());
root.Set(kPlayableIdKey, playable_id);
if (explicit_filter.has_value()) {
root.Set(kExplicitFilterKey,
GetExplicitFilterValue(explicit_filter.value()));
}
if (shuffle_mode.has_value()) {
root.Set(kShuffleModeKey, GetShuffleModeValue(shuffle_mode.value()));
}
const std::optional<std::string> json = base::WriteJson(root);
CHECK(json);
return json.value();
}
} // namespace google_apis::youtube_music

@ -0,0 +1,47 @@
// 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 GOOGLE_APIS_YOUTUBE_MUSIC_YOUTUBE_MUSIC_API_REQUEST_TYPES_H_
#define GOOGLE_APIS_YOUTUBE_MUSIC_YOUTUBE_MUSIC_API_REQUEST_TYPES_H_
#include <optional>
#include <string>
namespace google_apis::youtube_music {
// Payload used as a request body for the API request that prepares the playback
// queue.
struct PlaybackQueuePrepareRequestPayload {
enum class ExplicitFilter {
kNone,
kBestEffort,
};
enum class ShuffleMode {
kUnspecified,
kOff,
kOn,
};
PlaybackQueuePrepareRequestPayload(
std::string playable_id,
std::optional<ExplicitFilter> explicit_filter,
std::optional<ShuffleMode> shuffle_mode);
PlaybackQueuePrepareRequestPayload(const PlaybackQueuePrepareRequestPayload&);
PlaybackQueuePrepareRequestPayload& operator=(
const PlaybackQueuePrepareRequestPayload&);
~PlaybackQueuePrepareRequestPayload();
std::string ToJson() const;
std::string playable_id;
std::optional<ExplicitFilter> explicit_filter;
std::optional<ShuffleMode> shuffle_mode;
};
} // namespace google_apis::youtube_music
#endif // GOOGLE_APIS_YOUTUBE_MUSIC_YOUTUBE_MUSIC_API_REQUEST_TYPES_H_

@ -0,0 +1,252 @@
// 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.
#include "google_apis/youtube_music/youtube_music_api_requests.h"
#include <memory>
#include <string>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "google_apis/common/api_error_codes.h"
#include "google_apis/common/request_sender.h"
#include "google_apis/youtube_music/youtube_music_api_response_types.h"
#include "net/base/url_util.h"
#include "url/gurl.h"
namespace {
constexpr char kContentTypeJson[] = "application/json; charset=utf-8";
} // namespace
namespace google_apis::youtube_music {
GetPlaylistsRequest::GetPlaylistsRequest(RequestSender* sender,
Callback callback)
: UrlFetchRequestBase(sender, ProgressCallback(), ProgressCallback()),
callback_(std::move(callback)) {
CHECK(!callback_.is_null());
}
GetPlaylistsRequest::~GetPlaylistsRequest() = default;
GURL GetPlaylistsRequest::GetURL() const {
// TODO(b/341324009): Move to an util file or class.
return GURL(
"https://youtubemediaconnect.googleapis.com/v1/musicSections/"
"root:load?intent=focus&category=music&sectionRecommendationLimit=10");
}
ApiErrorCode GetPlaylistsRequest::MapReasonToError(ApiErrorCode code,
const std::string& reason) {
return code;
}
bool GetPlaylistsRequest::IsSuccessfulErrorCode(ApiErrorCode error) {
return error == HTTP_SUCCESS;
}
void GetPlaylistsRequest::ProcessURLFetchResults(
const network::mojom::URLResponseHead* response_head,
base::FilePath response_file,
std::string response_body) {
ApiErrorCode error = GetErrorCode();
switch (error) {
case HTTP_SUCCESS:
blocking_task_runner()->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&GetPlaylistsRequest::Parse, std::move(response_body)),
base::BindOnce(&GetPlaylistsRequest::OnDataParsed,
weak_ptr_factory_.GetWeakPtr()));
break;
default:
RunCallbackOnPrematureFailure(error);
OnProcessURLFetchResultsComplete();
break;
}
}
void GetPlaylistsRequest::RunCallbackOnPrematureFailure(ApiErrorCode error) {
std::move(callback_).Run(base::unexpected(error));
}
std::unique_ptr<TopLevelMusicRecommendations> GetPlaylistsRequest::Parse(
const std::string& json) {
std::unique_ptr<base::Value> value = ParseJson(json);
return value ? TopLevelMusicRecommendations::CreateFrom(*value) : nullptr;
}
void GetPlaylistsRequest::OnDataParsed(
std::unique_ptr<TopLevelMusicRecommendations> recommendations) {
if (!recommendations) {
std::move(callback_).Run(base::unexpected(PARSE_ERROR));
} else {
std::move(callback_).Run(std::move(recommendations));
}
OnProcessURLFetchResultsComplete();
}
PlaybackQueuePrepareRequest::PlaybackQueuePrepareRequest(
RequestSender* sender,
const PlaybackQueuePrepareRequestPayload& payload,
Callback callback)
: UrlFetchRequestBase(sender, ProgressCallback(), ProgressCallback()),
payload_(payload),
callback_(std::move(callback)) {
CHECK(!callback_.is_null());
}
PlaybackQueuePrepareRequest::~PlaybackQueuePrepareRequest() = default;
GURL PlaybackQueuePrepareRequest::GetURL() const {
// TODO(b/341324009): Move to an util file or class.
GURL url(
"https://youtubemediaconnect.googleapis.com/v1/queues/"
"default:preparePlayback");
return url;
}
ApiErrorCode PlaybackQueuePrepareRequest::MapReasonToError(
ApiErrorCode code,
const std::string& reason) {
return code;
}
bool PlaybackQueuePrepareRequest::IsSuccessfulErrorCode(ApiErrorCode error) {
return error == HTTP_SUCCESS;
}
HttpRequestMethod PlaybackQueuePrepareRequest::GetRequestType() const {
return HttpRequestMethod::kPost;
}
bool PlaybackQueuePrepareRequest::GetContentData(
std::string* upload_content_type,
std::string* upload_content) {
*upload_content_type = kContentTypeJson;
*upload_content = payload_.ToJson();
return true;
}
void PlaybackQueuePrepareRequest::ProcessURLFetchResults(
const network::mojom::URLResponseHead* response_head,
base::FilePath response_file,
std::string response_body) {
ApiErrorCode error = GetErrorCode();
switch (error) {
case HTTP_SUCCESS:
blocking_task_runner()->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&PlaybackQueuePrepareRequest::Parse,
std::move(response_body)),
base::BindOnce(&PlaybackQueuePrepareRequest::OnDataParsed,
weak_ptr_factory_.GetWeakPtr()));
break;
default:
RunCallbackOnPrematureFailure(error);
OnProcessURLFetchResultsComplete();
break;
}
}
void PlaybackQueuePrepareRequest::RunCallbackOnPrematureFailure(
ApiErrorCode error) {
std::move(callback_).Run(base::unexpected(error));
}
std::unique_ptr<Queue> PlaybackQueuePrepareRequest::Parse(
const std::string& json) {
std::unique_ptr<base::Value> value = ParseJson(json);
return value ? Queue::CreateFrom(*value) : nullptr;
}
void PlaybackQueuePrepareRequest::OnDataParsed(
std::unique_ptr<Queue> playback_queue) {
if (!playback_queue) {
std::move(callback_).Run(base::unexpected(PARSE_ERROR));
} else {
std::move(callback_).Run(std::move(playback_queue));
}
OnProcessURLFetchResultsComplete();
}
PlaybackQueueNextRequest::PlaybackQueueNextRequest(
RequestSender* sender,
Callback callback,
const std::string& playback_queue_name)
: UrlFetchRequestBase(sender, ProgressCallback(), ProgressCallback()),
callback_(std::move(callback)) {
CHECK(!callback_.is_null());
playback_queue_name_ = playback_queue_name;
}
PlaybackQueueNextRequest::~PlaybackQueueNextRequest() = default;
GURL PlaybackQueueNextRequest::GetURL() const {
// TODO(b/341324009): Move to an util file or class.
return GURL(base::StringPrintf(
"https://youtubemediaconnect.googleapis.com/v1/%s:next",
playback_queue_name_.c_str()));
}
ApiErrorCode PlaybackQueueNextRequest::MapReasonToError(
ApiErrorCode code,
const std::string& reason) {
return code;
}
bool PlaybackQueueNextRequest::IsSuccessfulErrorCode(ApiErrorCode error) {
return error == HTTP_SUCCESS;
}
HttpRequestMethod PlaybackQueueNextRequest::GetRequestType() const {
return HttpRequestMethod::kPost;
}
void PlaybackQueueNextRequest::ProcessURLFetchResults(
const network::mojom::URLResponseHead* response_head,
base::FilePath response_file,
std::string response_body) {
ApiErrorCode error = GetErrorCode();
switch (error) {
case HTTP_SUCCESS:
blocking_task_runner()->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&PlaybackQueueNextRequest::Parse,
std::move(response_body)),
base::BindOnce(&PlaybackQueueNextRequest::OnDataParsed,
weak_ptr_factory_.GetWeakPtr()));
break;
default:
RunCallbackOnPrematureFailure(error);
OnProcessURLFetchResultsComplete();
break;
}
}
void PlaybackQueueNextRequest::RunCallbackOnPrematureFailure(
ApiErrorCode error) {
std::move(callback_).Run(base::unexpected(error));
}
std::unique_ptr<QueueContainer> PlaybackQueueNextRequest::Parse(
const std::string& json) {
std::unique_ptr<base::Value> value = ParseJson(json);
return value ? QueueContainer::CreateFrom(*value) : nullptr;
}
void PlaybackQueueNextRequest::OnDataParsed(
std::unique_ptr<QueueContainer> playback_queue) {
if (!playback_queue) {
std::move(callback_).Run(base::unexpected(PARSE_ERROR));
} else {
std::move(callback_).Run(std::move(playback_queue));
}
OnProcessURLFetchResultsComplete();
}
} // namespace google_apis::youtube_music

@ -0,0 +1,156 @@
// 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 GOOGLE_APIS_YOUTUBE_MUSIC_YOUTUBE_MUSIC_API_REQUESTS_H_
#define GOOGLE_APIS_YOUTUBE_MUSIC_YOUTUBE_MUSIC_API_REQUESTS_H_
#include <memory>
#include <string>
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/types/expected.h"
#include "google_apis/common/base_requests.h"
#include "google_apis/youtube_music/youtube_music_api_request_types.h"
#include "google_apis/youtube_music/youtube_music_api_response_types.h"
namespace google_apis {
enum ApiErrorCode;
class RequestSender;
namespace youtube_music {
// Request that gets music recommendations from the API server. For API usage,
// please check below:
// https://developers.google.com/youtube/mediaconnect/reference/rest/v1/musicSections/load
class GetPlaylistsRequest : public UrlFetchRequestBase {
public:
using Callback = base::OnceCallback<void(
base::expected<std::unique_ptr<TopLevelMusicRecommendations>,
ApiErrorCode>)>;
GetPlaylistsRequest(RequestSender* sender, Callback callback);
GetPlaylistsRequest(const GetPlaylistsRequest&) = delete;
GetPlaylistsRequest& operator=(const GetPlaylistsRequest&) = delete;
~GetPlaylistsRequest() override;
protected:
// UrlFetchRequestBase:
GURL GetURL() const override;
ApiErrorCode MapReasonToError(ApiErrorCode code,
const std::string& reason) override;
bool IsSuccessfulErrorCode(ApiErrorCode error) override;
void ProcessURLFetchResults(
const network::mojom::URLResponseHead* response_head,
const base::FilePath response_file,
std::string response_body) override;
void RunCallbackOnPrematureFailure(ApiErrorCode code) override;
private:
static std::unique_ptr<TopLevelMusicRecommendations> Parse(
const std::string& json);
void OnDataParsed(std::unique_ptr<TopLevelMusicRecommendations> sections);
Callback callback_;
base::WeakPtrFactory<GetPlaylistsRequest> weak_ptr_factory_{this};
};
// Request that prepares the playback queue for the API server. For API usage,
// please check below:
// https://developers.google.com/youtube/mediaconnect/reference/rest/v1/queues/preparePlayback
class PlaybackQueuePrepareRequest : public UrlFetchRequestBase {
public:
using Callback = base::OnceCallback<void(
base::expected<std::unique_ptr<Queue>, ApiErrorCode>)>;
PlaybackQueuePrepareRequest(RequestSender* sender,
const PlaybackQueuePrepareRequestPayload& payload,
Callback callback);
PlaybackQueuePrepareRequest(const PlaybackQueuePrepareRequest&) = delete;
PlaybackQueuePrepareRequest& operator=(const PlaybackQueuePrepareRequest&) =
delete;
~PlaybackQueuePrepareRequest() override;
protected:
// UrlFetchRequestBase:
GURL GetURL() const override;
ApiErrorCode MapReasonToError(ApiErrorCode code,
const std::string& reason) override;
bool IsSuccessfulErrorCode(ApiErrorCode error) override;
HttpRequestMethod GetRequestType() const override;
bool GetContentData(std::string* upload_content_type,
std::string* upload_content) override;
void ProcessURLFetchResults(
const network::mojom::URLResponseHead* response_head,
const base::FilePath response_file,
std::string response_body) override;
void RunCallbackOnPrematureFailure(ApiErrorCode code) override;
private:
static std::unique_ptr<Queue> Parse(const std::string& json);
void OnDataParsed(std::unique_ptr<Queue> sections);
const PlaybackQueuePrepareRequestPayload payload_;
Callback callback_;
base::WeakPtrFactory<PlaybackQueuePrepareRequest> weak_ptr_factory_{this};
};
// Request to play the next in the playback queue for the API server. For API
// usage, please check below:
// https://developers.google.com/youtube/mediaconnect/reference/rest/v1/queues/next
class PlaybackQueueNextRequest : public UrlFetchRequestBase {
public:
using Callback = base::OnceCallback<void(
base::expected<std::unique_ptr<QueueContainer>, ApiErrorCode>)>;
PlaybackQueueNextRequest(RequestSender* sender,
Callback callback,
const std::string& playlist_name);
PlaybackQueueNextRequest(const PlaybackQueueNextRequest&) = delete;
PlaybackQueueNextRequest& operator=(const PlaybackQueueNextRequest&) = delete;
~PlaybackQueueNextRequest() override;
const std::string& playback_queue_name() const {
return playback_queue_name_;
}
void set_playback_queue_name(const std::string& playback_queue_name) {
playback_queue_name_ = playback_queue_name;
}
protected:
// UrlFetchRequestBase:
GURL GetURL() const override;
ApiErrorCode MapReasonToError(ApiErrorCode code,
const std::string& reason) override;
bool IsSuccessfulErrorCode(ApiErrorCode error) override;
HttpRequestMethod GetRequestType() const override;
void ProcessURLFetchResults(
const network::mojom::URLResponseHead* response_head,
const base::FilePath response_file,
std::string response_body) override;
void RunCallbackOnPrematureFailure(ApiErrorCode code) override;
private:
static std::unique_ptr<QueueContainer> Parse(const std::string& json);
void OnDataParsed(std::unique_ptr<QueueContainer> sections);
// Playlist queue name.
std::string playback_queue_name_;
Callback callback_;
base::WeakPtrFactory<PlaybackQueueNextRequest> weak_ptr_factory_{this};
};
} // namespace youtube_music
} // namespace google_apis
#endif // GOOGLE_APIS_YOUTUBE_MUSIC_YOUTUBE_MUSIC_API_REQUESTS_H_

@ -0,0 +1,415 @@
// 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.
#include "google_apis/youtube_music/youtube_music_api_response_types.h"
#include <string>
#include "base/json/json_value_converter.h"
#include "base/strings/stringprintf.h"
namespace google_apis::youtube_music {
namespace {
using ::base::JSONValueConverter;
constexpr char kApiResponseNameKey[] = "name";
constexpr char kApiResponseTitleKey[] = "title";
constexpr char kApiResponseMusicRecommendationsKey[] = "musicRecommendations";
constexpr char kApiResponseMusicSectionKey[] = "musicSection";
constexpr char kApiResponseWidthKey[] = "widthPixels";
constexpr char kApiResponseHeightKey[] = "heightPixels";
constexpr char kApiResponseUriKey[] = "uri";
constexpr char kApiResponseDescriptionKey[] = "description";
constexpr char kApiResponseItemCountKey[] = "itemCount";
constexpr char kApiResponseImagesKey[] = "images";
constexpr char kApiResponseOwnerKey[] = "owner";
constexpr char kApiResponsePlaylistKey[] = "playlist";
constexpr char kApiResponseContextKey[] = "context";
constexpr char kApiResponseSizeKey[] = "size";
constexpr char kApiResponseQueueKey[] = "queue";
constexpr char kApiResponseDurationKey[] = "duration";
constexpr char kApiResponseExplicitTypeKey[] = "explicitType";
constexpr char kApiResponseTrackKey[] = "track";
constexpr char kApiResponseStreamsKey[] = "streams";
constexpr char kApiResponseItemKey[] = "item";
constexpr char kApiResponseManifestKey[] = "manifest";
bool ConvertToGurl(std::string_view input, GURL* output) {
*output = GURL(input);
return true;
}
} // namespace
Image::Image() = default;
Image::~Image() = default;
// static
void Image::RegisterJSONConverter(JSONValueConverter<Image>* converter) {
converter->RegisterIntField(kApiResponseWidthKey, &Image::width_);
converter->RegisterIntField(kApiResponseHeightKey, &Image::height_);
converter->RegisterCustomField<GURL>(kApiResponseUriKey, &Image::url_,
&ConvertToGurl);
}
std::string Image::ToString() const {
return base::StringPrintf("Image(width=%d, height=%d, url=\"%s\")", width_,
height_, url_.spec().c_str());
}
Owner::Owner() = default;
Owner::~Owner() = default;
// static
void Owner::RegisterJSONConverter(JSONValueConverter<Owner>* converter) {
converter->RegisterStringField(kApiResponseTitleKey, &Owner::title_);
}
std::string Owner::ToString() const {
return base::StringPrintf("Owner(title=\"%s\"", title_.c_str());
}
Playlist::Playlist() = default;
Playlist::~Playlist() = default;
// static
void Playlist::RegisterJSONConverter(JSONValueConverter<Playlist>* converter) {
converter->RegisterStringField(kApiResponseNameKey, &Playlist::name_);
converter->RegisterStringField(kApiResponseTitleKey, &Playlist::title_);
converter->RegisterStringField(kApiResponseDescriptionKey,
&Playlist::description_);
converter->RegisterIntField(kApiResponseItemCountKey, &Playlist::item_count_);
converter->RegisterRepeatedMessage<Image>(kApiResponseImagesKey,
&Playlist::images_);
converter->RegisterNestedField(kApiResponseOwnerKey, &Playlist::owner_);
}
std::string Playlist::ToString() const {
std::string s;
for (size_t i = 0; i < images_.size(); i++) {
s += (i ? ", " : "") + images_[i]->ToString();
}
return base::StringPrintf(
"Playlist(name=\"%s\", title=\"%s\", description=\"%s\", item_count=%d, "
"images=[%s], owner=%s)",
name_.c_str(), title_.c_str(), description_.c_str(), item_count_,
s.c_str(), owner_.ToString().c_str());
}
MusicRecommendation::MusicRecommendation() = default;
MusicRecommendation::~MusicRecommendation() = default;
// static
void MusicRecommendation::RegisterJSONConverter(
JSONValueConverter<MusicRecommendation>* converter) {
converter->RegisterNestedField(kApiResponsePlaylistKey,
&MusicRecommendation::playlist_);
}
std::string MusicRecommendation::ToString() const {
return base::StringPrintf("MusicRecommendation(playlist=%s)",
playlist_.ToString().c_str());
}
MusicSection::MusicSection() = default;
MusicSection::~MusicSection() = default;
// static
void MusicSection::RegisterJSONConverter(
JSONValueConverter<MusicSection>* converter) {
converter->RegisterStringField(kApiResponseNameKey, &MusicSection::name_);
converter->RegisterStringField(kApiResponseTitleKey, &MusicSection::title_);
converter->RegisterRepeatedMessage<MusicRecommendation>(
kApiResponseMusicRecommendationsKey,
&MusicSection::music_recommendations_);
}
// static
std::unique_ptr<MusicSection> MusicSection::CreateFrom(
const base::Value& value) {
auto music_section = std::make_unique<MusicSection>();
JSONValueConverter<MusicSection> converter;
if (!converter.Convert(value, music_section.get())) {
DVLOG(1) << "Unable to construct `MusicSection` from parsed json.";
return nullptr;
}
return music_section;
}
std::string MusicSection::ToString() const {
std::string s;
for (size_t i = 0; i < music_recommendations_.size(); i++) {
s += (i ? ", " : "") + music_recommendations_[i]->ToString();
}
return base::StringPrintf(
"MusicSection(name=\"%s\", title=\"%s\", "
"music_recommendations=[%s])",
name_.c_str(), title_.c_str(), s.c_str());
}
TopLevelMusicRecommendation::TopLevelMusicRecommendation() = default;
TopLevelMusicRecommendation::~TopLevelMusicRecommendation() = default;
// static
void TopLevelMusicRecommendation::RegisterJSONConverter(
JSONValueConverter<TopLevelMusicRecommendation>* converter) {
converter->RegisterNestedField(kApiResponseMusicSectionKey,
&TopLevelMusicRecommendation::music_section_);
}
// static
std::unique_ptr<TopLevelMusicRecommendation>
TopLevelMusicRecommendation::CreateFrom(const base::Value& value) {
auto top_level_music_recommendation =
std::make_unique<TopLevelMusicRecommendation>();
JSONValueConverter<TopLevelMusicRecommendation> converter;
if (!converter.Convert(value, top_level_music_recommendation.get())) {
DVLOG(1) << "Unable to construct `TopLevelMusicRecommendation` from parsed "
"json.";
return nullptr;
}
return top_level_music_recommendation;
}
std::string TopLevelMusicRecommendation::ToString() const {
return base::StringPrintf("TopLevelMusicRecommendation(music_section=%s)",
music_section_.ToString().c_str());
}
TopLevelMusicRecommendations::TopLevelMusicRecommendations() = default;
TopLevelMusicRecommendations::~TopLevelMusicRecommendations() = default;
// static
void TopLevelMusicRecommendations::RegisterJSONConverter(
JSONValueConverter<TopLevelMusicRecommendations>* converter) {
converter->RegisterRepeatedMessage<TopLevelMusicRecommendation>(
kApiResponseMusicRecommendationsKey,
&TopLevelMusicRecommendations::top_level_music_recommendations_);
}
// static
std::unique_ptr<TopLevelMusicRecommendations>
TopLevelMusicRecommendations::CreateFrom(const base::Value& value) {
auto top_level_music_recommendations =
std::make_unique<TopLevelMusicRecommendations>();
JSONValueConverter<TopLevelMusicRecommendations> converter;
if (!converter.Convert(value, top_level_music_recommendations.get())) {
DVLOG(1) << "Unable to construct `TopLevelMusicRecommendations` from "
"parsed json.";
return nullptr;
}
return top_level_music_recommendations;
}
std::string TopLevelMusicRecommendations::ToString() const {
std::string s;
for (size_t i = 0; i < top_level_music_recommendations_.size(); i++) {
s += (i ? ", " : "") + top_level_music_recommendations_[i]->ToString();
}
return base::StringPrintf(
"TopLevelMusicRecommendations(top_level_music_recommendations=[%s])",
s.c_str());
}
Track::Track() = default;
Track::~Track() = default;
// static
void Track::RegisterJSONConverter(JSONValueConverter<Track>* converter) {
converter->RegisterStringField(kApiResponseNameKey, &Track::name_);
converter->RegisterStringField(kApiResponseTitleKey, &Track::title_);
converter->RegisterStringField(kApiResponseDurationKey, &Track::duration_);
converter->RegisterStringField(kApiResponseExplicitTypeKey,
&Track::explicit_type_);
converter->RegisterRepeatedMessage<Image>(kApiResponseImagesKey,
&Track::images_);
}
// static
std::unique_ptr<Track> Track::CreateFrom(const base::Value& value) {
auto track = std::make_unique<Track>();
JSONValueConverter<Track> converter;
if (!converter.Convert(value, track.get())) {
DVLOG(1) << "Unable to construct `Track` from parsed json.";
return nullptr;
}
return track;
}
std::string Track::ToString() const {
std::string s;
for (size_t i = 0; i < images_.size(); i++) {
s += (i ? ", " : "") + images_[i]->ToString();
}
return base::StringPrintf(
"Track(name=\"%s\", title=\"%s\", duration=\"%s\", explicit_type_=%s, "
"images=[%s])",
name_.c_str(), title_.c_str(), duration_.c_str(), explicit_type_.c_str(),
s.c_str());
}
QueueItem::QueueItem() = default;
QueueItem::~QueueItem() = default;
// static
void QueueItem::RegisterJSONConverter(
JSONValueConverter<QueueItem>* converter) {
converter->RegisterNestedField(kApiResponseTrackKey, &QueueItem::track_);
}
// static
std::unique_ptr<QueueItem> QueueItem::CreateFrom(const base::Value& value) {
auto queue_item = std::make_unique<QueueItem>();
JSONValueConverter<QueueItem> converter;
if (!converter.Convert(value, queue_item.get())) {
DVLOG(1) << "Unable to construct `QueueItem` from parsed json.";
return nullptr;
}
return queue_item;
}
std::string QueueItem::ToString() const {
return base::StringPrintf("QueueItem(track=%s)", track_.ToString().c_str());
}
Stream::Stream() = default;
Stream::~Stream() = default;
// static
void Stream::RegisterJSONConverter(JSONValueConverter<Stream>* converter) {
converter->RegisterCustomField<GURL>(kApiResponseUriKey, &Stream::url_,
&ConvertToGurl);
}
// static
std::unique_ptr<Stream> Stream::CreateFrom(const base::Value& value) {
auto stream = std::make_unique<Stream>();
JSONValueConverter<Stream> converter;
if (!converter.Convert(value, stream.get())) {
DVLOG(1) << "Unable to construct `Stream` from parsed json.";
return nullptr;
}
return stream;
}
std::string Stream::ToString() const {
return base::StringPrintf("Stream(url=\"%s\")", url_.spec().c_str());
}
PlaybackManifest::PlaybackManifest() = default;
PlaybackManifest::~PlaybackManifest() = default;
// static
void PlaybackManifest::RegisterJSONConverter(
JSONValueConverter<PlaybackManifest>* converter) {
converter->RegisterRepeatedMessage<Stream>(kApiResponseStreamsKey,
&PlaybackManifest::streams_);
}
// static
std::unique_ptr<PlaybackManifest> PlaybackManifest::CreateFrom(
const base::Value& value) {
auto playback_manifest = std::make_unique<PlaybackManifest>();
JSONValueConverter<PlaybackManifest> converter;
if (!converter.Convert(value, playback_manifest.get())) {
DVLOG(1) << "Unable to construct `PlaybackManifest` from parsed json.";
return nullptr;
}
return playback_manifest;
}
std::string PlaybackManifest::ToString() const {
std::string s;
for (size_t i = 0; i < streams_.size(); i++) {
s += (i ? ", " : "") + streams_[i]->ToString();
}
return base::StringPrintf("PlaybackManifest(streams=[%s])", s.c_str());
}
PlaybackContext::PlaybackContext() = default;
PlaybackContext::~PlaybackContext() = default;
// static
void PlaybackContext::RegisterJSONConverter(
JSONValueConverter<PlaybackContext>* converter) {
converter->RegisterNestedField(kApiResponseItemKey,
&PlaybackContext::queue_item_);
converter->RegisterNestedField(kApiResponseManifestKey,
&PlaybackContext::playback_manifest_);
}
// static
std::unique_ptr<PlaybackContext> PlaybackContext::CreateFrom(
const base::Value& value) {
auto playback_context = std::make_unique<PlaybackContext>();
JSONValueConverter<PlaybackContext> converter;
if (!converter.Convert(value, playback_context.get())) {
DVLOG(1) << "Unable to construct `PlaybackManifest` from parsed json.";
return nullptr;
}
return playback_context;
}
std::string PlaybackContext::ToString() const {
return base::StringPrintf(
"PlaybackContext(queue_item=%s, playback_manifest=%s)",
queue_item_.ToString().c_str(), playback_manifest_.ToString().c_str());
}
Queue::Queue() = default;
Queue::~Queue() = default;
// static
void Queue::RegisterJSONConverter(JSONValueConverter<Queue>* converter) {
converter->RegisterStringField(kApiResponseNameKey, &Queue::name_);
converter->RegisterIntField(kApiResponseSizeKey, &Queue::size_);
converter->RegisterNestedField(kApiResponseContextKey,
&Queue::playback_context_);
}
// static
std::unique_ptr<Queue> Queue::CreateFrom(const base::Value& value) {
auto queue = std::make_unique<Queue>();
JSONValueConverter<Queue> converter;
if (!converter.Convert(value, queue.get())) {
DVLOG(1) << "Unable to construct `Queue` from parsed json.";
return nullptr;
}
return queue;
}
std::string Queue::ToString() const {
return base::StringPrintf("Queue(name=\"%s\", size=%d, playback_context=%s)",
name_.c_str(), size_,
playback_context_.ToString().c_str());
}
QueueContainer::QueueContainer() = default;
QueueContainer::~QueueContainer() = default;
// static
void QueueContainer::RegisterJSONConverter(
JSONValueConverter<QueueContainer>* converter) {
converter->RegisterNestedField(kApiResponseQueueKey, &QueueContainer::queue_);
}
// static
std::unique_ptr<QueueContainer> QueueContainer::CreateFrom(
const base::Value& value) {
auto queue_container = std::make_unique<QueueContainer>();
JSONValueConverter<QueueContainer> converter;
if (!converter.Convert(value, queue_container.get())) {
DVLOG(1) << "Unable to construct `QueueContainer` from parsed json.";
return nullptr;
}
return queue_container;
}
std::string QueueContainer::ToString() const {
return base::StringPrintf("QueueContainer(queue=%s)",
queue_.ToString().c_str());
}
} // namespace google_apis::youtube_music

@ -0,0 +1,382 @@
// 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 GOOGLE_APIS_YOUTUBE_MUSIC_YOUTUBE_MUSIC_API_RESPONSE_TYPES_H_
#define GOOGLE_APIS_YOUTUBE_MUSIC_YOUTUBE_MUSIC_API_RESPONSE_TYPES_H_
#include <memory>
#include <string>
#include <vector>
#include "url/gurl.h"
namespace base {
template <class StructType>
class JSONValueConverter;
class Value;
} // namespace base
namespace google_apis::youtube_music {
// Image object from the API response. For object details, check below:
// https://developers.google.com/youtube/mediaconnect/reference/rest/v1/Image
class Image {
public:
Image();
Image(const Image&) = delete;
Image& operator=(const Image&) = delete;
~Image();
static void RegisterJSONConverter(base::JSONValueConverter<Image>* converter);
int width() const { return width_; }
int height() const { return height_; }
const GURL& url() const { return url_; }
std::string ToString() const;
private:
int width_;
int height_;
GURL url_;
};
// Owner object from the API response. For object details, check below:
// https://developers.google.com/youtube/mediaconnect/reference/rest/v1/playlists#PlaylistOwner
class Owner {
public:
Owner();
Owner(const Owner&) = delete;
Owner& operator=(const Owner&) = delete;
~Owner();
static void RegisterJSONConverter(base::JSONValueConverter<Owner>* converter);
const std::string title() const { return title_; }
std::string ToString() const;
private:
std::string title_;
};
// Playlist object from the API response. For object details, check below:
// https://developers.google.com/youtube/mediaconnect/reference/rest/v1/playlists#Playlist
class Playlist {
public:
Playlist();
Playlist(const Playlist&) = delete;
Playlist& operator=(const Playlist&) = delete;
~Playlist();
static void RegisterJSONConverter(
base::JSONValueConverter<Playlist>* converter);
const std::string name() const { return name_; }
const std::string title() const { return title_; }
const std::string description() const { return description_; }
int item_count() const { return item_count_; }
const std::vector<std::unique_ptr<Image>>& images() const { return images_; }
std::vector<std::unique_ptr<Image>>* mutable_images() { return &images_; }
Owner& owner() { return owner_; }
std::string ToString() const;
private:
std::string name_;
std::string title_;
std::string description_;
int item_count_;
std::vector<std::unique_ptr<Image>> images_;
Owner owner_;
};
// Music recommendation object from the API response. For object details, check
// below:
// https://developers.google.com/youtube/mediaconnect/reference/rest/v1/musicSections/load#musicrecommendation
class MusicRecommendation {
public:
MusicRecommendation();
MusicRecommendation(const MusicRecommendation&) = delete;
MusicRecommendation& operator=(const MusicRecommendation&) = delete;
~MusicRecommendation();
static void RegisterJSONConverter(
base::JSONValueConverter<MusicRecommendation>* converter);
Playlist& playlist() { return playlist_; }
std::string ToString() const;
private:
Playlist playlist_;
};
// Music section object from the API response. For object details, check below:
// https://developers.google.com/youtube/mediaconnect/reference/rest/v1/musicSections/load
class MusicSection {
public:
MusicSection();
MusicSection(const MusicSection&) = delete;
MusicSection& operator=(const MusicSection&) = delete;
~MusicSection();
static void RegisterJSONConverter(
base::JSONValueConverter<MusicSection>* converter);
static std::unique_ptr<MusicSection> CreateFrom(const base::Value& value);
const std::string& name() const { return name_; }
const std::string& title() const { return title_; }
const std::vector<std::unique_ptr<MusicRecommendation>>&
music_recommendations() const {
return music_recommendations_;
}
std::vector<std::unique_ptr<MusicRecommendation>>*
mutable_music_recommendations() {
return &music_recommendations_;
}
std::string ToString() const;
private:
std::string name_;
std::string title_;
std::vector<std::unique_ptr<MusicRecommendation>> music_recommendations_;
};
// Top level music recommendations object from the API response. For object
// details, check below:
// https://developers.google.com/youtube/mediaconnect/reference/rest/v1/musicSections/load
class TopLevelMusicRecommendation {
public:
TopLevelMusicRecommendation();
TopLevelMusicRecommendation(const TopLevelMusicRecommendation&) = delete;
TopLevelMusicRecommendation& operator=(const TopLevelMusicRecommendation&) =
delete;
~TopLevelMusicRecommendation();
static void RegisterJSONConverter(
base::JSONValueConverter<TopLevelMusicRecommendation>* converter);
static std::unique_ptr<TopLevelMusicRecommendation> CreateFrom(
const base::Value& value);
MusicSection& music_section() { return music_section_; }
std::string ToString() const;
private:
MusicSection music_section_;
};
// Top level music section and music recommendations object from the API
// response. For object details, check below:
// https://developers.google.com/youtube/mediaconnect/reference/rest/v1/musicSections/load
class TopLevelMusicRecommendations {
public:
TopLevelMusicRecommendations();
TopLevelMusicRecommendations(const TopLevelMusicRecommendations&) = delete;
TopLevelMusicRecommendations& operator=(const TopLevelMusicRecommendations&) =
delete;
~TopLevelMusicRecommendations();
static void RegisterJSONConverter(
base::JSONValueConverter<TopLevelMusicRecommendations>* converter);
static std::unique_ptr<TopLevelMusicRecommendations> CreateFrom(
const base::Value& value);
const std::vector<std::unique_ptr<TopLevelMusicRecommendation>>&
top_level_music_recommendations() const {
return top_level_music_recommendations_;
}
std::vector<std::unique_ptr<TopLevelMusicRecommendation>>*
mutable_top_level_music_recommendations() {
return &top_level_music_recommendations_;
}
std::string ToString() const;
private:
std::vector<std::unique_ptr<TopLevelMusicRecommendation>>
top_level_music_recommendations_;
};
// Track object from the API response. For object details, check below:
// https://developers.google.com/youtube/mediaconnect/reference/rest/v1/tracks#Track
class Track {
public:
Track();
Track(const Track&) = delete;
Track& operator=(const Track&) = delete;
~Track();
static void RegisterJSONConverter(base::JSONValueConverter<Track>* converter);
static std::unique_ptr<Track> CreateFrom(const base::Value& value);
const std::string& name() const { return name_; }
const std::string& title() const { return title_; }
const std::string& duration() const { return duration_; }
const std::string& explicit_type() const { return explicit_type_; }
const std::vector<std::unique_ptr<Image>>& images() const { return images_; }
std::vector<std::unique_ptr<Image>>* mutable_images() { return &images_; }
std::string ToString() const;
private:
std::string name_;
std::string title_;
std::string duration_;
std::string explicit_type_;
std::vector<std::unique_ptr<Image>> images_;
};
// Queue item object from the API response. For object details, check below:
// https://developers.google.com/youtube/mediaconnect/reference/rest/v1/queues#QueueItem
class QueueItem {
public:
QueueItem();
QueueItem(const QueueItem&) = delete;
QueueItem& operator=(const QueueItem&) = delete;
~QueueItem();
static void RegisterJSONConverter(
base::JSONValueConverter<QueueItem>* converter);
static std::unique_ptr<QueueItem> CreateFrom(const base::Value& value);
Track& track() { return track_; }
std::string ToString() const;
private:
Track track_;
};
// Stream object from the API response. For object details, check below:
// https://developers.google.com/youtube/mediaconnect/reference/rest/v1/queues#Stream
class Stream {
public:
Stream();
Stream(const Stream&) = delete;
Stream& operator=(const Stream&) = delete;
~Stream();
static void RegisterJSONConverter(
base::JSONValueConverter<Stream>* converter);
static std::unique_ptr<Stream> CreateFrom(const base::Value& value);
const GURL& url() const { return url_; }
std::string ToString() const;
private:
GURL url_;
};
// Playback manifest object from the API response. For object details, check
// below:
// https://developers.google.com/youtube/mediaconnect/reference/rest/v1/queues#PlaybackManifest
class PlaybackManifest {
public:
PlaybackManifest();
PlaybackManifest(const PlaybackManifest&) = delete;
PlaybackManifest& operator=(const PlaybackManifest&) = delete;
~PlaybackManifest();
static void RegisterJSONConverter(
base::JSONValueConverter<PlaybackManifest>* converter);
static std::unique_ptr<PlaybackManifest> CreateFrom(const base::Value& value);
const std::vector<std::unique_ptr<Stream>>& streams() const {
return streams_;
}
std::vector<std::unique_ptr<Stream>>* mutable_streams() { return &streams_; }
std::string ToString() const;
private:
std::vector<std::unique_ptr<Stream>> streams_;
};
// Playback context object from the API response. For object details, check
// below:
// https://developers.google.com/youtube/mediaconnect/reference/rest/v1/queues#PlaybackContext
class PlaybackContext {
public:
PlaybackContext();
PlaybackContext(const PlaybackContext&) = delete;
PlaybackContext& operator=(const PlaybackContext&) = delete;
~PlaybackContext();
static void RegisterJSONConverter(
base::JSONValueConverter<PlaybackContext>* converter);
static std::unique_ptr<PlaybackContext> CreateFrom(const base::Value& value);
QueueItem& queue_item() { return queue_item_; }
PlaybackManifest& playback_manifest() { return playback_manifest_; }
std::string ToString() const;
private:
QueueItem queue_item_;
PlaybackManifest playback_manifest_;
};
// Queue object from the API response. For object details, check below:
// https://developers.google.com/youtube/mediaconnect/reference/rest/v1/queues#Queue
class Queue {
public:
Queue();
Queue(const Queue&) = delete;
Queue& operator=(const Queue&) = delete;
~Queue();
static void RegisterJSONConverter(base::JSONValueConverter<Queue>* converter);
static std::unique_ptr<Queue> CreateFrom(const base::Value& value);
const std::string name() const { return name_; }
int size() const { return size_; }
PlaybackContext& playback_context() { return playback_context_; }
std::string ToString() const;
private:
std::string name_;
int size_;
PlaybackContext playback_context_;
};
// Playback queue container object from the API response. For object details,
// check below:
// https://developers.google.com/youtube/mediaconnect/reference/rest/v1/queues/next
class QueueContainer {
public:
QueueContainer();
QueueContainer(const QueueContainer&) = delete;
QueueContainer& operator=(const QueueContainer&) = delete;
~QueueContainer();
static void RegisterJSONConverter(
base::JSONValueConverter<QueueContainer>* converter);
static std::unique_ptr<QueueContainer> CreateFrom(const base::Value& value);
Queue& queue() { return queue_; }
std::string ToString() const;
private:
Queue queue_;
};
} // namespace google_apis::youtube_music
#endif // GOOGLE_APIS_YOUTUBE_MUSIC_YOUTUBE_MUSIC_API_RESPONSE_TYPES_H_