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系の基本勉強してないものなぁ。

4 件のコメント:

  1. ユーチューブのライブ配信でのアンケートとてもいい機能だと思います。

    私も眠たいお粥さんのブログを元に作ってみようと思います。

    今後も拝見させていただきますのでよろしくお願いお願いします。

    返信削除
    返信
    1. 何かしらの参考になれたらうれしいです。 ( ´ ▽ ` )ノ

      削除
  2. vote2の129にエラーが出るんですが原因がわかりません;;
    Parse error: syntax error, unexpected ‘ ‘ (T_STRING)

    返信削除
  3. こちらのミスですね・・・
    vote2の128と129行のあたりに全角のスペースがありました。
    これだとエラーがでます。
    削除すればいけるはずです。

    返信削除