Backed out changeset 8b455a7d180d - revert NSSound caching. Refs #12098
authorEvan Schoenberg
Mon Jul 27 15:41:10 2009 -0500 (2009-07-27)
changeset 25444703302ec97b
parent 2165 8b455a7d180d
child 2552 028328d74309
Backed out changeset 8b455a7d180d - revert NSSound caching. Refs #12098
Source/AdiumSound.m
     1.1 --- a/Source/AdiumSound.m	Sun May 10 15:45:25 2009 -0700
     1.2 +++ b/Source/AdiumSound.m	Mon Jul 27 15:41:10 2009 -0500
     1.3 @@ -19,12 +19,20 @@
     1.4  #import <AIUtilities/AIDictionaryAdditions.h>
     1.5  #import <AIUtilities/AISleepNotification.h>
     1.6  #import <CoreAudio/AudioHardware.h>
     1.7 +#import <CoreServices/CoreServices.h>
     1.8  #import <sys/sysctl.h>
     1.9  
    1.10  #define SOUND_DEFAULT_PREFS				@"SoundPrefs"
    1.11 +#define MAX_CACHED_SOUNDS				4			//Max cached sounds
    1.12  
    1.13  @interface AdiumSound ()
    1.14 -@property (readonly, nonatomic) NSString *systemAudioDeviceID;
    1.15 +- (void)_stopAndReleaseAllSounds;
    1.16 +- (void)_setVolumeOfAllSoundsTo:(CGFloat)inVolume;
    1.17 +- (void)cachedPlaySound:(NSString *)inPath;
    1.18 +- (void)_uncacheLeastRecentlyUsedSound;
    1.19 +- (NSString *)systemAudioDeviceID;
    1.20 +- (void)configureAudioContextForSound:(NSSound *)sound;
    1.21 +- (NSArray *)allSounds;
    1.22  @end
    1.23  
    1.24  @interface NSProcessInfo (AIProcessorInfoAdditions)
    1.25 @@ -41,7 +49,9 @@
    1.26  - (id)init
    1.27  {
    1.28  	if ((self = [super init])) {
    1.29 -		currentlyPlayingSounds = [[NSMutableSet alloc] init];
    1.30 +		soundCacheDict = [[NSMutableDictionary alloc] init];
    1.31 +		soundCacheArray = [[NSMutableArray alloc] init];
    1.32 +		soundCacheCleanupTimer = nil;
    1.33  		soundsAreMuted = NO;
    1.34  
    1.35  		//Observe workspace activity changes so we can mute sounds as necessary
    1.36 @@ -96,24 +106,41 @@
    1.37  	[[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
    1.38  	[[NSNotificationCenter defaultCenter] removeObserver:self];
    1.39  
    1.40 -	[currentlyPlayingSounds release]; currentlyPlayingSounds = nil;
    1.41 +	[self _stopAndReleaseAllSounds];
    1.42 +
    1.43 +	[soundCacheDict release]; soundCacheDict = nil;
    1.44 +	[soundCacheArray release]; soundCacheArray = nil;
    1.45 +	[soundCacheCleanupTimer invalidate]; [soundCacheCleanupTimer release]; soundCacheCleanupTimer = nil;
    1.46  
    1.47  	[super dealloc];
    1.48  }
    1.49  
    1.50 +- (void)playSoundAtPath:(NSString *)inPath
    1.51 +{
    1.52 +	if (inPath && customVolume != 0.0 && !soundsAreMuted) {
    1.53 +		[self cachedPlaySound:inPath];
    1.54 +	}
    1.55 +}
    1.56 +
    1.57 +- (void)stopPlayingSoundAtPath:(NSString *)inPath
    1.58 +{
    1.59 +	NSSound *sound = [soundCacheDict objectForKey:inPath];
    1.60 +	if (sound) {
    1.61 +		[sound stop];
    1.62 +	}
    1.63 +}
    1.64 +
    1.65  /*!
    1.66   * @brief Preferences changed, adjust to the new values
    1.67   */
    1.68 -- (void)preferencesChangedForGroup:(NSString *)group 
    1.69 -							   key:(NSString *)key
    1.70 -							object:(AIListObject *)object 
    1.71 -					preferenceDict:(NSDictionary *)prefDict 
    1.72 -						 firstTime:(BOOL)firstTime
    1.73 +- (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
    1.74 +							object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
    1.75  {
    1.76  	CGFloat newVolume = [[prefDict objectForKey:KEY_SOUND_CUSTOM_VOLUME_LEVEL] doubleValue];
    1.77  
    1.78 -	for (NSSound *sound in currentlyPlayingSounds) {
    1.79 -		[sound setVolume:newVolume];
    1.80 +	//If sound volume has changed, we must update all existing sounds to the new volume
    1.81 +	if (customVolume != newVolume) {
    1.82 +		[self _setVolumeOfAllSoundsTo:newVolume];
    1.83  	}
    1.84  
    1.85  	//Load the new preferences
    1.86 @@ -123,72 +150,134 @@
    1.87  /*!
    1.88   * @brief Stop and release all cached sounds
    1.89   */
    1.90 -- (void)stopCurrentlyPlayingSounds
    1.91 +- (void)_stopAndReleaseAllSounds
    1.92  {
    1.93 -	[currentlyPlayingSounds removeAllObjects];
    1.94 +	[[soundCacheDict allValues] makeObjectsPerformSelector:@selector(stop)];
    1.95 +	[soundCacheDict removeAllObjects];
    1.96 +	[soundCacheArray removeAllObjects];
    1.97 +}
    1.98 +
    1.99 +/*!
   1.100 + * @brief Update the volume of all cached sounds
   1.101 + */
   1.102 +- (void)_setVolumeOfAllSoundsTo:(CGFloat)inVolume
   1.103 +{
   1.104 +	for (NSSound *sound in [soundCacheDict objectEnumerator]) {
   1.105 +		[sound setVolume:inVolume];
   1.106 +	}
   1.107  }
   1.108  
   1.109  /*!
   1.110   * @brief Play an NSSound, possibly cached
   1.111   * 
   1.112 - * @param inURL file url to the sound file
   1.113 + * @param inPath path to the sound file
   1.114   */
   1.115 -- (void)playSoundAtURL:(NSURL *)inURL
   1.116 +- (void)cachedPlaySound:(NSString *)inPath
   1.117  {
   1.118 -	if (!inURL || customVolume == 0.0 || soundsAreMuted > 0)
   1.119 -		return;
   1.120 +	NSSound *sound = [soundCacheDict objectForKey:inPath];
   1.121  
   1.122 -	//Load the sound
   1.123 -	NSSound *sound = [[[NSSound alloc] initWithContentsOfURL:inURL byReference:YES] autorelease];
   1.124 +	//Load the sound if necessary
   1.125 +	if (!sound) {
   1.126 +		//If the cache is full, remove the least recently used cached sound
   1.127 +		if ([soundCacheDict count] >= MAX_CACHED_SOUNDS) {
   1.128 +			[self _uncacheLeastRecentlyUsedSound];
   1.129 +		}
   1.130 +
   1.131 +		//Load and cache the sound
   1.132 +		NSError *error = nil;
   1.133 +		sound = [[NSSound alloc] initWithContentsOfFile:inPath byReference:NO];
   1.134 +		if (sound) {
   1.135 +			//Insert the player at the front of our cache
   1.136 +			[soundCacheArray insertObject:inPath atIndex:0];
   1.137 +			[soundCacheDict setObject:sound forKey:inPath];
   1.138 +			[sound release];
   1.139 +
   1.140 +			//Set the volume (otherwise #2283 happens)
   1.141 +			[sound setVolume:customVolume];
   1.142 +
   1.143 +			[self configureAudioContextForSound:sound];
   1.144 +		} else {
   1.145 +			AILogWithSignature(@"Error loading %@: %@", inPath, error);
   1.146 +		}
   1.147 +
   1.148 +	} else {
   1.149 +		//Move this sound to the front of the cache (This will naturally move lesser used sounds to the back for removal)
   1.150 +		[soundCacheArray removeObject:inPath];
   1.151 +		[soundCacheArray insertObject:inPath atIndex:0];
   1.152 +		
   1.153 +		if (reconfigureAudioContextBeforeEachPlay) {
   1.154 +			[sound stop];
   1.155 +			[self configureAudioContextForSound:sound];
   1.156 +		}
   1.157 +	}
   1.158  
   1.159  	//Engage!
   1.160  	if (sound) {
   1.161 -		[sound setVolume:customVolume];
   1.162 -		[sound setPlaybackDeviceIdentifier:self.systemAudioDeviceID];
   1.163 -		[currentlyPlayingSounds addObject:sound];
   1.164 -		[sound setDelegate:self];
   1.165  		[sound setCurrentTime:0.0];
   1.166 +
   1.167 +		//This only has an effect if the sound is not already playing. It won't stop it, and it won't start it over (the latter is what setCurrentTime: is for).
   1.168  		[sound play];
   1.169  	}
   1.170  }
   1.171  
   1.172 -- (void)sound:(NSSound *)sound didFinishPlaying:(BOOL)finishedPlaying
   1.173 +/*!
   1.174 + * @brief Remove the least recently used sound from the cache
   1.175 + */
   1.176 +- (void)_uncacheLeastRecentlyUsedSound
   1.177  {
   1.178 -	[currentlyPlayingSounds removeObject:sound];
   1.179 +	NSString			*lastCachedPath = [soundCacheArray lastObject];
   1.180 +	NSSound *sound = [soundCacheDict objectForKey:lastCachedPath];
   1.181 +
   1.182 +	//Remove it from the cache only if it is not playing.
   1.183 +	if (![sound isPlaying]) {
   1.184 +		[soundCacheDict removeObjectForKey:lastCachedPath];
   1.185 +		[soundCacheArray removeLastObject];
   1.186 +	}
   1.187  }
   1.188  
   1.189  - (NSString *)systemAudioDeviceID
   1.190  {
   1.191 -	if (reconfigureAudioContextBeforeEachPlay) {
   1.192 -		[outputDeviceUID release];
   1.193 -		outputDeviceUID = nil;
   1.194 +	OSStatus err;
   1.195 +	UInt32 dataSize;
   1.196 +
   1.197 +	//First, obtain the device itself.
   1.198 +	AudioDeviceID systemOutputDevice = 0;
   1.199 +	dataSize = sizeof(systemOutputDevice);
   1.200 +	err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultSystemOutputDevice, &dataSize, &systemOutputDevice);
   1.201 +	if (err != noErr) {
   1.202 +		NSLog(@"%s: Could not get the system output device: AudioHardwareGetProperty returned error %i", __PRETTY_FUNCTION__, err);
   1.203 +		return NULL;
   1.204  	}
   1.205 -	if (!outputDeviceUID) {
   1.206 -		OSStatus err;
   1.207 -		UInt32 dataSize;
   1.208 -		
   1.209 -		//First, obtain the device itself.
   1.210 -		AudioDeviceID systemOutputDevice = 0;
   1.211 -		dataSize = sizeof(systemOutputDevice);
   1.212 -		err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultSystemOutputDevice, &dataSize, &systemOutputDevice);
   1.213 -		if (err != noErr) {
   1.214 -			NSLog(@"%s: Could not get the system output device: AudioHardwareGetProperty returned error %i", __PRETTY_FUNCTION__, err);
   1.215 -			return NULL;
   1.216 -		}
   1.217 -		
   1.218 -		//Now get its UID. We'll need to release this.
   1.219 -		CFStringRef deviceUID = NULL;
   1.220 -		dataSize = sizeof(deviceUID);
   1.221 -		err = AudioDeviceGetProperty(systemOutputDevice, /*channel*/ 0, /*isInput*/ false, kAudioDevicePropertyDeviceUID, &dataSize, &deviceUID);
   1.222 -		if (err != noErr) {
   1.223 -			NSLog(@"%s: Could not get the device UID for device %p: AudioDeviceGetProperty returned error %i", __PRETTY_FUNCTION__, systemOutputDevice, err);
   1.224 -			return NULL;
   1.225 -		}
   1.226 -		outputDeviceUID = (NSString *)deviceUID;
   1.227 +
   1.228 +	//Now get its UID. We'll need to release this.
   1.229 +	CFStringRef deviceUID = NULL;
   1.230 +	dataSize = sizeof(deviceUID);
   1.231 +	err = AudioDeviceGetProperty(systemOutputDevice, /*channel*/ 0, /*isInput*/ false, kAudioDevicePropertyDeviceUID, &dataSize, &deviceUID);
   1.232 +	if (err != noErr) {
   1.233 +		NSLog(@"%s: Could not get the device UID for device %p: AudioDeviceGetProperty returned error %i", __PRETTY_FUNCTION__, systemOutputDevice, err);
   1.234 +		return NULL;
   1.235  	}
   1.236 +	[(NSString *)deviceUID autorelease];
   1.237  	
   1.238 +	return (NSString *)deviceUID;
   1.239 +}
   1.240 +
   1.241 +- (void)configureAudioContextForSound:(NSSound *)sound
   1.242 +{
   1.243 +	[sound pause];
   1.244  	
   1.245 -	return outputDeviceUID;
   1.246 +	//Exchange the audio context for a new one with the new device.
   1.247 +	NSString *deviceUID = [self systemAudioDeviceID];
   1.248 +	
   1.249 +	[sound setPlaybackDeviceIdentifier:deviceUID];
   1.250 +	
   1.251 +	//Resume playback, now on the new device.
   1.252 +	[sound resume];
   1.253 +}
   1.254 +
   1.255 +- (NSArray *)allSounds
   1.256 +{
   1.257 +	return [soundCacheDict allValues];
   1.258  }
   1.259  
   1.260  /*!
   1.261 @@ -209,7 +298,7 @@
   1.262  
   1.263  - (void)systemWillSleep:(NSNotification *)notification
   1.264  {
   1.265 -	[self stopCurrentlyPlayingSounds];
   1.266 +	[self _stopAndReleaseAllSounds];
   1.267  }
   1.268  
   1.269  - (void)setSoundsAreMuted:(BOOL)mute
   1.270 @@ -221,16 +310,13 @@
   1.271  		soundsAreMuted++;
   1.272  
   1.273  	if (soundsAreMuted == 1)
   1.274 -		[self stopCurrentlyPlayingSounds];
   1.275 +		[self _stopAndReleaseAllSounds];
   1.276  }
   1.277  
   1.278  - (void)systemOutputDeviceDidChange
   1.279  {
   1.280 -	[outputDeviceUID release]; outputDeviceUID = nil; //clear our cache
   1.281 -	for (NSSound *sound in currentlyPlayingSounds) {
   1.282 -		[sound pause];
   1.283 -		[sound setPlaybackDeviceIdentifier:self.systemAudioDeviceID];
   1.284 -		[sound resume];
   1.285 +	for (NSSound *sound in [self allSounds]) {
   1.286 +		[self configureAudioContextForSound:sound];
   1.287  	}
   1.288  }
   1.289