Backed out changeset 8b455a7d180d - revert NSSound caching. Refs #12098
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