2236 lines
72 KiB
C++
2236 lines
72 KiB
C++
|
/*
|
||
|
*
|
||
|
* @APPLE_LICENSE_HEADER_START@
|
||
|
*
|
||
|
* Copyright (c) 1999-2008 Apple 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@
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
8.11.99 rt - changed references to "PlaylistBroadcaster Setup" to Broadcast Description File
|
||
|
|
||
|
8.4.99 rt - changed references to "PlaylistBroadcaster Description" to Broadcast Setup File
|
||
|
- addded error messages
|
||
|
- prefilght config file access
|
||
|
- require log file creation
|
||
|
|
||
|
|
||
|
7.27.99 rt - removed license from about display
|
||
|
- updated credit names
|
||
|
- fixed mapping of --stop to 's' from 'l'
|
||
|
- added about to help
|
||
|
|
||
|
|
||
|
8.2.99 rt - changed reference to "channel setup" to "PlaylistBroadcaster Description"
|
||
|
- changed &d's in qtss_printf's to %d
|
||
|
|
||
|
*/
|
||
|
|
||
|
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include "SafeStdLib.h"
|
||
|
#include <signal.h>
|
||
|
#ifndef __MacOSX__
|
||
|
#include "getopt.h"
|
||
|
#endif
|
||
|
|
||
|
#ifndef __Win32__
|
||
|
#if defined (__solaris__) || defined (__osf__) || defined (__sgi__) || defined (__hpux__)
|
||
|
#include "daemon.h"
|
||
|
#else
|
||
|
#ifndef __FreeBSD__
|
||
|
#include <sys/sysctl.h>
|
||
|
#endif
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
#ifndef kVersionString
|
||
|
#include "../revision.h"
|
||
|
#endif
|
||
|
|
||
|
#include <errno.h>
|
||
|
#include "QTRTPFile.h"
|
||
|
|
||
|
#include "OSHeaders.h"
|
||
|
#include "OS.h"
|
||
|
#include "OSMemory.h"
|
||
|
#include "SocketUtils.h"
|
||
|
#include "Socket.h"
|
||
|
#include "Task.h"
|
||
|
#include "TimeoutTask.h"
|
||
|
#include "BroadcasterSession.h"
|
||
|
#include "OSArrayObjectDeleter.h"
|
||
|
#include "PickerFromFile.h"
|
||
|
#include "PLBroadcastDef.h"
|
||
|
#include "BroadcastLog.h"
|
||
|
#include "StringTranslator.h"
|
||
|
|
||
|
#ifndef __Win32__
|
||
|
#include "BCasterTracker.h"
|
||
|
#endif
|
||
|
|
||
|
#ifdef __Win32__
|
||
|
#include "getopt.h"
|
||
|
#endif
|
||
|
|
||
|
// must now inlcude this from the project level using the -i switch in the compiler
|
||
|
#ifndef __MacOSX__
|
||
|
#include "../revision.h"
|
||
|
#endif
|
||
|
|
||
|
#ifdef __MacOSX__
|
||
|
#include <sys/stat.h>
|
||
|
#endif
|
||
|
|
||
|
#include "playlist_SDPGen.h"
|
||
|
#include "playlist_broadcaster.h"
|
||
|
|
||
|
|
||
|
#include "MyAssert.h"
|
||
|
|
||
|
/*
|
||
|
local functions
|
||
|
*/
|
||
|
|
||
|
static void usage();
|
||
|
static void version();
|
||
|
static void help();
|
||
|
static PlaylistPicker* MakePickerFromConfig( PLBroadcastDef* broadcastParms );
|
||
|
static void SignalEventHandler( int signalID );
|
||
|
static int EvalBroadcasterErr(int err);
|
||
|
static const char* GetMovieFileErrString( int err );
|
||
|
|
||
|
static void RegisterEventHandlers();
|
||
|
static void ShowPlaylistStatus();
|
||
|
static void StopABroadcast( const char* arg );
|
||
|
static bool DoSDPGen( PLBroadcastDef *broadcastParms, bool preflight, bool overWriteSDP, bool generateNewSDP, int* numErrorsPtr, char* refMovie);
|
||
|
|
||
|
static bool AddOurPIDToTracker( const char* bcastSetupFilePath );
|
||
|
|
||
|
static void Cleanup();
|
||
|
|
||
|
/* -- preflighting --*/
|
||
|
|
||
|
static int MyAccess( const char* path, int mode );
|
||
|
static int PreflightSDPFileAccess( const char * sdpPath );
|
||
|
static int PreflightDestinationAddress( const char *destAddress );
|
||
|
static int PreflightBasePort( const char *basePort );
|
||
|
static int PreflightReferenceMovieAccess( const char *refMoviePath );
|
||
|
static bool PreflightTrackerFileAccess( int mode );
|
||
|
static bool PreFlightSetupFile( const char * bcastSetupFilePath );
|
||
|
static int PreflightClientBufferDelay( const char * delay, Float32 *ioDelay);
|
||
|
static void ShowSetupParams(PLBroadcastDef* broadcastParms, const char *file);
|
||
|
static int PreflightDestSDPFileAccess( const char * sdpPath );
|
||
|
static void PreFlightOrBroadcast( const char *bcastSetupFilePath, bool preflight, bool daemonize, bool showMovieList, bool writeCurrentMovieFile, char *destinationIP,bool writeNewSDP, const char* errorFilePath);
|
||
|
|
||
|
/* changed by emil@popwire.com */
|
||
|
static bool FileCreateAndCheckAccess(char *theFileName);
|
||
|
static void PrintPlaylistElement(PLDoubleLinkedListNode<SimplePlayListElement> *node,void *file);
|
||
|
static void ShowPlaylistElements(PlaylistPicker *picker,FILE *file);
|
||
|
static void RemoveFiles(PLBroadcastDef* broadcastParms);
|
||
|
/* ***************************************************** */
|
||
|
|
||
|
/*
|
||
|
local variables
|
||
|
*/
|
||
|
static char* sgProgramName; // the actual program name as input at commmand line
|
||
|
static char* sgTrackerDirPath = "/var/run";
|
||
|
static char* sgTrackerFilePath = "/var/run/broadcastlist";
|
||
|
static BroadcastLog* sgLogger = NULL;
|
||
|
static bool sgTrackingSucceeded = false;
|
||
|
static BroadcasterSession* sBroadcasterSession = NULL;
|
||
|
static StrPtrLen sSetupDirPath;
|
||
|
static PlaylistPicker* sTempPicker = NULL;
|
||
|
static SInt32 sElementCount = 0;
|
||
|
static SInt32 sMaxUpcomingListSize = 5;
|
||
|
static PLBroadcastDef* sBroadcastParms = NULL;
|
||
|
|
||
|
enum {maxSDPbuffSize = 10000};
|
||
|
static char sSDPBuffer[maxSDPbuffSize] = {0};
|
||
|
static bool sQuitImmediate = false;
|
||
|
static bool sAnnounceBroadcast = false;
|
||
|
static bool sPushOverTCP = false;
|
||
|
static int sRTSPClientError = 0;
|
||
|
static bool sErrorEvaluted = false;
|
||
|
static bool sDeepDebug = false;
|
||
|
|
||
|
static int sNumWarnings = 0;
|
||
|
static int sNumErrors = 0;
|
||
|
static bool sPreflight = false;
|
||
|
static bool sCleanupDone = false;
|
||
|
static bool sBurst = false;
|
||
|
|
||
|
//+ main is getting too big.. need to clean up and separate the command actions
|
||
|
// into a separate file.
|
||
|
|
||
|
|
||
|
int main(int argc, char *argv[]) {
|
||
|
|
||
|
char *bcastSetupFilePath = NULL;
|
||
|
bool daemonize = true;
|
||
|
int anOption = 0;
|
||
|
bool preflight = false;
|
||
|
bool showMovieList = false;
|
||
|
bool writeCurrentMovieFile = false;
|
||
|
int displayedOptions = 0; // count number of command line options we displayed
|
||
|
bool needsTracker = false; // set to true when PLB needs tracker access
|
||
|
bool needsLogfile = false; // set to true when PLB needs tracker access
|
||
|
char* destinationIP = NULL;
|
||
|
bool writeNewSDP = false;
|
||
|
char* errorlog = NULL;
|
||
|
extern char* optarg;
|
||
|
extern int optind;
|
||
|
|
||
|
#ifdef __Win32__
|
||
|
//
|
||
|
// Start Win32 DLLs
|
||
|
WORD wsVersion = MAKEWORD(1, 1);
|
||
|
WSADATA wsData;
|
||
|
(void)::WSAStartup(wsVersion, &wsData);
|
||
|
#endif
|
||
|
|
||
|
|
||
|
QTRTPFile::Initialize(); // do only once
|
||
|
OS::Initialize();
|
||
|
OSThread::Initialize();
|
||
|
OSMemory::SetMemoryError(ENOMEM);
|
||
|
Socket::Initialize();
|
||
|
SocketUtils::Initialize(false);
|
||
|
|
||
|
if (SocketUtils::GetNumIPAddrs() == 0)
|
||
|
{
|
||
|
qtss_printf("Network initialization failed. IP must be enabled to run PlaylistBroadcaster\n");
|
||
|
::exit(0);
|
||
|
}
|
||
|
|
||
|
#ifdef __MacOSX__
|
||
|
(void) ::umask(S_IWOTH); // make sure files are opened with default of owner -rw-rw-r-
|
||
|
#endif
|
||
|
|
||
|
|
||
|
sgProgramName = argv[0];
|
||
|
#ifdef __Win32__
|
||
|
while ((anOption = getopt(argc, argv, "vhdcpbDtai:fe:" )) != EOF)
|
||
|
#else
|
||
|
while ((anOption = getopt(argc, argv, "vhdcpbDls:tai:fe:" )) != EOF)
|
||
|
#endif
|
||
|
{
|
||
|
|
||
|
switch(anOption)
|
||
|
{
|
||
|
case 'b':
|
||
|
sBurst = true;
|
||
|
break;
|
||
|
|
||
|
case 'v':
|
||
|
::version();
|
||
|
::usage();
|
||
|
return 0;
|
||
|
|
||
|
case 'h':
|
||
|
::help();
|
||
|
displayedOptions++;
|
||
|
break;
|
||
|
|
||
|
case 'd':
|
||
|
daemonize = false;
|
||
|
break;
|
||
|
|
||
|
case 'c':
|
||
|
writeCurrentMovieFile = true;
|
||
|
break;
|
||
|
|
||
|
/*
|
||
|
case 'm':
|
||
|
showMovieList = true;
|
||
|
daemonize = false;
|
||
|
break;
|
||
|
*/
|
||
|
|
||
|
case 'p':
|
||
|
preflight = true;
|
||
|
daemonize = false;
|
||
|
needsTracker = true;
|
||
|
needsLogfile = true;
|
||
|
break;
|
||
|
|
||
|
case 'D':
|
||
|
sDeepDebug = true;
|
||
|
daemonize = false;
|
||
|
break;
|
||
|
|
||
|
#ifndef __Win32__
|
||
|
|
||
|
|
||
|
case 'l':
|
||
|
// show playlist broadcast status
|
||
|
if (!PreflightTrackerFileAccess( R_OK )) // check for read access.
|
||
|
::exit(-1);
|
||
|
ShowPlaylistStatus(); // <- exits() on failure
|
||
|
displayedOptions++;
|
||
|
break;
|
||
|
|
||
|
case 's':
|
||
|
// stop a playlist broadcast
|
||
|
// is there one to kill?
|
||
|
if (!PreflightTrackerFileAccess( R_OK | W_OK )) // check for read access.
|
||
|
::exit(-1);
|
||
|
StopABroadcast( optarg ); // <- exits() on failure
|
||
|
displayedOptions++;
|
||
|
break;
|
||
|
#endif
|
||
|
|
||
|
case 't':
|
||
|
sAnnounceBroadcast = true;
|
||
|
sPushOverTCP = true;
|
||
|
break;
|
||
|
|
||
|
case 'a':
|
||
|
sAnnounceBroadcast = true;
|
||
|
break;
|
||
|
|
||
|
case 'i':
|
||
|
destinationIP = (char*)malloc(strlen(optarg)+1);
|
||
|
strcpy(destinationIP, optarg);
|
||
|
qtss_printf("destinationIP =%s\n",destinationIP);
|
||
|
break;
|
||
|
|
||
|
case 'f':
|
||
|
writeNewSDP = true;
|
||
|
break;
|
||
|
|
||
|
case 'e':
|
||
|
errorlog = (char*)malloc(strlen(optarg)+1);
|
||
|
strcpy(errorlog, optarg);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
::usage();
|
||
|
::exit(-1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (argv[optind] != NULL)
|
||
|
{
|
||
|
bcastSetupFilePath = (char*)malloc(strlen(argv[optind])+1);
|
||
|
strcpy(bcastSetupFilePath, argv[optind]);
|
||
|
}
|
||
|
|
||
|
sPreflight = preflight;
|
||
|
|
||
|
if (errorlog != NULL)
|
||
|
{
|
||
|
if (preflight)
|
||
|
freopen(errorlog, "w", stdout);
|
||
|
else
|
||
|
freopen(errorlog, "a", stdout);
|
||
|
::setvbuf(stdout, (char *)NULL, _IONBF, 0);
|
||
|
}
|
||
|
|
||
|
// preflight needs a description file
|
||
|
if ( preflight && !bcastSetupFilePath )
|
||
|
{ qtss_printf("- PlaylistBroadcaster: Error. \"Preflight\" requires a broadcast description file.\n" );
|
||
|
::usage();
|
||
|
::exit(-1);
|
||
|
}
|
||
|
|
||
|
// don't complain about lack of description File if we were asked to display some options.
|
||
|
if ( displayedOptions > 0 && !bcastSetupFilePath )
|
||
|
::exit(0); // <-- we displayed option info, but have no description file to broadcast
|
||
|
|
||
|
PreFlightOrBroadcast( bcastSetupFilePath, preflight, daemonize, showMovieList, writeCurrentMovieFile,destinationIP,writeNewSDP, errorlog ); // <- exits() on failure -- NOT ANYMORE
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
}
|
||
|
|
||
|
BroadcasterSession *StartBroadcastRTSPSession(PLBroadcastDef *broadcastParms)
|
||
|
{
|
||
|
TaskThreadPool::AddThreads(1);
|
||
|
TimeoutTask::Initialize();
|
||
|
Socket::StartThread();
|
||
|
|
||
|
UInt32 inAddr = SocketUtils::ConvertStringToAddr(broadcastParms->mDestAddress);
|
||
|
|
||
|
UInt16 inPort = atoi(broadcastParms->mRTSPPort);
|
||
|
char * theURL = new char[512];
|
||
|
StringTranslator::EncodeURL(broadcastParms->mDestSDPFile, strlen(broadcastParms->mDestSDPFile) + 1, theURL, 512);
|
||
|
BroadcasterSession::BroadcasterType inBroadcasterType;
|
||
|
|
||
|
if (sPushOverTCP)
|
||
|
inBroadcasterType = BroadcasterSession::kRTSPTCPBroadcasterType;
|
||
|
else
|
||
|
inBroadcasterType = BroadcasterSession::kRTSPUDPBroadcasterType;
|
||
|
|
||
|
UInt32 inDurationInSec = 0;
|
||
|
UInt32 inStartPlayTimeInSec = 0;
|
||
|
UInt32 inRTCPIntervalInSec = 5;
|
||
|
UInt32 inOptionsIntervalInSec = 0;
|
||
|
UInt32 inHTTPCookie = 1;
|
||
|
Bool16 inAppendJunkData = false;
|
||
|
UInt32 inReadInterval = 50;
|
||
|
UInt32 inSockRcvBufSize = 32768;
|
||
|
StrPtrLen sdpSPL(sSDPBuffer,maxSDPbuffSize);
|
||
|
sRTSPClientError = QTFileBroadcaster::eNetworkConnectionError;
|
||
|
sBroadcasterSession = NEW BroadcasterSession(inAddr,
|
||
|
inPort,
|
||
|
theURL,
|
||
|
inBroadcasterType, // Client type
|
||
|
inDurationInSec, // Movie length
|
||
|
inStartPlayTimeInSec, // Movie start time
|
||
|
inRTCPIntervalInSec, // RTCP interval
|
||
|
inOptionsIntervalInSec, // Options interval
|
||
|
inHTTPCookie, // HTTP cookie
|
||
|
inAppendJunkData,
|
||
|
inReadInterval, // Interval between data reads
|
||
|
inSockRcvBufSize,
|
||
|
&sdpSPL,
|
||
|
broadcastParms->mName,
|
||
|
broadcastParms->mPassword,
|
||
|
sDeepDebug,
|
||
|
sBurst);
|
||
|
|
||
|
return sBroadcasterSession;
|
||
|
}
|
||
|
|
||
|
Bool16 AnnounceBroadcast(PLBroadcastDef *broadcastParms,QTFileBroadcaster *fileBroadcasterPtr)
|
||
|
{ // return true if no Announce required or broadcast is ok.
|
||
|
|
||
|
if (!sAnnounceBroadcast) return true;
|
||
|
|
||
|
// if the address is a multicast address then we can't announce the broadcast.
|
||
|
|
||
|
if(SocketUtils::IsMulticastIPAddr(ntohl(inet_addr(broadcastParms->mDestAddress)))) {
|
||
|
sAnnounceBroadcast = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
#if !MACOSXEVENTQUEUE
|
||
|
::select_startevents();//initialize the select() implementation of the event queue
|
||
|
#endif
|
||
|
|
||
|
if (SocketUtils::GetNumIPAddrs() == 0)
|
||
|
{
|
||
|
qtss_printf("IP must be enabled to run PlaylistBroadcaster\n");
|
||
|
//::exit(0); // why exit here? If we return false here, the calling function will take care of the error
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
broadcastParms->mTheSession = sBroadcasterSession = StartBroadcastRTSPSession(broadcastParms);
|
||
|
while (!sBroadcasterSession->IsDone() && BroadcasterSession::kBroadcasting != sBroadcasterSession->GetState())
|
||
|
{ OSThread::Sleep(100);
|
||
|
}
|
||
|
sRTSPClientError = 0;
|
||
|
int broadcastErr = sBroadcasterSession->GetRequestStatus();
|
||
|
|
||
|
Bool16 isOK = false;
|
||
|
if (broadcastErr != 200) do
|
||
|
{
|
||
|
if (412 == broadcastErr)
|
||
|
{
|
||
|
::EvalBroadcasterErr(broadcastErr);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (200 != broadcastErr)
|
||
|
{ //qtss_printf("broadcastErr = %"_S32BITARG_" sBroadcasterSession->GetDeathState()=%"_S32BITARG_" sBroadcasterSession->GetReasonForDying()=%"_S32BITARG_"\n",broadcastErr,sBroadcasterSession->GetDeathState(),sBroadcasterSession->GetReasonForDying());
|
||
|
if (sBroadcasterSession->GetDeathState() == BroadcasterSession::kSendingAnnounce && sBroadcasterSession->GetReasonForDying() == BroadcasterSession::kConnectionFailed)
|
||
|
::EvalBroadcasterErr(QTFileBroadcaster::eNetworkConnectionError);
|
||
|
else if (sBroadcasterSession->GetDeathState() == BroadcasterSession::kSendingAnnounce && sBroadcasterSession->GetReasonForDying() == BroadcasterSession::kBadSDP)
|
||
|
::EvalBroadcasterErr(QTFileBroadcaster::eSDPFileInvalid);
|
||
|
else if (401 == broadcastErr)
|
||
|
::EvalBroadcasterErr(QTFileBroadcaster::eNetworkAuthorization);
|
||
|
else if ((500 == broadcastErr) || (400 == broadcastErr) )
|
||
|
::EvalBroadcasterErr(QTFileBroadcaster::eNetworkNotSupported);
|
||
|
else
|
||
|
::EvalBroadcasterErr(QTFileBroadcaster::eNetworkRequestError);
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (sBroadcasterSession != NULL && BroadcasterSession::kDiedNormally != sBroadcasterSession->GetReasonForDying())
|
||
|
{ ::EvalBroadcasterErr(QTFileBroadcaster::eNetworkRequestError);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
isOK = true;
|
||
|
|
||
|
} while (false);
|
||
|
else
|
||
|
isOK = true;
|
||
|
|
||
|
if (isOK)
|
||
|
{
|
||
|
broadcastErr = fileBroadcasterPtr->SetUp(broadcastParms, &sQuitImmediate);
|
||
|
if ( broadcastErr )
|
||
|
{ ::EvalBroadcasterErr(broadcastErr);
|
||
|
qtss_printf( "- Broadcaster setup failed.\n" );
|
||
|
isOK = false;
|
||
|
if (sBroadcasterSession != NULL && !sBroadcasterSession->IsDone())
|
||
|
sBroadcasterSession->TearDownNow();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return isOK;
|
||
|
}
|
||
|
|
||
|
char* GetBroadcastDirPath(const char * setupFilePath)
|
||
|
{
|
||
|
int len = 2;
|
||
|
|
||
|
if (setupFilePath != NULL)
|
||
|
len = ::strlen(setupFilePath);
|
||
|
|
||
|
char *setupDirPath = new char [ len + 1 ];
|
||
|
|
||
|
if ( setupDirPath )
|
||
|
{
|
||
|
strcpy( setupDirPath, setupFilePath );
|
||
|
char* endOfDirPath = strrchr( setupDirPath, kPathDelimiterChar );
|
||
|
|
||
|
if ( endOfDirPath )
|
||
|
{
|
||
|
endOfDirPath++;
|
||
|
*endOfDirPath= 0;
|
||
|
|
||
|
int chDirErr = ::chdir( setupDirPath );
|
||
|
if ( chDirErr )
|
||
|
chDirErr = errno;
|
||
|
|
||
|
//qtss_printf("- PLB DEBUG MSG: setupDirPath==%s\n chdir err: %i\n", setupDirPath, chDirErr);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
setupDirPath[0] = '.';
|
||
|
setupDirPath[1] = kPathDelimiterChar;
|
||
|
setupDirPath[2] = 0;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
//qtss_printf("GetBroadcastDirPath setupDirPath = %s\n",setupDirPath);
|
||
|
return setupDirPath;
|
||
|
}
|
||
|
|
||
|
void CreateCurrentAndUpcomingFiles(PLBroadcastDef* broadcastParms)
|
||
|
{
|
||
|
if (!::strcmp(broadcastParms->mShowCurrent, "enabled"))
|
||
|
{ if(FileCreateAndCheckAccess(broadcastParms->mCurrentFile))
|
||
|
{ /* error */
|
||
|
sgLogger->LogInfo( "PlaylistBroadcaster Error: Failed to create current broadcast file" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
if (!::strcmp(broadcastParms->mShowUpcoming, "enabled"))
|
||
|
{ if(FileCreateAndCheckAccess(broadcastParms->mUpcomingFile))
|
||
|
{ /* error */
|
||
|
sgLogger->LogInfo( "PlaylistBroadcaster Error: Failed to create upcoming broadcast file" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
void UpdatePlaylistFiles(PLBroadcastDef* broadcastParms, PlaylistPicker *picker,PlaylistPicker *insertPicker)
|
||
|
{
|
||
|
if ( (NULL == broadcastParms)
|
||
|
|| (NULL == picker)
|
||
|
|| (NULL == insertPicker)
|
||
|
) return;
|
||
|
|
||
|
if(!access(broadcastParms->mStopFile, R_OK))
|
||
|
{
|
||
|
picker->CleanList();
|
||
|
PopulatePickerFromFile(picker, broadcastParms->mStopFile, "", NULL);
|
||
|
|
||
|
sTempPicker->CleanList();
|
||
|
|
||
|
remove(broadcastParms->mStopFile);
|
||
|
picker->mStopFlag = true;
|
||
|
}
|
||
|
|
||
|
/* if .replacelist file exists - replace current playlist */
|
||
|
if(!access(broadcastParms->mReplaceFile, R_OK))
|
||
|
{
|
||
|
picker->CleanList();
|
||
|
PopulatePickerFromFile(picker, broadcastParms->mReplaceFile, "", NULL);
|
||
|
|
||
|
sTempPicker->CleanList();
|
||
|
|
||
|
remove(broadcastParms->mReplaceFile);
|
||
|
picker->mStopFlag = false;
|
||
|
}
|
||
|
|
||
|
/* if .insertlist file exists - insert into current playlist */
|
||
|
if(!access(broadcastParms->mInsertFile, R_OK))
|
||
|
{
|
||
|
insertPicker->CleanList();
|
||
|
sTempPicker->CleanList();
|
||
|
|
||
|
PopulatePickerFromFile(insertPicker, broadcastParms->mInsertFile, "", NULL);
|
||
|
remove(broadcastParms->mInsertFile);
|
||
|
picker->mStopFlag = false;
|
||
|
}
|
||
|
|
||
|
|
||
|
// write upcoming playlist to .upcoming file
|
||
|
if (!::strcmp(broadcastParms->mShowUpcoming, "enabled"))
|
||
|
{
|
||
|
FILE *upcomingFile = fopen(broadcastParms->mUpcomingFile, "w");
|
||
|
if(upcomingFile)
|
||
|
{
|
||
|
sElementCount = 0;
|
||
|
|
||
|
if (!::strcmp(broadcastParms->mPlayMode, "weighted_random"))
|
||
|
qtss_fprintf(upcomingFile,"#random play - upcoming list not supported\n");
|
||
|
else
|
||
|
{ qtss_fprintf(upcomingFile,"*PLAY-LIST*\n");
|
||
|
ShowPlaylistElements(insertPicker,upcomingFile);
|
||
|
ShowPlaylistElements(picker,upcomingFile);
|
||
|
if ( picker->GetNumMovies() == 0
|
||
|
&& !picker->mStopFlag
|
||
|
&& 0 != ::strcmp(broadcastParms->mPlayMode, "sequential")
|
||
|
)
|
||
|
{ picker->CleanList();
|
||
|
PopulatePickerFromFile(picker,broadcastParms->mPlayListFile,"",NULL);
|
||
|
ShowPlaylistElements(picker,upcomingFile);
|
||
|
sTempPicker->CleanList();
|
||
|
PopulatePickerFromFile(sTempPicker,broadcastParms->mPlayListFile,"",NULL);
|
||
|
}
|
||
|
|
||
|
if ( sElementCount <= sMaxUpcomingListSize
|
||
|
&& 0 == ::strcmp(broadcastParms->mPlayMode, "sequential_looped")
|
||
|
)
|
||
|
{ if (sTempPicker->GetNumMovies() == 0)
|
||
|
{ sTempPicker->CleanList();
|
||
|
PopulatePickerFromFile(sTempPicker,broadcastParms->mPlayListFile,"",NULL);
|
||
|
}
|
||
|
//sElementCount can be zero if the playlist contains no paths to valid files
|
||
|
while ( (sElementCount != 0) && sElementCount <= sMaxUpcomingListSize )
|
||
|
ShowPlaylistElements(sTempPicker,upcomingFile);
|
||
|
}
|
||
|
}
|
||
|
fclose(upcomingFile);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( picker->GetNumMovies() == 0
|
||
|
&& !picker->mStopFlag
|
||
|
&& ::strcmp(broadcastParms->mPlayMode, "sequential")
|
||
|
)
|
||
|
{ picker->CleanList();
|
||
|
PopulatePickerFromFile(picker,broadcastParms->mPlayListFile,"",NULL);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
void UpdateCurrentFile(PLBroadcastDef* broadcastParms, char *thePick)
|
||
|
{
|
||
|
if ( (NULL == broadcastParms) || (NULL == thePick) )
|
||
|
return;
|
||
|
|
||
|
// save currently playing song to .current file
|
||
|
if (!::strcmp(broadcastParms->mShowCurrent, "enabled"))
|
||
|
{ FILE *currentFile = fopen(broadcastParms->mCurrentFile, "w");
|
||
|
if(currentFile)
|
||
|
{
|
||
|
if (sSetupDirPath.EqualIgnoreCase(thePick, sSetupDirPath.Len) || '\\' == thePick[0] || '/' == thePick[0])
|
||
|
qtss_fprintf(currentFile,"u=%s\n",thePick);
|
||
|
else
|
||
|
qtss_fprintf(currentFile,"u=%s%s\n", sSetupDirPath.Ptr,thePick);
|
||
|
|
||
|
fclose(currentFile);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
static void PreFlightOrBroadcast( const char *bcastSetupFilePath, bool preflight, bool daemonize, bool showMovieList, bool writeCurrentMovieFile, char *destinationIP,bool writeNewSDP, const char* errorFilePath)
|
||
|
{
|
||
|
PLBroadcastDef* broadcastParms = NULL;
|
||
|
PlaylistPicker* picker = NULL;
|
||
|
PlaylistPicker* insertPicker = NULL;
|
||
|
|
||
|
QTFileBroadcaster fileBroadcaster;
|
||
|
int broadcastErr = 0;
|
||
|
SInt32 moviePlayCount;
|
||
|
char* thePick = NULL;
|
||
|
int numMovieErrors;
|
||
|
bool didAtLeastOneMoviePlay = false;
|
||
|
bool sdpFileCreated = false;
|
||
|
char *allocatedIPStr = NULL;
|
||
|
bool generateNewSDP = false;
|
||
|
int numErrorsBeforeSDPGen = 0;
|
||
|
char theUserAgentStr[128];
|
||
|
char* thePLBStr = "PlaylistBroadcaster";
|
||
|
|
||
|
RegisterEventHandlers();
|
||
|
|
||
|
if ( !PreFlightSetupFile( bcastSetupFilePath ) ) // returns true on success and false on failure
|
||
|
{
|
||
|
sNumErrors++;
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
if (destinationIP != NULL)
|
||
|
{
|
||
|
allocatedIPStr = new char[strlen(destinationIP)+1];
|
||
|
strcpy(allocatedIPStr,destinationIP);
|
||
|
generateNewSDP = true;
|
||
|
}
|
||
|
|
||
|
broadcastParms = new PLBroadcastDef( bcastSetupFilePath, allocatedIPStr, sDeepDebug);
|
||
|
sBroadcastParms = broadcastParms;
|
||
|
|
||
|
if( !broadcastParms )
|
||
|
{
|
||
|
qtss_printf("- PlaylistBroadcaster: Error. Out of memory.\n" );
|
||
|
sNumErrors++;
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
|
||
|
if ( !broadcastParms->ParamsAreValid() )
|
||
|
{
|
||
|
qtss_printf("- PlaylistBroadcaster: Error reading the broadcast description file \"%s\". (bad format or missing file)\n", bcastSetupFilePath );
|
||
|
broadcastParms->ShowErrorParams();
|
||
|
sNumErrors++;
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
|
||
|
if ( preflight || sDeepDebug)
|
||
|
ShowSetupParams(broadcastParms, bcastSetupFilePath);
|
||
|
|
||
|
if (NULL == GetBroadcastDirPath(bcastSetupFilePath))
|
||
|
{
|
||
|
qtss_printf("- PlaylistBroadcaster: Error. Out of memory.\n" );
|
||
|
sNumErrors++;
|
||
|
goto bail;
|
||
|
}
|
||
|
sSetupDirPath.Set(GetBroadcastDirPath(bcastSetupFilePath), strlen(GetBroadcastDirPath(bcastSetupFilePath)));
|
||
|
|
||
|
if (preflight)
|
||
|
{
|
||
|
picker = new PlaylistPicker(false); // make sequential picker, no looping
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
picker = MakePickerFromConfig( broadcastParms ); // make picker according to parms
|
||
|
sTempPicker = new PlaylistPicker(false);
|
||
|
insertPicker = new PlaylistPicker(false);
|
||
|
insertPicker->mRemoveFlag = true;
|
||
|
}
|
||
|
|
||
|
if ( !picker )
|
||
|
{
|
||
|
qtss_printf("- PlaylistBroadcaster: Error. Out of memory.\n" );
|
||
|
sNumErrors++;
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
if ( preflight || sDeepDebug)
|
||
|
qtss_printf("\n");
|
||
|
|
||
|
Assert( broadcastParms->mPlayListFile );
|
||
|
if ( broadcastParms->mPlayListFile )
|
||
|
{
|
||
|
char* fileName;
|
||
|
|
||
|
fileName = broadcastParms->mPlayListFile;
|
||
|
// initial call uses empty string for path, NULL for loop detection list
|
||
|
(void)PopulatePickerFromFile( picker, fileName, "", NULL );
|
||
|
|
||
|
// ignore errors, if we have movies in the list, play them
|
||
|
}
|
||
|
|
||
|
if ( preflight )
|
||
|
{
|
||
|
if ( picker->mNumToPickFrom == 1 )
|
||
|
qtss_printf( "\nThere is (%li) movie in the Playlist.\n\n", (SInt32) picker->mNumToPickFrom );
|
||
|
else
|
||
|
qtss_printf( "\nThere are (%li) movies in the Playlist.\n\n", (SInt32) picker->mNumToPickFrom );
|
||
|
}
|
||
|
|
||
|
if ( !picker->mNumToPickFrom )
|
||
|
{
|
||
|
qtss_printf( "- PlaylistBroadcaster setup failed: There are no movies to play.\n" );
|
||
|
sNumErrors++;
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
|
||
|
// check that we have enough movies to cover the recent movies list.
|
||
|
if ( preflight && broadcastParms->ParamsAreValid() )
|
||
|
{
|
||
|
if ( !strcmp( broadcastParms->mPlayMode, "weighted_random" ) ) // this implies "random" play
|
||
|
{
|
||
|
if ( broadcastParms->mLimitPlayQueueLength >= picker->mNumToPickFrom )
|
||
|
{
|
||
|
broadcastParms->mLimitPlayQueueLength = picker->mNumToPickFrom-1;
|
||
|
qtss_printf("- PlaylistBroadcaster Warning:\n The recent_movies_list_size setting is greater than \n");
|
||
|
qtss_printf(" or equal to the number of movies in the playlist.\n" );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// create the log file
|
||
|
sgLogger = new BroadcastLog( broadcastParms, &sSetupDirPath );
|
||
|
|
||
|
|
||
|
Assert( sgLogger != NULL );
|
||
|
|
||
|
if( sgLogger == NULL )
|
||
|
{
|
||
|
qtss_printf("- PlaylistBroadcaster: Error. Out of memory.\n" );
|
||
|
sNumErrors++;
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
numErrorsBeforeSDPGen = sNumErrors;
|
||
|
sdpFileCreated = DoSDPGen( broadcastParms, preflight, writeNewSDP,generateNewSDP, &sNumErrors, picker->GetFirstFile());
|
||
|
if( sNumErrors > numErrorsBeforeSDPGen )
|
||
|
goto bail;
|
||
|
|
||
|
if (!sAnnounceBroadcast)
|
||
|
broadcastErr = fileBroadcaster.SetUp(broadcastParms, &sQuitImmediate);
|
||
|
|
||
|
if ( broadcastErr )
|
||
|
{
|
||
|
::EvalBroadcasterErr(broadcastErr);
|
||
|
qtss_printf( "- Broadcaster setup failed.\n" );
|
||
|
sNumErrors++;
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
if (preflight)
|
||
|
{
|
||
|
fileBroadcaster.fPlay = false;
|
||
|
}
|
||
|
if ( !PreflightTrackerFileAccess( R_OK | W_OK ) )
|
||
|
{
|
||
|
sNumErrors++;
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
//Unless the Debug command line option is set, daemonize the process at this point
|
||
|
if (daemonize)
|
||
|
{
|
||
|
#ifndef __Win32__
|
||
|
qtss_printf("- PlaylistBroadcaster: Started in background.\n");
|
||
|
|
||
|
// keep the same working directory..
|
||
|
if (::daemon( 1, 0 ) != 0)
|
||
|
{
|
||
|
qtss_printf("- PlaylistBroadcaster: System error (%i).\n", errno);
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
#endif //__Win32__
|
||
|
}
|
||
|
|
||
|
// If daemonize, then reopen stdout to the error file
|
||
|
if (daemonize && (errorFilePath != NULL))
|
||
|
{
|
||
|
freopen(errorFilePath, "a", stdout);
|
||
|
::setvbuf(stdout, (char *)NULL, _IONBF, 0);
|
||
|
}
|
||
|
|
||
|
|
||
|
#ifndef __Win32__
|
||
|
qtss_snprintf(theUserAgentStr, ::strlen(thePLBStr) + 1 + ::strlen(kVersionString) + 1, "%s/%s", thePLBStr, kVersionString);
|
||
|
#else
|
||
|
_snprintf(theUserAgentStr, ::strlen(thePLBStr) + 1 + ::strlen(kVersionString) + 1, "%s/%s", thePLBStr, kVersionString);
|
||
|
#endif
|
||
|
RTSPClient::SetUserAgentStr(theUserAgentStr);
|
||
|
|
||
|
if (!preflight && !AnnounceBroadcast(broadcastParms,&fileBroadcaster))
|
||
|
{
|
||
|
sNumErrors++;
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
// ^ daemon must be called before we Open the log and tracker since we'll
|
||
|
// get a new pid, our files close, ( does SIGTERM get sent? )
|
||
|
|
||
|
if (( sgLogger != NULL ) && ( sgLogger->WantsLogging() ))
|
||
|
sgLogger->EnableLog( false ); // don't append ".log" to name for PLB
|
||
|
|
||
|
if ( sgLogger->WantsLogging() && !sgLogger->IsLogEnabled() )
|
||
|
{
|
||
|
if ( sgLogger->LogDirName() && *sgLogger->LogDirName() )
|
||
|
qtss_printf("- PlaylistBroadcaster: The log file failed to open.\n ( path: %s/%s )\n Exiting.\n", sgLogger->LogDirName(), sgLogger->LogFileName() );
|
||
|
else
|
||
|
qtss_printf("- PlaylistBroadcaster: The log file failed to open.\n ( path: %s )\n Exiting.\n", sgLogger->LogFileName() );
|
||
|
|
||
|
sNumErrors++;
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
|
||
|
if (broadcastParms->mPIDFile != NULL)
|
||
|
{
|
||
|
if(!FileCreateAndCheckAccess(broadcastParms->mPIDFile))
|
||
|
{
|
||
|
FILE *pidFile = fopen(broadcastParms->mPIDFile, "w");
|
||
|
if(pidFile)
|
||
|
{
|
||
|
qtss_fprintf(pidFile,"%d\n",getpid());
|
||
|
fclose(pidFile);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if ( !AddOurPIDToTracker( bcastSetupFilePath ) ) // <-- doesn't exit on failure anymore - returns false if failed
|
||
|
{
|
||
|
// writes to the broadcast list file only if the pid_file config param doesn't exist
|
||
|
sNumErrors++;
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
if ( !preflight )
|
||
|
sgLogger->LogInfo( "PlaylistBroadcaster started." );
|
||
|
else
|
||
|
sgLogger->LogInfo( "PlaylistBroadcaster preflight started." );
|
||
|
|
||
|
//+ make the RTP movie broadcaster
|
||
|
|
||
|
if ( !preflight )
|
||
|
{
|
||
|
qtss_printf( "\n" );
|
||
|
qtss_printf( "[pick#] movie path\n" );
|
||
|
qtss_printf( "----------------------------\n" );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
qtss_printf( "\n" );
|
||
|
qtss_printf( "Problems found\n" );
|
||
|
qtss_printf( "--------------\n" );
|
||
|
}
|
||
|
|
||
|
if(!preflight)
|
||
|
CreateCurrentAndUpcomingFiles(broadcastParms);
|
||
|
|
||
|
moviePlayCount = 0;
|
||
|
numMovieErrors = 0;
|
||
|
didAtLeastOneMoviePlay = false;
|
||
|
sMaxUpcomingListSize = ::atoi( broadcastParms->mMaxUpcomingMovieListSize );
|
||
|
|
||
|
while (true)
|
||
|
{
|
||
|
|
||
|
if (!showMovieList && !preflight)
|
||
|
{
|
||
|
UpdatePlaylistFiles(broadcastParms, picker, insertPicker);
|
||
|
}
|
||
|
|
||
|
if (NULL != insertPicker)
|
||
|
thePick = insertPicker->PickOne();
|
||
|
|
||
|
if (NULL == thePick && (NULL != picker))
|
||
|
thePick = picker->PickOne();
|
||
|
|
||
|
|
||
|
if ( (thePick != NULL) && (!preflight || showMovieList) )
|
||
|
{
|
||
|
// display the picks in debug mode, but not preflight
|
||
|
qtss_printf( "[%li] ", moviePlayCount );
|
||
|
{
|
||
|
if (sSetupDirPath.EqualIgnoreCase(thePick, sSetupDirPath.Len) || '\\' == thePick[0] || '/' == thePick[0])
|
||
|
qtss_printf("%s picked\n", thePick);
|
||
|
else
|
||
|
qtss_printf("%s%s picked\n", sSetupDirPath.Ptr,thePick);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
if ( !showMovieList )
|
||
|
{
|
||
|
int playError;
|
||
|
|
||
|
if(!preflight)
|
||
|
{ UpdateCurrentFile(broadcastParms, thePick);
|
||
|
|
||
|
/* if playlist is about to run out repopulate it */
|
||
|
if ( !::strcmp(broadcastParms->mPlayMode, "sequential_looped") )
|
||
|
{
|
||
|
if(NULL == thePick &&!picker->mStopFlag)
|
||
|
{
|
||
|
if (picker->GetNumMovies() == 0)
|
||
|
break;
|
||
|
else
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (thePick == NULL)
|
||
|
break;
|
||
|
|
||
|
++moviePlayCount;
|
||
|
|
||
|
if(!preflight)
|
||
|
{
|
||
|
SInt64 startTime = OS::Milliseconds();
|
||
|
SInt64 endTime = 0;
|
||
|
playError = fileBroadcaster.PlayMovie( thePick, broadcastParms->mCurrentFile );
|
||
|
endTime = OS::Milliseconds();
|
||
|
|
||
|
//were we able to actually play the movie?
|
||
|
didAtLeastOneMoviePlay = didAtLeastOneMoviePlay || (playError == 0);
|
||
|
|
||
|
//ok, we've reached the end of the current playlist
|
||
|
if (picker->GetNumMovies() == 0)
|
||
|
{
|
||
|
//If we determine that every one of the movies resulted in an error, then bail
|
||
|
if (!didAtLeastOneMoviePlay)
|
||
|
{
|
||
|
qtss_printf("Quitting: Playlist contains no valid files.\n");
|
||
|
sgLogger->LogInfo( "Quitting: Playlist contains no valid files.\n" );
|
||
|
goto bail;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
didAtLeastOneMoviePlay = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// log the result of broacasting the picked movie
|
||
|
sgLogger->LogMediaData( thePick,
|
||
|
fileBroadcaster.fCurrentMovieName,
|
||
|
fileBroadcaster.fCurrentMovieCopyright,
|
||
|
fileBroadcaster.fCurrentMovieComment,
|
||
|
fileBroadcaster.fCurrentMovieAuthor,
|
||
|
fileBroadcaster.fCurrentMovieArtist,
|
||
|
fileBroadcaster.fCurrentMovieAlbum,
|
||
|
(UInt32) ((endTime - startTime)/1000L),
|
||
|
playError);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
playError = fileBroadcaster.PlayMovie( thePick, NULL );
|
||
|
}
|
||
|
|
||
|
if (sQuitImmediate)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (sBroadcasterSession != NULL && sBroadcasterSession->GetReasonForDying() != BroadcasterSession::kDiedNormally)
|
||
|
{
|
||
|
playError = ::EvalBroadcasterErr(QTFileBroadcaster::eNetworkConnectionFailed);
|
||
|
sNumErrors++;
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
if ( playError == 0 && sAnnounceBroadcast)
|
||
|
{
|
||
|
int theErr = sBroadcasterSession->GetRequestStatus();
|
||
|
if (200 != theErr)
|
||
|
{
|
||
|
if (401 == theErr)
|
||
|
playError = QTFileBroadcaster::eNetworkAuthorization;
|
||
|
else if (500 == theErr)
|
||
|
playError = QTFileBroadcaster::eNetworkNotSupported;
|
||
|
else
|
||
|
playError = QTFileBroadcaster::eNetworkConnectionError;
|
||
|
|
||
|
sNumErrors++;
|
||
|
goto bail;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (playError)
|
||
|
{
|
||
|
playError = ::EvalBroadcasterErr(playError);
|
||
|
|
||
|
if (playError == QTFileBroadcaster::eNetworkConnectionError)
|
||
|
goto bail;
|
||
|
|
||
|
qtss_printf(" (file: %s err: %d %s)\n", thePick, playError,GetMovieFileErrString( playError ) );
|
||
|
sNumWarnings++;
|
||
|
numMovieErrors++;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int tracks = fileBroadcaster.GetMovieTrackCount() ;
|
||
|
int mtracks = fileBroadcaster.GetMappedMovieTrackCount();
|
||
|
|
||
|
if (tracks != mtracks)
|
||
|
{
|
||
|
sNumWarnings++;
|
||
|
numMovieErrors++;
|
||
|
qtss_printf("- PlaylistBroadcaster: Warning, movie tracks do not match the SDP file.\n" );
|
||
|
qtss_printf(" Movie: %s .\n", thePick );
|
||
|
qtss_printf(" %i of %i hinted tracks will not broadcast.\n", tracks- mtracks, tracks );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( !preflight && (playError != 0) )
|
||
|
sgLogger->LogMediaError( thePick, GetMovieFileErrString( playError ),NULL );
|
||
|
}
|
||
|
|
||
|
|
||
|
delete [] thePick;
|
||
|
thePick = NULL;
|
||
|
|
||
|
} //while (true)
|
||
|
|
||
|
remove(broadcastParms->mCurrentFile);
|
||
|
remove(broadcastParms->mUpcomingFile);
|
||
|
|
||
|
if ( preflight )
|
||
|
{
|
||
|
|
||
|
|
||
|
char str[256];
|
||
|
qtss_printf( " - " );
|
||
|
if (numMovieErrors == 1)
|
||
|
strcpy(str, "PlaylistBroadcaster found one problem movie file.");
|
||
|
else
|
||
|
qtss_sprintf( str, "PlaylistBroadcaster found %d problem movie files." , numMovieErrors );
|
||
|
qtss_printf( "%s\n", str );
|
||
|
if (sgLogger != NULL)
|
||
|
sgLogger->LogInfo( str );
|
||
|
|
||
|
if (numMovieErrors == moviePlayCount)
|
||
|
{
|
||
|
qtss_printf("There are no valid movies to play\n");
|
||
|
sNumErrors++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
if (NULL != sBroadcasterSession && !sBroadcasterSession->IsDone() )
|
||
|
{ sErrorEvaluted = true;
|
||
|
}
|
||
|
|
||
|
bail:
|
||
|
|
||
|
Cleanup();
|
||
|
|
||
|
delete picker;
|
||
|
|
||
|
sBroadcastParms = NULL;
|
||
|
delete broadcastParms;
|
||
|
|
||
|
if ( !preflight )
|
||
|
{
|
||
|
if (sgLogger != NULL)
|
||
|
{ if (!sQuitImmediate)
|
||
|
sgLogger->LogInfo( "PlaylistBroadcaster finished." );
|
||
|
else
|
||
|
sgLogger->LogInfo( "PlaylistBroadcaster stopped." );
|
||
|
}
|
||
|
|
||
|
if (!sQuitImmediate) // test sQuitImmediate again
|
||
|
qtss_printf( "\nPlaylistBroadcaster broadcast finished.\n" );
|
||
|
else
|
||
|
qtss_printf( "\nPlaylistBroadcaster broadcast stopped.\n" );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (sgLogger != NULL)
|
||
|
{ if (!sQuitImmediate)
|
||
|
sgLogger->LogInfo( "PlaylistBroadcaster preflight finished." );
|
||
|
else
|
||
|
sgLogger->LogInfo( "PlaylistBroadcaster preflight stopped." );
|
||
|
}
|
||
|
|
||
|
if (!sQuitImmediate) // test sQuitImmediate again
|
||
|
qtss_printf( "\nPlaylistBroadcaster preflight finished.\n" );
|
||
|
else
|
||
|
qtss_printf( "\nPlaylistBroadcaster preflight stopped.\n" );
|
||
|
|
||
|
}
|
||
|
sgLogger = NULL; // protect the interrupt handler and just let it die don't delete because it is a task thread
|
||
|
|
||
|
#ifndef __Win32__
|
||
|
if ( sgTrackingSucceeded )
|
||
|
{
|
||
|
// remove ourselves from the tracker
|
||
|
// this is the "normal" remove, also signal handlers
|
||
|
// may remove us.
|
||
|
|
||
|
BCasterTracker tracker( sgTrackerFilePath );
|
||
|
|
||
|
tracker.RemoveByProcessID( getpid() );
|
||
|
tracker.Save();
|
||
|
}
|
||
|
#endif //__Win32__
|
||
|
if (sBroadcasterSession != NULL && !sBroadcasterSession->IsDone())
|
||
|
{
|
||
|
//qtss_printf("QUIT now sBroadcasterSession->TearDownNow();\n");
|
||
|
sBroadcasterSession->TearDownNow();
|
||
|
int count=0;
|
||
|
while (count++ < 30 && !sBroadcasterSession->IsDone() )
|
||
|
{ sBroadcasterSession->Run();
|
||
|
OSThread::Sleep(100);
|
||
|
}
|
||
|
sBroadcasterSession = NULL;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
static void Cleanup()
|
||
|
{
|
||
|
if (sCleanupDone == true)
|
||
|
return;
|
||
|
|
||
|
sCleanupDone = true;
|
||
|
|
||
|
if (sPreflight)
|
||
|
{
|
||
|
qtss_printf("Warnings: %d\n", sNumWarnings);
|
||
|
qtss_printf("Errors: %d\n", sNumErrors);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
qtss_printf("Broadcast Warnings: %d\n", sNumWarnings);
|
||
|
qtss_printf("Broadcast Errors: %d\n", sNumErrors);
|
||
|
}
|
||
|
|
||
|
RemoveFiles(sBroadcastParms);
|
||
|
|
||
|
}
|
||
|
|
||
|
static void version()
|
||
|
{
|
||
|
/*
|
||
|
print PlaylistBroadcaster version and build info
|
||
|
|
||
|
see revision.h
|
||
|
*/
|
||
|
|
||
|
//RoadRunner/2.0.0-v24 Built on: Sep 17 1999 , 16:09:53
|
||
|
//revision.h (project level file) -- include with -i option
|
||
|
qtss_printf("PlaylistBroadcaster/%s Built on: %s, %s\n", kVersionString, __DATE__, __TIME__ );
|
||
|
|
||
|
}
|
||
|
|
||
|
static void usage()
|
||
|
{
|
||
|
/*
|
||
|
print PlaylistBroadcaster usage string
|
||
|
|
||
|
*/
|
||
|
#ifndef __Win32__
|
||
|
qtss_printf("usage: PlaylistBroadcaster [-v] [-h] [-p] [-c] [-a] [-t] [-i destAddress] [-e filename] [-f] [-d] [-l] [-s broadcastNum] filename\n" );
|
||
|
#else
|
||
|
qtss_printf("usage: PlaylistBroadcaster [-v] [-h] [-p] [-c] [-a] [-t] [-i destAddress] [-e filename] [-f] filename\n" );
|
||
|
#endif
|
||
|
|
||
|
qtss_printf(" -v: Display version\n" );
|
||
|
qtss_printf(" -h: Display help\n" );
|
||
|
qtss_printf(" -p: Verify a broadcast description file and movie list.\n" );
|
||
|
qtss_printf(" -c: Show the current movie in the log file.\n" );
|
||
|
qtss_printf(" -a: Announce the broadcast to the server.\n" );
|
||
|
qtss_printf(" -t: Send the broadcast over TCP to the server.\n" );
|
||
|
qtss_printf(" -i: Specify the destination ip address. Over-rides config file value.\n" );
|
||
|
qtss_printf(" -e: Log errors to filename.\n" );
|
||
|
qtss_printf(" -f: Force a new SDP file to be created even if one already exists.\n" );
|
||
|
#ifndef __Win32__
|
||
|
qtss_printf(" -d: Run attached to the terminal.\n" );
|
||
|
qtss_printf(" -l: List running currently broadcasts.\n" );
|
||
|
qtss_printf(" -s: Stop a running broadcast.\n" );
|
||
|
#endif
|
||
|
qtss_printf(" filename: Broadcast description filename.\n" );
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
static void help()
|
||
|
{
|
||
|
/*
|
||
|
print PlaylistBroadcaster help info
|
||
|
|
||
|
*/
|
||
|
::version();
|
||
|
|
||
|
::usage();
|
||
|
|
||
|
|
||
|
qtss_printf("\n\nSample broadcast description file: ");
|
||
|
PLBroadcastDef(NULL,NULL,false);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
static PlaylistPicker* MakePickerFromConfig( PLBroadcastDef* broadcastParms )
|
||
|
{
|
||
|
// construct a PlaylistPicker object using options set from a PLBroadcastDef
|
||
|
|
||
|
PlaylistPicker *picker = NULL;
|
||
|
|
||
|
if ( broadcastParms && broadcastParms->ParamsAreValid() )
|
||
|
{
|
||
|
if ( broadcastParms->mPlayMode )
|
||
|
{
|
||
|
if ( !::strcmp( broadcastParms->mPlayMode, "weighted_random" ) )
|
||
|
{
|
||
|
int noPlayQueueLen = 0;
|
||
|
|
||
|
noPlayQueueLen = broadcastParms->mLimitPlayQueueLength;
|
||
|
|
||
|
picker = new PlaylistPicker( 10, noPlayQueueLen );
|
||
|
|
||
|
}
|
||
|
else if ( !::strcmp( broadcastParms->mPlayMode, "sequential_looped" ) )
|
||
|
{
|
||
|
picker = new PlaylistPicker(true);
|
||
|
picker->mRemoveFlag = true;
|
||
|
}
|
||
|
else if ( !::strcmp( broadcastParms->mPlayMode, "sequential" ) )
|
||
|
{
|
||
|
picker = new PlaylistPicker(false);
|
||
|
picker->mRemoveFlag = true;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return picker;
|
||
|
}
|
||
|
|
||
|
static const char* GetMovieFileErrString( int err )
|
||
|
{
|
||
|
static char buff[80];
|
||
|
|
||
|
switch ( err )
|
||
|
{
|
||
|
case 0:
|
||
|
break;
|
||
|
|
||
|
case QTFileBroadcaster::eMovieFileNotFound:
|
||
|
return "Movie file not found.";
|
||
|
|
||
|
case QTFileBroadcaster::eMovieFileNoHintedTracks:
|
||
|
return "Movie file has no hinted tracks.";
|
||
|
|
||
|
case QTFileBroadcaster::eMovieFileNoSDPMatches :
|
||
|
return "Movie file does not match SDP.";
|
||
|
|
||
|
case QTFileBroadcaster::eMovieFileInvalid:
|
||
|
return "Movie file is invalid.";
|
||
|
|
||
|
case QTFileBroadcaster::eMovieFileInvalidName:
|
||
|
return "Movie file name is missing or too long.";
|
||
|
|
||
|
default:
|
||
|
qtss_sprintf( buff, "Movie set up error %d occured.",err );
|
||
|
return buff;
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
|
||
|
}
|
||
|
|
||
|
static int sErr = 0;
|
||
|
static int EvalBroadcasterErr(int err)
|
||
|
{
|
||
|
int returnErr = err;
|
||
|
if (sErr != 0)
|
||
|
return sErr;
|
||
|
|
||
|
switch (err)
|
||
|
{ case 412:
|
||
|
{ qtss_printf("- Server Session Failed: The request was denied.");
|
||
|
qtss_printf("\n");
|
||
|
break;
|
||
|
}
|
||
|
case QTFileBroadcaster::eNoAvailableSockets :
|
||
|
case QTFileBroadcaster::eSDPFileNotFound :
|
||
|
case QTFileBroadcaster::eSDPDestAddrInvalid :
|
||
|
case QTFileBroadcaster::eSDPFileInvalid :
|
||
|
case QTFileBroadcaster::eSDPFileNoMedia :
|
||
|
case QTFileBroadcaster::eSDPFileNoPorts :
|
||
|
case QTFileBroadcaster::eSDPFileInvalidPort :
|
||
|
{ qtss_printf("- SDP set up failed: ");
|
||
|
switch( err )
|
||
|
{
|
||
|
case QTFileBroadcaster::eNoAvailableSockets : qtss_printf("System error. No sockets are available to broadcast from.");
|
||
|
break;
|
||
|
case QTFileBroadcaster::eSDPFileNotFound : qtss_printf("The SDP file is missing.");
|
||
|
break;
|
||
|
case QTFileBroadcaster::eSDPDestAddrInvalid : qtss_printf("The SDP file server address is invalid.");
|
||
|
break;
|
||
|
case QTFileBroadcaster::eSDPFileInvalid : qtss_printf("The SDP file is invalid.");
|
||
|
break;
|
||
|
case QTFileBroadcaster::eSDPFileNoMedia : qtss_printf("The SDP file is missing media (m=) references.");
|
||
|
break;
|
||
|
case QTFileBroadcaster::eSDPFileNoPorts : qtss_printf("The SDP file is missing server port information.");
|
||
|
break;
|
||
|
case QTFileBroadcaster::eSDPFileInvalidPort : qtss_printf("The SDP file contains an invalid port. Valid range is 5004 - 65530.");
|
||
|
break;
|
||
|
|
||
|
default: qtss_printf("SDP set up error %d occured.",err);
|
||
|
break;
|
||
|
|
||
|
};
|
||
|
qtss_printf("\n");
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case QTFileBroadcaster::eSDPFileInvalidTTL:
|
||
|
case QTFileBroadcaster::eDescriptionInvalidDestPort:
|
||
|
case QTFileBroadcaster::eSDPFileInvalidName:
|
||
|
case QTFileBroadcaster::eNetworkSDPFileNameInvalidBadPath:
|
||
|
case QTFileBroadcaster::eNetworkSDPFileNameInvalidMissing:
|
||
|
{ qtss_printf("- Description set up failed: ");
|
||
|
switch( err )
|
||
|
{
|
||
|
|
||
|
case QTFileBroadcaster::eSDPFileInvalidTTL : qtss_printf("The multicast_ttl value is incorrect. Valid range is 1 to 255.");
|
||
|
break;
|
||
|
case QTFileBroadcaster::eDescriptionInvalidDestPort : qtss_printf("The destination_base_port value is incorrect. Valid range is 5004 - 65530.");
|
||
|
break;
|
||
|
case QTFileBroadcaster::eSDPFileInvalidName : qtss_printf("The sdp_file name is missing or too long.");
|
||
|
break;
|
||
|
case QTFileBroadcaster::eNetworkSDPFileNameInvalidBadPath: qtss_printf("The specified destination_sdp_file must a be relative file path in the movies directory.");
|
||
|
break;
|
||
|
case QTFileBroadcaster::eNetworkSDPFileNameInvalidMissing: qtss_printf("The specified destination_sdp_file name is missing.");
|
||
|
break;
|
||
|
|
||
|
default: qtss_printf("Description set up error %d occured.",err);
|
||
|
break;
|
||
|
}
|
||
|
qtss_printf("\n");
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
|
||
|
|
||
|
case QTFileBroadcaster::eMovieFileNotFound :
|
||
|
case QTFileBroadcaster::eMovieFileNoHintedTracks :
|
||
|
case QTFileBroadcaster::eMovieFileNoSDPMatches :
|
||
|
case QTFileBroadcaster::eMovieFileInvalid :
|
||
|
case QTFileBroadcaster::eMovieFileInvalidName :
|
||
|
{ qtss_printf("- Movie set up failed: ");
|
||
|
switch( err )
|
||
|
{
|
||
|
case QTFileBroadcaster::eMovieFileNotFound : qtss_printf("Movie file not found.");
|
||
|
break;
|
||
|
case QTFileBroadcaster::eMovieFileNoHintedTracks : qtss_printf("Movie file has no hinted tracks.");
|
||
|
break;
|
||
|
case QTFileBroadcaster::eMovieFileNoSDPMatches : qtss_printf("Movie file does not match SDP.");
|
||
|
break;
|
||
|
case QTFileBroadcaster::eMovieFileInvalid : qtss_printf("Movie file is invalid.");
|
||
|
break;
|
||
|
case QTFileBroadcaster::eMovieFileInvalidName : qtss_printf("Movie file name is missing or too long.");
|
||
|
break;
|
||
|
|
||
|
default: qtss_printf("Movie set up error %d occured.",err);
|
||
|
break;
|
||
|
|
||
|
};
|
||
|
qtss_printf("\n");
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case QTFileBroadcaster::eMem:
|
||
|
case QTFileBroadcaster::eInternalError:
|
||
|
{ qtss_printf("- Internal Error: ");
|
||
|
switch( err )
|
||
|
{
|
||
|
case QTFileBroadcaster::eMem : qtss_printf("Memory error.");
|
||
|
break;
|
||
|
|
||
|
case QTFileBroadcaster::eInternalError : qtss_printf("Internal error.");
|
||
|
break;
|
||
|
|
||
|
default: qtss_printf("internal error %d occured.",err);
|
||
|
break;
|
||
|
}
|
||
|
qtss_printf("\n");
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case QTFileBroadcaster::eFailedBind:
|
||
|
case QTFileBroadcaster::eNetworkConnectionError:
|
||
|
case QTFileBroadcaster::eNetworkRequestError:
|
||
|
case QTFileBroadcaster::eNetworkConnectionStopped:
|
||
|
case QTFileBroadcaster::eNetworkAuthorization:
|
||
|
case QTFileBroadcaster::eNetworkNotSupported:
|
||
|
case QTFileBroadcaster::eNetworkConnectionFailed:
|
||
|
{ sErrorEvaluted = true;
|
||
|
qtss_printf("- Network Connection: ");
|
||
|
switch( err )
|
||
|
{
|
||
|
case QTFileBroadcaster::eFailedBind : qtss_printf("A Socket failed trying to open and bind to a local port.\n.");
|
||
|
break;
|
||
|
|
||
|
case QTFileBroadcaster::eNetworkConnectionError : qtss_printf("Failed to connect.");
|
||
|
break;
|
||
|
|
||
|
case QTFileBroadcaster::eNetworkRequestError : qtss_printf("Server returned error.");
|
||
|
break;
|
||
|
|
||
|
case QTFileBroadcaster::eNetworkConnectionStopped: qtss_printf("Connection stopped.");
|
||
|
break;
|
||
|
|
||
|
case QTFileBroadcaster::eNetworkAuthorization: qtss_printf("Authorization failed.");
|
||
|
break;
|
||
|
|
||
|
case QTFileBroadcaster::eNetworkNotSupported: qtss_printf("Connection not supported by server.");
|
||
|
break;
|
||
|
|
||
|
case QTFileBroadcaster::eNetworkConnectionFailed : qtss_printf("Disconnected.");
|
||
|
break;
|
||
|
|
||
|
default: qtss_printf("internal error %d occured.",err);
|
||
|
break;
|
||
|
}
|
||
|
qtss_printf("\n");
|
||
|
returnErr = QTFileBroadcaster::eNetworkConnectionError;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
sErr = returnErr;
|
||
|
|
||
|
return returnErr;
|
||
|
}
|
||
|
|
||
|
|
||
|
static int MyAccess( const char* path, int mode )
|
||
|
{
|
||
|
int error = 0;
|
||
|
|
||
|
#ifndef __Win32__
|
||
|
if ( access( path, mode ) )
|
||
|
error = errno;
|
||
|
else
|
||
|
error = 0;
|
||
|
#endif
|
||
|
|
||
|
return error;
|
||
|
}
|
||
|
|
||
|
static int PreflightClientBufferDelay( const char * delay, Float32 *ioDelay)
|
||
|
{
|
||
|
|
||
|
int numPreflightErrors = 0;
|
||
|
|
||
|
if ( NULL == delay|| 0 == delay[0] )
|
||
|
numPreflightErrors++;
|
||
|
else
|
||
|
{ Float32 delayValue = 0.0;
|
||
|
::sscanf(delay, "%f", &delayValue);
|
||
|
if (delayValue < 0.0)
|
||
|
numPreflightErrors++;
|
||
|
if (ioDelay != NULL)
|
||
|
*ioDelay = delayValue;
|
||
|
}
|
||
|
|
||
|
if (numPreflightErrors > 0)
|
||
|
qtss_printf("- PlaylistBroadcaster: The client_buffer_delay parameter is invalid.\n" );
|
||
|
|
||
|
return numPreflightErrors;
|
||
|
}
|
||
|
|
||
|
static int PreflightDestSDPFileAccess( const char * sdpPath )
|
||
|
{
|
||
|
int numPreflightErrors = 0;
|
||
|
|
||
|
if ( NULL == sdpPath || 0==sdpPath[0] || 0 == ::strcmp(sdpPath, "no_name") )
|
||
|
{ qtss_printf("- PlaylistBroadcaster: The destination_sdp_file parameter is missing from the Broadcaster description file.\n" );
|
||
|
numPreflightErrors++;
|
||
|
}
|
||
|
|
||
|
return numPreflightErrors;
|
||
|
|
||
|
}
|
||
|
|
||
|
static int PreflightSDPFileAccess( const char * sdpPath )
|
||
|
{
|
||
|
int numPreflightErrors = 0;
|
||
|
|
||
|
Assert( sdpPath );
|
||
|
|
||
|
if ( !sdpPath )
|
||
|
{ qtss_printf("- PlaylistBroadcaster: The sdp_file parameter is missing from the Broadcaster description file.\n" );
|
||
|
numPreflightErrors++;
|
||
|
}
|
||
|
else
|
||
|
{ int accessError;
|
||
|
|
||
|
accessError = MyAccess( sdpPath, R_OK | W_OK );
|
||
|
|
||
|
switch( accessError )
|
||
|
{
|
||
|
|
||
|
case 0:
|
||
|
case ENOENT: // if its not there, we'll create it
|
||
|
break;
|
||
|
|
||
|
case EACCES:
|
||
|
qtss_printf("- PlaylistBroadcaster: Permission to access the SDP File was denied.\n Read/Write access is required.\n (path: %s, errno: %i).\n", sdpPath, accessError);
|
||
|
numPreflightErrors++;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
qtss_printf("- PlaylistBroadcaster: Unable to access the SDP File.\n (path: %s, errno: %i).\n", sdpPath, accessError);
|
||
|
numPreflightErrors++;
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return numPreflightErrors;
|
||
|
|
||
|
}
|
||
|
|
||
|
static int PreflightDestinationAddress( const char *destAddress )
|
||
|
{
|
||
|
int numPreflightErrors = 0;
|
||
|
|
||
|
|
||
|
if ( !destAddress || (SocketUtils::ConvertStringToAddr(destAddress) == INADDR_NONE) )
|
||
|
{ qtss_printf("- PlaylistBroadcaster: Error, desitination_ip_address parameter missing or incorrect in the Broadcaster description file.\n" );
|
||
|
numPreflightErrors++;
|
||
|
}
|
||
|
|
||
|
return numPreflightErrors;
|
||
|
|
||
|
}
|
||
|
|
||
|
static int PreflightBasePort( const char *basePort )
|
||
|
{
|
||
|
int numPreflightErrors = 0;
|
||
|
|
||
|
|
||
|
Assert( basePort );
|
||
|
if ( basePort == NULL )
|
||
|
{ qtss_printf("- PlaylistBroadcaster: Error, destination_base_port parameter missing from the Broadcaster description file.\n" );
|
||
|
numPreflightErrors++;
|
||
|
}
|
||
|
else if ( (atoi(basePort) & 1) != 0)
|
||
|
{
|
||
|
qtss_printf("- PlaylistBroadcaster: Warning, the destination_base_port parameter(%s) is an odd port number. It should be even.\n", basePort );
|
||
|
}
|
||
|
|
||
|
return numPreflightErrors;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
static int PreflightReferenceMovieAccess( const char *refMoviePath )
|
||
|
{
|
||
|
int numPreflightErrors = 0;
|
||
|
|
||
|
Assert( refMoviePath );
|
||
|
if ( !refMoviePath )
|
||
|
{ qtss_printf("- PlaylistBroadcaster: Error, sdp_reference_movie parameter missing from the Broadcaster description file.\n" );
|
||
|
numPreflightErrors++;
|
||
|
}
|
||
|
else
|
||
|
{ int accessError;
|
||
|
|
||
|
accessError = MyAccess( refMoviePath, R_OK );
|
||
|
|
||
|
switch( accessError )
|
||
|
{
|
||
|
case 0:
|
||
|
break;
|
||
|
|
||
|
case ENOENT:
|
||
|
qtss_printf("- PlaylistBroadcaster: Error, SDP Reference Movie is missing.\n (path: %s, errno: %i).\n", refMoviePath, accessError);
|
||
|
numPreflightErrors++;
|
||
|
break;
|
||
|
|
||
|
case EACCES:
|
||
|
qtss_printf("- PlaylistBroadcaster: Permission to access the SDP reference movie was denied.\n Read access required.\n (path: %s, errno: %i).\n", refMoviePath, accessError);
|
||
|
numPreflightErrors++;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
qtss_printf("- PlaylistBroadcaster: Unable to access the SDP reference movie.\n (path: %s, errno: %i).\n", refMoviePath, accessError);
|
||
|
numPreflightErrors++;
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return numPreflightErrors;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void ShowPlaylistStatus()
|
||
|
{
|
||
|
#ifndef __Win32__
|
||
|
BCasterTracker tracker( sgTrackerFilePath );
|
||
|
|
||
|
if ( tracker.IsOpen() )
|
||
|
{
|
||
|
tracker.Show();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
qtss_printf("- PlaylistBroadcaster: Could not open %s. Reason: access denied.\n", sgTrackerFilePath );
|
||
|
qtss_printf("- PlaylistBroadcaster: Change user or change the directory's privileges to access %s.\n",sgTrackerFilePath) ;
|
||
|
::exit(-1);
|
||
|
|
||
|
}
|
||
|
#else
|
||
|
qtss_printf("Showing Playlist status is currently not supported on this platform\n");
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static void StopABroadcast( const char* arg )
|
||
|
{
|
||
|
#ifndef __Win32__
|
||
|
if ( arg )
|
||
|
{
|
||
|
|
||
|
BCasterTracker tracker( sgTrackerFilePath );
|
||
|
int playlistIDToKill;
|
||
|
|
||
|
playlistIDToKill = ::atoi( arg );
|
||
|
playlistIDToKill--; // convert from UI one based to to zero based ID for BCasterTracker
|
||
|
|
||
|
|
||
|
if ( tracker.IsOpen() )
|
||
|
{
|
||
|
bool broadcastIDIsValid;
|
||
|
int error;
|
||
|
|
||
|
error = tracker.Remove( playlistIDToKill );
|
||
|
|
||
|
if ( !error ) // remove the xth item from the list.
|
||
|
{ tracker.Save();
|
||
|
broadcastIDIsValid = true;
|
||
|
}
|
||
|
else
|
||
|
broadcastIDIsValid = false;
|
||
|
|
||
|
if ( !broadcastIDIsValid )
|
||
|
{
|
||
|
if ( playlistIDToKill >= 0 )
|
||
|
{
|
||
|
if ( error == ESRCH || error == -1 )
|
||
|
qtss_printf( "- PlaylistBroadcaster Broadcast ID (%s) not running.\n", arg );
|
||
|
else
|
||
|
qtss_printf( "- PlaylistBroadcaster Broadcast ID (%s), permission to stop denied (%i).\n", arg, error );
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{ qtss_printf( "- Bad argument for stop: (%s).\n", arg );
|
||
|
::exit( -1 );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
qtss_printf("PlaylistBroadcaster stopped Broadcast ID: %s.\n", arg);
|
||
|
}
|
||
|
else
|
||
|
{ qtss_printf("- PlaylistBroadcaster: Could not open %s. Reason: access denied.\n", sgTrackerFilePath );
|
||
|
qtss_printf("- PlaylistBroadcaster: Change user or change the directory's privileges to access %s.\n",sgTrackerFilePath) ;
|
||
|
::exit( -1 );
|
||
|
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{ // getopt will catch this problem before we see it.
|
||
|
qtss_printf("- Stop requires a Broadcast ID.\n");
|
||
|
::exit( -1 );
|
||
|
}
|
||
|
#else
|
||
|
qtss_printf("Stopping a broadcast is currently not supported on this platform\n");
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
static bool DoSDPGen( PLBroadcastDef *broadcastParms, bool preflight, bool overWriteSDP, bool generateNewSDP, int* numErrorsPtr, char* refMovie)
|
||
|
{
|
||
|
int numSDPSetupErrors = 0;
|
||
|
bool sdpFileCreated = false;
|
||
|
|
||
|
if (sAnnounceBroadcast)
|
||
|
{ numSDPSetupErrors += PreflightDestSDPFileAccess( broadcastParms->mDestSDPFile );
|
||
|
Assert(broadcastParms->mBasePort != NULL);
|
||
|
Assert(broadcastParms->mBasePort[0]!=0);
|
||
|
broadcastParms->mBasePort[0] = '0';//set to dynamic. ignore any defined.
|
||
|
broadcastParms->mBasePort[1] = 0;
|
||
|
}
|
||
|
|
||
|
if (broadcastParms->mSDPReferenceMovie != NULL)
|
||
|
refMovie = broadcastParms->mSDPReferenceMovie;
|
||
|
|
||
|
qtss_printf("Ref Movie = %s\n", refMovie);
|
||
|
qtss_printf("SDP file = %s\n", broadcastParms->mSDPFile);
|
||
|
|
||
|
numSDPSetupErrors += PreflightSDPFileAccess( broadcastParms->mSDPFile );
|
||
|
|
||
|
|
||
|
numSDPSetupErrors += PreflightReferenceMovieAccess( refMovie );
|
||
|
|
||
|
|
||
|
numSDPSetupErrors += PreflightDestinationAddress( broadcastParms->mDestAddress );
|
||
|
|
||
|
|
||
|
numSDPSetupErrors += PreflightBasePort( broadcastParms->mBasePort );
|
||
|
|
||
|
Float32 bufferDelay = 0.0;
|
||
|
numSDPSetupErrors += PreflightClientBufferDelay( broadcastParms->mClientBufferDelay,&bufferDelay );
|
||
|
|
||
|
if ( numSDPSetupErrors == 0 )
|
||
|
{
|
||
|
SDPGen* sdpGen = new SDPGen;
|
||
|
|
||
|
int sdpResult = -1;
|
||
|
|
||
|
Assert( sdpGen );
|
||
|
|
||
|
if ( sdpGen )
|
||
|
{
|
||
|
sdpGen->SetClientBufferDelay(bufferDelay); // sdp "a=x-bufferdelay: value"
|
||
|
sdpGen->KeepSDPTracks(false); // set this to keep a=control track ids if we are going to ANNOUNCE the sdp to the server.
|
||
|
sdpGen->AddIndexTracks(true); // set this if KeepTracks is false and to ANNOUNCE the sdp to the server.
|
||
|
sdpResult = sdpGen->Run( refMovie, broadcastParms->mSDPFile,
|
||
|
broadcastParms->mBasePort, broadcastParms->mDestAddress,
|
||
|
sSDPBuffer, maxSDPbuffSize,
|
||
|
overWriteSDP, generateNewSDP,
|
||
|
broadcastParms->mStartTime,
|
||
|
broadcastParms->mEndTime,
|
||
|
broadcastParms->mIsDynamic,
|
||
|
broadcastParms->mTTL
|
||
|
);
|
||
|
|
||
|
sdpFileCreated = sdpGen->fSDPFileCreated;
|
||
|
|
||
|
if ( sdpGen->fSDPFileCreated && !preflight)
|
||
|
qtss_printf( "- PlaylistBroadcaster: Created SDP file.\n (path: %s)\n", broadcastParms->mSDPFile);
|
||
|
}
|
||
|
|
||
|
|
||
|
if ( sdpResult )
|
||
|
{
|
||
|
if (sdpResult == -2)
|
||
|
qtss_printf("- SDP generation failed: Unable to create the SDP File.\n (path: %s, errno: %d).\n", broadcastParms->mSDPFile, sdpResult);
|
||
|
else
|
||
|
qtss_printf( "- SDP generation failed (error: %li).\n", (SInt32)sdpResult );
|
||
|
|
||
|
(*numErrorsPtr)++;
|
||
|
return sdpFileCreated;
|
||
|
}
|
||
|
|
||
|
if ( sdpGen )
|
||
|
delete sdpGen;
|
||
|
|
||
|
sdpGen = NULL;
|
||
|
}
|
||
|
else
|
||
|
{ qtss_printf( "- PlaylistBroadcaster: Too many SDP set up errors, exiting.\n" );
|
||
|
*numErrorsPtr += numSDPSetupErrors;
|
||
|
}
|
||
|
|
||
|
return sdpFileCreated;
|
||
|
|
||
|
}
|
||
|
|
||
|
static bool PreflightTrackerFileAccess( int mode )
|
||
|
{
|
||
|
|
||
|
#ifndef __Win32__
|
||
|
int trackerError;
|
||
|
|
||
|
int dirlen = strlen(sgTrackerDirPath) + 1;
|
||
|
OSCharArrayDeleter trackDir(new char[dirlen]);
|
||
|
char *trackDirPtr = trackDir.GetObject();
|
||
|
::memcpy(trackDirPtr, sgTrackerDirPath, dirlen);
|
||
|
|
||
|
trackerError= OS::RecursiveMakeDir( trackDirPtr );
|
||
|
|
||
|
if ( trackerError == 0 )
|
||
|
trackerError = MyAccess( sgTrackerFilePath, mode );
|
||
|
|
||
|
switch ( trackerError )
|
||
|
{
|
||
|
case 0:
|
||
|
case ENOENT:
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
qtss_printf("- PlaylistBroadcaster: Error opening %s. (errno: %i).\n", sgTrackerFilePath, trackerError);
|
||
|
qtss_printf("- PlaylistBroadcaster: Change user or change the directory's privileges to access %s.\n",sgTrackerFilePath) ;
|
||
|
return false;
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
#endif
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static void ShowSetupParams(PLBroadcastDef* broadcastParms, const char *bcastSetupFilePath)
|
||
|
{
|
||
|
qtss_printf( "\n" );
|
||
|
qtss_printf( "PlaylistBroadcaster broadcast description File\n" );
|
||
|
qtss_printf( "----------------------------------------------\n" );
|
||
|
qtss_printf( "%s\n", bcastSetupFilePath );
|
||
|
|
||
|
broadcastParms->ShowSettings();
|
||
|
|
||
|
}
|
||
|
|
||
|
static bool PreFlightSetupFile( const char * bcastSetupFilePath )
|
||
|
{
|
||
|
bool success = true;
|
||
|
|
||
|
// now complain!
|
||
|
if ( !bcastSetupFilePath )
|
||
|
{ qtss_printf("- PlaylistBroadcaster: A broadcast description file is required.\n" );
|
||
|
::usage();
|
||
|
success = false;
|
||
|
}
|
||
|
else if (::strlen(bcastSetupFilePath) > PLBroadcastDef::kMaxBufferStringLen)
|
||
|
{ qtss_printf("- PlaylistBroadcaster: A broadcast description file path cannot be longer than %d in length.\n",PLBroadcastDef::kMaxBufferStringLen );
|
||
|
::usage();
|
||
|
success = false;
|
||
|
}
|
||
|
else
|
||
|
{ int accessError;
|
||
|
|
||
|
accessError = MyAccess(bcastSetupFilePath, R_OK );
|
||
|
|
||
|
switch( accessError )
|
||
|
{
|
||
|
|
||
|
case 0:
|
||
|
break;
|
||
|
|
||
|
case ENOENT:
|
||
|
qtss_printf("- PlaylistBroadcaster: The broadcast description file is missing.\n (path: %s, errno: %i).\n", bcastSetupFilePath, accessError);
|
||
|
success = false;
|
||
|
break;
|
||
|
|
||
|
case EACCES:
|
||
|
qtss_printf("- PlaylistBroadcaster: Permission denied to access the broadcast description file.\n Read access required.\n (path: %s, errno: %i).\n", bcastSetupFilePath, accessError);
|
||
|
success = false;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
qtss_printf("- PlaylistBroadcaster: Unable to access the broadcast description file.\n (path: %s, errno: %i).\n", bcastSetupFilePath, accessError);
|
||
|
success = false;
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return success;
|
||
|
}
|
||
|
|
||
|
static bool AddOurPIDToTracker( const char* bcastSetupFilePath )
|
||
|
{
|
||
|
// add our pid and Broadcast description file File to the tracker
|
||
|
#ifndef __Win32__
|
||
|
BCasterTracker tracker( sgTrackerFilePath );
|
||
|
|
||
|
if ( tracker.IsOpen() )
|
||
|
{ sgTrackingSucceeded = 1;
|
||
|
tracker.Add( getpid(), bcastSetupFilePath );
|
||
|
tracker.Save();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
qtss_printf("- PlaylistBroadcaster: Could not open %s. Reason: access denied.\n", sgTrackerFilePath );
|
||
|
qtss_printf("- PlaylistBroadcaster: Change user or change the directory's privileges to access %s.\n",sgTrackerFilePath) ;
|
||
|
return false;
|
||
|
}
|
||
|
#endif
|
||
|
return true; // return true if pid was successfully added to the tracker
|
||
|
}
|
||
|
|
||
|
#if 0
|
||
|
|
||
|
static void ShowPickDistribution( PlaylistPicker *picker )
|
||
|
{
|
||
|
qtss_printf( "\n" );
|
||
|
qtss_printf( "Pick Distribution by Bucket\n" );
|
||
|
qtss_printf( "---------------------------\n" );
|
||
|
|
||
|
UInt32 bucketIndex;
|
||
|
|
||
|
for ( bucketIndex = 0; bucketIndex < picker->GetNumBuckets(); bucketIndex++ )
|
||
|
{
|
||
|
qtss_printf( "bucket total for w: %li, (%li)\n", (bucketIndex + 1), (SInt32)picker->mPickCounts[bucketIndex] );
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// archaic debug code
|
||
|
|
||
|
static void PrintContents( PLDoubleLinkedListNode<SimplePlayListElement> *node, void * );
|
||
|
static void ShowPicker(PlaylistPicker *picker);
|
||
|
|
||
|
|
||
|
static void PrintContents( PLDoubleLinkedListNode<SimplePlayListElement> *node, void * )
|
||
|
{
|
||
|
qtss_printf( "element name %s\n", node->mElement->mElementName );
|
||
|
}
|
||
|
|
||
|
static void ShowPicker(PlaylistPicker *picker)
|
||
|
{
|
||
|
int x;
|
||
|
|
||
|
for ( x= 0; x < picker->GetNumBuckets(); x++ )
|
||
|
{
|
||
|
picker->GetBucket(x)->ForEach( PrintContents, NULL );
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
/* changed by emil@popwire.com (see relaod.txt for info) */
|
||
|
bool FileCreateAndCheckAccess(char *theFileName){
|
||
|
FILE *theFile;
|
||
|
|
||
|
|
||
|
#ifndef __Win32__
|
||
|
if(access(theFileName, F_OK)){
|
||
|
/* file does not exist - create and set rights */
|
||
|
theFile = ::fopen(theFileName, "w+");
|
||
|
if(theFile){
|
||
|
/* make sure everybody has r/w access to file */
|
||
|
(void)::chmod(theFileName, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
|
||
|
(void)::fclose(theFile);
|
||
|
}else return 1;
|
||
|
}else{
|
||
|
/* file exists - check rights */
|
||
|
if(access(theFileName, R_OK|W_OK))return 2;
|
||
|
}
|
||
|
|
||
|
#else
|
||
|
|
||
|
theFile = ::fopen(theFileName,"a");
|
||
|
|
||
|
if (theFile) ::fclose(theFile);
|
||
|
|
||
|
#endif
|
||
|
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void PrintPlaylistElement(PLDoubleLinkedListNode<SimplePlayListElement> *node,void *file)
|
||
|
{
|
||
|
sElementCount ++;
|
||
|
if (sElementCount <= sMaxUpcomingListSize)
|
||
|
{ char* thePick = node->fElement->mElementName;
|
||
|
if (sSetupDirPath.EqualIgnoreCase(thePick, sSetupDirPath.Len) || '\\' == thePick[0] || '/' == thePick[0])
|
||
|
qtss_fprintf((FILE*)file,"%s\n", thePick);
|
||
|
else
|
||
|
qtss_fprintf((FILE*)file,"%s%s\n", sSetupDirPath.Ptr,thePick);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void ShowPlaylistElements(PlaylistPicker *picker,FILE *file)
|
||
|
{
|
||
|
if (sElementCount > sMaxUpcomingListSize)
|
||
|
return;
|
||
|
|
||
|
UInt32 x;
|
||
|
for (x= 0;x<picker->GetNumBuckets();x++)
|
||
|
{
|
||
|
picker->GetBucket(x)->ForEach(PrintPlaylistElement,file);
|
||
|
}
|
||
|
}
|
||
|
/* ***************************************************** */
|
||
|
|
||
|
static void RegisterEventHandlers()
|
||
|
{
|
||
|
#ifdef __Win32__
|
||
|
SetConsoleCtrlHandler( (PHANDLER_ROUTINE) SignalEventHandler, true);
|
||
|
return;
|
||
|
#endif
|
||
|
|
||
|
#ifndef __Win32__
|
||
|
|
||
|
struct sigaction act;
|
||
|
|
||
|
#if defined(sun) || defined(i386) || defined(__MacOSX__) || defined(__powerpc__) || defined (__sgi_cc__) || defined(__osf__) || defined(__hpux__) || defined(__linux__)
|
||
|
sigemptyset(&act.sa_mask);
|
||
|
act.sa_flags = 0;
|
||
|
act.sa_handler = (void(*)(int))&SignalEventHandler;
|
||
|
#elif defined(__sgi__)
|
||
|
sigemptyset(&act.sa_mask);
|
||
|
act.sa_flags = 0;
|
||
|
act.sa_handler = (void(*)(...))&SignalEventHandler;
|
||
|
#else
|
||
|
act.sa_mask = 0;
|
||
|
act.sa_flags = 0;
|
||
|
act.sa_handler = (void(*)(...))&SignalEventHandler;
|
||
|
#endif
|
||
|
|
||
|
if ( ::signal(SIGTERM, SIG_IGN) != SIG_IGN)
|
||
|
{ // from kill...
|
||
|
if ( ::sigaction(SIGTERM, &act, NULL) != 0 )
|
||
|
{ qtss_printf( "- PlaylistBroadcaster: System error (%"_SPOINTERSIZEARG_").\n", (PointerSizedInt)SIG_ERR );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( ::signal(SIGINT, SIG_IGN) != SIG_IGN)
|
||
|
{ // ^C signal
|
||
|
if ( ::sigaction(SIGINT, &act, NULL) != 0 )
|
||
|
{ qtss_printf( "- PlaylistBroadcaster: System error (%"_SPOINTERSIZEARG_").\n", (PointerSizedInt)SIG_ERR );
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
if ( ::signal(SIGPIPE, SIG_IGN) != SIG_IGN)
|
||
|
{ // broken pipe probably from a failed RTSP session (the server went down?)
|
||
|
if ( ::sigaction(SIGPIPE, &act, NULL) != 0 )
|
||
|
{ qtss_printf( "- PlaylistBroadcaster: System error (%"_SPOINTERSIZEARG_").\n", (PointerSizedInt)SIG_ERR );
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
if ( ::signal(SIGHUP, SIG_IGN) != SIG_IGN)
|
||
|
{ // broken pipe probably from a failed RTSP session (the server went down?)
|
||
|
if ( ::sigaction(SIGHUP, &act, NULL) != 0)
|
||
|
{ qtss_printf( "- PlaylistBroadcaster: System error (%"_SPOINTERSIZEARG_").\n", (PointerSizedInt)SIG_ERR );
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
#endif
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
/* ========================================================================
|
||
|
* Signal and error handler.
|
||
|
*/
|
||
|
static void RemoveFiles(PLBroadcastDef* broadcastParms)
|
||
|
{
|
||
|
if (broadcastParms != NULL)
|
||
|
{
|
||
|
if (broadcastParms->mPIDFile != NULL)
|
||
|
remove(broadcastParms->mPIDFile);
|
||
|
if (broadcastParms->mStopFile != NULL)
|
||
|
remove(broadcastParms->mStopFile);
|
||
|
if (broadcastParms->mReplaceFile != NULL)
|
||
|
remove(broadcastParms->mReplaceFile);
|
||
|
if (broadcastParms->mInsertFile != NULL)
|
||
|
remove(broadcastParms->mInsertFile);
|
||
|
if (broadcastParms->mCurrentFile != NULL)
|
||
|
remove(broadcastParms->mCurrentFile);
|
||
|
if (broadcastParms->mUpcomingFile != NULL)
|
||
|
remove(broadcastParms->mUpcomingFile);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
/* ========================================================================
|
||
|
* Signal and error handler.
|
||
|
*/
|
||
|
static void SignalEventHandler( int signalID )
|
||
|
{
|
||
|
|
||
|
#ifdef __Win32__
|
||
|
if ( (signalID != SIGINT) && (signalID != SIGTERM) )
|
||
|
#else
|
||
|
if (signalID == SIGPIPE)
|
||
|
#endif
|
||
|
sNumErrors++;
|
||
|
|
||
|
// evaluate the error
|
||
|
if (sRTSPClientError != 0 && !sErrorEvaluted)
|
||
|
{
|
||
|
EvalBroadcasterErr(sRTSPClientError);
|
||
|
sErrorEvaluted = true;
|
||
|
}
|
||
|
else if (sBroadcasterSession != NULL && !sErrorEvaluted)
|
||
|
{
|
||
|
#ifndef __Win32__
|
||
|
if ( (signalID == SIGINT) || (signalID == SIGTERM) )
|
||
|
{ EvalBroadcasterErr(QTFileBroadcaster::eNetworkConnectionStopped);
|
||
|
}
|
||
|
else
|
||
|
{ EvalBroadcasterErr(QTFileBroadcaster::eNetworkConnectionFailed);
|
||
|
}
|
||
|
#else
|
||
|
EvalBroadcasterErr(QTFileBroadcaster::eNetworkConnectionStopped);
|
||
|
#endif
|
||
|
sErrorEvaluted = true;
|
||
|
}
|
||
|
|
||
|
// do the cleanup - write warning and error messages and remove files
|
||
|
Cleanup();
|
||
|
|
||
|
#ifndef __Win32__
|
||
|
if (sBroadcasterSession != NULL)
|
||
|
{
|
||
|
|
||
|
if ( (signalID == SIGINT) || (signalID == SIGTERM) )
|
||
|
sQuitImmediate = true; // just in case we get called again
|
||
|
|
||
|
return; //we need to let the broadcaster session teardown and clean up
|
||
|
}
|
||
|
|
||
|
if ( sgTrackingSucceeded )
|
||
|
{
|
||
|
// give tracker scope so that it really does it's
|
||
|
// thing before "exit" is called.
|
||
|
BCasterTracker tracker( sgTrackerFilePath );
|
||
|
|
||
|
tracker.RemoveByProcessID( getpid() );
|
||
|
tracker.Save();
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
if (!sQuitImmediate) // do once
|
||
|
{
|
||
|
sQuitImmediate = true; // just in case we get called again
|
||
|
|
||
|
#ifdef __Win32__
|
||
|
|
||
|
if (sBroadcasterSession && !sBroadcasterSession->IsDone())
|
||
|
{ sBroadcasterSession->TearDownNow();
|
||
|
OSThread::Sleep(1000);
|
||
|
}
|
||
|
if (NULL == sBroadcasterSession)
|
||
|
qtss_printf("\n"); // make sure the message was printed before quitting.
|
||
|
|
||
|
#endif
|
||
|
return;
|
||
|
|
||
|
}
|
||
|
::exit(-1);
|
||
|
}
|