以下のクライアントは、localhost の red5 で実行されている oflaDemo アプリケーションで実行することを目的としています。アバター映画のプロモーションが含まれています。
問題は、クライアントが 15 秒のムービーを読み取ってハングすることです。なんで?
以下のプログラムをコンパイルするには、 http: //wiki.red5.org/wiki/1_0_RC1 から red5 をダウンロードし、インストールして実行します。ルート ページhttp://localhost:5080を開き、デモのインストール ページに移動します。サンプルをインストールしoflaDemo
ます。次に、oflaDemo ページに移動して、動作していることを確認します。
次に、red5 のすべての jar をライブラリとして使用して、新しい Java プロジェクトを作成します。red5 を実行して実行します。
クライアントはポート 1935 経由でサーバーと通信します。
アプリの構造は次のとおりです。
1)connect()
メソッドがアプリケーションに接続する
2)connectCallback
前の操作の結果、新しいストリームが作成されます。ライブラリ関数は、カスタム ストリーム クラスを挿入するために使用されません
3)createStreamCallback
ストリーム作成の結果に注入しています
4) カスタムストリームはMyClientStream
; 発送されたものを印刷するだけです
私のマシンでは、タイムスタンプ 15203 まで動作し、ハングします。
public class SSCCE_RTMPPlayer extends RTMPClient{
private String server = "localhost";
private int port = 1935;
private String application = "oflaDemo";
private String filename = "avatar.flv";
private static boolean finished = false;
public static void main(String[] args) throws InterruptedException {
final SSCCE_RTMPPlayer player = new SSCCE_RTMPPlayer ();
player.connect();
synchronized( SSCCE_RTMPPlayer.class ) {
if( !finished ) SSCCE_RTMPPlayer.class.wait();
}
System.out.println("Ended");
}
public void connect() {
connect(server, port, application, connectCallback);
setExceptionHandler(new ClientExceptionHandler() {
@Override
public void handleException(Throwable throwable) {
throwable.printStackTrace();
}
});
}
private IPendingServiceCallback connectCallback = new IPendingServiceCallback() {
@Override
public void resultReceived(IPendingServiceCall call) {
System.out.println("connectCallback");
invoke("createStream", null, createStreamCallback);
}
};
private IPendingServiceCallback createStreamCallback = new IPendingServiceCallback() {
@Override
public void resultReceived(IPendingServiceCall call) {
Integer streamIdInteger = (Integer) call.getResult();
MyClientStream myClientStream = new MyClientStream();
myClientStream.setStreamId(streamIdInteger.intValue());
myClientStream.setConnection(conn);
conn.addClientStream(myClientStream);
play(streamIdInteger.intValue(), filename, 0, -2);
}
};
protected void onInvoke(RTMPConnection conn, Channel channel, Header header, Notify notify, RTMP rtmp) {
super.onInvoke(conn, channel, header, notify, rtmp);
System.out.println("onInvoke, header = " + header.toString());
System.out.println("onInvoke, notify = " + notify.toString());
System.out.println("onInvoke, rtmp = " + rtmp.toString());
};
public static class MyClientStream extends AbstractClientStream implements IEventDispatcher {
@Override
public void start() {
// TODO Auto-generated method stub
}
@Override
public void stop() {
// TODO Auto-generated method stub
}
@Override
public void close() {
// TODO Auto-generated method stub
}
@Override
public void dispatchEvent(IEvent event) {
System.out.println("AudioListenerClientStream.dispachEvent()" + event.toString());
}
}
}
更新 1
従来のバージョンsetStreamEventDispatcher()
も同じように動作します。
public class SSCCE_RTMPPlayer2 extends RTMPClient {
private String server = "localhost";
private int port = 1935;
private String application = "oflaDemo";
private String filename = "avatar.flv";
private static boolean finished = false;
public static void main(String[] args) throws InterruptedException {
final SSCCE_RTMPPlayer2 player = new SSCCE_RTMPPlayer2();
player.connect();
synchronized( SSCCE_RTMPPlayer.class ) {
if( !finished ) SSCCE_RTMPPlayer.class.wait();
}
System.out.println("Ended");
}
public void connect() {
setExceptionHandler(new ClientExceptionHandler() {
@Override
public void handleException(Throwable throwable) {
throwable.printStackTrace();
}
});
setStreamEventDispatcher(streamEventDispatcher);
connect(server, port, application, connectCallback);
}
private IEventDispatcher streamEventDispatcher = new IEventDispatcher() {
@Override
public void dispatchEvent(IEvent event) {
System.out.println("AudioListenerClientStream.dispachEvent()" + event.toString());
}
};
private IPendingServiceCallback connectCallback = new IPendingServiceCallback() {
@Override
public void resultReceived(IPendingServiceCall call) {
System.out.println("connectCallback");
createStream(createStreamCallback);
}
};
private IPendingServiceCallback createStreamCallback = new IPendingServiceCallback() {
@Override
public void resultReceived(IPendingServiceCall call) {
Integer streamIdInteger = (Integer) call.getResult();
play(streamIdInteger.intValue(), filename, 0, -2);
}
};
protected void onInvoke(RTMPConnection conn, Channel channel, Header header, Notify notify, RTMP rtmp) {
super.onInvoke(conn, channel, header, notify, rtmp);
System.out.println("onInvoke, header = " + header.toString());
System.out.println("onInvoke, notify = " + notify.toString());
System.out.println("onInvoke, rtmp = " + rtmp.toString());
/*
ObjectMap<String, String> map = (ObjectMap) notify.getCall().getArguments()[0];
String code = map.get("code");
if (StatusCodes.NS_PLAY_STOP.equals(code)) {
synchronized( SSCCE_RTMPPlayer.class ) {
finished = true;
SSCCE_RTMPPlayer.class.notifyAll();
}
disconnect();
System.out.println("Disconnected");
}
*/
};
}
更新 2
ハングが発生した後、パケットがドロップし始めることがわかりました。ドロップ方法はRTMPProtocolEncoder#dropMessage()
更新 3
「遅刻」がリアルタイムで増えているのがわかります。値が 8000 を超えると、ドロップが開始されます。
更新 4
より正確には、サーバー側では、プロセスは約 8 秒が経過した後にパケットをドロップし始めています。これはおそらく許容時間である 8000 の値です。同時に、パケットのタイムスタンプが約 15 ~ 16 秒に達しています。クライアントはこの時間まで再生し、その後停止します。
したがって、サーバーがクライアントを 2 回追い越し、制限に達すると、待機せずにパケットをドロップし始めるという図です。
クライアントがタイムスタンプに到達して続行するまで、正しい動作が待機するように見えます....
更新 5
おそらく、クライアント ストリーム クラスはサーバーからのストリームをリッスンすることを意図していないため、適切な同期ロジックが含まれていませんか?
魔法の解決策
oflaDemo クライアントと私のアプリケーションでストリームを再生しているときにログの違いを観察しているときに、標準のクライアントは 5000 ミリ秒のバッファー サイズを報告し、私のクライアントはそうではないことがわかりました。これがどのように機能するかはわかりませんが、アプリケーションに RTMP ping を追加すると、機能し始めます。魔法のラインが続きます
conn.ping(new Ping(Ping.CLIENT_BUFFER, streamId, 5000));
役割は遅刻をその値でシフトすることなので、全体的な問題は red5 のバグによるもので、遅刻を正しく計算できないと思われます。