PlaylistManager.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. #include <strsafe.h>
  2. #include <shlwapi.h>
  3. #include <algorithm>
  4. #include "main.h"
  5. #include "resource.h"
  6. #include "PlaylistManager.h"
  7. #include "ifc_playlistloader.h"
  8. #include "M3ULoader.h"
  9. #include "M3UWriter.h"
  10. #include "PLSWriter.h"
  11. #include "M3U8Writer.h"
  12. #include "B4SWriter.h"
  13. #include "../nu/AutoChar.h"
  14. #include "Playlist.h"
  15. #include "../playlist/svc_playlisthandler.h"
  16. #include "../playlist/Handler.h"
  17. #include "../nu/AutoWide.h"
  18. #include "Playlist.h"
  19. #include "api/service/services.h"
  20. #include "api__playlist.h"
  21. #include "api/service/waservicefactory.h"
  22. #include "PlaylistCounter.h"
  23. #include "ifc_playlistloadercallback.h"
  24. #include "ifc_playlistdirectorycallback.h"
  25. #include "..\WAT\WAT.h"
  26. class NoRecurseCallback : public ifc_playlistdirectorycallback
  27. {
  28. public:
  29. NoRecurseCallback( ifc_playlistdirectorycallback *_callback ) : callback( _callback ) {}
  30. bool ShouldRecurse( const wchar_t *path ) { return false; }
  31. bool ShouldLoad( const wchar_t *filename ) { return callback->ShouldLoad( filename ); }
  32. ifc_playlistdirectorycallback *callback;
  33. protected:
  34. RECVS_DISPATCH;
  35. };
  36. #define CBCLASS NoRecurseCallback
  37. START_DISPATCH;
  38. CB( IFC_PLAYLISTDIRECTORYCALLBACK_SHOULDRECURSE, ShouldRecurse )
  39. CB( IFC_PLAYLISTDIRECTORYCALLBACK_SHOULDLOAD, ShouldLoad )
  40. END_DISPATCH;
  41. #undef CBCLASS
  42. static void MakeRelativePathName( const wchar_t *filename, wchar_t *outFile, size_t cch, const wchar_t *path )
  43. {
  44. wchar_t outPath[ MAX_PATH ] = { 0 };
  45. int common = PathCommonPrefixW( path, filename, outPath );
  46. if ( common && common == wcslen( path ) )
  47. {
  48. PathAddBackslashW( outPath );
  49. const wchar_t *p = filename + wcslen( outPath );
  50. lstrcpynW( outFile, p, (int)cch );
  51. }
  52. else if ( !PathIsUNCW( filename ) && PathIsSameRootW( filename, path ) )
  53. {
  54. if ( outFile[ 1 ] == ':' )
  55. lstrcpynW( outFile, filename + 2, (int)cch );
  56. }
  57. }
  58. static void PlayList_makerelative( const wchar_t *base, wchar_t *filename, size_t cch )
  59. {
  60. MakeRelativePathName( filename, filename, cch, base );
  61. }
  62. PlaylistManager playlistManager;
  63. struct LoaderPair
  64. {
  65. ifc_playlistloader *loader;
  66. svc_playlisthandler *handler;
  67. };
  68. static LoaderPair CreateLoader( const wchar_t *filename )
  69. {
  70. LoaderPair ret = { 0, 0 };
  71. int n = 0;
  72. waServiceFactory *sf = 0;
  73. while ( sf = WASABI_API_SVC->service_enumService( WaSvc::PLAYLISTHANDLER, n++ ) )
  74. {
  75. svc_playlisthandler *handler = static_cast<svc_playlisthandler *>( sf->getInterface() );
  76. if ( handler )
  77. {
  78. if ( handler->SupportedFilename( filename ) == SVC_PLAYLISTHANDLER_SUCCESS )
  79. {
  80. ret.loader = handler->CreateLoader( filename );
  81. ret.handler = handler;
  82. break;
  83. }
  84. else
  85. {
  86. sf->releaseInterface( handler );
  87. }
  88. }
  89. }
  90. // TODO: sniff file if no one claims it
  91. return ret;
  92. }
  93. void DestroyLoader( LoaderPair &loader )
  94. {
  95. loader.handler->ReleaseLoader( loader.loader );
  96. }
  97. // a simple loader...
  98. int PlaylistManager::Load( const wchar_t *filename, ifc_playlistloadercallback *playlist )
  99. {
  100. LoaderPair loaderPair = CreateLoader( filename );
  101. ifc_playlistloader *loader = loaderPair.loader;
  102. if ( !loader )
  103. return PLAYLISTMANAGER_LOAD_NO_LOADER; // failed to find a loader
  104. // TODO: make our own ifc_playlistloadercallback, so we can handle nested playlists
  105. int res = loader->Load( filename, playlist );
  106. DestroyLoader( loaderPair );
  107. if ( res != IFC_PLAYLISTLOADER_SUCCESS ) // TODO: switch on the error code and return a more specific error
  108. return PLAYLISTMANAGER_LOAD_LOADER_OPEN_FAILED;
  109. return PLAYLISTMANAGER_SUCCESS;
  110. }
  111. int PlaylistManager::LoadAs( const wchar_t *filename, const wchar_t *ext, ifc_playlistloadercallback *playlist )
  112. {
  113. LoaderPair loaderPair = CreateLoader( ext );
  114. ifc_playlistloader *loader = loaderPair.loader;
  115. if ( !loader )
  116. return PLAYLISTMANAGER_LOAD_NO_LOADER; // failed to find a loader
  117. // TODO: make our own ifc_playlistloadercallback, so we can handle nested playlists
  118. int res = loader->Load( filename, playlist );
  119. DestroyLoader( loaderPair );
  120. if ( res != IFC_PLAYLISTLOADER_SUCCESS ) // TODO: switch on the error code and return a more specific error
  121. return PLAYLISTMANAGER_LOAD_LOADER_OPEN_FAILED;
  122. return PLAYLISTMANAGER_SUCCESS;
  123. }
  124. int PlaylistManager::LoadFromDialog( const wchar_t *fns, ifc_playlistloadercallback *playlist )
  125. {
  126. wchar_t buf[ MAX_PATH ] = { 0 };
  127. const wchar_t *path = fns;
  128. fns += wcslen( fns ) + 1;
  129. while ( fns && *fns )
  130. {
  131. if ( *path )
  132. PathCombineW( buf, path, fns );
  133. else
  134. StringCchCopyW( buf, MAX_PATH, fns );
  135. if ( Load( buf, playlist ) != PLAYLISTMANAGER_SUCCESS )
  136. {
  137. if ( playlist->OnFile( buf, 0, -1, 0 ) != ifc_playlistloadercallback::LOAD_CONTINUE )
  138. return PLAYLIST_SUCCESS;
  139. }
  140. fns += wcslen( fns ) + 1;
  141. }
  142. return PLAYLIST_SUCCESS;
  143. }
  144. int PlaylistManager::LoadFromANSIDialog( const char *fns, ifc_playlistloadercallback *playlist )
  145. {
  146. char buf[ MAX_PATH ] = { 0 };
  147. const char *path = fns;
  148. fns += lstrlenA( fns ) + 1;
  149. while ( fns && *fns )
  150. {
  151. if ( *path )
  152. PathCombineA( buf, path, fns );
  153. else
  154. lstrcpynA( buf, fns, MAX_PATH );
  155. AutoWide wideFn( buf );
  156. if ( Load( wideFn, playlist ) != PLAYLISTMANAGER_SUCCESS )
  157. {
  158. if ( playlist->OnFile( wideFn, 0, -1, 0 ) != ifc_playlistloadercallback::LOAD_CONTINUE )
  159. return PLAYLIST_SUCCESS;
  160. }
  161. fns += lstrlenA( fns ) + 1;
  162. }
  163. return PLAYLIST_SUCCESS;
  164. }
  165. int PlaylistManager::Save( const wchar_t *filename, ifc_playlist *playlist )
  166. {
  167. const wchar_t *ext = PathFindExtensionW( filename );
  168. PlaylistWriter *writer = 0;
  169. if ( !lstrcmpiW( ext, L".M3U" ) )
  170. writer = new M3UWriter;
  171. else if ( !lstrcmpiW( ext, L".M3U8" ) )
  172. writer = new M3U8Writer;
  173. else if ( !lstrcmpiW( ext, L".PLS" ) )
  174. writer = new PLSWriter;
  175. else if ( !lstrcmpiW( ext, L".B4S" ) )
  176. writer = new B4SWriter;
  177. else
  178. return PLAYLISTMANAGER_FAILED;
  179. wchar_t base[ MAX_PATH ] = { 0 };
  180. StringCchCopyW( base, MAX_PATH, filename );
  181. PathRemoveFileSpecW( base );
  182. PathRemoveBackslashW( base );
  183. if ( !writer->Open( filename ) )
  184. {
  185. delete writer;
  186. return PLAYLISTMANAGER_FAILED;
  187. }
  188. size_t numItems = playlist->GetNumItems();
  189. wchar_t itemname[ FILENAME_SIZE ] = { 0 };
  190. wchar_t title[ FILETITLE_SIZE ] = { 0 };
  191. wchar_t cloud_info[ 512 ] = { 0 };
  192. int length = 0;
  193. wchar_t l_tvg_id[ 10 ] = { 0 };
  194. wchar_t l_tvg_name[ FILETITLE_SIZE ] = { 0 };
  195. wchar_t l_tvg_logo[ 512 ] = { 0 };
  196. wchar_t l_group_title[ 64 ] = { 0 };
  197. wchar_t l_ext[ 10 ] = { 0 };
  198. wa::strings::wa_string l_extented_infos_line( "" );
  199. for ( size_t i = 0; i != numItems; i++ )
  200. {
  201. if ( playlist->GetItem( i, itemname, FILENAME_SIZE ) )
  202. {
  203. //PlayList_makerelative( base, itemname, FILENAME_SIZE );
  204. // this is used to preserve 'cloud' specific data in playlists
  205. // and should only get a response from a cloud-based ml_playlist
  206. if ( playlist->GetItemExtendedInfo( i, L"cloud", cloud_info, 512 ) )
  207. {
  208. writer->Write( cloud_info );
  209. }
  210. l_extented_infos_line.clear();
  211. if ( playlist->GetItemExtendedInfo( i, L"tvg-name", l_tvg_name, FILETITLE_SIZE ) )
  212. {
  213. playlist->GetItemExtendedInfo( i, L"tvg-id", l_tvg_id, 10 );
  214. playlist->GetItemExtendedInfo( i, L"tvg-logo", l_tvg_logo, 512 );
  215. playlist->GetItemExtendedInfo( i, L"group-title", l_group_title, 64 );
  216. l_extented_infos_line = L"tvg-id";
  217. l_extented_infos_line.append( L"=\"" );
  218. l_extented_infos_line.append( l_tvg_id );
  219. l_extented_infos_line.append( L"\" " );
  220. l_extented_infos_line.append( L"tvg-name" );
  221. l_extented_infos_line.append( L"=\"" );
  222. l_extented_infos_line.append( l_tvg_name );
  223. l_extented_infos_line.append( L"\" " );
  224. l_extented_infos_line.append( L"tvg-logo" );
  225. l_extented_infos_line.append( L"=\"" );
  226. l_extented_infos_line.append( l_tvg_logo );
  227. l_extented_infos_line.append( L"\" " );
  228. l_extented_infos_line.append( L"group-title" );
  229. l_extented_infos_line.append( L"=\"" );
  230. l_extented_infos_line.append( l_group_title );
  231. l_extented_infos_line.append( L"\" " );
  232. }
  233. wa::strings::wa_string l_item_name( itemname );
  234. if ( l_item_name.contains( "://" ) && playlist->GetItemExtendedInfo(i, L"ext", l_ext, 10) )
  235. {
  236. l_extented_infos_line = L"ext";
  237. l_extented_infos_line.append( L"=\"" );
  238. l_extented_infos_line.append( l_ext );
  239. l_extented_infos_line.append( L"\"" );
  240. }
  241. if ( playlist->GetItemTitle( i, title, FILETITLE_SIZE ) )
  242. {
  243. length = playlist->GetItemLengthMilliseconds( i );
  244. if ( l_extented_infos_line.empty() )
  245. writer->Write( itemname, title, length / 1000 );
  246. else
  247. writer->Write( itemname, title, l_extented_infos_line.GetW().c_str(), length / 1000);
  248. }
  249. else
  250. writer->Write( itemname );
  251. }
  252. }
  253. writer->Close();
  254. delete writer;
  255. return PLAYLISTMANAGER_SUCCESS;
  256. }
  257. size_t PlaylistManager::Copy( const wchar_t *destFn, const wchar_t *srcFn )
  258. {
  259. Playlist copy;
  260. Load( srcFn, &copy );
  261. Save( destFn, &copy );
  262. return copy.GetNumItems();
  263. }
  264. size_t PlaylistManager::CountItems( const wchar_t *filename )
  265. {
  266. LoaderPair loaderPair = CreateLoader( filename );
  267. ifc_playlistloader *loader = loaderPair.loader;
  268. if ( !loader )
  269. return 0;
  270. PlaylistCounter counter;
  271. loader->Load( filename, &counter );
  272. DestroyLoader( loaderPair );
  273. return counter.count;
  274. }
  275. int PlaylistManager::GetLengthMilliseconds( const wchar_t *filename )
  276. {
  277. LoaderPair loaderPair = CreateLoader( filename );
  278. ifc_playlistloader *loader = loaderPair.loader;
  279. if ( !loader )
  280. return 0;
  281. PlaylistCounter counter;
  282. loader->Load( filename, &counter );
  283. DestroyLoader( loaderPair );
  284. return (int)counter.length;
  285. }
  286. uint64_t PlaylistManager::GetLongLengthMilliseconds( const wchar_t *filename )
  287. {
  288. LoaderPair loaderPair = CreateLoader( filename );
  289. ifc_playlistloader *loader = loaderPair.loader;
  290. if ( !loader )
  291. return 0;
  292. PlaylistCounter counter;
  293. loader->Load( filename, &counter );
  294. DestroyLoader( loaderPair );
  295. return counter.length;
  296. }
  297. void PlaylistManager::Randomize( ifc_playlist *playlist )
  298. {
  299. if ( playlist->Randomize( warand ) == PLAYLIST_UNIMPLEMENTED )
  300. {
  301. // TODO: do it the hard way
  302. }
  303. }
  304. void PlaylistManager::Reverse( ifc_playlist *playlist )
  305. {
  306. if ( playlist->Reverse() == PLAYLIST_UNIMPLEMENTED )
  307. {
  308. // TODO: do it the hard way
  309. }
  310. }
  311. void PlaylistManager::LoadDirectory( const wchar_t *directory, ifc_playlistloadercallback *callback, ifc_playlistdirectorycallback *dirCallback )
  312. {
  313. WIN32_FIND_DATAW found = { 0 };
  314. wchar_t filespec[ MAX_PATH ] = { 0 };
  315. PathCombineW( filespec, directory, L"*.*" );
  316. HANDLE i = FindFirstFileW( filespec, &found );
  317. if ( i != INVALID_HANDLE_VALUE )
  318. {
  319. do
  320. {
  321. // if it's another folder, then we might want to recurse into it
  322. if ( ( found.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) // if it's a directory
  323. && wcscmp( found.cFileName, L"." ) && wcscmp( found.cFileName, L".." ) // but not . or ..
  324. && ( !dirCallback || dirCallback->ShouldRecurse( found.cFileName ) ) ) // and we're allowed to recurse
  325. {
  326. if ( PathCombineW( filespec, directory, found.cFileName ) )
  327. {
  328. LoadDirectory( filespec, callback, dirCallback );
  329. }
  330. }
  331. if ( !( found.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) )
  332. {
  333. const wchar_t *ext = PathFindExtensionW( found.cFileName );
  334. if ( ext[ 0 ] )
  335. {
  336. if ( !_wcsicmp( ext, L".lnk" ) )
  337. {
  338. wchar_t thisf[ MAX_PATH ] = { 0 };
  339. wchar_t temp2[ MAX_PATH ] = { 0 };
  340. PathCombineW( temp2, directory, found.cFileName );
  341. if ( ResolveShortCut( NULL, temp2, thisf ) && GetLongPathNameW( thisf, temp2, MAX_PATH ) && lstrcmpiW( temp2, directory ) )
  342. {
  343. WIN32_FIND_DATAW d2 = { 0 };
  344. if ( IsUrl( temp2 ) && ( !dirCallback || dirCallback->ShouldLoad( temp2 ) ) )
  345. {
  346. if ( callback->OnFile( temp2, 0, -1, 0 ) != ifc_playlistloadercallback::LOAD_CONTINUE )
  347. break;
  348. }
  349. else
  350. {
  351. HANDLE h2 = FindFirstFileW( temp2, &d2 );
  352. if ( h2 != INVALID_HANDLE_VALUE )
  353. {
  354. if ( !( d2.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) )
  355. {
  356. if ( !dirCallback || dirCallback->ShouldLoad( temp2 ) )
  357. {
  358. if ( callback->OnFile( temp2, 0, -1, 0 ) != ifc_playlistloadercallback::LOAD_CONTINUE )
  359. {
  360. FindClose( h2 );
  361. break;
  362. }
  363. }
  364. }
  365. else
  366. {
  367. // recursively load a shortcut w/o fear of infinite recursion
  368. NoRecurseCallback noRecurse( dirCallback );
  369. LoadDirectory( temp2, callback, &noRecurse );
  370. }
  371. FindClose( h2 );
  372. }
  373. }
  374. }
  375. }
  376. else // !shortcut
  377. {
  378. if ( PathCombineW( filespec, directory, found.cFileName ) &&
  379. ( !dirCallback || dirCallback->ShouldLoad( filespec ) ) )
  380. {
  381. if ( callback->OnFile( filespec, 0, -1, 0 ) != ifc_playlistloadercallback::LOAD_CONTINUE )
  382. break;
  383. }
  384. }
  385. }
  386. }
  387. } while ( FindNextFileW( i, &found ) );
  388. FindClose( i );
  389. }
  390. }
  391. bool PlaylistManager::CanLoad( const wchar_t *filename )
  392. {
  393. int n = 0;
  394. waServiceFactory *sf = 0;
  395. while ( sf = WASABI_API_SVC->service_enumService( WaSvc::PLAYLISTHANDLER, n++ ) )
  396. {
  397. svc_playlisthandler *handler = static_cast<svc_playlisthandler *>( sf->getInterface() );
  398. if ( handler )
  399. {
  400. if ( handler->SupportedFilename( filename ) == SVC_PLAYLISTHANDLER_SUCCESS )
  401. {
  402. sf->releaseInterface( handler );
  403. return true;
  404. }
  405. else
  406. {
  407. sf->releaseInterface( handler );
  408. }
  409. }
  410. }
  411. return false;
  412. }
  413. void PlaylistManager::GetExtensionList( wchar_t *extensionList, size_t extensionListCch )
  414. {
  415. extensionList[ 0 ] = 0;
  416. bool first = true;
  417. int n = 0, extListCch = (int)extensionListCch;
  418. wchar_t *extList = extensionList;
  419. waServiceFactory *sf = 0;
  420. while ( sf = WASABI_API_SVC->service_enumService( WaSvc::PLAYLISTHANDLER, n++ ) )
  421. {
  422. svc_playlisthandler *handler = static_cast<svc_playlisthandler *>( sf->getInterface() );
  423. if ( handler )
  424. {
  425. const wchar_t *ext = 0;
  426. int k = 0;
  427. while ( ext = handler->EnumerateExtensions( k++ ) )
  428. {
  429. if ( first )
  430. StringCchCatExW( extensionList, extensionListCch, L"*.", &extensionList, &extensionListCch, 0 );
  431. else
  432. StringCchCatExW( extensionList, extensionListCch, L";*.", &extensionList, &extensionListCch, 0 );
  433. first = false;
  434. StringCchCatExW( extensionList, extensionListCch, ext, &extensionList, &extensionListCch, 0 );
  435. }
  436. sf->releaseInterface( handler );
  437. }
  438. }
  439. CharUpperBuffW( extList, extListCch );
  440. }
  441. void PlaylistManager::GetFilterList( wchar_t *extensionList, size_t extensionListCch )
  442. {
  443. extensionListCch--; // this needs to be DOUBLE null terminated, so we'll make sure there's room
  444. StringCchCopyExW( extensionList, extensionListCch, WASABI_API_LNGSTRINGW( IDS_ALL_PLAYLIST_TYPES ), &extensionList, &extensionListCch, 0 );
  445. extensionListCch--;
  446. extensionList++;
  447. GetExtensionList( extensionList, extensionListCch );
  448. extensionListCch -= ( wcslen( extensionList ) + 1 );
  449. extensionList += wcslen( extensionList ) + 1;
  450. int n = 0;
  451. waServiceFactory *sf = 0;
  452. while ( sf = WASABI_API_SVC->service_enumService( WaSvc::PLAYLISTHANDLER, n++ ) )
  453. {
  454. svc_playlisthandler *handler = static_cast<svc_playlisthandler *>( sf->getInterface() );
  455. if ( handler )
  456. {
  457. const wchar_t *name = handler->GetName();
  458. if ( !name )
  459. name = WASABI_API_LNGSTRINGW( IDS_PLAYLIST );
  460. StringCchCopyExW( extensionList, extensionListCch, name, &extensionList, &extensionListCch, 0 );
  461. extensionList++;
  462. extensionListCch--;
  463. bool first = true;
  464. const wchar_t *ext = 0;
  465. int k = 0;
  466. while ( ext = handler->EnumerateExtensions( k++ ) )
  467. {
  468. if ( first )
  469. StringCchCopyExW( extensionList, extensionListCch, L"*.", &extensionList, &extensionListCch, 0 );
  470. else
  471. StringCchCatExW( extensionList, extensionListCch, L";*.", &extensionList, &extensionListCch, 0 );
  472. first = false;
  473. StringCchCatExW( extensionList, extensionListCch, ext, &extensionList, &extensionListCch, 0 );
  474. }
  475. extensionList++;
  476. extensionListCch--;
  477. sf->releaseInterface( handler );
  478. }
  479. }
  480. extensionList[ 0 ] = 0; // ok because we reserved the room for it above
  481. }
  482. const wchar_t *PlaylistManager::EnumExtensions( size_t num )
  483. {
  484. int n = 0;
  485. int total = 0;
  486. waServiceFactory *sf = 0;
  487. while ( sf = WASABI_API_SVC->service_enumService( WaSvc::PLAYLISTHANDLER, n++ ) )
  488. {
  489. svc_playlisthandler *handler = static_cast<svc_playlisthandler *>( sf->getInterface() );
  490. if ( handler )
  491. {
  492. const wchar_t *ext = 0;
  493. int k = 0;
  494. while ( ext = handler->EnumerateExtensions( k++ ) )
  495. {
  496. if ( total++ == num )
  497. return ext;
  498. }
  499. sf->releaseInterface( handler );
  500. }
  501. }
  502. return 0;
  503. }
  504. #define CBCLASS PlaylistManager
  505. START_DISPATCH;
  506. CB( API_PLAYLISTMANAGER_LOAD, Load )
  507. CB( API_PLAYLISTMANAGER_LOADAS, LoadAs )
  508. CB( API_PLAYLISTMANAGER_LOADNULLDELIMITED, LoadFromDialog )
  509. CB( API_PLAYLISTMANAGER_LOADNULLDELIMITED_ANSI, LoadFromANSIDialog )
  510. CB( API_PLAYLISTMANAGER_SAVE, Save )
  511. CB( API_PLAYLISTMANAGER_COPY, Copy )
  512. CB( API_PLAYLISTMANAGER_COUNT, CountItems )
  513. CB( API_PLAYLISTMANAGER_GETLENGTH, GetLengthMilliseconds )
  514. CB( API_PLAYLISTMANAGER_GETLONGLENGTH, GetLongLengthMilliseconds )
  515. VCB( API_PLAYLISTMANAGER_RANDOMIZE, Randomize )
  516. VCB( API_PLAYLISTMANAGER_REVERSE, Reverse )
  517. VCB( API_PLAYLISTMANAGER_LOADDIRECTORY, LoadDirectory )
  518. CB( API_PLAYLISTMANAGER_CANLOAD, CanLoad )
  519. VCB( API_PLAYLISTMANAGER_GETEXTENSIONLIST, GetExtensionList )
  520. VCB( API_PLAYLISTMANAGER_GETFILTERLIST, GetFilterList )
  521. CB( API_PLAYLISTMANAGER_ENUMEXTENSION, EnumExtensions )
  522. END_DISPATCH;
  523. #undef CBCLASS