[SCM] liblivemedia/master: Imported Upstream version 2010.09.25

xtophe-guest at users.alioth.debian.org xtophe-guest at users.alioth.debian.org
Mon Oct 11 22:47:46 UTC 2010


The following commit has been merged in the master branch:
commit 40b2ed384196425b26d1a28773f6abfe7a8f6114
Author: Christophe Mutricy <xtophe at videolan.org>
Date:   Fri Oct 1 22:44:39 2010 +0100

    Imported Upstream version 2010.09.25

diff --git a/BasicUsageEnvironment/BasicTaskScheduler.cpp b/BasicUsageEnvironment/BasicTaskScheduler.cpp
index ffc2012..06e3c8c 100644
--- a/BasicUsageEnvironment/BasicTaskScheduler.cpp
+++ b/BasicUsageEnvironment/BasicTaskScheduler.cpp
@@ -35,6 +35,8 @@ BasicTaskScheduler* BasicTaskScheduler::createNew() {
 BasicTaskScheduler::BasicTaskScheduler()
   : fMaxNumSockets(0) {
   FD_ZERO(&fReadSet);
+  FD_ZERO(&fWriteSet);
+  FD_ZERO(&fExceptionSet);
 }
 
 BasicTaskScheduler::~BasicTaskScheduler() {
@@ -46,6 +48,8 @@ BasicTaskScheduler::~BasicTaskScheduler() {
 
 void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) {
   fd_set readSet = fReadSet; // make a copy for this select() call
+  fd_set writeSet = fWriteSet; // ditto
+  fd_set exceptionSet = fExceptionSet; // ditto
 
   DelayInterval const& timeToDelay = fDelayQueue.timeToNextAlarm();
   struct timeval tv_timeToDelay;
@@ -66,8 +70,7 @@ void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) {
     tv_timeToDelay.tv_usec = maxDelayTime%MILLION;
   }
 
-  int selectResult = select(fMaxNumSockets, &readSet, NULL, NULL,
-			    &tv_timeToDelay);
+  int selectResult = select(fMaxNumSockets, &readSet, &writeSet, &exceptionSet, &tv_timeToDelay);
   if (selectResult < 0) {
 #if defined(__WIN32__) || defined(_WIN32)
     int err = WSAGetLastError();
@@ -75,9 +78,11 @@ void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) {
     // it was called with no entries set in "readSet".  If this happens, ignore it:
     if (err == WSAEINVAL && readSet.fd_count == 0) {
       err = EINTR;
-      // To stop this from happening again, create a dummy readable socket:
+      // To stop this from happening again, create a dummy socket:
       int dummySocketNum = socket(AF_INET, SOCK_DGRAM, 0);
       FD_SET((unsigned)dummySocketNum, &fReadSet);
+      FD_SET((unsigned)dummySocketNum, &fWriteSet);
+      FD_SET((unsigned)dummySocketNum, &fExceptionSet);
     }
     if (err != EINTR) {
 #else
@@ -87,12 +92,12 @@ void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) {
 #if !defined(_WIN32_WCE)
 	perror("BasicTaskScheduler::SingleStep(): select() fails");
 #endif
-	abort();
+	internalError();
       }
   }
 
   // Call the handler function for one readable socket:
-  HandlerIterator iter(*fReadHandlers);
+  HandlerIterator iter(*fHandlers);
   HandlerDescriptor* handler;
   // To ensure forward progress through the handlers, begin past the last
   // socket number that we handled:
@@ -106,13 +111,16 @@ void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) {
     }
   }
   while ((handler = iter.next()) != NULL) {
-    if (FD_ISSET(handler->socketNum, &readSet) &&
-	FD_ISSET(handler->socketNum, &fReadSet) /* sanity check */ &&
-	handler->handlerProc != NULL) {
-      fLastHandledSocketNum = handler->socketNum;
+    int sock = handler->socketNum; // alias
+    int resultConditionSet = 0;
+    if (FD_ISSET(sock, &readSet) && FD_ISSET(sock, &fReadSet)/*sanity check*/) resultConditionSet |= SOCKET_READABLE;
+    if (FD_ISSET(sock, &writeSet) && FD_ISSET(sock, &fWriteSet)/*sanity check*/) resultConditionSet |= SOCKET_WRITABLE;
+    if (FD_ISSET(sock, &exceptionSet) && FD_ISSET(sock, &fExceptionSet)/*sanity check*/) resultConditionSet |= SOCKET_EXCEPTION;
+    if ((resultConditionSet&handler->conditionSet) != 0 && handler->handlerProc != NULL) {
+      fLastHandledSocketNum = sock;
           // Note: we set "fLastHandledSocketNum" before calling the handler,
           // in case the handler calls "doEventLoop()" reentrantly.
-      (*handler->handlerProc)(handler->clientData, SOCKET_READABLE);
+      (*handler->handlerProc)(handler->clientData, resultConditionSet);
       break;
     }
   }
@@ -121,13 +129,16 @@ void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) {
     // so try again from the beginning:
     iter.reset();
     while ((handler = iter.next()) != NULL) {
-      if (FD_ISSET(handler->socketNum, &readSet) &&
-	  FD_ISSET(handler->socketNum, &fReadSet) /* sanity check */ &&
-	  handler->handlerProc != NULL) {
-	fLastHandledSocketNum = handler->socketNum;
+      int sock = handler->socketNum; // alias
+      int resultConditionSet = 0;
+      if (FD_ISSET(sock, &readSet) && FD_ISSET(sock, &fReadSet)/*sanity check*/) resultConditionSet |= SOCKET_READABLE;
+      if (FD_ISSET(sock, &writeSet) && FD_ISSET(sock, &fWriteSet)/*sanity check*/) resultConditionSet |= SOCKET_WRITABLE;
+      if (FD_ISSET(sock, &exceptionSet) && FD_ISSET(sock, &fExceptionSet)/*sanity check*/) resultConditionSet |= SOCKET_EXCEPTION;
+      if ((resultConditionSet&handler->conditionSet) != 0 && handler->handlerProc != NULL) {
+	fLastHandledSocketNum = sock;
 	    // Note: we set "fLastHandledSocketNum" before calling the handler,
             // in case the handler calls "doEventLoop()" reentrantly.
-	(*handler->handlerProc)(handler->clientData, SOCKET_READABLE);
+	(*handler->handlerProc)(handler->clientData, resultConditionSet);
 	break;
       }
     }
@@ -135,37 +146,38 @@ void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) {
   }
 
   // Also handle any delayed event that may have come due.  (Note that we do this *after* calling a socket
-  // handler, in case the delayed event handler modifies the set of readable socket.)
+  // handler, in case the delayed event handler modifies the set of readable sockets.)
   fDelayQueue.handleAlarm();
 }
 
-void BasicTaskScheduler::turnOnBackgroundReadHandling(int socketNum,
-				BackgroundHandlerProc* handlerProc,
-				void* clientData) {
-  if (socketNum < 0) return;
-  fReadHandlers->assignHandler(socketNum, handlerProc, clientData);
-  FD_SET((unsigned)socketNum, &fReadSet);
-
-  if (socketNum+1 > fMaxNumSockets) {
-    fMaxNumSockets = socketNum+1;
-  }
-}
-
-void BasicTaskScheduler::turnOffBackgroundReadHandling(int socketNum) {
+void BasicTaskScheduler
+  ::setBackgroundHandling(int socketNum, int conditionSet, BackgroundHandlerProc* handlerProc, void* clientData) {
   if (socketNum < 0) return;
   FD_CLR((unsigned)socketNum, &fReadSet);
-  fReadHandlers->removeHandler(socketNum);
-
-  if (socketNum+1 == fMaxNumSockets) {
-    --fMaxNumSockets;
+  FD_CLR((unsigned)socketNum, &fWriteSet);
+  FD_CLR((unsigned)socketNum, &fExceptionSet);
+  if (conditionSet == 0) {
+    fHandlers->clearHandler(socketNum);
+    if (socketNum+1 == fMaxNumSockets) {
+      --fMaxNumSockets;
+    }
+  } else {
+    fHandlers->assignHandler(socketNum, conditionSet, handlerProc, clientData);
+    if (socketNum+1 > fMaxNumSockets) {
+      fMaxNumSockets = socketNum+1;
+    }
+    if (conditionSet&SOCKET_READABLE) FD_SET((unsigned)socketNum, &fReadSet);
+    if (conditionSet&SOCKET_WRITABLE) FD_SET((unsigned)socketNum, &fWriteSet);
+    if (conditionSet&SOCKET_EXCEPTION) FD_SET((unsigned)socketNum, &fExceptionSet);
   }
 }
 
 void BasicTaskScheduler::moveSocketHandling(int oldSocketNum, int newSocketNum) {
   if (oldSocketNum < 0 || newSocketNum < 0) return; // sanity check
-  FD_CLR((unsigned)oldSocketNum, &fReadSet);
-  fReadHandlers->moveHandler(oldSocketNum, newSocketNum);
-  FD_SET((unsigned)newSocketNum, &fReadSet);
+  if (FD_ISSET(oldSocketNum, &fReadSet)) {FD_CLR((unsigned)oldSocketNum, &fReadSet); FD_SET((unsigned)newSocketNum, &fReadSet);}
+  if (FD_ISSET(oldSocketNum, &fWriteSet)) {FD_CLR((unsigned)oldSocketNum, &fWriteSet); FD_SET((unsigned)newSocketNum, &fWriteSet);}
+  if (FD_ISSET(oldSocketNum, &fExceptionSet)) {FD_CLR((unsigned)oldSocketNum, &fExceptionSet); FD_SET((unsigned)newSocketNum, &fExceptionSet);}
+  fHandlers->moveHandler(oldSocketNum, newSocketNum);
 
   if (oldSocketNum+1 == fMaxNumSockets) {
     --fMaxNumSockets;
diff --git a/BasicUsageEnvironment/BasicTaskScheduler0.cpp b/BasicUsageEnvironment/BasicTaskScheduler0.cpp
index cd22246..08c49a9 100644
--- a/BasicUsageEnvironment/BasicTaskScheduler0.cpp
+++ b/BasicUsageEnvironment/BasicTaskScheduler0.cpp
@@ -45,11 +45,11 @@ private:
 
 BasicTaskScheduler0::BasicTaskScheduler0()
   : fLastHandledSocketNum(-1) {
-  fReadHandlers = new HandlerSet;
+  fHandlers = new HandlerSet;
 }
 
 BasicTaskScheduler0::~BasicTaskScheduler0() {
-  delete fReadHandlers;
+  delete fHandlers;
 }
 
 TaskToken BasicTaskScheduler0::scheduleDelayedTask(int64_t microseconds,
@@ -80,7 +80,8 @@ void BasicTaskScheduler0::doEventLoop(char* watchVariable) {
 
 ////////// HandlerSet (etc.) implementation //////////
 
-HandlerDescriptor::HandlerDescriptor(HandlerDescriptor* nextHandler) {
+HandlerDescriptor::HandlerDescriptor(HandlerDescriptor* nextHandler)
+  : conditionSet(0), handlerProc(NULL) {
   // Link this descriptor into a doubly-linked list:
   if (nextHandler == this) { // initialization
     fNextHandler = fPrevHandler = this;
@@ -111,9 +112,7 @@ HandlerSet::~HandlerSet() {
 }
 
 void HandlerSet
-::assignHandler(int socketNum,
-		TaskScheduler::BackgroundHandlerProc* handlerProc,
-		void* clientData) {
+::assignHandler(int socketNum, int conditionSet, TaskScheduler::BackgroundHandlerProc* handlerProc, void* clientData) {
   // First, see if there's already a handler for this socket:
   HandlerDescriptor* handler = lookupHandler(socketNum);
   if (handler == NULL) { // No existing handler, so create a new descr:
@@ -121,11 +120,12 @@ void HandlerSet
     handler->socketNum = socketNum;
   }
 
+  handler->conditionSet = conditionSet;
   handler->handlerProc = handlerProc;
   handler->clientData = clientData;
 }
 
-void HandlerSet::removeHandler(int socketNum) {
+void HandlerSet::clearHandler(int socketNum) {
   HandlerDescriptor* handler = lookupHandler(socketNum);
   delete handler;
 }
diff --git a/BasicUsageEnvironment/BasicUsageEnvironment.cpp b/BasicUsageEnvironment/BasicUsageEnvironment.cpp
index a201012..9fc846e 100644
--- a/BasicUsageEnvironment/BasicUsageEnvironment.cpp
+++ b/BasicUsageEnvironment/BasicUsageEnvironment.cpp
@@ -32,7 +32,7 @@ BasicUsageEnvironment::BasicUsageEnvironment(TaskScheduler& taskScheduler)
   if (!initializeWinsockIfNecessary()) {
     setResultErrMsg("Failed to initialize 'winsock': ");
     reportBackgroundError();
-    abort();
+    internalError();
   }
 #endif
 }
diff --git a/BasicUsageEnvironment/BasicUsageEnvironment0.cpp b/BasicUsageEnvironment/BasicUsageEnvironment0.cpp
index 62a5992..64b29a4 100644
--- a/BasicUsageEnvironment/BasicUsageEnvironment0.cpp
+++ b/BasicUsageEnvironment/BasicUsageEnvironment0.cpp
@@ -59,11 +59,11 @@ void BasicUsageEnvironment0::setResultMsg(MsgString msg1, MsgString msg2,
   appendToResultMsg(msg3);
 }
 
-void BasicUsageEnvironment0::setResultErrMsg(MsgString msg) {
+void BasicUsageEnvironment0::setResultErrMsg(MsgString msg, int err) {
   setResultMsg(msg);
 
 #ifndef _WIN32_WCE
-  appendToResultMsg(strerror(getErrno()));
+  appendToResultMsg(strerror(err == 0 ? getErrno() : err));
 #endif
 }
 
diff --git a/BasicUsageEnvironment/include/BasicUsageEnvironment.hh b/BasicUsageEnvironment/include/BasicUsageEnvironment.hh
index 678def0..8681753 100644
--- a/BasicUsageEnvironment/include/BasicUsageEnvironment.hh
+++ b/BasicUsageEnvironment/include/BasicUsageEnvironment.hh
@@ -57,16 +57,15 @@ protected:
   // Redefined virtual functions:
   virtual void SingleStep(unsigned maxDelayTime);
 
-  virtual void turnOnBackgroundReadHandling(int socketNum,
-				    BackgroundHandlerProc* handlerProc,
-				    void* clientData);
-  virtual void turnOffBackgroundReadHandling(int socketNum);
+  virtual void setBackgroundHandling(int socketNum, int conditionSet, BackgroundHandlerProc* handlerProc, void* clientData);
   virtual void moveSocketHandling(int oldSocketNum, int newSocketNum);
 
 protected:
-  // To implement background reads:
+  // To implement background operations:
   int fMaxNumSockets;
   fd_set fReadSet;
+  fd_set fWriteSet;
+  fd_set fExceptionSet;
 };
 
 #endif
diff --git a/BasicUsageEnvironment/include/BasicUsageEnvironment0.hh b/BasicUsageEnvironment/include/BasicUsageEnvironment0.hh
index bbeb79f..37713cb 100644
--- a/BasicUsageEnvironment/include/BasicUsageEnvironment0.hh
+++ b/BasicUsageEnvironment/include/BasicUsageEnvironment0.hh
@@ -47,7 +47,7 @@ public:
   virtual void setResultMsg(MsgString msg1,
 		    MsgString msg2,
 		    MsgString msg3);
-  virtual void setResultErrMsg(MsgString msg);
+  virtual void setResultErrMsg(MsgString msg, int err = 0);
 
   virtual void appendToResultMsg(MsgString msg);
 
@@ -94,7 +94,7 @@ protected:
   DelayQueue fDelayQueue;
 
   // To implement background reads:
-  HandlerSet* fReadHandlers;
+  HandlerSet* fHandlers;
   int fLastHandledSocketNum;
 };
 
diff --git a/BasicUsageEnvironment/include/BasicUsageEnvironment_version.hh b/BasicUsageEnvironment/include/BasicUsageEnvironment_version.hh
index f64c7e2..ee65613 100644
--- a/BasicUsageEnvironment/include/BasicUsageEnvironment_version.hh
+++ b/BasicUsageEnvironment/include/BasicUsageEnvironment_version.hh
@@ -4,7 +4,7 @@
 #ifndef _BASICUSAGEENVIRONMENT_VERSION_HH
 #define _BASICUSAGEENVIRONMENT_VERSION_HH
 
-#define BASICUSAGEENVIRONMENT_LIBRARY_VERSION_STRING	"2010.04.09"
-#define BASICUSAGEENVIRONMENT_LIBRARY_VERSION_INT		1270771200
+#define BASICUSAGEENVIRONMENT_LIBRARY_VERSION_STRING	"2010.09.25"
+#define BASICUSAGEENVIRONMENT_LIBRARY_VERSION_INT		1285372800
 
 #endif
diff --git a/BasicUsageEnvironment/include/HandlerSet.hh b/BasicUsageEnvironment/include/HandlerSet.hh
index c2c3122..1ecf1a3 100644
--- a/BasicUsageEnvironment/include/HandlerSet.hh
+++ b/BasicUsageEnvironment/include/HandlerSet.hh
@@ -20,6 +20,10 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 #ifndef _HANDLER_SET_HH
 #define _HANDLER_SET_HH
 
+#ifndef _BOOLEAN_HH
+#include "Boolean.hh"
+#endif
+
 ////////// HandlerSet (etc.) definition //////////
 
 class HandlerDescriptor {
@@ -28,6 +32,7 @@ class HandlerDescriptor {
 
 public:
   int socketNum;
+  int conditionSet;
   TaskScheduler::BackgroundHandlerProc* handlerProc;
   void* clientData;
 
@@ -44,10 +49,8 @@ public:
   HandlerSet();
   virtual ~HandlerSet();
 
-  void assignHandler(int socketNum,
-		     TaskScheduler::BackgroundHandlerProc* handlerProc,
-		     void* clientData);
-  void removeHandler(int socketNum);
+  void assignHandler(int socketNum, int conditionSet, TaskScheduler::BackgroundHandlerProc* handlerProc, void* clientData);
+  void clearHandler(int socketNum);
   void moveHandler(int oldSocketNum, int newSocketNum);
 
 private:
diff --git a/UsageEnvironment/UsageEnvironment.cpp b/UsageEnvironment/UsageEnvironment.cpp
index 0677f0a..bad8100 100644
--- a/UsageEnvironment/UsageEnvironment.cpp
+++ b/UsageEnvironment/UsageEnvironment.cpp
@@ -31,6 +31,12 @@ UsageEnvironment::UsageEnvironment(TaskScheduler& scheduler)
 UsageEnvironment::~UsageEnvironment() {
 }
 
+// By default, we handle 'should not occur'-type library errors by calling abort().  Subclasses can redefine this, if desired.
+void UsageEnvironment::internalError() {
+  abort();
+}
+
+
 TaskScheduler::TaskScheduler() {
 }
 
@@ -43,3 +49,8 @@ void TaskScheduler::rescheduleDelayedTask(TaskToken& task,
   unscheduleDelayedTask(task);
   task = scheduleDelayedTask(microseconds, proc, clientData);
 }
+
+// By default, we handle 'should not occur'-type library errors by calling abort().  Subclasses can redefine this, if desired.
+void TaskScheduler::internalError() {
+  abort();
+}
diff --git a/UsageEnvironment/include/UsageEnvironment.hh b/UsageEnvironment/include/UsageEnvironment.hh
index ef530eb..d64b026 100644
--- a/UsageEnvironment/include/UsageEnvironment.hh
+++ b/UsageEnvironment/include/UsageEnvironment.hh
@@ -60,8 +60,8 @@ public:
   virtual void setResultMsg(MsgString msg) = 0;
   virtual void setResultMsg(MsgString msg1, MsgString msg2) = 0;
   virtual void setResultMsg(MsgString msg1, MsgString msg2, MsgString msg3) = 0;
-  virtual void setResultErrMsg(MsgString msg) = 0;
-	// like setResultMsg(), except that an 'errno' message is appended
+  virtual void setResultErrMsg(MsgString msg, int err = 0) = 0;
+	// like setResultMsg(), except that an 'errno' message is appended.  (If "err == 0", the "getErrno()" code is used instead.)
 
   virtual void appendToResultMsg(MsgString msg) = 0;
 
@@ -69,6 +69,8 @@ public:
 	// used to report a (previously set) error message within
 	// a background event
 
+  virtual void internalError(); // used to 'handle' a 'should not occur'-type error condition within the library.
+
   // 'errno'
   virtual int getErrno() const = 0;
 
@@ -117,17 +119,15 @@ public:
   // Combines "unscheduleDelayedTask()" with "scheduleDelayedTask()"
   // (setting "task" to the new task token).
 
-  // For handling socket reads in the background:
+  // For handling socket operations in the background (from the event loop):
   typedef void BackgroundHandlerProc(void* clientData, int mask);
     // Possible bits to set in "mask".  (These are deliberately defined
     // the same as those in Tcl, to make a Tcl-based subclass easy.)
     #define SOCKET_READABLE    (1<<1)
     #define SOCKET_WRITABLE    (1<<2)
     #define SOCKET_EXCEPTION   (1<<3)
-  virtual void turnOnBackgroundReadHandling(int socketNum,
-				BackgroundHandlerProc* handlerProc,
-				void* clientData) = 0;
-  virtual void turnOffBackgroundReadHandling(int socketNum) = 0;
+  virtual void setBackgroundHandling(int socketNum, int conditionSet, BackgroundHandlerProc* handlerProc, void* clientData) = 0;
+  void disableBackgroundHandling(int socketNum) { setBackgroundHandling(socketNum, 0, NULL, NULL); }
   virtual void moveSocketHandling(int oldSocketNum, int newSocketNum) = 0;
         // Changes any socket handling for "oldSocketNum" so that occurs with "newSocketNum" instead.
 
@@ -138,6 +138,14 @@ public:
         // (If "watchVariable" is not NULL, then we return from this
         // routine when *watchVariable != 0)
 
+  // The following two functions are deprecated, and are provided for backwards-compatibility only:
+  void turnOnBackgroundReadHandling(int socketNum, BackgroundHandlerProc* handlerProc, void* clientData) {
+    setBackgroundHandling(socketNum, SOCKET_READABLE, handlerProc, clientData);
+  }
+  void turnOffBackgroundReadHandling(int socketNum) { disableBackgroundHandling(socketNum); }
+
+  virtual void internalError(); // used to 'handle' a 'should not occur'-type error condition within the library.
+
 protected:
   TaskScheduler(); // abstract base class
 };
diff --git a/UsageEnvironment/include/UsageEnvironment_version.hh b/UsageEnvironment/include/UsageEnvironment_version.hh
index e936928..cfd2ef4 100644
--- a/UsageEnvironment/include/UsageEnvironment_version.hh
+++ b/UsageEnvironment/include/UsageEnvironment_version.hh
@@ -4,7 +4,7 @@
 #ifndef _USAGEENVIRONMENT_VERSION_HH
 #define _USAGEENVIRONMENT_VERSION_HH
 
-#define USAGEENVIRONMENT_LIBRARY_VERSION_STRING	"2010.04.09"
-#define USAGEENVIRONMENT_LIBRARY_VERSION_INT		1270771200
+#define USAGEENVIRONMENT_LIBRARY_VERSION_STRING	"2010.09.25"
+#define USAGEENVIRONMENT_LIBRARY_VERSION_INT		1285372800
 
 #endif
diff --git a/config.armlinux.orig b/config.armlinux.orig
deleted file mode 100644
index 3e604f7..0000000
--- a/config.armlinux.orig
+++ /dev/null
@@ -1,18 +0,0 @@
-CROSS_COMPILE=		arm-elf-
-COMPILE_OPTS =		$(INCLUDES) -I. -O2 -DSOCKLEN_T=socklen_t -DNO_SSTREAM=1 -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64
-C =			c
-C_COMPILER =		$(CROSS_COMPILE)gcc
-C_FLAGS =		$(COMPILE_OPTS)
-CPP =			cpp
-CPLUSPLUS_COMPILER =	$(CROSS_COMPILE)gcc
-CPLUSPLUS_FLAGS =	$(COMPILE_OPTS) -Wall -DBSD=1
-OBJ =			o
-LINK =			$(CROSS_COMPILE)gcc -o
-LINK_OPTS =		-L.
-CONSOLE_LINK_OPTS =	$(LINK_OPTS)
-LIBRARY_LINK =		$(CROSS_COMPILE)ar cr
-LIBRARY_LINK_OPTS =	$(LINK_OPTS)
-LIB_SUFFIX =			a
-LIBS_FOR_CONSOLE_APPLICATION =
-LIBS_FOR_GUI_APPLICATION =
-EXE =
diff --git a/config.armlinux.rej b/config.armlinux.rej
deleted file mode 100644
index 49e7c9b..0000000
--- a/config.armlinux.rej
+++ /dev/null
@@ -1,35 +0,0 @@
-***************
-*** 1,16 ****
-- CROSS_COMPILE=		arm-elf-
-  COMPILE_OPTS =		$(INCLUDES) -I. -O2 -DSOCKLEN_T=socklen_t -DNO_SSTREAM=1 -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64
-  C =			c
-  C_COMPILER =		$(CROSS_COMPILE)gcc
-  C_FLAGS =		$(COMPILE_OPTS)
-  CPP =			cpp
-- CPLUSPLUS_COMPILER =	$(CROSS_COMPILE)gcc
-  CPLUSPLUS_FLAGS =	$(COMPILE_OPTS) -Wall -DBSD=1
-  OBJ =			o
-- LINK =			$(CROSS_COMPILE)gcc -o
-- LINK_OPTS =		-L.
-  CONSOLE_LINK_OPTS =	$(LINK_OPTS)
-- LIBRARY_LINK =		$(CROSS_COMPILE)ar cr
-  LIBRARY_LINK_OPTS =	$(LINK_OPTS)
-  LIB_SUFFIX =			a
-  LIBS_FOR_CONSOLE_APPLICATION =
---- 1,16 ----
-+ CROSS_COMPILE?=		arm-elf-
-  COMPILE_OPTS =		$(INCLUDES) -I. -O2 -DSOCKLEN_T=socklen_t -DNO_SSTREAM=1 -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64
-  C =			c
-  C_COMPILER =		$(CROSS_COMPILE)gcc
-  C_FLAGS =		$(COMPILE_OPTS)
-  CPP =			cpp
-+ CPLUSPLUS_COMPILER =	$(CROSS_COMPILE)g++
-  CPLUSPLUS_FLAGS =	$(COMPILE_OPTS) -Wall -DBSD=1
-  OBJ =			o
-+ LINK =			$(CROSS_COMPILE)g++ -o
-+ LINK_OPTS =		
-  CONSOLE_LINK_OPTS =	$(LINK_OPTS)
-+ LIBRARY_LINK =		$(CROSS_COMPILE)ar cr 
-  LIBRARY_LINK_OPTS =	$(LINK_OPTS)
-  LIB_SUFFIX =			a
-  LIBS_FOR_CONSOLE_APPLICATION =
diff --git a/config.iphoneos b/config.iphoneos
new file mode 100644
index 0000000..df28b92
--- /dev/null
+++ b/config.iphoneos
@@ -0,0 +1,17 @@
+COMPILE_OPTS =          $(INCLUDES) -I. $(EXTRA_LDFLAGS) -DBSD=1 -O2 -DSOCKLEN_T=socklen_t -DHAVE_SOCKADDR_LEN=1 -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -fPIC -arch armv7 --sysroot=/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.0.sdk
+C =                     c
+C_COMPILER =            /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/gcc
+C_FLAGS =               $(COMPILE_OPTS)
+CPP =                   cpp
+CPLUSPLUS_COMPILER =    /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/g++
+CPLUSPLUS_FLAGS =       $(COMPILE_OPTS) -Wall
+OBJ =                   o
+LINK =                  /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/g++ -o 
+LINK_OPTS =             -L. -arch armv7 --sysroot=/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.0.sdk
+CONSOLE_LINK_OPTS =     $(LINK_OPTS)
+LIBRARY_LINK =          ar cr 
+LIBRARY_LINK_OPTS =
+LIB_SUFFIX =            a
+LIBS_FOR_CONSOLE_APPLICATION =
+LIBS_FOR_GUI_APPLICATION =
+EXE =
diff --git a/config.linux b/config.linux-64bit
similarity index 74%
copy from config.linux
copy to config.linux-64bit
index 5c7bc8f..09df0a7 100644
--- a/config.linux
+++ b/config.linux-64bit
@@ -1,4 +1,4 @@
-COMPILE_OPTS =		$(INCLUDES) -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64
+COMPILE_OPTS =		$(INCLUDES) -m64  -fPIC -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64
 C =			c
 C_COMPILER =		cc
 C_FLAGS =		$(COMPILE_OPTS)
diff --git a/groupsock/Groupsock.cpp b/groupsock/Groupsock.cpp
index c1d3137..1390b19 100644
--- a/groupsock/Groupsock.cpp
+++ b/groupsock/Groupsock.cpp
@@ -237,10 +237,14 @@ void Groupsock::removeAllDestinations() {
 }
 
 void Groupsock::multicastSendOnly() {
+  // We disable this code for now, because - on some systems - leaving the multicast group seems to cause sent packets
+  // to not be received by other applications (at least, on the same host).
+#if 0
   socketLeaveGroup(env(), socketNum(), fIncomingGroupEId.groupAddress().s_addr);
   for (destRecord* dests = fDests; dests != NULL; dests = dests->fNext) {
     socketLeaveGroup(env(), socketNum(), dests->fGroupEId.groupAddress().s_addr);
   }
+#endif
 }
 
 Boolean Groupsock::output(UsageEnvironment& env, u_int8_t ttlToSend,
diff --git a/groupsock/GroupsockHelper.cpp b/groupsock/GroupsockHelper.cpp
index c7c8098..5fe49e3 100644
--- a/groupsock/GroupsockHelper.cpp
+++ b/groupsock/GroupsockHelper.cpp
@@ -222,110 +222,39 @@ int setupStreamSocket(UsageEnvironment& env,
   return newSocket;
 }
 
-#ifndef IMN_PIM
-static int blockUntilReadable(UsageEnvironment& env,
-			      int socket, struct timeval* timeout) {
-  int result = -1;
-  do {
-    fd_set rd_set;
-    FD_ZERO(&rd_set);
-    if (socket < 0) break;
-    FD_SET((unsigned) socket, &rd_set);
-    const unsigned numFds = socket+1;
-
-    result = select(numFds, &rd_set, NULL, NULL, timeout);
-    if (timeout != NULL && result == 0) {
-      break; // this is OK - timeout occurred
-    } else if (result <= 0) {
-      int err = env.getErrno();
-      if (err == EINTR || err == EAGAIN || err == EWOULDBLOCK) continue;
-      socketErr(env, "select() error: ");
-      break;
-    }
-
-    if (!FD_ISSET(socket, &rd_set)) {
-      socketErr(env, "select() error - !FD_ISSET");
-      break;
-    }
-  } while (0);
-
-  return result;
-}
-#else
-extern int blockUntilReadable(UsageEnvironment& env,
-			      int socket, struct timeval* timeout);
-#endif
-
 int readSocket(UsageEnvironment& env,
 	       int socket, unsigned char* buffer, unsigned bufferSize,
-	       struct sockaddr_in& fromAddress,
-	       struct timeval* timeout) {
-  int bytesRead = -1;
-  do {
-    int result = blockUntilReadable(env, socket, timeout);
-    if (timeout != NULL && result == 0) {
-      bytesRead = 0;
-      break;
-    } else if (result <= 0) {
-      break;
-    }
-
-    SOCKLEN_T addressSize = sizeof fromAddress;
-    bytesRead = recvfrom(socket, (char*)buffer, bufferSize, 0,
-			 (struct sockaddr*)&fromAddress,
-			 &addressSize);
-    if (bytesRead < 0) {
-      //##### HACK to work around bugs in Linux and Windows:
-      int err = env.getErrno();
-      if (err == 111 /*ECONNREFUSED (Linux)*/
+	       struct sockaddr_in& fromAddress) {
+  SOCKLEN_T addressSize = sizeof fromAddress;
+  int bytesRead = recvfrom(socket, (char*)buffer, bufferSize, 0,
+			   (struct sockaddr*)&fromAddress,
+			   &addressSize);
+  if (bytesRead < 0) {
+    //##### HACK to work around bugs in Linux and Windows:
+    int err = env.getErrno();
+    if (err == 111 /*ECONNREFUSED (Linux)*/
 #if defined(__WIN32__) || defined(_WIN32)
-	  // What a piece of crap Windows is.  Sometimes
-	  // recvfrom() returns -1, but with an 'errno' of 0.
-	  // This appears not to be a real error; just treat
-	  // it as if it were a read of zero bytes, and hope
-	  // we don't have to do anything else to 'reset'
-	  // this alleged error:
-	  || err == 0
+	// What a piece of crap Windows is.  Sometimes
+	// recvfrom() returns -1, but with an 'errno' of 0.
+	// This appears not to be a real error; just treat
+	// it as if it were a read of zero bytes, and hope
+	// we don't have to do anything else to 'reset'
+	// this alleged error:
+	|| err == 0 || err == EWOULDBLOCK
 #else
-	  || err == EAGAIN
+	|| err == EAGAIN
 #endif
-	  || err == 113 /*EHOSTUNREACH (Linux)*/) {
-			        //Why does Linux return this for datagram sock?
-	fromAddress.sin_addr.s_addr = 0;
-	return 0;
-      }
-      //##### END HACK
-      socketErr(env, "recvfrom() error: ");
-      break;
+	|| err == 113 /*EHOSTUNREACH (Linux)*/) { // Why does Linux return this for datagram sock?
+      fromAddress.sin_addr.s_addr = 0;
+      return 0;
     }
-  } while (0);
+    //##### END HACK
+    socketErr(env, "recvfrom() error: ");
+  }
 
   return bytesRead;
 }
 
-
-int readSocketExact(UsageEnvironment& env,
-		    int socket, unsigned char* buffer, unsigned bufferSize,
-		    struct sockaddr_in& fromAddress,
-		    struct timeval* timeout) {
-  /* read EXACTLY bufferSize bytes from the socket into the buffer.
-     fromaddress is address of last read.
-     return the number of bytes actually read when an error occurs
-  */
-  int bsize = bufferSize;
-  int bytesRead = 0;
-  int totBytesRead =0;
-  do {
-    bytesRead = readSocket (env, socket, buffer + totBytesRead, bsize,
-                            fromAddress, timeout);
-    if (bytesRead <= 0) break;
-    totBytesRead += bytesRead;
-    bsize -= bytesRead;
-  } while (bsize != 0);
-
-  return totBytesRead;
-}
-
 Boolean writeSocket(UsageEnvironment& env,
 		    int socket, struct in_addr address, Port port,
 		    u_int8_t ttlArg,
@@ -591,17 +520,23 @@ netAddressBits ourIPAddress(UsageEnvironment& env) {
       if (!writeSocket(env, sock, testAddr, testPort, 0,
 		       testString, testStringLength)) break;
 
-      unsigned char readBuffer[20];
+      // Block until the socket is readable (with a 5-second timeout):
+      fd_set rd_set;
+      FD_ZERO(&rd_set);
+      FD_SET((unsigned)sock, &rd_set);
+      const unsigned numFds = sock+1;
       struct timeval timeout;
       timeout.tv_sec = 5;
       timeout.tv_usec = 0;
+      int result = select(numFds, &rd_set, NULL, NULL, &timeout);
+      if (result <= 0) break;
+
+      unsigned char readBuffer[20];
       int bytesRead = readSocket(env, sock,
 				 readBuffer, sizeof readBuffer,
-				 fromAddr, &timeout);
-      if (bytesRead == 0 // timeout occurred
-	  || bytesRead != (int)testStringLength
-	  || strncmp((char*)readBuffer, (char*)testString,
-		     testStringLength) != 0) {
+				 fromAddr);
+      if (bytesRead != (int)testStringLength
+	  || strncmp((char*)readBuffer, (char*)testString, testStringLength) != 0) {
 	break;
       }
 
@@ -613,9 +548,7 @@ netAddressBits ourIPAddress(UsageEnvironment& env) {
       // so try instead to look it up directly.
       char hostname[100];
       hostname[0] = '\0';
-#ifndef CRIS
       gethostname(hostname, sizeof hostname);
-#endif
       if (hostname[0] == '\0') {
 	env.setResultErrMsg("initial gethostname() failed");
 	break;
diff --git a/groupsock/include/GroupsockHelper.hh b/groupsock/include/GroupsockHelper.hh
index b1501ca..f9f2713 100644
--- a/groupsock/include/GroupsockHelper.hh
+++ b/groupsock/include/GroupsockHelper.hh
@@ -31,15 +31,7 @@ int setupStreamSocket(UsageEnvironment& env,
 
 int readSocket(UsageEnvironment& env,
 	       int socket, unsigned char* buffer, unsigned bufferSize,
-	       struct sockaddr_in& fromAddress,
-	       struct timeval* timeout = NULL);
-
-int readSocketExact(UsageEnvironment& env,
-		    int socket, unsigned char* buffer, unsigned bufferSize,
-		    struct sockaddr_in& fromAddress,
-		    struct timeval* timeout = NULL);
-    // like "readSocket()", except that it rereads as many times as needed until
-    // *exactly* "bufferSize" bytes are read.
+	       struct sockaddr_in& fromAddress);
 
 Boolean writeSocket(UsageEnvironment& env,
 		    int socket, struct in_addr address, Port port,
diff --git a/groupsock/include/groupsock_version.hh b/groupsock/include/groupsock_version.hh
index c28bcf3..286bd29 100644
--- a/groupsock/include/groupsock_version.hh
+++ b/groupsock/include/groupsock_version.hh
@@ -4,7 +4,7 @@
 #ifndef _GROUPSOCK_VERSION_HH
 #define _GROUPSOCK_VERSION_HH
 
-#define GROUPSOCK_LIBRARY_VERSION_STRING	"2010.04.09"
-#define GROUPSOCK_LIBRARY_VERSION_INT		1270771200
+#define GROUPSOCK_LIBRARY_VERSION_STRING	"2010.09.25"
+#define GROUPSOCK_LIBRARY_VERSION_INT		1285372800
 
 #endif
diff --git a/liveMedia/AMRAudioRTPSource.cpp b/liveMedia/AMRAudioRTPSource.cpp
index 01b2b2e..f2991cc 100644
--- a/liveMedia/AMRAudioRTPSource.cpp
+++ b/liveMedia/AMRAudioRTPSource.cpp
@@ -537,7 +537,7 @@ void AMRDeinterleavingBuffer
 #ifdef DEBUG
     fprintf(stderr, "AMRDeinterleavingBuffer::deliverIncomingFrame() param sanity check failed (%d,%d,%d,%d)\n", frameSize, fILL, ILP, frameIndex);
 #endif
-    abort();
+    source->envir().internalError();
   }
 
   --frameIndex; // because it was incremented by the source when this frame was read
diff --git a/liveMedia/AMRAudioSource.cpp b/liveMedia/AMRAudioSource.cpp
index fed1eb7..b8c3c4b 100644
--- a/liveMedia/AMRAudioSource.cpp
+++ b/liveMedia/AMRAudioSource.cpp
@@ -29,6 +29,10 @@ AMRAudioSource::AMRAudioSource(UsageEnvironment& env,
 AMRAudioSource::~AMRAudioSource() {
 }
 
+char const* AMRAudioSource::MIMEtype() const {
+  return "audio/AMR";
+}
+
 Boolean AMRAudioSource::isAMRAudioSource() const {
   return True;
 }
diff --git a/liveMedia/BasicUDPSink.cpp b/liveMedia/BasicUDPSink.cpp
index 7ab6c2e..2553e2d 100644
--- a/liveMedia/BasicUDPSink.cpp
+++ b/liveMedia/BasicUDPSink.cpp
@@ -82,13 +82,8 @@ void BasicUDPSink::afterGettingFrame1(unsigned frameSize, unsigned numTruncatedB
 
   struct timeval timeNow;
   gettimeofday(&timeNow, NULL);
-  int uSecondsToGo;
-  if (fNextSendTime.tv_sec < timeNow.tv_sec) {
-    uSecondsToGo = 0; // prevents integer underflow if too far behind
-  } else {
-    uSecondsToGo = (fNextSendTime.tv_sec - timeNow.tv_sec)*1000000
-      + (fNextSendTime.tv_usec - timeNow.tv_usec);
-  }
+  int64_t uSecondsToGo;
+  uSecondsToGo = (fNextSendTime.tv_sec - timeNow.tv_sec)*1000000 + (fNextSendTime.tv_usec - timeNow.tv_usec);
 
   // Delay this amount of time:
   nextTask() = envir().taskScheduler().scheduleDelayedTask(uSecondsToGo,
diff --git a/liveMedia/DarwinInjector.cpp b/liveMedia/DarwinInjector.cpp
index 0f335ca..12706f2 100644
--- a/liveMedia/DarwinInjector.cpp
+++ b/liveMedia/DarwinInjector.cpp
@@ -77,7 +77,7 @@ DarwinInjector::DarwinInjector(UsageEnvironment& env,
 
 DarwinInjector::~DarwinInjector() {
   if (fSession != NULL) { // close down and delete the session
-    fRTSPClient->teardownMediaSession(*fSession);
+    fRTSPClient->sendTeardownCommand(*fSession, NULL);
     Medium::close(fSession);
   }
 
@@ -100,6 +100,17 @@ void DarwinInjector::addStream(RTPSink* rtpSink, RTCPInstance* rtcpInstance) {
   fSubstreamSDPSizes += strlen(newDescriptor->sdpLines());
 }
 
+// Define a special subclass of "RTSPClient" that has a pointer field to a "DarwinInjector".  We'll use this to implement RTSP ops:
+class RTSPClientForDarwinInjector: public RTSPClient {
+public:
+  RTSPClientForDarwinInjector(UsageEnvironment& env, char const* rtspURL, int verbosityLevel, char const* applicationName,
+			      DarwinInjector* ourDarwinInjector)
+    : RTSPClient(env, rtspURL, verbosityLevel, applicationName, 0),
+      fOurDarwinInjector(ourDarwinInjector) {}
+  virtual ~RTSPClientForDarwinInjector() {}
+  DarwinInjector* fOurDarwinInjector;
+};
+
 Boolean DarwinInjector
 ::setDestination(char const* remoteRTSPServerNameOrAddress,
 		 char const* remoteFileName,
@@ -116,8 +127,15 @@ Boolean DarwinInjector
   Boolean success = False; // until we learn otherwise
 
   do {
+    // Construct a RTSP URL for the remote stream:
+    char const* const urlFmt = "rtsp://%s:%u/%s";
+    unsigned urlLen
+      = strlen(urlFmt) + strlen(remoteRTSPServerNameOrAddress) + 5 /* max short len */ + strlen(remoteFileName);
+    url = new char[urlLen];
+    sprintf(url, urlFmt, remoteRTSPServerNameOrAddress, remoteRTSPServerPortNumber, remoteFileName);
+
     // Begin by creating our RTSP client object:
-    fRTSPClient = RTSPClient::createNew(envir(), fVerbosityLevel, fApplicationName);
+    fRTSPClient = new RTSPClientForDarwinInjector(envir(), url, fVerbosityLevel, fApplicationName, this);
     if (fRTSPClient == NULL) break;
 
     // Get the remote RTSP server's IP address:
@@ -176,24 +194,23 @@ Boolean DarwinInjector
       p += strlen(p);
     }
 
-    // Construct a RTSP URL for the remote stream:
-    char const* const urlFmt = "rtsp://%s:%u/%s";
-    unsigned urlLen
-      = strlen(urlFmt) + strlen(remoteRTSPServerNameOrAddress) + 5 /* max short len */ + strlen(remoteFileName);
-    url = new char[urlLen];
-    sprintf(url, urlFmt, remoteRTSPServerNameOrAddress, remoteRTSPServerPortNumber, remoteFileName);
-
     // Do a RTSP "ANNOUNCE" with this SDP description:
-    Boolean announceSuccess;
+    Authenticator auth;
+    Authenticator* authToUse = NULL;
     if (remoteUserName[0] != '\0' || remotePassword[0] != '\0') {
-      announceSuccess
-	= fRTSPClient->announceWithPassword(url, sdp, remoteUserName, remotePassword, timeout);
-    } else {
-      announceSuccess = fRTSPClient->announceSDPDescription(url, sdp, NULL, timeout);
+      auth.setUsernameAndPassword(remoteUserName, remotePassword);
+      authToUse = &auth;
     }
-    if (!announceSuccess) break;
+    fWatchVariable = 0;
+    (void)fRTSPClient->sendAnnounceCommand(sdp, genericResponseHandler, authToUse);
 
-    // Tell the remote server to start receiving the stream from us.
+    // Now block (but handling events) until we get a response:
+    envir().taskScheduler().doEventLoop(&fWatchVariable);
+
+    delete[] fResultString;
+    if (fResultCode != 0) break; // an error occurred with the RTSP "ANNOUNCE" command
+
+    // Next, tell the remote server to start receiving the stream from us.
     // (To do this, we first create a "MediaSession" object from the SDP description.)
     fSession = MediaSession::createNew(envir(), sdp);
     if (fSession == NULL) break;
@@ -206,11 +223,15 @@ Boolean DarwinInjector
     while ((subsession = iter.next()) != NULL) {
       if (!subsession->initiate()) break;
 
-      if (!fRTSPClient->setupMediaSubsession(*subsession,
-					     True /*streamOutgoing*/,
-					     True /*streamUsingTCP*/)) {
-	break;
-      }
+      fWatchVariable = 0;
+      (void)fRTSPClient->sendSetupCommand(*subsession, genericResponseHandler,
+					  True /*streamOutgoing*/,
+					  True /*streamUsingTCP*/);
+      // Now block (but handling events) until we get a response:
+      envir().taskScheduler().doEventLoop(&fWatchVariable);
+
+      delete[] fResultString;
+      if (fResultCode != 0) break; // an error occurred with the RTSP "SETUP" command
 
       // Tell this subsession's RTPSink and RTCPInstance to use
       // the RTSP TCP connection:
@@ -224,7 +245,14 @@ Boolean DarwinInjector
     if (subsession != NULL) break; // an error occurred above
 
     // Tell the RTSP server to start:
-    if (!fRTSPClient->playMediaSession(*fSession)) break;
+    fWatchVariable = 0;
+    (void)fRTSPClient->sendPlayCommand(*fSession, genericResponseHandler);
+
+    // Now block (but handling events) until we get a response:
+    envir().taskScheduler().doEventLoop(&fWatchVariable);
+
+    delete[] fResultString;
+    if (fResultCode != 0) break; // an error occurred with the RTSP "PLAY" command
 
     // Finally, make sure that the output TCP buffer is a reasonable size:
     increaseSendBufferTo(envir(), fRTSPClient->socketNum(), 100*1024);
@@ -241,6 +269,19 @@ Boolean DarwinInjector::isDarwinInjector() const {
   return True;
 }
 
+void DarwinInjector::genericResponseHandler(RTSPClient* rtspClient, int responseCode, char* responseString) {
+  DarwinInjector* di = ((RTSPClientForDarwinInjector*)rtspClient)-> fOurDarwinInjector;
+  di->genericResponseHandler1(responseCode, responseString);
+}
+
+void DarwinInjector::genericResponseHandler1(int responseCode, char* responseString) {
+  // Set result values:
+  fResultCode = responseCode;
+  fResultString = responseString;
+
+  // Signal a break from the event loop (thereby returning from the blocking command):                                              
+  fWatchVariable = ~0;
+}
 
 ////////// SubstreamDescriptor implementation //////////
 
diff --git a/liveMedia/FileSink.cpp b/liveMedia/FileSink.cpp
index 0284f58..529e4b5 100644
--- a/liveMedia/FileSink.cpp
+++ b/liveMedia/FileSink.cpp
@@ -88,7 +88,7 @@ void FileSink::afterGettingFrame(void* clientData, unsigned frameSize,
   sink->afterGettingFrame1(frameSize, presentationTime);
 }
 
-void FileSink::addData(unsigned char* data, unsigned dataSize,
+void FileSink::addData(unsigned char const* data, unsigned dataSize,
 		       struct timeval presentationTime) {
   if (fPerFrameFileNameBuffer != NULL) {
     // Special case: Open a new file on-the-fly for this frame
diff --git a/liveMedia/FramedSource.cpp b/liveMedia/FramedSource.cpp
index cd6bc99..3471f9f 100644
--- a/liveMedia/FramedSource.cpp
+++ b/liveMedia/FramedSource.cpp
@@ -62,7 +62,7 @@ void FramedSource::getNextFrame(unsigned char* to, unsigned maxSize,
   // Make sure we're not already being read:
   if (fIsCurrentlyAwaitingData) {
     envir() << "FramedSource[" << this << "]::getNextFrame(): attempting to read more than once at the same time!\n";
-    abort();
+    envir().internalError();
   }
 
   fTo = to;
diff --git a/liveMedia/H264VideoFileSink.cpp b/liveMedia/H264VideoFileSink.cpp
index fbbc9d3..8da17ab 100644
--- a/liveMedia/H264VideoFileSink.cpp
+++ b/liveMedia/H264VideoFileSink.cpp
@@ -20,13 +20,16 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 
 #include "H264VideoFileSink.hh"
 #include "OutputFile.hh"
+#include "H264VideoRTPSource.hh"
 
 ////////// H264VideoFileSink //////////
 
 H264VideoFileSink
-::H264VideoFileSink(UsageEnvironment& env, FILE* fid, unsigned bufferSize,
-		   char const* perFrameFileNamePrefix)
-  : FileSink(env, fid, bufferSize, perFrameFileNamePrefix) {
+::H264VideoFileSink(UsageEnvironment& env, FILE* fid,
+		    char const* sPropParameterSetsStr,
+		    unsigned bufferSize, char const* perFrameFileNamePrefix)
+  : FileSink(env, fid, bufferSize, perFrameFileNamePrefix),
+    fSPropParameterSetsStr(sPropParameterSetsStr), fHaveWrittenFirstFrame(False) {
 }
 
 H264VideoFileSink::~H264VideoFileSink() {
@@ -34,7 +37,8 @@ H264VideoFileSink::~H264VideoFileSink() {
 
 H264VideoFileSink*
 H264VideoFileSink::createNew(UsageEnvironment& env, char const* fileName,
-			    unsigned bufferSize, Boolean oneFilePerFrame) {
+			     char const* sPropParameterSetsStr,
+			     unsigned bufferSize, Boolean oneFilePerFrame) {
   do {
     FILE* fid;
     char const* perFrameFileNamePrefix;
@@ -49,20 +53,28 @@ H264VideoFileSink::createNew(UsageEnvironment& env, char const* fileName,
       perFrameFileNamePrefix = NULL;
     }
 
-    return new H264VideoFileSink(env, fid, bufferSize, perFrameFileNamePrefix);
+    return new H264VideoFileSink(env, fid, sPropParameterSetsStr, bufferSize, perFrameFileNamePrefix);
   } while (0);
 
   return NULL;
 }
 
-Boolean H264VideoFileSink::sourceIsCompatibleWithUs(MediaSource& source) {
-  // Just return true, should be checking for H.264 video streams though
-    return True;
-}
+void H264VideoFileSink::afterGettingFrame1(unsigned frameSize, struct timeval presentationTime) {
+  unsigned char const start_code[4] = {0x00, 0x00, 0x00, 0x01};
+
+  if (!fHaveWrittenFirstFrame) {
+    // If we have PPS/SPS NAL units encoded in a "sprop parameter string", prepend these to the file:
+    unsigned numSPropRecords;
+    SPropRecord* sPropRecords = parseSPropParameterSets(fSPropParameterSetsStr, numSPropRecords);
+    for (unsigned i = 0; i < numSPropRecords; ++i) {
+      addData(start_code, 4, presentationTime);
+      addData(sPropRecords[i].sPropBytes, sPropRecords[i].sPropLength, presentationTime);
+    }
+    delete[] sPropRecords;
+    fHaveWrittenFirstFrame = True; // for next time
+  }
 
-void H264VideoFileSink::afterGettingFrame1(unsigned frameSize,
-					  struct timeval presentationTime) {
-  unsigned char start_code[4] = {0x00, 0x00, 0x00, 0x01};
+  // Write the input data to the file, with the start code in front:
   addData(start_code, 4, presentationTime);
 
   // Call the parent class to complete the normal file write with the input data:
diff --git a/liveMedia/MP3ADU.cpp b/liveMedia/MP3ADU.cpp
index ab5d5f7..f0711cd 100644
--- a/liveMedia/MP3ADU.cpp
+++ b/liveMedia/MP3ADU.cpp
@@ -49,7 +49,7 @@ public:
 
 unsigned const Segment::headerSize = 4;
 
-#define SegmentQueueSize 10
+#define SegmentQueueSize 20
 
 class SegmentQueue {
 public:
diff --git a/liveMedia/MPEG1or2Demux.cpp b/liveMedia/MPEG1or2Demux.cpp
index c552878..ad1637c 100644
--- a/liveMedia/MPEG1or2Demux.cpp
+++ b/liveMedia/MPEG1or2Demux.cpp
@@ -162,7 +162,7 @@ void MPEG1or2Demux::registerReadInterest(u_int8_t streamIdTag,
   if (out.isCurrentlyAwaitingData) {
     envir() << "MPEG1or2Demux::registerReadInterest(): attempt to read stream id "
 	    << (void*)streamIdTag << " more than once!\n";
-    abort();
+    envir().internalError();
   }
 
   out.to = to; out.maxSize = maxSize;
diff --git a/liveMedia/MPEG1or2VideoRTPSource.cpp b/liveMedia/MPEG1or2VideoRTPSource.cpp
index f3d7443..ecb1abd 100644
--- a/liveMedia/MPEG1or2VideoRTPSource.cpp
+++ b/liveMedia/MPEG1or2VideoRTPSource.cpp
@@ -45,7 +45,7 @@ Boolean MPEG1or2VideoRTPSource
   // There's a 4-byte video-specific header
   if (packet->dataSize() < 4) return False;
 
-  u_int32_t header = ntohl(*(unsigned*)(packet->data()));
+  u_int32_t header = ntohl(*(u_int32_t*)(packet->data()));
 
   u_int32_t sBit = header&0x00002000; // sequence-header-present
   u_int32_t bBit = header&0x00001000; // beginning-of-slice
@@ -66,7 +66,7 @@ Boolean MPEG1or2VideoRTPSource
 
   // Extract the "Picture-Type" field from this, to determine whether
   // this packet can be used in jitter calculations:
-  unsigned header = ntohl(*(unsigned*)packet);
+  unsigned header = ntohl(*(u_int32_t*)packet);
 
   unsigned short pictureType = (header>>8)&0x7;
   if (pictureType == 1) { // an I frame
diff --git a/liveMedia/MPEG2TransportStreamFramer.cpp b/liveMedia/MPEG2TransportStreamFramer.cpp
index 7625963..3ea9a1d 100644
--- a/liveMedia/MPEG2TransportStreamFramer.cpp
+++ b/liveMedia/MPEG2TransportStreamFramer.cpp
@@ -24,15 +24,29 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 #include <GroupsockHelper.hh> // for "gettimeofday()"
 
 #define TRANSPORT_PACKET_SIZE 188
+
+////////// Definitions of constants that control the behavior of this code /////////
+
+#if !defined(NEW_DURATION_WEIGHT)
 #define NEW_DURATION_WEIGHT 0.5
   // How much weight to give to the latest duration measurement (must be <= 1)
+#endif
+
+#if !defined(TIME_ADJUSTMENT_FACTOR)
 #define TIME_ADJUSTMENT_FACTOR 0.8
   // A factor by which to adjust the duration estimate to ensure that the overall
   // packet transmission times remains matched with the PCR times (which will be the
   // times that we expect receivers to play the incoming packets).
   // (must be <= 1)
+#endif
+
+#if !defined(MAX_PLAYOUT_BUFFER_DURATION)
 #define MAX_PLAYOUT_BUFFER_DURATION 0.1 // (seconds)
+#endif
+
+#if !defined(PCR_PERIOD_VARIATION_RATIO)
 #define PCR_PERIOD_VARIATION_RATIO 0.5
+#endif
 
 ////////// PIDStatus //////////
 
diff --git a/liveMedia/Makefile.tail b/liveMedia/Makefile.tail
index ac9eaae..ad685f2 100644
--- a/liveMedia/Makefile.tail
+++ b/liveMedia/Makefile.tail
@@ -179,7 +179,7 @@ BasicUDPSink.$(CPP):	include/BasicUDPSink.hh
 include/BasicUDPSink.hh:	include/MediaSink.hh
 AMRAudioFileSink.$(CPP):	include/AMRAudioFileSink.hh include/AMRAudioSource.hh include/OutputFile.hh
 include/AMRAudioFileSink.hh:	include/FileSink.hh
-H264VideoFileSink.$(CPP):       include/H264VideoFileSink.hh include/OutputFile.hh
+H264VideoFileSink.$(CPP):       include/H264VideoFileSink.hh include/OutputFile.hh include/H264VideoRTPSource.hh
 include/H264VideoFileSink.hh:   include/FileSink.hh
 HTTPSink.$(CPP):	include/HTTPSink.hh
 include/HTTPSink.hh:		include/MediaSink.hh
diff --git a/liveMedia/Media.cpp b/liveMedia/Media.cpp
index b705359..5accb58 100644
--- a/liveMedia/Media.cpp
+++ b/liveMedia/Media.cpp
@@ -118,8 +118,8 @@ Boolean Medium::isDarwinInjector() const {
 
 ////////// _Tables implementation //////////
 
-_Tables* _Tables::getOurTables(UsageEnvironment& env) {
-  if (env.liveMediaPriv == NULL) {
+_Tables* _Tables::getOurTables(UsageEnvironment& env, Boolean createIfNotPresent) {
+  if (env.liveMediaPriv == NULL && createIfNotPresent) {
     env.liveMediaPriv = new _Tables(env);
   }
   return (_Tables*)(env.liveMediaPriv);
diff --git a/liveMedia/MediaSession.cpp b/liveMedia/MediaSession.cpp
index 6a598d4..9003333 100644
--- a/liveMedia/MediaSession.cpp
+++ b/liveMedia/MediaSession.cpp
@@ -964,10 +964,10 @@ void MediaSubsession::setDestinations(netAddressBits defaultDestAddress) {
 }
 
 double MediaSubsession::getNormalPlayTime(struct timeval const& presentationTime) {
-  // First, check whether our "RTPSource" object has already been synchronized using RTCP.
-  // If it hasn't, then - as a special case - we need to use the RTP timestamp to compute the NPT.
   if (rtpSource() == NULL || rtpSource()->timestampFrequency() == 0) return 0.0; // no RTP source, or bad freq!
 
+  // First, check whether our "RTPSource" object has already been synchronized using RTCP.
+  // If it hasn't, then - as a special case - we need to use the RTP timestamp to compute the NPT.
   if (!rtpSource()->hasBeenSynchronizedUsingRTCP()) {
     if (!rtpInfo.infoIsNew) return 0.0; // the "rtpInfo" structure has not been filled in
     u_int32_t timestampOffset = rtpSource()->curPacketRTPTimestamp() - rtpInfo.timestamp;
@@ -983,6 +983,7 @@ double MediaSubsession::getNormalPlayTime(struct timeval const& presentationTime
     if (rtpInfo.infoIsNew) {
       // This is the first time we've been called with a synchronized presentation time since the "rtpInfo"
       // structure was last filled in.  Use this "presentationTime" to compute "fNPT_PTS_Offset":
+      if (seqNumLT(rtpSource()->curPacketRTPSeqNum(), rtpInfo.seqNum)) return -0.1; // sanity check; ignore old packets
       u_int32_t timestampOffset = rtpSource()->curPacketRTPTimestamp() - rtpInfo.timestamp;
       double nptOffset = (timestampOffset/(double)(rtpSource()->timestampFrequency()))*scale();
       double npt = playStartTime() + nptOffset;
diff --git a/liveMedia/MediaSink.cpp b/liveMedia/MediaSink.cpp
index 885360a..2c736cb 100644
--- a/liveMedia/MediaSink.cpp
+++ b/liveMedia/MediaSink.cpp
@@ -134,8 +134,8 @@ void OutPacketBuffer::enqueue(unsigned char const* from, unsigned numBytes) {
   increment(numBytes);
 }
 
-void OutPacketBuffer::enqueueWord(unsigned word) {
-  unsigned nWord = htonl(word);
+void OutPacketBuffer::enqueueWord(u_int32_t word) {
+  u_int32_t nWord = htonl(word);
   enqueue((unsigned char*)&nWord, 4);
 }
 
@@ -153,8 +153,8 @@ void OutPacketBuffer::insert(unsigned char const* from, unsigned numBytes,
   }
 }
 
-void OutPacketBuffer::insertWord(unsigned word, unsigned toPosition) {
-  unsigned nWord = htonl(word);
+void OutPacketBuffer::insertWord(u_int32_t word, unsigned toPosition) {
+  u_int32_t nWord = htonl(word);
   insert((unsigned char*)&nWord, 4, toPosition);
 }
 
@@ -169,8 +169,8 @@ void OutPacketBuffer::extract(unsigned char* to, unsigned numBytes,
   memmove(to, &fBuf[realFromPosition], numBytes);
 }
 
-unsigned OutPacketBuffer::extractWord(unsigned fromPosition) {
-  unsigned nWord;
+u_int32_t OutPacketBuffer::extractWord(unsigned fromPosition) {
+  u_int32_t nWord;
   extract((unsigned char*)&nWord, 4, fromPosition);
   return ntohl(nWord);
 }
diff --git a/liveMedia/MultiFramedRTPSink.cpp b/liveMedia/MultiFramedRTPSink.cpp
index 42066fd..268d13c 100644
--- a/liveMedia/MultiFramedRTPSink.cpp
+++ b/liveMedia/MultiFramedRTPSink.cpp
@@ -389,17 +389,10 @@ void MultiFramedRTPSink::sendPacketIfNecessary() {
     // sending the next packet.
     struct timeval timeNow;
     gettimeofday(&timeNow, NULL);
-    int uSecondsToGo;
-    if (fNextSendTime.tv_sec < timeNow.tv_sec
-	|| (fNextSendTime.tv_sec == timeNow.tv_sec && fNextSendTime.tv_usec < timeNow.tv_usec)) {
-      uSecondsToGo = 0; // prevents integer underflow if too far behind
-    } else {
-      uSecondsToGo = (fNextSendTime.tv_sec - timeNow.tv_sec)*1000000 + (fNextSendTime.tv_usec - timeNow.tv_usec);
-    }
+    int64_t uSecondsToGo = (fNextSendTime.tv_sec - timeNow.tv_sec)*1000000 + (fNextSendTime.tv_usec - timeNow.tv_usec);
 
     // Delay this amount of time:
-    nextTask() = envir().taskScheduler().scheduleDelayedTask(uSecondsToGo,
-						(TaskFunc*)sendNext, this);
+    nextTask() = envir().taskScheduler().scheduleDelayedTask(uSecondsToGo, (TaskFunc*)sendNext, this);
   }
 }
 
diff --git a/liveMedia/MultiFramedRTPSource.cpp b/liveMedia/MultiFramedRTPSource.cpp
index 4b11a66..85acfff 100644
--- a/liveMedia/MultiFramedRTPSource.cpp
+++ b/liveMedia/MultiFramedRTPSource.cpp
@@ -77,6 +77,7 @@ void MultiFramedRTPSource::reset() {
   fCurrentPacketBeginsFrame = True; // by default
   fCurrentPacketCompletesFrame = True; // by default
   fAreDoingNetworkReads = False;
+  fPacketReadInProgress = NULL;
   fNeedDelivery = False;
   fPacketLossInFragmentedFrame = False;
 }
@@ -214,28 +215,41 @@ void MultiFramedRTPSource
 
 #define ADVANCE(n) do { bPacket->skip(n); } while (0)
 
-void MultiFramedRTPSource::networkReadHandler(MultiFramedRTPSource* source,
-					      int /*mask*/) {
-  // Get a free BufferedPacket descriptor to hold the new network packet:
-  BufferedPacket* bPacket
-    = source->fReorderingBuffer->getFreePacket(source);
+void MultiFramedRTPSource::networkReadHandler(MultiFramedRTPSource* source, int /*mask*/) {
+  source->networkReadHandler1();
+}
+
+void MultiFramedRTPSource::networkReadHandler1() {
+  BufferedPacket* bPacket = fPacketReadInProgress;
+  if (bPacket == NULL) {
+    // Normal case: Get a free BufferedPacket descriptor to hold the new network packet:
+    bPacket = fReorderingBuffer->getFreePacket(this);
+  }
 
   // Read the network packet, and perform sanity checks on the RTP header:
   Boolean readSuccess = False;
   do {
-    if (!bPacket->fillInData(source->fRTPInterface)) break;
+    Boolean packetReadWasIncomplete = fPacketReadInProgress != NULL;
+    if (!bPacket->fillInData(fRTPInterface, packetReadWasIncomplete)) break;
+    if (packetReadWasIncomplete) {
+      // We need additional read(s) before we can process the incoming packet:
+      fPacketReadInProgress = bPacket;
+      return;
+    } else {
+      fPacketReadInProgress = NULL;
+    }
 #ifdef TEST_LOSS
-    source->setPacketReorderingThresholdTime(0);
+    setPacketReorderingThresholdTime(0);
        // don't wait for 'lost' packets to arrive out-of-order later
     if ((our_random()%10) == 0) break; // simulate 10% packet loss
 #endif
 
     // Check for the 12-byte RTP header:
     if (bPacket->dataSize() < 12) break;
-    unsigned rtpHdr = ntohl(*(unsigned*)(bPacket->data())); ADVANCE(4);
+    unsigned rtpHdr = ntohl(*(u_int32_t*)(bPacket->data())); ADVANCE(4);
     Boolean rtpMarkerBit = (rtpHdr&0x00800000) >> 23;
-    unsigned rtpTimestamp = ntohl(*(unsigned*)(bPacket->data()));ADVANCE(4);
-    unsigned rtpSSRC = ntohl(*(unsigned*)(bPacket->data())); ADVANCE(4);
+    unsigned rtpTimestamp = ntohl(*(u_int32_t*)(bPacket->data()));ADVANCE(4);
+    unsigned rtpSSRC = ntohl(*(u_int32_t*)(bPacket->data())); ADVANCE(4);
 
     // Check the RTP version number (it should be 2):
     if ((rtpHdr&0xC0000000) != 0x80000000) break;
@@ -248,7 +262,7 @@ void MultiFramedRTPSource::networkReadHandler(MultiFramedRTPSource* source,
     // Check for (& ignore) any RTP header extension
     if (rtpHdr&0x10000000) {
       if (bPacket->dataSize() < 4) break;
-      unsigned extHdr = ntohl(*(unsigned*)(bPacket->data())); ADVANCE(4);
+      unsigned extHdr = ntohl(*(u_int32_t*)(bPacket->data())); ADVANCE(4);
       unsigned remExtSize = 4*(extHdr&0xFFFF);
       if (bPacket->dataSize() < remExtSize) break;
       ADVANCE(remExtSize);
@@ -264,21 +278,21 @@ void MultiFramedRTPSource::networkReadHandler(MultiFramedRTPSource* source,
     }
     // Check the Payload Type.
     if ((unsigned char)((rtpHdr&0x007F0000)>>16)
-	!= source->rtpPayloadFormat()) {
+	!= rtpPayloadFormat()) {
       break;
     }
 
     // The rest of the packet is the usable data.  Record and save it:
-    source->fLastReceivedSSRC = rtpSSRC;
+    fLastReceivedSSRC = rtpSSRC;
     unsigned short rtpSeqNo = (unsigned short)(rtpHdr&0xFFFF);
     Boolean usableInJitterCalculation
-      = source->packetIsUsableInJitterCalculation((bPacket->data()),
+      = packetIsUsableInJitterCalculation((bPacket->data()),
 						  bPacket->dataSize());
     struct timeval presentationTime; // computed by:
     Boolean hasBeenSyncedUsingRTCP; // computed by:
-    source->receptionStatsDB()
+    receptionStatsDB()
       .noteIncomingPacket(rtpSSRC, rtpSeqNo, rtpTimestamp,
-			  source->timestampFrequency(),
+			  timestampFrequency(),
 			  usableInJitterCalculation, presentationTime,
 			  hasBeenSyncedUsingRTCP, bPacket->dataSize());
 
@@ -288,13 +302,13 @@ void MultiFramedRTPSource::networkReadHandler(MultiFramedRTPSource* source,
     bPacket->assignMiscParams(rtpSeqNo, rtpTimestamp, presentationTime,
 			      hasBeenSyncedUsingRTCP, rtpMarkerBit,
 			      timeNow);
-    if (!source->fReorderingBuffer->storePacket(bPacket)) break;
+    if (!fReorderingBuffer->storePacket(bPacket)) break;
 
     readSuccess = True;
   } while (0);
-  if (!readSuccess) source->fReorderingBuffer->freePacket(bPacket);
+  if (!readSuccess) fReorderingBuffer->freePacket(bPacket);
 
-  source->doGetNextFrame1();
+  doGetNextFrame1();
   // If we didn't get proper data this time, we'll get another chance
 }
 
@@ -344,13 +358,12 @@ void BufferedPacket
   frameDurationInMicroseconds = 0; // by default.  Subclasses should correct this.
 }
 
-Boolean BufferedPacket::fillInData(RTPInterface& rtpInterface) {
-  reset();
+Boolean BufferedPacket::fillInData(RTPInterface& rtpInterface, Boolean& packetReadWasIncomplete) {
+  if (!packetReadWasIncomplete) reset();
 
   unsigned numBytesRead;
   struct sockaddr_in fromAddress;
-  if (!rtpInterface.handleRead(&fBuf[fTail], fPacketSize-fTail, numBytesRead,
-			       fromAddress)) {
+  if (!rtpInterface.handleRead(&fBuf[fTail], fPacketSize-fTail, numBytesRead, fromAddress, packetReadWasIncomplete)) {
     return False;
   }
   fTail += numBytesRead;
@@ -459,8 +472,7 @@ void ReorderingPacketBuffer::reset() {
   fSavedPacket = NULL;
 }
 
-BufferedPacket* ReorderingPacketBuffer
-::getFreePacket(MultiFramedRTPSource* ourSource) {
+BufferedPacket* ReorderingPacketBuffer::getFreePacket(MultiFramedRTPSource* ourSource) {
   if (fSavedPacket == NULL) { // we're being called for the first time
     fSavedPacket = fPacketFactory->createNewPacket(ourSource);
     fSavedPacketFree = True;
diff --git a/liveMedia/OnDemandServerMediaSubsession.cpp b/liveMedia/OnDemandServerMediaSubsession.cpp
index 2524250..069132e 100644
--- a/liveMedia/OnDemandServerMediaSubsession.cpp
+++ b/liveMedia/OnDemandServerMediaSubsession.cpp
@@ -466,7 +466,7 @@ void StreamState
     // Change RTP and RTCP to use the TCP socket instead of UDP:
     if (fRTPSink != NULL) {
       fRTPSink->addStreamSocket(dests->tcpSocketNum, dests->rtpChannelId);
-      fRTPSink->setServerRequestAlternativeByteHandler(serverRequestAlternativeByteHandler, serverRequestAlternativeByteHandlerClientData);
+      fRTPSink->setServerRequestAlternativeByteHandler(dests->tcpSocketNum, serverRequestAlternativeByteHandler, serverRequestAlternativeByteHandlerClientData);
     }
     if (fRTCPInstance != NULL) {
       fRTCPInstance->addStreamSocket(dests->tcpSocketNum, dests->rtcpChannelId);
diff --git a/liveMedia/RTCP.cpp b/liveMedia/RTCP.cpp
index ea2d74a..e004feb 100644
--- a/liveMedia/RTCP.cpp
+++ b/liveMedia/RTCP.cpp
@@ -149,6 +149,7 @@ RTCPInstance::RTCPInstance(UsageEnvironment& env, Groupsock* RTCPgs,
   fKnownMembers = new RTCPMemberDatabase(*this);
   fInBuf = new unsigned char[maxPacketSize];
   if (fKnownMembers == NULL || fInBuf == NULL) return;
+  fNumBytesAlreadyRead = 0;
 
   // A hack to save buffer space, because RTCP packets are always small:
   unsigned savedMaxSize = OutPacketBuffer::maxSize;
@@ -297,7 +298,7 @@ void RTCPInstance::setStreamSocket(int sockNum,
 void RTCPInstance::addStreamSocket(int sockNum,
 				   unsigned char streamChannelId) {
   // First, turn off background read handling for the default (UDP) socket:
-  fRTCPInterface.stopNetworkReading();
+  envir().taskScheduler().turnOffBackgroundReadHandling(fRTCPInterface.gs()->socketNum());
 
   // Add the RTCP-over-TCP interface:
   fRTCPInterface.addStreamSocket(sockNum, streamChannelId);
@@ -319,18 +320,24 @@ void RTCPInstance::incomingReportHandler(RTCPInstance* instance,
 }
 
 void RTCPInstance::incomingReportHandler1() {
-  unsigned char* pkt = fInBuf;
-  unsigned packetSize;
-  struct sockaddr_in fromAddress;
-  int typeOfPacket = PACKET_UNKNOWN_TYPE;
-
   do {
     int tcpReadStreamSocketNum = fRTCPInterface.nextTCPReadStreamSocketNum();
     unsigned char tcpReadStreamChannelId = fRTCPInterface.nextTCPReadStreamChannelId();
-    if (!fRTCPInterface.handleRead(pkt, maxPacketSize,
-				   packetSize, fromAddress)) {
-      break;
+    unsigned packetSize = 0;
+    unsigned numBytesRead;
+    struct sockaddr_in fromAddress;
+    Boolean packetReadWasIncomplete;
+    Boolean readResult
+      = fRTCPInterface.handleRead(&fInBuf[fNumBytesAlreadyRead], maxPacketSize - fNumBytesAlreadyRead,
+				  numBytesRead, fromAddress, packetReadWasIncomplete);
+    if (packetReadWasIncomplete) {
+      fNumBytesAlreadyRead += numBytesRead;
+      return; // more reads are needed to get the entire packet
+    } else { // normal case: We've read the entire packet 
+      packetSize = fNumBytesAlreadyRead + numBytesRead;
+      fNumBytesAlreadyRead = 0; // for next time
     }
+    if (!readResult) break;
 
     // Ignore the packet if it was looped-back from ourself:
     if (RTCPgs()->wasLoopedBackFromUs(envir(), fromAddress)) {
@@ -346,6 +353,7 @@ void RTCPInstance::incomingReportHandler1() {
       }
     }
 
+    unsigned char* pkt = fInBuf;
     if (fIsSSMSource) {
       // This packet was received via unicast.  'Reflect' it by resending
       // it to the multicast group.
@@ -360,10 +368,9 @@ void RTCPInstance::incomingReportHandler1() {
 
 #ifdef DEBUG
     fprintf(stderr, "[%p]saw incoming RTCP packet (from address %s, port %d)\n", this, our_inet_ntoa(fromAddress.sin_addr), ntohs(fromAddress.sin_port));
-    unsigned char* p = pkt;
     for (unsigned i = 0; i < packetSize; ++i) {
       if (i%4 == 0) fprintf(stderr, " ");
-      fprintf(stderr, "%02x", p[i]);
+      fprintf(stderr, "%02x", pkt[i]);
     }
     fprintf(stderr, "\n");
 #endif
@@ -374,7 +381,7 @@ void RTCPInstance::incomingReportHandler1() {
     // must be version=2, with no padding bit, and a payload type of
     // SR (200) or RR (201):
     if (packetSize < 4) break;
-    unsigned rtcpHdr = ntohl(*(unsigned*)pkt);
+    unsigned rtcpHdr = ntohl(*(u_int32_t*)pkt);
     if ((rtcpHdr & 0xE0FE0000) != (0x80000000 | (RTCP_PT_SR<<16))) {
 #ifdef DEBUG
       fprintf(stderr, "rejected bad RTCP packet: header 0x%08x\n", rtcpHdr);
@@ -384,6 +391,7 @@ void RTCPInstance::incomingReportHandler1() {
 
     // Process each of the individual RTCP 'subpackets' in (what may be)
     // a compound RTCP packet.
+    int typeOfPacket = PACKET_UNKNOWN_TYPE;
     unsigned reportSenderSSRC = 0;
     Boolean packetOK = False;
     while (1) {
@@ -395,7 +403,7 @@ void RTCPInstance::incomingReportHandler1() {
 
       // Assume that each RTCP subpacket begins with a 4-byte SSRC:
       if (length < 4) break; length -= 4;
-      reportSenderSSRC = ntohl(*(unsigned*)pkt); ADVANCE(4);
+      reportSenderSSRC = ntohl(*(u_int32_t*)pkt); ADVANCE(4);
 
       Boolean subPacketOK = False;
       switch (pt) {
@@ -406,9 +414,9 @@ void RTCPInstance::incomingReportHandler1() {
 	  if (length < 20) break; length -= 20;
 
 	  // Extract the NTP timestamp, and note this:
-	  unsigned NTPmsw = ntohl(*(unsigned*)pkt); ADVANCE(4);
-	  unsigned NTPlsw = ntohl(*(unsigned*)pkt); ADVANCE(4);
-	  unsigned rtpTimestamp = ntohl(*(unsigned*)pkt); ADVANCE(4);
+	  unsigned NTPmsw = ntohl(*(u_int32_t*)pkt); ADVANCE(4);
+	  unsigned NTPlsw = ntohl(*(u_int32_t*)pkt); ADVANCE(4);
+	  unsigned rtpTimestamp = ntohl(*(u_int32_t*)pkt); ADVANCE(4);
 	  if (fSource != NULL) {
 	    RTPReceptionStatsDB& receptionStats
 	      = fSource->receptionStatsDB();
@@ -434,14 +442,14 @@ void RTCPInstance::incomingReportHandler1() {
 	    // Use this information to update stats about our transmissions:
             RTPTransmissionStatsDB& transmissionStats = fSink->transmissionStatsDB();
             for (unsigned i = 0; i < rc; ++i) {
-              unsigned senderSSRC = ntohl(*(unsigned*)pkt); ADVANCE(4);
+              unsigned senderSSRC = ntohl(*(u_int32_t*)pkt); ADVANCE(4);
               // We care only about reports about our own transmission, not others'
               if (senderSSRC == fSink->SSRC()) {
-                unsigned lossStats = ntohl(*(unsigned*)pkt); ADVANCE(4);
-                unsigned highestReceived = ntohl(*(unsigned*)pkt); ADVANCE(4);
-                unsigned jitter = ntohl(*(unsigned*)pkt); ADVANCE(4);
-                unsigned timeLastSR = ntohl(*(unsigned*)pkt); ADVANCE(4);
-                unsigned timeSinceLastSR = ntohl(*(unsigned*)pkt); ADVANCE(4);
+                unsigned lossStats = ntohl(*(u_int32_t*)pkt); ADVANCE(4);
+                unsigned highestReceived = ntohl(*(u_int32_t*)pkt); ADVANCE(4);
+                unsigned jitter = ntohl(*(u_int32_t*)pkt); ADVANCE(4);
+                unsigned timeLastSR = ntohl(*(u_int32_t*)pkt); ADVANCE(4);
+                unsigned timeSinceLastSR = ntohl(*(u_int32_t*)pkt); ADVANCE(4);
                 transmissionStats.noteIncomingRR(reportSenderSSRC, fromAddress,
 						 lossStats,
 						 highestReceived, jitter,
@@ -541,7 +549,7 @@ void RTCPInstance::incomingReportHandler1() {
 #endif
 	break;
       }
-      rtcpHdr = ntohl(*(unsigned*)pkt);
+      rtcpHdr = ntohl(*(u_int32_t*)pkt);
       if ((rtcpHdr & 0xC0000000) != 0x80000000) {
 #ifdef DEBUG
 	fprintf(stderr, "bad RTCP subpacket: header 0x%08x\n", rtcpHdr);
@@ -860,10 +868,11 @@ void RTCPInstance::schedule(double nextTime) {
   fNextReportTime = nextTime;
 
   double secondsToDelay = nextTime - dTimeNow();
+  if (secondsToDelay < 0) secondsToDelay = 0;
 #ifdef DEBUG
   fprintf(stderr, "schedule(%f->%f)\n", secondsToDelay, nextTime);
 #endif
-  int usToGo = (int)(secondsToDelay * 1000000);
+  int64_t usToGo = (int64_t)(secondsToDelay * 1000000);
   nextTask() = envir().taskScheduler().scheduleDelayedTask(usToGo,
 				(TaskFunc*)RTCPInstance::onExpire, this);
 }
diff --git a/liveMedia/RTPInterface.cpp b/liveMedia/RTPInterface.cpp
index d49d544..ccba41e 100644
--- a/liveMedia/RTPInterface.cpp
+++ b/liveMedia/RTPInterface.cpp
@@ -37,8 +37,10 @@ static void sendRTPOverTCP(unsigned char* packet, unsigned packetSize,
 // "SocketDescriptor" that contains a hash table for each of the
 // sub-channels that are reading from this socket.
 
-static HashTable* socketHashTable(UsageEnvironment& env) {
-  _Tables* ourTables = _Tables::getOurTables(env);
+static HashTable* socketHashTable(UsageEnvironment& env, Boolean createIfNotPresent = True) {
+  _Tables* ourTables = _Tables::getOurTables(env, createIfNotPresent);
+  if (ourTables == NULL) return NULL;
+
   if (ourTables->socketTable == NULL) {
     // Create a new socket number -> SocketDescriptor mapping table:
     ourTables->socketTable = HashTable::create(ONE_WORD_HASH_KEYS);
@@ -63,6 +65,7 @@ public:
 
 private:
   static void tcpReadHandler(SocketDescriptor*, int mask);
+  void tcpReadHandler1(int mask);
 
 private:
   UsageEnvironment& fEnv;
@@ -70,14 +73,19 @@ private:
   HashTable* fSubChannelHashTable;
   ServerRequestAlternativeByteHandler* fServerRequestAlternativeByteHandler;
   void* fServerRequestAlternativeByteHandlerClientData;
+  u_int8_t fStreamChannelId, fSizeByte1;
+  enum { AWAITING_DOLLAR, AWAITING_STREAM_CHANNEL_ID, AWAITING_SIZE1, AWAITING_SIZE2, AWAITING_PACKET_DATA } fTCPReadingState;
 };
 
 static SocketDescriptor* lookupSocketDescriptor(UsageEnvironment& env, int sockNum, Boolean createIfNotFound = True) {
+  HashTable* table = socketHashTable(env, createIfNotFound);
+  if (table == NULL) return NULL;
+
   char const* key = (char const*)(long)sockNum;
-  SocketDescriptor* socketDescriptor = (SocketDescriptor*)(socketHashTable(env)->Lookup(key));
+  SocketDescriptor* socketDescriptor = (SocketDescriptor*)(table->Lookup(key));
   if (socketDescriptor == NULL && createIfNotFound) {
     socketDescriptor = new SocketDescriptor(env, sockNum);
-    socketHashTable(env)->Add((char const*)(long)(sockNum), socketDescriptor);
+    table->Add((char const*)(long)(sockNum), socketDescriptor);
   }
 
   return socketDescriptor;
@@ -166,14 +174,11 @@ void RTPInterface::removeStreamSocket(int sockNum,
   }
 }
 
-void RTPInterface::setServerRequestAlternativeByteHandler(ServerRequestAlternativeByteHandler* handler, void* clientData) {
-  for (tcpStreamRecord* streams = fTCPStreams; streams != NULL;
-       streams = streams->fNext) {
-    // Get (or create, if necessary) a socket descriptor for "streams->fStreamSocketNum":
-    SocketDescriptor* socketDescriptor = lookupSocketDescriptor(envir(), streams->fStreamSocketNum);
+void RTPInterface
+::setServerRequestAlternativeByteHandler(int socketNum, ServerRequestAlternativeByteHandler* handler, void* clientData) {
+  SocketDescriptor* socketDescriptor = lookupSocketDescriptor(envir(), socketNum);
 
-    socketDescriptor->setServerRequestAlternativeByteHandler(handler, clientData);
-  }
+  if (socketDescriptor != NULL) socketDescriptor->setServerRequestAlternativeByteHandler(handler, clientData);
 }
 
 
@@ -207,10 +212,9 @@ void RTPInterface
   }
 }
 
-Boolean RTPInterface::handleRead(unsigned char* buffer,
-				 unsigned bufferMaxSize,
-				 unsigned& bytesRead,
-				 struct sockaddr_in& fromAddress) {
+Boolean RTPInterface::handleRead(unsigned char* buffer, unsigned bufferMaxSize,
+				 unsigned& bytesRead, struct sockaddr_in& fromAddress, Boolean& packetReadWasIncomplete) {
+  packetReadWasIncomplete = False; // by default
   Boolean readSuccess;
   if (fNextTCPReadStreamSocketNum < 0) {
     // Normal case: read from the (datagram) 'groupsock':
@@ -229,7 +233,11 @@ Boolean RTPInterface::handleRead(unsigned char* buffer,
       if (bytesRead >= totBytesToRead) break;
       curBytesToRead -= curBytesRead;
     }
-    if (curBytesRead <= 0) {
+    fNextTCPReadSize -= bytesRead;
+    if (curBytesRead == 0 && curBytesToRead > 0) {
+      packetReadWasIncomplete = True;
+      return True;
+    } else if (curBytesRead < 0) {
       bytesRead = 0;
       readSuccess = False;
     } else {
@@ -294,7 +302,8 @@ void sendRTPOverTCP(unsigned char* packet, unsigned packetSize,
 SocketDescriptor::SocketDescriptor(UsageEnvironment& env, int socketNum)
   :fEnv(env), fOurSocketNum(socketNum),
     fSubChannelHashTable(HashTable::create(ONE_WORD_HASH_KEYS)),
-   fServerRequestAlternativeByteHandler(NULL), fServerRequestAlternativeByteHandlerClientData(NULL) {
+   fServerRequestAlternativeByteHandler(NULL), fServerRequestAlternativeByteHandlerClientData(NULL),
+   fTCPReadingState(AWAITING_DOLLAR) {
 }
 
 SocketDescriptor::~SocketDescriptor() {
@@ -335,54 +344,87 @@ void SocketDescriptor
 }
 
 void SocketDescriptor::tcpReadHandler(SocketDescriptor* socketDescriptor, int mask) {
-  do {
-    UsageEnvironment& env = socketDescriptor->fEnv; // abbrev
-    int socketNum = socketDescriptor->fOurSocketNum;
-
-    // Begin by reading any characters that aren't '$'.  Any such characters are regular RTSP commands or responses,
-    // which need to be handled separately.
-    unsigned char c;
-    struct sockaddr_in fromAddress;
-    struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 0;
-    while (1) {
-      int result = readSocket(env, socketNum, &c, 1, fromAddress, &timeout);
-      if (result != 1) { // error reading TCP socket
-	if (result < 0) {
-	  env.taskScheduler().turnOffBackgroundReadHandling(socketNum); // stops further calls to us
+  socketDescriptor->tcpReadHandler1(mask);
+}
+
+void SocketDescriptor::tcpReadHandler1(int mask) {
+  // We expect the following data over the TCP channel:
+  //   optional RTSP command or response bytes (before the first '$' character)
+  //   a '$' character
+  //   a 1-byte channel id
+  //   a 2-byte packet size (in network byte order)
+  //   the packet data.
+  // However, because the socket is being read asynchronously, this data might arrive in pieces.
+  
+  u_int8_t c;
+  struct sockaddr_in fromAddress;
+  if (fTCPReadingState != AWAITING_PACKET_DATA) {
+    int result = readSocket(fEnv, fOurSocketNum, &c, 1, fromAddress);
+    if (result != 1) { // error reading TCP socket, or no more data available
+      if (result < 0) { // error
+	fEnv.taskScheduler().turnOffBackgroundReadHandling(fOurSocketNum); // stops further calls to us
+      }
+      return;
+    }
+  }
+  
+  switch (fTCPReadingState) {
+    case AWAITING_DOLLAR: {
+      if (c == '$') {
+	fTCPReadingState = AWAITING_STREAM_CHANNEL_ID;
+      } else {
+	// This character is part of a RTSP request or command, which is handled separately:
+	if (fServerRequestAlternativeByteHandler != NULL) {
+	  (*fServerRequestAlternativeByteHandler)(fServerRequestAlternativeByteHandlerClientData, c);
 	}
-	return;
       }
-
-      if (c == '$') break;
-      if (socketDescriptor->fServerRequestAlternativeByteHandler != NULL) {
-	(*socketDescriptor->fServerRequestAlternativeByteHandler)(socketDescriptor->fServerRequestAlternativeByteHandlerClientData, c);
+      break;
+    }
+    case AWAITING_STREAM_CHANNEL_ID: {
+      // The byte that we read is the stream channel id.
+      fStreamChannelId = c;
+      fTCPReadingState = AWAITING_SIZE1;
+      break;
+    }
+    case AWAITING_SIZE1: {
+      // The byte that we read is the first (high) byte of the 16-bit RTP or RTCP packet 'size'.
+      fSizeByte1 = c;
+      fTCPReadingState = AWAITING_SIZE2;
+      break;
+    }
+    case AWAITING_SIZE2: {
+      // The byte that we read is the second (low) byte of the 16-bit RTP or RTCP packet 'size'.
+      unsigned short size = (fSizeByte1<<8)|c;
+      
+      // Record the information about the packet data that will be read next:
+      RTPInterface* rtpInterface = lookupRTPInterface(fStreamChannelId);
+      if (rtpInterface != NULL) {
+	rtpInterface->fNextTCPReadSize = size;
+	rtpInterface->fNextTCPReadStreamSocketNum = fOurSocketNum;
+	rtpInterface->fNextTCPReadStreamChannelId = fStreamChannelId;
       }
+      fTCPReadingState = AWAITING_PACKET_DATA;
+      break;
     }
-
-    // The next byte is the stream channel id:
-    unsigned char streamChannelId;
-    if (readSocket(env, socketNum, &streamChannelId, 1, fromAddress) != 1) break;
-    RTPInterface* rtpInterface = socketDescriptor->lookupRTPInterface(streamChannelId);
-    if (rtpInterface == NULL) break; // we're not interested in this channel
-
-    // The next two bytes are the RTP or RTCP packet size (in network order)
-    unsigned short size;
-    if (readSocketExact(env, socketNum, (unsigned char*)&size, 2,
-			fromAddress) != 2) break;
-    rtpInterface->fNextTCPReadSize = ntohs(size);
-    rtpInterface->fNextTCPReadStreamSocketNum = socketNum;
-    rtpInterface->fNextTCPReadStreamChannelId = streamChannelId;
+    case AWAITING_PACKET_DATA: {
+      // Call the appropriate read handler to get the packet data from the TCP stream:
+      RTPInterface* rtpInterface = lookupRTPInterface(fStreamChannelId);
+      if (rtpInterface != NULL) {
+	if (rtpInterface->fNextTCPReadSize == 0) {
+	  // We've already read all the data for this packet.
+	  fTCPReadingState = AWAITING_DOLLAR;
+	  break;
+	}
+	if (rtpInterface->fReadHandlerProc != NULL) {
 #ifdef DEBUG
-    fprintf(stderr, "SocketDescriptor::tcpReadHandler() reading %d bytes on channel %d\n", rtpInterface->fNextTCPReadSize, streamChannelId);
+	  fprintf(stderr, "SocketDescriptor::tcpReadHandler() reading %d bytes on channel %d\n", rtpInterface->fNextTCPReadSize, rtpInterface->fNextTCPReadStreamChannelId);
 #endif
-
-    // Now that we have the data set up, call this subchannel's
-    // read handler:
-    if (rtpInterface->fReadHandlerProc != NULL) {
-      rtpInterface->fReadHandlerProc(rtpInterface->fOwner, mask);
+	  rtpInterface->fReadHandlerProc(rtpInterface->fOwner, mask);
+	}
+      }
+      return;
     }
-
-  } while (0);
+  }
 }
 
 
diff --git a/liveMedia/RTPSource.cpp b/liveMedia/RTPSource.cpp
index ac289b0..810449a 100644
--- a/liveMedia/RTPSource.cpp
+++ b/liveMedia/RTPSource.cpp
@@ -217,8 +217,8 @@ void RTPReceptionStats::init(u_int32_t SSRC) {
 }
 
 void RTPReceptionStats::initSeqNum(u_int16_t initialSeqNum) {
-    fBaseExtSeqNumReceived = initialSeqNum-1;
-    fHighestExtSeqNumReceived = initialSeqNum;
+    fBaseExtSeqNumReceived = 0x10000 | initialSeqNum;
+    fHighestExtSeqNumReceived = 0x10000 | initialSeqNum;
     fHaveSeenInitialSequenceNumber = True;
 }
 
@@ -245,19 +245,33 @@ void RTPReceptionStats
 
   // Check whether the new sequence number is the highest yet seen:
   unsigned oldSeqNum = (fHighestExtSeqNumReceived&0xFFFF);
+  unsigned seqNumCycle = (fHighestExtSeqNumReceived&0xFFFF0000);
+  unsigned seqNumDifference = (unsigned)((int)seqNum-(int)oldSeqNum);
+  unsigned newSeqNum = 0;
   if (seqNumLT((u_int16_t)oldSeqNum, seqNum)) {
     // This packet was not an old packet received out of order, so check it:
-    unsigned seqNumCycle = (fHighestExtSeqNumReceived&0xFFFF0000);
-    unsigned seqNumDifference = (unsigned)((int)seqNum-(int)oldSeqNum);
+    
     if (seqNumDifference >= 0x8000) {
       // The sequence number wrapped around, so start a new cycle:
       seqNumCycle += 0x10000;
     }
-
-    unsigned newSeqNum = seqNumCycle|seqNum;
+    
+    newSeqNum = seqNumCycle|seqNum;
     if (newSeqNum > fHighestExtSeqNumReceived) {
       fHighestExtSeqNumReceived = newSeqNum;
     }
+  } else if (fTotNumPacketsReceived > 1) {
+    // This packet was an old packet received out of order
+    
+    if ((int)seqNumDifference >= 0x8000) {
+      // The sequence number wrapped around, so switch to an old cycle:
+      seqNumCycle -= 0x10000;
+    }
+    
+    newSeqNum = seqNumCycle|seqNum;
+    if (newSeqNum < fBaseExtSeqNumReceived) {
+      fBaseExtSeqNumReceived = newSeqNum;
+    }
   }
 
   // Record the inter-packet delay
diff --git a/liveMedia/RTSPClient.cpp b/liveMedia/RTSPClient.cpp
index e7382fa..f1f03b1 100644
--- a/liveMedia/RTSPClient.cpp
+++ b/liveMedia/RTSPClient.cpp
@@ -25,752 +25,579 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 #include <GroupsockHelper.hh>
 #include "our_md5.h"
 
-////////// RTSPClient //////////
+////////// RTSPClient implementation //////////
 
-RTSPClient* RTSPClient::createNew(UsageEnvironment& env,
+RTSPClient* RTSPClient::createNew(UsageEnvironment& env, char const* rtspURL,
 				  int verbosityLevel,
 				  char const* applicationName,
 				  portNumBits tunnelOverHTTPPortNum) {
-  return new RTSPClient(env, verbosityLevel,
-			applicationName, tunnelOverHTTPPortNum);
+  return new RTSPClient(env, rtspURL,
+			verbosityLevel, applicationName, tunnelOverHTTPPortNum);
 }
 
-Boolean RTSPClient::lookupByName(UsageEnvironment& env,
-				 char const* instanceName,
-				 RTSPClient*& resultClient) {
-  resultClient = NULL; // unless we succeed
-
-  Medium* medium;
-  if (!Medium::lookupByName(env, instanceName, medium)) return False;
-
-  if (!medium->isRTSPClient()) {
-    env.setResultMsg(instanceName, " is not a RTSP client");
-    return False;
-  }
-
-  resultClient = (RTSPClient*)medium;
-  return True;
+unsigned RTSPClient::sendDescribeCommand(responseHandler* responseHandler, Authenticator* authenticator) {
+  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  return sendRequest(new RequestRecord(++fCSeq, "DESCRIBE", responseHandler));
 }
 
-RTSPClient::RTSPClient(UsageEnvironment& env,
-		       int verbosityLevel, char const* applicationName,
-		       portNumBits tunnelOverHTTPPortNum)
-  : Medium(env),
-    fVerbosityLevel(verbosityLevel),
-    fTunnelOverHTTPPortNum(tunnelOverHTTPPortNum),
-    fInputSocketNum(-1), fOutputSocketNum(-1), fServerAddress(0), fCSeq(0),
-    fBaseURL(NULL), fTCPStreamIdCount(0), fLastSessionId(NULL),
-    fSessionTimeoutParameter(0),
-    fServerIsKasenna(False), fKasennaContentType(NULL),
-    fServerIsMicrosoft(False)
-{
-  fResponseBufferSize = 20000;
-  fResponseBuffer = new char[fResponseBufferSize+1];
+unsigned RTSPClient::sendOptionsCommand(responseHandler* responseHandler, Authenticator* authenticator) {
+  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  return sendRequest(new RequestRecord(++fCSeq, "OPTIONS", responseHandler));
+}
 
-  // Set the "User-Agent:" header to use in each request:
-  char const* const libName = "LIVE555 Streaming Media v";
-  char const* const libVersionStr = LIVEMEDIA_LIBRARY_VERSION_STRING;
-  char const* libPrefix; char const* libSuffix;
-  if (applicationName == NULL || applicationName[0] == '\0') {
-    applicationName = libPrefix = libSuffix = "";
-  } else {
-    libPrefix = " (";
-    libSuffix = ")";
-  }
-  char const* const formatStr = "User-Agent: %s%s%s%s%s\r\n";
-  unsigned headerSize
-    = strlen(formatStr) + strlen(applicationName) + strlen(libPrefix)
-    + strlen(libName) + strlen(libVersionStr) + strlen(libSuffix);
-  fUserAgentHeaderStr = new char[headerSize];
-  sprintf(fUserAgentHeaderStr, formatStr,
-	  applicationName, libPrefix, libName, libVersionStr, libSuffix);
-  fUserAgentHeaderStrSize = strlen(fUserAgentHeaderStr);
+unsigned RTSPClient::sendAnnounceCommand(char const* sdpDescription, responseHandler* responseHandler, Authenticator* authenticator) {
+  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  return sendRequest(new RequestRecord(++fCSeq, "ANNOUNCE", responseHandler, NULL, NULL, False, 0.0, 0.0, 0.0, sdpDescription));
 }
 
-void RTSPClient::setUserAgentString(char const* userAgentStr) {
-  if (userAgentStr == NULL) return;
+unsigned RTSPClient::sendSetupCommand(MediaSubsession& subsession, responseHandler* responseHandler,
+                                      Boolean streamOutgoing, Boolean streamUsingTCP, Boolean forceMulticastOnUnspecified,
+				      Authenticator* authenticator) {
+  if (fTunnelOverHTTPPortNum != 0) streamUsingTCP = True; // RTSP-over-HTTP tunneling uses TCP (by definition)
+  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+
+  u_int32_t booleanFlags = 0;
+  if (streamUsingTCP) booleanFlags |= 0x1;
+  if (streamOutgoing) booleanFlags |= 0x2;
+  if (forceMulticastOnUnspecified) booleanFlags |= 0x4;
+  return sendRequest(new RequestRecord(++fCSeq, "SETUP", responseHandler, NULL, &subsession, booleanFlags));
+}
 
-  // Change the existing user agent header string:
-  char const* const formatStr = "User-Agent: %s\r\n";
-  unsigned const headerSize = strlen(formatStr) + strlen(userAgentStr) + 1;
-  delete[] fUserAgentHeaderStr;
-  fUserAgentHeaderStr = new char[headerSize];
-  sprintf(fUserAgentHeaderStr, formatStr, userAgentStr);
-  fUserAgentHeaderStrSize = strlen(fUserAgentHeaderStr);
+unsigned RTSPClient::sendPlayCommand(MediaSession& session, responseHandler* responseHandler,
+                                     double start, double end, float scale,
+                                     Authenticator* authenticator) {
+  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  return sendRequest(new RequestRecord(++fCSeq, "PLAY", responseHandler, &session, NULL, 0, start, end, scale));
 }
 
-RTSPClient::~RTSPClient() {
-  envir().taskScheduler().turnOffBackgroundReadHandling(fInputSocketNum); // must be called before:
-  reset();
+unsigned RTSPClient::sendPlayCommand(MediaSubsession& subsession, responseHandler* responseHandler,
+                                     double start, double end, float scale,
+                                     Authenticator* authenticator) {
+  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  return sendRequest(new RequestRecord(++fCSeq, "PLAY", responseHandler, NULL, &subsession, 0, start, end, scale));
+}
 
-  delete[] fResponseBuffer;
-  delete[] fUserAgentHeaderStr;
+unsigned RTSPClient::sendPauseCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator) {
+  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  return sendRequest(new RequestRecord(++fCSeq, "PAUSE", responseHandler, &session));
 }
 
-Boolean RTSPClient::isRTSPClient() const {
-  return True;
+unsigned RTSPClient::sendPauseCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator) {
+  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  return sendRequest(new RequestRecord(++fCSeq, "PAUSE", responseHandler, NULL, &subsession));
 }
 
-void RTSPClient::resetTCPSockets() {
-  if (fInputSocketNum >= 0) {
-    ::closeSocket(fInputSocketNum);
-    if (fOutputSocketNum != fInputSocketNum) ::closeSocket(fOutputSocketNum);
-  }
-  fInputSocketNum = fOutputSocketNum = -1;
+unsigned RTSPClient::sendRecordCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator) {
+  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  return sendRequest(new RequestRecord(++fCSeq, "RECORD", responseHandler, &session));
 }
 
-void RTSPClient::reset() {
-  resetTCPSockets();
-  fServerAddress = 0;
+unsigned RTSPClient::sendRecordCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator) {
+  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  return sendRequest(new RequestRecord(++fCSeq, "RECORD", responseHandler, NULL, &subsession));
+}
 
-  delete[] fBaseURL; fBaseURL = NULL;
+unsigned RTSPClient::sendTeardownCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator) {
+  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  return sendRequest(new RequestRecord(++fCSeq, "TEARDOWN", responseHandler, &session));
+}
 
-  fCurrentAuthenticator.reset();
+unsigned RTSPClient::sendTeardownCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator) {
+  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  return sendRequest(new RequestRecord(++fCSeq, "TEARDOWN", responseHandler, NULL, &subsession));
+}
 
-  delete[] fKasennaContentType; fKasennaContentType = NULL;
-  delete[] fLastSessionId; fLastSessionId = NULL;
+unsigned RTSPClient::sendSetParameterCommand(MediaSession& session, responseHandler* responseHandler,
+                                             char const* parameterName, char const* parameterValue,
+                                             Authenticator* authenticator) {
+  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  char* paramString = new char[strlen(parameterName) + strlen(parameterValue) + 10];
+  sprintf(paramString, "%s: %s\r\n", parameterName, parameterValue);
+  unsigned result = sendRequest(new RequestRecord(++fCSeq, "SET_PARAMETER", responseHandler, &session, NULL, False, 0.0, 0.0, 0.0, paramString));
+  delete[] paramString;
+  return result;
 }
 
-static char* getLine(char* startOfLine) {
-  // returns the start of the next line, or NULL if none
-  for (char* ptr = startOfLine; *ptr != '\0'; ++ptr) {
-    // Check for the end of line: \r\n (but also accept \r or \n by itself):
-    if (*ptr == '\r' || *ptr == '\n') {
-      // We found the end of the line
-      if (*ptr == '\r') {
-	*ptr++ = '\0';
-	if (*ptr == '\n') ++ptr;
-      } else {
-        *ptr++ = '\0';
-      }
-      return ptr;
-    }
+unsigned RTSPClient::sendGetParameterCommand(MediaSession& session, responseHandler* responseHandler, char const* parameterName,
+                                             Authenticator* authenticator) {
+  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+
+  // We assume that:
+  //    parameterName is NULL means: Send no body in the request.
+  //    parameterName is "" means: Send only \r\n in the request body.  
+  //    parameterName is non-empty means: Send "<parameterName>\r\n" as the request body.  
+  unsigned parameterNameLen = parameterName == NULL ? 0 : strlen(parameterName);
+  char* paramString = new char[parameterNameLen + 3]; // the 3 is for \r\n + the '\0' byte
+  if (parameterName == NULL) {
+    paramString[0] = '\0';
+  } else {
+    sprintf(paramString, "%s\r\n", parameterName);
   }
-
-  return NULL;
+  unsigned result = sendRequest(new RequestRecord(++fCSeq, "GET_PARAMETER", responseHandler, &session, NULL, False, 0.0, 0.0, 0.0, paramString));
+  delete[] paramString;
+  return result;
 }
 
-char* RTSPClient::describeURL(char const* url, Authenticator* authenticator,
-			      Boolean allowKasennaProtocol, int timeout) {
-  char* cmd = NULL;
-  fDescribeStatusCode = 0;
-  do {
-    // First, check whether "url" contains a username:password to be used:
-    char* username; char* password;
-    if (authenticator == NULL
-	&& parseRTSPURLUsernamePassword(url, username, password)) {
-      char* result = describeWithPassword(url, username, password, allowKasennaProtocol, timeout);
-      delete[] username; delete[] password; // they were dynamically allocated
-      return result;
-    }
+Boolean RTSPClient::changeResponseHandler(unsigned cseq, responseHandler* newResponseHandler) { 
+  // Look for the matching request record in each of our 'pending requests' queues:
+  RequestRecord* request;
+  if ((request = fRequestsAwaitingConnection.findByCSeq(cseq)) != NULL
+      || (request = fRequestsAwaitingHTTPTunneling.findByCSeq(cseq)) != NULL
+      || (request = fRequestsAwaitingResponse.findByCSeq(cseq)) != NULL) {
+    request->handler() = newResponseHandler;
+    return True;
+  }
 
-    if (!openConnectionFromURL(url, authenticator, timeout)) break;
+  return False;
+}
 
-    // Send the DESCRIBE command:
+Boolean RTSPClient::lookupByName(UsageEnvironment& env,
+				 char const* instanceName,
+				 RTSPClient*& resultClient) {
+  resultClient = NULL; // unless we succeed
 
-    // First, construct an authenticator string:
-    fCurrentAuthenticator.reset();
-    char* authenticatorStr
-      = createAuthenticatorString(authenticator, "DESCRIBE", url);
+  Medium* medium;
+  if (!Medium::lookupByName(env, instanceName, medium)) return False;
 
-    char const* acceptStr = allowKasennaProtocol
-      ? "Accept: application/x-rtsp-mh, application/sdp\r\n"
-      : "Accept: application/sdp\r\n";
+  if (!medium->isRTSPClient()) {
+    env.setResultMsg(instanceName, " is not a RTSP client");
+    return False;
+  }
 
-    // (Later implement more, as specified in the RTSP spec, sec D.1 #####)
-    char const* const cmdFmt =
-      "DESCRIBE %s RTSP/1.0\r\n"
-      "CSeq: %d\r\n"
-      "%s"
-      "%s"
-      "%s"
-      "\r\n";
-    unsigned cmdSize = strlen(cmdFmt)
-      + strlen(url)
-      + 20 /* max int len */
-      + strlen(acceptStr)
-      + strlen(authenticatorStr)
-      + fUserAgentHeaderStrSize;
-    cmd = new char[cmdSize];
-    sprintf(cmd, cmdFmt,
-	    url,
-	    ++fCSeq,
-            acceptStr,
-	    authenticatorStr,
-	    fUserAgentHeaderStr);
-    delete[] authenticatorStr;
+  resultClient = (RTSPClient*)medium;
+  return True;
+}
 
-    if (!sendRequest(cmd, "DESCRIBE")) break;
-
-    // Get the response from the server:
-    unsigned bytesRead; unsigned responseCode;
-    char* firstLine; char* nextLineStart;
-    if (!getResponse("DESCRIBE", bytesRead, responseCode, firstLine, nextLineStart,
-		     False /*don't check for response code 200*/)) break;
-
-    // Inspect the first line to check whether it's a result code that
-    // we can handle.
-    Boolean wantRedirection = False;
-    char* redirectionURL = NULL;
-    if (responseCode == 301 || responseCode == 302) {
-      wantRedirection = True;
-      redirectionURL = new char[fResponseBufferSize]; // ensures enough space
-    } else if (responseCode != 200) {
-      checkForAuthenticationFailure(responseCode, nextLineStart, authenticator);
-      envir().setResultMsg("cannot handle DESCRIBE response: ", firstLine);
+Boolean RTSPClient::parseRTSPURL(UsageEnvironment& env, char const* url,
+				 NetAddress& address,
+				 portNumBits& portNum,
+				 char const** urlSuffix) {
+  do {
+    // Parse the URL as "rtsp://<address>:<port>/<etc>"
+    // (with ":<port>" and "/<etc>" optional)
+    // Also, skip over any "<username>[:<password>]@" preceding <address>
+    char const* prefix = "rtsp://";
+    unsigned const prefixLength = 7;
+    if (_strncasecmp(url, prefix, prefixLength) != 0) {
+      env.setResultMsg("URL is not of the form \"", prefix, "\"");
       break;
     }
 
-    // Skip over subsequent header lines, until we see a blank line.
-    // The remaining data is assumed to be the SDP descriptor that we want.
-    // While skipping over the header lines, we also check for certain headers
-    // that we recognize.
-    // (We should also check for "Content-type: application/sdp",
-    // "Content-location", "CSeq", etc.) #####
-    char* serverType = new char[fResponseBufferSize]; // ensures enough space
-    int contentLength = -1;
-    char* lineStart;
-    while (1) {
-      lineStart = nextLineStart;
-      if (lineStart == NULL) break;
-
-      nextLineStart = getLine(lineStart);
-      if (lineStart[0] == '\0') break; // this is a blank line
-
-      if (sscanf(lineStart, "Content-Length: %d", &contentLength) == 1
-	  || sscanf(lineStart, "Content-length: %d", &contentLength) == 1) {
-	if (contentLength < 0) {
-	  envir().setResultMsg("Bad \"Content-length:\" header: \"",
-			       lineStart, "\"");
-	  break;
-	}
-      } else if (strncmp(lineStart, "Content-Base:", 13) == 0) {
-	int cbIndex = 13;
+    unsigned const parseBufferSize = 100;
+    char parseBuffer[parseBufferSize];
+    char const* from = &url[prefixLength];
 
-	while (lineStart[cbIndex] == ' ' || lineStart[cbIndex] == '\t') ++cbIndex;
-	if (lineStart[cbIndex] != '\0'/*sanity check*/) {
-	  delete[] fBaseURL; fBaseURL = strDup(&lineStart[cbIndex]);
-	}
-      } else if (sscanf(lineStart, "Server: %s", serverType) == 1) {
-	if (strncmp(serverType, "Kasenna", 7) == 0) fServerIsKasenna = True;
-	if (strncmp(serverType, "WMServer", 8) == 0) fServerIsMicrosoft = True;
-      } else if (wantRedirection) {
-	if (sscanf(lineStart, "Location: %s", redirectionURL) == 1) {
-	  // Try again with this URL
-	  if (fVerbosityLevel >= 1) {
-	    envir() << "Redirecting to the new URL \""
-		    << redirectionURL << "\"\n";
-	  }
-	  reset();
-	  char* result = describeURL(redirectionURL, authenticator, allowKasennaProtocol, timeout);
-	  delete[] redirectionURL;
-	  delete[] serverType;
-	  delete[] cmd;
-	  return result;
-	}
+    // Skip over any "<username>[:<password>]@"
+    // (Note that this code fails if <password> contains '@' or '/', but
+    // given that these characters can also appear in <etc>, there seems to
+    // be no way of unambiguously parsing that situation.)
+    char const* from1 = from;
+    while (*from1 != '\0' && *from1 != '/') {
+      if (*from1 == '@') {
+	from = ++from1;
+	break;
       }
+      ++from1;
     }
-    delete[] serverType;
 
-    // We're now at the end of the response header lines
-    if (wantRedirection) {
-      envir().setResultMsg("Saw redirection response code, but not a \"Location:\" header");
-      delete[] redirectionURL;
-      break;
+    char* to = &parseBuffer[0];
+    unsigned i;
+    for (i = 0; i < parseBufferSize; ++i) {
+      if (*from == '\0' || *from == ':' || *from == '/') {
+	// We've completed parsing the address
+	*to = '\0';
+	break;
+      }
+      *to++ = *from++;
     }
-    if (lineStart == NULL) {
-      envir().setResultMsg("no content following header lines: ", fResponseBuffer);
+    if (i == parseBufferSize) {
+      env.setResultMsg("URL is too long");
       break;
     }
 
-    // Use the remaining data as the SDP descr, but first, check
-    // the "Content-length:" header (if any) that we saw.  We may need to
-    // read more data, or we may have extraneous data in the buffer.
-    char* bodyStart = nextLineStart;
-    if (contentLength >= 0) {
-      // We saw a "Content-length:" header
-      unsigned numBodyBytes = &firstLine[bytesRead] - bodyStart;
-      if (contentLength > (int)numBodyBytes) {
-	// We need to read more data.  First, make sure we have enough
-	// space for it:
-	unsigned numExtraBytesNeeded = contentLength - numBodyBytes;
-	unsigned remainingBufferSize
-	  = fResponseBufferSize - (bytesRead + (firstLine - fResponseBuffer));
-	if (numExtraBytesNeeded > remainingBufferSize) {
-	  char tmpBuf[200];
-	  sprintf(tmpBuf, "Read buffer size (%d) is too small for \"Content-length:\" %d (need a buffer size of >= %d bytes\n",
-		  fResponseBufferSize, contentLength,
-		  fResponseBufferSize + numExtraBytesNeeded - remainingBufferSize);
-	  envir().setResultMsg(tmpBuf);
-	  break;
-	}
-
-	// Keep reading more data until we have enough:
-	if (fVerbosityLevel >= 1) {
-	  envir() << "Need to read " << numExtraBytesNeeded
-		  << " extra bytes\n";
-	}
-	while (numExtraBytesNeeded > 0) {
-	  struct sockaddr_in fromAddress;
-	  char* ptr = &firstLine[bytesRead];
-	  int bytesRead2 = readSocket(envir(), fInputSocketNum, (unsigned char*)ptr,
-				      numExtraBytesNeeded, fromAddress);
-	  if (bytesRead2 <= 0) break;
-	  ptr[bytesRead2] = '\0';
-	  if (fVerbosityLevel >= 1) {
-	    envir() << "Read " << bytesRead2 << " extra bytes: "
-		    << ptr << "\n";
-	  }
-
-	  bytesRead += bytesRead2;
-	  numExtraBytesNeeded -= bytesRead2;
-	}
-	if (numExtraBytesNeeded > 0) break; // one of the reads failed
-      }
+    NetAddressList addresses(parseBuffer);
+    if (addresses.numAddresses() == 0) {
+      env.setResultMsg("Failed to find network address for \"",
+		       parseBuffer, "\"");
+      break;
+    }
+    address = *(addresses.firstAddress());
 
-      // Remove any '\0' characters from inside the SDP description.
-      // Any such characters would violate the SDP specification, but
-      // some RTSP servers have been known to include them:
-      int from, to = 0;
-      for (from = 0; from < contentLength; ++from) {
-	if (bodyStart[from] != '\0') {
-	  if (to != from) bodyStart[to] = bodyStart[from];
-	  ++to;
-	}
+    portNum = 554; // default value
+    char nextChar = *from;
+    if (nextChar == ':') {
+      int portNumInt;
+      if (sscanf(++from, "%d", &portNumInt) != 1) {
+	env.setResultMsg("No port number follows ':'");
+	break;
       }
-      if (from != to && fVerbosityLevel >= 1) {
-	envir() << "Warning: " << from-to << " invalid 'NULL' bytes were found in (and removed from) the SDP description.\n";
+      if (portNumInt < 1 || portNumInt > 65535) {
+	env.setResultMsg("Bad port number");
+	break;
       }
-      bodyStart[to] = '\0'; // trims any extra data
+      portNum = (portNumBits)portNumInt;
+      while (*from >= '0' && *from <= '9') ++from; // skip over port number
     }
 
-    ////////// BEGIN Kasenna BS //////////
-    // If necessary, handle Kasenna's non-standard BS response:
-    if (fServerIsKasenna && strncmp(bodyStart, "<MediaDescription>", 18) == 0) {
-      // Translate from x-rtsp-mh to sdp
-      int videoPid, audioPid;
-      u_int64_t mh_duration;
-      char* currentWord = new char[fResponseBufferSize]; // ensures enough space
-      delete[] fKasennaContentType;
-      fKasennaContentType = new char[fResponseBufferSize]; // ensures enough space
-      char* currentPos = bodyStart;
-
-      while (strcmp(currentWord, "</MediaDescription>") != 0) {
-          sscanf(currentPos, "%s", currentWord);
-
-          if (strcmp(currentWord, "VideoPid") == 0) {
-	    currentPos += strlen(currentWord) + 1;
-	    sscanf(currentPos, "%s", currentWord);
-	    currentPos += strlen(currentWord) + 1;
-	    sscanf(currentPos, "%d", &videoPid);
-	    currentPos += 3;
-          }
-
-          if (strcmp(currentWord, "AudioPid") == 0) {
-	    currentPos += strlen(currentWord) + 1;
-	    sscanf(currentPos, "%s", currentWord);
-	    currentPos += strlen(currentWord) + 1;
-	    sscanf(currentPos, "%d", &audioPid);
-	    currentPos += 3;
-          }
-
-          if (strcmp(currentWord, "Duration") == 0) {
-	    currentPos += strlen(currentWord) + 1;
-	    sscanf(currentPos, "%s", currentWord);
-	    currentPos += strlen(currentWord) + 1;
-	    sscanf(currentPos, "%llu", &mh_duration);
-	    currentPos += 3;
-          }
+    // The remainder of the URL is the suffix:
+    if (urlSuffix != NULL) *urlSuffix = from;
 
-          if (strcmp(currentWord, "TypeSpecificData") == 0) {
-	    currentPos += strlen(currentWord) + 1;
-	    sscanf(currentPos, "%s", currentWord);
-	    currentPos += strlen(currentWord) + 1;
-	    sscanf(currentPos, "%s", fKasennaContentType);
-	    currentPos += 3;
-	    printf("Kasenna Content Type: %s\n", fKasennaContentType);
-          }
+    return True;
+  } while (0);
 
-          currentPos += strlen(currentWord) + 1;
-	}
+  return False;
+}
 
-      if (fKasennaContentType != NULL
-	  && strcmp(fKasennaContentType, "PARTNER_41_MPEG-4") == 0) {
-          char* describeSDP = describeURL(url, authenticator, True, timeout);
+Boolean RTSPClient::parseRTSPURLUsernamePassword(char const* url,
+						 char*& username,
+						 char*& password) {
+  username = password = NULL; // by default
+  do {
+    // Parse the URL as "rtsp://<username>[:<password>]@<whatever>"
+    char const* prefix = "rtsp://";
+    unsigned const prefixLength = 7;
+    if (_strncasecmp(url, prefix, prefixLength) != 0) break;
 
-	  delete[] currentWord;
-          delete[] cmd;
-          return describeSDP;
+    // Look for the ':' and '@':
+    unsigned usernameIndex = prefixLength;
+    unsigned colonIndex = 0, atIndex = 0;
+    for (unsigned i = usernameIndex; url[i] != '\0' && url[i] != '/'; ++i) {
+      if (url[i] == ':' && colonIndex == 0) {
+	colonIndex = i;
+      } else if (url[i] == '@') {
+	atIndex = i;
+	break; // we're done
       }
+    }
+    if (atIndex == 0) break; // no '@' found
 
-      unsigned char byte1 = fServerAddress & 0x000000ff;
-      unsigned char byte2 = (fServerAddress & 0x0000ff00) >>  8;
-      unsigned char byte3 = (fServerAddress & 0x00ff0000) >> 16;
-      unsigned char byte4 = (fServerAddress & 0xff000000) >> 24;
-
-      char const* sdpFmt =
-	"v=0\r\n"
-	"o=NoSpacesAllowed 1 1 IN IP4 %u.%u.%u.%u\r\n"
-	"s=%s\r\n"
-	"c=IN IP4 %u.%u.%u.%u\r\n"
-	"t=0 0\r\n"
-	"a=control:*\r\n"
-	"a=range:npt=0-%llu\r\n"
-	"m=video 1554 RAW/RAW/UDP 33\r\n"
-	"a=control:trackID=%d\r\n";
-      unsigned sdpBufSize = strlen(sdpFmt)
-	+ 4*3 // IP address
-	+ strlen(url)
-	+ 20 // max int length
-	+ 20; // max int length
-      char* sdpBuf = new char[sdpBufSize];
-      sprintf(sdpBuf, sdpFmt,
-	      byte1, byte2, byte3, byte4,
-	      url,
-	      byte1, byte2, byte3, byte4,
-	      mh_duration/1000000,
-	      videoPid);
-
-      char* result = strDup(sdpBuf);
-      delete[] sdpBuf; delete[] currentWord;
-      delete[] cmd;
-      return result;
+    char* urlCopy = strDup(url);
+    urlCopy[atIndex] = '\0';
+    if (colonIndex > 0) {
+      urlCopy[colonIndex] = '\0';
+      password = strDup(&urlCopy[colonIndex+1]);
+    } else {
+      password = strDup("");
     }
-    ////////// END Kasenna BS //////////
+    username = strDup(&urlCopy[usernameIndex]);
+    delete[] urlCopy;
 
-    delete[] cmd;
-    return strDup(bodyStart);
+    return True;
   } while (0);
 
-  delete[] cmd;
-  if (fDescribeStatusCode == 0) fDescribeStatusCode = 2;
-  return NULL;
+  return False;
 }
 
-char* RTSPClient
-::describeWithPassword(char const* url,
-		       char const* username, char const* password,
-		       Boolean allowKasennaProtocol, int timeout) {
-  Authenticator authenticator;
-  authenticator.setUsernameAndPassword(username, password);
-  char* describeResult = describeURL(url, &authenticator, allowKasennaProtocol, timeout);
-  if (describeResult != NULL) {
-    // We are already authorized
-    return describeResult;
-  }
-
-  // The "realm" field should have been filled in:
-  if (authenticator.realm() == NULL) {
-    // We haven't been given enough information to try again, so fail:
-    return NULL;
-  }
-
-  // Try again:
-  describeResult = describeURL(url, &authenticator, allowKasennaProtocol, timeout);
-  if (describeResult != NULL) {
-    // The authenticator worked, so use it in future requests:
-    fCurrentAuthenticator = authenticator;
-  }
+void RTSPClient::setUserAgentString(char const* userAgentName) {
+  if (userAgentName == NULL) return;
 
-  return describeResult;
+  // Change the existing user agent header string:
+  char const* const formatStr = "User-Agent: %s\r\n";
+  unsigned const headerSize = strlen(formatStr) + strlen(userAgentName);
+  delete[] fUserAgentHeaderStr;
+  fUserAgentHeaderStr = new char[headerSize];
+  sprintf(fUserAgentHeaderStr, formatStr, userAgentName);
+  fUserAgentHeaderStrLen = strlen(fUserAgentHeaderStr);
 }
 
-char* RTSPClient::sendOptionsCmd(char const* url,
-				 char* username, char* password,
-				 Authenticator* authenticator,
-				 int timeout) {
-  char* result = NULL;
-  char* cmd = NULL;
-  Boolean haveAllocatedAuthenticator = False;
-  do {
-    if (authenticator == NULL) {
-      // First, check whether "url" contains a username:password to be used
-      // (and no username,password pair was supplied separately):
-      if (username == NULL && password == NULL
-	  && parseRTSPURLUsernamePassword(url, username, password)) {
-	Authenticator newAuthenticator;
-	newAuthenticator.setUsernameAndPassword(username, password);
-	result = sendOptionsCmd(url, username, password, &newAuthenticator, timeout);
-	delete[] username; delete[] password; // they were dynamically allocated
-	break;
-      } else if (username != NULL && password != NULL) {
-	// Use the separately supplied username and password:
-	authenticator = new Authenticator;
-	haveAllocatedAuthenticator = True;
-	authenticator->setUsernameAndPassword(username, password);
-
-	result = sendOptionsCmd(url, username, password, authenticator, timeout);
-	if (result != NULL) break; // We are already authorized
-
-	// The "realm" field should have been filled in:
-	if (authenticator->realm() == NULL) {
-	  // We haven't been given enough information to try again, so fail:
-	  break;
-	}
-	// Try again:
-      }
-    }
+unsigned RTSPClient::responseBufferSize = 20000; // default value; you can reassign this in your application if you need to
 
-    if (!openConnectionFromURL(url, authenticator, timeout)) break;
+RTSPClient::RTSPClient(UsageEnvironment& env, char const* rtspURL,
+		       int verbosityLevel, char const* applicationName,
+		       portNumBits tunnelOverHTTPPortNum)
+  : Medium(env),
+    fVerbosityLevel(verbosityLevel), fTunnelOverHTTPPortNum(tunnelOverHTTPPortNum),
+    fUserAgentHeaderStr(NULL), fUserAgentHeaderStrLen(0), fInputSocketNum(-1), fOutputSocketNum(-1), fServerAddress(0), fCSeq(1),
+    fBaseURL(NULL), fTCPStreamIdCount(0), fLastSessionId(NULL), fSessionTimeoutParameter(0),
+    fSessionCookieCounter(0), fHTTPTunnelingConnectionIsPending(False) {
+  setBaseURL(rtspURL);
 
-    // Send the OPTIONS command:
+  fResponseBuffer = new char[responseBufferSize+1];
+  resetResponseBuffer();
 
-    // First, construct an authenticator string:
-    char* authenticatorStr
-      = createAuthenticatorString(authenticator, "OPTIONS", url);
+  // Set the "User-Agent:" header to use in each request:
+  char const* const libName = "LIVE555 Streaming Media v";
+  char const* const libVersionStr = LIVEMEDIA_LIBRARY_VERSION_STRING;
+  char const* libPrefix; char const* libSuffix;
+  if (applicationName == NULL || applicationName[0] == '\0') {
+    applicationName = libPrefix = libSuffix = "";
+  } else {
+    libPrefix = " (";
+    libSuffix = ")";
+  }
+  unsigned userAgentNameSize
+    = strlen(applicationName) + strlen(libPrefix) + strlen(libName) + strlen(libVersionStr) + strlen(libSuffix) + 1;
+  char* userAgentName = new char[userAgentNameSize];
+  sprintf(userAgentName, "%s%s%s%s%s", applicationName, libPrefix, libName, libVersionStr, libSuffix);
+  setUserAgentString(userAgentName);
+  delete[] userAgentName;
+}
 
-    char const* const cmdFmt =
-      "OPTIONS %s RTSP/1.0\r\n"
-      "CSeq: %d\r\n"
-      "%s"
-      "%s"
-      "\r\n";
-    unsigned cmdSize = strlen(cmdFmt)
-      + strlen(url)
-      + 20 /* max int len */
-      + strlen(authenticatorStr)
-      + fUserAgentHeaderStrSize;
-    cmd = new char[cmdSize];
-    sprintf(cmd, cmdFmt,
-	    url,
-	    ++fCSeq,
-	    authenticatorStr,
-	    fUserAgentHeaderStr);
-    delete[] authenticatorStr;
+RTSPClient::~RTSPClient() {
+  reset();
 
-    if (!sendRequest(cmd, "OPTIONS")) break;
+  delete[] fResponseBuffer;
+  delete[] fUserAgentHeaderStr;
+}
 
-    // Get the response from the server:
-    unsigned bytesRead; unsigned responseCode;
-    char* firstLine; char* nextLineStart;
-    if (!getResponse("OPTIONS", bytesRead, responseCode, firstLine, nextLineStart,
-		     False /*don't check for response code 200*/)) break;
-    if (responseCode != 200) {
-      checkForAuthenticationFailure(responseCode, nextLineStart, authenticator);
-      envir().setResultMsg("cannot handle OPTIONS response: ", firstLine);
-      break;
-    }
+Boolean RTSPClient::isRTSPClient() const {
+  return True;
+}
 
-    // Look for a "Public:" header (which will contain our result str):
-    char* lineStart;
-    while (1) {
-      lineStart = nextLineStart;
-      if (lineStart == NULL) break;
+void RTSPClient::reset() {
+  resetTCPSockets();
+  resetResponseBuffer();
+  fServerAddress = 0;
 
-      nextLineStart = getLine(lineStart);
+  setBaseURL(NULL);
 
-      if (_strncasecmp(lineStart, "Public: ", 8) == 0) {
-	delete[] result; result = strDup(&lineStart[8]);
-      }
-    }
-  } while (0);
+  fCurrentAuthenticator.reset();
 
-  delete[] cmd;
-  if (haveAllocatedAuthenticator) delete authenticator;
-  return result;
+  delete[] fLastSessionId; fLastSessionId = NULL;
 }
 
-static Boolean isAbsoluteURL(char const* url) {
-  // Assumption: "url" is absolute if it contains a ':', before any
-  // occurrence of '/'
-  while (*url != '\0' && *url != '/') {
-    if (*url == ':') return True;
-    ++url;
+void RTSPClient::resetTCPSockets() {
+  if (fInputSocketNum >= 0) {
+    envir().taskScheduler().disableBackgroundHandling(fInputSocketNum);
+    ::closeSocket(fInputSocketNum);
+    if (fOutputSocketNum != fInputSocketNum) {
+      envir().taskScheduler().disableBackgroundHandling(fOutputSocketNum);
+      ::closeSocket(fOutputSocketNum);
+    }
   }
-
-  return False;
+  fInputSocketNum = fOutputSocketNum = -1;
 }
 
-char const* RTSPClient::sessionURL(MediaSession const& session) const {
-  char const* url = session.controlPath();
-  if (url == NULL || strcmp(url, "*") == 0) url = fBaseURL;
-
-  return url;
+void RTSPClient::resetResponseBuffer() {
+  fResponseBytesAlreadySeen = 0;
+  fResponseBufferBytesLeft = responseBufferSize;
 }
 
-void RTSPClient::constructSubsessionURL(MediaSubsession const& subsession,
-					char const*& prefix,
-					char const*& separator,
-					char const*& suffix) {
-  // Figure out what the URL describing "subsession" will look like.
-  // The URL is returned in three parts: prefix; separator; suffix
-  //##### NOTE: This code doesn't really do the right thing if "sessionURL()"
-  // doesn't end with a "/", and "subsession.controlPath()" is relative.
-  // The right thing would have been to truncate "sessionURL()" back to the
-  // rightmost "/", and then add "subsession.controlPath()".
-  // In practice, though, each "DESCRIBE" response typically contains
-  // a "Content-Base:" header that consists of "sessionURL()" followed by
-  // a "/", in which case this code ends up giving the correct result.
-  // However, we should really fix this code to do the right thing, and
-  // also check for and use the "Content-Base:" header appropriately. #####
-  prefix = sessionURL(subsession.parentSession());
-  if (prefix == NULL) prefix = "";
-
-  suffix = subsession.controlPath();
-  if (suffix == NULL) suffix = "";
-
-  if (isAbsoluteURL(suffix)) {
-    prefix = separator = "";
-  } else {
-    unsigned prefixLen = strlen(prefix);
-    separator = (prefix[prefixLen-1] == '/' || suffix[0] == '/') ? "" : "/";
-  }
+void RTSPClient::setBaseURL(char const* url) {
+  delete[] fBaseURL; fBaseURL = strDup(url);
 }
 
-Boolean RTSPClient::announceSDPDescription(char const* url,
-					   char const* sdpDescription,
-					   Authenticator* authenticator,
-					   int timeout) {
-  char* cmd = NULL;
+int RTSPClient::openConnection() {
   do {
-    if (!openConnectionFromURL(url, authenticator, timeout)) break;
+    // Set up a connection to the server.  Begin by parsing the URL:
 
-    // Send the ANNOUNCE command:
+    NetAddress destAddress;
+    portNumBits urlPortNum;
+    char const* urlSuffix;
+    if (!parseRTSPURL(envir(), fBaseURL, destAddress, urlPortNum, &urlSuffix)) break;
+    portNumBits destPortNum
+      = fTunnelOverHTTPPortNum == 0 ? urlPortNum : fTunnelOverHTTPPortNum;
 
-    // First, construct an authenticator string:
-    fCurrentAuthenticator.reset();
-    char* authenticatorStr
-      = createAuthenticatorString(authenticator, "ANNOUNCE", url);
+    // We don't yet have a TCP socket (or we used to have one, but it got closed).  Set it up now.
+    fInputSocketNum = fOutputSocketNum = setupStreamSocket(envir(), 0);
+    if (fInputSocketNum < 0) break;
+      
+    // Connect to the remote endpoint:
+    fServerAddress = *(unsigned*)(destAddress.data());
+    int connectResult = connectToServer(fInputSocketNum, destPortNum);
+    if (connectResult < 0) break;
+    else if (connectResult > 0) {
+      // The connection succeeded.  Arrange to handle responses to requests sent on it:
+      envir().taskScheduler().setBackgroundHandling(fInputSocketNum, SOCKET_READABLE,
+						    (TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler, this);
+    }
+    return connectResult;
+  } while (0);
+  
+  resetTCPSockets();
+  return -1;
+}
 
-    char const* const cmdFmt =
-      "ANNOUNCE %s RTSP/1.0\r\n"
-      "CSeq: %d\r\n"
-      "Content-Type: application/sdp\r\n"
-      "%s"
-      "Content-length: %d\r\n\r\n"
-      "%s";
-	    // Note: QTSS hangs if an "ANNOUNCE" contains a "User-Agent:" field (go figure), so don't include one here
-    unsigned sdpSize = strlen(sdpDescription);
-    unsigned cmdSize = strlen(cmdFmt)
-      + strlen(url)
-      + 20 /* max int len */
-      + strlen(authenticatorStr)
-      + 20 /* max int len */
-      + sdpSize;
-    cmd = new char[cmdSize];
-    sprintf(cmd, cmdFmt,
-	    url,
-	    ++fCSeq,
-	    authenticatorStr,
-	    sdpSize,
-	    sdpDescription);
-    delete[] authenticatorStr;
+int RTSPClient::connectToServer(int socketNum, portNumBits remotePortNum) {
+  MAKE_SOCKADDR_IN(remoteName, fServerAddress, htons(remotePortNum));
+  if (fVerbosityLevel >= 1) {
+    envir() << "Opening connection to " << our_inet_ntoa(remoteName.sin_addr) << ", port " << remotePortNum << "...\n";
+  }
+  if (connect(socketNum, (struct sockaddr*) &remoteName, sizeof remoteName) != 0) {
+    if (envir().getErrno() == EINPROGRESS) {
+      // The connection is pending; we'll need to handle it later.  Wait for our socket to be 'writable', or have an exception.
+      envir().taskScheduler().setBackgroundHandling(socketNum, SOCKET_WRITABLE|SOCKET_EXCEPTION,
+						    (TaskScheduler::BackgroundHandlerProc*)&connectionHandler, this);
+      return 0;
+    }
+    envir().setResultErrMsg("connect() failed: ");
+    if (fVerbosityLevel >= 1) envir() << "..." << envir().getResultMsg() << "\n";
+    return -1;
+  }
+  if (fVerbosityLevel >= 1) envir() << "...local connection opened\n";
 
-    if (!sendRequest(cmd, "ANNOUNCE")) break;
+  return 1;
+}
+
+char*
+RTSPClient::createAuthenticatorString(char const* cmd, char const* url) {
+  Authenticator& auth = fCurrentAuthenticator; // alias, for brevity
+  if (auth.realm() != NULL && auth.username() != NULL && auth.password() != NULL) {
+    // We have a filled-in authenticator, so use it:
+    char* authenticatorStr;
+    if (auth.nonce() != NULL) { // Digest authentication
+      char const* const authFmt =
+	"Authorization: Digest username=\"%s\", realm=\"%s\", "
+	"nonce=\"%s\", uri=\"%s\", response=\"%s\"\r\n";
+      char const* response = auth.computeDigestResponse(cmd, url);
+      unsigned authBufSize = strlen(authFmt)
+	+ strlen(auth.username()) + strlen(auth.realm())
+	+ strlen(auth.nonce()) + strlen(url) + strlen(response);
+      authenticatorStr = new char[authBufSize];
+      sprintf(authenticatorStr, authFmt,
+	      auth.username(), auth.realm(),
+	      auth.nonce(), url, response);
+      auth.reclaimDigestResponse(response);
+    } else { // Basic authentication
+      char const* const authFmt = "Authorization: Basic %s\r\n";
 
-    // Get the response from the server:
-    unsigned bytesRead; unsigned responseCode;
-    char* firstLine; char* nextLineStart;
-    if (!getResponse("ANNOUNCE", bytesRead, responseCode, firstLine, nextLineStart,
-		     False /*don't check for response code 200*/)) break;
+      unsigned usernamePasswordLength = strlen(auth.username()) + 1 + strlen(auth.password());
+      char* usernamePassword = new char[usernamePasswordLength+1];
+      sprintf(usernamePassword, "%s:%s", auth.username(), auth.password());
 
-    // Inspect the first line to check whether it's a result code 200
-    if (responseCode != 200) {
-      checkForAuthenticationFailure(responseCode, nextLineStart, authenticator);
-      envir().setResultMsg("cannot handle ANNOUNCE response: ", firstLine);
-      break;
+      char* response = base64Encode(usernamePassword, usernamePasswordLength);
+      unsigned const authBufSize = strlen(authFmt) + strlen(response) + 1;
+      authenticatorStr = new char[authBufSize];
+      sprintf(authenticatorStr, authFmt, response);
+      delete[] response; delete[] usernamePassword;
     }
 
-    delete[] cmd;
-    return True;
-  } while (0);
+    return authenticatorStr;
+  }
 
-  delete[] cmd;
-  return False;
+  // We don't have a (filled-in) authenticator.
+  return strDup("");
 }
 
-Boolean RTSPClient
-::announceWithPassword(char const* url, char const* sdpDescription,
-		       char const* username, char const* password, int timeout) {
-  Authenticator authenticator;
-  authenticator.setUsernameAndPassword(username, password);
-  if (announceSDPDescription(url, sdpDescription, &authenticator, timeout)) {
-    // We are already authorized
-    return True;
+static char* createSessionString(char const* sessionId) {
+  char* sessionStr;
+  if (sessionId != NULL) {
+    sessionStr = new char[20+strlen(sessionId)];
+    sprintf(sessionStr, "Session: %s\r\n", sessionId);
+  } else {
+    sessionStr = strDup("");
   }
+  return sessionStr;
+}
 
-  // The "realm" field should have been filled in:
-  if (authenticator.realm() == NULL) {
-    // We haven't been given enough information to try again, so fail:
-    return False;
+static char* createScaleString(float scale, float currentScale) {
+  char buf[100];
+  if (scale == 1.0f && currentScale == 1.0f) {
+    // This is the default value; we don't need a "Scale:" header:
+    buf[0] = '\0';
+  } else {
+    Locale l("C", LC_NUMERIC);
+    sprintf(buf, "Scale: %f\r\n", scale);
   }
 
-  // Try again:
-  Boolean secondTrySuccess
-    = announceSDPDescription(url, sdpDescription, &authenticator, timeout);
+  return strDup(buf);
+}
 
-  if (secondTrySuccess) {
-    // The authenticator worked, so use it in future requests:
-    fCurrentAuthenticator = authenticator;
+static char* createRangeString(double start, double end) {
+  char buf[100];
+  if (start < 0) {
+    // We're resuming from a PAUSE; there's no "Range:" header at all
+    buf[0] = '\0';
+  } else if (end < 0) {
+    // There's no end time:
+    Locale l("C", LC_NUMERIC);
+    sprintf(buf, "Range: npt=%.3f-\r\n", start);
+  } else {
+    // There's both a start and an end time; include them both in the "Range:" hdr
+    Locale l("C", LC_NUMERIC);
+    sprintf(buf, "Range: npt=%.3f-%.3f\r\n", start, end);
   }
 
-  return secondTrySuccess;
+  return strDup(buf);
 }
 
-Boolean RTSPClient::setupMediaSubsession(MediaSubsession& subsession,
-					 Boolean streamOutgoing,
-					 Boolean streamUsingTCP,
-					 Boolean forceMulticastOnUnspecified) {
+unsigned RTSPClient::sendRequest(RequestRecord* request) {
   char* cmd = NULL;
-  char* setupStr = NULL;
-
-  if (fServerIsMicrosoft) {
-    // Microsoft doesn't send the right endTime on live streams.  Correct this:
-    char *tmpStr = subsession.parentSession().mediaSessionType();
-    if (tmpStr != NULL && strncmp(tmpStr, "broadcast", 9) == 0) {
-      subsession.parentSession().playEndTime() = 0.0;
-    }
-  }
-
   do {
-    // Construct the SETUP command:
-
-    // First, construct an authenticator string:
-    char* authenticatorStr
-      = createAuthenticatorString(&fCurrentAuthenticator,
-				  "SETUP", fBaseURL);
-
-    // When sending more than one "SETUP" request, include a "Session:"
-    // header in the 2nd and later "SETUP"s.
-    char* sessionStr;
-    if (fLastSessionId != NULL) {
-      sessionStr = new char[20+strlen(fLastSessionId)];
-      sprintf(sessionStr, "Session: %s\r\n", fLastSessionId);
-    } else {
-      sessionStr = strDup("");
-    }
-
-    char* transportStr = NULL;
-
-    char const *prefix, *separator, *suffix;
-    constructSubsessionURL(subsession, prefix, separator, suffix);
-    char const* transportFmt;
-
-    if (strcmp(subsession.protocolName(), "UDP") == 0) {
-      char const* setupFmt = "SETUP %s%s RTSP/1.0\r\n";
-      unsigned setupSize = strlen(setupFmt)
-        + strlen(prefix) + strlen (separator);
-      setupStr = new char[setupSize];
-      sprintf(setupStr, setupFmt, prefix, separator);
-
-      transportFmt = "Transport: RAW/RAW/UDP%s%s%s=%d-%d\r\n";
-    } else {
-      char const* setupFmt = "SETUP %s%s%s RTSP/1.0\r\n";
-      unsigned setupSize = strlen(setupFmt)
-        + strlen(prefix) + strlen (separator) + strlen(suffix);
-      setupStr = new char[setupSize];
-      sprintf(setupStr, setupFmt, prefix, separator, suffix);
+    Boolean connectionIsPending = False;
+    if (!fRequestsAwaitingConnection.isEmpty()) {
+      // A connection is currently pending (with at least one enqueued request).  Enqueue this request also:
+      connectionIsPending = True;
+    } else if (fInputSocketNum < 0) { // we need to open a connection
+      int connectResult = openConnection();
+      if (connectResult < 0) break; // an error occurred
+      else if (connectResult == 0) {
+	// A connection is pending
+        connectionIsPending = True;
+      } // else the connection succeeded.  Continue sending the command.u
+    }
+    if (connectionIsPending) {
+      fRequestsAwaitingConnection.enqueue(request);
+      return request->cseq();
+    }
+
+    // If requested (and we're not already doing it, or have done it), set up the special protocol for tunneling RTSP-over-HTTP:
+    if (fTunnelOverHTTPPortNum != 0 && strcmp(request->commandName(), "GET") != 0 && fOutputSocketNum == fInputSocketNum) {
+      if (!setupHTTPTunneling1()) break;
+      fRequestsAwaitingHTTPTunneling.enqueue(request);
+      return request->cseq();
+    }
+
+    // Construct and send the command:
+
+    // First, construct command-specific headers that we need:
+
+    char* cmdURL = fBaseURL; // by default
+    Boolean cmdURLWasAllocated = False;
+
+    char const* protocolStr = "RTSP/1.0"; // by default
+
+    char* extraHeaders = (char*)""; // by default
+    Boolean extraHeadersWereAllocated = False; 
+
+    char* contentLengthHeader = (char*)""; // by default
+    Boolean contentLengthHeaderWasAllocated = False;
+
+    char const* contentStr = request->contentStr(); // by default
+    if (contentStr == NULL) contentStr = "";
+    unsigned contentStrLen = strlen(contentStr);
+    if (contentStrLen > 0) {
+      char const* contentLengthHeaderFmt =
+	"Content-length: %d\r\n";
+      unsigned contentLengthHeaderSize = strlen(contentLengthHeaderFmt)
+	+ 20 /* max int len */;
+      contentLengthHeader = new char[contentLengthHeaderSize];
+      sprintf(contentLengthHeader, contentLengthHeaderFmt, contentStrLen);
+      contentLengthHeaderWasAllocated = True;
+    }
+
+    if (strcmp(request->commandName(), "DESCRIBE") == 0) {
+      extraHeaders = (char*)"Accept: application/sdp\r\n";
+    } else if (strcmp(request->commandName(), "OPTIONS") == 0) {
+    } else if (strcmp(request->commandName(), "ANNOUNCE") == 0) {
+      extraHeaders = (char*)"Content-Type: application/sdp\r\n";
+    } else if (strcmp(request->commandName(), "SETUP") == 0) {
+      MediaSubsession& subsession = *request->subsession();
+      Boolean streamUsingTCP = (request->booleanFlags()&0x1) != 0;
+      Boolean streamOutgoing = (request->booleanFlags()&0x2) != 0;
+      Boolean forceMulticastOnUnspecified = (request->booleanFlags()&0x4) != 0;
+
+      char const *prefix, *separator, *suffix;
+      constructSubsessionURL(subsession, prefix, separator, suffix);
+
+      char const* transportFmt;
+      if (strcmp(subsession.protocolName(), "UDP") == 0) {
+	suffix = "";
+	transportFmt = "Transport: RAW/RAW/UDP%s%s%s=%d-%d\r\n";
+      } else {
+	transportFmt = "Transport: RTP/AVP%s%s%s=%d-%d\r\n";
+      }
 
-      transportFmt = "Transport: RTP/AVP%s%s%s=%d-%d\r\n";
-    }
+      cmdURL = new char[strlen(prefix) + strlen(separator) + strlen(suffix) + 1];
+      cmdURLWasAllocated = True;
+      sprintf(cmdURL, "%s%s%s", prefix, separator, suffix);
 
-    if (transportStr == NULL) {
       // Construct a "Transport:" header.
       char const* transportTypeStr;
       char const* modeStr = streamOutgoing ? ";mode=receive" : "";
           // Note: I think the above is nonstandard, but DSS wants it this way
       char const* portTypeStr;
-      unsigned short rtpNumber, rtcpNumber;
+      portNumBits rtpNumber, rtcpNumber;
       if (streamUsingTCP) { // streaming over the RTSP connection
 	transportTypeStr = "/TCP;unicast";
 	portTypeStr = ";interleaved";
@@ -778,1648 +605,1329 @@ Boolean RTSPClient::setupMediaSubsession(MediaSubsession& subsession,
 	rtcpNumber = fTCPStreamIdCount++;
       } else { // normal RTP streaming
 	unsigned connectionAddress = subsession.connectionEndpointAddress();
-	Boolean requestMulticastStreaming = IsMulticastAddress(connectionAddress)
-	  || (connectionAddress == 0 && forceMulticastOnUnspecified);
+        Boolean requestMulticastStreaming
+	  = IsMulticastAddress(connectionAddress) || (connectionAddress == 0 && forceMulticastOnUnspecified);
 	transportTypeStr = requestMulticastStreaming ? ";multicast" : ";unicast";
 	portTypeStr = ";client_port";
 	rtpNumber = subsession.clientPortNum();
 	if (rtpNumber == 0) {
 	  envir().setResultMsg("Client port number unknown\n");
-	  delete[] authenticatorStr; delete[] sessionStr; delete[] setupStr;
+	  delete[] cmdURL;
 	  break;
 	}
 	rtcpNumber = rtpNumber + 1;
       }
-
       unsigned transportSize = strlen(transportFmt)
 	+ strlen(transportTypeStr) + strlen(modeStr) + strlen(portTypeStr) + 2*5 /* max port len */;
-      transportStr = new char[transportSize];
+      char* transportStr = new char[transportSize];
       sprintf(transportStr, transportFmt,
 	      transportTypeStr, modeStr, portTypeStr, rtpNumber, rtcpNumber);
+
+      // When sending more than one "SETUP" request, include a "Session:" header in the 2nd and later commands:
+      char* sessionStr = createSessionString(fLastSessionId);
+
+      // The "Transport:" and "Session:" (if present) headers make up the 'extra headers':
+      extraHeaders = new char[transportSize + strlen(sessionStr)];
+      extraHeadersWereAllocated = True;
+      sprintf(extraHeaders, "%s%s", transportStr, sessionStr);
+      delete[] transportStr; delete[] sessionStr;
+    } else if (strcmp(request->commandName(), "GET") == 0 || strcmp(request->commandName(), "POST") == 0) {
+      NetAddress destAddress;
+      portNumBits urlPortNum;
+      if (!parseRTSPURL(envir(), fBaseURL, destAddress, urlPortNum, (char const**)&cmdURL)) break;
+      if (cmdURL[0] == '\0') cmdURL = (char*)"/";
+
+      protocolStr = "HTTP/1.0";
+
+      if (strcmp(request->commandName(), "GET") == 0) {
+	// Create a 'session cookie' string, using MD5:
+	struct {
+	  struct timeval timestamp;
+	  unsigned counter;
+	} seedData;
+	gettimeofday(&seedData.timestamp, NULL);
+	seedData.counter = ++fSessionCookieCounter;
+	our_MD5Data((unsigned char*)(&seedData), sizeof seedData, fSessionCookie);
+	// DSS seems to require that the 'session cookie' string be 22 bytes long:
+	fSessionCookie[23] = '\0';
+	
+	char const* const extraHeadersFmt =
+	  "x-sessioncookie: %s\r\n"
+	  "Accept: application/x-rtsp-tunnelled\r\n"
+	  "Pragma: no-cache\r\n"
+	  "Cache-Control: no-cache\r\n";
+	unsigned extraHeadersSize = strlen(extraHeadersFmt)
+	  + strlen(fSessionCookie);
+	extraHeaders = new char[extraHeadersSize];
+	extraHeadersWereAllocated = True;
+	sprintf(extraHeaders, extraHeadersFmt,
+		fSessionCookie);
+      } else { // "POST"
+	protocolStr = "HTTP/1.0";
+	
+	char const* const extraHeadersFmt =
+	  "x-sessioncookie: %s\r\n"
+	  "Content-Type: application/x-rtsp-tunnelled\r\n"
+	  "Pragma: no-cache\r\n"
+	  "Cache-Control: no-cache\r\n"
+	  "Content-Length: 32767\r\n"
+	  "Expires: Sun, 9 Jan 1972 00:00:00 GMT\r\n";
+	unsigned extraHeadersSize = strlen(extraHeadersFmt)
+	  + strlen(fSessionCookie);
+	extraHeaders = new char[extraHeadersSize];
+	extraHeadersWereAllocated = True;
+	sprintf(extraHeaders, extraHeadersFmt,
+		fSessionCookie);
+      }
+    } else { // "PLAY", "PAUSE", "TEARDOWN", "RECORD", "SET_PARAMETER", "GET_PARAMETER"
+      // First, make sure that we have a RTSP session in progress
+      if (fLastSessionId == NULL) {
+	envir().setResultMsg("No RTSP session is currently in progress\n");
+	break;
+      }
+
+      char const* sessionId;
+      float originalScale;
+      if (request->session() != NULL) {
+	// Session-level operation
+	cmdURL = (char*)sessionURL(*request->session());
+
+	sessionId = fLastSessionId;
+	originalScale = request->session()->scale();
+      } else {
+	// Media-level operation
+	char const *prefix, *separator, *suffix;
+	constructSubsessionURL(*request->subsession(), prefix, separator, suffix);
+	cmdURL = new char[strlen(prefix) + strlen(separator) + strlen(suffix) + 1];
+	cmdURLWasAllocated = True;
+	sprintf(cmdURL, "%s%s%s", prefix, separator, suffix);
+	
+	sessionId = request->subsession()->sessionId;
+	originalScale = request->subsession()->scale();
+      }
+
+      if (strcmp(request->commandName(), "PLAY") == 0) {
+	// Create "Session:", "Scale:", and "Range:" headers; these make up the 'extra headers':
+	char* sessionStr = createSessionString(sessionId);
+	char* scaleStr = createScaleString(request->scale(), originalScale);
+	char* rangeStr = createRangeString(request->start(), request->end());
+	extraHeaders = new char[strlen(sessionStr) + strlen(scaleStr) + strlen(rangeStr) + 1];
+	extraHeadersWereAllocated = True;
+	sprintf(extraHeaders, "%s%s%s", sessionStr, scaleStr, rangeStr);
+	delete[] sessionStr; delete[] scaleStr; delete[] rangeStr;
+      } else {
+	// Create a "Session:" header; this makes up our 'extra headers':
+	extraHeaders = createSessionString(sessionId);
+	extraHeadersWereAllocated = True;
+      }
     }
 
-    // (Later implement more, as specified in the RTSP spec, sec D.1 #####)
+    char* authenticatorStr = createAuthenticatorString(request->commandName(), fBaseURL);
+
     char const* const cmdFmt =
-      "%s"
+      "%s %s %s\r\n"
       "CSeq: %d\r\n"
       "%s"
       "%s"
       "%s"
       "%s"
-      "\r\n";
-
+      "\r\n"
+      "%s";
     unsigned cmdSize = strlen(cmdFmt)
-      + strlen(setupStr)
+      + strlen(request->commandName()) + strlen(cmdURL) + strlen(protocolStr)
       + 20 /* max int len */
-      + strlen(transportStr)
-      + strlen(sessionStr)
       + strlen(authenticatorStr)
-      + fUserAgentHeaderStrSize;
+      + fUserAgentHeaderStrLen
+      + strlen(extraHeaders)
+      + strlen(contentLengthHeader)
+      + contentStrLen;
     cmd = new char[cmdSize];
     sprintf(cmd, cmdFmt,
-	    setupStr,
-	    ++fCSeq,
-	    transportStr,
-	    sessionStr,
+	    request->commandName(), cmdURL, protocolStr,
+	    request->cseq(),
 	    authenticatorStr,
-	    fUserAgentHeaderStr);
-    delete[] authenticatorStr; delete[] sessionStr; delete[] setupStr; delete[] transportStr;
-
-    // And then send it:
-    if (!sendRequest(cmd, "SETUP")) break;
-
-    // Get the response from the server:
-    unsigned bytesRead; unsigned responseCode;
-    char* firstLine; char* nextLineStart;
-    if (!getResponse("SETUP", bytesRead, responseCode, firstLine, nextLineStart)) break;
-
-    // Look for a "Session:" header (to set our session id), and
-    // a "Transport: " header (to set the server address/port)
-    // For now, ignore other headers.
-    char* lineStart;
-    char* sessionId = new char[fResponseBufferSize]; // ensures we have enough space
-    unsigned cLength = 0;
-    while (1) {
-      lineStart = nextLineStart;
-      if (lineStart == NULL) break;
-
-      nextLineStart = getLine(lineStart);
-
-      if (sscanf(lineStart, "Session: %[^;]", sessionId) == 1) {
-	subsession.sessionId = strDup(sessionId);
-	delete[] fLastSessionId; fLastSessionId = strDup(sessionId);
-
-	// Also look for an optional "; timeout = " parameter following this:
-	char* afterSessionId
-	  = lineStart + strlen(sessionId) + strlen ("Session: ");;
-	int timeoutVal;
-	if (sscanf(afterSessionId, "; timeout = %d", &timeoutVal) == 1) {
-	  fSessionTimeoutParameter = timeoutVal;
-	}
-	continue;
-      }
-
-      char* serverAddressStr;
-      portNumBits serverPortNum;
-      unsigned char rtpChannelId, rtcpChannelId;
-      if (parseTransportResponse(lineStart,
-				 serverAddressStr, serverPortNum,
-				 rtpChannelId, rtcpChannelId)) {
-	delete[] subsession.connectionEndpointName();
-	subsession.connectionEndpointName() = serverAddressStr;
-	subsession.serverPortNum = serverPortNum;
-	subsession.rtpChannelId = rtpChannelId;
-	subsession.rtcpChannelId = rtcpChannelId;
-	continue;
-      }
-
-      // Also check for a "Content-Length:" header.  Some weird servers include this
-      // in the RTSP "SETUP" response.
-      if (sscanf(lineStart, "Content-Length: %d", &cLength) == 1) continue;
-    }
-    delete[] sessionId;
-
-    if (subsession.sessionId == NULL) {
-      envir().setResultMsg("\"Session:\" header is missing in the response");
+	    fUserAgentHeaderStr,
+            extraHeaders,
+	    contentLengthHeader,
+	    contentStr);
+    delete[] authenticatorStr;
+    if (cmdURLWasAllocated) delete[] cmdURL;
+    if (extraHeadersWereAllocated) delete[] extraHeaders;
+    if (contentLengthHeaderWasAllocated) delete[] contentLengthHeader;
+
+    if (fVerbosityLevel >= 1) envir() << "Sending request: " << cmd << "\n";
+
+    if (fTunnelOverHTTPPortNum != 0 && strcmp(request->commandName(), "GET") != 0 && strcmp(request->commandName(), "POST") != 0) {
+      // When we're tunneling RTSP-over-HTTP, we Base-64-encode the request before we send it.
+      // (However, we don't do this for the HTTP "GET" and "POST" commands that we use to set up the tunnel.)
+      char* origCmd = cmd;
+      cmd = base64Encode(origCmd, strlen(cmd));
+      if (fVerbosityLevel >= 1) envir() << "\tThe request was base-64 encoded to: " << cmd << "\n\n";
+      delete[] origCmd;
+    }
+
+    if (send(fOutputSocketNum, cmd, strlen(cmd), 0) < 0) {
+      char const* errFmt = "%s send() failed: ";
+      unsigned const errLength = strlen(errFmt) + strlen(request->commandName());
+      char* err = new char[errLength];
+      sprintf(err, errFmt, request->commandName());
+      envir().setResultErrMsg(err);
+      delete[] err;
       break;
     }
 
-    // If we saw a "Content-Length:" header in the response, then discard whatever
-    // included data it refers to:
-    if (cLength > 0) {
-      char* dummyBuf = new char[cLength+1]; // allow for a trailing '\0'
-      getResponse1(dummyBuf, cLength);
-      delete[] dummyBuf;
-    }
-
-    if (streamUsingTCP) {
-      // Tell the subsession to receive RTP (and send/receive RTCP)
-      // over the RTSP stream:
-      if (subsession.rtpSource() != NULL)
-	subsession.rtpSource()->setStreamSocket(fInputSocketNum,
-						subsession.rtpChannelId);
-      if (subsession.rtcpInstance() != NULL)
-	subsession.rtcpInstance()->setStreamSocket(fInputSocketNum,
-						   subsession.rtcpChannelId);
-    } else {
-      // Normal case.
-      // Set the RTP and RTCP sockets' destination address and port
-      // from the information in the SETUP response (if present):
-      netAddressBits destAddress = subsession.connectionEndpointAddress();
-      if (destAddress == 0) destAddress = fServerAddress;
-      subsession.setDestinations(destAddress);
-    }
+    // The command send succeeded, so enqueue the request record, so that its response (when it comes) can be handled:
+    fRequestsAwaitingResponse.enqueue(request);
 
     delete[] cmd;
-    return True;
+    return request->cseq();
   } while (0);
 
+  // An error occurred, so call the response handler immediately (indicating the error):
   delete[] cmd;
-  return False;
+  handleRequestError(request);
+  delete request;
+  return 0;
 }
 
-static char* createScaleString(float scale, float currentScale) {
-  char buf[100];
-  if (scale == 1.0f && currentScale == 1.0f) {
-    // This is the default value; we don't need a "Scale:" header:
-    buf[0] = '\0';
-  } else {
-    Locale l("C", LC_NUMERIC);
-    sprintf(buf, "Scale: %f\r\n", scale);
+void RTSPClient::handleRequestError(RequestRecord* request) {
+  int resultCode = -envir().getErrno();
+  if (resultCode == 0) {
+    // Choose some generic error code instead:
+#if defined(__WIN32__) || defined(_WIN32) || defined(_QNX4)
+    resultCode = -WSAENOTCONN;
+#else
+    resultCode = -ENOTCONN;
+#endif
   }
+  if (request->handler() != NULL) (*request->handler())(this, resultCode, strDup(envir().getResultMsg()));
+}
 
-  return strDup(buf);
+Boolean RTSPClient
+::parseResponseCode(char const* line, unsigned& responseCode, char const*& responseString, Boolean& responseIsHTTP) {
+  responseIsHTTP = False; // by default
+  if (sscanf(line, "RTSP/%*s%u", &responseCode) != 1) {
+    if (sscanf(line, "HTTP/%*s%u", &responseCode) != 1) return False;
+    responseIsHTTP = True;
+    // Note: We check for HTTP responses as well as RTSP responses, both in order to setup RTSP-over-HTTP tunneling,
+    // and so that we get back a meaningful error if the client tried to mistakenly send a RTSP command to a HTTP-only server.
+  }
+
+  // Use everything after the RTSP/* as the response string:
+  responseString = line;
+  while (responseString[0] != '\0' && responseString[0] != ' '  && responseString[0] != '\t') ++responseString;
+  while (responseString[0] != '\0' && (responseString[0] == ' '  || responseString[0] == '\t')) ++responseString; // skip whitespace
+
+  return True;
 }
 
-static char* createRangeString(double start, double end) {
-  char buf[100];
-  if (start < 0) {
-    // We're resuming from a PAUSE; there's no "Range:" header at all
-    buf[0] = '\0';
-  } else if (end < 0) {
-    // There's no end time:
-    Locale l("C", LC_NUMERIC);
-    sprintf(buf, "Range: npt=%.3f-\r\n", start);
+void RTSPClient::handleIncomingRequest() {
+  // Parse the request string into command name and 'CSeq', then 'handle' the command (by responding that we don't support it):
+  char cmdName[RTSP_PARAM_STRING_MAX];
+  char urlPreSuffix[RTSP_PARAM_STRING_MAX];
+  char urlSuffix[RTSP_PARAM_STRING_MAX];
+  char cseq[RTSP_PARAM_STRING_MAX];
+  if (!parseRTSPRequestString(fResponseBuffer, fResponseBytesAlreadySeen,
+			      cmdName, sizeof cmdName,
+			      urlPreSuffix, sizeof urlPreSuffix,
+			      urlSuffix, sizeof urlSuffix,
+			      cseq, sizeof cseq)) {
+    return;
   } else {
-    // There's both a start and an end time; include them both in the "Range:" hdr
-    Locale l("C", LC_NUMERIC);
-    sprintf(buf, "Range: npt=%.3f-%.3f\r\n", start, end);
+    if (fVerbosityLevel >= 1) {
+      envir() << "Received incoming RTSP request: " << fResponseBuffer << "\n";
+    }
+    char tmpBuf[2*RTSP_PARAM_STRING_MAX];
+    snprintf((char*)tmpBuf, sizeof tmpBuf,
+             "RTSP/1.0 405 Method Not Allowed\r\nCSeq: %s\r\n\r\n", cseq);
+    send(fOutputSocketNum, tmpBuf, strlen(tmpBuf), 0);
   }
-
-  return strDup(buf);
 }
 
-static char const* NoSessionErr = "No RTSP session is currently in progress\n";
+Boolean RTSPClient::checkForHeader(char const* line, char const* headerName, unsigned headerNameLength, char const*& headerParams) {
+  if (_strncasecmp(line, headerName, headerNameLength) != 0) return False;
 
-Boolean RTSPClient::playMediaSession(MediaSession& session,
-				     double start, double end, float scale) {
-  char* cmd = NULL;
-  do {
-    // First, make sure that we have a RTSP session in progress
-    if (fLastSessionId == NULL) {
-      envir().setResultMsg(NoSessionErr);
-      break;
-    }
+  // The line begins with the desired header name.  Trim off any whitespace, and return the header parameters:
+  unsigned paramIndex = headerNameLength;
+  while (line[paramIndex] != '\0' && (line[paramIndex] == ' ' || line[paramIndex] == '\t')) ++paramIndex;
+  if (&line[paramIndex] == '\0') return False; // the header is assumed to be bad if it has no parameters
 
-    // Send the PLAY command:
+  headerParams = &line[paramIndex];
+  return True;
+}
 
-    // First, construct an authenticator string:
-    char* authenticatorStr
-      = createAuthenticatorString(&fCurrentAuthenticator, "PLAY", fBaseURL);
-    // And then a "Scale:" string:
-    char* scaleStr = createScaleString(scale, session.scale());
-    // And then a "Range:" string:
-    char* rangeStr = createRangeString(start, end);
+Boolean RTSPClient::parseTransportParams(char const* paramsStr,
+					 char*& serverAddressStr, portNumBits& serverPortNum,
+					 unsigned char& rtpChannelId, unsigned char& rtcpChannelId) {
+  // Initialize the return parameters to 'not found' values:
+  serverAddressStr = NULL;
+  serverPortNum = 0;
+  rtpChannelId = rtcpChannelId = 0xFF;
 
-    char const* const cmdFmt =
-      "PLAY %s RTSP/1.0\r\n"
-      "CSeq: %d\r\n"
-      "Session: %s\r\n"
-      "%s"
-      "%s"
-      "%s"
-      "%s"
-      "\r\n";
+  char* foundServerAddressStr = NULL;
+  Boolean foundServerPortNum = False;
+  portNumBits clientPortNum = 0;
+  Boolean foundClientPortNum = False;
+  Boolean foundChannelIds = False;
+  unsigned rtpCid, rtcpCid;
+  Boolean isMulticast = True; // by default
+  char* foundDestinationStr = NULL;
+  portNumBits multicastPortNumRTP, multicastPortNumRTCP;
+  Boolean foundMulticastPortNum = False;
 
-    char const* sessURL = sessionURL(session);
-    unsigned cmdSize = strlen(cmdFmt)
-      + strlen(sessURL)
-      + 20 /* max int len */
-      + strlen(fLastSessionId)
-      + strlen(scaleStr)
-      + strlen(rangeStr)
-      + strlen(authenticatorStr)
-      + fUserAgentHeaderStrSize;
-    cmd = new char[cmdSize];
-    sprintf(cmd, cmdFmt,
-	    sessURL,
-	    ++fCSeq,
-	    fLastSessionId,
-	    scaleStr,
-	    rangeStr,
-	    authenticatorStr,
-	    fUserAgentHeaderStr);
-    delete[] scaleStr;
-    delete[] rangeStr;
-    delete[] authenticatorStr;
+  // Run through each of the parameters, looking for ones that we handle:
+  char const* fields = paramsStr;
+  char* field = strDupSize(fields);
+  while (sscanf(fields, "%[^;]", field) == 1) {
+    if (sscanf(field, "server_port=%hu", &serverPortNum) == 1) {
+      foundServerPortNum = True;
+    } else if (sscanf(field, "client_port=%hu", &clientPortNum) == 1) {
+      foundClientPortNum = True;
+    } else if (_strncasecmp(field, "source=", 7) == 0) {
+      delete[] foundServerAddressStr;
+      foundServerAddressStr = strDup(field+7);
+    } else if (sscanf(field, "interleaved=%u-%u", &rtpCid, &rtcpCid) == 2) {
+      rtpChannelId = (unsigned char)rtpCid;
+      rtcpChannelId = (unsigned char)rtcpCid;
+      foundChannelIds = True;
+    } else if (strcmp(field, "unicast") == 0) {
+      isMulticast = False;
+    } else if (_strncasecmp(field, "destination=", 12) == 0) {
+      delete[] foundDestinationStr;
+      foundDestinationStr = strDup(field+12);
+    } else if (sscanf(field, "port=%hu-%hu", &multicastPortNumRTP, &multicastPortNumRTCP) == 2 ||
+	       sscanf(field, "port=%hu", &multicastPortNumRTP) == 1) {
+      foundMulticastPortNum = True;
+    }
 
-    if (!sendRequest(cmd, "PLAY")) break;
+    fields += strlen(field);
+    while (fields[0] == ';') ++fields; // skip over all leading ';' chars
+    if (fields[0] == '\0') break;
+  }
+  delete[] field;
 
-    // Get the response from the server:
-    unsigned bytesRead; unsigned responseCode;
-    char* firstLine; char* nextLineStart;
-    if (!getResponse("PLAY", bytesRead, responseCode, firstLine, nextLineStart)) break;
+  // If we're multicast, and have a "destination=" (multicast) address, then use this
+  // as the 'server' address (because some weird servers don't specify the multicast
+  // address earlier, in the "DESCRIBE" response's SDP:
+  if (isMulticast && foundDestinationStr != NULL && foundMulticastPortNum) {
+    delete[] foundServerAddressStr;
+    serverAddressStr = foundDestinationStr;
+    serverPortNum = multicastPortNumRTP;
+    return True;
+  }
+  delete[] foundDestinationStr;
 
-    // Look for various headers that we understand:
-    char* lineStart;
-    while (1) {
-      lineStart = nextLineStart;
-      if (lineStart == NULL) break;
+  // We have a valid "Transport:" header if any of the following are true:
+  //   - We saw a "interleaved=" field, indicating RTP/RTCP-over-TCP streaming, or
+  //   - We saw a "server_port=" field, or
+  //   - We saw a "client_port=" field.
+  //     If we didn't also see a "server_port=" field, then the server port is assumed to be the same as the client port.
+  if (foundChannelIds || foundServerPortNum || foundClientPortNum) {
+    if (foundClientPortNum && !foundServerPortNum) {
+      serverPortNum = clientPortNum;
+    }
+    serverAddressStr = foundServerAddressStr;
+    return True;
+  }
 
-      nextLineStart = getLine(lineStart);
+  delete[] foundServerAddressStr;
+  return False;
+}
 
-      if (parseScaleHeader(lineStart, session.scale())) continue;
-      if (parseRangeHeader(lineStart, session.playStartTime(), session.playEndTime())) continue;
+Boolean RTSPClient::parseScaleParam(char const* paramStr, float& scale) {
+  Locale l("C", LC_NUMERIC);
+  return sscanf(paramStr, "%f", &scale) == 1;
+}
 
-      u_int16_t seqNum; u_int32_t timestamp;
-      if (parseRTPInfoHeader(lineStart, seqNum, timestamp)) {
-	// This is data for our first subsession.  Fill it in, and do the same for our other subsessions:
-	MediaSubsessionIterator iter(session);
-	MediaSubsession* subsession;
-	while ((subsession = iter.next()) != NULL) {
-	  subsession->rtpInfo.seqNum = seqNum;
-	  subsession->rtpInfo.timestamp = timestamp;
-	  subsession->rtpInfo.infoIsNew = True;
+Boolean RTSPClient::parseRTPInfoParams(char const*& paramsStr, u_int16_t& seqNum, u_int32_t& timestamp) {
+  while (paramsStr[0] == ',') ++paramsStr;
 
-	  if (!parseRTPInfoHeader(lineStart, seqNum, timestamp)) break;
-	}
-	continue;
-      }
-    }
+  // "paramsStr" now consists of a ';'-separated list of parameters, ending with ',' or '\0'.
+  char* field = strDupSize(paramsStr);
 
-    if (fTCPStreamIdCount == 0) { // we're not receiving RTP-over-TCP
-      // Arrange to handle incoming requests sent by the server
-      envir().taskScheduler().turnOnBackgroundReadHandling(fInputSocketNum,
-	   (TaskScheduler::BackgroundHandlerProc*)&incomingRequestHandler, this);
+  while (sscanf(paramsStr, "%[^;,]", field) == 1) {
+    if (sscanf(field, "seq=%hu", &seqNum) == 1 ||
+	sscanf(field, "rtptime=%u", &timestamp) == 1) {
     }
 
-    delete[] cmd;
-    return True;
-  } while (0);
+    paramsStr += strlen(field);
+    if (paramsStr[0] == '\0' || paramsStr[0] == ',') break;
+    // ASSERT: paramsStr[0] == ';'
+    ++paramsStr; // skip over the ';'
+  }
 
-  delete[] cmd;
-  return False;
+  delete[] field;
+  return True;
 }
 
-Boolean RTSPClient::playMediaSubsession(MediaSubsession& subsession,
-					double start, double end, float scale,
-					Boolean hackForDSS) {
-  char* cmd = NULL;
+Boolean RTSPClient::handleSETUPResponse(MediaSubsession& subsession, char const* sessionParamsStr, char const* transportParamsStr,
+                                        Boolean streamUsingTCP) {
+  char* sessionId = new char[responseBufferSize]; // ensures we have enough space
+  Boolean success = False;
   do {
-    // First, make sure that we have a RTSP session in progress
-    if (subsession.sessionId == NULL) {
-      envir().setResultMsg(NoSessionErr);
+    // Check for a session id:
+    if (sessionParamsStr == NULL || sscanf(sessionParamsStr, "%[^;]", sessionId) != 1) {
+      envir().setResultMsg("Missing or bad \"Session:\" header");
       break;
     }
+    subsession.sessionId = strDup(sessionId);
+    delete[] fLastSessionId; fLastSessionId = strDup(sessionId);
 
-    // Send the PLAY command:
-
-    // First, construct an authenticator string:
-    char* authenticatorStr
-      = createAuthenticatorString(&fCurrentAuthenticator, "PLAY", fBaseURL);
-    // And then a "Scale:" string:
-    char* scaleStr = createScaleString(scale, subsession.scale());
-    // And then a "Range:" string:
-    char* rangeStr = createRangeString(start, end);
+    // Also look for an optional "; timeout = " parameter following this:
+    char const* afterSessionId = sessionParamsStr + strlen(sessionId);
+    int timeoutVal;
+    if (sscanf(afterSessionId, "; timeout = %d", &timeoutVal) == 1) {
+      fSessionTimeoutParameter = timeoutVal;
+    }
 
-    char const* const cmdFmt =
-      "PLAY %s%s%s RTSP/1.0\r\n"
-      "CSeq: %d\r\n"
-      "Session: %s\r\n"
-      "%s"
-      "%s"
-      "%s"
-      "%s"
-      "\r\n";
-
-    char const *prefix, *separator, *suffix;
-    constructSubsessionURL(subsession, prefix, separator, suffix);
-    if (hackForDSS || fServerIsKasenna) {
-      // When "PLAY" is used to inject RTP packets into a DSS
-      // (violating the RTSP spec, btw; "RECORD" should have been used)
-      // the DSS can crash (or hang) if the '/trackid=...' portion of
-      // the URL is present.
-      separator = suffix = "";
+    // Parse the "Transport:" header parameters:
+    char* serverAddressStr;
+    portNumBits serverPortNum;
+    unsigned char rtpChannelId, rtcpChannelId;
+    if (!parseTransportParams(transportParamsStr, serverAddressStr, serverPortNum, rtpChannelId, rtcpChannelId)) {
+      envir().setResultMsg("Missing or bad \"Transport:\" header");
+      break;
     }
+    delete[] subsession.connectionEndpointName();
+    subsession.connectionEndpointName() = serverAddressStr;
+    subsession.serverPortNum = serverPortNum;
+    subsession.rtpChannelId = rtpChannelId;
+    subsession.rtcpChannelId = rtcpChannelId;
 
-    unsigned cmdSize = strlen(cmdFmt)
-      + strlen(prefix) + strlen(separator) + strlen(suffix)
-      + 20 /* max int len */
-      + strlen(subsession.sessionId)
-      + strlen(scaleStr)
-      + strlen(rangeStr)
-      + strlen(authenticatorStr)
-      + fUserAgentHeaderStrSize;
-    cmd = new char[cmdSize];
-    sprintf(cmd, cmdFmt,
-	    prefix, separator, suffix,
-	    ++fCSeq,
-	    subsession.sessionId,
-	    scaleStr,
-	    rangeStr,
-	    authenticatorStr,
-	    fUserAgentHeaderStr);
-    delete[] scaleStr;
-    delete[] rangeStr;
-    delete[] authenticatorStr;
+    if (streamUsingTCP) {
+      // Tell the subsession to receive RTP (and send/receive RTCP) over the RTSP stream:
+      if (subsession.rtpSource() != NULL) {
+	subsession.rtpSource()->setStreamSocket(fInputSocketNum, subsession.rtpChannelId);
+	subsession.rtpSource()->setServerRequestAlternativeByteHandler(fInputSocketNum, handleAlternativeRequestByte, this);
+      }
+      if (subsession.rtcpInstance() != NULL) subsession.rtcpInstance()->setStreamSocket(fInputSocketNum, subsession.rtcpChannelId);
+    } else {
+      // Normal case.
+      // Set the RTP and RTCP sockets' destination address and port from the information in the SETUP response (if present):
+      netAddressBits destAddress = subsession.connectionEndpointAddress();
+      if (destAddress == 0) destAddress = fServerAddress;
+      subsession.setDestinations(destAddress);
+    }
 
-    if (!sendRequest(cmd, "PLAY")) break;
+    success = True;
+  } while (0);
 
-    // Get the response from the server:
-    unsigned bytesRead; unsigned responseCode;
-    char* firstLine; char* nextLineStart;
-    if (!getResponse("PLAY", bytesRead, responseCode, firstLine, nextLineStart)) break;
+  delete[] sessionId;
+  return success;
+}
 
-    // Look for various headers that we understand:
-    char* lineStart;
-    while (1) {
-      lineStart = nextLineStart;
-      if (lineStart == NULL) break;
+Boolean RTSPClient::handlePLAYResponse(MediaSession& session, MediaSubsession& subsession,
+                                       char const* scaleParamsStr, char const* rangeParamsStr, char const* rtpInfoParamsStr) {
+  Boolean scaleOK = False, rangeOK = False;
+  do {
+    if (&session != NULL) {
+      // The command was on the whole session
+      if (scaleParamsStr != NULL && !parseScaleParam(scaleParamsStr, session.scale())) break;
+      scaleOK = True;
+      if (rangeParamsStr != NULL && !parseRangeParam(rangeParamsStr, session.playStartTime(), session.playEndTime())) break;
+      rangeOK = True;
 
-      nextLineStart = getLine(lineStart);
+      u_int16_t seqNum; u_int32_t timestamp;
+      if (rtpInfoParamsStr != NULL) {
+	if (!parseRTPInfoParams(rtpInfoParamsStr, seqNum, timestamp)) break;
+	// This is data for our first subsession.  Fill it in, and do the same for our other subsessions:
+	MediaSubsessionIterator iter(session);
+	MediaSubsession* subsession;
+	while ((subsession = iter.next()) != NULL) {
+	  subsession->rtpInfo.seqNum = seqNum;
+	  subsession->rtpInfo.timestamp = timestamp;
+	  subsession->rtpInfo.infoIsNew = True;
 
-      if (parseScaleHeader(lineStart, subsession.scale())) continue;
-      if (parseRangeHeader(lineStart, subsession._playStartTime(), subsession._playEndTime())) continue;
+	  if (!parseRTPInfoParams(rtpInfoParamsStr, seqNum, timestamp)) break;
+	}
+      }
+    } else {
+      // The command was on a subsession
+      if (scaleParamsStr != NULL && !parseScaleParam(scaleParamsStr, subsession.scale())) break;
+      scaleOK = True;
+      if (rangeParamsStr != NULL && !parseRangeParam(rangeParamsStr, subsession._playStartTime(), subsession._playEndTime())) break;
+      rangeOK = True;
 
       u_int16_t seqNum; u_int32_t timestamp;
-      if (parseRTPInfoHeader(lineStart, seqNum, timestamp)) {
+      if (rtpInfoParamsStr != NULL) {
+	if (!parseRTPInfoParams(rtpInfoParamsStr, seqNum, timestamp)) break;
 	subsession.rtpInfo.seqNum = seqNum;
 	subsession.rtpInfo.timestamp = timestamp;
 	subsession.rtpInfo.infoIsNew = True;
-	continue;
       }
     }
 
-    delete[] cmd;
     return True;
   } while (0);
 
-  delete[] cmd;
+  // An error occurred:
+  if (!scaleOK) {
+    envir().setResultMsg("Bad \"Scale:\" header");
+  } else if (!rangeOK) {
+    envir().setResultMsg("Bad \"Range:\" header");
+  } else {
+    envir().setResultMsg("Bad \"RTP-Info:\" header");
+  }
   return False;
 }
 
-Boolean RTSPClient::pauseMediaSession(MediaSession& session) {
-  char* cmd = NULL;
-  do {
-    // First, make sure that we have a RTSP session in progress
-    if (fLastSessionId == NULL) {
-      envir().setResultMsg(NoSessionErr);
-      break;
+Boolean RTSPClient::handleTEARDOWNResponse(MediaSession& session, MediaSubsession& subsession) {
+  if (&session != NULL) {
+    // The command was on the whole session
+    // Run through each subsession, deleting its "sessionId":
+    MediaSubsessionIterator iter(session);
+    MediaSubsession* subsession;
+    while ((subsession = iter.next()) != NULL) {
+      delete[] (char*)subsession->sessionId;
+      subsession->sessionId = NULL;
     }
-
-    // Send the PAUSE command:
-
-    // First, construct an authenticator string:
-    char* authenticatorStr
-      = createAuthenticatorString(&fCurrentAuthenticator, "PAUSE", fBaseURL);
-
-    char const* sessURL = sessionURL(session);
-    char const* const cmdFmt =
-      "PAUSE %s RTSP/1.0\r\n"
-      "CSeq: %d\r\n"
-      "Session: %s\r\n"
-      "%s"
-      "%s"
-      "\r\n";
-
-    unsigned cmdSize = strlen(cmdFmt)
-      + strlen(sessURL)
-      + 20 /* max int len */
-      + strlen(fLastSessionId)
-      + strlen(authenticatorStr)
-      + fUserAgentHeaderStrSize;
-    cmd = new char[cmdSize];
-    sprintf(cmd, cmdFmt,
-	    sessURL,
-	    ++fCSeq,
-	    fLastSessionId,
-	    authenticatorStr,
-	    fUserAgentHeaderStr);
-    delete[] authenticatorStr;
-
-    if (!sendRequest(cmd, "PAUSE")) break;
-
-    if (fTCPStreamIdCount == 0) { // When TCP streaming, don't look for a response
-      // Get the response from the server:
-      unsigned bytesRead; unsigned responseCode;
-      char* firstLine; char* nextLineStart;
-      if (!getResponse("PAUSE", bytesRead, responseCode, firstLine, nextLineStart)) break;
-    }
-
-    delete[] cmd;
-    return True;
-  } while (0);
-
-  delete[] cmd;
-  return False;
+  } else {
+    // The command was on a subsession
+    delete[] (char*)subsession.sessionId;
+    subsession.sessionId = NULL;
+  }
+  return True;
 }
 
-Boolean RTSPClient::pauseMediaSubsession(MediaSubsession& subsession) {
-  char* cmd = NULL;
+Boolean RTSPClient::handleGET_PARAMETERResponse(char const* parameterName, char*& resultValueString) {
   do {
-    // First, make sure that we have a RTSP session in progress
-    if (subsession.sessionId == NULL) {
-      envir().setResultMsg(NoSessionErr);
-      break;
-    }
-
-    // Send the PAUSE command:
-
-    // First, construct an authenticator string:
-    char* authenticatorStr
-      = createAuthenticatorString(&fCurrentAuthenticator, "PAUSE", fBaseURL);
-
-    char const* const cmdFmt =
-      "PAUSE %s%s%s RTSP/1.0\r\n"
-      "CSeq: %d\r\n"
-      "Session: %s\r\n"
-      "%s"
-      "%s"
-      "\r\n";
+    // If "parameterName" is non-empty, it should be (possibly followed by ':' and whitespace) at the start of the result string:
+    if (parameterName != NULL && parameterName[0] != '\0') {
+      if (parameterName[1] == '\0') break; // sanity check; there should have been \r\n at the end of "parameterName"
 
-    char const *prefix, *separator, *suffix;
-    constructSubsessionURL(subsession, prefix, separator, suffix);
-    if (fServerIsKasenna) separator = suffix = "";
-
-    unsigned cmdSize = strlen(cmdFmt)
-      + strlen(prefix) + strlen(separator) + strlen(suffix)
-      + 20 /* max int len */
-      + strlen(subsession.sessionId)
-      + strlen(authenticatorStr)
-      + fUserAgentHeaderStrSize;
-    cmd = new char[cmdSize];
-    sprintf(cmd, cmdFmt,
-	    prefix, separator, suffix,
-	    ++fCSeq,
-	    subsession.sessionId,
-	    authenticatorStr,
-	    fUserAgentHeaderStr);
-    delete[] authenticatorStr;
-
-    if (!sendRequest(cmd, "PAUSE")) break;
-
-    if (fTCPStreamIdCount == 0) { // When TCP streaming, don't look for a response
-      // Get the response from the server:
-      unsigned bytesRead; unsigned responseCode;
-      char* firstLine; char* nextLineStart;
-      if (!getResponse("PAUSE", bytesRead, responseCode, firstLine, nextLineStart)) break;
+      unsigned parameterNameLen = strlen(parameterName);
+      // ASSERT: parameterNameLen >= 2;
+      parameterNameLen -= 2; // because of the trailing \r\n
+      if (_strncasecmp(resultValueString, parameterName, parameterNameLen) != 0) break; // parameter name wasn't in the output
+      resultValueString += parameterNameLen;
+      if (resultValueString[0] == ':') ++resultValueString;
+      while (resultValueString[0] == ' ' || resultValueString[0] == '\t') ++resultValueString;
     }
 
-    delete[] cmd;
+    // The rest of "resultValueStr" should be our desired result, but first trim off any \r and/or \n characters at the end:
+    unsigned resultLen = strlen(resultValueString);
+    while (resultLen > 0 && (resultValueString[resultLen-1] == '\r' || resultValueString[resultLen-1] == '\n')) --resultLen;
+    resultValueString[resultLen] = '\0';
+
     return True;
   } while (0);
 
-  delete[] cmd;
+  // An error occurred:
+  envir().setResultMsg("Bad \"GET_PARAMETER\" response");
   return False;
 }
 
-Boolean RTSPClient::recordMediaSubsession(MediaSubsession& subsession) {
-  char* cmd = NULL;
-  do {
-    // First, make sure that we have a RTSP session in progress
-    if (subsession.sessionId == NULL) {
-      envir().setResultMsg(NoSessionErr);
-      break;
-    }
+Boolean RTSPClient::handleAuthenticationFailure(char const* paramsStr) {
+  // Fill in "fCurrentAuthenticator" with the information from the "WWW-Authenticate:" header:
+  Boolean alreadyHadRealm = fCurrentAuthenticator.realm() != NULL;
+  char* realm = strDupSize(paramsStr);
+  char* nonce = strDupSize(paramsStr);
+  Boolean success = True;
+  if (sscanf(paramsStr, "Digest realm=\"%[^\"]\", nonce=\"%[^\"]\"", realm, nonce) == 2) {
+    fCurrentAuthenticator.setRealmAndNonce(realm, nonce);
+  } else if (sscanf(paramsStr, "Basic realm=\"%[^\"]\"", realm) == 1) {
+    fCurrentAuthenticator.setRealmAndNonce(realm, NULL); // Basic authentication
+  } else {
+    success = False; // bad "WWW-Authenticate:" header
+  }
+  delete[] realm; delete[] nonce;
 
-    // Send the RECORD command:
+  if (alreadyHadRealm || fCurrentAuthenticator.username() == NULL || fCurrentAuthenticator.password() == NULL) {
+    // We already had a 'realm', or don't have a username and/or password,
+    // so the new "WWW-Authenticate:" header information won't help us.  We remain unauthenticated.
+    success = False;
+  }
 
-    // First, construct an authenticator string:
-    char* authenticatorStr
-      = createAuthenticatorString(&fCurrentAuthenticator,
-				  "RECORD", fBaseURL);
+  return success;
+}
 
-    char const* const cmdFmt =
-      "RECORD %s%s%s RTSP/1.0\r\n"
-      "CSeq: %d\r\n"
-      "Session: %s\r\n"
-      "Range: npt=0-\r\n"
-      "%s"
-      "%s"
-      "\r\n";
+Boolean RTSPClient::resendCommand(RequestRecord* request) {
+  if (fVerbosityLevel >= 1) envir() << "Resending...\n";
+  if (request != NULL) request->cseq() = ++fCSeq;
+  return sendRequest(request) != 0;
+}
 
-    char const *prefix, *separator, *suffix;
-    constructSubsessionURL(subsession, prefix, separator, suffix);
+char const* RTSPClient::sessionURL(MediaSession const& session) const {
+  char const* url = session.controlPath();
+  if (url == NULL || strcmp(url, "*") == 0) url = fBaseURL;
 
-    unsigned cmdSize = strlen(cmdFmt)
-      + strlen(prefix) + strlen(separator) + strlen(suffix)
-      + 20 /* max int len */
-      + strlen(subsession.sessionId)
-      + strlen(authenticatorStr)
-      + fUserAgentHeaderStrSize;
-    cmd = new char[cmdSize];
-    sprintf(cmd, cmdFmt,
-	    prefix, separator, suffix,
-	    ++fCSeq,
-	    subsession.sessionId,
-	    authenticatorStr,
-	    fUserAgentHeaderStr);
-    delete[] authenticatorStr;
+  return url;
+}
 
-    if (!sendRequest(cmd, "RECORD")) break;
+void RTSPClient::handleAlternativeRequestByte(void* rtspClient, u_int8_t requestByte) {
+  ((RTSPClient*)rtspClient)->handleAlternativeRequestByte1(requestByte);
+}
 
-    // Get the response from the server:
-    unsigned bytesRead; unsigned responseCode;
-    char* firstLine; char* nextLineStart;
-    if (!getResponse("RECORD", bytesRead, responseCode, firstLine, nextLineStart)) break;
+void RTSPClient::handleAlternativeRequestByte1(u_int8_t requestByte) {
+  fResponseBuffer[fResponseBytesAlreadySeen] = requestByte;
+  handleResponseBytes(1);
+}
 
-    delete[] cmd;
-    return True;
-  } while (0);
+static Boolean isAbsoluteURL(char const* url) {
+  // Assumption: "url" is absolute if it contains a ':', before any
+  // occurrence of '/'
+  while (*url != '\0' && *url != '/') {
+    if (*url == ':') return True;
+    ++url;
+  }
 
-  delete[] cmd;
   return False;
 }
 
-Boolean RTSPClient::setMediaSessionParameter(MediaSession& /*session*/,
-					     char const* parameterName,
-					     char const* parameterValue) {
-  char* cmd = NULL;
-  do {
-    // First, make sure that we have a RTSP session in progress
-    if (fLastSessionId == NULL) {
-      envir().setResultMsg(NoSessionErr);
-      break;
-    }
-
-    // Send the SET_PARAMETER command:
-
-    // First, construct an authenticator string:
-    char* authenticatorStr
-      = createAuthenticatorString(&fCurrentAuthenticator,
-				  "SET_PARAMETER", fBaseURL);
-
-    char const* const cmdFmt =
-      "SET_PARAMETER %s RTSP/1.0\r\n"
-      "CSeq: %d\r\n"
-      "Session: %s\r\n"
-      "%s"
-      "%s"
-      "Content-length: %d\r\n\r\n"
-      "%s: %s\r\n";
+void RTSPClient::constructSubsessionURL(MediaSubsession const& subsession,
+					char const*& prefix,
+					char const*& separator,
+					char const*& suffix) {
+  // Figure out what the URL describing "subsession" will look like.
+  // The URL is returned in three parts: prefix; separator; suffix
+  //##### NOTE: This code doesn't really do the right thing if "sessionURL()"
+  // doesn't end with a "/", and "subsession.controlPath()" is relative.
+  // The right thing would have been to truncate "sessionURL()" back to the
+  // rightmost "/", and then add "subsession.controlPath()".
+  // In practice, though, each "DESCRIBE" response typically contains
+  // a "Content-Base:" header that consists of "sessionURL()" followed by
+  // a "/", in which case this code ends up giving the correct result.
+  // However, we should really fix this code to do the right thing, and
+  // also check for and use the "Content-Base:" header appropriately. #####
+  prefix = sessionURL(subsession.parentSession());
+  if (prefix == NULL) prefix = "";
 
-    unsigned parameterNameLen = strlen(parameterName);
-    unsigned parameterValueLen = strlen(parameterValue);
-    unsigned cmdSize = strlen(cmdFmt)
-      + strlen(fBaseURL)
-      + 20 /* max int len */
-      + strlen(fLastSessionId)
-      + strlen(authenticatorStr)
-      + fUserAgentHeaderStrSize
-      + parameterNameLen + parameterValueLen;
-    cmd = new char[cmdSize];
-    sprintf(cmd, cmdFmt,
-	    fBaseURL,
-	    ++fCSeq,
-	    fLastSessionId,
-	    authenticatorStr,
-	    fUserAgentHeaderStr,
-	    parameterNameLen + parameterValueLen + 2, // the "+ 2" is for the \r\n after the parameter "name: value"
-	    parameterName, parameterValue);
-    delete[] authenticatorStr;
+  suffix = subsession.controlPath();
+  if (suffix == NULL) suffix = "";
 
-    if (!sendRequest(cmd, "SET_PARAMETER")) break;
+  if (isAbsoluteURL(suffix)) {
+    prefix = separator = "";
+  } else {
+    unsigned prefixLen = strlen(prefix);
+    separator = (prefixLen == 0 || prefix[prefixLen-1] == '/' || suffix[0] == '/') ? "" : "/";
+  }
+}
 
-    // Get the response from the server:
-    unsigned bytesRead; unsigned responseCode;
-    char* firstLine; char* nextLineStart;
-    if (!getResponse("SET_PARAMETER", bytesRead, responseCode, firstLine, nextLineStart)) break;
+Boolean RTSPClient::setupHTTPTunneling1() {
+  // Set up RTSP-over-HTTP tunneling, as described in
+  //     http://developer.apple.com/documentation/QuickTime/QTSS/Concepts/chapter_2_section_14.html
+  if (fVerbosityLevel >= 1) {
+    envir() << "Requesting RTSP-over-HTTP tunneling (on port " << fTunnelOverHTTPPortNum << ")\n\n";
+  }
 
-    delete[] cmd;
-    return True;
-  } while (0);
+  // Begin by sending a HTTP "GET", to set up the server->client link.  Continue when we handle the response:
+  return sendRequest(new RequestRecord(1, "GET", responseHandlerForHTTP_GET)) != 0;
+}
 
-  delete[] cmd;
-  return False;
+void RTSPClient::responseHandlerForHTTP_GET(RTSPClient* rtspClient, int responseCode, char* responseString) {
+  if (rtspClient != NULL) rtspClient->responseHandlerForHTTP_GET1(responseCode, responseString);
 }
 
-Boolean RTSPClient::getMediaSessionParameter(MediaSession& /*session*/,
-					     char const* parameterName,
-					     char*& parameterValue) {
-  parameterValue = NULL; // default result
-  Boolean const haveParameterName = parameterName != NULL && parameterName[0] != '\0';
-  char* cmd = NULL;
+void RTSPClient::responseHandlerForHTTP_GET1(int responseCode, char* responseString) {
+  RequestRecord* request;
   do {
-    // First, make sure that we have a RTSP session in progress
-    if (fLastSessionId == NULL) {
-      envir().setResultMsg(NoSessionErr);
-      break;
+    // Having successfully set up (using the HTTP "GET" command) the server->client link, set up a second TCP connection
+    // (to the same server & port as before) for the client->server link.  All future output will be to this new socket.
+    fOutputSocketNum = setupStreamSocket(envir(), 0);
+    if (fOutputSocketNum < 0) break;
+
+    fHTTPTunnelingConnectionIsPending = True;
+    int connectResult = connectToServer(fOutputSocketNum, fTunnelOverHTTPPortNum);
+    if (connectResult < 0) break; // an error occurred
+    else if (connectResult == 0) {
+      // A connection is pending.  Continue setting up RTSP-over-HTTP when the connection completes.
+      // First, move the pending requests to the 'awaiting connection' queue:
+      while ((request = fRequestsAwaitingHTTPTunneling.dequeue()) != NULL) {
+	fRequestsAwaitingConnection.enqueue(request);
+      }
+      return;
     }
 
-    // Send the GET_PARAMETER command:
-    // First, construct an authenticator string:
-    char* authenticatorStr
-      = createAuthenticatorString(&fCurrentAuthenticator,
-				  "GET_PARAMETER", fBaseURL);
-
-    if (haveParameterName) {
-      char const* const cmdFmt =
-	"GET_PARAMETER %s RTSP/1.0\r\n"
-	"CSeq: %d\r\n"
-	"Session: %s\r\n"
-	"%s"
-	"%s"
-	"Content-type: text/parameters\r\n"
-	"Content-length: %d\r\n\r\n"
-	"%s\r\n";
+    // The connection succeeded.  Continue setting up RTSP-over-HTTP:
+    if (!setupHTTPTunneling2()) break;
 
-      unsigned parameterNameLen = strlen(parameterName);
-      unsigned cmdSize = strlen(cmdFmt)
-	+ strlen(fBaseURL)
-	+ 20 /* max int len */
-	+ strlen(fLastSessionId)
-	+ strlen(authenticatorStr)
-	+ fUserAgentHeaderStrSize
-	+ parameterNameLen;
-      cmd = new char[cmdSize];
-      sprintf(cmd, cmdFmt,
-	      fBaseURL,
-	      ++fCSeq,
-	      fLastSessionId,
-	      authenticatorStr,
-	      fUserAgentHeaderStr,
-	      parameterNameLen + 2, // the "+ 2" is for the \r\n after the parameter name
-	      parameterName);
-    } else {
-      char const* const cmdFmt =
-	"GET_PARAMETER %s RTSP/1.0\r\n"
-	"CSeq: %d\r\n"
-	"Session: %s\r\n"
-	"%s"
-	"%s"
-	"\r\n";
-
-      unsigned cmdSize = strlen(cmdFmt)
-	+ strlen(fBaseURL)
-	+ 20 /* max int len */
-	+ strlen(fLastSessionId)
-	+ strlen(authenticatorStr)
-	+ fUserAgentHeaderStrSize;
-      cmd = new char[cmdSize];
-      sprintf(cmd, cmdFmt,
-	      fBaseURL,
-	      ++fCSeq,
-	      fLastSessionId,
-	      authenticatorStr,
-	      fUserAgentHeaderStr);
+    // RTSP-over-HTTP tunneling succeeded.  Resume the pending request(s):
+    while ((request = fRequestsAwaitingHTTPTunneling.dequeue()) != NULL) {
+      sendRequest(request);
     }
-    delete[] authenticatorStr;
-
-    if (!sendRequest(cmd, "GET_PARAMETER")) break;
+    return;
+  } while (0);
 
-    // Get the response from the server:
-    // This section was copied/modified from the RTSPClient::describeURL func
-    unsigned bytesRead; unsigned responseCode;
-    char* firstLine; char* nextLineStart;
-    if (!getResponse("GET_PARAMETER", bytesRead, responseCode, firstLine,
-            nextLineStart, False /*don't check for response code 200*/)) break;
+  // An error occurred.  Dequeue the pending request(s), and tell them about the error:
+  fHTTPTunnelingConnectionIsPending = False;
+  while ((request = fRequestsAwaitingHTTPTunneling.dequeue()) != NULL) {
+    handleRequestError(request);
+    delete request;
+  }
+  resetTCPSockets();
+}
 
-    // Inspect the first line to check whether it's a result code that
-    // we can handle.
-    if (responseCode != 200) {
-      envir().setResultMsg("cannot handle GET_PARAMETER response: ", firstLine);
-      break;
-    }
+Boolean RTSPClient::setupHTTPTunneling2() {
+  fHTTPTunnelingConnectionIsPending = False;
 
-    // Skip every subsequent header line, until we see a blank line
-    // The remaining data is assumed to be the parameter data that we want.
-    char* serverType = new char[fResponseBufferSize]; // ensures enough space
-    int contentLength = -1;
-    char* lineStart;
-    while (1) {
-      lineStart = nextLineStart;
-      if (lineStart == NULL) break;
+  // Send a HTTP "POST", to set up the client->server link.  (Note that we won't see a reply to the "POST".)
+  return sendRequest(new RequestRecord(1, "POST", NULL)) != 0;
+}
 
-      nextLineStart = getLine(lineStart);
-      if (lineStart[0] == '\0') break; // this is a blank line
+void RTSPClient::connectionHandler(void* instance, int /*mask*/) {
+  RTSPClient* client = (RTSPClient*)instance;
+  client->connectionHandler1();
+}
 
-      if (sscanf(lineStart, "Content-Length: %d", &contentLength) == 1
-	  || sscanf(lineStart, "Content-length: %d", &contentLength) == 1) {
-	if (contentLength < 0) {
-	  envir().setResultMsg("Bad \"Content-length:\" header: \"",
-			       lineStart, "\"");
-	  break;
-	}
-      }
-    }
-    delete[] serverType;
+void RTSPClient::connectionHandler1() {
+  // Restore normal handling on our sockets:
+  envir().taskScheduler().disableBackgroundHandling(fOutputSocketNum);
+  envir().taskScheduler().setBackgroundHandling(fInputSocketNum, SOCKET_READABLE,
+						(TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler, this);
+
+  // Move all requests awaiting connection into a new, temporary queue, to clear "fRequestsAwaitingConnection"
+  // (so that "sendRequest()" doesn't get confused by "fRequestsAwaitingConnection" being nonempty, and enqueue them all over again).
+  RequestQueue tmpRequestQueue;
+  RequestRecord* request;
+  while ((request = fRequestsAwaitingConnection.dequeue()) != NULL) {
+    tmpRequestQueue.enqueue(request);
+  }
 
-    // We're now at the end of the response header lines
-    if (lineStart == NULL) {
-      envir().setResultMsg("no content following header lines: ",
-                            fResponseBuffer);
+  // Find out whether the connection succeeded or failed:
+  do {
+    int err = 0;
+    SOCKLEN_T len = sizeof err;
+    if (getsockopt(fInputSocketNum, SOL_SOCKET, SO_ERROR, (char*)&err, &len) < 0 || err != 0) {
+      envir().setResultErrMsg("Connection to server failed: ", err);
+      if (fVerbosityLevel >= 1) envir() << "..." << envir().getResultMsg() << "\n";
       break;
     }
 
-    // Use the remaining data as the parameter data, but first, check
-    // the "Content-length:" header (if any) that we saw.  We may need to
-    // read more data, or we may have extraneous data in the buffer.
-    char* bodyStart = nextLineStart;
-    if (contentLength >= 0) {
-      // We saw a "Content-length:" header
-      unsigned numBodyBytes = &firstLine[bytesRead] - bodyStart;
-      if (contentLength > (int)numBodyBytes) {
-	// We need to read more data.  First, make sure we have enough
-	// space for it:
-	unsigned numExtraBytesNeeded = contentLength - numBodyBytes;
-	unsigned remainingBufferSize
-	  = fResponseBufferSize - (bytesRead + (firstLine - fResponseBuffer));
-	if (numExtraBytesNeeded > remainingBufferSize) {
-	  char tmpBuf[200];
-	  sprintf(tmpBuf, "Read buffer size (%d) is too small for \"Content-length:\" %d (need a buffer size of >= %d bytes\n",
-		  fResponseBufferSize, contentLength,
-		  fResponseBufferSize + numExtraBytesNeeded - remainingBufferSize);
-	  envir().setResultMsg(tmpBuf);
-	  break;
-	}
+    // The connection succeeded.  If the connection came about from an attempt to set up RTSP-over-HTTP, finish this now:
+    if (fVerbosityLevel >= 1) envir() << "...remote connection opened\n";
+    if (fHTTPTunnelingConnectionIsPending && !setupHTTPTunneling2()) break;
 
-	// Keep reading more data until we have enough:
-	if (fVerbosityLevel >= 1) {
-	  envir() << "Need to read " << numExtraBytesNeeded
-		  << " extra bytes\n";
-	}
-	while (numExtraBytesNeeded > 0) {
-	  struct sockaddr_in fromAddress;
-	  char* ptr = &firstLine[bytesRead];
-	  int bytesRead2 = readSocket(envir(), fInputSocketNum, (unsigned char*)ptr,
-				      numExtraBytesNeeded, fromAddress);
-	  if (bytesRead2 <= 0) break;
-	  ptr[bytesRead2] = '\0';
-	  if (fVerbosityLevel >= 1) {
-	    envir() << "Read " << bytesRead2 << " extra bytes: "
-		    << ptr << "\n";
-	  }
-
-	  bytesRead += bytesRead2;
-	  numExtraBytesNeeded -= bytesRead2;
-	}
-	if (numExtraBytesNeeded > 0) break; // one of the reads failed
-      }
+    // Resume sending all pending requests:
+    while ((request = tmpRequestQueue.dequeue()) != NULL) {
+      sendRequest(request);
     }
-
-    if (haveParameterName
-	&& !parseGetParameterHeader(bodyStart, parameterName, parameterValue)) break;
-
-    delete[] cmd;
-    return True;
+    return;
   } while (0);
 
-  delete[] cmd;
-  return False;
+  // An error occurred.  Tell all pending requests about the error:
+  while ((request = tmpRequestQueue.dequeue()) != NULL) {
+    handleRequestError(request);
+    delete request;
+  }
+  resetTCPSockets();
 }
 
-Boolean RTSPClient::teardownMediaSession(MediaSession& session) {
-  char* cmd = NULL;
-  do {
-    // First, make sure that we have a RTSP session in progreee
-    if (fLastSessionId == NULL) {
-      envir().setResultMsg(NoSessionErr);
-      break;
-    }
-
-    // Send the TEARDOWN command:
-
-    // First, construct an authenticator string:
-    char* authenticatorStr
-      = createAuthenticatorString(&fCurrentAuthenticator,
-				  "TEARDOWN", fBaseURL);
-
-    char const* sessURL = sessionURL(session);
-    char const* const cmdFmt =
-      "TEARDOWN %s RTSP/1.0\r\n"
-      "CSeq: %d\r\n"
-      "Session: %s\r\n"
-      "%s"
-      "%s"
-      "\r\n";
-
-    unsigned cmdSize = strlen(cmdFmt)
-      + strlen(sessURL)
-      + 20 /* max int len */
-      + strlen(fLastSessionId)
-      + strlen(authenticatorStr)
-      + fUserAgentHeaderStrSize;
-    cmd = new char[cmdSize];
-    sprintf(cmd, cmdFmt,
-	    sessURL,
-	    ++fCSeq,
-	    fLastSessionId,
-	    authenticatorStr,
-	    fUserAgentHeaderStr);
-    delete[] authenticatorStr;
+void RTSPClient::incomingDataHandler(void* instance, int /*mask*/) {
+  RTSPClient* client = (RTSPClient*)instance;
+  client->incomingDataHandler1();
+}
 
-    if (!sendRequest(cmd, "TEARDOWN")) break;
+void RTSPClient::incomingDataHandler1() {
+  struct sockaddr_in dummy; // 'from' address - not used
 
-    if (fTCPStreamIdCount == 0) { // When TCP streaming, don't look for a response
-      // Get the response from the server:
-      unsigned bytesRead; unsigned responseCode;
-      char* firstLine; char* nextLineStart;
-      getResponse("TEARDOWN", bytesRead, responseCode, firstLine, nextLineStart); // ignore the response; from our POV, we're done
+  int bytesRead = readSocket(envir(), fInputSocketNum, (unsigned char*)&fResponseBuffer[fResponseBytesAlreadySeen], fResponseBufferBytesLeft, dummy);
+  handleResponseBytes(bytesRead);
+}
 
-      // Run through each subsession, deleting its "sessionId":
-      MediaSubsessionIterator iter(session);
-      MediaSubsession* subsession;
-      while ((subsession = iter.next()) != NULL) {
-	delete[] (char*)subsession->sessionId;
-	subsession->sessionId = NULL;
+static char* getLine(char* startOfLine) {
+  // returns the start of the next line, or NULL if none.  Note that this modifies the input string to add '\0' characters.
+  for (char* ptr = startOfLine; *ptr != '\0'; ++ptr) {
+    // Check for the end of line: \r\n (but also accept \r or \n by itself):
+    if (*ptr == '\r' || *ptr == '\n') {
+      // We found the end of the line
+      if (*ptr == '\r') {
+	*ptr++ = '\0';
+	if (*ptr == '\n') ++ptr;
+      } else {
+        *ptr++ = '\0';
       }
-
-      delete[] fLastSessionId; fLastSessionId = NULL;
-      // we're done with this session
+      return ptr;
     }
+  }
 
-    delete[] cmd;
-    return True;
-  } while (0);
-
-  delete[] cmd;
-  return False;
+  return NULL;
 }
 
-Boolean RTSPClient::teardownMediaSubsession(MediaSubsession& subsession) {
-  char* cmd = NULL;
+void RTSPClient::handleResponseBytes(int newBytesRead) {
   do {
-    // First, make sure that we have a RTSP session in progreee
-    if (subsession.sessionId == NULL) {
-      envir().setResultMsg(NoSessionErr);
-      break;
-    }
-
-    // Send the TEARDOWN command:
+    if (newBytesRead > 0 && (unsigned)newBytesRead < fResponseBufferBytesLeft) break; // data was read OK; process it below
 
-    // First, construct an authenticator string:
-    char* authenticatorStr
-      = createAuthenticatorString(&fCurrentAuthenticator,
-				  "TEARDOWN", fBaseURL);
-
-    char const* const cmdFmt =
-      "TEARDOWN %s%s%s RTSP/1.0\r\n"
-      "CSeq: %d\r\n"
-      "Session: %s\r\n"
-      "%s"
-      "%s"
-      "\r\n";
-
-    char const *prefix, *separator, *suffix;
-    constructSubsessionURL(subsession, prefix, separator, suffix);
-
-    unsigned cmdSize = strlen(cmdFmt)
-      + strlen(prefix) + strlen(separator) + strlen(suffix)
-      + 20 /* max int len */
-      + strlen(subsession.sessionId)
-      + strlen(authenticatorStr)
-      + fUserAgentHeaderStrSize;
-    cmd = new char[cmdSize];
-    sprintf(cmd, cmdFmt,
-	    prefix, separator, suffix,
-	    ++fCSeq,
-	    subsession.sessionId,
-	    authenticatorStr,
-	    fUserAgentHeaderStr);
-    delete[] authenticatorStr;
+    if ((unsigned)newBytesRead >= fResponseBufferBytesLeft) {
+      // We filled up our response buffer.  Treat this as an error (for the first response handler):
+      envir().setResultMsg("RTSP response was truncated. Increase \"RTSPClient::responseBufferSize\"");
+    }
 
-    if (!sendRequest(cmd, "TEARDOWN")) break;
+    // An error occurred while reading our TCP socket.  Call all pending response handlers, indicating this error:
+    RequestRecord* request;
+    while ((request = fRequestsAwaitingResponse.dequeue()) != NULL) {
+      handleRequestError(request);
+      delete request;
 
-    if (fTCPStreamIdCount == 0) { // When TCP streaming, don't look for a response
-      // Get the response from the server:
-      unsigned bytesRead; unsigned responseCode;
-      char* firstLine; char* nextLineStart;
-      getResponse("TEARDOWN", bytesRead, responseCode, firstLine, nextLineStart); // ignore the response; from our POV, we're done
+      if (newBytesRead > 0) break; // The "RTSP response was truncated" error is applied to the first response handler only
     }
 
-    delete[] (char*)subsession.sessionId;
-    subsession.sessionId = NULL;
-    // we're done with this session
-
-    delete[] cmd;
-    return True;
+    if (newBytesRead <= 0) resetTCPSockets();
+    resetResponseBuffer();
+    return;    
   } while (0);
 
-  delete[] cmd;
-  return False;
-}
+  fResponseBufferBytesLeft -= newBytesRead;
+  fResponseBytesAlreadySeen += newBytesRead;
+  fResponseBuffer[fResponseBytesAlreadySeen] = '\0';
+  if (fVerbosityLevel >= 1 && newBytesRead > 1) envir() << "Received " << newBytesRead << " new bytes of response data.\n";
+  
+  // Data was read OK.  Look through the data that we've read so far, to see if it contains <CR><LF><CR><LF>.
+  // (If not, wait for more data to arrive.)
+  Boolean endOfHeaders = False;
+  if (fResponseBytesAlreadySeen > 3) {
+    char const* const ptrEnd = &fResponseBuffer[fResponseBytesAlreadySeen-3];
+    char const* ptr = fResponseBuffer;
+    while (ptr < ptrEnd) {
+      if (*ptr++ == '\r' && *ptr++ == '\n' && *ptr++ == '\r' && *ptr++ == '\n') {
+	// This is it
+        endOfHeaders = True;
+        break;
+      }
+    }
+  }
 
-Boolean RTSPClient
-::openConnectionFromURL(char const* url, Authenticator* authenticator, int timeout) {
+  if (!endOfHeaders) return; // subsequent reads will be needed to get the complete response
+
+  // Now that we have the complete response headers (ending with <CR><LF><CR><LF>), parse them to get the response code, CSeq,
+  // and various other header parameters.  To do this, we first make a copy of the received header data, because we'll be modifying
+  // it by adding '\0' bytes.
+  char* headerDataCopy;
+  unsigned responseCode = 200;
+  char const* responseStr = NULL;
+  Boolean responseIsHTTP = False;
+  RequestRecord* foundRequest = NULL;
+  char const* sessionParamsStr = NULL;
+  char const* transportParamsStr = NULL;
+  char const* scaleParamsStr = NULL;
+  char const* rangeParamsStr = NULL;
+  char const* rtpInfoParamsStr = NULL;
+  char const* wwwAuthenticateParamsStr = NULL;
+  char const* publicParamsStr = NULL;
+  char* bodyStart = NULL;
+  unsigned numBodyBytes = 0;
+  Boolean responseSuccess = False; // by default
   do {
-    // Set this as our base URL:
-    delete[] fBaseURL; fBaseURL = strDup(url); if (fBaseURL == NULL) break;
+    headerDataCopy = new char[responseBufferSize];
+    strncpy(headerDataCopy, fResponseBuffer, fResponseBytesAlreadySeen);
+    headerDataCopy[fResponseBytesAlreadySeen] = '\0';
 
-    // Begin by parsing the URL:
+    char* lineStart = headerDataCopy;
+    char* nextLineStart = getLine(lineStart);
+    if (!parseResponseCode(lineStart, responseCode, responseStr, responseIsHTTP)) {
+      // This does not appear to be a RTSP response; perhaps it's a RTSP request instead?
+      handleIncomingRequest();
+      break; // we're done with this data
+    }
 
-    NetAddress destAddress;
-    portNumBits urlPortNum;
-    char const* urlSuffix;
-    if (!parseRTSPURL(envir(), url, destAddress, urlPortNum, &urlSuffix)) break;
-    portNumBits destPortNum
-      = fTunnelOverHTTPPortNum == 0 ? urlPortNum : fTunnelOverHTTPPortNum;
+    // Scan through the headers, handling the ones that we're interested in:
+    Boolean reachedEndOfHeaders;
+    unsigned cseq = 0;
+    unsigned contentLength = 0;
 
-    if (fInputSocketNum < 0) {
-      // We don't yet have a TCP socket.  Set one up (blocking) now:
-      fInputSocketNum = fOutputSocketNum
-	= setupStreamSocket(envir(), 0, False /* =>blocking */);
-      if (fInputSocketNum < 0) break;
-      
-      // Connect to the remote endpoint:
-      fServerAddress = *(unsigned*)(destAddress.data());
-      MAKE_SOCKADDR_IN(remoteName, fServerAddress, htons(destPortNum));
-      fd_set set;
-      FD_ZERO(&set);
-      timeval tvout = {0,0};
-      
-      // If we were supplied with a timeout, make our socket temporarily non-blocking
-      if (timeout > 0) {
-	FD_SET((unsigned)fInputSocketNum, &set);
-	tvout.tv_sec = timeout;
-	tvout.tv_usec = 0;
-	makeSocketNonBlocking(fInputSocketNum);
-      }
-      if (connect(fInputSocketNum, (struct sockaddr*) &remoteName, sizeof remoteName) != 0) {
-	if (envir().getErrno() != EINPROGRESS && envir().getErrno() != EWOULDBLOCK) {
-	  envir().setResultErrMsg("connect() failed: ");
+    while (1) {
+      reachedEndOfHeaders = True; // by default; may get changed below
+      lineStart = nextLineStart;
+      if (lineStart == NULL) break;
+
+      nextLineStart = getLine(lineStart);
+      if (lineStart[0] == '\0') break; // this is a blank line
+      reachedEndOfHeaders = False;
+
+      char const* headerParamsStr; 
+      if (checkForHeader(lineStart, "CSeq:", 5, headerParamsStr)) {
+        if (sscanf(headerParamsStr, "%u", &cseq) != 1 || cseq <= 0) {
+	  envir().setResultMsg("Bad \"CSeq:\" header: \"", lineStart, "\"");
 	  break;
 	}
-	if (timeout > 0 && (select(fInputSocketNum + 1, NULL, &set, NULL, &tvout) <= 0)) {
-	  envir().setResultErrMsg("select/connect() failed: ");
+        // Find the handler function for "cseq":
+	RequestRecord* request;
+	while ((request = fRequestsAwaitingResponse.dequeue()) != NULL) {
+          if (request->cseq() < cseq) { // assumes that the CSeq counter will never wrap around
+            // We never received (and will never receive) a response for this handler, so delete it:
+            delete request;
+          } else if (request->cseq() == cseq) {
+            // This is the handler that we want. Remove its record, but remember it, so that we can later call its handler:
+            foundRequest = request;
+            break;
+          } else { // request->cseq() > cseq
+            // No handler was registered for this response, so ignore it.
+            break;
+          }
+        }
+      } else if (checkForHeader(lineStart, "Content-Length:", 15, headerParamsStr)) {
+        if (sscanf(headerParamsStr, "%u", &contentLength) != 1) {
+	  envir().setResultMsg("Bad \"Content-Length:\" header: \"", lineStart, "\"");
 	  break;
 	}
+      } else if (checkForHeader(lineStart, "Content-Base:", 13, headerParamsStr)) {
+        setBaseURL(headerParamsStr);
+      } else if (checkForHeader(lineStart, "Session:", 8, sessionParamsStr)) {
+      } else if (checkForHeader(lineStart, "Transport:", 10, transportParamsStr)) {
+      } else if (checkForHeader(lineStart, "Scale:", 6, scaleParamsStr)) {
+      } else if (checkForHeader(lineStart, "Range:", 6, rangeParamsStr)) {
+      } else if (checkForHeader(lineStart, "RTP-Info:", 9, rtpInfoParamsStr)) {
+      } else if (checkForHeader(lineStart, "WWW-Authenticate:", 17, headerParamsStr)) {
+	// If we've already seen a "WWW-Authenticate:" header, then we replace it with this new one only if
+	// the new one specifies "Digest" authentication:
+	if (wwwAuthenticateParamsStr == NULL || _strncasecmp(headerParamsStr, "Digest", 6) == 0) {
+	  wwwAuthenticateParamsStr = headerParamsStr;
+	}
+      } else if (checkForHeader(lineStart, "Public:", 7, publicParamsStr)) {
+      } else if (checkForHeader(lineStart, "Allow:", 6, publicParamsStr)) {
+	// Note: we accept "Allow:" instead of "Public:", so that "OPTIONS" requests made to HTTP servers will work.
+      } else if (checkForHeader(lineStart, "Location:", 9, headerParamsStr)) {
+        setBaseURL(headerParamsStr);
       }
-      // If we set our socket to non-blocking, put it back in blocking mode now.
-      if (timeout > 0) {
-	makeSocketBlocking(fInputSocketNum);
+      // For now, omit parsing the "Server:" header (unless someone convinces us that we still need to treat Windows Media Server especially
+    }
+    if (!reachedEndOfHeaders) break; // an error occurred
+
+    if (foundRequest == NULL && responseIsHTTP) {
+      // Hack: HTTP responses don't have a "CSeq:" header, so if we got a HTTP response, assume it's for our most recent request:
+      foundRequest = fRequestsAwaitingResponse.dequeue();
+    }
+
+    // If we saw a "Content-Length:" header, then make sure that we have the amount of data that it specified:
+    unsigned bodyOffset = nextLineStart - headerDataCopy;
+    bodyStart = &fResponseBuffer[bodyOffset];
+    numBodyBytes = fResponseBytesAlreadySeen - bodyOffset;
+    if (contentLength > numBodyBytes) {
+      // We need to read more data.  First, make sure we have enough space for it:
+      unsigned numExtraBytesNeeded = contentLength - numBodyBytes;
+      unsigned remainingBufferSize = responseBufferSize - fResponseBytesAlreadySeen;
+      if (numExtraBytesNeeded > remainingBufferSize) {
+        char tmpBuf[200];
+	sprintf(tmpBuf, "Response buffer size (%d) is too small for \"Content-length:\" %d (need a buffer size of >= %d bytes\n",
+                responseBufferSize, contentLength, fResponseBytesAlreadySeen + numExtraBytesNeeded);
+	envir().setResultMsg(tmpBuf);
+        break;
       }
-      
-      if (fTunnelOverHTTPPortNum != 0 && !setupHTTPTunneling(urlSuffix, authenticator)) break;
-    }
-    
-    return True;
-  } while (0);
-  
-  fDescribeStatusCode = 1;
-  resetTCPSockets();
-  return False;
-}
-
-Boolean RTSPClient::parseRTSPURL(UsageEnvironment& env, char const* url,
-				 NetAddress& address,
-				 portNumBits& portNum,
-				 char const** urlSuffix) {
-  do {
-    // Parse the URL as "rtsp://<address>:<port>/<etc>"
-    // (with ":<port>" and "/<etc>" optional)
-    // Also, skip over any "<username>[:<password>]@" preceding <address>
-    char const* prefix = "rtsp://";
-    unsigned const prefixLength = 7;
-    if (_strncasecmp(url, prefix, prefixLength) != 0) {
-      env.setResultMsg("URL is not of the form \"", prefix, "\"");
-      break;
-    }
 
-    unsigned const parseBufferSize = 100;
-    char parseBuffer[parseBufferSize];
-    char const* from = &url[prefixLength];
-
-    // Skip over any "<username>[:<password>]@"
-    // (Note that this code fails if <password> contains '@' or '/', but
-    // given that these characters can also appear in <etc>, there seems to
-    // be no way of unambiguously parsing that situation.)
-    char const* from1 = from;
-    while (*from1 != '\0' && *from1 != '/') {
-      if (*from1 == '@') {
-	from = ++from1;
-	break;
+      if (fVerbosityLevel >= 1) {
+        envir() << "Have received " << fResponseBytesAlreadySeen << " total bytes of a "
+                << (foundRequest != NULL ? foundRequest->commandName() : "(unknown)")
+                << " RTSP response; awaiting " << numExtraBytesNeeded << " bytes more.\n";
       }
-      ++from1;
+      delete[] headerDataCopy;
+      if (foundRequest != NULL) fRequestsAwaitingResponse.putAtHead(foundRequest); // put back our request record; we need it again
+      return; // We need to read more data
     }
 
-    char* to = &parseBuffer[0];
-    unsigned i;
-    for (i = 0; i < parseBufferSize; ++i) {
-      if (*from == '\0' || *from == ':' || *from == '/') {
-	// We've completed parsing the address
-	*to = '\0';
-	break;
+    // We now have a complete response (including all bytes specified by the "Content-Length:" header, if any).
+    if (fVerbosityLevel >= 1) {
+      envir() << "Received a complete "
+	      << (foundRequest != NULL ? foundRequest->commandName() : "(unknown)")
+	      << " response:\n" << fResponseBuffer << "\n";
+    }
+
+    if (foundRequest != NULL) {
+      Boolean needToResendCommand = False; // by default...
+      if (responseCode == 200) {
+	// Do special-case response handling for some commands:
+	if (strcmp(foundRequest->commandName(), "SETUP") == 0) {
+	  if (!handleSETUPResponse(*foundRequest->subsession(), sessionParamsStr, transportParamsStr, foundRequest->booleanFlags()&0x1)) break;
+	} else if (strcmp(foundRequest->commandName(), "PLAY") == 0) {
+	  if (!handlePLAYResponse(*foundRequest->session(), *foundRequest->subsession(), scaleParamsStr, rangeParamsStr, rtpInfoParamsStr)) break;
+	} else if (strcmp(foundRequest->commandName(), "TEARDOWN") == 0) {
+	  if (!handleTEARDOWNResponse(*foundRequest->session(), *foundRequest->subsession())) break;
+	} else if (strcmp(foundRequest->commandName(), "GET_PARAMETER") == 0) {
+	  if (!handleGET_PARAMETERResponse(foundRequest->contentStr(), bodyStart)) break;
+	}
+      } else if (responseCode == 401 && handleAuthenticationFailure(wwwAuthenticateParamsStr)) {
+	needToResendCommand = True;
+      } else if (responseCode == 301 || responseCode == 302) { // redirection
+	resetTCPSockets(); // because we need to connect somewhere else next
+	needToResendCommand = True;
       }
-      *to++ = *from++;
-    }
-    if (i == parseBufferSize) {
-      env.setResultMsg("URL is too long");
-      break;
-    }
 
-    NetAddressList addresses(parseBuffer);
-    if (addresses.numAddresses() == 0) {
-      env.setResultMsg("Failed to find network address for \"",
-		       parseBuffer, "\"");
-      break;
-    }
-    address = *(addresses.firstAddress());
-
-    portNum = 554; // default value
-    char nextChar = *from;
-    if (nextChar == ':') {
-      int portNumInt;
-      if (sscanf(++from, "%d", &portNumInt) != 1) {
-	env.setResultMsg("No port number follows ':'");
-	break;
+      if (needToResendCommand) {
+	resetResponseBuffer();
+	if (!resendCommand(foundRequest)) break;
+	delete[] headerDataCopy;
+	return; // without calling our response handler; the response to the resent command will do that
       }
-      if (portNumInt < 1 || portNumInt > 65535) {
-	env.setResultMsg("Bad port number");
-	break;
-      }
-      portNum = (portNumBits)portNumInt;
-      while (*from >= '0' && *from <= '9') ++from; // skip over port number
     }
 
-    // The remainder of the URL is the suffix:
-    if (urlSuffix != NULL) *urlSuffix = from;
-
-    return True;
+    responseSuccess = True;
   } while (0);
 
-  return False;
-}
-
-Boolean RTSPClient::parseRTSPURLUsernamePassword(char const* url,
-						 char*& username,
-						 char*& password) {
-  username = password = NULL; // by default
-  do {
-    // Parse the URL as "rtsp://<username>[:<password>]@<whatever>"
-    char const* prefix = "rtsp://";
-    unsigned const prefixLength = 7;
-    if (_strncasecmp(url, prefix, prefixLength) != 0) break;
-
-    // Look for the ':' and '@':
-    unsigned usernameIndex = prefixLength;
-    unsigned colonIndex = 0, atIndex = 0;
-    for (unsigned i = usernameIndex; url[i] != '\0' && url[i] != '/'; ++i) {
-      if (url[i] == ':' && colonIndex == 0) {
-	colonIndex = i;
-      } else if (url[i] == '@') {
-	atIndex = i;
-	break; // we're done
+  // If we have a handler function for this response, call it:
+  resetResponseBuffer(); // in preparation for our next response.  Do this now, in case the handler function goes to the event loop.
+  if (foundRequest != NULL && foundRequest->handler() != NULL) {
+    int resultCode;
+    char* resultString;
+    if (responseSuccess) {
+      if (responseCode == 200) {
+        resultCode = 0;
+        resultString = numBodyBytes != 0 ? strDup(bodyStart) : strDup(publicParamsStr);
+          // Note: The "strDup(bodyStart)" call assumes that the body is encoded without interior '\0' bytes
+      } else {
+        resultCode = responseCode;
+        resultString = strDup(responseStr);
+        envir().setResultMsg(responseStr);
       }
-    }
-    if (atIndex == 0) break; // no '@' found
-
-    char* urlCopy = strDup(url);
-    urlCopy[atIndex] = '\0';
-    if (colonIndex > 0) {
-      urlCopy[colonIndex] = '\0';
-      password = strDup(&urlCopy[colonIndex+1]);
+      (*foundRequest->handler())(this, resultCode, resultString);
     } else {
-      password = strDup("");
+      // An error occurred parsing the response, so call the handler, indicating an error:
+      handleRequestError(foundRequest);
     }
-    username = strDup(&urlCopy[usernameIndex]);
-    delete[] urlCopy;
+  }
+  delete foundRequest;
+  delete[] headerDataCopy;
+}
 
-    return True;
-  } while (0);
 
-  return False;
+////////// RTSPClient::RequestRecord implementation //////////
+
+RTSPClient::RequestRecord::RequestRecord(unsigned cseq, char const* commandName, responseHandler* handler,
+					 MediaSession* session, MediaSubsession* subsession, u_int32_t booleanFlags,
+					 double start, double end, float scale, char const* contentStr)
+  : fNext(NULL), fCSeq(cseq), fCommandName(commandName), fSession(session), fSubsession(subsession), fBooleanFlags(booleanFlags),
+    fStart(start), fEnd(end), fScale(scale), fContentStr(strDup(contentStr)), fHandler(handler) {
 }
 
-char*
-RTSPClient::createAuthenticatorString(Authenticator const* authenticator,
-				      char const* cmd, char const* url) {
-  if (authenticator != NULL && authenticator->realm() != NULL
-      && authenticator->username() != NULL && authenticator->password() != NULL) {
-    // We've been provided a filled-in authenticator, so use it:
-    char* authenticatorStr;
-    if (authenticator->nonce() != NULL) { // Digest authentication
-      char const* const authFmt =
-	"Authorization: Digest username=\"%s\", realm=\"%s\", "
-	"nonce=\"%s\", uri=\"%s\", response=\"%s\"\r\n";
-      char const* response = authenticator->computeDigestResponse(cmd, url);
-      unsigned authBufSize = strlen(authFmt)
-	+ strlen(authenticator->username()) + strlen(authenticator->realm())
-	+ strlen(authenticator->nonce()) + strlen(url) + strlen(response);
-      authenticatorStr = new char[authBufSize];
-      sprintf(authenticatorStr, authFmt,
-	      authenticator->username(), authenticator->realm(),
-	      authenticator->nonce(), url, response);
-      authenticator->reclaimDigestResponse(response);
-    } else { // Basic authentication
-      char const* const authFmt = "Authorization: Basic %s\r\n";
+RTSPClient::RequestRecord::~RequestRecord() {
+  // Delete the rest of the list first:
+  delete fNext;
 
-      unsigned usernamePasswordLength = strlen(authenticator->username()) + 1 + strlen(authenticator->password());
-      char* usernamePassword = new char[usernamePasswordLength+1];
-      sprintf(usernamePassword, "%s:%s", authenticator->username(), authenticator->password());
+  delete[] fContentStr;
+}
 
-      char* response = base64Encode(usernamePassword, usernamePasswordLength);
-      unsigned const authBufSize = strlen(authFmt) + strlen(response) + 1;
-      authenticatorStr = new char[authBufSize];
-      sprintf(authenticatorStr, authFmt, response);
-      delete[] response; delete[] usernamePassword;
-    }
 
-    return authenticatorStr;
-  }
+////////// RTSPClient::RequestQueue implementation //////////
 
-  return strDup("");
+RTSPClient::RequestQueue::RequestQueue()
+  : fHead(NULL), fTail(NULL) {
 }
 
-void RTSPClient::checkForAuthenticationFailure(unsigned responseCode,
-					       char*& nextLineStart,
-					       Authenticator* authenticator) {
-  if (responseCode == 401 && authenticator != NULL) {
-    // We have an authentication failure, so fill in "authenticator"
-    // using the contents of a following "WWW-Authenticate:" line.
-    // (Once we compute a 'response' for "authenticator", it can be
-    //  used in a subsequent request - that will hopefully succeed.)
-    char* lineStart;
-    while (1) {
-      lineStart = nextLineStart;
-      if (lineStart == NULL) break;
-
-      nextLineStart = getLine(lineStart);
-      if (lineStart[0] == '\0') break; // this is a blank line
+RTSPClient::RequestQueue::~RequestQueue() {
+  delete fHead;
+}
 
-      char* realm = strDupSize(lineStart);
-      char* nonce = strDupSize(lineStart);
-      Boolean foundAuthenticateHeader = False;
-      if (sscanf(lineStart, "WWW-Authenticate: Digest realm=\"%[^\"]\", nonce=\"%[^\"]\"",
-		 realm, nonce) == 2) {
-	authenticator->setRealmAndNonce(realm, nonce);
-	foundAuthenticateHeader = True;
-      } else if (sscanf(lineStart, "WWW-Authenticate: Basic realm=\"%[^\"]\"",
-		 realm) == 1) {
-	authenticator->setRealmAndNonce(realm, NULL); // Basic authentication
-	foundAuthenticateHeader = True;
-      }
-      delete[] realm; delete[] nonce;
-      if (foundAuthenticateHeader) break;
-    }
+void RTSPClient::RequestQueue::enqueue(RequestRecord* request) {
+  if (fTail == NULL) {
+    fHead = request;
+  } else {
+    fTail->next() = request;
   }
+  fTail = request;
 }
 
-Boolean RTSPClient::sendRequest(char const* requestString, char const* tag,
-				Boolean base64EncodeIfOverHTTP) {
-  if (fVerbosityLevel >= 1) {
-    envir() << "Sending request: " << requestString << "\n";
+RTSPClient::RequestRecord* RTSPClient::RequestQueue::dequeue() {
+  RequestRecord* request = fHead;
+  if (fHead == fTail) {
+    fHead = NULL;
+    fTail = NULL;
+  } else {
+    fHead = fHead->next();
   }
+  if (request != NULL) request->next() = NULL;
+  return request;
+}
 
-  char* newRequestString = NULL;
-  if (fTunnelOverHTTPPortNum != 0 && base64EncodeIfOverHTTP) {
-    requestString = newRequestString = base64Encode(requestString, strlen(requestString));
-    if (fVerbosityLevel >= 1) {
-      envir() << "\tThe request was base-64 encoded to: " << requestString << "\n\n";
-    }
+void RTSPClient::RequestQueue::putAtHead(RequestRecord* request) {
+  request->next() = fHead;
+  fHead = request;
+  if (fTail == NULL) {
+    fTail = request;
   }
+}
 
-  Boolean result
-    = send(fOutputSocketNum, requestString, strlen(requestString), 0) >= 0;
-  delete[] newRequestString;
-
-  if (!result) {
-    if (tag == NULL) tag = "";
-    char const* errFmt = "%s send() failed: ";
-    unsigned const errLength = strlen(errFmt) + strlen(tag);
-    char* err = new char[errLength];
-    sprintf(err, errFmt, tag);
-    envir().setResultErrMsg(err);
-    delete[] err;
+RTSPClient::RequestRecord* RTSPClient::RequestQueue::findByCSeq(unsigned cseq) {
+  RequestRecord* request;
+  for (request = fHead; request != NULL; request = request->next()) {
+    if (request->cseq() == cseq) return request;
   }
-  return result;
+  return NULL;
 }
 
-Boolean RTSPClient::getResponse(char const* tag,
-				unsigned& bytesRead, unsigned& responseCode,
-				char*& firstLine, char*& nextLineStart,
-				Boolean checkFor200Response) {
-  do {
-    char* readBuf = fResponseBuffer;
-    bytesRead = getResponse1(readBuf, fResponseBufferSize);
-    if (bytesRead == 0) {
-      envir().setResultErrMsg("Failed to read response: ");
-      break;
-    }
-    if (fVerbosityLevel >= 1) {
-      envir() << "Received " << tag << " response: " << readBuf << "\n";
-    }
-
-    firstLine = readBuf;
-    nextLineStart = getLine(firstLine);
-    if (!parseResponseCode(firstLine, responseCode)) break;
 
-
-    if (responseCode != 200 && checkFor200Response) {
-      envir().setResultMsg(tag, ": cannot handle response: ", firstLine);
-      break;
-    }
-
-    return True;
-  } while (0);
-
-  // An error occurred:
-  return False;
+#ifdef RTSPCLIENT_SYNCHRONOUS_INTERFACE
+// Implementation of the old (synchronous) "RTSPClient" interface, using the new (asynchronous) interface:
+RTSPClient* RTSPClient::createNew(UsageEnvironment& env,
+				  int verbosityLevel,
+				  char const* applicationName,
+				  portNumBits tunnelOverHTTPPortNum) {
+  return new RTSPClient(env, NULL,
+			verbosityLevel, applicationName, tunnelOverHTTPPortNum);
 }
 
-unsigned RTSPClient::getResponse1(char*& responseBuffer,
-				  unsigned responseBufferSize) {
-  struct sockaddr_in fromAddress;
-
-  if (responseBufferSize == 0) return 0; // just in case...
-  responseBuffer[0] = '\0'; // ditto
-
-  // Begin by reading and checking the first byte of the response.
-  // If it's '$', then there's an interleaved RTP (or RTCP)-over-TCP
-  // packet here.  We need to read and discard it first.
-  Boolean success = False;
-  while (1) {
-    unsigned char firstByte;
-    struct timeval timeout;
-    timeout.tv_sec = 30; timeout.tv_usec = 0;
-    if (readSocket(envir(), fInputSocketNum, &firstByte, 1, fromAddress, &timeout)
-	!= 1) break;
-    if (firstByte != '$') {
-      // Normal case: This is the start of a regular response; use it:
-      responseBuffer[0] = firstByte;
-      success = True;
-      break;
-    } else {
-      // This is an interleaved packet; read and discard it:
-      unsigned char streamChannelId;
-      if (readSocket(envir(), fInputSocketNum, &streamChannelId, 1, fromAddress)
-	  != 1) break;
-
-      unsigned short size;
-      if (readSocketExact(envir(), fInputSocketNum, (unsigned char*)&size, 2,
-		     fromAddress) != 2) break;
-      size = ntohs(size);
-      if (fVerbosityLevel >= 1) {
-	envir() << "Discarding interleaved RTP or RTCP packet ("
-		<< size << " bytes, channel id "
-		<< streamChannelId << ")\n";
-      }
-
-      unsigned char* tmpBuffer = new unsigned char[size];
-      if (tmpBuffer == NULL) break;
-      unsigned bytesRead = 0;
-      unsigned bytesToRead = size;
-      int curBytesRead;
-      while ((curBytesRead = readSocket(envir(), fInputSocketNum,
-					&tmpBuffer[bytesRead], bytesToRead,
-					fromAddress)) > 0) {
-	bytesRead += curBytesRead;
-	if (bytesRead >= size) break;
-	bytesToRead -= curBytesRead;
-      }
-      delete[] tmpBuffer;
-      if (bytesRead != size) break;
-
-      success = True;
-    }
-  }
-  if (!success) return 0;
-
-  // Keep reading data from the socket until we see "\r\n\r\n" (except
-  // at the start), or until we fill up our buffer.
-  // Don't read any more than this.
-  char* p = responseBuffer;
-  Boolean haveSeenNonCRLF = False;
-  int bytesRead = 1; // because we've already read the first byte
-  while (bytesRead < (int)responseBufferSize) {
-    int bytesReadNow
-      = readSocket(envir(), fInputSocketNum,
-		   (unsigned char*)(responseBuffer+bytesRead),
-		   1, fromAddress);
-    if (bytesReadNow <= 0) {
-      envir().setResultMsg("RTSP response was truncated");
-      break;
-    }
-    bytesRead += bytesReadNow;
-
-    // Check whether we have "\r\n\r\n" (or "\r\r" or "\n\n"):
-    char* lastToCheck = responseBuffer+bytesRead-4;
-    if (lastToCheck < responseBuffer) continue;
-    for (; p <= lastToCheck; ++p) {
-      if (haveSeenNonCRLF) {
-	if ((*p == '\r' && *(p+1) == '\n' && *(p+2) == '\r' && *(p+3) == '\n')
-	    || (*(p+2) == '\r' && *(p+3) == '\r')
-	    || (*(p+2) == '\n' && *(p+3) == '\n')) {
-	  responseBuffer[bytesRead] = '\0';
-
-	  // Before returning, trim any \r or \n from the start:
-	  while (*responseBuffer == '\r' || *responseBuffer == '\n') {
-	    ++responseBuffer;
-	    --bytesRead;
-	  }
-	  return bytesRead;
-	}
-      } else {
-	if (*p != '\r' && *p != '\n') {
-	  haveSeenNonCRLF = True;
-	}
-      }
-    }
+char* RTSPClient::describeURL(char const* url, Authenticator* authenticator,
+			      Boolean allowKasennaProtocol, int timeout) {
+  // Sorry 'Kasenna', but the party's over.  You've had 6 years to make your servers compliant with the standard RTSP protocol.
+  // We're not going to support your non-standard hacked version of the protocol any more.  Starting now, the "allowKasennaProtocol"
+  // parameter is a noop (and eventually, when the synchronous interface goes away completely, then so will this parameter).
+
+  // First, check whether "url" contains a username:password to be used.  If so, handle this using "describeWithPassword()" instead:
+  char* username; char* password;
+  if (authenticator == NULL
+      && parseRTSPURLUsernamePassword(url, username, password)) {
+    char* result = describeWithPassword(url, username, password, allowKasennaProtocol, timeout);
+    delete[] username; delete[] password; // they were dynamically allocated
+    return result;
   }
 
-  envir().setResultMsg("We received a response not ending with <CR><LF><CR><LF>");
-  return 0;
-}
-
-Boolean RTSPClient::parseResponseCode(char const* line,
-				      unsigned& responseCode) {
-  if (sscanf(line, "%*s%u", &responseCode) != 1) {
-    envir().setResultMsg("no response code in line: \"", line, "\"");
-    return False;
+  setBaseURL(url);
+  fWatchVariableForSyncInterface = 0;
+  fTimeoutTask = NULL;  // by default, unless:
+  if (timeout > 0) {
+    // Schedule a task to be called when the specified timeout interval expires.
+    // Note that we do this *before* attempting to send the RTSP command, in case this attempt fails immediately, calling the
+    // command response handler - because we want the response handler to unschedule any pending timeout handler.
+    fTimeoutTask = envir().taskScheduler().scheduleDelayedTask(timeout*1000000, timeoutHandlerForSyncInterface, this);
   }
+  (void)sendDescribeCommand(responseHandlerForSyncInterface, authenticator);
 
-  return True;
+  // Now block (but handling events) until we get a response (or a timeout):
+  envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
+  if (fResultCode == 0) return fResultString; // success
+  delete[] fResultString;
+  return NULL;
 }
 
-Boolean RTSPClient::parseTransportResponse(char const* line,
-					   char*& serverAddressStr,
-					   portNumBits& serverPortNum,
-					   unsigned char& rtpChannelId,
-					   unsigned char& rtcpChannelId) {
-  // Initialize the return parameters to 'not found' values:
-  serverAddressStr = NULL;
-  serverPortNum = 0;
-  rtpChannelId = rtcpChannelId = 0xFF;
-
-  char* foundServerAddressStr = NULL;
-  Boolean foundServerPortNum = False;
-  Boolean foundChannelIds = False;
-  unsigned rtpCid, rtcpCid;
-  Boolean isMulticast = True; // by default
-  char* foundDestinationStr = NULL;
-  portNumBits multicastPortNumRTP, multicastPortNumRTCP;
-  Boolean foundMulticastPortNum = False;
+char* RTSPClient::describeWithPassword(char const* url,
+				       char const* username, char const* password,
+				       Boolean allowKasennaProtocol, int timeout) {
+  Authenticator authenticator;
+  authenticator.setUsernameAndPassword(username, password);
+  return describeURL(url, &authenticator, allowKasennaProtocol, timeout);
+}
 
-  // First, check for "Transport:"
-  if (_strncasecmp(line, "Transport: ", 11) != 0) return False;
-  line += 11;
+char* RTSPClient::sendOptionsCmd(char const* url,
+				 char* username, char* password,
+				 Authenticator* authenticator,
+				 int timeout) {
+  char* result = NULL;
+  Boolean haveAllocatedAuthenticator = False;
+  if (authenticator == NULL) {
+    // First, check whether "url" contains a username:password to be used
+    // (and no username,password pair was supplied separately):
+    if (username == NULL && password == NULL
+	&& parseRTSPURLUsernamePassword(url, username, password)) {
+      Authenticator newAuthenticator;
+      newAuthenticator.setUsernameAndPassword(username, password);
+      result = sendOptionsCmd(url, username, password, &newAuthenticator, timeout);
+      delete[] username; delete[] password; // they were dynamically allocated
+      return result;
+    } else if (username != NULL && password != NULL) {
+      // Use the separately supplied username and password:
+      authenticator = new Authenticator;
+      haveAllocatedAuthenticator = True;
+      authenticator->setUsernameAndPassword(username, password);
+      
+      result = sendOptionsCmd(url, username, password, authenticator, timeout);
+      if (result != NULL) return result; // We are already authorized
 
-  // Then, run through each of the fields, looking for ones we handle:
-  char const* fields = line;
-  char* field = strDupSize(fields);
-  while (sscanf(fields, "%[^;]", field) == 1) {
-    if (sscanf(field, "server_port=%hu", &serverPortNum) == 1) {
-      foundServerPortNum = True;
-    } else if (_strncasecmp(field, "source=", 7) == 0) {
-      delete[] foundServerAddressStr;
-      foundServerAddressStr = strDup(field+7);
-    } else if (sscanf(field, "interleaved=%u-%u", &rtpCid, &rtcpCid) == 2) {
-      rtpChannelId = (unsigned char)rtpCid;
-      rtcpChannelId = (unsigned char)rtcpCid;
-      foundChannelIds = True;
-    } else if (strcmp(field, "unicast") == 0) {
-      isMulticast = False;
-    } else if (_strncasecmp(field, "destination=", 12) == 0) {
-      delete[] foundDestinationStr;
-      foundDestinationStr = strDup(field+12);
-    } else if (sscanf(field, "port=%hu-%hu",
-		      &multicastPortNumRTP, &multicastPortNumRTCP) == 2) {
-      foundMulticastPortNum = True;
+      // The "realm" field should have been filled in:
+      if (authenticator->realm() == NULL) {
+	// We haven't been given enough information to try again, so fail:
+	return NULL;
+      }
     }
-
-    fields += strlen(field);
-    while (fields[0] == ';') ++fields; // skip over all leading ';' chars
-    if (fields[0] == '\0') break;
   }
-  delete[] field;
 
-  // If we're multicast, and have a "destination=" (multicast) address, then use this
-  // as the 'server' address (because some weird servers don't specify the multicast
-  // address earlier, in the "DESCRIBE" response's SDP:
-  if (isMulticast && foundDestinationStr != NULL && foundMulticastPortNum) {
-    delete[] foundServerAddressStr;
-    serverAddressStr = foundDestinationStr;
-    serverPortNum = multicastPortNumRTP;
-    return True;
-  }
-  delete[] foundDestinationStr;
-
-  if (foundServerPortNum || foundChannelIds) {
-    serverAddressStr = foundServerAddressStr;
-    return True;
+  setBaseURL(url);
+  fWatchVariableForSyncInterface = 0;
+  fTimeoutTask = NULL;  // by default, unless:
+  if (timeout > 0) {
+    // Schedule a task to be called when the specified timeout interval expires.
+    // Note that we do this *before* attempting to send the RTSP command, in case this attempt fails immediately, calling the
+    // command response handler - because we want the response handler to unschedule any pending timeout handler.
+    fTimeoutTask = envir().taskScheduler().scheduleDelayedTask(timeout*1000000, timeoutHandlerForSyncInterface, this);
   }
+  (void)sendOptionsCommand(responseHandlerForSyncInterface, authenticator);
 
-  delete[] foundServerAddressStr;
-  return False;
+  // Now block (but handling events) until we get a response (or a timeout):
+  envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
+  if (fResultCode == 0) return fResultString; // success
+  delete[] fResultString;
+  return NULL;
 }
 
-Boolean RTSPClient::parseRTPInfoHeader(char*& line, u_int16_t& seqNum, u_int32_t& timestamp) {
-  // At this point in the parsing, "line" should begin with either "RTP-Info: " (for the start of the header),
-  // or ",", indicating the RTP-Info parameter list for the 2nd-through-nth subsessions:
-  if (_strncasecmp(line, "RTP-Info: ", 10) == 0) {
-    line += 10;
-  } else if (line[0] == ',') {
-    ++line;
-  } else {
-    return False;
-  }
-
-  // "line" now consists of a ';'-separated list of parameters, ending with ',' or '\0'.
-  char* field = strDupSize(line);
-
-  while (sscanf(line, "%[^;,]", field) == 1) {
-    if (sscanf(field, "seq=%hu", &seqNum) == 1 ||
-	sscanf(field, "rtptime=%u", &timestamp) == 1) {
-    }
-
-    line += strlen(field);
-    if (line[0] == '\0' || line[0] == ',') break;
-    // ASSERT: line[0] == ';'
-    ++line; // skip over the ';'
+Boolean RTSPClient::announceSDPDescription(char const* url,
+					   char const* sdpDescription,
+					   Authenticator* authenticator,
+					   int timeout) {
+  setBaseURL(url);
+  fWatchVariableForSyncInterface = 0;
+  fTimeoutTask = NULL;  // by default, unless:
+  if (timeout > 0) {
+    // Schedule a task to be called when the specified timeout interval expires.
+    // Note that we do this *before* attempting to send the RTSP command, in case this attempt fails immediately, calling the
+    // command response handler - because we want the response handler to unschedule any pending timeout handler.
+    fTimeoutTask = envir().taskScheduler().scheduleDelayedTask(timeout*1000000, timeoutHandlerForSyncInterface, this);
   }
+  (void)sendAnnounceCommand(sdpDescription, responseHandlerForSyncInterface, authenticator);
 
-  delete[] field;
-  return True;
+  // Now block (but handling events) until we get a response (or a timeout):
+  envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
+  delete[] fResultString;
+  return fResultCode == 0;
 }
 
-Boolean RTSPClient::parseScaleHeader(char const* line, float& scale) {
-  if (_strncasecmp(line, "Scale: ", 7) != 0) return False;
-  line += 7;
+Boolean RTSPClient
+::announceWithPassword(char const* url, char const* sdpDescription,
+		       char const* username, char const* password, int timeout) {
+  Authenticator authenticator;
+  authenticator.setUsernameAndPassword(username, password);
+  return announceSDPDescription(url, sdpDescription, &authenticator, timeout);
+}
 
-  Locale l("C", LC_NUMERIC);
-  return sscanf(line, "%f", &scale) == 1;
+Boolean RTSPClient::setupMediaSubsession(MediaSubsession& subsession,
+					 Boolean streamOutgoing,
+					 Boolean streamUsingTCP,
+					 Boolean forceMulticastOnUnspecified) {
+  fWatchVariableForSyncInterface = 0;
+  fTimeoutTask = NULL;
+  (void)sendSetupCommand(subsession, responseHandlerForSyncInterface, streamOutgoing, streamUsingTCP, forceMulticastOnUnspecified);
+
+  // Now block (but handling events) until we get a response (or a timeout):
+  envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
+  delete[] fResultString;
+  return fResultCode == 0;
 }
 
-Boolean RTSPClient::parseGetParameterHeader(char const* line,
-                                            const char* param,
-                                            char*& value) {
-  if ((param != NULL && param[0] != '\0') &&
-      (line != NULL && line[0] != '\0')) {
-    int param_len = strlen(param);
-    int line_len = strlen(line);
+Boolean RTSPClient::playMediaSession(MediaSession& session,
+				     double start, double end, float scale) {
+  fWatchVariableForSyncInterface = 0;
+  fTimeoutTask = NULL;
+  (void)sendPlayCommand(session, responseHandlerForSyncInterface, start, end, scale);
+
+  // Now block (but handling events) until we get a response (or a timeout):
+  envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
+  delete[] fResultString;
+  return fResultCode == 0;
+}
 
-    if (_strncasecmp(line, param, param_len) != 0) {
-      if (fVerbosityLevel >= 1) {
-        envir() << "Parsing for \""<< param << "\" and didn't find it, return False\n";
-      }
-      return False;
-    }
+Boolean RTSPClient::playMediaSubsession(MediaSubsession& subsession,
+					double start, double end, float scale,
+					Boolean /*hackForDSS*/) {
+  // NOTE: The "hackForDSS" flag is no longer supported.  (However, we will consider resupporting it
+  // if we get reports that it is still needed.)
+  fWatchVariableForSyncInterface = 0;
+  fTimeoutTask = NULL;
+  (void)sendPlayCommand(subsession, responseHandlerForSyncInterface, start, end, scale);
+
+  // Now block (but handling events) until we get a response (or a timeout):
+  envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
+  delete[] fResultString;
+  return fResultCode == 0;
+}
 
-    // Strip \r\n from the end if it's there.
-    if (line[line_len-2] == '\r' && line[line_len-1] == '\n') {
-      line_len -= 2;
-    }
+Boolean RTSPClient::pauseMediaSession(MediaSession& session) {
+  fWatchVariableForSyncInterface = 0;
+  fTimeoutTask = NULL;
+  (void)sendPauseCommand(session, responseHandlerForSyncInterface);
+
+  // Now block (but handling events) until we get a response (or a timeout):
+  envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
+  delete[] fResultString;
+  return fResultCode == 0;
+}
 
-    // Look for ": " appended to our requested parameter
-    if (line[param_len] == ':' && line[param_len+1] == ' ') {
-      // But make sure ": " wasn't in our actual serach param before adjusting
-      if (param[param_len-2] != ':' && param[param_len-1] != ' ') {
-        if (fVerbosityLevel >= 1) {
-          envir() << "Found \": \" appended to parameter\n";
-        }
-        param_len += 2;
-      }
-    }
+Boolean RTSPClient::pauseMediaSubsession(MediaSubsession& subsession) {
+  fWatchVariableForSyncInterface = 0;
+  fTimeoutTask = NULL;
+  (void)sendPauseCommand(subsession, responseHandlerForSyncInterface);
+
+  // Now block (but handling events) until we get a response (or a timeout):
+  envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
+  delete[] fResultString;
+  return fResultCode == 0;
+}
 
-    // Get the string we want out of the line:
-    value = strDup(line+param_len);
-    return True;
-  }
-  return False;
+Boolean RTSPClient::recordMediaSubsession(MediaSubsession& subsession) {
+  fWatchVariableForSyncInterface = 0;
+  fTimeoutTask = NULL;
+  (void)sendRecordCommand(subsession, responseHandlerForSyncInterface);
+
+  // Now block (but handling events) until we get a response (or a timeout):
+  envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
+  delete[] fResultString;
+  return fResultCode == 0;
 }
 
-Boolean RTSPClient::setupHTTPTunneling(char const* urlSuffix,
-				       Authenticator* authenticator) {
-  // Set up RTSP-over-HTTP tunneling, as described in
-  //     http://developer.apple.com/documentation/QuickTime/QTSS/Concepts/chapter_2_section_14.html
-  if (fVerbosityLevel >= 1) {
-    envir() << "Requesting RTSP-over-HTTP tunneling (on port "
-	    << fTunnelOverHTTPPortNum << ")\n\n";
-  }
-  if (urlSuffix == NULL || urlSuffix[0] == '\0') urlSuffix = "/";
-  char* cmd = NULL;
+Boolean RTSPClient::setMediaSessionParameter(MediaSession& session,
+					     char const* parameterName,
+					     char const* parameterValue) {
+  fWatchVariableForSyncInterface = 0;
+  fTimeoutTask = NULL;
+  (void)sendSetParameterCommand(session, responseHandlerForSyncInterface, parameterName, parameterValue);
+
+  // Now block (but handling events) until we get a response (or a timeout):
+  envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
+  delete[] fResultString;
+  return fResultCode == 0;
+}
 
-  do {
-    // Create a 'session cookie' string, using MD5:
-    struct {
-      struct timeval timestamp;
-      unsigned counter;
-    } seedData;
-    gettimeofday(&seedData.timestamp, NULL);
-    static unsigned counter = 0;
-    seedData.counter = ++counter;
-    char sessionCookie[33];
-    our_MD5Data((unsigned char*)(&seedData), sizeof seedData, sessionCookie);
-    // DSS seems to require that the 'session cookie' string be 22 bytes long:
-    sessionCookie[23] = '\0';
-
-    // Construct an authenticator string:
-    char* authenticatorStr
-      = createAuthenticatorString(authenticator, "GET", urlSuffix);
-
-    // Begin by sending a HTTP "GET", to set up the server->client link:
-    char const* const getCmdFmt =
-      "GET %s HTTP/1.0\r\n"
-      "%s"
-      "%s"
-      "x-sessioncookie: %s\r\n"
-      "Accept: application/x-rtsp-tunnelled\r\n"
-      "Pragma: no-cache\r\n"
-      "Cache-Control: no-cache\r\n"
-      "\r\n";
-    unsigned cmdSize = strlen(getCmdFmt)
-      + strlen(urlSuffix)
-      + strlen(authenticatorStr)
-      + fUserAgentHeaderStrSize
-      + strlen(sessionCookie);
-    cmd = new char[cmdSize];
-    sprintf(cmd, getCmdFmt,
-	    urlSuffix,
-	    authenticatorStr,
-	    fUserAgentHeaderStr,
-	    sessionCookie);
-    delete[] authenticatorStr;
-    if (!sendRequest(cmd, "HTTP GET", False/*don't base64-encode*/)) break;
-
-    // Get the response from the server:
-    unsigned bytesRead; unsigned responseCode;
-    char* firstLine; char* nextLineStart;
-    if (!getResponse("HTTP GET", bytesRead, responseCode, firstLine, nextLineStart,
-		     False /*don't check for response code 200*/)) break;
-    if (responseCode != 200) {
-      checkForAuthenticationFailure(responseCode, nextLineStart, authenticator);
-      envir().setResultMsg("cannot handle HTTP GET response: ", firstLine);
-      break;
-    }
+Boolean RTSPClient::getMediaSessionParameter(MediaSession& session,
+					     char const* parameterName,
+					     char*& parameterValue) {
+  fWatchVariableForSyncInterface = 0;
+  fTimeoutTask = NULL;
+  (void)sendGetParameterCommand(session, responseHandlerForSyncInterface, parameterName);
+
+  // Now block (but handling events) until we get a response (or a timeout):
+  envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
+  parameterValue = fResultString;
+  return fResultCode == 0;
+}
 
-    // Next, set up a second TCP connection (to the same server & port as before)
-    // for the HTTP-tunneled client->server link.  All future output will be to
-    // this socket.
-    fOutputSocketNum = setupStreamSocket(envir(), 0, False /* =>blocking */);
-    if (fOutputSocketNum < 0) break;
+Boolean RTSPClient::teardownMediaSession(MediaSession& session) {
+  fWatchVariableForSyncInterface = 0;
+  fTimeoutTask = NULL;
+  (void)sendTeardownCommand(session, responseHandlerForSyncInterface);
+
+  // Now block (but handling events) until we get a response (or a timeout):
+  envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
+  delete[] fResultString;
+  return fResultCode == 0;
+}
 
-    // Connect to the remote endpoint:
-    MAKE_SOCKADDR_IN(remoteName, fServerAddress, htons(fTunnelOverHTTPPortNum));
-    if (connect(fOutputSocketNum,
-		(struct sockaddr*)&remoteName, sizeof remoteName) != 0) {
-      envir().setResultErrMsg("connect() failed: ");
-      break;
-    }
+Boolean RTSPClient::teardownMediaSubsession(MediaSubsession& subsession) {
+  fWatchVariableForSyncInterface = 0;
+  fTimeoutTask = NULL;
+  (void)sendTeardownCommand(subsession, responseHandlerForSyncInterface);
+
+  // Now block (but handling events) until we get a response (or a timeout):
+  envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
+  delete[] fResultString;
+  return fResultCode == 0;
+}
 
-    // Then, send a HTTP "POST", to set up the client->server link:
-    authenticatorStr = createAuthenticatorString(authenticator, "POST", urlSuffix);
-    char const* const postCmdFmt =
-      "POST %s HTTP/1.0\r\n"
-      "%s"
-      "%s"
-      "x-sessioncookie: %s\r\n"
-      "Content-Type: application/x-rtsp-tunnelled\r\n"
-      "Pragma: no-cache\r\n"
-      "Cache-Control: no-cache\r\n"
-      "Content-Length: 32767\r\n"
-      "Expires: Sun, 9 Jan 1972 00:00:00 GMT\r\n"
-      "\r\n";
-    cmdSize = strlen(postCmdFmt)
-      + strlen(urlSuffix)
-      + strlen(authenticatorStr)
-      + fUserAgentHeaderStrSize
-      + strlen(sessionCookie);
-    delete[] cmd; cmd = new char[cmdSize];
-    sprintf(cmd, postCmdFmt,
-	    urlSuffix,
-	    authenticatorStr,
-	    fUserAgentHeaderStr,
-	    sessionCookie);
-    delete[] authenticatorStr;
-    if (!sendRequest(cmd, "HTTP POST", False/*don't base64-encode*/)) break;
+void RTSPClient::responseHandlerForSyncInterface(RTSPClient* rtspClient, int responseCode, char* responseString) {
+  if (rtspClient != NULL) rtspClient->responseHandlerForSyncInterface1(responseCode, responseString);
+}
 
-    // Note that there's no response to the "POST".
+void RTSPClient::responseHandlerForSyncInterface1(int responseCode, char* responseString) {
+  // If we have a 'timeout task' pending, then unschedule it:
+  if (fTimeoutTask != NULL) envir().taskScheduler().unscheduleDelayedTask(fTimeoutTask);
 
-    delete[] cmd;
-    return True;
-  } while (0);
+  // Set result values:
+  fResultCode = responseCode;
+  fResultString = responseString;
 
-  // An error occurred:
-  delete[] cmd;
-  return False;
+  // Signal a break from the event loop (thereby returning from the blocking command):
+  fWatchVariableForSyncInterface = ~0;
 }
 
-void RTSPClient::incomingRequestHandler(void* instance, int /*mask*/) {
-  RTSPClient* session = (RTSPClient*)instance;
-  session->incomingRequestHandler1();
+void RTSPClient::timeoutHandlerForSyncInterface(void* rtspClient) {
+  if (rtspClient != NULL) ((RTSPClient*)rtspClient)->timeoutHandlerForSyncInterface1();
 }
 
-void RTSPClient::incomingRequestHandler1() {
-  unsigned bytesRead;
-  char* readBuf = fResponseBuffer;
-  bytesRead = getResponse1(readBuf, fResponseBufferSize);
-  if (bytesRead == 0) {
-    envir().setResultMsg("Failed to read response: Connection was closed by the remote host.");
-    envir().taskScheduler().turnOffBackgroundReadHandling(fInputSocketNum); // because the connection died
-    return;
-  }
-  // Parse the request string into command name and 'CSeq',
-  // then handle the command:
-  char cmdName[RTSP_PARAM_STRING_MAX];
-  char urlPreSuffix[RTSP_PARAM_STRING_MAX];
-  char urlSuffix[RTSP_PARAM_STRING_MAX];
-  char cseq[RTSP_PARAM_STRING_MAX];
-  if (!parseRTSPRequestString((char*)readBuf, bytesRead,
-			      cmdName, sizeof cmdName,
-			      urlPreSuffix, sizeof urlPreSuffix,
-			      urlSuffix, sizeof urlSuffix,
-			      cseq, sizeof cseq)) {
-    return;
-  } else {
-    if (fVerbosityLevel >= 1) {
-      envir() << "Received request: " << readBuf << "\n";
-    }
-    handleCmd_notSupported(cseq);
-  }
-}
+void RTSPClient::timeoutHandlerForSyncInterface1() {
+  // A RTSP command has timed out, so we should have a queued request record.  Disable it by setting its response handler to NULL.
+  // (Because this is a synchronous interface, there should be exactly one pending response handler - for "fCSeq".)
+  // all of them.)
+  changeResponseHandler(fCSeq, NULL);
+  fTimeoutTask = NULL;
 
-void RTSPClient::handleCmd_notSupported(char const* cseq) {
-  char tmpBuf[512];
-  snprintf((char*)tmpBuf, sizeof tmpBuf,
-	   "RTSP/1.0 405 Method Not Allowed\r\nCSeq: %s\r\n\r\n", cseq);
-  send(fOutputSocketNum, tmpBuf, strlen(tmpBuf), 0);
+  // Fill in 'negative' return values:
+  fResultCode = ~0;
+  fResultString = NULL;
+
+  // Signal a break from the event loop (thereby returning from the blocking command):
+  fWatchVariableForSyncInterface = ~0;
 }
+
+#endif
diff --git a/liveMedia/RTSPCommon.cpp b/liveMedia/RTSPCommon.cpp
index 3f5ab36..6e22c3d 100644
--- a/liveMedia/RTSPCommon.cpp
+++ b/liveMedia/RTSPCommon.cpp
@@ -134,28 +134,35 @@ Boolean parseRTSPRequestString(char const* reqStr,
   return True;
 }
 
-Boolean parseRangeHeader(char const* buf, double& rangeStart, double& rangeEnd) {
-  // First, find "Range:"
-  while (1) {
-    if (*buf == '\0') return False; // not found
-    if (_strncasecmp(buf, "Range: ", 7) == 0) break;
-    ++buf;
-  }
-
-  // Then, run through each of the fields, looking for ones we handle:
-  char const* fields = buf + 7;
-  while (*fields == ' ') ++fields;
+Boolean parseRangeParam(char const* paramStr, double& rangeStart, double& rangeEnd) {
   double start, end;
   Locale l("C", LC_NUMERIC);
-  if (sscanf(fields, "npt = %lf - %lf", &start, &end) == 2) {
+  if (sscanf(paramStr, "npt = %lf - %lf", &start, &end) == 2) {
     rangeStart = start;
     rangeEnd = end;
-  } else if (sscanf(fields, "npt = %lf -", &start) == 1) {
+  } else if (sscanf(paramStr, "npt = %lf -", &start) == 1) {
     rangeStart = start;
     rangeEnd = 0.0;
+  } else if (strcmp(paramStr, "npt=now-") == 0) {
+    rangeStart = 0.0;
+    rangeEnd = 0.0;
   } else {
     return False; // The header is malformed
   }
 
   return True;
 }
+
+Boolean parseRangeHeader(char const* buf, double& rangeStart, double& rangeEnd) {
+  // First, find "Range:"
+  while (1) {
+    if (*buf == '\0') return False; // not found
+    if (_strncasecmp(buf, "Range: ", 7) == 0) break;
+    ++buf;
+  }
+
+  // Then, run through each of the fields, looking for ones we handle:
+  char const* fields = buf + 7;
+  while (*fields == ' ') ++fields;
+  return parseRangeParam(fields, rangeStart, rangeEnd);
+}
diff --git a/liveMedia/RTSPServer.cpp b/liveMedia/RTSPServer.cpp
index 5cb2cc2..4dde484 100644
--- a/liveMedia/RTSPServer.cpp
+++ b/liveMedia/RTSPServer.cpp
@@ -318,7 +318,7 @@ void RTSPServer::RTSPClientSession::handleAlternativeRequestByte(void* instance,
 
 void RTSPServer::RTSPClientSession::handleAlternativeRequestByte1(u_int8_t requestByte) {
   // Add this character to our buffer; then try to handle the data that we have buffered so far:
-  if (fRequestBufferBytesLeft == 0) return;
+  if (fRequestBufferBytesLeft == 0|| fRequestBytesAlreadySeen >= RTSP_BUFFER_SIZE) return;
   fRequestBuffer[fRequestBytesAlreadySeen] = requestByte;
   handleRequestBytes(1);
 }
@@ -740,16 +740,28 @@ void RTSPServer::RTSPClientSession
   delete[] clientsDestinationAddressStr;
   Port serverRTPPort(0);
   Port serverRTCPPort(0);
+
+  // Make sure that we transmit on the same interface that's used by the client (in case we're a multi-homed server):
+  struct sockaddr_in sourceAddr; SOCKLEN_T namelen = sizeof sourceAddr;
+  getsockname(fClientSocket, (struct sockaddr*)&sourceAddr, &namelen);
+  netAddressBits origSendingInterfaceAddr = SendingInterfaceAddr;
+  netAddressBits origReceivingInterfaceAddr = ReceivingInterfaceAddr;
+  // NOTE: The following might not work properly, so we ifdef it out for now:
+#ifdef HACK_FOR_MULTIHOMED_SERVERS
+  ReceivingInterfaceAddr = SendingInterfaceAddr = sourceAddr.sin_addr.s_addr;
+#endif
+
   subsession->getStreamParameters(fOurSessionId, fClientAddr.sin_addr.s_addr,
 				  clientRTPPort, clientRTCPPort,
 				  tcpSocketNum, rtpChannelId, rtcpChannelId,
 				  destinationAddress, destinationTTL, fIsMulticast,
 				  serverRTPPort, serverRTCPPort,
 				  fStreamStates[streamNum].streamToken);
+  SendingInterfaceAddr = origSendingInterfaceAddr;
+  ReceivingInterfaceAddr = origReceivingInterfaceAddr;
+
   struct in_addr destinationAddr; destinationAddr.s_addr = destinationAddress;
   char* destAddrStr = strDup(our_inet_ntoa(destinationAddr));
-  struct sockaddr_in sourceAddr; SOCKLEN_T namelen = sizeof sourceAddr;
-  getsockname(fClientSocket, (struct sockaddr*)&sourceAddr, &namelen);
   char* sourceAddrStr = strDup(our_inet_ntoa(sourceAddr.sin_addr));
   if (fIsMulticast) {
     switch (streamingMode) {
diff --git a/liveMedia/StreamParser.cpp b/liveMedia/StreamParser.cpp
index b4f3ca2..711158e 100644
--- a/liveMedia/StreamParser.cpp
+++ b/liveMedia/StreamParser.cpp
@@ -79,7 +79,7 @@ void StreamParser::ensureValidBytes1(unsigned numBytesNeeded) {
 			  << fCurParserIndex << "+ "
 			  << numBytesNeeded << " > "
 			  << BANK_SIZE << ")\n";
-    abort();
+    fInputSource->envir().internalError();
   }
 
   // Try to read as many new bytes as will fit in the current bank:
diff --git a/liveMedia/include/AMRAudioSource.hh b/liveMedia/include/AMRAudioSource.hh
index 738ffb0..a40168c 100644
--- a/liveMedia/include/AMRAudioSource.hh
+++ b/liveMedia/include/AMRAudioSource.hh
@@ -40,6 +40,7 @@ protected:
 
 private:
   // redefined virtual functions:
+  virtual char const* MIMEtype() const;
   virtual Boolean isAMRAudioSource() const;
 
 protected:
diff --git a/liveMedia/include/DarwinInjector.hh b/liveMedia/include/DarwinInjector.hh
index 556032f..d8e397b 100644
--- a/liveMedia/include/DarwinInjector.hh
+++ b/liveMedia/include/DarwinInjector.hh
@@ -86,6 +86,9 @@ private:
 
   virtual ~DarwinInjector();
 
+  static void genericResponseHandler(RTSPClient* rtspClient, int responseCode, char* responseString);
+  void genericResponseHandler1(int responseCode, char* responseString);
+
 private:
   char const* fApplicationName;
   int fVerbosityLevel;
@@ -95,6 +98,9 @@ private:
   SubstreamDescriptor* fTailSubstream;
   MediaSession* fSession;
   unsigned fLastTrackId;
+  char fWatchVariable;
+  int fResultCode;
+  char* fResultString;
 };
 
 #endif
diff --git a/liveMedia/include/FileSink.hh b/liveMedia/include/FileSink.hh
index 99cc406..a0cdfcb 100644
--- a/liveMedia/include/FileSink.hh
+++ b/liveMedia/include/FileSink.hh
@@ -37,7 +37,7 @@ public:
   //   file name suffix).  The default behavior ("oneFilePerFrame" == False)
   //   is to output all incoming data into a single file.
 
-  void addData(unsigned char* data, unsigned dataSize,
+  void addData(unsigned char const* data, unsigned dataSize,
 	       struct timeval presentationTime);
   // (Available in case a client wants to add extra data to the output file)
 
diff --git a/liveMedia/include/H264VideoFileSink.hh b/liveMedia/include/H264VideoFileSink.hh
index e63a604..f172657 100644
--- a/liveMedia/include/H264VideoFileSink.hh
+++ b/liveMedia/include/H264VideoFileSink.hh
@@ -28,20 +28,25 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 class H264VideoFileSink: public FileSink {
 public:
   static H264VideoFileSink* createNew(UsageEnvironment& env, char const* fileName,
-				     unsigned bufferSize = 10000,
-				     Boolean oneFilePerFrame = False);
-  // (See "FileSink.hh" for a description of these parameters.)
+				      char const* sPropParameterSetsStr = NULL,
+  // An optional 'SDP format' string (comma-separated Base64-encoded) representing SPS and/or PPS NAL-units to prepend to the output
+				      unsigned bufferSize = 10000,
+				      Boolean oneFilePerFrame = False);
+  // See "FileSink.hh" for a description of these parameters.
 
 protected:
-  H264VideoFileSink(UsageEnvironment& env, FILE* fid, unsigned bufferSize,
-		   char const* perFrameFileNamePrefix);
+  H264VideoFileSink(UsageEnvironment& env, FILE* fid,
+		    char const* sPropParameterSetsStr,
+		    unsigned bufferSize, char const* perFrameFileNamePrefix);
       // called only by createNew()
   virtual ~H264VideoFileSink();
 
 protected: // redefined virtual functions:
-  virtual Boolean sourceIsCompatibleWithUs(MediaSource& source);
-  virtual void afterGettingFrame1(unsigned frameSize,
-				  struct timeval presentationTime);
+  virtual void afterGettingFrame1(unsigned frameSize, struct timeval presentationTime);
+
+private:
+  char const* fSPropParameterSetsStr;
+  Boolean fHaveWrittenFirstFrame;
 };
 
 #endif
diff --git a/liveMedia/include/Media.hh b/liveMedia/include/Media.hh
index a80b720..a5cdc3a 100644
--- a/liveMedia/include/Media.hh
+++ b/liveMedia/include/Media.hh
@@ -88,7 +88,7 @@ private:
 // The structure pointed to by the "liveMediaPriv" UsageEnvironment field:
 class _Tables {
 public:
-  static _Tables* getOurTables(UsageEnvironment& env);
+  static _Tables* getOurTables(UsageEnvironment& env, Boolean createIfNotPresent = True);
       // returns a pointer to an "ourTables" structure (creating it if necessary)
   void reclaimIfPossible();
       // used to delete ourselves when we're no longer used
diff --git a/liveMedia/include/MediaSession.hh b/liveMedia/include/MediaSession.hh
index 5325713..ef2caa2 100644
--- a/liveMedia/include/MediaSession.hh
+++ b/liveMedia/include/MediaSession.hh
@@ -218,6 +218,7 @@ public:
   // (e.g., by a "RTP-Info:" header in a RTSP response).
   // Also, for this function to work properly, the RTP stream's presentation times must (eventually) be
   // synchronized via RTCP.
+  // (Note: If this function returns a negative number, then the result should be ignored by the caller.)
 
 protected:
   friend class MediaSession;
diff --git a/liveMedia/include/MediaSink.hh b/liveMedia/include/MediaSink.hh
index 763a428..01686e6 100644
--- a/liveMedia/include/MediaSink.hh
+++ b/liveMedia/include/MediaSink.hh
@@ -85,11 +85,11 @@ public:
   void increment(unsigned numBytes) {fCurOffset += numBytes;}
 
   void enqueue(unsigned char const* from, unsigned numBytes);
-  void enqueueWord(unsigned word);
+  void enqueueWord(u_int32_t word);
   void insert(unsigned char const* from, unsigned numBytes, unsigned toPosition);
-  void insertWord(unsigned word, unsigned toPosition);
+  void insertWord(u_int32_t word, unsigned toPosition);
   void extract(unsigned char* to, unsigned numBytes, unsigned fromPosition);
-  unsigned extractWord(unsigned fromPosition);
+  u_int32_t extractWord(unsigned fromPosition);
 
   void skipBytes(unsigned numBytes);
 
diff --git a/liveMedia/include/MultiFramedRTPSource.hh b/liveMedia/include/MultiFramedRTPSource.hh
index 13cfcad..494deae 100644
--- a/liveMedia/include/MultiFramedRTPSource.hh
+++ b/liveMedia/include/MultiFramedRTPSource.hh
@@ -65,9 +65,10 @@ private:
   void doGetNextFrame1();
 
   static void networkReadHandler(MultiFramedRTPSource* source, int /*mask*/);
-  friend void networkReadHandler(MultiFramedRTPSource*, int);
+  void networkReadHandler1();
 
   Boolean fAreDoingNetworkReads;
+  BufferedPacket* fPacketReadInProgress;
   Boolean fNeedDelivery;
   Boolean fPacketLossInFragmentedFrame;
   unsigned char* fSavedTo;
@@ -90,7 +91,7 @@ public:
   Boolean hasUsableData() const { return fTail > fHead; }
   unsigned useCount() const { return fUseCount; }
 
-  Boolean fillInData(RTPInterface& rtpInterface);
+  Boolean fillInData(RTPInterface& rtpInterface, Boolean& packetReadWasIncomplete);
   void assignMiscParams(unsigned short rtpSeqNo, unsigned rtpTimestamp,
 			struct timeval presentationTime,
 			Boolean hasBeenSyncedUsingRTCP,
diff --git a/liveMedia/include/RTCP.hh b/liveMedia/include/RTCP.hh
index ee7d549..074180a 100644
--- a/liveMedia/include/RTCP.hh
+++ b/liveMedia/include/RTCP.hh
@@ -131,6 +131,7 @@ private:
 
 private:
   unsigned char* fInBuf;
+  unsigned fNumBytesAlreadyRead;
   OutPacketBuffer* fOutBuf;
   RTPInterface fRTCPInterface;
   unsigned fTotSessionBW;
diff --git a/liveMedia/include/RTPInterface.hh b/liveMedia/include/RTPInterface.hh
index ddbd030..4ab22a3 100644
--- a/liveMedia/include/RTPInterface.hh
+++ b/liveMedia/include/RTPInterface.hh
@@ -62,14 +62,13 @@ public:
   void setStreamSocket(int sockNum, unsigned char streamChannelId);
   void addStreamSocket(int sockNum, unsigned char streamChannelId);
   void removeStreamSocket(int sockNum, unsigned char streamChannelId);
-  void setServerRequestAlternativeByteHandler(ServerRequestAlternativeByteHandler* handler, void* clientData);
+  void setServerRequestAlternativeByteHandler(int socketNum, ServerRequestAlternativeByteHandler* handler, void* clientData);
 
   void sendPacket(unsigned char* packet, unsigned packetSize);
   void startNetworkReading(TaskScheduler::BackgroundHandlerProc*
                            handlerProc);
   Boolean handleRead(unsigned char* buffer, unsigned bufferMaxSize,
-		     unsigned& bytesRead,
-		     struct sockaddr_in& fromAddress);
+		     unsigned& bytesRead, struct sockaddr_in& fromAddress, Boolean& packetReadWasIncomplete);
   void stopNetworkReading();
 
   UsageEnvironment& envir() const { return fOwner->envir(); }
diff --git a/liveMedia/include/RTPSink.hh b/liveMedia/include/RTPSink.hh
index b8eeff3..3f75be5 100644
--- a/liveMedia/include/RTPSink.hh
+++ b/liveMedia/include/RTPSink.hh
@@ -80,8 +80,8 @@ public:
   void removeStreamSocket(int sockNum, unsigned char streamChannelId) {
     fRTPInterface.removeStreamSocket(sockNum, streamChannelId);
   }
-  void setServerRequestAlternativeByteHandler(ServerRequestAlternativeByteHandler* handler, void* clientData) {
-    fRTPInterface.setServerRequestAlternativeByteHandler(handler, clientData);
+  void setServerRequestAlternativeByteHandler(int socketNum, ServerRequestAlternativeByteHandler* handler, void* clientData) {
+    fRTPInterface.setServerRequestAlternativeByteHandler(socketNum, handler, clientData);
   }
     // hacks to allow sending RTP over TCP (RFC 2236, section 10.12)
 
diff --git a/liveMedia/include/RTPSource.hh b/liveMedia/include/RTPSource.hh
index d0fc9b1..10be0f5 100644
--- a/liveMedia/include/RTPSource.hh
+++ b/liveMedia/include/RTPSource.hh
@@ -63,6 +63,9 @@ public:
     // hack to allow sending RTP over TCP (RFC 2236, section 10.12)
     fRTPInterface.setStreamSocket(sockNum, streamChannelId);
   }
+  void setServerRequestAlternativeByteHandler(int socketNum, ServerRequestAlternativeByteHandler* handler, void* clientData) {
+    fRTPInterface.setServerRequestAlternativeByteHandler(socketNum, handler, clientData);
+  }
 
   void setAuxilliaryReadHandler(AuxHandlerFunc* handlerFunc,
                                 void* handlerClientData) {
@@ -175,7 +178,7 @@ public:
   double totNumKBytesReceived() const;
 
   unsigned totNumPacketsExpected() const {
-    return fHighestExtSeqNumReceived - fBaseExtSeqNumReceived;
+    return (fHighestExtSeqNumReceived - fBaseExtSeqNumReceived) + 1;
   }
 
   unsigned baseExtSeqNumReceived() const { return fBaseExtSeqNumReceived; }
diff --git a/liveMedia/include/RTSPClient.hh b/liveMedia/include/RTSPClient.hh
index 8dacaf5..2b685fe 100644
--- a/liveMedia/include/RTSPClient.hh
+++ b/liveMedia/include/RTSPClient.hh
@@ -15,7 +15,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 **********/
 // "liveMedia"
 // Copyright (c) 1996-2010 Live Networks, Inc.  All rights reserved.
-// A generic RTSP client
+// A generic RTSP client - for a single "rtsp://" URL
 // C++ header
 
 #ifndef _RTSP_CLIENT_HH
@@ -31,9 +31,11 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 #include "DigestAuthentication.hh"
 #endif
 
+#define RTSPCLIENT_SYNCHRONOUS_INTERFACE 1 // For now, continue to support the old synchronous interface as well
+
 class RTSPClient: public Medium {
 public:
-  static RTSPClient* createNew(UsageEnvironment& env,
+  static RTSPClient* createNew(UsageEnvironment& env, char const* rtspURL,
 			       int verbosityLevel = 0,
 			       char const* applicationName = NULL,
 			       portNumBits tunnelOverHTTPPortNum = 0);
@@ -41,170 +43,229 @@ public:
   // over a HTTP connection with the given port number, using the technique
   // described in Apple's document <http://developer.apple.com/documentation/QuickTime/QTSS/Concepts/chapter_2_section_14.html>
 
-  int socketNum() const { return fInputSocketNum; }
+  typedef void (responseHandler)(RTSPClient* rtspClient,
+				 int resultCode, char* resultString);
+      // A function that is called in response to a RTSP command.  The parameters are as follows:
+      //     "rtspClient": The "RTSPClient" object on which the original command was issued.
+      //     "resultCode": If zero, then the command completed successfully.  If non-zero, then the command did not complete
+      //         successfully, and "resultCode" indicates the error, as follows:
+      //             A positive "resultCode" is a RTSP error code (for example, 404 means "not found")
+      //             A negative "resultCode" indicates a socket/network error; 0-"resultCode" is the standard "errno" code.
+      //     "resultString": A ('\0'-terminated) string returned along with the response, or else NULL.
+      //         In particular:
+      //             "resultString" for a successful "DESCRIBE" command will be the media session's SDP description.
+      //             "resultString" for a successful "OPTIONS" command will be a list of allowed commands.
+      //         Note that this string can be present (i.e., not NULL) even if "resultCode" is non-zero - i.e., an error message.
+      //         Note also that this string is dynamically allocated, and must be freed by the handler (or the caller)
+      //             - using "delete[]".
 
-  static Boolean lookupByName(UsageEnvironment& env,
-			      char const* sourceName,
-			      RTSPClient*& resultClient);
+  unsigned sendDescribeCommand(responseHandler* responseHandler, Authenticator* authenticator = NULL);
+      // Issues a RTSP "DESCRIBE" command, then returns the "CSeq" sequence number that was used in the command.
+      // The (programmer-supplied) "responseHandler" function is called later to handle the response
+      //     (or is called immediately - with an error code - if the command cannot be sent).
+      // "authenticator" (optional) is used for access control.  If you have username and password strings, you can use this by
+      //     passing an actual parameter that you created by creating an "Authenticator(username, password) object".
+      //     (Note that if you supply a non-NULL "authenticator" parameter, you need do this only for the first command you send.)
 
-  char* describeURL(char const* url, Authenticator* authenticator = NULL,
-		    Boolean allowKasennaProtocol = False, int timeout = -1);
-      // Issues a RTSP "DESCRIBE" command
-      // Returns the SDP description of a session, or NULL if none
-      // (This is dynamically allocated, and must later be freed
-      //  by the caller - using "delete[]")
-  char* describeWithPassword(char const* url,
-			     char const* username, char const* password,
-			     Boolean allowKasennaProtocol = False, 
-			     int timeout = -1);
-      // Uses "describeURL()" to do a "DESCRIBE" - first
-      // without using "password", then (if we get an Unauthorized
-      // response) with an authentication response computed from "password"
-
-  Boolean announceSDPDescription(char const* url,
-				 char const* sdpDescription,
-				 Authenticator* authenticator = NULL,
-				 int timeout = -1);
-      // Issues a RTSP "ANNOUNCE" command
-      // Returns True iff this command succeeds
-  Boolean announceWithPassword(char const* url, char const* sdpDescription,
-			       char const* username, char const* password, int timeout = -1);
-      // Uses "announceSDPDescription()" to do an "ANNOUNCE" - first
-      // without using "password", then (if we get an Unauthorized
-      // response) with an authentication response computed from "password"
+  unsigned sendOptionsCommand(responseHandler* responseHandler, Authenticator* authenticator = NULL);
+      // Issues a RTSP "OPTIONS" command, then returns the "CSeq" sequence number that was used in the command.
+      // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
 
-  char* sendOptionsCmd(char const* url,
-		       char* username = NULL, char* password = NULL,
-		       Authenticator* authenticator = NULL,
-		       int timeout = -1);
-      // Issues a RTSP "OPTIONS" command
-      // Returns a string containing the list of options, or NULL
+  unsigned sendAnnounceCommand(char const* sdpDescription, responseHandler* responseHandler, Authenticator* authenticator = NULL);
+      // Issues a RTSP "ANNOUNCE" command (with "sdpDescription" as parameter),
+      //     then returns the "CSeq" sequence number that was used in the command.
+      // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
 
-  Boolean setupMediaSubsession(MediaSubsession& subsession,
-			       Boolean streamOutgoing = False,
-			       Boolean streamUsingTCP = False,
-			       Boolean forceMulticastOnUnspecified = False);
-      // Issues a RTSP "SETUP" command on "subsession".
-      // Returns True iff this command succeeds
-      // If "forceMulticastOnUnspecified" is True (and "streamUsingTCP" is False),
-      // then the client will request a multicast stream if the media address
-      // in the original SDP response was unspecified (i.e., 0.0.0.0).
-      // Note, however, that not all servers will support this.
+  unsigned sendSetupCommand(MediaSubsession& subsession, responseHandler* responseHandler,
+			    Boolean streamOutgoing = False,
+			    Boolean streamUsingTCP = False,
+			    Boolean forceMulticastOnUnspecified = False,
+			    Authenticator* authenticator = NULL);
+      // Issues a RTSP "SETUP" command, then returns the "CSeq" sequence number that was used in the command.
+      // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
 
-  Boolean playMediaSession(MediaSession& session,
-			   double start = 0.0f, double end = -1.0f,
-			   float scale = 1.0f);
-      // Issues an aggregate RTSP "PLAY" command on "session".
-      // Returns True iff this command succeeds
+  unsigned sendPlayCommand(MediaSession& session, responseHandler* responseHandler,
+			   double start = 0.0f, double end = -1.0f, float scale = 1.0f,
+			   Authenticator* authenticator = NULL);
+      // Issues an aggregate RTSP "PLAY" command on "session", then returns the "CSeq" sequence number that was used in the command.
       // (Note: start=-1 means 'resume'; end=-1 means 'play to end')
-  Boolean playMediaSubsession(MediaSubsession& subsession,
-			      double start = 0.0f, double end = -1.0f,
-			      float scale = 1.0f,
-			      Boolean hackForDSS = False);
-      // Issues a RTSP "PLAY" command on "subsession".
-      // Returns True iff this command succeeds
+      // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
+  unsigned sendPlayCommand(MediaSubsession& subsession, responseHandler* responseHandler,
+			   double start = 0.0f, double end = -1.0f, float scale = 1.0f,
+			   Authenticator* authenticator = NULL);
+      // Issues a RTSP "PLAY" command on "subsession", then returns the "CSeq" sequence number that was used in the command.
       // (Note: start=-1 means 'resume'; end=-1 means 'play to end')
+      // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
 
-  Boolean pauseMediaSession(MediaSession& session);
-      // Issues an aggregate RTSP "PAUSE" command on "session".
-      // Returns True iff this command succeeds
-  Boolean pauseMediaSubsession(MediaSubsession& subsession);
-      // Issues a RTSP "PAUSE" command on "subsession".
-      // Returns True iff this command succeeds
+  unsigned sendPauseCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator = NULL);
+      // Issues an aggregate RTSP "PAUSE" command on "session", then returns the "CSeq" sequence number that was used in the command.
+      // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
+  unsigned sendPauseCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator = NULL);
+      // Issues a RTSP "PAUSE" command on "subsession", then returns the "CSeq" sequence number that was used in the command.
+      // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
 
-  Boolean recordMediaSubsession(MediaSubsession& subsession);
-      // Issues a RTSP "RECORD" command on "subsession".
-      // Returns True iff this command succeeds
+  unsigned sendRecordCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator = NULL);
+      // Issues an aggregate RTSP "RECORD" command on "session", then returns the "CSeq" sequence number that was used in the command.
+      // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
+  unsigned sendRecordCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator = NULL);
+      // Issues a RTSP "RECORD" command on "subsession", then returns the "CSeq" sequence number that was used in the command.
+      // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
 
-  Boolean setMediaSessionParameter(MediaSession& session,
-				   char const* parameterName,
-				   char const* parameterValue);
-      // Issues a RTSP "SET_PARAMETER" command on "subsession".
-      // Returns True iff this command succeeds
+  unsigned sendTeardownCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator = NULL);
+      // Issues an aggregate RTSP "TEARDOWN" command on "session", then returns the "CSeq" sequence number that was used in the command.
+      // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
+  unsigned sendTeardownCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator = NULL);
+      // Issues a RTSP "TEARDOWN" command on "subsession", then returns the "CSeq" sequence number that was used in the command.
+      // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
 
-  Boolean getMediaSessionParameter(MediaSession& session,
-				   char const* parameterName,
-				   char*& parameterValue);
-      // Issues a RTSP "GET_PARAMETER" command on "subsession".
-      // Returns True iff this command succeeds
+  unsigned sendSetParameterCommand(MediaSession& session, responseHandler* responseHandler,
+				   char const* parameterName, char const* parameterValue,
+				   Authenticator* authenticator = NULL);
+      // Issues an aggregate RTSP "SET_PARAMETER" command on "session", then returns the "CSeq" sequence number that was used in the command.
+      // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
 
-  Boolean teardownMediaSession(MediaSession& session);
-      // Issues an aggregate RTSP "TEARDOWN" command on "session".
-      // Returns True iff this command succeeds
-  Boolean teardownMediaSubsession(MediaSubsession& subsession);
-      // Issues a RTSP "TEARDOWN" command on "subsession".
-      // Returns True iff this command succeeds
+  unsigned sendGetParameterCommand(MediaSession& session, responseHandler* responseHandler, char const* parameterName,
+				   Authenticator* authenticator = NULL);
+      // Issues an aggregate RTSP "GET_PARAMETER" command on "session", then returns the "CSeq" sequence number that was used in the command.
+      // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
+
+  Boolean changeResponseHandler(unsigned cseq, responseHandler* newResponseHandler);
+      // Changes the response handler for the previously-performed command (whose operation returned "cseq").
+      // (To turn off any response handling for the command, use a "newResponseHandler" value of NULL.  This might be done as part
+      //  of an implementation of a 'timeout handler' on the command, for example.)
+      // This function returns True iff "cseq" was for a valid previously-performed command (whose response is still unhandled).
+
+  int socketNum() const { return fInputSocketNum; }
+
+  static Boolean lookupByName(UsageEnvironment& env,
+			      char const* sourceName,
+			      RTSPClient*& resultClient);
 
   static Boolean parseRTSPURL(UsageEnvironment& env, char const* url,
-			      NetAddress& address, portNumBits& portNum,
-			      char const** urlSuffix = NULL);
-      // (ignores any "<username>[:<password>]@" in "url")
+			      NetAddress& address, portNumBits& portNum, char const** urlSuffix = NULL);
+      // (ignores any "<username>[:<password>]@" in "url"); to get those, use:
   static Boolean parseRTSPURLUsernamePassword(char const* url,
-					      char*& username,
-					      char*& password);
+					      char*& username, char*& password);
 
-  unsigned describeStatus() const { return fDescribeStatusCode; }
-
-  void setUserAgentString(char const* userAgentStr);
+  void setUserAgentString(char const* userAgentName);
   // sets an alternative string to be used in RTSP "User-Agent:" headers
 
   unsigned sessionTimeoutParameter() const { return fSessionTimeoutParameter; }
 
+  static unsigned responseBufferSize;
+
 protected:
-  RTSPClient(UsageEnvironment& env, int verbosityLevel,
-	     char const* applicationName, portNumBits tunnelOverHTTPPortNum);
+  RTSPClient(UsageEnvironment& env, char const* rtspURL,
+	     int verbosityLevel, char const* applicationName, portNumBits tunnelOverHTTPPortNum);
       // called only by createNew();
   virtual ~RTSPClient();
 
 private: // redefined virtual functions
   virtual Boolean isRTSPClient() const;
 
+public: // Some compilers complain if this is "private:"
+  // The state of a request-in-progress:
+  class RequestRecord {
+  public:
+    RequestRecord(unsigned cseq, char const* commandName, responseHandler* handler,
+		  MediaSession* session = NULL, MediaSubsession* subsession = NULL, u_int32_t booleanFlags = 0,
+		  double start = 0.0f, double end = -1.0f, float scale = 1.0f, char const* contentStr = NULL);
+    virtual ~RequestRecord();
+
+    RequestRecord*& next() { return fNext; }
+    unsigned& cseq() { return fCSeq; }
+    char const* commandName() const { return fCommandName; }
+    MediaSession* session() const { return fSession; }
+    MediaSubsession* subsession() const { return fSubsession; }
+    u_int32_t booleanFlags() const { return fBooleanFlags; }
+    double start() const { return fStart; }
+    double end() const { return fEnd; }
+    float scale() const { return fScale; }
+    char* contentStr() const { return fContentStr; }
+    responseHandler*& handler() { return fHandler; }
+
+  private:
+    RequestRecord* fNext;
+    unsigned fCSeq;
+    char const* fCommandName;
+    MediaSession* fSession;
+    MediaSubsession* fSubsession;
+    u_int32_t fBooleanFlags;
+    double fStart, fEnd;
+    float fScale;
+    char* fContentStr;
+    responseHandler* fHandler;
+  };
 private:
+  class RequestQueue {
+  public:
+    RequestQueue();
+    virtual ~RequestQueue();
+
+    void enqueue(RequestRecord* request); // "request" must not be NULL
+    RequestRecord* dequeue();
+    void putAtHead(RequestRecord* request); // "request" must not be NULL
+    RequestRecord* findByCSeq(unsigned cseq);
+    Boolean isEmpty() const { return fHead == NULL; }
+
+  private:
+    RequestRecord* fHead;
+    RequestRecord* fTail;
+  };
+
   void reset();
   void resetTCPSockets();
-
-  Boolean openConnectionFromURL(char const* url, Authenticator* authenticator,
-				int timeout = -1);
-  char* createAuthenticatorString(Authenticator const* authenticator,
-				  char const* cmd, char const* url);
-  static void checkForAuthenticationFailure(unsigned responseCode,
-					    char*& nextLineStart,
-					    Authenticator* authenticator);
-  Boolean sendRequest(char const* requestString, char const* tag,
-		      Boolean base64EncodeIfOverHTTP = True);
-  Boolean getResponse(char const* tag,
-		      unsigned& bytesRead, unsigned& responseCode,
-		      char*& firstLine, char*& nextLineStart,
-		      Boolean checkFor200Response = True);
-  unsigned getResponse1(char*& responseBuffer, unsigned responseBufferSize);
-  Boolean parseResponseCode(char const* line, unsigned& responseCode);
-  Boolean parseTransportResponse(char const* line,
-				 char*& serverAddressStr,
-				 portNumBits& serverPortNum,
-				 unsigned char& rtpChannelId,
-				 unsigned char& rtcpChannelId);
-  Boolean parseRTPInfoHeader(char*& line, u_int16_t& seqNum, u_int32_t& timestamp);
-  Boolean parseScaleHeader(char const* line, float& scale);
-  Boolean parseGetParameterHeader(char const* line,
-                                  const char* param,
-                                  char*& value);
+  void resetResponseBuffer();
+  void setBaseURL(char const* url);
+  int openConnection(); // -1: failure; 0: pending; 1: success
+  int connectToServer(int socketNum, portNumBits remotePortNum); // used to implement "openConnection()"; result values are the same
+  char* createAuthenticatorString(char const* cmd, char const* url);
+  unsigned sendRequest(RequestRecord* request);
+  void handleRequestError(RequestRecord* request);
+  Boolean parseResponseCode(char const* line, unsigned& responseCode, char const*& responseString, Boolean& responseIsHTTP);
+  void handleIncomingRequest();
+  static Boolean checkForHeader(char const* line, char const* headerName, unsigned headerNameLength, char const*& headerParams);
+  Boolean parseTransportParams(char const* paramsStr,
+			       char*& serverAddressStr, portNumBits& serverPortNum,
+			       unsigned char& rtpChannelId, unsigned char& rtcpChannelId);
+  Boolean parseScaleParam(char const* paramStr, float& scale);
+  Boolean parseRTPInfoParams(char const*& paramStr, u_int16_t& seqNum, u_int32_t& timestamp);
+  Boolean handleSETUPResponse(MediaSubsession& subsession, char const* sessionParamsStr, char const* transportParamsStr,
+			      Boolean streamUsingTCP);
+  Boolean handlePLAYResponse(MediaSession& session, MediaSubsession& subsession,
+                             char const* scaleParamsStr, char const* rangeParamsStr, char const* rtpInfoParamsStr);
+  Boolean handleTEARDOWNResponse(MediaSession& session, MediaSubsession& subsession);
+  Boolean handleGET_PARAMETERResponse(char const* parameterName, char*& resultValueString);
+  Boolean handleAuthenticationFailure(char const* wwwAuthenticateParamsStr);
+  Boolean resendCommand(RequestRecord* request);
   char const* sessionURL(MediaSession const& session) const;
+  static void handleAlternativeRequestByte(void*, u_int8_t requestByte);
+  void handleAlternativeRequestByte1(u_int8_t requestByte);
   void constructSubsessionURL(MediaSubsession const& subsession,
 			      char const*& prefix,
 			      char const*& separator,
 			      char const*& suffix);
-  Boolean setupHTTPTunneling(char const* urlSuffix, Authenticator* authenticator);
 
-  // Support for handling requests sent back by a server:
-  static void incomingRequestHandler(void*, int /*mask*/);
-  void incomingRequestHandler1();
-  void handleCmd_notSupported(char const* cseq);
+  // Support for tunneling RTSP-over-HTTP:
+  Boolean setupHTTPTunneling1(); // send the HTTP "GET"
+  static void responseHandlerForHTTP_GET(RTSPClient* rtspClient, int responseCode, char* responseString);
+  void responseHandlerForHTTP_GET1(int responseCode, char* responseString);
+  Boolean setupHTTPTunneling2(); // send the HTTP "POST"
+
+  // Support for asynchronous connections to the server:
+  static void connectionHandler(void*, int /*mask*/);
+  void connectionHandler1();
+
+  // Support for handling data sent back by a server:
+  static void incomingDataHandler(void*, int /*mask*/);
+  void incomingDataHandler1();
+  void handleResponseBytes(int newBytesRead);
 
 private:
   int fVerbosityLevel;
   portNumBits fTunnelOverHTTPPortNum;
   char* fUserAgentHeaderStr;
-      unsigned fUserAgentHeaderStrSize;
+  unsigned fUserAgentHeaderStrLen;
   int fInputSocketNum, fOutputSocketNum;
   unsigned fServerAddress;
   unsigned fCSeq; // sequence number, used in consecutive requests
@@ -213,17 +274,72 @@ private:
   unsigned char fTCPStreamIdCount; // used for (optional) RTP/TCP
   char* fLastSessionId;
   unsigned fSessionTimeoutParameter; // optionally set in response "Session:" headers
-  unsigned fDescribeStatusCode;
-  // 0: OK; 1: connection failed; 2: stream unavailable
   char* fResponseBuffer;
-  unsigned fResponseBufferSize;
+  unsigned fResponseBytesAlreadySeen, fResponseBufferBytesLeft;
+  RequestQueue fRequestsAwaitingConnection, fRequestsAwaitingHTTPTunneling, fRequestsAwaitingResponse;
 
-  // The following fields are used to implement the non-standard Kasenna protocol:
-  Boolean fServerIsKasenna;
-  char* fKasennaContentType;
+  // Support for tunneling RTSP-over-HTTP:
+  char fSessionCookie[33];
+  unsigned fSessionCookieCounter;
+  Boolean fHTTPTunnelingConnectionIsPending;
 
-  // The following is used to deal with Microsoft servers' non-standard use of RTSP:
-  Boolean fServerIsMicrosoft;
+#ifdef RTSPCLIENT_SYNCHRONOUS_INTERFACE
+  // Old "RTSPClient" interface, which performs synchronous (blocking) operations.
+  // This will eventually go away, so new applications should not use it.
+public:
+  static RTSPClient* createNew(UsageEnvironment& env,
+                               int verbosityLevel = 0,
+                               char const* applicationName = NULL,
+                               portNumBits tunnelOverHTTPPortNum = 0);
+  char* describeURL(char const* url, Authenticator* authenticator = NULL,
+                    Boolean allowKasennaProtocol = False, int timeout = -1);
+  char* describeWithPassword(char const* url,
+			     char const* username, char const* password,
+			     Boolean allowKasennaProtocol = False, 
+			     int timeout = -1);
+  char* sendOptionsCmd(char const* url,
+		       char* username = NULL, char* password = NULL,
+		       Authenticator* authenticator = NULL,
+		       int timeout = -1);
+  Boolean announceSDPDescription(char const* url,
+				 char const* sdpDescription,
+				 Authenticator* authenticator = NULL,
+				 int timeout = -1);
+  Boolean announceWithPassword(char const* url, char const* sdpDescription,
+			       char const* username, char const* password, int timeout = -1);
+  Boolean setupMediaSubsession(MediaSubsession& subsession,
+			       Boolean streamOutgoing = False,
+			       Boolean streamUsingTCP = False,
+			       Boolean forceMulticastOnUnspecified = False);
+  Boolean playMediaSession(MediaSession& session,
+			   double start = 0.0f, double end = -1.0f,
+			   float scale = 1.0f);
+  Boolean playMediaSubsession(MediaSubsession& subsession,
+			      double start = 0.0f, double end = -1.0f,
+			      float scale = 1.0f,
+			      Boolean hackForDSS = False);
+  Boolean pauseMediaSession(MediaSession& session);
+  Boolean pauseMediaSubsession(MediaSubsession& subsession);
+  Boolean recordMediaSubsession(MediaSubsession& subsession);
+  Boolean setMediaSessionParameter(MediaSession& session,
+				   char const* parameterName,
+				   char const* parameterValue);
+  Boolean getMediaSessionParameter(MediaSession& session,
+				   char const* parameterName,
+				   char*& parameterValue);
+  Boolean teardownMediaSession(MediaSession& session);
+  Boolean teardownMediaSubsession(MediaSubsession& subsession);
+private: // used to implement the old interface:
+  static void responseHandlerForSyncInterface(RTSPClient* rtspClient,
+					      int responseCode, char* responseString);
+  void responseHandlerForSyncInterface1(int responseCode, char* responseString);
+  static void timeoutHandlerForSyncInterface(void* rtspClient);
+  void timeoutHandlerForSyncInterface1();
+  TaskToken fTimeoutTask;
+  char fWatchVariableForSyncInterface;
+  char* fResultString;
+  int fResultCode;
+#endif
 };
 
 #endif
diff --git a/liveMedia/include/RTSPCommon.hh b/liveMedia/include/RTSPCommon.hh
index 9e526d5..9c555b2 100644
--- a/liveMedia/include/RTSPCommon.hh
+++ b/liveMedia/include/RTSPCommon.hh
@@ -44,6 +44,7 @@ Boolean parseRTSPRequestString(char const *reqStr, unsigned reqStrSize,
 			       char* resultCSeq,
 			       unsigned resultCSeqMaxSize);
 
+Boolean parseRangeParam(char const* paramStr, double& rangeStart, double& rangeEnd);
 Boolean parseRangeHeader(char const* buf, double& rangeStart, double& rangeEnd);
 
 #endif
diff --git a/liveMedia/include/liveMedia_version.hh b/liveMedia/include/liveMedia_version.hh
index 4dc2f03..4fa44aa 100644
--- a/liveMedia/include/liveMedia_version.hh
+++ b/liveMedia/include/liveMedia_version.hh
@@ -4,7 +4,7 @@
 #ifndef _LIVEMEDIA_VERSION_HH
 #define _LIVEMEDIA_VERSION_HH
 
-#define LIVEMEDIA_LIBRARY_VERSION_STRING	"2010.04.09"
-#define LIVEMEDIA_LIBRARY_VERSION_INT		1270771200
+#define LIVEMEDIA_LIBRARY_VERSION_STRING	"2010.09.25"
+#define LIVEMEDIA_LIBRARY_VERSION_INT		1285372800
 
 #endif
diff --git a/mediaServer/version.hh b/mediaServer/version.hh
index 6a65ff8..b141f45 100644
--- a/mediaServer/version.hh
+++ b/mediaServer/version.hh
@@ -5,6 +5,6 @@
 #ifndef _MEDIA_SERVER_VERSION_HH
 #define _MEDIA_SERVER_VERSION_HH
 
-#define MEDIA_SERVER_VERSION_STRING "0.42"
+#define MEDIA_SERVER_VERSION_STRING "0.5"
 
 #endif
diff --git a/testProgs/_rtp_over_tcp.txt b/testProgs/_rtp_over_tcp.txt
new file mode 100644
index 0000000..b504e13
--- /dev/null
+++ b/testProgs/_rtp_over_tcp.txt
@@ -0,0 +1,8 @@
+Opening connection to 192.168.3.75, port 554...
+...Connection to server failed: No route to host
+Sending request: DESCRIBE rtsp://192.168.3.75/VideoInput/1/jpeg/1 RTSP/1.0
+CSeq: 3
+User-Agent: ./openRTSP (LIVE555 Streaming Media v2010.07.10)
+Accept: application/sdp
+
+
diff --git a/testProgs/openRTSP.cpp b/testProgs/openRTSP.cpp
index a313b12..104384f 100644
--- a/testProgs/openRTSP.cpp
+++ b/testProgs/openRTSP.cpp
@@ -19,65 +19,31 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 
 #include "playCommon.hh"
 
-Medium* createClient(UsageEnvironment& env,
-                     int verbosityLevel, char const* applicationName) {
+RTSPClient* ourRTSPClient = NULL;
+Medium* createClient(UsageEnvironment& env, char const* url, int verbosityLevel, char const* applicationName) {
   extern portNumBits tunnelOverHTTPPortNum;
-  return RTSPClient::createNew(env, verbosityLevel, applicationName,
-			       tunnelOverHTTPPortNum);
+  return ourRTSPClient = RTSPClient::createNew(env, url, verbosityLevel, applicationName, tunnelOverHTTPPortNum);
 }
 
-char* getOptionsResponse(Medium* client, char const* url,
-			 char* username, char* password) {
-  RTSPClient* rtspClient = (RTSPClient*)client;
-  return rtspClient->sendOptionsCmd(url, username, password);
+void getOptions(RTSPClient::responseHandler* afterFunc) { 
+  ourRTSPClient->sendOptionsCommand(afterFunc, ourAuthenticator);
 }
 
-char* getSDPDescriptionFromURL(Medium* client, char const* url,
-			       char const* username, char const* password,
-			       char const* /*proxyServerName*/,
-			       unsigned short /*proxyServerPortNum*/,
-			       unsigned short /*clientStartPort*/) {
-  RTSPClient* rtspClient = (RTSPClient*)client;
-  char* result;
-  if (username != NULL && password != NULL) {
-    result = rtspClient->describeWithPassword(url, username, password);
-  } else {
-    result = rtspClient->describeURL(url);
-  }
-
-  return result;
+void getSDPDescription(RTSPClient::responseHandler* afterFunc) {
+  ourRTSPClient->sendDescribeCommand(afterFunc, ourAuthenticator);
 }
 
-Boolean clientSetupSubsession(Medium* client, MediaSubsession* subsession,
-			      Boolean streamUsingTCP) {
-  if (client == NULL || subsession == NULL) return False;
-  RTSPClient* rtspClient = (RTSPClient*)client;
-  return rtspClient->setupMediaSubsession(*subsession,
-					  False, streamUsingTCP);
+void setupSubsession(MediaSubsession* subsession, Boolean streamUsingTCP, RTSPClient::responseHandler* afterFunc) {
+  Boolean forceMulticastOnUnspecified = False;
+  ourRTSPClient->sendSetupCommand(*subsession, afterFunc, False, streamUsingTCP, forceMulticastOnUnspecified, ourAuthenticator);
 }
 
-Boolean clientStartPlayingSession(Medium* client,
-				  MediaSession* session) {
-  extern double initialSeekTime, duration, scale;
-  double endTime = initialSeekTime;
-  if (scale > 0) {
-    if (duration <= 0) endTime = -1.0f;
-    else endTime = initialSeekTime + duration;
-  } else {
-    endTime = initialSeekTime - duration;
-    if (endTime < 0) endTime = 0.0f;
-  }
-
-  if (client == NULL || session == NULL) return False;
-  RTSPClient* rtspClient = (RTSPClient*)client;
-  return rtspClient->playMediaSession(*session, initialSeekTime, endTime, (float)scale);
+void startPlayingSession(MediaSession* session, double start, double end, float scale, RTSPClient::responseHandler* afterFunc) {
+  ourRTSPClient->sendPlayCommand(*session, afterFunc, start, end, scale, ourAuthenticator);
 }
 
-Boolean clientTearDownSession(Medium* client,
-			      MediaSession* session) {
-  if (client == NULL || session == NULL) return False;
-  RTSPClient* rtspClient = (RTSPClient*)client;
-  return rtspClient->teardownMediaSession(*session);
+void tearDownSession(MediaSession* session, RTSPClient::responseHandler* afterFunc) {
+  ourRTSPClient->sendTeardownCommand(*session, afterFunc, ourAuthenticator);
 }
 
 Boolean allowProxyServers = False;
diff --git a/testProgs/playCommon.cpp b/testProgs/playCommon.cpp
index fbbfcb6..ca33219 100644
--- a/testProgs/playCommon.cpp
+++ b/testProgs/playCommon.cpp
@@ -29,9 +29,13 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 #endif
 
 // Forward function definitions:
+void continueAfterOPTIONS(RTSPClient* client, int resultCode, char* resultString);
+void continueAfterDESCRIBE(RTSPClient* client, int resultCode, char* resultString);
+void continueAfterSETUP(RTSPClient* client, int resultCode, char* resultString);
+void continueAfterPLAY(RTSPClient* client, int resultCode, char* resultString);
+void continueAfterTEARDOWN(RTSPClient* client, int resultCode, char* resultString);
+
 void setupStreams();
-void startPlayingStreams();
-void tearDownStreams();
 void closeMediaSinks();
 void subsessionAfterPlaying(void* clientData);
 void subsessionByeHandler(void* clientData);
@@ -46,6 +50,8 @@ void beginQOSMeasurement();
 char const* progName;
 UsageEnvironment* env;
 Medium* ourClient = NULL;
+Authenticator* ourAuthenticator = NULL;
+char const* streamURL = NULL;
 MediaSession* session = NULL;
 TaskToken sessionTimerTask = NULL;
 TaskToken arrivalCheckTimerTask = NULL;
@@ -64,7 +70,8 @@ int verbosityLevel = 1; // by default, print verbose output
 double duration = 0;
 double durationSlop = -1.0; // extra seconds to play at the end
 double initialSeekTime = 0.0f;
-double scale = 1.0f;
+float scale = 1.0f;
+double endTime;
 unsigned interPacketGapMaxTime = 0;
 unsigned totNumPacketsReceived = ~0; // used if checking inter-packet gaps
 Boolean playContinuously = False;
@@ -74,6 +81,7 @@ Boolean sendOptionsRequestOnly = False;
 Boolean oneFilePerFrame = False;
 Boolean notifyOnPacketArrival = False;
 Boolean streamUsingTCP = False;
+unsigned short desiredPortNum = 0;
 portNumBits tunnelOverHTTPPortNum = 0;
 char* username = NULL;
 char* password = NULL;
@@ -106,7 +114,6 @@ void usage() {
        << "]" << (supportCodecSelection ? " [-A <audio-codec-rtp-payload-format-code>|-M <mime-subtype-name>]" : "")
        << " [-s <initial-seek-time>] [-z <scale>]"
        << " [-w <width> -h <height>] [-f <frames-per-second>] [-y] [-H] [-Q [<measurement-interval>]] [-F <filename-prefix>] [-b <file-sink-buffer-size>] [-B <input-socket-buffer-size>] [-I <input-interface-ip-address>] [-m] <url> (or " << progName << " -o [-V] <url>)\n";
-  //##### Add "-R <dest-rtsp-url>" #####
   shutdown();
 }
 
@@ -125,8 +132,6 @@ int main(int argc, char** argv) {
   signal(SIGUSR1, signalHandlerShutdown);
 #endif
 
-  unsigned short desiredPortNum = 0;
-
   // unfortunately we can't use getopt() here, as Windoze doesn't have it
   while (argc > 2) {
     char* const opt = argv[1];
@@ -304,6 +309,9 @@ int main(int argc, char** argv) {
 	  ++argv; --argc;
 	}
       }
+
+      ourAuthenticator = new Authenticator;
+      ourAuthenticator->setUsernameAndPassword(username, password);
       break;
     }
 
@@ -481,10 +489,10 @@ int main(int argc, char** argv) {
     durationSlop = qosMeasurementIntervalMS > 0 ? 0.0 : 5.0;
   }
 
-  char* url = argv[1];
+  streamURL = argv[1];
 
   // Create our client object:
-  ourClient = createClient(*env, verbosityLevel, progName);
+  ourClient = createClient(*env, streamURL, verbosityLevel, progName);
   if (ourClient == NULL) {
     *env << "Failed to create " << clientProtocolName
 		<< " client: " << env->getResultMsg() << "\n";
@@ -493,34 +501,40 @@ int main(int argc, char** argv) {
 
   if (sendOptionsRequest) {
     // Begin by sending an "OPTIONS" command:
-    char* optionsResponse
-      = getOptionsResponse(ourClient, url, username, password);
-    if (sendOptionsRequestOnly) {
-      if (optionsResponse == NULL) {
-	*env << clientProtocolName << " \"OPTIONS\" request failed: "
-	     << env->getResultMsg() << "\n";
-      } else {
-	*env << clientProtocolName << " \"OPTIONS\" request returned: "
-	     << optionsResponse << "\n";
-      }
-      shutdown();
+    getOptions(continueAfterOPTIONS);
+  } else {
+    continueAfterOPTIONS(NULL, 0, NULL);
+  }
+
+  // All subsequent activity takes place within the event loop:
+  env->taskScheduler().doEventLoop(); // does not return
+
+  return 0; // only to prevent compiler warning
+}
+
+void continueAfterOPTIONS(RTSPClient*, int resultCode, char* resultString) {
+  if (sendOptionsRequestOnly) {
+    if (resultCode != 0) {
+      *env << clientProtocolName << " \"OPTIONS\" request failed: " << resultString << "\n";
+    } else {
+      *env << clientProtocolName << " \"OPTIONS\" request returned: " << resultString << "\n";
     }
-    delete[] optionsResponse;
+    shutdown();
   }
+  delete[] resultString;
 
-  // Open the URL, to get a SDP description:
-  char* sdpDescription
-    = getSDPDescriptionFromURL(ourClient, url, username, password,
-			       proxyServerName, proxyServerPortNum,
-			       desiredPortNum);
-  if (sdpDescription == NULL) {
-    *env << "Failed to get a SDP description from URL \"" << url
-		<< "\": " << env->getResultMsg() << "\n";
+  // Next, get a SDP description for the stream:
+  getSDPDescription(continueAfterDESCRIBE);
+}
+
+void continueAfterDESCRIBE(RTSPClient*, int resultCode, char* resultString) {
+  if (resultCode != 0) {
+    *env << "Failed to get a SDP description from URL \"" << streamURL << "\": " << resultString << "\n";
     shutdown();
   }
 
-  *env << "Opened URL \"" << url
-	  << "\", returning a SDP description:\n" << sdpDescription << "\n";
+  char* sdpDescription = resultString;
+  *env << "Opened URL \"" << streamURL << "\", returning a SDP description:\n" << sdpDescription << "\n";
 
   // Create a media session object from this SDP description:
   session = MediaSession::createNew(*env, sdpDescription);
@@ -613,6 +627,41 @@ int main(int argc, char** argv) {
 
   // Perform additional 'setup' on each subsession, before playing them:
   setupStreams();
+}
+
+MediaSubsession *subsession;
+Boolean madeProgress = False;
+void continueAfterSETUP(RTSPClient*, int resultCode, char* resultString) {
+  if (resultCode == 0) {
+      *env << "Setup \"" << subsession->mediumName()
+	   << "/" << subsession->codecName()
+	   << "\" subsession (client ports " << subsession->clientPortNum()
+	   << "-" << subsession->clientPortNum()+1 << ")\n";
+      madeProgress = True;
+  } else {
+    *env << "Failed to setup \"" << subsession->mediumName()
+	 << "/" << subsession->codecName()
+	 << "\" subsession: " << env->getResultMsg() << "\n";
+  }
+
+  // Set up the next subsession, if any:
+  setupStreams();
+}
+
+void setupStreams() {
+  static MediaSubsessionIterator* setupIter = NULL;
+  if (setupIter == NULL) setupIter = new MediaSubsessionIterator(*session);
+  while ((subsession = setupIter->next()) != NULL) {
+    // We have another subsession left to set up:
+    if (subsession->clientPortNum() == 0) continue; // port # was not set
+
+    setupSubsession(subsession, streamUsingTCP, continueAfterSETUP);
+    return;
+  }
+
+  // We're done setting up subsessions.
+  delete setupIter;
+  if (!madeProgress) shutdown();
 
   // Create output files:
   if (createReceivers) {
@@ -648,7 +697,7 @@ int main(int argc, char** argv) {
     } else {
       // Create and start "FileSink"s for each subsession:
       madeProgress = False;
-      iter.reset();
+      MediaSubsessionIterator iter(*session);
       while ((subsession = iter.next()) != NULL) {
 	if (subsession->readSource() == NULL) continue; // was not initiated
 
@@ -675,7 +724,8 @@ int main(int argc, char** argv) {
 	    (strcmp(subsession->codecName(), "H264") == 0)) {
 	  // For H.264 video stream, we use a special sink that insert start_codes:
 	  fileSink = H264VideoFileSink::createNew(*env, outFileName,
-						 fileSinkBufferSize, oneFilePerFrame);
+						  subsession->fmtp_spropparametersets(),
+						  fileSinkBufferSize, oneFilePerFrame);
 	} else {
 	  // Normal case:
 	  fileSink = FileSink::createNew(*env, outFileName,
@@ -728,47 +778,27 @@ int main(int argc, char** argv) {
   }
 
   // Finally, start playing each subsession, to start the data flow:
-
-  startPlayingStreams();
-
-  env->taskScheduler().doEventLoop(); // does not return
-
-  return 0; // only to prevent compiler warning
-}
-
-
-void setupStreams() {
-  MediaSubsessionIterator iter(*session);
-  MediaSubsession *subsession;
-  Boolean madeProgress = False;
-
-  while ((subsession = iter.next()) != NULL) {
-    if (subsession->clientPortNum() == 0) continue; // port # was not set
-
-    if (!clientSetupSubsession(ourClient, subsession, streamUsingTCP)) {
-      *env << "Failed to setup \"" << subsession->mediumName()
-		<< "/" << subsession->codecName()
-		<< "\" subsession: " << env->getResultMsg() << "\n";
-    } else {
-      *env << "Setup \"" << subsession->mediumName()
-		<< "/" << subsession->codecName()
-		<< "\" subsession (client ports " << subsession->clientPortNum()
-		<< "-" << subsession->clientPortNum()+1 << ")\n";
-      madeProgress = True;
-    }
-  }
-  if (!madeProgress) shutdown();
-}
-
-void startPlayingStreams() {
   if (duration == 0) {
     if (scale > 0) duration = session->playEndTime() - initialSeekTime; // use SDP end time
     else if (scale < 0) duration = initialSeekTime;
   }
   if (duration < 0) duration = 0.0;
 
-  if (!clientStartPlayingSession(ourClient, session)) {
-    *env << "Failed to start playing session: " << env->getResultMsg() << "\n";
+  endTime = initialSeekTime;
+  if (scale > 0) {
+    if (duration <= 0) endTime = -1.0f;
+    else endTime = initialSeekTime + duration;
+  } else {
+    endTime = initialSeekTime - duration;
+    if (endTime < 0) endTime = 0.0f;
+  }
+
+  startPlayingSession(session, initialSeekTime, endTime, scale, continueAfterPLAY);
+}
+
+void continueAfterPLAY(RTSPClient*, int resultCode, char* resultString) {
+  if (resultCode != 0) {
+    *env << "Failed to start playing session: " << resultString << "\n";
     shutdown();
   } else {
     *env << "Started playing session\n";
@@ -815,12 +845,6 @@ void startPlayingStreams() {
   checkInterPacketGaps(NULL);
 }
 
-void tearDownStreams() {
-  if (session == NULL) return;
-
-  clientTearDownSession(ourClient, session);
-}
-
 void closeMediaSinks() {
   Medium::close(qtOut);
   Medium::close(aviOut);
@@ -871,7 +895,7 @@ void sessionAfterPlaying(void* /*clientData*/) {
     shutdown(0);
   } else {
     // We've been asked to play the stream(s) over again:
-    startPlayingStreams();
+    startPlayingSession(session, initialSeekTime, endTime, scale, continueAfterPLAY);
   }
 }
 
@@ -1031,7 +1055,7 @@ void printQOSData(int exitCode) {
 	numPacketsExpected = curQOSRecord->totNumPacketsExpected;
       }
       *env << "num_packets_received\t" << numPacketsReceived << "\n";
-      *env << "num_packets_lost\t" << numPacketsExpected - numPacketsReceived << "\n";
+      *env << "num_packets_lost\t" << int(numPacketsExpected - numPacketsReceived) << "\n";
       
       if (curQOSRecord != NULL) {
 	unsigned secsDiff = curQOSRecord->measurementEndTime.tv_sec
@@ -1088,7 +1112,9 @@ void printQOSData(int exitCode) {
   delete qosRecordHead;
 }
 
+int shutdownExitCode;
 void shutdown(int exitCode) {
+  shutdownExitCode = exitCode;
   if (env != NULL) {
     env->taskScheduler().unscheduleDelayedTask(sessionTimerTask);
     env->taskScheduler().unscheduleDelayedTask(arrivalCheckTimerTask);
@@ -1100,18 +1126,25 @@ void shutdown(int exitCode) {
     printQOSData(exitCode);
   }
 
-  // Close our output files:
-  closeMediaSinks();
-
   // Teardown, then shutdown, any outstanding RTP/RTCP subsessions
-  tearDownStreams();
+  if (session != NULL) {
+    tearDownSession(session, continueAfterTEARDOWN);
+  } else {
+    continueAfterTEARDOWN(NULL, 0, NULL);
+  }
+}
+
+void continueAfterTEARDOWN(RTSPClient*, int /*resultCode*/, char* /*resultString*/) {
+  // Now that we've stopped any more incoming data from arriving, close our output files:
+  closeMediaSinks();
   Medium::close(session);
 
   // Finally, shut down our client:
+  delete ourAuthenticator;
   Medium::close(ourClient);
 
   // Adios...
-  exit(exitCode);
+  exit(shutdownExitCode);
 }
 
 void signalHandlerShutdown(int /*sig*/) {
diff --git a/testProgs/playCommon.hh b/testProgs/playCommon.hh
index 57679f3..c2f35da 100644
--- a/testProgs/playCommon.hh
+++ b/testProgs/playCommon.hh
@@ -19,29 +19,21 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 
 #include "liveMedia.hh"
 
-extern Medium* createClient(UsageEnvironment& env, int verbosityLevel,
-			    char const* applicationName);
+extern Medium* createClient(UsageEnvironment& env, char const* URL, int verbosityLevel, char const* applicationName);
+extern RTSPClient* ourRTSPClient;
+extern SIPClient* ourSIPClient;
 
-extern char* getOptionsResponse(Medium* client, char const* url,
-				char* username, char* password);
+extern void getOptions(RTSPClient::responseHandler* afterFunc);
 
-extern char* getSDPDescriptionFromURL(Medium* client, char const* url,
-				      char const* username,
-				      char const* password,
-				      char const* proxyServerName,
-				      unsigned short proxyServerPortNum,
-				      unsigned short clientStartPortNum);
+extern void getSDPDescription(RTSPClient::responseHandler* afterFunc);
 
-extern Boolean clientSetupSubsession(Medium* client,
-				     MediaSubsession* subsession,
-				     Boolean streamUsingTCP);
+extern void setupSubsession(MediaSubsession* subsession, Boolean streamUsingTCP, RTSPClient::responseHandler* afterFunc);
 
-extern Boolean clientStartPlayingSession(Medium* client,
-					 MediaSession* session);
+extern void startPlayingSession(MediaSession* session, double start, double end, float scale, RTSPClient::responseHandler* afterFunc);
 
-extern Boolean clientTearDownSession(Medium* client,
-				     MediaSession* session);
+extern void tearDownSession(MediaSession* session, RTSPClient::responseHandler* afterFunc);
 
+extern Authenticator* ourAuthenticator;
 extern Boolean allowProxyServers;
 extern Boolean controlConnectionUsesTCP;
 extern Boolean supportCodecSelection;
diff --git a/testProgs/playSIP.cpp b/testProgs/playSIP.cpp
index 9f6ca5c..0dca406 100644
--- a/testProgs/playSIP.cpp
+++ b/testProgs/playSIP.cpp
@@ -20,8 +20,8 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 #include "playCommon.hh"
 #include "SIPClient.hh"
 
-Medium* createClient(UsageEnvironment& env,
-                     int verbosityLevel, char const* applicationName) {
+SIPClient* ourSIPClient = NULL;
+Medium* createClient(UsageEnvironment& env, char const* /*url*/, int verbosityLevel, char const* applicationName) {
   // First, trim any directory prefixes from "applicationName":
   char const* suffix = &applicationName[strlen(applicationName)];
   while (suffix != applicationName) {
@@ -34,74 +34,74 @@ Medium* createClient(UsageEnvironment& env,
 
   extern unsigned char desiredAudioRTPPayloadFormat;
   extern char* mimeSubtype;
-  return SIPClient::createNew(env,
-			      desiredAudioRTPPayloadFormat, mimeSubtype,
-			      verbosityLevel, applicationName);
+  return ourSIPClient = SIPClient::createNew(env, desiredAudioRTPPayloadFormat, mimeSubtype, verbosityLevel, applicationName);
 }
 
-char* getOptionsResponse(Medium* client, char const* url,
-			 char* username, char* password) {
-  SIPClient* sipClient = (SIPClient*)client;
-  sipClient->envir().setResultMsg("NOT SUPPORTED IN CLIENT");//#####
-  return NULL;//#####
+void getOptions(RTSPClient::responseHandler* afterFunc) { 
+  ourSIPClient->envir().setResultMsg("NOT SUPPORTED IN CLIENT");
+  afterFunc(NULL, -1, strDup(ourSIPClient->envir().getResultMsg()));
 }
 
-char* getSDPDescriptionFromURL(Medium* client, char const* url,
-			       char const* username, char const* password,
-			       char const* proxyServerName,
-			       unsigned short proxyServerPortNum,
-			       unsigned short clientStartPortNum) {
-  SIPClient* sipClient = (SIPClient*)client;
-
+void getSDPDescription(RTSPClient::responseHandler* afterFunc) {
+  extern char* proxyServerName;
   if (proxyServerName != NULL) {
     // Tell the SIP client about the proxy:
     NetAddressList addresses(proxyServerName);
     if (addresses.numAddresses() == 0) {
-      client->envir() << "Failed to find network address for \""
-		      << proxyServerName << "\"\n";
+      ourSIPClient->envir() << "Failed to find network address for \"" << proxyServerName << "\"\n";
     } else {
       NetAddress address = *(addresses.firstAddress());
       unsigned proxyServerAddress // later, allow for IPv6 #####
 	= *(unsigned*)(address.data());
+      extern unsigned short proxyServerPortNum;
       if (proxyServerPortNum == 0) proxyServerPortNum = 5060; // default
 
-      sipClient->setProxyServer(proxyServerAddress, proxyServerPortNum);
+      ourSIPClient->setProxyServer(proxyServerAddress, proxyServerPortNum);
     }
   }
 
+  extern unsigned short desiredPortNum;
+  unsigned short clientStartPortNum = desiredPortNum;
   if (clientStartPortNum == 0) clientStartPortNum = 8000; // default
-  sipClient->setClientStartPortNum(clientStartPortNum);
+  ourSIPClient->setClientStartPortNum(clientStartPortNum);
 
+  extern char const* streamURL;
+  char const* username = ourAuthenticator->username();
+  char const* password = ourAuthenticator->password();
   char* result;
   if (username != NULL && password != NULL) {
-    result = sipClient->inviteWithPassword(url, username, password);
+    result = ourSIPClient->inviteWithPassword(streamURL, username, password);
   } else {
-    result = sipClient->invite(url);
+    result = ourSIPClient->invite(streamURL);
   }
 
-  return result;
+  int resultCode = result == NULL ? -1 : 0;
+  afterFunc(NULL, resultCode, strDup(result));
 }
 
-Boolean clientSetupSubsession(Medium* client, MediaSubsession* subsession,
-			      Boolean streamUsingTCP) {
+void setupSubsession(MediaSubsession* subsession, Boolean /*streamUsingTCP*/, RTSPClient::responseHandler* afterFunc) {
   subsession->sessionId = "mumble"; // anything that's non-NULL will work
-  return True;
+
+  afterFunc(NULL, 0, NULL);
 }
 
-Boolean clientStartPlayingSession(Medium* client,
-				  MediaSession* /*session*/) {
-  SIPClient* sipClient = (SIPClient*)client;
-  return sipClient->sendACK();
-  //##### This isn't quite right, because we should really be allowing
-  //##### for the possibility of this ACK getting lost, by retransmitting
-  //##### it *each time* we get a 2xx response from the server.
+void startPlayingSession(MediaSession* /*session*/, double /*start*/, double /*end*/, float /*scale*/, RTSPClient::responseHandler* afterFunc) {
+  if (ourSIPClient->sendACK()) {
+    //##### This isn't quite right, because we should really be allowing
+    //##### for the possibility of this ACK getting lost, by retransmitting
+    //##### it *each time* we get a 2xx response from the server.
+    afterFunc(NULL, 0, NULL);
+  } else {
+    afterFunc(NULL, -1, strDup(ourSIPClient->envir().getResultMsg()));
+  }
 }
 
-Boolean clientTearDownSession(Medium* client,
-			      MediaSession* /*session*/) {
-  if (client == NULL) return False;
-  SIPClient* sipClient = (SIPClient*)client;
-  return sipClient->sendBYE();
+void tearDownSession(MediaSession* /*session*/, RTSPClient::responseHandler* afterFunc) {
+  if (ourSIPClient == NULL || ourSIPClient->sendBYE()) {
+    afterFunc(NULL, 0, NULL);
+  } else {
+    afterFunc(NULL, -1, strDup(ourSIPClient->envir().getResultMsg()));
+  }
 }
 
 Boolean allowProxyServers = True;

-- 
liblivemedia packaging



More information about the pkg-multimedia-commits mailing list