// // 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" /*Constants sourced from VLC's rtmp_amf_flv.c*/ const unsigned char RTMP_HANDSHAKE = 0x03; const unsigned short RTMP_HANDSHAKE_BODY_SIZE = 1536; 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)sendHandshake; - (void)disconnect; - (void)sendConnectRequest; - (void)waitForHandshakeResponse; - (NSData *)encodeAMFString:(NSString *)str; - (NSData *)encodeAMFNumber:(unsigned long long)number; - (NSData *)encodeAMFBoolean:(BOOL)value; - (NSData *)encodeAMFObject:(NSData *)object; - (NSData *)encodeAMFStringVariable:(NSString *)value key:(NSString *)key; - (NSData *)encodeAMFBooleanVariable:(BOOL)value key:(NSString *)key; @end @implementation RTMPStream - (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)requestStream:(NSString *)file { NSMutableData *packet = [NSMutableData data]; } - (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]; [data appendData:[self encodeAMFString:@"connect"]]; [data appendData:[self encodeAMFNumber:AMF_CALL_NETCONNECTION_CONNECT]]; [data appendData:[self encodeAMFObject:NULL]]; [data appendData:[self encodeAMFStringVariable:@"TUvé" key:@"app"]]; [data appendData:[self encodeAMFStringVariable:@"LNX 9,0,48,0" key:@"flashVer"]]; [data appendData:[self encodeAMFStringVariable:@"file://iphone.flv" key:@"swfUrl"]]; } - (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 *)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 *)encodeAMFBooleanVariable:(BOOL)value key:(NSString *)key { NSMutableData *encodedVariable = [NSMutableData data]; [encodedVariable appendUShort:[key length] ordered:NSBigEndian]; [encodedVariable appendString:key encoding:NSUTF8StringEncoding]; [encodedVariable appendData:[self encodedAMFBoolean:value]]; return encodedVariable; } @end