Added Last.fm support for those with the last.fm client.
Fixed up remote and scrobbler preferences.CQTexperiment
parent
629587d47d
commit
35b1c886a8
|
@ -25,15 +25,13 @@
|
||||||
// Listen to the remote in exclusive mode, only when Cog is the active application
|
// Listen to the remote in exclusive mode, only when Cog is the active application
|
||||||
- (void)applicationDidBecomeActive:(NSNotification *)notification
|
- (void)applicationDidBecomeActive:(NSNotification *)notification
|
||||||
{
|
{
|
||||||
BOOL onlyOnActive = [[[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"remoteOnlyOnActive"] boolValue];
|
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"remoteEnabled"] && [[NSUserDefaults standardUserDefaults] boolForKey:@"remoteOnlyOnActive"]) {
|
||||||
if (onlyOnActive) {
|
|
||||||
[remote startListening: self];
|
[remote startListening: self];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
- (void)applicationDidResignActive:(NSNotification *)motification
|
- (void)applicationDidResignActive:(NSNotification *)motification
|
||||||
{
|
{
|
||||||
BOOL onlyOnActive = [[[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"remoteOnlyOnActive"] boolValue];
|
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"remoteEnabled"] && [[NSUserDefaults standardUserDefaults] boolForKey:@"remoteOnlyOnActive"]) {
|
||||||
if (onlyOnActive) {
|
|
||||||
[remote stopListening: self];
|
[remote stopListening: self];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -204,7 +202,7 @@ increase/decrease as long as the user holds the left/right, plus/minus button */
|
||||||
[self registerHotKeys];
|
[self registerHotKeys];
|
||||||
|
|
||||||
//Init Remote
|
//Init Remote
|
||||||
if (![[[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"remoteOnlyOnActive"] boolValue]) {
|
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"remoteEnabled"] && ![[NSUserDefaults standardUserDefaults] boolForKey:@"remoteOnlyOnActive"]) {
|
||||||
[remote startListening:self];
|
[remote startListening:self];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,17 +358,30 @@ increase/decrease as long as the user holds the left/right, plus/minus button */
|
||||||
|
|
||||||
[userDefaultsValuesDict setObject:[@"~/Music" stringByExpandingTildeInPath] forKey:@"fileDrawerRootPath"];
|
[userDefaultsValuesDict setObject:[@"~/Music" stringByExpandingTildeInPath] forKey:@"fileDrawerRootPath"];
|
||||||
|
|
||||||
|
[userDefaultsValuesDict setObject:[NSNumber numberWithBool:YES] forKey:@"remoteEnabled"];
|
||||||
[userDefaultsValuesDict setObject:[NSNumber numberWithBool:YES] forKey:@"remoteOnlyOnActive"];
|
[userDefaultsValuesDict setObject:[NSNumber numberWithBool:YES] forKey:@"remoteOnlyOnActive"];
|
||||||
|
|
||||||
|
[userDefaultsValuesDict setObject:[NSNumber numberWithBool:YES] forKey:@"enableAudioScrobbler"];
|
||||||
|
[userDefaultsValuesDict setObject:[NSNumber numberWithBool:YES] forKey:@"automaticallyLaunchLastFM"];
|
||||||
|
|
||||||
//Register and sync defaults
|
//Register and sync defaults
|
||||||
[[NSUserDefaults standardUserDefaults] registerDefaults:userDefaultsValuesDict];
|
[[NSUserDefaults standardUserDefaults] registerDefaults:userDefaultsValuesDict];
|
||||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||||
|
|
||||||
|
//Add observers
|
||||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.hotKeyPlayKeyCode" options:0 context:nil];
|
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.hotKeyPlayKeyCode" options:0 context:nil];
|
||||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.hotKeyPreviousKeyCode" options:0 context:nil];
|
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.hotKeyPreviousKeyCode" options:0 context:nil];
|
||||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.hotKeyNextKeyCode" options:0 context:nil];
|
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.hotKeyNextKeyCode" options:0 context:nil];
|
||||||
|
|
||||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.fileDrawerRootPath" options:0 context:nil];
|
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.fileDrawerRootPath" options:0 context:nil];
|
||||||
|
|
||||||
|
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.fileDrawerRootPath" options:0 context:nil];
|
||||||
|
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.fileDrawerRootPath" options:0 context:nil];
|
||||||
|
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.fileDrawerRootPath" options:0 context:nil];
|
||||||
|
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.fileDrawerRootPath" options:0 context:nil];
|
||||||
|
|
||||||
|
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.remoteEnabled" options:0 context:nil];
|
||||||
|
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.remoteOnlyOnActive" options:0 context:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) observeValueForKeyPath:(NSString *)keyPath
|
- (void) observeValueForKeyPath:(NSString *)keyPath
|
||||||
|
@ -390,8 +401,10 @@ increase/decrease as long as the user holds the left/right, plus/minus button */
|
||||||
else if ([keyPath isEqualToString:@"values.fileDrawerRootPath"]) {
|
else if ([keyPath isEqualToString:@"values.fileDrawerRootPath"]) {
|
||||||
[fileTreeController setRootPath:[[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"fileDrawerRootPath"]];
|
[fileTreeController setRootPath:[[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"fileDrawerRootPath"]];
|
||||||
}
|
}
|
||||||
else if ([keyPath isEqualToString:@"values.remoteOnlyOnActive"]) {
|
else if ([keyPath isEqualToString:@"values.remoteEnabled"] || [keyPath isEqualToString:@"values.remoteOnlyOnActive"]) {
|
||||||
BOOL onlyOnActive = [[[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"remoteOnlyOnActive"] boolValue];
|
if([[NSUserDefaults standardUserDefaults] boolForKey:@"remoteEnabled"]) {
|
||||||
|
NSLog(@"Remote enabled...");
|
||||||
|
BOOL onlyOnActive = [[NSUserDefaults standardUserDefaults] boolForKey:@"remoteOnlyOnActive"];
|
||||||
if (!onlyOnActive || [NSApp isActive]) {
|
if (!onlyOnActive || [NSApp isActive]) {
|
||||||
[remote startListening: self];
|
[remote startListening: self];
|
||||||
}
|
}
|
||||||
|
@ -399,7 +412,11 @@ increase/decrease as long as the user holds the left/right, plus/minus button */
|
||||||
[remote stopListening: self];
|
[remote stopListening: self];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
NSLog(@"DISABLE REMOTE");
|
||||||
|
[remote stopListening: self];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)registerHotKeys
|
- (void)registerHotKeys
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#import "CogAudio/AudioPlayer.h"
|
#import "CogAudio/AudioPlayer.h"
|
||||||
#import "PlaylistController.h"
|
#import "PlaylistController.h"
|
||||||
#import "TrackingSlider.h"
|
#import "TrackingSlider.h"
|
||||||
|
#import "AudioScrobbler.h"
|
||||||
|
|
||||||
@class PlaylistView;
|
@class PlaylistView;
|
||||||
|
|
||||||
|
@ -33,6 +34,8 @@
|
||||||
double currentVolume;
|
double currentVolume;
|
||||||
|
|
||||||
BOOL showTimeRemaining;
|
BOOL showTimeRemaining;
|
||||||
|
|
||||||
|
AudioScrobbler *scrobbler;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)toggleShowTimeRemaining:(id)sender;
|
- (IBAction)toggleShowTimeRemaining:(id)sender;
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
playbackStatus = kCogStatusStopped;
|
playbackStatus = kCogStatusStopped;
|
||||||
|
|
||||||
showTimeRemaining = NO;
|
showTimeRemaining = NO;
|
||||||
|
|
||||||
|
scrobbler = [[AudioScrobbler alloc] init];
|
||||||
}
|
}
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
|
@ -50,12 +52,21 @@
|
||||||
{
|
{
|
||||||
// DBLog(@"Pause Sent!");
|
// DBLog(@"Pause Sent!");
|
||||||
[audioPlayer pause];
|
[audioPlayer pause];
|
||||||
|
|
||||||
|
if([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) {
|
||||||
|
[scrobbler pause];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)resume:(id)sender
|
- (IBAction)resume:(id)sender
|
||||||
{
|
{
|
||||||
// DBLog(@"Resume Sent!");
|
// DBLog(@"Resume Sent!");
|
||||||
[audioPlayer resume];
|
[audioPlayer resume];
|
||||||
|
|
||||||
|
|
||||||
|
if([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) {
|
||||||
|
[scrobbler resume];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)stop:(id)sender
|
- (IBAction)stop:(id)sender
|
||||||
|
@ -63,6 +74,10 @@
|
||||||
// DBLog(@"Stop Sent!");
|
// DBLog(@"Stop Sent!");
|
||||||
|
|
||||||
[audioPlayer stop];
|
[audioPlayer stop];
|
||||||
|
|
||||||
|
if([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) {
|
||||||
|
[scrobbler stop];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//called by double-clicking on table
|
//called by double-clicking on table
|
||||||
|
@ -81,7 +96,7 @@
|
||||||
[self playEntryAtIndex:[playlistView selectedRow]];
|
[self playEntryAtIndex:[playlistView selectedRow]];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)playEntry:(PlaylistEntry *)pe;
|
- (void)playEntry:(PlaylistEntry *)pe
|
||||||
{
|
{
|
||||||
// DBLog(@"PlayEntry: %@ Sent!", [pe filename]);
|
// DBLog(@"PlayEntry: %@ Sent!", [pe filename]);
|
||||||
if (playbackStatus != kCogStatusStopped)
|
if (playbackStatus != kCogStatusStopped)
|
||||||
|
@ -95,6 +110,10 @@
|
||||||
|
|
||||||
[audioPlayer play:[NSURL fileURLWithPath:[pe filename]] withUserInfo:pe];
|
[audioPlayer play:[NSURL fileURLWithPath:[pe filename]] withUserInfo:pe];
|
||||||
[audioPlayer setVolume:currentVolume];
|
[audioPlayer setVolume:currentVolume];
|
||||||
|
|
||||||
|
if([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) {
|
||||||
|
[scrobbler start:pe];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)next:(id)sender
|
- (IBAction)next:(id)sender
|
||||||
|
@ -264,6 +283,9 @@
|
||||||
|
|
||||||
[self updateTimeField:0.0f];
|
[self updateTimeField:0.0f];
|
||||||
|
|
||||||
|
if([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) {
|
||||||
|
[scrobbler start:pe];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)updatePosition:(id)sender
|
- (void)updatePosition:(id)sender
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* $Id: AudioScrobbler.h 238 2007-01-26 22:55:20Z stephen_booth $
|
||||||
|
*
|
||||||
|
* Copyright (C) 2006 - 2007 Stephen F. Booth <me@sbooth.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
#import <mach/mach.h>
|
||||||
|
|
||||||
|
@class PlaylistEntry;
|
||||||
|
|
||||||
|
@interface AudioScrobbler : NSObject
|
||||||
|
{
|
||||||
|
NSString *_pluginID;
|
||||||
|
NSMutableArray *_queue;
|
||||||
|
|
||||||
|
BOOL _audioScrobblerThreadCompleted;
|
||||||
|
BOOL _keepProcessingAudioScrobblerCommands;
|
||||||
|
semaphore_t _semaphore;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) start:(PlaylistEntry *)pe;
|
||||||
|
- (void) stop;
|
||||||
|
- (void) pause;
|
||||||
|
- (void) resume;
|
||||||
|
|
||||||
|
- (void) shutdown;
|
||||||
|
|
||||||
|
@end
|
|
@ -0,0 +1,257 @@
|
||||||
|
/*
|
||||||
|
* $Id: AudioScrobbler.m 238 2007-01-26 22:55:20Z stephen_booth $
|
||||||
|
*
|
||||||
|
* Copyright (C) 2006 - 2007 Stephen F. Booth <me@sbooth.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "AudioScrobbler.h"
|
||||||
|
|
||||||
|
#import "AudioScrobblerClient.h"
|
||||||
|
#import "PlaylistEntry.h"
|
||||||
|
|
||||||
|
static NSString *
|
||||||
|
escapeForLastFM(NSString *string)
|
||||||
|
{
|
||||||
|
NSMutableString *result = [string mutableCopy];
|
||||||
|
|
||||||
|
[result replaceOccurrencesOfString:@"&"
|
||||||
|
withString:@"&&"
|
||||||
|
options:NSLiteralSearch
|
||||||
|
range:NSMakeRange(0, [result length])];
|
||||||
|
|
||||||
|
return (nil == result ? @"" : [result autorelease]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@interface AudioScrobbler (Private)
|
||||||
|
|
||||||
|
- (NSMutableArray *) queue;
|
||||||
|
- (NSString *) pluginID;
|
||||||
|
|
||||||
|
- (void) sendCommand:(NSString *)command;
|
||||||
|
|
||||||
|
- (BOOL) keepProcessingAudioScrobblerCommands;
|
||||||
|
- (void) setKeepProcessingAudioScrobblerCommands:(BOOL)keepProcessingAudioScrobblerCommands;
|
||||||
|
|
||||||
|
- (BOOL) audioScrobblerThreadCompleted;
|
||||||
|
- (void) setAudioScrobblerThreadCompleted:(BOOL)audioScrobblerThreadCompleted;
|
||||||
|
|
||||||
|
- (semaphore_t) semaphore;
|
||||||
|
|
||||||
|
- (void) processAudioScrobblerCommands:(AudioScrobbler *)myself;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation AudioScrobbler
|
||||||
|
|
||||||
|
- (id) init
|
||||||
|
{
|
||||||
|
if((self = [super init])) {
|
||||||
|
|
||||||
|
_pluginID = @"tst";
|
||||||
|
|
||||||
|
if([[NSUserDefaults standardUserDefaults] boolForKey:@"automaticallyLaunchLastFM"]) {
|
||||||
|
[[NSWorkspace sharedWorkspace] launchApplication:@"Last.fm.app"];
|
||||||
|
}
|
||||||
|
|
||||||
|
_keepProcessingAudioScrobblerCommands = YES;
|
||||||
|
|
||||||
|
kern_return_t result = semaphore_create(mach_task_self(), &_semaphore, SYNC_POLICY_FIFO, 0);
|
||||||
|
|
||||||
|
if(KERN_SUCCESS != result) {
|
||||||
|
NSLog(@"Couldn't create semaphore (%s).", mach_error_type(result));
|
||||||
|
|
||||||
|
[self release];
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
[NSThread detachNewThreadSelector:@selector(processAudioScrobblerCommands:) toTarget:self withObject:self];
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) dealloc
|
||||||
|
{
|
||||||
|
if([self keepProcessingAudioScrobblerCommands] || NO == [self audioScrobblerThreadCompleted]) {
|
||||||
|
[self shutdown];
|
||||||
|
}
|
||||||
|
|
||||||
|
[_queue release], _queue = nil;
|
||||||
|
|
||||||
|
semaphore_destroy(mach_task_self(), _semaphore), _semaphore = 0;
|
||||||
|
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) start:(PlaylistEntry *)pe
|
||||||
|
{
|
||||||
|
[self sendCommand:[NSString stringWithFormat:@"START c=%@&a=%@&t=%@&b=%@&m=%@&l=%i&p=%@\n",
|
||||||
|
[self pluginID],
|
||||||
|
escapeForLastFM([pe artist]),
|
||||||
|
escapeForLastFM([pe title]),
|
||||||
|
escapeForLastFM([pe album]),
|
||||||
|
@"", // TODO: MusicBrainz support
|
||||||
|
(int)([pe length]/1000.0),
|
||||||
|
escapeForLastFM([pe filename])
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) stop
|
||||||
|
{
|
||||||
|
[self sendCommand:[NSString stringWithFormat:@"STOP c=%@\n", [self pluginID]]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) pause
|
||||||
|
{
|
||||||
|
[self sendCommand:[NSString stringWithFormat:@"PAUSE c=%@\n", [self pluginID]]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) resume
|
||||||
|
{
|
||||||
|
[self sendCommand:[NSString stringWithFormat:@"RESUME c=%@\n", [self pluginID]]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) shutdown
|
||||||
|
{
|
||||||
|
[self setKeepProcessingAudioScrobblerCommands:NO];
|
||||||
|
semaphore_signal([self semaphore]);
|
||||||
|
|
||||||
|
// Wait for the thread to terminate
|
||||||
|
while(NO == [self audioScrobblerThreadCompleted]) {
|
||||||
|
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation AudioScrobbler (Private)
|
||||||
|
|
||||||
|
- (NSMutableArray *) queue
|
||||||
|
{
|
||||||
|
if(nil == _queue) {
|
||||||
|
_queue = [[NSMutableArray alloc] init];
|
||||||
|
}
|
||||||
|
|
||||||
|
return _queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *) pluginID
|
||||||
|
{
|
||||||
|
return _pluginID;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) sendCommand:(NSString *)command
|
||||||
|
{
|
||||||
|
NSLog(@"Command: %@", command);
|
||||||
|
@synchronized([self queue]) {
|
||||||
|
[[self queue] addObject:command];
|
||||||
|
}
|
||||||
|
semaphore_signal([self semaphore]);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL) keepProcessingAudioScrobblerCommands
|
||||||
|
{
|
||||||
|
return _keepProcessingAudioScrobblerCommands;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) setKeepProcessingAudioScrobblerCommands:(BOOL)keepProcessingAudioScrobblerCommands
|
||||||
|
{
|
||||||
|
_keepProcessingAudioScrobblerCommands = keepProcessingAudioScrobblerCommands;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL) audioScrobblerThreadCompleted
|
||||||
|
{
|
||||||
|
return _audioScrobblerThreadCompleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) setAudioScrobblerThreadCompleted:(BOOL)audioScrobblerThreadCompleted
|
||||||
|
{
|
||||||
|
_audioScrobblerThreadCompleted = audioScrobblerThreadCompleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (semaphore_t) semaphore
|
||||||
|
{
|
||||||
|
return _semaphore;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) processAudioScrobblerCommands:(AudioScrobbler *)myself
|
||||||
|
{
|
||||||
|
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||||
|
AudioScrobblerClient *client = [[AudioScrobblerClient alloc] init];
|
||||||
|
mach_timespec_t timeout = { 5, 0 };
|
||||||
|
NSEnumerator *enumerator = nil;
|
||||||
|
NSString *command = nil;
|
||||||
|
NSString *response = nil;
|
||||||
|
in_port_t port = 33367;
|
||||||
|
|
||||||
|
while([myself keepProcessingAudioScrobblerCommands]) {
|
||||||
|
|
||||||
|
// Get the first command to be sent
|
||||||
|
@synchronized([myself queue]) {
|
||||||
|
|
||||||
|
enumerator = [[myself queue] objectEnumerator];
|
||||||
|
command = [enumerator nextObject];
|
||||||
|
|
||||||
|
[[myself queue] removeObjectIdenticalTo:command];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nil != command) {
|
||||||
|
@try {
|
||||||
|
port = [client connectToHost:@"localhost" port:port];
|
||||||
|
[client send:command];
|
||||||
|
|
||||||
|
response = [client receive];
|
||||||
|
if(2 > [response length] || NSOrderedSame != [response compare:@"OK" options:NSLiteralSearch range:NSMakeRange(0,2)]) {
|
||||||
|
NSLog(@"AudioScrobbler error: %@", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
[client shutdown];
|
||||||
|
}
|
||||||
|
|
||||||
|
@catch(NSException *exception) {
|
||||||
|
[client shutdown];
|
||||||
|
// NSLog(@"Exception: %@",exception);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
semaphore_timedwait([myself semaphore], timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a final stop command to cleanup
|
||||||
|
@try {
|
||||||
|
port = [client connectToHost:@"localhost" port:port];
|
||||||
|
[client send:[NSString stringWithFormat:@"STOP c=%@\n", [myself pluginID]]];
|
||||||
|
|
||||||
|
response = [client receive];
|
||||||
|
if(2 > [response length] || NSOrderedSame != [response compare:@"OK" options:NSLiteralSearch range:NSMakeRange(0,2)]) {
|
||||||
|
NSLog(@"AudioScrobbler error: %@", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
[client shutdown];
|
||||||
|
}
|
||||||
|
|
||||||
|
@catch(NSException *exception) {
|
||||||
|
[client shutdown];
|
||||||
|
}
|
||||||
|
|
||||||
|
[client release];
|
||||||
|
[pool release];
|
||||||
|
|
||||||
|
[myself setAudioScrobblerThreadCompleted:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* $Id: AudioScrobblerClient.h 241 2007-01-26 23:02:09Z stephen_booth $
|
||||||
|
*
|
||||||
|
* Copyright (C) 2006 - 2007 Stephen F. Booth <me@sbooth.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
#include <netdb.h>
|
||||||
|
|
||||||
|
@interface AudioScrobblerClient : NSObject
|
||||||
|
{
|
||||||
|
int _socket;
|
||||||
|
BOOL _doPortStepping;
|
||||||
|
in_port_t _lastPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (in_port_t) connectToHost:(NSString *)hostname port:(in_port_t)port;
|
||||||
|
|
||||||
|
- (void) send:(NSString *)data;
|
||||||
|
- (NSString *) receive;
|
||||||
|
|
||||||
|
- (void) shutdown;
|
||||||
|
|
||||||
|
@end
|
|
@ -0,0 +1,189 @@
|
||||||
|
/*
|
||||||
|
* $Id: AudioScrobblerClient.m 362 2007-02-13 05:30:49Z stephen_booth $
|
||||||
|
*
|
||||||
|
* Copyright (C) 2006 - 2007 Stephen F. Booth <me@sbooth.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is a port of the BlockingClient client class from
|
||||||
|
* the Last.fm ScrobSub library by sharevari
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "AudioScrobblerClient.h"
|
||||||
|
|
||||||
|
#define kBufferSize 1024
|
||||||
|
#define kPortsToStep 5
|
||||||
|
|
||||||
|
static in_addr_t
|
||||||
|
addressForHost(NSString *hostname)
|
||||||
|
{
|
||||||
|
NSCParameterAssert(nil != hostname);
|
||||||
|
|
||||||
|
in_addr_t address;
|
||||||
|
struct hostent *hostinfo;
|
||||||
|
|
||||||
|
address = inet_addr([hostname cStringUsingEncoding:NSASCIIStringEncoding]);
|
||||||
|
|
||||||
|
if(INADDR_NONE == address) {
|
||||||
|
hostinfo = gethostbyname([hostname cStringUsingEncoding:NSASCIIStringEncoding]);
|
||||||
|
if(NULL == hostinfo) {
|
||||||
|
NSLog(@"Unable to resolve address for \"%@\".", hostname);
|
||||||
|
return INADDR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
address = *((in_addr_t *)hostinfo->h_addr_list[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@interface AudioScrobblerClient (Private)
|
||||||
|
- (void) connectToSocket:(in_addr_t)remoteAddress port:(in_port_t)port;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation AudioScrobblerClient
|
||||||
|
|
||||||
|
- (id) init
|
||||||
|
{
|
||||||
|
if((self = [super init])) {
|
||||||
|
_socket = -1;
|
||||||
|
_lastPort = -1;
|
||||||
|
_doPortStepping = YES;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (in_port_t) connectToHost:(NSString *)hostname port:(in_port_t)port
|
||||||
|
{
|
||||||
|
in_addr_t remoteAddress = addressForHost(hostname);
|
||||||
|
|
||||||
|
[self connectToSocket:remoteAddress port:port];
|
||||||
|
|
||||||
|
return _lastPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) send:(NSString *)data
|
||||||
|
{
|
||||||
|
const char *utf8data = [data UTF8String];
|
||||||
|
unsigned len = strlen(utf8data);
|
||||||
|
unsigned bytesToSend = len;
|
||||||
|
unsigned totalBytesSent = 0;
|
||||||
|
ssize_t bytesSent = 0;
|
||||||
|
|
||||||
|
while(totalBytesSent < bytesToSend && -1 != bytesSent) {
|
||||||
|
bytesSent = send(_socket, utf8data + totalBytesSent, bytesToSend - totalBytesSent, 0);
|
||||||
|
|
||||||
|
if(-1 == bytesSent || 0 == bytesSent) {
|
||||||
|
NSLog(@"Unable to send data through socket");
|
||||||
|
}
|
||||||
|
|
||||||
|
totalBytesSent += bytesSent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *) receive
|
||||||
|
{
|
||||||
|
char buffer [ kBufferSize ];
|
||||||
|
int readSize = kBufferSize - 1;
|
||||||
|
ssize_t bytesRead = 0;
|
||||||
|
BOOL keepGoing = YES;
|
||||||
|
NSString *result = nil;
|
||||||
|
|
||||||
|
do {
|
||||||
|
bytesRead = recv(_socket, buffer, readSize, 0);
|
||||||
|
NSAssert1(-1 != bytesRead && 0 < bytesRead, @"Unable to receive data through socket (%s).", strerror(errno));
|
||||||
|
|
||||||
|
if('\n' == buffer[bytesRead - 1]) {
|
||||||
|
--bytesRead;
|
||||||
|
keepGoing = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[bytesRead] = '\0';
|
||||||
|
result = [[NSString alloc] initWithUTF8String:buffer];
|
||||||
|
|
||||||
|
} while(keepGoing);
|
||||||
|
|
||||||
|
return [result autorelease];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) shutdown
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
char buffer [ kBufferSize ];
|
||||||
|
ssize_t bytesRead;
|
||||||
|
|
||||||
|
if(-1 == _socket) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = shutdown(_socket, SHUT_WR);
|
||||||
|
NSAssert1(-1 != result, @"Socket shutdown failed (%s).", strerror(errno));
|
||||||
|
|
||||||
|
for(;;) {
|
||||||
|
bytesRead = recv(_socket, buffer, kBufferSize, 0);
|
||||||
|
|
||||||
|
NSAssert1(-1 != bytesRead, @"Waiting for shutdown confirmation failed (%s).", strerror(errno));
|
||||||
|
|
||||||
|
if(0 != bytesRead) {
|
||||||
|
NSLog(@"Received unexpected bytes during shutdown: %@.", [[[NSString alloc] initWithBytes:buffer length:bytesRead encoding:NSUTF8StringEncoding] autorelease]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = close(_socket);
|
||||||
|
NSAssert1(-1 != result, @"Couldn't close socket (%s).", strerror(errno));
|
||||||
|
|
||||||
|
_socket = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation AudioScrobblerClient (Private)
|
||||||
|
|
||||||
|
- (void) connectToSocket:(in_addr_t)remoteAddress port:(in_port_t)port
|
||||||
|
{
|
||||||
|
struct sockaddr_in socketAddress;
|
||||||
|
int result;
|
||||||
|
|
||||||
|
_socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
NSAssert1(-1 != _socket, @"Unable to create socket (%s).", strerror(errno));
|
||||||
|
|
||||||
|
_lastPort = port;
|
||||||
|
socketAddress.sin_family = AF_INET;
|
||||||
|
socketAddress.sin_addr.s_addr = remoteAddress;
|
||||||
|
socketAddress.sin_port = htons(_lastPort);
|
||||||
|
|
||||||
|
result = connect(_socket, (const struct sockaddr *)&socketAddress, sizeof(struct sockaddr_in));
|
||||||
|
|
||||||
|
if(_doPortStepping) {
|
||||||
|
while(-1 == result && _lastPort <= (port + kPortsToStep)) {
|
||||||
|
socketAddress.sin_port = htons(++_lastPort);
|
||||||
|
result = connect(_socket, (const struct sockaddr *)&socketAddress, sizeof(struct sockaddr_in));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(-1 == result) {
|
||||||
|
_doPortStepping = NO;
|
||||||
|
close(_socket);
|
||||||
|
_socket = -1;
|
||||||
|
NSAssert1(-1 != result, @"Couldn't connect to server (%s).", strerror(errno));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
|
@ -9,6 +9,10 @@
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
1705F1510B8BCB0C00C8B40D /* Help in Resources */ = {isa = PBXBuildFile; fileRef = 1705F1420B8BCB0C00C8B40D /* Help */; };
|
1705F1510B8BCB0C00C8B40D /* Help in Resources */ = {isa = PBXBuildFile; fileRef = 1705F1420B8BCB0C00C8B40D /* Help */; };
|
||||||
171678C00AC8C39E00C28CF3 /* SmartFolderNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 171678BE0AC8C39E00C28CF3 /* SmartFolderNode.m */; };
|
171678C00AC8C39E00C28CF3 /* SmartFolderNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 171678BE0AC8C39E00C28CF3 /* SmartFolderNode.m */; };
|
||||||
|
1766C6920B911DF1004A7AE4 /* AudioScrobbler.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1766C68E0B911DF1004A7AE4 /* AudioScrobbler.h */; };
|
||||||
|
1766C6930B911DF1004A7AE4 /* AudioScrobbler.m in Sources */ = {isa = PBXBuildFile; fileRef = 1766C68F0B911DF1004A7AE4 /* AudioScrobbler.m */; };
|
||||||
|
1766C6940B911DF1004A7AE4 /* AudioScrobblerClient.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1766C6900B911DF1004A7AE4 /* AudioScrobblerClient.h */; };
|
||||||
|
1766C6950B911DF1004A7AE4 /* AudioScrobblerClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 1766C6910B911DF1004A7AE4 /* AudioScrobblerClient.m */; };
|
||||||
1770429C0B8BC53600B86321 /* AppController.m in Sources */ = {isa = PBXBuildFile; fileRef = 177042980B8BC53600B86321 /* AppController.m */; };
|
1770429C0B8BC53600B86321 /* AppController.m in Sources */ = {isa = PBXBuildFile; fileRef = 177042980B8BC53600B86321 /* AppController.m */; };
|
||||||
1770429E0B8BC53600B86321 /* PlaybackController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1770429A0B8BC53600B86321 /* PlaybackController.m */; };
|
1770429E0B8BC53600B86321 /* PlaybackController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1770429A0B8BC53600B86321 /* PlaybackController.m */; };
|
||||||
177EBF9E0B8BC2A70000BC8C /* AMRemovableColumnsTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 177EBF7A0B8BC2A70000BC8C /* AMRemovableColumnsTableView.m */; };
|
177EBF9E0B8BC2A70000BC8C /* AMRemovableColumnsTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 177EBF7A0B8BC2A70000BC8C /* AMRemovableColumnsTableView.m */; };
|
||||||
|
@ -139,6 +143,8 @@
|
||||||
files = (
|
files = (
|
||||||
17B61B630B90A28100BC003F /* CogAudio.framework in CopyFiles */,
|
17B61B630B90A28100BC003F /* CogAudio.framework in CopyFiles */,
|
||||||
17F94CCD0B8D090800A34E87 /* Sparkle.framework in CopyFiles */,
|
17F94CCD0B8D090800A34E87 /* Sparkle.framework in CopyFiles */,
|
||||||
|
1766C6920B911DF1004A7AE4 /* AudioScrobbler.h in CopyFiles */,
|
||||||
|
1766C6940B911DF1004A7AE4 /* AudioScrobblerClient.h in CopyFiles */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -150,6 +156,10 @@
|
||||||
1705F1420B8BCB0C00C8B40D /* Help */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Help; sourceTree = "<group>"; };
|
1705F1420B8BCB0C00C8B40D /* Help */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Help; sourceTree = "<group>"; };
|
||||||
171678BD0AC8C39E00C28CF3 /* SmartFolderNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SmartFolderNode.h; sourceTree = "<group>"; };
|
171678BD0AC8C39E00C28CF3 /* SmartFolderNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SmartFolderNode.h; sourceTree = "<group>"; };
|
||||||
171678BE0AC8C39E00C28CF3 /* SmartFolderNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SmartFolderNode.m; sourceTree = "<group>"; };
|
171678BE0AC8C39E00C28CF3 /* SmartFolderNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SmartFolderNode.m; sourceTree = "<group>"; };
|
||||||
|
1766C68E0B911DF1004A7AE4 /* AudioScrobbler.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AudioScrobbler.h; sourceTree = "<group>"; };
|
||||||
|
1766C68F0B911DF1004A7AE4 /* AudioScrobbler.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = AudioScrobbler.m; sourceTree = "<group>"; };
|
||||||
|
1766C6900B911DF1004A7AE4 /* AudioScrobblerClient.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AudioScrobblerClient.h; sourceTree = "<group>"; };
|
||||||
|
1766C6910B911DF1004A7AE4 /* AudioScrobblerClient.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = AudioScrobblerClient.m; sourceTree = "<group>"; };
|
||||||
1770424E0B8BC41800B86321 /* Cog.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Cog.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
1770424E0B8BC41800B86321 /* Cog.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Cog.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
177042970B8BC53600B86321 /* AppController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AppController.h; sourceTree = "<group>"; };
|
177042970B8BC53600B86321 /* AppController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AppController.h; sourceTree = "<group>"; };
|
||||||
177042980B8BC53600B86321 /* AppController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = AppController.m; sourceTree = "<group>"; };
|
177042980B8BC53600B86321 /* AppController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = AppController.m; sourceTree = "<group>"; };
|
||||||
|
@ -310,6 +320,7 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
177042960B8BC53600B86321 /* Application */,
|
177042960B8BC53600B86321 /* Application */,
|
||||||
|
1766C68D0B911DF1004A7AE4 /* AudioScrobbler */,
|
||||||
8E07AAEA0AAC90DC00A4B32F /* Preferences */,
|
8E07AAEA0AAC90DC00A4B32F /* Preferences */,
|
||||||
8EFFCD410AA093AF00C458A5 /* FileDrawer */,
|
8EFFCD410AA093AF00C458A5 /* FileDrawer */,
|
||||||
8E75752309F31D5A0080F1EE /* Feedback */,
|
8E75752309F31D5A0080F1EE /* Feedback */,
|
||||||
|
@ -345,6 +356,17 @@
|
||||||
name = "Other Frameworks";
|
name = "Other Frameworks";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
1766C68D0B911DF1004A7AE4 /* AudioScrobbler */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
1766C68E0B911DF1004A7AE4 /* AudioScrobbler.h */,
|
||||||
|
1766C68F0B911DF1004A7AE4 /* AudioScrobbler.m */,
|
||||||
|
1766C6900B911DF1004A7AE4 /* AudioScrobblerClient.h */,
|
||||||
|
1766C6910B911DF1004A7AE4 /* AudioScrobblerClient.m */,
|
||||||
|
);
|
||||||
|
path = AudioScrobbler;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
177042960B8BC53600B86321 /* Application */ = {
|
177042960B8BC53600B86321 /* Application */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -802,6 +824,8 @@
|
||||||
1770429C0B8BC53600B86321 /* AppController.m in Sources */,
|
1770429C0B8BC53600B86321 /* AppController.m in Sources */,
|
||||||
1770429E0B8BC53600B86321 /* PlaybackController.m in Sources */,
|
1770429E0B8BC53600B86321 /* PlaybackController.m in Sources */,
|
||||||
17D21DF60B8BE86900D1EBDE /* CoreAudioUtils.m in Sources */,
|
17D21DF60B8BE86900D1EBDE /* CoreAudioUtils.m in Sources */,
|
||||||
|
1766C6930B911DF1004A7AE4 /* AudioScrobbler.m in Sources */,
|
||||||
|
1766C6950B911DF1004A7AE4 /* AudioScrobblerClient.m in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,25 +9,25 @@
|
||||||
<key>10</key>
|
<key>10</key>
|
||||||
<string>587 659 506 102 0 0 1680 1028 </string>
|
<string>587 659 506 102 0 0 1680 1028 </string>
|
||||||
<key>11</key>
|
<key>11</key>
|
||||||
<string>703 634 273 151 0 0 1680 1028 </string>
|
<string>699 367 273 151 0 0 1680 1028 </string>
|
||||||
<key>43</key>
|
<key>43</key>
|
||||||
<string>516 528 337 116 0 0 1680 1028 </string>
|
<string>383 476 337 116 0 0 1680 1028 </string>
|
||||||
<key>50</key>
|
<key>50</key>
|
||||||
<string>662 662 355 96 0 0 1680 1028 </string>
|
<string>769 536 355 96 0 0 1680 1028 </string>
|
||||||
<key>58</key>
|
<key>58</key>
|
||||||
<string>634 659 411 101 0 0 1680 1028 </string>
|
<string>385 271 411 101 0 0 1680 1028 </string>
|
||||||
<key>85</key>
|
<key>85</key>
|
||||||
<string>614 652 452 116 0 0 1680 1028 </string>
|
<string>596 829 452 116 0 0 1680 1028 </string>
|
||||||
</dict>
|
</dict>
|
||||||
<key>IBFramework Version</key>
|
<key>IBFramework Version</key>
|
||||||
<string>446.1</string>
|
<string>446.1</string>
|
||||||
<key>IBOpenObjects</key>
|
<key>IBOpenObjects</key>
|
||||||
<array>
|
<array>
|
||||||
<integer>10</integer>
|
<integer>10</integer>
|
||||||
<integer>58</integer>
|
|
||||||
<integer>85</integer>
|
<integer>85</integer>
|
||||||
<integer>43</integer>
|
<integer>43</integer>
|
||||||
<integer>50</integer>
|
<integer>50</integer>
|
||||||
|
<integer>58</integer>
|
||||||
<integer>11</integer>
|
<integer>11</integer>
|
||||||
</array>
|
</array>
|
||||||
<key>IBSystem Version</key>
|
<key>IBSystem Version</key>
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue