diff --git a/Audjustable.xcodeproj/project.pbxproj b/Audjustable.xcodeproj/project.pbxproj new file mode 100644 index 0000000..3ea6ff9 --- /dev/null +++ b/Audjustable.xcodeproj/project.pbxproj @@ -0,0 +1,340 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 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 */; }; + A186B4F0157F80E700BD0084 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = A186B4EF157F80E700BD0084 /* main.m */; }; + A186B4F4157F80E700BD0084 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A186B4F3157F80E700BD0084 /* AppDelegate.m */; }; + A186B50C157F813100BD0084 /* AudioPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = A186B501157F813100BD0084 /* AudioPlayer.m */; }; + A186B50D157F813100BD0084 /* CoreFoundationDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = A186B503157F813100BD0084 /* CoreFoundationDataSource.m */; }; + A186B50E157F813100BD0084 /* DataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = A186B505157F813100BD0084 /* DataSource.m */; }; + A186B50F157F813100BD0084 /* HttpDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = A186B507157F813100BD0084 /* HttpDataSource.m */; }; + A186B510157F813100BD0084 /* LocalFileDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = A186B509157F813100BD0084 /* LocalFileDataSource.m */; }; + A186B511157F813100BD0084 /* AudioPlayerView.m in Sources */ = {isa = PBXBuildFile; fileRef = A186B50B157F813100BD0084 /* AudioPlayerView.m */; }; + A186B514157F817500BD0084 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A186B513157F817500BD0084 /* AudioToolbox.framework */; }; + A186B518157F818900BD0084 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A186B517157F818900BD0084 /* CoreFoundation.framework */; }; + A186B51A157F819500BD0084 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A186B519157F819500BD0084 /* CFNetwork.framework */; }; + A186B51D157F825400BD0084 /* sample.m4a in Resources */ = {isa = PBXBuildFile; fileRef = A186B51C157F820F00BD0084 /* sample.m4a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 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; }; + A186B4E7157F80E700BD0084 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + A186B4EB157F80E700BD0084 /* Audjustable-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Audjustable-Info.plist"; sourceTree = ""; }; + A186B4ED157F80E700BD0084 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + A186B4EF157F80E700BD0084 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + A186B4F1157F80E700BD0084 /* Audjustable-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Audjustable-Prefix.pch"; sourceTree = ""; }; + A186B4F2157F80E700BD0084 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + A186B4F3157F80E700BD0084 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + A186B500157F813100BD0084 /* AudioPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioPlayer.h; sourceTree = ""; }; + A186B501157F813100BD0084 /* AudioPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioPlayer.m; sourceTree = ""; }; + A186B502157F813100BD0084 /* CoreFoundationDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CoreFoundationDataSource.h; sourceTree = ""; }; + A186B503157F813100BD0084 /* CoreFoundationDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CoreFoundationDataSource.m; sourceTree = ""; }; + A186B504157F813100BD0084 /* DataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataSource.h; sourceTree = ""; }; + A186B505157F813100BD0084 /* DataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DataSource.m; sourceTree = ""; }; + A186B506157F813100BD0084 /* HttpDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HttpDataSource.h; sourceTree = ""; }; + A186B507157F813100BD0084 /* HttpDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HttpDataSource.m; sourceTree = ""; }; + A186B508157F813100BD0084 /* LocalFileDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LocalFileDataSource.h; sourceTree = ""; }; + A186B509157F813100BD0084 /* LocalFileDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocalFileDataSource.m; sourceTree = ""; }; + A186B50A157F813100BD0084 /* AudioPlayerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioPlayerView.h; sourceTree = ""; }; + A186B50B157F813100BD0084 /* AudioPlayerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioPlayerView.m; sourceTree = ""; }; + A186B513157F817500BD0084 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; + A186B517157F818900BD0084 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + A186B519157F819500BD0084 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; + A186B51C157F820F00BD0084 /* sample.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = sample.m4a; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A186B4DC157F80E600BD0084 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A186B51A157F819500BD0084 /* CFNetwork.framework in Frameworks */, + A186B518157F818900BD0084 /* CoreFoundation.framework in Frameworks */, + A186B514157F817500BD0084 /* AudioToolbox.framework in Frameworks */, + A186B4E4157F80E700BD0084 /* UIKit.framework in Frameworks */, + A186B4E6157F80E700BD0084 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A186B4D4157F80E600BD0084 = { + isa = PBXGroup; + children = ( + A186B4E9157F80E700BD0084 /* Audjustable */, + A186B4E2157F80E700BD0084 /* Frameworks */, + A186B4E0157F80E600BD0084 /* Products */, + ); + sourceTree = ""; + }; + A186B4E0157F80E600BD0084 /* Products */ = { + isa = PBXGroup; + children = ( + A186B4DF157F80E600BD0084 /* Audjustable.app */, + ); + name = Products; + sourceTree = ""; + }; + A186B4E2157F80E700BD0084 /* Frameworks */ = { + isa = PBXGroup; + children = ( + A186B519157F819500BD0084 /* CFNetwork.framework */, + A186B517157F818900BD0084 /* CoreFoundation.framework */, + A186B513157F817500BD0084 /* AudioToolbox.framework */, + A186B4E3157F80E700BD0084 /* UIKit.framework */, + A186B4E5157F80E700BD0084 /* Foundation.framework */, + A186B4E7157F80E700BD0084 /* CoreGraphics.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + A186B4E9157F80E700BD0084 /* Audjustable */ = { + isa = PBXGroup; + children = ( + A186B4FE157F813100BD0084 /* Classes */, + A186B4F2157F80E700BD0084 /* AppDelegate.h */, + A186B4F3157F80E700BD0084 /* AppDelegate.m */, + A186B4EA157F80E700BD0084 /* Supporting Files */, + ); + path = Audjustable; + sourceTree = ""; + }; + A186B4EA157F80E700BD0084 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + A186B51B157F81D900BD0084 /* Resources */, + A186B4EB157F80E700BD0084 /* Audjustable-Info.plist */, + A186B4EC157F80E700BD0084 /* InfoPlist.strings */, + A186B4EF157F80E700BD0084 /* main.m */, + A186B4F1157F80E700BD0084 /* Audjustable-Prefix.pch */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + A186B4FE157F813100BD0084 /* Classes */ = { + isa = PBXGroup; + children = ( + A186B4FF157F813100BD0084 /* AudioPlayer */, + A186B50A157F813100BD0084 /* AudioPlayerView.h */, + A186B50B157F813100BD0084 /* AudioPlayerView.m */, + ); + path = Classes; + sourceTree = ""; + }; + A186B4FF157F813100BD0084 /* AudioPlayer */ = { + isa = PBXGroup; + children = ( + A186B500157F813100BD0084 /* AudioPlayer.h */, + A186B501157F813100BD0084 /* AudioPlayer.m */, + A186B502157F813100BD0084 /* CoreFoundationDataSource.h */, + A186B503157F813100BD0084 /* CoreFoundationDataSource.m */, + A186B504157F813100BD0084 /* DataSource.h */, + A186B505157F813100BD0084 /* DataSource.m */, + A186B506157F813100BD0084 /* HttpDataSource.h */, + A186B507157F813100BD0084 /* HttpDataSource.m */, + A186B508157F813100BD0084 /* LocalFileDataSource.h */, + A186B509157F813100BD0084 /* LocalFileDataSource.m */, + ); + path = AudioPlayer; + sourceTree = ""; + }; + A186B51B157F81D900BD0084 /* Resources */ = { + isa = PBXGroup; + children = ( + A186B51C157F820F00BD0084 /* sample.m4a */, + ); + name = Resources; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + A186B4DE157F80E600BD0084 /* Audjustable */ = { + isa = PBXNativeTarget; + buildConfigurationList = A186B4F7157F80E700BD0084 /* Build configuration list for PBXNativeTarget "Audjustable" */; + buildPhases = ( + A186B4DB157F80E600BD0084 /* Sources */, + A186B4DC157F80E600BD0084 /* Frameworks */, + A186B4DD157F80E600BD0084 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Audjustable; + productName = Audjustable; + productReference = A186B4DF157F80E600BD0084 /* Audjustable.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A186B4D6157F80E600BD0084 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0430; + }; + buildConfigurationList = A186B4D9157F80E600BD0084 /* Build configuration list for PBXProject "Audjustable" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = A186B4D4157F80E600BD0084; + productRefGroup = A186B4E0157F80E600BD0084 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A186B4DE157F80E600BD0084 /* Audjustable */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A186B4DD157F80E600BD0084 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A186B4EE157F80E700BD0084 /* InfoPlist.strings in Resources */, + A186B51D157F825400BD0084 /* sample.m4a in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A186B4DB157F80E600BD0084 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A186B4F0157F80E700BD0084 /* main.m in Sources */, + A186B4F4157F80E700BD0084 /* AppDelegate.m in Sources */, + A186B50C157F813100BD0084 /* AudioPlayer.m in Sources */, + A186B50D157F813100BD0084 /* CoreFoundationDataSource.m in Sources */, + A186B50E157F813100BD0084 /* DataSource.m in Sources */, + A186B50F157F813100BD0084 /* HttpDataSource.m in Sources */, + A186B510157F813100BD0084 /* LocalFileDataSource.m in Sources */, + A186B511157F813100BD0084 /* AudioPlayerView.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + A186B4EC157F80E700BD0084 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + A186B4ED157F80E700BD0084 /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + A186B4F5157F80E700BD0084 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + CLANG_ENABLE_OBJC_ARC = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 5.1; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + A186B4F6157F80E700BD0084 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + CLANG_ENABLE_OBJC_ARC = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 5.1; + OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + A186B4F8157F80E700BD0084 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "Audjustable/Audjustable-Prefix.pch"; + INFOPLIST_FILE = "Audjustable/Audjustable-Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + A186B4F9157F80E700BD0084 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "Audjustable/Audjustable-Prefix.pch"; + INFOPLIST_FILE = "Audjustable/Audjustable-Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A186B4D9157F80E600BD0084 /* Build configuration list for PBXProject "Audjustable" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A186B4F5157F80E700BD0084 /* Debug */, + A186B4F6157F80E700BD0084 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A186B4F7157F80E700BD0084 /* Build configuration list for PBXNativeTarget "Audjustable" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A186B4F8157F80E700BD0084 /* Debug */, + A186B4F9157F80E700BD0084 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = A186B4D6157F80E600BD0084 /* Project object */; +} diff --git a/Audjustable/AppDelegate.h b/Audjustable/AppDelegate.h new file mode 100644 index 0000000..7d84248 --- /dev/null +++ b/Audjustable/AppDelegate.h @@ -0,0 +1,20 @@ +// +// AppDelegate.h +// BlueCucumber-AudioPlayer +// +// Created by Thong Nguyen on 01/06/2012. +// Copyright (c) 2012 Thong Nguyen All rights reserved. +// + +#import +#import "AudioPlayerView.h" + +@interface AppDelegate : UIResponder +{ +@private + AudioPlayer* audioPlayer; +} + +@property (strong, nonatomic) UIWindow *window; + +@end diff --git a/Audjustable/AppDelegate.m b/Audjustable/AppDelegate.m new file mode 100644 index 0000000..d26435c --- /dev/null +++ b/Audjustable/AppDelegate.m @@ -0,0 +1,50 @@ +// +// AppDelegate.m +// BlueCucumber-AudioPlayer +// +// Created by Thong Nguyen on 01/06/2012. +// Copyright (c) 2012 Thong Nguyen All rights reserved. +// + +#import "AppDelegate.h" +#import "AudioPlayerView.h" + +@implementation AppDelegate + +@synthesize window = _window; + +-(BOOL) application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions +{ + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + + self.window.backgroundColor = [UIColor whiteColor]; + + audioPlayer = [[AudioPlayer alloc] init]; + AudioPlayerView* audioPlayerView = [[AudioPlayerView alloc] initWithFrame:self.window.bounds]; + + audioPlayerView.delegate = self; + audioPlayerView.audioPlayer = audioPlayer; + + [self.window addSubview:audioPlayerView]; + + [self.window makeKeyAndVisible]; + + return YES; +} + +-(void) audioPlayerViewPlayFromHTTPSelected:(AudioPlayerView*)audioPlayerView +{ + NSURL* url = [NSURL URLWithString:@"http://audjustable.googlecode.com/files/sample.m4a"]; + + [audioPlayer setDataSource:[audioPlayer dataSourceFromURL:url] withQueueItemId:url]; +} + +-(void) audioPlayerViewPlayFromLocalFileSelected:(AudioPlayerView *)audioPlayerView +{ + NSString * path = [[NSBundle mainBundle] pathForResource:@"sample" ofType:@"m4a"]; + NSURL* url = [NSURL fileURLWithPath:path]; + + [audioPlayer setDataSource:[audioPlayer dataSourceFromURL:url] withQueueItemId:url]; +} + +@end diff --git a/Audjustable/Audjustable-Info.plist b/Audjustable/Audjustable-Info.plist new file mode 100644 index 0000000..e3f619b --- /dev/null +++ b/Audjustable/Audjustable-Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + AbstractPath.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Audjustable/Audjustable-Prefix.pch b/Audjustable/Audjustable-Prefix.pch new file mode 100644 index 0000000..250976e --- /dev/null +++ b/Audjustable/Audjustable-Prefix.pch @@ -0,0 +1,14 @@ +// +// Prefix header for all source files of the 'Audjustable' target in the 'Audjustable' project +// + +#import + +#ifndef __IPHONE_3_0 +#warning "This project uses features only available in iOS SDK 3.0 and later." +#endif + +#ifdef __OBJC__ + #import + #import +#endif diff --git a/Audjustable/Classes/AudioPlayer/AudioPlayer.h b/Audjustable/Classes/AudioPlayer/AudioPlayer.h new file mode 100644 index 0000000..10be6ec --- /dev/null +++ b/Audjustable/Classes/AudioPlayer/AudioPlayer.h @@ -0,0 +1,192 @@ +/********************************************************************************** + AudioPlayer.m + + Created by Thong Nguyen on 14/05/2012. + http://http://code.google.com/p/bluecucumber + + Inspired by Matt Gallagher's AudioStreamer: + https://github.com/mattgallagher/AudioStreamer + + Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + This product includes software developed by the . + 4. Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**********************************************************************************/ + +#import +#import +#import "DataSource.h" +#include + +#define AudioPlayerDefaultNumberOfAudioQueueBuffers (8 * 1024) + +typedef enum +{ + AudioPlayerInternalStateInitialised = 0, + AudioPlayerInternalStateRunning = 1, + AudioPlayerInternalStatePlaying = (1 << 1) | AudioPlayerInternalStateRunning, + AudioPlayerInternalStateStartingThread = (1 << 2) | AudioPlayerInternalStateRunning, + AudioPlayerInternalStateWaitingForData = (1 << 3) | AudioPlayerInternalStateRunning, + AudioPlayerInternalStateWaitingForQueueToStart = (1 << 5) | AudioPlayerInternalStateRunning, + AudioPlayerInternalStatePaused = (1 << 6) | AudioPlayerInternalStateRunning, + AudioPlayerInternalStateStopping = (1 << 7), + AudioPlayerInternalStateStopped = (1 << 8), + AudioPlayerInternalStateDisposed = (1 << 9), + AudioPlayerInternalStateError = (1 << 10) +} +AudioPlayerInternalState; + +typedef enum +{ + AudioPlayerStateReady, + AudioPlayerStateRunning = 1, + AudioPlayerStatePlaying = (1 << 1) | AudioPlayerStateRunning, + AudioPlayerStatePaused = (1 << 2) | AudioPlayerStateRunning, + AudioPlayerStateStopped = (1 << 3), + AudioPlayerStateError = (1 << 4), + AudioPlayerStateDisposed = (1 << 5) +} +AudioPlayerState; + +typedef enum +{ + AudioPlayerStopReasonNoStop = 0, + AudioPlayerStopReasonEof, + AudioPlayerStopReasonUserAction, + AudioPlayerStopReasonUserActionFlushStop +} +AudioPlayerStopReason; + +typedef enum +{ + AudioPlayerErrorNone = 0, + AudioPlayerErrorDataSource, + AudioPlayerErrorStreamParseBytesFailed, + AudioPlayerErrorDataNotFound, + AudioPlayerErrorQueueStartFailed, + AudioPlayerErrorQueuePauseFailed, + AudioPlayerErrorUnknownBuffer, + AudioPlayerErrorQueueStopFailed, + AudioPlayerErrorOther +} +AudioPlayerErrorCode; + +@class AudioPlayer; + +@protocol AudioPlayerDelegate +-(void) audioPlayer:(AudioPlayer*)audioPlayer stateChanged:(AudioPlayerState)state; +-(void) audioPlayer:(AudioPlayer*)audioPlayer didEncounterError:(AudioPlayerErrorCode)errorCode; +-(void) audioPlayer:(AudioPlayer*)audioPlayer didStartPlayingQueueItemId:(NSObject*)queueItemId; +-(void) audioPlayer:(AudioPlayer*)audioPlayer didFinishBufferingSourceWithQueueItemId:(NSObject*)queueItemId; +-(void) audioPlayer:(AudioPlayer*)audioPlayer didFinishPlayingQueueItemId:(NSObject*)queueItemId withReason:(AudioPlayerStopReason)stopReason andProgress:(double)progress andDuration:(double)duration; +@optional +-(void) audioPlayer:(AudioPlayer*)audioPlayer internalStateChanged:(AudioPlayerInternalState)state; +@end + +@class QueueEntry; + +typedef struct +{ + AudioQueueBufferRef ref; + int bufferIndex; +} +AudioQueueBufferRefLookupEntry; + +@interface AudioPlayer : NSObject +{ +@private + UInt8* readBuffer; + int readBufferSize; + + dispatch_queue_t fastApiSerialQueue; + + QueueEntry* currentlyPlayingEntry; + QueueEntry* currentlyReadingEntry; + + NSMutableArray* upcomingQueue; + NSMutableArray* bufferingQueue; + + AudioQueueBufferRef* audioQueueBuffer; + AudioQueueBufferRefLookupEntry* audioQueueBufferLookup; + int audioQueueBufferRefLookupCount; + int audioQueueBufferCount; + AudioStreamPacketDescription* packetDescs; + bool* bufferUsed; + int numberOfBuffersUsed; + + AudioQueueRef audioQueue; + AudioStreamBasicDescription currentAudioStreamBasicDescription; + + NSThread* playbackThread; + NSRunLoop* playbackThreadRunLoop; + NSConditionLock* threadFinishedCondLock; + + AudioFileStreamID audioFileStream; + + BOOL discontinuous; + BOOL nextIsIncompatible; + + int bytesFilled; + int packetsFilled; + + int fillBufferIndex; + + UIBackgroundTaskIdentifier backgroundTaskId; + + AudioPlayerErrorCode errorCode; + AudioPlayerStopReason stopReason; + + pthread_mutex_t queueBuffersMutex; + pthread_cond_t queueBufferReadyCondition; + + volatile BOOL disposeWasRequested; + volatile BOOL seekToTimeWasRequested; + volatile BOOL newFileToPlay; + volatile double requestedSeekTime; + volatile BOOL audioQueueFlushing; + volatile SInt64 audioPacketsReadCount; + volatile SInt64 audioPacketsPlayedCount; +} + +@property (readonly) double duration; +@property (readonly) double progress; +@property (readwrite) AudioPlayerState state; +@property (readonly) AudioPlayerStopReason stopReason; +@property (readwrite, unsafe_unretained) id delegate; + +-(id) init; +-(id) initWithNumberOfAudioQueueBuffers:(int)numberOfAudioQueueBuffers andReadBufferSize:(int)readBufferSizeIn; +-(DataSource*) dataSourceFromURL:(NSURL*)url; +-(void) play:(NSURL*)url; +-(void) queueDataSource:(DataSource*)dataSource withQueueItemId:(NSObject*)queueItemId; +-(void) setDataSource:(DataSource*)dataSourceIn withQueueItemId:(NSObject*)queueItemId; +-(void) seekToTime:(double)value; +-(void) pause; +-(void) resume; +-(void) stop; +-(void) flushStop; +-(void) dispose; + +@end diff --git a/Audjustable/Classes/AudioPlayer/AudioPlayer.m b/Audjustable/Classes/AudioPlayer/AudioPlayer.m new file mode 100644 index 0000000..5d0c7c7 --- /dev/null +++ b/Audjustable/Classes/AudioPlayer/AudioPlayer.m @@ -0,0 +1,1794 @@ +/********************************************************************************** + AudioPlayer.m + + Created by Thong Nguyen on 14/05/2012. + http://http://code.google.com/p/bluecucumber + + Inspired by Matt Gallagher's AudioStreamer: + https://github.com/mattgallagher/AudioStreamer + + Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + This product includes software developed by the . + 4. Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**********************************************************************************/ + +#import "AudioPlayer.h" +#import "AudioToolbox/AudioToolbox.h" +#import "HttpDataSource.h" +#import "LocalFileDataSource.h" +#import "libkern/OSAtomic.h" + +#define BitRateEstimationMinPackets (64) +#define AudioPlayerBuffersNeededToStart (16) +#define AudioPlayerDefaultReadBufferSize (4 * 1024) +#define AudioPlayerDefaultPacketBufferSize (1024) + +@interface NSMutableArray(AudioPlayerExtensions) +-(void) enqueue:(id)obj; +-(id) dequeue; +-(id) peek; +@end + +@implementation NSMutableArray(AudioPlayerExtensions) + +-(void) enqueue:(id)obj +{ + [self insertObject:obj atIndex:0]; +} + +-(void) skipQueue:(id)obj +{ + [self addObject:obj]; +} + +-(id) dequeue +{ + if ([self count] == 0) + { + return nil; + } + + id retval = [self lastObject]; + + [self removeLastObject]; + + return retval; +} + +-(id) peek +{ + return [self lastObject]; +} + +-(id) peekRecent +{ + if (self.count == 0) + { + return nil; + } + + return [self objectAtIndex:0]; +} + +@end + +@interface QueueEntry : NSObject +{ +@public + BOOL parsedHeader; + double sampleRate; + double lastProgress; + double packetDuration; + UInt64 audioDataOffset; + UInt64 audioDataByteCount; + UInt32 packetBufferSize; + volatile double seekTime; + volatile int bytesPlayed; + volatile int processedPacketsCount; + volatile int processedPacketsSizeTotal; + AudioStreamBasicDescription audioStreamBasicDescription; +} +@property (readwrite, retain) NSObject* queueItemId; +@property (readwrite, retain) DataSource* dataSource; +@property (readwrite) int bufferIndex; +@property (readonly) UInt64 audioDataLengthInBytes; + +-(double) duration; +-(double) calculatedBitRate; +-(double) progress; + +-(id) initWithDataSource:(DataSource*)dataSource andQueueItemId:(NSObject*)queueItemId; +-(id) initWithDataSource:(DataSource*)dataSource andQueueItemId:(NSObject*)queueItemId andBufferIndex:(int)bufferIndex; + +@end + +@implementation QueueEntry +@synthesize dataSource, queueItemId, bufferIndex; + +-(id) initWithDataSource:(DataSource*)dataSourceIn andQueueItemId:(NSObject*)queueItemIdIn +{ + return [self initWithDataSource:dataSourceIn andQueueItemId:queueItemIdIn andBufferIndex:-1]; +} + +-(id) initWithDataSource:(DataSource*)dataSourceIn andQueueItemId:(NSObject*)queueItemIdIn andBufferIndex:(int)bufferIndexIn +{ + if (self = [super init]) + { + self.dataSource = dataSourceIn; + self.queueItemId = queueItemIdIn; + self.bufferIndex = bufferIndexIn; + } + + return self; +} + +-(double) calculatedBitRate +{ + double retval; + + if (packetDuration && processedPacketsCount > BitRateEstimationMinPackets) + { + double averagePacketByteSize = processedPacketsSizeTotal / processedPacketsCount; + + retval = averagePacketByteSize / packetDuration * 8; + + return retval; + } + + retval = (audioStreamBasicDescription.mBytesPerFrame * audioStreamBasicDescription.mSampleRate) * 8; + + return retval; +} + +-(double) progress +{ + double retval = lastProgress; + double duration = [self duration]; + + if (self->sampleRate > 0) + { + double calculatedBitrate = [self calculatedBitRate]; + + retval = self->bytesPlayed / calculatedBitrate * 8; + + retval = seekTime + retval; + } + + if (retval > duration) + { + retval = duration; + } + + return retval; +} + +-(double) duration +{ + if (self->sampleRate <= 0) + { + return 0; + } + + UInt64 audioDataLengthInBytes = [self audioDataLengthInBytes]; + + double calculatedBitRate = [self calculatedBitRate]; + + if (calculatedBitRate == 0 || self->dataSource.length == 0) + { + return 0; + } + + return audioDataLengthInBytes / (calculatedBitRate / 8); +} + +-(UInt64) audioDataLengthInBytes +{ + if (audioDataByteCount) + { + return audioDataByteCount; + } + else + { + if (!dataSource.length) + { + return 0; + } + + return dataSource.length - audioDataOffset; + } +} + +-(NSString*) description +{ + return [[self queueItemId] description]; +} + +@end + +@interface AudioPlayer() +@property (readwrite) AudioPlayerInternalState internalState; + +-(void) processQueue:(BOOL)skipCurrent; +-(void) createAudioQueue; +-(void) enqueueBuffer; +-(void) resetAudioQueue; +-(BOOL) startAudioQueue; +-(void) stopAudioQueue; +-(BOOL) processRunloop; +-(void) wakeupPlaybackThread; +-(void) audioQueueFinishedPlaying:(QueueEntry*)entry; +-(void) processSeekToTime; +-(void) didEncounterError:(AudioPlayerErrorCode)errorCode; +-(void) setInternalState:(AudioPlayerInternalState)value; +-(void) processDidFinishPlaying:(QueueEntry*)entry withNext:(QueueEntry*)next; +-(void) handlePropertyChangeForFileStream:(AudioFileStreamID)audioFileStreamIn fileStreamPropertyID:(AudioFileStreamPropertyID)propertyID ioFlags:(UInt32*)ioFlags; +-(void) handleAudioPackets:(const void*)inputData numberBytes:(UInt32)numberBytes numberPackets:(UInt32)numberPackets packetDescriptions:(AudioStreamPacketDescription*)packetDescriptions; +-(void) handleAudioQueueOutput:(AudioQueueRef)audioQueue buffer:(AudioQueueBufferRef)buffer; +-(void) handlePropertyChangeForQueue:(AudioQueueRef)audioQueue propertyID:(AudioQueuePropertyID)propertyID; +@end + +static void AudioFileStreamPropertyListenerProc(void* clientData, AudioFileStreamID audioFileStream, AudioFileStreamPropertyID propertyId, UInt32* flags) +{ + AudioPlayer* player = (__bridge AudioPlayer*)clientData; + + [player handlePropertyChangeForFileStream:audioFileStream fileStreamPropertyID:propertyId ioFlags:flags]; +} + +static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UInt32 numberPackets, const void* inputData, AudioStreamPacketDescription* packetDescriptions) +{ + AudioPlayer* player = (__bridge AudioPlayer*)clientData; + + [player handleAudioPackets:inputData numberBytes:numberBytes numberPackets:numberPackets packetDescriptions:packetDescriptions]; +} + +static void AudioQueueOutputCallbackProc(void* clientData, AudioQueueRef audioQueue, AudioQueueBufferRef buffer) +{ + AudioPlayer* player = (__bridge AudioPlayer*)clientData; + + [player handleAudioQueueOutput:audioQueue buffer:buffer]; +} + +static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQueue, AudioQueuePropertyID propertyId) +{ + AudioPlayer* player = (__bridge AudioPlayer*)userData; + + [player handlePropertyChangeForQueue:audioQueue propertyID:propertyId]; +} + +@implementation AudioPlayer +@synthesize delegate, internalState, state; + +-(AudioPlayerInternalState) internalState +{ + return internalState; +} + +-(void) setInternalState:(AudioPlayerInternalState)value +{ + if (value == internalState) + { + return; + } + + internalState = value; + + if ([self.delegate respondsToSelector:@selector(internalStateChanged:)]) + { + dispatch_async(dispatch_get_main_queue(), ^ + { + [self.delegate audioPlayer:self internalStateChanged:internalState]; + }); + } + + AudioPlayerState newState; + + switch (internalState) + { + case AudioPlayerInternalStateInitialised: + newState = AudioPlayerStateReady; + break; + case AudioPlayerInternalStateRunning: + case AudioPlayerInternalStateStartingThread: + case AudioPlayerInternalStateWaitingForData: + case AudioPlayerInternalStateWaitingForQueueToStart: + case AudioPlayerInternalStatePlaying: + newState = AudioPlayerStatePlaying; + break; + case AudioPlayerInternalStateStopping: + case AudioPlayerInternalStateStopped: + newState = AudioPlayerStateStopped; + break; + case AudioPlayerInternalStatePaused: + newState = AudioPlayerStatePaused; + break; + case AudioPlayerInternalStateDisposed: + newState = AudioPlayerStateDisposed; + break; + case AudioPlayerInternalStateError: + newState = AudioPlayerStateError; + break; + } + + if (newState != self.state) + { + self.state = newState; + + dispatch_async(dispatch_get_main_queue(), ^ + { + [self.delegate audioPlayer:self stateChanged:self.state]; + }); + } +} + +-(AudioPlayerStopReason) stopReason +{ + return stopReason; +} + +-(BOOL) audioQueueIsRunning +{ + UInt32 isRunning; + UInt32 isRunningSize = sizeof(isRunning); + + AudioQueueGetProperty(audioQueue, kAudioQueueProperty_IsRunning, &isRunning, &isRunningSize); + + return isRunning ? YES : NO; +} + +-(id) init +{ + return [self initWithNumberOfAudioQueueBuffers:AudioPlayerDefaultNumberOfAudioQueueBuffers andReadBufferSize:AudioPlayerDefaultReadBufferSize]; +} + +-(id) initWithNumberOfAudioQueueBuffers:(int)numberOfAudioQueueBuffers andReadBufferSize:(int)readBufferSizeIn +{ + if (self = [super init]) + { + fastApiSerialQueue = dispatch_queue_create("AudioPlayer.fastepi", 0); + + readBufferSize = readBufferSizeIn; + readBuffer = calloc(sizeof(UInt8), readBufferSize); + + audioQueueBufferCount = numberOfAudioQueueBuffers; + audioQueueBuffer = calloc(sizeof(AudioQueueBufferRef), audioQueueBufferCount); + + audioQueueBufferRefLookupCount = audioQueueBufferCount * 2; + audioQueueBufferLookup = calloc(sizeof(AudioQueueBufferRefLookupEntry), audioQueueBufferRefLookupCount); + + packetDescs = calloc(sizeof(AudioStreamPacketDescription), audioQueueBufferCount); + bufferUsed = calloc(sizeof(bool), audioQueueBufferCount); + + pthread_mutex_init(&queueBuffersMutex, NULL); + pthread_cond_init(&queueBufferReadyCondition, NULL); + + threadFinishedCondLock = [[NSConditionLock alloc] initWithCondition:0]; + + self.internalState = AudioPlayerInternalStateInitialised; + + upcomingQueue = [[NSMutableArray alloc] init]; + bufferingQueue = [[NSMutableArray alloc] init]; + } + + return self; +} + +-(void) dealloc +{ + dispatch_release(fastApiSerialQueue); + + pthread_mutex_destroy(&queueBuffersMutex); + pthread_cond_destroy(&queueBufferReadyCondition); + + if (audioFileStream) + { + AudioFileStreamClose(audioFileStream); + } + + if (audioQueue) + { + AudioQueueDispose(audioQueue, true); + } + + free(bufferUsed); + free(readBuffer); + free(packetDescs); + free(audioQueueBuffer); + free(audioQueueBufferLookup); +} + +-(void) startSystemBackgroundTask +{ + @synchronized(self) + { + if (backgroundTaskId != UIBackgroundTaskInvalid) + { + return; + } + + backgroundTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^ + { + [self stopSystemBackgroundTask]; + }]; + } +} + +-(void) stopSystemBackgroundTask +{ + @synchronized(self) + { + if (backgroundTaskId != UIBackgroundTaskInvalid) + { + [[UIApplication sharedApplication] endBackgroundTask:backgroundTaskId]; + + backgroundTaskId = UIBackgroundTaskInvalid; + } + } +} + +-(DataSource*) dataSourceFromURL:(NSURL*)url +{ + DataSource* retval; + + if ([url.scheme isEqualToString:@"file"]) + { + retval = [[LocalFileDataSource alloc] initWithFilePath:url.path]; + } + else + { + retval = [[HttpDataSource alloc] initWithURL:url]; + } + + return retval; +} + +-(void) clearQueue +{ + @synchronized(self) + { + [upcomingQueue removeAllObjects]; + } +} + +-(void) play:(NSURL*)url +{ + [self setDataSource:[self dataSourceFromURL:url] withQueueItemId:url]; +} + +-(void) setDataSource:(DataSource*)dataSourceIn withQueueItemId:(NSObject*)queueItemId +{ + dispatch_async(fastApiSerialQueue, ^ + { + @synchronized(self) + { + [self startSystemBackgroundTask]; + + [self clearQueue]; + + [upcomingQueue enqueue:[[QueueEntry alloc] initWithDataSource:dataSourceIn andQueueItemId:queueItemId]]; + + self.internalState = AudioPlayerInternalStateInitialised; + [self processQueue:YES]; + } + }); +} + +-(void) queueDataSource:(DataSource*)dataSourceIn withQueueItemId:(NSObject*)queueItemId +{ + dispatch_async(fastApiSerialQueue, ^ + { + @synchronized(self) + { + [upcomingQueue enqueue:[[QueueEntry alloc] initWithDataSource:dataSourceIn andQueueItemId:queueItemId]]; + + [self processQueue:NO]; + } + }); +} + +-(void) handlePropertyChangeForFileStream:(AudioFileStreamID)inAudioFileStream fileStreamPropertyID:(AudioFileStreamPropertyID)inPropertyID ioFlags:(UInt32*)ioFlags +{ + OSStatus error; + + switch (inPropertyID) + { + case kAudioFileStreamProperty_DataOffset: + { + SInt64 offset; + UInt32 offsetSize = sizeof(offset); + + AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_DataOffset, &offsetSize, &offset); + + currentlyReadingEntry->parsedHeader = YES; + currentlyReadingEntry->audioDataOffset = offset; + } + break; + case kAudioFileStreamProperty_DataFormat: + { + AudioStreamBasicDescription newBasicDescription; + UInt32 size = sizeof(newBasicDescription); + + AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataFormat, &size, &newBasicDescription); + + currentlyReadingEntry->audioStreamBasicDescription = newBasicDescription; + + currentlyReadingEntry->sampleRate = currentlyReadingEntry->audioStreamBasicDescription.mSampleRate; + currentlyReadingEntry->packetDuration = currentlyReadingEntry->audioStreamBasicDescription.mFramesPerPacket / currentlyReadingEntry->sampleRate; + + UInt32 packetBufferSize = 0; + UInt32 sizeOfPacketBufferSize = sizeof(packetBufferSize); + + error = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_PacketSizeUpperBound, &sizeOfPacketBufferSize, &packetBufferSize); + + if (error || packetBufferSize == 0) + { + error = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_MaximumPacketSize, &sizeOfPacketBufferSize, &packetBufferSize); + + if (error || packetBufferSize == 0) + { + currentlyReadingEntry->packetBufferSize = AudioPlayerDefaultPacketBufferSize; + } + } + } + break; + case kAudioFileStreamProperty_AudioDataByteCount: + { + UInt64 audioDataByteCount; + UInt32 byteCountSize = sizeof(audioDataByteCount); + + AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_AudioDataByteCount, &byteCountSize, &audioDataByteCount); + + currentlyReadingEntry->audioDataByteCount = audioDataByteCount; + } + break; + case kAudioFileStreamProperty_ReadyToProducePackets: + { + discontinuous = YES; + } + break; + } +} + +-(void) handleAudioPackets:(const void*)inputData numberBytes:(UInt32)numberBytes numberPackets:(UInt32)numberPackets packetDescriptions:(AudioStreamPacketDescription*)packetDescriptionsIn +{ + if (currentlyReadingEntry == nil) + { + return; + } + + if (audioQueue == nil || memcmp(¤tAudioStreamBasicDescription, ¤tlyReadingEntry->audioStreamBasicDescription, sizeof(currentAudioStreamBasicDescription)) != 0) + { + [self createAudioQueue]; + } + + if (discontinuous) + { + discontinuous = NO; + } + + if (packetDescriptionsIn) + { + // VBR + + for (int i = 0; i < numberPackets; i++) + { + SInt64 packetOffset = packetDescriptionsIn[i].mStartOffset; + SInt64 packetSize = packetDescriptionsIn[i].mDataByteSize; + int bufSpaceRemaining; + + if (currentlyReadingEntry->processedPacketsSizeTotal < 0xfffff) + { + OSAtomicAdd32(packetSize, ¤tlyReadingEntry->processedPacketsSizeTotal); + OSAtomicIncrement32(¤tlyReadingEntry->processedPacketsCount); + } + + if (packetSize > currentlyReadingEntry->packetBufferSize) + { + return; + } + + bufSpaceRemaining = currentlyReadingEntry->packetBufferSize - bytesFilled; + + if (bufSpaceRemaining < packetSize) + { + [self enqueueBuffer]; + } + + if (bytesFilled + packetSize > currentlyReadingEntry->packetBufferSize) + { + return; + } + + AudioQueueBufferRef bufferToFill = audioQueueBuffer[fillBufferIndex]; + memcpy((char*)bufferToFill->mAudioData + bytesFilled, (const char*)inputData + packetOffset, packetSize); + + packetDescs[packetsFilled] = packetDescriptionsIn[i]; + packetDescs[packetsFilled].mStartOffset = bytesFilled; + + bytesFilled += packetSize; + packetsFilled++; + + int packetsDescRemaining = audioQueueBufferCount - packetsFilled; + + if (packetsDescRemaining <= 0) + { + [self enqueueBuffer]; + } + } + } + else + { + // CBR + + int offset = 0; + + while (numberBytes) + { + int bytesLeft = AudioPlayerDefaultPacketBufferSize - bytesFilled; + + if (bytesLeft < numberBytes) + { + [self enqueueBuffer]; + } + + @synchronized(self) + { + int copySize; + bytesLeft = AudioPlayerDefaultPacketBufferSize - bytesFilled; + + if (bytesLeft < numberBytes) + { + copySize = bytesLeft; + } + else + { + copySize = numberBytes; + } + + if (bytesFilled > currentlyPlayingEntry->packetBufferSize) + { + return; + } + + AudioQueueBufferRef fillBuf = audioQueueBuffer[fillBufferIndex]; + memcpy((char*)fillBuf->mAudioData + bytesFilled, (const char*)(inputData + offset), copySize); + + bytesFilled += copySize; + packetsFilled = 0; + numberBytes -= copySize; + offset += copySize; + } + } + } +} + +-(void) handleAudioQueueOutput:(AudioQueueRef)audioQueueIn buffer:(AudioQueueBufferRef)bufferIn +{ + int bufferIndex = -1; + + if (audioQueueIn != audioQueue) + { + return; + } + + if (currentlyPlayingEntry) + { + currentlyPlayingEntry->bytesPlayed += bufferIn->mAudioDataByteSize; + } + + int index = (int)bufferIn % audioQueueBufferRefLookupCount; + + for (int i = 0; i < audioQueueBufferCount; i++) + { + if (audioQueueBufferLookup[index].ref == bufferIn) + { + bufferIndex = audioQueueBufferLookup[index].bufferIndex; + + break; + } + + index++; + index %= audioQueueBufferRefLookupCount; + } + + audioPacketsPlayedCount++; + + if (bufferIndex == -1) + { + [self didEncounterError:AudioPlayerErrorUnknownBuffer]; + + pthread_mutex_lock(&queueBuffersMutex); + pthread_cond_signal(&queueBufferReadyCondition); + pthread_mutex_unlock(&queueBuffersMutex); + + return; + } + + pthread_mutex_lock(&queueBuffersMutex); + + bufferUsed[bufferIndex] = false; + numberOfBuffersUsed--; + + if (!audioQueueFlushing) + { + QueueEntry* entry = currentlyPlayingEntry; + + if (entry != nil) + { + if (entry.bufferIndex <= audioPacketsPlayedCount && entry.bufferIndex != -1) + { + entry.bufferIndex = -1; + + BLOG(@"Finished playing %@", entry.queueItemId); + + if (playbackThread) + { + BLOG(@"Trying to call CFRunLoopPerformBlock due to finished playing %@", entry.queueItemId); + + CFRunLoopPerformBlock([playbackThreadRunLoop getCFRunLoop], NSDefaultRunLoopMode, ^ + { + [self audioQueueFinishedPlaying:entry]; + }); + + CFRunLoopWakeUp([playbackThreadRunLoop getCFRunLoop]); + } + } + } + } + + // No need to signal constantly if we're reseting the AudioQueue + if ((audioQueueFlushing && numberOfBuffersUsed < 5) || !audioQueueFlushing) + { + pthread_cond_signal(&queueBufferReadyCondition); + } + + pthread_mutex_unlock(&queueBuffersMutex); +} + +-(void) handlePropertyChangeForQueue:(AudioQueueRef)audioQueueIn propertyID:(AudioQueuePropertyID)propertyId +{ + if (audioQueueIn != audioQueue) + { + return; + } + + if (propertyId == kAudioQueueProperty_IsRunning) + { + if (![self audioQueueIsRunning] && self.internalState == AudioPlayerInternalStateStopping) + { + self.internalState = AudioPlayerInternalStateStopped; + } + else if (self.internalState == AudioPlayerInternalStateWaitingForQueueToStart) + { + [NSRunLoop currentRunLoop]; + + self.internalState = AudioPlayerInternalStatePlaying; + } + } +} + +-(void) enqueueBuffer +{ + @synchronized(self) + { + OSStatus error; + + if (audioFileStream == 0) + { + return; + } + + if (self.internalState == AudioPlayerInternalStateStopped) + { + return; + } + + pthread_mutex_lock(&queueBuffersMutex); + + bufferUsed[fillBufferIndex] = true; + numberOfBuffersUsed++; + + pthread_mutex_unlock(&queueBuffersMutex); + + AudioQueueBufferRef buffer = audioQueueBuffer[fillBufferIndex]; + + buffer->mAudioDataByteSize = bytesFilled; + + if (packetsFilled) + { + + error = AudioQueueEnqueueBuffer(audioQueue, buffer, packetsFilled, packetDescs); + } + else + { + error = AudioQueueEnqueueBuffer(audioQueue, buffer, 0, NULL); + } + + audioPacketsReadCount++; + + if (error) + { + return; + } + + if (self.internalState == AudioPlayerInternalStateWaitingForData && numberOfBuffersUsed >= AudioPlayerBuffersNeededToStart) + { + if (![self startAudioQueue]) + { + return; + } + } + + if (++fillBufferIndex >= audioQueueBufferCount) + { + fillBufferIndex = 0; + } + + bytesFilled = 0; + packetsFilled = 0; + } + + pthread_mutex_lock(&queueBuffersMutex); + + while (bufferUsed[fillBufferIndex]) + { + pthread_cond_wait(&queueBufferReadyCondition, &queueBuffersMutex); + } + + pthread_mutex_unlock(&queueBuffersMutex); +} + +-(void) didEncounterError:(AudioPlayerErrorCode)errorCodeIn +{ + errorCode = errorCode; + self.internalState = AudioPlayerInternalStateError; +} + +-(void) createAudioQueue +{ + OSStatus error; + + [self startSystemBackgroundTask]; + + if (audioQueue) + { + AudioQueueStop(audioQueue, YES); + AudioQueueDispose(audioQueue, YES); + + audioQueue = nil; + } + + currentAudioStreamBasicDescription = currentlyPlayingEntry->audioStreamBasicDescription; + + error = AudioQueueNewOutput(¤tlyPlayingEntry->audioStreamBasicDescription, AudioQueueOutputCallbackProc, (__bridge void*)self, NULL, NULL, 0, &audioQueue); + + if (error) + { + return; + } + + error = AudioQueueAddPropertyListener(audioQueue, kAudioQueueProperty_IsRunning, AudioQueueIsRunningCallbackProc, (__bridge void*)self); + + if (error) + { + return; + } + +#if TARGET_OS_IPHONE + UInt32 val = kAudioQueueHardwareCodecPolicy_PreferHardware; + + error = AudioQueueSetProperty(audioQueue, kAudioQueueProperty_HardwareCodecPolicy, &val, sizeof(UInt32)); + + if (error) + { + BLOG(@"Could not set hardware codec policy"); + } +#endif + + memset(audioQueueBufferLookup, 0, sizeof(AudioQueueBufferRefLookupEntry) * audioQueueBufferRefLookupCount); + + // Allocate AudioQueue buffers + + for (int i = 0; i < audioQueueBufferCount; i++) + { + error = AudioQueueAllocateBuffer(audioQueue, currentlyPlayingEntry->packetBufferSize, &audioQueueBuffer[i]); + + int hash = (int)audioQueueBuffer[i] % audioQueueBufferRefLookupCount; + + while (true) + { + if (audioQueueBufferLookup[hash].ref == 0) + { + audioQueueBufferLookup[hash].ref = audioQueueBuffer[i]; + audioQueueBufferLookup[hash].bufferIndex = i; + + break; + } + else + { + hash++; + hash %= audioQueueBufferRefLookupCount; + } + } + + bufferUsed[i] = false; + + if (error) + { + return; + } + } + + audioPacketsReadCount = 0; + audioPacketsPlayedCount = 0; + + // Get file cookie/magic bytes information + + UInt32 cookieSize; + Boolean writable; + + error = AudioFileStreamGetPropertyInfo(audioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, &writable); + + if (error) + { + return; + } + + void* cookieData = calloc(1, cookieSize); + + error = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, cookieData); + + if (error) + { + free(cookieData); + + return; + } + + error = AudioQueueSetProperty(audioQueue, kAudioQueueProperty_MagicCookie, cookieData, cookieSize); + + if (error) + { + free(cookieData); + + return; + } + + free(cookieData); +} + +-(double) duration +{ + if (newFileToPlay) + { + return 0; + } + + QueueEntry* entry = currentlyPlayingEntry; + + if (entry == nil) + { + return 0; + } + + return [entry duration]; +} + +-(double) progress +{ + if (seekToTimeWasRequested) + { + return requestedSeekTime; + } + + if (newFileToPlay) + { + return 0; + } + + QueueEntry* entry = currentlyPlayingEntry; + + return [entry progress]; +} + +-(void) wakeupPlaybackThread +{ + NSRunLoop* runLoop = playbackThreadRunLoop; + + if (runLoop) + { + CFRunLoopPerformBlock([runLoop getCFRunLoop], NSDefaultRunLoopMode, ^ + { + [self processRunloop]; + }); + + CFRunLoopWakeUp([runLoop getCFRunLoop]); + } +} + +-(void) seekToTime:(double)value +{ + @synchronized(self) + { + BOOL seekAlreadyRequested = seekToTimeWasRequested; + + seekToTimeWasRequested = YES; + requestedSeekTime = value; + + if (!seekAlreadyRequested) + { + [self wakeupPlaybackThread]; + } + } +} + +-(void) processQueue:(BOOL)skipCurrent +{ + if (playbackThread == nil) + { + newFileToPlay = YES; + + playbackThread = [[NSThread alloc] initWithTarget:self selector:@selector(startInternal) object:nil]; + + [playbackThread start]; + + [self wakeupPlaybackThread]; + } + else + { + if (skipCurrent) + { + newFileToPlay = YES; + + [self resetAudioQueue]; + } + + [self wakeupPlaybackThread]; + } +} + +-(void) setCurrentlyReadingEntry:(QueueEntry*)entry andStartPlaying:(BOOL)startPlaying +{ + OSStatus error; + + pthread_mutex_lock(&queueBuffersMutex); + + if (startPlaying) + { + if (audioQueue) + { + pthread_mutex_unlock(&queueBuffersMutex); + + [self resetAudioQueue]; + + pthread_mutex_lock(&queueBuffersMutex); + } + } + + BLOG(@"Started buffering %@", entry.queueItemId); + + if (audioFileStream) + { + AudioFileStreamClose(audioFileStream); + + audioFileStream = 0; + } + + error = AudioFileStreamOpen((__bridge void*)self, AudioFileStreamPropertyListenerProc, AudioFileStreamPacketsProc, kAudioFileM4AType, &audioFileStream); + + if (error) + { + BLOG(@"Error creating AudioFileStream"); + + return; + } + + if (currentlyReadingEntry) + { + currentlyReadingEntry.dataSource.delegate = nil; + [currentlyReadingEntry.dataSource unregisterForEvents]; + [currentlyReadingEntry.dataSource close]; + } + + currentlyReadingEntry = entry; + currentlyReadingEntry.dataSource.delegate = self; + [currentlyReadingEntry.dataSource registerForEvents:[NSRunLoop currentRunLoop]]; + + if (startPlaying) + { + [bufferingQueue removeAllObjects]; + + [self processDidFinishPlaying:currentlyPlayingEntry withNext:entry]; + } + else + { + [bufferingQueue enqueue:entry]; + } + + pthread_mutex_unlock(&queueBuffersMutex); +} + +-(void) audioQueueFinishedPlaying:(QueueEntry*)entry +{ + pthread_mutex_lock(&queueBuffersMutex); + + BLOG(@"didFinishPlaying on runloop"); + + QueueEntry* next = [bufferingQueue dequeue]; + + [self processDidFinishPlaying:entry withNext:next]; + + pthread_mutex_unlock(&queueBuffersMutex); +} + +-(void) processDidFinishPlaying:(QueueEntry*)entry withNext:(QueueEntry*)next +{ + if (entry != currentlyPlayingEntry) + { + return; + } + + NSObject* queueItemId = entry.queueItemId; + double progress = [entry progress]; + double duration = [entry duration]; + + BOOL nextIsDifferent = currentlyPlayingEntry != next; + + if (next) + { + if (nextIsDifferent) + { + next->seekTime = 0; + + seekToTimeWasRequested = NO; + } + + currentlyPlayingEntry = next; + currentlyPlayingEntry->bytesPlayed = 0; + + NSObject* playingQueueItemId = currentlyPlayingEntry.queueItemId; + + if (nextIsDifferent && entry) + { + dispatch_async(dispatch_get_main_queue(), ^ + { + BLOG(@"didFinishPlaying: %@", queueItemId); + + [self.delegate audioPlayer:self didFinishPlayingQueueItemId:queueItemId withReason:stopReason andProgress:progress andDuration:duration]; + }); + } + + if (nextIsDifferent) + { + BLOG(@"Switch to playing: %@", currentlyPlayingEntry.queueItemId); + + dispatch_async(dispatch_get_main_queue(), ^ + { + BLOG(@"didStartPlaying: %@", playingQueueItemId); + + [self.delegate audioPlayer:self didStartPlayingQueueItemId:playingQueueItemId]; + }); + } + } + else + { + currentlyPlayingEntry = nil; + + if (currentlyReadingEntry == nil) + { + self.internalState = AudioPlayerInternalStateStopping; + } + + if (nextIsDifferent && entry) + { + dispatch_async(dispatch_get_main_queue(), ^ + { + BLOG(@"didFinishPlaying: %@", queueItemId); + + [self.delegate audioPlayer:self didFinishPlayingQueueItemId:queueItemId withReason:stopReason andProgress:progress andDuration:duration]; + }); + } + } +} + +-(BOOL) processRunloop +{ + @synchronized(self) + { + if (self.internalState == AudioPlayerInternalStatePaused) + { + return YES; + } + else if (newFileToPlay) + { + BLOG(@"Playing new file"); + + QueueEntry* entry = [upcomingQueue dequeue]; + + self.internalState = AudioPlayerInternalStateWaitingForData; + + [self setCurrentlyReadingEntry:entry andStartPlaying:YES]; + + newFileToPlay = NO; + nextIsIncompatible = NO; + } + else if (seekToTimeWasRequested && currentlyPlayingEntry && currentlyPlayingEntry != currentlyReadingEntry) + { + BLOG(@"Seek was requested -- switching files necessary"); + + currentlyPlayingEntry.bufferIndex = -1; + [self setCurrentlyReadingEntry:currentlyPlayingEntry andStartPlaying:YES]; + + currentlyReadingEntry->parsedHeader = NO; + [currentlyReadingEntry.dataSource seekToOffset:0]; + + nextIsIncompatible = NO; + } + else if (currentlyReadingEntry == nil) + { + BLOG(@"Finished reading previous file"); + + if (nextIsIncompatible && currentlyPlayingEntry != nil) + { + BLOG(@"Holding off cause next is incompatible"); + } + else + { + if (upcomingQueue.count > 0) + { + QueueEntry* entry = [upcomingQueue dequeue]; + + BOOL startPlaying = currentlyPlayingEntry == nil; + + [self setCurrentlyReadingEntry:entry andStartPlaying:startPlaying]; + } + else if (currentlyPlayingEntry == nil) + { + if (self.internalState != AudioPlayerInternalStateStopped) + { + BLOG(@"End of playing queue -- Stopping AudioQueue"); + + [self stopAudioQueue]; + } + } + } + } + else if (self.internalState == AudioPlayerInternalStateStopped && stopReason == AudioPlayerStopReasonUserAction) + { + [self stopAudioQueue]; + + currentlyReadingEntry.dataSource.delegate = nil; + [currentlyReadingEntry.dataSource unregisterForEvents]; + + if (currentlyReadingEntry) + { + [self processDidFinishPlaying:currentlyPlayingEntry withNext:nil]; + } + + pthread_mutex_lock(&queueBuffersMutex); + + currentlyPlayingEntry = nil; + currentlyReadingEntry = nil; + seekToTimeWasRequested = NO; + + pthread_mutex_unlock(&queueBuffersMutex); + } + else if (self.internalState == AudioPlayerInternalStateStopped && stopReason == AudioPlayerStopReasonUserActionFlushStop) + { + currentlyReadingEntry.dataSource.delegate = nil; + [currentlyReadingEntry.dataSource unregisterForEvents]; + + if (currentlyReadingEntry) + { + [self processDidFinishPlaying:currentlyPlayingEntry withNext:nil]; + } + + pthread_mutex_lock(&queueBuffersMutex); + currentlyPlayingEntry = nil; + currentlyReadingEntry = nil; + + pthread_mutex_unlock(&queueBuffersMutex); + + [self resetAudioQueue]; + } + + if (disposeWasRequested) + { + return NO; + } + } + + if (currentlyReadingEntry && currentlyReadingEntry->parsedHeader && currentlyReadingEntry != currentlyPlayingEntry) + { + if (currentAudioStreamBasicDescription.mSampleRate != 0) + { + if (memcmp(¤tAudioStreamBasicDescription, ¤tlyReadingEntry->audioStreamBasicDescription, sizeof(currentAudioStreamBasicDescription)) != 0) + { + [upcomingQueue skipQueue:[[QueueEntry alloc] initWithDataSource:currentlyReadingEntry.dataSource andQueueItemId:currentlyReadingEntry.queueItemId]]; + + currentlyReadingEntry = nil; + nextIsIncompatible = YES; + } + } + } + + if (currentlyPlayingEntry && currentlyPlayingEntry->parsedHeader) + { + if (seekToTimeWasRequested && currentlyReadingEntry == currentlyPlayingEntry) + { + BLOG(@"Seeking to %d in file %@", (int)requestedSeekTime, currentlyReadingEntry.queueItemId); + + [self processSeekToTime]; + + seekToTimeWasRequested = NO; + } + } + + return YES; +} + +-(void) startInternal +{ + playbackThreadRunLoop = [NSRunLoop currentRunLoop]; + + NSThread.currentThread.threadPriority = 1; + + bytesFilled = 0; + packetsFilled = 0; + + [playbackThreadRunLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode]; + + do + { + [playbackThreadRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:5]]; + + if (![self processRunloop]) + { + break; + } + } + while (true); + + disposeWasRequested = NO; + seekToTimeWasRequested = NO; + + currentlyReadingEntry.dataSource.delegate = nil; + currentlyPlayingEntry.dataSource.delegate = nil; + + currentlyReadingEntry = nil; + currentlyPlayingEntry = nil; + + self.internalState = AudioPlayerInternalStateDisposed; + + [threadFinishedCondLock lock]; + [threadFinishedCondLock unlockWithCondition:1]; +} + +-(void) processSeekToTime +{ + OSStatus error; + NSAssert(currentlyReadingEntry == currentlyPlayingEntry, @"playing and reading must be the same"); + + if ([currentlyPlayingEntry calculatedBitRate] == 0.0 || currentlyPlayingEntry.dataSource.length <= 0) + { + return; + } + + long long seekByteOffset = currentlyPlayingEntry->audioDataOffset + (requestedSeekTime / self.duration) * (currentlyReadingEntry.audioDataLengthInBytes); + + if (seekByteOffset > currentlyPlayingEntry.dataSource.length - (2 * currentlyPlayingEntry->packetBufferSize)) + { + seekByteOffset = currentlyPlayingEntry.dataSource.length - 2 * currentlyPlayingEntry->packetBufferSize; + } + + currentlyPlayingEntry->seekTime = requestedSeekTime; + currentlyPlayingEntry->lastProgress = requestedSeekTime; + + double calculatedBitRate = [currentlyPlayingEntry calculatedBitRate]; + + if (currentlyPlayingEntry->packetDuration > 0 && calculatedBitRate > 0) + { + UInt32 ioFlags = 0; + SInt64 packetAlignedByteOffset; + SInt64 seekPacket = floor(requestedSeekTime / currentlyPlayingEntry->packetDuration); + + error = AudioFileStreamSeek(audioFileStream, seekPacket, &packetAlignedByteOffset, &ioFlags); + + if (!error && !(ioFlags & kAudioFileStreamSeekFlag_OffsetIsEstimated)) + { + double delta = ((seekByteOffset - (SInt64)currentlyPlayingEntry->audioDataOffset) - packetAlignedByteOffset) / calculatedBitRate * 8; + + currentlyPlayingEntry->seekTime -= delta; + + seekByteOffset = packetAlignedByteOffset + currentlyPlayingEntry->audioDataOffset; + } + } + + [currentlyReadingEntry.dataSource seekToOffset:seekByteOffset]; + + if (seekByteOffset > 0) + { + discontinuous = YES; + } + + if (audioQueue) + { + [self resetAudioQueue]; + } + + currentlyPlayingEntry->bytesPlayed = 0; +} + +-(BOOL) startAudioQueue +{ + OSStatus error; + + self.internalState = AudioPlayerInternalStateWaitingForQueueToStart; + + error = AudioQueueStart(audioQueue, NULL); + + if (error) + { + if (backgroundTaskId == UIBackgroundTaskInvalid) + { + [self startSystemBackgroundTask]; + } + + [self stopAudioQueue]; + [self createAudioQueue]; + + AudioQueueStart(audioQueue, NULL); + } + + [self stopSystemBackgroundTask]; + + return YES; +} + +-(void) stopAudioQueue +{ + OSStatus error; + + if (!audioQueue) + { + self.internalState = AudioPlayerInternalStateStopped; + + return; + } + else + { + audioQueueFlushing = YES; + + BLOG(@"AudioQueueStop"); + + error = AudioQueueStop(audioQueue, true); + + audioQueue = nil; + } + + if (error) + { + [self didEncounterError:AudioPlayerErrorQueueStopFailed]; + } + + pthread_mutex_lock(&queueBuffersMutex); + + if (numberOfBuffersUsed != 0) + { + numberOfBuffersUsed = 0; + + for (int i = 0; i < audioQueueBufferCount; i++) + { + bufferUsed[i] = false; + } + } + + pthread_cond_signal(&queueBufferReadyCondition); + pthread_mutex_unlock(&queueBuffersMutex); + + bytesFilled = 0; + fillBufferIndex = 0; + packetsFilled = 0; + + audioPacketsReadCount = 0; + audioPacketsPlayedCount = 0; + audioQueueFlushing = NO; + + self.internalState = AudioPlayerInternalStateStopped; +} + +-(void) resetAudioQueue +{ + OSStatus error; + + if (!audioQueue) + { + return; + } + + audioQueueFlushing = YES; + + BLOG(@"AudioQueueReset"); + + error = AudioQueueReset(audioQueue); + + if (error) + { + dispatch_async(dispatch_get_main_queue(), ^ + { + [self didEncounterError:AudioPlayerErrorQueueStopFailed];; + }); + } + + pthread_mutex_lock(&queueBuffersMutex); + + if (numberOfBuffersUsed != 0) + { + numberOfBuffersUsed = 0; + + for (int i = 0; i < audioQueueBufferCount; i++) + { + bufferUsed[i] = false; + } + } + + pthread_cond_signal(&queueBufferReadyCondition); + pthread_mutex_unlock(&queueBuffersMutex); + + bytesFilled = 0; + fillBufferIndex = 0; + packetsFilled = 0; + + if (currentlyPlayingEntry) + { + currentlyPlayingEntry->lastProgress = 0; + } + + audioPacketsReadCount = 0; + audioPacketsPlayedCount = 0; + audioQueueFlushing = NO; +} + +-(void) dataSourceDataAvailable:(DataSource*)dataSourceIn +{ + OSStatus error; + + if (currentlyReadingEntry.dataSource != dataSourceIn) + { + return; + } + + if (!currentlyReadingEntry.dataSource.hasBytesAvailable) + { + return; + } + + int read = [currentlyReadingEntry.dataSource readIntoBuffer:readBuffer withSize:readBufferSize]; + + if (read == 0) + { + return; + } + + if (read < 0) + { + // iOS will shutdown network connections if the app is backgrounded (i.e. device is locked when player is paused) + // We try to reopen -- should probably add a back-off protocol in the future + + long long position = currentlyReadingEntry.dataSource.position; + + [currentlyReadingEntry.dataSource seekToOffset:position]; + + return; + } + + int flags = 0; + + if (discontinuous) + { + flags = kAudioFileStreamParseFlag_Discontinuity; + } + + error = AudioFileStreamParseBytes(audioFileStream, read, readBuffer, flags); + + if (error) + { + if (dataSourceIn == currentlyPlayingEntry.dataSource) + { + [self didEncounterError:AudioPlayerErrorStreamParseBytesFailed]; + } + + return; + } +} + +-(void) dataSourceErrorOccured:(DataSource*)dataSourceIn +{ + if (currentlyReadingEntry.dataSource != dataSourceIn) + { + return; + } + + [self didEncounterError:AudioPlayerErrorDataNotFound]; +} + +-(void) dataSourceEof:(DataSource*)dataSourceIn +{ + BLOG(@"eof %@", [dataSourceIn description]); + + if (currentlyReadingEntry.dataSource != dataSourceIn) + { + return; + } + + if (bytesFilled) + { + [self enqueueBuffer]; + } + + NSObject* queueItemId = currentlyReadingEntry.queueItemId; + + dispatch_async(dispatch_get_main_queue(), ^ + { + [self.delegate audioPlayer:self didFinishBufferingSourceWithQueueItemId:queueItemId]; + }); + + @synchronized(self) + { + if (audioQueue) + { + BLOG(@"Finished buffering %@", currentlyReadingEntry.queueItemId); + + currentlyReadingEntry.bufferIndex = audioPacketsReadCount; + currentlyReadingEntry = nil; + } + else + { + stopReason = AudioPlayerStopReasonEof; + self.internalState = AudioPlayerInternalStateStopped; + } + } +} + +-(void) pause +{ + @synchronized(self) + { + OSStatus error; + + if (self.internalState != AudioPlayerInternalStatePaused) + { + self.internalState = AudioPlayerInternalStatePaused; + + BLOG(@"AudioQueuePause"); + + if (audioQueue) + { + error = AudioQueuePause(audioQueue); + + if (error) + { + [self didEncounterError:AudioPlayerErrorQueuePauseFailed]; + + return; + } + } + + [self wakeupPlaybackThread]; + } + } +} + +-(void) resume +{ + @synchronized(self) + { + OSStatus error; + + if (self.internalState == AudioPlayerInternalStatePaused) + { + self.internalState = AudioPlayerInternalStatePlaying; + + if (seekToTimeWasRequested) + { + [self resetAudioQueue]; + } + + error = AudioQueueStart(audioQueue, 0); + + if (error) + { + [self didEncounterError:AudioPlayerErrorQueueStartFailed]; + + return; + } + + [self wakeupPlaybackThread]; + } + } +} + +-(void) stop +{ + @synchronized(self) + { + if (self.internalState == AudioPlayerInternalStateStopped) + { + return; + } + + stopReason = AudioPlayerStopReasonUserAction; + self.internalState = AudioPlayerInternalStateStopped; + + [self wakeupPlaybackThread]; + } +} + +-(void) flushStop +{ + @synchronized(self) + { + if (self.internalState == AudioPlayerInternalStateStopped) + { + return; + } + + stopReason = AudioPlayerStopReasonUserActionFlushStop; + self.internalState = AudioPlayerInternalStateStopped; + + [self wakeupPlaybackThread]; + } +} + +-(void) stopThread +{ + BOOL wait = NO; + + @synchronized(self) + { + disposeWasRequested = YES; + + if (playbackThread && playbackThreadRunLoop) + { + wait = YES; + + CFRunLoopStop([playbackThreadRunLoop getCFRunLoop]); + } + } + + if (wait) + { + [threadFinishedCondLock lockWhenCondition:1]; + [threadFinishedCondLock unlockWithCondition:0]; + } +} + +-(void) dispose +{ + [self stop]; + [self stopThread]; +} + +@end diff --git a/Audjustable/Classes/AudioPlayer/CoreFoundationDataSource.h b/Audjustable/Classes/AudioPlayer/CoreFoundationDataSource.h new file mode 100644 index 0000000..1554223 --- /dev/null +++ b/Audjustable/Classes/AudioPlayer/CoreFoundationDataSource.h @@ -0,0 +1,57 @@ +/********************************************************************************** + AudioPlayer.m + + Created by Thong Nguyen on 14/05/2012. + http://http://code.google.com/p/bluecucumber + + Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + This product includes software developed by the . + 4. Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**********************************************************************************/ + +#import "DataSource.h" + +@class CoreFoundationDataSource; + +@interface CoreFoundationDataSourceClientInfo : NSObject +@property (readwrite) CFReadStreamRef readStreamRef; +@property (readwrite, retain) CoreFoundationDataSource* datasource; +@end + +@interface CoreFoundationDataSource : DataSource +{ +@protected + CFReadStreamRef stream; + NSRunLoop* eventsRunLoop; +} + +-(BOOL) reregisterForEvents; + +-(void) dataAvailable; +-(void) eof; +-(void) errorOccured; + +@end diff --git a/Audjustable/Classes/AudioPlayer/CoreFoundationDataSource.m b/Audjustable/Classes/AudioPlayer/CoreFoundationDataSource.m new file mode 100644 index 0000000..b245328 --- /dev/null +++ b/Audjustable/Classes/AudioPlayer/CoreFoundationDataSource.m @@ -0,0 +1,148 @@ +/********************************************************************************** + AudioPlayer.m + + Created by Thong Nguyen on 14/05/2012. + http://http://code.google.com/p/bluecucumber + + Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + This product includes software developed by the . + 4. Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**********************************************************************************/ + +#import "CoreFoundationDataSource.h" + +static void ReadStreamCallbackProc(CFReadStreamRef stream, CFStreamEventType eventType, void* inClientInfo) +{ + CoreFoundationDataSource* datasource = (__bridge CoreFoundationDataSource*)inClientInfo; + + switch (eventType) + { + case kCFStreamEventErrorOccurred: + [datasource errorOccured]; + break; + case kCFStreamEventEndEncountered: + [datasource eof]; + break; + case kCFStreamEventHasBytesAvailable: + [datasource dataAvailable]; + break; + } +} + +@implementation CoreFoundationDataSourceClientInfo +@synthesize readStreamRef, datasource; +@end + +@implementation CoreFoundationDataSource + +-(void) dataAvailable +{ + [self.delegate dataSourceDataAvailable:self]; +} + +-(void) eof +{ + [self.delegate dataSourceEof:self]; +} + +-(void) errorOccured +{ + [self.delegate dataSourceErrorOccured:self]; +} + +-(void) dealloc +{ + if (stream) + { + [self unregisterForEvents]; + + CFReadStreamClose(stream); + + stream = 0; + } +} + +-(void) seekToOffset:(long long)offset +{ +} + +-(int) readIntoBuffer:(UInt8*)buffer withSize:(int)size +{ + return CFReadStreamRead(stream, buffer, size); +} + +-(void) unregisterForEvents +{ + if (stream) + { + CFReadStreamSetClient(stream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, NULL, NULL); + CFReadStreamUnscheduleFromRunLoop(stream, [eventsRunLoop getCFRunLoop], kCFRunLoopCommonModes); + } +} + +-(BOOL) reregisterForEvents +{ + if (eventsRunLoop && stream) + { + CFStreamClientContext context = {0, (__bridge void*)self, NULL, NULL, NULL}; + CFReadStreamSetClient(stream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, ReadStreamCallbackProc, &context); + CFReadStreamScheduleWithRunLoop(stream, [eventsRunLoop getCFRunLoop], kCFRunLoopCommonModes); + + return YES; + } + + return NO; +} + +-(BOOL) registerForEvents:(NSRunLoop*)runLoop +{ + eventsRunLoop = runLoop; + + if (stream) + { + CFStreamClientContext context = {0, (__bridge void*)self, NULL, NULL, NULL}; + + CFReadStreamSetClient(stream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, ReadStreamCallbackProc, &context); + + CFReadStreamScheduleWithRunLoop(stream, [eventsRunLoop getCFRunLoop], kCFRunLoopCommonModes); + + return YES; + } + + return NO; +} + +-(BOOL) hasBytesAvailable +{ + if (!stream) + { + return NO; + } + + return CFReadStreamHasBytesAvailable(stream); +} + +@end diff --git a/Audjustable/Classes/AudioPlayer/DataSource.h b/Audjustable/Classes/AudioPlayer/DataSource.h new file mode 100644 index 0000000..bb530f8 --- /dev/null +++ b/Audjustable/Classes/AudioPlayer/DataSource.h @@ -0,0 +1,59 @@ +/********************************************************************************** + AudioPlayer.m + + Created by Thong Nguyen on 14/05/2012. + http://http://code.google.com/p/bluecucumber + + Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + This product includes software developed by the . + 4. Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**********************************************************************************/ + +#import + +@class DataSource; + +@protocol DataSourceDelegate +-(void) dataSourceDataAvailable:(DataSource*)dataSource; +-(void) dataSourceErrorOccured:(DataSource*)dataSource; +-(void) dataSourceEof:(DataSource*)dataSource; +@end + +@interface DataSource : NSObject + +@property (readonly) long long position; +@property (readonly) long long length; +@property (readonly) BOOL hasBytesAvailable; +@property (readwrite, unsafe_unretained) id delegate; + +-(BOOL) registerForEvents:(NSRunLoop*)runLoop; +-(void) unregisterForEvents; +-(void) close; + +-(void) seekToOffset:(long long)offset; +-(int) readIntoBuffer:(UInt8*)buffer withSize:(int)size; + +@end diff --git a/Audjustable/Classes/AudioPlayer/DataSource.m b/Audjustable/Classes/AudioPlayer/DataSource.m new file mode 100644 index 0000000..51eeb05 --- /dev/null +++ b/Audjustable/Classes/AudioPlayer/DataSource.m @@ -0,0 +1,77 @@ +/********************************************************************************** + AudioPlayer.m + + Created by Thong Nguyen on 14/05/2012. + http://http://code.google.com/p/bluecucumber + + Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + This product includes software developed by the . + 4. Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**********************************************************************************/ + +#import "DataSource.h" + +@implementation DataSource +@synthesize delegate; + +-(long long) length +{ + return 0; +} + +-(void) seekToOffset:(long long)offset +{ +} + +-(int) readIntoBuffer:(UInt8*)buffer withSize:(int)size +{ + return -1; +} + +-(long long) position +{ + return 0; +} + +-(BOOL) registerForEvents:(NSRunLoop*)runLoop +{ + return NO; +} + +-(void) unregisterForEvents +{ +} + +-(void) close +{ +} + +-(BOOL) hasBytesAvailable +{ + return NO; +} + +@end diff --git a/Audjustable/Classes/AudioPlayer/HttpDataSource.h b/Audjustable/Classes/AudioPlayer/HttpDataSource.h new file mode 100644 index 0000000..608165f --- /dev/null +++ b/Audjustable/Classes/AudioPlayer/HttpDataSource.h @@ -0,0 +1,51 @@ +/********************************************************************************** + AudioPlayer.m + + Created by Thong Nguyen on 14/05/2012. + http://http://code.google.com/p/bluecucumber + + Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + This product includes software developed by the . + 4. Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**********************************************************************************/ + +#import "CoreFoundationDataSource.h" + +@interface HttpDataSource : CoreFoundationDataSource +{ +@private + int seekStart; + int relativePosition; + int fileLength; + int discontinuous; + NSDictionary* httpHeaders; +} + +@property (readwrite, retain) NSURL* url; + +-(id) initWithURL:(NSURL*)url; + +@end diff --git a/Audjustable/Classes/AudioPlayer/HttpDataSource.m b/Audjustable/Classes/AudioPlayer/HttpDataSource.m new file mode 100644 index 0000000..9a5185f --- /dev/null +++ b/Audjustable/Classes/AudioPlayer/HttpDataSource.m @@ -0,0 +1,180 @@ +/********************************************************************************** + AudioPlayer.m + + Created by Thong Nguyen on 14/05/2012. + http://http://code.google.com/p/bluecucumber + + Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + This product includes software developed by the . + 4. Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**********************************************************************************/ + +#import "HttpDataSource.h" + +@interface HttpDataSource() +-(void) open; +@end + +@implementation HttpDataSource +@synthesize url; + +-(id) initWithURL:(NSURL*)urlIn +{ + if (self = [super init]) + { + seekStart = 0; + relativePosition = 0; + fileLength = -1; + + self.url = urlIn; + + [self open]; + } + + return self; +} + +-(void) dataAvailable +{ + if (fileLength < 0) + { + CFTypeRef copyPropertyMessage = CFReadStreamCopyProperty(stream, kCFStreamPropertyHTTPResponseHeader); + + httpHeaders = (__bridge NSDictionary*)CFHTTPMessageCopyAllHeaderFields((CFHTTPMessageRef)copyPropertyMessage); + + CFRelease(copyPropertyMessage); + + if (seekStart == 0) + { + fileLength = [[httpHeaders objectForKey:@"Content-Length"] integerValue]; + } + } + + [super dataAvailable]; +} + +-(long long) position +{ + return seekStart + relativePosition; +} + +-(long long) length +{ + return fileLength >= 0 ? fileLength : 0; +} + +-(void) seekToOffset:(long long)offset +{ + if (eventsRunLoop) + { + [self unregisterForEvents]; + } + + CFReadStreamClose(stream); + + stream = nil; + relativePosition = 0; + seekStart = offset; + + [self open]; + [self reregisterForEvents]; +} + +-(int) readIntoBuffer:(UInt8*)buffer withSize:(int)size +{ + if (size == 0) + { + return 0; + } + + int read = CFReadStreamRead(stream, buffer, size); + + if (read < 0) + { + return read; + } + + relativePosition += read; + + return read; +} + +-(void) open +{ + CFHTTPMessageRef message = CFHTTPMessageCreateRequest(NULL, (CFStringRef)@"GET", (__bridge CFURLRef)self.url, kCFHTTPVersion1_1); + + if (seekStart > 0) + { + CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Range"), (__bridge CFStringRef)[NSString stringWithFormat:@"bytes=%d-", seekStart]); + + discontinuous = YES; + } + + stream = CFReadStreamCreateForHTTPRequest(NULL, message); + + if (!CFReadStreamSetProperty(stream, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue)) + { + CFRelease(message); + + return; + } + + // Proxy support + + CFDictionaryRef proxySettings = CFNetworkCopySystemProxySettings(); + CFReadStreamSetProperty(stream, kCFStreamPropertyHTTPProxy, proxySettings); + CFRelease(proxySettings); + + // SSL support + + if ([url.scheme caseInsensitiveCompare:@"https"] == NSOrderedSame) + { + NSDictionary* sslSettings = [NSDictionary dictionaryWithObjectsAndKeys: + (NSString*)kCFStreamSocketSecurityLevelNegotiatedSSL, kCFStreamSSLLevel, + [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates, + [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredRoots, + [NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot, + [NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain, + [NSNull null], kCFStreamSSLPeerName, + nil]; + + CFReadStreamSetProperty(stream, kCFStreamPropertySSLSettings, (__bridge CFTypeRef)sslSettings); + } + + // Open + + if (!CFReadStreamOpen(stream)) + { + CFRelease(stream); + CFRelease(message); + + return; + } + + CFRelease(message); +} + +@end diff --git a/Audjustable/Classes/AudioPlayer/LocalFileDataSource.h b/Audjustable/Classes/AudioPlayer/LocalFileDataSource.h new file mode 100644 index 0000000..d51ac8a --- /dev/null +++ b/Audjustable/Classes/AudioPlayer/LocalFileDataSource.h @@ -0,0 +1,48 @@ +/********************************************************************************** + AudioPlayer.m + + Created by Thong Nguyen on 14/05/2012. + http://http://code.google.com/p/bluecucumber + + Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + This product includes software developed by the . + 4. Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**********************************************************************************/ + +#import "CoreFoundationDataSource.h" + +@interface LocalFileDataSource : CoreFoundationDataSource +{ +@private + long long position; + long long length; +} + +@property (readonly, copy) NSString* filePath; + +-(id) initWithFilePath:(NSString*)filePath; + +@end diff --git a/Audjustable/Classes/AudioPlayer/LocalFileDataSource.m b/Audjustable/Classes/AudioPlayer/LocalFileDataSource.m new file mode 100644 index 0000000..83e1ede --- /dev/null +++ b/Audjustable/Classes/AudioPlayer/LocalFileDataSource.m @@ -0,0 +1,178 @@ +/********************************************************************************** + AudioPlayer.m + + Created by Thong Nguyen on 14/05/2012. + http://http://code.google.com/p/bluecucumber + + Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + This product includes software developed by the . + 4. Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**********************************************************************************/ + +#import "LocalFileDataSource.h" + +@interface LocalFileDataSource() +@property (readwrite, copy) NSString* filePath; + +-(void) open; +@end + +@implementation LocalFileDataSource +@synthesize filePath; + +-(id) initWithFilePath:(NSString*)filePathIn +{ + if (self = [super init]) + { + self.filePath = filePathIn; + + [self open]; + } + + return self; +} + +-(void) dealloc +{ + [self close]; +} + +-(void) close +{ + if (stream) + { + CFReadStreamClose(stream); + + stream = 0; + } +} + +-(void) open +{ + NSURL* url = [[NSURL alloc] initFileURLWithPath:self.filePath]; + + BLOG(@"Opening %@", filePath); + + if (stream) + { + CFReadStreamClose(stream); + + stream = 0; + } + + stream = CFReadStreamCreateWithFile(NULL, (__bridge CFURLRef)url); + + SInt32 errorCode; + + NSNumber* number = (__bridge_transfer NSNumber*)CFURLCreatePropertyFromResource(NULL, (__bridge CFURLRef)url, kCFURLFileLength, &errorCode); + + if (number) + { + length = number.longLongValue; + } + + CFReadStreamOpen(stream); +} + +-(long long) position +{ + return position; +} + +-(long long) length +{ + return length; +} + +-(int) readIntoBuffer:(UInt8*)buffer withSize:(int)size +{ + int retval = CFReadStreamRead(stream, buffer, size); + + if (retval > 0) + { + position += retval; + } + else + { + NSNumber* property = (__bridge_transfer NSNumber*)CFReadStreamCopyProperty(stream, kCFStreamPropertyFileCurrentOffset); + + position = property.longLongValue; + } + + return retval; +} + +-(void) seekToOffset:(long long)offset +{ + CFStreamStatus status = kCFStreamStatusClosed; + + if (stream != 0) + { + status = CFReadStreamGetStatus(stream); + } + + BOOL reopened = NO; + + if (status == kCFStreamStatusAtEnd || status == kCFStreamStatusClosed || status == kCFStreamStatusError) + { + reopened = YES; + + [self close]; + [self open]; + [self reregisterForEvents]; + } + + if (CFReadStreamSetProperty(stream, kCFStreamPropertyFileCurrentOffset, (__bridge CFTypeRef)[NSNumber numberWithLongLong:offset]) != TRUE) + { + position = 0; + + BLOG(@"Problem setting stream position"); + } + else + { + position = offset; + } + + if (!reopened) + { + CFRunLoopPerformBlock(eventsRunLoop.getCFRunLoop, NSRunLoopCommonModes, ^ + { + if ([self hasBytesAvailable]) + { + [self dataAvailable]; + } + }); + + CFRunLoopWakeUp(eventsRunLoop.getCFRunLoop); + } +} + +-(NSString*) description +{ + return self->filePath; +} + +@end diff --git a/Audjustable/Classes/AudioPlayerView.h b/Audjustable/Classes/AudioPlayerView.h new file mode 100644 index 0000000..09f7929 --- /dev/null +++ b/Audjustable/Classes/AudioPlayerView.h @@ -0,0 +1,32 @@ +// +// AudioPlayerView.h +// BlueCucumber-AudioPlayer +// +// Created by Thong Nguyen on 01/06/2012. +// Copyright (c) 2012 Thong Nguyen All rights reserved. +// + +#import +#import "AudioPlayer.h" + +@class AudioPlayerView; + +@protocol AudioPlayerViewDelegate +-(void) audioPlayerViewPlayFromHTTPSelected:(AudioPlayerView*)audioPlayerView; +-(void) audioPlayerViewPlayFromLocalFileSelected:(AudioPlayerView*)audioPlayerView; +@end + +@interface AudioPlayerView : UIView +{ +@private + NSTimer* timer; + UISlider* slider; + UIButton* playButton; + UIButton* playFromHTTPButton; + UIButton* playFromLocalFileButton; +} + +@property (readwrite, retain) AudioPlayer* audioPlayer; +@property (readwrite, unsafe_unretained) id delegate; + +@end diff --git a/Audjustable/Classes/AudioPlayerView.m b/Audjustable/Classes/AudioPlayerView.m new file mode 100644 index 0000000..b39a08d --- /dev/null +++ b/Audjustable/Classes/AudioPlayerView.m @@ -0,0 +1,181 @@ +// +// AudioPlayerView.m +// BlueCucumber-AudioPlayer +// +// Created by Thong Nguyen on 01/06/2012. +// Copyright (c) 2012 Thong Nguyen All rights reserved. +// + +#import "AudioPlayerView.h" + +@interface AudioPlayerView() +-(void) setupTimer; +-(void) updateControls; +@end + +@implementation AudioPlayerView +@synthesize audioPlayer, delegate; + +- (id)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + + if (self) + { + CGSize size = CGSizeMake(180, 50); + + playFromHTTPButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; + playFromHTTPButton.frame = CGRectMake((320 - size.width) / 2, 60, size.width, size.height); + [playFromHTTPButton addTarget:self action:@selector(playFromHTTPButtonTouched) forControlEvents:UIControlEventTouchUpInside]; + [playFromHTTPButton setTitle:@"Play from HTTP" forState:UIControlStateNormal]; + + playFromLocalFileButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; + playFromLocalFileButton.frame = CGRectMake((320 - size.width) / 2, 120, size.width, size.height); + [playFromLocalFileButton addTarget:self action:@selector(playFromLocalFileButtonTouched) forControlEvents:UIControlEventTouchUpInside]; + [playFromLocalFileButton setTitle:@"Play from Local File" forState:UIControlStateNormal]; + + playButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; + playButton.frame = CGRectMake((320 - size.width) / 2, 350, size.width, size.height); + [playButton addTarget:self action:@selector(playButtonPressed) forControlEvents:UIControlEventTouchUpInside]; + + slider = [[UISlider alloc] initWithFrame:CGRectMake(20, 290, 280, 20)]; + slider.continuous = YES; + [slider addTarget:self action:@selector(sliderChanged) forControlEvents:UIControlEventValueChanged]; + + [self addSubview:slider]; + [self addSubview:playButton]; + [self addSubview:playFromHTTPButton]; + [self addSubview:playFromLocalFileButton]; + + [self setupTimer]; + [self updateControls]; + } + + return self; +} + +-(void) sliderChanged +{ + if (!audioPlayer) + { + return; + } + + NSLog(@"Slider Changed: %f", slider.value); + + [audioPlayer seekToTime:slider.value]; +} + +-(void) setupTimer +{ + timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(tick) userInfo:nil repeats:YES]; + + [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; +} + +-(void) tick +{ + if (!audioPlayer || audioPlayer.duration == 0) + { + slider.value = 0; + + return; + } + + slider.minimumValue = 0; + slider.maximumValue = audioPlayer.duration; + + slider.value = audioPlayer.progress; +} + +-(void) playFromHTTPButtonTouched +{ + [self.delegate audioPlayerViewPlayFromHTTPSelected:self]; +} + +-(void) playFromLocalFileButtonTouched +{ + [self.delegate audioPlayerViewPlayFromLocalFileSelected:self]; +} + +-(void) playButtonPressed +{ + if (!audioPlayer) + { + return; + } + + if (audioPlayer.state == AudioPlayerStatePaused) + { + [audioPlayer resume]; + } + else + { + [audioPlayer pause]; + } +} + +-(void) updateControls +{ + if (audioPlayer == nil) + { + [playButton setTitle:@"Play" forState:UIControlStateNormal]; + } + else if (audioPlayer.state == AudioPlayerStatePaused) + { + [playButton setTitle:@"Resume" forState:UIControlStateNormal]; + } + else if (audioPlayer.state == AudioPlayerStatePlaying) + { + [playButton setTitle:@"Pause" forState:UIControlStateNormal]; + } + else + { + [playButton setTitle:@"Play" forState:UIControlStateNormal]; + } +} + +-(void) setAudioPlayer:(AudioPlayer*)value +{ + if (audioPlayer) + { + audioPlayer.delegate = nil; + } + + audioPlayer = value; + audioPlayer.delegate = self; + + [self updateControls]; +} + +-(AudioPlayer*) audioPlayer +{ + return audioPlayer; +} + +-(void) audioPlayer:(AudioPlayer*)audioPlayer stateChanged:(AudioPlayerState)state +{ + [self updateControls]; +} + +-(void) audioPlayer:(AudioPlayer*)audioPlayer didEncounterError:(AudioPlayerErrorCode)errorCode +{ + [self updateControls]; +} + +-(void) audioPlayer:(AudioPlayer*)audioPlayer didStartPlayingQueueItemId:(NSObject*)queueItemId +{ + [self updateControls]; +} + +-(void) audioPlayer:(AudioPlayer*)audioPlayer didFinishBufferingSourceWithQueueItemId:(NSObject*)queueItemId +{ + [self updateControls]; +} + +-(void) audioPlayer:(AudioPlayer*)audioPlayer didFinishPlayingQueueItemId:(NSObject*)queueItemId withReason:(AudioPlayerStopReason)stopReason andProgress:(double)progress andDuration:(double)duration +{ + [self updateControls]; +} + +@end diff --git a/Audjustable/en.lproj/InfoPlist.strings b/Audjustable/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/Audjustable/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/Audjustable/main.m b/Audjustable/main.m new file mode 100644 index 0000000..aa92128 --- /dev/null +++ b/Audjustable/main.m @@ -0,0 +1,18 @@ +// +// main.m +// Audjustable +// +// Created by Thong Nguyen on 06/06/2012. +// Copyright (c) 2012 __MyCompanyName__. All rights reserved. +// + +#import + +#import "AppDelegate.h" + +int main(int argc, char *argv[]) +{ + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/Audjustable/sample.m4a b/Audjustable/sample.m4a new file mode 100644 index 0000000..e13731e Binary files /dev/null and b/Audjustable/sample.m4a differ diff --git a/README.md b/README.md index e69de29..36f6cbe 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,5 @@ +### Audjustable Audio Streamer + +Audjustable is audio streaming class for iOS and OSX. Audjustable uses CoreAudio to decompress and playback audio whilst providing a clean and simple object-oriented API. + +Visit the [project homepage](http://tumtumtum.github.com/audjustable) \ No newline at end of file