[Live-devel] PATCH RTSP KeepAlive Support.

Glen Gray glen at lincor.com
Fri Jan 20 16:16:08 PST 2006


Hey Guys,

Here's my first stab at supporting KeepAlive sessions.

It's been working fine on my Kasenna MediaBase XMP 7.2.1 server. Let me 
know what you think...

One point to note. There was a bug on on the Kasenna where the normal 
vlc/live555 user-agent string,
e.g. "VLC Media Player (LIVE555 Streaming Media v2005.10.05)" when 
appened with the appropriate KeepAlive flag characters of "_KA" just 
wouldn't work. I've logged the bug with Kasenna, but have implemented a 
workaround in the form of a simplified user-agent string, based in part 
on their RTSP programmers manual example strings and my current usage. 
It's currently hardcoded as VLC_LIVE_PLAYER_KA. Lame I know.

-- 
Glen Gray <glen at lincor.com>              Digital Depot, Thomas Street
Senior Software Engineer                            Dublin 8, Ireland
Lincor Solutions Ltd.                          Ph: +353 (0) 1 4893682

-------------- next part --------------
diff -uNr live/liveMedia/include/RTSPClient.hh live.lincor/liveMedia/include/RTSPClient.hh
--- live/liveMedia/include/RTSPClient.hh	2006-01-05 02:01:01.000000000 +0000
+++ live.lincor/liveMedia/include/RTSPClient.hh	2006-01-20 15:30:11.000000000 +0000
@@ -36,12 +36,14 @@
   static RTSPClient* createNew(UsageEnvironment& env,
 			       int verbosityLevel = 0,
 			       char const* applicationName = NULL,
-			       portNumBits tunnelOverHTTPPortNum = 0);
+			       portNumBits tunnelOverHTTPPortNum = 0,
+                               Boolean serverKeepAlve = False);
   // If "tunnelOverHTTPPortNum" is non-zero, we tunnel RTSP (and RTP)
   // 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; }
+  long keepAliveTimeout() const { return fKeepAliveTimeout; }
 
   static Boolean lookupByName(UsageEnvironment& env,
 			      char const* sourceName,
@@ -85,7 +87,9 @@
       // 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. 
+      // Note, however, that not all servers will support this.
+      // If serverKeepAlive is True, then we'll append "_KA" to our user agent
+      // string and watch out for ";timeout=n" in the "Session:" response 
 
   Boolean playMediaSession(MediaSession& session,
 			   float start = 0.0f, float end = -1.0f,
@@ -124,6 +128,10 @@
       // Issues a RTSP "GET_PARAMETER" command on "subsession".
       // Returns True iff this command succeeds
 
+  Boolean sendMediaSessionKeepAlive(void);
+      // Issues a RTSP "GET_PARAMETER" command on "subsession" with no body.
+      // Returns True iff this command succeeds
+
   Boolean teardownMediaSession(MediaSession& session);
       // Issues an aggregate RTSP "TEARDOWN" command on "session".
       // Returns True iff this command succeeds
@@ -153,7 +161,8 @@
 
 private:
   RTSPClient(UsageEnvironment& env, int verbosityLevel,
-	     char const* applicationName, portNumBits tunnelOverHTTPPortNum);
+	     char const* applicationName, portNumBits tunnelOverHTTPPortNum,
+             Boolean serverKeepAlive);
       // called only by createNew();
 
   void reset();
@@ -190,11 +199,21 @@
 			      char const*& suffix);
   Boolean setupHTTPTunneling(char const* urlSuffix, Authenticator* authenticator);
 
+  // Create a TaskScheduler task to send a KeepAlive message to the server
+  void scheduleKeepAlive(void);
+
+  // Clear down an existing task and then call the scheduleKeepAlive func
+  void rescheduleKeepAlive(void);
+
+  // Polls the server with a GET_PARAMETER call with an empty paylod. If the 
+  // server stops getting these, it'll teardown the stream itself.
+  static void mediaSessionKeepAlive (RTSPClient* rtspClient);
+
 private:
   int fVerbosityLevel;
   portNumBits fTunnelOverHTTPPortNum;
   char* fUserAgentHeaderStr;
-      unsigned fUserAgentHeaderStrSize;
+  unsigned fUserAgentHeaderStrSize;
   int fInputSocketNum, fOutputSocketNum;
   unsigned fServerAddress;
   static unsigned fCSeq; // sequence number, used in consecutive requests
@@ -220,6 +239,10 @@
 
   // The following is used to deal with Microsoft servers' non-standard use of RTSP:
   Boolean fServerIsMicrosoft;
+
+  // The following is used to determine if we us the KeepAlive session method
+  Boolean fServerKeepAlive;
+  long fKeepAliveTimeout; //Server supplied timeout for session KeepAlive
 };
 
 #endif
diff -uNr live/liveMedia/RTSPClient.cpp live.lincor/liveMedia/RTSPClient.cpp
--- live/liveMedia/RTSPClient.cpp	2006-01-05 02:01:02.000000000 +0000
+++ live.lincor/liveMedia/RTSPClient.cpp	2006-01-20 15:29:28.000000000 +0000
@@ -73,9 +73,12 @@
 RTSPClient* RTSPClient::createNew(UsageEnvironment& env,
 				  int verbosityLevel,
 				  char const* applicationName,
-				  portNumBits tunnelOverHTTPPortNum) {
+				  portNumBits tunnelOverHTTPPortNum,
+                                  Boolean serverKeepAlive) {
   return new RTSPClient(env, verbosityLevel,
-			applicationName, tunnelOverHTTPPortNum);
+			applicationName, 
+                        tunnelOverHTTPPortNum, 
+                        serverKeepAlive);
 }
 
 Boolean RTSPClient::lookupByName(UsageEnvironment& env,
@@ -99,7 +102,8 @@
 
 RTSPClient::RTSPClient(UsageEnvironment& env,
 		       int verbosityLevel, char const* applicationName,
-		       portNumBits tunnelOverHTTPPortNum)
+		       portNumBits tunnelOverHTTPPortNum,
+                       Boolean serverKeepAlive)
   : Medium(env),
     fVerbosityLevel(verbosityLevel),
     fTunnelOverHTTPPortNum(tunnelOverHTTPPortNum),
@@ -109,7 +113,8 @@
     fRealChallengeStr(NULL), fRealETagStr(NULL),
 #endif
     fServerIsKasenna(False), fKasennaContentType(NULL),
-    fServerIsMicrosoft(False)
+    fServerIsMicrosoft(False),
+    fServerKeepAlive(serverKeepAlive), fKeepAliveTimeout(0)
 {
   fResponseBufferSize = 20000;
   fResponseBuffer = new char[fResponseBufferSize+1];
@@ -118,19 +123,27 @@
   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 = "";
+    
+  if (fServerKeepAlive == False) {
+    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);
   } else {
-    libPrefix = " (";
-    libSuffix = ")";
+    char const* const formatStr = "User-Agent: VLC_LIVE_PLAYER_KA\r\n";
+    unsigned headerSize = strlen(formatStr);
+    fUserAgentHeaderStr = new char[headerSize];
+    sprintf(fUserAgentHeaderStr, formatStr);
   }
-  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);
 }
 
@@ -942,6 +955,25 @@
       nextLineStart = getLine(lineStart);
 
       if (sscanf(lineStart, "Session: %[^;]", sessionId) == 1) {
+        if (fServerKeepAlive == True) {
+          // If we find a ";timeout=" string in the response, then we are using
+          // the Keep Alive method
+          char *tmp = NULL;
+
+          if ((tmp = strstr (lineStart, "; timeout = ")) != NULL) {
+            char *strTimeout = NULL;
+
+            tmp = strstr (tmp, "=");
+            if (strlen (tmp) > 2) {
+              strTimeout = strDup (tmp+2);
+              fKeepAliveTimeout = strtol (strTimeout, NULL, 10);
+              fKeepAliveTimeout = fKeepAliveTimeout * 1000000;
+              scheduleKeepAlive();
+              
+              delete[] strTimeout;
+            }
+          } 
+        }
 	subsession.sessionId = strDup(sessionId);
 	delete[] fLastSessionId; fLastSessionId = strDup(sessionId);
 	continue;
@@ -1585,6 +1617,69 @@
   return False;
 }
 
+Boolean RTSPClient::sendMediaSessionKeepAlive(void) {
+  char* cmd = NULL;
+  do {
+    // First, make sure that we have a RTSP session in progress
+    if (fLastSessionId == NULL) {
+      envir().setResultMsg(NoSessionErr);
+      break;
+    }
+
+    // Send the GET_PARAMETER command with no body:
+    // First, construct an authenticator string:
+    char* authenticatorStr
+      = createAuthenticatorString(&fCurrentAuthenticator,
+				  "GET_PARAMETER", fBaseURL);
+
+    char* 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);
+    delete[] authenticatorStr;
+
+    if (!sendRequest(cmd, "GET_PARAMETER")) break;
+
+    // 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;
+
+    // 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;
+    }
+
+    delete[] cmd;
+    return True;
+  } while (0);
+  
+  delete[] cmd;
+
+  return False;
+}
+
 Boolean RTSPClient::teardownMediaSession(MediaSession& session) {
   char* cmd = NULL;
   do {
@@ -2379,3 +2474,24 @@
   delete[] cmd;
   return False;
 }
+
+void RTSPClient::scheduleKeepAlive(void) 
+{
+  nextTask() = envir().taskScheduler().scheduleDelayedTask(fKeepAliveTimeout,
+                                (TaskFunc*)RTSPClient::mediaSessionKeepAlive, 
+                                this);
+}
+
+void RTSPClient::rescheduleKeepAlive(void) 
+{
+  if (nextTask()) {
+    envir().taskScheduler().unscheduleDelayedTask(nextTask());
+  }
+  scheduleKeepAlive();
+}
+
+void RTSPClient::mediaSessionKeepAlive(RTSPClient* rtspClient)
+{
+  rtspClient->sendMediaSessionKeepAlive();
+  rtspClient->rescheduleKeepAlive();
+}


More information about the live-devel mailing list