なんでそんなものを。まさか Perl で flash player を作ろうなどということは考えてなくて、単に Flash Media Server (うちにあるのは古い FCS-1.5 だけど) の死活監視をしたかった。
# exe 化した SWF を Windows XP で動かしてチェック、とかしてたんですが XP のほうがサーバよりはるかに安定しないもので
Perl で RTMP ということなら Kamaitachi、ということで github で fork して Kamaitachi::Client を作ってみました。最初は別の名前空間にしようかとも思ったんだけど、やはり共通部分が多いので。
http://github.com/fujiwara/kamaitachi/tree/master
使い方。
コールバックを定義したクライアントを用意。
package MyClient; use Moose; extends "Kamaitachi::Client"; __PACKAGE__->meta->make_immutable; no Moose; sub on_invoke_onStatus { my $self = shift; my $packet = shift; my $args = $packet->args; $self->logger->debug("onStatus.code=" . $args->[1]->{code}); if ( $args->[1]->{code} eq 'NetStream.Publish.Start' ) { $self->logger->info("publish started successfuly."); $self->send_packet( Kamaitachi::Packet::Function->new( number => 3, type => 0x14, id => 5, method => "closeStream", ), ); } else { $self->logger->error("publish started failed. exit"); } $self->stop; }
送りつけるパケットを Kamaitachi::Packet またはバイナリ列で用意して $client->run(@packet) するインターフェース。使いにくいような気もするけど……「なんか動かない」って時には本物の Flash player が送ってるパケットをキャプチャして、それをバイナリでそのまま送りつけてやる、みたいなことも可能。
my @packets = ( Kamaitachi::Packet::Function->new( number => 3, type => 0x14, id => 1, method => "connect", args => [], ), Kamaitachi::Packet::Function->new( number => 3, type => 0x14, id => 3, method => "createStream", args => [], ), Kamaitachi::Packet::Function->new( number => 3, type => 0x14, id => 4, obj => 0x01000000, method => "publish", args => [ undef, "test" ], ), ); my $client = MyClient->new({ url => "rtmp://localhost/stream/live", }); $client->run(@packets);
この例だと、handshake してから connect, createStream, publish を送信して、onStatus が code eq 'NetStream.Publish.Start' で返ってきたら closeStream 送りつけて終了、という流れ。
デフォルトの動作 $client->auto(1) だと、サーバから packet_invoke が来たら次のパケットを自動的に返信するので、自分で送りたい場合は auto(0) にして $client->send_packet($packet) とか $client->send_next_packet() とか。
FCS-1.5 に対して実行したログはこんな。Data::Hexdumper でパケットの hex dump をみられるようにしました。送信パケットは全体、サーバからの受信パケットは $packet->data 部分。
[info] host: fcs port: 1935 app: sample [debug] on_write_ready [debug] on_read_ready handshake [debug] recieved packet length 3073. [debug] server token recieved. [debug] client token recieved. [debug] client token validate ok. [debug] send handshake packet. [debug] sending packet. type: 'packet_invoke' method: 'connect' [debug] 0x0000 : 03 00 00 00 00 00 F5 14 00 00 00 00 02 00 07 63 : ...............c 0x0010 : 6F 6E 6E 65 63 74 00 3F F0 00 00 00 00 00 00 03 : onnect.?........ 0x0020 : 00 0B 61 75 64 69 6F 43 6F 64 65 63 73 00 40 A8 : ..audioCodecs.@. 0x0030 : EE 00 00 00 00 00 00 07 70 61 67 65 55 72 6C 05 : ........pageUrl. 0x0040 : 00 03 61 70 70 02 00 06 73 61 6D 70 6C 65 00 0B : ..app...sample.. 0x0050 : 76 69 64 65 6F 43 6F 64 65 63 73 00 40 6F 80 00 : videoCodecs.@o.. 0x0060 : 00 00 00 00 00 05 74 63 55 72 6C 02 00 15 72 74 : ......tcUrl...rt 0x0070 : 6D 70 3A 2F 2F 70 2D 63 68 69 6E 61 2F 73 61 6D : mp://p-china/sam 0x0080 : 70 6C 65 00 06 73 77 66 55 72 6C 05 C3 00 0D 76 : ple..swfUrl....v 0x0090 : 69 64 65 6F 46 75 6E 63 74 69 6F 6E 00 3F F0 00 : ideoFunction.?.. 0x00A0 : 00 00 00 00 00 00 08 66 6C 61 73 68 56 65 72 02 : .......flashVer. 0x00B0 : 00 0E 57 49 4E 20 31 30 2C 30 2C 32 32 2C 38 37 : ..WIN.10,0,22,87 0x00C0 : 00 04 66 70 61 64 00 00 00 00 00 00 00 00 00 00 : ..fpad.......... 0x00D0 : 0C 63 61 70 61 62 69 6C 69 74 69 65 73 00 40 2E : .capabilities.@. 0x00E0 : 00 00 00 00 00 00 00 0E 6F 62 6A 65 63 74 45 6E : ........objectEn 0x00F0 : 63 6F 64 69 6E 67 00 00 00 00 00 00 00 00 00 00 : coding.......... 0x0100 : 00 09 : .. [debug] got packet from server. type: 'packet_server_bw' [debug] 0x0000 : 07 A1 20 00 : .... [debug] got packet from server. type: 'packet_client_bw' [debug] 0x0000 : 00 03 D0 90 02 : ..... [debug] got packet from server. type: 'packet_server_bw' [debug] 0x0000 : 00 03 D0 90 : .... [debug] got packet from server. type: 'packet_invoke' [debug] 0x0000 : 02 00 07 5F 72 65 73 75 6C 74 00 3F F0 00 00 00 : ..._result.?.... 0x0010 : 00 00 00 05 03 00 05 6C 65 76 65 6C 02 00 06 73 : .......level...s 0x0020 : 74 61 74 75 73 00 04 63 6F 64 65 02 00 1D 4E 65 : tatus..code...Ne 0x0030 : 74 43 6F 6E 6E 65 63 74 69 6F 6E 2E 43 6F 6E 6E : tConnection.Conn 0x0040 : 65 63 74 2E 53 75 63 63 65 73 73 00 0B 64 65 73 : ect.Success..des 0x0050 : 63 72 69 70 74 69 6F 6E 02 00 15 43 6F 6E 6E 65 : cription...Conne 0x0060 : 63 74 69 6F 6E 20 73 75 63 63 65 65 64 65 64 2E : ction.succeeded. 0x0070 : 00 00 09 : ... [debug] $VAR1 = { 'args' => [ undef, { 'level' => 'status', 'description' => 'Connection succeeded.', 'code' => 'NetConnection.Connect.Success' } ], 'method' => '_result', 'id' => '1' }; [debug] sending packet. type: 'packet_invoke' method: 'createStream' [debug] 0x0000 : 03 00 00 00 00 00 18 14 00 00 00 00 02 00 0C 63 : ...............c 0x0010 : 72 65 61 74 65 53 74 72 65 61 6D 00 40 08 00 00 : reateStream.@... 0x0020 : 00 00 00 00 : .... [debug] got packet from server. type: 'packet_client_bw' [debug] 0x0000 : 00 03 D0 90 02 : ..... [debug] got packet from server. type: 'packet_ping' [debug] 0x0000 : 00 00 00 00 00 00 : ...... [debug] got packet from server. type: 'packet_invoke' [debug] 0x0000 : 02 00 07 5F 72 65 73 75 6C 74 00 40 08 00 00 00 : ..._result.@.... 0x0010 : 00 00 00 05 00 3F F0 00 00 00 00 00 00 : .....?....... [debug] $VAR1 = { 'args' => [ undef, '1' ], 'method' => '_result', 'id' => '3' }; [debug] sending packet. type: 'packet_invoke' method: 'publish' [debug] 0x0000 : 03 00 00 00 00 00 25 14 01 00 00 00 02 00 07 70 : ......%........p 0x0010 : 75 62 6C 69 73 68 00 40 10 00 00 00 00 00 00 05 : ublish.@........ 0x0020 : 02 00 0E 74 65 73 74 31 32 33 38 33 39 37 36 38 : ...test123839768 0x0030 : 35 : 5 [debug] got packet from server. type: 'packet_ping' [debug] 0x0000 : 00 00 00 00 00 01 : ...... [debug] got packet from server. type: 'packet_invoke' [debug] 0x0000 : 02 00 08 6F 6E 53 74 61 74 75 73 00 40 10 00 00 : ...onStatus.@... 0x0010 : 00 00 00 00 05 03 00 05 6C 65 76 65 6C 02 00 06 : ........level... 0x0020 : 73 74 61 74 75 73 00 04 63 6F 64 65 02 00 17 4E : status..code...N 0x0030 : 65 74 53 74 72 65 61 6D 2E 50 75 62 6C 69 73 68 : etStream.Publish 0x0040 : 2E 53 74 61 72 74 00 0B 64 65 73 63 72 69 70 74 : .Start..descript 0x0050 : 69 6F 6E 02 00 1A 50 75 62 6C 69 73 68 69 6E 67 : ion...Publishing 0x0060 : 20 74 65 73 74 31 32 33 38 33 39 37 36 38 35 2E : .test1238397685. 0x0070 : 00 08 63 6C 69 65 6E 74 69 64 00 41 A1 36 D4 30 : ..clientid.A.6.0 [debug] got packet from server. type: 'packet_invoke' [debug] 0x0000 : 02 00 08 6F 6E 53 74 61 74 75 73 00 40 10 00 00 : ...onStatus.@... 0x0010 : 00 00 00 00 05 03 00 05 6C 65 76 65 6C 02 00 06 : ........level... 0x0020 : 73 74 61 74 75 73 00 04 63 6F 64 65 02 00 17 4E : status..code...N 0x0030 : 65 74 53 74 72 65 61 6D 2E 50 75 62 6C 69 73 68 : etStream.Publish 0x0040 : 2E 53 74 61 72 74 00 0B 64 65 73 63 72 69 70 74 : .Start..descript 0x0050 : 69 6F 6E 02 00 1A 50 75 62 6C 69 73 68 69 6E 67 : ion...Publishing 0x0060 : 20 74 65 73 74 31 32 33 38 33 39 37 36 38 35 2E : .test1238397685. 0x0070 : 00 08 63 6C 69 65 6E 74 69 64 00 41 A1 36 D4 30 : ..clientid.A.6.0 0x0080 : 00 00 00 00 00 09 : ...... [debug] $VAR1 = { 'args' => [ undef, { 'clientid' => '144402968', 'level' => 'status', 'description' => 'Publishing test1238397685.', 'code' => 'NetStream.Publish.Start' } ], 'method' => 'onStatus', 'id' => '4' }; [debug] callback 'on_invoke_onStatus' [debug] onStatus.code=NetStream.Publish.Start [info] publish started successfuly. [debug] sending packet. type: 'packet_invoke' method: 'closeStream' [debug] 0x0000 : 03 00 00 00 00 00 17 14 00 00 00 00 02 00 0B 63 : ...............c 0x0010 : 6C 6F 73 65 53 74 72 65 61 6D 00 40 14 00 00 00 : loseStream.@.... 0x0020 : 00 00 00 : ...