Index: trunk/Cocoa/Pester/Source/NJRQTMediaPopUpButton.m
===================================================================
--- trunk/Cocoa/Pester/Source/NJRQTMediaPopUpButton.m	(revision 103)
+++ trunk/Cocoa/Pester/Source/NJRQTMediaPopUpButton.m	(revision 133)
@@ -9,4 +9,5 @@
 #import "NJRQTMediaPopUpButton.h"
 #import "SoundFileManager.h"
+#import "NJRSoundManager.h"
 #import "NSMovie-NJRExtensions.h"
 #import "NSImage-NJRExtensions.h"
@@ -27,4 +28,6 @@
 - (NSMenuItem *)_itemForAlias:(BDAlias *)alias;
 - (BOOL)_validateWithPreview:(BOOL)doPreview;
+- (void)_updateOutputVolume;
+- (void)_startSoundPreview;
 @end
 
@@ -267,4 +270,27 @@
 }
 
+- (BOOL)hasAudio;
+{
+    return movieHasAudio;
+}
+
+- (float)outputVolume;
+{
+    return outputVolume;
+}
+
+- (void)setOutputVolume:(float)volume withPreview:(BOOL)doPreview;
+{
+    if (![NJRSoundManager volumeIsNotMutedOrInvalid: volume]) return;
+    outputVolume = volume;
+    if (!doPreview) return;
+    // NSLog(@"setting volume to %f, preview movie %@", volume, [preview movie]);
+    if ([preview movie] == nil) {
+        [self _validateWithPreview: YES];
+    } else { // don't restart preview if already playing
+        [self _updateOutputVolume];
+    }
+}
+
 #pragma mark selected media validation
 
@@ -276,11 +302,50 @@
 }
 
+- (void)_updateOutputVolume;
+{
+    if ([preview movie] != nil && outputVolume != kNoVolume) {
+        if (!savedVolume && ![NJRSoundManager saveDefaultOutputVolume])
+            return;
+        savedVolume = YES;
+        [NJRSoundManager setDefaultOutputVolume: outputVolume];
+        if (![preview isPlaying]) [self _startSoundPreview];
+    }
+}
+
+- (void)_resetOutputVolume;
+{
+    [NJRSoundManager restoreSavedDefaultOutputVolumeIfCurrently: outputVolume];
+    savedVolume = NO;
+}
+
+- (void)_resetPreview;
+{
+    // if we donÕt do this after the runloop has finished, then we crash in MCIdle because itÕs expecting a movie and doesnÕt have one any more
+    [preview setMovie: nil]; // otherwise we get an extra runloop timer which uses a lot of CPU from +[NSMovieView _idleMovies]
+    // need to wait for runloop to stop movie, otherwise we're still playing at the time the volume changes
+    [self performSelector: @selector(_resetOutputVolume) withObject: nil afterDelay: 0];
+}
+
 void
 MovieStoppedCB(QTCallBack cb, long refCon)
 {
-    NSMovieView *preview = (NSMovieView *)refCon;
-    // if we donÕt do this after the runloop has finished, then we crash in MCIdle because itÕs expecting a movie and doesnÕt have one any more
-    [preview performSelector: @selector(setMovie:) withObject: nil afterDelay: 0]; // otherwise we get an extra runloop timer which uses a lot of CPU from +[NSMovieView _idleMovies]
+    NJRQTMediaPopUpButton *self = (NJRQTMediaPopUpButton *)refCon;
+    // avoid multiple messages from multiple movie playback cycles in the same runloop
+    [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(_resetPreview) object: nil];
+    [self performSelector: @selector(_resetPreview) withObject: nil afterDelay: 0];
     DisposeCallBack(cb);
+}
+
+- (void)_startSoundPreview;
+{
+    Movie qtMovie = [[preview movie] QTMovie];
+    QTCallBack cbStop = NewCallBack(GetMovieTimeBase(qtMovie), callBackAtExtremes);
+    QTCallBackUPP cbStopUPP = NewQTCallBackUPP(MovieStoppedCB);
+    OSErr err = CallMeWhen(cbStop, cbStopUPP, (long)self, triggerAtStop, 0, 0);
+    if (err != noErr) {
+        NSLog(@"Can't register QuickTime stop timebase callback for preview: %ld", err);
+        DisposeCallBack(cbStop);
+    }
+    [preview start: self];
 }
 
@@ -291,12 +356,19 @@
         [preview setMovie: nil];
         movieCanRepeat = YES;
-        if (doPreview) NSBeep();
+        movieHasAudio = NO; // XXX should be YES - this is broken, NSBeep() is asynchronous
+        if (doPreview) {
+            // XXX [self _updateOutputVolume];
+            NSBeep();
+            // XXX [self _resetOutputVolume];
+        }
     } else {
         NSMovie *movie = [[NSMovie alloc] initWithURL: [NSURL fileURLWithPath: [selectedAlias fullPath]] byReference: YES];
         movieCanRepeat = ![movie isStatic];
-        if ([movie hasAudio]) {
+        if (movieHasAudio = [movie hasAudio]) {
             [preview setMovie: doPreview ? movie : nil];
+            [self _updateOutputVolume];
         } else {
-            [preview setMovie: nil];
+            [self _resetPreview];
+            doPreview = NO;
             if (movie == nil) {
                 NSBeginAlertSheet(@"Format not recognized", nil, nil, nil, [self window], nil, nil, nil, nil, NSLocalizedString(@"The item you selected isn't a sound or movie recognized by QuickTime.  Please select a different item.", "Message displayed in alert sheet when media document is not recognized by QuickTime"));
@@ -312,13 +384,5 @@
         }
         if (doPreview) {
-            Movie qtMovie = [movie QTMovie];
-            QTCallBack cbStop = NewCallBack(GetMovieTimeBase(qtMovie), callBackAtExtremes);
-            QTCallBackUPP cbStopUPP = NewQTCallBackUPP(MovieStoppedCB);
-            OSErr err = CallMeWhen(cbStop, cbStopUPP, (long)preview, triggerAtStop, 0, 0);
-            if (err != noErr) {
-                NSLog(@"Can't register QuickTime stop timebase callback for preview: %ld", err);
-                DisposeCallBack(cbStop);
-            }
-            [preview start: self];
+            [self _startSoundPreview];
         }
         [movie release];
@@ -333,5 +397,5 @@
 {
     [preview stop: self];
-    [preview setMovie: nil]; // otherwise we get an extra runloop timer which uses a lot of CPU from +[NSMovieView _idleMovies]
+    [self _resetPreview];
 }
 
