Newer
Older
TUve-iPhone / Classes / RTMPStream.m
@phausler phausler on 18 Sep 2008 10 KB Packet encapsulation methods started,
//
//  RTMPStream.m
//  Tuve
//
//  Created by Philippe Hausler on 9/10/08.
//  Copyright 2008 Philippe Hausler. All rights reserved.
//

#import "RTMPStream.h"
#import "NSData-BytePacking.h"
const unsigned short RTMP_DEFAULT_PORT = 1935;
/*Constants sourced from VLC's rtmp_amf_flv.c*/
const unsigned char RTMP_HANDSHAKE = 0x03;
const unsigned short RTMP_HANDSHAKE_BODY_SIZE = 1536;

/* content types */
const unsigned char RTMP_CONTENT_TYPE_CHUNK_SIZE = 0x01;
const unsigned char RTMP_CONTENT_TYPE_UNKNOWN_02 = 0x02;
const unsigned char RTMP_CONTENT_TYPE_BYTES_READ = 0x03;
const unsigned char RTMP_CONTENT_TYPE_PING = 0x04;
const unsigned char RTMP_CONTENT_TYPE_SERVER_BW = 0x05;
const unsigned char RTMP_CONTENT_TYPE_CLIENT_BW = 0x06;
const unsigned char RTMP_CONTENT_TYPE_UNKNOWN_07 = 0x07;
const unsigned char RTMP_CONTENT_TYPE_AUDIO_DATA = 0x08;
const unsigned char RTMP_CONTENT_TYPE_VIDEO_DATA = 0x09;
const unsigned char RTMP_CONTENT_TYPE_UNKNOWN_0A_0E = 0x0A;
const unsigned char RTMP_CONTENT_TYPE_FLEX_STREAM = 0x0F;
const unsigned char RTMP_CONTENT_TYPE_FLEX_SHARED_OBJECT = 0x10;
const unsigned char RTMP_CONTENT_TYPE_MESSAGE = 0x11;
const unsigned char RTMP_CONTENT_TYPE_NOTIFY = 0x12;
const unsigned char RTMP_CONTENT_TYPE_SHARED_OBJECT = 0x13;
const unsigned char RTMP_CONTENT_TYPE_INVOKE = 0x14;

const unsigned char  AMF_BOOLEAN_FALSE = 0x00;
const unsigned char  AMF_BOOLEAN_TRUE = 0x01;

/* datatypes */
const unsigned char  AMF_DATATYPE_NUMBER = 0x00;
const unsigned char  AMF_DATATYPE_BOOLEAN = 0x01;
const unsigned char  AMF_DATATYPE_STRING = 0x02;
const unsigned char  AMF_DATATYPE_OBJECT = 0x03;
const unsigned char  AMF_DATATYPE_MOVIE_CLIP = 0x04;
const unsigned char  AMF_DATATYPE_NULL = 0x05;
const unsigned char  AMF_DATATYPE_UNDEFINED = 0x06;
const unsigned char  AMF_DATATYPE_REFERENCE = 0x07;
const unsigned char  AMF_DATATYPE_MIXED_ARRAY = 0x08;
const unsigned char  AMF_DATATYPE_END_OF_OBJECT = 0x09;
const unsigned char  AMF_DATATYPE_ARRAY = 0x0A;
const unsigned char  AMF_DATATYPE_DATE = 0x0B;
const unsigned char  AMF_DATATYPE_LONG_STRING = 0x0C;
const unsigned char  AMF_DATATYPE_UNSUPPORTED = 0x0D;
const unsigned char  AMF_DATATYPE_RECORDSET = 0x0E;
const unsigned char  AMF_DATATYPE_XML = 0x0F;
const unsigned char  AMF_DATATYPE_TYPED_OBJECT = 0x10;
const unsigned char  AMF_DATATYPE_AMF3_DATA = 0x11;

/* datatypes sizes */
const unsigned char  AMF_DATATYPE_SIZE_NUMBER = 9;
const unsigned char  AMF_DATATYPE_SIZE_BOOLEAN = 2;
const unsigned char  AMF_DATATYPE_SIZE_STRING = 3;
const unsigned char  AMF_DATATYPE_SIZE_OBJECT = 1;
const unsigned char  AMF_DATATYPE_SIZE_NULL = 1;
const unsigned char  AMF_DATATYPE_SIZE_OBJECT_VARIABLE = 2;
const unsigned char  AMF_DATATYPE_SIZE_MIXED_ARRAY = 5;
const unsigned char  AMF_DATATYPE_SIZE_END_OF_OBJECT = 3;

/* amf remote calls */
const unsigned long long AMF_CALL_NETCONNECTION_CONNECT = 0x3FF0000000000000;
const unsigned long long AMF_CALL_NETCONNECTION_CONNECT_AUDIOCODECS = 0x4083380000000000;
const unsigned long long AMF_CALL_NETCONNECTION_CONNECT_VIDEOCODECS = 0x405F000000000000;
const unsigned long long AMF_CALL_NETCONNECTION_CONNECT_VIDEOFUNCTION = 0x3FF0000000000000;
const unsigned long long AMF_CALL_NETCONNECTION_CONNECT_OBJECTENCODING = 0x0;
const double AMF_CALL_STREAM_CLIENT_NUMBER = 3.0;
const double AMF_CALL_ONBWDONE = 2.0; 
const unsigned long long AMF_CALL_NETSTREAM_PLAY = 0x0;

/*Private calls not visible to the fellow classes*/
@interface RTMPStream (Private)
- (void)connectToServer:(NSString *)server onPort:(unsigned short)port;

- (void)sendHandshake;
- (void)disconnect;
- (void)sendConnectRequest;
- (void)waitForHandshakeResponse;

- (void)sendPacket:(NSData *)packet type:(unsigned char)type;
- (NSData *)encodePacket:(NSData *)packet type:(unsigned char)type;

- (NSData *)encodeAMFString:(NSString *)str;
- (NSData *)encodeAMFNumber:(unsigned long long)number;
- (NSData *)encodeAMFBoolean:(BOOL)value;
- (NSData *)encodeAMFObject:(NSData *)object;
- (NSData *)encodeAMFEndObject;

- (NSData *)encodeAMFStringVariable:(NSString *)value key:(NSString *)key;
- (NSData *)encodeAMFNumberVariable:(unsigned long long)value key:(NSString *)key;
- (NSData *)encodeAMFBooleanVariable:(BOOL)value key:(NSString *)key;
@end
@implementation RTMPStream
- (NSURL *)streamURL
{
	return streamURL;
}
- (void)setStreamURL:(NSURL *)aStreamURL
{
	streamURL = [aStreamURL copy];
	unsigned short port = RTMP_DEFAULT_PORT;
	if([streamURL port])
	{
		port = [[streamURL port] unsignedShortValue];
	}
	[self connectToServer:[streamURL host] onPort:port];
}
- (void)connectToServer:(NSString *)server onPort:(unsigned short)port
{
	//if for some reason we are re-connecting to the server, the socket, buffer, and file handle need to be invalidated and re-created
	[connection release];
	[socket release];
	[buffer release];
	buffer = [[NSMutableData data] retain];
	socket = [[TCPSocket alloc] initWithConnectionTo:server onTCPPort:port];
	connection = [[NSFileHandle alloc] initWithFileDescriptor:[socket socket]];
	[self sendHandshake];
}
- (void)dataAvailable:(NSNotification *)aNotification
{
	
	[connection waitForDataInBackgroundAndNotify];
}

- (void)sendHandshake
{
	NSMutableData *packet = [NSMutableData data];
	[packet appendByte:RTMP_HANDSHAKE];
	for(int i = 0; i < RTMP_HANDSHAKE_BODY_SIZE; i++)
	{
		[packet appendByte:i & 0xFF];
	}
	[connection writeData:packet];
	[self waitForHandshakeResponse];
}
- (void)waitForHandshakeResponse
{
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handshakeResponse:) name:NSFileHandleDataAvailableNotification object:NULL];
	[connection waitForDataInBackgroundAndNotify];
}
- (void)handshakeResponse:(NSNotification *)aNotification
{
	[buffer appendData:[connection availableData]];
	if([buffer length] == RTMP_HANDSHAKE_BODY_SIZE * 2 + 1) //if the server has sent a response of the appropriate size we need to step forward into the protocol
	{
		[[NSNotificationCenter defaultCenter] removeObserver:self name:NSFileHandleDataAvailableNotification object:NULL]; //the notification of the handshakeResponse is no longer valid, remove it from the center
		if(![buffer hasPrefix:[NSData dataWithByte:RTMP_HANDSHAKE]])
		{
			NSLog(@"Massive error: Protocol breach from the server side. Packet header RTMP_HANDSHAKE expected but not received. Aborting connection");
			[self disconnect];
		}
		else
		{
			//Acknowege the handshake with the first 1935 bytes after the header from the server response pair
			[connection writeData:[buffer subdataWithRange:NSMakeRange(1, RTMP_HANDSHAKE_BODY_SIZE)]];
			[self sendConnectRequest];
		}
	}
	else
	{
		[connection waitForDataInBackgroundAndNotify];
	}
}
- (void)sendConnectRequest
{
	NSMutableData *packet = [NSMutableData data];
	[packet appendData:[self encodeAMFString:@"connect"]];
	[packet appendData:[self encodeAMFNumber:AMF_CALL_NETCONNECTION_CONNECT]];
	[packet appendData:[self encodeAMFObject:NULL]];
	[packet appendData:[self encodeAMFStringVariable:@"TUvé" key:@"app"]];
	[packet appendData:[self encodeAMFStringVariable:@"LNX 9,0,48,0" key:@"flashVer"]];
	[packet appendData:[self encodeAMFStringVariable:@"file://iphone.flv" key:@"swfUrl"]];
	[packet appendData:[self encodeAMFStringVariable:[streamURL absoluteString] key:@"tcUrl"]];
	[packet appendData:[self encodeAMFBooleanVariable:FALSE key:@"fpad"]];
	[packet appendData:[self encodeAMFNumberVariable:AMF_CALL_NETCONNECTION_CONNECT_AUDIOCODECS key:@"audioCodecs"]];
	[packet appendData:[self encodeAMFNumberVariable:AMF_CALL_NETCONNECTION_CONNECT_VIDEOCODECS key:@"videoCodecs"]];
	[packet appendData:[self encodeAMFNumberVariable:AMF_CALL_NETCONNECTION_CONNECT_VIDEOFUNCTION key:@"videoFunction"]];
	[packet appendData:[self encodeAMFStringVariable:@"file://iphone.html" key:@"pageUrl"]];
	[packet appendData:[self encodeAMFNumberVariable:AMF_CALL_NETCONNECTION_CONNECT_OBJECTENCODING key:@"objectEncoding"]];
	[packet appendData:[self encodeAMFEndObject]];
	[self sendPacket:packet type:RTMP_CONTENT_TYPE_INVOKE];
}
- (void)sendPacket:(NSData *)packet type:(unsigned char)type
{
	[connection writeData:[self encodePacket:connection type:type]]; 
}
- (NSData *)encodePacket:(NSData *)packet type:(unsigned char)type
{
}
- (NSData *)encodeAMFString:(NSString *)str
{
	NSMutableData *encodedString = [NSMutableData dataWithByte:AMF_DATATYPE_STRING];
	unsigned short len = [str length];
	[encodedString appendUShort:len ordered:NSBigEndian];
	[encodedString appendData:[str dataUsingEncoding:NSUTF8StringEncoding]];
	return encodedString;
}
- (NSData *)encodeAMFNumber:(unsigned long long)number
{
	NSMutableData *encodedNumber = [NSMutableData dataWithByte:AMF_DATATYPE_NUMBER];
	[encodedNumber appendULongLong:number];
	return encodedNumber;
}
- (NSData *)encodeAMFBoolean:(BOOL)value
{
	NSMutableData *encodedBoolean = [NSMutableData dataWithByte:AMF_DATATYPE_BOOLEAN];
	if(value)
	{
		[encodedBoolean appendByte:AMF_BOOLEAN_TRUE];
	}
	else
	{
		[encodedBoolean appendByte:AMF_BOOLEAN_FALSE];
	}
	return encodedBoolean;
}
- (NSData *)encodeAMFObject:(NSData *)object
{
	NSMutableData *encodedObject = [NSMutableData dataWithByte:AMF_DATATYPE_OBJECT];
	[encodedObject pad:[NSData dataWithByte:0x00] count:AMF_DATATYPE_SIZE_OBJECT-1];
	return encodedObject;
}
- (NSData *)encodeAMFEndObject
{
	NSMutableData *encodedObject = [NSMutableData data];
	[encodedObject pad:[NSData dataWithByte:0x00] count:AMF_DATATYPE_SIZE_END_OF_OBJECT - 1];
	[encodedObject appendByte:AMF_DATATYPE_END_OF_OBJECT];
	return encodedObject;
}
- (NSData *)encodeAMFStringVariable:(NSString *)value key:(NSString *)key
{
	NSMutableData *encodedVariable = [NSMutableData data];
	[encodedVariable appendUShort:[key length] ordered:NSBigEndian];
	[encodedVariable appendString:key encoding:NSUTF8StringEncoding];
	[encodedVariable appendData:[self encodeAMFString:value]];
	return encodedVariable;
}
- (NSData *)encodeAMFNumberVariable:(unsigned long long)value key:(NSString *)key
{
	NSMutableData *encodedVariable = [NSMutableData data];
	[encodedVariable appendUShort:[key length] ordered:NSBigEndian];
	[encodedVariable appendString:key encoding:NSUTF8StringEncoding];
	[encodedVariable appendData:[self encodeAMFNumber:value]];
	return encodedVariable;
}
- (NSData *)encodeAMFBooleanVariable:(BOOL)value key:(NSString *)key
{
	NSMutableData *encodedVariable = [NSMutableData data];
	[encodedVariable appendUShort:[key length] ordered:NSBigEndian];
	[encodedVariable appendString:key encoding:NSUTF8StringEncoding];
	[encodedVariable appendData:[self encodeAMFBoolean:value]];
	return encodedVariable;
}

@end