「FCS/FMS から Red5 への移行ガイド」の和訳

今度は Red5 の簡単な使い方ガイドみたいな感じの Migration guide from FCS/FMS to Red5 の 2006/11/14 版を和訳。他にストリーミング専用の文書があるようなのでそっちも和訳した方がいいか。

FCS/FMS から Red5 への移行ガイド

この文書のライセンスは、Creative Commons の「表示・継承 2.5」です。

author
Joachim Bauch
contact
jojo@struktur.de
Date
$Date: 2006-11-15 00:22:04 +0100 (Mi, 15 Nov 2006) $
Revision
$Revision: 1540 $
Id
$Id: MigrationGuide.txt 1540 2006-11-14 23:22:04Z jbauch $

... コンテンツ::

前書き

このドキュメントは Macromedia Flash Communication Server / Flash Media Server 2 と Red5 の間の差について記述しています。既存のアプリケーションから Red5 への移行の手助けをすることが目的としています。

もし Red5 をまだ持っていない場合は、`howto create new applications` を初めに読んでください。

アプリケーションコールバック

多くの機能のうちサーバーサイドアプリケーションを実装する際に最も重要なものの一つが、接続か切断したクライアントの通知を取得しアプリケーションの新しいインスタンスの生成に関する情報を受け取ることです。

インターフェイス IScopeHandler

Red5 は IScopeHandler インターフェイスでこれらのアクションを定義しています。詳細は API ドキュメントを見てください。

クラス ApplicationAdapter

いくつかの関数は一度のリクエストで複数回呼ばれます(たとえば、`connect` はクライアントが接続するツリーの全てのスコープで一度呼ばれます)。ApplicationAdapter_ クラスは更に関数を定義しています。

このクラスは通常新しいアプリケーションの基幹クラスとして利用されます。

FCS / FMS の `application` クラスの関数の概要と、それに該当する Red5 の AppilcationAdapter_ の関数は次の通りです:

FCS / FMSRed5
onAppStartappStart
roomStart
onAppStopappStop
roomStop
onConnectappConnect
roomConnect
appJoin
roomJoin
onDisconnectappDisconnect
roomDisconnect
appLeave
roomLeave

`app*` 関数はメインアプリケーションで呼ばれ、`room*` 関数はアプリケーションのルーム(即ちインスタンス)で呼ばれます。

それと同様に、ストリームやシェアードオブジェクトのチェックや承認にも ApplicationAdapter_ を使うことができます。更に細かいことについては API ドキュメントを見てください。

コネクション関数の実行

`rtmp://server/app/room1/room2` へ接続すると仮定します。

まず初めにコネクションが確立され、そして `room2` に至る全てのスコープに対してユーザーが「接続」されます。

  1. `app` (-> appConnect)
  2. `room1` (-> roomConnect)
  3. `room2` (-> roomConnect)

コネクション確立後、このクライアントオブジェクトは回収され、そのスコープにおいてこのクライアントが始めて接続する場合はクライアントがスコープに「加入」します。

  1. `app` (-> appJoin)
  2. `room1` (-> roomJoin)
  3. `room2` (-> roomJoin)

同じクライアントが同じスコープに二度目のコネクションを確立する場合、単に `connect` 関数が呼ばれます。部分的に同じスコープへ接続する場合、単にいくつかの `join` 関数が呼ばれます。たとえば `rtmp://server/app/room1/room3` をトリガーとした場合は次のようになります。

  1. `appConnect`
  2. `joinConnect("room1")`
  3. `joinConnect("room3")`
  4. `roomJoin("room3")`

`appStart` 関数は今のところ Red5 の起動時に一度だけ呼ばれ、FCS/FMS のようにアプリケーションをロード/アンロードすることはできません。`roomStart` 関数は最初のクライアントがルームに接続した時に呼ばれます。

クライアントの承認 / 拒否

FCS / FMS は新しいクライアントの承認と拒否のために関数 `acceptConnection` と `rejectConnection` を提供しています。Red5 アプリケーションがクライアントの接続を許可する時には `*Connect` 関数が `true` を返す以外に特別な処理は必要ありません。

クライアントが接続を許可すべきでない時、ApplicationAdapter_ クラスで実装された関数 `rejectClient` が呼び出されます。`rejectClient` へ渡されたいくつかのパラメーターは、呼び出し元に返されたステータスオブジェクトの `application` プロパティで利用できます。

現在のコネクションとクライアント

Red5 は呼び出された関数から現在のコネクションにアクセスするために二つの異なる方法がサポートされています。コネクションは接続中のスコープとアクティブなクライアントを取得するために使うことができます。一つ目の方法は、「不思議な」Red5 オブジェクトを使います::

import org.red5.server.api.IClient;
import org.red5.server.api.IConnection;
import org.red5.server.api.IScope;
import org.red5.server.api.Red5;

public void whoami() {
    IConnection conn = Red5.getConnectionLocal();
    IClient client = conn.getClient();
    IScope scope = conn.getScope();
    // ...
}

二つ目の方法では、クライアントが関数を呼んだ時に Red5 によって自動的に追加される暗黙の最初のパラメーターである IConnection_ タイプの引数で定義されている関数を使います::

import org.red5.server.api.IClient;
import org.red5.server.api.IConnection;
import org.red5.server.api.IScope;

public void whoami(IConnection conn) {
    IClient client = conn.getClient();
    IScope scope = conn.getScope();
    // ...
}
追加のハンドラー

多くのアプリケーションのために、再利用を義務付けられた Red5 に関連しない、アプリケーションロジックを含むクラスが存在します。RTMP 経由で接続するクライアントで利用できるようにそれらのクラスは Red5 のハンドラーとして登録されている必要があります。

現在ハンドラーに登録するには二つの方法があります:

  1. 設定ファイルに追加する。
  2. アプリケーションのコード内で手動で登録する。

ハンドラーはこのようなコードでクライアントから実行されます::

nc = new NetConnection();
nc.connect("rtmp://localhost/myapp");
nc.call("handler.method", nc, "Hello world!");

ハンドラーが要求された場合、Red5 は常に、設定ファイルでセットアップされたハンドラーをチェックするより先に、カスタムのスコープのハンドラー(訳注: アプリケーションコード上で登録したハンドラー、という意味だろうか)から探します。

設定ファイル内のハンドラー

このメソッドは、アプリケーションの中にある全てのスコープで共有され、かつアプリケーションの生存時間中に変化しないハンドラーに最も向いています。

ハンドラー `sample` としてクラス `com.fancycode.red5.HandlerSample` を登録する場合、以下の bean を `WEB-INF/red5-web.xml` に追加する必要があります。

<bean id="sample.service" 
      class="com.fancycode.red5.HandlerSample" 
      singleton="true" />

この bean の ID はハンドラー名(ここでは `sample`)とキーワード `service` から構成されていることに注意してください。

アプリケーションコードからのハンドラー

ハンドラーの変更を行いたい場合や様々なスコープで異なるハンドラーを使いたい場合、全てのアプリケーションはサービス内部のコードでハンドラーを登録する必要があります。これらのハンドラーは常に `red5-web.xml` で設定したハンドラーをオーバーライドします。登録に必要な関数は、ApplicationAdapter_ で実装されているインターフェイス IServiceHandlerProvider_ に記載されています。

前述のものと同じクラスはこのコードで登録することができます::

public boolean appStart(IScope app) {
    if (!super.appStart(scope))
        return false;
    
    Object handler = new com.fancycode.red5.HandlerSample();
    app.registerServiceHandler("sample", handler);
    return true;
}

この例では、このアプリケーションスコープは `sample` ハンドラーを持っていますが、そのサブスコープには適用されないことに注意してください。ハンドラーをルームでも同様に有効にしたい場合は、ルームのスコープの `roomStart` に登録されている必要があります。

クライアント関数の呼び出し

クライアント上の Red5 アプリケーションから関数を呼ぶには、まず現在のコネクションオブジェクトへの参照が必要です。

import org.red5.server.api.IConnection;
import org.red5.server.api.Red5;
import org.red5.server.api.service.IServiceCapableConnection;
...
IConnection conn = Red5.getConnectionLocal();

コネクションが IServiceCapableConnection_ インターフェイスを実装している場合、他の端末上での関数呼び出しをサポートします。

if (conn instanceof IServiceCapableConnection) {
    IServiceCapableConnection sc = (IServiceCapableConnection) conn;
    sc.invoke("the_method", new Object[]{"One", 1});
}

関数呼び出しの結果が必要な場合は、IPendingServiceCallback_ インターフェイスを実装しているクラスを準備しなければなりません。

import org.red5.server.api.service.IPendingService;
import org.red5.server.api.service.IPendingServiceCallback;

class MyCallback implements IPendingServiceCallback {

    public void resultReceived(IPendingServiceCall call) { 
        // "call.getResult()" でなんかする
    }
}

関数呼び出しは丁度このようになります::

if (conn instanceof IServiceCapableConnection) {
    IServiceCapableConnection sc = (IServiceCapableConnection) conn;
    sc.invoke("the_method", new Object[]{"One", 1}, new MyCallback());
}

もちろんアプリケーションにこのインターフェイスを実装してアプリケーションのインスタンスへの参照を渡すことができます。

シェアードオブジェクト

アプリケーションにおけるシェアードオブジェクトへのアクセス関数はインターフェイス ISharedObjectService_ で定義されています。

サーバー側のスクリプトでシェアードオブジェクトを分配する時、生成されたスコープについて特に注意する必要があります。

ルームの作成時に新しいシェアードオブジェクトを作るために、アプリケーションの関数 `roomStart` をオーバーライドすることができます。

import org.red5.server.adapter.ApplicationAdapter;
import org.red5.server.api.IScope;
import org.red5.server.api.so.ISharedObject;

public class SampleApplication extends ApplicationAdapter {

  public boolean roomStart(IScope room) {
      if (!super.roomStart(room))
          return false;
      
      createSharedObject(room, "sampleSO", true);
      ISharedObject so = getSharedObject(room, "sampleSO");
      
      // ここでシェアードオブジェクトを使ってなんかする...
      
      return true;
  }
  
}

この場合、例えば `rtmp://server/application/room1` のようなアプリケーションのルームへ最初のユーザーが接続するたび、シェアードオブジェクト `sampleSO` がサーバーで生成されます。

たとえば `rtmp://server/application` のようなメインアプリケーションへ接続した時にシェアードオブジェクトが生成されなければならない場合、関数 `appStart` 内で同じことをする必要があります。

シェアードオブジェクトが提供する利用可能な関数に関するこれ以上の情報は、インターフェイス ISharedObject_ の API ドキュメントを参照してください。

サーバー側の更新の受信

FCS / FMS における `onSync` のようにしてシェアードオブジェクトの変更通知を取得するには、受信側がインターフェイス ISharedObjectListener_ を実装している必要があります。

import org.red5.server.api.so.ISharedObject;
import org.red5.server.api.so.ISharedObjectListener;

public class SampleSharedObjectListener
       implements ISharedObjectListener {

  public void onSharedObjectUpdate(ISharedObject so,
                                   String key, Object value) {
      // シェアードオブジェクト <so> の <key> 属性が
      // <value> で変更された
  }

  public void onSharedObjectDelete(ISharedObject so, String key) {
      // シェアードオブジェクト <so> の <key> 属性が削除された
  }

  public void onSharedObjectSend(ISharedObject so,
                                 String method, List params) {
      // シェアードオブジェクト <so> のハンドラー <method> が
      // パラメーター <params> で呼ばれた
  }
  
  // インターフェイスに記載された他の関数群...
}

更に、受信側はシェアードオブジェクトの登録も行わなければなりません::

ISharedObject so = getSharedObject(scope, "sampleSO");
so.addSharedObjectListener(new SampleSharedObjectListener())
アプリケーションコードにおける変更

更に、シェアードオブジェクトはサーバー側で変更することもできます::

ISharedObject so = getSharedObject(scope, "sampleSO");
so.setAttribute("fullname", "Sample user");

この場合、登録されたハンドラーだけでなく登録されたクライアントも属性の追加や更新の通知を受けることができます。

シェアードオブジェクトに対する複数の動作が、登録クライアントへの一度の更新イベント内で一度に処理される場合、関数 `beginUpdate` と `endUpdate` を使わなければなりません::

ISharedObject so = getSharedObject(scope, "sampleSO");
so.beginUpdate();
so.setAttribute("One", "1");
so.setAttribute("Two", "2");
so.removeAttribute("Three");
so.endUpdate();

サーバー側では `beginUpdate` と `endUpdate` を除いて関数呼び出しごとに分割した上で更新通知を受け取ります。

シェアードオブジェクトイベントのハンドラー

Flash クライアントから `remote_so.send(<handler>, <args>)` を通じたシェアードオブジェクトの呼び出しを行う場合、もしくはサーバー側の呼び出しを行う場合、Red5 の関数にマッピングすることができます。そのため、ハンドラーはアプリケーションハンドラーのように ISharedObjectHandlerProvider_ インターフェイスの関数を通じて登録を行わなければなりません::

package com.fancycode.red5;

class MySharedObjectHandler {

    public void myMethod(String arg1) {
        // ここでなんかする
    }
    
}

...
ISharedObject so = getSharedObject(scope, "sampleSO");
so.registerServiceHandler(new MySharedObjectHandler());

更に、ハンドラーに名前を付けて登録することもできます::

ISharedObject so = getSharedObject(scope, "sampleSO");
so.registerServiceHandler("one.two", new MySharedObjectHandler());

この場合、関数は `one.two.myMethod` として呼び出すことができます。

シェアードオブジェクトのイベントハンドラーを定義する他の方法は、ファイルを使ったアプリケーションハンドラーのように `red5-web.xml` へハンドラーを追加することです。`<SharedObjectName>.<DottedServiceName>.soservice` といった命名規則でなければならないため、前述のサンプルを同じように定義するには次のようにします::

<bean id="sampleSO.one.two.soservice" 
      class="com.fancycode.red5.MySharedObjectHandler" 
      singleton="true" />
パーシステンス

パーシステンスを使うとサーバーを再起動してもそのままオブジェクトのプロパティが使用できるようになります。FCS / FMS では通常、サーバーサイドのローカルシェアードオブジェクトを使用してこれを実現しています。

Red5 はオブジェクトを自由にパーシステントにすることができ、そのために必要なものは全てインターフェイス IPersistable_ に実装されています。基本的にこれらのオブジェクトは `type`、`path`、`name` (全て文字列)を持ち、自分自身をシリアライズ・デシリアライズする方法を知っています。

シリアライズ・デシリアライズするサンプルを示します::

import java.io.IOException;
import org.red5.io.object.Input;
import org.red5.io.object.Output;
import org.red5.server.api.persistence.IPersistable;

class MyPersistentObject implements IPersistable {

  // パーシステンスな感じに生成された属性
  private String data = "My persistent value";

  void serialize(Output output) throws IOException {
      // オブジェクトの情報をセーブ。
      output.writeString(data);
  }
  
  void deserialize(Input input) throws IOException {
      // オブジェクトの情報をロード。
      data = input.readString();
  }
  
  // インターフェイスに記載された関数群...
}

以下のコードでこのオブジェクトに対するセーブやロードを行うことができます::

import org.red5.server.adapter.ApplicationAdapter;
import org.red5.server.api.IScope;
import org.red5.server.api.Red5;
import org.red5.server.api.persistence.IPersistenceStore;

class MyApplication extends ApplicationAdapter {

  private void saveObject(MyPersistentObject object) {
      // 現在のスコープを取得。
      IScope scope = Red5.getConnectionLocal().getScope();
      // 現在のスコープのオブジェクトを保存。
      // Save object in current scope.
      scope.getStore().save(object);
  }

  private void loadObject(MyPersistentObject object) {
      // 現在のスコープを取得。
      IScope scope = Red5.getConnectionLocal().getScope();
      // 現在のスコープからオブジェクトをロード。
      scope.getStore().load(object);
  }
  
}

アプリケーションにカスタムオブジェクト(訳注: 動的に登録されたオブジェクト、という意味だろうか)が必要ではなかったがデータを後で再利用するために保存しなければならない場合、インターフェイス IAttributeStore_ を通じて IScope_ に追加することができます。スコープ内では、`IPersistable.TRANSIENT_PREFIX` で始まらない全ての属性はパーシステンスです。

オブジェクトを保存するために使われる処理は各種設定が可能です。デフォルトのパーシステンスではメモリとファイルシステムが使用可能です。

全てのオブジェクトでファイルシステムのパーシステンスを使う場合、ファイルは "webapps/<app>/persistence/<type>/<path>/<name>.red5" に生成されます。例えば、"rtmp://server/myApp/room1" に接続しシェアードオブジェクト "theSO" を保存する場合は"webapps/myApp/persistence/SharedObject/room1/theSO.red5" にファイルが生成されます。

定期イベント

定期的にタスクを実行しなければならないアプリケーションは、FCS/ FMS で定期的に関数を実行するスケジュールを決定するために実装されている `setInterval` を使うことができます。

Red5 は他の多くのサービスに似た形でスケジューリングサービス(ISchedulingService_)を ApplicationAdapter_ に実装しています。そのサービスは `execute` 関数が一定の間隔で呼び出されるオブジェクト(IScheduledJob_ インターフェイスを実装している必要があります)を登録することができます。

次のようなコードでオブジェクトを登録できます::

import org.red5.server.api.IScope;
import org.red5.server.api.IScheduledJob;
import org.red5.server.api.ISchedulingService;
import org.red5.server.adapter.ApplicationAdapter;

class MyJob implements IScheduledJob {

  public void execute(ISchedulingService service) {
      // なんかする
  }
}

public class SampleApplication extends ApplicationAdapter {

  public boolean roomStart(IScope room) {
      if (!super.roomStart(room))
          return false;
      
      // 十秒ごとにスケジュールを実行するよう予定を入れる。
      String id = addScheduledJob(10000, new MyJob());
      room.setAttribute("MyJobId", id);
      return true;
  }
}

`addScheduledJob` が返す ID は後で登録したジョブを停止するのに使うことができます::

public void roomStop(IScope room) {
    String id = (String) room.getAttribute("MyJobId");
    removeScheduledJob(id);
    super.roomStop(room);
}
リモート

リモートは RTMP ではないクライアントが Red5 の関数を起動するために使うことができます。他の可能性として、Red5 から遠隔サービスを提供する他のサーバーの関数を呼ぶことができます。

リモートサーバー

クライアントを利用できなければならないサービスはアプリケーションハンドラーの登録の追加と同じように登録できる必要があります。詳細は前述の情報を見てください。

アプリケーションのリモートサポートを有効にするには、`WEB-INF/web.xml` ファイルに次の項を追加しなければなりません。

<servlet>
  <servlet-name>gateway</servlet-name>
  <servlet-class>
      org.red5.server.net.servlet.AMFGatewayServlet
  </servlet-class>
</servlet>
  
<servlet-mapping>
  <servlet-name>gateway</servlet-name>
  <url-pattern>/gateway/*</url-pattern>
</servlet-mapping>

`<url-pattern>` タグで定義されているパス(この場合 `gateway`)は接続 URL としてリモートのクライアントから利用することができます。このサンプルがアプリケーション `myApp` で定義されていたなら、URL はこうなるでしょう::

http://localhost:5080/myApp/gateway

関数はアプリケーションのスコープの中で作成されたコネクションを通じて呼び出されます。関数をサブスコープで実行しなければならない場合、サブスコープのパスをこのように URL に追加しなければなりません::

http://localhost:5080/myApp/gateway/room1/room2
リモートクライアント

クラス RemotingClient_ はリモートプロトコルを通じて関数を呼び出す必要のある全ての関数を定義しています。

以下のコードはリモートクライアントの使い方の例です::

import org.red5.server.net.remoting.RemotingClient;

String url = "http://server/path/to/service";
RemotingClient client = new RemotingClient(url);
Object[] args = new Object[]{"Hello world!"};
Object result = client.invokeMethod("service.remotingMethod", args);
// ここで結果を返したりする

デフォルトでは、呼び出しごとに 30秒でタイムアウトしますが、コンストラクタの第二引数にミリ秒で最大タイムアウト時間を渡して変更することができます。

リモートヘッダーの `AppendToGatewayUrl`、`ReplaceGatewayUrl`、`RequestPersistentHeader` は Red5 のリモートクライアントで自動的に処理されます。

いくつかの関数はサーバー呼び出し完了までにかなり長い時間を要すため、Red5 のスレッドの邪魔にならないように非同期呼び出しを実行した方が良いでしょう。そのため、インターフェイス IRemotingCallback_ を実装したオブジェクトには追加のパラメーターを渡さなければなりません::

import org.red5.server.net.remoting.RemotingClient;
import org.red5.server.net.remoting.IRemotingCallback;

public class CallbackHandler implements IRemotingCallback {

  void errorReceived(RemotingClient client, String method,
                     Object[] params, Throwable error) {
      // リモート呼び出しを実行してる最中にエラー発生。
  }
  
  void resultReceived(RemotingClient client, String method,
                      Object[] params, Object result) {
      // サーバーから結果を受信。
  }
}

String url = "http://server/path/to/service";
RemotingClient client = new RemotingClient(url);
Object[] args = new Object[]{"Hello world!"};
IRemotingCallback callback = new CallbackHandler();
client.invokeMethod("service.remotingMethod", args, callback);
ストリーミング

TODO: アプリケーションからストリームにアクセスするにはどうしたらいいのだろう

現状、既に Red5 0.6.2 を改造し、Red5 内部に別サーバーのライブストリームを取得する機能を追加し終えている。その辺は長くなるのであとで書く。っていうかこの和訳文書群って今んとこかなり前に社内に書いたものの転載なのよ。