サーバ サイド ライブ ストリーミング アプリ

server-side live streaming application を試作。ソース一式(newvieLive-20091025.lzh)
VOD と Live は似てますが、相違点が多々あります。作成に当たっての最大の違いは、VOD は Red5 に埋め込まれてますが、 Live はサーバサイドアプリのコード書きが必要なことです。それでも、VOD 用のストリーミング環境が Live にも使えるので、かなり楽にライブストリーミングが書けます。

Red5 が起動すると、サーバサイドアプリが起動し(appStart)、動画ファイルをライブストリーミング方式で配信します(垂れ流し状態で、繰り返し配信)。Flex4(AIR1.5)製クライアントアプリが3個、接続して動画を再生します。クライアントアプリを終了・再起動を行っても、同じタイミングで動画が再生されます。

参考にしたのは、

環境は、これまでと同一なので省略(例えばここ参照)

VOD vs. Live (revise)

以前も書きましたが、ここでは視聴者(クライアントサイドアプリ)へのサービスの違いでまとめ直します。どのようなサービス方式にするかによってコードの書き方が変わることになります。今回作成したアプリは、次の 2.TV型・録画済み動画のライブストリーミングになります。
1. VOD型サービス
視聴者が接続してくると、「冒頭から動画を再生」して提供します。別の視聴者が接続して来ると、やはり「冒頭から動画を再生」して提供します。視聴者は常に冒頭から動画を見ることができます。この為、VODサービスでは、録画済み(recorded video)の動画が使われます。サーバサイドの動画ストリーム(serverStream)は、視聴者毎に必要になります。
2. TV型サービス
ライブストリーミングには違いないですが、TV と同様のサービスです。視聴者には「再生途中の動画」が提供されます。サーバ側では、サービス期間中、常に動画が再生されており、視聴者は各々が接続したタイミングに応じて、動画の途中から視聴をすることになります。動画は録画済みでも、生中継でも構いません。視聴者がいなくても動画は再生を続けているので、サーバは無駄なサービスを提供していますが、生中継の場合は止むを得ません(視聴率が低いTV番組と同じです)。ただ、serverStream はひとつで賄えます。
3. ハイブリッド(VOD+TV)型サービス
最初の視聴者が接続して来たタイミングで、動画の再生が始まります。先客がいる場合には「再生途中の動画」を見ることになります。接続している視聴者がゼロになったら再生を終了し、次の接続を待ちます。このサービスのメリットは動画を常に再生しなくて良いので、サーバの負担や無駄が減るということです。しかも、serverStream はひとつで済みます。この方式では、録画済みの動画が使われます。

Red5 の oflaDemo は VOD 型です。VOD 型も大きく分類すれば、クライアント毎のライブストリーミングになると考えられます。違いは、Flex クライアントでの接続時には live="false" が指定されることです。このフラグに依存して、Red5の内部組み込みのストリーミングをするか、サーバサイドアプリ(開発者)に任せるかの判断をしているように思えます(完全に想像です。実験も調査もしていません)。

さて、上記の真偽の程は計りかねます。ただ、プログラムは期待通りに動いていますので、それ程、間違っているとは思いません。

接続のタイミング

移行ガイドに接続のタイミングについてまとめた記述があります。この記事に関係するものだけをまとめます。

boolean appStart( IScope app) 起動時に一度だけ呼ばれる。
サービス全体の初期化。
boolean appConnect( IConnection conn, Object[] params ) クライアント接続毎。
最初のクライアントに対する特別な処理もここで。
void appDisconnect( IConnection conn ) クライアント切断毎。
最後のクライアントに対する特別な処理もここで。
void appStop( IScope app ) 終了時に一度だけ呼ばれる。
サービス全体の後始末。

サーバサイド

フォルダ構成


サーバサイドアプリ

(後付でコメント入れました。)

package org.eggtoothcroc;

import java.io.*;             // System.out.println などを使うため

import org.slf4j.Logger;                    // 汎用ロギングライブラリ
import org.red5.logging.Red5LoggerFactory;  // Red5用ロギングライブラリ

import org.red5.server.adapter.ApplicationAdapter; // デフォルトのサーバサイドアプリ
import org.red5.server.api.IScope;                 // Red5 が渡してくれるアプリの識別子のようなもの
import org.red5.server.api.stream.IServerStream;   // 動画のストリーム
import org.red5.server.api.stream.support.StreamUtils;     // ストリームの作成、取得を代行してくれる
import org.red5.server.api.stream.support.SimplePlayItem;  // ストリームに渡す動画の識別子のようなもの

public class Application extends ApplicationAdapter
{
  private static Logger log
    =Red5LoggerFactory.getLogger(Application.class, "newbieLive");

  private IServerStream serverStream; // Startで作成し、Stopで破棄する

  @Override public boolean appStart( IScope app )
  {
    boolean b =super.appStart(app);

    System.out.println("newvieLive appStart");
    log.info( "newvieLive appStart" );

    // "MaxFactor-live"というパブリッシュ名でストリームを作成
    serverStream =StreamUtils.createServerStream( app, "MaxFactor-live" ); 

    SimplePlayItem item = new SimplePlayItem();
    item.setName("Max Factor (web limited edition)"); // streamsフォルダの動画名から.flvを除いたもの
    serverStream.addItem(item);
    serverStream.start();
    serverStream.setRepeat(true); // 繰り返しを指定する(start後でないといけない?)

    return b; 
  }

  @Override public void appStop( IScope app )
  {
    System.out.println("newvieLive appStop");
    log.info("newvieLive appStop");

    if( serverStream!=null ){ // 絶対に null でないはずだけど・・・
      serverStream.stop();  // ストリームを止めて、
      serverStream.close(); // 破棄する
    }

    super.appStop( app );
  }
}
web.xmlred5-web.xmllogback-newvieLive.xml

これらは、以前と変わりません。ただし、今回から oflaDemo の宣言などを踏襲したものに変更して、これまでよりも Red5 の推奨(?)に近くなっています。

クライアントサイド

SuonoDolce.mxml

これまでと違うのは、live="true"フラグが付き、動画ファイル名ではなく、サーバサイドアプリで指定したパブリッシュ名「MaxFactor-live」を使用していることです。ここに至り、Adobe の広告が謳っているように、動画ファイルの名称を隠したことになる訳です。

<?xml version="1.0" encoding="utf-8"?>

<s:WindowedApplication
   xmlns:fx="http://ns.adobe.com/mxml/2009"
   xmlns:s="library://ns.adobe.com/flex/spark"
   xmlns:mx="library://ns.adobe.com/flex/halo">

  <s:VideoPlayer autoPlay="true">
    <s:StreamingVideoSource 
       serverURI="rtmp://localhost/newbieLive" live="true">
      <s:StreamItem streamName="MaxFactor-live"/>
    </s:StreamingVideoSource>
  </s:VideoPlayer>

</s:WindowedApplication>
SuonoDolce.xml、SuonoDolce-Another.xml、SuonoDolce-OneMore.xml

ライブストリーミングの確認の為に、複数のクライアントアプリの起動を行います。その為に、3つのアプリ定義(.xml)を行いました(実行本体は同じもの.mxml)。いずれも、adl.exe で実行します。また、air化して実行もできます。各々の違いの本質は、アプリIDとアプリ名の相違です。それぞれの起動タイミングが違ったとしても、再生される動画のタイミングが同じことが確認できれば成功です。実際のソースコードは「ソース一式」に格納されてますので、ここでは日本語でコメントした疑似コードとします。

<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://ns.adobe.com/air/application/1.5">
  <id>egc.SuonoDolce.Another</id>         ・・・これがアプリケーションID。実行時の名前。
  <version>0.9</version>
  <filename>SuonoDolce-Another</filename> ・・・ これがインストール時のアプリケーション名
  <description>                 「c:/Program Files/SuonoDolce-Another/SuonoDolce-Another.exe」となります。
    SuonoDolce Another
  </description>
  <copyright>@copyleft 2009 eggtoothcroc. All wrongs reserved.</copyright>

  <initialWindow>
    <content>SuonoDolce.swf</content>
    <systemChrome>standard</systemChrome>
    <transparent>false</transparent>
    <visible>true</visible>
  </initialWindow>

  <icon>
    <image16x16>icons/SuonoDolce16.png</image16x16>
    <image32x32>icons/SuonoDolce32.png</image32x32>
    <image48x48>icons/SuonoDolce48.png</image48x48>
    <image128x128>icons/SuonoDolce128.png</image128x128>
  </icon>

</application>

ビルド・実行

ビルドは以前と同じなので省略。ただし、ant build.xmldaemon 起動は止めました(記事(2009.10.24)参照)。また、air をインストールしてみるのも一興ですが、わざわざアンインストールしなければならないので、adl.exe で実行する方をおすすめします。

実行手順

1. Red5.bat (あるいは、サービスの再開)で Red5 を起動させます。
2. client フォルダにて、

  $ adl.exe SuonoDolce.xml &
  $ adl.exe SuonoDolce-Another.xml &
  $ adl.exe SuonoDolce-OneMore.xml &

などとしてください。動画が同じタイミングで再生されるはずです。クライアントアプリを終了・再起動させることで確認できます。


ライブストリーミングが出来たのだと思います。少なくとも、クライアントサイドのコード上では、インターネットラジオ Suono Dolche へのアクセスと同じになりました。これで、Suono Dolce にご迷惑をかけることなく、実験が出来るという訳です。あとは、CurrentPlayList 取得などの HTTPプロトコル 周りを実装すれば、自分専用のSuono Dolce Player が作れるはず・・・。