1049 lines
31 KiB
C++
1049 lines
31 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@
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include "MP3Broadcaster.h"
|
||
|
#include "MP3MetaInfoUpdater.h"
|
||
|
#include "StringTranslator.h"
|
||
|
|
||
|
#include "../defaultPaths.h"
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include "SafeStdLib.h"
|
||
|
#include <signal.h>
|
||
|
#include <sys/stat.h>
|
||
|
#ifndef __Win32__
|
||
|
#include <unistd.h>
|
||
|
#endif
|
||
|
|
||
|
|
||
|
#ifndef __Win32__
|
||
|
#include <netdb.h>
|
||
|
#if defined (__solaris__) || defined (__osf__) || defined (__hpux__)
|
||
|
#include "daemon.h"
|
||
|
#else
|
||
|
#ifndef __FreeBSD__
|
||
|
#include <sys/sysctl.h>
|
||
|
#endif
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
|
||
|
#include <errno.h>
|
||
|
|
||
|
#include "OSHeaders.h"
|
||
|
#include "OS.h"
|
||
|
#include "OSMemory.h"
|
||
|
#include "Task.h"
|
||
|
#include "TimeoutTask.h"
|
||
|
#include "OSArrayObjectDeleter.h"
|
||
|
#include "ConfParser.h"
|
||
|
#include "MP3FileBroadcaster.h"
|
||
|
// must now inlcude this from the project level using the -i switch in the compiler
|
||
|
#ifndef __MacOSX__
|
||
|
#include "../revision.h"
|
||
|
#endif
|
||
|
|
||
|
|
||
|
MP3Broadcaster* MP3Broadcaster::sBroadcaster = NULL;
|
||
|
|
||
|
//#include "MyAssert.h"
|
||
|
|
||
|
MP3Broadcaster::MP3Broadcaster(char* ipaddr, int port, char* config, char* playList, char* workingDir, Bool16 useICY) :
|
||
|
mValid(true),
|
||
|
mPort(8000),
|
||
|
mBitRate(0),
|
||
|
mFrequency(0),
|
||
|
mUpcomingSongsListSize(7),
|
||
|
mRecentSongsListSize(0),
|
||
|
mLogging(0),
|
||
|
mShowCurrent(true),
|
||
|
mShowUpcoming(true),
|
||
|
mNumErrors(0),
|
||
|
mNumWarnings(0),
|
||
|
mPreflight(false),
|
||
|
mCleanupDone(false),
|
||
|
mTempPicker(NULL),
|
||
|
mSocket(NULL, 0),
|
||
|
mLog(NULL),
|
||
|
mUseICY(useICY)
|
||
|
{
|
||
|
sBroadcaster = this;
|
||
|
|
||
|
strcpy(mIPAddr, "128.0.0.1");
|
||
|
strcpy(mPlayListPath, DEFAULTPATHS_ETC_DIR "mp3playlist.ply");
|
||
|
strcpy(mWorkingDirPath, DEFAULTPATHS_ETC_DIR);
|
||
|
strcpy(mPlayMode, "sequential");
|
||
|
strcpy(mMountPoint, "/");
|
||
|
strcpy(mGenre, "Pop");
|
||
|
strcpy(mURL, "");
|
||
|
strcpy(mPIDFile, "");
|
||
|
|
||
|
//see if there is a defaults File.
|
||
|
//if there is load it and over-ride the other defaults
|
||
|
int len = ::strlen(config) + 10;
|
||
|
char *defaultFileName = new char[len];
|
||
|
qtss_snprintf(defaultFileName, len, "%s%s", config, ".def");
|
||
|
(void) ::ParseConfigFile( false, defaultFileName, ConfigSetter, this ); //ignore if no defaults file
|
||
|
delete [] defaultFileName;
|
||
|
|
||
|
int err = ::ParseConfigFile( false, config, ConfigSetter, this );
|
||
|
if (err)
|
||
|
{
|
||
|
mValid = false;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (ipaddr != NULL)
|
||
|
{
|
||
|
// override config file
|
||
|
// size limit for any IP addr is 255
|
||
|
strncpy(mIPAddr, ipaddr, 255);
|
||
|
}
|
||
|
|
||
|
if (port != 0)
|
||
|
{
|
||
|
// override config file
|
||
|
mPort = port;
|
||
|
}
|
||
|
|
||
|
if (playList)
|
||
|
{
|
||
|
// override config file
|
||
|
// size limit for any playlist string is PATH_MAX - 1
|
||
|
strncpy(mPlayListPath, playList, PATH_MAX-1);
|
||
|
}
|
||
|
|
||
|
if (workingDir)
|
||
|
{
|
||
|
// override config file
|
||
|
// size limit for any working path is PATH_MAX - extension - 1
|
||
|
strncpy(mWorkingDirPath, playList, PATH_MAX-12);
|
||
|
}
|
||
|
|
||
|
CreateWorkingFilePath(".current", mCurrentFile);
|
||
|
CreateWorkingFilePath(".upcoming", mUpcomingFile);
|
||
|
CreateWorkingFilePath(".replacelist", mReplaceFile);
|
||
|
CreateWorkingFilePath(".stoplist", mStopFile);
|
||
|
CreateWorkingFilePath(".insertlist", mInsertFile);
|
||
|
CreateWorkingFilePath("mp3_broadcast.log", mLogFile);
|
||
|
}
|
||
|
|
||
|
Bool16 MP3Broadcaster::ConfigSetter( const char* paramName, const char* paramValue[], void* userData )
|
||
|
{
|
||
|
// return true if set fails
|
||
|
MP3Broadcaster* thisPtr = (MP3Broadcaster*)userData;
|
||
|
|
||
|
if (!::strcmp( "destination_ip_address", paramName) )
|
||
|
{
|
||
|
if (strlen(paramValue[0]) >= sizeof(thisPtr->mIPAddr))
|
||
|
return true;
|
||
|
|
||
|
strcpy(thisPtr->mIPAddr, paramValue[0]);
|
||
|
return false;
|
||
|
}
|
||
|
else if (!::strcmp( "destination_base_port", paramName) )
|
||
|
{
|
||
|
thisPtr->mPort = atoi(paramValue[0]);
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
else if (!::strcmp( "max_upcoming_list_size", paramName) )
|
||
|
{
|
||
|
if ( ::atoi( paramValue[0] ) < 0 )
|
||
|
return true;
|
||
|
|
||
|
thisPtr->mUpcomingSongsListSize = ::atoi( paramValue[0] );
|
||
|
return false;
|
||
|
}
|
||
|
else if (!::strcmp( "play_mode", paramName) )
|
||
|
{
|
||
|
if (strlen(paramValue[0]) >= sizeof(thisPtr->mPlayMode))
|
||
|
return true;
|
||
|
|
||
|
strcpy(thisPtr->mPlayMode, paramValue[0]);
|
||
|
return false;
|
||
|
}
|
||
|
else if (!::strcmp( "recent_songs_list_size", paramName) )
|
||
|
{
|
||
|
if ( ::atoi( paramValue[0] ) < 0 )
|
||
|
return true;
|
||
|
|
||
|
thisPtr->mRecentSongsListSize = ::atoi( paramValue[0] );
|
||
|
return false;
|
||
|
}
|
||
|
else if (!::strcmp( "playlist_file", paramName) )
|
||
|
{
|
||
|
if (strlen(paramValue[0]) >= sizeof(thisPtr->mPlayListPath))
|
||
|
return true;
|
||
|
|
||
|
strcpy(thisPtr->mPlayListPath, paramValue[0]);
|
||
|
return false;
|
||
|
}
|
||
|
else if (!::strcmp( "working_dir", paramName) )
|
||
|
{
|
||
|
if (strlen(paramValue[0]) >= sizeof(thisPtr->mWorkingDirPath))
|
||
|
return true;
|
||
|
|
||
|
strcpy(thisPtr->mWorkingDirPath, paramValue[0]);
|
||
|
return false;
|
||
|
}
|
||
|
else if (!::strcmp( "logging", paramName) )
|
||
|
{
|
||
|
return SetEnabled(paramValue[0], &thisPtr->mLogging);
|
||
|
}
|
||
|
else if (!::strcmp( "show_current", paramName) )
|
||
|
{
|
||
|
return SetEnabled(paramValue[0], &thisPtr->mShowCurrent);
|
||
|
}
|
||
|
else if (!::strcmp( "show_upcoming", paramName) )
|
||
|
{
|
||
|
return SetEnabled(paramValue[0], &thisPtr->mShowUpcoming);
|
||
|
}
|
||
|
else if (!::strcmp( "use_icy", paramName) )
|
||
|
{
|
||
|
return SetEnabled(paramValue[0], &thisPtr->mUseICY);
|
||
|
}
|
||
|
else if (!::strcmp( "broadcast_name", paramName) )
|
||
|
{
|
||
|
if (strlen(paramValue[0]) >= sizeof(thisPtr->mName))
|
||
|
return true;
|
||
|
|
||
|
strcpy(thisPtr->mName, paramValue[0]);
|
||
|
return false;
|
||
|
}
|
||
|
else if (!::strcmp( "broadcast_password", paramName) )
|
||
|
{
|
||
|
if (strlen(paramValue[0]) >= sizeof(thisPtr->mPassword))
|
||
|
return true;
|
||
|
|
||
|
strcpy(thisPtr->mPassword, paramValue[0]);
|
||
|
return false;
|
||
|
}
|
||
|
else if (!::strcmp( "broadcast_genre", paramName) )
|
||
|
{
|
||
|
if (strlen(paramValue[0]) >= sizeof(thisPtr->mGenre))
|
||
|
return true;
|
||
|
|
||
|
strcpy(thisPtr->mGenre, paramValue[0]);
|
||
|
return false;
|
||
|
}
|
||
|
else if (!::strcmp( "broadcast_url", paramName) )
|
||
|
{
|
||
|
if (strlen(paramValue[0]) >= sizeof(thisPtr->mURL))
|
||
|
return true;
|
||
|
|
||
|
strcpy(thisPtr->mURL, paramValue[0]);
|
||
|
return false;
|
||
|
}
|
||
|
else if (!::strcmp( "broadcast_bitrate", paramName) )
|
||
|
{
|
||
|
if ( ::atoi( paramValue[0] ) < 0 )
|
||
|
return true;
|
||
|
|
||
|
thisPtr->mBitRate = ::atoi( paramValue[0] );
|
||
|
return false;
|
||
|
}
|
||
|
else if (!::strcmp( "broadcast_sample_rate", paramName) )
|
||
|
{
|
||
|
if ( ::atoi( paramValue[0] ) < -1 )
|
||
|
return true;
|
||
|
|
||
|
thisPtr->mFrequency = ::atoi( paramValue[0] );
|
||
|
return false;
|
||
|
}
|
||
|
else if (!::strcmp( "broadcast_mount_point", paramName) )
|
||
|
{
|
||
|
if (strlen(paramValue[0]) >= sizeof(thisPtr->mMountPoint))
|
||
|
return true;
|
||
|
// Make sure the mountpoint always begins with a '/' character.
|
||
|
// If its missing prepend it to the mountpoint name for them.
|
||
|
thisPtr->mMountPoint[0] = '\0';
|
||
|
if (*paramValue[0] != '/')
|
||
|
strcpy(thisPtr->mMountPoint, "/");
|
||
|
strcat(thisPtr->mMountPoint, paramValue[0]);
|
||
|
return false;
|
||
|
}
|
||
|
else if (!::strcmp( "pid_file", paramName) )
|
||
|
{
|
||
|
if (strlen(paramValue[0]) >= sizeof(thisPtr->mPIDFile))
|
||
|
return true;
|
||
|
|
||
|
strcpy(thisPtr->mPIDFile, paramValue[0]);
|
||
|
return false;
|
||
|
}
|
||
|
else if (!::strcmp( "max_err_file_k_size", paramName) )
|
||
|
{
|
||
|
if ( !paramValue[0] || !::strlen(paramValue[0]) )
|
||
|
return true;
|
||
|
|
||
|
UInt32 setvalue = kSInt32_Max;
|
||
|
int maxValue = ::atoi( paramValue[0] );
|
||
|
if (maxValue >= 0)
|
||
|
setvalue = (UInt32) maxValue;
|
||
|
|
||
|
qtss_setmaxprintfcharsinK( (UInt32) setvalue);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
Bool16 MP3Broadcaster::SetEnabled( const char* value, Bool16* field)
|
||
|
{
|
||
|
if ( ::strcmp( "enabled", value) && ::strcmp( "disabled", value) )
|
||
|
return true;
|
||
|
|
||
|
*field = !strcmp( "enabled", value );
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void MP3Broadcaster::CreateWorkingFilePath(char* extension, char* result)
|
||
|
{
|
||
|
if (strlen(mWorkingDirPath) + strlen(extension) >= PATH_MAX)
|
||
|
result[0] = 0;
|
||
|
else
|
||
|
{
|
||
|
strcpy(result, mWorkingDirPath);
|
||
|
strcat(result, extension);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void MP3Broadcaster::CreateCurrentAndUpcomingFiles()
|
||
|
{
|
||
|
if (mShowCurrent)
|
||
|
{
|
||
|
if(FileCreateAndCheckAccess(mCurrentFile))
|
||
|
{ /* error */
|
||
|
mLog->LogInfo( "MP3Broadcaster Error: Failed to create current broadcast file" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
if (mShowUpcoming)
|
||
|
{
|
||
|
if(FileCreateAndCheckAccess(mUpcomingFile))
|
||
|
{ /* error */
|
||
|
mLog->LogInfo( "MP3Broadcaster Error: Failed to create upcoming broadcast file" );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void MP3Broadcaster::UpdatePlaylistFiles(PlaylistPicker *picker,PlaylistPicker *insertPicker)
|
||
|
{
|
||
|
if ( (NULL == picker) || (NULL == insertPicker) )
|
||
|
return;
|
||
|
|
||
|
/* if .stoplist file exists - prepare to stop broadcast */
|
||
|
if(!access(mStopFile, R_OK))
|
||
|
{
|
||
|
picker->CleanList();
|
||
|
PopulatePickerFromFile(picker, mStopFile, "", NULL);
|
||
|
|
||
|
mTempPicker->CleanList();
|
||
|
|
||
|
remove(mStopFile);
|
||
|
picker->mStopFlag = true;
|
||
|
}
|
||
|
|
||
|
/* if .replacelist file exists - replace current playlist */
|
||
|
if(!access(mReplaceFile, R_OK))
|
||
|
{
|
||
|
picker->CleanList();
|
||
|
PopulatePickerFromFile(picker, mReplaceFile, "", NULL);
|
||
|
|
||
|
mTempPicker->CleanList();
|
||
|
|
||
|
remove(mReplaceFile);
|
||
|
picker->mStopFlag = false;
|
||
|
}
|
||
|
|
||
|
/* if .insertlist file exists - insert into current playlist */
|
||
|
if(!access(mInsertFile, R_OK))
|
||
|
{
|
||
|
insertPicker->CleanList();
|
||
|
mTempPicker->CleanList();
|
||
|
|
||
|
PopulatePickerFromFile(insertPicker, mInsertFile, "", NULL);
|
||
|
remove(mInsertFile);
|
||
|
picker->mStopFlag = false;
|
||
|
}
|
||
|
|
||
|
|
||
|
// write upcoming playlist to .upcoming file
|
||
|
if (mShowUpcoming)
|
||
|
{
|
||
|
FILE *upcomingFile = fopen(mUpcomingFile, "w");
|
||
|
if(upcomingFile)
|
||
|
{
|
||
|
mElementCount = 0;
|
||
|
|
||
|
if (!::strcmp(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(mPlayMode, "sequential")
|
||
|
)
|
||
|
{ picker->CleanList();
|
||
|
PopulatePickerFromFile(picker,mPlayListPath,"",NULL);
|
||
|
ShowPlaylistElements(picker,upcomingFile);
|
||
|
mTempPicker->CleanList();
|
||
|
PopulatePickerFromFile(mTempPicker,mPlayListPath,"",NULL);
|
||
|
}
|
||
|
|
||
|
if ( mElementCount <= mUpcomingSongsListSize
|
||
|
&& 0 == ::strcmp(mPlayMode, "sequential_looped")
|
||
|
)
|
||
|
{ if (mTempPicker->GetNumMovies() == 0)
|
||
|
{ mTempPicker->CleanList();
|
||
|
PopulatePickerFromFile(mTempPicker,mPlayListPath,"",NULL);
|
||
|
}
|
||
|
//sElementCount can be zero if the playlist contains no paths to valid files
|
||
|
while ( (mElementCount != 0) && mElementCount <= mUpcomingSongsListSize )
|
||
|
ShowPlaylistElements(mTempPicker,upcomingFile);
|
||
|
}
|
||
|
}
|
||
|
fclose(upcomingFile);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( picker->GetNumMovies() == 0
|
||
|
&& !picker->mStopFlag
|
||
|
&& ::strcmp(mPlayMode, "sequential")
|
||
|
)
|
||
|
{ picker->CleanList();
|
||
|
PopulatePickerFromFile(picker,mPlayListPath,"",NULL);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void MP3Broadcaster::UpdateCurrentFile(char *thePick)
|
||
|
{
|
||
|
if (NULL == thePick)
|
||
|
return;
|
||
|
|
||
|
// save currently playing song to .current file
|
||
|
if (mShowCurrent)
|
||
|
{ FILE *currentFile = fopen(mCurrentFile, "w");
|
||
|
if(currentFile)
|
||
|
{
|
||
|
qtss_fprintf(currentFile,"u=%s\n",thePick);
|
||
|
|
||
|
fclose(currentFile);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void MP3Broadcaster::PrintPlaylistElement(PLDoubleLinkedListNode<SimplePlayListElement> *node,void *file)
|
||
|
{
|
||
|
sBroadcaster->mElementCount ++;
|
||
|
if (sBroadcaster->mElementCount <= sBroadcaster->mUpcomingSongsListSize)
|
||
|
{
|
||
|
char* thePick = node->fElement->mElementName;
|
||
|
qtss_fprintf((FILE*)file,"%s\n", thePick);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void MP3Broadcaster::ShowPlaylistElements(PlaylistPicker *picker,FILE *file)
|
||
|
{
|
||
|
if (mElementCount > mUpcomingSongsListSize)
|
||
|
return;
|
||
|
|
||
|
UInt32 x;
|
||
|
for (x= 0;x<picker->GetNumBuckets();x++)
|
||
|
{
|
||
|
picker->GetBucket(x)->ForEach(PrintPlaylistElement,file);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void MP3Broadcaster::PreFlightOrBroadcast( bool preflight, bool daemonize, bool showMovieList, bool currentMovie, bool checkMP3s, const char* errorlog)
|
||
|
{
|
||
|
PlaylistPicker* picker = NULL;
|
||
|
PlaylistPicker* insertPicker = NULL;
|
||
|
MP3FileBroadcaster fileBroadcaster(&mSocket, mBitRate, mFrequency);
|
||
|
MP3MetaInfoUpdater* metaInfoUpdater = NULL;
|
||
|
|
||
|
SInt32 moviePlayCount;
|
||
|
char* thePick = NULL;
|
||
|
SInt32 numMovieErrors;
|
||
|
bool didAtLeastOneMoviePlay = false;
|
||
|
int err;
|
||
|
|
||
|
mPreflight = preflight;
|
||
|
|
||
|
if ( preflight )
|
||
|
ShowSetupParams();
|
||
|
|
||
|
if (preflight)
|
||
|
picker = new PlaylistPicker(false); // make sequential picker, no looping
|
||
|
else
|
||
|
{
|
||
|
picker = MakePickerFromConfig(); // make picker according to parms
|
||
|
mTempPicker = new PlaylistPicker(false);
|
||
|
insertPicker = new PlaylistPicker(false);
|
||
|
insertPicker->mRemoveFlag = true;
|
||
|
}
|
||
|
|
||
|
// initial call uses empty string for path, NULL for loop detection list
|
||
|
(void)PopulatePickerFromFile( picker, mPlayListPath, "", NULL );
|
||
|
|
||
|
if ( preflight )
|
||
|
{
|
||
|
if ( picker->mNumToPickFrom == 1 )
|
||
|
qtss_printf( "\nThere is one movie in the Playlist.\n\n" );
|
||
|
else
|
||
|
qtss_printf( "\nThere are (%li) movies in the Playlist.\n\n", (SInt32) picker->mNumToPickFrom );
|
||
|
}
|
||
|
|
||
|
if ( picker->mNumToPickFrom == 0 )
|
||
|
{
|
||
|
qtss_printf( "- MP3Broadcaster setup failed: There are no movies to play.\n" );
|
||
|
mNumErrors++;
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
|
||
|
// check that we have enough movies to cover the recent movies list.
|
||
|
if ( preflight )
|
||
|
{
|
||
|
if ( !strcmp( mPlayMode, "weighted_random" ) ) // this implies "random" play
|
||
|
{
|
||
|
if ( mRecentSongsListSize >= picker->mNumToPickFrom )
|
||
|
{
|
||
|
mRecentSongsListSize = 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" );
|
||
|
mNumWarnings++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// create the log file
|
||
|
mLog = new MP3BroadcasterLog( mWorkingDirPath, mLogFile, mLogging );
|
||
|
|
||
|
// if ( !PreflightTrackerFileAccess( R_OK | W_OK ) )
|
||
|
// goto bail;
|
||
|
|
||
|
if (!preflight)
|
||
|
{
|
||
|
err = ConnectToServer();
|
||
|
if (err)
|
||
|
{
|
||
|
if (err == EACCES)
|
||
|
qtss_printf("- MP3Broadcaster: Disconnected from Server. Bad password or mount point\n Exiting.\n" );
|
||
|
else
|
||
|
qtss_printf("- MP3Broadcaster: Couldn't connect to server\n Exiting.\n" );
|
||
|
mNumErrors++;
|
||
|
goto bail;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//Unless the Debug command line option is set, daemonize the process at this point
|
||
|
if (daemonize)
|
||
|
{
|
||
|
#ifndef __Win32__
|
||
|
qtss_printf("- MP3Broadcaster: Started in background.\n");
|
||
|
|
||
|
// keep the same working directory..
|
||
|
#ifdef __sgi__
|
||
|
if (::_daemonize(_DF_NOCHDIR, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO) != 0)
|
||
|
#else
|
||
|
if (::daemon( 1, 0 ) != 0)
|
||
|
#endif
|
||
|
{
|
||
|
qtss_printf("- MP3Broadcaster: System error (%i).\n", errno);
|
||
|
mNumErrors++;
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
if (daemonize && (errorlog != NULL))
|
||
|
{
|
||
|
freopen(errorlog, "a", stdout);
|
||
|
::setvbuf(stdout, (char *)NULL, _IONBF, 0);
|
||
|
}
|
||
|
|
||
|
if (!preflight)
|
||
|
{
|
||
|
metaInfoUpdater = new MP3MetaInfoUpdater(mPassword, mMountPoint, mSocket.GetRemoteAddr(), mPort);
|
||
|
metaInfoUpdater->Start();
|
||
|
fileBroadcaster.SetInfoUpdater(metaInfoUpdater);
|
||
|
}
|
||
|
|
||
|
// ^ 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 (( mLog ) && ( mLogging ))
|
||
|
mLog->EnableLog( false ); // don't append ".log" to name for PLB
|
||
|
|
||
|
if ( mLogging && !mLog->IsLogEnabled() )
|
||
|
{
|
||
|
if ( mLog->LogDirName() && *mLog->LogDirName() )
|
||
|
qtss_printf("- MP3Broadcaster: The log file failed to open.\n ( path: %s/%s )\n Exiting.\n", mLog->LogDirName(), mLog->LogFileName() );
|
||
|
else
|
||
|
qtss_printf("- MP3Broadcaster: The log file failed to open.\n ( path: %s )\n Exiting.\n", mLog->LogFileName() );
|
||
|
|
||
|
mNumErrors++;
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
|
||
|
if (mPIDFile[0] != 0)
|
||
|
{
|
||
|
if(!FileCreateAndCheckAccess(mPIDFile))
|
||
|
{
|
||
|
FILE *pidFile = fopen(mPIDFile, "w");
|
||
|
if(pidFile)
|
||
|
{
|
||
|
qtss_fprintf(pidFile,"%d\n",getpid());
|
||
|
fclose(pidFile);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// AddOurPIDToTracker( bcastSetupFilePath ); // <-- exits on failure
|
||
|
|
||
|
if ( !preflight )
|
||
|
mLog->LogInfo( "MP3Broadcaster started." );
|
||
|
else
|
||
|
mLog->LogInfo( "MP3Broadcaster preflight started." );
|
||
|
|
||
|
if(!preflight)
|
||
|
{
|
||
|
CreateCurrentAndUpcomingFiles();
|
||
|
SendXAudioCastHeaders();
|
||
|
}
|
||
|
|
||
|
if (!preflight)
|
||
|
{
|
||
|
// check the frequency of the first song
|
||
|
fileBroadcaster.PlaySong( picker->GetFirstFile(), NULL, true, true );
|
||
|
}
|
||
|
|
||
|
moviePlayCount = 0;
|
||
|
numMovieErrors = 0;
|
||
|
didAtLeastOneMoviePlay = false;
|
||
|
|
||
|
while (true)
|
||
|
{
|
||
|
|
||
|
if (!showMovieList && !preflight)
|
||
|
UpdatePlaylistFiles(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] %s picked\n", moviePlayCount, thePick );
|
||
|
}
|
||
|
|
||
|
|
||
|
if ( !showMovieList )
|
||
|
{
|
||
|
int playError;
|
||
|
|
||
|
if(!preflight)
|
||
|
{ UpdateCurrentFile(thePick);
|
||
|
/* if playlist is about to run out repopulate it */
|
||
|
if ( !::strcmp(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.PlaySong( thePick, 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");
|
||
|
mLog->LogInfo( "Quitting: Playlist contains no valid files.\n" );
|
||
|
goto bail;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
didAtLeastOneMoviePlay = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mLog->LogMediaData(thePick, fileBroadcaster.GetTitle(),
|
||
|
fileBroadcaster.GetArtist(),
|
||
|
fileBroadcaster.GetAlbum(),
|
||
|
(UInt32) ((endTime - startTime)/1000L),
|
||
|
playError);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
playError = fileBroadcaster.PlaySong( thePick, NULL, preflight, !checkMP3s );
|
||
|
}
|
||
|
|
||
|
if (playError == MP3FileBroadcaster::kConnectionError)
|
||
|
{
|
||
|
// do something
|
||
|
mNumErrors++;
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
if ( !preflight && (playError != 0))
|
||
|
{
|
||
|
qtss_printf("File %s : ", thePick);
|
||
|
mLog->LogMediaError( thePick, MapErrorToString(playError), NULL );
|
||
|
}
|
||
|
else if (playError != 0)
|
||
|
{
|
||
|
qtss_printf("File %s : ", thePick);
|
||
|
MapErrorToString(playError);
|
||
|
numMovieErrors++;
|
||
|
mNumWarnings++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
delete [] thePick;
|
||
|
thePick = NULL;
|
||
|
} //while (true)
|
||
|
|
||
|
remove(mCurrentFile);
|
||
|
remove(mUpcomingFile);
|
||
|
|
||
|
if ( preflight )
|
||
|
{
|
||
|
char str[256];
|
||
|
qtss_printf( " - " );
|
||
|
if (numMovieErrors == 1)
|
||
|
strcpy(str, "MP3Broadcaster found one problem MP3 file.");
|
||
|
else
|
||
|
qtss_sprintf( str, "MP3Broadcaster found %li problem MP3 files." , numMovieErrors );
|
||
|
qtss_printf( "%s\n", str );
|
||
|
if (mLog) mLog->LogInfo( str );
|
||
|
|
||
|
if (numMovieErrors == moviePlayCount)
|
||
|
{
|
||
|
qtss_printf("There are no valid MP3s to play\n");
|
||
|
mNumErrors++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bail:
|
||
|
|
||
|
delete picker;
|
||
|
|
||
|
if (metaInfoUpdater)
|
||
|
delete metaInfoUpdater;
|
||
|
|
||
|
Cleanup();
|
||
|
|
||
|
#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
|
||
|
}
|
||
|
|
||
|
PlaylistPicker* MP3Broadcaster::MakePickerFromConfig()
|
||
|
{
|
||
|
// construct a PlaylistPicker object using options set
|
||
|
|
||
|
PlaylistPicker *picker = NULL;
|
||
|
|
||
|
if ( !::strcmp( mPlayMode, "weighted_random" ) )
|
||
|
{
|
||
|
picker = new PlaylistPicker( 10, mRecentSongsListSize );
|
||
|
|
||
|
}
|
||
|
else if ( !::strcmp( mPlayMode, "sequential_looped" ) )
|
||
|
{
|
||
|
picker = new PlaylistPicker(true);
|
||
|
picker->mRemoveFlag = true;
|
||
|
}
|
||
|
else if ( !::strcmp( mPlayMode, "sequential" ) )
|
||
|
{
|
||
|
picker = new PlaylistPicker(false);
|
||
|
picker->mRemoveFlag = true;
|
||
|
}
|
||
|
|
||
|
return picker;
|
||
|
}
|
||
|
|
||
|
bool MP3Broadcaster::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|S_IWOTH);
|
||
|
(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;
|
||
|
}
|
||
|
|
||
|
int MP3Broadcaster::ConnectToServer()
|
||
|
{
|
||
|
UInt32 addr;
|
||
|
addr = SocketUtils::ConvertStringToAddr(mIPAddr);
|
||
|
if (addr == INADDR_NONE)
|
||
|
{
|
||
|
struct hostent* theHostent = ::gethostbyname(mIPAddr);
|
||
|
if (theHostent != NULL)
|
||
|
addr = ntohl(*(UInt32*)(theHostent->h_addr_list[0]));
|
||
|
else
|
||
|
qtss_printf("Couldn't resolve address %s\n", mIPAddr);
|
||
|
}
|
||
|
|
||
|
OS_Error err = mSocket.Open();
|
||
|
err = mSocket.Connect(addr, mPort);
|
||
|
|
||
|
if (err == 0)
|
||
|
{
|
||
|
char buffer1[512];
|
||
|
char buffer2[512];
|
||
|
UInt32 len;
|
||
|
|
||
|
StringTranslator::EncodeURL(mMountPoint, strlen(mMountPoint) + 1, buffer1, sizeof(buffer1));
|
||
|
if (strlen(buffer1) + strlen(mPassword) + 12 <= 512)
|
||
|
{
|
||
|
if (mUseICY)
|
||
|
{
|
||
|
// in the ICY protocol there is no mountpoint
|
||
|
// the reflector assumes a default mountpoint of "/"
|
||
|
qtss_sprintf(buffer2, "%s\n", mPassword);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// if the mountpoint does not start with a "/" we prepend one for the
|
||
|
// reflector before we send the broadcast request.
|
||
|
if (buffer1[0] == '/')
|
||
|
qtss_sprintf(buffer2, "SOURCE %s %s\n\n", mPassword, buffer1);
|
||
|
else
|
||
|
qtss_sprintf(buffer2, "SOURCE %s /%s\n\n", mPassword, buffer1);
|
||
|
}
|
||
|
mSocket.Send(buffer2, strlen(buffer2), &len);
|
||
|
}
|
||
|
else return -1;
|
||
|
|
||
|
char buffer3[3];
|
||
|
len = 0;
|
||
|
mSocket.Read(buffer3, 2, &len);
|
||
|
buffer3[2] = '\0';
|
||
|
if (::strcmp(buffer3, "OK") != 0)
|
||
|
err = EACCES;
|
||
|
}
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
int MP3Broadcaster::SendXAudioCastHeaders()
|
||
|
{
|
||
|
char buffer[1024];
|
||
|
char temp[256];
|
||
|
UInt32 len;
|
||
|
|
||
|
if (mUseICY)
|
||
|
{
|
||
|
qtss_sprintf(buffer, "icy-name:%s\n", mName);
|
||
|
qtss_sprintf(temp, "icy-genre:%s\n", mGenre);
|
||
|
strcat(buffer, temp);
|
||
|
qtss_sprintf(temp, "icy-pub:%s\n", "0");
|
||
|
strcat(buffer, temp);
|
||
|
qtss_sprintf(temp, "icy-url:%s\n", mURL);
|
||
|
strcat(buffer, temp);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
qtss_sprintf(buffer, "x-audiocast-name:%s\n", mName);
|
||
|
qtss_sprintf(temp, "x-audiocast-genre:%s\n", mGenre);
|
||
|
strcat(buffer, temp);
|
||
|
qtss_sprintf(temp, "x-audiocast-public:%s\n", "0");
|
||
|
strcat(buffer, temp);
|
||
|
qtss_sprintf(temp, "x-audiocast-description:%s\n", "");
|
||
|
strcat(buffer, temp);
|
||
|
}
|
||
|
mSocket.Send(buffer, strlen(buffer), &len);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void MP3Broadcaster::ShowSetupParams()
|
||
|
{
|
||
|
qtss_printf( "\n" );
|
||
|
qtss_printf( "Configuration Settings\n" );
|
||
|
qtss_printf( "----------------------------\n" );
|
||
|
qtss_printf( "Destination address %s:%d\n", mIPAddr, mPort );
|
||
|
qtss_printf( "MP3 bitrate %d\n", mBitRate );
|
||
|
qtss_printf( "play_mode %s\n", mPlayMode );
|
||
|
qtss_printf( "recent_movies_list_size %d\n", mRecentSongsListSize );
|
||
|
qtss_printf( "playlist_file %s\n", mPlayListPath );
|
||
|
qtss_printf( "working_dir %s\n", mWorkingDirPath );
|
||
|
qtss_printf( "logging %d\n", mLogging );
|
||
|
qtss_printf( "log_file %s\n", mLogFile );
|
||
|
qtss_printf( "max_upcoming_list_size %d\n", mUpcomingSongsListSize );
|
||
|
qtss_printf( "show_current %d\n", mShowCurrent );
|
||
|
qtss_printf( "show_upcoming %d\n", mShowUpcoming );
|
||
|
qtss_printf( "use_icy %d\n", mUseICY );
|
||
|
qtss_printf( "broadcast_name \"%s\"\n", mName);
|
||
|
qtss_printf( "broadcast_genre \"%s\"\n", mGenre);
|
||
|
qtss_printf( "broadcast_mount_point \"%s\"\n", mMountPoint);
|
||
|
qtss_printf( "broadcast_password \"XXXXX\"\n");
|
||
|
qtss_printf( "max_err_file_k_size %"_U32BITARG_"\n", qtss_getmaxprintfcharsinK());
|
||
|
qtss_printf( "\n" );
|
||
|
}
|
||
|
|
||
|
void MP3Broadcaster::RemoveFiles()
|
||
|
{
|
||
|
if (mPIDFile[0] != 0)
|
||
|
{
|
||
|
remove(mPIDFile);
|
||
|
}
|
||
|
|
||
|
remove(mStopFile);
|
||
|
remove(mReplaceFile);
|
||
|
remove(mInsertFile);
|
||
|
remove(mCurrentFile);
|
||
|
remove(mUpcomingFile);
|
||
|
}
|
||
|
|
||
|
char* MP3Broadcaster::MapErrorToString(int error)
|
||
|
{
|
||
|
char* result = NULL;
|
||
|
|
||
|
if (error == MP3FileBroadcaster::kBadFileFormat)
|
||
|
result = "Bad file format.";
|
||
|
else if (error == MP3FileBroadcaster::kWrongFrequency)
|
||
|
result = "Encoded at wrong frequency.";
|
||
|
else if (error == MP3FileBroadcaster::kWrongBitRate)
|
||
|
result = "Doesn't use desired bit rate.";
|
||
|
else if (error == MP3FileBroadcaster::kCouldntOpenFile)
|
||
|
result = "Couldn't open file.";
|
||
|
|
||
|
if (result != NULL)
|
||
|
qtss_printf("%s\n", result);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
void MP3Broadcaster::Cleanup(bool signalHandler)
|
||
|
{
|
||
|
if (mCleanupDone)
|
||
|
return;
|
||
|
|
||
|
mCleanupDone = true;
|
||
|
|
||
|
if (signalHandler)
|
||
|
{
|
||
|
mNumErrors++;
|
||
|
qtss_printf("- MP3Broadcaster: Disconnected from Server while playing. Exiting.\n");
|
||
|
}
|
||
|
|
||
|
if (mPreflight)
|
||
|
{
|
||
|
qtss_printf("Warnings: %"_S32BITARG_"\n", mNumWarnings);
|
||
|
qtss_printf("Errors: %"_S32BITARG_"\n", mNumErrors);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
qtss_printf("Broadcast Warnings: %"_S32BITARG_"\n", mNumWarnings);
|
||
|
qtss_printf("Broadcast Errors: %"_S32BITARG_"\n", mNumErrors);
|
||
|
}
|
||
|
|
||
|
RemoveFiles();
|
||
|
|
||
|
if (mLog)
|
||
|
{
|
||
|
if ( mPreflight )
|
||
|
mLog->LogInfo( "MP3Broadcaster preflight finished." );
|
||
|
else
|
||
|
mLog->LogInfo( "MP3Broadcaster finished." );
|
||
|
}
|
||
|
|
||
|
// mLog = NULL; // protect the interrupt handler and just let it die don't delete because it is a task thread
|
||
|
}
|