?? cpi_playlist.c
字號:
////////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "globals.h"
#include "CPI_Playlist.h"
#include "CPI_PlaylistItem.h"
#include "CPI_PlaylistItem_Internal.h"
#include "CPI_Player.h"
#define CPC_TRACKSTACK_BUFFER_QUANTISATION 32
typedef int (__cdecl *wp_SortFN)(const void *elem1, const void *elem2);
int __cdecl exp_CompareStrings(const void *elem1, const void *elem2);
DWORD WINAPI CPI_PlaylistWorkerThreadEP(void* pCookie);
////////////////////////////////////////////////////////////////////////////////
//
typedef struct _CPs_PlaylistWorkerThreadInfo
{
DWORD m_dwHostThreadID;
DWORD m_dwCurrentBatchID;
} CPs_PlaylistWorkerThreadInfo;
//
//
typedef struct _CPs_Playlist
{
CP_HPLAYLISTITEM m_hFirst;
CP_HPLAYLISTITEM m_hLast;
CP_HPLAYLISTITEM m_hCurrent;
CP_HPLAYLISTITEM* m_pTrackStack;
unsigned int m_iTrackStackSize;
unsigned int m_iTrackStackBufferSize;
unsigned int m_iTrackStackCursor;
HANDLE m_hWorkerThread;
DWORD m_dwWorkerThreadID;
CPs_PlaylistWorkerThreadInfo m_WorkerThreadInfo;
BOOL m_bSyncLoadNextFile;
BOOL m_bAutoActivateInitial;
} CPs_Playlist;
//
//
typedef enum _CPe_PlayListFileType
{
pftUnknown,
pftPLS,
pftM3U
} CPe_PlayListFileType;
//
typedef struct _CPs_FilenameLLItem
{
char* m_pcFilename;
void* m_pNextItem;
} CPs_FilenameLLItem;
//
//
#define CPC_PLAYLISTWORKER_NOTIFYCHUNKSIZE 32
typedef struct _CPs_NotifyChunk
{
int m_iNumberInChunk;
CP_HPLAYLISTITEM m_aryItems[CPC_PLAYLISTWORKER_NOTIFYCHUNKSIZE];
DWORD m_aryBatchIDs[CPC_PLAYLISTWORKER_NOTIFYCHUNKSIZE];
} CPs_NotifyChunk;
//
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
//
//
CP_HPLAYLIST CPL_CreatePlaylist()
{
CPs_Playlist* pNewPlaylist = (CPs_Playlist*)malloc(sizeof(CPs_Playlist));
pNewPlaylist->m_hFirst = NULL;
pNewPlaylist->m_hLast = NULL;
pNewPlaylist->m_hCurrent = NULL;
pNewPlaylist->m_pTrackStack = NULL;
pNewPlaylist->m_iTrackStackSize = 0;
pNewPlaylist->m_iTrackStackBufferSize = 0;
pNewPlaylist->m_iTrackStackCursor = 0;
pNewPlaylist->m_bSyncLoadNextFile = FALSE;
pNewPlaylist->m_bAutoActivateInitial = FALSE;
pNewPlaylist->m_WorkerThreadInfo.m_dwHostThreadID = GetCurrentThreadId();
pNewPlaylist->m_WorkerThreadInfo.m_dwCurrentBatchID = 0;
// Create worker thread
pNewPlaylist->m_hWorkerThread = CreateThread(NULL, 0, CPI_PlaylistWorkerThreadEP, &pNewPlaylist->m_WorkerThreadInfo, 0, &(pNewPlaylist->m_dwWorkerThreadID) );
CP_ASSERT(pNewPlaylist->m_hWorkerThread);
return pNewPlaylist;
}
//
//
//
void CPL_DestroyPlaylist(CP_HPLAYLIST hPlaylist)
{
CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
CP_CHECKOBJECT(pPlaylist);
// Stop the worker thread from processing any more pending ID3 reads
pPlaylist->m_WorkerThreadInfo.m_dwCurrentBatchID++;
// Request worker thread to shutdown
PostThreadMessage(pPlaylist->m_dwWorkerThreadID, CPPLWT_TERMINATE, 0, 0);
// Clean up list
CPL_Empty(hPlaylist);
// Delete an unattached active item
if(pPlaylist->m_hCurrent && CPLII_DECODEHANDLE(pPlaylist->m_hCurrent)->m_bDestroyOnDeactivate)
CPLII_DestroyItem(pPlaylist->m_hCurrent);
// Wait for shutdown to actually happen
WaitForSingleObject(pPlaylist->m_hWorkerThread, INFINITE);
CloseHandle(pPlaylist->m_hWorkerThread);
// Remove any read ID3s from our message queue
{
MSG msg;
while(PeekMessage(&msg, NULL, CPPLNM_TAGREAD, CPPLNM_TAGREAD, PM_REMOVE) )
{
CPs_NotifyChunk* pChunk = (CPs_NotifyChunk*)msg.wParam;
int iChunkItemIDX;
// Add all of the items in the chunk
for(iChunkItemIDX = 0; iChunkItemIDX < pChunk->m_iNumberInChunk; iChunkItemIDX++)
CPLII_DestroyItem(pChunk->m_aryItems[iChunkItemIDX]);
free(pChunk);
}
}
// Clean up object
free(pPlaylist);
}
//
//
//
void CPL_UnlinkItem(CP_HPLAYLIST hPlaylist, CP_HPLAYLISTITEM hItem)
{
CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
CPs_PlaylistItem* pItemToUnlink = CPLII_DECODEHANDLE(hItem);
CP_CHECKOBJECT(pPlaylist);
// Remove item from list
if(pItemToUnlink->m_hPrev)
CPLII_DECODEHANDLE(pItemToUnlink->m_hPrev)->m_hNext = pItemToUnlink->m_hNext;
else
{
CP_ASSERT(pPlaylist->m_hFirst == hItem);
pPlaylist->m_hFirst = pItemToUnlink->m_hNext;
}
if(pItemToUnlink->m_hNext)
CPLII_DECODEHANDLE(pItemToUnlink->m_hNext)->m_hPrev = pItemToUnlink->m_hPrev;
else
{
CP_ASSERT(pPlaylist->m_hLast == hItem);
pPlaylist->m_hLast = pItemToUnlink->m_hPrev;
}
}
//
//
//
void CPL_Empty(CP_HPLAYLIST hPlaylist)
{
CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
CP_HPLAYLISTITEM hCursor, hNext;
CP_CHECKOBJECT(pPlaylist);
// Stop the worker thread from processing any more pending ID3 reads
pPlaylist->m_WorkerThreadInfo.m_dwCurrentBatchID++;
// Unlink the active item
if(pPlaylist->m_hCurrent)
{
// This is the active item - clear it's next and prev entries and mark it
// so that it's destroyed when activation next changes
CPs_PlaylistItem* pActiveItem = CPLII_DECODEHANDLE(pPlaylist->m_hCurrent);
if(pActiveItem->m_bDestroyOnDeactivate == FALSE)
{
CPL_UnlinkItem(hPlaylist, pPlaylist->m_hCurrent);
pActiveItem->m_hNext = NULL;
pActiveItem->m_hPrev = NULL;
pActiveItem->m_bDestroyOnDeactivate = TRUE;
CPL_cb_OnPlaylistActivationChange(pPlaylist->m_hCurrent, FALSE);
pActiveItem->m_iCookie = CPC_INVALIDITEM;
}
}
// Callback
CPL_cb_OnPlaylistEmpty();
// Clean up items
hCursor = pPlaylist->m_hFirst;
while(hCursor)
{
hNext = CPLI_Next(hCursor);
CPLII_DestroyItem(hCursor);
hCursor = hNext;
}
// Reset state
pPlaylist->m_hFirst = NULL;
pPlaylist->m_hLast = NULL;
// Clean up the trackstack
if(pPlaylist->m_pTrackStack)
free(pPlaylist->m_pTrackStack);
pPlaylist->m_pTrackStack = NULL;
pPlaylist->m_iTrackStackSize = 0;
pPlaylist->m_iTrackStackBufferSize = 0;
pPlaylist->m_iTrackStackCursor = 0;
}
//
//
//
void CPL_AddSingleFile_pt2(CP_HPLAYLIST hPlaylist, CP_HPLAYLISTITEM hNewFile, const DWORD dwBatchID)
{
CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
const char* pcPath = CPLI_GetPath(hNewFile);
CP_CHECKOBJECT(pPlaylist);
// Batch has changed since this message was sent
if(dwBatchID != pPlaylist->m_WorkerThreadInfo.m_dwCurrentBatchID)
{
CPLII_DestroyItem(hNewFile);
return;
}
// If items are only allowed once - look for another instance of this item
// and skip this add if it is found
if(options.allow_file_once_in_playlist)
{
CP_HPLAYLISTITEM hCursor;
hCursor = pPlaylist->m_hFirst;
while(hCursor)
{
// Is this item in the list already?
if(stricmp(CPLII_DECODEHANDLE(hCursor)->m_pcPath, pcPath) == 0)
{
CPLII_DestroyItem(hNewFile);
return;
}
hCursor = CPLI_Next(hCursor);
}
}
// Add item to the list
CPLII_DECODEHANDLE(hNewFile)->m_hPrev = pPlaylist->m_hLast;
if(pPlaylist->m_hLast)
CPLII_DECODEHANDLE(pPlaylist->m_hLast)->m_hNext = hNewFile;
pPlaylist->m_hLast = hNewFile;
if(pPlaylist->m_hFirst == NULL)
pPlaylist->m_hFirst = hNewFile;
// If there is no track name (ID3 read off or failed) - create one from the path
if(CPLII_DECODEHANDLE(hNewFile)->m_pcTrackName == NULL)
{
int iNumChars;
int iCharIDX;
int iLastSlashIDX = CPC_INVALIDCHAR;
int iLastDotIDX = CPC_INVALIDCHAR;
int iLastCharIDX = CPC_INVALIDCHAR;
if(strnicmp(pcPath,CIC_HTTPHEADER,strlen(CIC_HTTPHEADER)) == 0)
iLastCharIDX = strlen(pcPath);
else
for(iCharIDX=0; pcPath[iCharIDX]; iCharIDX++)
{
if(pcPath[iCharIDX] == '\\')
iLastSlashIDX = iCharIDX;
if(pcPath[iCharIDX] == '.')
iLastDotIDX = iCharIDX;
iLastCharIDX = iCharIDX;
}
// Correct indices
if(iLastSlashIDX == CPC_INVALIDCHAR)
iLastSlashIDX = 0;
else
iLastSlashIDX++; // We want the char after the last slash
if(iLastDotIDX == CPC_INVALIDCHAR || iLastDotIDX < iLastSlashIDX)
iLastDotIDX = iLastCharIDX;
else
iLastDotIDX--; // We want the string up to the char before the last dot
// Create title buffer
iNumChars = (iLastDotIDX-iLastSlashIDX)+1;
CPLII_DECODEHANDLE(hNewFile)->m_pcTrackName = (char*)malloc(iNumChars+1);
memcpy(CPLII_DECODEHANDLE(hNewFile)->m_pcTrackName, pcPath+iLastSlashIDX, iNumChars);
CPLII_DECODEHANDLE(hNewFile)->m_pcTrackName[iNumChars] = '\0';
}
// Add to track stack
CPL_Stack_Append(hPlaylist, hNewFile);
// Callback
CPL_cb_OnPlaylistAppend(hNewFile);
}
//
//
//
void CPL_AddSingleFile(CP_HPLAYLIST hPlaylist, const char* pcPath, const char* pcTitle)
{
CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
CP_HPLAYLISTITEM hNewFile;
CP_CHECKOBJECT(pPlaylist);
hNewFile = CPLII_CreateItem(pcPath);
// There was a title passed - setup the item accordingly
if(pcTitle && pcTitle[0])
STR_AllocSetString(&CPLII_DECODEHANDLE(hNewFile)->m_pcTrackName, pcTitle, FALSE);
// Defer this add to the worker thread if we are reading tags
if(options.read_id3_tag && options.read_id3_tag_in_background && !pPlaylist->m_bSyncLoadNextFile)
{
while(!PostThreadMessage(pPlaylist->m_dwWorkerThreadID, CPPLWT_READTAG, (WPARAM)pPlaylist->m_WorkerThreadInfo.m_dwCurrentBatchID, (LPARAM)hNewFile))
{
Sleep(50);
}
if(pPlaylist->m_bAutoActivateInitial && stricmp(pcPath, options.initial_file) == 0)
PostThreadMessage(pPlaylist->m_dwWorkerThreadID, CPPLWT_SETACTIVE, (WPARAM)pPlaylist->m_WorkerThreadInfo.m_dwCurrentBatchID, (LPARAM)hNewFile);
}
else
{
pPlaylist->m_bSyncLoadNextFile = FALSE;
if(options.read_id3_tag)
CPLI_ReadTag(hNewFile);
// If we didn't get a track length from the tag - work it out
if(CPLI_GetTrackLength(hNewFile) == 0
&& options.work_out_track_lengths)
{
CPLI_CalculateLength(hNewFile);
}
CPL_AddSingleFile_pt2(hPlaylist, hNewFile, pPlaylist->m_WorkerThreadInfo.m_dwCurrentBatchID);
}
}
//
//
//
void CPL_HandleAsyncNotify(CP_HPLAYLIST hPlaylist, WPARAM wParam, LPARAM lParam)
{
CPs_NotifyChunk* pChunk = (CPs_NotifyChunk*)wParam;
int iChunkItemIDX;
// Add all of the items in the chunk
CLV_BeginBatch(globals.m_hPlaylistViewControl);
for(iChunkItemIDX = 0; iChunkItemIDX < pChunk->m_iNumberInChunk; iChunkItemIDX++)
CPL_AddSingleFile_pt2(globals.m_hPlaylist, pChunk->m_aryItems[iChunkItemIDX], pChunk->m_aryBatchIDs[iChunkItemIDX]);
CLV_EndBatch(globals.m_hPlaylistViewControl);
// Cleanup
free(pChunk);
}
//
//
//
void CPL_RemoveDuplicates(CP_HPLAYLIST hPlaylist)
{
CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
CP_HPLAYLISTITEM hCursor;
CP_CHECKOBJECT(pPlaylist);
// Scan the playlist removing duplicates
hCursor = pPlaylist->m_hFirst;
while(hCursor)
{
CP_HPLAYLISTITEM hCursor_Scan;
// Look for duplicates after this item (as all items will be scanned
// in this way there is no need to look for duplicates before this item)
hCursor_Scan = CPLI_Next(hCursor);
while(hCursor_Scan)
{
// Is this a duplicate
if(stricmp( CPLII_DECODEHANDLE(hCursor_Scan)->m_pcPath,
CPLII_DECODEHANDLE(hCursor)->m_pcPath) == 0)
{
CPL_RemoveItem(hPlaylist, hCursor_Scan);
// Items before the current are already unique - stop scanning
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -