UnityでYouTubeLiveのAPIを叩いてUIに変数を表示する

# 最初に
ニコ生とYoutubeLiveで同時配信をしていて、ニコ生しかコメントを取得できていなかったため、YoutubeLiveからチャットを取得するプログラムを組みました。
一応下のコードで、YouTubeLiveからチャットが取得できます。
あと、その下の方で、何をしたのかちょっとずつ解説を書きます。
間違ってたらご指摘下さい。

あと、こんなクソ汚いコード使われたくないので、ちゃんとしたの、だれかunitypackageで、だしてくりー。。。
一応プロジェクト置いておくね。
https://github.com/platoronical/youtubelivecommentReciever
※色々あって、プロジェクトが変わりました。

## コード！

```csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using System.IO;
using MiniJSON;
using System;
using Newtonsoft.Json;
using UnityEngine.UI;



public class GetHttpSample : MonoBehaviour {


    //ここから使う

    private string apikey = "xxxx";//APIキー  

    private string searchBaseURI = "https://www.googleapis.com/youtube/v3/search?key=[APIkey]&part=snippet&channelId=";

    private string searchBaseChannnel = "UCBGdwqBtOebIhrtOPzOXj0w";//ここを書き換えるYoutubeチャンネル

    private string searchBaseStr = "&eventType=live&type=video";

    private string videoId;

    private string youtubeAPIbase = "https://www.googleapis.com/youtube/v3/";

    private string channnelSearch = "videos?part=liveStreamingDetails&id=";

    private string chatId;

    private string pagetoken = "&pageToken=";

    private string chatURIUp = "liveChat/messages?liveChatId=";

    private bool connectionflag = false;

    private string nextPageTokenstr = null;

    private string jsontext;

    //コメントと投稿時間だけ出るやつ
    private string chatURIbottom = "&part=snippet&hl=ja&maxResults=2000&fields=items/snippet/displayMessage,items/snippet/publishedAt,items/authorDetails/displayName&key=";//&part=snippet&hl=ja&maxResults=2000&fields=items/snippet/displayMessage,items/snippet/publishedAt&key=
    //全部出るやつ
    private string chatURIbottom2 = "&part=snippet,authorDetails&key=";

    [SerializeField]
    private GameObject canvas;

    // Use this for initialization
    void Start () {
        StartCoroutine(GetYoutubeAPI());
	}
	
	// Update is called once per frame
	void Update () {
    }

    private IEnumerator GetYoutubeAPI()
    {
        var urisample = searchBaseURI + searchBaseChannnel + searchBaseStr;

        UnityWebRequest liverequest = UnityWebRequest.Get(urisample);//testURI + apikey);
        yield return liverequest.SendWebRequest();

        if (liverequest.isHttpError || liverequest.isNetworkError)
        {
            Debug.Log(liverequest.error);
        }
        else
        {
            jsontext = liverequest.downloadHandler.text;
            //MiniJSON　つかうー！！！！
            var mjson = (IDictionary)MiniJSON.Json.Deserialize(jsontext);

            var mitems = (IList)mjson["items"];
            var mid = (IDictionary)mitems[0];
            var sid = (IDictionary)mid["id"];
            string mvideoId = (string)sid["videoId"];
            
            //videoIdを取得
            videoId = (string)sid["videoId"];

            /*vs2017ならできる？？
            var chatJsonObj = JsonConvert.DeserializeObject<dynamic>(jsontext);
            string videoId2 = chatJsonObj.items[0].id.videoId;
            Debug.Log("videoID2017 : " + videoId2);
            */           
            StartCoroutine(GetChatId());
        }
    }

    private IEnumerator GetChatId()
    {
        StopCoroutine(GetYoutubeAPI());

        //ChatIdを取得しにいくよ！！
        var searchChannel = youtubeAPIbase + channnelSearch + videoId + "&key=" + apikey;

        UnityWebRequest channelrequest = UnityWebRequest.Get(searchChannel);
        yield return channelrequest.SendWebRequest();

        var mchanjson = (IDictionary)Json.Deserialize(channelrequest.downloadHandler.text);

        var citems = (IList)mchanjson["items"];
        var cslsd = (IDictionary)citems[0];
        var clad = (IDictionary)cslsd["liveStreamingDetails"];
        string mvchatId = (string)clad["activeLiveChatId"];
        //chatIdを取得
        chatId = (string)clad["activeLiveChatId"];

        StartCoroutine(GetComment());
    }

    private IEnumerator GetComment()
    {
        StopCoroutine(GetChatId());
        yield return new WaitForSeconds(5.0f);

        //チャットを取りに行く！！！
        var chatURI = youtubeAPIbase + chatURIUp + chatId + pagetoken + nextPageTokenstr + chatURIbottom2 + apikey;

        UnityWebRequest connectChatrequest = UnityWebRequest.Get(chatURI);
        yield return connectChatrequest.SendWebRequest();

        var commentlogjson = (IDictionary)Json.Deserialize(connectChatrequest.downloadHandler.text);

        //このif文は全くの無意味
        if(nextPageTokenstr == (string)commentlogjson["nextPageToken"])
        {
            Debug.Log("sameToken");
        }
        else
        {
            nextPageTokenstr = (string)commentlogjson["nextPageToken"];

            var pageinfo = (IDictionary)commentlogjson["pageInfo"];
            int commentcount = int.Parse(pageinfo["totalResults"].ToString());
           
            //コメント分だけ描画
            for(var i = 0; i < (int)commentcount; i++)
            {
                GameObject cvn = Instantiate(canvas);

                var citems = (IList)commentlogjson["items"];
                var cslsd = (IDictionary)citems[i];
                var clad = (IDictionary)cslsd["snippet"];
                string message = (string)clad["displayMessage"];

                cvn.transform.Find("Description").gameObject.GetComponent<Text>().text = message;

                var author = (IDictionary)cslsd["authorDetails"];
                var dispName = (string)author["displayName"];

                cvn.transform.Find("Name").gameObject.GetComponent<Text>().text = dispName;

                float _x = UnityEngine.Random.Range(-400f, 400f);
                float _y = UnityEngine.Random.Range(-250f, 250f);
                cvn.transform.position = new Vector3(_x, _y, cvn.transform.position.z);
            }
        }
        StartCoroutine(stopWait());
    }

    IEnumerator stopWait()
    {
        yield return new WaitForSeconds(1f);
        StartCoroutine(GetComment());
    }
}
```

山盛りですが、ひとつづつ。
何をやりたいかということはこの前のエントリーとかぶりますが、まぁ。
https://qiita.com/platoronical/items/ba99be66d0cfb85e6038

## 構造！
今回はコルーチンの塊です。

```csharp
private IEnumerator GetYoutubeAPI()
{
 //Youtubeへ接続しに行ってます
}
private IEnumerator GetChatId()
{
 //videoIdを基に、chatIdを探しに行っています。
}
private IEnumerator GetComment()
{
 //chatIdを基に、コメントを取得します。
}
IEnumerator stopWait()
{
 //　StartCoroutine(GetComment()); で、コメントを再取得しにいっています。
}
```

## JSON！
jsonのパースは、いろいろあってきれいな書き方が、VS2017から.NET4.6から出来るようです。

```csharp
var chatJsonObj = JsonConvert.DeserializeObject<dynamic>(jsontext);
string videoId2 = chatJsonObj.items[0].id.videoId;
Debug.Log("videoID2017 : " + videoId2);
```

上記のようなコードで取れるようです。きれいでいいなー。
今回はそれを知らなかったので、MiniJSONというプラグイン使ってパースしています。

```csharp
var mjson = (IDictionary)MiniJSON.Json.Deserialize(jsontext);

var mitems = (IList)mjson["items"];
var mid = (IDictionary)mitems[0];
var sid = (IDictionary)mid["id"];
string mvideoId = (string)sid["videoId"];            
```

さっきのコードと同じことをしてます。
今回はこのような形で、展開していくことにしました。
色々つまづきポイントはあったんですが、最後の最後に整数値へのキャストで、躓いたので紹介します。

```csharp
int commentcount = int.Parse(pageinfo["totalResults"].ToString());
```

もうね、キャストの仕方がありすぎて、どうしたものかと思った。

## YoutubeAPI!
おまけ程度だけど、紹介します。
多分一番意味分かんないのは、uriを作っているところで、もっとスマートに出来るでしょーって感じなんだけど、それは置いておいて。
pageTokenだと思います。
コメント差分が欲しかったため、どうしたものかと思って**部屋の中で土下座していると**、API側で教えてくれることがわかり。
nextPageTokenと言うものを取得して、
&pageToken=GNvdg86Q_9gCIJi49NOQ_9gC
みたいなパラメーターを、URIにつけてやると、差分だけ取得できます。
で、このパラメーターはNULLでもオッケーなので、初回起動時は、
&pageToken=＆key=xxxxx
みたいな感じで何もないと全件取得してくれます。

```csharp
UnityWebRequest connectChatrequest = UnityWebRequest.Get(chatURI);
yield return connectChatrequest.SendWebRequest();
var commentlogjson = (IDictionary)Json.Deserialize(connectChatrequest.downloadHandler.text);
//中略
nextPageTokenstr = (string)commentlogjson["nextPageToken"];
```

上記のようなことをして、トークン取得してます。
URI作るときは、以下のように作っています。
各、変数は、まぁ、書いてるので…。

```csharp
//チャットを取りに行く！！！
var chatURI = youtubeAPIbase + chatURIUp + chatId + pagetoken + nextPageTokenstr + chatURIbottom2 + apikey;
```

## 何度も取得する！
コルーチンでやっています。

```csharp
private IEnumerator GetComment()
{
   yield return new WaitForSeconds(5.0f);
   StartCoroutine(stopWait());
}
IEnumerator stopWait()
{
   yield return new WaitForSeconds(1f);
   StartCoroutine(GetComment());
}
```

上記のようにコルーチンをループさせてます。
正しいのかは知りません。

## UIに表示

prefabで、CanvasにTextを2つ貼り付けたものを用意します。
読み込めるフィールドを、用意して使います。

```csharp
[SerializeField]
private GameObject canvas;
//中略
//使うときに生成
GameObject cvn = Instantiate(canvas);
//パーッ寿司ながら中身取り出しにいく。
var citems = (IList)commentlogjson["items"];
var cslsd = (IDictionary)citems[i];
var clad = (IDictionary)cslsd["snippet"];
string message = (string)clad["displayMessage"];
//本文を取得
cvn.transform.Find("Description").gameObject.GetComponent<Text>().text = message;

var author = (IDictionary)cslsd["authorDetails"];
var dispName = (string)author["displayName"];
//名前をつける
cvn.transform.Find("Name").gameObject.GetComponent<Text>().text = dispName;
```

## 最後に
ここ二日ほど付き合ってくれた様々な方、ありがとうございました…。
さて、これを本体側に組み込んでいくか…。

**よかったら、Youtubeチャンネル登録お願いします。**
次回の放送はわかりませんが、チャットが取得できてるはず！
https://www.youtube.com/channel/UC8bcQkG7UiUlxb3wcqrs86Q

きっと！