2018年5月11日金曜日

YouTubeチャットで視聴者参加のためのツールを考える

5/8の某生放送でバーチャルユーチューバーのマネージャーのタンバリンさんが言っていたYouTubeのコメント欄で視聴者参加できるWPP1システム?というやつを想像で作ってみる。
PHPじゃなくてもっと立派なものだろうと思うけどね。
PHPほんと動的につかいづらい
っていうか、上位のバーチャルユーチューバーってチャット欄が人数に対して、速すぎて読めなくない


参加者を募る(キーワードを決めてそれをコメントした人のみを抽出)

参加者のコメントのみを表示する
っていう機能があればよさそう。

初期画面


モード1:決めたキーワードをコメントした人だけが表示される(今回のコードではキーワードを含むにしている。本来は一致だろう)
例はキーワードの所に「あ」を入力しkeyボタンを押した場合。
「あ」を含むコメントした人だけが表示されている。


 アイコン、ID(青字)、名前(赤字)、コメント(黒字)の順に表示されるので、青字のIDをコピーしてIDに入力しIDボタンを押す。


モード2:対象IDの方のみチャット が表示される
対象ボタンを押した先に遷移します。(入力欄が空白の場合は初期画面に遷移)

 コードは以下の通り。①にはAPIキー、②には生放送中の動画のidを入れます。
https://www.youtube.com/watch?v=xxxxxxxxxxx
のxxxxxxxxxxxの文字列のことです。
今回はアイコンも表示したいのでブラウザで無理やり表示してます。
ブラウザで「http://localhost/modoki.php」と入力。



modoki.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
<!DOCTYPE html>
<html>
    <head>
        <title>もどき</title>
    </head>
    <body>
        <style>
          t0 {color: blue; font-size:16px;}
          t1 {color: red; font-size:32px;}
          t2 {color: black; font-size:32px;clear: both;}
         img.gazo{
               width: 44px;
               height: auto;
               float: left;
               }
        </style>
         
<form action = "modoki.php" method="post">
<p>キーワード:<input type="keyword" name="keyword">
<input type = "submit" value="key" name="button1"></p>
<p>ID:<input type="userid" name="userid">
<input type = "submit" value="ID" name="button2"></p>
<?php
set_time_limit(60*60);//タイムアウト時間を60分に変更
$mode = 0;
if(isset($_POST["button1"]))
{
    if($_POST["keyword"] !=  "")
    {
        $mode = 1;
        $keyword = $_POST["keyword"];
        echo "キーワード:" . $keyword;
    }
}
else if (isset($_POST["button2"]))
{
    if($_POST["userid"] != "")
    {
        $mode = 2;
        $chkid = $_POST['userid'];
        echo "対象ID:" . $chkid;
    }
}
$key = "①";
$videoId = "②";
//ChatIdの取得
$search_api="https://www.googleapis.com/youtube/v3/videos?part=liveStreamingDetails&id=" . $videoId ."&key=" . $key;
$search_contents = file_get_contents($search_api);
$search_json = json_decode($search_contents,true);
$ChatId = $search_json["items"][0]["liveStreamingDetails"]["activeLiveChatId"];
//ChatIdからliveChatにアクセス用のURL
$search_api="https://www.googleapis.com/youtube/v3/liveChat/messages?part=snippet,authorDetails&liveChatId=" . $ChatId ."&key=" . $key;
$next = "";
$url = $search_api;
while(1)
{
    if($next)
    {
        $url = $search_api . "&pageToken=" . $next;
    }
     
    if($search_contents = @file_get_contents($url))
    {
        $search_json = json_decode($search_contents,true);
        $time_start = microtime(true);
        foreach($search_json["items"] as $k => $data)
        {
            $exsit = 0;
            $text = $data["snippet"]["textMessageDetails"]["messageText"];
            $trgid = $data["authorDetails"]["channelId"];
            if($mode == 1)
            {
                if(strpos($text,$keyword) === false)
                {
                    //見つからない場合なにもしない
                }
                else
                {
                    $exsit = 1;//指定文字列が含まれていた場合
                }
            }
             
            if($mode == 2)
            {
                if($trgid==$chkid)
                {
                    $exsit = 1;
                }
            }
            if($exsit == 1)
            {
                echo "<p>";
                echo "<t0>" .  $trgid . "</t0>";
                $image = $data["authorDetails"]["profileImageUrl"];
                echo "<img src=$image  class=gazo>";
                echo "<t1>" . $data["authorDetails"]["displayName"] . "</t1>";
                echo "<t2>";
                $text = $data["snippet"]["textMessageDetails"]["messageText"];
                echo $text;
                echo "<t2>";
                echo "</p>";
            }
        }
        $next = empty($search_json["nextPageToken"])? 0 : $search_json["nextPageToken"];
        //ブラウザに少しずつ表示するため必要
        ob_flush();
        flush();
        $delay = $search_json["pollingIntervalMillis"]/1000.0;
        $time = microtime(true) - $time_start;
         
        do{
            $time = microtime(true) - $time_start;
            usleep(100);//0.1ms待ち
        }while($time < $delay);
    }
}
?>
     </body>
</html>


注釈としては、
7行目から16行目で文字の装飾と画像のサイズを指定しています。
サイズを指定してそれっぽく表示 。うまくできなくてえらい時間がかかった。
24行目。PHPの処理は無限ループでまわしているため処理が終了しません。
ブラウザ上の場合は30秒でタイムアウトの処理により停止するようなので60分に延長しています。
26行目から43行目(組込み系のコードだと空のelse書くものだけど忘れた)がボタンを押した場合の処理です。
ボタンが押された場合、どのボタンが押されたかと二つのテキスト入力の値が$_POSTに格納された状態で再度同じPHPが呼び出されます。
( 18行目で自身を呼び出すように設定しています。)
62行目 file_get_contenstsでwarninngが返った時の対策に@をつけてみましたが、
これで対策うまくいってるのかなぁ
73行目 キーワードを含む場合のチェック。キーワードと一致に変更するなら両者を==で比較する
85行目 対象IDかをチェック
106,107行目 ブラウザ上の場合、通常PHPの処理すべてが終了しないと表示されないが、無限ループ(55行目)なので途中の状態を表示するための関数呼び出しです。
92行目から101行目がコメントとアイコンの表示部分。IDはモード2で出力しなくていいかも。
追記:2018/5/12
スーパーチャットには$data["snippet"]["textMessageDetails"]["messageText"]は存在しない。本当は対応が必要。共通としては$data["snippet"]["displayMessage"]とすると楽だと思うがスーパーチャットの場合「¥1,000 from 名前 : ”コメント”」のような構造なのでキーワードの一致で判定しにくいので場合わけが必要だろう。
[snippet][type] でsuperChatEventならば[snippet] [superChatDetails][userComment]を$text。textMessageEventならば["snippet"]["textMessageDetails"]["messageText"]で一致判定がOK。あと、98行目は重複しているのでいらない。
思えば取り込み後に削除されたコメントも保持してしまう。既に削除されているならば上記の判定で無視できるはずだ。その辺は運用で対応。

やりたいことはこんな感じだろうけど、右クリックで対象者選択とかもっと使いやすいのだろうなぁ。配布したりするのだろうか。
PHPって不向きだと思う。
本当はモード1にID表示なくして何番の人か指定でIDを保存していた配列から出力したかったけどPHPの処理が無限ループで完了しないからPOSTに渡せなそうなのであきらめたり、レイアウト関係がむずかしくてあきらめたりと妥協しまくって。このコード量でそれでも1日かかった。まぁ自分C言語の人なので。

0 件のコメント:

コメントを投稿