KMCM(1)
京大マイコンクラブはC91 1日目(木) 西ほ40-b に配置されました!部員の制作物の入ったCDや部誌を頒布します! pic.twitter.com/0Pgye7mWrF
— 京大マイコンクラブ (@KMC_JP) 2016年10月29日
はじめに
これは KMCアドベントカレンダー2016 1日目の記事です。
こんにちは
KMC2回生で副広報*1の id:utgwkk です。KMC では utgw という ID で活動しています。
さて、アドベントカレンダー1日目ということなので、ゆるーく進行していきましょう。
#nowplaying
みなさんは、#nowplaying をシェアしていますか?
🎶 ハイファイ☆デイズ (M@STER VERSION) by 佐々木千枝 (今井麻夏), 櫻井桃華 (照井春佳), 市原仁奈 (久野美咲), 龍崎薫 (春瀬なつみ), 赤城みりあ (黒沢ともよ) #nowplaying
— うたがわきき (@utgwkk) 2016年11月18日
#nowplaying とは、このように自分が今聴いている楽曲をシェアすることを表しています。起源は不明ですが、おそらくリアルタイムな SNS 文化の浸透とともに広まったものと推測されます。
Subsonic
Subsonic は、音楽をストリーミング配信するサーバーです。自分のサーバーに簡単に設置でき、インターネット一つでどこにでも音楽を持って行くことができます。また、API も用意されているので、必要なデータを簡単に取得することができます。
Subsonic API を叩く
以前のバージョンでは username と password をクエリに入れて叩いていたのですが、現在は username と salt と token (password
の後に salt
をつなげた文字列の md5sum) で叩くことができます。
どの API を叩くのにも、次のクエリが必須とされています。
名前 | 説明 |
---|---|
u | ユーザー名 |
s | md5sum を取る際にパスワードに付ける salt |
t | トークン。md5sum(password の後に salt をつなげた文字列) |
v | Subsonic REST API のバージョン。通常は 1.14.0 など、最新バージョンでよい |
c | API を叩くアプリの識別子 |
次のクエリは必須ではありませんが、指定しないとデータが XML で返ってきます。
名前 | 説明 |
---|---|
f | データのフォーマット。xml json jsonp のいずれかが指定できる。普通は json でよさそう |
これらに従って、http://example.com/subsonic/rest/ping.view?u=utgwkk&t=13fbc5dddddddaaaa&s=aaabb&v=1.14.0&f=json&c=myapp
などの URL に対して GET リクエストを送ることによって API を叩くことができます。
より詳しい情報は、公式の API ドキュメントを閲覧してください。
SSE (Server-Sent Events)
SSE (Server-Sent Events) とは、ざっくり言うとサーバーからクライアントにデータを push する方式の1つです。詳しくは MDN の記事を参照してください。
event: ping data: {"name": "utgwkk", "message": "Yo"} data: {"name": "kkutgw", "message": "Yo"} ...
こういった行ベースのフォーマットで、手軽にデータを push することができます。
普通の HTTP ストリーミングとは違い、
EventSource
のインタフェースに沿って直感的にクライアントを実装できる- ブラウザで動く
event
の種類を付けることができる
などの利点があります。
SSE で配信する
MDN の記事に、サーバーサイドの PHP 実装の例があります。要するに、1つのリクエストに対してレスポンスを返してそのまま終了ではなく、レスポンスを生かしたまま次々にデータを流していく、ということをすればよいです。
単純に CGI 等でやる場合はこのようにすればよいですが、最近は WAF を用いて web ページを作ることが多いので、それぞれのフレームワークのやり方に従っていく必要があります。
たとえば、Python 製の Web アプリケーションフレームワークである Flask では、ジェネレーターを用いてこのようなデータの push を実現できるように実装されています。
次の例では、GET /stream
すると1秒おきに ping
イベントを push します。
def do_stream(): while True: yield 'event: ping\n\n' time.sleep(1) @app.route('/stream') def streaming(): return Response(do_stream(), mimetype="text/event-stream") if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, threaded=True)
このようにジェネレーターで直感的に書けるのはよいですね。
SSE で流れてくるものを見る
さて、手元に UNIX 端末がおありの方は、
$ curl https://utgw.net/nowplaying/stream
を実行してみてください。結果はたいてい次のようになるかと思われます。
$ curl https://utgw.net/nowplaying/stream event: ping data: {"track": 2, "title": "Nebula Sky", "created": "2016-11-15T08:37:27.000Z", "type": "music", "playCount": 39, "albumId": "70", "duration": 280, "transcodedSuffix": "mp3", "discNumber": 1, "transcodedContentType": "audio/mpeg", "id": "1010", "isDir": false, "genre": "Soundtrack", "year": 2015, "album": "THE IDOLM@STER CINDERELLA GIRLS ANIMATION PROJECT 2nd Season 05", "suffix": "m4a", "username": "utgw", "parent": "735", "contentType": "audio/mp4", "playerId": 4, "minutesAgo": 3, "path": "utgw/THE IDOLM@STER CINDERELLA GIRLS/THE IDOLM@STER CINDERELLA GIRLS ANIMATIO/02 Nebula Sky.m4a", "size": 9146912, "artist": "\u30a2\u30ca\u30b9\u30bf\u30b7\u30a2 (\u4e0a\u5742\u3059\u307f\u308c)", "bitRate": 256} : no new data : no new data ...(しばらくして) : no new data data: {"track": 3, "title": "PANDEMIC ALONE", "created": "2016-10-26T03:35:13.000Z", "type": "music", "playCount": 86, "albumId": "63", "duration": 243, "transcodedSuffix": "mp3", "discNumber": 1, "transcodedContentType": "audio/mpeg", "id": "978", "isDir": false, "genre": "Soundtrack", "year": 2016, "album": "THE IDOLM@STER CINDERELLA GIRLS STARLIGHT MASTER 06", "suffix": "m4a", "username": "utgw", "parent": "976", "contentType": "audio/mp4", "playerId": 4, "minutesAgo": 0, "path": "utgw/THE IDOLM@STER CINDERELLA GIRLS/THE IDOLM@STER CINDERELLA GIRLS STARLIGHT MASTER/06/03 PANDEMIC ALONE.m4a", "size": 7951890, "artist": "\u661f\u8f1d\u5b50 (\u677e\u7530\u98af\u6c34)", "bitRate": 256} : no new data ...
接続に成功すると、ping
イベントと共に #nowplaying を返す*2か、: no new data
コメント*3を返すかします。
私が新しい曲を聴き始めたら新しい #nowplaying が、そうでなければ : no new data
コメントが流れてきます。
データの配信(つまり、新しい曲を聴いているかどうかの判定)は、5秒おきに行われます。
SSE のデータを受けとる
先述した通り、EventSource
に従って直感的にクライアントを実装することができます。
document.addEventListener('DOMContentLoaded', () => { const vm = new Vue({ el: "#main", data: { title: "", artist: "" } }); const renderData = (evt) => { console.log(evt); const data = JSON.parse(evt.data); vm.title = data.title; vm.artist = data.artist; } const evtSource = new EventSource("/nowplaying/stream"); evtSource.addEventListener('ping', renderData); evtSource.onmessage = renderData; });
https://sugarheart.utgw.net/labs/nowplaying.htmで #nowplaying が取得できるようにしているのですが、たったこれだけのコード*4でリアルタイムにデータを取得することができるので便利です。
また、データを取得して Slack に #nowplaying を投稿する BOT を作りました。NodeJS で書かれています。
BOT が動いている様子です。
成果物
先述したように、https://sugarheart.utgw.net/labs/nowplaying.htmで、今私が Subsonic で聴いている曲の情報をリアルタイムに得ることができます。
また、配信サーバーと Slack BOT のソースコードを GitHub で公開しています。
curl https://sugarheart.utgw.net/nowplaying/
すると、私の #nowplaying を JSON で取得することができます。
おわりに
こうして #nowplaying を手軽に取得できる API を作ることによって、私たちは次の展望を考えることができるようになります。よかったですね。
次回予告
次は KMC-ID: kata さんの「スケベな絵を描くべきnの理由 - KMCアドベントカレンダー用のブログ」です! スケベな絵に関して一言ある先輩の記事が楽しみですね。
あわせて読みたい
KMCお絵かき Advent Calendar 2016は、KMCで主にお絵描き系のプロジェクトに参加している部員や、そうでない部員も1日1人1枚ずつ絵を描いていくというアドベントカレンダーです。こちらもどうぞ!
KMCM(2)
KMCこと京大マイコンクラブでは、#nowplaying をリアルタイムに配信したり、BOT を作りまくったりしたい部員を募集しています。KMCには入部制限はありません。年齢や学歴、人種、宗教、信条、性別、社会的身分、門地、国籍、経験などは不問です。また活動に関する制約もありません。Slackのチャット越しに会話に参加することだけでも大丈夫です。詳細は下記Webページを御覧ください。