聴衆者の力を借りてイベントを盛り上げよう


~~#デモ(2018/12/28追記)~~
~~IBM Cloud Kubernetes Serviceに、今回作成したアプリケーション(dockerコンテナ)をデプロイ・公開してみました。(無料枠利用の制約で、2019年1月末までの期間限定公開)~~

~~**Live Tweeeets Sender(つぶやき送信画面)**~~
~~http://live-tweeeets-lt.tk~~

~~**Live Tweeeets Viewer(つぶやき表示画面)**~~
~~http://live-tweeeets-lt-view.tk~~

#はじめに
プレゼンテーションやパネルディスカッション、ライトニングトークなどのイベントで、聴衆者のつぶやきをスクリーンに表示しているイベントがありますが、結構盛り上がりますよね。聴衆者のリアルタイムなつぶやきが面白かったり、発表者の意見よりも聴衆者のつぶやきの方に共感したりして。
そんな聴衆者参加型のイベントをいつでも気軽にできたらいいなと思い、非常に~~質素な~~シンプルな聴衆者のつぶやきを表示するアプリを作ってみました。

#アプリケーション(Live Tweeeets)の概要
つぶやき内容が表示される画面をスクリーンに表示しておき、聴衆者がスマホやPCのブラウザを利用してつぶやきを入力・送信すると、聴衆者のつぶやきがスクリーンに表示されます(15秒間)。

**利用イメージ**
![image.png](https://qiita-image-store.s3.amazonaws.com/0/142541/a1b4cc21-4deb-e0fc-6ae3-6393d0f1295a.png)

**Live Tweeeets Sender(つぶやき送信画面)**
![image.png](https://qiita-image-store.s3.amazonaws.com/0/142541/5ca63754-c25b-73ad-263d-c9e2a4260b35.png)

**Live Tweeeets Viewer(つぶやき表示画面)**
![image.png](https://qiita-image-store.s3.amazonaws.com/0/142541/47eb9967-2d65-3e17-a4f7-0f218ae787c9.png)

#アーキテクチャ概要
Docker Hub上のNode-Redコンテナをベースにして、今回のLive Tweeeetsアプリケーションを作りました。作成したアプリケーションはDockerコンテナ化し、Docker Hubに公開しています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/142541/f236640a-a4c6-2573-da2d-7e7b8995aeb9.png)

Docker Hubのリンクです。
https://hub.docker.com/r/has2y/live-tweeeets/

作成したNode-Redフローです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/142541/065066d3-da57-a7fa-bf32-7a24f3a68e4d.png)

```
[{"id":"9567d52f.9d38d8","type":"http in","z":"2a5fa454.4d923c","name":"","url":"/lt","method":"get","swaggerDoc":"","x":90,"y":180,"wires":[["e36cbeee.f3191"]]},{"id":"e36cbeee.f3191","type":"template","z":"2a5fa454.4d923c","name":"live tweeeets client","field":"payload","fieldType":"msg","syntax":"mustache","template":"<head>\n    <title>Live Tweeeets</title>\n\n    <meta name=\"viewport\" content=\"initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no\" />\n</head>\n\n<body>\n    <div class=\"content\">\n        <div id=\"title\"><br>Live Tweeeets</div>\n        <br>\n        <input type=\"button\" id=\"send_btn\" value=\"Send your tweet\" onclick=\"sendMessage()\">\n        <br>\n        <input type=\"text\" id=\"message\" placeholder=\"Please input any tweet(^^)/\" />\n        <div class=\"guide\">\n          <br>[Usage]\n          <br>After inputting your tweet and pressing the \"Send your tweet\" button, your tweet will be displayed for 15 seconds.\n        </div>\n    </div>\n</body>\n\n<script type=\"text/javascript\">\n    var wsUri = \"ws://{{req.headers.host}}/ws/lt\";\n    var ws;\n    ws = new WebSocket(wsUri);\n    ws.onclose = onClose;\n\n    function onClose(e) {\n//        alert(\"WebSocketの接続が切れました。再接続します。\");\n        ws = null;\n        ws = new WebSocket(wsUri);\n        ws.onclose = onClose;\n    }\n\n    function sendMessage() {\n        var message = document.getElementById('message');\n\n        var payload = {\n            message: message.value\n        };\n\n        ws.send(JSON.stringify(payload));\n        \n        message.value = \"\";\n    };\n\n</script>\n\n<style type=\"text/css\">\n* {\n        font-family: 'Quicksand', sans-serif;\n        font-size: 24px;\n    }\n    html,\n    body {\n        margin: 0;\n        padding: 0;\n        height: 100%;\n        background-color: #FFFFFF;\n        margin : 0 10 0 10px ; \n    }\n    #title {\n        text-align: center;\n        font-family: 'Quicksand', sans-serif;\n        color: #107FC9;\n    }\n    #send_btn {\n        width: 100%;\n        right: 0;\n        margin: 0;\n        border: 0;\n        color: #fff;\n        -webkit-appearance: none;\n        border-radius: 0;\n        background-color: #79ceed;\n        display: inline-block;\n    }\n    #message {\n        background-color: #fff;\n        width: 100%;\n    }\n    .guide {\n        color: #152935;\n    }\n    .content {\n        padding-top: 4px;\n        position: relative;\n        width: 100%;\n    }\n}\n</style>","x":310,"y":180,"wires":[["419d88a1.db7238"]]},{"id":"419d88a1.db7238","type":"http response","z":"2a5fa454.4d923c","name":"","x":510,"y":180,"wires":[]},{"id":"c2f51532.106d68","type":"http in","z":"2a5fa454.4d923c","name":"","url":"/lt/view","method":"get","swaggerDoc":"","x":105,"y":260,"wires":[["654cbe30.6743f"]]},{"id":"654cbe30.6743f","type":"template","z":"2a5fa454.4d923c","name":"live tweeeets viewer","field":"payload","fieldType":"msg","syntax":"mustache","template":"<html>\n\n<head>\n    <meta name=\"viewport\" content=\"width=320, initial-scale=1\">\n    <title>Live Tweeeets</title>\n</head>\n\n<body>\n</body>\n</html>\n\n<script type=\"text/javascript\" src=\"//code.jquery.com/jquery-2.0.3.min.js\"></script>\n<script type=\"text/javascript\">\n    var wsUri = \"ws://{{req.headers.host}}/ws/lt\"\n    var ws;\n    ws = new WebSocket(wsUri);\n    ws.onclose = onClose;\n    ws.onmessage = onMessage;\n    var count = 0;\n\n    function onClose(e) {\n//        alert(\"WebSocketの接続が切れました。再接続します。\");\n        ws = null;\n        ws = new WebSocket(wsUri);\n        ws.onclose = onClose;\n        ws.onmessage = onMessage;\n    }\n\n    function onMessage (ev) {\n        count = count + 1;\n        var divid = count;\n\n        var payload = JSON.parse(ev.data);\n        if (payload.message != \"\") {\n            var left = Math.floor(Math.random() * (window.innerWidth - 500 - 50) + 50);\n            var top = Math.floor(Math.random() * (window.innerHeight - 150 - 50) + 50);\n            var div = \"<div id=\\\"\" + divid + \"\\\" class=\\\"tweeeet\\\" style=\\\"position:absolute; top:\" + top + \"px; left:\" + left + \"px;\\\">\" + escapeHtml(payload.message) + \"</div>\";\n            $('body').append(div);\n\n            setTimeout(function () {\n                $('#' + divid).remove();\n            }, 15000);\n\n        }\n        \n    }\n    \n    function escapeHtml(str) {\n        str = str.replace(/&/g, '&amp;');\n        str = str.replace(/</g, '&lt;');\n        str = str.replace(/>/g, '&gt;');\n        str = str.replace(/\"/g, '&quot;');\n        str = str.replace(/'/g, '&#39;');\n        return str;\n    }\n</script>\n<style type=\"text/css\">\n    body {\n        background-color: black;\n        background-size: contain100% 100%;\n        overflow: hidden;\n    }\n    .tweeeet {\n        font-size: 4vw;\n        position: relative;\n        display: inline-block;\n        padding: 0 30px;\n        width: auto;\n        min-width: 230px;\n        height: 80px;\n        line-height: 68px;\n        text-align: center;\n        z-index: 0;\n        color: #F6F6F6;\n    }\n</style>\n\n","x":320,"y":260,"wires":[["497bdf36.cd9d2"]]},{"id":"497bdf36.cd9d2","type":"http response","z":"2a5fa454.4d923c","name":"","x":510,"y":260,"wires":[]},{"id":"74a8ede3.728ac4","type":"websocket in","z":"2a5fa454.4d923c","name":"","server":"1edeb26e.675dfe","client":"","x":100,"y":80,"wires":[["484639c5.9ea718"]]},{"id":"484639c5.9ea718","type":"function","z":"2a5fa454.4d923c","name":"send msg","func":"delete msg._session;\nreturn msg;\n\n","outputs":1,"noerr":0,"x":300,"y":80,"wires":[["4591dcda.6c1244"]]},{"id":"4591dcda.6c1244","type":"websocket out","z":"2a5fa454.4d923c","name":"","server":"1edeb26e.675dfe","client":"","x":500,"y":80,"wires":[]},{"id":"1edeb26e.675dfe","type":"websocket-listener","z":"","path":"/ws/lt","wholemsg":"false"}]
```


#アプリケーションの利用準備
1. Dockerコンテナの実行環境を準備する ※この記事では省略します

2. 以下Dockerコマンドで、Dockerコンテナを実行する(だけ)。

`docker run -it -p 80:1880 has2y/live-tweeeets`



#アプリケーションの使い方
1．PCブラウザで以下URLにアクセスして、つぶやき表示画面を表示する

`http://server-ip-address/lt/view`

2．スマホ or PCブラウザで以下URLにアクセスして、つぶやきを入力・送信する

`http://server-ip-address/lt`

![image.png](https://qiita-image-store.s3.amazonaws.com/0/142541/709af8b8-0949-2573-b5cb-013ebe8396d9.png)

3．つぶやき表示画面に、聴衆者のつぶやきが表示される
![image.png](https://qiita-image-store.s3.amazonaws.com/0/142541/47eb9967-2d65-3e17-a4f7-0f218ae787c9.png)

#まとめ

このアプリケーションは、Cloud上のDockerコンテナの実行環境があれば、5分で利用できるようになります。
今、あなたが企画しているイベント、聴衆者の力を借りてもっと盛り上げてみませんか？