YouTube Data API Client Library for .NETをUnityで利用してYoutubeライブのコメント、スパチャを取得する

# 概要
Youtube公式に提供されているAPIを利用して、Youtubeライブのコメント、スパチャを取得します。
下記は作りかけの機能ですが、APIを利用するとこんなの作れます。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">UnityでYoutubeLiveのコメビュをつくってみるテスト。 <a href="https://t.co/TZbgfsZltV">pic.twitter.com/TZbgfsZltV</a></p>&mdash; unagi (@UnagiHuman) <a href="https://twitter.com/UnagiHuman/status/1341424815794581505?ref_src=twsrc%5Etfw">December 22, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# Youtube APIを利用出来るようにする
下記URLを参考に以下の3つを作成します。

* クライアントID
* クライアントシークレット
* APIキー
https://dev.classmethod.jp/articles/oauth2-youtube-data-api/


# YouTube Data API Client Library for .NETのインストール

YouTube Data API Client Library for .NETはNugetにありますが、UnityでNugetを利用するには[Nuget for Unity](https://github.com/GlitchEnzo/NuGetForUnity)をインストールします。
そして、Nuget for Unityで「Google.Apis.Youtube.v3」を検索してインストールします。

# Oauthの実装

下記コードをGameObjectにアタッチして実行するとOAuthできます。_cliendIdと_clientSecretは↑で手に入れたものを入力します（下記コードの実行にはUniTaskのインストールが別途必要です）

```csharp:YoutubeAuthorize.cs
using System.Collections.Specialized;
using System.Text.RegularExpressions;
using UnityEngine;
using System.Threading;
using Cysharp.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.YouTube.v3;

public class YoutubeAuthorizeSample : MonoBehaviour
{

	[SerializeField] private string _cliendId;
	[SerializeField] private string _clientSecret;
	[SerializeField] private string _user = "SampleGame";

	private void Start()
	{
		Authorize().Forget();
	}

	

	async UniTask Authorize()
	{
		YoutubeAuthorize.AuthorizeAsync(_cliendId, _clientSecret, _user).Forget();
	}

	private NameValueCollection ParseQueryString(string s)
	{
		NameValueCollection nvc = new NameValueCollection();

		// remove anything other than query string from url
		if (s.Contains("?"))
		{
			s = s.Substring(s.IndexOf('?') + 1);
		}

		foreach (string vp in Regex.Split(s, "&"))
		{
			string[] singlePair = Regex.Split(vp, "=");
			if (singlePair.Length == 2)
			{
				nvc.Add(singlePair[0], singlePair[1]);
			}
			else
			{
				// only one key with no value specified in query string
				nvc.Add(singlePair[0], string.Empty);
			}
		}

		return nvc;
	}

	async UniTask<UserCredential> AuthorizeAsync(string cliendId, string clientSecret, string user)
	{
		var secrets = new ClientSecrets
		{
			ClientId = cliendId,
			ClientSecret = clientSecret
		};

		var scopes = new string[]
		{
			YouTubeService.Scope.YoutubeReadonly
		};

		return await GoogleWebAuthorizationBroker.AuthorizeAsync(secrets, scopes, user, CancellationToken.None);
	}
}

```

# コメント、スーパーチャットの取得

下記コードをGameObjectにアタッチし、_apiKeyに↑のAPIキー、_liveChatURLにはコメントを取得したいYoutubeライブのURLを入力して実行すると、OnReceiveLiveChatMessageにコメントとスパチャの情報が渡ります。コメント取得はポーリング式で、APIから取得できるLiveChatMessageのPollingIntervalMillis間隔で取得できます。PollingIntervalMillisはサーバーの状況によって変動するらしいですが、大体が5秒程度です。

```csharp:YoutubeChatProvider.cs
using System;
using System.Collections.Specialized;
using System.Text.RegularExpressions;
using UnityEngine;
using System.Threading;
using Cysharp.Threading.Tasks;
using Google.Apis.Services;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using UniRx;
using UnityEngine.UI;
public class YoutubeChatSample : MonoBehaviour
{
	public System.Action<LiveChatMessage> OnReceiveLiveChatMessage;

	[SerializeField] private string _apiKey;
	[SerializeField] private string _liveChatURL;


	void Start()
	{
		var cancel = this.GetCancellationTokenOnDestroy();
		ConnectAsync(cancel).Forget();
	}

	async UniTask ConnectAsync(CancellationToken cancel)
	{
		var youtubeService = new YouTubeService(new BaseClientService.Initializer
		{
			ApiKey = _apiKey
		});


		var response = await GetVideoListResponse(_liveChatURL, youtubeService);

		if (response.Items.Count > 0)
		{
			var item = response.Items[0];

			var chatId = item.LiveStreamingDetails.ActiveLiveChatId;

			var livechatRequest = youtubeService.LiveChatMessages.List(chatId, new string[] { "snippet", "authorDetails" });

			livechatRequest.PageToken = null;

			var livechatResponse = await livechatRequest.ExecuteAsync();

			foreach (var liveChatItem in livechatResponse.Items)
			{
				OnReceiveLiveChatMessage?.Invoke(liveChatItem);
			}

			await UniTask.Delay((int)livechatResponse.PollingIntervalMillis);

			var nextPageToken = livechatResponse.NextPageToken;

			while (true)
			{
				if (cancel.IsCancellationRequested)
				{
					youtubeService.Dispose();
					break;
				}

				try
				{
					livechatRequest.PageToken = nextPageToken;
					livechatResponse = await livechatRequest.ExecuteAsync();


					foreach (LiveChatMessage liveChatItem in livechatResponse.Items)
					{
						OnReceiveLiveChatMessage?.Invoke(liveChatItem);
					}

				}
				catch (Exception e)
				{
					Debug.Log(e.Message);
				}
				nextPageToken = livechatResponse.NextPageToken;
				Debug.Log((int)livechatResponse.PollingIntervalMillis);
				await UniTask.Delay((int)livechatResponse.PollingIntervalMillis, false, PlayerLoopTiming.Update, cancel);
			}

		}

	}


	async UniTask<VideoListResponse> GetVideoListResponse(string liveChatURL, YouTubeService youtubeService)
	{
		var uri = new Uri(_liveChatURL);
		var qscoll = ParseQueryString(uri.Query);
		var videoId = qscoll["v"];
		var videosList = youtubeService.Videos.List("LiveStreamingDetails");
		videosList.Id = videoId;

		try
		{
			return await videosList.ExecuteAsync();

		}
		catch (Exception e)
		{
			throw e;
		}
	}

	private NameValueCollection ParseQueryString(string s)
	{
		NameValueCollection nvc = new NameValueCollection();

		// remove anything other than query string from url
		if (s.Contains("?"))
		{
			s = s.Substring(s.IndexOf('?') + 1);
		}

		foreach (string vp in Regex.Split(s, "&"))
		{
			string[] singlePair = Regex.Split(vp, "=");
			if (singlePair.Length == 2)
			{
				nvc.Add(singlePair[0], singlePair[1]);
			}
			else
			{
				// only one key with no value specified in query string
				nvc.Add(singlePair[0], string.Empty);
			}
		}

		return nvc;
	}
}

```