最初に
ニコ生とYoutubeLiveで同時配信をしていて、ニコ生しかコメントを取得できていなかったため、YoutubeLiveからチャットを取得するプログラムを組みました。
一応下のコードで、YouTubeLiveからチャットが取得できます。
あと、その下の方で、何をしたのかちょっとずつ解説を書きます。
間違ってたらご指摘下さい。
あと、こんなクソ汚いコード使われたくないので、ちゃんとしたの、だれかunitypackageで、だしてくりー。。。
一応プロジェクト置いておくね。
https://github.com/platoronical/youtubelivecommentReciever
※色々あって、プロジェクトが変わりました。
コード!
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
構造!
今回はコルーチンの塊です。
private IEnumerator GetYoutubeAPI()
{
//Youtubeへ接続しに行ってます
}
private IEnumerator GetChatId()
{
//videoIdを基に、chatIdを探しに行っています。
}
private IEnumerator GetComment()
{
//chatIdを基に、コメントを取得します。
}
IEnumerator stopWait()
{
// StartCoroutine(GetComment()); で、コメントを再取得しにいっています。
}
JSON!
jsonのパースは、いろいろあってきれいな書き方が、VS2017から.NET4.6から出来るようです。
var chatJsonObj = JsonConvert.DeserializeObject<dynamic>(jsontext);
string videoId2 = chatJsonObj.items[0].id.videoId;
Debug.Log("videoID2017 : " + videoId2);
上記のようなコードで取れるようです。きれいでいいなー。
今回はそれを知らなかったので、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"];
さっきのコードと同じことをしてます。
今回はこのような形で、展開していくことにしました。
色々つまづきポイントはあったんですが、最後の最後に整数値へのキャストで、躓いたので紹介します。
int commentcount = int.Parse(pageinfo["totalResults"].ToString());
もうね、キャストの仕方がありすぎて、どうしたものかと思った。
YoutubeAPI!
おまけ程度だけど、紹介します。
多分一番意味分かんないのは、uriを作っているところで、もっとスマートに出来るでしょーって感じなんだけど、それは置いておいて。
pageTokenだと思います。
コメント差分が欲しかったため、どうしたものかと思って部屋の中で土下座していると、API側で教えてくれることがわかり。
nextPageTokenと言うものを取得して、
&pageToken=GNvdg86Q_9gCIJi49NOQ_9gC
みたいなパラメーターを、URIにつけてやると、差分だけ取得できます。
で、このパラメーターはNULLでもオッケーなので、初回起動時は、
&pageToken=&key=xxxxx
みたいな感じで何もないと全件取得してくれます。
UnityWebRequest connectChatrequest = UnityWebRequest.Get(chatURI);
yield return connectChatrequest.SendWebRequest();
var commentlogjson = (IDictionary)Json.Deserialize(connectChatrequest.downloadHandler.text);
//中略
nextPageTokenstr = (string)commentlogjson["nextPageToken"];
上記のようなことをして、トークン取得してます。
URI作るときは、以下のように作っています。
各、変数は、まぁ、書いてるので…。
//チャットを取りに行く!!!
var chatURI = youtubeAPIbase + chatURIUp + chatId + pagetoken + nextPageTokenstr + chatURIbottom2 + apikey;
何度も取得する!
コルーチンでやっています。
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つ貼り付けたものを用意します。
読み込めるフィールドを、用意して使います。
[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
きっと!