void RTSPServerSupportingHTTPStreaming::RTSPClientConnectionSupportingHTTPStreaming ::handleHTTPCmd_StreamingGET(char const* urlSuffix, char const* /*fullRequestStr*/) { // If "urlSuffix" ends with "?segment=<offset-in-seconds>,<duration-in-seconds>", then strip this off, and send the // specified segment. Otherwise, construct and send a playlist that consists of segments from the specified file. do { char const* questionMarkPos = strrchr(urlSuffix, '?'); if (questionMarkPos == NULL) break; unsigned offsetInSeconds, durationInSeconds; if (sscanf(questionMarkPos, "?segment=%u,%u", &offsetInSeconds, &durationInSeconds) != 2) break; char* streamName = strDup(urlSuffix); streamName[questionMarkPos-urlSuffix] = '\0'; do { ServerMediaSession* session = fOurServer.lookupServerMediaSession(streamName); if (session == NULL) { handleHTTPCmd_notFound(); break; } // We can't send multi-subsession streams over HTTP (because there's no defined way to multiplex more than one subsession). // Therefore, use the first (and presumed only) substream: ServerMediaSubsessionIterator iter(*session); ServerMediaSubsession* subsession = iter.next(); if (subsession == NULL) { // Treat an 'empty' ServerMediaSession the same as one that doesn't exist at all: handleHTTPCmd_notFound(); break; } // Call "getStreamParameters()" to create the stream's source. (Because we're not actually streaming via RTP/RTCP, most // of the parameters to the call are dummy.) ++fClientSessionId; Port clientRTPPort(0), clientRTCPPort(0), serverRTPPort(0), serverRTCPPort(0); netAddressBits destinationAddress = 0; u_int8_t destinationTTL = 0; Boolean isMulticast = False; void* streamToken; subsession->getStreamParameters(fClientSessionId, 0, clientRTPPort,clientRTCPPort, -1,0,0, destinationAddress,destinationTTL, isMulticast, serverRTPPort,serverRTCPPort, streamToken); // Seek the stream source to the desired place, with the desired duration, and (as a side effect) get the number of bytes: double dOffsetInSeconds = (double)offsetInSeconds; u_int64_t numBytes; subsession->seekStream(fClientSessionId, streamToken, dOffsetInSeconds, (double)durationInSeconds, numBytes); unsigned numTSBytesToStream = (unsigned)numBytes; if (numTSBytesToStream == 0) { // For some reason, we do not know the size of the requested range. We can't handle this request: handleHTTPCmd_notSupported(); break; } // Construct our response: snprintf((char*)fResponseBuffer, sizeof fResponseBuffer, "HTTP/1.1 200 OK\r\n" "%s" "Server: LIVE555 Streaming Media v%s\r\n" "%s" "Content-Length: %d\r\n" "Content-Type: text/plain; charset=ISO-8859-1\r\n" "\r\n", dateHeader(), LIVEMEDIA_LIBRARY_VERSION_STRING, lastModifiedHeader(streamName), numTSBytesToStream); // Send the response now, because we're about to add more data (from the source): send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0); fResponseBuffer[0] = '\0'; // We've already sent the response. This tells the calling code not to send it again. // Ask the media source to deliver - to the TCP sink - the desired data: if (fStreamSource != NULL) { // sanity check if (fTCPSink != NULL) fTCPSink->stopPlaying(); Medium::close(fStreamSource); } fStreamSource = subsession->getStreamSource(streamToken); if (fStreamSource != NULL) { if (fTCPSink == NULL) fTCPSink = TCPStreamSink::createNew(envir(), fClientOutputSocket); fTCPSink->startPlaying(*fStreamSource, afterStreaming, this); } } while(0); delete[] streamName; return; } while (0); // "urlSuffix" does not end with "?segment=<offset-in-seconds>,<duration-in-seconds>". // Construct and send a playlist that describes segments from the specified file. // First, make sure that the named file exists, and is streamable: ServerMediaSession* session = fOurServer.lookupServerMediaSession(urlSuffix); if (session == NULL) { handleHTTPCmd_notFound(); return; } // To be able to construct a playlist for the requested file, we need to know its duration: float duration = session->duration(); if (duration <= 0.0) { // We can't handle this request: handleHTTPCmd_notSupported(); return; } // Now, construct the playlist. It will consist of a prefix, one or more media file specifications, and a suffix: unsigned const maxIntLen = 10; // >= the maximum possible strlen() of an integer in the playlist char const* const playlistPrefixFmt = "#EXTM3U\r\n" "#EXT-X-ALLOW-CACHE:YES\r\n" "#EXT-X-MEDIA-SEQUENCE:0\r\n" "#EXT-X-TARGETDURATION:%d\r\n"; unsigned const playlistPrefixFmt_maxLen = strlen(playlistPrefixFmt) + maxIntLen; char const* const playlistMediaFileSpecFmt = "#EXTINF:%d,\r\n" "%s?segment=%d,%d\r\n"; unsigned const playlistMediaFileSpecFmt_maxLen = strlen(playlistMediaFileSpecFmt) + maxIntLen + strlen(urlSuffix) + 2*maxIntLen; char const* const playlistSuffixFmt = "#EXT-X-ENDLIST\r\n"; unsigned const playlistSuffixFmt_maxLen = strlen(playlistSuffixFmt); // Figure out the 'target duration' that will produce a playlist that will fit in our response buffer. (But make it at least 10s.) unsigned const playlistMaxSize = 10000; unsigned const mediaFileSpecsMaxSize = playlistMaxSize - (playlistPrefixFmt_maxLen + playlistSuffixFmt_maxLen); unsigned const maxNumMediaFileSpecs = mediaFileSpecsMaxSize/playlistMediaFileSpecFmt_maxLen; unsigned targetDuration = (unsigned)(duration/maxNumMediaFileSpecs + 1); if (targetDuration < 10) targetDuration = 10; char* playlist = new char[playlistMaxSize]; char* s = playlist; sprintf(s, playlistPrefixFmt, targetDuration); s += strlen(s); unsigned durSoFar = 0; while (1) { unsigned dur = targetDuration < duration ? targetDuration : (unsigned)duration; duration -= dur; sprintf(s, playlistMediaFileSpecFmt, dur, urlSuffix, durSoFar, dur); s += strlen(s); if (duration < 1.0) break; durSoFar += dur; } sprintf(s, playlistSuffixFmt); s += strlen(s); unsigned playlistLen = s - playlist; // Construct our response: snprintf((char*)fResponseBuffer, sizeof fResponseBuffer, "HTTP/1.1 200 OK\r\n" "%s" "Server: LIVE555 Streaming Media v%s\r\n" "%s" "Content-Length: %d\r\n" "Content-Type: application/vnd.apple.mpegurl\r\n" "\r\n", dateHeader(), LIVEMEDIA_LIBRARY_VERSION_STRING, lastModifiedHeader(urlSuffix), playlistLen); // Send the response header now, because we're about to add more data (the playlist): send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0); fResponseBuffer[0] = '\0'; // We've already sent the response. This tells the calling code not to send it again. // Then, send the playlist. Because it's large, we don't do so using "send()", because that might not send it all at once. // Instead, we stream the playlist over the TCP socket: if (fPlaylistSource != NULL) { // sanity check if (fTCPSink != NULL) fTCPSink->stopPlaying(); Medium::close(fPlaylistSource); } fPlaylistSource = ByteStreamMemoryBufferSource::createNew(envir(), (u_int8_t*)playlist, playlistLen); if (fTCPSink == NULL) fTCPSink = TCPStreamSink::createNew(envir(), fClientOutputSocket); fTCPSink->startPlaying(*fPlaylistSource, afterStreaming, this); }
void RTSPServer::RTSPClientSession ::handleCmd_SETUP(char const* cseq, char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr) { // "urlPreSuffix" should be the session (stream) name, and // "urlSuffix" should be the subsession (track) name. char const* streamName = urlPreSuffix; char const* trackId = urlSuffix; // Check whether we have existing session state, and, if so, whether it's // for the session that's named in "streamName". (Note that we don't // support more than one concurrent session on the same client connection.) ##### if (fOurServerMediaSession != NULL && strcmp(streamName, fOurServerMediaSession->streamName()) != 0) { fOurServerMediaSession = NULL; } if (fOurServerMediaSession == NULL) { // Set up this session's state. // Look up the "ServerMediaSession" object for the specified stream: if (streamName[0] != '\0' || fOurServer.lookupServerMediaSession("") != NULL) { // normal case } else { // weird case: there was no track id in the URL streamName = urlSuffix; trackId = NULL; } fOurServerMediaSession = fOurServer.lookupServerMediaSession(streamName); if (fOurServerMediaSession == NULL) { handleCmd_notFound(cseq); return; } fOurServerMediaSession->incrementReferenceCount(); // Set up our array of states for this session's subsessions (tracks): reclaimStreamStates(); ServerMediaSubsessionIterator iter(*fOurServerMediaSession); for (fNumStreamStates = 0; iter.next() != NULL; ++fNumStreamStates) {} fStreamStates = new struct streamState[fNumStreamStates]; iter.reset(); ServerMediaSubsession* subsession; for (unsigned i = 0; i < fNumStreamStates; ++i) { subsession = iter.next(); fStreamStates[i].subsession = subsession; fStreamStates[i].streamToken = NULL; // for now; reset by SETUP later } } // Look up information for the specified subsession (track): ServerMediaSubsession* subsession = NULL; unsigned streamNum; if (trackId != NULL && trackId[0] != '\0') { // normal case for (streamNum = 0; streamNum < fNumStreamStates; ++streamNum) { subsession = fStreamStates[streamNum].subsession; if (subsession != NULL && strcmp(trackId, subsession->trackId()) == 0) break; } if (streamNum >= fNumStreamStates) { // The specified track id doesn't exist, so this request fails: handleCmd_notFound(cseq); return; } } else { // Weird case: there was no track id in the URL. // This works only if we have only one subsession: if (fNumStreamStates != 1) { handleCmd_bad(cseq); return; } streamNum = 0; subsession = fStreamStates[streamNum].subsession; } // ASSERT: subsession != NULL // Look for a "Transport:" header in the request string, // to extract client parameters: StreamingMode streamingMode; char* streamingModeString = NULL; // set when RAW_UDP streaming is specified char* clientsDestinationAddressStr; u_int8_t clientsDestinationTTL; portNumBits clientRTPPortNum, clientRTCPPortNum; unsigned char rtpChannelId, rtcpChannelId; parseTransportHeader(fullRequestStr, streamingMode, streamingModeString, fIsMulticast, clientsDestinationAddressStr, clientsDestinationTTL, clientRTPPortNum, clientRTCPPortNum, rtpChannelId, rtcpChannelId); if (streamingMode == RTP_TCP && rtpChannelId == 0xFF) { // TCP streaming was requested, but with no "interleaving=" fields. // (QuickTime Player sometimes does this.) Set the RTP and RTCP channel ids to // proper values: rtpChannelId = fTCPStreamIdCount; rtcpChannelId = fTCPStreamIdCount+1; } fTCPStreamIdCount += 2; Port clientRTPPort(clientRTPPortNum); Port clientRTCPPort(clientRTCPPortNum); // Next, check whether a "Range:" header is present in the request. // This isn't legal, but some clients do this to combine "SETUP" and "PLAY": double rangeStart = 0.0, rangeEnd = 0.0; fStreamAfterSETUP = parseRangeHeader(fullRequestStr, rangeStart, rangeEnd) || parsePlayNowHeader(fullRequestStr); // Then, get server parameters from the 'subsession': int tcpSocketNum = streamingMode == RTP_TCP ? fClientSocket : -1; netAddressBits destinationAddress = 0; u_int8_t destinationTTL = 5; #ifdef RTSP_ALLOW_CLIENT_DESTINATION_SETTING if (clientsDestinationAddressStr != NULL) { // Use the client-provided "destination" address. // Note: This potentially allows the server to be used in denial-of-service // attacks, so don't enable this code unless you're sure that clients are // trusted. destinationAddress = our_inet_addr(clientsDestinationAddressStr); } // Also use the client-provided TTL. destinationTTL = clientsDestinationTTL; #endif delete[] clientsDestinationAddressStr; Port serverRTPPort(0); Port serverRTCPPort(0); subsession->getStreamParameters(fOurSessionId, fClientAddr.sin_addr.s_addr, clientRTPPort, clientRTCPPort, tcpSocketNum, rtpChannelId, rtcpChannelId, destinationAddress, destinationTTL, fIsMulticast, serverRTPPort, serverRTCPPort, fStreamStates[streamNum].streamToken); 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) { case RTP_UDP: snprintf((char*)fResponseBuffer, sizeof fResponseBuffer, "RTSP/1.0 200 OK\r\n" "CSeq: %s\r\n" "%s" "Transport: RTP/AVP;multicast;destination=%s;source=%s;port=%d-%d;ttl=%d\r\n" "Session: %08X\r\n\r\n", cseq, dateHeader(), destAddrStr, sourceAddrStr, ntohs(serverRTPPort.num()), ntohs(serverRTCPPort.num()), destinationTTL, fOurSessionId); break; case RTP_TCP: // multicast streams can't be sent via TCP handleCmd_unsupportedTransport(cseq); break; case RAW_UDP: snprintf((char*)fResponseBuffer, sizeof fResponseBuffer, "RTSP/1.0 200 OK\r\n" "CSeq: %s\r\n" "%s" "Transport: %s;multicast;destination=%s;source=%s;port=%d;ttl=%d\r\n" "Session: %08X\r\n\r\n", cseq, dateHeader(), streamingModeString, destAddrStr, sourceAddrStr, ntohs(serverRTPPort.num()), destinationTTL, fOurSessionId); break; } } else { switch (streamingMode) { case RTP_UDP: { snprintf((char*)fResponseBuffer, sizeof fResponseBuffer, "RTSP/1.0 200 OK\r\n" "CSeq: %s\r\n" "%s" "Transport: RTP/AVP;unicast;destination=%s;source=%s;client_port=%d-%d;server_port=%d-%d\r\n" "Session: %08X\r\n\r\n", cseq, dateHeader(), destAddrStr, sourceAddrStr, ntohs(clientRTPPort.num()), ntohs(clientRTCPPort.num()), ntohs(serverRTPPort.num()), ntohs(serverRTCPPort.num()), fOurSessionId); break; } case RTP_TCP: { snprintf((char*)fResponseBuffer, sizeof fResponseBuffer, "RTSP/1.0 200 OK\r\n" "CSeq: %s\r\n" "%s" "Transport: RTP/AVP/TCP;unicast;destination=%s;source=%s;interleaved=%d-%d\r\n" "Session: %08X\r\n\r\n", cseq, dateHeader(), destAddrStr, sourceAddrStr, rtpChannelId, rtcpChannelId, fOurSessionId); break; } case RAW_UDP: { snprintf((char*)fResponseBuffer, sizeof fResponseBuffer, "RTSP/1.0 200 OK\r\n" "CSeq: %s\r\n" "%s" "Transport: %s;unicast;destination=%s;source=%s;client_port=%d;server_port=%d\r\n" "Session: %08X\r\n\r\n", cseq, dateHeader(), streamingModeString, destAddrStr, sourceAddrStr, ntohs(clientRTPPort.num()), ntohs(serverRTPPort.num()), fOurSessionId); break; } } } delete[] destAddrStr; delete[] sourceAddrStr; delete[] streamingModeString; }