Kawe Mazidjatari b3a68ed095 Add EABase, EAThread and DirtySDK to R5sdk
DirtySDK (EA's Dirty Sockets library) will be used for the LiveAPI implementation, and depends on: EABase, EAThread.
2024-04-05 18:29:03 +02:00

1286 lines
37 KiB
C

/*H********************************************************************************/
/*!
\File stream.c
\Description
Test the ProtoStream module.
\Copyright
Copyright (c) 2005 Electronic Arts Inc.
\Version 11/16/2005 (jbrookes) First Version
*/
/********************************************************************************H*/
/*** Include files ****************************************************************/
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "DirtySDK/platform.h"
#include "DirtySDK/dirtysock/dirtylib.h"
#include "DirtySDK/proto/protostream.h"
#include "DirtySDK/xml/xmlparse.h"
#include "libsample/zlib.h"
#include "libsample/zmem.h"
#include "libsample/zfile.h"
#include "testersubcmd.h"
#include "testermodules.h"
/*** Defines **********************************************************************/
#define STREAMER_USECALLBACK (TRUE)
#define STREAMER_DEBUG (FALSE)
#define STREAM_DEFAULTSKIP (15) // 15 seconds
#define STREAM_MAXURLS (4)
/*** Type Definitions *************************************************************/
typedef struct StreamStationT
{
char strName[32];
char strInfo[64];
char strType[32];
char strUrl[STREAM_MAXURLS][256];
} StreamStationT;
typedef struct StreamPlaylistT
{
int32_t iNumStations;
StreamStationT Stations[1]; //!< variable-length array of stations
} StreamPlaylistT;
typedef struct StreamCmdRefT
{
ProtoStreamRefT *pProtoStream;
StreamPlaylistT *pPlaylist;
int32_t *pRandom;
int32_t iStartTick;
int32_t iSkipTick;
int32_t iSkipTime;
int32_t iCurStation;
int32_t iCurUrl;
int32_t iMetaInterval;
int32_t iMetaOffset;
int32_t iMetaSize;
uint8_t bRandomPlay;
char strMetaBuffer[(255*16)+1];
char strLastMetaBuffer[(255*16)+1];
} StreamCmdRefT;
/*** Function Prototypes **********************************************************/
static void _SubcmdStreamCreate(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp);
static void _SubcmdStreamDestroy(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp);
static void _SubcmdStreamOpen(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp);
static void _SubcmdStreamClose(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp);
static void _SubcmdStreamSkip(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp);
static void _SubcmdStreamControl(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp);
/*** Variables ********************************************************************/
//! subcommand table
static T2SubCmdT _Stream_Commands[] =
{
{ "create", _SubcmdStreamCreate },
{ "destroy", _SubcmdStreamDestroy },
{ "open", _SubcmdStreamOpen },
{ "close", _SubcmdStreamClose },
{ "skip", _SubcmdStreamSkip },
{ "ctrl", _SubcmdStreamControl },
{ "", NULL }
};
//! single instance of the stream module
static StreamCmdRefT *_Stream_pCmdRef = NULL;
//! basic playlist with sportscenter ref
static StreamPlaylistT _Playlist =
{
1,
{
{
"ESPN Sportscenter",
"sportscenter 32k/sec",
"Sports",
{
"http://stestbesl01.beta.ea.com/espnradio/sportscenter/sportscenter.mp3",
}
}
},
};
/*** Private Functions ************************************************************/
/*F********************************************************************************/
/*!
\Function _StreamOpen
\Description
Open the stream specified by the given url
\Input *pCmdRef - module state
\Input *pStation - station reference
\Input iRestartFreq - restart frequency
\Output
None.
\Version 11/21/2005 (jbrookes)
*/
/********************************************************************************F*/
static void _StreamOpen(StreamCmdRefT *pCmdRef, StreamStationT *pStation, int32_t iRestartFreq)
{
char strTempUrl[128], *pUrl;
// ref url
pUrl = pStation->strUrl[pCmdRef->iCurUrl];
// validate url
if (strncmp(pUrl, "http://", 7))
{
ZPrintf("stream: invalid URL '%s'\n", pUrl);
return;
}
// see if URL needs a trailing slash
if (!strchr(pUrl+7, '/'))
{
ds_strnzcpy(strTempUrl, pUrl, sizeof(strTempUrl));
ds_strnzcat(strTempUrl, "/", sizeof(strTempUrl));
pUrl = strTempUrl;
}
// open the stream
if (pStation->strName[0] != '\0')
{
ZPrintf("stream: opening [%d] %s (url=%s)\n", (int32_t)(pStation - pCmdRef->pPlaylist->Stations), pStation->strName, pUrl);
}
else
{
ZPrintf("stream: opening url %s\n", pUrl);
}
ProtoStreamOpen(pCmdRef->pProtoStream, pUrl, iRestartFreq);
pCmdRef->iMetaInterval = 0;
pCmdRef->iMetaSize = 0;
pCmdRef->iMetaOffset = 0;
}
/*F********************************************************************************/
/*!
\Function _StreamXmlGetString
\Description
Get an xml entity's string contents
\Input *pXml - source xml to get data from
\Input *pName - name of entity
\Input *pBuffer - [out] storage for entity contents
\Input iBufSize - size of buffer pointed to by pBuffer
\Output
None.
\Version 11/21/2005 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _StreamXmlGetString(const char *pXml, const char *pName, char *pBuffer, int32_t iBufSize)
{
ds_memclr(pBuffer, iBufSize);
if ((pXml = XmlFind(pXml, pName)) != NULL)
{
return(XmlContentGetString(pXml, pBuffer, iBufSize, ""));
}
return(-1);
}
/*F********************************************************************************/
/*!
\Function _StreamDisplayPlaylist
\Description
Display the given playlist
\Input *pCmdRef - module state
\Input *pPlaylist - playlist to display
\Output
None.
\Version 08/18/2006 (jbrookes)
*/
/********************************************************************************F*/
static void _StreamDisplayPlaylist(StreamCmdRefT *pCmdRef, StreamPlaylistT *pPlaylist)
{
int32_t iStation;
for (iStation = 0; iStation < pPlaylist->iNumStations; iStation++)
{
ZPrintf("stream: [%2d] %s\n", iStation, pPlaylist->Stations[iStation].strName);
}
}
/*F********************************************************************************/
/*!
\Function _StreamOpenPlaylist
\Description
Open the playlist specified by the given filename
\Input *pCmdRef - module state
\Input *pName - name of playlist to open
\Output
None.
\Version 08/16/2006 (jbrookes)
*/
/********************************************************************************F*/
static void _StreamOpenPlaylist(StreamCmdRefT *pCmdRef, const char *pName)
{
const char *pPlayFile, *pXml, *pXml2;
int32_t iFileSize, iNumStations;
// open the playlist
ZPrintf("stream: opening playlist '%s'\n", pName);
if ((pPlayFile = ZFileLoad(pName, &iFileSize, FALSE)) == NULL)
{
ZPrintf("stream: error opening playlist\n");
return;
}
// see if it is a playlist
if ((pXml = XmlFind(pPlayFile, "playlist.station")) == NULL)
{
ZPrintf("stream: file is not a playlist\n", pName);
return;
}
// see how many stations are defined
for (pXml2 = pXml, iNumStations = 1; ; )
{
if ((pXml2 = XmlSkip(pXml2)) == NULL)
{
break;
}
if (!strncmp(pXml2+1, "station", 7))
{
iNumStations += 1;
}
}
ZPrintf("stream: parsed %d stations\n", iNumStations);
if (iNumStations != 0)
{
int32_t iPlaylistSize, iRandom, iRandIdx;
StreamStationT *pStation;
int32_t *pTmpRandom;
// allocate a new playlist structure
if ((pCmdRef->pPlaylist != NULL) && (pCmdRef->pPlaylist != &_Playlist))
{
ZMemFree(pCmdRef->pPlaylist);
}
if (pCmdRef->pRandom != NULL)
{
ZMemFree(pCmdRef->pPlaylist);
}
iPlaylistSize = sizeof(*pCmdRef->pPlaylist) + (sizeof(pCmdRef->pPlaylist->Stations[0]) * (iNumStations - 1));
pCmdRef->pPlaylist = ZMemAlloc(iPlaylistSize);
ds_memclr(pCmdRef->pPlaylist, iPlaylistSize);
pCmdRef->pPlaylist->iNumStations = iNumStations;
// parse xml into new playlist
for (pStation = pCmdRef->pPlaylist->Stations; ; )
{
if (!strncmp((char *)pXml+1, "station", 7))
{
_StreamXmlGetString(pXml, "station.name", pStation->strName, sizeof(pStation->strName));
_StreamXmlGetString(pXml, "station.info", pStation->strInfo, sizeof(pStation->strInfo));
_StreamXmlGetString(pXml, "station.type", pStation->strType, sizeof(pStation->strType));
_StreamXmlGetString(pXml, "station.url0", pStation->strUrl[0], sizeof(pStation->strUrl[0]));
_StreamXmlGetString(pXml, "station.url1", pStation->strUrl[1], sizeof(pStation->strUrl[1]));
_StreamXmlGetString(pXml, "station.url2", pStation->strUrl[2], sizeof(pStation->strUrl[2]));
_StreamXmlGetString(pXml, "station.url3", pStation->strUrl[3], sizeof(pStation->strUrl[3]));
pStation += 1;
}
if ((pXml = XmlSkip(pXml)) == NULL)
{
break;
}
}
// create new buffer for randomizing playlist
pCmdRef->pRandom = ZMemAlloc(iNumStations*sizeof(*pCmdRef->pRandom));
// create playlist indices
pTmpRandom = ZMemAlloc((iNumStations+1)*sizeof(*pCmdRef->pRandom));
for (iRandom = 0; iRandom < iNumStations; iRandom++)
{
pTmpRandom[iRandom] = iRandom;
}
// generate random walk through playlist
for (iRandom = 0; iRandom < (iNumStations-1); iRandom++)
{
iRandIdx = rand() % (iNumStations-iRandom);
pCmdRef->pRandom[iRandom] = pTmpRandom[iRandIdx];
memmove(&pTmpRandom[iRandIdx], &pTmpRandom[iRandIdx+1], (iNumStations-iRandIdx-1)*sizeof(*pTmpRandom));
}
pCmdRef->pRandom[iRandom] = pTmpRandom[0];
// free temp buffer
ZMemFree(pTmpRandom);
}
// done with the file
ZMemFree((void *)pPlayFile);
}
/*F********************************************************************************/
/*!
\Function _StreamGetRandIndex
\Description
Get index of given station in random playlist.
\Input *pCmdRef - module state
\Input iIndex - station index in playlist
\Output
int32_t - index of station in random playlist
\Version 11/14/2006 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _StreamGetRandIndex(StreamCmdRefT *pCmdRef, int32_t iIndex)
{
int32_t iStation;
for (iStation = 0; iStation < pCmdRef->pPlaylist->iNumStations; iStation++)
{
if (pCmdRef->pRandom[iStation] == iIndex)
{
return(iStation);
}
}
NetPrintf(("stream: unable to find station %d in random playlist\n", iIndex));
return(0);
}
/*F********************************************************************************/
/*!
\Function _StreamClose
\Description
Close the current stream, if any
\Input *pCmdRef - module state
\Output
None.
\Version 11/21/2005 (jbrookes)
*/
/********************************************************************************F*/
static void _StreamClose(StreamCmdRefT *pCmdRef)
{
// close the stream
ZPrintf("stream: closing active stream\n");
ProtoStreamClose(pCmdRef->pProtoStream);
}
/*F********************************************************************************/
/*!
\Function _StreamGetCurStation
\Description
Get index of current station (possibly applying random index)
\Input *pCmdRef - module state
\Output
int32_t - index of current station
\Version 11/24/2005 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _StreamGetCurStation(StreamCmdRefT *pCmdRef)
{
int32_t iStation;
iStation = (pCmdRef->bRandomPlay) ? pCmdRef->pRandom[pCmdRef->iCurStation] : pCmdRef->iCurStation;
return(iStation);
}
/*F********************************************************************************/
/*!
\Function _StreamNextUrl
\Description
Skip to next url for current station
\Input *pCmdRef - module state
\Output
None.
\Version 11/22/2005 (jbrookes)
*/
/********************************************************************************F*/
static void _StreamNext(StreamCmdRefT *pCmdRef)
{
pCmdRef->iCurUrl = pCmdRef->iCurUrl + 1;
_StreamOpen(pCmdRef, &pCmdRef->pPlaylist->Stations[_StreamGetCurStation(pCmdRef)], PROTOSTREAM_FREQ_IMMED);
}
/*F********************************************************************************/
/*!
\Function _StreamSkip
\Description
Skip to next stream.
\Input *pCmdRef - module state
\Input iCurTick - current tick
\Output
None.
\Version 11/22/2005 (jbrookes)
*/
/********************************************************************************F*/
static void _StreamSkip(StreamCmdRefT *pCmdRef, int32_t iCurTick)
{
// switch to next station
pCmdRef->iCurStation = (pCmdRef->iCurStation + 1) % pCmdRef->pPlaylist->iNumStations;
// switch to next stream
pCmdRef->iCurUrl = 0;
// open the stream
_StreamOpen(pCmdRef, &pCmdRef->pPlaylist->Stations[_StreamGetCurStation(pCmdRef)], PROTOSTREAM_FREQ_IMMED);
// set next update
pCmdRef->iSkipTick = iCurTick;
}
/*F********************************************************************************/
/*!
\Function _StreamParseHeader
\Description
Parse data from from icy header response
\Input *pCmdRef - module state
\Input *pHeader - http response header
\Output
None.
\Version 03/23/2006 (jbrookes)
*/
/********************************************************************************F*/
static void _StreamParseHeader(StreamCmdRefT *pCmdRef, const char *pHeader)
{
char strData[256], *pData, *pHeaderInfo;
// parse meta-interval data, if present
if ((pHeaderInfo = ds_stristr(pHeader, "icy-metaint")) != NULL)
{
// parse meta interval
while (!isdigit(*pHeaderInfo))
{
pHeaderInfo += 1;
}
pCmdRef->iMetaInterval = strtol(pHeaderInfo, NULL, 10);
NetPrintf(("stream: metadata interval is %d\n", pCmdRef->iMetaInterval));
pCmdRef->iMetaOffset = pCmdRef->iMetaInterval;
}
// parse icy-name, if present
if ((pHeaderInfo = ds_stristr(pHeader, "icy-name:")) != NULL)
{
pHeaderInfo += sizeof("icy-name:")-1;
for (pData = strData; *pHeaderInfo != '\r'; pHeaderInfo += 1, pData += 1)
{
*pData = *pHeaderInfo;
}
*pData = '\0';
ZPrintf("=====================================================================================================\n");
ZPrintf("stream: %s\n", strData);
}
// parse icy-genre, if present
if ((pHeaderInfo = ds_stristr(pHeader, "icy-genre:")) != NULL)
{
pHeaderInfo += sizeof("icy-genre:")-1;
for (pData = strData; *pHeaderInfo != '\r'; pHeaderInfo += 1, pData += 1)
{
*pData = *pHeaderInfo;
}
*pData = '\0';
ZPrintf("stream: %s\n", strData);
}
// parse icy-br, if present
if ((pHeaderInfo = ds_stristr(pHeader, "icy-br:")) != NULL)
{
pHeaderInfo += sizeof("icy-br:")-1;
for (pData = strData; *pHeaderInfo != '\r'; pHeaderInfo += 1, pData += 1)
{
*pData = *pHeaderInfo;
}
*pData = '\0';
ZPrintf("stream: %skbps\n", strData);
ZPrintf("=====================================================================================================\n");
}
}
/*F********************************************************************************/
/*!
\Function _StreamProcessMetaData
\Description
Process metadata embedded in mp3 stream.
\Input *pCmdRef - module state
\Input *pData - stream data
\Input iSize - amount of data available for processing
\Output
int32_t - number of bytes consumed
\Version 03/23/2006 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _StreamProcessMetaData(StreamCmdRefT *pCmdRef, const uint8_t *pData, int32_t iSize)
{
int32_t iConsume, iConsumed;
// process all data
for ( iConsumed = 0; iSize > 0; )
{
// if we're processing mp3 data, bail
if (pCmdRef->iMetaOffset > 0)
{
break;
}
// are we processing a metadata header?
if (pCmdRef->iMetaSize > 0)
{
if (iSize >= pCmdRef->iMetaSize)
{
iConsume = pCmdRef->iMetaSize;
pCmdRef->iMetaOffset = pCmdRef->iMetaInterval;
}
else
{
iConsume = iSize;
}
// copy metadata into buffer
ds_strsubzcat(pCmdRef->strMetaBuffer, sizeof(pCmdRef->strMetaBuffer), (char *)pData, iConsume);
// skip metadata
pData += iConsume;
iSize -= iConsume;
// mark metasize as consumed
pCmdRef->iMetaSize -= iConsume;
iConsumed += iConsume;
}
// do we have a metadata header?
else if (pCmdRef->iMetaOffset == 0)
{
pCmdRef->iMetaSize = *pData * 16;
pCmdRef->strMetaBuffer[0] = '\0';
pData += 1;
iSize -= 1;
iConsumed += 1;
}
// if we're done with the header, reset the offset
if (pCmdRef->iMetaSize == 0)
{
// did we get any metadata?
if (pCmdRef->strMetaBuffer[0] != '\0')
{
// did the metadata change since the last update?
if (strcmp(pCmdRef->strLastMetaBuffer, pCmdRef->strMetaBuffer))
{
char *pTitle, *pEndTitle;
// first, save the update
strcpy(pCmdRef->strLastMetaBuffer, pCmdRef->strMetaBuffer);
// try and find title for display
if ((pTitle = strstr(pCmdRef->strMetaBuffer, "StreamTitle")) != NULL)
{
if ((pTitle = strchr(pTitle, '\'')) != NULL)
{
time_t uTime;
pTitle += 1;
if ((pEndTitle = strchr(pTitle, ';')) != NULL)
{
*(pEndTitle-1) = '\0';
}
else if ((pEndTitle = strchr(pTitle, '\'')) != NULL)
{
*pEndTitle = '\0';
}
if ((uTime = ds_timeinsecs()) != 0)
{
struct tm CurTime;
ds_secstotime(&CurTime, uTime);
ZPrintf("stream: %02d:%02d now playing - %s\n", CurTime.tm_hour, CurTime.tm_min, pTitle);
}
else
{
ZPrintf("stream: now playing - %s\n", pTitle);
}
}
}
}
}
// reset offset
pCmdRef->iMetaOffset = pCmdRef->iMetaInterval;
}
}
return(iConsumed);
}
/*F********************************************************************************/
/*!
\Function _StreamDone
\Description
Process stream completion
\Input *pCmdRef - module state
\Input iCurTick - current tick
\Output
None.
\Version 03/31/2008 (jbrookes)
*/
/********************************************************************************F*/
static void _StreamDone(StreamCmdRefT *pCmdRef, int32_t iCurTick)
{
if (pCmdRef->pPlaylist->iNumStations > 1)
{
int32_t iNextUrl = pCmdRef->iCurUrl + 1;
if ((iNextUrl < STREAM_MAXURLS) && (pCmdRef->pPlaylist->Stations[_StreamGetCurStation(pCmdRef)].strUrl[iNextUrl][0] != '\0'))
{
_StreamNext(pCmdRef);
}
else
{
_StreamSkip(pCmdRef, iCurTick);
}
}
else
{
// if it's a one-shot, just stop playback
ProtoStreamClose(pCmdRef->pProtoStream);
}
}
/*F********************************************************************************/
/*!
\Function _ProtoStreamCallback
\Description
ProtoStream callback handler
\Input *pProtoStream - protostream module state
\Input eStatus - callback status
\Input *pBuffer - pointer to buffered data, or NULL if no data
\Input iBufSize - amount of data available in buffer
\Input *pUserData - user data pointer
\Output
int32_t - number of bytes consumed
\Version 11/16/2005 (jbrookes)
*/
/********************************************************************************F*/
#if STREAMER_USECALLBACK
static int32_t _ProtoStreamCallback(ProtoStreamRefT *pProtoStream, ProtoStreamStatusE eStatus, const uint8_t *pBuffer, int32_t iBufSize, void *pUserData)
{
StreamCmdRefT *pCmdRef = (StreamCmdRefT *)pUserData;
const uint8_t *pBufStart = pBuffer;
int32_t iResult;
#if STREAMER_DEBUG
int32_t iTick;
#endif
// validate cmdref
if ((pCmdRef == NULL) || (pCmdRef->pProtoStream != pProtoStream))
{
NetPrintf(("stream: error -- invalid callback ref\n"));
return(0);
}
// handle stream start state
if (eStatus == PROTOSTREAM_STATUS_BEGIN)
{
char strHeader[512];
// get header
ProtoStreamStatus(pProtoStream, 'htxt', strHeader, sizeof(strHeader));
// print header
NetPrintf(("stream: stream start\n"));
// parse icy header
_StreamParseHeader(pCmdRef, strHeader);
// save start time
pCmdRef->iStartTick = ZTick();
}
// calc current tick
#if STREAMER_DEBUG
iTick = ZTick() - pCmdRef->iStartTick;
#endif
// handle stream data ready state
if ((eStatus == PROTOSTREAM_STATUS_BEGIN) || (eStatus == PROTOSTREAM_STATUS_DATA))
{
// decode processing
for ( iResult = 1; iResult > 0; )
{
// process metadata, if any
if (pCmdRef->iMetaInterval != 0)
{
// clamp maximum amount of data
iResult = _StreamProcessMetaData(pCmdRef, pBuffer, iBufSize);
if (iResult > 0)
{
pBuffer += iResult;
iBufSize -= iResult;
}
// make sure we don't consume data into the next metaheader
if (iBufSize > pCmdRef->iMetaOffset)
{
iBufSize = pCmdRef->iMetaOffset;
}
}
// process mp3 data
if (iBufSize > 0)
{
// $$TODO$$ mimic mp3 decoding: for now just consume the entire buffer
pBuffer += iBufSize;
// update meta offset
if (pCmdRef->iMetaInterval != 0)
{
pCmdRef->iMetaOffset -= iBufSize;
}
}
else
{
break;
}
}
}
// handle stream done state
if (eStatus == PROTOSTREAM_STATUS_DONE)
{
// kill the player
NetPrintf(("stream: stream done; resetting player\n"));
_StreamDone(pCmdRef, ZTick());
}
#if STREAMER_DEBUG
if ((pBuffer - pBufStart) > 0)
{
// display status info
ZPrintf("stream: consumed %d bytes at %d:%02d:%03d\n", pBuffer - pBufStart,
iTick / (1000*60), // minutes
(iTick / 1000) % 60, // seconds
iTick % 1000); // thousandths of a second
}
#endif
return(pBuffer - pBufStart);
}
#endif
/*F********************************************************************************/
/*!
\Function _StreamUpdate
\Description
Read data from ProtoStream (non-callback interface)
\Input *pProtoStream - protostream module state
\Input eStatus - callback status
\Input *pBuffer - pointer to buffered data, or NULL if no data
\Input iBufSize - amount of data available in buffer
\Input *pUserData - user data pointer
\Output
int32_t - number of bytes consumed
\Version 11/16/2005 (jbrookes)
*/
/********************************************************************************F*/
#if !STREAMER_USECALLBACK
static int32_t _StreamUpdate(StreamCmdRefT *pCmdRef)
{
static char aBuffer[64 * 1024];
int32_t iResult;
if ((iResult = ProtoStreamRead(pCmdRef->pProtoStream, aBuffer, sizeof(aBuffer), 64)) > 0)
{
}
return(iResult);
}
#endif
/*
Stream Commands
*/
/*F*************************************************************************************/
/*!
\Function _SubcmdStreamCreate
\Description
Stream subcommand - create the module
\Input *_pCmdRef - unused
\Input argc - argument count
\Input *argv[] - argument list
\Input bHelp - TRUE means display command help
\Output None.
\Version 1.0 11/21/2005 (jbrookes) First Version
*/
/**************************************************************************************F*/
static void _SubcmdStreamCreate(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp)
{
StreamCmdRefT *pCmdRef;
int32_t iBufSize;
// usage
if ((argc > 3) || (bHelp == TRUE))
{
ZPrintf(" usage: %s create [bufsize]\n", argv[0]);
return;
}
// get buffer size
iBufSize = (argc == 3) ? strtol(argv[2], NULL, 10) * 1024 : 32 * 1024;
// allocate context
pCmdRef = _Stream_pCmdRef = ZMemAlloc(sizeof(*pCmdRef));
ds_memclr(pCmdRef, sizeof(*pCmdRef));
// create the streaming module
pCmdRef->pProtoStream = ProtoStreamCreate(iBufSize);
#if STREAMER_USECALLBACK
// set up callback info
ProtoStreamSetCallback(pCmdRef->pProtoStream, 100, _ProtoStreamCallback, pCmdRef);
#endif
// allow shoutcast server compatibility (shoutcast returns "ICY" instead of "HTTP" response)
ProtoStreamControl(pCmdRef->pProtoStream, 'hver', FALSE, 0, NULL);
// pretend to be WinAmp and enable metadata, as some servers require these
ProtoStreamControl(pCmdRef->pProtoStream, 'apnd', 0, 0,
"User-Agent: WinampMPEG/5.11\r\n"
"Icy-MetaData:1\r\n"
"Accept: */*\r\n");
// set default playlist
pCmdRef->pPlaylist = &_Playlist;
// seed random number generator for random playlist traversal
srand(NetTick());
// enable random play
pCmdRef->bRandomPlay = TRUE;
}
/*F*************************************************************************************/
/*!
\Function _SubcmdStreamDestroy
\Description
Stream subcommand - destroy module
\Input *_pCmdRef - module state
\Input argc - argument count
\Input *argv[] - argument list
\Input bHelp - TRUE means display command help
\Output None.
\Version 1.0 11/21/2005 (jbrookes) First Version
*/
/**************************************************************************************F*/
static void _SubcmdStreamDestroy(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp)
{
StreamCmdRefT *pCmdRef = (StreamCmdRefT *)_pCmdRef;
// validate arguments
if ((argc != 2) || bHelp)
{
ZPrintf(" usage: %s destroy\n", argv[0]);
return;
}
// close current strean, if any
_StreamClose(pCmdRef);
// free playlist, if any
if (pCmdRef->pPlaylist != &_Playlist)
{
ZMemFree(pCmdRef->pPlaylist);
}
if (pCmdRef->pRandom != NULL)
{
ZMemFree(pCmdRef->pRandom);
}
// destroy streamer
ProtoStreamDestroy(pCmdRef->pProtoStream);
// destroy module state
ZMemFree(pCmdRef);
_Stream_pCmdRef = NULL;
}
/*F*************************************************************************************/
/*!
\Function _SubcmdStreamOpen
\Description
Stream subcommand - open a stream
\Input *_pCmdRef - module state
\Input argc - argument count
\Input *argv[] - argument list
\Input bHelp - TRUE means display command help
\Output None.
\Version 1.0 11/21/2005 (jbrookes) First Version
*/
/**************************************************************************************F*/
static void _SubcmdStreamOpen(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp)
{
StreamCmdRefT *pCmdRef = (StreamCmdRefT *)_pCmdRef;
StreamStationT Station, *pStation;
int32_t iFreq, iStation = -1;
// validate arguments
if (((argc != 3) && (argc != 4)) || bHelp)
{
ZPrintf(" usage: %s open [url|playlist|index] [freq]\n", argv[0]);
return;
}
// playlist?
if (strstr(argv[2], ".xml"))
{
_StreamOpenPlaylist(pCmdRef, argv[2]);
_StreamDisplayPlaylist(pCmdRef, pCmdRef->pPlaylist);
return;
}
else if (!ds_strnicmp(argv[2], "http", 4)) // built-in url?
{
ds_memclr(&Station, sizeof(Station));
ds_strnzcpy(Station.strUrl[0], argv[2], sizeof(Station.strUrl[0]));
pCmdRef->iCurUrl = 0;
pStation = &Station;
}
else // assume it is a playlist index
{
if ((iStation = strtol(argv[2], NULL, 10)) >= pCmdRef->pPlaylist->iNumStations)
{
iStation = 0;
}
pStation = &pCmdRef->pPlaylist->Stations[iStation];
pCmdRef->iCurStation = pCmdRef->bRandomPlay ? _StreamGetRandIndex(pCmdRef, iStation) : iStation;
}
// get restart frequency
iFreq = (argc == 4) ? strtol(argv[3], NULL, 10) : PROTOSTREAM_FREQ_IMMED;
// reset url index
pCmdRef->iCurUrl = 0;
// open the stream
_StreamOpen(pCmdRef, pStation, iFreq);
}
/*F*************************************************************************************/
/*!
\Function _SubcmdStreamClose
\Description
Stream subcommand - close a stream
\Input *_pCmdRef - module state
\Input argc - argument count
\Input *argv[] - argument list
\Input bHelp - TRUE means display command help
\Output None.
\Version 1.0 11/21/2005 (jbrookes) First Version
*/
/**************************************************************************************F*/
static void _SubcmdStreamClose(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp)
{
StreamCmdRefT *pCmdRef = (StreamCmdRefT *)_pCmdRef;
// validate arguments
if ((argc != 2) || bHelp)
{
ZPrintf(" usage: %s close\n", argv[0]);
return;
}
// reset skip
pCmdRef->iSkipTime = 0;
// destroy stream
_StreamClose(pCmdRef);
}
/*F*************************************************************************************/
/*!
\Function _SubcmdStreamSkip
\Description
Stream subcommand - enable skip mode
\Input *_pCmdRef - module state
\Input argc - argument count
\Input *argv[] - argument list
\Input bHelp - TRUE means display command help
\Output None.
\Version 1.0 11/21/2005 (jbrookes) First Version
*/
/**************************************************************************************F*/
static void _SubcmdStreamSkip(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp)
{
StreamCmdRefT *pCmdRef = (StreamCmdRefT *)_pCmdRef;
// validate arguments
if (((argc != 2) && (argc != 3)) || bHelp)
{
ZPrintf(" usage: %s skip [random]|[time]\n", argv[0]);
return;
}
// activate?
if ((pCmdRef->iSkipTime == 0) || (argc == 3))
{
// get skip time in seconds
pCmdRef->iSkipTime = (argc == 3) ? strtol(argv[2], NULL, 10) : STREAM_DEFAULTSKIP;
// convert to milliseconds
pCmdRef->iSkipTime *= 1000;
}
else
{
// stop skipping
pCmdRef->iSkipTime = 0;
}
}
/*F*************************************************************************************/
/*!
\Function _SubcmdStreamControl
\Description
Stream subcommand - call control function
\Input *_pCmdRef - module state
\Input argc - argument count
\Input *argv[] - argument list
\Input bHelp - TRUE means display command help
\Output None.
\Version 1.0 11/21/2005 (jbrookes) First Version
*/
/**************************************************************************************F*/
static void _SubcmdStreamControl(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp)
{
StreamCmdRefT *pCmdRef = (StreamCmdRefT *)_pCmdRef;
int32_t iCmd, iValue=0, iValue2=0;
void *pValue=NULL;
if ((bHelp == TRUE) || (argc < 3))
{
ZPrintf(" usage: %s ctrl <args>\n", argv[0]);
return;
}
iCmd = argv[2][0] << 24;
iCmd |= argv[2][1] << 16;
iCmd |= argv[2][2] << 8;
iCmd |= argv[2][3];
if (argc > 3)
{
iValue = strtol(argv[3], NULL, 10);
}
if (argc > 4)
{
iValue2 = strtol(argv[4], NULL, 10);
}
// handle stream-specific commands
if (iCmd == 'rand')
{
pCmdRef->bRandomPlay = !pCmdRef->bRandomPlay;
ZPrintf("stream: random play %s\n", pCmdRef->bRandomPlay ? "enabled" : "disabled");
return;
}
// pass unhandled selectors to ProtoStreamControl();
ZPrintf("stream: executing ProtoStreamControl(pProtoUpnp, '%s', %d, %d, %s)\n", argv[2], iValue, iValue2, pValue ? pValue : "(null)");
ProtoStreamControl(pCmdRef->pProtoStream, iCmd, iValue, iValue2, pValue);
}
/*F********************************************************************************/
/*!
\Function _CmdStreamCb
\Description
Recurring stream callback.
\Input *argz - environment
\Input argc - number of args
\Input *argv[] - argument list
\Output int32_t - standard return code
\Version 11/16/2005 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _CmdStreamCb(ZContext *argz, int32_t argc, char *argv[])
{
StreamCmdRefT *pCmdRef = _Stream_pCmdRef;
int32_t iCurTick = ZTick();
// if no ref, we're done
if (pCmdRef == NULL)
{
return(0);
}
// check for kill
if (argc == 0)
{
char *strArgs[2] = { "stream", "destroy" };
ZPrintf("%s: killed\n", argv[0]);
_SubcmdStreamDestroy(pCmdRef, 2, strArgs, 0);
return(0);
}
// update skip processing
if (pCmdRef->iSkipTime != 0)
{
if ((iCurTick - pCmdRef->iSkipTick) > pCmdRef->iSkipTime)
{
_StreamSkip(pCmdRef, iCurTick);
}
}
// update protostream module
ProtoStreamUpdate(pCmdRef->pProtoStream);
#if !STREAMER_USECALLBACK
_StreamUpdate(pCmdRef);
#endif
// keep running
return(ZCallback(&_CmdStreamCb, 16));
}
/*** Public functions *************************************************************/
/*F********************************************************************************/
/*!
\Function CmdStream
\Description
Create the Module module.
\Input *argz - environment
\Input argc - number of args
\Input *argv[] - argument list
\Output int32_t - standard return code
\Version 11/16/2005 (jbrookes)
*/
/********************************************************************************F*/
int32_t CmdStream(ZContext *argz, int32_t argc, char *argv[])
{
unsigned char bHelp, bCreate = FALSE;
StreamCmdRefT *pCmdRef = _Stream_pCmdRef;
T2SubCmdT *pCmd;
// handle basic help
if ((argc < 2) || (((pCmd = T2SubCmdParse(_Stream_Commands, argc, argv, &bHelp)) == NULL)))
{
ZPrintf(" test the protostream module\n");
T2SubCmdUsage(argv[0], _Stream_Commands);
return(0);
}
// if no ref yet, make one
if ((pCmdRef == NULL) && strcmp(pCmd->strName, "create"))
{
char *pCreate = "create";
ZPrintf(" %s: ref has not been created - creating\n", argv[0]);
_SubcmdStreamCreate(pCmdRef, 1, &pCreate, bHelp);
pCmdRef = _Stream_pCmdRef;
bCreate = TRUE;
}
// hand off to command
pCmd->pFunc(pCmdRef, argc, argv, bHelp);
// if we executed create, remember
if (pCmd->pFunc == _SubcmdStreamCreate)
{
bCreate = TRUE;
}
// if we executed create, install periodic callback
return((bCreate == TRUE) ? ZCallback(_CmdStreamCb, 100) : 0);
}