diff --git a/ExampleApp/ExampleApp.xcodeproj/project.pbxproj b/ExampleApp/ExampleApp.xcodeproj/project.pbxproj index bad1390..33d2d4c 100644 --- a/ExampleApp/ExampleApp.xcodeproj/project.pbxproj +++ b/ExampleApp/ExampleApp.xcodeproj/project.pbxproj @@ -412,6 +412,7 @@ GCC_PREFIX_HEADER = "ExampleApp/ExampleApp-Prefix.pch"; INFOPLIST_FILE = "ExampleApp/ExampleApp-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 6.0; + OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; @@ -427,6 +428,7 @@ GCC_PREFIX_HEADER = "ExampleApp/ExampleApp-Prefix.pch"; INFOPLIST_FILE = "ExampleApp/ExampleApp-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 6.0; + OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; diff --git a/ExampleApp/ExampleApp/AppDelegate.m b/ExampleApp/ExampleApp/AppDelegate.m index 4fe456a..495753b 100644 --- a/ExampleApp/ExampleApp/AppDelegate.m +++ b/ExampleApp/ExampleApp/AppDelegate.m @@ -32,11 +32,16 @@ self.window.backgroundColor = [UIColor whiteColor]; - audioPlayer = [[STKAudioPlayer alloc] init]; + audioPlayer = [[STKAudioPlayer alloc] initWithOptions:STKAudioPlayerOptionFlushQueueOnSeek]; + audioPlayer.meteringEnabled = YES; + AudioPlayerView* audioPlayerView = [[AudioPlayerView alloc] initWithFrame:self.window.bounds]; audioPlayerView.delegate = self; audioPlayerView.audioPlayer = audioPlayer; + + [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; + [self becomeFirstResponder]; [self.window addSubview:audioPlayerView]; @@ -45,11 +50,16 @@ return YES; } +-(BOOL) canBecomeFirstResponder +{ + return YES; +} + -(void) audioPlayerViewPlayFromHTTPSelected:(AudioPlayerView*)audioPlayerView { NSURL* url = [NSURL URLWithString:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"]; - STKAutoRecoveringHTTPDataSource* dataSource = [[STKAutoRecoveringHTTPDataSource alloc] initWithHTTPDataSource:(STKHTTPDataSource*)[audioPlayer dataSourceFromURL:url]]; + STKDataSource* dataSource = [STKAudioPlayer dataSourceFromURL:url]; [audioPlayer setDataSource:dataSource withQueueItemId:[[SampleQueueId alloc] initWithUrl:url andCount:0]]; } @@ -58,8 +68,10 @@ { NSString* path = [[NSBundle mainBundle] pathForResource:@"airplane" ofType:@"aac"]; NSURL* url = [NSURL fileURLWithPath:path]; + + STKDataSource* dataSource = [STKAudioPlayer dataSourceFromURL:url]; - [audioPlayer queueDataSource:[audioPlayer dataSourceFromURL:url] withQueueItemId:[[SampleQueueId alloc] initWithUrl:url andCount:0]]; + [audioPlayer queueDataSource:dataSource withQueueItemId:[[SampleQueueId alloc] initWithUrl:url andCount:0]]; } -(void) audioPlayerViewPlayFromLocalFileSelected:(AudioPlayerView*)audioPlayerView @@ -67,7 +79,18 @@ NSString* path = [[NSBundle mainBundle] pathForResource:@"sample" ofType:@"m4a"]; NSURL* url = [NSURL fileURLWithPath:path]; - [audioPlayer setDataSource:[audioPlayer dataSourceFromURL:url] withQueueItemId:[[SampleQueueId alloc] initWithUrl:url andCount:0]]; + STKDataSource* dataSource = [STKAudioPlayer dataSourceFromURL:url]; + + [audioPlayer setDataSource:dataSource withQueueItemId:[[SampleQueueId alloc] initWithUrl:url andCount:0]]; +} + +-(void) audioPlayerViewQueuePcmWaveFileSelected:(AudioPlayerView*)audioPlayerView +{ + NSURL* url = [NSURL URLWithString:@"http://fs.bloom.fm/oss/audiosamples/perfectly.wav"]; + + STKDataSource* dataSource = [STKAudioPlayer dataSourceFromURL:url]; + + [audioPlayer queueDataSource:dataSource withQueueItemId:[[SampleQueueId alloc] initWithUrl:url andCount:0]]; } @end diff --git a/ExampleApp/ExampleApp/AudioPlayerView.h b/ExampleApp/ExampleApp/AudioPlayerView.h index 9f50974..92d06cb 100644 --- a/ExampleApp/ExampleApp/AudioPlayerView.h +++ b/ExampleApp/ExampleApp/AudioPlayerView.h @@ -41,6 +41,7 @@ -(void) audioPlayerViewPlayFromHTTPSelected:(AudioPlayerView*)audioPlayerView; -(void) audioPlayerViewQueueShortFileSelected:(AudioPlayerView*)audioPlayerView; -(void) audioPlayerViewPlayFromLocalFileSelected:(AudioPlayerView*)audioPlayerView; +-(void) audioPlayerViewQueuePcmWaveFileSelected:(AudioPlayerView*)audioPlayerView; @end @interface AudioPlayerView : UIView @@ -51,11 +52,14 @@ UILabel* statusLabel; UISlider* slider; UISwitch* repeatSwitch; + UIButton* muteButton; UIButton* playButton; - UIButton* disposeButton; + UIButton* stopButton; UIButton* playFromHTTPButton; UIButton* queueShortFileButton; + UIButton* queuePcmWaveFileFromHTTPButton; UIButton* playFromLocalFileButton; + UIView* meter; } @property (readwrite, retain) STKAudioPlayer* audioPlayer; diff --git a/ExampleApp/ExampleApp/AudioPlayerView.m b/ExampleApp/ExampleApp/AudioPlayerView.m index 6813dcc..f2680ba 100644 --- a/ExampleApp/ExampleApp/AudioPlayerView.m +++ b/ExampleApp/ExampleApp/AudioPlayerView.m @@ -53,31 +53,43 @@ if (self) { - CGSize size = CGSizeMake(180, 50); + CGSize size = CGSizeMake(220, 50); playFromHTTPButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; - playFromHTTPButton.frame = CGRectMake((320 - size.width) / 2, frame.size.height * 0.15, size.width, size.height); + playFromHTTPButton.frame = CGRectMake((320 - size.width) / 2, frame.size.height * 0.10, 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, frame.size.height * 0.15 + 50, size.width, size.height); + playFromLocalFileButton.frame = CGRectMake((320 - size.width) / 2, frame.size.height * 0.10 + 50, size.width, size.height); [playFromLocalFileButton addTarget:self action:@selector(playFromLocalFileButtonTouched) forControlEvents:UIControlEventTouchUpInside]; [playFromLocalFileButton setTitle:@"Play from Local File" forState:UIControlStateNormal]; queueShortFileButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; - queueShortFileButton.frame = CGRectMake((320 - size.width) / 2, frame.size.height * 0.15 + 100, size.width, size.height); + queueShortFileButton.frame = CGRectMake((320 - size.width) / 2, frame.size.height * 0.10 + 100, size.width, size.height); [queueShortFileButton addTarget:self action:@selector(queueShortFileButtonTouched) forControlEvents:UIControlEventTouchUpInside]; [queueShortFileButton setTitle:@"Queue short file" forState:UIControlStateNormal]; + + queuePcmWaveFileFromHTTPButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; + queuePcmWaveFileFromHTTPButton.frame = CGRectMake((320 - size.width) / 2, frame.size.height * 0.10 + 150, size.width, size.height); + [queuePcmWaveFileFromHTTPButton addTarget:self action:@selector(queuePcmWaveFileButtonTouched) forControlEvents:UIControlEventTouchUpInside]; + [queuePcmWaveFileFromHTTPButton setTitle:@"Queue PCM/WAVE from HTTP" forState:UIControlStateNormal]; + + size = CGSizeMake(90, 40); playButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; playButton.frame = CGRectMake(30, 380, size.width, size.height); [playButton addTarget:self action:@selector(playButtonPressed) forControlEvents:UIControlEventTouchUpInside]; - disposeButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; - disposeButton.frame = CGRectMake((320 - size.width) - 30, 380, size.width, size.height); - [disposeButton addTarget:self action:@selector(disposeButtonPressed) forControlEvents:UIControlEventTouchUpInside]; - [disposeButton setTitle:@"Dispose" forState:UIControlStateNormal]; + stopButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; + stopButton.frame = CGRectMake((320 - size.width) - 30, 380, size.width, size.height); + [stopButton addTarget:self action:@selector(stopButtonPressed) forControlEvents:UIControlEventTouchUpInside]; + [stopButton setTitle:@"Stop" forState:UIControlStateNormal]; + + muteButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; + muteButton.frame = CGRectMake((320 - size.width) - 30, 410, size.width, size.height); + [muteButton addTarget:self action:@selector(muteButtonPressed) forControlEvents:UIControlEventTouchUpInside]; + [muteButton setTitle:@"Mute" forState:UIControlStateNormal]; slider = [[UISlider alloc] initWithFrame:CGRectMake(20, 320, 280, 20)]; slider.continuous = YES; @@ -85,25 +97,33 @@ size = CGSizeMake(80, 50); - repeatSwitch = [[UISwitch alloc] initWithFrame:CGRectMake((320 - size.width) / 2, frame.size.height * 0.15 + 170, size.width, size.height)]; + repeatSwitch = [[UISwitch alloc] initWithFrame:CGRectMake((320 - size.width) / 2, frame.size.height * 0.15 + 180, size.width, size.height)]; - label = [[UILabel alloc] initWithFrame:CGRectMake(0, slider.frame.origin.y + slider.frame.size.height, frame.size.width, 50)]; + label = [[UILabel alloc] initWithFrame:CGRectMake(0, slider.frame.origin.y + slider.frame.size.height + 10, frame.size.width, 25)]; label.textAlignment = NSTextAlignmentCenter; statusLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, slider.frame.origin.y + slider.frame.size.height + label.frame.size.height + 8, frame.size.width, 50)]; statusLabel.textAlignment = NSTextAlignmentCenter; + + + meter = [[UIView alloc] initWithFrame:CGRectMake(0, 450, 0, 20)]; + + meter.backgroundColor = [UIColor greenColor]; [self addSubview:slider]; [self addSubview:playButton]; [self addSubview:playFromHTTPButton]; [self addSubview:playFromLocalFileButton]; [self addSubview:queueShortFileButton]; + [self addSubview:queuePcmWaveFileFromHTTPButton]; [self addSubview:repeatSwitch]; [self addSubview:label]; [self addSubview:statusLabel]; - [self addSubview:disposeButton]; + [self addSubview:stopButton]; + [self addSubview:meter]; + [self addSubview:muteButton]; [self setupTimer]; [self updateControls]; @@ -126,7 +146,7 @@ -(void) setupTimer { - timer = [NSTimer timerWithTimeInterval:0.05 target:self selector:@selector(tick) userInfo:nil repeats:YES]; + timer = [NSTimer timerWithTimeInterval:0.01 target:self selector:@selector(tick) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; } @@ -160,6 +180,10 @@ } statusLabel.text = audioPlayer.state == STKAudioPlayerStateBuffering ? @"buffering" : @""; + + CGFloat newWidth = 320 * (([audioPlayer averagePowerInDecibelsForChannel:1] + 60) / 60); + + meter.frame = CGRectMake(0, 460, newWidth, 20); } -(void) playFromHTTPButtonTouched @@ -177,10 +201,28 @@ [self.delegate audioPlayerViewQueueShortFileSelected:self]; } --(void) disposeButtonPressed +-(void) queuePcmWaveFileButtonTouched { - [audioPlayer dispose]; - audioPlayer = nil; + [self.delegate audioPlayerViewQueuePcmWaveFileSelected:self]; +} + +-(void) muteButtonPressed +{ + audioPlayer.muted = !audioPlayer.muted; + + if (audioPlayer.muted) + { + [muteButton setTitle:@"Unmute" forState:UIControlStateNormal]; + } + else + { + [muteButton setTitle:@"Mute" forState:UIControlStateNormal]; + } +} + +-(void) stopButtonPressed +{ + [audioPlayer stop]; } -(void) playButtonPressed @@ -190,10 +232,6 @@ return; } - [audioPlayer dispose]; - - return; - if (audioPlayer.state == STKAudioPlayerStatePaused) { [audioPlayer resume]; @@ -254,12 +292,12 @@ return audioPlayer; } --(void) audioPlayer:(STKAudioPlayer*)audioPlayer stateChanged:(STKAudioPlayerState)state +-(void) audioPlayer:(STKAudioPlayer*)audioPlayer stateChanged:(STKAudioPlayerState)state previousState:(STKAudioPlayerState)previousState { [self updateControls]; } --(void) audioPlayer:(STKAudioPlayer*)audioPlayer didEncounterError:(STKAudioPlayerErrorCode)errorCode +-(void) audioPlayer:(STKAudioPlayer*)audioPlayer unexpectedError:(STKAudioPlayerErrorCode)errorCode { [self updateControls]; } @@ -285,7 +323,7 @@ NSLog(@"Requeuing: %@", [queueId.url description]); - [self->audioPlayer queueDataSource:[self->audioPlayer dataSourceFromURL:queueId.url] withQueueItemId:[[SampleQueueId alloc] initWithUrl:queueId.url andCount:queueId.count + 1]]; + [self->audioPlayer queueDataSource:[STKAudioPlayer dataSourceFromURL:queueId.url] withQueueItemId:[[SampleQueueId alloc] initWithUrl:queueId.url andCount:queueId.count + 1]]; } } diff --git a/ExampleAppMac/ExampleAppMac.xcodeproj/project.pbxproj b/ExampleAppMac/ExampleAppMac.xcodeproj/project.pbxproj new file mode 100644 index 0000000..16d179c --- /dev/null +++ b/ExampleAppMac/ExampleAppMac.xcodeproj/project.pbxproj @@ -0,0 +1,535 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + A1A499A5189E765800E2A2E2 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A499A4189E765800E2A2E2 /* Cocoa.framework */; }; + A1A499AF189E765800E2A2E2 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = A1A499AD189E765800E2A2E2 /* InfoPlist.strings */; }; + A1A499B1189E765800E2A2E2 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = A1A499B0189E765800E2A2E2 /* main.m */; }; + A1A499B5189E765800E2A2E2 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = A1A499B3189E765800E2A2E2 /* Credits.rtf */; }; + A1A499B8189E765800E2A2E2 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A1A499B7189E765800E2A2E2 /* AppDelegate.m */; }; + A1A499BB189E765800E2A2E2 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = A1A499B9189E765800E2A2E2 /* MainMenu.xib */; }; + A1A499BD189E765800E2A2E2 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A1A499BC189E765800E2A2E2 /* Images.xcassets */; }; + A1A499C4189E765800E2A2E2 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A499C3189E765800E2A2E2 /* XCTest.framework */; }; + A1A499C5189E765800E2A2E2 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A499A4189E765800E2A2E2 /* Cocoa.framework */; }; + A1A499CD189E765800E2A2E2 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = A1A499CB189E765800E2A2E2 /* InfoPlist.strings */; }; + A1A499CF189E765800E2A2E2 /* ExampleAppMacTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A1A499CE189E765800E2A2E2 /* ExampleAppMacTests.m */; }; + A1A499EC189E793300E2A2E2 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A499A9189E765800E2A2E2 /* Foundation.framework */; }; + A1A499F0189E793D00E2A2E2 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A499EF189E793D00E2A2E2 /* CoreAudio.framework */; }; + A1A499F2189E799400E2A2E2 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A499F1189E799400E2A2E2 /* AudioToolbox.framework */; }; + A1A499F3189E799F00E2A2E2 /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A499ED189E793700E2A2E2 /* AudioUnit.framework */; }; + A1A499F5189E79CB00E2A2E2 /* CoreAudioKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A499F4189E79CB00E2A2E2 /* CoreAudioKit.framework */; }; + A1A499F9189E7A3500E2A2E2 /* libStreamingKitMac.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A499F8189E7A3500E2A2E2 /* libStreamingKitMac.a */; }; + A1A499FA189E7A5600E2A2E2 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A499F1189E799400E2A2E2 /* AudioToolbox.framework */; }; + A1A499FC189E7A6D00E2A2E2 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A499FB189E7A6D00E2A2E2 /* SystemConfiguration.framework */; }; + A1A499FD189E7BFC00E2A2E2 /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A499ED189E793700E2A2E2 /* AudioUnit.framework */; }; + A1A49A01189E82EC00E2A2E2 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A49A00189E82EC00E2A2E2 /* QuartzCore.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + A1A499C6189E765800E2A2E2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A1A49999189E765800E2A2E2 /* Project object */; + proxyType = 1; + remoteGlobalIDString = A1A499A0189E765800E2A2E2; + remoteInfo = ExampleAppMac; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + A1A499A1189E765800E2A2E2 /* ExampleAppMac.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ExampleAppMac.app; sourceTree = BUILT_PRODUCTS_DIR; }; + A1A499A4189E765800E2A2E2 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; + A1A499A7189E765800E2A2E2 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; + A1A499A8189E765800E2A2E2 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; + A1A499A9189E765800E2A2E2 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + A1A499AC189E765800E2A2E2 /* ExampleAppMac-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ExampleAppMac-Info.plist"; sourceTree = ""; }; + A1A499AE189E765800E2A2E2 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + A1A499B0189E765800E2A2E2 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + A1A499B2189E765800E2A2E2 /* ExampleAppMac-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ExampleAppMac-Prefix.pch"; sourceTree = ""; }; + A1A499B4189E765800E2A2E2 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = ""; }; + A1A499B6189E765800E2A2E2 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + A1A499B7189E765800E2A2E2 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + A1A499BA189E765800E2A2E2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + A1A499BC189E765800E2A2E2 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + A1A499C2189E765800E2A2E2 /* ExampleAppMacTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleAppMacTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + A1A499C3189E765800E2A2E2 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + A1A499CA189E765800E2A2E2 /* ExampleAppMacTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ExampleAppMacTests-Info.plist"; sourceTree = ""; }; + A1A499CC189E765800E2A2E2 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + A1A499CE189E765800E2A2E2 /* ExampleAppMacTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExampleAppMacTests.m; sourceTree = ""; }; + A1A499EA189E76BD00E2A2E2 /* libStreamingKitMac.a */ = {isa = PBXFileReference; lastKnownFileType = file; name = libStreamingKitMac.a; path = ../StreamingKit/build/Debug/libStreamingKitMac.a; sourceTree = ""; }; + A1A499ED189E793700E2A2E2 /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; }; + A1A499EF189E793D00E2A2E2 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; }; + A1A499F1189E799400E2A2E2 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; + A1A499F4189E79CB00E2A2E2 /* CoreAudioKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudioKit.framework; path = System/Library/Frameworks/CoreAudioKit.framework; sourceTree = SDKROOT; }; + A1A499F8189E7A3500E2A2E2 /* libStreamingKitMac.a */ = {isa = PBXFileReference; lastKnownFileType = file; name = libStreamingKitMac.a; path = ../StreamingKit/build/Debug/libStreamingKitMac.a; sourceTree = ""; }; + A1A499FB189E7A6D00E2A2E2 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; + A1A499FE189E82DD00E2A2E2 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + A1A49A00189E82EC00E2A2E2 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A1A4999E189E765800E2A2E2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A1A49A01189E82EC00E2A2E2 /* QuartzCore.framework in Frameworks */, + A1A499FD189E7BFC00E2A2E2 /* AudioUnit.framework in Frameworks */, + A1A499FC189E7A6D00E2A2E2 /* SystemConfiguration.framework in Frameworks */, + A1A499FA189E7A5600E2A2E2 /* AudioToolbox.framework in Frameworks */, + A1A499F9189E7A3500E2A2E2 /* libStreamingKitMac.a in Frameworks */, + A1A499F0189E793D00E2A2E2 /* CoreAudio.framework in Frameworks */, + A1A499EC189E793300E2A2E2 /* Foundation.framework in Frameworks */, + A1A499A5189E765800E2A2E2 /* Cocoa.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A1A499BF189E765800E2A2E2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A1A499F5189E79CB00E2A2E2 /* CoreAudioKit.framework in Frameworks */, + A1A499F3189E799F00E2A2E2 /* AudioUnit.framework in Frameworks */, + A1A499F2189E799400E2A2E2 /* AudioToolbox.framework in Frameworks */, + A1A499C5189E765800E2A2E2 /* Cocoa.framework in Frameworks */, + A1A499C4189E765800E2A2E2 /* XCTest.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A1A49998189E765800E2A2E2 = { + isa = PBXGroup; + children = ( + A1A499AA189E765800E2A2E2 /* ExampleAppMac */, + A1A499C8189E765800E2A2E2 /* ExampleAppMacTests */, + A1A499A3189E765800E2A2E2 /* Frameworks */, + A1A499A2189E765800E2A2E2 /* Products */, + ); + sourceTree = ""; + }; + A1A499A2189E765800E2A2E2 /* Products */ = { + isa = PBXGroup; + children = ( + A1A499A1189E765800E2A2E2 /* ExampleAppMac.app */, + A1A499C2189E765800E2A2E2 /* ExampleAppMacTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + A1A499A3189E765800E2A2E2 /* Frameworks */ = { + isa = PBXGroup; + children = ( + A1A49A00189E82EC00E2A2E2 /* QuartzCore.framework */, + A1A499FE189E82DD00E2A2E2 /* CoreGraphics.framework */, + A1A499FB189E7A6D00E2A2E2 /* SystemConfiguration.framework */, + A1A499F8189E7A3500E2A2E2 /* libStreamingKitMac.a */, + A1A499F4189E79CB00E2A2E2 /* CoreAudioKit.framework */, + A1A499F1189E799400E2A2E2 /* AudioToolbox.framework */, + A1A499EF189E793D00E2A2E2 /* CoreAudio.framework */, + A1A499ED189E793700E2A2E2 /* AudioUnit.framework */, + A1A499EA189E76BD00E2A2E2 /* libStreamingKitMac.a */, + A1A499A4189E765800E2A2E2 /* Cocoa.framework */, + A1A499C3189E765800E2A2E2 /* XCTest.framework */, + A1A499A6189E765800E2A2E2 /* Other Frameworks */, + ); + name = Frameworks; + sourceTree = ""; + }; + A1A499A6189E765800E2A2E2 /* Other Frameworks */ = { + isa = PBXGroup; + children = ( + A1A499A7189E765800E2A2E2 /* AppKit.framework */, + A1A499A8189E765800E2A2E2 /* CoreData.framework */, + A1A499A9189E765800E2A2E2 /* Foundation.framework */, + ); + name = "Other Frameworks"; + sourceTree = ""; + }; + A1A499AA189E765800E2A2E2 /* ExampleAppMac */ = { + isa = PBXGroup; + children = ( + A1A499B6189E765800E2A2E2 /* AppDelegate.h */, + A1A499B7189E765800E2A2E2 /* AppDelegate.m */, + A1A499B9189E765800E2A2E2 /* MainMenu.xib */, + A1A499BC189E765800E2A2E2 /* Images.xcassets */, + A1A499AB189E765800E2A2E2 /* Supporting Files */, + ); + path = ExampleAppMac; + sourceTree = ""; + }; + A1A499AB189E765800E2A2E2 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + A1A499AC189E765800E2A2E2 /* ExampleAppMac-Info.plist */, + A1A499AD189E765800E2A2E2 /* InfoPlist.strings */, + A1A499B0189E765800E2A2E2 /* main.m */, + A1A499B2189E765800E2A2E2 /* ExampleAppMac-Prefix.pch */, + A1A499B3189E765800E2A2E2 /* Credits.rtf */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + A1A499C8189E765800E2A2E2 /* ExampleAppMacTests */ = { + isa = PBXGroup; + children = ( + A1A499CE189E765800E2A2E2 /* ExampleAppMacTests.m */, + A1A499C9189E765800E2A2E2 /* Supporting Files */, + ); + path = ExampleAppMacTests; + sourceTree = ""; + }; + A1A499C9189E765800E2A2E2 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + A1A499CA189E765800E2A2E2 /* ExampleAppMacTests-Info.plist */, + A1A499CB189E765800E2A2E2 /* InfoPlist.strings */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + A1A499A0189E765800E2A2E2 /* ExampleAppMac */ = { + isa = PBXNativeTarget; + buildConfigurationList = A1A499D2189E765800E2A2E2 /* Build configuration list for PBXNativeTarget "ExampleAppMac" */; + buildPhases = ( + A1A4999D189E765800E2A2E2 /* Sources */, + A1A4999E189E765800E2A2E2 /* Frameworks */, + A1A4999F189E765800E2A2E2 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ExampleAppMac; + productName = ExampleAppMac; + productReference = A1A499A1189E765800E2A2E2 /* ExampleAppMac.app */; + productType = "com.apple.product-type.application"; + }; + A1A499C1189E765800E2A2E2 /* ExampleAppMacTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = A1A499D5189E765800E2A2E2 /* Build configuration list for PBXNativeTarget "ExampleAppMacTests" */; + buildPhases = ( + A1A499BE189E765800E2A2E2 /* Sources */, + A1A499BF189E765800E2A2E2 /* Frameworks */, + A1A499C0189E765800E2A2E2 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + A1A499C7189E765800E2A2E2 /* PBXTargetDependency */, + ); + name = ExampleAppMacTests; + productName = ExampleAppMacTests; + productReference = A1A499C2189E765800E2A2E2 /* ExampleAppMacTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A1A49999189E765800E2A2E2 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0500; + ORGANIZATIONNAME = "Thong Nguyen"; + TargetAttributes = { + A1A499C1189E765800E2A2E2 = { + TestTargetID = A1A499A0189E765800E2A2E2; + }; + }; + }; + buildConfigurationList = A1A4999C189E765800E2A2E2 /* Build configuration list for PBXProject "ExampleAppMac" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = A1A49998189E765800E2A2E2; + productRefGroup = A1A499A2189E765800E2A2E2 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A1A499A0189E765800E2A2E2 /* ExampleAppMac */, + A1A499C1189E765800E2A2E2 /* ExampleAppMacTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A1A4999F189E765800E2A2E2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A1A499AF189E765800E2A2E2 /* InfoPlist.strings in Resources */, + A1A499BD189E765800E2A2E2 /* Images.xcassets in Resources */, + A1A499B5189E765800E2A2E2 /* Credits.rtf in Resources */, + A1A499BB189E765800E2A2E2 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A1A499C0189E765800E2A2E2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A1A499CD189E765800E2A2E2 /* InfoPlist.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A1A4999D189E765800E2A2E2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A1A499B8189E765800E2A2E2 /* AppDelegate.m in Sources */, + A1A499B1189E765800E2A2E2 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A1A499BE189E765800E2A2E2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A1A499CF189E765800E2A2E2 /* ExampleAppMacTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + A1A499C7189E765800E2A2E2 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = A1A499A0189E765800E2A2E2 /* ExampleAppMac */; + targetProxy = A1A499C6189E765800E2A2E2 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + A1A499AD189E765800E2A2E2 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + A1A499AE189E765800E2A2E2 /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; + A1A499B3189E765800E2A2E2 /* Credits.rtf */ = { + isa = PBXVariantGroup; + children = ( + A1A499B4189E765800E2A2E2 /* en */, + ); + name = Credits.rtf; + sourceTree = ""; + }; + A1A499B9189E765800E2A2E2 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + A1A499BA189E765800E2A2E2 /* Base */, + ); + name = MainMenu.xib; + sourceTree = ""; + }; + A1A499CB189E765800E2A2E2 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + A1A499CC189E765800E2A2E2 /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + A1A499D0189E765800E2A2E2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.9; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + A1A499D1189E765800E2A2E2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.9; + SDKROOT = macosx; + }; + name = Release; + }; + A1A499D3189E765800E2A2E2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + COMBINE_HIDPI_IMAGES = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "ExampleAppMac/ExampleAppMac-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../StreamingKit/StreamingKit", + ); + INFOPLIST_FILE = "ExampleAppMac/ExampleAppMac-Info.plist"; + MACOSX_DEPLOYMENT_TARGET = 10.8; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + A1A499D4189E765800E2A2E2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + COMBINE_HIDPI_IMAGES = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "ExampleAppMac/ExampleAppMac-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../StreamingKit/StreamingKit", + ); + INFOPLIST_FILE = "ExampleAppMac/ExampleAppMac-Info.plist"; + MACOSX_DEPLOYMENT_TARGET = 10.8; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; + A1A499D6189E765800E2A2E2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/ExampleAppMac.app/Contents/MacOS/ExampleAppMac"; + COMBINE_HIDPI_IMAGES = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "ExampleAppMac/ExampleAppMac-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = "ExampleAppMacTests/ExampleAppMacTests-Info.plist"; + MACOSX_DEPLOYMENT_TARGET = 10.8; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUNDLE_LOADER)"; + WRAPPER_EXTENSION = xctest; + }; + name = Debug; + }; + A1A499D7189E765800E2A2E2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/ExampleAppMac.app/Contents/MacOS/ExampleAppMac"; + COMBINE_HIDPI_IMAGES = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "ExampleAppMac/ExampleAppMac-Prefix.pch"; + INFOPLIST_FILE = "ExampleAppMacTests/ExampleAppMacTests-Info.plist"; + MACOSX_DEPLOYMENT_TARGET = 10.8; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUNDLE_LOADER)"; + WRAPPER_EXTENSION = xctest; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A1A4999C189E765800E2A2E2 /* Build configuration list for PBXProject "ExampleAppMac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A1A499D0189E765800E2A2E2 /* Debug */, + A1A499D1189E765800E2A2E2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A1A499D2189E765800E2A2E2 /* Build configuration list for PBXNativeTarget "ExampleAppMac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A1A499D3189E765800E2A2E2 /* Debug */, + A1A499D4189E765800E2A2E2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A1A499D5189E765800E2A2E2 /* Build configuration list for PBXNativeTarget "ExampleAppMacTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A1A499D6189E765800E2A2E2 /* Debug */, + A1A499D7189E765800E2A2E2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = A1A49999189E765800E2A2E2 /* Project object */; +} diff --git a/ExampleAppMac/ExampleAppMac.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ExampleAppMac/ExampleAppMac.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..436fba6 --- /dev/null +++ b/ExampleAppMac/ExampleAppMac.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ExampleAppMac/ExampleAppMac.xcodeproj/project.xcworkspace/xcshareddata/ExampleAppMac.xccheckout b/ExampleAppMac/ExampleAppMac.xcodeproj/project.xcworkspace/xcshareddata/ExampleAppMac.xccheckout new file mode 100644 index 0000000..0eeebd3 --- /dev/null +++ b/ExampleAppMac/ExampleAppMac.xcodeproj/project.xcworkspace/xcshareddata/ExampleAppMac.xccheckout @@ -0,0 +1,41 @@ + + + + + IDESourceControlProjectFavoriteDictionaryKey + + IDESourceControlProjectIdentifier + B1180E29-F9F8-4232-A985-F8E21716EF14 + IDESourceControlProjectName + ExampleAppMac + IDESourceControlProjectOriginsDictionary + + DD310C30-B3D0-4BD7-9565-9F29F09CC4F8 + https://github.com/tumtumtum/StreamingKit.git + + IDESourceControlProjectPath + ExampleAppMac/ExampleAppMac.xcodeproj/project.xcworkspace + IDESourceControlProjectRelativeInstallPathDictionary + + DD310C30-B3D0-4BD7-9565-9F29F09CC4F8 + ../../.. + + IDESourceControlProjectURL + https://github.com/tumtumtum/StreamingKit.git + IDESourceControlProjectVersion + 110 + IDESourceControlProjectWCCIdentifier + DD310C30-B3D0-4BD7-9565-9F29F09CC4F8 + IDESourceControlProjectWCConfigurations + + + IDESourceControlRepositoryExtensionIdentifierKey + public.vcs.git + IDESourceControlWCCIdentifierKey + DD310C30-B3D0-4BD7-9565-9F29F09CC4F8 + IDESourceControlWCCName + StreamingKit + + + + diff --git a/ExampleAppMac/ExampleAppMac/AppDelegate.h b/ExampleAppMac/ExampleAppMac/AppDelegate.h new file mode 100644 index 0000000..a113dfd --- /dev/null +++ b/ExampleAppMac/ExampleAppMac/AppDelegate.h @@ -0,0 +1,16 @@ +// +// AppDelegate.h +// ExampleAppMac +// +// Created by Thong Nguyen on 02/02/2014. +// Copyright (c) 2014 Thong Nguyen. All rights reserved. +// + +#import +#import "STKAudioPlayer.h" + +@interface AppDelegate : NSObject + +@property (assign) IBOutlet NSWindow *window; + +@end diff --git a/ExampleAppMac/ExampleAppMac/AppDelegate.m b/ExampleAppMac/ExampleAppMac/AppDelegate.m new file mode 100644 index 0000000..b22990b --- /dev/null +++ b/ExampleAppMac/ExampleAppMac/AppDelegate.m @@ -0,0 +1,120 @@ +// +// AppDelegate.m +// ExampleAppMac +// +// Created by Thong Nguyen on 02/02/2014. +// Copyright (c) 2014 Thong Nguyen. All rights reserved. +// + +#import "AppDelegate.h" +#import "STKAudioPlayer.h" + +@interface AppDelegate() +{ + NSView* meter; + NSSlider* slider; + STKAudioPlayer* audioPlayer; +} +@end + +@implementation AppDelegate + +-(void) applicationDidFinishLaunching:(NSNotification *)aNotification +{ + CGRect frame = [self.window.contentView frame]; + + NSButton* playFromHTTPButton = [[NSButton alloc] initWithFrame:CGRectMake(10, 10, frame.size.width - 20, 100)]; + + [playFromHTTPButton setTitle:@"Play from HTTP"]; + [playFromHTTPButton setAction:@selector(playFromHTTP)]; + + slider = [[NSSlider alloc] initWithFrame:CGRectMake(10, 140, frame.size.width - 20, 20)]; + [slider setAction:@selector(sliderChanged:)]; + + meter = [[NSView alloc] initWithFrame:CGRectMake(10, 200, 0, 20)]; + [meter setLayer:[CALayer new]]; + [meter setWantsLayer:YES]; + meter.layer.backgroundColor = [NSColor greenColor].CGColor; + + [[self.window contentView] addSubview:slider]; + [[self.window contentView] addSubview:playFromHTTPButton]; + [[self.window contentView] addSubview:meter]; + + audioPlayer = [[STKAudioPlayer alloc] init]; + audioPlayer.delegate = self; + audioPlayer.meteringEnabled = YES; + + [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(tick:) userInfo:nil repeats:YES]; +} + +-(void) playFromHTTP +{ + [audioPlayer play:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"]; +} + +-(void) tick:(NSTimer*)timer +{ + if (!audioPlayer) + { + slider.doubleValue = 0; + + return; + } + + CGFloat meterWidth = 0; + + if (audioPlayer.duration != 0) + { + slider.minValue = 0; + slider.maxValue = audioPlayer.duration; + slider.doubleValue = audioPlayer.progress; + + meterWidth = [self.window.contentView frame].size.width - 20; + meterWidth *= (([audioPlayer averagePowerInDecibelsForChannel:0] + 60) / 60); + } + else + { + slider.doubleValue = 0; + slider.minValue = 0; + slider.maxValue = 0; + + meterWidth = 0; + } + + CGRect frame = meter.frame; + + frame.size.width = meterWidth; + + meter.frame = frame; +} + +-(void) sliderChanged:(NSSlider*)sliderIn +{ + [audioPlayer seekToTime:sliderIn.doubleValue]; +} + +-(void) updateControls +{ +} + +-(void) audioPlayer:(STKAudioPlayer*)audioPlayer didStartPlayingQueueItemId:(NSObject*)queueItemId +{ +} + +-(void) audioPlayer:(STKAudioPlayer*)audioPlayer didFinishBufferingSourceWithQueueItemId:(NSObject*)queueItemId +{ +} + +-(void) audioPlayer:(STKAudioPlayer*)audioPlayer stateChanged:(STKAudioPlayerState)state previousState:(STKAudioPlayerState)previousState +{ +} + +-(void) audioPlayer:(STKAudioPlayer*)audioPlayer didFinishPlayingQueueItemId:(NSObject*)queueItemId withReason:(STKAudioPlayerStopReason)stopReason andProgress:(double)progress andDuration:(double)duration +{ +} + +-(void) audioPlayer:(STKAudioPlayer*)audioPlayer unexpectedError:(STKAudioPlayerErrorCode)errorCode +{ +} + +@end diff --git a/ExampleAppMac/ExampleAppMac/Base.lproj/MainMenu.xib b/ExampleAppMac/ExampleAppMac/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..eefd534 --- /dev/null +++ b/ExampleAppMac/ExampleAppMac/Base.lproj/MainMenu.xib @@ -0,0 +1,467 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + Left to Right + + + + Right to Left + + + + + + + + Default + + + + Left to Right + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ExampleAppMac/ExampleAppMac/ExampleAppMac-Info.plist b/ExampleAppMac/ExampleAppMac/ExampleAppMac-Info.plist new file mode 100644 index 0000000..9248f20 --- /dev/null +++ b/ExampleAppMac/ExampleAppMac/ExampleAppMac-Info.plist @@ -0,0 +1,34 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + + CFBundleIdentifier + com.abstractpath.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + NSHumanReadableCopyright + Copyright © 2014 Thong Nguyen. All rights reserved. + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/ExampleAppMac/ExampleAppMac/ExampleAppMac-Prefix.pch b/ExampleAppMac/ExampleAppMac/ExampleAppMac-Prefix.pch new file mode 100644 index 0000000..926a42b --- /dev/null +++ b/ExampleAppMac/ExampleAppMac/ExampleAppMac-Prefix.pch @@ -0,0 +1,9 @@ +// +// Prefix header +// +// The contents of this file are implicitly included at the beginning of every source file. +// + +#ifdef __OBJC__ + #import +#endif diff --git a/ExampleAppMac/ExampleAppMac/Images.xcassets/AppIcon.appiconset/Contents.json b/ExampleAppMac/ExampleAppMac/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..2db2b1c --- /dev/null +++ b/ExampleAppMac/ExampleAppMac/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "512x512", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "512x512", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ExampleAppMac/ExampleAppMac/en.lproj/Credits.rtf b/ExampleAppMac/ExampleAppMac/en.lproj/Credits.rtf new file mode 100644 index 0000000..46576ef --- /dev/null +++ b/ExampleAppMac/ExampleAppMac/en.lproj/Credits.rtf @@ -0,0 +1,29 @@ +{\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;} +{\colortbl;\red255\green255\blue255;} +\paperw9840\paperh8400 +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural + +\f0\b\fs24 \cf0 Engineering: +\b0 \ + Some people\ +\ + +\b Human Interface Design: +\b0 \ + Some other people\ +\ + +\b Testing: +\b0 \ + Hopefully not nobody\ +\ + +\b Documentation: +\b0 \ + Whoever\ +\ + +\b With special thanks to: +\b0 \ + Mom\ +} diff --git a/ExampleAppMac/ExampleAppMac/en.lproj/InfoPlist.strings b/ExampleAppMac/ExampleAppMac/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/ExampleAppMac/ExampleAppMac/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/ExampleAppMac/ExampleAppMac/main.m b/ExampleAppMac/ExampleAppMac/main.m new file mode 100644 index 0000000..9322aa7 --- /dev/null +++ b/ExampleAppMac/ExampleAppMac/main.m @@ -0,0 +1,14 @@ +// +// main.m +// ExampleAppMac +// +// Created by Thong Nguyen on 02/02/2014. +// Copyright (c) 2014 Thong Nguyen. All rights reserved. +// + +#import + +int main(int argc, const char * argv[]) +{ + return NSApplicationMain(argc, argv); +} diff --git a/ExampleAppMac/ExampleAppMacTests/ExampleAppMacTests-Info.plist b/ExampleAppMac/ExampleAppMacTests/ExampleAppMacTests-Info.plist new file mode 100644 index 0000000..552d5b1 --- /dev/null +++ b/ExampleAppMac/ExampleAppMacTests/ExampleAppMacTests-Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.abstractpath.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/ExampleAppMac/ExampleAppMacTests/ExampleAppMacTests.m b/ExampleAppMac/ExampleAppMacTests/ExampleAppMacTests.m new file mode 100644 index 0000000..3ae839a --- /dev/null +++ b/ExampleAppMac/ExampleAppMacTests/ExampleAppMacTests.m @@ -0,0 +1,34 @@ +// +// ExampleAppMacTests.m +// ExampleAppMacTests +// +// Created by Thong Nguyen on 02/02/2014. +// Copyright (c) 2014 Thong Nguyen. All rights reserved. +// + +#import + +@interface ExampleAppMacTests : XCTestCase + +@end + +@implementation ExampleAppMacTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown +{ + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testExample +{ + XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); +} + +@end diff --git a/ExampleAppMac/ExampleAppMacTests/en.lproj/InfoPlist.strings b/ExampleAppMac/ExampleAppMacTests/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/ExampleAppMac/ExampleAppMacTests/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/StreamingKit-head.podspec b/StreamingKit-head.podspec index c55bdd7..5eae266 100644 --- a/StreamingKit-head.podspec +++ b/StreamingKit-head.podspec @@ -9,5 +9,5 @@ Pod::Spec.new do |s| s.platform = :ios s.requires_arc = true s.source_files = 'StreamingKit/StreamingKit/*.{h,m}' - s.frameworks = 'AVFoundation', 'SystemConfiguration', 'CFNetwork', 'CoreFoundation', 'AudioToolbox' + s.frameworks = 'AVFoundation', 'SystemConfiguration', 'CFNetwork', 'CoreFoundation', 'AudioToolbox', 'AudioUnit' end diff --git a/StreamingKit.podspec b/StreamingKit.podspec index 57a76ab..1a18544 100644 --- a/StreamingKit.podspec +++ b/StreamingKit.podspec @@ -9,5 +9,5 @@ Pod::Spec.new do |s| s.platform = :ios s.requires_arc = true s.source_files = 'StreamingKit/StreamingKit/*.{h,m}' - s.frameworks = 'AVFoundation', 'SystemConfiguration', 'CFNetwork', 'CoreFoundation', 'AudioToolbox' + s.frameworks = 'AVFoundation', 'SystemConfiguration', 'CFNetwork', 'CoreFoundation', 'AudioToolbox', 'AudioUnit' end diff --git a/StreamingKit.xcworkspace/contents.xcworkspacedata b/StreamingKit.xcworkspace/contents.xcworkspacedata index eb695ed..1155645 100644 --- a/StreamingKit.xcworkspace/contents.xcworkspacedata +++ b/StreamingKit.xcworkspace/contents.xcworkspacedata @@ -4,6 +4,9 @@ + + diff --git a/StreamingKit/StreamingKit.xcodeproj/project.pbxproj b/StreamingKit/StreamingKit.xcodeproj/project.pbxproj index 5f6d53b..bdb68cc 100644 --- a/StreamingKit/StreamingKit.xcodeproj/project.pbxproj +++ b/StreamingKit/StreamingKit.xcodeproj/project.pbxproj @@ -7,7 +7,24 @@ objects = { /* Begin PBXBuildFile section */ - A1E7C4CC188D57F50010896F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1E7C4CB188D57F50010896F /* Foundation.framework */; }; + A1A4996B189E744400E2A2E2 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A4996A189E744400E2A2E2 /* Cocoa.framework */; }; + A1A49975189E744500E2A2E2 /* StreamingKitMac.m in Sources */ = {isa = PBXBuildFile; fileRef = A1A49974189E744500E2A2E2 /* StreamingKitMac.m */; }; + A1A4997B189E744500E2A2E2 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1E7C4D9188D57F60010896F /* XCTest.framework */; }; + A1A4997C189E744500E2A2E2 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A4996A189E744400E2A2E2 /* Cocoa.framework */; }; + A1A4997F189E744500E2A2E2 /* libStreamingKitMac.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A49969189E744400E2A2E2 /* libStreamingKitMac.a */; }; + A1A49985189E744500E2A2E2 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = A1A49983189E744500E2A2E2 /* InfoPlist.strings */; }; + A1A49987189E744500E2A2E2 /* StreamingKitMacTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A1A49986189E744500E2A2E2 /* StreamingKitMacTests.m */; }; + A1A4998E189E745900E2A2E2 /* STKAudioPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = A1E7C4F2188D5E550010896F /* STKAudioPlayer.m */; }; + A1A4998F189E745C00E2A2E2 /* STKAutoRecoveringHTTPDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = A1E7C4F4188D5E550010896F /* STKAutoRecoveringHTTPDataSource.m */; }; + A1A49991189E746000E2A2E2 /* STKCoreFoundationDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = A1E7C4F6188D5E550010896F /* STKCoreFoundationDataSource.m */; }; + A1A49992189E746300E2A2E2 /* STKDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = A1E7C4F8188D5E550010896F /* STKDataSource.m */; }; + A1A49993189E746500E2A2E2 /* STKDataSourceWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = A1E7C4FA188D5E550010896F /* STKDataSourceWrapper.m */; }; + A1A49994189E746900E2A2E2 /* STKHTTPDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = A1E7C4FC188D5E550010896F /* STKHTTPDataSource.m */; }; + A1A49995189E746B00E2A2E2 /* STKLocalFileDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = A1E7C4FE188D5E550010896F /* STKLocalFileDataSource.m */; }; + A1A49996189E746E00E2A2E2 /* STKQueueEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = A1BF65D1189A6582004DD08C /* STKQueueEntry.m */; }; + A1A49997189E747000E2A2E2 /* NSMutableArray+STKAudioPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = A1BF65D4189A65C6004DD08C /* NSMutableArray+STKAudioPlayer.m */; }; + A1BF65D2189A6582004DD08C /* STKQueueEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = A1BF65D1189A6582004DD08C /* STKQueueEntry.m */; }; + A1BF65D5189A65C6004DD08C /* NSMutableArray+STKAudioPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = A1BF65D4189A65C6004DD08C /* NSMutableArray+STKAudioPlayer.m */; }; A1E7C4DA188D57F60010896F /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1E7C4D9188D57F60010896F /* XCTest.framework */; }; A1E7C4DB188D57F60010896F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1E7C4CB188D57F50010896F /* Foundation.framework */; }; A1E7C4E0188D57F60010896F /* libStreamingKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A1E7C4C8188D57F50010896F /* libStreamingKit.a */; }; @@ -20,10 +37,30 @@ A1E7C503188D5E550010896F /* STKDataSourceWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = A1E7C4FA188D5E550010896F /* STKDataSourceWrapper.m */; }; A1E7C504188D5E550010896F /* STKHTTPDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = A1E7C4FC188D5E550010896F /* STKHTTPDataSource.m */; }; A1E7C505188D5E550010896F /* STKLocalFileDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = A1E7C4FE188D5E550010896F /* STKLocalFileDataSource.m */; }; - A1E7C508188D62D20010896F /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1E7C507188D62D20010896F /* UIKit.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + A1A4997D189E744500E2A2E2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A1E7C4C0188D57F50010896F /* Project object */; + proxyType = 1; + remoteGlobalIDString = A1A49968189E744400E2A2E2; + remoteInfo = StreamingKitMac; + }; + A1A499E6189E769A00E2A2E2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A1A499E1189E769A00E2A2E2 /* ExampleAppMac.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = A1A499A1189E765800E2A2E2; + remoteInfo = ExampleAppMac; + }; + A1A499E8189E769A00E2A2E2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A1A499E1189E769A00E2A2E2 /* ExampleAppMac.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = A1A499C2189E765800E2A2E2; + remoteInfo = ExampleAppMacTests; + }; A1E7C4DE188D57F60010896F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = A1E7C4C0188D57F50010896F /* Project object */; @@ -46,6 +83,25 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + A1A49969189E744400E2A2E2 /* libStreamingKitMac.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libStreamingKitMac.a; sourceTree = BUILT_PRODUCTS_DIR; }; + A1A4996A189E744400E2A2E2 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = Library/Frameworks/Cocoa.framework; sourceTree = DEVELOPER_DIR; }; + A1A4996D189E744500E2A2E2 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + A1A4996E189E744500E2A2E2 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; + A1A4996F189E744500E2A2E2 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; + A1A49972189E744500E2A2E2 /* StreamingKitMac-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "StreamingKitMac-Prefix.pch"; sourceTree = ""; }; + A1A49973189E744500E2A2E2 /* StreamingKitMac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StreamingKitMac.h; sourceTree = ""; }; + A1A49974189E744500E2A2E2 /* StreamingKitMac.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StreamingKitMac.m; sourceTree = ""; }; + A1A4997A189E744500E2A2E2 /* StreamingKitMacTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StreamingKitMacTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + A1A49982189E744500E2A2E2 /* StreamingKitMacTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "StreamingKitMacTests-Info.plist"; sourceTree = ""; }; + A1A49984189E744500E2A2E2 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + A1A49986189E744500E2A2E2 /* StreamingKitMacTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StreamingKitMacTests.m; sourceTree = ""; }; + A1A499E1189E769A00E2A2E2 /* ExampleAppMac.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ExampleAppMac.xcodeproj; path = ../ExampleAppMac/ExampleAppMac.xcodeproj; sourceTree = ""; }; + A1A499F6189E79EA00E2A2E2 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; + A1BF65D0189A6582004DD08C /* STKQueueEntry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = STKQueueEntry.h; sourceTree = ""; }; + A1BF65D1189A6582004DD08C /* STKQueueEntry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STKQueueEntry.m; sourceTree = ""; }; + A1BF65D3189A65C6004DD08C /* NSMutableArray+STKAudioPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableArray+STKAudioPlayer.h"; sourceTree = ""; }; + A1BF65D4189A65C6004DD08C /* NSMutableArray+STKAudioPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableArray+STKAudioPlayer.m"; sourceTree = ""; }; + A1C9767618981BFE0057F881 /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; }; A1E7C4C8188D57F50010896F /* libStreamingKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libStreamingKit.a; sourceTree = BUILT_PRODUCTS_DIR; }; A1E7C4CB188D57F50010896F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; A1E7C4CF188D57F50010896F /* StreamingKit-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "StreamingKit-Prefix.pch"; sourceTree = ""; }; @@ -72,12 +128,28 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + A1A49966189E744400E2A2E2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A1A4996B189E744400E2A2E2 /* Cocoa.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A1A49977189E744500E2A2E2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A1A4997C189E744500E2A2E2 /* Cocoa.framework in Frameworks */, + A1A4997B189E744500E2A2E2 /* XCTest.framework in Frameworks */, + A1A4997F189E744500E2A2E2 /* libStreamingKitMac.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; A1E7C4C5188D57F50010896F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - A1E7C508188D62D20010896F /* UIKit.framework in Frameworks */, - A1E7C4CC188D57F50010896F /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -94,13 +166,71 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + A1A4996C189E744500E2A2E2 /* Other Frameworks */ = { + isa = PBXGroup; + children = ( + A1A4996D189E744500E2A2E2 /* Foundation.framework */, + A1A4996E189E744500E2A2E2 /* CoreData.framework */, + A1A4996F189E744500E2A2E2 /* AppKit.framework */, + ); + name = "Other Frameworks"; + sourceTree = ""; + }; + A1A49970189E744500E2A2E2 /* StreamingKitMac */ = { + isa = PBXGroup; + children = ( + A1A49973189E744500E2A2E2 /* StreamingKitMac.h */, + A1A49974189E744500E2A2E2 /* StreamingKitMac.m */, + A1A49971189E744500E2A2E2 /* Supporting Files */, + ); + path = StreamingKitMac; + sourceTree = ""; + }; + A1A49971189E744500E2A2E2 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + A1A49972189E744500E2A2E2 /* StreamingKitMac-Prefix.pch */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + A1A49980189E744500E2A2E2 /* StreamingKitMacTests */ = { + isa = PBXGroup; + children = ( + A1A49986189E744500E2A2E2 /* StreamingKitMacTests.m */, + A1A49981189E744500E2A2E2 /* Supporting Files */, + ); + path = StreamingKitMacTests; + sourceTree = ""; + }; + A1A49981189E744500E2A2E2 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + A1A49982189E744500E2A2E2 /* StreamingKitMacTests-Info.plist */, + A1A49983189E744500E2A2E2 /* InfoPlist.strings */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + A1A499E2189E769A00E2A2E2 /* Products */ = { + isa = PBXGroup; + children = ( + A1A499E7189E769A00E2A2E2 /* ExampleAppMac.app */, + A1A499E9189E769A00E2A2E2 /* ExampleAppMacTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; A1E7C4BF188D57F50010896F = { isa = PBXGroup; children = ( A1E7C4CD188D57F50010896F /* StreamingKit */, A1E7C4E1188D57F60010896F /* StreamingKitTests */, + A1A49970189E744500E2A2E2 /* StreamingKitMac */, + A1A49980189E744500E2A2E2 /* StreamingKitMacTests */, A1E7C4CA188D57F50010896F /* Frameworks */, A1E7C4C9188D57F50010896F /* Products */, + A1A499E1189E769A00E2A2E2 /* ExampleAppMac.xcodeproj */, ); sourceTree = ""; }; @@ -109,6 +239,8 @@ children = ( A1E7C4C8188D57F50010896F /* libStreamingKit.a */, A1E7C4D8188D57F60010896F /* StreamingKitTests.xctest */, + A1A49969189E744400E2A2E2 /* libStreamingKitMac.a */, + A1A4997A189E744500E2A2E2 /* StreamingKitMacTests.xctest */, ); name = Products; sourceTree = ""; @@ -116,9 +248,13 @@ A1E7C4CA188D57F50010896F /* Frameworks */ = { isa = PBXGroup; children = ( + A1A499F6189E79EA00E2A2E2 /* AudioToolbox.framework */, + A1C9767618981BFE0057F881 /* AudioUnit.framework */, A1E7C507188D62D20010896F /* UIKit.framework */, A1E7C4CB188D57F50010896F /* Foundation.framework */, A1E7C4D9188D57F60010896F /* XCTest.framework */, + A1A4996A189E744400E2A2E2 /* Cocoa.framework */, + A1A4996C189E744500E2A2E2 /* Other Frameworks */, ); name = Frameworks; sourceTree = ""; @@ -140,6 +276,10 @@ A1E7C4FC188D5E550010896F /* STKHTTPDataSource.m */, A1E7C4FD188D5E550010896F /* STKLocalFileDataSource.h */, A1E7C4FE188D5E550010896F /* STKLocalFileDataSource.m */, + A1BF65D0189A6582004DD08C /* STKQueueEntry.h */, + A1BF65D1189A6582004DD08C /* STKQueueEntry.m */, + A1BF65D3189A65C6004DD08C /* NSMutableArray+STKAudioPlayer.h */, + A1BF65D4189A65C6004DD08C /* NSMutableArray+STKAudioPlayer.m */, A1E7C4CE188D57F50010896F /* Supporting Files */, ); path = StreamingKit; @@ -173,7 +313,52 @@ }; /* End PBXGroup section */ +/* Begin PBXHeadersBuildPhase section */ + A1A49967189E744400E2A2E2 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + /* Begin PBXNativeTarget section */ + A1A49968189E744400E2A2E2 /* StreamingKitMac */ = { + isa = PBXNativeTarget; + buildConfigurationList = A1A4998C189E744500E2A2E2 /* Build configuration list for PBXNativeTarget "StreamingKitMac" */; + buildPhases = ( + A1A49965189E744400E2A2E2 /* Sources */, + A1A49966189E744400E2A2E2 /* Frameworks */, + A1A49967189E744400E2A2E2 /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = StreamingKitMac; + productName = StreamingKitMac; + productReference = A1A49969189E744400E2A2E2 /* libStreamingKitMac.a */; + productType = "com.apple.product-type.library.static"; + }; + A1A49979189E744500E2A2E2 /* StreamingKitMacTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = A1A4998D189E744500E2A2E2 /* Build configuration list for PBXNativeTarget "StreamingKitMacTests" */; + buildPhases = ( + A1A49976189E744500E2A2E2 /* Sources */, + A1A49977189E744500E2A2E2 /* Frameworks */, + A1A49978189E744500E2A2E2 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + A1A4997E189E744500E2A2E2 /* PBXTargetDependency */, + ); + name = StreamingKitMacTests; + productName = StreamingKitMacTests; + productReference = A1A4997A189E744500E2A2E2 /* StreamingKitMacTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; A1E7C4C7188D57F50010896F /* StreamingKit */ = { isa = PBXNativeTarget; buildConfigurationList = A1E7C4EB188D57F60010896F /* Build configuration list for PBXNativeTarget "StreamingKit" */; @@ -229,15 +414,48 @@ mainGroup = A1E7C4BF188D57F50010896F; productRefGroup = A1E7C4C9188D57F50010896F /* Products */; projectDirPath = ""; + projectReferences = ( + { + ProductGroup = A1A499E2189E769A00E2A2E2 /* Products */; + ProjectRef = A1A499E1189E769A00E2A2E2 /* ExampleAppMac.xcodeproj */; + }, + ); projectRoot = ""; targets = ( A1E7C4C7188D57F50010896F /* StreamingKit */, A1E7C4D7188D57F60010896F /* StreamingKitTests */, + A1A49968189E744400E2A2E2 /* StreamingKitMac */, + A1A49979189E744500E2A2E2 /* StreamingKitMacTests */, ); }; /* End PBXProject section */ +/* Begin PBXReferenceProxy section */ + A1A499E7189E769A00E2A2E2 /* ExampleAppMac.app */ = { + isa = PBXReferenceProxy; + fileType = wrapper.application; + path = ExampleAppMac.app; + remoteRef = A1A499E6189E769A00E2A2E2 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + A1A499E9189E769A00E2A2E2 /* ExampleAppMacTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ExampleAppMacTests.xctest; + remoteRef = A1A499E8189E769A00E2A2E2 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + /* Begin PBXResourcesBuildPhase section */ + A1A49978189E744500E2A2E2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A1A49985189E744500E2A2E2 /* InfoPlist.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; A1E7C4D6188D57F60010896F /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -249,6 +467,31 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + A1A49965189E744400E2A2E2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A1A4998F189E745C00E2A2E2 /* STKAutoRecoveringHTTPDataSource.m in Sources */, + A1A49994189E746900E2A2E2 /* STKHTTPDataSource.m in Sources */, + A1A49991189E746000E2A2E2 /* STKCoreFoundationDataSource.m in Sources */, + A1A49995189E746B00E2A2E2 /* STKLocalFileDataSource.m in Sources */, + A1A49997189E747000E2A2E2 /* NSMutableArray+STKAudioPlayer.m in Sources */, + A1A4998E189E745900E2A2E2 /* STKAudioPlayer.m in Sources */, + A1A49993189E746500E2A2E2 /* STKDataSourceWrapper.m in Sources */, + A1A49992189E746300E2A2E2 /* STKDataSource.m in Sources */, + A1A49975189E744500E2A2E2 /* StreamingKitMac.m in Sources */, + A1A49996189E746E00E2A2E2 /* STKQueueEntry.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A1A49976189E744500E2A2E2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A1A49987189E744500E2A2E2 /* StreamingKitMacTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; A1E7C4C4188D57F50010896F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -256,9 +499,11 @@ A1E7C501188D5E550010896F /* STKCoreFoundationDataSource.m in Sources */, A1E7C4FF188D5E550010896F /* STKAudioPlayer.m in Sources */, A1E7C505188D5E550010896F /* STKLocalFileDataSource.m in Sources */, + A1BF65D2189A6582004DD08C /* STKQueueEntry.m in Sources */, A1E7C504188D5E550010896F /* STKHTTPDataSource.m in Sources */, A1E7C503188D5E550010896F /* STKDataSourceWrapper.m in Sources */, A1E7C502188D5E550010896F /* STKDataSource.m in Sources */, + A1BF65D5189A65C6004DD08C /* NSMutableArray+STKAudioPlayer.m in Sources */, A1E7C500188D5E550010896F /* STKAutoRecoveringHTTPDataSource.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -274,6 +519,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + A1A4997E189E744500E2A2E2 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = A1A49968189E744400E2A2E2 /* StreamingKitMac */; + targetProxy = A1A4997D189E744500E2A2E2 /* PBXContainerItemProxy */; + }; A1E7C4DF188D57F60010896F /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = A1E7C4C7188D57F50010896F /* StreamingKit */; @@ -282,6 +532,14 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + A1A49983189E744500E2A2E2 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + A1A49984189E744500E2A2E2 /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; A1E7C4E4188D57F60010896F /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( @@ -293,6 +551,86 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + A1A49988189E744500E2A2E2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(DEVELOPER_FRAMEWORKS_DIR)", + ); + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "StreamingKitMac/StreamingKitMac-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + MACOSX_DEPLOYMENT_TARGET = 10.8; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + }; + name = Debug; + }; + A1A49989189E744500E2A2E2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(DEVELOPER_FRAMEWORKS_DIR)", + ); + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "StreamingKitMac/StreamingKitMac-Prefix.pch"; + MACOSX_DEPLOYMENT_TARGET = 10.8; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + }; + name = Release; + }; + A1A4998A189E744500E2A2E2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "StreamingKitMac/StreamingKitMac-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = "StreamingKitMacTests/StreamingKitMacTests-Info.plist"; + MACOSX_DEPLOYMENT_TARGET = 10.8; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + WRAPPER_EXTENSION = xctest; + }; + name = Debug; + }; + A1A4998B189E744500E2A2E2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "StreamingKitMac/StreamingKitMac-Prefix.pch"; + INFOPLIST_FILE = "StreamingKitMacTests/StreamingKitMacTests-Info.plist"; + MACOSX_DEPLOYMENT_TARGET = 10.8; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + WRAPPER_EXTENSION = xctest; + }; + name = Release; + }; A1E7C4E9188D57F60010896F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -438,6 +776,22 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + A1A4998C189E744500E2A2E2 /* Build configuration list for PBXNativeTarget "StreamingKitMac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A1A49988189E744500E2A2E2 /* Debug */, + A1A49989189E744500E2A2E2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; + A1A4998D189E744500E2A2E2 /* Build configuration list for PBXNativeTarget "StreamingKitMacTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A1A4998A189E744500E2A2E2 /* Debug */, + A1A4998B189E744500E2A2E2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; A1E7C4C3188D57F50010896F /* Build configuration list for PBXProject "StreamingKit" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/StreamingKit/StreamingKit/NSMutableArray+STKAudioPlayer.h b/StreamingKit/StreamingKit/NSMutableArray+STKAudioPlayer.h new file mode 100644 index 0000000..12ca839 --- /dev/null +++ b/StreamingKit/StreamingKit/NSMutableArray+STKAudioPlayer.h @@ -0,0 +1,17 @@ +// +// NSMutableArray+STKAudioPlayer.h +// StreamingKit +// +// Created by Thong Nguyen on 30/01/2014. +// Copyright (c) 2014 Thong Nguyen. All rights reserved. +// + +#import + +@interface NSMutableArray (STKAudioPlayer) +-(void) enqueue:(id)obj; +-(void) skipQueue:(id)obj; +-(void) skipQueueWithQueue:(NSMutableArray*)queue; +-(id) dequeue; +-(id) peek; +@end diff --git a/StreamingKit/StreamingKit/NSMutableArray+STKAudioPlayer.m b/StreamingKit/StreamingKit/NSMutableArray+STKAudioPlayer.m new file mode 100644 index 0000000..493868f --- /dev/null +++ b/StreamingKit/StreamingKit/NSMutableArray+STKAudioPlayer.m @@ -0,0 +1,50 @@ +// +// NSMutableArray+STKAudioPlayer.m +// StreamingKit +// +// Created by Thong Nguyen on 30/01/2014. +// Copyright (c) 2014 Thong Nguyen. All rights reserved. +// + +#import "NSMutableArray+STKAudioPlayer.h" + +@implementation NSMutableArray (STKAudioPlayer) + +-(void) enqueue:(id)obj +{ + [self insertObject:obj atIndex:0]; +} + +-(void) skipQueue:(id)obj +{ + [self addObject:obj]; +} + +-(void) skipQueueWithQueue:(NSMutableArray*)queue +{ + for (id item in queue) + { + [self addObject:item]; + } +} + +-(id) dequeue +{ + if ([self count] == 0) + { + return nil; + } + + id retval = [self lastObject]; + + [self removeLastObject]; + + return retval; +} + +-(id) peek +{ + return [self lastObject]; +} + +@end diff --git a/StreamingKit/StreamingKit/STKAudioPlayer.h b/StreamingKit/StreamingKit/STKAudioPlayer.h index 43761b5..d42e401 100644 --- a/StreamingKit/StreamingKit/STKAudioPlayer.h +++ b/StreamingKit/StreamingKit/STKAudioPlayer.h @@ -2,12 +2,12 @@ AudioPlayer.m Created by Thong Nguyen on 14/05/2012. - https://github.com/tumtumtum/audjustable + https://github.com/tumtumtum/StreamingKit Inspired by Matt Gallagher's AudioStreamer: https://github.com/mattgallagher/AudioStreamer - Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved. + Copyright (c) 2012-2014 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: @@ -44,31 +44,12 @@ #include "UIKit/UIApplication.h" #endif -typedef enum -{ - STKAudioPlayerInternalStateInitialised = 0, - STKAudioPlayerInternalStateRunning = 1, - STKAudioPlayerInternalStatePlaying = (1 << 1) | STKAudioPlayerInternalStateRunning, - STKAudioPlayerInternalStateRebuffering = (1 << 2) | STKAudioPlayerInternalStateRunning, - STKAudioPlayerInternalStateStartingThread = (1 << 3) | STKAudioPlayerInternalStateRunning, - STKAudioPlayerInternalStateWaitingForData = (1 << 4) | STKAudioPlayerInternalStateRunning, - /* Same as STKAudioPlayerInternalStateWaitingForData but isn't immediately raised as a buffering event */ - STKAudioPlayerInternalStateWaitingForDataAfterSeek = (1 << 5) | STKAudioPlayerInternalStateRunning, - STKAudioPlayerInternalStatePaused = (1 << 6) | STKAudioPlayerInternalStateRunning, - STKAudioPlayerInternalStateFlushingAndStoppingButStillPlaying = (1 << 7) | STKAudioPlayerInternalStateRunning, - STKAudioPlayerInternalStateStopping = (1 << 8), - STKAudioPlayerInternalStateStopped = (1 << 9), - STKAudioPlayerInternalStateDisposed = (1 << 10), - STKAudioPlayerInternalStateError = (1 << 31) -} -STKAudioPlayerInternalState; - typedef enum { STKAudioPlayerStateReady, STKAudioPlayerStateRunning = 1, STKAudioPlayerStatePlaying = (1 << 1) | STKAudioPlayerStateRunning, - STKAudioPlayerStateBuffering = (1 << 2) | STKAudioPlayerStatePlaying, + STKAudioPlayerStateBuffering = (1 << 2) | STKAudioPlayerStateRunning, STKAudioPlayerStatePaused = (1 << 3) | STKAudioPlayerStateRunning, STKAudioPlayerStateStopped = (1 << 4), STKAudioPlayerStateError = (1 << 5), @@ -78,10 +59,10 @@ STKAudioPlayerState; typedef enum { - AudioPlayerStopReasonNoStop = 0, - AudioPlayerStopReasonEof, - AudioPlayerStopReasonUserAction, - AudioPlayerStopReasonUserActionFlushStop + STKAudioPlayerStopReasonNone = 0, + STKAudioPlayerStopReasonEof, + STKAudioPlayerStopReasonUserAction, + STKAudioPlayerStopReasonError = 0xffff } STKAudioPlayerStopReason; @@ -90,61 +71,143 @@ typedef enum STKAudioPlayerErrorNone = 0, STKAudioPlayerErrorDataSource, STKAudioPlayerErrorStreamParseBytesFailed, + STKAudioPlayerErrorAudioSystemError, + STKAudioPlayerErrorCodecError, STKAudioPlayerErrorDataNotFound, - STKAudioPlayerErrorQueueStartFailed, - STKAudioPlayerErrorQueuePauseFailed, - STKAudioPlayerErrorUnknownBuffer, - STKAudioPlayerErrorQueueStopFailed, - STKAudioPlayerErrorQueueCreationFailed, - STKAudioPlayerErrorOther = -1 + STKAudioPlayerErrorOther = 0xffff } STKAudioPlayerErrorCode; +typedef enum +{ + STKAudioPlayerOptionNone = 0, + STKAudioPlayerOptionFlushQueueOnSeek = 1 +} +STKAudioPlayerOptions; + @class STKAudioPlayer; @protocol STKAudioPlayerDelegate --(void) audioPlayer:(STKAudioPlayer*)audioPlayer stateChanged:(STKAudioPlayerState)state; --(void) audioPlayer:(STKAudioPlayer*)audioPlayer didEncounterError:(STKAudioPlayerErrorCode)errorCode; +/// Raised when an item has started playing -(void) audioPlayer:(STKAudioPlayer*)audioPlayer didStartPlayingQueueItemId:(NSObject*)queueItemId; +/// Raised when an item has finished buffering (may or may not be the currently playing item) +/// This event may be raised multiple times for the same item if seek is invoked on the player -(void) audioPlayer:(STKAudioPlayer*)audioPlayer didFinishBufferingSourceWithQueueItemId:(NSObject*)queueItemId; +/// Raised when the state of the player has changed +-(void) audioPlayer:(STKAudioPlayer*)audioPlayer stateChanged:(STKAudioPlayerState)state previousState:(STKAudioPlayerState)previousState; +/// Raised when an item has finished playing -(void) audioPlayer:(STKAudioPlayer*)audioPlayer didFinishPlayingQueueItemId:(NSObject*)queueItemId withReason:(STKAudioPlayerStopReason)stopReason andProgress:(double)progress andDuration:(double)duration; +/// Raised when an unexpected and possibly unrecoverable error has occured (usually best to recreate the STKAudioPlauyer) +-(void) audioPlayer:(STKAudioPlayer*)audioPlayer unexpectedError:(STKAudioPlayerErrorCode)errorCode; @optional +/// Optionally implemented to get logging information from the STKAudioPlayer (used internally for debugging) -(void) audioPlayer:(STKAudioPlayer*)audioPlayer logInfo:(NSString*)line; --(void) audioPlayer:(STKAudioPlayer*)audioPlayer internalStateChanged:(STKAudioPlayerInternalState)state; +/// Raised when items queued items are cleared (usually because of a call to play, setDataSource or stop) -(void) audioPlayer:(STKAudioPlayer*)audioPlayer didCancelQueuedItems:(NSArray*)queuedItems; @end +typedef void(^STKFrameFilter)(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UInt32 frameCount, void* frames); + @interface STKAudioPlayer : NSObject +/// Gets or sets the player muted state +@property (readwrite) BOOL muted; +/// Gets the current item duration in seconds @property (readonly) double duration; +/// Gets the current item progress in seconds @property (readonly) double progress; -@property (readwrite) STKAudioPlayerState state; -@property (readonly) STKAudioPlayerStopReason stopReason; -@property (readwrite, unsafe_unretained) id delegate; +/// Enables or disables peak and average decibel meteting @property (readwrite) BOOL meteringEnabled; +/// Returns an array of STKFrameFilterEntry objects representing the filters currently in use +@property (readonly) NSArray* frameFilters; +/// Returns the items pending to be played (includes buffering and upcoming items but does not include the current item) +@property (readonly) NSArray* pendingQueue; +/// The number of items pending to be played (includes buffering and upcoming items but does not include the current item) +@property (readonly) NSUInteger pendingQueueCount; +/// Gets the most recently queued item that is still pending to play +@property (readonly) NSObject* mostRecentlyQueuedStillPendingItem; +/// Gets the current state of the player +@property (readwrite) STKAudioPlayerState state; +/// Gets the options provided to the player on startup +@property (readonly) STKAudioPlayerOptions options; +/// Gets the reason why the player is stopped (if any) +@property (readonly) STKAudioPlayerStopReason stopReason; +/// Gets and sets the delegate used for receiving events from the STKAudioPlayer +@property (readwrite, unsafe_unretained) id delegate; +/// Creates a datasource from a given URL. +/// URLs with FILE schemes will return an STKLocalFileDataSource. +/// URLs with HTTP schemes will return an STKHTTPDataSource wrapped within an STKAutoRecoveringHTTPDataSource. +/// URLs with unrecognised schemes will return nil. ++(STKDataSource*) dataSourceFromURL:(NSURL*)url; + +/// Initializes a new STKAudioPlayer with the default options -(id) init; --(id) initWithNumberOfAudioQueueBuffers:(int)numberOfAudioQueueBuffers andReadBufferSize:(int)readBufferSizeIn; --(STKDataSource*) dataSourceFromURL:(NSURL*)url; + +/// Initializes a new STKAudioPlayer with the given options +-(id) initWithOptions:(STKAudioPlayerOptions)optionsIn; + +/// Plays an item from the given URL (all pending queued items are removed) -(void) play:(NSString*)urlString; + +/// Plays an item from the given URL (all pending queued items are removed) -(void) playWithURL:(NSURL*)url; + +/// Plays the given item (all pending queued items are removed) -(void) playWithDataSource:(STKDataSource*)dataSource; + +/// Queues a DataSource with te given Item ID for playback -(void) queueDataSource:(STKDataSource*)dataSource withQueueItemId:(NSObject*)queueItemId; + +/// Plays the given item (all pending queued items are removed) -(void) setDataSource:(STKDataSource*)dataSourceIn withQueueItemId:(NSObject*)queueItemId; + +/// Seeks to a specific time (in seconds) -(void) seekToTime:(double)value; + +/// Clears any upcoming items already queued for playback (does not stop the current item). +/// The didCancelItems event will be raised for the items removed from the queue. -(void) clearQueue; + +/// Pauses playback -(void) pause; + +/// Resumes playback from pause -(void) resume; + +/// Stops playback of the current file, flushes all the buffers and removes any pending queued items -(void) stop; --(void) flushStop; + +/// Mutes playback -(void) mute; + +/// Unmutes playback -(void) unmute; + +/// Disposes the STKAudioPlayer and frees up all resources before returning -(void) dispose; + +/// The QueueItemId of the currently playing item -(NSObject*) currentlyPlayingQueueItemId; --(void) updateMeters; + +/// Removes a frame filter with the given name +-(void) removeFrameFilterWithName:(NSString*)name; + +/// Appends a frame filter with the given name and filter block to the end of the filter chain +-(void) appendFrameFilterWithName:(NSString*)name block:(STKFrameFilter)block; + +/// Appends a frame filter with the given name and filter block just after the filter with the given name. +/// If the given name is nil, the filter will be inserted at the beginning of the filter change +-(void) addFrameFilterWithName:(NSString*)name afterFilterWithName:(NSString*)afterFilterWithName block:(STKFrameFilter)block; + +/// Reads the peak power in decibals for the given channel (0 or 1). +/// Return values are between -60 (low) and 0 (high). -(float) peakPowerInDecibelsForChannel:(NSUInteger)channelNumber; + +/// Reads the average power in decibals for the given channel (0 or 1) +/// Return values are between -60 (low) and 0 (high). -(float) averagePowerInDecibelsForChannel:(NSUInteger)channelNumber; @end diff --git a/StreamingKit/StreamingKit/STKAudioPlayer.m b/StreamingKit/StreamingKit/STKAudioPlayer.m index c5e1100..c2eedb8 100644 --- a/StreamingKit/StreamingKit/STKAudioPlayer.m +++ b/StreamingKit/StreamingKit/STKAudioPlayer.m @@ -2,12 +2,9 @@ AudioPlayer.m Created by Thong Nguyen on 14/05/2012. - https://github.com/tumtumtum/audjustable + https://github.com/tumtumtum/StreamingKit - Inspired by Matt Gallagher's AudioStreamer: - https://github.com/mattgallagher/AudioStreamer - - Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved. + Copyright (c) 2014 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: @@ -38,343 +35,135 @@ #import "STKAudioPlayer.h" #import "AudioToolbox/AudioToolbox.h" #import "STKHTTPDataSource.h" +#import "STKAutoRecoveringHTTPDataSource.h" #import "STKLocalFileDataSource.h" +#import "STKQueueEntry.h" +#import "NSMutableArray+STKAudioPlayer.h" #import "libkern/OSAtomic.h" -#define STK_BIT_RATE_ESTIMATION_MIN_PACKETS (64) -#define STK_BUFFERS_NEEDED_TO_START (32) -#define STK_BUFFERS_NEEDED_WHEN_UNDERUNNING (128) -#define STK_DEFAULT_READ_BUFFER_SIZE (2 * 1024) -#define STK_DEFAULT_PACKET_BUFFER_SIZE (2048) -#define STK_FRAMES_MISSED_BEFORE_CONSIDERED_UNDERRUN (1024) -#define STK_DEFAULT_NUMBER_OF_AUDIOQUEUE_BUFFERS (1024) +#define kOutputBus 0 +#define kInputBus 1 -#define OSSTATUS_PARAM_ERROR (-50) +#define STK_DBMIN (-60) +#define STK_DBOFFSET (-74.0) +#define STK_LOWPASSFILTERTIMESLICE (0.0005) + +#define STK_DEFAULT_PCM_BUFFER_SIZE_IN_SECONDS (10) +#define STK_DEFAULT_SECONDS_REQUIRED_TO_START_PLAYING (0.1) +#define STK_MAX_COMPRESSED_PACKETS_FOR_BITRATE_CALCULATION (2048) +#define STK_DEFAULT_READ_BUFFER_SIZE (64 * 1024) +#define STK_DEFAULT_PACKET_BUFFER_SIZE (2048) #define LOGINFO(x) [self logInfo:[NSString stringWithFormat:@"%s %@", sel_getName(_cmd), x]]; -typedef struct +typedef enum { - AudioQueueBufferRef ref; - int bufferIndex; + STKAudioPlayerInternalStateInitialised = 0, + STKAudioPlayerInternalStateRunning = 1, + STKAudioPlayerInternalStatePlaying = (1 << 1) | STKAudioPlayerInternalStateRunning, + STKAudioPlayerInternalStateRebuffering = (1 << 2) | STKAudioPlayerInternalStateRunning, + STKAudioPlayerInternalStateStartingThread = (1 << 3) | STKAudioPlayerInternalStateRunning, + STKAudioPlayerInternalStateWaitingForData = (1 << 4) | STKAudioPlayerInternalStateRunning, + /* Same as STKAudioPlayerInternalStateWaitingForData but isn't immediately raised as a buffering event */ + STKAudioPlayerInternalStateWaitingForDataAfterSeek = (1 << 5) | STKAudioPlayerInternalStateRunning, + STKAudioPlayerInternalStatePaused = (1 << 6) | STKAudioPlayerInternalStateRunning, + STKAudioPlayerInternalStateStopped = (1 << 9), + STKAudioPlayerInternalStatePendingNext = (1 << 10), + STKAudioPlayerInternalStateDisposed = (1 << 30), + STKAudioPlayerInternalStateError = (1 << 31) } -AudioQueueBufferRefLookupEntry; +STKAudioPlayerInternalState; -@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 STKQueueEntry : NSObject +@interface STKFrameFilterEntry : NSObject { @public - BOOL parsedHeader; - double sampleRate; - double lastProgress; - double packetDuration; - UInt64 audioDataOffset; - UInt64 audioDataByteCount; - UInt32 packetBufferSize; - volatile BOOL cancel; - volatile int processedPacketsCount; - volatile int processedPacketsSizeTotal; - AudioStreamBasicDescription audioStreamBasicDescription; + const NSString* name; + STKFrameFilter filter; } -@property (readwrite, retain) NSObject* queueItemId; -@property (readwrite, retain) STKDataSource* dataSource; -@property (readwrite) Float64 seekTime; -@property (readwrite) int bytesBuffered; -@property (readwrite) int lastByteIndex; -@property (readwrite) Float64 lastFrameIndex; -@property (readwrite) Float64 timeWhenLastBufferReturned; -@property (readwrite) Float64 firstFrameIndex; -@property (readonly) UInt64 audioDataLengthInBytes; - --(double) duration; --(double) calculatedBitRate; - --(id) initWithDataSource:(STKDataSource*)dataSource andQueueItemId:(NSObject*)queueItemId; - @end -@implementation STKQueueEntry - --(id) initWithDataSource:(STKDataSource*)dataSourceIn andQueueItemId:(NSObject*)queueItemIdIn +@implementation STKFrameFilterEntry +-(id) initWithFilter:(STKFrameFilter)filterIn andName:(NSString*)nameIn { - if (self = [super init]) - { - self.dataSource = dataSourceIn; - self.queueItemId = queueItemIdIn; - self.lastFrameIndex = -1; - self.lastByteIndex = -1; - } - - return self; -} - --(double) calculatedBitRate -{ - double retval; - - if (packetDuration && processedPacketsCount > STK_BIT_RATE_ESTIMATION_MIN_PACKETS) + if (self = [super init]) { - double averagePacketByteSize = processedPacketsSizeTotal / processedPacketsCount; - - retval = averagePacketByteSize / packetDuration * 8; - - return retval; + self->filter = [filterIn copy]; + self->name = nameIn; } - retval = (audioStreamBasicDescription.mBytesPerFrame * audioStreamBasicDescription.mSampleRate) * 8; - - return retval; + return self; } - --(void) updateAudioDataSource -{ - if ([self.dataSource conformsToProtocol:@protocol(AudioDataSource)]) - { - double calculatedBitrate = [self calculatedBitRate]; - - id audioDataSource = (id)self.dataSource; - - audioDataSource.averageBitRate = calculatedBitrate; - audioDataSource.audioDataOffset = audioDataOffset; - } -} - --(Float64) calculateProgressWithTotalFramesPlayed:(Float64)framesPlayed -{ - return (Float64)self.seekTime + ((framesPlayed - self.firstFrameIndex) / (Float64)self->audioStreamBasicDescription.mSampleRate); -} - --(double) calculateProgressWithBytesPlayed:(Float64)bytesPlayed -{ - double retval = lastProgress; - - if (self->sampleRate > 0) - { - double calculatedBitrate = [self calculatedBitRate]; - - retval = bytesPlayed / calculatedBitrate * 8; - - retval = self.seekTime + retval; - - [self updateAudioDataSource]; - } - - return retval; -} - --(double) duration -{ - if (self->sampleRate <= 0) - { - return 0; - } - - UInt64 audioDataLengthInBytes = [self audioDataLengthInBytes]; - - double calculatedBitRate = [self calculatedBitRate]; - - if (calculatedBitRate < 1.0 || self.dataSource.length == 0) - { - return 0; - } - - return audioDataLengthInBytes / (calculatedBitRate / 8); -} - --(UInt64) audioDataLengthInBytes -{ - if (audioDataByteCount) - { - return audioDataByteCount; - } - else - { - if (!self.dataSource.length) - { - return 0; - } - - return self.dataSource.length - audioDataOffset; - } -} - --(BOOL) isDefinitelyCompatible:(AudioStreamBasicDescription*)basicDescription -{ - if (self->audioStreamBasicDescription.mSampleRate == 0) - { - return NO; - } - - return (memcmp(&(self->audioStreamBasicDescription), basicDescription, sizeof(*basicDescription)) == 0); -} - --(BOOL) isKnownToBeIncompatible:(AudioStreamBasicDescription*)basicDescription -{ - if (self->audioStreamBasicDescription.mSampleRate == 0) - { - return NO; - } - - return (memcmp(&(self->audioStreamBasicDescription), basicDescription, sizeof(*basicDescription)) != 0); -} - --(BOOL) couldBeIncompatible:(AudioStreamBasicDescription*)basicDescription -{ - if (self->audioStreamBasicDescription.mSampleRate == 0) - { - return YES; - } - - return memcmp(&(self->audioStreamBasicDescription), basicDescription, sizeof(*basicDescription)) != 0; -} - --(NSString*) description -{ - return [[self queueItemId] description]; -} - @end @interface STKAudioPlayer() { + BOOL muted; + UInt8* readBuffer; int readBufferSize; + + Float32 peakPowerDb[2]; + Float32 averagePowerDb[2]; - STKQueueEntry* currentlyPlayingEntry; - STKQueueEntry* currentlyReadingEntry; + BOOL meteringEnabled; + STKAudioPlayerOptions options; + AudioComponentInstance audioUnit; + + UInt32 framesRequiredToStartPlaying; + UInt32 framesRequiredToPlayAfterRebuffering; + + STKQueueEntry* volatile currentlyPlayingEntry; + STKQueueEntry* volatile currentlyReadingEntry; NSMutableArray* upcomingQueue; NSMutableArray* bufferingQueue; - bool* bufferUsed; - AudioQueueBufferRef* audioQueueBuffer; - AudioQueueBufferRefLookupEntry* audioQueueBufferLookup; - unsigned int audioQueueBufferRefLookupCount; - unsigned int audioQueueBufferCount; - AudioStreamPacketDescription* packetDescs; - UInt32 numberOfBuffersUsed; + OSSpinLock pcmBufferSpinLock; + volatile UInt32 pcmBufferTotalFrameCount; + volatile UInt32 pcmBufferFrameStartIndex; + volatile UInt32 pcmBufferUsedFrameCount; + volatile UInt32 pcmBufferFrameSizeInBytes; - AudioQueueRef audioQueue; - AudioStreamBasicDescription currentAudioStreamBasicDescription; - - NSThread* playbackThread; - NSRunLoop* playbackThreadRunLoop; - NSConditionLock* threadStartedLock; - NSConditionLock* threadFinishedCondLock; - Float64 averageHardwareDelay; - - AudioFileStreamID audioFileStream; + AudioBuffer* pcmAudioBuffer; + AudioBufferList pcmAudioBufferList; + AudioConverterRef audioConverterRef; + + AudioStreamBasicDescription canonicalAudioStreamBasicDescription; + AudioStreamBasicDescription audioConverterAudioStreamBasicDescription; BOOL discontinuous; - - int32_t bytesFilled; - int32_t packetsFilled; - int32_t framesFilled; - int32_t fillBufferIndex; - - volatile Float64 framesQueued; - volatile Float64 timelineAdjust; - volatile Float64 rebufferingStartFrames; + NSArray* frameFilters; + NSThread* playbackThread; + NSRunLoop* playbackThreadRunLoop; + AudioFileStreamID audioFileStream; + NSConditionLock* threadStartedLock; + NSConditionLock* threadFinishedCondLock; #if TARGET_OS_IPHONE UIBackgroundTaskIdentifier backgroundTaskId; #endif - STKAudioPlayerErrorCode errorCode; - STKAudioPlayerStopReason stopReason; - int32_t seekVersion; OSSpinLock seekLock; OSSpinLock currentEntryReferencesLock; pthread_mutex_t playerMutex; - pthread_mutex_t queueBuffersMutex; - pthread_cond_t queueBufferReadyCondition; + pthread_cond_t playerThreadReadyCondition; pthread_mutex_t mainThreadSyncCallMutex; pthread_cond_t mainThreadSyncCallReadyCondition; volatile BOOL waiting; + volatile double requestedSeekTime; volatile BOOL disposeWasRequested; volatile BOOL seekToTimeWasRequested; - volatile BOOL newFileToPlay; - volatile double requestedSeekTime; - volatile BOOL audioQueueFlushing; - volatile SInt64 audioPacketsReadCount; - volatile SInt64 audioPacketsPlayedCount; - - BOOL meteringEnabled; - UInt32 numberOfChannels; - AudioQueueLevelMeterState* levelMeterState; + volatile STKAudioPlayerStopReason stopReason; } @property (readwrite) STKAudioPlayerInternalState internalState; @property (readwrite) STKAudioPlayerInternalState stateBeforePaused; --(void) logInfo:(NSString*)line; --(void) createAudioQueue; --(void) enqueueBuffer; --(void) resetAudioQueueWithReason:(NSString*)reason; --(BOOL) startAudioQueue; --(void) stopAudioQueueWithReason:(NSString*)reason; --(BOOL) processRunloop; --(void) wakeupPlaybackThread; --(void) audioQueueFinishedPlaying:(STKQueueEntry*)entry; --(void) processSeekToTime; --(void) didEncounterError:(STKAudioPlayerErrorCode)errorCode; --(void) setInternalState:(STKAudioPlayerInternalState)value; --(void) processFinishPlayingIfAnyAndPlayingNext:(STKQueueEntry*)entry withNext:(STKQueueEntry*)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) @@ -391,23 +180,15 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn [player handleAudioPackets:inputData numberBytes:numberBytes numberPackets:numberPackets packetDescriptions:packetDescriptions]; } -static void AudioQueueOutputCallbackProc(void* clientData, AudioQueueRef audioQueue, AudioQueueBufferRef buffer) -{ - STKAudioPlayer* player = (__bridge STKAudioPlayer*)clientData; - - [player handleAudioQueueOutput:audioQueue buffer:buffer]; -} - -static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQueue, AudioQueuePropertyID propertyId) -{ - STKAudioPlayer* player = (__bridge STKAudioPlayer*)userData; - - [player handlePropertyChangeForQueue:audioQueue propertyID:propertyId]; -} - @implementation STKAudioPlayer @synthesize delegate, internalState, state; + +-(STKAudioPlayerOptions) options +{ + return options; +} + -(STKAudioPlayerInternalState) internalState { return internalState; @@ -422,54 +203,53 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ internalState = value; - if ([self.delegate respondsToSelector:@selector(audioPlayer:internalStateChanged:)]) - { - dispatch_async(dispatch_get_main_queue(), ^ - { - [self.delegate audioPlayer:self internalStateChanged:internalState]; - }); - } - STKAudioPlayerState newState; switch (internalState) { case STKAudioPlayerInternalStateInitialised: newState = STKAudioPlayerStateReady; + stopReason = STKAudioPlayerStopReasonNone; break; case STKAudioPlayerInternalStateRunning: + case STKAudioPlayerInternalStatePendingNext: case STKAudioPlayerInternalStateStartingThread: case STKAudioPlayerInternalStatePlaying: case STKAudioPlayerInternalStateWaitingForDataAfterSeek: - case STKAudioPlayerInternalStateFlushingAndStoppingButStillPlaying: newState = STKAudioPlayerStatePlaying; + stopReason = STKAudioPlayerStopReasonNone; break; case STKAudioPlayerInternalStateRebuffering: case STKAudioPlayerInternalStateWaitingForData: newState = STKAudioPlayerStateBuffering; + stopReason = STKAudioPlayerStopReasonNone; break; - case STKAudioPlayerInternalStateStopping: case STKAudioPlayerInternalStateStopped: newState = STKAudioPlayerStateStopped; break; case STKAudioPlayerInternalStatePaused: newState = STKAudioPlayerStatePaused; + stopReason = STKAudioPlayerStopReasonNone; break; case STKAudioPlayerInternalStateDisposed: newState = STKAudioPlayerStateDisposed; + stopReason = STKAudioPlayerStopReasonUserAction; break; case STKAudioPlayerInternalStateError: newState = STKAudioPlayerStateError; + stopReason = STKAudioPlayerStopReasonError; break; } - if (newState != self.state) + STKAudioPlayerState previousState = self.state; + + if (newState != previousState) { self.state = newState; dispatch_async(dispatch_get_main_queue(), ^ { - [self.delegate audioPlayer:self stateChanged:self.state]; + [self.delegate audioPlayer:self stateChanged:self.state previousState:previousState]; }); } } @@ -479,16 +259,6 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ return stopReason; } --(BOOL) audioQueueIsRunning -{ - UInt32 isRunning; - UInt32 isRunningSize = sizeof(isRunning); - - AudioQueueGetProperty(audioQueue, kAudioQueueProperty_IsRunning, &isRunning, &isRunningSize); - - return isRunning ? YES : NO; -} - -(void) logInfo:(NSString*)line { if ([NSThread currentThread].isMainThread) @@ -509,37 +279,57 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ -(id) init { - return [self initWithNumberOfAudioQueueBuffers:STK_DEFAULT_NUMBER_OF_AUDIOQUEUE_BUFFERS andReadBufferSize:STK_DEFAULT_READ_BUFFER_SIZE]; + return [self initWithReadBufferSize:STK_DEFAULT_READ_BUFFER_SIZE andOptions:STKAudioPlayerOptionNone]; } --(id) initWithNumberOfAudioQueueBuffers:(int)numberOfAudioQueueBuffers andReadBufferSize:(int)readBufferSizeIn +-(id) initWithOptions:(STKAudioPlayerOptions)optionsIn +{ + return [self initWithReadBufferSize:STK_DEFAULT_READ_BUFFER_SIZE andOptions:optionsIn]; +} + +-(id) initWithReadBufferSize:(int)readBufferSizeIn andOptions:(STKAudioPlayerOptions)optionsIn { if (self = [super init]) { + options = optionsIn; + + const int bytesPerSample = sizeof(AudioSampleType); + + canonicalAudioStreamBasicDescription.mSampleRate = 44100.00; + canonicalAudioStreamBasicDescription.mFormatID = kAudioFormatLinearPCM; + canonicalAudioStreamBasicDescription.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; + canonicalAudioStreamBasicDescription.mFramesPerPacket = 1; + canonicalAudioStreamBasicDescription.mChannelsPerFrame = 2; + canonicalAudioStreamBasicDescription.mBytesPerFrame = bytesPerSample * canonicalAudioStreamBasicDescription.mChannelsPerFrame; + canonicalAudioStreamBasicDescription.mBitsPerChannel = 8 * bytesPerSample; + canonicalAudioStreamBasicDescription.mBytesPerPacket = canonicalAudioStreamBasicDescription.mBytesPerFrame * canonicalAudioStreamBasicDescription.mFramesPerPacket; + + framesRequiredToStartPlaying = canonicalAudioStreamBasicDescription.mSampleRate * STK_DEFAULT_SECONDS_REQUIRED_TO_START_PLAYING; + framesRequiredToPlayAfterRebuffering = canonicalAudioStreamBasicDescription.mSampleRate * STK_DEFAULT_PCM_BUFFER_SIZE_IN_SECONDS; + + pcmAudioBuffer = &pcmAudioBufferList.mBuffers[0]; + + pcmAudioBufferList.mNumberBuffers = 1; + pcmAudioBufferList.mBuffers[0].mDataByteSize = (canonicalAudioStreamBasicDescription.mSampleRate * STK_DEFAULT_PCM_BUFFER_SIZE_IN_SECONDS) * canonicalAudioStreamBasicDescription.mBytesPerFrame; + pcmAudioBufferList.mBuffers[0].mData = (void*)calloc(pcmAudioBuffer->mDataByteSize, 1); + pcmAudioBufferList.mBuffers[0].mNumberChannels = 2; + + pcmBufferFrameSizeInBytes = canonicalAudioStreamBasicDescription.mBytesPerFrame; + pcmBufferTotalFrameCount = pcmAudioBuffer->mDataByteSize / pcmBufferFrameSizeInBytes; + 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_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&playerMutex, &attr); - pthread_mutex_init(&queueBuffersMutex, NULL); - pthread_cond_init(&queueBufferReadyCondition, NULL); - pthread_mutex_init(&mainThreadSyncCallMutex, NULL); + pthread_cond_init(&playerThreadReadyCondition, NULL); pthread_cond_init(&mainThreadSyncCallReadyCondition, NULL); - + threadStartedLock = [[NSConditionLock alloc] initWithCondition:0]; threadFinishedCondLock = [[NSConditionLock alloc] initWithCondition:0]; @@ -547,7 +337,9 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ upcomingQueue = [[NSMutableArray alloc] init]; bufferingQueue = [[NSMutableArray alloc] init]; - + + [self resetPcmBuffers]; + [self createAudioUnit]; [self createPlaybackThread]; } @@ -559,37 +351,38 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ if (currentlyReadingEntry) { currentlyReadingEntry.dataSource.delegate = nil; + [currentlyReadingEntry.dataSource unregisterForEvents]; } if (currentlyPlayingEntry) { currentlyPlayingEntry.dataSource.delegate = nil; + [currentlyReadingEntry.dataSource unregisterForEvents]; } - pthread_mutex_destroy(&playerMutex); - pthread_mutex_destroy(&queueBuffersMutex); - pthread_cond_destroy(&queueBufferReadyCondition); + [self stopAudioUnitWithReason:STKAudioPlayerStopReasonEof]; - pthread_mutex_destroy(&mainThreadSyncCallMutex); - pthread_cond_destroy(&mainThreadSyncCallReadyCondition); - - if (audioFileStream) { AudioFileStreamClose(audioFileStream); } - if (audioQueue) + if (audioConverterRef) { - AudioQueueDispose(audioQueue, true); + AudioConverterDispose(audioConverterRef); } - free(bufferUsed); + if (audioUnit) + { + AudioComponentInstanceDispose(audioUnit); + } + + pthread_mutex_destroy(&playerMutex); + pthread_mutex_destroy(&mainThreadSyncCallMutex); + pthread_cond_destroy(&playerThreadReadyCondition); + pthread_cond_destroy(&mainThreadSyncCallReadyCondition); + free(readBuffer); - free(packetDescs); - free(audioQueueBuffer); - free(audioQueueBufferLookup); - free(levelMeterState); } -(void) startSystemBackgroundTask @@ -629,74 +422,59 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ #endif } --(STKDataSource*) dataSourceFromURL:(NSURL*)url ++(STKDataSource*) dataSourceFromURL:(NSURL*)url { - STKDataSource* retval; + STKDataSource* retval = nil; if ([url.scheme isEqualToString:@"file"]) { retval = [[STKLocalFileDataSource alloc] initWithFilePath:url.path]; } - else + else if ([url.scheme caseInsensitiveCompare:@"http"] == NSOrderedSame || [url.scheme caseInsensitiveCompare:@"https"] == NSOrderedSame) { - retval = [[STKHTTPDataSource alloc] initWithURL:url]; + retval = [[STKAutoRecoveringHTTPDataSource alloc] initWithHTTPDataSource:[[STKHTTPDataSource alloc] initWithURL:url]]; } return retval; } -(void) clearQueue -{ - [self clearQueueIncludingUpcoming:YES]; -} - --(void) clearQueueIncludingUpcoming:(BOOL)includeUpcoming { pthread_mutex_lock(&playerMutex); { - pthread_mutex_lock(&queueBuffersMutex); - - NSMutableArray* array = [[NSMutableArray alloc] initWithCapacity:bufferingQueue.count + (includeUpcoming ? upcomingQueue.count : 0)]; - - STKQueueEntry* entry = [bufferingQueue dequeue]; - - if (entry && entry != currentlyPlayingEntry) + if ([self.delegate respondsToSelector:@selector(audioPlayer:didCancelQueuedItems:)]) { - [array addObject:[entry queueItemId]]; - } - - while (bufferingQueue.count > 0) - { - id queueItemId = [[bufferingQueue dequeue] queueItemId]; + NSMutableArray* array = [[NSMutableArray alloc] initWithCapacity:bufferingQueue.count + upcomingQueue.count]; - if (queueItemId != nil) - { - [array addObject:queueItemId]; - } - } - - if (includeUpcoming) - { for (STKQueueEntry* entry in upcomingQueue) { [array addObject:entry.queueItemId]; } + + for (STKQueueEntry* entry in bufferingQueue) + { + [array addObject:entry.queueItemId]; + } [upcomingQueue removeAllObjects]; - } - - if (array.count > 0) - { - [self playbackThreadQueueMainThreadSyncBlock:^ + [bufferingQueue removeAllObjects]; + + if (array.count > 0) { - if ([self.delegate respondsToSelector:@selector(audioPlayer:didCancelQueuedItems:)]) + [self playbackThreadQueueMainThreadSyncBlock:^ { - [self.delegate audioPlayer:self didCancelQueuedItems:array]; - } - }]; + if ([self.delegate respondsToSelector:@selector(audioPlayer:didCancelQueuedItems:)]) + { + [self.delegate audioPlayer:self didCancelQueuedItems:array]; + } + }]; + } + } + else + { + [bufferingQueue removeAllObjects]; + [upcomingQueue removeAllObjects]; } - - pthread_mutex_unlock(&queueBuffersMutex); } pthread_mutex_unlock(&playerMutex); } @@ -705,12 +483,12 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ { NSURL* url = [NSURL URLWithString:urlString]; - [self setDataSource:[self dataSourceFromURL:url] withQueueItemId:urlString]; + [self setDataSource:[STKAudioPlayer dataSourceFromURL:url] withQueueItemId:urlString]; } -(void) playWithURL:(NSURL*)url { - [self setDataSource:[self dataSourceFromURL:url] withQueueItemId:url]; + [self setDataSource:[STKAudioPlayer dataSourceFromURL:url] withQueueItemId:url]; } -(void) playWithDataSource:(STKDataSource*)dataSource @@ -720,43 +498,32 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ -(void) setDataSource:(STKDataSource*)dataSourceIn withQueueItemId:(NSObject*)queueItemId { - [self invokeOnPlaybackThread:^ + pthread_mutex_lock(&playerMutex); { - pthread_mutex_lock(&playerMutex); - { - LOGINFO(([NSString stringWithFormat:@"Playing: %@", [queueItemId description]])); - - [self startSystemBackgroundTask]; + LOGINFO(([NSString stringWithFormat:@"Playing: %@", [queueItemId description]])); + + [self startSystemBackgroundTask]; + + [self clearQueue]; - [self resetAudioQueueWithReason:@"from skipCurrent"]; - [self clearQueue]; - - pthread_mutex_lock(&queueBuffersMutex); - - [upcomingQueue enqueue:[[STKQueueEntry alloc] initWithDataSource:dataSourceIn andQueueItemId:queueItemId]]; - - pthread_mutex_unlock(&queueBuffersMutex); - - self.internalState = STKAudioPlayerInternalStateRunning; - - newFileToPlay = YES; - } - pthread_mutex_unlock(&playerMutex); - }]; + [upcomingQueue enqueue:[[STKQueueEntry alloc] initWithDataSource:dataSourceIn andQueueItemId:queueItemId]]; + + self.internalState = STKAudioPlayerInternalStatePendingNext; + } + pthread_mutex_unlock(&playerMutex); [self wakeupPlaybackThread]; } -(void) queueDataSource:(STKDataSource*)dataSourceIn withQueueItemId:(NSObject*)queueItemId { - [self invokeOnPlaybackThread:^ + pthread_mutex_lock(&playerMutex); { - pthread_mutex_lock(&playerMutex); - { - [upcomingQueue enqueue:[[STKQueueEntry alloc] initWithDataSource:dataSourceIn andQueueItemId:queueItemId]]; - } - pthread_mutex_unlock(&playerMutex); - }]; + [self startSystemBackgroundTask]; + + [upcomingQueue enqueue:[[STKQueueEntry alloc] initWithDataSource:dataSourceIn andQueueItemId:queueItemId]]; + } + pthread_mutex_unlock(&playerMutex); [self wakeupPlaybackThread]; } @@ -800,53 +567,13 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ AudioStreamBasicDescription newBasicDescription; STKQueueEntry* entryToUpdate = currentlyReadingEntry; - if (currentlyReadingEntry->audioStreamBasicDescription.mSampleRate == 0) + if (!currentlyReadingEntry->parsedHeader) { UInt32 size = sizeof(newBasicDescription); AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataFormat, &size, &newBasicDescription); pthread_mutex_lock(&playerMutex); - pthread_mutex_lock(&queueBuffersMutex); - - BOOL cancel = NO; - - AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, 1); - - if (currentlyReadingEntry->cancel) - { - cancel = YES; - } - else - { - if (currentlyReadingEntry != currentlyPlayingEntry && audioQueue && currentAudioStreamBasicDescription.mSampleRate != 0) - { - if (memcmp(¤tAudioStreamBasicDescription, &newBasicDescription, sizeof(currentAudioStreamBasicDescription)) != 0) - { - cancel = YES; - } - } - } - - if (cancel) - { - [currentlyReadingEntry.dataSource unregisterForEvents]; - - if ([bufferingQueue objectAtIndex:0] == currentlyReadingEntry) - { - [bufferingQueue removeObjectAtIndex:0]; - } - - STKQueueEntry* newEntry = [[STKQueueEntry alloc] initWithDataSource:currentlyReadingEntry.dataSource andQueueItemId:currentlyReadingEntry.queueItemId]; - - entryToUpdate = newEntry; - - [upcomingQueue skipQueue:newEntry]; - - OSSpinLockLock(¤tEntryReferencesLock); - currentlyReadingEntry = nil; - OSSpinLockUnlock(¤tEntryReferencesLock); - } entryToUpdate->audioStreamBasicDescription = newBasicDescription; entryToUpdate->sampleRate = entryToUpdate->audioStreamBasicDescription.mSampleRate; @@ -877,7 +604,8 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ [entryToUpdate updateAudioDataSource]; - pthread_mutex_unlock(&queueBuffersMutex); + [self createAudioConverter:¤tlyReadingEntry->audioStreamBasicDescription]; + pthread_mutex_unlock(&playerMutex); } @@ -898,7 +626,10 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ } case kAudioFileStreamProperty_ReadyToProducePackets: { - discontinuous = YES; + if (!audioConverterAudioStreamBasicDescription.mFormatID == kAudioFormatLinearPCM) + { + discontinuous = YES; + } break; } @@ -948,944 +679,29 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ } } --(void) handleAudioPackets:(const void*)inputData numberBytes:(UInt32)numberBytes numberPackets:(UInt32)numberPackets packetDescriptions:(AudioStreamPacketDescription*)packetDescriptionsIn -{ - if (currentlyReadingEntry == nil) - { - return; - } - - if (seekToTimeWasRequested || disposeWasRequested) - { - return; - } - - if (audioQueue == nil) - { - if (currentlyPlayingEntry == nil) - { - return; - } - - [self createAudioQueue]; - - if (audioQueue == nil) - { - return; - } - - if (self.internalState == STKAudioPlayerInternalStateStopped) - { - if (stopReason == AudioPlayerStopReasonEof) - { - stopReason = AudioPlayerStopReasonNoStop; - self.internalState = STKAudioPlayerInternalStateWaitingForData; - } - else - { - return; - } - } - } - else if (memcmp(¤tAudioStreamBasicDescription, ¤tlyReadingEntry->audioStreamBasicDescription, sizeof(currentAudioStreamBasicDescription)) != 0) - { - if (currentlyReadingEntry == currentlyPlayingEntry && currentlyReadingEntry != nil) - { - [self createAudioQueue]; - - if (audioQueue == nil) - { - return; - } - } - else - { - return; - } - } - - 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; - - int framesPerPacket; - - if (packetDescriptionsIn[i].mVariableFramesInPacket > 0) - { - framesPerPacket = packetDescriptionsIn[i].mVariableFramesInPacket; - } - else - { - framesPerPacket = currentlyReadingEntry->audioStreamBasicDescription.mFramesPerPacket; - } - - if (currentlyReadingEntry->processedPacketsCount * framesPerPacket < currentlyReadingEntry->audioStreamBasicDescription.mSampleRate * 10 * currentlyReadingEntry->audioStreamBasicDescription.mChannelsPerFrame) - { - OSAtomicAdd32((int32_t)packetSize, ¤tlyReadingEntry->processedPacketsSizeTotal); - OSAtomicIncrement32(¤tlyReadingEntry->processedPacketsCount); - } - - if (packetSize > currentlyReadingEntry->packetBufferSize) - { - return; - } - - bufSpaceRemaining = currentlyReadingEntry->packetBufferSize - bytesFilled; - - if (bufSpaceRemaining < packetSize) - { - [self enqueueBuffer]; - - if (audioQueue == nil || disposeWasRequested || seekToTimeWasRequested || self.internalState == STKAudioPlayerInternalStateStopped || self.internalState == STKAudioPlayerInternalStateStopping || self.internalState == STKAudioPlayerInternalStateDisposed) - { - return; - } - } - - if (bytesFilled + packetSize > currentlyReadingEntry->packetBufferSize) - { - return; - } - - AudioQueueBufferRef bufferToFill = audioQueueBuffer[fillBufferIndex]; - memcpy((char*)bufferToFill->mAudioData + bytesFilled, (const char*)inputData + packetOffset, (unsigned long)packetSize); - - framesFilled += framesPerPacket; - - packetDescs[packetsFilled] = packetDescriptionsIn[i]; - packetDescs[packetsFilled].mStartOffset = bytesFilled; - - bytesFilled += packetSize; - packetsFilled++; - - int packetsDescRemaining = audioQueueBufferCount - packetsFilled; - - if (packetsDescRemaining <= 0) - { - [self enqueueBuffer]; - - if (audioQueue == nil || disposeWasRequested || seekToTimeWasRequested || self.internalState == STKAudioPlayerInternalStateStopped || self.internalState == STKAudioPlayerInternalStateStopping || self.internalState == STKAudioPlayerInternalStateDisposed) - { - return; - } - } - } - } - else - { - // CBR - - int offset = 0; - - while (numberBytes) - { - int bytesLeft = currentlyReadingEntry->packetBufferSize - bytesFilled; - - if (bytesLeft < numberBytes) - { - [self enqueueBuffer]; - - if (audioQueue == nil || disposeWasRequested || seekToTimeWasRequested || self.internalState == STKAudioPlayerInternalStateStopped || self.internalState == STKAudioPlayerInternalStateStopping || self.internalState == STKAudioPlayerInternalStateDisposed) - { - return; - } - } - - int copySize; - bytesLeft = currentlyReadingEntry->packetBufferSize - bytesFilled; - - if (bytesLeft < numberBytes) - { - copySize = bytesLeft; - } - else - { - copySize = numberBytes; - } - - if (bytesFilled > currentlyPlayingEntry->packetBufferSize) - { - pthread_mutex_unlock(&playerMutex); - - return; - } - - AudioQueueBufferRef fillBuf = audioQueueBuffer[fillBufferIndex]; - memcpy((char*)fillBuf->mAudioData + bytesFilled, (const char*)(inputData + offset), copySize); - - bytesFilled += copySize; - packetsFilled = 0; - numberBytes -= copySize; - offset += copySize; - } - } -} - - --(BOOL) moreFramesAreDefinitelyAvailableToPlay -{ - return [self moreFramesAreDefinitelyAvailableToPlayIgnoreExisting:NO]; -} - --(BOOL) moreFramesAreDefinitelyAvailableToPlayIgnoreExisting:(BOOL)ignoreExisting -{ - if (numberOfBuffersUsed > 0 && !ignoreExisting) - { - return YES; - } - - if (currentlyReadingEntry == currentlyPlayingEntry) - { - if (currentlyReadingEntry.dataSource.position == currentlyReadingEntry.dataSource.length) - { - return NO; - } - else - { - return YES; - } - } - - if (bufferingQueue.count > 1) - { - return YES; - } - - if (bufferingQueue.count == 1 && [((STKQueueEntry*)[bufferingQueue peek]) couldBeIncompatible:¤tAudioStreamBasicDescription]) - { - return NO; - } - - if (upcomingQueue.count > 0) - { - if ([((STKQueueEntry*)[upcomingQueue peek]) isDefinitelyCompatible:¤tAudioStreamBasicDescription]) - { - return YES; - } - } - - return NO; -} - --(void) makeSureIncompatibleNextBufferingIsCancelled -{ - if (bufferingQueue.count == 1) - { - STKQueueEntry* nextBuffering = [bufferingQueue peek]; - - if ([nextBuffering couldBeIncompatible:¤tAudioStreamBasicDescription]) - { - nextBuffering->cancel = YES; - } - } -} - --(void) handleAudioQueueOutput:(AudioQueueRef)audioQueueIn buffer:(AudioQueueBufferRef)bufferIn -{ - int bufferIndex = -1; - - if (audioQueueIn != audioQueue) - { - return; - } - - STKQueueEntry* entry = nil; - - if (currentlyPlayingEntry) - { - OSSpinLockLock(¤tEntryReferencesLock); - { - if (currentlyPlayingEntry) - { - entry = currentlyPlayingEntry; - - if (!audioQueueFlushing) - { - entry.bytesBuffered += bufferIn->mAudioDataByteSize; - } - } - } - OSSpinLockUnlock(¤tEntryReferencesLock); - } - - int index = (int)bufferIn % audioQueueBufferRefLookupCount; - - for (int i = 0; i < audioQueueBufferCount; i++) - { - if (audioQueueBufferLookup[index].ref == bufferIn) - { - bufferIndex = audioQueueBufferLookup[index].bufferIndex; - - break; - } - - index = (index + 1) % audioQueueBufferRefLookupCount; - } - - audioPacketsPlayedCount++; - - if (bufferIndex == -1) - { - [self playbackThreadQueueMainThreadSyncBlock:^ - { - [self didEncounterError:STKAudioPlayerErrorUnknownBuffer]; - }]; - - pthread_mutex_lock(&queueBuffersMutex); - pthread_cond_signal(&queueBufferReadyCondition); - pthread_mutex_unlock(&queueBuffersMutex); - - return; - } - - pthread_mutex_lock(&queueBuffersMutex); - - BOOL signal = NO; - - if (bufferUsed[bufferIndex]) - { - bufferUsed[bufferIndex] = false; - numberOfBuffersUsed--; - } - else - { - // This should never happen - - signal = YES; - } - - Float64 currentTime = [self currentTimeInFrames]; - - if (entry != nil && !seekToTimeWasRequested) - { - if (entry.lastByteIndex == audioPacketsPlayedCount || ![self moreFramesAreDefinitelyAvailableToPlay]) - { - LOGINFO(@"Final AudioBuffer returned"); - - entry.timeWhenLastBufferReturned = currentTime; - - if (entry.lastFrameIndex < currentTime && entry.lastByteIndex == audioPacketsPlayedCount) - { - LOGINFO(@"Timeline drift"); - - entry.lastFrameIndex = currentTime + 1; - } - - if (![self moreFramesAreDefinitelyAvailableToPlay]) - { - [self makeSureIncompatibleNextBufferingIsCancelled]; - - if (self.internalState != STKAudioPlayerInternalStateFlushingAndStoppingButStillPlaying) - { - if (audioQueue && [self audioQueueIsRunning]) - { - self.internalState = STKAudioPlayerInternalStateFlushingAndStoppingButStillPlaying; - - LOGINFO(@"AudioQueueStop from handleAudioQueueOutput"); - - [self invokeOnPlaybackThread:^ - { - AudioQueueStop(audioQueue, NO); - }]; - } - } - } - } - - if (self.internalState != STKAudioPlayerInternalStateFlushingAndStoppingButStillPlaying) - { - if ((entry.lastFrameIndex != -1 && currentTime >= entry.lastFrameIndex && audioPacketsPlayedCount >= entry.lastByteIndex) || ![self moreFramesAreDefinitelyAvailableToPlay]) - { - LOGINFO(@"Final frame played"); - - Float64 hardwareDelay = currentTime - entry.timeWhenLastBufferReturned; - - if (averageHardwareDelay == 0) - { - averageHardwareDelay = hardwareDelay; - } - else - { - averageHardwareDelay = (averageHardwareDelay + hardwareDelay) / 2; - } - - LOGINFO(([NSString stringWithFormat:@"Current Hardware Delay: %f", hardwareDelay])); - LOGINFO(([NSString stringWithFormat:@"Average Hardware Delay: %f", averageHardwareDelay])); - - entry.lastFrameIndex = -1; - - [self invokeOnPlaybackThread:^ - { - [self audioQueueFinishedPlaying:entry]; - }]; - - signal = YES; - } - } - } - - if (!audioQueueFlushing) - { - if (numberOfBuffersUsed == 0 - && !seekToTimeWasRequested - && !disposeWasRequested - && self.internalState != STKAudioPlayerInternalStateFlushingAndStoppingButStillPlaying) - { - if (self->rebufferingStartFrames == 0) - { - if (self->numberOfBuffersUsed == 0 - && !disposeWasRequested - && !seekToTimeWasRequested - && self->rebufferingStartFrames == 0 - && self.internalState != STKAudioPlayerInternalStateWaitingForData - && self.internalState != STKAudioPlayerInternalStateFlushingAndStoppingButStillPlaying - && [self moreFramesAreDefinitelyAvailableToPlay]) - { - [self invokeOnPlaybackThread:^ - { - self->rebufferingStartFrames = currentTime; - AudioQueuePause(audioQueue); - }]; - - LOGINFO(([NSString stringWithFormat:@"Buffer underrun with time: %f", [self currentTimeInFrames]])); - } - } - - if (self.internalState != STKAudioPlayerInternalStateRebuffering && self.internalState != STKAudioPlayerInternalStatePaused) - { - Float64 interval = STK_FRAMES_MISSED_BEFORE_CONSIDERED_UNDERRUN / currentAudioStreamBasicDescription.mSampleRate; - - [self invokeOnPlaybackThreadAtInterval:interval withBlock:^ - { - [self setRebufferingStateIfApplicable]; - }]; - } - - signal = YES; - } - else - { - if (self.internalState == STKAudioPlayerInternalStateRebuffering && ([self readyToEndRebufferingState] || [self readyToEndWaitingForDataState])) - { - [self invokeOnPlaybackThread:^ - { - self->rebufferingStartFrames = 0; - [self startAudioQueue]; - }]; - - signal = YES; - } - } - } - - signal = signal || disposeWasRequested; - - if (self.internalState == STKAudioPlayerInternalStateStopped - || self.internalState == STKAudioPlayerInternalStateStopping - || self.internalState == STKAudioPlayerInternalStateDisposed - || self.internalState == STKAudioPlayerInternalStateError) - { - signal = signal || waiting || numberOfBuffersUsed < (STK_BUFFERS_NEEDED_TO_START * 2); - } - else if (audioQueueFlushing) - { - signal = signal || (audioQueueFlushing && numberOfBuffersUsed < (STK_BUFFERS_NEEDED_TO_START * 2)); - } - else - { - signal = signal || seekToTimeWasRequested || (numberOfBuffersUsed < self->audioQueueBufferCount / 2); - } - - if (signal) - { - pthread_cond_signal(&queueBufferReadyCondition); - } - - pthread_mutex_unlock(&queueBuffersMutex); -} - --(void) setRebufferingStateIfApplicable -{ - pthread_mutex_lock(&playerMutex); - - if (self->rebufferingStartFrames > 0) - { - if ([self currentTimeInFrames] > STK_FRAMES_MISSED_BEFORE_CONSIDERED_UNDERRUN - && self.internalState != STKAudioPlayerInternalStateRebuffering - && self.internalState != STKAudioPlayerInternalStatePaused) - { - self.internalState = STKAudioPlayerInternalStateRebuffering; - } - else - { - Float64 interval = STK_FRAMES_MISSED_BEFORE_CONSIDERED_UNDERRUN / currentAudioStreamBasicDescription.mSampleRate / 2; - - [self invokeOnPlaybackThreadAtInterval:interval withBlock:^ - { - [self setRebufferingStateIfApplicable]; - }]; - } - } - - pthread_mutex_unlock(&playerMutex); -} - -(Float64) currentTimeInFrames { - if (audioQueue == nil) + if (audioUnit == nil) { return 0; } - AudioTimeStamp timeStamp; - Boolean outTimelineDiscontinuity; - - AudioQueueGetCurrentTime(audioQueue, NULL, &timeStamp, &outTimelineDiscontinuity); - - return timeStamp.mSampleTime - timelineAdjust; + return 0; } --(void) processFinishedPlayingViaAudioQueueStop +-(void) unexpectedError:(STKAudioPlayerErrorCode)errorCodeIn { - if (currentlyPlayingEntry) - { - pthread_mutex_lock(&playerMutex); - - STKQueueEntry* entry = currentlyPlayingEntry; - - entry.lastFrameIndex = -1; - - pthread_mutex_unlock(&playerMutex); - - [self invokeOnPlaybackThread:^ - { - self->stopReason = AudioPlayerStopReasonEof; - self.internalState = STKAudioPlayerInternalStateStopped; - - if (audioQueue) - { - [self stopAudioQueueWithReason:@"processFinishedPlayingViaAudioQueueStop"]; - } - - [self audioQueueFinishedPlaying:entry]; - }]; - } - else - { - pthread_mutex_lock(&playerMutex); - - STKQueueEntry* entry = currentlyPlayingEntry; - - entry.lastFrameIndex = -1; - - pthread_mutex_unlock(&playerMutex); - - [self invokeOnPlaybackThread:^ - { - if (audioQueue) - { - [self stopAudioQueueWithReason:@"processFinishedPlayingViaAudioQueueStop"]; - } - - [self audioQueueFinishedPlaying:entry]; - }]; - } -} - --(void) handlePropertyChangeForQueue:(AudioQueueRef)audioQueueIn propertyID:(AudioQueuePropertyID)propertyId -{ - if (audioQueueIn != audioQueue) - { - return; - } - - if (propertyId == kAudioQueueProperty_IsRunning) - { - if (![self audioQueueIsRunning] && self.internalState == STKAudioPlayerInternalStateStopping) - { - self.internalState = STKAudioPlayerInternalStateStopped; - } - else if (![self audioQueueIsRunning] && self.internalState == STKAudioPlayerInternalStateFlushingAndStoppingButStillPlaying) - { - LOGINFO(@"AudioQueue not IsRunning") - - [self invokeOnPlaybackThread:^ - { - [self processFinishedPlayingViaAudioQueueStop]; - }]; - - pthread_mutex_lock(&queueBuffersMutex); - - if (signal) - { - pthread_cond_signal(&queueBufferReadyCondition); - } - - pthread_mutex_unlock(&queueBuffersMutex); - } - } -} - --(BOOL) readyToEndRebufferingState -{ - BOOL tailEndOfBuffer = ![self moreFramesAreDefinitelyAvailableToPlayIgnoreExisting:YES]; - - return self->rebufferingStartFrames > 0 && (numberOfBuffersUsed > STK_BUFFERS_NEEDED_WHEN_UNDERUNNING || tailEndOfBuffer); -} - --(BOOL) readyToEndWaitingForDataState -{ - BOOL tailEndOfBuffer = ![self moreFramesAreDefinitelyAvailableToPlayIgnoreExisting:YES]; - - return (self.internalState == STKAudioPlayerInternalStateWaitingForData || self.internalState == STKAudioPlayerInternalStateWaitingForDataAfterSeek) && (numberOfBuffersUsed >= STK_BUFFERS_NEEDED_TO_START || tailEndOfBuffer); -} - --(void) enqueueBuffer -{ - BOOL queueBuffersMutexUnlocked = NO; - - pthread_mutex_lock(&playerMutex); - { - OSStatus error; - - if (audioFileStream == 0) - { - pthread_mutex_unlock(&playerMutex); - - return; - } - - if (self.internalState == STKAudioPlayerInternalStateStopped) - { - pthread_mutex_unlock(&playerMutex); - - return; - } - - if (audioQueueFlushing || newFileToPlay) - { - pthread_mutex_unlock(&playerMutex); - - return; - } - - pthread_mutex_lock(&queueBuffersMutex); - - if (audioQueue == nil) - { - pthread_mutex_unlock(&queueBuffersMutex); - - return; - } - - bufferUsed[fillBufferIndex] = true; - numberOfBuffersUsed++; - - AudioQueueBufferRef buffer = audioQueueBuffer[fillBufferIndex]; - - buffer->mAudioDataByteSize = bytesFilled; - - if (packetsFilled) - { - error = AudioQueueEnqueueBuffer(audioQueue, buffer, packetsFilled, packetDescs); - } - else - { - error = AudioQueueEnqueueBuffer(audioQueue, buffer, 0, NULL); - } - - audioPacketsReadCount++; - framesQueued += framesFilled; - - if (error) - { - pthread_mutex_unlock(&queueBuffersMutex); - pthread_mutex_unlock(&playerMutex); - - [self stopAudioQueueWithReason:@"enqueueBuffer critical error"]; - - return; - } - - if ([self readyToEndRebufferingState]) - { - if (self.internalState != STKAudioPlayerInternalStatePaused) - { - self->rebufferingStartFrames = 0; - - pthread_mutex_unlock(&queueBuffersMutex); - - queueBuffersMutexUnlocked = YES; - - if (![self startAudioQueue] || audioQueue == nil) - { - pthread_mutex_unlock(&playerMutex); - - return; - } - } - } - - if ([self readyToEndWaitingForDataState]) - { - if (self.internalState != STKAudioPlayerInternalStatePaused) - { - pthread_mutex_unlock(&queueBuffersMutex); - - queueBuffersMutexUnlocked = YES; - - if (![self startAudioQueue] || audioQueue == nil) - { - pthread_mutex_unlock(&playerMutex); - - return; - } - } - } - - if (++fillBufferIndex >= audioQueueBufferCount) - { - fillBufferIndex = 0; - } - - bytesFilled = 0; - framesFilled = 0; - packetsFilled = 0; - - if (!queueBuffersMutexUnlocked) - { - pthread_mutex_unlock(&queueBuffersMutex); - } - } - pthread_mutex_unlock(&playerMutex); - - pthread_mutex_lock(&queueBuffersMutex); - - waiting = YES; - - while (bufferUsed[fillBufferIndex] && !(disposeWasRequested || seekToTimeWasRequested || self.internalState == STKAudioPlayerInternalStateStopped || self.internalState == STKAudioPlayerInternalStateStopping || self.internalState == STKAudioPlayerInternalStateDisposed)) - { - if (numberOfBuffersUsed == 0) - { - memset(&bufferUsed[0], 0, sizeof(bool) * audioQueueBufferCount); - - break; - } - - pthread_cond_wait(&queueBufferReadyCondition, &queueBuffersMutex); - } - - waiting = NO; - - pthread_mutex_unlock(&queueBuffersMutex); -} - --(void) didEncounterError:(STKAudioPlayerErrorCode)errorCodeIn -{ - errorCode = errorCodeIn; self.internalState = STKAudioPlayerInternalStateError; [self playbackThreadQueueMainThreadSyncBlock:^ { - [self.delegate audioPlayer:self didEncounterError:errorCode]; + [self.delegate audioPlayer:self unexpectedError:errorCodeIn]; }]; } --(void) createAudioQueue -{ - OSStatus error; - - LOGINFO(@"Called"); - - [self startSystemBackgroundTask]; - - pthread_mutex_lock(&playerMutex); - pthread_mutex_lock(&queueBuffersMutex); - - if (audioQueue) - { - LOGINFO(@"AudioQueueStop/1"); - - pthread_mutex_unlock(&queueBuffersMutex); - AudioQueueStop(audioQueue, YES); - AudioQueueDispose(audioQueue, YES); - - memset(¤tAudioStreamBasicDescription, 0, sizeof(currentAudioStreamBasicDescription)); - - audioQueue = nil; - } - - if (currentlyPlayingEntry == nil) - { - LOGINFO(@"currentlyPlayingEntry == nil"); - - pthread_mutex_unlock(&queueBuffersMutex); - pthread_mutex_unlock(&playerMutex); - - return; - } - - currentAudioStreamBasicDescription = currentlyPlayingEntry->audioStreamBasicDescription; - - error = AudioQueueNewOutput(¤tAudioStreamBasicDescription, AudioQueueOutputCallbackProc, (__bridge void*)self, NULL, NULL, 0, &audioQueue); - - if (error) - { - [self playbackThreadQueueMainThreadSyncBlock:^ - { - [self.delegate audioPlayer:self didEncounterError:STKAudioPlayerErrorQueueCreationFailed]; - }]; - - pthread_mutex_unlock(&queueBuffersMutex); - pthread_mutex_unlock(&playerMutex); - - return; - } - - AudioQueuePause(audioQueue); - - error = AudioQueueAddPropertyListener(audioQueue, kAudioQueueProperty_IsRunning, AudioQueueIsRunningCallbackProc, (__bridge void*)self); - - if (error) - { - [self playbackThreadQueueMainThreadSyncBlock:^ - { - [self.delegate audioPlayer:self didEncounterError:STKAudioPlayerErrorQueueCreationFailed]; - }]; - - pthread_mutex_unlock(&queueBuffersMutex); - pthread_mutex_unlock(&playerMutex); - - return; - } - -#if TARGET_OS_IPHONE - UInt32 val = kAudioQueueHardwareCodecPolicy_PreferHardware; - - AudioQueueSetProperty(audioQueue, kAudioQueueProperty_HardwareCodecPolicy, &val, sizeof(UInt32)); - - AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, 1); -#endif - - memset(audioQueueBufferLookup, 0, sizeof(AudioQueueBufferRefLookupEntry) * audioQueueBufferRefLookupCount); - - // Allocate AudioQueue buffers - - for (int i = 0; i < audioQueueBufferCount; i++) - { - error = AudioQueueAllocateBuffer(audioQueue, currentlyPlayingEntry->packetBufferSize, &audioQueueBuffer[i]); - - unsigned int hash = (unsigned 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) - { - [self playbackThreadQueueMainThreadSyncBlock:^ - { - [self.delegate audioPlayer:self didEncounterError:STKAudioPlayerErrorQueueCreationFailed]; - }]; - - pthread_mutex_unlock(&playerMutex); - - return; - } - } - - audioPacketsReadCount = 0; - audioPacketsPlayedCount = 0; - framesQueued = 0; - timelineAdjust = 0; - rebufferingStartFrames = 0; - - // Get file cookie/magic bytes information - - UInt32 cookieSize; - Boolean writable; - - error = AudioFileStreamGetPropertyInfo(audioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, &writable); - - if (!error) - { - void* cookieData = calloc(1, cookieSize); - - error = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, cookieData); - - if (error) - { - free(cookieData); - - pthread_mutex_unlock(&queueBuffersMutex); - pthread_mutex_unlock(&playerMutex); - - return; - } - - error = AudioQueueSetProperty(audioQueue, kAudioQueueProperty_MagicCookie, cookieData, cookieSize); - - if (error) - { - free(cookieData); - - [self playbackThreadQueueMainThreadSyncBlock:^ - { - [self.delegate audioPlayer:self didEncounterError:STKAudioPlayerErrorQueueCreationFailed]; - }]; - - pthread_mutex_unlock(&queueBuffersMutex); - pthread_mutex_unlock(&playerMutex); - - return; - } - - free(cookieData); - } - - AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, 1); - - // Reset metering enabled in case the user set it before the queue was created - - [self setMeteringEnabled:meteringEnabled]; - - pthread_mutex_unlock(&queueBuffersMutex); - pthread_mutex_unlock(&playerMutex); -} - -(double) duration { - if (newFileToPlay) - { - return 0; - } - - if (self.internalState == AudioPlayerInternalStateStopped) + if (self.internalState == STKAudioPlayerInternalStatePendingNext) { return 0; } @@ -1922,33 +738,21 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ return requestedSeekTime; } - if (self.internalState == AudioPlayerInternalStateStopped) + if (self.internalState == STKAudioPlayerInternalStatePendingNext) { return 0; } - if (newFileToPlay) - { - return 0; - } - - OSSpinLockLock(¤tEntryReferencesLock); - - Float64 currentTime = [self currentTimeInFrames]; STKQueueEntry* entry = currentlyPlayingEntry; if (entry == nil) { - OSSpinLockUnlock(¤tEntryReferencesLock); - return 0; } - Float64 time = currentTime; - - double retval = [entry calculateProgressWithTotalFramesPlayed:time]; - - OSSpinLockUnlock(¤tEntryReferencesLock); + OSSpinLockLock(&entry->spinLock); + double retval = entry->seekTime + (entry->framesPlayed / canonicalAudioStreamBasicDescription.mSampleRate); + OSSpinLockUnlock(&entry->spinLock); return retval; } @@ -1968,44 +772,21 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ return NO; } --(void)invokeOnPlaybackThreadAtIntervalHelper:(NSTimer*)timer -{ - void(^block)() = (void(^)())timer.userInfo; - - block(); -} - --(BOOL) invokeOnPlaybackThreadAtInterval:(NSTimeInterval)interval withBlock:(void(^)())block -{ - NSRunLoop* runLoop = playbackThreadRunLoop; - - if (runLoop) - { - NSTimer* timer = [NSTimer timerWithTimeInterval:interval target:self selector:@selector(invokeOnPlaybackThreadAtIntervalHelper:) userInfo:[block copy] repeats:NO]; - - [runLoop addTimer:timer forMode:NSRunLoopCommonModes]; - - return YES; - } - - return NO; -} - -(void) wakeupPlaybackThread { [self invokeOnPlaybackThread:^ - { - [self processRunloop]; - }]; - - pthread_mutex_lock(&queueBuffersMutex); - - if (waiting) - { - pthread_cond_signal(&queueBufferReadyCondition); - } - - pthread_mutex_unlock(&queueBuffersMutex); + { + [self processRunloop]; + }]; + + pthread_mutex_lock(&playerMutex); + + if (waiting) + { + pthread_cond_signal(&playerThreadReadyCondition); + } + + pthread_mutex_unlock(&playerMutex); } -(void) seekToTime:(double)value @@ -2048,18 +829,26 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ NSAssert(playbackThreadRunLoop != nil, @"playbackThreadRunLoop != nil"); } +-(void) audioQueueFinishedPlaying:(STKQueueEntry*)entry +{ + STKQueueEntry* next = [bufferingQueue dequeue]; + + [self processFinishPlayingIfAnyAndPlayingNext:entry withNext:next]; + [self processRunloop]; +} + -(void) setCurrentlyReadingEntry:(STKQueueEntry*)entry andStartPlaying:(BOOL)startPlaying +{ + [self setCurrentlyReadingEntry:entry andStartPlaying:startPlaying clearQueue:YES]; +} + +-(void) setCurrentlyReadingEntry:(STKQueueEntry*)entry andStartPlaying:(BOOL)startPlaying clearQueue:(BOOL)clearQueue { LOGINFO(([entry description])); - - NSAssert([NSThread currentThread] == playbackThread, @"[NSThread currentThread] == playbackThread"); - + if (startPlaying) { - if (audioQueue) - { - [self resetAudioQueueWithReason:@"from setCurrentlyReadingEntry" andPause:YES]; - } + memset(&pcmAudioBuffer->mData[0], 0, pcmBufferTotalFrameCount * pcmBufferFrameSizeInBytes); } if (audioFileStream) @@ -2081,50 +870,25 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ OSSpinLockUnlock(¤tEntryReferencesLock); currentlyReadingEntry.dataSource.delegate = self; - [currentlyReadingEntry.dataSource registerForEvents:[NSRunLoop currentRunLoop]]; [currentlyReadingEntry.dataSource seekToOffset:0]; if (startPlaying) { - [self clearQueueIncludingUpcoming:NO]; + if (clearQueue) + { + [self clearQueue]; + } [self processFinishPlayingIfAnyAndPlayingNext:currentlyPlayingEntry withNext:entry]; + [self startAudioUnit]; } else { - pthread_mutex_lock(&queueBuffersMutex); [bufferingQueue enqueue:entry]; - pthread_mutex_unlock(&queueBuffersMutex); } } --(void) audioQueueFinishedPlaying:(STKQueueEntry*)entry -{ - pthread_mutex_lock(&playerMutex); - { - pthread_mutex_lock(&queueBuffersMutex); - - STKQueueEntry* next = [bufferingQueue peek]; - - pthread_mutex_unlock(&queueBuffersMutex); - - if (next == nil) - { - [self processRunloop]; - } - - pthread_mutex_lock(&queueBuffersMutex); - next = [bufferingQueue dequeue]; - pthread_mutex_unlock(&queueBuffersMutex); - - [self processFinishPlayingIfAnyAndPlayingNext:entry withNext:next]; - - [self processRunloop]; - } - pthread_mutex_unlock(&playerMutex); -} - -(void) processFinishPlayingIfAnyAndPlayingNext:(STKQueueEntry*)entry withNext:(STKQueueEntry*)next { if (entry != currentlyPlayingEntry) @@ -2135,7 +899,7 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ LOGINFO(([NSString stringWithFormat:@"Finished: %@, Next: %@, buffering.count=%d,upcoming.count=%d", entry ? [entry description] : @"nothing", [next description], (int)bufferingQueue.count, (int)upcomingQueue.count])); NSObject* queueItemId = entry.queueItemId; - double progress = [entry calculateProgressWithTotalFramesPlayed:[self currentTimeInFrames]]; + double progress = [entry progressInFrames] / canonicalAudioStreamBasicDescription.mSampleRate; double duration = [entry duration]; BOOL isPlayingSameItemProbablySeek = currentlyPlayingEntry == next; @@ -2144,7 +908,9 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ { if (!isPlayingSameItemProbablySeek) { - next.seekTime = 0; + OSSpinLockLock(&next->spinLock); + next->seekTime = 0; + OSSpinLockUnlock(&next->spinLock); OSSpinLockLock(&seekLock); seekToTimeWasRequested = NO; @@ -2153,8 +919,6 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ OSSpinLockLock(¤tEntryReferencesLock); currentlyPlayingEntry = next; - currentlyPlayingEntry.bytesBuffered = 0; - currentlyPlayingEntry.firstFrameIndex = [self currentTimeInFrames]; NSObject* playingQueueItemId = playingQueueItemId = currentlyPlayingEntry.queueItemId; OSSpinLockUnlock(¤tEntryReferencesLock); @@ -2168,6 +932,8 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ if (!isPlayingSameItemProbablySeek) { + [self setInternalState:STKAudioPlayerInternalStateWaitingForData]; + [self playbackThreadQueueMainThreadSyncBlock:^ { [self.delegate audioPlayer:self didStartPlayingQueueItemId:playingQueueItemId]; @@ -2180,15 +946,6 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ currentlyPlayingEntry = nil; OSSpinLockUnlock(¤tEntryReferencesLock); - if (currentlyReadingEntry == nil) - { - if (upcomingQueue.count == 0) - { - stopReason = AudioPlayerStopReasonEof; - self.internalState = STKAudioPlayerInternalStateStopping; - } - } - if (!isPlayingSameItemProbablySeek && entry) { [self playbackThreadQueueMainThreadSyncBlock:^ @@ -2197,43 +954,48 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ }]; } } + + [self wakeupPlaybackThread]; } -(void) dispatchSyncOnMainThread:(void(^)())block { - __block BOOL finished = NO; - - if (disposeWasRequested) - { - return; - } - - dispatch_async(dispatch_get_main_queue(), ^ - { - block(); - - pthread_mutex_lock(&mainThreadSyncCallMutex); - finished = YES; - pthread_cond_signal(&mainThreadSyncCallReadyCondition); - pthread_mutex_unlock(&mainThreadSyncCallMutex); - }); - - while (true) - { - if (disposeWasRequested) - { - break; - } - - if (finished) - { - break; - } - - pthread_mutex_lock(&mainThreadSyncCallMutex); - pthread_cond_wait(&mainThreadSyncCallReadyCondition, &mainThreadSyncCallMutex); - pthread_mutex_unlock(&mainThreadSyncCallMutex); - } + __block BOOL finished = NO; + + if (disposeWasRequested) + { + return; + } + + dispatch_async(dispatch_get_main_queue(), ^ + { + if (!disposeWasRequested) + { + block(); + } + + pthread_mutex_lock(&mainThreadSyncCallMutex); + finished = YES; + pthread_cond_signal(&mainThreadSyncCallReadyCondition); + pthread_mutex_unlock(&mainThreadSyncCallMutex); + }); + + while (true) + { + if (disposeWasRequested) + { + break; + } + + if (finished) + { + break; + } + + pthread_mutex_lock(&mainThreadSyncCallMutex); + pthread_cond_wait(&mainThreadSyncCallReadyCondition, &mainThreadSyncCallMutex); + pthread_mutex_unlock(&mainThreadSyncCallMutex); + } } -(void) playbackThreadQueueMainThreadSyncBlock:(void(^)())block @@ -2251,139 +1013,83 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ }]; } +-(void) requeueBufferingEntries +{ + if (bufferingQueue.count > 0) + { + for (STKQueueEntry* queueEntry in bufferingQueue) + { + queueEntry->parsedHeader = NO; + + [queueEntry reset]; + } + + [upcomingQueue skipQueueWithQueue:bufferingQueue]; + + [bufferingQueue removeAllObjects]; + } +} + -(BOOL) processRunloop { - BOOL dontPlayNew = NO; - pthread_mutex_lock(&playerMutex); { - dontPlayNew = self.internalState == STKAudioPlayerInternalStateFlushingAndStoppingButStillPlaying; - if (self.internalState == STKAudioPlayerInternalStatePaused) { pthread_mutex_unlock(&playerMutex); return YES; } - else if (newFileToPlay) + else if (self.internalState == STKAudioPlayerInternalStatePendingNext) { STKQueueEntry* entry = [upcomingQueue dequeue]; self.internalState = STKAudioPlayerInternalStateWaitingForData; [self setCurrentlyReadingEntry:entry andStartPlaying:YES]; - - newFileToPlay = NO; + [self resetPcmBuffers]; } else if (seekToTimeWasRequested && currentlyPlayingEntry && currentlyPlayingEntry != currentlyReadingEntry) { - currentlyPlayingEntry.lastFrameIndex = -1; - currentlyPlayingEntry.lastByteIndex = -1; + currentlyPlayingEntry->parsedHeader = NO; + [currentlyPlayingEntry reset]; - self.internalState = STKAudioPlayerInternalStateWaitingForDataAfterSeek; - - [self setCurrentlyReadingEntry:currentlyPlayingEntry andStartPlaying:YES]; - - currentlyReadingEntry->parsedHeader = NO; - } - else if (self.internalState == STKAudioPlayerInternalStateStopped - && (stopReason == AudioPlayerStopReasonUserAction || stopReason == AudioPlayerStopReasonUserActionFlushStop)) - { - [self stopAudioQueueWithReason:@"from processRunLoop/1"]; - - currentlyReadingEntry.dataSource.delegate = nil; - [currentlyReadingEntry.dataSource unregisterForEvents]; - [currentlyReadingEntry.dataSource close]; - - if (currentlyPlayingEntry) + if (currentlyReadingEntry != nil) { - [self processFinishPlayingIfAnyAndPlayingNext:currentlyPlayingEntry withNext:nil]; + currentlyReadingEntry.dataSource.delegate = nil; + [currentlyReadingEntry.dataSource unregisterForEvents]; } - pthread_mutex_lock(&queueBuffersMutex); - - if ([bufferingQueue peek] == currentlyPlayingEntry) + if (self->options & STKAudioPlayerOptionFlushQueueOnSeek) { - [bufferingQueue dequeue]; - } - - OSSpinLockLock(¤tEntryReferencesLock); - currentlyPlayingEntry = nil; - currentlyReadingEntry = nil; - seekToTimeWasRequested = NO; - OSSpinLockUnlock(¤tEntryReferencesLock); - - pthread_mutex_unlock(&queueBuffersMutex); - - if (stopReason == AudioPlayerStopReasonUserActionFlushStop) - { - [self resetAudioQueueWithReason:@"from processRunLoop"]; - } - } - else if (currentlyReadingEntry == nil && !dontPlayNew) - { - pthread_mutex_lock(&queueBuffersMutex); - - BOOL processNextToRead = YES; - STKQueueEntry* next = [bufferingQueue peek]; - - if (next != nil && next->audioStreamBasicDescription.mSampleRate == 0) - { - pthread_mutex_unlock(&queueBuffersMutex); - - processNextToRead = NO; - } - - if (processNextToRead) - { - next = [upcomingQueue peek]; - - if ([next isKnownToBeIncompatible:¤tAudioStreamBasicDescription] && currentlyPlayingEntry != nil) - { - pthread_mutex_unlock(&queueBuffersMutex); - - processNextToRead = NO; - } - } - - if (processNextToRead) - { - if (upcomingQueue.count > 0) - { - STKQueueEntry* entry = [upcomingQueue dequeue]; - - BOOL startPlaying = currentlyPlayingEntry == nil; - BOOL wasCurrentlyPlayingNothing = currentlyPlayingEntry == nil; - - pthread_mutex_unlock(&queueBuffersMutex); - - [self setCurrentlyReadingEntry:entry andStartPlaying:startPlaying]; - - if (wasCurrentlyPlayingNothing) - { - currentAudioStreamBasicDescription.mSampleRate = 0; - - [self setInternalState:STKAudioPlayerInternalStateWaitingForData]; - } - } - else if (currentlyPlayingEntry == nil) - { - pthread_mutex_unlock(&queueBuffersMutex); - - if (self.internalState != STKAudioPlayerInternalStateStopped) - { - [self stopAudioQueueWithReason:@"from processRunLoop/2"]; - stopReason = AudioPlayerStopReasonEof; - } - } - else - { - pthread_mutex_unlock(&queueBuffersMutex); - } + self.internalState = STKAudioPlayerInternalStateWaitingForDataAfterSeek; + [self setCurrentlyReadingEntry:currentlyPlayingEntry andStartPlaying:YES clearQueue:YES]; } else { - pthread_mutex_unlock(&queueBuffersMutex); + [self requeueBufferingEntries]; + + self.internalState = STKAudioPlayerInternalStateWaitingForDataAfterSeek; + [self setCurrentlyReadingEntry:currentlyPlayingEntry andStartPlaying:YES clearQueue:NO]; + } + } + else if (currentlyReadingEntry == nil) + { + if (upcomingQueue.count > 0) + { + STKQueueEntry* entry = [upcomingQueue dequeue]; + + BOOL startPlaying = currentlyPlayingEntry == nil; + + self.internalState = STKAudioPlayerInternalStateWaitingForData; + [self setCurrentlyReadingEntry:entry andStartPlaying:startPlaying]; + } + else if (currentlyPlayingEntry == nil) + { + if (self.internalState != STKAudioPlayerInternalStateStopped) + { + [self stopAudioUnitWithReason:STKAudioPlayerStopReasonEof]; + } } } @@ -2437,9 +1143,6 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ [threadStartedLock lockWhenCondition:0]; [threadStartedLock unlockWithCondition:1]; - bytesFilled = 0; - packetsFilled = 0; - [playbackThreadRunLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode]; while (true) @@ -2479,9 +1182,7 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ -(void) processSeekToTime { OSStatus error; - OSSpinLockLock(¤tEntryReferencesLock); STKQueueEntry* currentEntry = currentlyReadingEntry; - OSSpinLockUnlock(¤tEntryReferencesLock); NSAssert(currentEntry == currentlyPlayingEntry, @"playing and reading must be the same"); @@ -2497,8 +1198,9 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ seekByteOffset = currentEntry.dataSource.length - 2 * currentEntry->packetBufferSize; } - currentEntry.seekTime = requestedSeekTime; - currentEntry->lastProgress = requestedSeekTime; + OSSpinLockLock(¤tEntry->spinLock); + currentEntry->seekTime = requestedSeekTime; + OSSpinLockUnlock(¤tEntry->spinLock); double calculatedBitRate = [currentEntry calculatedBitRate]; @@ -2514,214 +1216,29 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ { double delta = ((seekByteOffset - (SInt64)currentEntry->audioDataOffset) - packetAlignedByteOffset) / calculatedBitRate * 8; - currentEntry.seekTime -= delta; + OSSpinLockLock(¤tEntry->spinLock); + currentEntry->seekTime -= delta; + OSSpinLockUnlock(¤tEntry->spinLock); seekByteOffset = packetAlignedByteOffset + currentEntry->audioDataOffset; } } - currentEntry.lastFrameIndex = -1; + if (audioConverterRef) + { + AudioConverterReset(audioConverterRef); + } + [currentEntry updateAudioDataSource]; + [currentEntry reset]; [currentEntry.dataSource seekToOffset:seekByteOffset]; - if (self.internalState == STKAudioPlayerInternalStateFlushingAndStoppingButStillPlaying) + self.internalState = STKAudioPlayerInternalStateWaitingForDataAfterSeek; + + if (audioUnit) { - self.internalState = STKAudioPlayerInternalStatePlaying; + [self resetPcmBuffers]; } - - if (seekByteOffset > 0) - { - discontinuous = YES; - } - - if (audioQueue) - { - [self resetAudioQueueWithReason:@"from seekToTime"]; - } - - currentEntry.bytesBuffered = 0; - currentEntry.firstFrameIndex = [self currentTimeInFrames]; - - [self clearQueue]; -} - --(BOOL) startAudioQueue -{ - OSStatus error; - - LOGINFO(@"Called"); - - AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, 1); - - error = AudioQueueStart(audioQueue, NULL); - - if (error) - { -#if TARGET_OS_IPHONE - if (backgroundTaskId == UIBackgroundTaskInvalid) - { - [self startSystemBackgroundTask]; - } -#endif - - [self stopAudioQueueWithReason:@"from startAudioQueue"]; - [self createAudioQueue]; - - if (audioQueue != nil) - { - AudioQueueStart(audioQueue, NULL); - } - } - - [self stopSystemBackgroundTask]; - - self.internalState = STKAudioPlayerInternalStatePlaying; - - return YES; -} - --(void) stopAudioQueueWithReason:(NSString*)reason -{ - OSStatus error; - - LOGINFO(([NSString stringWithFormat:@"With Reason: %@", reason])); - - if (!audioQueue) - { - LOGINFO(@"Already No AudioQueue"); - - self.internalState = STKAudioPlayerInternalStateStopped; - - return; - } - else - { - LOGINFO(@"Stopping AudioQueue"); - - audioQueueFlushing = YES; - - error = AudioQueueStop(audioQueue, YES); - error = error | AudioQueueDispose(audioQueue, YES); - - memset(¤tAudioStreamBasicDescription, 0, sizeof(currentAudioStreamBasicDescription)); - - audioQueue = nil; - } - - if (error) - { - [self didEncounterError:STKAudioPlayerErrorQueueStopFailed]; - } - - pthread_mutex_lock(&queueBuffersMutex); - - if (numberOfBuffersUsed != 0) - { - numberOfBuffersUsed = 0; - - memset(&bufferUsed[0], 0, sizeof(bool) * audioQueueBufferCount); - } - - pthread_cond_signal(&queueBufferReadyCondition); - pthread_mutex_unlock(&queueBuffersMutex); - - bytesFilled = 0; - fillBufferIndex = 0; - packetsFilled = 0; - framesQueued = 0; - timelineAdjust = 0; - rebufferingStartFrames = 0; - - audioPacketsReadCount = 0; - audioPacketsPlayedCount = 0; - audioQueueFlushing = NO; - - self.internalState = STKAudioPlayerInternalStateStopped; -} - - --(void) resetAudioQueueWithReason:(NSString*)reason -{ - [self resetAudioQueueWithReason:reason andPause:NO]; -} - --(void) resetAudioQueueWithReason:(NSString*)reason andPause:(BOOL)pause -{ - OSStatus error; - - LOGINFO(([NSString stringWithFormat:@"With Reason: %@", reason])); - - pthread_mutex_lock(&playerMutex); - { - audioQueueFlushing = YES; - - if (audioQueue) - { - AudioTimeStamp timeStamp; - Boolean outTimelineDiscontinuity; - - error = AudioQueueReset(audioQueue); - - if (pause) - { - AudioQueuePause(audioQueue); - } - - AudioQueueGetCurrentTime(audioQueue, NULL, &timeStamp, &outTimelineDiscontinuity); - - timelineAdjust = timeStamp.mSampleTime; - - BOOL startAudioQueue = NO; - - if (rebufferingStartFrames > 0) - { - startAudioQueue = YES; - rebufferingStartFrames = 0; - } - - if (!pause && startAudioQueue) - { - [self startAudioQueue]; - } - - if (error) - { - [self playbackThreadQueueMainThreadSyncBlock:^ - { - [self didEncounterError:STKAudioPlayerErrorQueueStopFailed];; - }]; - } - } - } - pthread_mutex_unlock(&playerMutex); - - pthread_mutex_lock(&queueBuffersMutex); - - if (numberOfBuffersUsed != 0) - { - numberOfBuffersUsed = 0; - - memset(&bufferUsed[0], 0, sizeof(bool) * audioQueueBufferCount); - } - - pthread_cond_signal(&queueBufferReadyCondition); - - - bytesFilled = 0; - fillBufferIndex = 0; - packetsFilled = 0; - framesQueued = 0; - - if (currentlyPlayingEntry) - { - currentlyPlayingEntry->lastProgress = 0; - } - - audioPacketsReadCount = 0; - audioPacketsPlayedCount = 0; - audioQueueFlushing = NO; - - pthread_mutex_unlock(&queueBuffersMutex); } -(void) dataSourceDataAvailable:(STKDataSource*)dataSourceIn @@ -2751,22 +1268,12 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ if (error) { + [self unexpectedError:STKAudioPlayerErrorAudioSystemError]; + 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) @@ -2782,7 +1289,7 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ { if (dataSourceIn == currentlyPlayingEntry.dataSource) { - [self didEncounterError:STKAudioPlayerErrorStreamParseBytesFailed]; + [self unexpectedError:STKAudioPlayerErrorStreamParseBytesFailed]; } return; @@ -2807,100 +1314,58 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ return; } - [self didEncounterError:STKAudioPlayerErrorDataNotFound]; + [self unexpectedError:STKAudioPlayerErrorDataNotFound]; } -(void) dataSourceEof:(STKDataSource*)dataSourceIn { - if (currentlyReadingEntry.dataSource != dataSourceIn) + if (currentlyReadingEntry == nil || currentlyReadingEntry.dataSource != dataSourceIn) { + dataSourceIn.delegate = nil; + [dataSourceIn unregisterForEvents]; + [dataSourceIn close]; + return; } - LOGINFO(([NSString stringWithFormat:@"eof: %lld %lld", audioPacketsReadCount, audioPacketsPlayedCount])); - - if (bytesFilled > 0) - { - LOGINFO(([NSString stringWithFormat:@"eof: enqueueBuffer"])); - - [self enqueueBuffer]; - } - - LOGINFO(([NSString stringWithFormat:@" %@ (ptr:%d)", dataSourceIn, (int)dataSourceIn])); - - NSObject* queueItemId = currentlyReadingEntry.queueItemId; - if (disposeWasRequested) { return; } + NSObject* queueItemId = currentlyReadingEntry.queueItemId; + [self dispatchSyncOnMainThread:^ { [self.delegate audioPlayer:self didFinishBufferingSourceWithQueueItemId:queueItemId]; }]; + + pthread_mutex_lock(&playerMutex); - if (disposeWasRequested) + if (currentlyReadingEntry == nil) { + dataSourceIn.delegate = nil; + [dataSourceIn unregisterForEvents]; + [dataSourceIn close]; + return; } - pthread_mutex_lock(&playerMutex); + OSSpinLockLock(¤tlyReadingEntry->spinLock); + currentlyReadingEntry->lastFrameQueued = currentlyReadingEntry->framesQueued; + OSSpinLockUnlock(¤tlyReadingEntry->spinLock); - if (audioQueue) - { - currentlyReadingEntry.lastFrameIndex = self->framesQueued; - currentlyReadingEntry.lastByteIndex = audioPacketsReadCount; - - if (numberOfBuffersUsed == 0 && currentlyReadingEntry == currentlyPlayingEntry) - { - seekToTimeWasRequested = NO; - - if (audioQueue) - { - if ([self audioQueueIsRunning]) - { - self.internalState = STKAudioPlayerInternalStateFlushingAndStoppingButStillPlaying; - - LOGINFO(@"Stopping AudioQueue asynchronously"); - - if (AudioQueueStop(audioQueue, NO) != 0) - { - LOGINFO(@"Stopping AudioQueue asynchronously failed"); - - [self processFinishedPlayingViaAudioQueueStop]; - } - } - else - { - LOGINFO(@"AudioQueue already stopped"); - - [self processFinishedPlayingViaAudioQueueStop]; - } - } - } - } - else - { - stopReason = AudioPlayerStopReasonEof; - self.internalState = STKAudioPlayerInternalStateStopped; - } - - pthread_mutex_lock(&queueBuffersMutex); - - if (self.internalState == STKAudioPlayerInternalStateRebuffering && ([self readyToEndRebufferingState] || [self readyToEndWaitingForDataState])) - { - self->rebufferingStartFrames = 0; - [self startAudioQueue]; - } + currentlyReadingEntry.dataSource.delegate = nil; + [currentlyReadingEntry.dataSource unregisterForEvents]; + [currentlyReadingEntry.dataSource close]; OSSpinLockLock(¤tEntryReferencesLock); currentlyReadingEntry = nil; OSSpinLockUnlock(¤tEntryReferencesLock); - pthread_mutex_unlock(&queueBuffersMutex); - pthread_mutex_unlock(&playerMutex); + + [self processRunloop]; } -(void) pause @@ -2914,13 +1379,13 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ self.stateBeforePaused = self.internalState; self.internalState = STKAudioPlayerInternalStatePaused; - if (audioQueue) + if (audioUnit) { - error = AudioQueuePause(audioQueue); + error = AudioOutputUnitStop(audioUnit); if (error) { - [self didEncounterError:STKAudioPlayerErrorQueuePauseFailed]; + [self unexpectedError:STKAudioPlayerErrorAudioSystemError]; pthread_mutex_unlock(&playerMutex); @@ -2946,25 +1411,20 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ if (seekToTimeWasRequested) { - [self resetAudioQueueWithReason:@"from resume"]; + [self resetPcmBuffers]; } - if (audioQueue != nil) + if (audioUnit != nil) { - if(!((self.internalState == STKAudioPlayerInternalStateWaitingForData) || (self.internalState == STKAudioPlayerInternalStateRebuffering) || (self.internalState == STKAudioPlayerInternalStateWaitingForDataAfterSeek)) - || [self readyToEndRebufferingState] - || [self readyToEndWaitingForDataState]) + error = AudioOutputUnitStart(audioUnit); + + if (error) { - error = AudioQueueStart(audioQueue, 0); + [self unexpectedError:STKAudioPlayerErrorAudioSystemError]; - if (error) - { - [self didEncounterError:STKAudioPlayerErrorQueueStartFailed]; - - pthread_mutex_unlock(&playerMutex); - - return; - } + pthread_mutex_unlock(&playerMutex); + + return; } } @@ -2974,6 +1434,20 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ pthread_mutex_unlock(&playerMutex); } +-(void) resetPcmBuffers +{ + OSSpinLockLock(&pcmBufferSpinLock); + + self->pcmBufferFrameStartIndex = 0; + self->pcmBufferUsedFrameCount = 0; + self->peakPowerDb[0] = STK_DBMIN; + self->peakPowerDb[1] = STK_DBMIN; + self->averagePowerDb[0] = STK_DBMIN; + self->averagePowerDb[1] = STK_DBMIN; + + OSSpinLockUnlock(&pcmBufferSpinLock); +} + -(void) stop { pthread_mutex_lock(&playerMutex); @@ -2985,28 +1459,34 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ return; } - stopReason = AudioPlayerStopReasonUserAction; - self.internalState = STKAudioPlayerInternalStateStopped; - - [self wakeupPlaybackThread]; - } - pthread_mutex_unlock(&playerMutex); -} + [self stopAudioUnitWithReason:STKAudioPlayerStopReasonUserAction]; --(void) flushStop -{ - pthread_mutex_lock(&playerMutex); - { - if (self.internalState == STKAudioPlayerInternalStateStopped) - { - pthread_mutex_unlock(&playerMutex); - - return; - } - - stopReason = AudioPlayerStopReasonUserActionFlushStop; - self.internalState = STKAudioPlayerInternalStateStopped; + [self resetPcmBuffers]; + [self invokeOnPlaybackThread:^ + { + pthread_mutex_lock(&playerMutex); + { + currentlyReadingEntry.dataSource.delegate = nil; + [currentlyReadingEntry.dataSource unregisterForEvents]; + [currentlyReadingEntry.dataSource close]; + + if (currentlyPlayingEntry) + { + [self processFinishPlayingIfAnyAndPlayingNext:currentlyPlayingEntry withNext:nil]; + } + + [self clearQueue]; + + OSSpinLockLock(¤tEntryReferencesLock); + currentlyPlayingEntry = nil; + currentlyReadingEntry = nil; + seekToTimeWasRequested = NO; + OSSpinLockUnlock(¤tEntryReferencesLock); + } + pthread_mutex_unlock(&playerMutex); + }]; + [self wakeupPlaybackThread]; } pthread_mutex_unlock(&playerMutex); @@ -3024,19 +1504,16 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ [self invokeOnPlaybackThread:^ { - pthread_mutex_lock(&queueBuffersMutex); disposeWasRequested = YES; - pthread_mutex_unlock(&queueBuffersMutex); }]; - pthread_mutex_lock(&queueBuffersMutex); - pthread_cond_signal(&queueBufferReadyCondition); - pthread_mutex_unlock(&queueBuffersMutex); + pthread_mutex_lock(&playerMutex); + pthread_cond_signal(&playerThreadReadyCondition); + pthread_mutex_unlock(&playerMutex); pthread_mutex_lock(&mainThreadSyncCallMutex); pthread_cond_signal(&mainThreadSyncCallReadyCondition); pthread_mutex_unlock(&mainThreadSyncCallMutex); - CFRunLoopStop([runLoop getCFRunLoop]); } @@ -3048,14 +1525,24 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ } } +-(BOOL) muted +{ + return self->muted; +} + +-(void) setMuted:(BOOL)value +{ + self->muted = value; +} + -(void) mute { - AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, 0); + self.muted = YES; } -(void) unmute { - AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, 1); + self.muted = NO; } -(void) dispose @@ -3084,77 +1571,1016 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ return retval; } -#pragma mark Metering - --(void) setMeteringEnabled:(BOOL)value +static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* classDesc) { - if (!audioQueue) +#if TARGET_OS_IPHONE + UInt32 size; + + if (AudioFormatGetPropertyInfo(kAudioFormatProperty_Decoders, sizeof(formatId), &formatId, &size) != 0) { - meteringEnabled = value; + return NO; + } + + UInt32 decoderCount = size / sizeof(AudioClassDescription); + AudioClassDescription encoderDescriptions[decoderCount]; + + if (AudioFormatGetProperty(kAudioFormatProperty_Decoders, sizeof(formatId), &formatId, &size, encoderDescriptions) != 0) + { + return NO; + } + + for (UInt32 i = 0; i < decoderCount; ++i) + { + if (encoderDescriptions[i].mManufacturer == kAppleHardwareAudioCodecManufacturer) + { + *classDesc = encoderDescriptions[i]; + + return YES; + } + } +#endif + + return NO; +} + +-(void) destroyAudioConverter +{ + if (audioConverterRef) + { + AudioConverterDispose(audioConverterRef); + + audioConverterRef = nil; + } +} + +-(void) createAudioConverter:(AudioStreamBasicDescription*)asbd +{ + OSStatus status; + Boolean writable; + UInt32 cookieSize; + + if (memcmp(asbd, &audioConverterAudioStreamBasicDescription, sizeof(AudioStreamBasicDescription)) == 0) + { + AudioConverterReset(audioConverterRef); + + return; + } + + [self destroyAudioConverter]; + + AudioClassDescription classDesc; + + if (GetHardwareCodecClassDesc(asbd->mFormatID, &classDesc)) + { + AudioConverterNewSpecific(asbd, &canonicalAudioStreamBasicDescription, 1, &classDesc, &audioConverterRef); + } + + if (!audioConverterRef) + { + status = AudioConverterNew(asbd, &canonicalAudioStreamBasicDescription, &audioConverterRef); + + if (status) + { + [self unexpectedError:STKAudioPlayerErrorAudioSystemError]; + + return; + } + } + + audioConverterAudioStreamBasicDescription = *asbd; + + status = AudioFileStreamGetPropertyInfo(audioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, &writable); + + if (!status) + { + void* cookieData = alloca(cookieSize); + + status = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, cookieData); + + if (status) + { + return; + } + + status = AudioConverterSetProperty(audioConverterRef, kAudioConverterDecompressionMagicCookie, cookieSize, &cookieData); + + if (status) + { + return; + } + } +} + +-(void) createAudioUnit +{ + pthread_mutex_lock(&playerMutex); + + OSStatus status; + AudioComponentDescription desc; + + desc.componentType = kAudioUnitType_Output; +#if TARGET_OS_IPHONE + desc.componentSubType = kAudioUnitSubType_RemoteIO; +#else + desc.componentSubType = kAudioUnitSubType_DefaultOutput; +#endif + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + + AudioComponent component = AudioComponentFindNext(NULL, &desc); + + status = AudioComponentInstanceNew(component, &audioUnit); + + if (status) + { + [self unexpectedError:STKAudioPlayerErrorAudioSystemError]; return; } - UInt32 on = value ? 1 : 0; - OSStatus error = AudioQueueSetProperty(audioQueue, kAudioQueueProperty_EnableLevelMetering, &on, sizeof(on)); +#if TARGET_OS_IPHONE + UInt32 flag = 1; + status = AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag)); - if (error) + if (status) { - meteringEnabled = NO; - } - else - { - meteringEnabled = YES; - } -} - --(BOOL) meteringEnabled -{ - return meteringEnabled; -} - --(void) updateMeters -{ - if (!meteringEnabled) - { - NSAssert(NO, @"Metering is not enabled. Make sure to set meteringEnabled = YES."); - } - - UInt32 channels = currentAudioStreamBasicDescription.mChannelsPerFrame; - - if (numberOfChannels != channels) - { - numberOfChannels = channels; + [self unexpectedError:STKAudioPlayerErrorAudioSystemError]; - if (levelMeterState) free(levelMeterState); + return; + } +#endif + + status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &canonicalAudioStreamBasicDescription, sizeof(canonicalAudioStreamBasicDescription)); + + if (status) + { + [self unexpectedError:STKAudioPlayerErrorAudioSystemError]; + + return; + } + + AURenderCallbackStruct callbackStruct; + + callbackStruct.inputProc = OutputRenderCallback; + callbackStruct.inputProcRefCon = (__bridge void*)self; + + status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, kOutputBus, &callbackStruct, sizeof(callbackStruct)); + + if (status) + { + [self unexpectedError:STKAudioPlayerErrorAudioSystemError]; + + return; + } + + status = AudioUnitInitialize(audioUnit); + + if (status) + { + [self unexpectedError:STKAudioPlayerErrorAudioSystemError]; + + return; + } + + pthread_mutex_unlock(&playerMutex); +} + +-(BOOL) startAudioUnit +{ + OSStatus status; + + [self resetPcmBuffers]; + + status = AudioOutputUnitStart(audioUnit); + + if (status) + { + [self unexpectedError:STKAudioPlayerErrorAudioSystemError]; + + return NO; + } + + return YES; +} + +-(void) stopAudioUnitWithReason:(STKAudioPlayerStopReason)stopReasonIn +{ + OSStatus status; + + if (!audioUnit) + { + stopReason = stopReasonIn; + self.internalState = STKAudioPlayerInternalStateStopped; + + return; + } + + status = AudioOutputUnitStop(audioUnit); + + [self resetPcmBuffers]; + + if (status) + { + [self unexpectedError:STKAudioPlayerErrorAudioSystemError]; + + return; + } + + stopReason = stopReasonIn; + self.internalState = STKAudioPlayerInternalStateStopped; +} + +typedef struct +{ + BOOL done; + UInt32 numberOfPackets; + AudioBuffer audioBuffer; + AudioStreamPacketDescription* packetDescriptions; +} +AudioConvertInfo; + +OSStatus AudioConverterCallback(AudioConverterRef inAudioConverter, UInt32* ioNumberDataPackets, AudioBufferList* ioData, AudioStreamPacketDescription **outDataPacketDescription, void* inUserData) +{ + AudioConvertInfo* convertInfo = (AudioConvertInfo*)inUserData; + + if (convertInfo->done) + { + ioNumberDataPackets = 0; + + return 100; + } + + ioData->mNumberBuffers = 1; + ioData->mBuffers[0] = convertInfo->audioBuffer; + + if (outDataPacketDescription) + { + *outDataPacketDescription = convertInfo->packetDescriptions; + } + + *ioNumberDataPackets = convertInfo->numberOfPackets; + convertInfo->done = YES; + + return 0; +} + +-(void) handleAudioPackets:(const void*)inputData numberBytes:(UInt32)numberBytes numberPackets:(UInt32)numberPackets packetDescriptions:(AudioStreamPacketDescription*)packetDescriptionsIn +{ + if (currentlyReadingEntry == nil) + { + return; + } + + if (!currentlyReadingEntry->parsedHeader) + { + return; + } + + if (seekToTimeWasRequested || disposeWasRequested) + { + return; + } + + if (audioConverterRef == nil) + { + return; + } + + discontinuous = NO; + + OSStatus status; + + AudioConvertInfo convertInfo; + + convertInfo.done = NO; + convertInfo.numberOfPackets = numberPackets; + convertInfo.packetDescriptions = packetDescriptionsIn; + convertInfo.audioBuffer.mData = (void *)inputData; + convertInfo.audioBuffer.mDataByteSize = numberBytes; + convertInfo.audioBuffer.mNumberChannels = audioConverterAudioStreamBasicDescription.mChannelsPerFrame; + + if (packetDescriptionsIn && currentlyReadingEntry->processedPacketsCount < STK_MAX_COMPRESSED_PACKETS_FOR_BITRATE_CALCULATION) + { + int count = MIN(numberPackets, STK_MAX_COMPRESSED_PACKETS_FOR_BITRATE_CALCULATION - currentlyReadingEntry->processedPacketsCount); + + for (int i = 0; i < count; i++) { - levelMeterState = malloc(sizeof(AudioQueueLevelMeterState) * numberOfChannels); + SInt64 packetSize; + + packetSize = packetDescriptionsIn[i].mDataByteSize; + + OSAtomicAdd32((int32_t)packetSize, ¤tlyReadingEntry->processedPacketsSizeTotal); + OSAtomicIncrement32(¤tlyReadingEntry->processedPacketsCount); } } - UInt32 sizeofMeters = (UInt32)(sizeof(AudioQueueLevelMeterState) * numberOfChannels); + while (true) + { + OSSpinLockLock(&pcmBufferSpinLock); + UInt32 used = pcmBufferUsedFrameCount; + UInt32 start = pcmBufferFrameStartIndex; + UInt32 end = (pcmBufferFrameStartIndex + pcmBufferUsedFrameCount) % pcmBufferTotalFrameCount; + UInt32 framesLeftInsideBuffer = pcmBufferTotalFrameCount - used; + OSSpinLockUnlock(&pcmBufferSpinLock); + + if (framesLeftInsideBuffer == 0) + { + pthread_mutex_lock(&playerMutex); + + while (true) + { + OSSpinLockLock(&pcmBufferSpinLock); + used = pcmBufferUsedFrameCount; + start = pcmBufferFrameStartIndex; + end = (pcmBufferFrameStartIndex + pcmBufferUsedFrameCount) % pcmBufferTotalFrameCount; + framesLeftInsideBuffer = pcmBufferTotalFrameCount - used; + OSSpinLockUnlock(&pcmBufferSpinLock); + + if (framesLeftInsideBuffer > 0) + { + break; + } + + if (disposeWasRequested + || seekToTimeWasRequested + || self.internalState == STKAudioPlayerInternalStateStopped + || self.internalState == STKAudioPlayerInternalStateDisposed + || self.internalState == STKAudioPlayerInternalStatePendingNext) + { + pthread_mutex_unlock(&playerMutex); + + return; + } + + waiting = YES; + + pthread_cond_wait(&playerThreadReadyCondition, &playerMutex); + + waiting = NO; + } + + pthread_mutex_unlock(&playerMutex); + } + + AudioBuffer* localPcmAudioBuffer; + AudioBufferList localPcmBufferList; + + localPcmBufferList.mNumberBuffers = 1; + localPcmAudioBuffer = &localPcmBufferList.mBuffers[0]; + + if (end >= start) + { + UInt32 framesAdded = 0; + UInt32 framesToDecode = pcmBufferTotalFrameCount - end; + + localPcmAudioBuffer->mData = pcmAudioBuffer->mData + (end * pcmBufferFrameSizeInBytes); + localPcmAudioBuffer->mDataByteSize = framesToDecode * pcmBufferFrameSizeInBytes; + localPcmAudioBuffer->mNumberChannels = pcmAudioBuffer->mNumberChannels; + + status = AudioConverterFillComplexBuffer(audioConverterRef, AudioConverterCallback, (void*)&convertInfo, &framesToDecode, &localPcmBufferList, NULL); + + framesAdded = framesToDecode; + + if (status == 100) + { + OSSpinLockLock(&pcmBufferSpinLock); + pcmBufferUsedFrameCount += framesAdded; + OSSpinLockUnlock(&pcmBufferSpinLock); + + OSSpinLockLock(¤tlyReadingEntry->spinLock); + currentlyReadingEntry->framesQueued += framesAdded; + OSSpinLockUnlock(¤tlyReadingEntry->spinLock); + + return; + } + else if (status != 0) + { + [self unexpectedError:STKAudioPlayerErrorCodecError]; + + return; + } + + framesToDecode = start; + + if (framesToDecode == 0) + { + OSSpinLockLock(&pcmBufferSpinLock); + pcmBufferUsedFrameCount += framesAdded; + OSSpinLockUnlock(&pcmBufferSpinLock); + + OSSpinLockLock(¤tlyReadingEntry->spinLock); + currentlyReadingEntry->framesQueued += framesAdded; + OSSpinLockUnlock(¤tlyReadingEntry->spinLock); + + continue; + } + + localPcmAudioBuffer->mData = pcmAudioBuffer->mData; + localPcmAudioBuffer->mDataByteSize = framesToDecode * pcmBufferFrameSizeInBytes; + localPcmAudioBuffer->mNumberChannels = pcmAudioBuffer->mNumberChannels; + + status = AudioConverterFillComplexBuffer(audioConverterRef, AudioConverterCallback, (void*)&convertInfo, &framesToDecode, &localPcmBufferList, NULL); + + framesAdded += framesToDecode; + + if (status == 100) + { + OSSpinLockLock(&pcmBufferSpinLock); + pcmBufferUsedFrameCount += framesAdded; + OSSpinLockUnlock(&pcmBufferSpinLock); + + OSSpinLockLock(¤tlyReadingEntry->spinLock); + currentlyReadingEntry->framesQueued += framesAdded; + OSSpinLockUnlock(¤tlyReadingEntry->spinLock); + + return; + } + else if (status == 0) + { + OSSpinLockLock(&pcmBufferSpinLock); + pcmBufferUsedFrameCount += framesAdded; + OSSpinLockUnlock(&pcmBufferSpinLock); + + OSSpinLockLock(¤tlyReadingEntry->spinLock); + currentlyReadingEntry->framesQueued += framesAdded; + OSSpinLockUnlock(¤tlyReadingEntry->spinLock); + + continue; + } + else if (status != 0) + { + [self unexpectedError:STKAudioPlayerErrorCodecError]; + + return; + } + } + else + { + UInt32 framesAdded = 0; + UInt32 framesToDecode = start - end; + + localPcmAudioBuffer->mData = pcmAudioBuffer->mData + (end * pcmBufferFrameSizeInBytes); + localPcmAudioBuffer->mDataByteSize = framesToDecode * pcmBufferFrameSizeInBytes; + localPcmAudioBuffer->mNumberChannels = pcmAudioBuffer->mNumberChannels; + + status = AudioConverterFillComplexBuffer(audioConverterRef, AudioConverterCallback, (void*)&convertInfo, &framesToDecode, &localPcmBufferList, NULL); + + framesAdded = framesToDecode; + + if (status == 100) + { + OSSpinLockLock(&pcmBufferSpinLock); + pcmBufferUsedFrameCount += framesAdded; + OSSpinLockUnlock(&pcmBufferSpinLock); + + OSSpinLockLock(¤tlyReadingEntry->spinLock); + currentlyReadingEntry->framesQueued += framesAdded; + OSSpinLockUnlock(¤tlyReadingEntry->spinLock); + + return; + } + else if (status == 0) + { + OSSpinLockLock(&pcmBufferSpinLock); + pcmBufferUsedFrameCount += framesAdded; + OSSpinLockUnlock(&pcmBufferSpinLock); + + OSSpinLockLock(¤tlyReadingEntry->spinLock); + currentlyReadingEntry->framesQueued += framesAdded; + OSSpinLockUnlock(¤tlyReadingEntry->spinLock); + + continue; + } + else if (status != 0) + { + [self unexpectedError:STKAudioPlayerErrorCodecError]; + + return; + } + } + } +} + +static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData) +{ + STKAudioPlayer* audioPlayer = (__bridge STKAudioPlayer*)inRefCon; + + OSSpinLockLock(&audioPlayer->pcmBufferSpinLock); - AudioQueueGetProperty(audioQueue, kAudioQueueProperty_CurrentLevelMeterDB, levelMeterState, &sizeofMeters); + BOOL waitForBuffer = NO; + BOOL muted = audioPlayer->muted; + STKQueueEntry* entry = audioPlayer->currentlyPlayingEntry; + AudioBuffer* audioBuffer = audioPlayer->pcmAudioBuffer; + UInt32 frameSizeInBytes = audioPlayer->pcmBufferFrameSizeInBytes; + UInt32 used = audioPlayer->pcmBufferUsedFrameCount; + UInt32 start = audioPlayer->pcmBufferFrameStartIndex; + UInt32 end = (audioPlayer->pcmBufferFrameStartIndex + audioPlayer->pcmBufferUsedFrameCount) % audioPlayer->pcmBufferTotalFrameCount; + BOOL signal = audioPlayer->waiting && used < audioPlayer->pcmBufferTotalFrameCount / 2; + NSArray* frameFilters = audioPlayer->frameFilters; + + STKAudioPlayerInternalState state = audioPlayer.internalState; + + if (state == STKAudioPlayerInternalStatePendingNext) + { + OSSpinLockUnlock(&audioPlayer->pcmBufferSpinLock); + + return 0; + } + + if (entry) + { + if (state == STKAudioPlayerInternalStateWaitingForData) + { + int64_t framesRequiredToStartPlaying = audioPlayer->framesRequiredToStartPlaying; + + if (entry->lastFrameQueued >= 0) + { + framesRequiredToStartPlaying = MIN(framesRequiredToStartPlaying, audioPlayer->currentlyPlayingEntry->lastFrameQueued); + } + + if (entry && audioPlayer->currentlyReadingEntry == entry + && entry->framesQueued < framesRequiredToStartPlaying) + { + waitForBuffer = YES; + } + } + else if (state == STKAudioPlayerInternalStateRebuffering) + { + int64_t framesRequiredToStartPlaying = audioPlayer->framesRequiredToStartPlaying; + + if (audioPlayer->currentlyPlayingEntry->lastFrameQueued >= 0) + { + framesRequiredToStartPlaying = MIN(framesRequiredToStartPlaying, entry->lastFrameQueued - entry->framesQueued); + } + + if (used < framesRequiredToStartPlaying) + { + waitForBuffer = YES; + } + } + } + + OSSpinLockUnlock(&audioPlayer->pcmBufferSpinLock); + + UInt32 totalFramesCopied = 0; + + if (used > 0 && !waitForBuffer && entry != nil) + { + if (state == STKAudioPlayerInternalStateWaitingForData) + { + // Starting + } + else if (state == STKAudioPlayerInternalStateRebuffering) + { + // Resuming from buffering + } + + if (end > start) + { + UInt32 framesToCopy = MIN(inNumberFrames, used); + + ioData->mBuffers[0].mNumberChannels = 2; + ioData->mBuffers[0].mDataByteSize = frameSizeInBytes * framesToCopy; + + if (muted) + { + memset(ioData->mBuffers[0].mData, 0, ioData->mBuffers[0].mDataByteSize); + } + else + { + memcpy(ioData->mBuffers[0].mData, audioBuffer->mData + (start * frameSizeInBytes), ioData->mBuffers[0].mDataByteSize); + } + + totalFramesCopied = framesToCopy; + + OSSpinLockLock(&audioPlayer->pcmBufferSpinLock); + audioPlayer->pcmBufferFrameStartIndex = (audioPlayer->pcmBufferFrameStartIndex + totalFramesCopied) % audioPlayer->pcmBufferTotalFrameCount; + audioPlayer->pcmBufferUsedFrameCount -= totalFramesCopied; + OSSpinLockUnlock(&audioPlayer->pcmBufferSpinLock); + } + else + { + UInt32 framesToCopy = MIN(inNumberFrames, audioPlayer->pcmBufferTotalFrameCount - start); + + ioData->mBuffers[0].mNumberChannels = 2; + ioData->mBuffers[0].mDataByteSize = frameSizeInBytes * framesToCopy; + + if (muted) + { + memset(ioData->mBuffers[0].mData, 0, ioData->mBuffers[0].mDataByteSize); + } + else + { + memcpy(ioData->mBuffers[0].mData, audioBuffer->mData + (start * frameSizeInBytes), ioData->mBuffers[0].mDataByteSize); + } + + UInt32 moreFramesToCopy = 0; + UInt32 delta = inNumberFrames - framesToCopy; + + if (delta > 0) + { + moreFramesToCopy = MIN(delta, end); + + ioData->mBuffers[0].mNumberChannels = 2; + ioData->mBuffers[0].mDataByteSize += frameSizeInBytes * moreFramesToCopy; + + if (muted) + { + memset(ioData->mBuffers[0].mData + (framesToCopy * frameSizeInBytes), 0, frameSizeInBytes * moreFramesToCopy); + } + else + { + memcpy(ioData->mBuffers[0].mData + (framesToCopy * frameSizeInBytes), audioBuffer->mData, frameSizeInBytes * moreFramesToCopy); + } + } + + totalFramesCopied = framesToCopy + moreFramesToCopy; + + OSSpinLockLock(&audioPlayer->pcmBufferSpinLock); + audioPlayer->pcmBufferFrameStartIndex = (audioPlayer->pcmBufferFrameStartIndex + totalFramesCopied) % audioPlayer->pcmBufferTotalFrameCount; + audioPlayer->pcmBufferUsedFrameCount -= totalFramesCopied; + OSSpinLockUnlock(&audioPlayer->pcmBufferSpinLock); + } + + audioPlayer.internalState = STKAudioPlayerInternalStatePlaying; + } + + if (totalFramesCopied < inNumberFrames) + { + UInt32 delta = inNumberFrames - totalFramesCopied; + + memset(ioData->mBuffers[0].mData + (totalFramesCopied * frameSizeInBytes), 0, delta * frameSizeInBytes); + + if (!(entry == nil || state == STKAudioPlayerInternalStateWaitingForDataAfterSeek || state == STKAudioPlayerInternalStateWaitingForData || state == STKAudioPlayerInternalStateRebuffering)) + { + // Buffering + + audioPlayer.internalState = STKAudioPlayerInternalStateRebuffering; + } + } + + if (frameFilters) + { + NSUInteger count = frameFilters.count; + AudioStreamBasicDescription asbd = audioPlayer->canonicalAudioStreamBasicDescription; + + for (int i = 0; i < count; i++) + { + STKFrameFilterEntry* entry = [frameFilters objectAtIndex:i]; + + entry->filter(asbd.mChannelsPerFrame, asbd.mBytesPerFrame, inNumberFrames, ioData->mBuffers[0].mData); + } + } + + if (entry == nil) + { + return 0; + } + + OSSpinLockLock(&entry->spinLock); + + int64_t extraFramesPlayedNotAssigned = 0; + int64_t framesPlayedForCurrent = totalFramesCopied; + + if (entry->lastFrameQueued >= 0) + { + framesPlayedForCurrent = MIN(entry->lastFrameQueued - entry->framesPlayed, framesPlayedForCurrent); + } + + entry->framesPlayed += framesPlayedForCurrent; + extraFramesPlayedNotAssigned = totalFramesCopied - framesPlayedForCurrent; + + BOOL lastFramePlayed = entry->framesPlayed == entry->lastFrameQueued; + + OSSpinLockUnlock(&entry->spinLock); + + if (signal || lastFramePlayed) + { + pthread_mutex_lock(&audioPlayer->playerMutex); + + if (lastFramePlayed && entry == audioPlayer->currentlyPlayingEntry) + { + [audioPlayer audioQueueFinishedPlaying:entry]; + + while (extraFramesPlayedNotAssigned > 0) + { + STKQueueEntry* newEntry = audioPlayer->currentlyPlayingEntry; + + if (newEntry != nil) + { + int64_t framesPlayedForCurrent = extraFramesPlayedNotAssigned; + + OSSpinLockLock(&newEntry->spinLock); + + if (newEntry->lastFrameQueued > 0) + { + framesPlayedForCurrent = MIN(newEntry->lastFrameQueued - newEntry->framesPlayed, framesPlayedForCurrent); + } + + newEntry->framesPlayed += framesPlayedForCurrent; + + if (newEntry->framesPlayed == newEntry->lastFrameQueued) + { + OSSpinLockUnlock(&newEntry->spinLock); + + [audioPlayer audioQueueFinishedPlaying:newEntry]; + } + else + { + OSSpinLockUnlock(&newEntry->spinLock); + } + + extraFramesPlayedNotAssigned -= framesPlayedForCurrent; + } + else + { + break; + } + } + } + + pthread_cond_signal(&audioPlayer->playerThreadReadyCondition); + pthread_mutex_unlock(&audioPlayer->playerMutex); + } + + return 0; +} + +-(NSArray*) pendingQueue +{ + pthread_mutex_lock(&playerMutex); + + NSArray* retval; + NSMutableArray* mutableArray = [[NSMutableArray alloc] initWithCapacity:upcomingQueue.count + bufferingQueue.count]; + + [mutableArray skipQueueWithQueue:upcomingQueue]; + [mutableArray skipQueueWithQueue:bufferingQueue]; + + retval = [NSArray arrayWithArray:mutableArray]; + + pthread_mutex_unlock(&playerMutex); + + return retval; +} + +-(NSUInteger) pendingQueueCount +{ + pthread_mutex_lock(&playerMutex); + + NSUInteger retval = upcomingQueue.count + bufferingQueue.count; + + pthread_mutex_unlock(&playerMutex); + + return retval; +} + +-(NSObject*) mostRecentlyQueuedStillPendingItem +{ + pthread_mutex_lock(&playerMutex); + + if (upcomingQueue.count > 0) + { + NSObject* retval = [upcomingQueue objectAtIndex:0]; + + pthread_mutex_unlock(&playerMutex); + + return retval; + } + + if (bufferingQueue.count > 0) + { + NSObject* retval = [bufferingQueue objectAtIndex:0]; + + pthread_mutex_unlock(&playerMutex); + + return retval; + } + + pthread_mutex_unlock(&playerMutex); + + return nil; } -(float) peakPowerInDecibelsForChannel:(NSUInteger)channelNumber { - if (!meteringEnabled || !levelMeterState || (channelNumber > numberOfChannels)) - { - return 0; - } - - return levelMeterState[channelNumber].mPeakPower; + if (channelNumber >= canonicalAudioStreamBasicDescription.mChannelsPerFrame) + { + return 0; + } + + return peakPowerDb[channelNumber]; } -(float) averagePowerInDecibelsForChannel:(NSUInteger)channelNumber { - if (!meteringEnabled || !levelMeterState || (channelNumber > numberOfChannels)) - { - return 0; - } - - return levelMeterState[channelNumber].mAveragePower; + if (channelNumber >= canonicalAudioStreamBasicDescription.mChannelsPerFrame) + { + return 0; + } + + return averagePowerDb[channelNumber]; +} + +-(BOOL) meteringEnabled +{ + return self->meteringEnabled; +} + +#define CALCULATE_METER(channel) \ + Float32 currentFilteredValueOfSampleAmplitude##channel = STK_LOWPASSFILTERTIMESLICE * absoluteValueOfSampleAmplitude##channel + (1.0 - STK_LOWPASSFILTERTIMESLICE) * previousFilteredValueOfSampleAmplitude##channel; \ + previousFilteredValueOfSampleAmplitude##channel = currentFilteredValueOfSampleAmplitude##channel; \ + Float32 sampleDB##channel = 20.0 * log10(currentFilteredValueOfSampleAmplitude##channel) + STK_DBOFFSET; \ + if ((sampleDB##channel == sampleDB##channel) && (sampleDB##channel != -DBL_MAX)) \ + { \ + if(sampleDB##channel > peakValue##channel) \ + { \ + peakValue##channel = sampleDB##channel; \ + } \ + if (sampleDB##channel > -DBL_MAX) \ + { \ + count##channel++; \ + totalValue##channel += sampleDB##channel; \ + } \ + decibels##channel = peakValue##channel; \ + }; + +-(void) setMeteringEnabled:(BOOL)value +{ + if (self->meteringEnabled == value) + { + return; + } + + if (!value) + { + [self removeFrameFilterWithName:@"STKMeteringFilter"]; + self->meteringEnabled = NO; + } + else + { + [self appendFrameFilterWithName:@"STKMeteringFilter" block:^(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UInt32 frameCount, void* frames) + { + SInt16* samples16 = (SInt16*)frames; + SInt32* samples32 = (SInt32*)frames; + UInt32 countLeft = 0; + UInt32 countRight = 0; + Float32 decibelsLeft = STK_DBMIN; + Float32 peakValueLeft = STK_DBMIN; + Float64 totalValueLeft = 0; + Float32 previousFilteredValueOfSampleAmplitudeLeft = 0; + Float32 decibelsRight = STK_DBMIN; + Float32 peakValueRight = STK_DBMIN; + Float64 totalValueRight = 0; + Float32 previousFilteredValueOfSampleAmplitudeRight = 0; + + if (bytesPerFrame / channelsPerFrame == 2) + { + for (int i = 0; i < frameCount * channelsPerFrame; i += channelsPerFrame) + { + Float32 absoluteValueOfSampleAmplitudeLeft = abs(samples16[i]); + Float32 absoluteValueOfSampleAmplitudeRight = abs(samples16[i + 1]); + + CALCULATE_METER(Left); + CALCULATE_METER(Right); + } + } + else if (bytesPerFrame / channelsPerFrame == 4) + { + for (int i = 0; i < frameCount * channelsPerFrame; i += channelsPerFrame) + { + Float32 absoluteValueOfSampleAmplitudeLeft = abs(samples32[i]) / 32768.0; + Float32 absoluteValueOfSampleAmplitudeRight = abs(samples32[i + 1]) / 32768.0; + + CALCULATE_METER(Left); + CALCULATE_METER(Right); + } + } + else + { + return; + } + + peakPowerDb[0] = MIN(MAX(decibelsLeft, -60), 0); + peakPowerDb[1] = MIN(MAX(decibelsRight, -60), 0); + + if (countLeft > 0) + { + averagePowerDb[0] = MIN(MAX(totalValueLeft / frameCount, -60), 0); + } + + if (countRight != 0) + { + averagePowerDb[1] = MIN(MAX(totalValueRight / frameCount, -60), 0); + } + }]; + } +} + +-(NSArray*) frameFilters +{ + return frameFilters; +} + +-(void) appendFrameFilterWithName:(NSString*)name block:(STKFrameFilter)block +{ + [self addFrameFilterWithName:name afterFilterWithName:nil block:block]; +} + +-(void) removeFrameFilterWithName:(NSString*)name +{ + pthread_mutex_lock(&self->playerMutex); + + NSMutableArray* newFrameFilters = [[NSMutableArray alloc] initWithCapacity:frameFilters.count + 1]; + + for (STKFrameFilterEntry* filterEntry in frameFilters) + { + if (![filterEntry->name isEqualToString:name]) + { + [newFrameFilters addObject:filterEntry]; + } + } + + NSArray* replacement = [NSArray arrayWithArray:newFrameFilters]; + + OSSpinLockLock(&pcmBufferSpinLock); + if (newFrameFilters.count > 0) + { + frameFilters = replacement; + } + else + { + frameFilters = nil; + } + OSSpinLockUnlock(&pcmBufferSpinLock); + + pthread_mutex_unlock(&self->playerMutex); +} + +-(void) addFrameFilterWithName:(NSString*)name afterFilterWithName:(NSString*)afterFilterWithName block:(STKFrameFilter)block +{ + pthread_mutex_lock(&self->playerMutex); + + NSMutableArray* newFrameFilters = [[NSMutableArray alloc] initWithCapacity:frameFilters.count + 1]; + + if (afterFilterWithName == nil) + { + [newFrameFilters addObject:[[STKFrameFilterEntry alloc] initWithFilter:block andName:name]]; + [newFrameFilters addObjectsFromArray:frameFilters]; + } + else + { + for (STKFrameFilterEntry* filterEntry in frameFilters) + { + if (afterFilterWithName != nil && [filterEntry->name isEqualToString:afterFilterWithName]) + { + [newFrameFilters addObject:[[STKFrameFilterEntry alloc] initWithFilter:block andName:name]]; + } + + [newFrameFilters addObject:filterEntry]; + } + } + + NSArray* replacement = [NSArray arrayWithArray:newFrameFilters]; + + OSSpinLockLock(&pcmBufferSpinLock); + frameFilters = replacement; + OSSpinLockUnlock(&pcmBufferSpinLock); + + pthread_mutex_unlock(&self->playerMutex); +} + +-(void) addFrameFilter:(STKFrameFilter)frameFilter withName:(NSString*)name afterFilterWithName:(NSString*)afterFilterWithName +{ + pthread_mutex_lock(&self->playerMutex); + + NSMutableArray* newFrameFilters = [[NSMutableArray alloc] initWithCapacity:frameFilters.count + 1]; + + if (afterFilterWithName == nil) + { + [newFrameFilters addObjectsFromArray:frameFilters]; + [newFrameFilters addObject:[[STKFrameFilterEntry alloc] initWithFilter:frameFilter andName:name]]; + } + else + { + for (STKFrameFilterEntry* filterEntry in frameFilters) + { + [newFrameFilters addObject:filterEntry]; + + if (afterFilterWithName != nil && [filterEntry->name isEqualToString:afterFilterWithName]) + { + [newFrameFilters addObject:[[STKFrameFilterEntry alloc] initWithFilter:frameFilter andName:name]]; + } + } + } + + NSArray* replacement = [NSArray arrayWithArray:newFrameFilters]; + + OSSpinLockLock(&pcmBufferSpinLock); + frameFilters = replacement; + OSSpinLockUnlock(&pcmBufferSpinLock); + + pthread_mutex_unlock(&self->playerMutex); } @end diff --git a/StreamingKit/StreamingKit/STKAutoRecoveringHTTPDataSource.h b/StreamingKit/StreamingKit/STKAutoRecoveringHTTPDataSource.h index e08f767..45cc2bc 100644 --- a/StreamingKit/StreamingKit/STKAutoRecoveringHTTPDataSource.h +++ b/StreamingKit/StreamingKit/STKAutoRecoveringHTTPDataSource.h @@ -4,7 +4,7 @@ Created by Thong Nguyen on 16/10/2012. https://github.com/tumtumtum/audjustable - Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved. + Copyright (c) 2012-2014 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: diff --git a/StreamingKit/StreamingKit/STKAutoRecoveringHTTPDataSource.m b/StreamingKit/StreamingKit/STKAutoRecoveringHTTPDataSource.m index cab2c0d..d8c11a0 100644 --- a/StreamingKit/StreamingKit/STKAutoRecoveringHTTPDataSource.m +++ b/StreamingKit/StreamingKit/STKAutoRecoveringHTTPDataSource.m @@ -4,7 +4,7 @@ Created by Thong Nguyen on 16/10/2012. https://github.com/tumtumtum/audjustable - Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved. + Copyright (c) 2012-2014 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: @@ -208,6 +208,8 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach return; } + waitingForNetwork = NO; + NSRunLoop* runLoop = self.innerDataSource.eventsRunLoop; if (runLoop == nil) @@ -226,7 +228,9 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach -(void) dataSourceEof:(STKDataSource*)dataSource { - if ([self position] != [self length]) + NSLog(@"dataSourceEof"); + + if ([self position] < [self length]) { [self processRetryOnError]; diff --git a/StreamingKit/StreamingKit/STKHTTPDataSource.m b/StreamingKit/StreamingKit/STKHTTPDataSource.m index 287e8b5..7874015 100644 --- a/StreamingKit/StreamingKit/STKHTTPDataSource.m +++ b/StreamingKit/StreamingKit/STKHTTPDataSource.m @@ -138,40 +138,61 @@ -(void) dataAvailable { - if (fileLength < 0) - { - CFTypeRef response = CFReadStreamCopyProperty(stream, kCFStreamPropertyHTTPResponseHeader); + if (self.httpStatusCode == 0) + { + CFTypeRef response = CFReadStreamCopyProperty(stream, kCFStreamPropertyHTTPResponseHeader); httpHeaders = (__bridge_transfer NSDictionary*)CFHTTPMessageCopyAllHeaderFields((CFHTTPMessageRef)response); self.httpStatusCode = CFHTTPMessageGetResponseStatusCode((CFHTTPMessageRef)response); CFRelease(response); - - if (self.httpStatusCode == 200) - { - if (seekStart == 0) - { - fileLength = (long long)[[httpHeaders objectForKey:@"Content-Length"] integerValue]; - } - - NSString* contentType = [httpHeaders objectForKey:@"Content-Type"]; - AudioFileTypeID typeIdFromMimeType = [STKHTTPDataSource audioFileTypeHintFromMimeType:contentType]; - - if (typeIdFromMimeType != 0) - { - audioFileTypeHint = typeIdFromMimeType; - } - } - else - { - [self errorOccured]; - - return; - } - } - - [super dataAvailable]; + + if (self.httpStatusCode == 200) + { + if (seekStart == 0) + { + fileLength = (long long)[[httpHeaders objectForKey:@"Content-Length"] integerValue]; + } + + NSString* contentType = [httpHeaders objectForKey:@"Content-Type"]; + AudioFileTypeID typeIdFromMimeType = [STKHTTPDataSource audioFileTypeHintFromMimeType:contentType]; + + if (typeIdFromMimeType != 0) + { + audioFileTypeHint = typeIdFromMimeType; + } + } + else if (self.httpStatusCode == 206) + { + NSString* contentRange = [httpHeaders objectForKey:@"Content-Range"]; + NSArray* components = [contentRange componentsSeparatedByString:@"/"]; + + if (components.count == 2) + { + fileLength = [[components objectAtIndex:1] integerValue]; + } + } + else if (self.httpStatusCode == 416) + { + if (self.length >= 0) + { + seekStart = self.length; + } + + [self eof]; + + return; + } + else if (self.httpStatusCode >= 300) + { + [self errorOccured]; + + return; + } + } + + [super dataAvailable]; } -(long long) position @@ -196,7 +217,7 @@ CFReadStreamClose(stream); CFRelease(stream); } - + NSAssert([NSRunLoop currentRunLoop] == eventsRunLoop, @"Seek called on wrong thread"); stream = 0; @@ -296,6 +317,8 @@ [self reregisterForEvents]; + self.httpStatusCode = 0; + // Open if (!CFReadStreamOpen(stream)) diff --git a/StreamingKit/StreamingKit/STKQueueEntry.h b/StreamingKit/StreamingKit/STKQueueEntry.h new file mode 100644 index 0000000..9033fec --- /dev/null +++ b/StreamingKit/StreamingKit/STKQueueEntry.h @@ -0,0 +1,46 @@ +// +// STKQueueEntry.h +// StreamingKit +// +// Created by Thong Nguyen on 30/01/2014. +// Copyright (c) 2014 Thong Nguyen. All rights reserved. +// + +#import "STKDataSource.h" +#import "libkern/OSAtomic.h" +#import "AudioToolbox/AudioToolbox.h" + +@interface STKQueueEntry : NSObject +{ +@public + OSSpinLock spinLock; + + BOOL parsedHeader; + Float64 sampleRate; + double packetDuration; + UInt64 audioDataOffset; + UInt64 audioDataByteCount; + UInt32 packetBufferSize; + volatile Float64 seekTime; + volatile int64_t framesQueued; + volatile int64_t framesPlayed; + volatile int64_t lastFrameQueued; + volatile int processedPacketsCount; + volatile int processedPacketsSizeTotal; + AudioStreamBasicDescription audioStreamBasicDescription; +} + +@property (readonly) UInt64 audioDataLengthInBytes; +@property (readwrite, retain) NSObject* queueItemId; +@property (readwrite, retain) STKDataSource* dataSource; + +-(id) initWithDataSource:(STKDataSource*)dataSource andQueueItemId:(NSObject*)queueItemId; + +-(void) reset; +-(double) duration; +-(Float64) progressInFrames; +-(double) calculatedBitRate; +-(void) updateAudioDataSource; +-(BOOL) isDefinitelyCompatible:(AudioStreamBasicDescription*)basicDescription; + +@end \ No newline at end of file diff --git a/StreamingKit/StreamingKit/STKQueueEntry.m b/StreamingKit/StreamingKit/STKQueueEntry.m new file mode 100644 index 0000000..caf9c42 --- /dev/null +++ b/StreamingKit/StreamingKit/STKQueueEntry.m @@ -0,0 +1,128 @@ +// +// STKQueueEntry.m +// StreamingKit +// +// Created by Thong Nguyen on 30/01/2014. +// Copyright (c) 2014 Thong Nguyen. All rights reserved. +// + +#import "STKQueueEntry.h" +#import "STKDataSource.h" + +#define STK_BIT_RATE_ESTIMATION_MIN_PACKETS (64) + +@implementation STKQueueEntry + +-(id) initWithDataSource:(STKDataSource*)dataSourceIn andQueueItemId:(NSObject*)queueItemIdIn +{ + if (self = [super init]) + { + self.dataSource = dataSourceIn; + self.queueItemId = queueItemIdIn; + self->lastFrameQueued = -1; + } + + return self; +} + +-(void) reset +{ + OSSpinLockLock(&self->spinLock); + self->framesQueued = 0; + self->framesPlayed = 0; + self->lastFrameQueued = -1; + OSSpinLockUnlock(&self->spinLock); +} + +-(double) calculatedBitRate +{ + double retval; + + if (packetDuration && processedPacketsCount > STK_BIT_RATE_ESTIMATION_MIN_PACKETS) + { + double averagePacketByteSize = processedPacketsSizeTotal / processedPacketsCount; + + retval = averagePacketByteSize / packetDuration * 8; + + return retval; + } + + retval = (audioStreamBasicDescription.mBytesPerFrame * audioStreamBasicDescription.mSampleRate) * 8; + + return retval; +} + +-(void) updateAudioDataSource +{ + if ([self.dataSource conformsToProtocol:@protocol(AudioDataSource)]) + { + double calculatedBitrate = [self calculatedBitRate]; + + id audioDataSource = (id)self.dataSource; + + audioDataSource.averageBitRate = calculatedBitrate; + audioDataSource.audioDataOffset = audioDataOffset; + } +} + +-(double) duration +{ + if (self->sampleRate <= 0) + { + return 0; + } + + UInt64 audioDataLengthInBytes = [self audioDataLengthInBytes]; + + double calculatedBitRate = [self calculatedBitRate]; + + if (calculatedBitRate < 1.0 || self.dataSource.length == 0) + { + return 0; + } + + return audioDataLengthInBytes / (calculatedBitRate / 8); +} + +-(UInt64) audioDataLengthInBytes +{ + if (audioDataByteCount) + { + return audioDataByteCount; + } + else + { + if (!self.dataSource.length) + { + return 0; + } + + return self.dataSource.length - audioDataOffset; + } +} + +-(BOOL) isDefinitelyCompatible:(AudioStreamBasicDescription*)basicDescription +{ + if (self->audioStreamBasicDescription.mSampleRate == 0) + { + return NO; + } + + return (memcmp(&(self->audioStreamBasicDescription), basicDescription, sizeof(*basicDescription)) == 0); +} + +-(Float64) progressInFrames +{ + OSSpinLockLock(&self->spinLock); + Float64 retval = self->seekTime + self->framesPlayed; + OSSpinLockUnlock(&self->spinLock); + + return retval; +} + +-(NSString*) description +{ + return [[self queueItemId] description]; +} + +@end \ No newline at end of file diff --git a/StreamingKit/StreamingKitMac/StreamingKitMac-Prefix.pch b/StreamingKit/StreamingKitMac/StreamingKitMac-Prefix.pch new file mode 100644 index 0000000..926a42b --- /dev/null +++ b/StreamingKit/StreamingKitMac/StreamingKitMac-Prefix.pch @@ -0,0 +1,9 @@ +// +// Prefix header +// +// The contents of this file are implicitly included at the beginning of every source file. +// + +#ifdef __OBJC__ + #import +#endif diff --git a/StreamingKit/StreamingKitMac/StreamingKitMac.h b/StreamingKit/StreamingKitMac/StreamingKitMac.h new file mode 100644 index 0000000..5fd9917 --- /dev/null +++ b/StreamingKit/StreamingKitMac/StreamingKitMac.h @@ -0,0 +1,13 @@ +// +// StreamingKitMac.h +// StreamingKitMac +// +// Created by Thong Nguyen on 02/02/2014. +// Copyright (c) 2014 Thong Nguyen. All rights reserved. +// + +#import + +@interface StreamingKitMac : NSObject + +@end diff --git a/StreamingKit/StreamingKitMac/StreamingKitMac.m b/StreamingKit/StreamingKitMac/StreamingKitMac.m new file mode 100644 index 0000000..9247514 --- /dev/null +++ b/StreamingKit/StreamingKitMac/StreamingKitMac.m @@ -0,0 +1,13 @@ +// +// StreamingKitMac.m +// StreamingKitMac +// +// Created by Thong Nguyen on 02/02/2014. +// Copyright (c) 2014 Thong Nguyen. All rights reserved. +// + +#import "StreamingKitMac.h" + +@implementation StreamingKitMac + +@end diff --git a/StreamingKit/StreamingKitMacTests/StreamingKitMacTests-Info.plist b/StreamingKit/StreamingKitMacTests/StreamingKitMacTests-Info.plist new file mode 100644 index 0000000..552d5b1 --- /dev/null +++ b/StreamingKit/StreamingKitMacTests/StreamingKitMacTests-Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.abstractpath.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/StreamingKit/StreamingKitMacTests/StreamingKitMacTests.m b/StreamingKit/StreamingKitMacTests/StreamingKitMacTests.m new file mode 100644 index 0000000..f0656ae --- /dev/null +++ b/StreamingKit/StreamingKitMacTests/StreamingKitMacTests.m @@ -0,0 +1,34 @@ +// +// StreamingKitMacTests.m +// StreamingKitMacTests +// +// Created by Thong Nguyen on 02/02/2014. +// Copyright (c) 2014 Thong Nguyen. All rights reserved. +// + +#import + +@interface StreamingKitMacTests : XCTestCase + +@end + +@implementation StreamingKitMacTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown +{ + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testExample +{ + XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); +} + +@end diff --git a/StreamingKit/StreamingKitMacTests/en.lproj/InfoPlist.strings b/StreamingKit/StreamingKitMacTests/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/StreamingKit/StreamingKitMacTests/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ +