Client below is intended to run over oflaDemo application, running on red5 on localhost. It contains Avatar movie promo.
The problem is that client reads 15 seconds of movie and hangs. Why?
To compile program below, just download red5 from here http://wiki.red5.org/wiki/1_0_RC1, install it and run it. Open root page http://localhost:5080 and navigate to demos installation page. Install oflaDemo
sample. Then navigate to oflaDemo page and check it is working.
Then create new Java project with all jars from red5 as a library. Run it with running red5.
Client communicates with server via port 1935.
The structure of app follows:
1) connect()
method connects to application
2) connectCallback
on result of previous operation new stream is created; library function is not used to inject custom stream class
3) createStreamCallback
it is injecting on result of stream creation
4) custom stream is MyClientStream
; it just prints what was dispatched
On my machine I it works up to the timestamp 15203 and hangs.
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());
}
}
}
UPDATE 1
The version with conventional setStreamEventDispatcher()
behaves in the same way.
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");
}
*/
};
}
UPDATE 2
I found that packets start to drop after the hang occurs. The drop method is RTMPProtocolEncoder#dropMessage()
UPDATE 3
I see that 'tardiness` is increasing at realtime rate. When it exceeds value of 8000, dropping starts.
UPDATE 4
More precisely, at the server side the process is starting to drop packets approximately after 8 seconds passed. This is probably the value of 8000 which is tolerance time. At the same moment the packet timestamp is reaching approx 15-16 seconds. Client is playing until this time and stops only then.
So the picture it that server outruns client 2 times and when it reaches some limit, it does not wait but starting to drop packets.
It looks like that correct behaviour would wait until client reaches timestamp and continue....
UPDATE 5
Probably Client Stream class does not intended to listen stream from server and so does not contain appropriate synchronization logic?
MAGIC SOLUTION
While observing the differences in the log while playing stream with oflaDemo client and my application, I found that standard client reports buffer size of 5000 ms while my client does not. I don't understand how this can play, but when I added RTMP ping to my application, it starts to work. The magic line follows
conn.ping(new Ping(Ping.CLIENT_BUFFER, streamId, 5000));
Since it's role is just to shift tardiness by it's value, I suppose that overall problem is due to red5 bug, which makes it unable to calculate tardiness correctly.