2018年5月26日土曜日

YouTubeの生放送のチャットを使ってアンケートを行う


追記
youtubeライブに2021年ごろ?に便利なアンケート機能が追加されました。
追記終わり

さてタンバリンさんのツールが動いているのを見ました。
あれは過去のコメントも保存しているようですね。それどうだろうなぁ?
アンケート機能があるようですね。便利そうですね。
じゃあYouTubeの生放送のチャットを使ってアンケートを作ってみましょ


ニコニコのようなアンケート機能が視聴者には匿名ですしやりやすい思うのですが、
YouTubeにはそのような機能はないのでお試しに作ってみました。

検索すると、YouTubeでアンケートカードというものもあるらしいですが、
生放送には対応していないのかな?誰か作ってそうですけどねぇ




仕様は、
・投票項目を入力できる
・投票開始ボタンが押されてからのチャットのみを投票の対象とする
・アンケート中に投票数が確認できる
・IDで同じ人からの投票を弾く(途中で投票先の変更はダメ)
・投票終了時の結果を円グラフで表示する。
※スーパーチャットには今回未対応

苦労した所は、
ほんとはしちゃいけないだろうけど、PHPのコード部分に無限ループする部分があります。
ブラウザがChrome
かつ
sessionを使用した場合
スクリプトが終了していないのと次のページ遷移できない
という問題があるようで解決に時間がすごくかかりました。
(IE,FireFoxは停止しなくても遷移しました。)
解決策として正しいかわからないですが、
window.stop();
によってブラウザの中止ボタンの機能によりスクリプトを停止して解決してます。
そしたら今度は、IEがwindow.stop()関数に対応していないので使用ブラウザの判断をして
回避しています。
これでIE,Chrome,FireFoxで動いているのを確認しました。

3ページ分のコードをいつものフォルダ(C:\xampp\htdocs)に配置して
ブラウザ上で
http://localhost/vote1.php
とすれば使用できます。
いままでのやってきたことを組み合わせたようなもんです。


外見はこんな感じです。
見れば動きはなんとかくわかるでしょう。
1ページ目
例は雑に1文字ずつになっていましが、ちゃんと指定できます。
これは投票先を「あ」「い」「う」にしています。
本来は好きなお菓子の名前などちゃんとしたものを入れましょう。
2ページ目
投票項目が見えるようにしています。
この画像の動作では「あ」「い」「う」のワードを含むチャットを「投票した」
としていますが、
一致でもいいし、
投票項目のワードが長いと入力が大変だと思い「#1」~「#3」でもコードをいじればできます。(数字だけでもいいでしょう)
何も表示されないと寂しいのでチャットと現在の投票数を表示しています。
アンケートを打ち切る場合は投票終了ボタンを押します。

3ページ目
HighChartsを使いアンケート結果が円グラフで表示されます。
それぞれの結果を%と票数も全部載せです。
再びアンケートをやる場合は1ページに目から開始してください。




コードです。
1ページ目
vote1.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang = "ja">
  <head>
    <meta charset = "utf-8">
    <title>アンケート項目設定</title>
  </head>
<body>
<form action = "vote2.php" method="post">
<p>投票項目を入力してください<br>
<textarea name="keyword" cols="60" rows="6"></textarea>
<br>
<input type = "submit" value="投票開始" name="button1">
</form>
</body>
</html>


form部分だけなので注釈はありません。
php部分が1行もないのにファイル名がphpですね。。。


2ページ目
①にはAPIキー、②には今回は生放送中の動画のidを入れます。
https://www.youtube.com/watch?v=xxxxxxxxxxx
のxxxxxxxxxxxの文字列のことです。




vote2.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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
<!DOCTYPE html>
<html lang = "ja">
  <head>
    <meta charset = "utf-8">
    <title>アンケート中</title>
    <script language="javascript" type="text/javascript">
        function OnButtonClick()
        {
         var userAgent = window.navigator.userAgent.toLowerCase();
         console.log(userAgent);
        if(userAgent.indexOf('msie') != -1 ||
           userAgent.indexOf('trident') != -1 )
        {
          //IEは何もしない
        }
        else
        {
          window.stop();
        }
        document.frm.submit();
        return false;
       }
    </script>
  </head>
 
  <body>
        <style>
          t0 {color: blue; font-size:16px;}
        </style>
<form name="frm"  action = "vote3.php" method="post">
<p>投票項目     
<input type="button" onclick="OnButtonClick()" value="投票終了" name="button2">
</p>
</form>
 
<?php
session_start();
session_unset();
 
set_time_limit(60*60);//タイムアウト時間を60分に変更
 
if(isset($_POST["button1"]))
{
 
    if($_POST["keyword"] !=  "")
    {
        //投票項目の作成
        $str = str_replace(array("\r\n","\r","\n"), "\n", $_POST["keyword"]);
        $tmplist = explode("\n", $str);
        $length = count($tmplist);
        $keylist = array();
        for($i=0;$i<$length;$i++)
        {
            if($tmplist[$i] != "")
            {
                array_push($keylist,$tmplist[$i]);
            }
             
        }
        //投票数をクリア
        $cnt  = array_fill(0,count($keylist),0);
 
        //投票先を表示
        foreach($keylist as $t => $keyword)
        {
            $chkkey = "#".((int)$t+1) .": ". $keyword;
            print_r($chkkey);
            echo "<br>";
            }
        echo "<hr>";
 
        //セッションに保存する(初期化)
        $_SESSION["chart"] = 0;
        foreach($keylist as $k => $keyword)
        {
            $_SESSION["key".$k] = $keyword;
            $_SESSION["cnt".$k] = $cnt[$k];
 
        }
        $_SESSION["num"] = count($keylist);
    }
    else
    {
        print_r("投票項目をいれてください");
        goto end;
    }
}
else
{
    print_r("エラー");
    goto end;
}
 
$apikey = "①";
$videoId = "②";
 
 
 
//ChatIdの取得
$search_api="https://www.googleapis.com/youtube/v3/videos?part=liveStreamingDetails&id=" . $videoId ."&key=" . $apikey;
$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=" . $apikey;
 
 
$idlist = array();//回答者のIDリストのクリア
$next = "";
$url = $search_api;
$skip = 0;
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)
        {
 
            //初回は過去データなのでスキップ
            if($skip == 0)
            {
                $skip = 1;
                break;
            }
            $text = $data["snippet"]["displayMessage"];
            print_r($text);
            echo "<br>";
 
            $trgid = $data["authorDetails"]["channelId"];
            foreach($keylist as $t => $keyword)
            {
                //$keyword = "#".((int)$t+1);
                //if($text == $keyword)
                //if($text == "#".((int)$t+1))
                if(strpos($text,$keyword) === false){}else
                {
                     
                    if(in_array($trgid, $idlist) == false)//重複者チェック
                    {
                        array_push($idlist,$trgid);
                        $cnt[$t] = $cnt[$t] + 1;
                        //セッションの値を更新する
                        $_SESSION["cnt".$t] = $cnt[$t];
                    }
                    break;
                }
            }
        }
        $next = empty($search_json["nextPageToken"])? 0 : $search_json["nextPageToken"];
 
 
        $delay = $search_json["pollingIntervalMillis"]/1000.0;
        $time = microtime(true) - $time_start;
         
 
        echo "<t0>現在の投票数" .  count($idlist) . "</t0>";
        echo "<br>";
         
        //ブラウザに少しずつ表示するため必要
        ob_flush();
        flush();
 
        do{
            $time = microtime(true) - $time_start;
            usleep(100);//0.1ms待ち
        }while($time < $delay);
         
    }
}
 
end:
?>
 
  </body>
 
 
</html>


注釈として
7行目から22行目あたりがスクリプトを停止して3ページ目へ遷移させる部分です。
48行目から59行目あたりが1ページ目の入力を分解して投票項目を作成しています。
空の行を無視します。
94行から下は基本的にはチャット表示のコードの改造です。
130行目あたりは、APIが初回の入力はボタンを押したものの過去のチャットとなりますのでスキップする処理です。
142行から145行あたりは、一致するか、含むにするか、#nの一致にするかです。
好きなコードを選んでください。
148行目から156行目はまだ投票していないIDでの投票一致の場合、票として数えているところです。

3ページ目
vote3.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
<!DOCTYPE html>
<html lang = "ja">
  <head>
    <meta charset = "utf-8">
    <title>アンケート結果</title>
<script type="text/javascript"  src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script type="text/javascript"  src="https://code.highcharts.com/highcharts.js"></script>
<script type="text/javascript"  src="https://code.highcharts.com/modules/exporting.js"></script>
  </head>
  <body>
 
<form>
<div id="container" style="min-width: 310px; height: 400px; margin: 0 auto"></div>
</form>
 
 
<?php
session_start();
if($_SESSION["num"]  !="")
{
    $_SESSION["chart"]  = 1;
}
else
{
    print_r("エラー");
}
 
?>
 
 
 
<script>
var phpSession = <?php echo json_encode($_SESSION); ?>;
 
if(phpSession["chart"] ==1)
{
Highcharts.chart('container', {
    chart: {
        animation: Highcharts.svg, // don't animate in old IE
        plotBackgroundColor: null,
        plotBorderWidth: null,
        plotShadow: false,
        type: 'pie'
    },
    title: {
        text: 'アンケート結果'
    },
    tooltip: {
        pointFormat: '{series.name}: <b>{point.percentage:.1f}%</b>'
    },
    plotOptions: {
        pie: {
            allowPointSelect: true,
            cursor: 'pointer',
            dataLabels: {
                enabled: true,
                format: '<b>{point.name}</b>: {point.percentage:.1f} % :{point.y}票',
                style: {
                    color: (Highcharts.theme && Highcharts.theme.contrastTextColor) || 'black'
                }
            }
        }
    },
    series: [{
        name: 'vote',
        colorByPoint: true,
 
    data: (function () {
      var data = [],i;
        for( i=0;i<phpSession["num"];i+=1)
       {
        data.push({
               name:  "#"+(i+1)+": "+phpSession["key" + i],
               y: phpSession["cnt"+i]
        });
      }
      return data;
    }())
 
 
 
    }]
});
}
</script>
 
  </body>
 
</html>



注釈として
Highchartsの円グラフの例 を改造し、2ページ目の結果を入力としています。
33行目でPHPから_SESSIONのデータを受け取り、
それを68行目から78行目で使用しています。



ちゃんと勉強せず、行き当たりばったりで作っているから苦労する。
いままでのコードもformやhead、bodyの対応がよくなかったようだ。
WEB系の基本勉強してないものなぁ。

2018年5月18日金曜日

マウスのチャタリングを確認する

 マウスが壊れてシングルクリックがダブルクリックになったり、ドラッグ中に離した判定になったりすることがあるじゃないですか。でも指が疲れているせいか本当に壊れいてるかはっきりなしないことがありますよね。


そこで Highcharts(グラフ)を使って可視化してみました。JavaScriptを使いマウスイベントをグラフに反映します。いつも通りやりかたは雑いです。
画像の赤い部分でマウス操作するとグラフに反映されます。
コードを少し変更すればクリックとダブルクリックが確認できます。

押してしばらくして離すを繰り返した結果が以下です。
[追記:ためしにブログ上においてみました]

 マウスダウン(押す)すると青の点が1になり、マウスアップ(離す)と黒の点が2になります。
上記の図だと離したときにチャタリングおこって意図していない結果になっています。

左ボタンを確認したら、


マウスのプロパティを左きき用にして右ボタンを確認してもいいでしょう。
(変更せずとも反応しますが、右クリックだとメニューが出るのでイベントを拾えているか不安…)
下記がコードです。htmlファイルとして保存してブラウザで表示すれば使えます。
外部のリソース(Highcharts)を使っているけどいいんかな?


ClickChart.html
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
<!DOCTYPE html>
<html lang = "ja">
  <head>
    <meta charset = "utf-8">
    <title>マウスチェック</title>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://code.highcharts.com/highcharts.js"></script>
<script src="https://code.highcharts.com/modules/exporting.js"></script>
     
<div id="container" style="min-width: 310px; height: 400px; margin: 0 auto"></div>
<div id="text-button" onmousedown="fMouseDown(this)" onmouseup="fMouseUp(this)" style="background-color: red;"><p id="text">クリック</p></div>
  </head>
  <body>
   
<script>
var input_x0=Array(32);
var input_x1=Array(32);
var x0_rp = 0;
var x0_wp = 0;
var x1_rp = 0;
var x1_wp = 0;
 
document.getElementById("text-button").fMouseDown = function() {
    var x = (new Date()).getTime();
    input_x0[(x0_wp)] = x;
    x0_wp = (x0_wp +1)%32;
}
 
document.getElementById("text-button").fMouseUp = function() {
    var x = (new Date()).getTime();
    input_x1[(x1_wp)] = x;
    x1_wp = (x1_wp +1)%32;
}
 
/*
document.getElementById("text-button").onclick = function() {
    var x = (new Date()).getTime();
    input_x0[(x0_wp)] = x;
    x0_wp = (x0_wp +1)%32;
}
 
document.getElementById("text-button").ondblclick = function() {
    var x = (new Date()).getTime();
    input_x1[(x1_wp)] = x;
    x1_wp = (x1_wp +1)%32;
}
*/
 
Highcharts.setOptions({
  global: {
    useUTC: false
  },
    lang: {
        numericSymbols: null//軸のkキロ表示をやめる
    }
});
 
Highcharts.chart(&#039;container', {
  chart: {
   type: &#039;line',
    animation: Highcharts.svg, // don't animate in old IE
    marginRight: 100,//右の軸が隠れるので適切な値にする
    events: {
      load: function () {
        var series0 = this.series[0];
        var series1 = this.series[1];
 
        setInterval(function () {
        var x = (new Date()).getTime(); // current time
//series0に対する処理
        var p = x0_wp;
        while (x0_wp != x0_rp) {
         series0.addPoint([input_x0[x0_rp], 1], true, true);
         series0.addPoint([input_x0[x0_rp]+1, 0], true, true);
          
         series1.addPoint([input_x0[x0_rp], 0], true, true);
         series1.addPoint([input_x0[x0_rp]+1, 0], true, true);
         x0_rp = (x0_rp +1)%32;
        }
//series1に対する処理
        p = x1_wp;
        while (p != x1_rp) {
         series1.addPoint([input_x1[x1_rp], 2], true, true);
         series1.addPoint([input_x1[x1_rp]+1, 0], true, true);
          
         series0.addPoint([input_x0[x1_rp], 0], true, true);
         series0.addPoint([input_x0[x1_rp]+1, 0], true, true);
         x1_rp = (x1_rp +1)%32;
        }
       }, 500);//0.5秒のインターバルタイマ
      }
    }
     
  },
  title: {
    text: &#039;チャタリングチェック(赤い部分をクリックでチェック)'
  },
  plotOptions: {
        series: {
         marker: {
                enabledThreshold: 0//マークが見えるように
            }
        }
    },
  xAxis: {
    type: &#039;datetime',
    tickPixelInterval: 150
  },
  yAxis: [
      {
          title: {
              text: &#039;クリック'
          },
          plotLines: [{
              value: 0,
              width: 1,
              color: &#039;#FF8080'
          }],
          allowDecimals: false    //軸を整数表示
      },
  ],
  tooltip: {
    formatter: function () {
      return &#039;<b>' + this.series.name + '</b><br/>' +
        Highcharts.dateFormat(&#039;%Y-%m-%d %H:%M:%S', this.x) + '<br/>' + this.y;
    }
  },
  legend: {
    enabled: true    //凡例表示
  },
  exporting: {
    enabled: false
  },
   
  series: [{
    name: &#039;マウスダウン',
    yAxis: 0,
    data: (function () {
      var data = [],
        time = (new Date()).getTime(),
        i;
 
      for (i = -19; i <= 0; i += 1) {
        data.push({
          x: time + i * 500,
          y: 0
        });
      }
      return data;
    }())
  },
  {
    name: &#039;マウスアップ',
    yAxis: 0,
    data: (function () {
      var data = [],
        time = (new Date()).getTime(),
        i;
 
      for (i = -19; i <= 0; i += 1) {
        data.push({
          x: time + i * 500,
          y: 0
        });
      }
      return data;
    }())
   },
   ]
});
 
 
</script>
 
 
  </body>
</html>


注釈として、
マウスのイベントとタイマーのイベントが重なっても大丈夫なようにリングバッファでイベントの時間を格納しています。
イベントのタイミングでグラフに直接反映できればいいのですが、JavaScriptよくわからないのであきらめました。
23行目から33行目をコメントアウトして35行目から46行目を反映させればクリックとダブルクリックのイベントに変更できます。
70行目から88行目、ダウンもアップも同じ数が発生するようにしています。そうしないとX軸は全部のポイントを表示するようにしているためその対策です。
※アップとダウンはペアなので問題ないが、クリックとダブルクリックは困る。
またX軸の時間は可変なので時間がながい細かい部分がみづらくなります。対策はあきらめました。
101行目 値ありから0にすぐ戻しているため、デフォルト設定だとマークが近すぎでません。その対策で0を設定しています。
ちなみに今回は線ありでやっていますが、線の幅を0にするとマークだけになります。


ブログで直接実行もできるけどやめとこ。
マウスの調子が悪いので思いつきで作ってみました。明らかに壊れてるわ

2018年5月16日水曜日

片鱗が見えたかなぁ

昨日のタンバリンさんのyoutubeコメント拾いマシーン(名前前回と違うじゃん)の部分を見て思ったこと。

更新がうまくっている部分を見ていると
2秒で4コメントぐらい追加されているように見える。
 ・一度API投げて保存したコメントを吐き出しているとしたら、人気バーチャルユーチューバーさんの場合ピークでは170コメント/9.5秒(実測)なので処理量としては追いつかない。
 ・停止したときにアイコンは違うけど名前とコメントが同じ人が表示されているからなんかバグっている。
ということがわかりますね。

 2秒毎にAPIを投げているということはなさそう。なぜならpollingIntervalMillisを無視することになるのでwarningを出てコメントを取得できない。保存したコメントを少しずつ出しているのでしょう。

 発言から動作テスト時は動いていたみたいですが限定公開でリスナーなしでやっていたのかもしれませんね。その場合はコメントが多い生放送してるときと差があります。
 pollingIntervalMillisは5秒以下で短いですし、最初のAPIでだいたい過去のコメント75ぐらいが一気に取得してしまいます。
 放送を見る限りpollingIntervalMillisが動作テスト時と違い長すぎて止まったと勘違いしたってことはなさそうです。
  適切なpollingIntervalMillisをwaitしてAPIを使用しているのか、それとコメントを保存している部分に不具合があるような気がしますねぇ。


Unityは見栄えがいいなぁ。アイコンも丸くなっているし。フィルタしてキーワードのだけ表示じゃなくて全部保存してから検索って形なのかしら 。

テスト時と実際の時の差異によるバグってなんかわくわくしますね。

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言語の人なので。