MojoliciousとWebSocketとiTunesと

これはMojolicious Advent Calendar 24日目の記事です。

iTunesに英語教材などを放り込んでエンドレス再生していると、「ここにテキストが表示されていればいいのになあ」と思うことがあります。あるいは、洋楽を流しっぱなしにしているときに、ふと聞き取れなかった歌詞を確認したくなるとか。

もちろんその都度メモなりなんなりを開いてもいいのですが、いちいち該当のトラックに関係のあるファイルを探すのも面倒ですから、トラックが変わるたびに自動でテキストが表示されるようにしたいものです。GUIをごりごり書くのは面倒ですし、ターミナルに流し続けるのも見づらいですから、ここはテキストはブラウザに表示することにし、WebSocket経由で更新し続けるようにしてみましょう。

Windows環境では以下のようにWin32::OLEを利用することでiTunesからトラック情報を取得できます(Mac環境の場合はCPANMac::iTunesというモジュールがあるようですので、適宜読み替えてください)。

use Win32::OLE;

my $itunes = Win32::OLE->new('iTunes.Application');
my $track = $itunes->CurrentTrack;
my $name = $track->Name;
my $album = $track->Album;
my $path = $track->Location;

Mojoliciousには特定の処理を繰り返す仕組みが用意されていますので、ここではそれを利用して、毎秒iTunesのカレントトラックを確認し、変更があったら関連するテキストファイルを読み込むことにしましょう。

use Mojolicious::Lite;
use Mojo::Asset::File;
use Mojo::IOLoop;
use File::Basename;

my $current;
Mojo::IOLoop->recurring(1 => sub {
  my $track = $itunes->CurrentTrack or return;
  if (!$current or $current ne $track->Name) {
    $current = $track->Name;
    my $album = $track->Album;
    my $basename = basename($track->Location);
    $basename =~ s/\.m4a$/.txt/;
    my $textfile = app->home->rel_file("text/$album/$basename");
    my $text = Mojo::Asset::File->new(path => $textfile)->slurp;

  }
});

続いて、WebSocketまわりの処理を実装します。ここではWebSocketの接続を%clientsという変数に保存して、あとでループの中から利用できるようにしています。

use Mojolicious::Lite;

get '/' => 'index';

my %clients;
websocket '/connect' => sub {
  my $self = shift;
  my $id = "".$self->tx;
  $clients{$id} = $self->tx;

  $self->on(finish => sub {
    delete $clients{$id};
  });
};

app->start;

あとはループの中で、更新された情報をJSONにくるんで送り出してやり(cp932でdecodeしているのはもちろんWindows環境だからです)、

use Mojo::ByteStream 'b';
use Mojo::JSON 'j';

Mojo::IOLoop->recurring(1 => sub {
  if (!$current or $current ne $track->Name) {
    ...
    my $text = Mojo::Asset::File->new(path => $textfile)->slurp;

    my $json = b(j({
      title => b($current)->decode('cp932'),
      text => b($text)->decode('cp932'),
    }))->decode;
    $clients{$_}->send($json) for keys %clients;
  }
});

その情報をJavaScriptで画面に書き出してあげれば完成です。

@@ index.html.ep
<html>
<head>
%= javascript '/mojo/jquery/jquery.js';
%= javascript begin
$(function(){
  var ws = new WebSocket('<%= url_for('connect')->to_abs %>');
  ws.onmessage = function(msg) {
    var res = JSON.parse(msg.data);
    $('.title').text(res.title);
    $('.text').text(res.text);
  };
});
% end
</head>
<body>
<h1 class="title"></h1>
<pre class="text"></pre>
</body>
</html>

このようなWebSocketを使ったMojolicious::Liteアプリは、ChromeなどWebSocket対応のブラウザでないと正しく動作しませんし、現状ではplackupも、Windows環境の場合はhypnotoadはもちろん、morboさえも使えない(perl myapp.pl daemonならOK)という、なかなか厳しい制約はありますが、WebSocketを使うと、手元のブラウザだけでなく、ネットワーク上の他のブラウザでも同じ内容を表示させられますので、表示する内容を工夫すればなかなかおもしろいこともできそうです。

全体像は https://github.com/charsbar/lyrics_viewer_win32 からどうぞ。