当请求播放列表时,Google API 使用一种分页形式(这也适用于其他类型的 API 响应)。
每个响应页面的最大条目数为 50。
▶ 响应格式是一个 JSON 对象,其中包含查询响应的描述,包括请求的项目总数和当前响应中包含的项目。
▶ 如果查询包含的项目多于每页的最大值,则 nextPageToken 属性设置为代表 Page Token 的字符串值,可用于查询下一页结果(您可以在 YouTubePlayList 类的 LoadPlaylistAsync() 方法中看到它是如何在代码中工作的)。
当没有其他页面可用时,nextPageToken 设置为 null (Nothing)。
通过查看这个Token值,我们可以判断Playlist内容是否完整。
可以使用带有访问密钥的 OAuth 2.0 调用 API,这需要 Google API 客户端库,或者使用简单的 HTTP GET 和由FormUrlEncodedContent 组成的 URI,标准 URL 查询元组。
在这里,我使用的是后一种方法,因为它非常简化并且效果很好。
如果您有 Google SDK,可以按照PlaylistItems list documentation page 中的示例进行操作。
我正在使用类结构(模型)来反序列化 JSON 响应:使用标准 .Net 类处理内容非常简单,其中 JSON 属性值已经转换为正确格式的强类型值。
辅助类 YouTubePlayList 包含类模型和反序列化 JSON 或将类对象序列化回 JSON 字符串所需的方法。
如果需要,它还使用静态 HttpClient 执行 HTTP GET 请求并处理 API 响应的分页。
该类使用 HttpClient 类提供的异步方法。
▶ 类通过查询 API 所需的访问密钥进行初始化。
如果密钥尚不可用,请按照说明在API Reference documentation page 中创建一个(免费),这会将您转到开发者控制台的 API 访问窗格。
要加载播放列表,请将播放列表令牌传递给 LoadPlaylistAsync() 方法。
此方法返回一个 PlayList 对象,其中包含与 API 查询本身相关的所有信息以及所有生成的项目,在一个 List(Of PlayListEntry) 对象中。
所有反序列化和 HTTP 调用任务都在内部处理。
例如,在Button.Click处理程序中,初始化传递Key的帮助类,调用LoadPlaylistAsync()方法指定要下载的播放列表的Token,然后处置YouTubePlayList类对象并使用返回的任何需要的类对象:
我正在使用您在上一个问题中提供的播放列表令牌
Private Async Sub btnLoadPlaylist_Click(sender As Object, e As EventArgs) Handles btnLoadPlaylist.Click
' Initialize with the Google API Key
Dim playListHelper As New YouTubePlayList("AIzaSy---------------------------------")
' Load the Playlist passing the playListId
Dim playList = Await playListHelper.LoadPlaylistAsync("PL4_Dx88dpu7epfH6ybwqJpf9uL2tAl368")
' Dispose of the class object, to close and dispose of the HttpClient object
playListHelper.Dispose()
For Each item As YouTubePlayList.PlayListEntry In playList.Items
Dim itemTitle = item.ItemContent.Title
Dim itemChannel = item.ItemContent.ChannelTitle
Dim videoAddress = "https://www.youtube.com/watch?v=" & item.ItemContent.ResourceId.VideoId
Next
End Sub
类对象的示例功能:
Google PlayList 下载器帮助类:
需要Json.Net (Newtonsoft.Json) 12.0.3+
Imports System
Imports System.Collections.Generic
Imports System.Globalization
Imports System.Net.Http
Imports System.Threading.Tasks
Imports Newtonsoft.Json
Imports Newtonsoft.Json.Converters
Public Class YouTubePlayList
Implements IDisposable
Private m_Json As String = String.Empty
Private m_KeyToken As String = String.Empty
Private m_APIUrl As String = "https://www.googleapis.com/youtube/v3/playlistItems?"
Private Shared client As HttpClient = Nothing
Public Sub New(key As String)
Me.New(key, String.Empty)
End Sub
Public Sub New(key As String, json As String)
m_KeyToken = key
If Not String.IsNullOrEmpty(json) Then
m_Json = json
End If
client = New HttpClient()
End Sub
Public Function Deserialize() As PlayList
Return Deserialize(m_Json)
End Function
Public Function Deserialize(json As String) As PlayList
If String.IsNullOrEmpty(json) Then Return Nothing
Return JsonConvert.DeserializeObject(Of PlayList)(json, ConverterSettings.Settings)
End Function
Public Function Serialize(root As PlayList) As String
Return JsonConvert.SerializeObject(root, ConverterSettings.Settings)
End Function
Friend NotInheritable Class ConverterSettings
Public Shared ReadOnly Settings As New JsonSerializerSettings() With {
.MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
.DateParseHandling = DateParseHandling.None,
.Converters = {New IsoDateTimeConverter() With {
.DateTimeStyles = DateTimeStyles.AssumeUniversal
}}
}
End Class
Public Async Function LoadPlaylistAsync(playListToken As String) As Task(Of PlayList)
Dim resultPlayList As PlayList = Nothing
Dim nextPageToken As String = String.Empty
Dim listItemsRead As Integer = 0
Dim resultsPerPage As Integer = 50
While nextPageToken IsNot Nothing
Dim queryUrl = Await GetRequestUrl(playListToken, nextPageToken, resultsPerPage)
Using response = Await client.GetAsync(queryUrl)
If response.IsSuccessStatusCode Then
Dim jsonResponse = Await response.Content.ReadAsStringAsync()
Dim playList = Deserialize(jsonResponse)
listItemsRead += playList.Items.Count
If resultPlayList Is Nothing Then
resultPlayList = playList
Else
resultPlayList.Items.AddRange(playList.Items)
End If
nextPageToken = playList.NextPageToken
resultPlayList.PageInfo.ResultsPerPage = listItemsRead
End If
End Using
End While
Return resultPlayList
End Function
Private Async Function GetRequestUrl(playListToken As String, nextPageToken As String, resultsPerPage As Integer) As Task(Of String)
Dim content = New FormUrlEncodedContent(New KeyValuePair(Of String, String)() {
New KeyValuePair(Of String, String)("part", "contentDetails,snippet,id"),
New KeyValuePair(Of String, String)("maxResults", resultsPerPage.ToString()),
New KeyValuePair(Of String, String)("pageToken", nextPageToken),
New KeyValuePair(Of String, String)("playlistId", playListToken),
New KeyValuePair(Of String, String)("key", m_KeyToken)
})
Return m_APIUrl & Await content.ReadAsStringAsync()
End Function
Public Class PlayList
<JsonProperty("kind")>
Public Property Kind As String
<JsonProperty("etag")>
Public Property Etag As String
<JsonProperty("nextPageToken")>
Public Property NextPageToken As String
<JsonProperty("prevPageToken")>
Public Property PrevPageToken As String
<JsonProperty("items")>
Public Property Items As List(Of PlayListEntry)
<JsonProperty("pageInfo")>
Public Property PageInfo As PageInfo
End Class
Public Class PlayListEntry
<JsonProperty("kind")>
Public Property Kind As String
<JsonProperty("etag")>
Public Property Etag As String
<JsonProperty("id")>
Public Property Id As String
<JsonProperty("snippet")>
Public Property ItemContent As Content
<JsonProperty("contentDetails")>
Public Property ContentDetails As ContentDetails
End Class
Public Class ContentDetails
<JsonProperty("videoId")>
Public Property VideoId As String
<JsonProperty("videoPublishedAt")>
Public Property VideoPublishedAt As DateTimeOffset
End Class
Public Class Content
<JsonProperty("publishedAt")>
Public Property PublishedAt As DateTimeOffset
<JsonProperty("channelId")>
Public Property ChannelId As String
<JsonProperty("title")>
Public Property Title As String
<JsonProperty("description")>
Public Property Description As String
<JsonProperty("thumbnails")>
Public Property Thumbnails As Thumbnails
<JsonProperty("channelTitle")>
Public Property ChannelTitle As String
<JsonProperty("playlistId")>
Public Property PlaylistId As String
<JsonProperty("position")>
Public Property Position As Long
<JsonProperty("resourceId")>
Public Property ResourceId As ResourceId
End Class
Public Class ResourceId
<JsonProperty("kind")>
Public Property Kind As String
<JsonProperty("videoId")>
Public Property VideoId As String
End Class
Public Class Thumbnails
<JsonProperty("default")>
Public Property DefaultImage As ImageDescriptor
<JsonProperty("medium")>
Public Property Medium As ImageDescriptor
<JsonProperty("high")>
Public Property High As ImageDescriptor
<JsonProperty("standard", NullValueHandling:=NullValueHandling.Ignore)>
Public Property Standard As ImageDescriptor
<JsonProperty("maxres", NullValueHandling:=NullValueHandling.Ignore)>
Public Property MaxResolution As ImageDescriptor
End Class
Public Class ImageDescriptor
<JsonProperty("url")>
Public Property Url As Uri
<JsonProperty("width")>
Public Property Width As Integer
<JsonProperty("height")>
Public Property Height As Integer
End Class
Public Class PageInfo
<JsonProperty("totalResults")>
Public Property TotalResults As Integer
<JsonProperty("resultsPerPage")>
Public Property ResultsPerPage As Integer
End Class
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
Protected Sub Dispose(disposing As Boolean)
If disposing Then
client?.CancelPendingRequests()
client?.Dispose()
client = Nothing
End If
End Sub
End Class
C# 版本:
using System.Collections.Generic;
using System.Globalization;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
public class YouTubePlayList : IDisposable
{
private string m_Json = string.Empty;
private string m_KeyToken = string.Empty;
private string m_APIUrl = "https://www.googleapis.com/youtube/v3/playlistItems?";
private static HttpClient client = null;
public YouTubePlayList(string key) : this(key, string.Empty) { }
public YouTubePlayList(string key, string json)
{
m_KeyToken = key;
if (!string.IsNullOrEmpty(json)) {
m_Json = json;
}
client = new HttpClient();
}
public PlayList Deserialize() => Deserialize(m_Json);
public PlayList Deserialize(string json)
{
if (string.IsNullOrEmpty(json)) return null;
return JsonConvert.DeserializeObject<PlayList>(json, ConverterSettings.Settings);
}
public string Serialize(PlayList root) =>
JsonConvert.SerializeObject(root, ConverterSettings.Settings);
internal sealed class ConverterSettings
{
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings {
MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
DateParseHandling = DateParseHandling.None,
Converters = {
new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
}
};
}
public async Task<PlayList> LoadPlaylistAsync(string playListToken)
{
PlayList resultPlayList = null;
string nextPageToken = string.Empty;
int listItemsRead = 0;
int resultsPerPage = 50;
while (nextPageToken != null) {
string queryUrl = await GetRequestUrl(playListToken, nextPageToken, resultsPerPage);
using (var response = await client.GetAsync(queryUrl)) {
if (response.IsSuccessStatusCode) {
string jsonResponse = await response.Content.ReadAsStringAsync();
var playList = Deserialize(jsonResponse);
listItemsRead += playList.Items.Count;
if (resultPlayList == null) {
resultPlayList = playList;
}
else {
resultPlayList.Items.AddRange(playList.Items);
}
nextPageToken = playList.NextPageToken;
resultPlayList.PageInfo.ResultsPerPage = listItemsRead;
}
}
}
return resultPlayList;
}
private async Task<string> GetRequestUrl(string playListToken, string nextPageToken, int resultsPerPage)
{
var content = new FormUrlEncodedContent(new KeyValuePair<string, string>[] {
new KeyValuePair<string, string>("part", "contentDetails,snippet,id"),
new KeyValuePair<string, string>("maxResults", resultsPerPage.ToString()),
new KeyValuePair<string, string>("pageToken", nextPageToken),
new KeyValuePair<string, string>("playlistId", playListToken),
new KeyValuePair<string, string>("key", m_KeyToken)
});
return m_APIUrl + await content.ReadAsStringAsync();
}
public class PlayList
{
public string Kind { get; set; }
public string Etag { get; set; }
public string NextPageToken { get; set; }
public string PrevPageToken { get; set; }
public List<PlayListEntry> Items { get; set; }
public PageInfo PageInfo { get; set; }
}
public class PlayListEntry
{
public string Kind { get; set; }
public string Etag { get; set; }
public string Id { get; set; }
[JsonProperty("snippet")]
public Content ItemContent { get; set; }
public ContentDetails ContentDetails { get; set; }
}
public class ContentDetails
{
public string VideoId { get; set; }
public DateTimeOffset VideoPublishedAt { get; set; }
}
public class Content
{
public DateTimeOffset PublishedAt { get; set; }
public string ChannelId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public Thumbnails Thumbnails { get; set; }
public string ChannelTitle { get; set; }
public string PlaylistId { get; set; }
public long Position { get; set; }
public ResourceId ResourceId { get; set; }
}
public class ResourceId
{
public string Kind { get; set; }
public string VideoId { get; set; }
}
public class Thumbnails
{
[JsonProperty("default")]
public ImageDescriptor DefaultImage { get; set; }
public ImageDescriptor Medium { get; set; }
public ImageDescriptor High { get; set; }
[JsonProperty("standard", NullValueHandling = NullValueHandling.Ignore)]
public ImageDescriptor Standard { get; set; }
[JsonProperty("maxres", NullValueHandling = NullValueHandling.Ignore)]
public ImageDescriptor MaxResolution { get; set; }
}
public class ImageDescriptor
{
public Uri Url { get; set; }
public int Width { get; set; }
public int Height { get; set; }
}
public class PageInfo
{
public int TotalResults { get; set; }
public int ResultsPerPage { get; set; }
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (disposing) {
client?.CancelPendingRequests();
client?.Dispose();
client = null;
}
}
}