--- live-2013.06.14/liveMedia/include/RTSPClient.hh 2013-06-14 06:24:48.000000000 -0400 +++ live-2013.01.04/liveMedia/include/RTSPClient.hh 2013-01-04 00:07:48.000000000 -0500 @@ -308,6 +305,67 @@ private: char fSessionCookie[33]; unsigned fSessionCookieCounter; Boolean fHTTPTunnelingConnectionIsPending; + +#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); + + static Boolean parseRTSPURLUsernamePassword(char const* url, + char*& username, char*& password); +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 }; --- live-2013.06.14/liveMedia/RTSPClient.cpp 2013-06-14 06:24:48.000000000 -0400 +++ live-2013.01.04/liveMedia/RTSPClient.cpp 2013-01-04 00:07:48.000000000 -0500 @@ -1765,6 +1754,330 @@ RTSPClient::RequestRecord* RTSPClient::R } return NULL; } + + +#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, -1); +} + +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; + } + + 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); + + // 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; +} + +char* RTSPClient::describeWithPassword(char const* url, + char const* username, char const* password, + Boolean allowKasennaProtocol, int timeout) { + Authenticator authenticator(username, password); + return describeURL(url, &authenticator, allowKasennaProtocol, timeout); +} + +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(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(username,password); + haveAllocatedAuthenticator = True; + + result = sendOptionsCmd(url, username, password, authenticator, timeout); + if (result != NULL) { // We are already authorized + delete authenticator; + return result; + } + + // The "realm" field should have been filled in: + if (authenticator->realm() == NULL) { + // We haven't been given enough information to try again, so fail: + delete authenticator; + return NULL; + } + } + } + + 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); + if (haveAllocatedAuthenticator) delete authenticator; + + // 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::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); + + // Now block (but handling events) until we get a response (or a timeout): + envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface); + delete[] fResultString; + return fResultCode == 0; +} + +Boolean RTSPClient +::announceWithPassword(char const* url, char const* sdpDescription, + char const* username, char const* password, int timeout) { + Authenticator authenticator(username,password); + return announceSDPDescription(url, sdpDescription, &authenticator, timeout); +} + +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::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; +} + +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; +} + +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; +} + +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; +} + +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::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; +} + +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; +} + +Boolean RTSPClient::teardownMediaSession(MediaSession& session) { + fWatchVariableForSyncInterface = 0; + fTimeoutTask = NULL; + (void)sendTeardownCommand(session, NULL); + return True; // we don't wait for a response to the "TEARDOWN" +} + +Boolean RTSPClient::teardownMediaSubsession(MediaSubsession& subsession) { + fWatchVariableForSyncInterface = 0; + fTimeoutTask = NULL; + (void)sendTeardownCommand(subsession, NULL); + return True; // we don't wait for a response to the "TEARDOWN" +} + +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 (atIndex == 0) break; // no '@' found + + char* urlCopy = strDup(url); + urlCopy[atIndex] = '\0'; + if (colonIndex > 0) { + urlCopy[colonIndex] = '\0'; + password = strDup(&urlCopy[colonIndex+1]); + } else { + password = strDup(""); + } + username = strDup(&urlCopy[usernameIndex]); + delete[] urlCopy; + + return True; + } while (0); + + return False; +} + +void RTSPClient::responseHandlerForSyncInterface(RTSPClient* rtspClient, int responseCode, char* responseString) { + if (rtspClient != NULL) rtspClient->responseHandlerForSyncInterface1(responseCode, responseString); +} + +void RTSPClient::responseHandlerForSyncInterface1(int responseCode, char* responseString) { + // If we have a 'timeout task' pending, then unschedule it: + if (fTimeoutTask != NULL) envir().taskScheduler().unscheduleDelayedTask(fTimeoutTask); + + // Set result values: + fResultCode = responseCode; + fResultString = responseString; + + // Signal a break from the event loop (thereby returning from the blocking command): + fWatchVariableForSyncInterface = ~0; +} + +void RTSPClient::timeoutHandlerForSyncInterface(void* rtspClient) { + if (rtspClient != NULL) ((RTSPClient*)rtspClient)->timeoutHandlerForSyncInterface1(); +} + +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; + + // 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 ////////// HandlerServerForREGISTERCommand implementation /////////