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:

committed by
Chromium LUCI CQ

parent
5ccb6e05a4
commit
18acfd6e54
@@ -4024,6 +4024,7 @@ static_library("ui") {
|
|||||||
"//google_apis/common",
|
"//google_apis/common",
|
||||||
"//google_apis/drive",
|
"//google_apis/drive",
|
||||||
"//google_apis/tasks",
|
"//google_apis/tasks",
|
||||||
|
"//google_apis/youtube_music",
|
||||||
"//media/capture:capture_lib",
|
"//media/capture:capture_lib",
|
||||||
"//mojo/public/js:resources_grit",
|
"//mojo/public/js:resources_grit",
|
||||||
"//remoting/host/chromeos:features",
|
"//remoting/host/chromeos:features",
|
||||||
|
24
google_apis/youtube_music/BUILD.gn
Normal file
24
google_apis/youtube_music/BUILD.gn
Normal file
@@ -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",
|
||||||
|
]
|
||||||
|
}
|
1
google_apis/youtube_music/OWNERS
Normal file
1
google_apis/youtube_music/OWNERS
Normal file
@@ -0,0 +1 @@
|
|||||||
|
file://ash/system/focus_mode/youtube_music/OWNERS
|
86
google_apis/youtube_music/youtube_music_api_request_types.cc
Normal file
86
google_apis/youtube_music/youtube_music_api_request_types.cc
Normal file
@@ -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
|
47
google_apis/youtube_music/youtube_music_api_request_types.h
Normal file
47
google_apis/youtube_music/youtube_music_api_request_types.h
Normal file
@@ -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_
|
252
google_apis/youtube_music/youtube_music_api_requests.cc
Normal file
252
google_apis/youtube_music/youtube_music_api_requests.cc
Normal file
@@ -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§ionRecommendationLimit=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
|
156
google_apis/youtube_music/youtube_music_api_requests.h
Normal file
156
google_apis/youtube_music/youtube_music_api_requests.h
Normal file
@@ -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_
|
415
google_apis/youtube_music/youtube_music_api_response_types.cc
Normal file
415
google_apis/youtube_music/youtube_music_api_response_types.cc
Normal file
@@ -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
|
382
google_apis/youtube_music/youtube_music_api_response_types.h
Normal file
382
google_apis/youtube_music/youtube_music_api_response_types.h
Normal file
@@ -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_
|
Reference in New Issue
Block a user