Fixed problems with iTunes 5 and repeat mode. Made it so shuffle and loop work on...
[MenuTunes.git] / iTunesRemote.m
1 #import "iTunesRemote.h"
2
3 @implementation iTunesRemote
4
5 + (id)remote
6 {
7     return [[[iTunesRemote alloc] init] autorelease];
8 }
9
10 - (NSString *)remoteTitle
11 {
12     return @"iTunes Remote";
13 }
14
15 - (NSString *)remoteInformation
16 {
17     return @"Default MenuTunes plugin to control iTunes, by iThink Software.";
18 }
19
20 - (NSImage *)remoteIcon
21 {
22     return nil;
23 }
24
25 - (BOOL)begin
26 {
27     ITDebugLog(@"iTunesRemote begun");
28     savedPSN = [self iTunesPSN];
29         [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationHandler:) name:@"com.apple.iTunes.playerInfo" object:@"com.apple.iTunes.player"];
30     return YES;
31 }
32
33 - (BOOL)halt
34 {
35     ITDebugLog(@"iTunesRemote halted");
36         [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
37     return YES;
38 }
39
40 - (NSString *)playerFullName
41 {
42     return @"iTunes";
43 }
44
45 - (NSString *)playerSimpleName
46 {
47     return @"iTunes";
48 }
49
50 - (NSDictionary *)capabilities
51 {
52     return [NSDictionary dictionaryWithObjectsAndKeys:
53                 [NSNumber numberWithBool: YES], @"Remote",
54                 [NSNumber numberWithBool: YES], @"Basic Track Control",
55                 [NSNumber numberWithBool: YES], @"Track Information",
56                 [NSNumber numberWithBool: YES], @"Track Navigation",
57                 [NSNumber numberWithBool: YES], @"Upcoming Songs",
58                 [NSNumber numberWithBool: YES], @"Playlists",
59                 [NSNumber numberWithBool: YES], @"Volume",
60                 [NSNumber numberWithBool: YES], @"Shuffle",
61                 [NSNumber numberWithBool: YES], @"Repeat Modes",
62                 [NSNumber numberWithBool: YES], @"Equalizer",
63                 [NSNumber numberWithBool: YES], @"Track Rating",
64                 nil];
65 }
66
67 - (BOOL)showPrimaryInterface
68 {
69     ITDebugLog(@"Showing player primary interface.");
70     
71     if ([self playerRunningState] == ITMTRemotePlayerRunning) {
72         ITDebugLog(@"Showing player interface.");
73         //If not minimized and visible
74         if ( ([ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pMin'), from:obj { form:'indx', want:type('cBrW'), seld:1, from:'null'() } }", 'core', 'getd', &savedPSN) booleanValue] == 0) &&
75                          ([ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pvis'), from:obj { form:'indx', want:type('cBrW'), seld:1, from:'null'() } }", 'core', 'getd', &savedPSN) booleanValue] != 0) &&
76              [[[[NSWorkspace sharedWorkspace] activeApplication] objectForKey:@"NSApplicationName"] isEqualToString:@"iTunes"] ) {
77             //set minimized of browser window 1 to true
78                         ITSendAEWithString(@"data:long(1), '----':obj { form:'prop', want:type('prop'), seld:type('pMin'), from:obj { form:'indx', want:type('cBrW'), seld:long(1), from:'null'() } }", 'core', 'setd', &savedPSN);
79         } else {
80             //set minimized of browser window 1 to false
81                         ITSendAEWithString(@"data:long(0), '----':obj { form:'prop', want:type('prop'), seld:type('pMin'), from:obj { form:'indx', want:type('cBrW'), seld:long(1), from:'null'() } }", 'core', 'setd', &savedPSN);
82         }
83         //set visible of browser window 1 to true
84                 ITSendAEWithString(@"data:long(1), '----':obj { form:'prop', want:type('prop'), seld:type('pvis'), from:obj { form:'indx', want:type('cBrW'), seld:long(1), from:'null'() } }", 'core', 'setd', &savedPSN);
85         //active iTunes
86                 ITSendAEWithString(@"data:long(1), '----':obj { form:'prop', want:type('prop'), seld:type('pisf'), from:'null'() }", 'core', 'setd', &savedPSN);
87         ITDebugLog(@"Done showing player primary interface.");
88         return YES;
89     } else {
90         NSString *path;
91         ITDebugLog(@"Launching player.");
92         if ( (path = [[NSUserDefaults standardUserDefaults] stringForKey:@"CustomPlayerPath"]) ) {
93         } else {
94             path = [self playerFullName];
95         }
96         if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
97             ITDebugLog(@"Error Launching Player");
98             return NO;
99         }
100         return YES;
101     }
102 }
103
104 - (ITMTRemotePlayerRunningState)playerRunningState
105 {
106     NSArray *apps = [[NSWorkspace sharedWorkspace] launchedApplications];
107     int i;
108     int count = [apps count];
109     
110     for (i = 0; i < count; i++) {
111         if ([[[apps objectAtIndex:i] objectForKey:@"NSApplicationName"] isEqualToString:@"iTunes"]) {
112             ITDebugLog(@"Player running state: 1");
113             return ITMTRemotePlayerRunning;
114         }
115     }
116     ITDebugLog(@"Player running state: 0");
117     return ITMTRemotePlayerNotRunning;
118 }
119
120 - (ITMTRemotePlayerPlayingState)playerPlayingState
121 {
122     SInt32 result;
123     
124     ITDebugLog(@"Getting player playing state");
125     result = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pPlS'), from:'null'() }", 'core', 'getd', &savedPSN) typeCodeValue];
126     switch (result)
127     {
128         case 'kPSP':
129             ITDebugLog(@"Getting player playing state done. Player state: Playing");
130             return ITMTRemotePlayerPlaying;
131         case 'kPSp':
132             ITDebugLog(@"Getting player playing state done. Player state: Paused");
133             return ITMTRemotePlayerPaused;
134         case 'kPSR':
135             ITDebugLog(@"Getting player playing state done. Player state: Rewinding");
136             return ITMTRemotePlayerRewinding;
137         case 'kPSF':
138             ITDebugLog(@"Getting player playing state done. Player state: Forwarding");
139             return ITMTRemotePlayerForwarding;
140         case 'kPSS':
141         default:
142             ITDebugLog(@"Getting player playing state done. Player state: Stopped");
143             return ITMTRemotePlayerStopped;
144     }
145     ITDebugLog(@"Getting player playing state done. Player state: Stopped");
146     return ITMTRemotePlayerStopped;
147 }
148
149 /*- (NSArray *)playlists
150 {
151     long i = 0;
152     const signed long numPlaylists = [[ITAppleEventCenter sharedCenter] sendAEWithSendStringForNumber:@"kocl:type('cPly'), '----':()" eventClass:@"core" eventID:@"cnte" appPSN:savedPSN];
153     NSMutableArray *playlists = [[NSMutableArray alloc] initWithCapacity:numPlaylists];
154     
155     for (i = 1; i <= numPlaylists; i++) {
156         const long j = i;
157         NSString *sendStr = [NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pnam'), from:obj { form:'indx', want:type('cPly'), seld:long(%lu), from:'null'() } }",(unsigned long)j];
158         NSString *theObj = [[ITAppleEventCenter sharedCenter] sendAEWithSendString:sendStr eventClass:@"core" eventID:@"getd" appPSN:savedPSN];
159         [playlists addObject:theObj];
160     }
161     return [playlists autorelease];
162 }*/
163
164 //Full source awareness
165 - (NSArray *)playlists
166 {
167     unsigned long i, k;
168     SInt32 numSources = [ITSendAEWithString(@"kocl:type('cSrc'), '----':()", 'core', 'cnte', &savedPSN) int32Value];
169     NSMutableArray *allSources = [[NSMutableArray alloc] init];
170     
171     ITDebugLog(@"Getting playlists.");
172     if (numSources == 0) {
173                 [allSources release];
174         ITDebugLog(@"No sources.");
175         return nil;
176     }
177     
178     for (k = 1; k <= numSources ; k++) {
179         SInt32 numPlaylists = [ITSendAEWithString([NSString stringWithFormat:@"kocl:type('cPly'), '----':obj { form:'indx', want:type('cSrc'), seld:long(%u), from:() }",k], 'core', 'cnte', &savedPSN) int32Value];
180         SInt32 fourcc = [ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pKnd'), from:obj { form:'indx', want:type('cSrc'), seld:long(%u), from:() } }",k], 'core', 'getd', &savedPSN) int32Value];
181         NSString *sourceName = [ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pnam'), from:obj { form:'indx', want:type('cSrc'), seld:long(%u), from:() } }",k], 'core', 'getd', &savedPSN) stringValue];
182         SInt32 index = [ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pidx'), from:obj { form:'indx', want:type('cSrc'), seld:long(%u), from:() } }",k], 'core', 'getd', &savedPSN) int32Value];
183         unsigned long class;
184         if (sourceName) {
185             NSMutableArray *aSource = [[NSMutableArray alloc] init];
186             [aSource addObject:sourceName];
187             switch (fourcc) {
188                 case 'kTun':
189                     class = ITMTRemoteRadioSource;
190                     break;
191                 case 'kDev':
192                     class = ITMTRemoteGenericDeviceSource;
193                     break;
194                 case 'kPod':
195                     class = ITMTRemoteiPodSource;
196                     break;
197                 case 'kMCD':
198                 case 'kACD':
199                     class = ITMTRemoteCDSource;
200                     break;
201                 case 'kShd':
202                     class = ITMTRemoteSharedLibrarySource;
203                     break;
204                 case 'kUnk':
205                 case 'kLib':
206                 default:
207                     class = ITMTRemoteLibrarySource;
208                     break;
209             }
210             ITDebugLog(@"Adding source %@ of type %i at index %i", sourceName, class, index);
211             [aSource addObject:[NSNumber numberWithInt:class]];
212             [aSource addObject:[NSNumber numberWithInt:index]];
213             for (i = 1; i <= numPlaylists; i++) {
214                 NSString *sendStr = [NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pnam'), from:obj { form:'indx', want:type('cPly'), seld:long(%u), from:obj { form:'indx', want:type('cSrc'), seld:long(%u), from:() } } }",i,k];
215                 NSString *theObj = [ITSendAEWithString(sendStr, 'core', 'getd', &savedPSN) stringValue];
216                 ITDebugLog(@" - Adding playlist %@", theObj);
217                 if (theObj) {
218                     [aSource addObject:theObj];
219                 }
220             }
221             [allSources addObject:[aSource autorelease]];
222         } else {
223             ITDebugLog(@"Source at index %i disappeared.", k);
224         }
225     }
226     ITDebugLog(@"Finished getting playlists.");
227     return [allSources autorelease];
228 }
229
230 - (NSArray *)artists
231 {
232     NSAppleEventDescriptor *rawr = ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pArt'), from:obj { form:'indx', want:type('cTrk'), seld:abso($616C6C20$), from:obj { form:'indx', want:type('cPly'), seld:long(1), from:obj { form:'indx', want:type('cSrc'), seld:long(1), from:() } } } }", 'core', 'getd', &savedPSN);
233     int i;
234     NSMutableArray *array = [[NSMutableArray alloc] init];
235     NSArray *returnArray;
236     for (i = 1; i <= [rawr numberOfItems]; i++) {
237         NSString *artist = [[rawr descriptorAtIndex:i] stringValue];
238         if (artist && [artist length] && ![array containsObject:artist]) {
239             [array addObject:artist];
240         }
241     }
242     [array sortUsingSelector:@selector(caseInsensitiveCompare:)];
243     returnArray = [NSArray arrayWithArray:array];
244     [array release];
245     return returnArray;
246 }
247
248 - (NSArray *)albums
249 {
250     NSAppleEventDescriptor *rawr = ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pAlb'), from:obj { form:'indx', want:type('cTrk'), seld:abso($616C6C20$), from:obj { form:'indx', want:type('cPly'), seld:long(1), from:obj { form:'indx', want:type('cSrc'), seld:long(1), from:() } } } }", 'core', 'getd', &savedPSN);
251     int i;
252     NSMutableArray *array = [[NSMutableArray alloc] init];
253     NSArray *returnArray;
254     for (i = 1; i <= [rawr numberOfItems]; i++) {
255         NSString *album = [[rawr descriptorAtIndex:i] stringValue];
256         if (album && [album length] && ![array containsObject:album]) {
257             [array addObject:album];
258         }
259     }
260     [array sortUsingSelector:@selector(caseInsensitiveCompare:)];
261     returnArray = [NSArray arrayWithArray:array];
262     [array release];
263     return returnArray;
264 }
265
266 - (int)numberOfSongsInPlaylistAtIndex:(int)index
267 {
268         /*
269                 This method only returns the proper number if there's something playing.
270                 This is because it gets the container of the current playlist so that it
271                 gets the playlist index from the current source. Operating this way is fine,
272                 since MT only ever calls this method when there is something playlist.
273                 A working version of this that works in just the main source is in the
274                 makePlaylistWithTerm:ofType: method.
275         */
276     int temp1;
277         NSAppleEventDescriptor *result;
278     ITDebugLog(@"Getting number of songs in playlist at index %i", index);
279         result = ITSendAEWithString([NSString stringWithFormat:@"kocl:type('cTrk'), '----':obj { form:'indx', want:type('cPly'), seld:long(%lu), from:obj { form:'prop', want:type('prop'), seld:type('ctnr'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } } }", index], 'core', 'cnte', &savedPSN);
280         temp1 = (result == nil) ? -1 : (int)[result int32Value];
281     ITDebugLog(@"Getting number of songs in playlist at index %i done", index);
282     return temp1;
283 }
284
285 - (ITMTRemotePlayerSource)currentSource
286 {
287     SInt32 fourcc;
288
289     ITDebugLog(@"Getting current source.");   
290     
291     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';
292     
293     switch (fourcc) {
294         case 'kTun':
295             ITDebugLog(@"Getting current source done. Source: Radio.");
296             return ITMTRemoteRadioSource;
297             break;
298         case 'kDev':
299             ITDebugLog(@"Getting current source done. Source: Generic Device.");
300             return ITMTRemoteGenericDeviceSource;
301         case 'kPod':
302             ITDebugLog(@"Getting current source done. Source: iPod.");
303             return ITMTRemoteiPodSource; //this is stupid
304             break;
305         case 'kMCD':
306         case 'kACD':
307             ITDebugLog(@"Getting current source done. Source: CD.");
308             return ITMTRemoteCDSource;
309             break;
310         case 'kShd':
311             ITDebugLog(@"Getting current source done. Source: Shared Library.");
312             return ITMTRemoteSharedLibrarySource;
313             break;
314         case 'kUnk':
315         case 'kLib':
316         default:
317             ITDebugLog(@"Getting current source done. Source: Library.");
318             return ITMTRemoteLibrarySource;
319             break;
320     }
321 }
322
323 - (int)currentSourceIndex
324 {
325     ITDebugLog(@"Getting current source.");
326     return [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pidx'), 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];
327 }
328
329 - (ITMTRemotePlayerPlaylistClass)currentPlaylistClass
330 {
331     SInt32 realResult;
332     ITDebugLog(@"Getting current playlist class");
333     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];
334     switch (realResult)
335            {
336            case 'cLiP':
337                ITDebugLog(@"Getting current playlist class done. Class: Library.");
338                return ITMTRemotePlayerLibraryPlaylist;
339                break;
340            case 'cRTP':
341                ITDebugLog(@"Getting current playlist class done. Class: Radio.");
342                return ITMTRemotePlayerRadioPlaylist;
343                break;
344            default:
345                ITDebugLog(@"Getting current playlist class done. Class: Standard playlist.");
346                return ITMTRemotePlayerPlaylist;
347            }
348 }
349
350 - (int)currentPlaylistIndex
351 {  
352     int temp1;
353     ITDebugLog(@"Getting current playlist index.");
354     temp1 = ([self isPlaying] ? [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pidx'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value] : -1);
355     ITDebugLog(@"Getting current playlist index done.");
356     return temp1;
357 }
358
359 - (NSString *)songTitleAtIndex:(int)index
360 {
361     NSString *temp1;
362     ITDebugLog(@"Getting song title at index %i.", index);
363     temp1 = [ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pnam'), from:obj { form:'indx', want:type('cTrk'), seld:long(%lu), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } } }",index], 'core', 'getd', &savedPSN) stringValue];
364     ITDebugLog(@"Getting song title at index %i done.", index);
365     return ( ([temp1 length]) ? temp1 : nil ) ;
366 }
367
368 - (int)currentAlbumTrackCount
369 {
370     int temp1;
371     ITDebugLog(@"Getting current album track count.");
372     temp1 = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pTrC'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
373     if ( [self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist ) { temp1 = 0; }
374     ITDebugLog(@"Getting current album track count done.");
375     return temp1;
376 }
377
378 - (int)currentSongTrack
379 {
380     int temp1;
381     ITDebugLog(@"Getting current song track.");
382     temp1 = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pTrN'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
383     if ( [self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist ) { temp1 = 0; }
384     ITDebugLog(@"Getting current song track done.");
385     return temp1;
386 }
387
388 - (NSString *)playerStateUniqueIdentifier
389 {
390     NSString *temp1;
391     ITDebugLog(@"Getting current unique identifier.");
392         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);
393         if ([descriptor int32Value] == 'prop') {
394                 return @"0-0";
395         } else if (descriptor == nil) {
396                 return nil;
397         }
398     SInt32 cls = [descriptor int32Value];
399     if ( ([self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist) || (cls == 'cURT') ) {
400                 NSString *bad = [NSString stringWithUTF8String:"浳湧"];
401         temp1 = [ITSendAEWithKey('pStT', 'core', 'getd', &savedPSN) stringValue];
402         if ([temp1 isEqualToString:bad]) {
403             temp1 = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pnam'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) stringValue];
404         }
405     } else {
406         temp1 = [NSString stringWithFormat:@"%i-%i", [self currentPlaylistIndex], [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pDID'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value]];
407     }
408     ITDebugLog(@"Getting current unique identifier done.");
409     return ( ([temp1 length]) ? temp1 : nil ) ;
410 }
411
412 - (int)currentSongIndex
413 {
414     int temp1;
415     ITDebugLog(@"Getting current song index.");
416         temp1 = ([self isPlaying] ? [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pidx'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value] : -1);
417     ITDebugLog(@"Getting current song index done.");
418     return temp1;
419 }
420
421 - (NSString *)currentSongTitle
422 {
423     NSString *temp1;
424     ITDebugLog(@"Getting current song title.");
425     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];
426         
427     //If we're listening to the radio.
428     if (result == 'cURT') {
429         NSString *bad = [NSString stringWithUTF8String:"浳湧"];
430         temp1 = [ITSendAEWithKey('pStT', 'core', 'getd', &savedPSN) stringValue];
431         if ([temp1 isEqualToString:bad]) {
432             temp1 = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pnam'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) stringValue];
433         }
434         temp1 = [temp1 stringByAppendingString:@" (Stream)"];
435     } else if (result == 'prop') {
436                 temp1 = nil;
437         } else {
438         temp1 = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pnam'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) stringValue];
439     }
440     ITDebugLog(@"Getting current song title done.");
441     return ( ([temp1 length]) ? temp1 : nil ) ;
442 }
443
444 - (NSString *)currentSongArtist
445 {
446     NSString *temp1;
447     ITDebugLog(@"Getting current song artist.");
448     if ( [self currentPlaylistClass] != ITMTRemotePlayerRadioPlaylist ) {
449         temp1 = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pArt'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) stringValue];
450     } else {
451         temp1 = @"";
452     }
453     ITDebugLog(@"Getting current song artist done.");
454     return ( ([temp1 length]) ? temp1 : nil ) ;
455 }
456
457 - (NSString *)currentSongComposer
458 {
459     NSString *temp1;
460     ITDebugLog(@"Getting current song artist.");
461     if ( [self currentPlaylistClass] != ITMTRemotePlayerRadioPlaylist ) {
462         temp1 = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pCmp'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) stringValue];
463     } else {
464         temp1 = @"";
465     }
466     ITDebugLog(@"Getting current song artist done.");
467     return ( ([temp1 length]) ? temp1 : nil ) ;
468 }
469
470 - (NSString *)currentSongAlbum
471 {
472     NSString *temp1;
473     ITDebugLog(@"Getting current song album.");
474     if ( [self currentPlaylistClass] != ITMTRemotePlayerRadioPlaylist ) {
475         temp1 = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pAlb'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) stringValue];
476     } else {
477         temp1 = @"";
478     }
479     ITDebugLog(@"Getting current song album done.");
480     return ( ([temp1 length]) ? temp1 : nil ) ;
481 }
482
483 - (NSString *)currentSongGenre
484 {
485     NSString *temp1;
486     ITDebugLog(@"Getting current song genre.");
487     temp1 = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pGen'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) stringValue];
488     ITDebugLog(@"Getting current song genre done.");
489     return ( ([temp1 length]) ? temp1 : nil ) ;
490 }
491
492 - (NSString *)currentSongLength
493 {
494     SInt32 temp1;
495     NSString *temp2;
496     ITDebugLog(@"Getting current song length.");
497     temp1 = [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];
498     temp2 = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pTim'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) stringValue];
499     if ( ([self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist) || (temp1 == 'cURT') ) { temp2 = @"Continuous"; }
500     ITDebugLog(@"Getting current song length done.");
501     return temp2;
502 }
503
504 - (NSString *)currentSongRemaining
505 {
506     SInt32 duration, current, final;
507     NSString *finalString;
508     
509     ITDebugLog(@"Getting current song remaining time.");
510     
511     duration = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pDur'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
512     current = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pPos'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
513     final = duration - current;
514     finalString = [self formatTimeInSeconds:final];
515     
516     if ( [self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist ) { finalString = nil; }
517     
518     ITDebugLog(@"Getting current song remaining time done.");
519     
520     return finalString;
521 }
522
523 - (NSString *)currentSongElapsed
524 {
525     long final;
526     NSString *finalString;
527     
528     ITDebugLog(@"Getting current song elapsed time.");
529         final = (long)[ITSendAEWithKey('pPos', 'core', 'getd', &savedPSN) int32Value];
530     finalString = [self formatTimeInSeconds:final];
531     ITDebugLog(@"Getting current song elapsed time done.");
532     return finalString;
533 }
534
535 - (NSImage *)currentSongAlbumArt
536 {
537     ITDebugLog(@"Getting current song album art.");
538     NSData *data = ([self isPlaying]) ? [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pPCT'), from:obj { form:'indx', want:type('cArt'), seld:long(1), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } } }", 'core', 'getd', &savedPSN) data] : nil;
539     ITDebugLog(@"Getting current song album art done.");    
540     if (data) {
541         return [[[NSImage alloc] initWithData:data] autorelease];
542     } else {
543         return nil;
544     }
545 }
546
547 - (int)currentSongPlayCount
548 {
549     int count;
550     ITDebugLog(@"Getting current song play count.");
551     count = (int)[ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pPlC'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
552     ITDebugLog(@"Getting current song play count done.");
553     return count;
554 }
555
556 - (float)currentSongRating
557 {
558     float temp1;
559     ITDebugLog(@"Getting current song rating.");
560     temp1 = (![self isPlaying] || ([self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist)) ? -1.0 : ((float)[ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pRte'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value] / 100.0);
561     ITDebugLog(@"Getting current song rating done.");
562     return temp1;
563 }
564
565 - (BOOL)setCurrentSongRating:(float)rating
566 {
567     ITDebugLog(@"Setting current song rating to %f.", rating);
568     if ( [self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist ) { return NO; }
569         ITSendAEWithString([NSString stringWithFormat:@"data:long(%lu), '----':obj { form:'prop', want:type('prop'), seld:type('pRte'), from:obj { form:'indx', want:type('cTrk'), seld:long(%lu), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } } }",(long)(rating*100), [self currentSongIndex]], 'core', 'setd', &savedPSN);
570     ITDebugLog(@"Setting current song rating to %f done.", rating);
571     return YES;
572 }
573
574 - (BOOL)equalizerEnabled
575 {
576     ITDebugLog(@"Getting equalizer enabled status.");
577     int thingy = (int)[ITSendAEWithKey('pEQ ', 'core', 'getd', &savedPSN) int32Value];
578     ITDebugLog(@"Done getting equalizer enabled status.");
579     return (thingy != 0) ? YES : NO;
580 }
581
582 - (BOOL)setEqualizerEnabled:(BOOL)enabled
583 {
584     ITDebugLog(@"Setting equalizer enabled to %i.", enabled);
585         ITSendAEWithString([NSString stringWithFormat:@"data:long(%lu), '----':obj { form:'prop', want:type('prop'), seld:type('pEQ '), from:'null'() }", enabled], 'core', 'setd', &savedPSN);
586     ITDebugLog(@"Done setting equalizer enabled to %i.", enabled);
587     return YES;
588 }
589
590 - (NSArray *)eqPresets
591 {
592     int i;
593     SInt32 numPresets = [ITSendAEWithString(@"kocl:type('cEQP'), '----':(), &subj:()", 'core', 'cnte', &savedPSN) int32Value];
594     NSMutableArray *presets = [[NSMutableArray alloc] initWithCapacity:numPresets];
595     ITDebugLog(@"Getting EQ presets");
596     for (i = 1; i <= numPresets; i++) {
597         NSString *theObj = [ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pnam'), from:obj { form:'indx', want:type('cEQP'), seld:long(%lu), from:'null'() } }", i], 'core', 'getd', &savedPSN) stringValue];
598         if (theObj) {
599             ITDebugLog(@"Adding preset %@", theObj);
600             [presets addObject:theObj];
601         }
602     }
603     ITDebugLog(@"Done getting EQ presets");
604     return [presets autorelease];
605 }
606
607 - (int)currentEQPresetIndex
608 {
609     int result;
610     ITDebugLog(@"Getting current EQ preset index.");
611     result = (int)[ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pidx'), from:obj { form:'prop', want:type('prop'), seld:type('pEQP'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
612     ITDebugLog(@"Getting current EQ preset index done.");
613     return result;
614 }
615
616 - (float)volume
617 {
618     ITDebugLog(@"Getting volume.");
619     ITDebugLog(@"Getting volume done.");
620     return (float)[ITSendAEWithKey('pVol', 'core', 'getd', &savedPSN) int32Value] / 100;
621 }
622
623 - (BOOL)setVolume:(float)volume
624 {
625     ITDebugLog(@"Setting volume to %f.", volume);
626         ITSendAEWithString([NSString stringWithFormat:@"data:long(%lu), '----':obj { form:'prop', want:type('prop'), seld:type('pVol'), from:'null'() }", (long)(volume * 100)], 'core', 'setd', &savedPSN);
627     ITDebugLog(@"Setting volume to %f done.", volume);
628     return YES;
629 }
630
631 - (BOOL)shuffleEnabled
632 {
633         int result;
634     ITDebugLog(@"Getting shuffle enabled status.");
635         if ([[self playerStateUniqueIdentifier] isEqualToString:@"0-0"]) {
636                 ITDebugLog(@"No current playlist, getting shuffle status from visible playlist.");
637                 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];
638         } else {
639                 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];
640         }
641     ITDebugLog(@"Getting shuffle enabled status done.");
642     return (result != 0);
643 }
644
645 - (BOOL)setShuffleEnabled:(BOOL)enabled
646 {
647     ITDebugLog(@"Set shuffle enabled to %i", enabled);
648         if ([[self playerStateUniqueIdentifier] isEqualToString:@"0-0"]) {
649                 ITDebugLog(@"No current playlist, setting shuffle status on visible playlist.");
650                 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);
651         } else {
652                 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);
653         }
654     ITDebugLog(@"Set shuffle enabled to %i done", enabled);
655     return YES;
656 }
657
658 - (ITMTRemotePlayerRepeatMode)repeatMode
659 {
660     FourCharCode m00f = 0;
661     int result = 0;
662         ITDebugLog(@"Getting repeat mode.");
663     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];
664         
665         if (m00f == 0) {
666                 ITDebugLog(@"No current playlist, getting repeat mode from visible playlist.");
667                 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];
668         }
669         
670     switch (m00f)
671     {
672         //case 'kRp0':
673         case 1800564815:
674             ITDebugLog(@"Repeat off");
675             result = ITMTRemotePlayerRepeatOff;
676             break;
677         case 'kRp1':
678             ITDebugLog(@"Repeat one");
679             result = ITMTRemotePlayerRepeatOne;
680             break;
681         case 'kRpA':
682             ITDebugLog(@"Repeat all");
683             result = ITMTRemotePlayerRepeatAll;
684             break;
685     }
686     ITDebugLog(@"Getting repeat mode done.");
687     return result;
688 }
689
690 - (BOOL)setRepeatMode:(ITMTRemotePlayerRepeatMode)repeatMode
691 {
692     char *m00f;
693     ITDebugLog(@"Setting repeat mode to %i", repeatMode);
694     switch (repeatMode)
695     {
696         case ITMTRemotePlayerRepeatOne:
697             m00f = "kRp1";
698             break;
699         case ITMTRemotePlayerRepeatAll:
700             m00f = "kRpA";
701             break;
702         case ITMTRemotePlayerRepeatOff:
703         default:
704             m00f = "kRp0";
705             break;
706     }
707         if ([[self playerStateUniqueIdentifier] isEqualToString:@"0-0"]) {
708                 ITDebugLog(@"No current playlist, setting repeat mode on visible playlist.");
709                 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);
710         } else {
711                 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);
712         }
713     ITDebugLog(@"Setting repeat mode to %c done", m00f);
714     return YES;
715 }
716
717 - (BOOL)play
718 {
719     ITDebugLog(@"Play");
720         ITSendAE('hook', 'Play', &savedPSN);
721     ITDebugLog(@"Play done");
722     return YES;
723 }
724
725 - (BOOL)pause
726 {
727     ITDebugLog(@"Pause");
728     ITSendAE('hook', 'Paus', &savedPSN);
729     ITDebugLog(@"Pause done");
730     return YES;
731 }
732
733 - (BOOL)goToNextSong
734 {
735     ITDebugLog(@"Go to next track");
736     ITSendAE('hook', 'Next', &savedPSN);
737     ITDebugLog(@"Go to next track done");
738     return YES;
739 }
740
741 - (BOOL)goToPreviousSong
742 {
743     ITDebugLog(@"Go to previous track");
744     ITSendAE('hook', 'Back', &savedPSN);
745     ITDebugLog(@"Go to previous track done");
746     return YES;
747 }
748
749 - (BOOL)forward
750 {
751     ITDebugLog(@"Fast forward action");
752     ITSendAE('hook', 'Fast', &savedPSN);
753     ITDebugLog(@"Fast forward action done");
754     return YES;
755 }
756
757 - (BOOL)rewind
758 {
759     ITDebugLog(@"Rewind action");
760     ITSendAE('hook', 'Rwnd', &savedPSN);
761     ITDebugLog(@"Rewind action done");
762     return YES;
763 }
764
765 - (BOOL)switchToPlaylistAtIndex:(int)index
766 {
767     ITDebugLog(@"Switching to playlist at index %i", index);
768         ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'indx', want:type('cPly'), seld:long(%lu), from:() }", index], 'hook', 'Play', &savedPSN);
769     ITDebugLog(@"Done switching to playlist at index %i", index);
770     return YES;
771 }
772
773 - (BOOL)switchToPlaylistAtIndex:(int)index ofSourceAtIndex:(int)index2
774 {
775     ITDebugLog(@"Switching to playlist at index %i of source %i", index, index2);
776         ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'indx', want:type('cPly'), seld:long(%lu), from: obj { form:'indx', want:type('cSrc'), seld:long(%lu), from:'null'() } }", index - 1, index2 + 1], 'hook', 'Play', &savedPSN);
777     ITDebugLog(@"Done switching to playlist at index %i of source %i", index, index2);
778     return YES;
779 }
780
781 - (BOOL)switchToSongAtIndex:(int)index
782 {
783     ITDebugLog(@"Switching to track at index %i", index);
784         ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'indx', want:type('cTrk'), seld:long(%lu), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:() } }", index], 'hook' ,'Play', &savedPSN);
785     ITDebugLog(@"Done switching to track at index %i", index);
786     return YES;
787 }
788
789 - (BOOL)switchToEQAtIndex:(int)index
790 {
791     ITDebugLog(@"Switching to EQ preset at index %i", index);
792     // index should count from 0, but itunes counts from 1, so let's add 1.
793     [self setEqualizerEnabled:YES];
794         ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pEQP'), from:'null'() }, data:obj { form:'indx', want:type('cEQP'), seld:long(%lu), from:'null'() }", (index+1)], 'core', 'setd', &savedPSN);
795     ITDebugLog(@"Done switching to EQ preset at index %i", index);
796     return YES;
797 }
798
799 - (BOOL)makePlaylistWithTerm:(NSString *)term ofType:(int)type
800 {
801     int i;
802         
803     //Get fixed indexing status
804     BOOL fixed = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pFix'), from:'null'() }", 'core', 'getd', &savedPSN) booleanValue];
805     
806     //Enabled fixed indexing
807     ITSendAEWithString(@"data:long(1), '----':obj { form:'prop', want:type('prop'), seld:type('pFix'), from:'null'() }", 'core', 'setd', &savedPSN);
808     
809     //Search for the term
810     NSAppleEventDescriptor *searchResults = ITSendAEWithString([NSString stringWithFormat:@"pTrm:\"%@\", pAre:'%@', '----':obj { form:'indx', want:type('cPly'), seld:long(1), from:obj { form:'indx', want:type('cSrc'), seld:long(1), from:'null'() } }", term, ((type == 1) ? @"kSrR" : @"kSrL")], 'hook', 'Srch', &savedPSN);
811     
812     //If MenuTunes playlist exists
813     if ([ITSendAEWithString(@"'----':obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() }", 'core', 'doex', &savedPSN) booleanValue]) {
814         //Clear old MenuTunes playlist
815                 int numSongs = [ITSendAEWithString(@"kocl:type('cTrk'), '----':obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() }", 'core', 'cnte', &savedPSN) int32Value];
816         for (i = 1; i <= numSongs; i++) {
817             ITSendAEWithString(@"'----':obj { form:'indx', want:type('cTrk'), seld:long(1), from:obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() } }", 'core', 'delo', &savedPSN);
818         }
819     } else {
820         //Create MenuTunes playlist
821         ITSendAEWithString(@"prdt:{ pnam:\"MenuTunes\" }, kocl:type('cPly'), &subj:()", 'core', 'crel', &savedPSN);
822     }
823     
824     //Duplicate search results to playlist
825     for (i = 1; i <= [searchResults numberOfItems]; i++) {
826                 //NSLog(@"%@", ITSendAEWithStringAndParameter(@"'----':obj { form:'prop', want:type('prop'), seld:prop('pnam'), from:aevt(@) }", *[[searchResults descriptorAtIndex:i] aeDesc], 'core', 'getd', &savedPSN));
827                 
828         ITSendAEWithStringAndObject(@"insh:obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() }", [[searchResults descriptorAtIndex:i] aeDesc], 'core', 'clon', &savedPSN);
829     }
830     //Reset fixed indexing
831     ITSendAEWithString([NSString stringWithFormat:@"data:long(%i), '----':obj { form:'prop', want:type('prop'), seld:type('pFix'), from:'null'() }", fixed], 'core', 'setd', &savedPSN);
832     
833     //Play MenuTunes playlist
834     ITSendAEWithString(@"'----':obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() }", 'hook', 'Play', &savedPSN);
835     
836     return YES;
837 }
838
839 - (BOOL)isPlaying
840 {
841         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');
842 }
843
844 - (void)notificationHandler:(NSNotification *)note
845 {
846         ITDebugLog(@"Received notification: %@", note);
847         [[NSNotificationCenter defaultCenter] postNotificationName:@"ITMTTrackChanged" object:self userInfo:[note userInfo]];
848         ITDebugLog(@"Handled notification.");
849 }
850
851 - (ProcessSerialNumber)iTunesPSN
852 {
853     /*NSArray *apps = [[NSWorkspace sharedWorkspace] launchedApplications];
854     ProcessSerialNumber number;
855     int i;
856     int count = [apps count];
857     
858     number.highLongOfPSN = kNoProcess;
859     
860     for (i = 0; i < count; i++)
861     {
862         NSDictionary *curApp = [apps objectAtIndex:i];
863         
864         if ([[curApp objectForKey:@"NSApplicationName"] isEqualToString:@"iTunes"])
865         {
866             number.highLongOfPSN = [[curApp objectForKey:
867                 @"NSApplicationProcessSerialNumberHigh"] intValue];
868             number.lowLongOfPSN = [[curApp objectForKey:
869                 @"NSApplicationProcessSerialNumberLow"] intValue];
870         }
871     }
872     return number;*/
873     ProcessSerialNumber number;
874     number.highLongOfPSN = kNoProcess;
875     number.lowLongOfPSN = 0;
876     ITDebugLog(@"Getting iTunes' PSN.");
877     while ( (GetNextProcess(&number) == noErr) ) 
878     {
879         CFStringRef name;
880         if ( (CopyProcessName(&number, &name) == noErr) )
881         {
882             if ([(NSString *)name isEqualToString:@"iTunes"])
883             {
884                 ITDebugLog(@"iTunes' highLPongOfPSN: %lu.", number.highLongOfPSN);
885                 ITDebugLog(@"iTunes' lowLongOfPSN: %lu.", number.lowLongOfPSN);
886                 ITDebugLog(@"Done getting iTunes' PSN.");
887                 return number;
888             }
889             [(NSString *)name release];
890         }
891     }
892     ITDebugLog(@"Failed getting iTunes' PSN.");
893     return number;
894 }
895
896 - (NSString*)formatTimeInSeconds:(long)seconds {
897     long final = seconds;
898     NSString *finalString;
899     if (final >= 60) {
900         if (final > 3600) {
901             finalString = [NSString stringWithFormat:@"%i:%@:%@",(final / 3600),[self zeroSixty:(int)((final % 3600) / 60)],[self zeroSixty:(int)((final % 3600) % 60)]];
902         } else {
903             finalString = [NSString stringWithFormat:@"%i:%@",(final / 60),[self zeroSixty:(int)(final % 60)]];
904         }
905     } else {
906         finalString = [NSString stringWithFormat:@"0:%@",[self zeroSixty:(int)final]];
907     }
908     return finalString;
909 }
910 - (NSString*)zeroSixty:(int)seconds {
911     if ( (seconds < 10) && (seconds > 0) ) {
912         return [NSString stringWithFormat:@"0%i",seconds];
913     } else if ( (seconds == 0) ) {
914         return [NSString stringWithFormat:@"00"];
915     } else {
916         return [NSString stringWithFormat:@"%i",seconds];
917     }
918 }
919
920 @end