/*
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * Copyright (c) 1999-2003 Apple Computer, Inc.  All Rights Reserved.
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 *
 */
/*
    File:       QTSSFileModule.cpp

    Contains:   Implementation of module described in QTSSFileModule.h. 
                    
    $Log: QTSSRTPFileModule.cpp,v $
    Revision 1.12  2003/08/15 23:52:45  sbasu
    3370815 Need to update to the APSL 2
    Bug #:
    Submitted by:
    Reviewed by:

    Revision 1.11  2002/05/07 19:04:58  mythili
    fixed bug - expected GetValuePtr to return null-terminated string, but GetValuePtr returns a char array (not null-terminated); also changed some calls to GetValueAsString from LockObject/GetValuePtr where appropriate

    Revision 1.10  2002/05/02 03:41:18  murata
    Changes to support thread safe std lib calls.
    Bug #:
    Submitted by:
    Reviewed by:

    Revision 1.9  2002/05/01 00:10:59  mythili
    feature check-in: limiting broadcasts to a directory; rewrote home directory module; changed some request prefs to writable from read-only; server API implementation changes

    Revision 1.8  2002/02/26 00:18:23  murata
    Convert tabs to spaces in .cpp .c and .h files.
    Bug #:
    Submitted by:
    Reviewed by:

    Revision 1.7  2001/10/08 18:05:29  mythili
    changed QTSSModuleUtils calls from Get***Pref*** to Get***Attribute***
    
    Revision 1.6  2001/03/20 02:19:19  mythili
    updated/added APSL 1.2
    
    Revision 1.5  2000/11/17 02:12:43  serenyi
    Implemented a performance improvement for QTSS_Write on RTP packets
    
    Revision 1.4  2000/10/27 06:53:48  serenyi
    Optimize interface to QTFile
    
    Revision 1.3  2000/10/24 21:55:08  serenyi
    Moved to QTSS_AddStaticAttribute
    
    Revision 1.2  2000/10/20 22:10:37  serenyi
    Fixed a bug
    
    Revision 1.1  2000/10/20 07:24:52  serenyi
    Created
    
    Created
    

*/

#include <string.h>

#include "QTSSRTPFileModule.h"

#include "RTPFileSession.h"
#include "OSMemory.h"
#include "OSArrayObjectDeleter.h"
#include "QTSSModuleUtils.h"
#include "StringFormatter.h"
#include "SDPSourceInfo.h"
#include "QTSSMemoryDeleter.h"

#include "QTSS.h"


struct FileSession
{
    public:
    
        FileSession() :     fAdjustedPlayTime(0), fNextPacketLen(0), 
                            fStream(NULL), fSpeed(1), fStartTime(0), fStopTime(-1)
        {}
        
        RTPFileSession      fFile;
        SInt64              fAdjustedPlayTime;
        QTSS_PacketStruct   fPacketStruct;
        UInt32              fNextPacketLen;
        QTSS_RTPStreamObject    fStream;
        Float32             fSpeed;
        Float64             fStartTime;
        Float64             fStopTime;
};



// ref to the prefs dictionary object
static QTSS_ModulePrefsObject       sFileModulePrefs;

static StrPtrLen sRTPSuffix(".rtp");
static StrPtrLen sSDPHeader1("v=0\r\ns=");
static StrPtrLen sSDPHeader2;
static StrPtrLen sSDPHeader3("c=IN IP4 ");
static StrPtrLen sSDPHeader4("\r\na=control:/\r\n");
static const StrPtrLen              kCacheControlHeader("must-revalidate");

// ATTRIBUTES IDs

static QTSS_AttributeID sFileSessionAttr                = qtssIllegalAttrID;

static QTSS_AttributeID sSeekToNonexistentTimeErr       = qtssIllegalAttrID;
static QTSS_AttributeID sBadQTFileErr                   = qtssIllegalAttrID;
static QTSS_AttributeID sExpectedDigitFilenameErr       = qtssIllegalAttrID;
static QTSS_AttributeID sTrackDoesntExistErr            = qtssIllegalAttrID;

// OTHER DATA

static UInt32               sFlowControlProbeInterval   = 50;
static UInt32               sDefaultFlowControlProbeInterval= 50;
static Float32              sMaxAllowedSpeed            = 4;
static Float32              sDefaultMaxAllowedSpeed     = 4;

// FUNCTIONS

static QTSS_Error QTSSRTPFileModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParamBlock);
static QTSS_Error   Register(QTSS_Register_Params* inParams);
static QTSS_Error Initialize(QTSS_Initialize_Params* inParamBlock);
static QTSS_Error RereadPrefs();
static QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParamBlock);
static QTSS_Error DoDescribe(QTSS_StandardRTSP_Params* inParamBlock);
static QTSS_Error CreateRTPFileSession(QTSS_StandardRTSP_Params* inParamBlock, const StrPtrLen& inPath, FileSession** outFile);
static QTSS_Error DoSetup(QTSS_StandardRTSP_Params* inParamBlock);
static QTSS_Error DoPlay(QTSS_StandardRTSP_Params* inParamBlock);
static QTSS_Error SendPackets(QTSS_RTPSendPackets_Params* inParams);
static QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams);



QTSS_Error QTSSRTPFileModule_Main(void* inPrivateArgs)
{
    return _stublibrary_main(inPrivateArgs, QTSSRTPFileModuleDispatch);
}

QTSS_Error  QTSSRTPFileModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParamBlock)
{
    switch (inRole)
    {
        case QTSS_Register_Role:
            return Register(&inParamBlock->regParams);
        case QTSS_Initialize_Role:
            return Initialize(&inParamBlock->initParams);
        case QTSS_RereadPrefs_Role:
            return RereadPrefs();
        case QTSS_RTSPPreProcessor_Role:
            return ProcessRTSPRequest(&inParamBlock->rtspPreProcessorParams);
        case QTSS_RTPSendPackets_Role:
            return SendPackets(&inParamBlock->rtpSendPacketsParams);
        case QTSS_ClientSessionClosing_Role:
            return DestroySession(&inParamBlock->clientSessionClosingParams);
    }
    return QTSS_NoErr;
}

QTSS_Error Register(QTSS_Register_Params* inParams)
{
    // Register for roles
    (void)QTSS_AddRole(QTSS_Initialize_Role);
    (void)QTSS_AddRole(QTSS_RTSPPreProcessor_Role);
    (void)QTSS_AddRole(QTSS_ClientSessionClosing_Role);
    (void)QTSS_AddRole(QTSS_RereadPrefs_Role);

    // Add text messages attributes
    static char*        sSeekToNonexistentTimeName  = "QTSSFileModuleSeekToNonExistentTime";
    static char*        sBadQTFileName              = "QTSSFileModuleBadQTFile";
    static char*        sExpectedDigitFilenameName  = "QTSSFileModuleExpectedDigitFilename";
    static char*        sTrackDoesntExistName       = "QTSSFileModuleTrackDoesntExist";
    
    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sSeekToNonexistentTimeName, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sSeekToNonexistentTimeName, &sSeekToNonexistentTimeErr);

    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sBadQTFileName, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sBadQTFileName, &sBadQTFileErr);

    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sExpectedDigitFilenameName, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sExpectedDigitFilenameName, &sExpectedDigitFilenameErr);

    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sTrackDoesntExistName, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sTrackDoesntExistName, &sTrackDoesntExistErr);
    
    // Add an RTP session attribute for tracking FileSession objects
    static char*        sFileSessionName    = "QTSSRTPFileModuleSession";

    (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sFileSessionName, NULL, qtssAttrDataTypeVoidPointer);
    (void)QTSS_IDForAttr(qtssClientSessionObjectType, sFileSessionName, &sFileSessionAttr);
    
    // Tell the server our name!
    static char* sModuleName = "QTSSRTPFileModule";
    ::strcpy(inParams->outModuleName, sModuleName);

    return QTSS_NoErr;
}

QTSS_Error Initialize(QTSS_Initialize_Params* inParams)
{
    QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream);

    //
    // We need some prefs created and maintained by the QTSSFileModule,
    // cuz we don't want to duplicate basically the same stuff
    StrPtrLen theFileModule("QTSSFileModule");
    sFileModulePrefs = QTSSModuleUtils::GetModulePrefsObject(QTSSModuleUtils::GetModuleObjectByName(theFileModule));

    static StrPtrLen sEHeader("\r\ne=");
    static StrPtrLen sUHeader("\r\nu=");
    static StrPtrLen sHTTP("http://");
    static StrPtrLen sAdmin("admin@");
    
    // Read our preferences
    RereadPrefs();

    //build the sdp that looks like: \r\ne=http://streaming.apple.com\r\ne=qts@apple.com\r\n.

    // Get the default DNS name of the server
    StrPtrLen theDefaultDNS;
    (void)QTSS_GetValuePtr(inParams->inServer, qtssSvrDefaultDNSName, 0,
                                    (void**)&theDefaultDNS.Ptr, &theDefaultDNS.Len);
    
    StrPtrLen sdpURL;
    StrPtrLen adminEmail;
    sdpURL.Ptr = QTSSModuleUtils::GetStringAttribute(sFileModulePrefs, "sdp_url", "");
    sdpURL.Len = ::strlen(sdpURL.Ptr);
    
    adminEmail.Ptr = QTSSModuleUtils::GetStringAttribute(sFileModulePrefs, "admin_email", "");
    adminEmail.Len = ::strlen(adminEmail.Ptr);
    
    UInt32 sdpURLLen = sdpURL.Len;
    UInt32 adminEmailLen = adminEmail.Len;
    
    if (sdpURLLen == 0)
        sdpURLLen = theDefaultDNS.Len + sHTTP.Len + 1;
    if (adminEmailLen == 0)
        adminEmailLen = theDefaultDNS.Len + sAdmin.Len; 
    
    //calculate the length of the string & allocate memory
    sSDPHeader2.Len = (sEHeader.Len * 2) + sdpURLLen + adminEmailLen + 10;
    sSDPHeader2.Ptr = NEW char[sSDPHeader2.Len];

    //write it!
    StringFormatter sdpFormatter(sSDPHeader2);
    sdpFormatter.Put(sUHeader);

    //if there are preferences for default URL & admin email, use those. Otherwise, build the
    //proper string based on default dns name.
    if (sdpURL.Len == 0)
    {
        sdpFormatter.Put(sHTTP);
        sdpFormatter.Put(theDefaultDNS);
        sdpFormatter.PutChar('/');
    }
    else
        sdpFormatter.Put(sdpURL);
    
    sdpFormatter.Put(sEHeader);
    
    //now do the admin email.
    if (adminEmail.Len == 0)
    {
        sdpFormatter.Put(sAdmin);
        sdpFormatter.Put(theDefaultDNS);
    }
    else
        sdpFormatter.Put(adminEmail);
        
    sdpFormatter.PutEOL();
    sSDPHeader2.Len = (UInt32)sdpFormatter.GetCurrentOffset();
    
    delete [] sdpURL.Ptr;
    delete [] adminEmail.Ptr;

    // Report to the server that this module handles DESCRIBE, SETUP, PLAY, PAUSE, and TEARDOWN
    static QTSS_RTSPMethod sSupportedMethods[] = { qtssDescribeMethod, qtssSetupMethod, qtssTeardownMethod, qtssPlayMethod, qtssPauseMethod };
    QTSSModuleUtils::SetupSupportedMethods(inParams->inServer, sSupportedMethods, 5);
    
    return QTSS_NoErr;
}

QTSS_Error RereadPrefs()
{
    QTSSModuleUtils::GetAttribute(sFileModulePrefs, "flow_control_probe_interval",  qtssAttrDataTypeUInt32,
                                &sFlowControlProbeInterval, &sDefaultFlowControlProbeInterval, sizeof(sFlowControlProbeInterval));

    QTSSModuleUtils::GetAttribute(sFileModulePrefs, "max_allowed_speed",    qtssAttrDataTypeFloat32,
                                &sMaxAllowedSpeed, &sDefaultMaxAllowedSpeed, sizeof(sMaxAllowedSpeed));

    return QTSS_NoErr;
}


QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParams)
{
    QTSS_RTSPMethod* theMethod = NULL;
    UInt32 theMethodLen = 0;
    if ((QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqMethod, 0,
            (void**)&theMethod, &theMethodLen) != QTSS_NoErr) || (theMethodLen != sizeof(QTSS_RTSPMethod)))
    {
        Assert(0);
        return QTSS_RequestFailed;
    }
    
    switch (*theMethod)
    {
        case qtssDescribeMethod:
            return DoDescribe(inParams);
        case qtssSetupMethod:
            return DoSetup(inParams);
        case qtssPlayMethod:
            return DoPlay(inParams);
        case qtssTeardownMethod:
            // Tell the server that this session should be killed, and send a TEARDOWN response
            (void)QTSS_Teardown(inParams->inClientSession);
            (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, 0);
            break;
        case qtssPauseMethod:
            (void)QTSS_Pause(inParams->inClientSession);
            (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, 0);
            break;
        default:
            break;
    }           

    return QTSS_NoErr;
}

QTSS_Error DoDescribe(QTSS_StandardRTSP_Params* inParamBlock)
{
    // Check and see if this is a request we should handle. We handle all requests with URLs that
    // end in a '.rtp'
    char* theFullPathStr = NULL;
    QTSS_Error theError = QTSS_GetValueAsString(inParamBlock->inRTSPRequest, qtssRTSPReqLocalPath, 0, &theFullPathStr);
	QTSSCharArrayDeleter theFullPathDeleter(theFullPathStr);
    Assert(theError == QTSS_NoErr);
    
    StrPtrLen theFullPath(theFullPathStr);
    
    if ((theFullPath.Len <= sRTPSuffix.Len) ||
        (!sRTPSuffix.NumEqualIgnoreCase(&theFullPath.Ptr[theFullPath.Len - sRTPSuffix.Len], sRTPSuffix.Len)))
        return QTSS_RequestFailed;
    
    // It is, so let's set everything up...
    
    //
    // Get the FileSession for this DESCRIBE, if any.
    UInt32 theLen = sizeof(FileSession*);
    FileSession*    theFile = NULL;
    QTSS_Error      theErr = QTSS_NoErr;

    (void)QTSS_GetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, (void*)&theFile, &theLen);

    if ( theFile != NULL )  
    {
        //
        // There is already a file for this session. This can happen if there are multiple DESCRIBES,
        // or a DESCRIBE has been issued with a Session ID, or some such thing.
        
        if ( !theFullPath.Equal( *theFile->fFile.GetMoviePath() ) )
        {
            delete theFile;
            theFile = NULL;
            
            // NULL out the attribute value, just in case.
            (void)QTSS_SetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, &theFile, sizeof(theFile));
        }
    }
    
    if ( theFile == NULL )
    {   
        theErr = CreateRTPFileSession(inParamBlock, theFullPath, &theFile);
        if (theErr != QTSS_NoErr)
        {
            (void)QTSS_Teardown(inParamBlock->inClientSession);
            return theErr;
        }
    
        // Store this newly created file object in the RTP session.
        theErr = QTSS_SetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, &theFile, sizeof(theFile));
    }
    
    //generate the SDP.
    UInt32 totalSDPLength = sSDPHeader1.Len;
    iovec theSDPVec[10];//1 for the RTSP header, 6 for the sdp header, 1 for the sdp body
    theSDPVec[1].iov_base = sSDPHeader1.Ptr;
    theSDPVec[1].iov_len = sSDPHeader1.Len;
    
    //filename goes here
    //(void)QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqFilePath, 0, (void**)&theSDPVec[2].iov_base, (UInt32*)&theSDPVec[2].iov_len);
	char* filenameStr = NULL;
	(void)QTSS_GetValueAsString(inParamBlock->inRTSPRequest, qtssRTSPReqFilePath, 0, &filenameStr);
    QTSSCharArrayDeleter filenameStrDeleter(filenameStr);
	theSDPVec[2].iov_base = filenameStr;
	theSDPVec[2].iov_len = ::strlen(filenameStr);
	
    totalSDPLength += theSDPVec[2].iov_len;
    
    //url & admin email goes here
    theSDPVec[3].iov_base = sSDPHeader2.Ptr;
    theSDPVec[3].iov_len = sSDPHeader2.Len;
    totalSDPLength += sSDPHeader2.Len;

    //connection header
    theSDPVec[4].iov_base = sSDPHeader3.Ptr;
    theSDPVec[4].iov_len = sSDPHeader3.Len;
    totalSDPLength += sSDPHeader3.Len;
    
    //append IP addr
    (void)QTSS_GetValuePtr(inParamBlock->inRTSPSession, qtssRTSPSesLocalAddrStr, 0,
                                                (void**)&theSDPVec[5].iov_base, (UInt32*)&theSDPVec[5].iov_len);
    totalSDPLength += theSDPVec[5].iov_len;

    //last static sdp line
    theSDPVec[6].iov_base = sSDPHeader4.Ptr;
    theSDPVec[6].iov_len = sSDPHeader4.Len;
    totalSDPLength += sSDPHeader4.Len;
    
    //now append content-determined sdp
    theSDPVec[7].iov_base = theFile->fFile.GetSDPFile()->Ptr;
    theSDPVec[7].iov_len = theFile->fFile.GetSDPFile()->Len;
    totalSDPLength += theSDPVec[7].iov_len;
    
    Assert(theSDPVec[2].iov_base != NULL);


    // Append the Last Modified header to be a good caching proxy citizen before sending the Describe
    //(void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssLastModifiedHeader,
    //                              theFile->fFile.GetQTFile()->GetModDateStr(), DateBuffer::kDateBufferLen);
    (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssCacheControlHeader,
                                    kCacheControlHeader.Ptr, kCacheControlHeader.Len);

    //ok, we have a filled out iovec. Let's send it!
    QTSSModuleUtils::SendDescribeResponse(inParamBlock->inRTSPRequest, inParamBlock->inClientSession,
                                                                    &theSDPVec[0], 8, totalSDPLength);  
	
    return QTSS_NoErr;
}

QTSS_Error CreateRTPFileSession(QTSS_StandardRTSP_Params* inParamBlock, const StrPtrLen& inPath, FileSession** outFile)
{   
    *outFile = NEW FileSession();
    StrPtrLen thePath(inPath);
    RTPFileSession::ErrorCode theErr = (*outFile)->fFile.Initialize(thePath, 8);
    if (theErr != RTPFileSession::errNoError)
    {
        delete *outFile;
        *outFile = NULL;
        
        if (theErr == RTPFileSession::errFileNotFound)
            return QTSSModuleUtils::SendErrorResponse(  inParamBlock->inRTSPRequest,
                                                        qtssClientNotFound,
                                                        sBadQTFileErr);
        AssertV(0, theErr);
    }
    return QTSS_NoErr;
}


QTSS_Error DoSetup(QTSS_StandardRTSP_Params* inParamBlock)
{
    //setup this track in the file object 
    FileSession* theFile = NULL;
    UInt32 theLen = sizeof(FileSession*);
    QTSS_Error theErr = QTSS_GetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, (void*)&theFile, &theLen);
    if ((theErr != QTSS_NoErr) || (theLen != sizeof(FileSession*)))
    {
        // This is possible, as clients are not required to send a DESCRIBE. If we haven't set
        // anything up yet, set everything up
		char* theFullPathStr = NULL;
        theErr = QTSS_GetValueAsString(inParamBlock->inRTSPRequest, qtssRTSPReqLocalPath, 0, &theFullPathStr);
        Assert(theErr == QTSS_NoErr);
		QTSSCharArrayDeleter theFullPathDeleter(theFullPathStr);
		StrPtrLen theFullPath(theFullPathStr);
        	
        if ((theFullPath.Len <= sRTPSuffix.Len) ||
            (!sRTPSuffix.NumEqualIgnoreCase(&theFullPath.Ptr[theFullPath.Len - sRTPSuffix.Len], sRTPSuffix.Len)))
            return QTSS_RequestFailed;

        theErr = CreateRTPFileSession(inParamBlock, theFullPath, &theFile);
        if (theErr != QTSS_NoErr)
            return theErr;

        // Store this newly created file object in the RTP session.
        theErr = QTSS_SetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, &theFile, sizeof(theFile));
    }

    //unless there is a digit at the end of this path (representing trackID), don't
    //even bother with the request
    char* theDigitStr = NULL;
    (void)QTSS_GetValueAsString(inParamBlock->inRTSPRequest, qtssRTSPReqFileDigit, 0, &theDigitStr);
    QTSSCharArrayDeleter theDigitStrDeleter(theDigitStr);
	if (theDigitStr == NULL)
        return QTSSModuleUtils::SendErrorResponse(inParamBlock->inRTSPRequest,
                                                    qtssClientBadRequest, sExpectedDigitFilenameErr);
    
    UInt32 theTrackID = ::strtol(theDigitStr, NULL, 10);
	
    RTPFileSession::ErrorCode qtfileErr = theFile->fFile.AddTrack(theTrackID);
    
    //if we get an error back, forward that error to the client
    if (qtfileErr == RTPFileSession::errTrackDoesntExist)
        return QTSSModuleUtils::SendErrorResponse(inParamBlock->inRTSPRequest,
                                                    qtssClientNotFound, sTrackDoesntExistErr);
    else if (qtfileErr != RTPFileSession::errNoError)
        return QTSSModuleUtils::SendErrorResponse(inParamBlock->inRTSPRequest,
                                                    qtssUnsupportedMediaType, sBadQTFileErr);

    //find the payload for this track ID (if applicable)
    StrPtrLen* thePayload = NULL;
    UInt32 thePayloadType = qtssUnknownPayloadType;
    Float32 bufferDelay = (Float32) 3.0; // FIXME need a constant defined for 3.0 value. It is used multiple places
    
    for (UInt32 x = 0; x < theFile->fFile.GetSourceInfo()->GetNumStreams(); x++)
    {
        SourceInfo::StreamInfo* theStreamInfo = theFile->fFile.GetSourceInfo()->GetStreamInfo(x);
        if (theStreamInfo->fTrackID == theTrackID)
        {
            thePayload = &theStreamInfo->fPayloadName;
            thePayloadType = theStreamInfo->fPayloadType;
            bufferDelay = theStreamInfo->fBufferDelay;
            break;
        }   
    }

    //Create a new RTP stream           
    QTSS_RTPStreamObject newStream = NULL;
    theErr = QTSS_AddRTPStream(inParamBlock->inClientSession, inParamBlock->inRTSPRequest, &newStream, 0);
    if (theErr != QTSS_NoErr)
        return theErr;
    
    // Set the payload type, payload name & timescale of this track
    SInt32 theTimescale = theFile->fFile.GetTrackTimeScale(theTrackID);
    
    theErr = QTSS_SetValue(newStream, qtssRTPStrBufferDelayInSecs, 0, &bufferDelay, sizeof(bufferDelay));
    Assert(theErr == QTSS_NoErr);
    theErr = QTSS_SetValue(newStream, qtssRTPStrPayloadName, 0, thePayload->Ptr, thePayload->Len);
    Assert(theErr == QTSS_NoErr);
    theErr = QTSS_SetValue(newStream, qtssRTPStrPayloadType, 0, &thePayloadType, sizeof(thePayloadType));
    Assert(theErr == QTSS_NoErr);
    theErr = QTSS_SetValue(newStream, qtssRTPStrTimescale, 0, &theTimescale, sizeof(theTimescale));
    Assert(theErr == QTSS_NoErr);
    theErr = QTSS_SetValue(newStream, qtssRTPStrTrackID, 0, &theTrackID, sizeof(theTrackID));
    Assert(theErr == QTSS_NoErr);
    
    // Set the number of quality levels. Allow up to 6
    static UInt32 sNumQualityLevels = 0;
    
    theErr = QTSS_SetValue(newStream, qtssRTPStrNumQualityLevels, 0, &sNumQualityLevels, sizeof(sNumQualityLevels));
    Assert(theErr == QTSS_NoErr);
    
    // Get the SSRC of this track
    UInt32* theTrackSSRC = NULL;
    UInt32 theTrackSSRCSize = 0;
    (void)QTSS_GetValuePtr(newStream, qtssRTPStrSSRC, 0, (void**)&theTrackSSRC, &theTrackSSRCSize);

    // The RTP stream should ALWAYS have an SSRC assuming QTSS_AddStream succeeded.
    Assert((theTrackSSRC != NULL) && (theTrackSSRCSize == sizeof(UInt32)));
    
    //give the file some info it needs.
    theFile->fFile.SetTrackSSRC(theTrackID, *theTrackSSRC);
    theFile->fFile.SetTrackCookie(theTrackID, newStream);
    
    //
    // Our array has now been updated to reflect the fields requested by the client.
    //send the setup response
    //(void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssLastModifiedHeader,
    //                          theFile->fFile.GetQTFile()->GetModDateStr(), DateBuffer::kDateBufferLen);
    (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssCacheControlHeader,
                                kCacheControlHeader.Ptr, kCacheControlHeader.Len);

    //send the setup response
    (void)QTSS_SendStandardRTSPResponse(inParamBlock->inRTSPRequest, newStream, 0);
    return QTSS_NoErr;
}

QTSS_Error DoPlay(QTSS_StandardRTSP_Params* inParamBlock)
{
    FileSession** theFile = NULL;
    UInt32 theLen = 0;
    QTSS_Error theErr = QTSS_GetValuePtr(inParamBlock->inClientSession, sFileSessionAttr, 0, (void**)&theFile, &theLen);
    if ((theErr != QTSS_NoErr) || (theLen != sizeof(FileSession*)))
        return QTSS_RequestFailed;

    Float64* theStartTime = 0;
    theErr = QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqStartTime, 0, (void**)&theStartTime, &theLen);
    if ((theErr != QTSS_NoErr) || (theLen != sizeof(Float64)))
        return QTSSModuleUtils::SendErrorResponse(  inParamBlock->inRTSPRequest,
                                                    qtssClientBadRequest, sSeekToNonexistentTimeErr);
                                                    
    RTPFileSession::ErrorCode qtFileErr = (*theFile)->fFile.Seek(*theStartTime);
    if (qtFileErr != RTPFileSession::errNoError)
        return QTSSModuleUtils::SendErrorResponse(  inParamBlock->inRTSPRequest,
                                                    qtssClientBadRequest, sSeekToNonexistentTimeErr);
                                                        
    //make sure to clear the next packet the server would have sent!
    (*theFile)->fPacketStruct.packetData = NULL;
    
    // Set the movie duration and size parameters
    Float64 movieDuration = (*theFile)->fFile.GetMovieDuration();
    (void)QTSS_SetValue(inParamBlock->inClientSession, qtssCliSesMovieDurationInSecs, 0, &movieDuration, sizeof(movieDuration));
    
    UInt64 movieSize = (*theFile)->fFile.GetAddedTracksRTPBytes();
    (void)QTSS_SetValue(inParamBlock->inClientSession, qtssCliSesMovieSizeInBytes, 0, &movieSize, sizeof(movieSize));
    
    //UInt32 bitsPerSecond =    (*theFile)->fFile.GetBytesPerSecond() * 8;
    //(void)QTSS_SetValue(inParamBlock->inClientSession, qtssCliSesMovieAverageBitRate, 0, &bitsPerSecond, sizeof(bitsPerSecond));

    //
    // For the purposes of the speed header, check to make sure all tracks are
    // over a reliable transport
    Bool16 allTracksReliable = true;
    
    // Set the timestamp & sequence number parameters for each track.
    QTSS_RTPStreamObject* theRef = NULL;
    for (   UInt32 theStreamIndex = 0;
            QTSS_GetValuePtr(inParamBlock->inClientSession, qtssCliSesStreamObjects, theStreamIndex, (void**)&theRef, &theLen) == QTSS_NoErr;
            theStreamIndex++)
    {
        UInt32* theTrackID = NULL;
        theErr = QTSS_GetValuePtr(*theRef, qtssRTPStrTrackID, 0, (void**)&theTrackID, &theLen);
        Assert(theErr == QTSS_NoErr);
        Assert(theTrackID != NULL);
        Assert(theLen == sizeof(UInt32));
        
        SInt16 theSeqNum = (*theFile)->fFile.GetNextTrackSequenceNumber(*theTrackID);
        SInt32 theTimestamp = (*theFile)->fFile.GetSeekTimestamp(*theTrackID);
        
        Assert(theRef != NULL);
        Assert(theLen == sizeof(QTSS_RTPStreamObject));
        
        theErr = QTSS_SetValue(*theRef, qtssRTPStrFirstSeqNumber, 0, &theSeqNum, sizeof(theSeqNum));
        Assert(theErr == QTSS_NoErr);
        theErr = QTSS_SetValue(*theRef, qtssRTPStrFirstTimestamp, 0, &theTimestamp, sizeof(theTimestamp));
        Assert(theErr == QTSS_NoErr);

        if (allTracksReliable)
        {
            QTSS_RTPTransportType theTransportType = qtssRTPTransportTypeUDP;
            theLen = sizeof(theTransportType);
            theErr = QTSS_GetValue(*theRef, qtssRTPStrTransportType, 0, &theTransportType, &theLen);
            Assert(theErr == QTSS_NoErr);
            
            if (theTransportType == qtssRTPTransportTypeUDP)
                allTracksReliable = false;
        }
    }
    
    //Tell the server to start playing this movie. We do want it to send RTCP SRs, but
    //we DON'T want it to write the RTP header
    theErr = QTSS_Play(inParamBlock->inClientSession, inParamBlock->inRTSPRequest, qtssPlayFlagsSendRTCP);
    if (theErr != QTSS_NoErr)
        return theErr;

    // qtssRTPSesAdjustedPlayTimeInMsec is valid only after calling QTSS_Play
    // theAdjustedPlayTime is a way to delay the sending of data until a time after the 
    // the Play response should have been received.
    SInt64* theAdjustedPlayTime = 0;
    theErr = QTSS_GetValuePtr(inParamBlock->inClientSession, qtssCliSesAdjustedPlayTimeInMsec, 0, (void**)&theAdjustedPlayTime, &theLen);
    Assert(theErr == QTSS_NoErr);
    Assert(theAdjustedPlayTime != NULL);
    Assert(theLen == sizeof(SInt64));
    (*theFile)->fAdjustedPlayTime = *theAdjustedPlayTime;
    
    //
    // This module supports the Speed header if the client wants the stream faster than normal.
    Float32 theSpeed = 1;
    theLen = sizeof(theSpeed);
    theErr = QTSS_GetValue(inParamBlock->inRTSPRequest, qtssRTSPReqSpeed, 0, &theSpeed, &theLen);
    Assert(theErr != QTSS_BadArgument);
    Assert(theErr != QTSS_NotEnoughSpace);
    
    if (theErr == QTSS_NoErr)
    {
        if (theSpeed > sMaxAllowedSpeed)
            theSpeed = sMaxAllowedSpeed;
        if ((theSpeed <= 0) || (!allTracksReliable))
            theSpeed = 1;
    }
    
    (*theFile)->fSpeed = theSpeed;
    
    if (theSpeed != 1)
    {
        //
        // If our speed is not 1, append the RTSP speed header in the response
        char speedBuf[32];
        qtss_sprintf(speedBuf, "%10.5f", theSpeed);
        StrPtrLen speedBufPtr(speedBuf);
        (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssSpeedHeader,
                                    speedBufPtr.Ptr, speedBufPtr.Len);
    }
    
    //
    // Record the requested start and stop time.

    (*theFile)->fStopTime = -1;
    theLen = sizeof((*theFile)->fStopTime);
    theErr = QTSS_GetValue(inParamBlock->inRTSPRequest, qtssRTSPReqStopTime, 0, &(*theFile)->fStopTime, &theLen);
    Assert(theErr == QTSS_NoErr);
            
    (*theFile)->fStartTime = 0;
    theLen = sizeof((*theFile)->fStopTime);
    theErr = QTSS_GetValue(inParamBlock->inRTSPRequest, qtssRTSPReqStartTime, 0, &(*theFile)->fStartTime, &theLen);
    Assert(theErr == QTSS_NoErr);

    //the client doesn't necessarily specify this information in a play,
    //if it doesn't, fall back on some defaults.
    if ((*theFile)->fStartTime == -1)
        (*theFile)->fStartTime = 0;
        
    (void)QTSS_SendStandardRTSPResponse(inParamBlock->inRTSPRequest, inParamBlock->inClientSession, qtssPlayRespWriteTrackInfo);
    return QTSS_NoErr;
}

QTSS_Error SendPackets(QTSS_RTPSendPackets_Params* inParams)
{
    FileSession** theFile = NULL;
    UInt32 theLen = 0;
    QTSS_Error theErr = QTSS_GetValuePtr(inParams->inClientSession, sFileSessionAttr, 0, (void**)&theFile, &theLen);
    if ((theErr != QTSS_NoErr) || (theLen != sizeof(FileSession*)))
    {
        Assert( 0 );
        return QTSS_RequestFailed;
    }
    
    while (true)
    {   
        if ((*theFile)->fPacketStruct.packetData == NULL)
        {
            void* theCookie = NULL;
            Float64 theTransmitTime = (*theFile)->fFile.GetNextPacket((UInt8**)&(*theFile)->fPacketStruct.packetData, &(*theFile)->fNextPacketLen, &theCookie);
            
            //
            // Check to see if we should stop playing based on a client specified stop time
            if (((*theFile)->fStopTime != -1) && (theTransmitTime > (*theFile)->fStopTime))
            {
                // We should indeed stop playing
                (void)QTSS_Pause(inParams->inClientSession);
                inParams->outNextPacketTime = qtssDontCallSendPacketsAgain;
                return QTSS_NoErr;
            }

            //
            // Adjust transmit time based on speed
            Float64 theOffsetFromStartTime = theTransmitTime - (*theFile)->fStartTime;
            theTransmitTime = (*theFile)->fStartTime + (theOffsetFromStartTime / (*theFile)->fSpeed);
            
            (*theFile)->fStream = (QTSS_RTPStreamObject)theCookie;
            (*theFile)->fPacketStruct.packetTransmitTime = (*theFile)->fAdjustedPlayTime + ((SInt64)(theTransmitTime * 1000));
#if RTP_FILE_MODULE_DEBUGGING >= 8
            UInt16* theSeqNumPtr = (UInt16*)(*theFile)->fNextPacket;
            UInt32* theTimestampP = (UInt32*)(*theFile)->fNextPacket;
            UInt32* theTrackID = NULL;
            (void)QTSS_GetValuePtr((*theFile)->fStream, qtssRTPStrTrackID, 0, (void**)&theTrackID, &theLen);
            qtss_printf("Got packet. Seq num: %d. Timestamp: %d. TrackID: %d. Transmittime: %f\n",theSeqNumPtr[1], theTimestampP[1], *theTrackID, theTransmitTime);
#endif
        }
        
        //We are done playing all streams!
        if ((*theFile)->fPacketStruct.packetData == NULL)
        {
#if RTP_FILE_MODULE_DEBUGGING >= 8
            qtss_printf("done w all packets\n");
#endif
            inParams->outNextPacketTime = qtssDontCallSendPacketsAgain;
            return QTSS_NoErr;
        }
        //we have a packet that needs to be sent now
        Assert((*theFile)->fStream != NULL);

        // Send the packet!
        theErr =  QTSS_Write((*theFile)->fStream, &(*theFile)->fPacketStruct, (*theFile)->fNextPacketLen, NULL, qtssWriteFlagsIsRTP);

        if ( theErr == QTSS_WouldBlock )
        {   
            if ((*theFile)->fPacketStruct.packetTransmitTime == -1)
                inParams->outNextPacketTime = sFlowControlProbeInterval;    // for buffering, try me again in # MSec
            else
            {
                Assert((*theFile)->fPacketStruct.packetTransmitTime > inParams->inCurrentTime);
                inParams->outNextPacketTime = (*theFile)->fPacketStruct.packetTransmitTime - inParams->inCurrentTime;
            }
                
            return QTSS_NoErr;
        }

        (*theFile)->fPacketStruct.packetData = NULL;
    }
    Assert(0);
    return QTSS_NoErr;
}

QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams)
{
    FileSession** theFile = NULL;
    UInt32 theLen = 0;
    QTSS_Error theErr = QTSS_GetValuePtr(inParams->inClientSession, sFileSessionAttr, 0, (void**)&theFile, &theLen);
    if ((theErr != QTSS_NoErr) || (theLen != sizeof(FileSession*)) || (theFile == NULL))
        return QTSS_RequestFailed;

    delete *theFile;
    return QTSS_NoErr;
}
