PC-UNIXに関する記事など

Tatsumaki でニコ生放送のコメントを流してみた

最近 Tatsumaki 使ったアプリ作りたいなぁと考えていて
何かネタはないかと探していました。
chatはサンプルであるしTwitterのtimelineは既に作られてるし・・・

ニコ生見ててひらめきました。そうです放送中に流れるコメントです。
放送番組を指定して番組中に流れるリスナーのコメントが流れる様子をTatsumakiを使って実現してみる事になりました。

コメントだけ見れて誰得?みたいな感じですが・・・あくまでネタなので。。

ニコ生のコメントは番組毎のサーバーをsocketで叩くとchatデータがデレデロ流れてくるのでそいつをTatsumakiのstreamに渡してやれば上手くいきそうです。

でとりあえず実装できたのですが、はまったのがIEブラウザの挙動です。

どういう訳かIEのみ一度表示したデータを永遠と表示してしまうバグ?のようなものがあったり
JSONで送られてくるデータをcacheしてしまったりと散々な目にあいました。

一応まともに動く形になった?(と思う)ので公開してみます。

ニコ生チャットStream
http://tatsumaki.omakase.org/nico/live/1?sort=1
※サーバのリソースがもったいないのでデモサンプルは止めました。2010-05-09

デモで使われているスクリプト

スクリプトの主要な部分を載せておきます。

app.psgi

use Tatsumaki::Application;
use strict;
use warnings;
use NicoHandler;
use NicoChatHandler;
use RssHandler;
use File::Basename;

my $app = Tatsumaki::Application->new(
    [
        '/nico/live/(\d+)'   => 'RssHandler',
        '/nico/lv(\d+)'      => 'NicoHandler',
        '/nico_pool/lv(\d+)' => 'NicoChatHandler',
    ]
);

$app->template_path( dirname(__FILE__) . "/template" );
$app->static_path(dirname(__FILE__) . "/static");
return $app;

RssHandler.pm

package RssHandler;

use parent qw(Tatsumaki::Handler);
__PACKAGE__->asynchronous(1);
use strict;
use warnings;
use Tatsumaki::HTTPClient;
use XML::Simple qw(XMLin);

sub get {
    my $self   = shift;
    my $query  = shift || 1;
    my $sort   = defined $self->request->param("sort") ? "&sort=view" : "&sort=start";
    my $client = Tatsumaki::HTTPClient->new;
    $client->get(
        "http://NICONAMA/URL?tab=common&p=" 
          . $query
          . $sort,
        $self->async_cb( sub { $self->on_response(@_) } )
    );
}

sub on_response {
    my ( $self, $res ) = @_;
    if ( $res->is_error ) {
        Tatsumaki::Error::HTTP->throw(500);
    }
    else {
        my $rss = XMLin( $res->content );    # warn Dumper $rss;
        $self->render( 'nico_rss.html',
            { rss_item => $rss->{channel}->{item} } );
    }
}

1;

NicoHandler.pm

package NicoHandler;
use parent qw(Tatsumaki::Handler);
use strict;
use warnings;

sub get {
    my $self = shift;
    my ( $uid ) = @_;
    $self->render( 'nico.html' );
}
1;

NicoChatHandler.pm

package NicoChatHandler;

use parent qw(Tatsumaki::Handler);
__PACKAGE__->asynchronous(1);
use strict;
use warnings;
use utf8;
use Nico;
use Nico::TatsumakiStream;
use Tatsumaki::MessageQueue;
use Tatsumaki::Error;
use Encode qw(decode_utf8);

my %streams;


sub create_niconico {

    my ( $self, $live_id ) = @_;
    # Get Nico Live Status
    my $niko = Nico->new("/tasumaki_stream/config.yaml");

    $niko->login or die "Not login";
    my $live_info = $niko->live_info( $live_id );
    die "Status Faild" unless $live_info->status;
    my $stream_instance = Nico::TatsumakiStream->instance;

    # NicoNico Stream Session CallBack Return
    return $stream_instance->stream( $live_info  );
}

sub create_stream {
    my $self = shift;
    my ( $uid ) = @_;
    my $cnt ||= 0;
    
    my $mq = Tatsumaki::MessageQueue->instance( $uid );

    $streams{ $uid} ||= $self->create_niconico( $uid);
    
    my $cb = sub {
        # warn "Live Chat:", $_[0]->{text};
        $_[0]->{text} =  decode_utf8 $_[0]->{text};
        $_[0]->{text} =~ s/\n/
/g; $mq->publish( { type => 'chat', chat => $_[0] } ); }; my $err_cb = sub { $mq->publish( { type => 'message', text => $_[0], } ); }; $streams{ $uid }->($cb, $err_cb); } sub get { my $self = shift; my ( $uid ) = @_;# warn "GET UID:", $uid; my $session = $self->request->param('session') or Tatsumaki::Error::HTTP->throw(500, "'session' needed"); $streams{ $uid } or $self->create_stream( $uid ); my $mq = Tatsumaki::MessageQueue->instance( $uid ); $mq->poll_once( $session, sub { my @events_published = @_; $self->write( \@events_published ); $self->response->header( 'Cache-Control' => 'no-store, no-cache, must-revalidate,'. 'post-check=0, pre-check=0, max-age=0', 'Pragma' => 'no-cache', 'Expires' => 'Thu, 01 Jan 1970 00:00:00 GMT' ); $self->finish; } ); } 1;

TatsumakiStream.pm

package Nico::TatsumakiStream;

use strict;
use warnings;
use base "Class::Singleton";
use Nico::Chat       ();
use AnyEvent         ();
use AnyEvent::Handle ();
use AnyEvent::Socket qw(tcp_connect);
use Encode qw(decode_utf8);
use Nico::Utils qw(instanceof);

our $VERSION = '0.03';
our $DEBUG   = 0;

my $STREAM_POST_DATA =
  qq{\0};

# stream session getter and setter
sub stream {
    my ( $self, $connect_info ) = @_;

    die "stream method ARGS is (Nico::AlertInfo or Nico::Live) Instance only."
      unless ( instanceof( $connect_info => 'Nico::AlertInfo' ) )
      || ( instanceof( $connect_info => 'Nico::Live' ) );

    return $self->{ $connect_info->instance_name } ||=
      __create_stream($connect_info);
}

# New Stream Session Create
sub __create_stream {
    my $connect_info = shift;
    sub {
        my $on_read_cb  = shift || sub { warn $_[0] };
        my $on_error_cb = shift || sub { warn $_[0] };

        my $wsession;
        $wsession ||= tcp_connect $connect_info->addr, $connect_info->port,

          # TCP Connect CallBack
          sub {
            my ($fh) = @_
              or die "unable to connect: $!";

            my $handle;    # avoid direct assignment so on_eof has it in scope.
            $handle = new AnyEvent::Handle
              fh       => $fh,
              on_error => sub {
                $on_error_cb->("Handle Error");
                $_[0]->destroy;
                undef $wsession;
              },
              on_eof => sub {
                $on_error_cb->("/Disconnect");
                $handle->destroy;    # destroy handle
                undef $wsession;
              };

            $handle->push_write( sprintf $STREAM_POST_DATA,
                $connect_info->thread );

            $handle->push_read(
                line => "\0",
                sub {
                    my ( $handle, $line ) = @_;

                    print "HEADER\n$line\n\nBODY\n" if $DEBUG;
                    $handle->on_read(
                        sub {

                            # print response body
                            my $buffer = $_[0]->rbuf;
                            for my $chat (
                                @{ Nico::Chat::chat_line2hash( $buffer, 1 ) } )
                            {
                                $on_read_cb->($chat);

                                if ( $chat->{text} eq '/disconnect' ) {
                                    $on_error_cb->( $chat->{text} );
                                    $handle->destroy;
                                    undef $wsession;
                                }
                            }
                            $_[0]->rbuf = "" if defined $wsession;

                        }
                    );
                }
            );
          }, sub {
            my ($fh) = @_;
            # Also limit the connection timeout to 15 seconds.
            # could call $fh->bind etc. here
            15;
          };
      }
}
1;

nico.html

preタグ使ってるんだが崩れるなぁ
% my $uid = $_[0]->{handler}->args->[0];
<html>
<head>
<title>NicoNico Live  lv<%= $uid %></title>
<script type="text/javascript" src="/static/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="/static/jquery.ev.js"></script>
<script type="text/javascript" src="/static/jquery.dump.js"></script>
<script type="text/javascript">
jQuery( function ($) {
var last=0;
    $.ev.loop('/nico_pool/lv' + <%= $uid %> + '?session=' + Math.random(), {

        chat : function(ev) {
            if(! ev.chat.text) return;
            var kid = ev.chat.no;
            if(last >= kid){return;} /* これが無いとIEで悲惨な目に合います>< */
            $( "#chats" ).prepend( 
                ev.chat.no + ' : ' + ev.chat.text + "<br>" 
            );
            last = kid;
        },
        message : function(ev) {
        $("#last2").html("<div>" +  ev.text + "</div>");
            $( "#chats" ).prepend( 
                '<span style="color: #F00;">' + ev.text + "</span><br>" 
            );
        }
    });
} );
</script>
</head>
<body>
<div id="last" style="font-size:xx-small;"></div>
<div id="last2" style="font-size:xx-small;"></div>
<h1>NicoNico Live  <a href="http://live.nicovideo.jp/watch/lv<%= $uid %>">lv<%= $uid %></a></h1>
<div id="chats" style="font-size:xx-small;"></div>
</body>
</html>

このエントリーをはてなブックマークに追加

トップ  »  perl  »  Tatsumaki でニコ生放送のコメントを流してみた

トラックバック(0)

トラックバックURL: http://www.omakase.org/mt/mt-tb.cgi/32

コメントする

カテゴリperlの記事一覧