diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..61bcb88 Binary files /dev/null and b/.DS_Store differ diff --git a/Audjustable.xcodeproj/project.pbxproj b/Audjustable.xcodeproj/project.pbxproj index 3ea6ff9..eab1bea 100644 --- a/Audjustable.xcodeproj/project.pbxproj +++ b/Audjustable.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + A16CB9AD162EC4AB00CFD1E8 /* AutoRecoveringHttpDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = A16CB9AA162EC4AB00CFD1E8 /* AutoRecoveringHttpDataSource.m */; }; + A16CB9AE162EC4AB00CFD1E8 /* DataSourceWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = A16CB9AC162EC4AB00CFD1E8 /* DataSourceWrapper.m */; }; + A16CB9B1162EC5E300CFD1E8 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A16CB9B0162EC5E300CFD1E8 /* SystemConfiguration.framework */; }; + A16CB9B3162EC5E900CFD1E8 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = A16CB9B2162EC5E900CFD1E8 /* Default-568h@2x.png */; }; A186B4E4157F80E700BD0084 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A186B4E3157F80E700BD0084 /* UIKit.framework */; }; A186B4E6157F80E700BD0084 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A186B4E5157F80E700BD0084 /* Foundation.framework */; }; A186B4EE157F80E700BD0084 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = A186B4EC157F80E700BD0084 /* InfoPlist.strings */; }; @@ -25,6 +29,12 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + A16CB9A9162EC4AB00CFD1E8 /* AutoRecoveringHttpDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AutoRecoveringHttpDataSource.h; sourceTree = ""; }; + A16CB9AA162EC4AB00CFD1E8 /* AutoRecoveringHttpDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AutoRecoveringHttpDataSource.m; sourceTree = ""; }; + A16CB9AB162EC4AB00CFD1E8 /* DataSourceWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataSourceWrapper.h; sourceTree = ""; }; + A16CB9AC162EC4AB00CFD1E8 /* DataSourceWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DataSourceWrapper.m; sourceTree = ""; }; + A16CB9B0162EC5E300CFD1E8 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; + A16CB9B2162EC5E900CFD1E8 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; A186B4DF157F80E600BD0084 /* Audjustable.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Audjustable.app; sourceTree = BUILT_PRODUCTS_DIR; }; A186B4E3157F80E700BD0084 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; A186B4E5157F80E700BD0084 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; @@ -58,6 +68,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A16CB9B1162EC5E300CFD1E8 /* SystemConfiguration.framework in Frameworks */, A186B51A157F819500BD0084 /* CFNetwork.framework in Frameworks */, A186B518157F818900BD0084 /* CoreFoundation.framework in Frameworks */, A186B514157F817500BD0084 /* AudioToolbox.framework in Frameworks */, @@ -72,6 +83,7 @@ A186B4D4157F80E600BD0084 = { isa = PBXGroup; children = ( + A16CB9B0162EC5E300CFD1E8 /* SystemConfiguration.framework */, A186B4E9157F80E700BD0084 /* Audjustable */, A186B4E2157F80E700BD0084 /* Frameworks */, A186B4E0157F80E600BD0084 /* Products */, @@ -116,6 +128,7 @@ A186B51B157F81D900BD0084 /* Resources */, A186B4EB157F80E700BD0084 /* Audjustable-Info.plist */, A186B4EC157F80E700BD0084 /* InfoPlist.strings */, + A16CB9B2162EC5E900CFD1E8 /* Default-568h@2x.png */, A186B4EF157F80E700BD0084 /* main.m */, A186B4F1157F80E700BD0084 /* Audjustable-Prefix.pch */, ); @@ -135,6 +148,10 @@ A186B4FF157F813100BD0084 /* AudioPlayer */ = { isa = PBXGroup; children = ( + A16CB9A9162EC4AB00CFD1E8 /* AutoRecoveringHttpDataSource.h */, + A16CB9AA162EC4AB00CFD1E8 /* AutoRecoveringHttpDataSource.m */, + A16CB9AB162EC4AB00CFD1E8 /* DataSourceWrapper.h */, + A16CB9AC162EC4AB00CFD1E8 /* DataSourceWrapper.m */, A186B500157F813100BD0084 /* AudioPlayer.h */, A186B501157F813100BD0084 /* AudioPlayer.m */, A186B502157F813100BD0084 /* CoreFoundationDataSource.h */, @@ -209,6 +226,7 @@ files = ( A186B4EE157F80E700BD0084 /* InfoPlist.strings in Resources */, A186B51D157F825400BD0084 /* sample.m4a in Resources */, + A16CB9B3162EC5E900CFD1E8 /* Default-568h@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -227,6 +245,8 @@ A186B50F157F813100BD0084 /* HttpDataSource.m in Sources */, A186B510157F813100BD0084 /* LocalFileDataSource.m in Sources */, A186B511157F813100BD0084 /* AudioPlayerView.m in Sources */, + A16CB9AD162EC4AB00CFD1E8 /* AutoRecoveringHttpDataSource.m in Sources */, + A16CB9AE162EC4AB00CFD1E8 /* DataSourceWrapper.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Audjustable.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Audjustable.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..94b1065 --- /dev/null +++ b/Audjustable.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Audjustable.xcodeproj/project.xcworkspace/xcuserdata/tum.xcuserdatad/UserInterfaceState.xcuserstate b/Audjustable.xcodeproj/project.xcworkspace/xcuserdata/tum.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..c8f9c0d Binary files /dev/null and b/Audjustable.xcodeproj/project.xcworkspace/xcuserdata/tum.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Audjustable.xcodeproj/xcuserdata/tum.xcuserdatad/xcschemes/Audjustable.xcscheme b/Audjustable.xcodeproj/xcuserdata/tum.xcuserdatad/xcschemes/Audjustable.xcscheme new file mode 100644 index 0000000..c75fa69 --- /dev/null +++ b/Audjustable.xcodeproj/xcuserdata/tum.xcuserdatad/xcschemes/Audjustable.xcscheme @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Audjustable.xcodeproj/xcuserdata/tum.xcuserdatad/xcschemes/xcschememanagement.plist b/Audjustable.xcodeproj/xcuserdata/tum.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..02da18f --- /dev/null +++ b/Audjustable.xcodeproj/xcuserdata/tum.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,22 @@ + + + + + SchemeUserState + + Audjustable.xcscheme + + orderHint + 0 + + + SuppressBuildableAutocreation + + A186B4DE157F80E600BD0084 + + primary + + + + + diff --git a/Audjustable/Classes/.DS_Store b/Audjustable/Classes/.DS_Store new file mode 100644 index 0000000..0f9a380 Binary files /dev/null and b/Audjustable/Classes/.DS_Store differ diff --git a/Audjustable/Classes/AudioPlayer/AudioPlayer.h b/Audjustable/Classes/AudioPlayer/AudioPlayer.h index 737c4ae..b99a8dd 100644 --- a/Audjustable/Classes/AudioPlayer/AudioPlayer.h +++ b/Audjustable/Classes/AudioPlayer/AudioPlayer.h @@ -103,6 +103,7 @@ AudioPlayerErrorCode; -(void) audioPlayer:(AudioPlayer*)audioPlayer didFinishPlayingQueueItemId:(NSObject*)queueItemId withReason:(AudioPlayerStopReason)stopReason andProgress:(double)progress andDuration:(double)duration; @optional -(void) audioPlayer:(AudioPlayer*)audioPlayer internalStateChanged:(AudioPlayerInternalState)state; +-(void) audioPlayer:(AudioPlayer*)audioPlayer didCancelQueuedItems:(NSArray*)queuedItems; @end @class QueueEntry; diff --git a/Audjustable/Classes/AudioPlayer/AudioPlayer.m b/Audjustable/Classes/AudioPlayer/AudioPlayer.m index eb8697d..5c71db3 100644 --- a/Audjustable/Classes/AudioPlayer/AudioPlayer.m +++ b/Audjustable/Classes/AudioPlayer/AudioPlayer.m @@ -468,7 +468,34 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ { @synchronized(self) { + NSMutableArray* array = [[NSMutableArray alloc] initWithCapacity:bufferingQueue.count + upcomingQueue.count]; + + QueueEntry* entry = [bufferingQueue dequeue]; + + if (entry && entry != currentlyPlayingEntry) + { + [array addObject:[entry queueItemId]]; + } + + while (bufferingQueue.count > 0) + { + [array addObject:[[bufferingQueue dequeue] queueItemId]]; + } + + for (QueueEntry* entry in upcomingQueue) + { + [array addObject:entry.queueItemId]; + } + [upcomingQueue removeAllObjects]; + + dispatch_async(dispatch_get_main_queue(), ^ + { + if ([self.delegate respondsToSelector:@selector(audioPlayer:didCancelQueuedItems:)]) + { + [self.delegate audioPlayer:self didCancelQueuedItems:array]; + } + }); } } @@ -1258,13 +1285,18 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ currentlyReadingEntry.dataSource.delegate = nil; [currentlyReadingEntry.dataSource unregisterForEvents]; - if (currentlyReadingEntry) + if (currentlyPlayingEntry) { [self processDidFinishPlaying:currentlyPlayingEntry withNext:nil]; } pthread_mutex_lock(&queueBuffersMutex); + if ([bufferingQueue peek] == currentlyPlayingEntry) + { + [bufferingQueue dequeue]; + } + currentlyPlayingEntry = nil; currentlyReadingEntry = nil; seekToTimeWasRequested = NO; @@ -1276,15 +1308,20 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ currentlyReadingEntry.dataSource.delegate = nil; [currentlyReadingEntry.dataSource unregisterForEvents]; - if (currentlyReadingEntry) + if (currentlyPlayingEntry) { [self processDidFinishPlaying:currentlyPlayingEntry withNext:nil]; } pthread_mutex_lock(&queueBuffersMutex); + + if ([bufferingQueue peek] == currentlyPlayingEntry) + { + [bufferingQueue dequeue]; + } + currentlyPlayingEntry = nil; currentlyReadingEntry = nil; - pthread_mutex_unlock(&queueBuffersMutex); [self resetAudioQueue]; diff --git a/Audjustable/Classes/AudioPlayer/AutoRecoveringHttpDataSource.h b/Audjustable/Classes/AudioPlayer/AutoRecoveringHttpDataSource.h new file mode 100644 index 0000000..e83cedb --- /dev/null +++ b/Audjustable/Classes/AudioPlayer/AutoRecoveringHttpDataSource.h @@ -0,0 +1,19 @@ +// +// AutoRecoveringHttpDataSource.h +// bloom +// +// Created by Thong Nguyen on 16/10/2012. +// Copyright (c) 2012 DDN Ltd. All rights reserved. +// + +#import "DataSource.h" +#import "HttpDataSource.h" +#import "DataSourceWrapper.h" + +@interface AutoRecoveringHttpDataSource : DataSourceWrapper + +-(id) initWithHttpDataSource:(HttpDataSource*)innerDataSource; + +@property (readonly) HttpDataSource* innerDataSource; + +@end diff --git a/Audjustable/Classes/AudioPlayer/AutoRecoveringHttpDataSource.m b/Audjustable/Classes/AudioPlayer/AutoRecoveringHttpDataSource.m new file mode 100644 index 0000000..755143a --- /dev/null +++ b/Audjustable/Classes/AudioPlayer/AutoRecoveringHttpDataSource.m @@ -0,0 +1,169 @@ +// +// AutoRecoveringHttpDataSource.m +// bloom +// +// Created by Thong Nguyen on 16/10/2012. +// Copyright (c) 2012 DDN Ltd. All rights reserved. +// + +#import +#import +#import +#import +#import +#import +#import +#import +#import "AutoRecoveringHttpDataSource.h" + +#define MAX_IMMEDIATE_RECONNECT_ATTEMPTS (8) + +@interface AutoRecoveringHttpDataSource() +{ + int reconnectAttempts; + BOOL waitingForNetwork; + SCNetworkReachabilityRef reachabilityRef; +} + +-(void) reachabilityChanged; + +@end + +static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) +{ + @autoreleasepool + { + AutoRecoveringHttpDataSource* dataSource = (__bridge AutoRecoveringHttpDataSource*)info; + + [dataSource reachabilityChanged]; + } +} + +@implementation AutoRecoveringHttpDataSource + +-(HttpDataSource*) innerHttpDataSource +{ + return (HttpDataSource*)self.innerDataSource; +} + +-(id) initWithHttpDataSource:(HttpDataSource*)innerDataSourceIn +{ + if (self = [super initWithDataSource:innerDataSourceIn]) + { + self.innerDataSource.delegate = self; + + struct sockaddr_in zeroAddress; + + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + + reachabilityRef = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)&zeroAddress); + } + + return self; +} + +-(BOOL) startNotifierOnRunLoop:(NSRunLoop*)runLoop +{ + BOOL retVal = NO; + SCNetworkReachabilityContext context = { 0, (__bridge void*)self, NULL, NULL, NULL }; + + if (SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context)) + { + if(SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef, runLoop.getCFRunLoop, kCFRunLoopDefaultMode)) + { + retVal = YES; + } + } + + return retVal; +} + +-(BOOL) registerForEvents:(NSRunLoop*)runLoop +{ + [super registerForEvents:runLoop]; + [self startNotifierOnRunLoop:runLoop]; + + return YES; +} + +-(void) unregisterForEvents +{ + [self stopNotifier]; +} + +-(void) stopNotifier +{ + if (reachabilityRef != NULL) + { + SCNetworkReachabilityUnscheduleFromRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + } +} + +-(BOOL) hasGotNetworkConnection +{ + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + return ((flags & kSCNetworkReachabilityFlagsReachable) != 0); + } + + return NO; +} + +-(void) dealloc +{ + [self stopNotifier]; + + if (reachabilityRef!= NULL) + { + CFRelease(reachabilityRef); + } +} + +-(void) reachabilityChanged +{ + if (waitingForNetwork) + { + waitingForNetwork = NO; + + [self attemptReconnect]; + } +} + +-(void) dataSourceDataAvailable:(DataSource*)dataSource +{ + reconnectAttempts = 0; + + [super dataSourceDataAvailable:dataSource]; +} + +-(void) attemptReconnect +{ + reconnectAttempts++; + + [self seekToOffset:self.position]; +} + +-(void) dataSourceErrorOccured:(DataSource*)dataSource +{ + if (![self hasGotNetworkConnection]) + { + waitingForNetwork = YES; + + return; + } + + if (reconnectAttempts > MAX_IMMEDIATE_RECONNECT_ATTEMPTS) + { + [self performSelector:@selector(attemptReconnect) withObject:nil afterDelay:5]; + } + else + { + [self attemptReconnect]; + } +} + +@end diff --git a/Audjustable/Classes/AudioPlayer/DataSourceWrapper.h b/Audjustable/Classes/AudioPlayer/DataSourceWrapper.h new file mode 100644 index 0000000..b7b7406 --- /dev/null +++ b/Audjustable/Classes/AudioPlayer/DataSourceWrapper.h @@ -0,0 +1,17 @@ +// +// DataSourceWrapper.h +// bloom +// +// Created by Thong Nguyen on 16/10/2012. +// Copyright (c) 2012 DDN Ltd. All rights reserved. +// + +#import "DataSource.h" + +@interface DataSourceWrapper : DataSource + +-(id) initWithDataSource:(DataSource*)innerDataSource; + +@property (readonly) DataSource* innerDataSource; + +@end diff --git a/Audjustable/Classes/AudioPlayer/DataSourceWrapper.m b/Audjustable/Classes/AudioPlayer/DataSourceWrapper.m new file mode 100644 index 0000000..1df450a --- /dev/null +++ b/Audjustable/Classes/AudioPlayer/DataSourceWrapper.m @@ -0,0 +1,84 @@ +// +// DataSourceWrapper.m +// bloom +// +// Created by Thong Nguyen on 16/10/2012. +// Copyright (c) 2012 DDN Ltd. All rights reserved. +// + +#import "DataSourceWrapper.h" + +@interface DataSourceWrapper() +@property (readwrite) DataSource* innerDataSource; +@end + +@implementation DataSourceWrapper + +-(id) initWithDataSource:(DataSource*)innerDataSourceIn +{ + if (self = [super init]) + { + self.innerDataSource = innerDataSourceIn; + + self.innerDataSource.delegate = self; + } + + return self; +} + +-(long long) length +{ + return self.innerDataSource.length; +} + +-(void) seekToOffset:(long long)offset +{ + return [self.innerDataSource seekToOffset:offset]; +} + +-(int) readIntoBuffer:(UInt8*)buffer withSize:(int)size +{ + return [self.innerDataSource readIntoBuffer:buffer withSize:size]; +} + +-(long long) position +{ + return self.innerDataSource.position; +} + +-(BOOL) registerForEvents:(NSRunLoop*)runLoop +{ + return [self.innerDataSource registerForEvents:runLoop]; +} + +-(void) unregisterForEvents +{ + [self.innerDataSource unregisterForEvents]; +} + +-(void) close +{ + [self.innerDataSource close]; +} + +-(BOOL) hasBytesAvailable +{ + return self.innerDataSource.hasBytesAvailable; +} + +-(void) dataSourceDataAvailable:(DataSource*)dataSource +{ + [self.delegate dataSourceDataAvailable:self]; +} + +-(void) dataSourceErrorOccured:(DataSource*)dataSource +{ + [self.delegate dataSourceErrorOccured:self]; +} + +-(void) dataSourceEof:(DataSource*)dataSource +{ + [self.delegate dataSourceEof:self]; +} + +@end diff --git a/Default-568h@2x.png b/Default-568h@2x.png new file mode 100644 index 0000000..0891b7a Binary files /dev/null and b/Default-568h@2x.png differ