package com.laifeng.sopcastsdk.stream.sender.rtmp.packets;
import com.laifeng.sopcastsdk.stream.sender.rtmp.Util;
import com.laifeng.sopcastsdk.stream.sender.rtmp.io.SessionInfo;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
/**
* User Control message, such as ping
*
* @author francois
*/
public class UserControl extends Chunk {
/**
* Control message type
* Docstring adapted from the official Adobe RTMP spec, section 3.7
*/
public static enum Type {
/**
* Type: 0
* The server sends this event to notify the client that a stream has become
* functional and can be used for communication. By default, this event
* is sent on ID 0 after the application connect command is successfully
* received from the client.
*
* Event Data:
* eventData[0] (int) the stream ID of the stream that became functional
*/
STREAM_BEGIN(0),
/**
* Type: 1
* The server sends this event to notify the client that the playback of
* data is over as requested on this stream. No more data is sent without
* issuing additional commands. The client discards the messages received
* for the stream.
*
* Event Data:
* eventData[0]: the ID of thestream on which playback has ended.
*/
STREAM_EOF(1),
/**
* Type: 2
* The server sends this event to notify the client that there is no
* more data on the stream. If the server does not detect any message for
* a time period, it can notify the subscribed clients that the stream is
* dry.
*
* Event Data:
* eventData[0]: the stream ID of the dry stream.
*/
STREAM_DRY(2),
/**
* Type: 3
* The client sends this event to inform the server of the buffer size
* (in milliseconds) that is used to buffer any data coming over a stream.
* This event is sent before the server starts processing the stream.
*
* Event Data:
* eventData[0]: the stream ID and
* eventData[1]: the buffer length, in milliseconds.
*/
SET_BUFFER_LENGTH(3),
/**
* Type: 4
* The server sends this event to notify the client that the stream is a
* recorded stream.
*
* Event Data:
* eventData[0]: the stream ID of the recorded stream.
*/
STREAM_IS_RECORDED(4),
/**
* Type: 6
* The server sends this event to test whether the client is reachable.
*
* Event Data:
* eventData[0]: a timestamp representing the local server time when the server dispatched the command.
*
* The client responds with PING_RESPONSE on receiving PING_REQUEST.
*/
PING_REQUEST(6),
/**
* Type: 7
* The client sends this event to the server in response to the ping request.
*
* Event Data:
* eventData[0]: the 4-byte timestamp which was received with the PING_REQUEST.
*/
PONG_REPLY(7),
/**
* Type: 31 (0x1F)
*
* This user control type is not specified in any official documentation, but
* is sent by Flash Media Server 3.5. Thanks to the rtmpdump devs for their
* explanation:
*
* Buffer Empty (unofficial name): After the server has sent a complete buffer, and
* sends this Buffer Empty message, it will wait until the play
* duration of that buffer has passed before sending a new buffer.
* The Buffer Ready message will be sent when the new buffer starts.
*
* (see also: http://repo.or.cz/w/rtmpdump.git/blob/8880d1456b282ee79979adbe7b6a6eb8ad371081:/librtmp/rtmp.c#l2787)
*/
BUFFER_EMPTY(31),
/**
* Type: 32 (0x20)
*
* This user control type is not specified in any official documentation, but
* is sent by Flash Media Server 3.5. Thanks to the rtmpdump devs for their
* explanation:
*
* Buffer Ready (unofficial name): After the server has sent a complete buffer, and
* sends a Buffer Empty message, it will wait until the play
* duration of that buffer has passed before sending a new buffer.
* The Buffer Ready message will be sent when the new buffer starts.
* (There is no BufferReady message for the very first buffer;
* presumably the Stream Begin message is sufficient for that
* purpose.)
*
* (see also: http://repo.or.cz/w/rtmpdump.git/blob/8880d1456b282ee79979adbe7b6a6eb8ad371081:/librtmp/rtmp.c#l2787)
*/
BUFFER_READY(32);
private int intValue;
private static final Map<Integer, Type> quickLookupMap = new HashMap<Integer, Type>();
static {
for (Type type : Type.values()) {
quickLookupMap.put(type.getIntValue(), type);
}
}
private Type(int intValue) {
this.intValue = intValue;
}
public int getIntValue() {
return intValue;
}
public static Type valueOf(int intValue) {
return quickLookupMap.get(intValue);
}
}
private Type type;
private int[] eventData;
public UserControl(ChunkHeader header) {
super(header);
}
public UserControl() {
super(new ChunkHeader(ChunkType.TYPE_0_FULL, SessionInfo.RTMP_CONTROL_CHANNEL, MessageType.USER_CONTROL_MESSAGE));
}
/** Convenience construtor that creates a "pong" message for the specified ping */
public UserControl(UserControl replyToPing) {
this(Type.PONG_REPLY);
this.eventData = replyToPing.eventData;
}
public UserControl(Type type) {
this();
this.type = type;
}
public Type getType() {
return type;
}
public void setType(Type type) {
this.type = type;
}
/**
* Convenience method for getting the first event data item, as most user control
* message types only have one event data item anyway
* This is equivalent to calling <code>getEventData()[0]</code>
*/
public int getFirstEventData() {
return eventData[0];
}
public int[] getEventData() {
return eventData;
}
/** Used to set (a single) event data for most user control message types */
public void setEventData(int eventData) {
if (type == Type.SET_BUFFER_LENGTH) {
throw new IllegalStateException("SET_BUFFER_LENGTH requires two event data values; use setEventData(int, int) instead");
}
this.eventData = new int[]{eventData};
}
/** Used to set event data for the SET_BUFFER_LENGTH user control message types */
public void setEventData(int streamId, int bufferLength) {
if (type != Type.SET_BUFFER_LENGTH) {
throw new IllegalStateException("User control type " + type + " requires only one event data value; use setEventData(int) instead");
}
this.eventData = new int[]{streamId, bufferLength};
}
@Override
public void readBody(InputStream in) throws IOException {
// Bytes 0-1: first parameter: ping type (mandatory)
type = Type.valueOf(Util.readUnsignedInt16(in));
int bytesRead = 2;
// Event data (1 for most types, 2 for SET_BUFFER_LENGTH)
if (type == Type.SET_BUFFER_LENGTH) {
setEventData(Util.readUnsignedInt32(in), Util.readUnsignedInt32(in));
bytesRead += 8;
} else {
setEventData(Util.readUnsignedInt32(in));
bytesRead += 4;
}
// To ensure some strange non-specified UserControl/ping message does not slip through
assert header.getPacketLength() == bytesRead;
}
@Override
protected void writeBody(OutputStream out) throws IOException {
// Write the user control message type
Util.writeUnsignedInt16(out, type.getIntValue());
// Now write the event data
Util.writeUnsignedInt32(out, eventData[0]);
if (type == Type.SET_BUFFER_LENGTH) {
Util.writeUnsignedInt32(out, eventData[1]);
}
}
@Override
public String toString() {
return "RTMP User Control (type: " + type + ", event data: " + eventData + ")";
}
}