[rtmpdump] r12 - handshake.cpp rtmp.cpp rtmp.h rtmpdump.cpp

hyc subversion at mplayerhq.hu
Fri Oct 30 20:12:27 CET 2009


Author: hyc
Date: Fri Oct 30 20:12:27 2009
New Revision: 12

Log:
Partial merge from RTMPDump-YLE: fix buffer overruns, fix client reporting interval, attempt auto-resume

Modified:
   handshake.cpp
   rtmp.cpp
   rtmp.h
   rtmpdump.cpp

Modified: handshake.cpp
==============================================================================
--- handshake.cpp	Fri Oct 30 02:47:01 2009	(r11)
+++ handshake.cpp	Fri Oct 30 20:12:27 2009	(r12)
@@ -526,10 +526,10 @@ bool CRTMP::HandShake(bool FP9HandShake)
 		// generate signed answer
 		char clientResp[RTMP_SIG_SIZE]; 
 #ifdef _DEBUG
-        	for(int i=0; i<=RTMP_SIG_SIZE; i++)
+        	for(int i=0; i<RTMP_SIG_SIZE; i++)
                 	clientResp[i] = 0;//(char)(rand() % 256);//0xff;
 #else
-        	for(int i=0; i<=RTMP_SIG_SIZE; i++)
+        	for(int i=0; i<RTMP_SIG_SIZE; i++)
                 	clientResp[i] = (char)(rand() % 256);
 #endif
 

Modified: rtmp.cpp
==============================================================================
--- rtmp.cpp	Fri Oct 30 02:47:01 2009	(r11)
+++ rtmp.cpp	Fri Oct 30 20:12:27 2009	(r12)
@@ -23,6 +23,7 @@
 #include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
+#include <unistd.h>
 
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -56,6 +57,8 @@ static const int packetSize[] = { 12, 8,
 #define RTMP_PACKET_SIZE_SMALL    2
 #define RTMP_PACKET_SIZE_MINIMUM  3
 
+extern bool bCtrlC;
+
 int32_t GetTime()
 {
 #ifdef _DEBUG
@@ -64,7 +67,7 @@ int32_t GetTime()
 	return timeGetTime();
 #else
 	struct tms t;
-	return times(&t);
+	return times(&t)*1000/sysconf(_SC_CLK_TCK);
 #endif
 }
 
@@ -94,6 +97,11 @@ CRTMP::CRTMP() : m_socket(0)
   m_pBuffer = new char[RTMP_BUFFER_CACHE_SIZE];
   m_nBufferMS = 300;
   m_fDuration = 0;
+  m_stream_id = -1;
+  m_pBufferStart = NULL;
+  m_fAudioCodecs = 3191.0;
+  m_fVideoCodecs = 252.0;
+  m_bTimedout = false;
 }
 
 CRTMP::~CRTMP()
@@ -104,6 +112,7 @@ CRTMP::~CRTMP()
 
 double CRTMP::GetDuration() { return m_fDuration; }
 bool CRTMP::IsConnected() { return m_socket != 0; }
+bool CRTMP::IsTimedout() { return m_bTimedout; }
 
 void CRTMP::SetBufferMS(int size)
 {
@@ -115,20 +124,20 @@ void CRTMP::UpdateBufferMS()
   SendPing(3, 1, m_nBufferMS);
 }
 
-bool CRTMP::Connect(
+void CRTMP::SetupStream(
 	int protocol, 
-	char *hostname, 
+	const char *hostname, 
 	unsigned int port, 
-	char *playpath, 
-	char *tcUrl, 
-	char *swfUrl, 
-	char *pageUrl, 
-	char *app, 
-	char *auth,
-	char *swfSHA256Hash,
+	const char *playpath, 
+	const char *tcUrl, 
+	const char *swfUrl, 
+	const char *pageUrl, 
+	const char *app, 
+	const char *auth,
+	const char *swfSHA256Hash,
 	uint32_t swfSize,
-	char *flashVer, 
-	char *subscribepath, 
+	const char *flashVer, 
+	const char *subscribepath, 
 	double dTime,
 	bool bLiveStream,
 	long int timeout
@@ -190,10 +199,18 @@ bool CRTMP::Connect(
 
   if (Link.port == 0)
     Link.port = 1935;
-  
+}
+
+bool CRTMP::Connect() {
+  if (!Link.hostname)
+     return false;
+
   // close any previous connection
   Close();
 
+  m_bTimedout = false;
+  m_fDuration = 0.0;
+
   sockaddr_in service;
   memset(&service, 0, sizeof(sockaddr_in));
   service.sin_family = AF_INET;
@@ -215,7 +232,9 @@ bool CRTMP::Connect(
   {
     if (connect(m_socket, (sockaddr*) &service, sizeof(struct sockaddr)) < 0)
     {
-      Log(LOGERROR, "%s, failed to connect socket. Error: %d", __FUNCTION__, GetSockError());
+      int err = GetSockError();
+      Log(LOGERROR, "%s, failed to connect socket. %d (%s)", __FUNCTION__,
+	err, strerror(err));
       Close();
       return false;
     }
@@ -229,7 +248,7 @@ bool CRTMP::Connect(
     }
 
     Log(LOGDEBUG, "%s, handshaked", __FUNCTION__);
-    if (!Connect())
+    if (!RTMPConnect())
     {
       Log(LOGERROR, "%s, RTMP connect failed.", __FUNCTION__);
       Close();
@@ -254,6 +273,56 @@ bool CRTMP::Connect(
   return true;
 }
 
+bool CRTMP::ConnectStream(double seekTime) {
+  if (seekTime >= -2.0)
+    Link.seekTime = seekTime;
+
+  RTMPPacket packet;
+  while (!m_bPlaying && IsConnected() && ReadPacket(packet)) {
+    if (!packet.IsReady())
+    {
+      packet.FreePacket();
+      continue;
+    }
+    
+    if ((packet.m_packetType == 0x8) || \
+        (packet.m_packetType == 0x9) || \
+        (packet.m_packetType == 0x16))
+    {
+      Log(LOGDEBUG, "%s, received FLV packet before play()!", __FUNCTION__);
+      break;
+    }
+
+    HandlePacket(packet);
+  }
+
+  return m_bPlaying;
+}
+
+bool CRTMP::ReconnectStream(int bufferTime, double seekTime) {
+  DeleteStream();
+
+  SendCreateStream(2.0);
+
+  SetBufferMS(bufferTime);
+  UpdateBufferMS();
+
+  return ConnectStream(seekTime);
+}
+
+void CRTMP::DeleteStream() {
+  if (m_stream_id < 0)
+    return;
+
+  m_bPlaying = false;
+
+  SendDeleteStream(m_stream_id);
+
+  // No response expected for deleteStream
+  if (m_methodCalls.back() == "deleteStream")
+    m_methodCalls.erase(m_methodCalls.end());
+}
+
 int CRTMP::GetNextMediaPacket(RTMPPacket &packet)
 {
   int bHasMediaPacket = 0;
@@ -266,6 +335,21 @@ int CRTMP::GetNextMediaPacket(RTMPPacket
       continue;
     }
 
+    bHasMediaPacket = HandlePacket(packet);
+
+    if (!bHasMediaPacket) { 
+      packet.FreePacket();
+    }
+  }
+        
+  if (bHasMediaPacket)
+    m_bPlaying = true;
+
+  return bHasMediaPacket;
+}
+
+int CRTMP::HandlePacket(RTMPPacket &packet) {
+  int bHasMediaPacket = 0;
     switch (packet.m_packetType)
     {
       case 0x01:
@@ -285,14 +369,12 @@ int CRTMP::GetNextMediaPacket(RTMPPacket
 
       case 0x05:
         // server bw
-        Log(LOGDEBUG, "%s, received: server BW", __FUNCTION__);
-	//LogHex(packet.m_body, packet.m_nBodySize);
+	HandleServerBW(packet);
         break;
 
       case 0x06:
         // client bw
-        Log(LOGDEBUG, "%s, received: client BW", __FUNCTION__);
-	//LogHex(packet.m_body, packet.m_nBodySize);
+	HandleClientBW(packet);
         break;
 
       case 0x08:
@@ -386,14 +468,6 @@ int CRTMP::GetNextMediaPacket(RTMPPacket
 	#endif
     }
 
-    if (!bHasMediaPacket) { 
-      packet.FreePacket();
-    }
-  }
-        
-  if (bHasMediaPacket)
-    m_bPlaying = true;
-
   return bHasMediaPacket;
 }
 
@@ -406,6 +480,8 @@ int CRTMP::ReadN(char *buffer, int n)
 {
   int nOriginalSize = n;
   
+  m_bTimedout = false;
+
   #ifdef _DEBUG
   memset(buffer, 0, n);
   #endif
@@ -415,10 +491,11 @@ int CRTMP::ReadN(char *buffer, int n)
   {
     int nBytes = 0;
     if(m_nBufferSize == 0)
-		if (!FillBuffer()) {
-			Close();
-			return 0;
-		}
+	if (!FillBuffer()) {
+	   if (!m_bTimedout)
+	     Close();
+	   return 0;
+	}
     int nRead = ((n<m_nBufferSize)?n:m_nBufferSize);
     if(nRead > 0) {
     	memcpy(ptr, m_pBufferStart, nRead);
@@ -426,7 +503,7 @@ int CRTMP::ReadN(char *buffer, int n)
 	m_nBufferSize -= nRead;
 	nBytes = nRead;
 	m_nBytesIn += nRead;
-	if(m_nBytesIn > m_nBytesInSent + (600*1024)) // report every 600K
+	if(m_nBytesIn > m_nBytesInSent + m_nClientBW/2 )
 		SendBytesReceived();
     }
 
@@ -483,7 +560,12 @@ bool CRTMP::WriteN(const char *buffer, i
     
     if (nBytes < 0)
     {
-      Log(LOGERROR, "%s, RTMP send error %d (%d bytes)", __FUNCTION__, GetSockError(), n);
+      int sockerr = GetSockError();
+      Log(LOGERROR, "%s, RTMP send error %d (%d bytes)", __FUNCTION__, sockerr, n);
+
+      if (sockerr == EINTR && !bCtrlC)
+	continue;
+      
       Close();
 
       if(encrypted)
@@ -504,7 +586,7 @@ bool CRTMP::WriteN(const char *buffer, i
   return n == 0;
 }
 
-bool CRTMP::Connect()
+bool CRTMP::RTMPConnect()
 {
   if (!SendConnectPacket())
   {
@@ -540,8 +622,8 @@ bool CRTMP::SendConnectPacket()
   
   enc += EncodeBoolean(enc, "fpad", false);
   enc += EncodeNumber(enc, "capabilities", 15.0);
-  enc += EncodeNumber(enc, "audioCodecs", 3191.0);
-  enc += EncodeNumber(enc, "videoCodecs", 252.0);
+  enc += EncodeNumber(enc, "audioCodecs", m_fAudioCodecs);
+  enc += EncodeNumber(enc, "videoCodecs", m_fVideoCodecs);
   enc += EncodeNumber(enc, "videoFunction", 1.0);
   if(Link.pageUrl)
   	enc += EncodeString(enc, "pageUrl", Link.pageUrl);
@@ -558,8 +640,8 @@ bool CRTMP::SendConnectPacket()
   // add auth string
   if(Link.auth)
   {
-  	*enc = 0x01; enc++;
-  	*enc = 0x01; enc++;
+//  	*enc = 0x01; enc++;
+//  	*enc = 0x01; enc++;
 
   	enc += EncodeString(enc, Link.auth);
   }
@@ -608,7 +690,7 @@ bool CRTMP::SendCreateStream(double dStr
   return SendRTMP(packet);
 }
 
-bool CRTMP::SendFCSubscribe(char *subscribepath)
+bool CRTMP::SendFCSubscribe(const char *subscribepath)
 {
   RTMPPacket packet;
   packet.m_nChannel = 0x03;   // control channel (invoke)
@@ -700,7 +782,7 @@ bool CRTMP::SendServerBW()
   packet.AllocPacket(4);
   packet.m_nBodySize = 4;
 
-  EncodeInt32(packet.m_body, 0x001312d0); // hard coded for now
+  EncodeInt32(packet.m_body, m_nServerBW);
   return SendRTMP(packet);
 }
 
@@ -780,18 +862,14 @@ bool CRTMP::SendPlay()
   Log(LOGDEBUG, "%s, sending play: %s", __FUNCTION__, Link.playpath);
   enc += EncodeString(enc, Link.playpath);
 
+  // Optional parameters start and len.
+
   // start: -2, -1, 0, positive number
   //  -2: looks for a live stream, then a recorded stream, if not found any open a live stream
   //  -1: plays a live stream
   // >=0: plays a recorded streams from 'start' milliseconds
-  if(Link.bLiveStream)
-    enc += EncodeNumber(enc, -1000.0);
-  else {
-    if(Link.seekTime > 0.0)
-      enc += EncodeNumber(enc, Link.seekTime); // resume from here
-    else
-      enc += EncodeNumber(enc, 0.0);//-2000.0); // recorded as default, -2000.0 is not reliable since that freezes the player if the stream is not found
-  }
+  if(Link.seekTime > 0.0)
+    enc += EncodeNumber(enc, Link.seekTime); // resume from here
   
   // len: -1, 0, positive number
   //  -1: plays live or recorded stream to the end (default)
@@ -913,6 +991,7 @@ int CRTMP::HandleInvoke(const char *body
     }
     else if (CSCMP(methodInvoked,"play"))
     {
+	m_bPlaying = true;
 	  SendPlay();
     }
   }
@@ -952,8 +1031,16 @@ int CRTMP::HandleInvoke(const char *body
     if (code == "NetStream.Failed"
     ||  code == "NetStream.Play.Failed"
     ||  code == "NetStream.Play.StreamNotFound"
-    ||  code == "NetConnection.Connect.InvalidApp")
+    ||  code == "NetConnection.Connect.InvalidApp") {
+      m_stream_id = -1;
       Close();
+    }
+
+    if (code == "NetStream.Play.Start") {
+      m_bPlaying = true;
+      if (m_methodCalls[0] == "play")
+        m_methodCalls.erase(m_methodCalls.begin());
+    }
 
     // Return 1 if this is a Play.Complete or Play.Stop
     if (code == "NetStream.Play.Complete"
@@ -1132,6 +1219,20 @@ void CRTMP::HandlePing(const RTMPPacket 
   }
 }
 
+void CRTMP::HandleServerBW(const RTMPPacket &packet) {
+  m_nServerBW = ReadInt32(packet.m_body);
+  Log(LOGDEBUG, "%s: server BW = %d", __FUNCTION__, m_nServerBW);
+}
+
+void CRTMP::HandleClientBW(const RTMPPacket &packet) {
+  m_nClientBW = ReadInt32(packet.m_body);
+  if (packet.m_nBodySize > 4)
+    m_nClientBW2 = packet.m_body[4];
+  else
+    m_nClientBW2 = -1;
+  Log(LOGDEBUG, "%s: client BW = %d %d", __FUNCTION__, m_nClientBW, m_nClientBW2);
+}
+
 bool CRTMP::ReadPacket(RTMPPacket &packet)
 {
   char type;
@@ -1420,10 +1521,10 @@ bool CRTMP::HandShake(bool FP9HandShake)
   memset(&clientsig[5], 0, 4);
 
 #ifdef _DEBUG
-    for (int i=9; i<=RTMP_SIG_SIZE; i++) 
+    for (int i=9; i<RTMP_SIG_SIZE; i++) 
       clientsig[i] = 0xff;
 #else
-    for (int i=9; i<=RTMP_SIG_SIZE; i++)
+    for (int i=9; i<RTMP_SIG_SIZE; i++)
       clientsig[i] = (char)(rand() % 256);
 #endif
 
@@ -1543,8 +1644,10 @@ bool CRTMP::SendRTMP(RTMPPacket &packet)
 #endif
   }
 
-  if (packet.m_packetType == 0x14) // we invoked a remote method, keep it in call queue till result arrives
+  if (packet.m_packetType == 0x14) { // we invoked a remote method, keep it in call queue till result arrives
     m_methodCalls.push_back(ReadString(packet.m_body + 1));
+    Log(LOGDEBUG, "Invoking %s", ReadString(packet.m_body + 1).c_str());
+  }
 
   if (!m_vecChannelsOut[packet.m_nChannel])
     m_vecChannelsOut[packet.m_nChannel] = new RTMPPacket;
@@ -1558,11 +1661,15 @@ void CRTMP::Close()
   if (IsConnected())
     close(m_socket);
 
+  m_stream_id = -1;
   m_socket = 0;
   m_chunkSize = 128;
   m_nBWCheckCounter = 0;
   m_nBytesIn = 0;
   m_nBytesInSent = 0;
+  m_nClientBW = 2500000;
+  m_nClientBW2 = 2;
+  m_nServerBW = 2500000;
 
   for (int i=0; i<65600; i++)
   {
@@ -1575,6 +1682,7 @@ void CRTMP::Close()
 	  m_vecChannelsOut[i] = NULL;
 	}
   }
+  m_methodCalls.clear();
 
   m_bPlaying = false;
   m_nBufferSize = 0;
@@ -1583,15 +1691,25 @@ void CRTMP::Close()
 bool CRTMP::FillBuffer()
 {
     assert(m_nBufferSize == 0); // only fill buffer when it's empty
-    int nBytes = recv(m_socket, m_pBuffer, RTMP_BUFFER_CACHE_SIZE, 0);
+    int nBytes;
+
+again:
+    nBytes = recv(m_socket, m_pBuffer, RTMP_BUFFER_CACHE_SIZE, 0);
     if(nBytes != -1) {
     	m_nBufferSize += nBytes;
 	m_pBufferStart = m_pBuffer;
     }
     else
     {
-      Log(LOGDEBUG, "%s, recv returned %d. GetSockError(): %d", __FUNCTION__, nBytes, GetSockError());
-      Close();
+      int sockerr = GetSockError();
+      Log(LOGDEBUG, "%s, recv returned %d. GetSockError(): %d", __FUNCTION__, nBytes, sockerr);
+      if (sockerr == EINTR && !bCtrlC)
+        goto again;
+
+      if (sockerr == EWOULDBLOCK || sockerr == EAGAIN)
+        m_bTimedout = true;
+      else
+        Close();
       return false;
     }
 

Modified: rtmp.h
==============================================================================
--- rtmp.h	Fri Oct 30 02:47:01 2009	(r11)
+++ rtmp.h	Fri Oct 30 20:12:27 2009	(r12)
@@ -71,20 +71,20 @@ namespace RTMP_LIB
 
 typedef struct
 {
-        char *hostname;
+        const char *hostname;
         unsigned int port;
 	int protocol;
-	char *playpath;
+	const char *playpath;
 
-        char *tcUrl;
-        char *swfUrl;
-        char *pageUrl;
-        char *app;
-        char *auth;
-	char *SWFHash;
+        const char *tcUrl;
+        const char *swfUrl;
+        const char *pageUrl;
+        const char *app;
+        const char *auth;
+	const char *SWFHash;
 	uint32_t SWFSize;
-	char *flashVer;
-	char *subscribepath;
+	const char *flashVer;
+	const char *subscribepath;
 
 	double seekTime;
 	bool bLiveStream;
@@ -111,27 +111,33 @@ class CRTMP
       void SetBufferMS(int size);
       void UpdateBufferMS();
 
-      bool Connect(
+      void SetupStream(
       	int protocol, 
-	char *hostname, 
+	const char *hostname, 
 	unsigned int port, 
-	char *playpath, 
-	char *tcUrl, 
-	char *swfUrl, 
-	char *pageUrl, 
-	char *app, 
-	char *auth,
-	char *swfSHA256Hash,
+	const char *playpath, 
+	const char *tcUrl, 
+	const char *swfUrl, 
+	const char *pageUrl, 
+	const char *app, 
+	const char *auth,
+	const char *swfSHA256Hash,
 	uint32_t swfSize,
-	char *flashVer, 
-	char *subscribepath, 
+	const char *flashVer, 
+	const char *subscribepath, 
       	double dTime,
 	bool bLiveStream,
 	long int timeout=300);
 
+      bool Connect();
+
       bool IsConnected(); 
+      bool IsTimedout(); 
       double GetDuration();
 
+      bool ConnectStream(double seekTime=-10.0);
+      bool ReconnectStream(int bufferTime, double seekTime=-10.0);
+      void DeleteStream();
       int GetNextMediaPacket(RTMPPacket &packet);
 
       void Close();
@@ -156,7 +162,7 @@ class CRTMP
 
     protected:
       bool HandShake(bool FP9HandShake=true);
-      bool Connect();
+      bool RTMPConnect();
 
       bool SendConnectPacket();
       bool SendServerBW();
@@ -166,17 +172,20 @@ class CRTMP
       bool SendBGHasStream(double dId, char *playpath);
       bool SendCreateStream(double dStreamId);
       bool SendDeleteStream(double dStreamId);
-      bool SendFCSubscribe(char *subscribepath);
+      bool SendFCSubscribe(const char *subscribepath);
       bool SendPlay();
       bool SendSeek(double dTime);
       bool SendBytesReceived();
 
+      int HandlePacket(RTMPPacket &packet);
       int HandleInvoke(const char *body, unsigned int nBodySize);
       bool HandleMetadata(char *body, unsigned int len);
       void HandleChangeChunkSize(const RTMPPacket &packet);
       void HandleAudio(const RTMPPacket &packet);
       void HandleVideo(const RTMPPacket &packet);
       void HandlePing(const RTMPPacket &packet);
+      void HandleServerBW(const RTMPPacket &packet);
+      void HandleClientBW(const RTMPPacket &packet);
      
       int EncodeString(char *output, const std::string &strName, const std::string &strValue);
       int EncodeNumber(char *output, const std::string &strName, double dVal);
@@ -199,6 +208,10 @@ class CRTMP
       bool m_bPlaying;
       int  m_nBufferMS;
       int  m_stream_id; // returned in _result from invoking createStream
+      bool m_bTimedout;
+      int m_nClientBW;
+      uint8_t m_nClientBW2;
+      int m_nServerBW;
 
       //std::string m_strPlayer;
       //std::string m_strPageUrl;
@@ -215,6 +228,9 @@ class CRTMP
       RTMPPacket *m_vecChannelsOut[65600];
       int  m_channelTimestamp[65600]; // abs timestamp of last packet
 
+      double m_fAudioCodecs; // audioCodecs for the connect packet
+      double m_fVideoCodecs; // videoCodecs for the connect packet
+
       double m_fDuration; // duration of stream in seconds
   };
 };

Modified: rtmpdump.cpp
==============================================================================
--- rtmpdump.cpp	Fri Oct 30 02:47:01 2009	(r11)
+++ rtmpdump.cpp	Fri Oct 30 20:12:27 2009	(r12)
@@ -78,6 +78,15 @@ uint32_t nIgnoredFlvFrameCounter = 0;
 uint32_t nIgnoredFrameCounter = 0;
 #define MAX_IGNORED_FRAMES	50
 
+FILE *file = 0;
+bool bCtrlC = false;
+
+void sigIntHandler(int sig) {
+	bCtrlC = true;
+	LogPrintf("Caught signal: %d, cleaning up, just a second...\n", sig);
+	signal(SIGINT, SIG_DFL);
+}
+
 int WriteHeader(
 	        char **buf,                     // target pointer, maybe preallocated
                 unsigned int len                // length of buffer if preallocated
@@ -414,13 +423,402 @@ stopKeyframeSearch:
 	return -1; // no more media packets
 }
 
-FILE *file = 0;
-bool bCtrlC = false;
+int OpenResumeFile(const char *flvFile,        // file name [in]
+		   FILE **file,                // opened file [out]
+		   unsigned long *size,        // size of the file [out]
+		   char **metaHeader,          // meta data read from the file [out]
+		   uint32_t *nMetaHeaderSize,  // length of metaHeader [out]
+		   double *duration)           // duration of the stream in ms [out]
+{
+	const size_t bufferSize = 1024;
+	char buffer[bufferSize];
 
-void sigIntHandler(int sig) {
-	bCtrlC = true;
-	LogPrintf("Caught signal: %d, cleaning up, just a second...\n", sig);
-	signal(SIGINT, SIG_DFL);
+	*nMetaHeaderSize = 0;
+	*size = 0;
+
+	*file = fopen(flvFile, "r+b");
+	if (!*file)
+		return RD_SUCCESS; // RD_SUCCESS, because we go to fresh file mode instead of quiting
+	
+	fseek(*file, 0, SEEK_END);
+	*size = ftell(*file);
+	fseek(*file, 0, SEEK_SET);
+
+	if(*size > 0) {
+		// verify FLV format and read header 
+		uint32_t prevTagSize = 0;
+
+		// check we've got a valid FLV file to continue!
+		if(fread(buffer, 1, 13, *file) != 13) {
+			Log(LOGERROR, "Couldn't read FLV file header!");
+			return RD_FAILED;
+		}
+		if(buffer[0] != 'F' || buffer[1] != 'L' || buffer[2] != 'V' || buffer[3] != 0x01) {
+			Log(LOGERROR, "Inavlid FLV file!");
+			return RD_FAILED;
+		}
+
+		if((buffer[4]&0x05) == 0) {
+			Log(LOGERROR, "FLV file contains neither video nor audio, aborting!");
+			return RD_FAILED;
+		}
+	
+		uint32_t dataOffset = RTMP_LIB::CRTMP::ReadInt32(buffer+5);
+		fseek(*file, dataOffset, SEEK_SET);
+
+		if(fread(buffer, 1, 4, *file) != 4) {
+			Log(LOGERROR, "Invalid FLV file: missing first prevTagSize!");
+			return RD_FAILED;
+		}
+		prevTagSize = RTMP_LIB::CRTMP::ReadInt32(buffer);
+		if(prevTagSize != 0) {
+			Log(LOGWARNING, "First prevTagSize is not zero: prevTagSize = 0x%08X", prevTagSize);
+		}
+
+		// go through the file to find the meta data!
+		uint32_t pos = dataOffset+4;
+		bool bFoundMetaHeader = false;
+
+		while(pos < *size-4 && !bFoundMetaHeader) {
+			fseek(*file, pos, SEEK_SET);
+			if(fread(buffer, 1, 4, *file)!=4)
+				break;
+
+			uint32_t dataSize = RTMP_LIB::CRTMP::ReadInt24(buffer+1);
+				
+			if(buffer[0] == 0x12) {
+				if (dataSize > bufferSize) {
+					Log(LOGERROR, "%s: dataSize (%d) > bufferSize (%d)", __FUNCTION__, dataSize, bufferSize);
+					return RD_FAILED;
+				}
+
+				fseek(*file, pos+11, SEEK_SET);
+				if(fread(buffer, 1, dataSize, *file) != dataSize)
+					break;
+				
+				RTMP_LIB::AMFObject metaObj;
+				int nRes = metaObj.Decode(buffer, dataSize);
+				if(nRes < 0) {
+					Log(LOGERROR, "%s, error decoding meta data packet", __FUNCTION__);
+					break;
+				}
+					
+				std::string metastring = metaObj.GetProperty(0).GetString();
+
+				if(metastring == "onMetaData") {
+					metaObj.Dump();
+						
+					*nMetaHeaderSize = dataSize;
+					if (*metaHeader) free(*metaHeader);
+					*metaHeader = (char *)malloc(*nMetaHeaderSize);
+					memcpy(*metaHeader, buffer, *nMetaHeaderSize);
+
+					// get duration
+					AMFObjectProperty prop;
+					if(RTMP_LIB::CRTMP::FindFirstMatchingProperty(metaObj, "duration", prop)) {
+						*duration = prop.GetNumber();
+						Log(LOGDEBUG, "File has duration: %f", *duration);
+					}
+
+					bFoundMetaHeader = true;
+					break;
+				}
+				//metaObj.Reset();
+				//delete obj;
+			}
+			pos += (dataSize+11+4);
+		}
+
+		if(!bFoundMetaHeader)
+			Log(LOGWARNING, "Couldn't locate meta data!");
+	}
+
+	return RD_SUCCESS;
+}
+
+int GetLastKeyframe(FILE *file,                   // output file [in]
+		    int nSkipKeyFrames,           // max number of frames to skip when searching for key frame [in]
+		    uint32_t *dSeek,              // offset of the last key frame [out]
+		    char **initialFrame,          // content of the last keyframe [out]
+		    int *initialFrameType,        // initial frame type (audio/video) [out]
+		    uint32_t *nInitialFrameSize)  // length of initialFrame [out]
+{
+	const size_t bufferSize = 16;
+	char buffer[bufferSize];
+        uint8_t dataType;
+	bool bAudioOnly;
+	unsigned long size;
+
+	fseek(file, 0, SEEK_END);
+	size = ftell(file);
+
+        fseek(file, 4, SEEK_SET);
+        fread(&dataType, sizeof(uint8_t), 1, file);
+        bAudioOnly = (dataType & 0x4) && !(dataType & 0x1);
+
+	Log(LOGDEBUG, "bAudioOnly: %d, size: %d", bAudioOnly, size);
+
+	// ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams) 
+
+		//if(!bAudioOnly) // we have to handle video/video+audio different since we have non-seekable frames
+		//{
+			// find the last seekable frame
+			uint32_t tsize = 0;
+			uint32_t prevTagSize = 0;
+
+			// go through the file and find the last video keyframe
+			do {
+				int xread;
+skipkeyframe:
+				if(size-tsize < 13) {
+					Log(LOGERROR, "Unexpected start of file, error in tag sizes, couldn't arrive at prevTagSize=0");
+					return RD_FAILED;
+				}
+				fseek(file, size-tsize-4, SEEK_SET);
+				xread = fread(buffer, 1, 4, file);
+				if(xread != 4) {
+					Log(LOGERROR, "Couldn't read prevTagSize from file!");
+					return RD_FAILED;
+				}
+
+				prevTagSize = RTMP_LIB::CRTMP::ReadInt32(buffer);
+				//Log(LOGDEBUG, "Last packet: prevTagSize: %d", prevTagSize);
+				
+				if(prevTagSize == 0) {
+					Log(LOGERROR, "Couldn't find keyframe to resume from!");
+					return RD_FAILED;
+				}
+
+				if(prevTagSize < 0 || prevTagSize > size-4-13) {
+					Log(LOGERROR, "Last tag size must be greater/equal zero (prevTagSize=%d) and smaller then filesize, corrupt file!", prevTagSize);
+					return RD_FAILED;
+				}
+				tsize += prevTagSize+4;
+
+				// read header
+				fseek(file, size-tsize, SEEK_SET);
+				if(fread(buffer, 1, 12, file) != 12) {
+					Log(LOGERROR, "Couldn't read header!");
+					return RD_FAILED;
+				}
+				//*
+				#ifdef _DEBUG
+				uint32_t ts = RTMP_LIB::CRTMP::ReadInt24(buffer+4);
+				ts |= (buffer[7]<<24);
+				Log(LOGDEBUG, "%02X: TS: %d ms", buffer[0], ts);
+				#endif	//*/
+
+				// this just continues the loop whenever the number of skipped frames is > 0,
+				// so we look for the next keyframe to continue with
+				//
+				// this helps if resuming from the last keyframe fails and one doesn't want to start
+				// the download from the beginning
+				//
+				if(nSkipKeyFrames > 0 && !(!bAudioOnly && (buffer[0] != 0x09 || (buffer[11]&0xf0) != 0x10))) {
+					#ifdef _DEBUG
+					Log(LOGDEBUG, "xxxxxxxxxxxxxxxxxxxxxxxx Well, lets go one more back!");
+					#endif
+					nSkipKeyFrames--;
+					goto skipkeyframe;
+				}
+
+			} while(
+				(bAudioOnly && buffer[0] != 0x08) ||
+				(!bAudioOnly && (buffer[0] != 0x09 || (buffer[11]&0xf0) != 0x10))
+				); // as long as we don't have a keyframe / last audio frame
+		
+			// save keyframe to compare/find position in stream
+			*initialFrameType = buffer[0];
+			*nInitialFrameSize = prevTagSize-11;
+			*initialFrame = (char *)malloc(*nInitialFrameSize);
+			
+			fseek(file, size-tsize+11, SEEK_SET);
+			if(fread(*initialFrame, 1, *nInitialFrameSize, file) != *nInitialFrameSize) {
+				Log(LOGERROR, "Couldn't read last keyframe, aborting!");
+				return RD_FAILED;
+			}
+
+			*dSeek = RTMP_LIB::CRTMP::ReadInt24(buffer+4); // set seek position to keyframe tmestamp
+			*dSeek |= (buffer[7]<<24);
+		//} 
+		//else // handle audio only, we can seek anywhere we'd like
+		//{
+		//}
+
+		if(*dSeek < 0) {
+			Log(LOGERROR, "Last keyframe timestamp is negative, aborting, your file is corrupt!");
+			return RD_FAILED;
+		}
+		Log(LOGDEBUG,"Last keyframe found at: %d ms, size: %d, type: %02X", *dSeek, *nInitialFrameSize, *initialFrameType);
+
+		/*
+		// now read the timestamp of the frame before the seekable keyframe:
+		fseek(file, size-tsize-4, SEEK_SET);
+		if(fread(buffer, 1, 4, file) != 4) {
+			Log(LOGERROR, "Couldn't read prevTagSize from file!");
+			goto start;
+		}
+		uint32_t prevTagSize = RTMP_LIB::CRTMP::ReadInt32(buffer);
+		fseek(file, size-tsize-4-prevTagSize+4, SEEK_SET);
+		if(fread(buffer, 1, 4, file) != 4) {
+			Log(LOGERROR, "Couldn't read previous timestamp!");
+			goto start;
+                }
+		uint32_t timestamp = RTMP_LIB::CRTMP::ReadInt24(buffer);
+		timestamp |= (buffer[3]<<24);
+
+		Log(LOGDEBUG, "Previuos timestamp: %d ms", timestamp);
+		*/
+
+		if(*dSeek != 0) {
+			// seek to position after keyframe in our file (we will ignore the keyframes resent by the server
+			// since they are sent a couple of times and handling this would be a mess)
+			fseek(file, size-tsize+prevTagSize+4, SEEK_SET);
+			
+			// make sure the WriteStream doesn't write headers and ignores all the 0ms TS packets
+			// (including several meta data headers and the keyframe we seeked to)
+			//bNoHeader = true; if bResume==true this is true anyway
+		}
+
+	//}
+
+	return RD_SUCCESS;
+}
+
+int Download(CRTMP *rtmp,                      // connected CRTMP object
+	     FILE *file,
+	     uint32_t dSeek, 
+	     uint32_t dStopOffset, 
+	     double duration, 
+	     bool bResume,
+	     char *metaHeader, 
+	     uint32_t nMetaHeaderSize, 
+	     char *initialFrame, 
+	     int initialFrameType, 
+	     uint32_t nInitialFrameSize,
+	     int nSkipKeyFrames,
+	     bool bStdoutMode,
+	     bool bLiveStream,
+	     bool bOverrideBufferTime,
+	     uint32_t bufferTime,
+	     double *percent)                  // percentage downloaded [out]
+{
+	uint32_t timestamp = dSeek;
+	int32_t now, lastUpdate;
+	uint8_t dataType = 0;    // will be written into the FLV header (position 4)
+	int bufferSize = 1024*1024;
+	char *buffer = (char *)malloc(bufferSize);
+        int nRead = 0;
+	unsigned long size = ftell(file);
+
+	memset(buffer, 0, bufferSize);
+
+	*percent = 0.0;
+
+	if(timestamp != 0) {
+		LogPrintf("Continuing at TS: %d ms\n", timestamp);
+	}
+
+	// print initial status
+	LogPrintf("Starting download at ");
+	if(duration > 0) {
+		*percent = ((double)timestamp) / (duration*1000.0)*100.0;
+                *percent = round(*percent*10.0)/10.0;
+                LogPrintf("%.3f kB (%.1f%%)\n", (double)size/1024.0, *percent);
+        } else {
+                LogPrintf("%.3f kB\n", (double)size/1024.0);
+        }
+
+	// write FLV header if not resuming
+	if(!bResume) {
+		nRead = WriteHeader(&buffer, bufferSize);
+		if(nRead > 0) {
+			if(fwrite(buffer, sizeof(unsigned char), nRead, file) != (size_t)nRead) {
+				Log(LOGERROR, "%s: Failed writing FLV header, exiting!", __FUNCTION__);
+				free(buffer);
+				return RD_FAILED;
+			}
+			size += nRead;
+		} else {
+			Log(LOGERROR, "Couldn't obtain FLV header, exiting!");
+			free(buffer);
+			return RD_FAILED;
+		}
+	}
+
+	now = GetTime();
+	lastUpdate = now-1000;
+	do
+	{
+		nRead = WriteStream(rtmp, &buffer, bufferSize, &timestamp, bResume, dSeek, metaHeader, nMetaHeaderSize, initialFrame, initialFrameType, nInitialFrameSize, &dataType);
+
+		//LogPrintf("nRead: %d\n", nRead);
+		if(nRead > 0) {
+			if(fwrite(buffer, sizeof(unsigned char), nRead, file) != (size_t)nRead) {
+				Log(LOGERROR, "%s: Failed writing, exiting!", __FUNCTION__);
+				free(buffer);
+				return RD_FAILED;
+                        }
+			size += nRead;
+	
+			//LogPrintf("write %dbytes (%.1f kB)\n", nRead, nRead/1024.0);
+			if(duration <= 0) // if duration unknown try to get it from the stream (onMetaData)
+				duration = rtmp->GetDuration();
+
+			if(duration > 0) {
+				// make sure we claim to have enough buffer time!
+				if(!bOverrideBufferTime && bufferTime < (duration*1000.0)) {
+					bufferTime = (uint32_t)(duration*1000.0)+5000; // extra 5sec to make sure we've got enough
+					
+					Log(LOGDEBUG, "Detected that buffer time is less than duration, resetting to: %dms", bufferTime);
+					rtmp->SetBufferMS(bufferTime);
+					rtmp->UpdateBufferMS();
+				}
+				*percent = ((double)timestamp) / (duration*1000.0)*100.0;
+				*percent = round(*percent*10.0)/10.0;
+				now = GetTime();
+				if (abs(now - lastUpdate) > 200) {
+					LogPrintf("\r%.3f kB (%.1f%%)", (double)size/1024.0, *percent);
+					lastUpdate = now;
+				}
+			} else {
+				now = GetTime();
+				if (abs(now - lastUpdate) > 200) {
+					LogPrintf("\r%.3f kB", (double)size/1024.0);
+					lastUpdate = now;
+				}
+			}
+		}
+		#ifdef _DEBUG
+		else { Log(LOGDEBUG, "zero read!"); }
+		#endif
+
+               // Force clean close if a specified stop offset is reached
+                if (dStopOffset && timestamp >= dStopOffset) {
+                        LogPrintf("\nStop offset has been reached at %.2f seconds\n", (double)dStopOffset/1000.0);
+                        nRead = 0;
+                        rtmp->Close();
+                }
+
+	} while(!bCtrlC && nRead > -1 && rtmp->IsConnected());
+	free(buffer);
+
+	if(bResume && nRead == -2) {
+		LogPrintf("Couldn't resume FLV file, try --skip %d\n\n", nSkipKeyFrames+1);
+		return RD_FAILED;
+	}
+
+	// finalize header by writing the correct dataType (video, audio, video+audio)
+	if(!bResume && dataType != 0x5 && !bStdoutMode) {
+		//Log(LOGDEBUG, "Writing data type: %02X", dataType);
+		fseek(file, 4, SEEK_SET);
+		fwrite(&dataType, sizeof(unsigned char), 1, file);
+	}
+	if((duration > 0 && *percent < 99.9) || bCtrlC || nRead < 0 || rtmp->IsTimedout()) {
+		return RD_INCOMPLETE;
+	}
+
+	return RD_SUCCESS;
 }
 
 //#define _DEBUG_TEST_PLAYSTOP
@@ -436,15 +834,12 @@ int main(int argc, char **argv)
 	double percent = 0;
 	double duration = 0.0;
 
-	uint8_t dataType = 0;    // will be written into the FLV header (position 4)
 
 	int nSkipKeyFrames = 0;  // skip this number of keyframes when resuming
 
 	bool bOverrideBufferTime = false; // if the user specifies a buffer time override this is true
 	bool bStdoutMode = false;// if true print the stream directly to stdout, messages go to stderr
 	bool bResume = false;    // true in resume mode
-	//bool bNoHeader = false;  // in resume mode this will tell not to write an FLV header again
-	bool bAudioOnly = false; // when resuming this will tell whether its an audio only stream
 	uint32_t dSeek = 0;	 // seek position in resume mode, 0 otherwise
 	uint32_t bufferTime = 10*60*60*1000; // 10 hours as default
 
@@ -465,7 +860,7 @@ int main(int argc, char **argv)
 	int protocol = RTMP_PROTOCOL_UNDEFINED;
 	bool bLiveStream = false; // is it a live stream? then we can't seek/resume
 
-	long int timeout = 300; // timeout connection after 300 seconds
+	long int timeout = 120; // timeout connection after 120 seconds
 	uint32_t dStartOffset = 0; // seek position in non-live mode
 	uint32_t dStopOffset = 0;
 
@@ -560,7 +955,7 @@ int main(int argc, char **argv)
 				LogPrintf("--skip|-k num           Skip num keyframes when looking for last keyframe to resume from. Useful if resume fails (default: %d)\n\n",
 					nSkipKeyFrames);
 				LogPrintf("--quiet|-q              Supresses all command output.\n");
-				LogPrintf("--verbose|-X            Verbose command output.\n");
+				LogPrintf("--verbose|-V            Verbose command output.\n");
 				LogPrintf("--debug|-z              Debug level command output.\n");
 				LogPrintf("If you don't pass parameters for swfUrl, pageUrl, app or auth these propertiews will not be included in the connect ");
 				LogPrintf("packet.\n\n");
@@ -774,259 +1169,53 @@ int main(int argc, char **argv)
 		strcpy(tcUrl, str);
 	}
 
-
 	int bufferSize = 1024*1024;
 	char *buffer = (char *)malloc(bufferSize);
-	int nRead = 0, count = 0;
-
+	int first = 1;
 
 	memset(buffer, 0, bufferSize);
 
 	CRTMP  *rtmp = new CRTMP();
-
-	Log(LOGDEBUG, "Setting buffer time to: %dms", bufferTime);
-	rtmp->SetBufferMS(bufferTime);
+	rtmp->SetupStream(protocol, hostname, port, playpath, tcUrl, swfUrl,
+		pageUrl, app, auth, swfHash, swfSize, flashVer, subscribepath,
+		dSeek, bLiveStream, timeout);
 
 	unsigned long size = 0;
         uint32_t timestamp = 0;
+	int tries = 2;	/* 1 retry */
 
 	// ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams) 
 	if(bResume) {
-		file = fopen(flvFile, "r+b");
-		if(file == 0) {
-			bResume = false; // we are back in fresh file mode (otherwise finalizing file won't be done)
-			goto start; // file does not exist, so go back into normal mode
-		}
-
-		fseek(file, 0, SEEK_END);
-		size = ftell(file);
-		fseek(file, 0, SEEK_SET);
-
-		if(size > 0) {
-			// verify FLV format and read header 
-			uint32_t prevTagSize = 0;
-
-			// check we've got a valid FLV file to continue!
-			if(fread(buffer, 1, 13, file) != 13) {
-				Log(LOGERROR, "Couldn't read FLV file header!");
-				nStatus = RD_FAILED;
-				goto clean;
-			}
-			if(buffer[0] != 'F' || buffer[1] != 'L' || buffer[2] != 'V' || buffer[3] != 0x01) {
-				Log(LOGERROR, "Invalid FLV file!");
-				nStatus = RD_FAILED;
-                                goto clean;
-			}
-
-			if((buffer[4]&0x05) == 0) {
-                                Log(LOGERROR, "FLV file contains neither video nor audio, aborting!");
-				nStatus = RD_FAILED;
-				goto clean;
-			}
-                        bAudioOnly = (buffer[4] & 0x4) && !(buffer[4] & 0x1);
-                        if(bAudioOnly)
-				Log(LOGDEBUG, "Resuming audio only stream!");
-	
-			uint32_t dataOffset = RTMP_LIB::CRTMP::ReadInt32(buffer+5);
-			fseek(file, dataOffset, SEEK_SET);
-
-			if(fread(buffer, 1, 4, file) != 4) {
-				Log(LOGERROR, "Invalid FLV file: missing first prevTagSize!");
-				nStatus = RD_FAILED;
-                                goto clean;
-			}
-			prevTagSize = RTMP_LIB::CRTMP::ReadInt32(buffer);
-			if(prevTagSize != 0) {
-				Log(LOGWARNING, "First prevTagSize is not zero: prevTagSize = 0x%08X", prevTagSize);
-			}
-
-			// go through the file to find the meta data!
-			uint32_t pos = dataOffset+4;
-			bool bFoundMetaHeader = false;
-
-			while(pos < size-4 && !bFoundMetaHeader) {
-				fseek(file, pos, SEEK_SET);
-				if(fread(buffer, 1, 4, file)!=4)
-					break;
-
-				uint32_t dataSize = RTMP_LIB::CRTMP::ReadInt24(buffer+1);
-				
-				if(buffer[0] == 0x12) {
-					fseek(file, pos+11, SEEK_SET);
-					if(fread(buffer, 1, dataSize, file) != dataSize)
-						break;
-					
-					RTMP_LIB::AMFObject metaObj;
-					int nRes = metaObj.Decode(buffer, dataSize);
-					if(nRes < 0) {
-						Log(LOGERROR, "%s, error decoding meta data packet", __FUNCTION__);
-						break;
-					}
-					
-					std::string metastring = metaObj.GetProperty(0).GetString();
-
-					if(metastring == "onMetaData") {
-						metaObj.Dump();
-						
-						nMetaHeaderSize = dataSize;
-						metaHeader = (char *)malloc(nMetaHeaderSize);
-						memcpy(metaHeader, buffer, nMetaHeaderSize);
-
-						// get duration
-						AMFObjectProperty prop;
-                				if(RTMP_LIB::CRTMP::FindFirstMatchingProperty(metaObj, "duration", prop)) {
-							duration = prop.GetNumber();
-							Log(LOGDEBUG, "File has duration: %f", duration);
-						}		
-
-						bFoundMetaHeader = true;
-						break;
-					}
-					//metaObj.Reset();
-					//delete obj;
-				}
-				pos += (dataSize+11+4);
-			}
-
-			if(!bFoundMetaHeader)
-				Log(LOGWARNING, "Couldn't locate meta data!");
-
-			//if(!bAudioOnly) // we have to handle video/video+audio different since we have non-seekable frames
-			//{
-				// find the last seekable frame
-				uint32_t tsize = 0;
-
-				// go through the file and find the last video keyframe
-				do {
-skipkeyframe:
-					if(size-tsize < 13) {
-						Log(LOGERROR, "Unexpected start of file, error in tag sizes, couldn't arrive at prevTagSize=0");
-						nStatus = RD_FAILED; goto clean;
-					}
-
-					fseek(file, size-tsize-4, SEEK_SET);
-					if(fread(buffer, 1, 4, file) != 4) {
-						Log(LOGERROR, "Couldn't read prevTagSize from file!");
-						nStatus = RD_FAILED; goto clean;
-					}
-
-					prevTagSize = RTMP_LIB::CRTMP::ReadInt32(buffer);
-					//Log(LOGDEBUG, "Last packet: prevTagSize: %d", prevTagSize);
-				
-					if(prevTagSize == 0) {
-						Log(LOGERROR, "Couldn't find keyframe to resume from!");
-						nStatus = RD_FAILED; goto clean;
-					}
-
-					if(prevTagSize < 0 || prevTagSize > size-4-13) {
-						Log(LOGERROR, "Last tag size must be greater/equal zero (prevTagSize=%d) and smaller then filesize, corrupt file!", prevTagSize);
-						nStatus = RD_FAILED; goto clean;
-					}
-					tsize += prevTagSize+4;
-
-					// read header
-					fseek(file, size-tsize, SEEK_SET);
-					if(fread(buffer, 1, 12, file) != 12) {
-						Log(LOGERROR, "Couldn't read header!");
-						nStatus=RD_FAILED; goto clean;
-					}
-					//*
-					#ifdef _DEBUG
-					uint32_t ts = RTMP_LIB::CRTMP::ReadInt24(buffer+4);
-					ts |= (buffer[7]<<24);
-					Log(LOGDEBUG, "%02X: TS: %d ms", buffer[0], ts);
-					#endif	//*/
-
-					// this just continues the loop whenever the number of skipped frames is > 0,
-					// so we look for the next keyframe to continue with
-					//
-					// this helps if resuming from the last keyframe fails and one doesn't want to start
-					// the download from the beginning
-					//
-					if(nSkipKeyFrames > 0 && !(!bAudioOnly && (buffer[0] != 0x09 || (buffer[11]&0xf0) != 0x10))) {
-						#ifdef _DEBUG
-						Log(LOGDEBUG, "xxxxxxxxxxxxxxxxxxxxxxxx Well, lets go one more back!");
-						#endif
-						nSkipKeyFrames--;
-						goto skipkeyframe;
-					}
-
-				} while(
-						(bAudioOnly && buffer[0] != 0x08) ||
-						(!bAudioOnly && (buffer[0] != 0x09 || (buffer[11]&0xf0) != 0x10))
-					); // as long as we don't have a keyframe / last audio frame
-		
-				// save keyframe to compare/find position in stream
-				initialFrameType = buffer[0];
-				nInitialFrameSize = prevTagSize-11;
-				initialFrame = (char *)malloc(nInitialFrameSize);
-				
-				fseek(file, size-tsize+11, SEEK_SET);
-				if(fread(initialFrame, 1, nInitialFrameSize, file) != nInitialFrameSize) {
-					Log(LOGERROR, "Couldn't read last keyframe, aborting!");
-					nStatus=RD_FAILED;
-					goto clean;
-				}
-
-				dSeek = RTMP_LIB::CRTMP::ReadInt24(buffer+4); // set seek position to keyframe tmestamp
-				dSeek |= (buffer[7]<<24);
-			//} 
-			//else // handle audio only, we can seek anywhere we'd like
-			//{
-			//}
+		nStatus = OpenResumeFile(flvFile, &file, &size, &metaHeader, &nMetaHeaderSize, &duration);
+		if (nStatus == RD_FAILED)
+			goto clean;
 
-			if(dSeek < 0) {
-				Log(LOGERROR, "Last keyframe timestamp is negative, aborting, your file is corrupt!");
-				nStatus=RD_FAILED;
+		if (!file) {
+			// file does not exist, so go back into normal mode
+			bResume = false; // we are back in fresh file mode (otherwise finalizing file won't be done)
+		} else {
+			nStatus = GetLastKeyframe(file, nSkipKeyFrames,
+						  &dSeek, &initialFrame,
+						  &initialFrameType,
+						  &nInitialFrameSize);
+			if (nStatus == RD_FAILED) {
+				Log(LOGDEBUG, "Failed to get last keyframe.");
 				goto clean;
 			}
-			Log(LOGDEBUG,"Last keyframe found at: %d ms, size: %d, type: %02X", dSeek, nInitialFrameSize, initialFrameType);
 
-			/*
-			// now read the timestamp of the frame before the seekable keyframe:
-			fseek(file, size-tsize-4, SEEK_SET);
-			if(fread(buffer, 1, 4, file) != 4) {
-				Log(LOGERROR, "Couldn't read prevTagSize from file!");
-				goto start;
-			}
-			uint32_t prevTagSize = RTMP_LIB::CRTMP::ReadInt32(buffer);
-			fseek(file, size-tsize-4-prevTagSize+4, SEEK_SET);
-			if(fread(buffer, 1, 4, file) != 4) {
-                                Log(LOGERROR, "Couldn't read previous timestamp!");
-                                goto start;
-                        }
-			uint32_t timestamp = RTMP_LIB::CRTMP::ReadInt24(buffer);
-			timestamp |= (buffer[3]<<24);
-
-			Log(LOGDEBUG, "Previuos timestamp: %d ms", timestamp);
-			*/
-
-			if(dSeek == 0) {
-                                Log(LOGDEBUG, "Last keyframe is first frame in stream, switching from resume to normal mode!");
-                                bResume = false;
-				goto start;
-                        } 
-			else 
-			{
-				// seek to position after keyframe in our file (we will ignore the keyframes resent by the server
-				// since they are sent a couple of times and handling this would be a mess)
-				fseek(file, size-tsize+prevTagSize+4, SEEK_SET);
-			
-				// make sure the WriteStream doesn't write headers and ignores all the 0ms TS packets
-				// (including several meta data headers and the keyframe we seeked to)
-				//bNoHeader = true; if bResume==true this is true anyway
+			if (dSeek == 0) {
+				Log(LOGDEBUG, "Last keyframe is first frame in stream, switching from resume to normal mode!");
+				bResume = false;
 			}
 		}
-	} else {
-start:
-		if(file != 0)
-			fclose(file);
+	}
 
+	if (!file) {
 		if(bStdoutMode)
 			file = stdout;
 		else
 		{
-			file = fopen(flvFile, "w");
+			file = fopen(flvFile, "w+b");
 			if(file == 0) {
                         	LogPrintf("Failed to open file! %s\n", flvFile);
                         	return RD_FAILED;
@@ -1039,8 +1228,6 @@ start:
 	netstackdump_read = fopen("netstackdump_read", "wb");
 	#endif
 
-	LogPrintf("Connecting ...\n");
-
 	// User defined seek offset
 	if (dStartOffset > 0) {
         	if (bLiveStream)
@@ -1052,151 +1239,75 @@ start:
                         dSeek += dStartOffset;
         }
 
-	//{ // here we decrease the seek time by 10ms to make sure the server starts with the next keyframe
-	//double dFindSeek = dSeek;
+	while (tries > 0 && !bCtrlC) {
+		tries--;
 
-	//if(!bAudioOnly && dFindSeek >= 10.0)
-	//	dFindSeek-=10.0;
+	Log(LOGDEBUG, "Setting buffer time to: %dms", bufferTime);
+	rtmp->SetBufferMS(bufferTime);
 
-	if (!rtmp->Connect(protocol, hostname, port, playpath, tcUrl, swfUrl, pageUrl, app, auth, swfHash, swfSize, flashVer, subscribepath, dSeek, bLiveStream, timeout)) {
-		LogPrintf("Failed to connect!\n");
-		return RD_FAILED;
-	}
-	Log(LOGINFO, "Connected...");
-	//}
-	
-	/*#ifdef _DEBUG
-	debugTS = dSeek;
-	#endif*/
+	if (first) {
+		first = 0;
+		LogPrintf("Connecting ...\n");
 
-	timestamp  = dSeek;
-	if(dSeek != 0) {
-		LogPrintf("Continuing at TS: %d ms\n", timestamp);
-	}
+		if (!rtmp->Connect()) {
+			break;
+		}
 
-	// print initial status
-	// Workaround to exit with 0 if the file is fully (> 99.9%) downloaded
-	if( duration > 0 && (double)timestamp >= (double)duration*999.0 ) {
-                LogPrintf("Already Completed at: TS=%.1f Duration=%.1f\n", (double)timestamp, (double)duration);
-                goto clean;
-        } else if(duration > 0) {
-		percent = ((double)timestamp) / (duration*1000.0)*100.0;
-                percent = round(percent*10.0)/10.0;
-                LogPrintf("Starting download at %.3f kB (%.1f%%)\n", (double)size/1024.0, percent);
-        } else {
-                LogPrintf("Starting download at %.3f kB\n", (double)size/1024.0);
-        }
+		Log(LOGINFO, "Connected...");
+		
+		timestamp  = dSeek;
+		if(dSeek != 0) {
+			LogPrintf("Continuing at TS: %d ms\n", timestamp);
+		}
 
-	// write FLV header if not resuming
-	if(!bResume) {
-		nRead = WriteHeader(&buffer, bufferSize);
-		if(nRead > 0) {
-			if(fwrite(buffer, sizeof(unsigned char), nRead, file) != (size_t)nRead) {
-				Log(LOGERROR, "%s: Failed writing FLV header, exiting!", __FUNCTION__);
-				nStatus=RD_FAILED;
-				goto clean;
-			}
-			size += nRead;
-		} else {
-			Log(LOGERROR, "Couldn't obtain FLV header, exiting!");
-			nStatus=RD_FAILED;
-			goto clean;
+		if (!rtmp->ConnectStream(dSeek)) {
+			break;
 		}
 	}
-	do
-	{
-		nRead = WriteStream(rtmp, &buffer, bufferSize, &timestamp, bResume, dSeek, metaHeader, nMetaHeaderSize, initialFrame, initialFrameType, nInitialFrameSize, &dataType);
 
-#define PRINT_CHUNK	32768
+       nStatus = Download(rtmp, file, dSeek, dStopOffset, duration, bResume,
+                           metaHeader, nMetaHeaderSize, initialFrame,
+                           initialFrameType, nInitialFrameSize,
+                           nSkipKeyFrames, bStdoutMode, bLiveStream,
+                           bOverrideBufferTime, bufferTime, &percent);
+        free(initialFrame);
+	initialFrame = NULL;
 
-		//LogPrintf("nRead: %d\n", nRead);
-		if(nRead > 0) {
-			if(fwrite(buffer, sizeof(unsigned char), nRead, file) != (size_t)nRead) {
-				Log(LOGERROR, "%s: Failed writing, exiting!", __FUNCTION__);
-                                nStatus=RD_FAILED;
-                                goto clean;
-                        }
-			size += nRead;
-			count += nRead;
+	/* If we succeeded, we're done. If writing to stdout
+	 * we can't seek and retry.
+	 */
+	if (nStatus != RD_INCOMPLETE || bStdoutMode || !rtmp->IsTimedout())
+		break;
 	
-			//LogPrintf("write %dbytes (%.1f KB)\n", nRead, nRead/1024.0);
-			if(duration <= 0) // if duration unknown try to get it from the stream (onMetaData)
-				duration = rtmp->GetDuration();
-
-			if(duration > 0) {
-				// make sure we claim to have enough buffer time!
-				if(!bOverrideBufferTime && bufferTime < (duration*1000.0)) {
-					bufferTime = (uint32_t)(duration*1000.0)+5000; // extra 5sec to make sure we've got enough
-					
-					Log(LOGDEBUG, "Detected that buffer time is less than duration, resetting to: %dms", bufferTime);
-					rtmp->SetBufferMS(bufferTime);
-					rtmp->UpdateBufferMS();
-				}
-				percent = ((double)timestamp) / (duration*1000.0)*100.0;
-				percent = round(percent*10.0)/10.0;
-				if (count > PRINT_CHUNK && debuglevel >= LOGINFO) {
-					LogPrintf("\r%.3f KB (%.1f%%)", (double)size/1024.0, percent);
-					count = 0;
-				}
-			} else {
-				if (count > PRINT_CHUNK && debuglevel >= LOGINFO) {
-					LogPrintf("\r%.3f KB", (double)size/1024.0);
-					count = 0;
-				}
-			}
-		}
-		#ifdef _DEBUG
-		else { Log(LOGDEBUG, "zero read!"); }
-		#endif
-
-		// Force clean close if a specified stop offset is reached
-		if (dStopOffset && timestamp >= dStopOffset) {
-		        LogPrintf("\nStop offset has been reached at %.2f seconds\n", (double)dStopOffset/1000.0);
-		        nRead = 0;
-		        rtmp->Close();
-		}
-
-	} while(!bCtrlC && nRead > -1 && rtmp->IsConnected());
+        nInitialFrameSize = 0;
 
-	// finalize header by writing the correct dataType (video, audio, video+audio)
-	if(!bResume && dataType != 0x5 && !bStdoutMode) {
-		Log(LOGDEBUG, "Writing data type: %02X", dataType);
-		fseek(file, 4, SEEK_SET);
-		fwrite(&dataType, sizeof(unsigned char), 1, file);
-	}
+               Log(LOGINFO, "Connection timed out, trying to reconnect.\n\n");
+        
+                nStatus = GetLastKeyframe(file, nSkipKeyFrames,
+                                          &dSeek, &initialFrame,
+                                          &initialFrameType,
+                                          &nInitialFrameSize);
+                if (nStatus != RD_SUCCESS) {
+                        Log(LOGDEBUG, "Failed to get last keyframe.");
+                        break;
+                }
 
-	if(bResume && nRead == -2) {
-		LogPrintf("\rCouldn't resume FLV file, try --skip %d\n\n", nSkipKeyFrames+1);
-		nStatus = RD_FAILED;
-		goto clean;
-	}
-	
-	// If duration is available then assume the download is complete if > 99.9%
-	if (bLiveStream == false) {
-		if (duration > 0 && percent > 99.9) {
-			LogPrintf("\nDownload complete\n");
-			nStatus = RD_SUCCESS;
-			goto clean;
-		//} else if ( bCtrlC || nRead != (-1) ) {
-		} else {
-			LogPrintf("\nDownload may be incomplete (downloaded about %.2f%%), try --resume\n", percent);
-			nStatus = RD_INCOMPLETE;
-			goto clean;
-		}
+		if (!rtmp->ReconnectStream(bufferTime, dSeek)) {
+                        Log(LOGERROR, "Failed to resume the stream\n\n");
+                        if (!rtmp->IsTimedout()) {
+                          nStatus = RD_FAILED;
+                          break;
+                        }
+                }
+		bResume = true;
 	}
 
-	// If nRead is zero then assume complete
-	if(nRead == 0) {
-	        LogPrintf("\nDownload complete\n");
-	        nStatus = RD_SUCCESS;
-	        goto clean;
+	if (nStatus == RD_SUCCESS) {
+		LogPrintf("\nDownload complete\n");
+	} else if (nStatus == RD_INCOMPLETE)  {
+		LogPrintf("\nDownload may be incomplete (downloaded about %.2f%%), try --resume\n", percent);
 	}
-	
-	// Ensure we have a non-zero exit code where WriteStream has failed
-	if (nRead < 0)
-		nStatus = RD_INCOMPLETE;
 
-        //Log(LOGDEBUG, "nStatus: %d, nRead: %d", nStatus, nRead);
 clean:
 	LogPrintf("\rClosing connection.\n");
 	rtmp->Close();


More information about the rtmpdump mailing list