├── .gitattributes ├── .gitignore ├── Classes ├── AudioStreamer.h ├── AudioStreamer.m ├── MacStreamingPlayerController.h ├── MacStreamingPlayerController.m ├── iPhoneStreamingPlayerAppDelegate.h ├── iPhoneStreamingPlayerAppDelegate.m ├── iPhoneStreamingPlayerViewController.h └── iPhoneStreamingPlayerViewController.m ├── Default-568h@2x.png ├── English.lproj ├── InfoPlist.strings └── MainMenu.xib ├── Mac Resources └── English.lproj │ ├── InfoPlist.strings │ └── MainMenu.xib ├── MacInfo.plist ├── MacStreamingPlayer.xcodeproj └── project.pbxproj ├── MacStreamingPlayer_Prefix.pch ├── Shared Resources ├── loadingbutton.png ├── pausebutton.png ├── playbutton.png └── stopbutton.png ├── iPhone Resources ├── MainWindow.xib └── iPhoneStreamingPlayerViewController.xib ├── iPhoneInfo.plist ├── iPhoneStreamingPlayer.xcodeproj └── project.pbxproj ├── iPhoneStreamingPlayer_Prefix.pch └── main.m /.gitattributes: -------------------------------------------------------------------------------- 1 | *.[mh] diff=objc 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .DS_Store 3 | *.xcodeproj/*.mode* 4 | *.xcodeproj/*.pbxuser 5 | xcuserdata 6 | project.xcworkspace -------------------------------------------------------------------------------- /Classes/AudioStreamer.h: -------------------------------------------------------------------------------- 1 | // 2 | // AudioStreamer.h 3 | // StreamingAudioPlayer 4 | // 5 | // Created by Matt Gallagher on 27/09/08. 6 | // Copyright 2008 Matt Gallagher. All rights reserved. 7 | // 8 | // This software is provided 'as-is', without any express or implied 9 | // warranty. In no event will the authors be held liable for any damages 10 | // arising from the use of this software. Permission is granted to anyone to 11 | // use this software for any purpose, including commercial applications, and to 12 | // alter it and redistribute it freely, subject to the following restrictions: 13 | // 14 | // 1. The origin of this software must not be misrepresented; you must not 15 | // claim that you wrote the original software. If you use this software 16 | // in a product, an acknowledgment in the product documentation would be 17 | // appreciated but is not required. 18 | // 2. Altered source versions must be plainly marked as such, and must not be 19 | // misrepresented as being the original software. 20 | // 3. This notice may not be removed or altered from any source 21 | // distribution. 22 | // 23 | 24 | #if TARGET_OS_IPHONE 25 | #import 26 | #else 27 | #import 28 | #endif // TARGET_OS_IPHONE 29 | 30 | #include 31 | #include 32 | 33 | #define LOG_QUEUED_BUFFERS 0 34 | 35 | #define kNumAQBufs 16 // Number of audio queue buffers we allocate. 36 | // Needs to be big enough to keep audio pipeline 37 | // busy (non-zero number of queued buffers) but 38 | // not so big that audio takes too long to begin 39 | // (kNumAQBufs * kAQBufSize of data must be 40 | // loaded before playback will start). 41 | // 42 | // Set LOG_QUEUED_BUFFERS to 1 to log how many 43 | // buffers are queued at any time -- if it drops 44 | // to zero too often, this value may need to 45 | // increase. Min 3, typical 8-24. 46 | 47 | #define kAQDefaultBufSize 2048 // Number of bytes in each audio queue buffer 48 | // Needs to be big enough to hold a packet of 49 | // audio from the audio file. If number is too 50 | // large, queuing of audio before playback starts 51 | // will take too long. 52 | // Highly compressed files can use smaller 53 | // numbers (512 or less). 2048 should hold all 54 | // but the largest packets. A buffer size error 55 | // will occur if this number is too small. 56 | 57 | #define kAQMaxPacketDescs 512 // Number of packet descriptions in our array 58 | 59 | typedef enum 60 | { 61 | AS_INITIALIZED = 0, 62 | AS_STARTING_FILE_THREAD, 63 | AS_WAITING_FOR_DATA, 64 | AS_FLUSHING_EOF, 65 | AS_WAITING_FOR_QUEUE_TO_START, 66 | AS_PLAYING, 67 | AS_BUFFERING, 68 | AS_STOPPING, 69 | AS_STOPPED, 70 | AS_PAUSED 71 | } AudioStreamerState; 72 | 73 | typedef enum 74 | { 75 | AS_NO_STOP = 0, 76 | AS_STOPPING_EOF, 77 | AS_STOPPING_USER_ACTION, 78 | AS_STOPPING_ERROR, 79 | AS_STOPPING_TEMPORARILY 80 | } AudioStreamerStopReason; 81 | 82 | typedef enum 83 | { 84 | AS_NO_ERROR = 0, 85 | AS_NETWORK_CONNECTION_FAILED, 86 | AS_FILE_STREAM_GET_PROPERTY_FAILED, 87 | AS_FILE_STREAM_SET_PROPERTY_FAILED, 88 | AS_FILE_STREAM_SEEK_FAILED, 89 | AS_FILE_STREAM_PARSE_BYTES_FAILED, 90 | AS_FILE_STREAM_OPEN_FAILED, 91 | AS_FILE_STREAM_CLOSE_FAILED, 92 | AS_AUDIO_DATA_NOT_FOUND, 93 | AS_AUDIO_QUEUE_CREATION_FAILED, 94 | AS_AUDIO_QUEUE_BUFFER_ALLOCATION_FAILED, 95 | AS_AUDIO_QUEUE_ENQUEUE_FAILED, 96 | AS_AUDIO_QUEUE_ADD_LISTENER_FAILED, 97 | AS_AUDIO_QUEUE_REMOVE_LISTENER_FAILED, 98 | AS_AUDIO_QUEUE_START_FAILED, 99 | AS_AUDIO_QUEUE_PAUSE_FAILED, 100 | AS_AUDIO_QUEUE_BUFFER_MISMATCH, 101 | AS_AUDIO_QUEUE_DISPOSE_FAILED, 102 | AS_AUDIO_QUEUE_STOP_FAILED, 103 | AS_AUDIO_QUEUE_FLUSH_FAILED, 104 | AS_AUDIO_STREAMER_FAILED, 105 | AS_GET_AUDIO_TIME_FAILED, 106 | AS_AUDIO_BUFFER_TOO_SMALL 107 | } AudioStreamerErrorCode; 108 | 109 | extern NSString * const ASStatusChangedNotification; 110 | 111 | @interface AudioStreamer : NSObject 112 | { 113 | NSURL *url; 114 | 115 | // 116 | // Special threading consideration: 117 | // The audioQueue property should only ever be accessed inside a 118 | // synchronized(self) block and only *after* checking that ![self isFinishing] 119 | // 120 | AudioQueueRef audioQueue; 121 | AudioFileStreamID audioFileStream; // the audio file stream parser 122 | AudioStreamBasicDescription asbd; // description of the audio 123 | NSThread *internalThread; // the thread where the download and 124 | // audio file stream parsing occurs 125 | 126 | AudioQueueBufferRef audioQueueBuffer[kNumAQBufs]; // audio queue buffers 127 | AudioStreamPacketDescription packetDescs[kAQMaxPacketDescs]; // packet descriptions for enqueuing audio 128 | unsigned int fillBufferIndex; // the index of the audioQueueBuffer that is being filled 129 | UInt32 packetBufferSize; 130 | size_t bytesFilled; // how many bytes have been filled 131 | size_t packetsFilled; // how many packets have been filled 132 | bool inuse[kNumAQBufs]; // flags to indicate that a buffer is still in use 133 | NSInteger buffersUsed; 134 | NSDictionary *httpHeaders; 135 | NSString *fileExtension; 136 | 137 | AudioStreamerState state; 138 | AudioStreamerState laststate; 139 | AudioStreamerStopReason stopReason; 140 | AudioStreamerErrorCode errorCode; 141 | OSStatus err; 142 | 143 | bool discontinuous; // flag to indicate middle of the stream 144 | 145 | pthread_mutex_t queueBuffersMutex; // a mutex to protect the inuse flags 146 | pthread_cond_t queueBufferReadyCondition; // a condition varable for handling the inuse flags 147 | 148 | CFReadStreamRef stream; 149 | NSNotificationCenter *notificationCenter; 150 | 151 | UInt32 bitRate; // Bits per second in the file 152 | NSInteger dataOffset; // Offset of the first audio packet in the stream 153 | NSInteger fileLength; // Length of the file in bytes 154 | NSInteger seekByteOffset; // Seek offset within the file in bytes 155 | UInt64 audioDataByteCount; // Used when the actual number of audio bytes in 156 | // the file is known (more accurate than assuming 157 | // the whole file is audio) 158 | 159 | UInt64 processedPacketsCount; // number of packets accumulated for bitrate estimation 160 | UInt64 processedPacketsSizeTotal; // byte size of accumulated estimation packets 161 | 162 | double seekTime; 163 | BOOL seekWasRequested; 164 | double requestedSeekTime; 165 | double sampleRate; // Sample rate of the file (used to compare with 166 | // samples played by the queue for current playback 167 | // time) 168 | double packetDuration; // sample rate times frames per packet 169 | double lastProgress; // last calculated progress point 170 | #if TARGET_OS_IPHONE 171 | BOOL pausedByInterruption; 172 | #endif 173 | } 174 | 175 | @property AudioStreamerErrorCode errorCode; 176 | @property (readonly) AudioStreamerState state; 177 | @property (readonly) double progress; 178 | @property (readonly) double duration; 179 | @property (readwrite) UInt32 bitRate; 180 | @property (readonly) NSDictionary *httpHeaders; 181 | @property (copy,readwrite) NSString *fileExtension; 182 | @property (nonatomic) BOOL shouldDisplayAlertOnError; // to control whether the alert is displayed in failWithErrorCode 183 | 184 | - (id)initWithURL:(NSURL *)aURL; 185 | - (void)start; 186 | - (void)stop; 187 | - (void)pause; 188 | - (BOOL)isPlaying; 189 | - (BOOL)isPaused; 190 | - (BOOL)isWaiting; 191 | - (BOOL)isIdle; 192 | - (BOOL)isAborted; // return YES if streaming halted due to error (AS_STOPPING + AS_STOPPING_ERROR) 193 | - (void)seekToTime:(double)newSeekTime; 194 | - (double)calculatedBitRate; 195 | 196 | @end 197 | 198 | 199 | 200 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /Classes/AudioStreamer.m: -------------------------------------------------------------------------------- 1 | // 2 | // AudioStreamer.m 3 | // StreamingAudioPlayer 4 | // 5 | // Created by Matt Gallagher on 27/09/08. 6 | // Copyright 2008 Matt Gallagher. All rights reserved. 7 | // 8 | // This software is provided 'as-is', without any express or implied 9 | // warranty. In no event will the authors be held liable for any damages 10 | // arising from the use of this software. Permission is granted to anyone to 11 | // use this software for any purpose, including commercial applications, and to 12 | // alter it and redistribute it freely, subject to the following restrictions: 13 | // 14 | // 1. The origin of this software must not be misrepresented; you must not 15 | // claim that you wrote the original software. If you use this software 16 | // in a product, an acknowledgment in the product documentation would be 17 | // appreciated but is not required. 18 | // 2. Altered source versions must be plainly marked as such, and must not be 19 | // misrepresented as being the original software. 20 | // 3. This notice may not be removed or altered from any source 21 | // distribution. 22 | // 23 | 24 | #import "AudioStreamer.h" 25 | #if TARGET_OS_IPHONE 26 | #import 27 | #endif 28 | 29 | #define BitRateEstimationMaxPackets 5000 30 | #define BitRateEstimationMinPackets 50 31 | 32 | NSString * const ASStatusChangedNotification = @"ASStatusChangedNotification"; 33 | NSString * const ASAudioSessionInterruptionOccuredNotification = @"ASAudioSessionInterruptionOccuredNotification"; 34 | 35 | NSString * const AS_NO_ERROR_STRING = @"No error."; 36 | NSString * const AS_FILE_STREAM_GET_PROPERTY_FAILED_STRING = @"File stream get property failed."; 37 | NSString * const AS_FILE_STREAM_SEEK_FAILED_STRING = @"File stream seek failed."; 38 | NSString * const AS_FILE_STREAM_PARSE_BYTES_FAILED_STRING = @"Parse bytes failed."; 39 | NSString * const AS_FILE_STREAM_OPEN_FAILED_STRING = @"Open audio file stream failed."; 40 | NSString * const AS_FILE_STREAM_CLOSE_FAILED_STRING = @"Close audio file stream failed."; 41 | NSString * const AS_AUDIO_QUEUE_CREATION_FAILED_STRING = @"Audio queue creation failed."; 42 | NSString * const AS_AUDIO_QUEUE_BUFFER_ALLOCATION_FAILED_STRING = @"Audio buffer allocation failed."; 43 | NSString * const AS_AUDIO_QUEUE_ENQUEUE_FAILED_STRING = @"Queueing of audio buffer failed."; 44 | NSString * const AS_AUDIO_QUEUE_ADD_LISTENER_FAILED_STRING = @"Audio queue add listener failed."; 45 | NSString * const AS_AUDIO_QUEUE_REMOVE_LISTENER_FAILED_STRING = @"Audio queue remove listener failed."; 46 | NSString * const AS_AUDIO_QUEUE_START_FAILED_STRING = @"Audio queue start failed."; 47 | NSString * const AS_AUDIO_QUEUE_BUFFER_MISMATCH_STRING = @"Audio queue buffers don't match."; 48 | NSString * const AS_AUDIO_QUEUE_DISPOSE_FAILED_STRING = @"Audio queue dispose failed."; 49 | NSString * const AS_AUDIO_QUEUE_PAUSE_FAILED_STRING = @"Audio queue pause failed."; 50 | NSString * const AS_AUDIO_QUEUE_STOP_FAILED_STRING = @"Audio queue stop failed."; 51 | NSString * const AS_AUDIO_DATA_NOT_FOUND_STRING = @"No audio data found."; 52 | NSString * const AS_AUDIO_QUEUE_FLUSH_FAILED_STRING = @"Audio queue flush failed."; 53 | NSString * const AS_GET_AUDIO_TIME_FAILED_STRING = @"Audio queue get current time failed."; 54 | NSString * const AS_AUDIO_STREAMER_FAILED_STRING = @"Audio playback failed"; 55 | NSString * const AS_NETWORK_CONNECTION_FAILED_STRING = @"Network connection failed"; 56 | NSString * const AS_AUDIO_BUFFER_TOO_SMALL_STRING = @"Audio packets are larger than kAQDefaultBufSize."; 57 | 58 | @interface AudioStreamer () 59 | @property (readwrite) AudioStreamerState state; 60 | @property (readwrite) AudioStreamerState laststate; 61 | 62 | - (void)handlePropertyChangeForFileStream:(AudioFileStreamID)inAudioFileStream 63 | fileStreamPropertyID:(AudioFileStreamPropertyID)inPropertyID 64 | ioFlags:(UInt32 *)ioFlags; 65 | - (void)handleAudioPackets:(const void *)inInputData 66 | numberBytes:(UInt32)inNumberBytes 67 | numberPackets:(UInt32)inNumberPackets 68 | packetDescriptions:(AudioStreamPacketDescription *)inPacketDescriptions; 69 | - (void)handleBufferCompleteForQueue:(AudioQueueRef)inAQ 70 | buffer:(AudioQueueBufferRef)inBuffer; 71 | - (void)handlePropertyChangeForQueue:(AudioQueueRef)inAQ 72 | propertyID:(AudioQueuePropertyID)inID; 73 | 74 | #if TARGET_OS_IPHONE 75 | - (void)handleInterruptionChangeToState:(NSNotification *)notification; 76 | #endif 77 | 78 | - (void)internalSeekToTime:(double)newSeekTime; 79 | - (void)enqueueBuffer; 80 | - (void)handleReadFromStream:(CFReadStreamRef)aStream 81 | eventType:(CFStreamEventType)eventType; 82 | 83 | @end 84 | 85 | #pragma mark Audio Callback Function Implementations 86 | 87 | // 88 | // ASPropertyListenerProc 89 | // 90 | // Receives notification when the AudioFileStream has audio packets to be 91 | // played. In response, this function creates the AudioQueue, getting it 92 | // ready to begin playback (playback won't begin until audio packets are 93 | // sent to the queue in ASEnqueueBuffer). 94 | // 95 | // This function is adapted from Apple's example in AudioFileStreamExample with 96 | // kAudioQueueProperty_IsRunning listening added. 97 | // 98 | static void ASPropertyListenerProc(void * inClientData, 99 | AudioFileStreamID inAudioFileStream, 100 | AudioFileStreamPropertyID inPropertyID, 101 | UInt32 * ioFlags) 102 | { 103 | // this is called by audio file stream when it finds property values 104 | AudioStreamer* streamer = (AudioStreamer *)inClientData; 105 | [streamer 106 | handlePropertyChangeForFileStream:inAudioFileStream 107 | fileStreamPropertyID:inPropertyID 108 | ioFlags:ioFlags]; 109 | } 110 | 111 | // 112 | // ASPacketsProc 113 | // 114 | // When the AudioStream has packets to be played, this function gets an 115 | // idle audio buffer and copies the audio packets into it. The calls to 116 | // ASEnqueueBuffer won't return until there are buffers available (or the 117 | // playback has been stopped). 118 | // 119 | // This function is adapted from Apple's example in AudioFileStreamExample with 120 | // CBR functionality added. 121 | // 122 | static void ASPacketsProc( void * inClientData, 123 | UInt32 inNumberBytes, 124 | UInt32 inNumberPackets, 125 | const void * inInputData, 126 | AudioStreamPacketDescription *inPacketDescriptions) 127 | { 128 | // this is called by audio file stream when it finds packets of audio 129 | AudioStreamer* streamer = (AudioStreamer *)inClientData; 130 | [streamer 131 | handleAudioPackets:inInputData 132 | numberBytes:inNumberBytes 133 | numberPackets:inNumberPackets 134 | packetDescriptions:inPacketDescriptions]; 135 | } 136 | 137 | // 138 | // ASAudioQueueOutputCallback 139 | // 140 | // Called from the AudioQueue when playback of specific buffers completes. This 141 | // function signals from the AudioQueue thread to the AudioStream thread that 142 | // the buffer is idle and available for copying data. 143 | // 144 | // This function is unchanged from Apple's example in AudioFileStreamExample. 145 | // 146 | static void ASAudioQueueOutputCallback(void* inClientData, 147 | AudioQueueRef inAQ, 148 | AudioQueueBufferRef inBuffer) 149 | { 150 | // this is called by the audio queue when it has finished decoding our data. 151 | // The buffer is now free to be reused. 152 | AudioStreamer* streamer = (AudioStreamer*)inClientData; 153 | [streamer handleBufferCompleteForQueue:inAQ buffer:inBuffer]; 154 | } 155 | 156 | // 157 | // ASAudioQueueIsRunningCallback 158 | // 159 | // Called from the AudioQueue when playback is started or stopped. This 160 | // information is used to toggle the observable "isPlaying" property and 161 | // set the "finished" flag. 162 | // 163 | static void ASAudioQueueIsRunningCallback(void *inUserData, AudioQueueRef inAQ, AudioQueuePropertyID inID) 164 | { 165 | AudioStreamer* streamer = (AudioStreamer *)inUserData; 166 | [streamer handlePropertyChangeForQueue:inAQ propertyID:inID]; 167 | } 168 | 169 | #if TARGET_OS_IPHONE 170 | // 171 | // ASAudioSessionInterruptionListener 172 | // 173 | // Invoked if the audio session is interrupted (like when the phone rings) 174 | // 175 | static void ASAudioSessionInterruptionListener(__unused void * inClientData, UInt32 inInterruptionState) { 176 | [[NSNotificationCenter defaultCenter] postNotificationName:ASAudioSessionInterruptionOccuredNotification object:@(inInterruptionState)]; 177 | } 178 | #endif 179 | 180 | #pragma mark CFReadStream Callback Function Implementations 181 | 182 | // 183 | // ReadStreamCallBack 184 | // 185 | // This is the callback for the CFReadStream from the network connection. This 186 | // is where all network data is passed to the AudioFileStream. 187 | // 188 | // Invoked when an error occurs, the stream ends or we have data to read. 189 | // 190 | static void ASReadStreamCallBack 191 | ( 192 | CFReadStreamRef aStream, 193 | CFStreamEventType eventType, 194 | void* inClientInfo 195 | ) 196 | { 197 | AudioStreamer* streamer = (AudioStreamer *)inClientInfo; 198 | [streamer handleReadFromStream:aStream eventType:eventType]; 199 | } 200 | 201 | @implementation AudioStreamer 202 | 203 | @synthesize errorCode; 204 | @synthesize state; 205 | @synthesize laststate; 206 | @synthesize bitRate; 207 | @synthesize httpHeaders; 208 | @synthesize fileExtension; 209 | 210 | // 211 | // initWithURL 212 | // 213 | // Init method for the object. 214 | // 215 | - (id)initWithURL:(NSURL *)aURL 216 | { 217 | self = [super init]; 218 | if (self != nil) 219 | { 220 | url = [aURL retain]; 221 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruptionChangeToState:) name:ASAudioSessionInterruptionOccuredNotification object:nil]; 222 | } 223 | return self; 224 | } 225 | 226 | // 227 | // dealloc 228 | // 229 | // Releases instance memory. 230 | // 231 | - (void)dealloc 232 | { 233 | [[NSNotificationCenter defaultCenter] removeObserver:self name:ASAudioSessionInterruptionOccuredNotification object:nil]; 234 | [self stop]; 235 | [url release]; 236 | [fileExtension release]; 237 | [super dealloc]; 238 | } 239 | 240 | // 241 | // isFinishing 242 | // 243 | // returns YES if the audio has reached a stopping condition. 244 | // 245 | - (BOOL)isFinishing 246 | { 247 | @synchronized (self) 248 | { 249 | if ((errorCode != AS_NO_ERROR && state != AS_INITIALIZED) || 250 | ((state == AS_STOPPING || state == AS_STOPPED) && 251 | stopReason != AS_STOPPING_TEMPORARILY)) 252 | { 253 | return YES; 254 | } 255 | } 256 | 257 | return NO; 258 | } 259 | 260 | // 261 | // runLoopShouldExit 262 | // 263 | // returns YES if the run loop should exit. 264 | // 265 | - (BOOL)runLoopShouldExit 266 | { 267 | @synchronized(self) 268 | { 269 | if (errorCode != AS_NO_ERROR || 270 | (state == AS_STOPPED && 271 | stopReason != AS_STOPPING_TEMPORARILY)) 272 | { 273 | return YES; 274 | } 275 | } 276 | 277 | return NO; 278 | } 279 | 280 | // 281 | // stringForErrorCode: 282 | // 283 | // Converts an error code to a string that can be localized or presented 284 | // to the user. 285 | // 286 | // Parameters: 287 | // anErrorCode - the error code to convert 288 | // 289 | // returns the string representation of the error code 290 | // 291 | + (NSString *)stringForErrorCode:(AudioStreamerErrorCode)anErrorCode 292 | { 293 | switch (anErrorCode) 294 | { 295 | case AS_NO_ERROR: 296 | return AS_NO_ERROR_STRING; 297 | case AS_FILE_STREAM_GET_PROPERTY_FAILED: 298 | return AS_FILE_STREAM_GET_PROPERTY_FAILED_STRING; 299 | case AS_FILE_STREAM_SEEK_FAILED: 300 | return AS_FILE_STREAM_SEEK_FAILED_STRING; 301 | case AS_FILE_STREAM_PARSE_BYTES_FAILED: 302 | return AS_FILE_STREAM_PARSE_BYTES_FAILED_STRING; 303 | case AS_AUDIO_QUEUE_CREATION_FAILED: 304 | return AS_AUDIO_QUEUE_CREATION_FAILED_STRING; 305 | case AS_AUDIO_QUEUE_BUFFER_ALLOCATION_FAILED: 306 | return AS_AUDIO_QUEUE_BUFFER_ALLOCATION_FAILED_STRING; 307 | case AS_AUDIO_QUEUE_ENQUEUE_FAILED: 308 | return AS_AUDIO_QUEUE_ENQUEUE_FAILED_STRING; 309 | case AS_AUDIO_QUEUE_ADD_LISTENER_FAILED: 310 | return AS_AUDIO_QUEUE_ADD_LISTENER_FAILED_STRING; 311 | case AS_AUDIO_QUEUE_REMOVE_LISTENER_FAILED: 312 | return AS_AUDIO_QUEUE_REMOVE_LISTENER_FAILED_STRING; 313 | case AS_AUDIO_QUEUE_START_FAILED: 314 | return AS_AUDIO_QUEUE_START_FAILED_STRING; 315 | case AS_AUDIO_QUEUE_BUFFER_MISMATCH: 316 | return AS_AUDIO_QUEUE_BUFFER_MISMATCH_STRING; 317 | case AS_FILE_STREAM_OPEN_FAILED: 318 | return AS_FILE_STREAM_OPEN_FAILED_STRING; 319 | case AS_FILE_STREAM_CLOSE_FAILED: 320 | return AS_FILE_STREAM_CLOSE_FAILED_STRING; 321 | case AS_AUDIO_QUEUE_DISPOSE_FAILED: 322 | return AS_AUDIO_QUEUE_DISPOSE_FAILED_STRING; 323 | case AS_AUDIO_QUEUE_PAUSE_FAILED: 324 | return AS_AUDIO_QUEUE_DISPOSE_FAILED_STRING; 325 | case AS_AUDIO_QUEUE_FLUSH_FAILED: 326 | return AS_AUDIO_QUEUE_FLUSH_FAILED_STRING; 327 | case AS_AUDIO_DATA_NOT_FOUND: 328 | return AS_AUDIO_DATA_NOT_FOUND_STRING; 329 | case AS_GET_AUDIO_TIME_FAILED: 330 | return AS_GET_AUDIO_TIME_FAILED_STRING; 331 | case AS_NETWORK_CONNECTION_FAILED: 332 | return AS_NETWORK_CONNECTION_FAILED_STRING; 333 | case AS_AUDIO_QUEUE_STOP_FAILED: 334 | return AS_AUDIO_QUEUE_STOP_FAILED_STRING; 335 | case AS_AUDIO_STREAMER_FAILED: 336 | return AS_AUDIO_STREAMER_FAILED_STRING; 337 | case AS_AUDIO_BUFFER_TOO_SMALL: 338 | return AS_AUDIO_BUFFER_TOO_SMALL_STRING; 339 | default: 340 | return AS_AUDIO_STREAMER_FAILED_STRING; 341 | } 342 | 343 | return AS_AUDIO_STREAMER_FAILED_STRING; 344 | } 345 | 346 | // 347 | // presentAlertWithTitle:message: 348 | // 349 | // Common code for presenting error dialogs 350 | // 351 | // Parameters: 352 | // title - title for the dialog 353 | // message - main test for the dialog 354 | // 355 | - (void)presentAlertWithTitle:(NSString*)title message:(NSString*)message 356 | { 357 | #if TARGET_OS_IPHONE 358 | UIAlertView *alert = [ 359 | [[UIAlertView alloc] 360 | initWithTitle:title 361 | message:message 362 | delegate:nil 363 | cancelButtonTitle:NSLocalizedString(@"OK", @"") 364 | otherButtonTitles: nil] 365 | autorelease]; 366 | [alert 367 | performSelector:@selector(show) 368 | onThread:[NSThread mainThread] 369 | withObject:nil 370 | waitUntilDone:NO]; 371 | #else 372 | NSAlert *alert = 373 | [NSAlert 374 | alertWithMessageText:title 375 | defaultButton:NSLocalizedString(@"OK", @"") 376 | alternateButton:nil 377 | otherButton:nil 378 | informativeTextWithFormat:message]; 379 | [alert 380 | performSelector:@selector(runModal) 381 | onThread:[NSThread mainThread] 382 | withObject:nil 383 | waitUntilDone:NO]; 384 | #endif 385 | } 386 | 387 | // 388 | // failWithErrorCode: 389 | // 390 | // Sets the playback state to failed and logs the error. 391 | // 392 | // Parameters: 393 | // anErrorCode - the error condition 394 | // 395 | - (void)failWithErrorCode:(AudioStreamerErrorCode)anErrorCode 396 | { 397 | @synchronized(self) 398 | { 399 | if (errorCode != AS_NO_ERROR) 400 | { 401 | // Only set the error once. 402 | return; 403 | } 404 | 405 | errorCode = anErrorCode; 406 | 407 | if (err) 408 | { 409 | char *errChars = (char *)&err; 410 | NSLog(@"%@ err: %c%c%c%c %d\n", 411 | [AudioStreamer stringForErrorCode:anErrorCode], 412 | errChars[3], errChars[2], errChars[1], errChars[0], 413 | (int)err); 414 | } 415 | else 416 | { 417 | NSLog(@"%@", [AudioStreamer stringForErrorCode:anErrorCode]); 418 | } 419 | 420 | if (state == AS_PLAYING || 421 | state == AS_PAUSED || 422 | state == AS_BUFFERING) 423 | { 424 | self.state = AS_STOPPING; 425 | stopReason = AS_STOPPING_ERROR; 426 | AudioQueueStop(audioQueue, true); 427 | } 428 | 429 | if (self.shouldDisplayAlertOnError) 430 | [self presentAlertWithTitle:NSLocalizedStringFromTable(@"File Error", @"Errors", nil) 431 | message:NSLocalizedStringFromTable(@"Unable to configure network read stream.", @"Errors", nil)]; 432 | } 433 | } 434 | 435 | // 436 | // mainThreadStateNotification 437 | // 438 | // Method invoked on main thread to send notifications to the main thread's 439 | // notification center. 440 | // 441 | - (void)mainThreadStateNotification 442 | { 443 | NSNotification *notification = 444 | [NSNotification 445 | notificationWithName:ASStatusChangedNotification 446 | object:self]; 447 | [[NSNotificationCenter defaultCenter] 448 | postNotification:notification]; 449 | } 450 | 451 | // 452 | // state 453 | // 454 | // returns the state value. 455 | // 456 | - (AudioStreamerState)state 457 | { 458 | @synchronized(self) 459 | { 460 | return state; 461 | } 462 | } 463 | 464 | // 465 | // setState: 466 | // 467 | // Sets the state and sends a notification that the state has changed. 468 | // 469 | // This method 470 | // 471 | // Parameters: 472 | // anErrorCode - the error condition 473 | // 474 | - (void)setState:(AudioStreamerState)aStatus 475 | { 476 | @synchronized(self) 477 | { 478 | if (state != aStatus) 479 | { 480 | state = aStatus; 481 | 482 | if ([[NSThread currentThread] isEqual:[NSThread mainThread]]) 483 | { 484 | [self mainThreadStateNotification]; 485 | } 486 | else 487 | { 488 | [self 489 | performSelectorOnMainThread:@selector(mainThreadStateNotification) 490 | withObject:nil 491 | waitUntilDone:NO]; 492 | } 493 | } 494 | } 495 | } 496 | 497 | // 498 | // isPlaying 499 | // 500 | // returns YES if the audio currently playing. 501 | // 502 | - (BOOL)isPlaying 503 | { 504 | if (state == AS_PLAYING) 505 | { 506 | return YES; 507 | } 508 | 509 | return NO; 510 | } 511 | 512 | // 513 | // isPaused 514 | // 515 | // returns YES if the audio currently playing. 516 | // 517 | - (BOOL)isPaused 518 | { 519 | if (state == AS_PAUSED) 520 | { 521 | return YES; 522 | } 523 | 524 | return NO; 525 | } 526 | 527 | // 528 | // isWaiting 529 | // 530 | // returns YES if the AudioStreamer is waiting for a state transition of some 531 | // kind. 532 | // 533 | - (BOOL)isWaiting 534 | { 535 | @synchronized(self) 536 | { 537 | if ([self isFinishing] || 538 | state == AS_STARTING_FILE_THREAD|| 539 | state == AS_WAITING_FOR_DATA || 540 | state == AS_WAITING_FOR_QUEUE_TO_START || 541 | state == AS_BUFFERING) 542 | { 543 | return YES; 544 | } 545 | } 546 | 547 | return NO; 548 | } 549 | 550 | // 551 | // isIdle 552 | // 553 | // returns YES if the AudioStream is in the AS_INITIALIZED state (i.e. 554 | // isn't doing anything). 555 | // 556 | - (BOOL)isIdle 557 | { 558 | if (state == AS_INITIALIZED) 559 | { 560 | return YES; 561 | } 562 | 563 | return NO; 564 | } 565 | 566 | // 567 | // isAborted 568 | // 569 | // returns YES if the AudioStream was stopped due to some errror, handled through failWithCodeError. 570 | // 571 | - (BOOL)isAborted 572 | { 573 | if (state == AS_STOPPING && stopReason == AS_STOPPING_ERROR) 574 | { 575 | return YES; 576 | } 577 | 578 | return NO; 579 | } 580 | 581 | // 582 | // hintForFileExtension: 583 | // 584 | // Generates a first guess for the file type based on the file's extension 585 | // 586 | // Parameters: 587 | // fileExtension - the file extension 588 | // 589 | // returns a file type hint that can be passed to the AudioFileStream 590 | // 591 | + (AudioFileTypeID)hintForFileExtension:(NSString *)fileExtension 592 | { 593 | AudioFileTypeID fileTypeHint = kAudioFileAAC_ADTSType; 594 | if ([fileExtension isEqual:@"mp3"]) 595 | { 596 | fileTypeHint = kAudioFileMP3Type; 597 | } 598 | else if ([fileExtension isEqual:@"wav"]) 599 | { 600 | fileTypeHint = kAudioFileWAVEType; 601 | } 602 | else if ([fileExtension isEqual:@"aifc"]) 603 | { 604 | fileTypeHint = kAudioFileAIFCType; 605 | } 606 | else if ([fileExtension isEqual:@"aiff"]) 607 | { 608 | fileTypeHint = kAudioFileAIFFType; 609 | } 610 | else if ([fileExtension isEqual:@"m4a"]) 611 | { 612 | fileTypeHint = kAudioFileM4AType; 613 | } 614 | else if ([fileExtension isEqual:@"mp4"]) 615 | { 616 | fileTypeHint = kAudioFileMPEG4Type; 617 | } 618 | else if ([fileExtension isEqual:@"caf"]) 619 | { 620 | fileTypeHint = kAudioFileCAFType; 621 | } 622 | else if ([fileExtension isEqual:@"aac"]) 623 | { 624 | fileTypeHint = kAudioFileAAC_ADTSType; 625 | } 626 | return fileTypeHint; 627 | } 628 | 629 | // 630 | // openReadStream 631 | // 632 | // Open the audioFileStream to parse data and the fileHandle as the data 633 | // source. 634 | // 635 | - (BOOL)openReadStream 636 | { 637 | @synchronized(self) 638 | { 639 | NSAssert([[NSThread currentThread] isEqual:internalThread], 640 | @"File stream download must be started on the internalThread"); 641 | NSAssert(stream == nil, @"Download stream already initialized"); 642 | 643 | // 644 | // Create the HTTP GET request 645 | // 646 | CFHTTPMessageRef message= CFHTTPMessageCreateRequest(NULL, (CFStringRef)@"GET", (CFURLRef)url, kCFHTTPVersion1_1); 647 | 648 | // 649 | // If we are creating this request to seek to a location, set the 650 | // requested byte range in the headers. 651 | // 652 | if (fileLength > 0 && seekByteOffset > 0) 653 | { 654 | CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Range"), 655 | (CFStringRef)[NSString stringWithFormat:@"bytes=%ld-%ld", (long)seekByteOffset, (long)fileLength]); 656 | discontinuous = YES; 657 | } 658 | 659 | // 660 | // Create the read stream that will receive data from the HTTP request 661 | // 662 | stream = CFReadStreamCreateForHTTPRequest(NULL, message); 663 | CFRelease(message); 664 | 665 | // 666 | // Enable stream redirection 667 | // 668 | if (CFReadStreamSetProperty( 669 | stream, 670 | kCFStreamPropertyHTTPShouldAutoredirect, 671 | kCFBooleanTrue) == false) 672 | { 673 | [self failWithErrorCode:AS_FILE_STREAM_SET_PROPERTY_FAILED]; 674 | 675 | return NO; 676 | } 677 | 678 | // 679 | // Handle proxies 680 | // 681 | CFDictionaryRef proxySettings = CFNetworkCopySystemProxySettings(); 682 | CFReadStreamSetProperty(stream, kCFStreamPropertyHTTPProxy, proxySettings); 683 | CFRelease(proxySettings); 684 | 685 | // 686 | // Handle SSL connections 687 | // 688 | if([[url scheme] isEqualToString:@"https"]) 689 | { 690 | NSDictionary *sslSettings = 691 | [NSDictionary dictionaryWithObjectsAndKeys: 692 | (NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL, kCFStreamSSLLevel, 693 | [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates, 694 | [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredRoots, 695 | [NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot, 696 | [NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain, 697 | [NSNull null], kCFStreamSSLPeerName, 698 | nil]; 699 | 700 | CFReadStreamSetProperty(stream, kCFStreamPropertySSLSettings, sslSettings); 701 | } 702 | 703 | // 704 | // We're now ready to receive data 705 | // 706 | self.state = AS_WAITING_FOR_DATA; 707 | 708 | // 709 | // Open the stream 710 | // 711 | if (!CFReadStreamOpen(stream)) 712 | { 713 | CFRelease(stream); 714 | 715 | [self failWithErrorCode:AS_FILE_STREAM_OPEN_FAILED]; 716 | 717 | return NO; 718 | } 719 | 720 | // 721 | // Set our callback function to receive the data 722 | // 723 | CFStreamClientContext context = {0, self, NULL, NULL, NULL}; 724 | CFReadStreamSetClient( 725 | stream, 726 | kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, 727 | ASReadStreamCallBack, 728 | &context); 729 | CFReadStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); 730 | } 731 | 732 | return YES; 733 | } 734 | 735 | // 736 | // startInternal 737 | // 738 | // This is the start method for the AudioStream thread. This thread is created 739 | // because it will be blocked when there are no audio buffers idle (and ready 740 | // to receive audio data). 741 | // 742 | // Activity in this thread: 743 | // - Creation and cleanup of all AudioFileStream and AudioQueue objects 744 | // - Receives data from the CFReadStream 745 | // - AudioFileStream processing 746 | // - Copying of data from AudioFileStream into audio buffers 747 | // - Stopping of the thread because of end-of-file 748 | // - Stopping due to error or failure 749 | // 750 | // Activity *not* in this thread: 751 | // - AudioQueue playback and notifications (happens in AudioQueue thread) 752 | // - Actual download of NSURLConnection data (NSURLConnection's thread) 753 | // - Creation of the AudioStreamer (other, likely "main" thread) 754 | // - Invocation of -start method (other, likely "main" thread) 755 | // - User/manual invocation of -stop (other, likely "main" thread) 756 | // 757 | // This method contains bits of the "main" function from Apple's example in 758 | // AudioFileStreamExample. 759 | // 760 | - (void)startInternal 761 | { 762 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 763 | 764 | @synchronized(self) 765 | { 766 | if (state != AS_STARTING_FILE_THREAD) 767 | { 768 | if (state != AS_STOPPING && 769 | state != AS_STOPPED) 770 | { 771 | NSLog(@"### Not starting audio thread. State code is: %ld", (long)state); 772 | } 773 | self.state = AS_INITIALIZED; 774 | [pool release]; 775 | return; 776 | } 777 | 778 | #if TARGET_OS_IPHONE 779 | // 780 | // Set the audio session category so that we continue to play if the 781 | // iPhone/iPod auto-locks. 782 | // 783 | AudioSessionInitialize ( 784 | NULL, // 'NULL' to use the default (main) run loop 785 | NULL, // 'NULL' to use the default run loop mode 786 | ASAudioSessionInterruptionListener, // a reference to your interruption callback 787 | self // data to pass to your interruption listener callback 788 | ); 789 | UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback; 790 | AudioSessionSetProperty ( 791 | kAudioSessionProperty_AudioCategory, 792 | sizeof (sessionCategory), 793 | &sessionCategory 794 | ); 795 | AudioSessionSetActive(true); 796 | #endif 797 | 798 | // initialize a mutex and condition so that we can block on buffers in use. 799 | pthread_mutex_init(&queueBuffersMutex, NULL); 800 | pthread_cond_init(&queueBufferReadyCondition, NULL); 801 | 802 | if (![self openReadStream]) 803 | { 804 | goto cleanup; 805 | } 806 | } 807 | 808 | // 809 | // Process the run loop until playback is finished or failed. 810 | // 811 | BOOL isRunning = YES; 812 | do 813 | { 814 | isRunning = [[NSRunLoop currentRunLoop] 815 | runMode:NSDefaultRunLoopMode 816 | beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.25]]; 817 | 818 | @synchronized(self) { 819 | if (seekWasRequested) { 820 | [self internalSeekToTime:requestedSeekTime]; 821 | seekWasRequested = NO; 822 | } 823 | } 824 | 825 | // 826 | // If there are no queued buffers, we need to check here since the 827 | // handleBufferCompleteForQueue:buffer: should not change the state 828 | // (may not enter the synchronized section). 829 | // 830 | if (buffersUsed == 0 && self.state == AS_PLAYING) 831 | { 832 | err = AudioQueuePause(audioQueue); 833 | if (err) 834 | { 835 | [self failWithErrorCode:AS_AUDIO_QUEUE_PAUSE_FAILED]; 836 | return; 837 | } 838 | self.state = AS_BUFFERING; 839 | } 840 | } while (isRunning && ![self runLoopShouldExit]); 841 | 842 | cleanup: 843 | 844 | @synchronized(self) 845 | { 846 | // 847 | // Cleanup the read stream if it is still open 848 | // 849 | if (stream) 850 | { 851 | CFReadStreamClose(stream); 852 | CFRelease(stream); 853 | stream = nil; 854 | } 855 | 856 | // 857 | // Close the audio file strea, 858 | // 859 | if (audioFileStream) 860 | { 861 | err = AudioFileStreamClose(audioFileStream); 862 | audioFileStream = nil; 863 | if (err) 864 | { 865 | [self failWithErrorCode:AS_FILE_STREAM_CLOSE_FAILED]; 866 | } 867 | } 868 | 869 | // 870 | // Dispose of the Audio Queue 871 | // 872 | if (audioQueue) 873 | { 874 | err = AudioQueueDispose(audioQueue, true); 875 | audioQueue = nil; 876 | if (err) 877 | { 878 | [self failWithErrorCode:AS_AUDIO_QUEUE_DISPOSE_FAILED]; 879 | } 880 | } 881 | 882 | pthread_mutex_destroy(&queueBuffersMutex); 883 | pthread_cond_destroy(&queueBufferReadyCondition); 884 | 885 | #if TARGET_OS_IPHONE 886 | AudioSessionSetActive(false); 887 | #endif 888 | 889 | [httpHeaders release]; 890 | httpHeaders = nil; 891 | 892 | bytesFilled = 0; 893 | packetsFilled = 0; 894 | seekByteOffset = 0; 895 | packetBufferSize = 0; 896 | self.state = AS_INITIALIZED; 897 | 898 | [internalThread release]; 899 | internalThread = nil; 900 | } 901 | 902 | [pool release]; 903 | } 904 | 905 | // 906 | // start 907 | // 908 | // Calls startInternal in a new thread. 909 | // 910 | - (void)start 911 | { 912 | @synchronized (self) 913 | { 914 | if (state == AS_PAUSED) 915 | { 916 | [self pause]; 917 | } 918 | else if (state == AS_INITIALIZED) 919 | { 920 | NSAssert([[NSThread currentThread] isEqual:[NSThread mainThread]], 921 | @"Playback can only be started from the main thread."); 922 | notificationCenter = 923 | [[NSNotificationCenter defaultCenter] retain]; 924 | self.state = AS_STARTING_FILE_THREAD; 925 | internalThread = 926 | [[NSThread alloc] 927 | initWithTarget:self 928 | selector:@selector(startInternal) 929 | object:nil]; 930 | [internalThread start]; 931 | } 932 | } 933 | } 934 | 935 | 936 | // internalSeekToTime: 937 | // 938 | // Called from our internal runloop to reopen the stream at a seeked location 939 | // 940 | - (void)internalSeekToTime:(double)newSeekTime 941 | { 942 | if ([self calculatedBitRate] == 0.0 || fileLength <= 0) 943 | { 944 | return; 945 | } 946 | 947 | // 948 | // Calculate the byte offset for seeking 949 | // 950 | seekByteOffset = dataOffset + 951 | (newSeekTime / self.duration) * (fileLength - dataOffset); 952 | 953 | // 954 | // Attempt to leave 1 useful packet at the end of the file (although in 955 | // reality, this may still seek too far if the file has a long trailer). 956 | // 957 | if (seekByteOffset > fileLength - 2 * packetBufferSize) 958 | { 959 | seekByteOffset = fileLength - 2 * packetBufferSize; 960 | } 961 | 962 | // 963 | // Store the old time from the audio queue and the time that we're seeking 964 | // to so that we'll know the correct time progress after seeking. 965 | // 966 | seekTime = newSeekTime; 967 | 968 | // 969 | // Attempt to align the seek with a packet boundary 970 | // 971 | double calculatedBitRate = [self calculatedBitRate]; 972 | if (packetDuration > 0 && 973 | calculatedBitRate > 0) 974 | { 975 | UInt32 ioFlags = 0; 976 | SInt64 packetAlignedByteOffset; 977 | SInt64 seekPacket = floor(newSeekTime / packetDuration); 978 | err = AudioFileStreamSeek(audioFileStream, seekPacket, &packetAlignedByteOffset, &ioFlags); 979 | if (!err && !(ioFlags & kAudioFileStreamSeekFlag_OffsetIsEstimated)) 980 | { 981 | seekTime -= ((seekByteOffset - dataOffset) - packetAlignedByteOffset) * 8.0 / calculatedBitRate; 982 | seekByteOffset = packetAlignedByteOffset + dataOffset; 983 | } 984 | } 985 | 986 | // 987 | // Close the current read straem 988 | // 989 | if (stream) 990 | { 991 | CFReadStreamClose(stream); 992 | CFRelease(stream); 993 | stream = nil; 994 | } 995 | 996 | // 997 | // Stop the audio queue 998 | // 999 | self.state = AS_STOPPING; 1000 | stopReason = AS_STOPPING_TEMPORARILY; 1001 | err = AudioQueueStop(audioQueue, true); 1002 | if (err) 1003 | { 1004 | [self failWithErrorCode:AS_AUDIO_QUEUE_STOP_FAILED]; 1005 | return; 1006 | } 1007 | 1008 | // 1009 | // Re-open the file stream. It will request a byte-range starting at 1010 | // seekByteOffset. 1011 | // 1012 | [self openReadStream]; 1013 | } 1014 | 1015 | // 1016 | // seekToTime: 1017 | // 1018 | // Attempts to seek to the new time. Will be ignored if the bitrate or fileLength 1019 | // are unknown. 1020 | // 1021 | // Parameters: 1022 | // newTime - the time to seek to 1023 | // 1024 | - (void)seekToTime:(double)newSeekTime 1025 | { 1026 | @synchronized(self) 1027 | { 1028 | seekWasRequested = YES; 1029 | requestedSeekTime = newSeekTime; 1030 | } 1031 | } 1032 | 1033 | // 1034 | // progress 1035 | // 1036 | // returns the current playback progress. Will return zero if sampleRate has 1037 | // not yet been detected. 1038 | // 1039 | - (double)progress 1040 | { 1041 | @synchronized(self) 1042 | { 1043 | if (sampleRate > 0 && (state == AS_STOPPING || ![self isFinishing])) 1044 | { 1045 | if (state != AS_PLAYING && state != AS_PAUSED && state != AS_BUFFERING && state != AS_STOPPING) 1046 | { 1047 | return lastProgress; 1048 | } 1049 | 1050 | AudioTimeStamp queueTime; 1051 | Boolean discontinuity; 1052 | err = AudioQueueGetCurrentTime(audioQueue, NULL, &queueTime, &discontinuity); 1053 | 1054 | const OSStatus AudioQueueStopped = 0x73746F70; // 0x73746F70 is 'stop' 1055 | if (err == AudioQueueStopped) 1056 | { 1057 | return lastProgress; 1058 | } 1059 | else if (err) 1060 | { 1061 | [self failWithErrorCode:AS_GET_AUDIO_TIME_FAILED]; 1062 | } 1063 | 1064 | double progress = seekTime + queueTime.mSampleTime / sampleRate; 1065 | if (progress < 0.0) 1066 | { 1067 | progress = 0.0; 1068 | } 1069 | 1070 | lastProgress = progress; 1071 | return progress; 1072 | } 1073 | } 1074 | 1075 | return lastProgress; 1076 | } 1077 | 1078 | // 1079 | // calculatedBitRate 1080 | // 1081 | // returns the bit rate, if known. Uses packet duration times running bits per 1082 | // packet if available, otherwise it returns the nominal bitrate. Will return 1083 | // zero if no useful option available. 1084 | // 1085 | - (double)calculatedBitRate 1086 | { 1087 | if (packetDuration && processedPacketsCount > BitRateEstimationMinPackets) 1088 | { 1089 | double averagePacketByteSize = processedPacketsSizeTotal / processedPacketsCount; 1090 | return 8.0 * averagePacketByteSize / packetDuration; 1091 | } 1092 | 1093 | if (bitRate) 1094 | { 1095 | return (double)bitRate; 1096 | } 1097 | 1098 | return 0; 1099 | } 1100 | 1101 | // 1102 | // duration 1103 | // 1104 | // Calculates the duration of available audio from the bitRate and fileLength. 1105 | // 1106 | // returns the calculated duration in seconds. 1107 | // 1108 | - (double)duration 1109 | { 1110 | double calculatedBitRate = [self calculatedBitRate]; 1111 | 1112 | if (calculatedBitRate == 0 || fileLength == 0) 1113 | { 1114 | return 0.0; 1115 | } 1116 | 1117 | return (fileLength - dataOffset) / (calculatedBitRate * 0.125); 1118 | } 1119 | 1120 | // 1121 | // pause 1122 | // 1123 | // A togglable pause function. 1124 | // 1125 | - (void)pause 1126 | { 1127 | @synchronized(self) 1128 | { 1129 | if (state == AS_PLAYING || state == AS_STOPPING) 1130 | { 1131 | err = AudioQueuePause(audioQueue); 1132 | if (err) 1133 | { 1134 | [self failWithErrorCode:AS_AUDIO_QUEUE_PAUSE_FAILED]; 1135 | return; 1136 | } 1137 | self.laststate = state; 1138 | self.state = AS_PAUSED; 1139 | } 1140 | else if (state == AS_PAUSED) 1141 | { 1142 | err = AudioQueueStart(audioQueue, NULL); 1143 | if (err) 1144 | { 1145 | [self failWithErrorCode:AS_AUDIO_QUEUE_START_FAILED]; 1146 | return; 1147 | } 1148 | self.state = self.laststate; 1149 | } 1150 | } 1151 | } 1152 | 1153 | // 1154 | // stop 1155 | // 1156 | // This method can be called to stop downloading/playback before it completes. 1157 | // It is automatically called when an error occurs. 1158 | // 1159 | // If playback has not started before this method is called, it will toggle the 1160 | // "isPlaying" property so that it is guaranteed to transition to true and 1161 | // back to false 1162 | // 1163 | - (void)stop 1164 | { 1165 | @synchronized(self) 1166 | { 1167 | if (audioQueue && 1168 | (state == AS_PLAYING || state == AS_PAUSED || 1169 | state == AS_BUFFERING || state == AS_WAITING_FOR_QUEUE_TO_START)) 1170 | { 1171 | self.state = AS_STOPPING; 1172 | stopReason = AS_STOPPING_USER_ACTION; 1173 | err = AudioQueueStop(audioQueue, true); 1174 | if (err) 1175 | { 1176 | [self failWithErrorCode:AS_AUDIO_QUEUE_STOP_FAILED]; 1177 | return; 1178 | } 1179 | } 1180 | else if (state != AS_INITIALIZED) 1181 | { 1182 | self.state = AS_STOPPED; 1183 | stopReason = AS_STOPPING_USER_ACTION; 1184 | } 1185 | seekWasRequested = NO; 1186 | } 1187 | 1188 | while (state != AS_INITIALIZED) 1189 | { 1190 | [NSThread sleepForTimeInterval:0.1]; 1191 | } 1192 | } 1193 | 1194 | // 1195 | // handleReadFromStream:eventType: 1196 | // 1197 | // Reads data from the network file stream into the AudioFileStream 1198 | // 1199 | // Parameters: 1200 | // aStream - the network file stream 1201 | // eventType - the event which triggered this method 1202 | // 1203 | - (void)handleReadFromStream:(CFReadStreamRef)aStream 1204 | eventType:(CFStreamEventType)eventType 1205 | { 1206 | if (aStream != stream) 1207 | { 1208 | // 1209 | // Ignore messages from old streams 1210 | // 1211 | return; 1212 | } 1213 | 1214 | if (eventType == kCFStreamEventErrorOccurred) 1215 | { 1216 | [self failWithErrorCode:AS_AUDIO_DATA_NOT_FOUND]; 1217 | } 1218 | else if (eventType == kCFStreamEventEndEncountered) 1219 | { 1220 | @synchronized(self) 1221 | { 1222 | if ([self isFinishing]) 1223 | { 1224 | return; 1225 | } 1226 | } 1227 | 1228 | // 1229 | // If there is a partially filled buffer, pass it to the AudioQueue for 1230 | // processing 1231 | // 1232 | if (bytesFilled) 1233 | { 1234 | if (self.state == AS_WAITING_FOR_DATA) 1235 | { 1236 | // 1237 | // Force audio data smaller than one whole buffer to play. 1238 | // 1239 | self.state = AS_FLUSHING_EOF; 1240 | } 1241 | [self enqueueBuffer]; 1242 | } 1243 | 1244 | @synchronized(self) 1245 | { 1246 | if (state == AS_WAITING_FOR_DATA) 1247 | { 1248 | [self failWithErrorCode:AS_AUDIO_DATA_NOT_FOUND]; 1249 | } 1250 | 1251 | // 1252 | // We left the synchronized section to enqueue the buffer so we 1253 | // must check that we are !finished again before touching the 1254 | // audioQueue 1255 | // 1256 | else if (![self isFinishing]) 1257 | { 1258 | if (audioQueue) 1259 | { 1260 | // 1261 | // Set the progress at the end of the stream 1262 | // 1263 | err = AudioQueueFlush(audioQueue); 1264 | if (err) 1265 | { 1266 | [self failWithErrorCode:AS_AUDIO_QUEUE_FLUSH_FAILED]; 1267 | return; 1268 | } 1269 | 1270 | self.state = AS_STOPPING; 1271 | stopReason = AS_STOPPING_EOF; 1272 | err = AudioQueueStop(audioQueue, false); 1273 | if (err) 1274 | { 1275 | [self failWithErrorCode:AS_AUDIO_QUEUE_FLUSH_FAILED]; 1276 | return; 1277 | } 1278 | } 1279 | else 1280 | { 1281 | self.state = AS_STOPPED; 1282 | stopReason = AS_STOPPING_EOF; 1283 | } 1284 | } 1285 | } 1286 | } 1287 | else if (eventType == kCFStreamEventHasBytesAvailable) 1288 | { 1289 | if (!httpHeaders) 1290 | { 1291 | CFTypeRef message = 1292 | CFReadStreamCopyProperty(stream, kCFStreamPropertyHTTPResponseHeader); 1293 | httpHeaders = 1294 | (NSDictionary *)CFHTTPMessageCopyAllHeaderFields((CFHTTPMessageRef)message); 1295 | CFRelease(message); 1296 | 1297 | // 1298 | // Only read the content length if we seeked to time zero, otherwise 1299 | // we only have a subset of the total bytes. 1300 | // 1301 | if (seekByteOffset == 0) 1302 | { 1303 | fileLength = [[httpHeaders objectForKey:@"Content-Length"] integerValue]; 1304 | } 1305 | } 1306 | 1307 | if (!audioFileStream) 1308 | { 1309 | // 1310 | // Attempt to guess the file type from the URL. Reading the MIME type 1311 | // from the httpHeaders might be a better approach since lots of 1312 | // URL's don't have the right extension. 1313 | // 1314 | // If you have a fixed file-type, you may want to hardcode this. 1315 | // 1316 | if (!self.fileExtension) 1317 | { 1318 | self.fileExtension = [[url path] pathExtension]; 1319 | } 1320 | AudioFileTypeID fileTypeHint = 1321 | [AudioStreamer hintForFileExtension:self.fileExtension]; 1322 | 1323 | // create an audio file stream parser 1324 | err = AudioFileStreamOpen(self, ASPropertyListenerProc, ASPacketsProc, 1325 | fileTypeHint, &audioFileStream); 1326 | if (err) 1327 | { 1328 | [self failWithErrorCode:AS_FILE_STREAM_OPEN_FAILED]; 1329 | return; 1330 | } 1331 | } 1332 | 1333 | UInt8 bytes[kAQDefaultBufSize]; 1334 | CFIndex length; 1335 | @synchronized(self) 1336 | { 1337 | if ([self isFinishing] || !CFReadStreamHasBytesAvailable(stream)) 1338 | { 1339 | return; 1340 | } 1341 | 1342 | // 1343 | // Read the bytes from the stream 1344 | // 1345 | length = CFReadStreamRead(stream, bytes, kAQDefaultBufSize); 1346 | 1347 | if (length == -1) 1348 | { 1349 | [self failWithErrorCode:AS_AUDIO_DATA_NOT_FOUND]; 1350 | return; 1351 | } 1352 | 1353 | if (length == 0) 1354 | { 1355 | return; 1356 | } 1357 | } 1358 | 1359 | if (discontinuous) 1360 | { 1361 | err = AudioFileStreamParseBytes(audioFileStream, length, bytes, kAudioFileStreamParseFlag_Discontinuity); 1362 | if (err) 1363 | { 1364 | [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; 1365 | return; 1366 | } 1367 | } 1368 | else 1369 | { 1370 | err = AudioFileStreamParseBytes(audioFileStream, length, bytes, 0); 1371 | if (err) 1372 | { 1373 | [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; 1374 | return; 1375 | } 1376 | } 1377 | } 1378 | } 1379 | 1380 | // 1381 | // enqueueBuffer 1382 | // 1383 | // Called from ASPacketsProc and connectionDidFinishLoading to pass filled audio 1384 | // bufffers (filled by ASPacketsProc) to the AudioQueue for playback. This 1385 | // function does not return until a buffer is idle for further filling or 1386 | // the AudioQueue is stopped. 1387 | // 1388 | // This function is adapted from Apple's example in AudioFileStreamExample with 1389 | // CBR functionality added. 1390 | // 1391 | - (void)enqueueBuffer 1392 | { 1393 | @synchronized(self) 1394 | { 1395 | if ([self isFinishing] || stream == 0) 1396 | { 1397 | return; 1398 | } 1399 | 1400 | inuse[fillBufferIndex] = true; // set in use flag 1401 | buffersUsed++; 1402 | 1403 | // enqueue buffer 1404 | AudioQueueBufferRef fillBuf = audioQueueBuffer[fillBufferIndex]; 1405 | fillBuf->mAudioDataByteSize = bytesFilled; 1406 | 1407 | if (packetsFilled) 1408 | { 1409 | err = AudioQueueEnqueueBuffer(audioQueue, fillBuf, packetsFilled, packetDescs); 1410 | } 1411 | else 1412 | { 1413 | err = AudioQueueEnqueueBuffer(audioQueue, fillBuf, 0, NULL); 1414 | } 1415 | 1416 | if (err) 1417 | { 1418 | [self failWithErrorCode:AS_AUDIO_QUEUE_ENQUEUE_FAILED]; 1419 | return; 1420 | } 1421 | 1422 | 1423 | if (state == AS_BUFFERING || 1424 | state == AS_WAITING_FOR_DATA || 1425 | state == AS_FLUSHING_EOF || 1426 | (state == AS_STOPPED && stopReason == AS_STOPPING_TEMPORARILY)) 1427 | { 1428 | // 1429 | // Fill all the buffers before starting. This ensures that the 1430 | // AudioFileStream stays a small amount ahead of the AudioQueue to 1431 | // avoid an audio glitch playing streaming files on iPhone SDKs < 3.0 1432 | // 1433 | if (state == AS_FLUSHING_EOF || buffersUsed == kNumAQBufs - 1) 1434 | { 1435 | if (self.state == AS_BUFFERING) 1436 | { 1437 | err = AudioQueueStart(audioQueue, NULL); 1438 | if (err) 1439 | { 1440 | [self failWithErrorCode:AS_AUDIO_QUEUE_START_FAILED]; 1441 | return; 1442 | } 1443 | self.state = AS_PLAYING; 1444 | } 1445 | else 1446 | { 1447 | self.state = AS_WAITING_FOR_QUEUE_TO_START; 1448 | 1449 | err = AudioQueueStart(audioQueue, NULL); 1450 | if (err) 1451 | { 1452 | [self failWithErrorCode:AS_AUDIO_QUEUE_START_FAILED]; 1453 | return; 1454 | } 1455 | } 1456 | } 1457 | } 1458 | 1459 | // go to next buffer 1460 | if (++fillBufferIndex >= kNumAQBufs) fillBufferIndex = 0; 1461 | bytesFilled = 0; // reset bytes filled 1462 | packetsFilled = 0; // reset packets filled 1463 | } 1464 | 1465 | // wait until next buffer is not in use 1466 | pthread_mutex_lock(&queueBuffersMutex); 1467 | while (inuse[fillBufferIndex]) 1468 | { 1469 | pthread_cond_wait(&queueBufferReadyCondition, &queueBuffersMutex); 1470 | } 1471 | pthread_mutex_unlock(&queueBuffersMutex); 1472 | } 1473 | 1474 | // 1475 | // createQueue 1476 | // 1477 | // Method to create the AudioQueue from the parameters gathered by the 1478 | // AudioFileStream. 1479 | // 1480 | // Creation is deferred to the handling of the first audio packet (although 1481 | // it could be handled any time after kAudioFileStreamProperty_ReadyToProducePackets 1482 | // is true). 1483 | // 1484 | - (void)createQueue 1485 | { 1486 | sampleRate = asbd.mSampleRate; 1487 | packetDuration = asbd.mFramesPerPacket / sampleRate; 1488 | 1489 | // create the audio queue 1490 | err = AudioQueueNewOutput(&asbd, ASAudioQueueOutputCallback, self, NULL, NULL, 0, &audioQueue); 1491 | if (err) 1492 | { 1493 | [self failWithErrorCode:AS_AUDIO_QUEUE_CREATION_FAILED]; 1494 | return; 1495 | } 1496 | 1497 | // start the queue if it has not been started already 1498 | // listen to the "isRunning" property 1499 | err = AudioQueueAddPropertyListener(audioQueue, kAudioQueueProperty_IsRunning, ASAudioQueueIsRunningCallback, self); 1500 | if (err) 1501 | { 1502 | [self failWithErrorCode:AS_AUDIO_QUEUE_ADD_LISTENER_FAILED]; 1503 | return; 1504 | } 1505 | 1506 | // get the packet size if it is available 1507 | UInt32 sizeOfUInt32 = sizeof(UInt32); 1508 | err = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_PacketSizeUpperBound, &sizeOfUInt32, &packetBufferSize); 1509 | if (err || packetBufferSize == 0) 1510 | { 1511 | err = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_MaximumPacketSize, &sizeOfUInt32, &packetBufferSize); 1512 | if (err || packetBufferSize == 0) 1513 | { 1514 | // No packet size available, just use the default 1515 | packetBufferSize = kAQDefaultBufSize; 1516 | } 1517 | } 1518 | 1519 | // allocate audio queue buffers 1520 | for (unsigned int i = 0; i < kNumAQBufs; ++i) 1521 | { 1522 | err = AudioQueueAllocateBuffer(audioQueue, packetBufferSize, &audioQueueBuffer[i]); 1523 | if (err) 1524 | { 1525 | [self failWithErrorCode:AS_AUDIO_QUEUE_BUFFER_ALLOCATION_FAILED]; 1526 | return; 1527 | } 1528 | } 1529 | 1530 | // get the cookie size 1531 | UInt32 cookieSize; 1532 | Boolean writable; 1533 | OSStatus ignorableError; 1534 | ignorableError = AudioFileStreamGetPropertyInfo(audioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, &writable); 1535 | if (ignorableError) 1536 | { 1537 | return; 1538 | } 1539 | 1540 | // get the cookie data 1541 | void* cookieData = calloc(1, cookieSize); 1542 | ignorableError = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, cookieData); 1543 | if (ignorableError) 1544 | { 1545 | return; 1546 | } 1547 | 1548 | // set the cookie on the queue. 1549 | ignorableError = AudioQueueSetProperty(audioQueue, kAudioQueueProperty_MagicCookie, cookieData, cookieSize); 1550 | free(cookieData); 1551 | if (ignorableError) 1552 | { 1553 | return; 1554 | } 1555 | } 1556 | 1557 | // 1558 | // handlePropertyChangeForFileStream:fileStreamPropertyID:ioFlags: 1559 | // 1560 | // Object method which handles implementation of ASPropertyListenerProc 1561 | // 1562 | // Parameters: 1563 | // inAudioFileStream - should be the same as self->audioFileStream 1564 | // inPropertyID - the property that changed 1565 | // ioFlags - the ioFlags passed in 1566 | // 1567 | - (void)handlePropertyChangeForFileStream:(AudioFileStreamID)inAudioFileStream 1568 | fileStreamPropertyID:(AudioFileStreamPropertyID)inPropertyID 1569 | ioFlags:(UInt32 *)ioFlags 1570 | { 1571 | @synchronized(self) 1572 | { 1573 | if ([self isFinishing]) 1574 | { 1575 | return; 1576 | } 1577 | 1578 | if (inPropertyID == kAudioFileStreamProperty_ReadyToProducePackets) 1579 | { 1580 | discontinuous = true; 1581 | } 1582 | else if (inPropertyID == kAudioFileStreamProperty_DataOffset) 1583 | { 1584 | SInt64 offset; 1585 | UInt32 offsetSize = sizeof(offset); 1586 | err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataOffset, &offsetSize, &offset); 1587 | if (err) 1588 | { 1589 | [self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED]; 1590 | return; 1591 | } 1592 | dataOffset = offset; 1593 | 1594 | if (audioDataByteCount) 1595 | { 1596 | fileLength = dataOffset + audioDataByteCount; 1597 | } 1598 | } 1599 | else if (inPropertyID == kAudioFileStreamProperty_AudioDataByteCount) 1600 | { 1601 | UInt32 byteCountSize = sizeof(UInt64); 1602 | err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_AudioDataByteCount, &byteCountSize, &audioDataByteCount); 1603 | if (err) 1604 | { 1605 | [self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED]; 1606 | return; 1607 | } 1608 | fileLength = dataOffset + audioDataByteCount; 1609 | } 1610 | else if (inPropertyID == kAudioFileStreamProperty_DataFormat) 1611 | { 1612 | if (asbd.mSampleRate == 0) 1613 | { 1614 | UInt32 asbdSize = sizeof(asbd); 1615 | 1616 | // get the stream format. 1617 | err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataFormat, &asbdSize, &asbd); 1618 | if (err) 1619 | { 1620 | [self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED]; 1621 | return; 1622 | } 1623 | } 1624 | } 1625 | else if (inPropertyID == kAudioFileStreamProperty_FormatList) 1626 | { 1627 | Boolean outWriteable; 1628 | UInt32 formatListSize; 1629 | err = AudioFileStreamGetPropertyInfo(inAudioFileStream, kAudioFileStreamProperty_FormatList, &formatListSize, &outWriteable); 1630 | if (err) 1631 | { 1632 | [self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED]; 1633 | return; 1634 | } 1635 | 1636 | AudioFormatListItem *formatList = malloc(formatListSize); 1637 | err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_FormatList, &formatListSize, formatList); 1638 | if (err) 1639 | { 1640 | free(formatList); 1641 | [self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED]; 1642 | return; 1643 | } 1644 | 1645 | for (int i = 0; i * sizeof(AudioFormatListItem) < formatListSize; i += sizeof(AudioFormatListItem)) 1646 | { 1647 | AudioStreamBasicDescription pasbd = formatList[i].mASBD; 1648 | 1649 | if (pasbd.mFormatID == kAudioFormatMPEG4AAC_HE || 1650 | pasbd.mFormatID == kAudioFormatMPEG4AAC_HE_V2) 1651 | { 1652 | // 1653 | // We've found HE-AAC, remember this to tell the audio queue 1654 | // when we construct it. 1655 | // 1656 | #if !TARGET_IPHONE_SIMULATOR 1657 | asbd = pasbd; 1658 | #endif 1659 | break; 1660 | } 1661 | } 1662 | free(formatList); 1663 | } 1664 | else 1665 | { 1666 | // NSLog(@"Property is %c%c%c%c", 1667 | // ((char *)&inPropertyID)[3], 1668 | // ((char *)&inPropertyID)[2], 1669 | // ((char *)&inPropertyID)[1], 1670 | // ((char *)&inPropertyID)[0]); 1671 | } 1672 | } 1673 | } 1674 | 1675 | // 1676 | // handleAudioPackets:numberBytes:numberPackets:packetDescriptions: 1677 | // 1678 | // Object method which handles the implementation of ASPacketsProc 1679 | // 1680 | // Parameters: 1681 | // inInputData - the packet data 1682 | // inNumberBytes - byte size of the data 1683 | // inNumberPackets - number of packets in the data 1684 | // inPacketDescriptions - packet descriptions 1685 | // 1686 | - (void)handleAudioPackets:(const void *)inInputData 1687 | numberBytes:(UInt32)inNumberBytes 1688 | numberPackets:(UInt32)inNumberPackets 1689 | packetDescriptions:(AudioStreamPacketDescription *)inPacketDescriptions; 1690 | { 1691 | @synchronized(self) 1692 | { 1693 | if ([self isFinishing]) 1694 | { 1695 | return; 1696 | } 1697 | 1698 | if (bitRate == 0) 1699 | { 1700 | // 1701 | // m4a and a few other formats refuse to parse the bitrate so 1702 | // we need to set an "unparseable" condition here. If you know 1703 | // the bitrate (parsed it another way) you can set it on the 1704 | // class if needed. 1705 | // 1706 | bitRate = ~0; 1707 | } 1708 | 1709 | // we have successfully read the first packests from the audio stream, so 1710 | // clear the "discontinuous" flag 1711 | if (discontinuous) 1712 | { 1713 | discontinuous = false; 1714 | } 1715 | 1716 | if (!audioQueue) 1717 | { 1718 | [self createQueue]; 1719 | } 1720 | } 1721 | 1722 | // the following code assumes we're streaming VBR data. for CBR data, the second branch is used. 1723 | if (inPacketDescriptions) 1724 | { 1725 | for (int i = 0; i < inNumberPackets; ++i) 1726 | { 1727 | SInt64 packetOffset = inPacketDescriptions[i].mStartOffset; 1728 | SInt64 packetSize = inPacketDescriptions[i].mDataByteSize; 1729 | size_t bufSpaceRemaining; 1730 | 1731 | if (processedPacketsCount < BitRateEstimationMaxPackets) 1732 | { 1733 | processedPacketsSizeTotal += packetSize; 1734 | processedPacketsCount += 1; 1735 | } 1736 | 1737 | @synchronized(self) 1738 | { 1739 | // If the audio was terminated before this point, then 1740 | // exit. 1741 | if ([self isFinishing]) 1742 | { 1743 | return; 1744 | } 1745 | 1746 | if (packetSize > packetBufferSize) 1747 | { 1748 | [self failWithErrorCode:AS_AUDIO_BUFFER_TOO_SMALL]; 1749 | } 1750 | 1751 | bufSpaceRemaining = packetBufferSize - bytesFilled; 1752 | } 1753 | 1754 | // if the space remaining in the buffer is not enough for this packet, then enqueue the buffer. 1755 | if (bufSpaceRemaining < packetSize) 1756 | { 1757 | [self enqueueBuffer]; 1758 | } 1759 | 1760 | @synchronized(self) 1761 | { 1762 | // If the audio was terminated while waiting for a buffer, then 1763 | // exit. 1764 | if ([self isFinishing]) 1765 | { 1766 | return; 1767 | } 1768 | 1769 | // 1770 | // If there was some kind of issue with enqueueBuffer and we didn't 1771 | // make space for the new audio data then back out 1772 | // 1773 | if (bytesFilled + packetSize > packetBufferSize) 1774 | { 1775 | return; 1776 | } 1777 | 1778 | // copy data to the audio queue buffer 1779 | AudioQueueBufferRef fillBuf = audioQueueBuffer[fillBufferIndex]; 1780 | memcpy((char*)fillBuf->mAudioData + bytesFilled, (const char*)inInputData + packetOffset, packetSize); 1781 | 1782 | // fill out packet description 1783 | packetDescs[packetsFilled] = inPacketDescriptions[i]; 1784 | packetDescs[packetsFilled].mStartOffset = bytesFilled; 1785 | // keep track of bytes filled and packets filled 1786 | bytesFilled += packetSize; 1787 | packetsFilled += 1; 1788 | } 1789 | 1790 | // if that was the last free packet description, then enqueue the buffer. 1791 | size_t packetsDescsRemaining = kAQMaxPacketDescs - packetsFilled; 1792 | if (packetsDescsRemaining == 0) { 1793 | [self enqueueBuffer]; 1794 | } 1795 | } 1796 | } 1797 | else 1798 | { 1799 | size_t offset = 0; 1800 | while (inNumberBytes) 1801 | { 1802 | // if the space remaining in the buffer is not enough for this packet, then enqueue the buffer. 1803 | size_t bufSpaceRemaining = kAQDefaultBufSize - bytesFilled; 1804 | if (bufSpaceRemaining < inNumberBytes) 1805 | { 1806 | [self enqueueBuffer]; 1807 | } 1808 | 1809 | @synchronized(self) 1810 | { 1811 | // If the audio was terminated while waiting for a buffer, then 1812 | // exit. 1813 | if ([self isFinishing]) 1814 | { 1815 | return; 1816 | } 1817 | 1818 | bufSpaceRemaining = kAQDefaultBufSize - bytesFilled; 1819 | size_t copySize; 1820 | if (bufSpaceRemaining < inNumberBytes) 1821 | { 1822 | copySize = bufSpaceRemaining; 1823 | } 1824 | else 1825 | { 1826 | copySize = inNumberBytes; 1827 | } 1828 | 1829 | // 1830 | // If there was some kind of issue with enqueueBuffer and we didn't 1831 | // make space for the new audio data then back out 1832 | // 1833 | if (bytesFilled > packetBufferSize) 1834 | { 1835 | return; 1836 | } 1837 | 1838 | // copy data to the audio queue buffer 1839 | AudioQueueBufferRef fillBuf = audioQueueBuffer[fillBufferIndex]; 1840 | memcpy((char*)fillBuf->mAudioData + bytesFilled, (const char*)(inInputData + offset), copySize); 1841 | 1842 | 1843 | // keep track of bytes filled and packets filled 1844 | bytesFilled += copySize; 1845 | packetsFilled = 0; 1846 | inNumberBytes -= copySize; 1847 | offset += copySize; 1848 | } 1849 | } 1850 | } 1851 | } 1852 | 1853 | // 1854 | // handleBufferCompleteForQueue:buffer: 1855 | // 1856 | // Handles the buffer completetion notification from the audio queue 1857 | // 1858 | // Parameters: 1859 | // inAQ - the queue 1860 | // inBuffer - the buffer 1861 | // 1862 | - (void)handleBufferCompleteForQueue:(AudioQueueRef)inAQ 1863 | buffer:(AudioQueueBufferRef)inBuffer 1864 | { 1865 | unsigned int bufIndex = -1; 1866 | for (unsigned int i = 0; i < kNumAQBufs; ++i) 1867 | { 1868 | if (inBuffer == audioQueueBuffer[i]) 1869 | { 1870 | bufIndex = i; 1871 | break; 1872 | } 1873 | } 1874 | 1875 | if (bufIndex == -1) 1876 | { 1877 | [self failWithErrorCode:AS_AUDIO_QUEUE_BUFFER_MISMATCH]; 1878 | pthread_mutex_lock(&queueBuffersMutex); 1879 | pthread_cond_signal(&queueBufferReadyCondition); 1880 | pthread_mutex_unlock(&queueBuffersMutex); 1881 | return; 1882 | } 1883 | 1884 | // signal waiting thread that the buffer is free. 1885 | pthread_mutex_lock(&queueBuffersMutex); 1886 | inuse[bufIndex] = false; 1887 | buffersUsed--; 1888 | 1889 | // 1890 | // Enable this logging to measure how many buffers are queued at any time. 1891 | // 1892 | #if LOG_QUEUED_BUFFERS 1893 | NSLog(@"Queued buffers: %ld", buffersUsed); 1894 | #endif 1895 | 1896 | pthread_cond_signal(&queueBufferReadyCondition); 1897 | pthread_mutex_unlock(&queueBuffersMutex); 1898 | } 1899 | 1900 | - (void)handlePropertyChange:(NSNumber *)num 1901 | { 1902 | [self handlePropertyChangeForQueue:NULL propertyID:[num intValue]]; 1903 | } 1904 | 1905 | // 1906 | // handlePropertyChangeForQueue:propertyID: 1907 | // 1908 | // Implementation for ASAudioQueueIsRunningCallback 1909 | // 1910 | // Parameters: 1911 | // inAQ - the audio queue 1912 | // inID - the property ID 1913 | // 1914 | - (void)handlePropertyChangeForQueue:(AudioQueueRef)inAQ 1915 | propertyID:(AudioQueuePropertyID)inID 1916 | { 1917 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 1918 | 1919 | if (![[NSThread currentThread] isEqual:internalThread]) 1920 | { 1921 | [self 1922 | performSelector:@selector(handlePropertyChange:) 1923 | onThread:internalThread 1924 | withObject:[NSNumber numberWithInt:inID] 1925 | waitUntilDone:NO 1926 | modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]]; 1927 | return; 1928 | } 1929 | @synchronized(self) 1930 | { 1931 | if (inID == kAudioQueueProperty_IsRunning) 1932 | { 1933 | if (state == AS_STOPPING) 1934 | { 1935 | // Should check value of isRunning to ensure this kAudioQueueProperty_IsRunning isn't 1936 | // the *start* of a very short stream 1937 | UInt32 isRunning = 0; 1938 | UInt32 size = sizeof(UInt32); 1939 | AudioQueueGetProperty(audioQueue, inID, &isRunning, &size); 1940 | if (isRunning == 0) 1941 | { 1942 | self.state = AS_STOPPED; 1943 | } 1944 | } 1945 | else if (state == AS_WAITING_FOR_QUEUE_TO_START) 1946 | { 1947 | // 1948 | // Note about this bug avoidance quirk: 1949 | // 1950 | // On cleanup of the AudioQueue thread, on rare occasions, there would 1951 | // be a crash in CFSetContainsValue as a CFRunLoopObserver was getting 1952 | // removed from the CFRunLoop. 1953 | // 1954 | // After lots of testing, it appeared that the audio thread was 1955 | // attempting to remove CFRunLoop observers from the CFRunLoop after the 1956 | // thread had already deallocated the run loop. 1957 | // 1958 | // By creating an NSRunLoop for the AudioQueue thread, it changes the 1959 | // thread destruction order and seems to avoid this crash bug -- or 1960 | // at least I haven't had it since (nasty hard to reproduce error!) 1961 | // 1962 | [NSRunLoop currentRunLoop]; 1963 | 1964 | self.state = AS_PLAYING; 1965 | } 1966 | else 1967 | { 1968 | NSLog(@"AudioQueue changed state in unexpected way."); 1969 | } 1970 | } 1971 | } 1972 | 1973 | [pool release]; 1974 | } 1975 | 1976 | #if TARGET_OS_IPHONE 1977 | // 1978 | // handleInterruptionChangeForQueue:propertyID: 1979 | // 1980 | // Implementation for ASAudioQueueInterruptionListener 1981 | // 1982 | // Parameters: 1983 | // inAQ - the audio queue 1984 | // inID - the property ID 1985 | // 1986 | - (void)handleInterruptionChangeToState:(NSNotification *)notification { 1987 | AudioQueuePropertyID inInterruptionState = (AudioQueuePropertyID) [notification.object unsignedIntValue]; 1988 | if (inInterruptionState == kAudioSessionBeginInterruption) 1989 | { 1990 | if ([self isPlaying]) { 1991 | [self pause]; 1992 | 1993 | pausedByInterruption = YES; 1994 | } 1995 | } 1996 | else if (inInterruptionState == kAudioSessionEndInterruption) 1997 | { 1998 | AudioSessionSetActive( true ); 1999 | 2000 | if ([self isPaused] && pausedByInterruption) { 2001 | [self pause]; // this is actually resume 2002 | 2003 | pausedByInterruption = NO; // this is redundant 2004 | } 2005 | } 2006 | } 2007 | #endif 2008 | 2009 | @end 2010 | 2011 | 2012 | -------------------------------------------------------------------------------- /Classes/MacStreamingPlayerController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MacStreamingPlayerController.h 3 | // MacStreamingPlayer 4 | // 5 | // Created by Matt Gallagher on 28/10/08. 6 | // Copyright Matt Gallagher 2008. All rights reserved. 7 | // 8 | // This software is provided 'as-is', without any express or implied 9 | // warranty. In no event will the authors be held liable for any damages 10 | // arising from the use of this software. Permission is granted to anyone to 11 | // use this software for any purpose, including commercial applications, and to 12 | // alter it and redistribute it freely, subject to the following restrictions: 13 | // 14 | // 1. The origin of this software must not be misrepresented; you must not 15 | // claim that you wrote the original software. If you use this software 16 | // in a product, an acknowledgment in the product documentation would be 17 | // appreciated but is not required. 18 | // 2. Altered source versions must be plainly marked as such, and must not be 19 | // misrepresented as being the original software. 20 | // 3. This notice may not be removed or altered from any source 21 | // distribution. 22 | // 23 | 24 | #import 25 | 26 | @class AudioStreamer; 27 | 28 | @interface MacStreamingPlayerController : NSObject 29 | { 30 | IBOutlet NSTextField *downloadSourceField; 31 | IBOutlet NSButton *button; 32 | IBOutlet NSTextField *positionLabel; 33 | IBOutlet NSSlider *progressSlider; 34 | AudioStreamer *streamer; 35 | NSTimer *progressUpdateTimer; 36 | } 37 | 38 | - (IBAction)buttonPressed:(id)sender; 39 | - (void)spinButton; 40 | - (void)updateProgress:(NSTimer *)aNotification; 41 | - (IBAction)sliderMoved:(NSSlider *)aSlider; 42 | 43 | @end 44 | 45 | -------------------------------------------------------------------------------- /Classes/MacStreamingPlayerController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MacStreamingPlayerController.m 3 | // MacStreamingPlayer 4 | // 5 | // Created by Matt Gallagher on 28/10/08. 6 | // Copyright Matt Gallagher 2008. All rights reserved. 7 | // 8 | // This software is provided 'as-is', without any express or implied 9 | // warranty. In no event will the authors be held liable for any damages 10 | // arising from the use of this software. Permission is granted to anyone to 11 | // use this software for any purpose, including commercial applications, and to 12 | // alter it and redistribute it freely, subject to the following restrictions: 13 | // 14 | // 1. The origin of this software must not be misrepresented; you must not 15 | // claim that you wrote the original software. If you use this software 16 | // in a product, an acknowledgment in the product documentation would be 17 | // appreciated but is not required. 18 | // 2. Altered source versions must be plainly marked as such, and must not be 19 | // misrepresented as being the original software. 20 | // 3. This notice may not be removed or altered from any source 21 | // distribution. 22 | // 23 | 24 | #import "MacStreamingPlayerController.h" 25 | #import "AudioStreamer.h" 26 | #import 27 | 28 | @implementation MacStreamingPlayerController 29 | 30 | - (void)awakeFromNib 31 | { 32 | [downloadSourceField setStringValue:@"http://192.168.1.2/~matt/inside.m4a"]; 33 | } 34 | 35 | // 36 | // setButtonImage: 37 | // 38 | // Used to change the image on the playbutton. This method exists for 39 | // the purpose of inter-thread invocation because 40 | // the observeValueForKeyPath:ofObject:change:context: method is invoked 41 | // from secondary threads and UI updates are only permitted on the main thread. 42 | // 43 | // Parameters: 44 | // image - the image to set on the play button. 45 | // 46 | - (void)setButtonImage:(NSImage *)image 47 | { 48 | [button.layer removeAllAnimations]; 49 | if (!image) 50 | { 51 | [button setImage:[NSImage imageNamed:@"playbutton"]]; 52 | } 53 | else 54 | { 55 | [button setImage:image]; 56 | 57 | if ([button.image isEqual:[NSImage imageNamed:@"loadingbutton"]]) 58 | { 59 | [self spinButton]; 60 | } 61 | } 62 | } 63 | 64 | // 65 | // destroyStreamer 66 | // 67 | // Removes the streamer, the UI update timer and the change notification 68 | // 69 | - (void)destroyStreamer 70 | { 71 | if (streamer) 72 | { 73 | [[NSNotificationCenter defaultCenter] 74 | removeObserver:self 75 | name:ASStatusChangedNotification 76 | object:streamer]; 77 | [progressUpdateTimer invalidate]; 78 | progressUpdateTimer = nil; 79 | 80 | [streamer stop]; 81 | [streamer release]; 82 | streamer = nil; 83 | } 84 | } 85 | 86 | // 87 | // createStreamer 88 | // 89 | // Creates or recreates the AudioStreamer object. 90 | // 91 | - (void)createStreamer 92 | { 93 | if (streamer) 94 | { 95 | return; 96 | } 97 | 98 | [self destroyStreamer]; 99 | 100 | NSString *escapedValue = 101 | [(NSString *)CFURLCreateStringByAddingPercentEscapes( 102 | nil, 103 | (CFStringRef)[downloadSourceField stringValue], 104 | NULL, 105 | NULL, 106 | kCFStringEncodingUTF8) 107 | autorelease]; 108 | 109 | NSURL *url = [NSURL URLWithString:escapedValue]; 110 | streamer = [[AudioStreamer alloc] initWithURL:url]; 111 | 112 | progressUpdateTimer = 113 | [NSTimer 114 | scheduledTimerWithTimeInterval:0.1 115 | target:self 116 | selector:@selector(updateProgress:) 117 | userInfo:nil 118 | repeats:YES]; 119 | [[NSNotificationCenter defaultCenter] 120 | addObserver:self 121 | selector:@selector(playbackStateChanged:) 122 | name:ASStatusChangedNotification 123 | object:streamer]; 124 | } 125 | 126 | // 127 | // spinButton 128 | // 129 | // Shows the spin button when the audio is loading. This is largely irrelevant 130 | // now that the audio is loaded from a local file. 131 | // 132 | - (void)spinButton 133 | { 134 | [CATransaction begin]; 135 | [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; 136 | CGRect frame = NSRectToCGRect([button frame]); 137 | button.layer.anchorPoint = CGPointMake(0.5, 0.5); 138 | button.layer.position = CGPointMake(frame.origin.x + 0.5 * frame.size.width, frame.origin.y + 0.5 * frame.size.height); 139 | [CATransaction commit]; 140 | 141 | [CATransaction begin]; 142 | [CATransaction setValue:(id)kCFBooleanFalse forKey:kCATransactionDisableActions]; 143 | [CATransaction setValue:[NSNumber numberWithFloat:2.0] forKey:kCATransactionAnimationDuration]; 144 | 145 | CABasicAnimation *animation; 146 | animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; 147 | animation.fromValue = [NSNumber numberWithFloat:0.0]; 148 | animation.toValue = [NSNumber numberWithFloat:-2 * M_PI]; 149 | animation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionLinear]; 150 | animation.delegate = self; 151 | [button.layer addAnimation:animation forKey:@"rotationAnimation"]; 152 | 153 | [CATransaction commit]; 154 | } 155 | 156 | // 157 | // animationDidStop:finished: 158 | // 159 | // Restarts the spin animation on the button when it ends. Again, this is 160 | // largely irrelevant now that the audio is loaded from a local file. 161 | // 162 | // Parameters: 163 | // theAnimation - the animation that rotated the button. 164 | // finished - is the animation finised? 165 | // 166 | - (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)finished 167 | { 168 | if (finished) 169 | { 170 | [self spinButton]; 171 | } 172 | } 173 | 174 | // 175 | // buttonPressed: 176 | // 177 | // Handles the play/stop button. Creates, observes and starts the 178 | // audio streamer when it is a play button. Stops the audio streamer when 179 | // it isn't. 180 | // 181 | // Parameters: 182 | // sender - normally, the play/stop button. 183 | // 184 | - (IBAction)buttonPressed:(id)sender 185 | { 186 | if ([button.image isEqual:[NSImage imageNamed:@"playbutton"]]) 187 | { 188 | [downloadSourceField resignFirstResponder]; 189 | 190 | [self createStreamer]; 191 | [self setButtonImage:[NSImage imageNamed:@"loadingbutton"]]; 192 | [streamer start]; 193 | } 194 | else 195 | { 196 | [streamer stop]; 197 | } 198 | } 199 | 200 | // 201 | // playbackStateChanged: 202 | // 203 | // Invoked when the AudioStreamer 204 | // reports that its playback status has changed. 205 | // 206 | - (void)playbackStateChanged:(NSNotification *)aNotification 207 | { 208 | if ([streamer isWaiting]) 209 | { 210 | [self setButtonImage:[NSImage imageNamed:@"loadingbutton"]]; 211 | } 212 | else if ([streamer isPlaying]) 213 | { 214 | [self setButtonImage:[NSImage imageNamed:@"stopbutton"]]; 215 | } 216 | else if ([streamer isIdle]) 217 | { 218 | [self destroyStreamer]; 219 | [self setButtonImage:[NSImage imageNamed:@"playbutton"]]; 220 | } 221 | } 222 | 223 | // 224 | // sliderMoved: 225 | // 226 | // Invoked when the user moves the slider 227 | // 228 | // Parameters: 229 | // aSlider - the slider (assumed to be the progress slider) 230 | // 231 | - (IBAction)sliderMoved:(NSSlider *)aSlider 232 | { 233 | if (streamer.duration) 234 | { 235 | double newSeekTime = ([aSlider doubleValue] / 100.0) * streamer.duration; 236 | [streamer seekToTime:newSeekTime]; 237 | } 238 | } 239 | 240 | // 241 | // updateProgress: 242 | // 243 | // Invoked when the AudioStreamer 244 | // reports that its playback progress has changed. 245 | // 246 | - (void)updateProgress:(NSTimer *)updatedTimer 247 | { 248 | if (streamer.bitRate != 0.0) 249 | { 250 | double progress = streamer.progress; 251 | double duration = streamer.duration; 252 | 253 | if (duration > 0) 254 | { 255 | [positionLabel setStringValue: 256 | [NSString stringWithFormat:@"Time Played: %.1f/%.1f seconds", 257 | progress, 258 | duration]]; 259 | [progressSlider setEnabled:YES]; 260 | [progressSlider setDoubleValue:100 * progress / duration]; 261 | } 262 | else 263 | { 264 | [progressSlider setEnabled:NO]; 265 | } 266 | } 267 | else 268 | { 269 | [positionLabel setStringValue:@"Time Played:"]; 270 | } 271 | } 272 | 273 | // 274 | // textFieldShouldReturn: 275 | // 276 | // Dismiss the text field when done is pressed 277 | // 278 | // Parameters: 279 | // sender - the text field 280 | // 281 | // returns YES 282 | // 283 | - (BOOL)textFieldShouldReturn:(NSTextField *)sender 284 | { 285 | [sender resignFirstResponder]; 286 | [self createStreamer]; 287 | return YES; 288 | } 289 | 290 | // 291 | // dealloc 292 | // 293 | // Releases instance memory. 294 | // 295 | - (void)dealloc 296 | { 297 | [self destroyStreamer]; 298 | if (progressUpdateTimer) 299 | { 300 | [progressUpdateTimer invalidate]; 301 | progressUpdateTimer = nil; 302 | } 303 | [super dealloc]; 304 | } 305 | 306 | @end 307 | -------------------------------------------------------------------------------- /Classes/iPhoneStreamingPlayerAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // iPhoneStreamingPlayerAppDelegate.h 3 | // iPhoneStreamingPlayer 4 | // 5 | // Created by Matt Gallagher on 28/10/08. 6 | // Copyright Matt Gallagher 2008. All rights reserved. 7 | // 8 | // This software is provided 'as-is', without any express or implied 9 | // warranty. In no event will the authors be held liable for any damages 10 | // arising from the use of this software. Permission is granted to anyone to 11 | // use this software for any purpose, including commercial applications, and to 12 | // alter it and redistribute it freely, subject to the following restrictions: 13 | // 14 | // 1. The origin of this software must not be misrepresented; you must not 15 | // claim that you wrote the original software. If you use this software 16 | // in a product, an acknowledgment in the product documentation would be 17 | // appreciated but is not required. 18 | // 2. Altered source versions must be plainly marked as such, and must not be 19 | // misrepresented as being the original software. 20 | // 3. This notice may not be removed or altered from any source 21 | // distribution. 22 | // 23 | 24 | #import 25 | 26 | @class iPhoneStreamingPlayerViewController; 27 | 28 | @interface iPhoneStreamingPlayerAppDelegate : NSObject { 29 | UIWindow *window; 30 | iPhoneStreamingPlayerViewController *viewController; 31 | } 32 | 33 | @property (nonatomic, retain) IBOutlet UIWindow *window; 34 | @property (nonatomic, retain) IBOutlet iPhoneStreamingPlayerViewController *viewController; 35 | 36 | @end 37 | 38 | -------------------------------------------------------------------------------- /Classes/iPhoneStreamingPlayerAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // iPhoneStreamingPlayerAppDelegate.m 3 | // iPhoneStreamingPlayer 4 | // 5 | // Created by Matt Gallagher on 28/10/08. 6 | // Copyright Matt Gallagher 2008. All rights reserved. 7 | // 8 | // This software is provided 'as-is', without any express or implied 9 | // warranty. In no event will the authors be held liable for any damages 10 | // arising from the use of this software. Permission is granted to anyone to 11 | // use this software for any purpose, including commercial applications, and to 12 | // alter it and redistribute it freely, subject to the following restrictions: 13 | // 14 | // 1. The origin of this software must not be misrepresented; you must not 15 | // claim that you wrote the original software. If you use this software 16 | // in a product, an acknowledgment in the product documentation would be 17 | // appreciated but is not required. 18 | // 2. Altered source versions must be plainly marked as such, and must not be 19 | // misrepresented as being the original software. 20 | // 3. This notice may not be removed or altered from any source 21 | // distribution. 22 | // 23 | 24 | #import "iPhoneStreamingPlayerAppDelegate.h" 25 | #import "iPhoneStreamingPlayerViewController.h" 26 | 27 | @implementation iPhoneStreamingPlayerAppDelegate 28 | 29 | @synthesize window; 30 | @synthesize viewController; 31 | 32 | - (void)applicationDidFinishLaunching:(UIApplication *)application { 33 | // Override point for customization after app launch 34 | [window addSubview:viewController.view]; 35 | [window makeKeyAndVisible]; 36 | 37 | [viewController buttonPressed:nil]; 38 | } 39 | 40 | 41 | - (void)dealloc { 42 | [viewController release]; 43 | [window release]; 44 | [super dealloc]; 45 | } 46 | 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Classes/iPhoneStreamingPlayerViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // iPhoneStreamingPlayerViewController.h 3 | // iPhoneStreamingPlayer 4 | // 5 | // Created by Matt Gallagher on 28/10/08. 6 | // Copyright Matt Gallagher 2008. All rights reserved. 7 | // 8 | // This software is provided 'as-is', without any express or implied 9 | // warranty. In no event will the authors be held liable for any damages 10 | // arising from the use of this software. Permission is granted to anyone to 11 | // use this software for any purpose, including commercial applications, and to 12 | // alter it and redistribute it freely, subject to the following restrictions: 13 | // 14 | // 1. The origin of this software must not be misrepresented; you must not 15 | // claim that you wrote the original software. If you use this software 16 | // in a product, an acknowledgment in the product documentation would be 17 | // appreciated but is not required. 18 | // 2. Altered source versions must be plainly marked as such, and must not be 19 | // misrepresented as being the original software. 20 | // 3. This notice may not be removed or altered from any source 21 | // distribution. 22 | // 23 | 24 | #import 25 | 26 | @class AudioStreamer; 27 | 28 | @interface iPhoneStreamingPlayerViewController : UIViewController 29 | { 30 | IBOutlet UITextField *downloadSourceField; 31 | IBOutlet UIButton *button; 32 | IBOutlet UIView *volumeSlider; 33 | IBOutlet UILabel *positionLabel; 34 | IBOutlet UISlider *progressSlider; 35 | AudioStreamer *streamer; 36 | NSTimer *progressUpdateTimer; 37 | NSString *currentImageName; 38 | } 39 | 40 | - (IBAction)buttonPressed:(id)sender; 41 | - (void)spinButton; 42 | - (void)updateProgress:(NSTimer *)aNotification; 43 | - (IBAction)sliderMoved:(UISlider *)aSlider; 44 | 45 | @end 46 | 47 | -------------------------------------------------------------------------------- /Classes/iPhoneStreamingPlayerViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // iPhoneStreamingPlayerViewController.m 3 | // iPhoneStreamingPlayer 4 | // 5 | // Created by Matt Gallagher on 28/10/08. 6 | // Copyright Matt Gallagher 2008. All rights reserved. 7 | // 8 | // This software is provided 'as-is', without any express or implied 9 | // warranty. In no event will the authors be held liable for any damages 10 | // arising from the use of this software. Permission is granted to anyone to 11 | // use this software for any purpose, including commercial applications, and to 12 | // alter it and redistribute it freely, subject to the following restrictions: 13 | // 14 | // 1. The origin of this software must not be misrepresented; you must not 15 | // claim that you wrote the original software. If you use this software 16 | // in a product, an acknowledgment in the product documentation would be 17 | // appreciated but is not required. 18 | // 2. Altered source versions must be plainly marked as such, and must not be 19 | // misrepresented as being the original software. 20 | // 3. This notice may not be removed or altered from any source 21 | // distribution. 22 | // 23 | 24 | #import "iPhoneStreamingPlayerViewController.h" 25 | #import "AudioStreamer.h" 26 | #import 27 | #import 28 | #import 29 | 30 | @implementation iPhoneStreamingPlayerViewController 31 | 32 | // 33 | // setButtonImageNamed: 34 | // 35 | // Used to change the image on the playbutton. This method exists for 36 | // the purpose of inter-thread invocation because 37 | // the observeValueForKeyPath:ofObject:change:context: method is invoked 38 | // from secondary threads and UI updates are only permitted on the main thread. 39 | // 40 | // Parameters: 41 | // imageNamed - the name of the image to set on the play button. 42 | // 43 | - (void)setButtonImageNamed:(NSString *)imageName 44 | { 45 | if (!imageName) 46 | { 47 | imageName = @"playButton"; 48 | } 49 | [currentImageName autorelease]; 50 | currentImageName = [imageName retain]; 51 | 52 | UIImage *image = [UIImage imageNamed:imageName]; 53 | 54 | [button.layer removeAllAnimations]; 55 | [button setImage:image forState:0]; 56 | 57 | if ([imageName isEqual:@"loadingbutton.png"]) 58 | { 59 | [self spinButton]; 60 | } 61 | } 62 | 63 | // 64 | // destroyStreamer 65 | // 66 | // Removes the streamer, the UI update timer and the change notification 67 | // 68 | - (void)destroyStreamer 69 | { 70 | if (streamer) 71 | { 72 | [[NSNotificationCenter defaultCenter] 73 | removeObserver:self 74 | name:ASStatusChangedNotification 75 | object:streamer]; 76 | [progressUpdateTimer invalidate]; 77 | progressUpdateTimer = nil; 78 | 79 | [streamer stop]; 80 | [streamer release]; 81 | streamer = nil; 82 | } 83 | } 84 | 85 | // 86 | // createStreamer 87 | // 88 | // Creates or recreates the AudioStreamer object. 89 | // 90 | - (void)createStreamer 91 | { 92 | if (streamer) 93 | { 94 | return; 95 | } 96 | 97 | [self destroyStreamer]; 98 | 99 | NSString *escapedValue = 100 | [(NSString *)CFURLCreateStringByAddingPercentEscapes( 101 | nil, 102 | (CFStringRef)downloadSourceField.text, 103 | NULL, 104 | NULL, 105 | kCFStringEncodingUTF8) 106 | autorelease]; 107 | 108 | NSURL *url = [NSURL URLWithString:escapedValue]; 109 | streamer = [[AudioStreamer alloc] initWithURL:url]; 110 | 111 | progressUpdateTimer = 112 | [NSTimer 113 | scheduledTimerWithTimeInterval:0.1 114 | target:self 115 | selector:@selector(updateProgress:) 116 | userInfo:nil 117 | repeats:YES]; 118 | [[NSNotificationCenter defaultCenter] 119 | addObserver:self 120 | selector:@selector(playbackStateChanged:) 121 | name:ASStatusChangedNotification 122 | object:streamer]; 123 | } 124 | 125 | // 126 | // viewDidLoad 127 | // 128 | // Creates the volume slider, sets the default path for the local file and 129 | // creates the streamer immediately if we already have a file at the local 130 | // location. 131 | // 132 | - (void)viewDidLoad 133 | { 134 | [super viewDidLoad]; 135 | 136 | MPVolumeView *volumeView = [[[MPVolumeView alloc] initWithFrame:volumeSlider.bounds] autorelease]; 137 | [volumeSlider addSubview:volumeView]; 138 | [volumeView sizeToFit]; 139 | 140 | [self setButtonImageNamed:@"playbutton.png"]; 141 | } 142 | 143 | // 144 | // spinButton 145 | // 146 | // Shows the spin button when the audio is loading. This is largely irrelevant 147 | // now that the audio is loaded from a local file. 148 | // 149 | - (void)spinButton 150 | { 151 | [CATransaction begin]; 152 | [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; 153 | CGRect frame = [button frame]; 154 | button.layer.anchorPoint = CGPointMake(0.5, 0.5); 155 | button.layer.position = CGPointMake(frame.origin.x + 0.5 * frame.size.width, frame.origin.y + 0.5 * frame.size.height); 156 | [CATransaction commit]; 157 | 158 | [CATransaction begin]; 159 | [CATransaction setValue:(id)kCFBooleanFalse forKey:kCATransactionDisableActions]; 160 | [CATransaction setValue:[NSNumber numberWithFloat:2.0] forKey:kCATransactionAnimationDuration]; 161 | 162 | CABasicAnimation *animation; 163 | animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; 164 | animation.fromValue = [NSNumber numberWithFloat:0.0]; 165 | animation.toValue = [NSNumber numberWithFloat:2 * M_PI]; 166 | animation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionLinear]; 167 | animation.delegate = self; 168 | [button.layer addAnimation:animation forKey:@"rotationAnimation"]; 169 | 170 | [CATransaction commit]; 171 | } 172 | 173 | // 174 | // animationDidStop:finished: 175 | // 176 | // Restarts the spin animation on the button when it ends. Again, this is 177 | // largely irrelevant now that the audio is loaded from a local file. 178 | // 179 | // Parameters: 180 | // theAnimation - the animation that rotated the button. 181 | // finished - is the animation finised? 182 | // 183 | - (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)finished 184 | { 185 | if (finished) 186 | { 187 | [self spinButton]; 188 | } 189 | } 190 | 191 | // 192 | // buttonPressed: 193 | // 194 | // Handles the play/stop button. Creates, observes and starts the 195 | // audio streamer when it is a play button. Stops the audio streamer when 196 | // it isn't. 197 | // 198 | // Parameters: 199 | // sender - normally, the play/stop button. 200 | // 201 | - (IBAction)buttonPressed:(id)sender 202 | { 203 | if ([currentImageName isEqual:@"playbutton.png"]) 204 | { 205 | [downloadSourceField resignFirstResponder]; 206 | 207 | [self createStreamer]; 208 | [self setButtonImageNamed:@"loadingbutton.png"]; 209 | [streamer start]; 210 | } 211 | else 212 | { 213 | [streamer stop]; 214 | } 215 | } 216 | 217 | // 218 | // sliderMoved: 219 | // 220 | // Invoked when the user moves the slider 221 | // 222 | // Parameters: 223 | // aSlider - the slider (assumed to be the progress slider) 224 | // 225 | - (IBAction)sliderMoved:(UISlider *)aSlider 226 | { 227 | if (streamer.duration) 228 | { 229 | double newSeekTime = (aSlider.value / 100.0) * streamer.duration; 230 | [streamer seekToTime:newSeekTime]; 231 | } 232 | } 233 | 234 | // 235 | // playbackStateChanged: 236 | // 237 | // Invoked when the AudioStreamer 238 | // reports that its playback status has changed. 239 | // 240 | - (void)playbackStateChanged:(NSNotification *)aNotification 241 | { 242 | if ([streamer isWaiting]) 243 | { 244 | [self setButtonImageNamed:@"loadingbutton.png"]; 245 | } 246 | else if ([streamer isPlaying]) 247 | { 248 | [self setButtonImageNamed:@"stopbutton.png"]; 249 | } 250 | else if ([streamer isIdle]) 251 | { 252 | [self destroyStreamer]; 253 | [self setButtonImageNamed:@"playbutton.png"]; 254 | } 255 | } 256 | 257 | // 258 | // updateProgress: 259 | // 260 | // Invoked when the AudioStreamer 261 | // reports that its playback progress has changed. 262 | // 263 | - (void)updateProgress:(NSTimer *)updatedTimer 264 | { 265 | if (streamer.bitRate != 0.0) 266 | { 267 | double progress = streamer.progress; 268 | double duration = streamer.duration; 269 | 270 | if (duration > 0) 271 | { 272 | [positionLabel setText: 273 | [NSString stringWithFormat:@"Time Played: %.1f/%.1f seconds", 274 | progress, 275 | duration]]; 276 | [progressSlider setEnabled:YES]; 277 | [progressSlider setValue:100 * progress / duration]; 278 | } 279 | else 280 | { 281 | [progressSlider setEnabled:NO]; 282 | } 283 | } 284 | else 285 | { 286 | positionLabel.text = @"Time Played:"; 287 | } 288 | } 289 | 290 | // 291 | // textFieldShouldReturn: 292 | // 293 | // Dismiss the text field when done is pressed 294 | // 295 | // Parameters: 296 | // sender - the text field 297 | // 298 | // returns YES 299 | // 300 | - (BOOL)textFieldShouldReturn:(UITextField *)sender 301 | { 302 | [sender resignFirstResponder]; 303 | [self createStreamer]; 304 | return YES; 305 | } 306 | 307 | // 308 | // dealloc 309 | // 310 | // Releases instance memory. 311 | // 312 | - (void)dealloc 313 | { 314 | [self destroyStreamer]; 315 | if (progressUpdateTimer) 316 | { 317 | [progressUpdateTimer invalidate]; 318 | progressUpdateTimer = nil; 319 | } 320 | [super dealloc]; 321 | } 322 | 323 | @end 324 | -------------------------------------------------------------------------------- /Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgallagher/AudioStreamer/2d1e4e0eeed754ac0ddeb85c2484433b2d4310bf/Default-568h@2x.png -------------------------------------------------------------------------------- /English.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgallagher/AudioStreamer/2d1e4e0eeed754ac0ddeb85c2484433b2d4310bf/English.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /Mac Resources/English.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgallagher/AudioStreamer/2d1e4e0eeed754ac0ddeb85c2484433b2d4310bf/Mac Resources/English.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /MacInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.yourcompany.${PRODUCT_NAME:identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | NSMainNibFile 24 | MainMenu 25 | NSPrincipalClass 26 | NSApplication 27 | 28 | 29 | -------------------------------------------------------------------------------- /MacStreamingPlayer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1DDD58160DA1D0A300B32029 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1DDD58140DA1D0A300B32029 /* MainMenu.xib */; }; 11 | 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; }; 12 | 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; 13 | 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; 14 | C9266EB80E8F1CCA00FFA634 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9266EB70E8F1CCA00FFA634 /* AudioToolbox.framework */; }; 15 | C92672FB0E905BBB00FFA634 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C92672FA0E905BBB00FFA634 /* QuartzCore.framework */; }; 16 | C9E673630FE8C6B20033BF43 /* playbutton.png in Resources */ = {isa = PBXBuildFile; fileRef = C9E6735F0FE8C6B20033BF43 /* playbutton.png */; }; 17 | C9E673640FE8C6B20033BF43 /* stopbutton.png in Resources */ = {isa = PBXBuildFile; fileRef = C9E673600FE8C6B20033BF43 /* stopbutton.png */; }; 18 | C9E673650FE8C6B20033BF43 /* loadingbutton.png in Resources */ = {isa = PBXBuildFile; fileRef = C9E673610FE8C6B20033BF43 /* loadingbutton.png */; }; 19 | C9E673660FE8C6B20033BF43 /* pausebutton.png in Resources */ = {isa = PBXBuildFile; fileRef = C9E673620FE8C6B20033BF43 /* pausebutton.png */; }; 20 | C9E673700FE8C6F80033BF43 /* AudioStreamer.m in Sources */ = {isa = PBXBuildFile; fileRef = C9E6736F0FE8C6F80033BF43 /* AudioStreamer.m */; }; 21 | C9E673760FE8C7340033BF43 /* MacStreamingPlayerController.m in Sources */ = {isa = PBXBuildFile; fileRef = C9E673740FE8C7340033BF43 /* MacStreamingPlayerController.m */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | 089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; 26 | 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; 27 | 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = ""; }; 28 | 1DDD58150DA1D0A300B32029 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/MainMenu.xib; sourceTree = ""; }; 29 | 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 30 | 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 31 | 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 32 | 8D1107320486CEB800E47090 /* StreamingAudioPlayer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StreamingAudioPlayer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | C9266EB70E8F1CCA00FFA634 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 34 | C92672FA0E905BBB00FFA634 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = /System/Library/Frameworks/QuartzCore.framework; sourceTree = ""; }; 35 | C9E6735F0FE8C6B20033BF43 /* playbutton.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = playbutton.png; path = "Shared Resources/playbutton.png"; sourceTree = ""; }; 36 | C9E673600FE8C6B20033BF43 /* stopbutton.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = stopbutton.png; path = "Shared Resources/stopbutton.png"; sourceTree = ""; }; 37 | C9E673610FE8C6B20033BF43 /* loadingbutton.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = loadingbutton.png; path = "Shared Resources/loadingbutton.png"; sourceTree = ""; }; 38 | C9E673620FE8C6B20033BF43 /* pausebutton.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = pausebutton.png; path = "Shared Resources/pausebutton.png"; sourceTree = ""; }; 39 | C9E673670FE8C6DA0033BF43 /* MacInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = MacInfo.plist; sourceTree = ""; }; 40 | C9E6736E0FE8C6F80033BF43 /* AudioStreamer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AudioStreamer.h; path = Classes/AudioStreamer.h; sourceTree = ""; }; 41 | C9E6736F0FE8C6F80033BF43 /* AudioStreamer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AudioStreamer.m; path = Classes/AudioStreamer.m; sourceTree = ""; }; 42 | C9E673740FE8C7340033BF43 /* MacStreamingPlayerController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MacStreamingPlayerController.m; path = Classes/MacStreamingPlayerController.m; sourceTree = ""; }; 43 | C9E673750FE8C7340033BF43 /* MacStreamingPlayerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MacStreamingPlayerController.h; path = Classes/MacStreamingPlayerController.h; sourceTree = ""; }; 44 | C9E673770FE8C73E0033BF43 /* MacStreamingPlayer_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MacStreamingPlayer_Prefix.pch; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 8D11072E0486CEB800E47090 /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, 53 | C9266EB80E8F1CCA00FFA634 /* AudioToolbox.framework in Frameworks */, 54 | C92672FB0E905BBB00FFA634 /* QuartzCore.framework in Frameworks */, 55 | ); 56 | runOnlyForDeploymentPostprocessing = 0; 57 | }; 58 | /* End PBXFrameworksBuildPhase section */ 59 | 60 | /* Begin PBXGroup section */ 61 | 080E96DDFE201D6D7F000001 /* Classes */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | C9E673740FE8C7340033BF43 /* MacStreamingPlayerController.m */, 65 | C9E673750FE8C7340033BF43 /* MacStreamingPlayerController.h */, 66 | C9E6736E0FE8C6F80033BF43 /* AudioStreamer.h */, 67 | C9E6736F0FE8C6F80033BF43 /* AudioStreamer.m */, 68 | ); 69 | name = Classes; 70 | sourceTree = ""; 71 | }; 72 | 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | C92672FA0E905BBB00FFA634 /* QuartzCore.framework */, 76 | C9266EB70E8F1CCA00FFA634 /* AudioToolbox.framework */, 77 | 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, 78 | ); 79 | name = "Linked Frameworks"; 80 | sourceTree = ""; 81 | }; 82 | 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 29B97324FDCFA39411CA2CEA /* AppKit.framework */, 86 | 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */, 87 | 29B97325FDCFA39411CA2CEA /* Foundation.framework */, 88 | ); 89 | name = "Other Frameworks"; 90 | sourceTree = ""; 91 | }; 92 | 19C28FACFE9D520D11CA2CBB /* Products */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 8D1107320486CEB800E47090 /* StreamingAudioPlayer.app */, 96 | ); 97 | name = Products; 98 | sourceTree = ""; 99 | }; 100 | 29B97314FDCFA39411CA2CEA /* StreamingAudioPlayer */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 080E96DDFE201D6D7F000001 /* Classes */, 104 | 29B97315FDCFA39411CA2CEA /* Other Sources */, 105 | 29B97317FDCFA39411CA2CEA /* Resources */, 106 | 29B97323FDCFA39411CA2CEA /* Frameworks */, 107 | 19C28FACFE9D520D11CA2CBB /* Products */, 108 | ); 109 | name = StreamingAudioPlayer; 110 | sourceTree = ""; 111 | }; 112 | 29B97315FDCFA39411CA2CEA /* Other Sources */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | C9E673770FE8C73E0033BF43 /* MacStreamingPlayer_Prefix.pch */, 116 | C9E673670FE8C6DA0033BF43 /* MacInfo.plist */, 117 | 29B97316FDCFA39411CA2CEA /* main.m */, 118 | ); 119 | name = "Other Sources"; 120 | sourceTree = ""; 121 | }; 122 | 29B97317FDCFA39411CA2CEA /* Resources */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | C9E6735F0FE8C6B20033BF43 /* playbutton.png */, 126 | C9E673600FE8C6B20033BF43 /* stopbutton.png */, 127 | C9E673610FE8C6B20033BF43 /* loadingbutton.png */, 128 | C9E673620FE8C6B20033BF43 /* pausebutton.png */, 129 | 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */, 130 | 1DDD58140DA1D0A300B32029 /* MainMenu.xib */, 131 | ); 132 | name = Resources; 133 | sourceTree = ""; 134 | }; 135 | 29B97323FDCFA39411CA2CEA /* Frameworks */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */, 139 | 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */, 140 | ); 141 | name = Frameworks; 142 | sourceTree = ""; 143 | }; 144 | /* End PBXGroup section */ 145 | 146 | /* Begin PBXNativeTarget section */ 147 | 8D1107260486CEB800E47090 /* StreamingAudioPlayer */ = { 148 | isa = PBXNativeTarget; 149 | buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "StreamingAudioPlayer" */; 150 | buildPhases = ( 151 | 8D1107290486CEB800E47090 /* Resources */, 152 | 8D11072C0486CEB800E47090 /* Sources */, 153 | 8D11072E0486CEB800E47090 /* Frameworks */, 154 | ); 155 | buildRules = ( 156 | ); 157 | dependencies = ( 158 | ); 159 | name = StreamingAudioPlayer; 160 | productInstallPath = "$(HOME)/Applications"; 161 | productName = StreamingAudioPlayer; 162 | productReference = 8D1107320486CEB800E47090 /* StreamingAudioPlayer.app */; 163 | productType = "com.apple.product-type.application"; 164 | }; 165 | /* End PBXNativeTarget section */ 166 | 167 | /* Begin PBXProject section */ 168 | 29B97313FDCFA39411CA2CEA /* Project object */ = { 169 | isa = PBXProject; 170 | attributes = { 171 | LastUpgradeCheck = 0460; 172 | }; 173 | buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "MacStreamingPlayer" */; 174 | compatibilityVersion = "Xcode 3.2"; 175 | developmentRegion = English; 176 | hasScannedForEncodings = 1; 177 | knownRegions = ( 178 | English, 179 | Japanese, 180 | French, 181 | German, 182 | ); 183 | mainGroup = 29B97314FDCFA39411CA2CEA /* StreamingAudioPlayer */; 184 | projectDirPath = ""; 185 | projectRoot = ""; 186 | targets = ( 187 | 8D1107260486CEB800E47090 /* StreamingAudioPlayer */, 188 | ); 189 | }; 190 | /* End PBXProject section */ 191 | 192 | /* Begin PBXResourcesBuildPhase section */ 193 | 8D1107290486CEB800E47090 /* Resources */ = { 194 | isa = PBXResourcesBuildPhase; 195 | buildActionMask = 2147483647; 196 | files = ( 197 | 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */, 198 | 1DDD58160DA1D0A300B32029 /* MainMenu.xib in Resources */, 199 | C9E673630FE8C6B20033BF43 /* playbutton.png in Resources */, 200 | C9E673640FE8C6B20033BF43 /* stopbutton.png in Resources */, 201 | C9E673650FE8C6B20033BF43 /* loadingbutton.png in Resources */, 202 | C9E673660FE8C6B20033BF43 /* pausebutton.png in Resources */, 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | }; 206 | /* End PBXResourcesBuildPhase section */ 207 | 208 | /* Begin PBXSourcesBuildPhase section */ 209 | 8D11072C0486CEB800E47090 /* Sources */ = { 210 | isa = PBXSourcesBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | 8D11072D0486CEB800E47090 /* main.m in Sources */, 214 | C9E673700FE8C6F80033BF43 /* AudioStreamer.m in Sources */, 215 | C9E673760FE8C7340033BF43 /* MacStreamingPlayerController.m in Sources */, 216 | ); 217 | runOnlyForDeploymentPostprocessing = 0; 218 | }; 219 | /* End PBXSourcesBuildPhase section */ 220 | 221 | /* Begin PBXVariantGroup section */ 222 | 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = { 223 | isa = PBXVariantGroup; 224 | children = ( 225 | 089C165DFE840E0CC02AAC07 /* English */, 226 | ); 227 | name = InfoPlist.strings; 228 | sourceTree = ""; 229 | }; 230 | 1DDD58140DA1D0A300B32029 /* MainMenu.xib */ = { 231 | isa = PBXVariantGroup; 232 | children = ( 233 | 1DDD58150DA1D0A300B32029 /* English */, 234 | ); 235 | name = MainMenu.xib; 236 | sourceTree = ""; 237 | }; 238 | /* End PBXVariantGroup section */ 239 | 240 | /* Begin XCBuildConfiguration section */ 241 | C01FCF4B08A954540054247B /* Debug */ = { 242 | isa = XCBuildConfiguration; 243 | buildSettings = { 244 | ALWAYS_SEARCH_USER_PATHS = NO; 245 | COMBINE_HIDPI_IMAGES = YES; 246 | COPY_PHASE_STRIP = NO; 247 | FRAMEWORK_SEARCH_PATHS = ( 248 | "$(inherited)", 249 | "\"$(SRCROOT)\"", 250 | ); 251 | GCC_DYNAMIC_NO_PIC = NO; 252 | GCC_MODEL_TUNING = G5; 253 | GCC_OPTIMIZATION_LEVEL = 0; 254 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 255 | GCC_PREFIX_HEADER = MacStreamingPlayer_Prefix.pch; 256 | INFOPLIST_FILE = MacInfo.plist; 257 | INSTALL_PATH = "$(HOME)/Applications"; 258 | PRODUCT_NAME = StreamingAudioPlayer; 259 | }; 260 | name = Debug; 261 | }; 262 | C01FCF4C08A954540054247B /* Release */ = { 263 | isa = XCBuildConfiguration; 264 | buildSettings = { 265 | ALWAYS_SEARCH_USER_PATHS = NO; 266 | COMBINE_HIDPI_IMAGES = YES; 267 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 268 | FRAMEWORK_SEARCH_PATHS = ( 269 | "$(inherited)", 270 | "\"$(SRCROOT)\"", 271 | ); 272 | GCC_MODEL_TUNING = G5; 273 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 274 | GCC_PREFIX_HEADER = MacStreamingPlayer_Prefix.pch; 275 | INFOPLIST_FILE = MacInfo.plist; 276 | INSTALL_PATH = "$(HOME)/Applications"; 277 | PRODUCT_NAME = StreamingAudioPlayer; 278 | }; 279 | name = Release; 280 | }; 281 | C01FCF4F08A954540054247B /* Debug */ = { 282 | isa = XCBuildConfiguration; 283 | buildSettings = { 284 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 285 | GCC_C_LANGUAGE_STANDARD = c99; 286 | GCC_OPTIMIZATION_LEVEL = 0; 287 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 288 | GCC_WARN_UNUSED_VARIABLE = YES; 289 | ONLY_ACTIVE_ARCH = YES; 290 | SDKROOT = macosx; 291 | WARNING_CFLAGS = "-Wall"; 292 | }; 293 | name = Debug; 294 | }; 295 | C01FCF5008A954540054247B /* Release */ = { 296 | isa = XCBuildConfiguration; 297 | buildSettings = { 298 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 299 | GCC_C_LANGUAGE_STANDARD = c99; 300 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 301 | GCC_WARN_UNUSED_VARIABLE = YES; 302 | SDKROOT = macosx; 303 | }; 304 | name = Release; 305 | }; 306 | /* End XCBuildConfiguration section */ 307 | 308 | /* Begin XCConfigurationList section */ 309 | C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "StreamingAudioPlayer" */ = { 310 | isa = XCConfigurationList; 311 | buildConfigurations = ( 312 | C01FCF4B08A954540054247B /* Debug */, 313 | C01FCF4C08A954540054247B /* Release */, 314 | ); 315 | defaultConfigurationIsVisible = 0; 316 | defaultConfigurationName = Release; 317 | }; 318 | C01FCF4E08A954540054247B /* Build configuration list for PBXProject "MacStreamingPlayer" */ = { 319 | isa = XCConfigurationList; 320 | buildConfigurations = ( 321 | C01FCF4F08A954540054247B /* Debug */, 322 | C01FCF5008A954540054247B /* Release */, 323 | ); 324 | defaultConfigurationIsVisible = 0; 325 | defaultConfigurationName = Release; 326 | }; 327 | /* End XCConfigurationList section */ 328 | }; 329 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 330 | } 331 | -------------------------------------------------------------------------------- /MacStreamingPlayer_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'StreamingAudioPlayer' target in the 'StreamingAudioPlayer' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /Shared Resources/loadingbutton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgallagher/AudioStreamer/2d1e4e0eeed754ac0ddeb85c2484433b2d4310bf/Shared Resources/loadingbutton.png -------------------------------------------------------------------------------- /Shared Resources/pausebutton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgallagher/AudioStreamer/2d1e4e0eeed754ac0ddeb85c2484433b2d4310bf/Shared Resources/pausebutton.png -------------------------------------------------------------------------------- /Shared Resources/playbutton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgallagher/AudioStreamer/2d1e4e0eeed754ac0ddeb85c2484433b2d4310bf/Shared Resources/playbutton.png -------------------------------------------------------------------------------- /Shared Resources/stopbutton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgallagher/AudioStreamer/2d1e4e0eeed754ac0ddeb85c2484433b2d4310bf/Shared Resources/stopbutton.png -------------------------------------------------------------------------------- /iPhone Resources/MainWindow.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 528 5 | 11B26 6 | 1934 7 | 1138 8 | 566.00 9 | 10 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 11 | 931 12 | 13 | 14 | YES 15 | IBUICustomObject 16 | IBUIWindow 17 | IBUIViewController 18 | IBProxyObject 19 | 20 | 21 | YES 22 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 23 | 24 | 25 | PluginDependencyRecalculationVersion 26 | 27 | 28 | 29 | YES 30 | 31 | IBFilesOwner 32 | IBCocoaTouchFramework 33 | 34 | 35 | IBFirstResponder 36 | IBCocoaTouchFramework 37 | 38 | 39 | IBCocoaTouchFramework 40 | 41 | 42 | iPhoneStreamingPlayerViewController 43 | 44 | 45 | 1 46 | 1 47 | 48 | IBCocoaTouchFramework 49 | NO 50 | 51 | 52 | 53 | 292 54 | {320, 480} 55 | 56 | 57 | 58 | 59 | 1 60 | MSAxIDEAA 61 | 62 | NO 63 | NO 64 | 65 | IBCocoaTouchFramework 66 | 67 | 68 | 69 | 70 | YES 71 | 72 | 73 | delegate 74 | 75 | 76 | 77 | 4 78 | 79 | 80 | 81 | window 82 | 83 | 84 | 85 | 14 86 | 87 | 88 | 89 | viewController 90 | 91 | 92 | 93 | 11 94 | 95 | 96 | 97 | 98 | YES 99 | 100 | 0 101 | 102 | YES 103 | 104 | 105 | 106 | 107 | 108 | -1 109 | 110 | 111 | File's Owner 112 | 113 | 114 | 3 115 | 116 | 117 | iPhoneStreamingPlayer App Delegate 118 | 119 | 120 | -2 121 | 122 | 123 | 124 | 125 | 12 126 | 127 | 128 | 129 | 130 | 10 131 | 132 | 133 | 134 | 135 | 136 | 137 | YES 138 | 139 | YES 140 | -1.CustomClassName 141 | -1.IBPluginDependency 142 | -2.CustomClassName 143 | -2.IBPluginDependency 144 | 10.CustomClassName 145 | 10.IBPluginDependency 146 | 12.IBPluginDependency 147 | 3.CustomClassName 148 | 3.IBPluginDependency 149 | 150 | 151 | YES 152 | UIApplication 153 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 154 | UIResponder 155 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 156 | iPhoneStreamingPlayerViewController 157 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 158 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 159 | iPhoneStreamingPlayerAppDelegate 160 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 161 | 162 | 163 | 164 | YES 165 | 166 | 167 | 168 | 169 | 170 | YES 171 | 172 | 173 | 174 | 175 | 14 176 | 177 | 178 | 179 | YES 180 | 181 | iPhoneStreamingPlayerAppDelegate 182 | NSObject 183 | 184 | YES 185 | 186 | YES 187 | viewController 188 | window 189 | 190 | 191 | YES 192 | iPhoneStreamingPlayerViewController 193 | UIWindow 194 | 195 | 196 | 197 | YES 198 | 199 | YES 200 | viewController 201 | window 202 | 203 | 204 | YES 205 | 206 | viewController 207 | iPhoneStreamingPlayerViewController 208 | 209 | 210 | window 211 | UIWindow 212 | 213 | 214 | 215 | 216 | IBProjectSource 217 | ./Classes/iPhoneStreamingPlayerAppDelegate.h 218 | 219 | 220 | 221 | iPhoneStreamingPlayerViewController 222 | UIViewController 223 | 224 | YES 225 | 226 | YES 227 | buttonPressed: 228 | sliderMoved: 229 | 230 | 231 | YES 232 | id 233 | UISlider 234 | 235 | 236 | 237 | YES 238 | 239 | YES 240 | buttonPressed: 241 | sliderMoved: 242 | 243 | 244 | YES 245 | 246 | buttonPressed: 247 | id 248 | 249 | 250 | sliderMoved: 251 | UISlider 252 | 253 | 254 | 255 | 256 | YES 257 | 258 | YES 259 | button 260 | downloadSourceField 261 | positionLabel 262 | progressSlider 263 | volumeSlider 264 | 265 | 266 | YES 267 | UIButton 268 | UITextField 269 | UILabel 270 | UISlider 271 | UIView 272 | 273 | 274 | 275 | YES 276 | 277 | YES 278 | button 279 | downloadSourceField 280 | positionLabel 281 | progressSlider 282 | volumeSlider 283 | 284 | 285 | YES 286 | 287 | button 288 | UIButton 289 | 290 | 291 | downloadSourceField 292 | UITextField 293 | 294 | 295 | positionLabel 296 | UILabel 297 | 298 | 299 | progressSlider 300 | UISlider 301 | 302 | 303 | volumeSlider 304 | UIView 305 | 306 | 307 | 308 | 309 | IBProjectSource 310 | ./Classes/iPhoneStreamingPlayerViewController.h 311 | 312 | 313 | 314 | 315 | 0 316 | IBCocoaTouchFramework 317 | 318 | com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS 319 | 320 | 321 | 322 | com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 323 | 324 | 325 | YES 326 | 3 327 | 931 328 | 329 | 330 | -------------------------------------------------------------------------------- /iPhone Resources/iPhoneStreamingPlayerViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 528 5 | 10J869 6 | 1864 7 | 1038.35 8 | 461.00 9 | 10 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 11 | 865 12 | 13 | 14 | YES 15 | IBUILabel 16 | IBUISlider 17 | IBUIButton 18 | IBUIView 19 | IBUITextField 20 | IBProxyObject 21 | 22 | 23 | YES 24 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 25 | 26 | 27 | PluginDependencyRecalculationVersion 28 | 29 | 30 | 31 | YES 32 | 33 | IBFilesOwner 34 | IBCocoaTouchFramework 35 | 36 | 37 | IBFirstResponder 38 | IBCocoaTouchFramework 39 | 40 | 41 | 42 | 274 43 | 44 | YES 45 | 46 | 47 | 292 48 | {{20, 49}, {280, 31}} 49 | 50 | 51 | NO 52 | NO 53 | IBCocoaTouchFramework 54 | 0 55 | http://shoutmedia.abc.net.au:10326 56 | 3 57 | 58 | 3 59 | MAA 60 | 61 | 2 62 | 63 | 64 | YES 65 | 17 66 | 67 | 1 68 | 3 69 | IBCocoaTouchFramework 70 | 71 | 72 | 1 73 | 4 74 | 75 | 76 | Helvetica 77 | 14 78 | 16 79 | 80 | 81 | 82 | 83 | 292 84 | {{20, 20}, {280, 21}} 85 | 86 | 87 | NO 88 | YES 89 | NO 90 | IBCocoaTouchFramework 91 | Download URL: 92 | 93 | 1 94 | MCAwIDAAA 95 | 96 | 97 | 1 98 | 10 99 | 100 | 101 | 102 | 103 | 104 | 292 105 | {{124, 88}, {72, 73}} 106 | 107 | 108 | NO 109 | NO 110 | IBCocoaTouchFramework 111 | 0 112 | 0 113 | 114 | 3 115 | MQA 116 | 117 | 118 | 1 119 | MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA 120 | 121 | 122 | 3 123 | MC41AA 124 | 125 | 126 | NSImage 127 | playbutton.png 128 | 129 | 130 | Helvetica-Bold 131 | Helvetica 132 | 2 133 | 15 134 | 135 | 136 | Helvetica-Bold 137 | 15 138 | 16 139 | 140 | 141 | 142 | 143 | 292 144 | {{20, 169}, {280, 21}} 145 | 146 | 147 | NO 148 | YES 149 | NO 150 | IBCocoaTouchFramework 151 | Time Played: 152 | 153 | 154 | 1 155 | 10 156 | 157 | 158 | 159 | 160 | 161 | 292 162 | {{20, 356}, {280, 21}} 163 | 164 | 165 | NO 166 | YES 167 | NO 168 | IBCocoaTouchFramework 169 | Volume: 170 | 171 | 172 | 1 173 | 10 174 | 175 | 176 | 177 | 178 | 179 | 292 180 | {{20, 385}, {280, 55}} 181 | 182 | 183 | 184 | 3 185 | MSAwAA 186 | 187 | NO 188 | IBCocoaTouchFramework 189 | 190 | 191 | 192 | 292 193 | {{18, 198}, {284, 23}} 194 | 195 | 196 | NO 197 | IBCocoaTouchFramework 198 | NO 199 | 0 200 | 0 201 | 50 202 | 100 203 | NO 204 | 205 | 206 | {{0, 20}, {320, 460}} 207 | 208 | 209 | 210 | 1 211 | MC44NTIwNDA4MyAwLjg1MjA0MDgzIDAuODUyMDQwODMAA 212 | 213 | NO 214 | 215 | IBCocoaTouchFramework 216 | 217 | 218 | 219 | 220 | YES 221 | 222 | 223 | view 224 | 225 | 226 | 227 | 7 228 | 229 | 230 | 231 | button 232 | 233 | 234 | 235 | 28 236 | 237 | 238 | 239 | buttonPressed: 240 | 241 | 242 | 7 243 | 244 | 29 245 | 246 | 247 | 248 | volumeSlider 249 | 250 | 251 | 252 | 36 253 | 254 | 255 | 256 | downloadSourceField 257 | 258 | 259 | 260 | 38 261 | 262 | 263 | 264 | positionLabel 265 | 266 | 267 | 268 | 41 269 | 270 | 271 | 272 | delegate 273 | 274 | 275 | 276 | 44 277 | 278 | 279 | 280 | progressSlider 281 | 282 | 283 | 284 | 47 285 | 286 | 287 | 288 | sliderMoved: 289 | 290 | 291 | 13 292 | 293 | 48 294 | 295 | 296 | 297 | 298 | YES 299 | 300 | 0 301 | 302 | YES 303 | 304 | 305 | 306 | 307 | 308 | -1 309 | 310 | 311 | File's Owner 312 | 313 | 314 | -2 315 | 316 | 317 | 318 | 319 | 6 320 | 321 | 322 | YES 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 16 335 | 336 | 337 | 338 | 339 | 21 340 | 341 | 342 | 343 | 344 | 23 345 | 346 | 347 | 348 | 349 | 26 350 | 351 | 352 | 353 | 354 | 35 355 | 356 | 357 | YES 358 | 359 | 360 | 361 | 362 | 25 363 | 364 | 365 | 366 | 367 | 46 368 | 369 | 370 | 371 | 372 | 373 | 374 | YES 375 | 376 | YES 377 | -1.CustomClassName 378 | -1.IBPluginDependency 379 | -2.CustomClassName 380 | -2.IBPluginDependency 381 | 16.IBPluginDependency 382 | 21.IBPluginDependency 383 | 23.IBPluginDependency 384 | 25.IBPluginDependency 385 | 26.IBPluginDependency 386 | 35.IBPluginDependency 387 | 46.IBPluginDependency 388 | 6.IBPluginDependency 389 | 390 | 391 | YES 392 | iPhoneStreamingPlayerViewController 393 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 394 | UIResponder 395 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 396 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 397 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 398 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 399 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 400 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 401 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 402 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 403 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 404 | 405 | 406 | 407 | YES 408 | 409 | 410 | 411 | 412 | 413 | YES 414 | 415 | 416 | 417 | 418 | 48 419 | 420 | 421 | 422 | YES 423 | 424 | iPhoneStreamingPlayerViewController 425 | UIViewController 426 | 427 | YES 428 | 429 | YES 430 | buttonPressed: 431 | sliderMoved: 432 | 433 | 434 | YES 435 | id 436 | UISlider 437 | 438 | 439 | 440 | YES 441 | 442 | YES 443 | buttonPressed: 444 | sliderMoved: 445 | 446 | 447 | YES 448 | 449 | buttonPressed: 450 | id 451 | 452 | 453 | sliderMoved: 454 | UISlider 455 | 456 | 457 | 458 | 459 | YES 460 | 461 | YES 462 | button 463 | downloadSourceField 464 | positionLabel 465 | progressSlider 466 | volumeSlider 467 | 468 | 469 | YES 470 | UIButton 471 | UITextField 472 | UILabel 473 | UISlider 474 | UIView 475 | 476 | 477 | 478 | YES 479 | 480 | YES 481 | button 482 | downloadSourceField 483 | positionLabel 484 | progressSlider 485 | volumeSlider 486 | 487 | 488 | YES 489 | 490 | button 491 | UIButton 492 | 493 | 494 | downloadSourceField 495 | UITextField 496 | 497 | 498 | positionLabel 499 | UILabel 500 | 501 | 502 | progressSlider 503 | UISlider 504 | 505 | 506 | volumeSlider 507 | UIView 508 | 509 | 510 | 511 | 512 | IBProjectSource 513 | ./Classes/iPhoneStreamingPlayerViewController.h 514 | 515 | 516 | 517 | 518 | 0 519 | IBCocoaTouchFramework 520 | 521 | com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS 522 | 523 | 524 | 525 | com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 526 | 527 | 528 | YES 529 | 3 530 | 531 | playbutton.png 532 | {64, 64} 533 | 534 | 865 535 | 536 | 537 | -------------------------------------------------------------------------------- /iPhoneInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIconFile 12 | 13 | CFBundleIdentifier 14 | com.mattgallagher.${PRODUCT_NAME:identifier} 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | ${PRODUCT_NAME} 19 | CFBundlePackageType 20 | APPL 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | NSMainNibFile 28 | MainWindow 29 | 30 | 31 | -------------------------------------------------------------------------------- /iPhoneStreamingPlayer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1D3623260D0F684500981E51 /* iPhoneStreamingPlayerAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D3623250D0F684500981E51 /* iPhoneStreamingPlayerAppDelegate.m */; }; 11 | 1D60589B0D05DD56006BFB54 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; }; 12 | 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; 13 | 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; }; 14 | 288765A50DF7441C002DB57D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 288765A40DF7441C002DB57D /* CoreGraphics.framework */; }; 15 | 28D7ACF80DDB3853001CB0EB /* iPhoneStreamingPlayerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 28D7ACF70DDB3853001CB0EB /* iPhoneStreamingPlayerViewController.m */; }; 16 | C90A804C14134C9400810E93 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = C9E673010FE8C55B0033BF43 /* MainWindow.xib */; }; 17 | C9423DF10EF8AA6B003B785B /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9423DF00EF8AA6B003B785B /* CFNetwork.framework */; }; 18 | C98DA4F4173200D0005FC5E7 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C98DA4F3173200D0005FC5E7 /* Default-568h@2x.png */; }; 19 | C9AB93E20FCF816F0047C0FA /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9AB93E10FCF816F0047C0FA /* AudioToolbox.framework */; }; 20 | C9AB93F30FCF81790047C0FA /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9AB93F20FCF81790047C0FA /* MediaPlayer.framework */; }; 21 | C9C2D87A0EB6E09C00A3D071 /* AudioStreamer.m in Sources */ = {isa = PBXBuildFile; fileRef = C9C2D8780EB6E09C00A3D071 /* AudioStreamer.m */; }; 22 | C9C2D8CE0EB6E31200A3D071 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9C2D8CD0EB6E31200A3D071 /* QuartzCore.framework */; }; 23 | C9E673020FE8C55B0033BF43 /* iPhoneStreamingPlayerViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C9E672FF0FE8C55B0033BF43 /* iPhoneStreamingPlayerViewController.xib */; }; 24 | C9E673090FE8C5650033BF43 /* playbutton.png in Resources */ = {isa = PBXBuildFile; fileRef = C9E673050FE8C5650033BF43 /* playbutton.png */; }; 25 | C9E6730A0FE8C5650033BF43 /* stopbutton.png in Resources */ = {isa = PBXBuildFile; fileRef = C9E673060FE8C5650033BF43 /* stopbutton.png */; }; 26 | C9E6730B0FE8C5650033BF43 /* loadingbutton.png in Resources */ = {isa = PBXBuildFile; fileRef = C9E673070FE8C5650033BF43 /* loadingbutton.png */; }; 27 | C9E6730C0FE8C5650033BF43 /* pausebutton.png in Resources */ = {isa = PBXBuildFile; fileRef = C9E673080FE8C5650033BF43 /* pausebutton.png */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | 1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 32 | 1D3623240D0F684500981E51 /* iPhoneStreamingPlayerAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iPhoneStreamingPlayerAppDelegate.h; sourceTree = ""; }; 33 | 1D3623250D0F684500981E51 /* iPhoneStreamingPlayerAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = iPhoneStreamingPlayerAppDelegate.m; sourceTree = ""; }; 34 | 1D6058910D05DD3D006BFB54 /* iPhoneStreamingPlayer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iPhoneStreamingPlayer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 36 | 288765A40DF7441C002DB57D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 37 | 28D7ACF60DDB3853001CB0EB /* iPhoneStreamingPlayerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iPhoneStreamingPlayerViewController.h; sourceTree = ""; }; 38 | 28D7ACF70DDB3853001CB0EB /* iPhoneStreamingPlayerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = iPhoneStreamingPlayerViewController.m; sourceTree = ""; }; 39 | 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 40 | 32CA4F630368D1EE00C91783 /* iPhoneStreamingPlayer_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iPhoneStreamingPlayer_Prefix.pch; sourceTree = ""; }; 41 | C9423DF00EF8AA6B003B785B /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; 42 | C98DA4F3173200D0005FC5E7 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 43 | C9AB93E10FCF816F0047C0FA /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 44 | C9AB93F20FCF81790047C0FA /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; }; 45 | C9C2D8780EB6E09C00A3D071 /* AudioStreamer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioStreamer.m; sourceTree = ""; }; 46 | C9C2D8790EB6E09C00A3D071 /* AudioStreamer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioStreamer.h; sourceTree = ""; }; 47 | C9C2D8CD0EB6E31200A3D071 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = /System/Library/Frameworks/QuartzCore.framework; sourceTree = ""; }; 48 | C9E672FF0FE8C55B0033BF43 /* iPhoneStreamingPlayerViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = iPhoneStreamingPlayerViewController.xib; path = "iPhone Resources/iPhoneStreamingPlayerViewController.xib"; sourceTree = ""; }; 49 | C9E673010FE8C55B0033BF43 /* MainWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = MainWindow.xib; path = "iPhone Resources/MainWindow.xib"; sourceTree = ""; }; 50 | C9E673050FE8C5650033BF43 /* playbutton.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = playbutton.png; path = "Shared Resources/playbutton.png"; sourceTree = ""; }; 51 | C9E673060FE8C5650033BF43 /* stopbutton.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = stopbutton.png; path = "Shared Resources/stopbutton.png"; sourceTree = ""; }; 52 | C9E673070FE8C5650033BF43 /* loadingbutton.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = loadingbutton.png; path = "Shared Resources/loadingbutton.png"; sourceTree = ""; }; 53 | C9E673080FE8C5650033BF43 /* pausebutton.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = pausebutton.png; path = "Shared Resources/pausebutton.png"; sourceTree = ""; }; 54 | C9E673410FE8C6510033BF43 /* iPhoneInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = iPhoneInfo.plist; sourceTree = ""; }; 55 | /* End PBXFileReference section */ 56 | 57 | /* Begin PBXFrameworksBuildPhase section */ 58 | 1D60588F0D05DD3D006BFB54 /* Frameworks */ = { 59 | isa = PBXFrameworksBuildPhase; 60 | buildActionMask = 2147483647; 61 | files = ( 62 | 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */, 63 | 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */, 64 | 288765A50DF7441C002DB57D /* CoreGraphics.framework in Frameworks */, 65 | C9C2D8CE0EB6E31200A3D071 /* QuartzCore.framework in Frameworks */, 66 | C9423DF10EF8AA6B003B785B /* CFNetwork.framework in Frameworks */, 67 | C9AB93E20FCF816F0047C0FA /* AudioToolbox.framework in Frameworks */, 68 | C9AB93F30FCF81790047C0FA /* MediaPlayer.framework in Frameworks */, 69 | ); 70 | runOnlyForDeploymentPostprocessing = 0; 71 | }; 72 | /* End PBXFrameworksBuildPhase section */ 73 | 74 | /* Begin PBXGroup section */ 75 | 080E96DDFE201D6D7F000001 /* Classes */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | C9C2D8790EB6E09C00A3D071 /* AudioStreamer.h */, 79 | C9C2D8780EB6E09C00A3D071 /* AudioStreamer.m */, 80 | 1D3623240D0F684500981E51 /* iPhoneStreamingPlayerAppDelegate.h */, 81 | 1D3623250D0F684500981E51 /* iPhoneStreamingPlayerAppDelegate.m */, 82 | 28D7ACF60DDB3853001CB0EB /* iPhoneStreamingPlayerViewController.h */, 83 | 28D7ACF70DDB3853001CB0EB /* iPhoneStreamingPlayerViewController.m */, 84 | ); 85 | path = Classes; 86 | sourceTree = ""; 87 | }; 88 | 19C28FACFE9D520D11CA2CBB /* Products */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 1D6058910D05DD3D006BFB54 /* iPhoneStreamingPlayer.app */, 92 | ); 93 | name = Products; 94 | sourceTree = ""; 95 | }; 96 | 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 080E96DDFE201D6D7F000001 /* Classes */, 100 | 29B97315FDCFA39411CA2CEA /* Other Sources */, 101 | 29B97317FDCFA39411CA2CEA /* Resources */, 102 | 29B97323FDCFA39411CA2CEA /* Frameworks */, 103 | 19C28FACFE9D520D11CA2CBB /* Products */, 104 | ); 105 | name = CustomTemplate; 106 | sourceTree = ""; 107 | }; 108 | 29B97315FDCFA39411CA2CEA /* Other Sources */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | C9E673410FE8C6510033BF43 /* iPhoneInfo.plist */, 112 | 32CA4F630368D1EE00C91783 /* iPhoneStreamingPlayer_Prefix.pch */, 113 | 29B97316FDCFA39411CA2CEA /* main.m */, 114 | ); 115 | name = "Other Sources"; 116 | sourceTree = ""; 117 | }; 118 | 29B97317FDCFA39411CA2CEA /* Resources */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | C98DA4F3173200D0005FC5E7 /* Default-568h@2x.png */, 122 | C9E672FF0FE8C55B0033BF43 /* iPhoneStreamingPlayerViewController.xib */, 123 | C9E673010FE8C55B0033BF43 /* MainWindow.xib */, 124 | C9E673050FE8C5650033BF43 /* playbutton.png */, 125 | C9E673060FE8C5650033BF43 /* stopbutton.png */, 126 | C9E673070FE8C5650033BF43 /* loadingbutton.png */, 127 | C9E673080FE8C5650033BF43 /* pausebutton.png */, 128 | ); 129 | name = Resources; 130 | sourceTree = ""; 131 | }; 132 | 29B97323FDCFA39411CA2CEA /* Frameworks */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | C9AB93F20FCF81790047C0FA /* MediaPlayer.framework */, 136 | C9AB93E10FCF816F0047C0FA /* AudioToolbox.framework */, 137 | C9423DF00EF8AA6B003B785B /* CFNetwork.framework */, 138 | C9C2D8CD0EB6E31200A3D071 /* QuartzCore.framework */, 139 | 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */, 140 | 1D30AB110D05D00D00671497 /* Foundation.framework */, 141 | 288765A40DF7441C002DB57D /* CoreGraphics.framework */, 142 | ); 143 | name = Frameworks; 144 | sourceTree = ""; 145 | }; 146 | /* End PBXGroup section */ 147 | 148 | /* Begin PBXNativeTarget section */ 149 | 1D6058900D05DD3D006BFB54 /* iPhoneStreamingPlayer */ = { 150 | isa = PBXNativeTarget; 151 | buildConfigurationList = 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "iPhoneStreamingPlayer" */; 152 | buildPhases = ( 153 | 1D60588D0D05DD3D006BFB54 /* Resources */, 154 | 1D60588E0D05DD3D006BFB54 /* Sources */, 155 | 1D60588F0D05DD3D006BFB54 /* Frameworks */, 156 | ); 157 | buildRules = ( 158 | ); 159 | dependencies = ( 160 | ); 161 | name = iPhoneStreamingPlayer; 162 | productName = iPhoneStreamingPlayer; 163 | productReference = 1D6058910D05DD3D006BFB54 /* iPhoneStreamingPlayer.app */; 164 | productType = "com.apple.product-type.application"; 165 | }; 166 | /* End PBXNativeTarget section */ 167 | 168 | /* Begin PBXProject section */ 169 | 29B97313FDCFA39411CA2CEA /* Project object */ = { 170 | isa = PBXProject; 171 | attributes = { 172 | LastUpgradeCheck = 0500; 173 | }; 174 | buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "iPhoneStreamingPlayer" */; 175 | compatibilityVersion = "Xcode 3.2"; 176 | developmentRegion = English; 177 | hasScannedForEncodings = 1; 178 | knownRegions = ( 179 | English, 180 | Japanese, 181 | French, 182 | German, 183 | ); 184 | mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; 185 | projectDirPath = ""; 186 | projectRoot = ""; 187 | targets = ( 188 | 1D6058900D05DD3D006BFB54 /* iPhoneStreamingPlayer */, 189 | ); 190 | }; 191 | /* End PBXProject section */ 192 | 193 | /* Begin PBXResourcesBuildPhase section */ 194 | 1D60588D0D05DD3D006BFB54 /* Resources */ = { 195 | isa = PBXResourcesBuildPhase; 196 | buildActionMask = 2147483647; 197 | files = ( 198 | C9E673020FE8C55B0033BF43 /* iPhoneStreamingPlayerViewController.xib in Resources */, 199 | C9E673090FE8C5650033BF43 /* playbutton.png in Resources */, 200 | C9E6730A0FE8C5650033BF43 /* stopbutton.png in Resources */, 201 | C9E6730B0FE8C5650033BF43 /* loadingbutton.png in Resources */, 202 | C9E6730C0FE8C5650033BF43 /* pausebutton.png in Resources */, 203 | C90A804C14134C9400810E93 /* MainWindow.xib in Resources */, 204 | C98DA4F4173200D0005FC5E7 /* Default-568h@2x.png in Resources */, 205 | ); 206 | runOnlyForDeploymentPostprocessing = 0; 207 | }; 208 | /* End PBXResourcesBuildPhase section */ 209 | 210 | /* Begin PBXSourcesBuildPhase section */ 211 | 1D60588E0D05DD3D006BFB54 /* Sources */ = { 212 | isa = PBXSourcesBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | 1D60589B0D05DD56006BFB54 /* main.m in Sources */, 216 | 1D3623260D0F684500981E51 /* iPhoneStreamingPlayerAppDelegate.m in Sources */, 217 | 28D7ACF80DDB3853001CB0EB /* iPhoneStreamingPlayerViewController.m in Sources */, 218 | C9C2D87A0EB6E09C00A3D071 /* AudioStreamer.m in Sources */, 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | }; 222 | /* End PBXSourcesBuildPhase section */ 223 | 224 | /* Begin XCBuildConfiguration section */ 225 | 1D6058940D05DD3E006BFB54 /* Debug */ = { 226 | isa = XCBuildConfiguration; 227 | buildSettings = { 228 | ALWAYS_SEARCH_USER_PATHS = NO; 229 | COPY_PHASE_STRIP = NO; 230 | GCC_DYNAMIC_NO_PIC = NO; 231 | GCC_OPTIMIZATION_LEVEL = 0; 232 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 233 | GCC_PREFIX_HEADER = iPhoneStreamingPlayer_Prefix.pch; 234 | INFOPLIST_FILE = iPhoneInfo.plist; 235 | PRODUCT_NAME = iPhoneStreamingPlayer; 236 | }; 237 | name = Debug; 238 | }; 239 | 1D6058950D05DD3E006BFB54 /* Release */ = { 240 | isa = XCBuildConfiguration; 241 | buildSettings = { 242 | ALWAYS_SEARCH_USER_PATHS = NO; 243 | COPY_PHASE_STRIP = YES; 244 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 245 | GCC_PREFIX_HEADER = iPhoneStreamingPlayer_Prefix.pch; 246 | INFOPLIST_FILE = iPhoneInfo.plist; 247 | PRODUCT_NAME = iPhoneStreamingPlayer; 248 | }; 249 | name = Release; 250 | }; 251 | C01FCF4F08A954540054247B /* Debug */ = { 252 | isa = XCBuildConfiguration; 253 | buildSettings = { 254 | CODE_SIGN_IDENTITY = "iPhone Developer"; 255 | GCC_C_LANGUAGE_STANDARD = c99; 256 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 257 | GCC_WARN_UNUSED_VARIABLE = YES; 258 | ONLY_ACTIVE_ARCH = YES; 259 | PROVISIONING_PROFILE = ""; 260 | SDKROOT = iphoneos; 261 | }; 262 | name = Debug; 263 | }; 264 | C01FCF5008A954540054247B /* Release */ = { 265 | isa = XCBuildConfiguration; 266 | buildSettings = { 267 | CODE_SIGN_IDENTITY = "iPhone Developer"; 268 | GCC_C_LANGUAGE_STANDARD = c99; 269 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 270 | GCC_WARN_UNUSED_VARIABLE = YES; 271 | PROVISIONING_PROFILE = ""; 272 | SDKROOT = iphoneos; 273 | }; 274 | name = Release; 275 | }; 276 | /* End XCBuildConfiguration section */ 277 | 278 | /* Begin XCConfigurationList section */ 279 | 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "iPhoneStreamingPlayer" */ = { 280 | isa = XCConfigurationList; 281 | buildConfigurations = ( 282 | 1D6058940D05DD3E006BFB54 /* Debug */, 283 | 1D6058950D05DD3E006BFB54 /* Release */, 284 | ); 285 | defaultConfigurationIsVisible = 0; 286 | defaultConfigurationName = Release; 287 | }; 288 | C01FCF4E08A954540054247B /* Build configuration list for PBXProject "iPhoneStreamingPlayer" */ = { 289 | isa = XCConfigurationList; 290 | buildConfigurations = ( 291 | C01FCF4F08A954540054247B /* Debug */, 292 | C01FCF5008A954540054247B /* Release */, 293 | ); 294 | defaultConfigurationIsVisible = 0; 295 | defaultConfigurationName = Release; 296 | }; 297 | /* End XCConfigurationList section */ 298 | }; 299 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 300 | } 301 | -------------------------------------------------------------------------------- /iPhoneStreamingPlayer_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'iPhoneStreamingPlayer' target in the 'iPhoneStreamingPlayer' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #import 8 | #endif 9 | -------------------------------------------------------------------------------- /main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // iPhone/MacStreamingPlayer 4 | // 5 | // Created by Matt Gallagher on 28/10/08. 6 | // Copyright Matt Gallagher 2008. All rights reserved. 7 | // 8 | // This software is provided 'as-is', without any express or implied 9 | // warranty. In no event will the authors be held liable for any damages 10 | // arising from the use of this software. Permission is granted to anyone to 11 | // use this software for any purpose, including commercial applications, and to 12 | // alter it and redistribute it freely, subject to the following restrictions: 13 | // 14 | // 1. The origin of this software must not be misrepresented; you must not 15 | // claim that you wrote the original software. If you use this software 16 | // in a product, an acknowledgment in the product documentation would be 17 | // appreciated but is not required. 18 | // 2. Altered source versions must be plainly marked as such, and must not be 19 | // misrepresented as being the original software. 20 | // 3. This notice may not be removed or altered from any source 21 | // distribution. 22 | // 23 | 24 | #if TARGET_OS_IPHONE 25 | #import 26 | #else 27 | #import 28 | #endif 29 | 30 | int main(int argc, const char *argv[]) { 31 | NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 32 | #if TARGET_OS_IPHONE 33 | int retVal = UIApplicationMain(argc, (char **)argv, nil, nil); 34 | #else 35 | int retVal = NSApplicationMain(argc, argv); 36 | #endif 37 | [pool release]; 38 | return retVal; 39 | } 40 | --------------------------------------------------------------------------------