#import "iTunesRemote.h"
+#import "PlaylistNode.h"
@implementation iTunesRemote
ITDebugLog(@"iTunesRemote begun");
savedPSN = [self iTunesPSN];
[[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationHandler:) name:@"com.apple.iTunes.playerInfo" object:@"com.apple.iTunes.player"];
+
+ NSString *iTunesPath = [[NSUserDefaults standardUserDefaults] stringForKey:@"CustomPlayerPath"];
+ NSDictionary *iTunesInfoPlist;
+ float iTunesVersion;
+
+ //Check if iTunes 5.0 or later is installed
+ if (!iTunesPath) {
+ iTunesPath = [[NSWorkspace sharedWorkspace] fullPathForApplication:@"iTunes.app"];
+ }
+ iTunesInfoPlist = [[NSBundle bundleWithPath:iTunesPath] infoDictionary];
+ iTunesVersion = [[iTunesInfoPlist objectForKey:@"CFBundleVersion"] floatValue];
+ ITDebugLog(@"iTunes version found: %f.", iTunesVersion);
+ _iTunesVersion = iTunesVersion;
+
return YES;
}
SInt32 result;
ITDebugLog(@"Getting player playing state");
- result = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pPlS'), from:'null'() }", 'core', 'getd', &savedPSN) int32Value];
+ result = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pPlS'), from:'null'() }", 'core', 'getd', &savedPSN) typeCodeValue];
switch (result)
{
case 'kPSP':
}*/
//Full source awareness
-- (NSArray *)playlists
+/*- (NSArray *)playlists
{
unsigned long i, k;
SInt32 numSources = [ITSendAEWithString(@"kocl:type('cSrc'), '----':()", 'core', 'cnte', &savedPSN) int32Value];
ITDebugLog(@"Source at index %i disappeared.", k);
}
}
+ NSLog(@"playlists: %@", allSources);
ITDebugLog(@"Finished getting playlists.");
return [allSources autorelease];
+}*/
+
+- (NSArray *)playlists
+{
+ SInt32 numSources = [ITSendAEWithString(@"kocl:type('cSrc'), '----':()", 'core', 'cnte', &savedPSN) int32Value];
+ NSMutableArray *sources = [[NSMutableArray alloc] init];
+ int i;
+
+ ITDebugLog(@"Getting playlists.");
+ if (numSources == 0) {
+ [sources release];
+ ITDebugLog(@"No sources.");
+ return nil;
+ }
+
+ //Loop through each source
+ for (i = 1; i <= numSources; i++) {
+ FourCharCode fourcc = [ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pKnd'), from:obj { form:'indx', want:type('cSrc'), seld:long(%i), from:() } }", i], 'core', 'getd', &savedPSN) typeCodeValue]; //Type of the current source
+ NSString *sourceName = [ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pnam'), from:obj { form:'indx', want:type('cSrc'), seld:long(%i), from:() } }", i], 'core', 'getd', &savedPSN) stringValue]; //Name of the current source
+ SInt32 index = [ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pidx'), from:obj { form:'indx', want:type('cSrc'), seld:long(%i), from:() } }", i], 'core', 'getd', &savedPSN) int32Value]; //Index of the current source
+ ITMTRemotePlayerSource class; //The class of the current source
+
+ //Make a new PlaylistNode for this source
+ PlaylistNode *sourceNode = [PlaylistNode playlistNodeWithName:sourceName type:ITMTSourceNode index:index];
+
+ switch (fourcc) {
+ case 'kTun':
+ class = ITMTRemoteRadioSource;
+ break;
+ case 'kDev':
+ class = ITMTRemoteGenericDeviceSource;
+ break;
+ case 'kPod':
+ class = ITMTRemoteiPodSource;
+ break;
+ case 'kMCD':
+ case 'kACD':
+ class = ITMTRemoteCDSource;
+ break;
+ case 'kShd':
+ class = ITMTRemoteSharedLibrarySource;
+ break;
+ case 'kUnk':
+ case 'kLib':
+ default:
+ class = ITMTRemoteLibrarySource;
+ break;
+ }
+ [sourceNode setSourceType:class];
+ ITDebugLog(@"New source %@ of type %i at index %i", sourceName, class, index);
+
+ int j;
+ SInt32 numPlaylists = [ITSendAEWithString([NSString stringWithFormat:@"kocl:type('cPly'), '----':obj { form:'indx', want:type('cSrc'), seld:long(%i), from:() }", i], 'core', 'cnte', &savedPSN) int32Value]; //Number of playlists in the current source
+
+ //Pass 1, add all the playlists into the main array
+ for (j = 1; j <= numPlaylists; j++) {
+ NSString *sendStr = [NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pnam'), from:obj { form:'indx', want:type('cPly'), seld:long(%i), from:obj { form:'indx', want:type('cSrc'), seld:long(%i), from:() } } }", j, (_iTunesVersion >= 5) ? i : index];
+ NSString *parentSendStr = [NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pnam'), from:obj { form:'prop', want:type('prop'), seld:type('pPlP'), from:obj { form:'indx', want:type('cPly'), seld:long(%i), from:obj { form:'indx', want:type('cSrc'), seld:long(%i), from:() } } } }", j, i];
+ NSString *theObj = [ITSendAEWithString(sendStr, 'core', 'getd', &savedPSN) stringValue], *parent = [ITSendAEWithString(parentSendStr, 'core', 'getd', &savedPSN) stringValue];
+ ITDebugLog(@" - Adding playlist %@", theObj);
+ if (theObj) {
+ FourCharCode code = [ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pSpK'), from:obj { form:'indx', want:type('cPly'), seld:long(%i), from:obj { form:'indx', want:type('cSrc'), seld:long(%i), from:() } } }", j, i], 'core', 'getd', &savedPSN) typeCodeValue];
+ ITMTNodeType type;
+ switch (code) {
+ case 'kSpN':
+ type = ITMTPlaylistNode;
+ break;
+ case 'kSpF':
+ type = ITMTFolderNode;
+ break;
+ case 'kSpS':
+ type = ITMTPartyShuffleNode;
+ break;
+ case 'kSpP':
+ type = ITMTPodcastsNode;
+ break;
+ case 'kSpM':
+ type = ITMTPurchasedMusicNode;
+ break;
+ case 'kSpV':
+ type = ITMTVideosNode;
+ break;
+ }
+ PlaylistNode *node = [PlaylistNode playlistNodeWithName:theObj type:type index:j];
+ [[sourceNode children] addObject:node];
+ if (parent) {
+ int parentIndex = [ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pidx'), from:obj { form:'prop', want:type('prop'), seld:type('pPlP'), from:obj { form:'indx', want:type('cPly'), seld:long(%i), from:obj { form:'indx', want:type('cSrc'), seld:long(%i), from:() } } } }", j, i], 'core', 'getd', &savedPSN) int32Value];
+ [node setParent:[PlaylistNode playlistNodeWithName:parent type:ITMTFolderNode index:parentIndex]];
+ } else {
+ [node setParent:sourceNode];
+ }
+ }
+ }
+
+ //Pass 2, nest each item under its proper parent. Once everything has been nested, delete the original from the main array.
+ NSEnumerator *enumerator = [[sourceNode children] objectEnumerator];
+ PlaylistNode *nextNode;
+ NSMutableArray *nested = [[NSMutableArray alloc] init];
+
+ while ( (nextNode = [enumerator nextObject]) ) {
+ PlaylistNode *pNode = [nextNode parent];
+ if ([pNode type] == ITMTFolderNode) {
+ PlaylistNode *newParent = nil;
+ int k;
+ for (k = 0; !newParent; k++) {
+ PlaylistNode *test = [[sourceNode children] objectAtIndex:k];
+ if ([test index] == [pNode index]) {
+ newParent = test;
+ }
+ }
+ [nextNode setParent:newParent];
+ [[newParent children] addObject:nextNode];
+ [newParent setType:ITMTFolderNode];
+ [nested addObject:nextNode];
+ }
+ }
+
+ NSEnumerator *nestEnumerator = [nested objectEnumerator];
+ while ( (nextNode = [nestEnumerator nextObject]) ) {
+ [[sourceNode children] removeObject:nextNode];
+ }
+ [nested release];
+
+ //Move all the folders to the beginning of the list
+ //Move the podcasts playlist to the top
+ BOOL movedPodcasts = NO;
+ enumerator = [[sourceNode children] reverseObjectEnumerator];
+ while ( (nextNode = [enumerator nextObject]) ) {
+ if ([nextNode type] == ITMTPodcastsNode) {
+ [nextNode retain];
+ [[sourceNode children] removeObject:nextNode];
+ [[sourceNode children] insertObject:nextNode atIndex:1];
+ movedPodcasts = YES;
+ } else if ([nextNode type] == ITMTFolderNode) {
+ [nextNode retain];
+ [[sourceNode children] removeObject:nextNode];
+ [[sourceNode children] insertObject:nextNode atIndex:1 + movedPodcasts];
+ }
+ }
+
+ [sources addObject:sourceNode];
+ }
+
+ return [sources autorelease];
}
- (NSArray *)artists
ITDebugLog(@"Getting current source.");
- fourcc = ([self isPlaying]) ? [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pKnd'), from:obj { form:'prop', want:type('prop'), seld:type('ctnr'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } } }", 'core', 'getd', &savedPSN) int32Value] : 'kLib';
+ fourcc = ([self isPlaying]) ? [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pKnd'), from:obj { form:'prop', want:type('prop'), seld:type('ctnr'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } } }", 'core', 'getd', &savedPSN) typeCodeValue] : 'kLib';
switch (fourcc) {
case 'kTun':
- (ITMTRemotePlayerPlaylistClass)currentPlaylistClass
{
- SInt32 realResult;
+ FourCharCode realResult;
ITDebugLog(@"Getting current playlist class");
- realResult = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pcls'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
+ realResult = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pcls'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } }", 'core', 'getd', &savedPSN) typeCodeValue];
switch (realResult)
{
case 'cLiP':
NSString *temp1;
ITDebugLog(@"Getting current unique identifier.");
NSAppleEventDescriptor *descriptor = ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pcls'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN);
- if ([descriptor int32Value] == 'prop') {
+ if ([descriptor typeCodeValue] == 'prop') {
return @"0-0";
} else if (descriptor == nil) {
return nil;
}
- SInt32 cls = [descriptor int32Value];
+ FourCharCode cls = [descriptor typeCodeValue];
if ( ([self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist) || (cls == 'cURT') ) {
NSString *bad = [NSString stringWithUTF8String:"浳湧"];
temp1 = [ITSendAEWithKey('pStT', 'core', 'getd', &savedPSN) stringValue];
{
NSString *temp1;
ITDebugLog(@"Getting current song title.");
- SInt32 result = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pcls'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
+ FourCharCode result = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pcls'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) typeCodeValue];
//If we're listening to the radio.
if (result == 'cURT') {
return YES;
}
+- (BOOL)currentSongShufflable
+{
+ BOOL temp1;
+ ITDebugLog(@"Getting current song shufflable status.");
+ temp1 = (![self isPlaying] || ([self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist)) ? NO : [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pSfa'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) booleanValue];
+ ITDebugLog(@"Getting current song shufflable status done.");
+ return temp1;
+}
+
+- (BOOL)setCurrentSongShufflable:(BOOL)shufflable
+{
+ ITDebugLog(@"Setting current song shufflable status to %i.", shufflable);
+ if ([self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist) {
+ ITDebugLog(@"Not a valid track to set status to, returning.");
+ return NO;
+ }
+ ITSendAEWithString([NSString stringWithFormat:@"data:long(%lu), '----':obj { form:'prop', want:type('prop'), seld:type('pSfa'), from:obj { form:'indx', want:type('cTrk'), seld:long(%lu), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } } }", shufflable, [self currentSongIndex]], 'core', 'setd', &savedPSN);
+ ITDebugLog(@"Setting current song shufflable status to %i done.", shufflable);
+ return YES;
+}
+
- (BOOL)equalizerEnabled
{
ITDebugLog(@"Getting equalizer enabled status.");
- (BOOL)shuffleEnabled
{
+ int result;
ITDebugLog(@"Getting shuffle enabled status.");
- int result = (int)[ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pShf'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
+ if (![self isPlaying]) {
+ ITDebugLog(@"No current playlist, getting shuffle status from visible playlist.");
+ result = (int)[ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pShf'), from:obj { form:'prop', want:type('prop'), seld:type('pPly'), from:obj { form:'indx', want:type('cBrW'), seld:1, from:'null'() } } }", 'core', 'getd', &savedPSN) int32Value];
+ } else {
+ result = (int)[ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pShf'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
+ }
ITDebugLog(@"Getting shuffle enabled status done.");
return (result != 0);
}
- (BOOL)setShuffleEnabled:(BOOL)enabled
{
ITDebugLog(@"Set shuffle enabled to %i", enabled);
- ITSendAEWithString([NSString stringWithFormat:@"data:long(%lu), '----':obj { form:'prop', want:type('prop'), seld:type('pShf'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } }", (unsigned long)enabled], 'core', 'setd', &savedPSN);
+ if (![self isPlaying]) {
+ ITDebugLog(@"No current playlist, setting shuffle status on visible playlist.");
+ ITSendAEWithString([NSString stringWithFormat:@"data:long(%lu), '----':obj { form:'prop', want:type('prop'), seld:type('pShf'), from:obj { form:'prop', want:type('prop'), seld:type('pPly'), from:obj { form:'indx', want:type('cBrW'), seld:1, from:'null'() } } }", (unsigned long)enabled], 'core', 'setd', &savedPSN);
+ } else {
+ ITSendAEWithString([NSString stringWithFormat:@"data:long(%lu), '----':obj { form:'prop', want:type('prop'), seld:type('pShf'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } }", (unsigned long)enabled], 'core', 'setd', &savedPSN);
+ }
ITDebugLog(@"Set shuffle enabled to %i done", enabled);
return YES;
}
{
FourCharCode m00f = 0;
int result = 0;
- m00f = (FourCharCode)[ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pRpt'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
- ITDebugLog(@"Getting repeat mode.");
+ ITDebugLog(@"Getting repeat mode.");
+ m00f = (FourCharCode)[ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pRpt'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } }", 'core', 'getd', &savedPSN) typeCodeValue];
+
+ if (m00f == 0) {
+ ITDebugLog(@"No current playlist, getting repeat mode from visible playlist.");
+ m00f = (FourCharCode)[ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pRpt'), from:obj { form:'prop', want:type('prop'), seld:type('pPly'), from:obj { form:'indx', want:type('cBrW'), seld:1, from:'null'() } } }", 'core', 'getd', &savedPSN) typeCodeValue];
+ }
+
switch (m00f)
{
//case 'kRp0':
m00f = "kRp0";
break;
}
- ITSendAEWithString([NSString stringWithFormat:@"data:'%s', '----':obj { form:'prop', want:type('prop'), seld:type('pRpt'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:() } }", m00f], 'core', 'setd', &savedPSN);
+ if (![self isPlaying]) {
+ ITDebugLog(@"No current playlist, setting repeat mode on visible playlist.");
+ ITSendAEWithString([NSString stringWithFormat:@"data:'%s', '----':obj { form:'prop', want:type('prop'), seld:type('pRpt'), from:obj { form:'prop', want:type('prop'), seld:type('pPly'), from:obj { form:'indx', want:type('cBrW'), seld:1, from:'null'() } } }", m00f], 'core', 'setd', &savedPSN);
+ } else {
+ ITSendAEWithString([NSString stringWithFormat:@"data:'%s', '----':obj { form:'prop', want:type('prop'), seld:type('pRpt'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:() } }", m00f], 'core', 'setd', &savedPSN);
+ }
ITDebugLog(@"Setting repeat mode to %c done", m00f);
return YES;
}
//Duplicate search results to playlist
for (i = 1; i <= [searchResults numberOfItems]; i++) {
//NSLog(@"%@", ITSendAEWithStringAndParameter(@"'----':obj { form:'prop', want:type('prop'), seld:prop('pnam'), from:aevt(@) }", *[[searchResults descriptorAtIndex:i] aeDesc], 'core', 'getd', &savedPSN));
+
ITSendAEWithStringAndObject(@"insh:obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() }", [[searchResults descriptorAtIndex:i] aeDesc], 'core', 'clon', &savedPSN);
}
//Reset fixed indexing
- (BOOL)isPlaying
{
- return ([ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pcls'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value] != 'prop');
+ return ([ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pcls'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) typeCodeValue] != 'prop');
}
- (void)notificationHandler:(NSNotification *)note