//
// 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