From 0aec125254bf5a344c7b813a44bcb8159947afd4 Mon Sep 17 00:00:00 2001 From: Matt Gallagher Date: Mon, 27 Jul 2009 10:02:59 +1000 Subject: [PATCH] Fixed error output and handling of packet too large error. --- Classes/AudioStreamer.h | 5 +- Classes/AudioStreamer.m | 29 +- Classes/MacStreamingPlayerController.m | 2 +- Info.plist | 30 -- Resources/MainWindow.xib | 206 ---------- .../iPhoneStreamingPlayerViewController.xib | 383 ------------------ Resources/loadingbutton.png | Bin 2730 -> 0 bytes Resources/pausebutton.png | Bin 2996 -> 0 bytes Resources/playbutton.png | Bin 3657 -> 0 bytes Resources/stopbutton.png | Bin 2971 -> 0 bytes 10 files changed, 25 insertions(+), 630 deletions(-) delete mode 100644 Info.plist delete mode 100644 Resources/MainWindow.xib delete mode 100644 Resources/iPhoneStreamingPlayerViewController.xib delete mode 100644 Resources/loadingbutton.png delete mode 100644 Resources/pausebutton.png delete mode 100644 Resources/playbutton.png delete mode 100644 Resources/stopbutton.png diff --git a/Classes/AudioStreamer.h b/Classes/AudioStreamer.h index 3926f5d..3495fb7 100644 --- a/Classes/AudioStreamer.h +++ b/Classes/AudioStreamer.h @@ -22,7 +22,7 @@ #include #define kNumAQBufs 32 // number of audio queue buffers we allocate -#define kAQBufSize 1024 // number of bytes in each audio queue buffer +#define kAQBufSize 2048 // number of bytes in each audio queue buffer #define kAQMaxPacketDescs 512 // number of packet descriptions in our array typedef enum @@ -69,7 +69,8 @@ typedef enum AS_AUDIO_QUEUE_STOP_FAILED, AS_AUDIO_QUEUE_FLUSH_FAILED, AS_AUDIO_STREAMER_FAILED, - AS_GET_AUDIO_TIME_FAILED + AS_GET_AUDIO_TIME_FAILED, + AS_AUDIO_BUFFER_TOO_SMALL } AudioStreamerErrorCode; extern NSString * const ASStatusChangedNotification; diff --git a/Classes/AudioStreamer.m b/Classes/AudioStreamer.m index b6696c4..45080d5 100644 --- a/Classes/AudioStreamer.m +++ b/Classes/AudioStreamer.m @@ -40,6 +40,7 @@ NSString * const AS_GET_AUDIO_TIME_FAILED_STRING = @"Audio queue get current time failed."; NSString * const AS_AUDIO_STREAMER_FAILED_STRING = @"Audio playback failed"; NSString * const AS_NETWORK_CONNECTION_FAILED_STRING = @"Network connection failed"; +NSString * const AS_AUDIO_BUFFER_TOO_SMALL_STRING = @"Audio packets are larger than kAQBufSize."; @interface AudioStreamer () @property (readwrite) AudioStreamerState state; @@ -335,6 +336,8 @@ + (NSString *)stringForErrorCode:(AudioStreamerErrorCode)anErrorCode return AS_AUDIO_QUEUE_STOP_FAILED_STRING; case AS_AUDIO_STREAMER_FAILED: return AS_AUDIO_STREAMER_FAILED_STRING; + case AS_AUDIO_BUFFER_TOO_SMALL: + return AS_AUDIO_BUFFER_TOO_SMALL_STRING; default: return AS_AUDIO_STREAMER_FAILED_STRING; } @@ -388,7 +391,7 @@ - (void)failWithErrorCode:(AudioStreamerErrorCode)anErrorCode UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:NSLocalizedStringFromTable(@"Audio Error", @"Errors", nil) - message:NSLocalizedStringFromTable(@"Attempt to play streaming audio failed.", @"Errors", nil) + message:NSLocalizedStringFromTable([AudioStreamer stringForErrorCode:self.errorCode], @"Errors", nil) delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil] @@ -405,7 +408,7 @@ - (void)failWithErrorCode:(AudioStreamerErrorCode)anErrorCode defaultButton:NSLocalizedString(@"OK", @"") alternateButton:nil otherButton:nil - informativeTextWithFormat:@"Attempt to play streaming audio failed."]; + informativeTextWithFormat:[AudioStreamer stringForErrorCode:self.errorCode]]; [alert performSelector:@selector(runModal) onThread:[NSThread mainThread] @@ -726,16 +729,18 @@ - (void)startInternal @synchronized(self) { - if (state == AS_STOPPING) + if (state != AS_STARTING_FILE_THREAD) { + if (state != AS_STOPPING && + state != AS_STOPPED) + { + NSLog(@"### Not starting audio thread. State code is: %ld", state); + } self.state = AS_INITIALIZED; [pool release]; return; } - NSAssert(state == AS_STARTING_FILE_THREAD, - @"Start illegally invoked on an audio stream that has already started."); - #ifdef TARGET_OS_IPHONE // // Set the audio session category so that we continue to play if the @@ -1373,6 +1378,7 @@ - (void)handleAudioPackets:(const void *)inInputData { SInt64 packetOffset = inPacketDescriptions[i].mStartOffset; SInt64 packetSize = inPacketDescriptions[i].mDataByteSize; + size_t bufSpaceRemaining; @synchronized(self) { @@ -1391,11 +1397,18 @@ - (void)handleAudioPackets:(const void *)inInputData { return; } + + if (packetSize > kAQBufSize) + { + [self failWithErrorCode:AS_AUDIO_BUFFER_TOO_SMALL]; + } + + bufSpaceRemaining = kAQBufSize - bytesFilled; } // if the space remaining in the buffer is not enough for this packet, then enqueue the buffer. - size_t bufSpaceRemaining = kAQBufSize - bytesFilled; - if (bufSpaceRemaining < packetSize) { + if (bufSpaceRemaining < packetSize) + { [self enqueueBuffer]; } diff --git a/Classes/MacStreamingPlayerController.m b/Classes/MacStreamingPlayerController.m index 2b2e58d..92393af 100644 --- a/Classes/MacStreamingPlayerController.m +++ b/Classes/MacStreamingPlayerController.m @@ -131,7 +131,7 @@ - (void)spinButton CABasicAnimation *animation; animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; animation.fromValue = [NSNumber numberWithFloat:0.0]; - animation.toValue = [NSNumber numberWithFloat:2 * M_PI]; + animation.toValue = [NSNumber numberWithFloat:-2 * M_PI]; animation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionLinear]; animation.delegate = self; [button.layer addAnimation:animation forKey:@"rotationAnimation"]; diff --git a/Info.plist b/Info.plist deleted file mode 100644 index 71715a2..0000000 --- a/Info.plist +++ /dev/null @@ -1,30 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleDisplayName - ${PRODUCT_NAME} - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIconFile - - CFBundleIdentifier - com.mattgallagher.${PRODUCT_NAME:identifier} - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - APPL - CFBundleSignature - ???? - CFBundleVersion - 1.0 - LSRequiresIPhoneOS - - NSMainNibFile - MainWindow - - diff --git a/Resources/MainWindow.xib b/Resources/MainWindow.xib deleted file mode 100644 index e3d2c31..0000000 --- a/Resources/MainWindow.xib +++ /dev/null @@ -1,206 +0,0 @@ - - - - 528 - 9E17 - 672 - 949.33 - 352.00 - - YES - - - - YES - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - - YES - - IBFilesOwner - - - IBFirstResponder - - - - iPhoneStreamingPlayerViewController - - - - - 292 - {320, 480} - - 1 - MSAxIDEAA - - NO - NO - - - - - - YES - - - delegate - - - - 4 - - - - viewController - - - - 11 - - - - window - - - - 14 - - - - - YES - - 0 - - YES - - - - - - -1 - - - RmlsZSdzIE93bmVyA - - - 3 - - - iPhoneStreamingPlayer App Delegate - - - -2 - - - - - 10 - - - - - 12 - - - - - - - YES - - YES - -1.CustomClassName - -2.CustomClassName - 10.CustomClassName - 10.IBEditorWindowLastContentRect - 10.IBPluginDependency - 12.IBEditorWindowLastContentRect - 12.IBPluginDependency - 3.CustomClassName - 3.IBPluginDependency - - - YES - UIApplication - UIResponder - iPhoneStreamingPlayerViewController - {{512, 351}, {320, 480}} - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - {{525, 346}, {320, 480}} - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - iPhoneStreamingPlayerAppDelegate - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - - - YES - - YES - - - YES - - - - - YES - - YES - - - YES - - - - 14 - - - - YES - - iPhoneStreamingPlayerAppDelegate - NSObject - - YES - - YES - viewController - window - - - YES - iPhoneStreamingPlayerViewController - UIWindow - - - - IBProjectSource - Classes/iPhoneStreamingPlayerAppDelegate.h - - - - iPhoneStreamingPlayerAppDelegate - NSObject - - IBUserSource - - - - - iPhoneStreamingPlayerViewController - UIViewController - - IBProjectSource - Classes/iPhoneStreamingPlayerViewController.h - - - - - 0 - iPhoneStreamingPlayer.xcodeproj - 3 - - diff --git a/Resources/iPhoneStreamingPlayerViewController.xib b/Resources/iPhoneStreamingPlayerViewController.xib deleted file mode 100644 index b393e54..0000000 --- a/Resources/iPhoneStreamingPlayerViewController.xib +++ /dev/null @@ -1,383 +0,0 @@ - - - - 528 - 9J61 - 677 - 949.46 - 353.00 - - YES - - - - YES - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - - YES - - YES - - - YES - - - - YES - - IBFilesOwner - - - IBFirstResponder - - - - 274 - - YES - - - 292 - {{20, 49}, {280, 31}} - - NO - NO - 0 - http://202.6.74.107:8060/triplej.mp3 - 3 - - 3 - MAA - - 2 - - - YES - 1.700000e+01 - - 1 - 3 - - - - - 292 - {{20, 20}, {280, 21}} - - NO - YES - NO - Download URL: - - 1 - MCAwIDAAA - - - 1 - 1.000000e+01 - - - - 292 - {{124, 88}, {72, 73}} - - NO - NO - 0 - 0 - - Helvetica-Bold - 1.500000e+01 - 16 - - - 3 - MQA - - - 1 - MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA - - - 3 - MC41AA - - - NSImage - playbutton.png - - - - - 292 - {{20, 169}, {280, 21}} - - NO - YES - NO - Time Played: - - - 1 - 1.000000e+01 - - - - 292 - {{20, 228}, {280, 21}} - - NO - YES - NO - Volume: - - - 1 - 1.000000e+01 - - - - 292 - {{20, 257}, {280, 55}} - - - 3 - MSAwAA - - NO - - - {320, 460} - - - 1 - MC44NTIwNDA4MyAwLjg1MjA0MDgzIDAuODUyMDQwODMAA - - NO - - - - - - YES - - - view - - - - 7 - - - - button - - - - 28 - - - - buttonPressed: - - - 7 - - 29 - - - - volumeSlider - - - - 36 - - - - downloadSourceField - - - - 38 - - - - positionLabel - - - - 41 - - - - delegate - - - - 44 - - - - - YES - - 0 - - YES - - - - - - -1 - - - RmlsZSdzIE93bmVyA - - - -2 - - - - - 6 - - - YES - - - - - - - - - - - 16 - - - - - 21 - - - - - 23 - - - - - 26 - - - - - 35 - - - YES - - - - - 25 - - - - - - - YES - - YES - -1.CustomClassName - -2.CustomClassName - 16.IBPluginDependency - 21.IBPluginDependency - 23.IBPluginDependency - 25.IBPluginDependency - 26.IBPluginDependency - 35.IBPluginDependency - 6.IBEditorWindowLastContentRect - 6.IBPluginDependency - - - YES - iPhoneStreamingPlayerViewController - UIResponder - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - {{208, 221}, {320, 480}} - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - - - YES - - YES - - - YES - - - - - YES - - YES - - - YES - - - - 44 - - - - YES - - iPhoneStreamingPlayerViewController - UIViewController - - buttonPressed: - id - - - YES - - YES - button - downloadSourceField - positionLabel - volumeSlider - - - YES - UIButton - UITextField - UILabel - UIView - - - - IBProjectSource - Classes/iPhoneStreamingPlayerViewController.h - - - - - 0 - iPhoneStreamingPlayer.xcodeproj - 3 - 3.0 - - diff --git a/Resources/loadingbutton.png b/Resources/loadingbutton.png deleted file mode 100644 index 8d13736dcf1591ca57eb673b7030e9706a830509..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2730 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 diff --git a/Resources/pausebutton.png b/Resources/pausebutton.png deleted file mode 100644 index 3c962bc1add9f82005e57a8f4bc2f8b9a2482026..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2996 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 Rx%&VB002ovPDHLkV1n+pp*#Qp