// XmlSchedule.cpp: implementation of the CXmlSchedule class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "Schedule.h"
#include "FileFind.h"
#include "GPRMCData.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
extern CConfig g_Config;
extern BOOL g_bExitDownloadThread;
extern BOOL IsFreeDiskSpaceEnough(DWORDLONG rdwDiskSpace);
extern CString GetMacAddress(void);
extern CString GetCurrentDirectory();
extern CString GetSoftwareVersion(CString filePath);
CScheduler::CScheduler(HWND hParent)
{
MYTRACE(L"*** Scheduler Initiated.");
m_hParent=hParent;
//Init all variables
m_DownloadPath = g_Config.m_Content.m_DownloadPath;
m_UserName = g_Config.m_Content.m_UserName;
m_Password = g_Config.m_Content.m_Password;
m_MacAddress = GetMacAddress();
m_ScheduleXmlId = L"";
m_DownloadTempPath.Format(L"%sTemp\\",m_DownloadPath);
m_VideosDownloadPath.Format(L"%sVideos\\",m_DownloadPath);
m_PicturesDownloadPath.Format(L"%sPictures\\",m_DownloadPath);
//Create Paths
CreateDirectory(m_DownloadPath,NULL);
CreateDirectory(m_VideosDownloadPath,NULL);
CreateDirectory(m_PicturesDownloadPath,NULL);
CreateDirectory(m_DownloadTempPath,NULL);
//Load current schedule xml
this->LoadSchedule(m_DownloadPath);
//Only allow delete of contents when # of minimum contents exist in XML file
if (this->m_SchedulerProfile.GetContentSize() > 0)
{
//Delete all contents not on schedule list
this->DeleteContents(m_VideosDownloadPath);
this->DeleteContents(m_PicturesDownloadPath);
}
m_hRunCompleted=CreateEvent(NULL,FALSE,TRUE,L"Run Completed Event");
}
BOOL CScheduler::Run()
{
CTaskList::Abort(FALSE);
if(!IsFreeDiskSpaceEnough(1024*1024*10))
return TRUE;
ResetEvent(m_hRunCompleted);
MYTRACE(L"*** Begin Download ***");
//Download new schedule xml file
if(!g_bExitDownloadThread)
DownloadScheduleFile();
//Download videos and pictures
if(!g_bExitDownloadThread)
DownloadCurrentTask();
MYTRACE(L"*** End Download ***");
SetEvent(m_hRunCompleted);
return TRUE;
}
BOOL CScheduler::DownloadScheduleFile()
{
CString requestUrl=L"";
CString tempFileName=XML_SCHEDULE;
CString appPath=GetCurrentDirectory()+APP_FILE_NAME;
CString tempSchedulePath=m_DownloadTempPath+tempFileName;
CString csPlaylistPattern=this->GetPlayablePattern();
CString sProdVersion=GetSoftwareVersion(appPath);
// construct the URL request
requestUrl.Format(L"%s?Action=GET_SCH&DevID=%s&AppID=%s&XmlID=%s&Gprmc=%s&CntStatus=%s",
g_Config.m_Content.m_ScheduleUrl, this->m_MacAddress, sProdVersion, m_ScheduleXmlId,
CGPRMCData::c_GprmcData,csPlaylistPattern);
CTaskList dlSchTask;
CTaskItem item;
item.m_Url=requestUrl;
item.m_FileName = tempFileName;
item.m_FilePath = tempSchedulePath;
item.m_UserName = m_UserName;
item.m_Password = m_Password;
BOOL bDownloadScheduleSuccess=FALSE;
MYTRACE(L"Requesting Schedule File: %s", item.m_Url);
for (int i = 0; i < 25; i++)
{
DeleteFile(item.m_FilePath);
bDownloadScheduleSuccess = dlSchTask.StartDownload(&item);
// retry download schedule file on fails
if (bDownloadScheduleSuccess)
{
MYTRACE(L"Download Schedule File: Ok.");
LOGDATA(LOG_TYPE_3G__, L"SUCC");
if (this->LoadSchedule(m_DownloadTempPath))
{
CopyFile(tempSchedulePath, g_Config.m_Content.m_DownloadPath+XML_SCHEDULE, FALSE);
}
break;
}
MYTRACE(L"Retrying download Schedule File.");
LOGDATA(LOG_TYPE_3G__, L"FAIL");
// within the 30sec, each sec check if customer is on, if so, exit all download and play videos
for (int j = 0; j < 30; j++)
{
if (g_bExitDownloadThread)
return FALSE;
Sleep(1000);
}
}
return TRUE;
}
BOOL CScheduler::DownloadCurrentTask()
{
CString szDownloadPath=L"";
CTaskList* taskList=new CTaskList();
int nCount = this->m_SchedulerProfile.GetContentSize();
for (int i = 0; i < nCount; i++)
{
CContentListProfile* pp = this->m_SchedulerProfile.GetContentAt(i);
int nCont = pp->GetSize();
for (int j = 0; j < nCont; j++)
{
CContentDetailProfile* pdp = pp->GetAt(j);
RESOURCETYPE type;
if (pdp->m_FileType.Compare(TYPE_BIN) == 0)
type=BINTYPE, szDownloadPath=m_VideosDownloadPath;
else if (pdp->m_FileType.Compare(TYPE_VIDEO) == 0)
type=VIDEOTYPE, szDownloadPath=m_VideosDownloadPath;
else if (pdp->m_FileType.Compare(TYPE_PICTURE) == 0)
type=PICTURETYPE, szDownloadPath=m_PicturesDownloadPath;
else
szDownloadPath=m_DownloadTempPath;
if (NeedToDownloadFile(szDownloadPath+pdp->m_FileName, _ttol(pdp->m_FileSize)))
{
CTaskItem *item=taskList->AddItem();
item->m_ResourceType=type;
item->m_Url=pdp->m_Url;
item->m_FileName=pdp->m_FileName;
item->m_FilePath=szDownloadPath+item->m_FileName;
item->m_UserName=m_UserName;
item->m_Password=m_Password;
}
else
{
#ifdef QA_RELEASE
MYTRACE(L"Completed Download File [%s] 100%%", szDownloadPath+pdp->m_FileName);
#endif
}
}
}
// start download all items
if(taskList->Run(m_hParent) > 0)
{
delete taskList;
taskList=NULL;
}
// reload play list every time
MYTRACE(L"Reloading Playlist");
::PostMessage(m_hParent,WM_PLAYLIST_CHANGED,0,0);
return TRUE;
}
BOOL CScheduler::DownloadFile(const CString& szUrl, const CString& sFileName)
{
/*
CTaskList task;
CTaskItem item;
item.m_Url = szUrl;
item.m_FileName=sFileName;
item.m_FilePath=glob_CurrentPath+glob_Config.m_Content.m_DownloadPath+item.m_FileName;
item.m_UserName=glob_Config.m_Content.m_UserName;
item.m_Password=glob_Config.m_Content.m_Password;
DeleteFile(item.m_FilePath);
if(task.StartDownload(&item))
return TRUE;
return FALSE;
*/
return TRUE;
}
BOOL CScheduler::NeedToDownloadFile(const CString& szFilePath, const DWORD dwFileSize)
{
return !this->IsEqualFileSize(szFilePath, dwFileSize);
}
BOOL CScheduler::LoadSchedule(const CString& sXmlPath)
{
CXmlDocument xmlDoc;
CString sFilePath=sXmlPath+XML_SCHEDULE;
MYTRACE(L"Parsing Schedule File: %s", sFilePath);
if (xmlDoc.Load(sFilePath))
{
this->m_ScheduleXmlId = this->GetHeaderAttribute(xmlDoc, L"@xmlid");
this->ParseVideoList(xmlDoc);
this->ParsePictureList(xmlDoc);
this->ParseContentList(xmlDoc);
this->ParseZoneList(xmlDoc);
MYTRACE(L"Parsing Schedule File: Ok.");
}
else
{
MYTRACE(L"Unable to load Schedule File.");
return FALSE;
}
return TRUE;
}
CString CScheduler::GetHeaderAttribute(CXmlDocument& xmlDoc, const CString& csAttr)
{
CXmlNodeList xmlList;
if (xmlDoc.SelectNodes(L"schedule", &xmlList))
{
CXmlNode xmlNode;
if (xmlList.GetAt(0, &xmlNode))
{
return xmlNode.GetAttribute(csAttr);
}
}
return L"";
}
BOOL CScheduler::ParseVideoList(CXmlDocument& xmlDoc)
{
CXmlNodeList xmlList;
if (xmlDoc.SelectNodes(L"schedule/videolists/videolist", &xmlList))
{
this->m_SchedulerProfile.m_VideoListProfile.RemoveAll();
for (int i = 0; i < xmlList.GetSize(); i++)
{
CXmlNode xmlNode;
CXmlNodeList xmlDList;
if (!xmlList.GetAt(i, &xmlNode))
continue;
CVideoListProfile* plist = this->m_SchedulerProfile.AddVideoProfile();
plist->m_ValidDays = xmlNode.GetAttribute(L"@validdays");
plist->m_VideoType = xmlNode.GetAttribute(L"@type");
if (xmlNode.SelectNodes(L"video", &xmlDList))
{
for (int j = 0; j < xmlDList.GetSize(); j++)
{
CXmlNode xmlDNode;
if (!xmlDList.GetAt(j, &xmlDNode))
continue;
CVideoContentProfile* pp= plist->AddProfile();
pp->m_AdvId = xmlDNode.GetAttribute(L"@advid");
pp->m_ContentId = xmlDNode.GetAttribute(L"@contentid");
pp->m_RightPicListId = xmlDNode.GetAttribute(L"@rightpiclistid");
pp->m_BottomPicListId = xmlDNode.GetAttribute(L"@bottompiclistid");
pp->m_Layout = xmlDNode.GetAttribute(L"@layout");
pp->m_StartTime = xmlDNode.GetAttribute(L"@starttime");
pp->m_EndTime = xmlDNode.GetAttribute(L"@endtime");
pp->m_CommenceDate = xmlDNode.GetAttribute(L"@commencedate");
pp->m_ExpireDate = xmlDNode.GetAttribute(L"@expiredate");
pp->m_ZoneId = xmlDNode.GetAttribute(L"@zoneid");
pp->m_ZoneArea = xmlDNode.GetAttribute(L"@zonearea");
pp->m_AudioLevel = xmlDNode.GetAttribute(L"@audiolevel");
pp->m_BrightnessLevel = xmlDNode.GetAttribute(L"@brightnesslevel");
}
}
}
return TRUE;
}
return FALSE;
}
BOOL CScheduler::ParsePictureList(CXmlDocument& xmlDoc)
{
CXmlNodeList xmlList;
if (xmlDoc.SelectNodes(L"schedule/picturelists/picturelist", &xmlList))
{
this->m_SchedulerProfile.m_PictureListProfile.RemoveAll();
for (int i = 0; i < xmlList.GetSize(); i++)
{
CXmlNode xmlNode;
CXmlNodeList xmlDList;
if (!xmlList.GetAt(i, &xmlNode))
continue;
CPictureListProfile* plist = this->m_SchedulerProfile.AddPictureProfile();
plist->m_PictureId = xmlNode.GetAttribute(L"@id");
plist->m_Duration = xmlNode.GetAttribute(L"@duration");
if (xmlNode.SelectNodes(L"picture", &xmlDList))
{
for (int j = 0; j < xmlDList.GetSize(); j++)
{
CXmlNode xmlDNode;
if (!xmlDList.GetAt(j, &xmlDNode))
continue;
CPictureDetailProfile* pp = plist->AddProfile();
pp->m_PictureId = xmlDNode.GetAttribute(L"@picid");
pp->m_FlyDirection = xmlDNode.GetAttribute(L"@flydirection");
}
}
}
return TRUE;
}
return FALSE;
}
BOOL CScheduler::ParseContentList(CXmlDocument& xmlDoc)
{
CXmlNodeList xmlList;
if (xmlDoc.SelectNodes(L"schedule/contentlists/contentlist", &xmlList))
{
this->m_SchedulerProfile.m_ContentListProfile.RemoveAll();
for (int i = 0; i < xmlList.GetSize(); i++)
{
CXmlNode xmlNode;
CXmlNodeList xmlDList;
if (!xmlList.GetAt(i, &xmlNode))
continue;
CContentListProfile* plist = this->m_SchedulerProfile.AddContentProfile();
plist->m_ContentId = xmlNode.GetAttribute(L"@id");
if (xmlNode.SelectNodes(L"content", &xmlDList))
{
for (int j = 0; j < xmlDList.GetSize(); j++)
{
CXmlNode xmlDNode;
if (!xmlDList.GetAt(j, &xmlDNode))
continue;
CContentDetailProfile* pp = plist->AddProfile();
pp->m_Url = xmlDNode.GetAttribute(L"@url");
pp->m_FileName = xmlDNode.GetAttribute(L"@file");
pp->m_FileType = xmlDNode.GetAttribute(L"@type");
pp->m_FileSize = xmlDNode.GetAttribute(L"@size");
}
}
}
return TRUE;
}
return FALSE;
}
BOOL CScheduler::ParseZoneList(CXmlDocument& xmlDoc)
{
CXmlNodeList xmlList;
if (xmlDoc.SelectNodes(L"schedule/zonelists/zone", &xmlList))
{
this->m_SchedulerProfile.m_ZoneListProfile.RemoveAll();
for (int i = 0; i < xmlList.GetSize(); i++)
{
CXmlNode xmlNode;
CXmlNodeList xmlDList;
if (!xmlList.GetAt(i, &xmlNode))
continue;
CZoneListProfile* plist = this->m_SchedulerProfile.AddZoneProfile();
plist->m_Name = xmlNode.GetAttribute(L"@name");
plist->m_ZoneId = xmlNode.GetAttribute(L"@id");
plist->m_Lat = xmlNode.GetAttribute(L"@lat");
plist->m_Lon = xmlNode.GetAttribute(L"@lon");
plist->m_Radius = xmlNode.GetAttribute(L"@radius");
}
return TRUE;
}
return FALSE;
}
CVideoListProfile* CScheduler::GetPlayList(const CString& csTypeId)
{
int nCount = this->m_SchedulerProfile.GetVideoSize();
for (int i = 0; i < nCount; i++)
{
CVideoListProfile* pp = this->m_SchedulerProfile.GetVideoAt(i);
if (pp->m_VideoType.Compare(csTypeId) == 0)
return pp;
}
return NULL;
}
CPictureListProfile* CScheduler::GetPictureList(const CString& csTypeId)
{
int nCount = this->m_SchedulerProfile.GetPictureSize();
for (int i = 0; i < nCount; i++)
{
CPictureListProfile* pp = this->m_SchedulerProfile.GetPictureAt(i);
if (pp->m_PictureId.Compare(csTypeId) == 0)
return pp;
}
return NULL;
}
CContentListProfile* CScheduler::GetContentList(const CString& csTypeId)
{
int nCount = this->m_SchedulerProfile.GetContentSize();
for (int i = 0; i < nCount; i++)
{
CContentListProfile* pp = this->m_SchedulerProfile.GetContentAt(i);
if (pp->m_ContentId.Compare(csTypeId) == 0)
return pp;
}
return NULL;
}
CZoneListProfile* CScheduler::GetZoneList(const CString& csTypeId)
{
int nCount = this->m_SchedulerProfile.GetZoneSize();
for (int i = 0; i < nCount; i++)
{
CZoneListProfile* pp = this->m_SchedulerProfile.GetZoneAt(i);
if (pp->m_ZoneId.Compare(csTypeId) == 0)
return pp;
}
return NULL;
}
BOOL CScheduler::LoadPlayList()
{
m_PlayList.RemoveAll();
CVideoList* item;
// customer playlist
item=&m_PlayList.m_SpecialVideoList;
item->Reset();
item->m_GUID=L"";
item->m_ValidDays=L"1,2,3,4,5,6,7";
item->m_StartTimes=L"";
item->m_ListType=(ListType)3;
item->Init();
this->LoadVideoList(item, PLAYLIST_CUSTOMER);
// normal playlist
item=&m_PlayList.m_NormalVideoList;
item->Reset();
item->m_GUID=L"";
item->m_ValidDays=L"1,2,3,4,5,6,7";
item->m_StartTimes=L"";
item->m_ListType=(ListType)1;
item->Init();
this->LoadVideoList(item, PLAYLIST_NORMAL);
// trigger playlist
item=&m_PlayList.m_TriggerVideoList;
item->Reset();
item->m_GUID=L"";
item->m_ValidDays=L"1,2,3,4,5,6,7";
item->m_StartTimes=L"";
item->m_ListType=(ListType)4;
item->Init();
this->LoadVideoList(item, PLAYLIST_TRIGGER);
// news playlist
item=&m_PlayList.m_TriggerAdVideoList;
item->Reset();
item->m_GUID=L"";
item->m_ValidDays=L"1,2,3,4,5,6,7";
item->m_StartTimes=L"";
item->m_ListType=(ListType)5;
item->Init();
this->LoadVideoList(item, PLAYLIST_NEWS);
return TRUE;
}
void CScheduler::LoadVideoList(CVideoList* videoList, const CString& szPlayType)
{
CVideoListProfile* vid = this->GetPlayList(szPlayType);
if (vid == NULL)
return;
int nVidCount = vid->GetSize();
for (int iVid = 0; iVid < nVidCount; iVid++)
{
CVideoContentProfile* con = vid->GetAt(iVid);
CContentListProfile* clp = this->GetContentList(con->m_ContentId);
CContentDetailProfile* cdp = clp->GetAt(0);
CVideoItem *item=videoList->AddItem(cdp->m_FileName);
item->m_AdvId=con->m_AdvId.IsEmpty() ? con->m_ContentId : con->m_AdvId;
item->m_ContentId=con->m_ContentId;
item->m_FilePath=m_VideosDownloadPath+item->m_GUID;
item->m_StartTime=con->m_StartTime;
item->m_EndTime=con->m_EndTime;
item->m_CommenceDate=con->m_CommenceDate;
item->m_ExpireDate=con->m_ExpireDate;
item->m_ZoneId=con->m_ZoneId;
item->m_AudioLevel=con->m_AudioLevel;
item->m_BrightnessLevel=con->m_BrightnessLevel;
item->m_Status=this->IsEqualFileSize(item->m_FilePath, _ttol(cdp->m_FileSize));
item->m_ZonePlay=con->m_ZoneArea.Compare(L"deny") == 0 ? FALSE : TRUE;
if (cdp->m_FileType.Compare(TYPE_BIN) == 0)
item->m_ResourceType=BINTYPE;
else if (cdp->m_FileType.Compare(TYPE_VIDEO) == 0)
item->m_ResourceType=VIDEOTYPE;
else if (cdp->m_FileType.Compare(TYPE_PICTURE) == 0)
item->m_ResourceType=PICTURETYPE;
if (item->m_Status)
{
CString layout=con->m_Layout;
if(layout=="")
item->m_Layout=LAYOUT_FULLSCREEN;
else
item->m_Layout=(Layout)_ttoi(layout);
CString rightPictureListID=con->m_RightPicListId;
CString bottomPictureListID=con->m_BottomPicListId;
if(rightPictureListID!="")
{
item->m_RightPictureList=new CPictureList(rightPictureListID);
LoadRefPictureList(item->m_RightPictureList);
//item->m_RightPictureList->Reset();
}
if(bottomPictureListID!="")
{
item->m_BottomPictureList=new CPictureList(bottomPictureListID);
LoadRefPictureList(item->m_BottomPictureList);
//item->m_BottomPictureList->Reset();
}
}
}
}
void CScheduler::LoadRefPictureList(CPictureList *pictureList)
{
CPictureListProfile* pic = this->GetPictureList(pictureList->m_GUID);
if (pic == NULL)
return;
int nPicCount = pic->GetSize();
CString duration=pic->m_Duration;
if(duration=="")
{
pictureList->m_Duration=10;
}
else
{
pictureList->m_Duration=_ttoi(duration);
pictureList->m_Duration=pictureList->m_Duration < 1 ? 1 : pictureList->m_Duration;
}
for (int iPic = 0; iPic < nPicCount; iPic++)
{
CPictureDetailProfile *pd = pic->GetAt(iPic);
CPictureItem * item=pictureList->AddItem(pd->m_PictureId);
item->m_FilePath=m_PicturesDownloadPath+item->m_GUID;
item->m_Status=this->IsEqualFileSize(item->m_FilePath, this->GetFileSizeInScheduleXml(item->m_GUID));
item->m_SizeMode=1;
//Get fly direction
CString direction=pd->m_FlyDirection;
if(direction=="")
{
item->m_FlyDirection=FlyDirection_FromLeft;
}
else
{
item->m_FlyDirection=(FlyDirection)_ttoi(direction);
}
}
}
BOOL CScheduler::IsEqualFileSize(const CString& szFilePath, const DWORD dwFileSize)
{
BOOL bRet=FALSE;
HANDLE hFile = CreateFile(szFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
if (GetFileSize(hFile, NULL)==dwFileSize)
bRet=TRUE;
CloseHandle(hFile);
}
return bRet;
}
BOOL CScheduler::IsFileExistInScheduleXml(const CString& szFileName)
{
int nCount = this->m_SchedulerProfile.GetContentSize();
for (int i = 0; i < nCount; i++)
{
CContentListProfile* pp = this->m_SchedulerProfile.GetContentAt(i);
int nFiles = pp->GetSize();
for (int f = 0; f < nFiles; f++)
{
CContentDetailProfile *cd = pp->GetAt(f);
if (cd->m_FileName.CompareNoCase(szFileName) == 0)
return TRUE;
}
}
return FALSE;
}
LONG CScheduler::GetFileSizeInScheduleXml(const CString& szFileName)
{
int nCount = this->m_SchedulerProfile.GetContentSize();
for (int i = 0; i < nCount; i++)
{
CContentListProfile* pp = this->m_SchedulerProfile.GetContentAt(i);
int nFiles = pp->GetSize();
for (int f = 0; f < nFiles; f++)
{
CContentDetailProfile *cd = pp->GetAt(f);
if (cd->m_FileName.CompareNoCase(szFileName) == 0)
return _ttol(cd->m_FileSize);
}
}
return 0;
}
BOOL CScheduler::DeleteContents(const CString& szFilePath)
{
CString sFileName = L"";
CString sFilePath = L"";
WIN32_FIND_DATA lpFindFileData;
HANDLE hFind = FindFirstFile(szFilePath + L"*.*", &lpFindFileData);
if (hFind == INVALID_HANDLE_VALUE)
return FALSE;
do
{
sFileName = lpFindFileData.cFileName;
sFilePath = szFilePath + sFileName;
if (sFileName == "." || sFileName == "..")
continue;
if (!(lpFindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
if (!this->IsFileExistInScheduleXml(sFileName))
{
MYTRACE(L"Deleted File: %s", sFilePath);
DeleteFile(sFilePath);
}
}
}
while (FindNextFile(hFind, &lpFindFileData));
CloseHandle(hFind);
return TRUE;
}
CString CScheduler::GetPlayablePattern()
{
CString szPlayPattern=L"";
CString szDownloadPath=L"";
if (this->LoadSchedule(m_DownloadPath))
{
int nCount = this->m_SchedulerProfile.GetContentSize();
for (int i = 0; i < nCount; i++)
{
CContentListProfile* pp = this->m_SchedulerProfile.GetContentAt(i);
int nCont = pp->GetSize();
for (int j = 0; j < nCont; j++)
{
CContentDetailProfile* pdp = pp->GetAt(j);
RESOURCETYPE type;
if (pdp->m_FileType.Compare(TYPE_BIN) == 0)
type=BINTYPE, szDownloadPath=m_VideosDownloadPath;
else if (pdp->m_FileType.Compare(TYPE_VIDEO) == 0)
type=VIDEOTYPE, szDownloadPath=m_VideosDownloadPath;
else if (pdp->m_FileType.Compare(TYPE_PICTURE) == 0)
type=PICTURETYPE, szDownloadPath=m_PicturesDownloadPath;
else
szDownloadPath=m_DownloadTempPath;
if (this->IsEqualFileSize(szDownloadPath+pdp->m_FileName, _ttol(pdp->m_FileSize)))
szPlayPattern.Insert(szPlayPattern.GetLength(), L"1");
else
szPlayPattern.Insert(szPlayPattern.GetLength(), L"0");
}
}
}
return szPlayPattern;
}