'09/08/11: perlRTMP と JW FLV Media Player でmp4ストリーミング

perlRTMPでRTMPサーバを立ち上げ、Webサーバー(Apache2:win32)から録画&エンコ済みのmp4を、JW FLV Media Playerを使ってストリーミング。RTMPなので、動画開始も早いし、非常に快適なシークができる。
流れとしてはこんな感じ
- Apacheを動かす
- Perl(Cygwin) をインストール(単接続ならActivePerlも可)
- perlRTMPをダウンロードして適当なフォルダに解凍
- Webサーバがアクセスできる「適当なひとつのフォルダ」に対して perlRTMP を立ち上げる
- mp4ファイルのファイル名は大概日本語(SJIS)なので、ハードリンクによって、適当なフォルダ下に半角英数ファイル名のエイリアスを作る。
- ハードリンクは各ドライブごとに必要だが、perlRTMPが扱えるdocument_rootはひとつしかない。よって、「適当なひとつのフォルダ」下に、ジャンクションまたはジャンクション機能を用いて上記ハードリンクを作ったフォルダをマウントする。
- JW FLV Media Playerのプレイリストを作る。
- Webサーバで表示。
実際の作業
構成例
- Webサーバドキュメントルート
- d:\webroot
- Webサーバがアクセスできる適当なフォルダ
- d:\webroot\videosource
- mp4実体ファイルのあるドライブとフォルダ
- e:\video1 f:\video2 g:\video3
- ドライブごとにhardlinkを作成するフォルダ
- e:\hardlink f:\hardlink g:\hardlink
junctionを張る。(てきとーなバッチファイル)
junction d:\webroot\videosource\vs1 e:\hardlink junction d:\webroot\videosource\vs2 f:\hardlink junction d:\webroot\videosource\vs3 g:\hardlink
perlRTMPを立ち上げる。(てきとーなvbsファイル)
コマンドプロンプトが開かないように裏で実行する。ただしデバッグ中は素直にコマンドプロンプトから実行して、標準出力されるログを見ておいたほうがいい。
CreateObject( "WScript.Shell").Run "C:\Cygwin\bin\perl.exe server.pl d:\webroot\videosource",0,False
phpでmp4ファイルのリストを取り、プレイリスト作成とハードリンク作成を行う
以下のコードはすべてutf-8を想定(画面横幅への収まりが悪いので、コメントが読みづらい位置にあるのは勘弁)
// フォルダごとの設定部分 // mp4実体ファイルのあるフォルダ $DirectoryToScan = mb_convert_encoding("g:/video3/所さん~1","SJIS","UTF-8"); $ScanCommand1 = mb_convert_encoding("g:\\video3\\所さん~1\\*.mp4","SJIS","UTF-8"); // ハードリンクを作成するフォルダ $HardlinkFolder = mb_convert_encoding("g:/hardlink/tokoro","SJIS","UTF-8"); $ScanCommand2 = mb_convert_encoding("g:\\hardlink\\tokoro\\*.mp4","SJIS","UTF-8"); // perlRTMPで指定したドキュメントルートからの相対アドレス $dirHeader = "vs3/tokoro/"; // プレイリストの出力先 $listpath = "d:/webroot/stream/tokoro/"; $title = "所さんの世田谷ベース"; // 以下共通処理 exec("dir " . $ScanCommand1 ,$count1); exec("dir " . $ScanCommand2 ,$count2); if(count($count1) > count($count2)){ // エイリアスより実体ファイルの数が多いとき exec("del " . $ScanCommand2); $fp = fopen($listpath . "list.xml","w"); // RSSプレイリスト fputs($fp, "<rss version=\"2.0\" xmlns:jwplayer=\"http://developer.longtailvideo.com/trac/wiki/FlashFormats\">\n"); fputs($fp, "<channel>\n<title>" . $title . "</title>\n"); $dir = opendir($DirectoryToScan); while (($file = readdir($dir)) !== false) { $FullFileName = realpath($DirectoryToScan.'/'.$file); if (is_file($FullFileName) && mb_eregi("\.mp4",$FullFileName)) { set_time_limit(10); $file8 = mb_convert_encoding($file,"UTF-8","SJIS"); fputs($fp, " <item>\n"); // ダミーのファイル名を与えておく fputs($fp, " <enclosure url=\"dummy.flv\" />\n"); // ファイル名をタイトルに fputs($fp, " <jwplayer:title>" . str_replace(".mp4", "", $file8) . "</jwplayer:title>\n"); // RTMP指定 fputs($fp, " <jwplayer:type>rtmp</jwplayer:type>"); // 適当に半角英数ファイル名を作成する $file8 = urlencode($file8); $file8 = str_replace("+","%20",$file8); // あまり長いファイル名はperlRTMPに嫌われる if(strlen($file8) > 120){ $pos = strpos($file8,"%",100); // 同名(になってしまった)ファイルの連番処理 if(file_exists($hlFolder . "/" . substr($file8,0,$pos) . ".mp4")){ for($i=0;;$i++){ if(!file_exists($hlFolder . "/" . substr($file8,0,$pos) . "_" . $i . ".mp4")){ $file8 = substr($file8,0,$pos) . "_" . $i . ".mp4"; break; } } } else { $file8 = substr($file8,0,$pos) . ".mp4"; } } // URL Encodeしたままだと、処理系に依っては日本語に戻される可能性があるので%を削除 $file8 = str_replace("%","",$file8); // ここで実際のファイル名を指定 fputs($fp, " <jwplayer:file>" . $dirHeader . $file8 . "</jwplayer:file>\n"); // 自鯖のアドレス(プロトコルはrtmp) fputs($fp, " <jwplayer:streamer>rtmp://www.your-server.jp/</jwplayer:streamer>\n"); fputs($fp, "</item>\n"); // ハードリンク作成 $cmd_exec = "fsutil.exe hardlink create " . $hlFolder . "/" . $file8 . " \"" . $DirectoryToScan . "/" . $file . "\""; } } fputs($fp, "</channel>\n</rss>\n"); fclose($fp); }
<rss version="2.0" xmlns:jwplayer="http://developer.longtailvideo.com/trac/wiki/FlashFormats">
<channel>
<title>所さんの世田谷ベース</title>
<item>
<enclosure url="dummy.flv" />
<jwplayer:title>001 世田谷ベースの秘密</jwplayer:title>
<jwplayer:type>rtmp</jwplayer:type>
<jwplayer:file>vs3/tokoro/00120E4B896E794B0E8B0B7E38399E383BCE382B9E381AEE7A798E5AF86.mp4</jwplayer:file>
<jwplayer:streamer>rtmp://www.your-server.jp/</jwplayer:streamer>
</item>
<item>
<enclosure url="dummy.flv" />
<jwplayer:title>007 レースカー&バイク かなり見せます語ります</jwplayer:title>
<jwplayer:type>rtmp</jwplayer:type>
<jwplayer:file>vs3/tokoro/00720E383ACE383BCE382B9E382ABE383BCEFBC86E38390E382A4E382AF20E3818BE3.mp4</jwplayer:file>
<jwplayer:streamer>rtmp://www.your-server.jp/</jwplayer:streamer>
</item>
</channel>
</rss>
<p id='preview'>Flash Player が必要です</p>
<script type='text/javascript' src='/script/swfobject.js'></script>
<script type='text/javascript'>
var s1 = new SWFObject('/swf/player.swf','player','1240','558','9');
// 960:540 = 16:9 width = 960 + 280, height = 540 + 18
s1.addParam('allowfullscreen', 'true');
s1.addParam('allowscriptaccess', 'always');
s1.addParam('wmode', 'opaque');
s1.addVariable('file', 'list.xml');
s1.addVariable('playlist', 'right');
s1.addVariable('playlistsize', '280');
s1.addVariable('displaytitle', 'true');
s1.write('preview');
</script>
<h2 id='title' style='display:none'></h2>
おまけ
JW FLV Media PlayerにListenerを追加して、ブラウザ上に title と description を表示する
var player;
function ChangeItem(obj){
if(document.getElementById("title")){
if(player.getPlaylist()[obj.index].description){
document.getElementById("title").innerHTML = player.getPlaylist()[obj.index].title +
"<br /><span style='font-weight:normal;font-size:60%'>" +
player.getPlaylist()[obj.index].description + "</desc>";
} else {
document.getElementById("title").innerHTML = player.getPlaylist()[obj.index].title;
}
document.getElementById("title").style.display = "block";
}
}
function playerReady(obj){ // playerが準備完了になったら呼ばれる関数
player = document.getElementById('player');
player.addControllerListener("ITEM", "ChangeItem");
}