Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bf13ba318f | |||
| d7d583c3ba | |||
| 7540045361 | |||
| 972ae0e15b | |||
| e3ed6c6dee | |||
| 3398e8c64e | |||
| f99201f54d | |||
| 44b9e7d2d1 | |||
| 7aae2bcb6b | |||
| 5a8068b859 | |||
| 728fc5bb21 | |||
| c4053c964e | |||
| c31df15a43 | |||
| 923baf5b89 | |||
| 9199785202 | |||
| 0618027252 | |||
| 188f880f5a | |||
| ae9cee68f0 | |||
| eeece64417 | |||
| 243dc1f8a2 | |||
| 8c608440ae | |||
| 9aed1b082a | |||
| 6eb149f83a | |||
| 1243dbf0e1 | |||
| a15c2c27ff | |||
| aa441045aa | |||
| 569764d869 | |||
| 511b756694 | |||
| ab0c4d1315 | |||
| 6216abb0ab | |||
| 8c5b4fb298 | |||
| 03e9b8b208 | |||
| 60d48a0682 | |||
| dee6322751 | |||
| 5e3048a7bf | |||
| 13fc64baa2 | |||
| 5e4b500785 |
@@ -230,7 +230,7 @@
|
||||
A1115929188D686000641365 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0500;
|
||||
LastUpgradeCheck = 0510;
|
||||
ORGANIZATIONNAME = "Thong Nguyen";
|
||||
TargetAttributes = {
|
||||
A111594B188D686000641365 = {
|
||||
@@ -332,7 +332,6 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
@@ -377,7 +376,6 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
@@ -415,9 +413,9 @@
|
||||
A111595E188D686000641365 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "ExampleApp/ExampleApp-Prefix.pch";
|
||||
INFOPLIST_FILE = "ExampleApp/ExampleApp-Info.plist";
|
||||
@@ -432,7 +430,6 @@
|
||||
A111595F188D686000641365 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
@@ -449,7 +446,6 @@
|
||||
A1115961188D686000641365 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
|
||||
BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/ExampleApp.app/ExampleApp";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
@@ -472,7 +468,6 @@
|
||||
A1115962188D686000641365 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
|
||||
BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/ExampleApp.app/ExampleApp";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
|
||||
@@ -26,19 +26,22 @@
|
||||
NSError* error;
|
||||
|
||||
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error];
|
||||
|
||||
[[AVAudioSession sharedInstance] setActive:YES error:&error];
|
||||
|
||||
Float32 bufferLength = 0.1;
|
||||
AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration, sizeof(bufferLength), &bufferLength);
|
||||
|
||||
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
|
||||
self.window.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
audioPlayer = [[STKAudioPlayer alloc] initWithOptions:(STKAudioPlayerOptions){ .flushQueueOnSeek = YES, .enableVolumeMixer = NO, .equalizerBandFrequencies = {50, 100, 200, 400, 800, 1600, 2600, 16000} }];
|
||||
audioPlayer.meteringEnabled = YES;
|
||||
audioPlayer.volume = 0.1;
|
||||
audioPlayer.volume = 1;
|
||||
|
||||
AudioPlayerView* audioPlayerView = [[AudioPlayerView alloc] initWithFrame:self.window.bounds];
|
||||
AudioPlayerView* audioPlayerView = [[AudioPlayerView alloc] initWithFrame:self.window.bounds andAudioPlayer:audioPlayer];
|
||||
|
||||
audioPlayerView.delegate = self;
|
||||
audioPlayerView.audioPlayer = audioPlayer;
|
||||
|
||||
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
|
||||
[self becomeFirstResponder];
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
UILabel* label;
|
||||
UILabel* statusLabel;
|
||||
UISlider* slider;
|
||||
UISwitch* enableEqSwitch;
|
||||
UISwitch* repeatSwitch;
|
||||
UIButton* muteButton;
|
||||
UIButton* playButton;
|
||||
@@ -65,4 +66,6 @@
|
||||
@property (readwrite, retain) STKAudioPlayer* audioPlayer;
|
||||
@property (readwrite, unsafe_unretained) id<AudioPlayerViewDelegate> delegate;
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame andAudioPlayer:(STKAudioPlayer*)audioPlayer;
|
||||
|
||||
@end
|
||||
|
||||
@@ -47,12 +47,14 @@
|
||||
@implementation AudioPlayerView
|
||||
@synthesize audioPlayer, delegate;
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
- (id)initWithFrame:(CGRect)frame andAudioPlayer:(STKAudioPlayer*)audioPlayerIn
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
self.audioPlayer = audioPlayerIn;
|
||||
|
||||
CGSize size = CGSizeMake(220, 50);
|
||||
|
||||
playFromHTTPButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
|
||||
@@ -97,7 +99,12 @@
|
||||
|
||||
size = CGSizeMake(80, 50);
|
||||
|
||||
repeatSwitch = [[UISwitch alloc] initWithFrame:CGRectMake((320 - size.width) / 2, frame.size.height * 0.15 + 180, size.width, size.height)];
|
||||
repeatSwitch = [[UISwitch alloc] initWithFrame:CGRectMake(30, frame.size.height * 0.15 + 180, size.width, size.height)];
|
||||
|
||||
enableEqSwitch = [[UISwitch alloc] initWithFrame:CGRectMake(320 - size.width - 30, frame.size.height * 0.15 + 180, size.width, size.height)];
|
||||
enableEqSwitch.on = audioPlayer.equalizerEnabled;
|
||||
|
||||
[enableEqSwitch addTarget:self action:@selector(onEnableEqSwitch) forControlEvents:UIControlEventAllTouchEvents];
|
||||
|
||||
label = [[UILabel alloc] initWithFrame:CGRectMake(0, slider.frame.origin.y + slider.frame.size.height + 10, frame.size.width, 25)];
|
||||
|
||||
@@ -123,6 +130,7 @@
|
||||
[self addSubview:stopButton];
|
||||
[self addSubview:meter];
|
||||
[self addSubview:muteButton];
|
||||
[self addSubview:enableEqSwitch];
|
||||
|
||||
[self setupTimer];
|
||||
[self updateControls];
|
||||
@@ -131,6 +139,11 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void) onEnableEqSwitch
|
||||
{
|
||||
audioPlayer.equalizerEnabled = self->enableEqSwitch.on;
|
||||
}
|
||||
|
||||
-(void) sliderChanged
|
||||
{
|
||||
if (!audioPlayer)
|
||||
|
||||
@@ -40,14 +40,22 @@
|
||||
[[self.window contentView] addSubview:playFromHTTPButton];
|
||||
[[self.window contentView] addSubview:meter];
|
||||
|
||||
audioPlayer = [[STKAudioPlayer alloc] initWithOptions:(STKAudioPlayerOptions){ .enableVolumeMixer = YES, .equalizerBandFrequencies = {0, 50, 100, 200, 400, 800, 1600, 2600, 16000} } ];
|
||||
audioPlayer = [[STKAudioPlayer alloc] initWithOptions:(STKAudioPlayerOptions){ .enableVolumeMixer = NO, .equalizerBandFrequencies = {50, 100, 200, 400, 800, 1600, 2600, 16000} } ];
|
||||
audioPlayer.delegate = self;
|
||||
audioPlayer.meteringEnabled = YES;
|
||||
audioPlayer.volume = 0.1;
|
||||
|
||||
[self performSelector:@selector(test) withObject:nil afterDelay:4];
|
||||
[self performSelector:@selector(test) withObject:nil afterDelay:8];
|
||||
|
||||
[NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(tick:) userInfo:nil repeats:YES];
|
||||
}
|
||||
|
||||
-(void) test
|
||||
{
|
||||
audioPlayer.equalizerEnabled = !audioPlayer.equalizerEnabled;
|
||||
}
|
||||
|
||||
-(void) playFromHTTP
|
||||
{
|
||||
[audioPlayer play:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"];
|
||||
|
||||
@@ -16,6 +16,8 @@ The primary motivation of this project was to decouple the input data sources fr
|
||||
* Optimised for low CPU/battery usage (0% - 1% CPU usage when streaming).
|
||||
* Optimised for linear data sources. Random access sources are required only for seeking.
|
||||
* StreamingKit 0.2.0 uses the AudioUnit API rather than the slower AudioQueues API which allows real-time interception of the raw PCM data for features such as level metering, EQ, etc.
|
||||
* Power metering
|
||||
* Inbuilt equalizer/EQ (iOS 5.0 and above, OSX 10.9 Mavericks and above) with support for dynamically changing/enabling/disabling EQ while playing.
|
||||
* Example apps for iOS and Mac OSX provided.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
A1682FA318B3903900F29FEC /* STKBufferingDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = A1682FA218B3903900F29FEC /* STKBufferingDataSource.m */; };
|
||||
A168C6F118BB67DC003D170D /* STKBufferChunk.m in Sources */ = {isa = PBXBuildFile; fileRef = A168C6F018BB67DC003D170D /* STKBufferChunk.m */; };
|
||||
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 */; };
|
||||
@@ -83,6 +85,10 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
A1682FA118B3903900F29FEC /* STKBufferingDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = STKBufferingDataSource.h; sourceTree = "<group>"; };
|
||||
A1682FA218B3903900F29FEC /* STKBufferingDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STKBufferingDataSource.m; sourceTree = "<group>"; };
|
||||
A168C6EF18BB67DC003D170D /* STKBufferChunk.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = STKBufferChunk.h; sourceTree = "<group>"; };
|
||||
A168C6F018BB67DC003D170D /* STKBufferChunk.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STKBufferChunk.m; sourceTree = "<group>"; };
|
||||
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; };
|
||||
@@ -266,6 +272,10 @@
|
||||
A1E7C4F2188D5E550010896F /* STKAudioPlayer.m */,
|
||||
A1E7C4F3188D5E550010896F /* STKAutoRecoveringHTTPDataSource.h */,
|
||||
A1E7C4F4188D5E550010896F /* STKAutoRecoveringHTTPDataSource.m */,
|
||||
A168C6EF18BB67DC003D170D /* STKBufferChunk.h */,
|
||||
A168C6F018BB67DC003D170D /* STKBufferChunk.m */,
|
||||
A1682FA118B3903900F29FEC /* STKBufferingDataSource.h */,
|
||||
A1682FA218B3903900F29FEC /* STKBufferingDataSource.m */,
|
||||
A1E7C4F5188D5E550010896F /* STKCoreFoundationDataSource.h */,
|
||||
A1E7C4F6188D5E550010896F /* STKCoreFoundationDataSource.m */,
|
||||
A1E7C4F7188D5E550010896F /* STKDataSource.h */,
|
||||
@@ -401,7 +411,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
CLASSPREFIX = STK;
|
||||
LastUpgradeCheck = 0500;
|
||||
LastUpgradeCheck = 0510;
|
||||
ORGANIZATIONNAME = "Thong Nguyen";
|
||||
};
|
||||
buildConfigurationList = A1E7C4C3188D57F50010896F /* Build configuration list for PBXProject "StreamingKit" */;
|
||||
@@ -502,7 +512,9 @@
|
||||
A1BF65D2189A6582004DD08C /* STKQueueEntry.m in Sources */,
|
||||
A1E7C504188D5E550010896F /* STKHTTPDataSource.m in Sources */,
|
||||
A1E7C503188D5E550010896F /* STKDataSourceWrapper.m in Sources */,
|
||||
A1682FA318B3903900F29FEC /* STKBufferingDataSource.m in Sources */,
|
||||
A1E7C502188D5E550010896F /* STKDataSource.m in Sources */,
|
||||
A168C6F118BB67DC003D170D /* STKBufferChunk.m in Sources */,
|
||||
A1BF65D5189A65C6004DD08C /* NSMutableArray+STKAudioPlayer.m in Sources */,
|
||||
A1E7C500188D5E550010896F /* STKAutoRecoveringHTTPDataSource.m in Sources */,
|
||||
);
|
||||
@@ -635,7 +647,6 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
@@ -673,7 +684,6 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
@@ -704,7 +714,6 @@
|
||||
A1E7C4EC188D57F60010896F /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
|
||||
DSTROOT = /tmp/StreamingKit.dst;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -721,7 +730,6 @@
|
||||
A1E7C4ED188D57F60010896F /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
|
||||
DSTROOT = /tmp/StreamingKit.dst;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -738,7 +746,6 @@
|
||||
A1E7C4EF188D57F60010896F /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
"$(inherited)",
|
||||
@@ -759,7 +766,6 @@
|
||||
A1E7C4F0188D57F60010896F /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
"$(inherited)",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -38,7 +38,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <pthread.h>
|
||||
#import "STKDataSource.h"
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
#include "UIKit/UIApplication.h"
|
||||
@@ -62,6 +62,8 @@ typedef enum
|
||||
STKAudioPlayerStopReasonNone = 0,
|
||||
STKAudioPlayerStopReasonEof,
|
||||
STKAudioPlayerStopReasonUserAction,
|
||||
STKAudioPlayerStopReasonPendingNext,
|
||||
STKAudioPlayerStopReasonDisposed,
|
||||
STKAudioPlayerStopReasonError = 0xffff
|
||||
}
|
||||
STKAudioPlayerStopReason;
|
||||
@@ -92,6 +94,8 @@ typedef struct
|
||||
UInt32 bufferSizeInSeconds;
|
||||
/// Number of seconds of decompressed audio is required before playback first starts for each item (Default is 0.5 seconds. Must be larger than bufferSizeInSeconds)
|
||||
Float32 secondsRequiredToStartPlaying;
|
||||
/// Seconds after a seek is performed before data needs to come in (after which the state will change to playing/buffering)
|
||||
Float32 gracePeriodAfterSeekInSeconds;
|
||||
/// Number of seconds of decompressed audio required before playback resumes after a buffer underrun (Default is 5 seconds. Must be larger than bufferSizeinSeconds)
|
||||
Float32 secondsRequiredToStartPlayingAfterBufferUnderun;
|
||||
}
|
||||
@@ -140,6 +144,8 @@ typedef void(^STKFrameFilter)(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UIn
|
||||
@property (readonly) double progress;
|
||||
/// Enables or disables peak and average decibel meteting
|
||||
@property (readwrite) BOOL meteringEnabled;
|
||||
/// Enables or disables the EQ
|
||||
@property (readwrite) BOOL equalizerEnabled;
|
||||
/// 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)
|
||||
|
||||
@@ -54,9 +54,10 @@
|
||||
#define STK_DEFAULT_PCM_BUFFER_SIZE_IN_SECONDS (10)
|
||||
#define STK_DEFAULT_SECONDS_REQUIRED_TO_START_PLAYING (1)
|
||||
#define STK_DEFAULT_SECONDS_REQUIRED_TO_START_PLAYING_AFTER_BUFFER_UNDERRUN (7.5)
|
||||
#define STK_MAX_COMPRESSED_PACKETS_FOR_BITRATE_CALCULATION (2048)
|
||||
#define STK_MAX_COMPRESSED_PACKETS_FOR_BITRATE_CALCULATION (4096)
|
||||
#define STK_DEFAULT_READ_BUFFER_SIZE (64 * 1024)
|
||||
#define STK_DEFAULT_PACKET_BUFFER_SIZE (2048)
|
||||
#define STK_DEFAULT_GRACE_PERIOD_AFTER_SEEK_SECONDS (0.5)
|
||||
|
||||
#define LOGINFO(x) [self logInfo:[NSString stringWithFormat:@"%s %@", sel_getName(_cmd), x]];
|
||||
|
||||
@@ -81,8 +82,19 @@ static void PopulateOptionsWithDefault(STKAudioPlayerOptions* options)
|
||||
{
|
||||
options->secondsRequiredToStartPlayingAfterBufferUnderun = MIN(STK_DEFAULT_SECONDS_REQUIRED_TO_START_PLAYING_AFTER_BUFFER_UNDERRUN, options->bufferSizeInSeconds);
|
||||
}
|
||||
|
||||
if (options->gracePeriodAfterSeekInSeconds == 0)
|
||||
{
|
||||
options->gracePeriodAfterSeekInSeconds = MIN(STK_DEFAULT_GRACE_PERIOD_AFTER_SEEK_SECONDS, options->bufferSizeInSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
#define CHECK_STATUS_AND_REPORT(call) \
|
||||
if ((status = (call))) \
|
||||
{ \
|
||||
[self unexpectedError:STKAudioPlayerErrorAudioSystemError]; \
|
||||
}
|
||||
|
||||
#define CHECK_STATUS_AND_RETURN(call) \
|
||||
if ((status = (call))) \
|
||||
{ \
|
||||
@@ -150,6 +162,8 @@ STKAudioPlayerInternalState;
|
||||
|
||||
#pragma mark STKAudioPlayer
|
||||
|
||||
static UInt32 maxFramesPerSlice = 4096;
|
||||
|
||||
static AudioComponentDescription mixerDescription;
|
||||
static AudioComponentDescription nbandUnitDescription;
|
||||
static AudioComponentDescription outputUnitDescription;
|
||||
@@ -169,8 +183,12 @@ static AudioStreamBasicDescription canonicalAudioStreamBasicDescription;
|
||||
Float32 averagePowerDb[2];
|
||||
|
||||
BOOL meteringEnabled;
|
||||
BOOL equalizerOn;
|
||||
BOOL equalizerEnabled;
|
||||
STKAudioPlayerOptions options;
|
||||
|
||||
NSMutableArray* converterNodes;
|
||||
|
||||
AUGraph audioGraph;
|
||||
AUNode eqNode;
|
||||
AUNode mixerNode;
|
||||
@@ -186,9 +204,11 @@ static AudioStreamBasicDescription canonicalAudioStreamBasicDescription;
|
||||
AudioComponentInstance outputUnit;
|
||||
|
||||
UInt32 eqBandCount;
|
||||
|
||||
int32_t waitingForDataAfterSeekFrameCount;
|
||||
|
||||
UInt32 framesRequiredToStartPlaying;
|
||||
UInt32 framesRequiredToPlayAfterRebuffering;
|
||||
UInt32 framesRequiredBeforeWaitingForDataAfterSeekBecomesPlaying;
|
||||
|
||||
STKQueueEntry* volatile currentlyPlayingEntry;
|
||||
STKQueueEntry* volatile currentlyReadingEntry;
|
||||
@@ -209,6 +229,7 @@ static AudioStreamBasicDescription canonicalAudioStreamBasicDescription;
|
||||
|
||||
AudioStreamBasicDescription audioConverterAudioStreamBasicDescription;
|
||||
|
||||
BOOL deallocating;
|
||||
BOOL discontinuous;
|
||||
NSArray* frameFilters;
|
||||
NSThread* playbackThread;
|
||||
@@ -216,10 +237,8 @@ static AudioStreamBasicDescription canonicalAudioStreamBasicDescription;
|
||||
AudioFileStreamID audioFileStream;
|
||||
NSConditionLock* threadStartedLock;
|
||||
NSConditionLock* threadFinishedCondLock;
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
UIBackgroundTaskIdentifier backgroundTaskId;
|
||||
#endif
|
||||
|
||||
void(^stopBackBackgroundTaskBlock)();
|
||||
|
||||
int32_t seekVersion;
|
||||
OSSpinLock seekLock;
|
||||
@@ -340,13 +359,13 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
stopReason = STKAudioPlayerStopReasonNone;
|
||||
break;
|
||||
case STKAudioPlayerInternalStateRunning:
|
||||
case STKAudioPlayerInternalStatePendingNext:
|
||||
case STKAudioPlayerInternalStateStartingThread:
|
||||
case STKAudioPlayerInternalStatePlaying:
|
||||
case STKAudioPlayerInternalStateWaitingForDataAfterSeek:
|
||||
case STKAudioPlayerInternalStateWaitingForDataAfterSeek:
|
||||
newState = STKAudioPlayerStatePlaying;
|
||||
stopReason = STKAudioPlayerStopReasonNone;
|
||||
break;
|
||||
case STKAudioPlayerInternalStatePendingNext:
|
||||
case STKAudioPlayerInternalStateRebuffering:
|
||||
case STKAudioPlayerInternalStateWaitingForData:
|
||||
newState = STKAudioPlayerStateBuffering;
|
||||
@@ -368,9 +387,11 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
stopReason = STKAudioPlayerStopReasonError;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
OSSpinLockLock(&internalStateLock);
|
||||
|
||||
|
||||
waitingForDataAfterSeekFrameCount = 0;
|
||||
|
||||
if (value == internalState)
|
||||
{
|
||||
OSSpinLockUnlock(&internalStateLock);
|
||||
@@ -392,7 +413,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
|
||||
STKAudioPlayerState previousState = self.state;
|
||||
|
||||
if (newState != previousState)
|
||||
if (newState != previousState && !deallocating)
|
||||
{
|
||||
self.state = newState;
|
||||
|
||||
@@ -444,11 +465,13 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
options = optionsIn;
|
||||
|
||||
self->volume = 1.0;
|
||||
self->equalizerEnabled = optionsIn.equalizerBandFrequencies[0] != 0;
|
||||
|
||||
PopulateOptionsWithDefault(&options);
|
||||
|
||||
framesRequiredToStartPlaying = canonicalAudioStreamBasicDescription.mSampleRate * options.secondsRequiredToStartPlaying;
|
||||
framesRequiredToPlayAfterRebuffering = canonicalAudioStreamBasicDescription.mSampleRate * options.secondsRequiredToStartPlayingAfterBufferUnderun;
|
||||
framesRequiredBeforeWaitingForDataAfterSeekBecomesPlaying = canonicalAudioStreamBasicDescription.mSampleRate * options.gracePeriodAfterSeekInSeconds;
|
||||
|
||||
pcmAudioBuffer = &pcmAudioBufferList.mBuffers[0];
|
||||
|
||||
@@ -489,39 +512,57 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void) dealloc
|
||||
-(void) destroyAudioResources
|
||||
{
|
||||
if (currentlyReadingEntry)
|
||||
if (currentlyReadingEntry)
|
||||
{
|
||||
currentlyReadingEntry.dataSource.delegate = nil;
|
||||
[currentlyReadingEntry.dataSource unregisterForEvents];
|
||||
|
||||
currentlyReadingEntry = nil;
|
||||
}
|
||||
|
||||
if (currentlyPlayingEntry)
|
||||
{
|
||||
currentlyPlayingEntry.dataSource.delegate = nil;
|
||||
[currentlyReadingEntry.dataSource unregisterForEvents];
|
||||
currentlyPlayingEntry = nil;
|
||||
}
|
||||
|
||||
[self stopAudioUnitWithReason:STKAudioPlayerStopReasonEof];
|
||||
|
||||
[self stopAudioUnitWithReason:STKAudioPlayerStopReasonDisposed];
|
||||
|
||||
[self clearQueue];
|
||||
|
||||
if (audioFileStream)
|
||||
{
|
||||
AudioFileStreamClose(audioFileStream);
|
||||
audioFileStream = nil;
|
||||
}
|
||||
|
||||
if (audioConverterRef)
|
||||
{
|
||||
AudioConverterDispose(audioConverterRef);
|
||||
audioConverterRef = nil;
|
||||
}
|
||||
|
||||
if (outputUnit)
|
||||
if (audioGraph)
|
||||
{
|
||||
AudioComponentInstanceDispose(outputUnit);
|
||||
AUGraphUninitialize(audioGraph);
|
||||
AUGraphClose(audioGraph);
|
||||
DisposeAUGraph(audioGraph);
|
||||
|
||||
audioGraph = nil;
|
||||
}
|
||||
|
||||
AUGraphClose(audioGraph);
|
||||
}
|
||||
|
||||
-(void) dealloc
|
||||
{
|
||||
NSLog(@"STKAudioPlayer dealloc");
|
||||
|
||||
deallocating = YES;
|
||||
|
||||
[self destroyAudioResources];
|
||||
|
||||
pthread_mutex_destroy(&playerMutex);
|
||||
pthread_mutex_destroy(&mainThreadSyncCallMutex);
|
||||
pthread_cond_destroy(&playerThreadReadyCondition);
|
||||
@@ -533,37 +574,33 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
-(void) startSystemBackgroundTask
|
||||
{
|
||||
#if TARGET_OS_IPHONE
|
||||
pthread_mutex_lock(&playerMutex);
|
||||
__block UIBackgroundTaskIdentifier backgroundTaskId = UIBackgroundTaskInvalid;
|
||||
|
||||
backgroundTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^
|
||||
{
|
||||
[[UIApplication sharedApplication] endBackgroundTask:backgroundTaskId];
|
||||
backgroundTaskId = UIBackgroundTaskInvalid;
|
||||
}];
|
||||
|
||||
stopBackBackgroundTaskBlock = [^
|
||||
{
|
||||
if (backgroundTaskId != UIBackgroundTaskInvalid)
|
||||
{
|
||||
pthread_mutex_unlock(&playerMutex);
|
||||
|
||||
return;
|
||||
[[UIApplication sharedApplication] endBackgroundTask:backgroundTaskId];
|
||||
backgroundTaskId = UIBackgroundTaskInvalid;
|
||||
}
|
||||
|
||||
backgroundTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^
|
||||
{
|
||||
[self stopSystemBackgroundTask];
|
||||
}];
|
||||
}
|
||||
pthread_mutex_unlock(&playerMutex);
|
||||
} copy];
|
||||
#endif
|
||||
}
|
||||
|
||||
-(void) stopSystemBackgroundTask
|
||||
{
|
||||
#if TARGET_OS_IPHONE
|
||||
pthread_mutex_lock(&playerMutex);
|
||||
if (stopBackBackgroundTaskBlock != NULL)
|
||||
{
|
||||
if (backgroundTaskId != UIBackgroundTaskInvalid)
|
||||
{
|
||||
[[UIApplication sharedApplication] endBackgroundTask:backgroundTaskId];
|
||||
|
||||
backgroundTaskId = UIBackgroundTaskInvalid;
|
||||
}
|
||||
stopBackBackgroundTaskBlock();
|
||||
stopBackBackgroundTaskBlock = NULL;
|
||||
}
|
||||
pthread_mutex_unlock(&playerMutex);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -662,7 +699,10 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
{
|
||||
LOGINFO(([NSString stringWithFormat:@"Playing: %@", [queueItemId description]]));
|
||||
|
||||
[self startSystemBackgroundTask];
|
||||
if (![self audioGraphIsRunning])
|
||||
{
|
||||
[self startSystemBackgroundTask];
|
||||
}
|
||||
|
||||
[self clearQueue];
|
||||
|
||||
@@ -699,7 +739,10 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
{
|
||||
pthread_mutex_lock(&playerMutex);
|
||||
{
|
||||
[self startSystemBackgroundTask];
|
||||
if (![self audioGraphIsRunning])
|
||||
{
|
||||
[self startSystemBackgroundTask];
|
||||
}
|
||||
|
||||
[upcomingQueue enqueue:[[STKQueueEntry alloc] initWithDataSource:dataSourceIn andQueueItemId:queueItemId]];
|
||||
}
|
||||
@@ -729,8 +772,6 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
currentlyReadingEntry->parsedHeader = YES;
|
||||
currentlyReadingEntry->audioDataOffset = offset;
|
||||
|
||||
[currentlyReadingEntry updateAudioDataSource];
|
||||
|
||||
break;
|
||||
}
|
||||
case kAudioFileStreamProperty_FileFormat:
|
||||
@@ -782,8 +823,6 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
entryToUpdate->packetBufferSize = packetBufferSize;
|
||||
}
|
||||
|
||||
[entryToUpdate updateAudioDataSource];
|
||||
|
||||
[self createAudioConverter:¤tlyReadingEntry->audioStreamBasicDescription];
|
||||
|
||||
pthread_mutex_unlock(&playerMutex);
|
||||
@@ -800,8 +839,6 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
|
||||
currentlyReadingEntry->audioDataByteCount = audioDataByteCount;
|
||||
|
||||
[currentlyReadingEntry updateAudioDataSource];
|
||||
|
||||
break;
|
||||
}
|
||||
case kAudioFileStreamProperty_ReadyToProducePackets:
|
||||
@@ -933,7 +970,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
OSSpinLockLock(&entry->spinLock);
|
||||
double retval = entry->seekTime + (entry->framesPlayed / canonicalAudioStreamBasicDescription.mSampleRate);
|
||||
OSSpinLockUnlock(&entry->spinLock);
|
||||
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -1161,6 +1198,8 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
pthread_mutex_unlock(&mainThreadSyncCallMutex);
|
||||
});
|
||||
|
||||
pthread_mutex_lock(&mainThreadSyncCallMutex);
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (disposeWasRequested)
|
||||
@@ -1173,10 +1212,10 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
break;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&mainThreadSyncCallMutex);
|
||||
pthread_cond_wait(&mainThreadSyncCallReadyCondition, &mainThreadSyncCallMutex);
|
||||
pthread_mutex_unlock(&mainThreadSyncCallMutex);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&mainThreadSyncCallMutex);
|
||||
}
|
||||
|
||||
-(void) playbackThreadQueueMainThreadSyncBlock:(void(^)())block
|
||||
@@ -1215,7 +1254,13 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
{
|
||||
pthread_mutex_lock(&playerMutex);
|
||||
{
|
||||
if (self.internalState == STKAudioPlayerInternalStatePaused)
|
||||
if (disposeWasRequested)
|
||||
{
|
||||
pthread_mutex_unlock(&playerMutex);
|
||||
|
||||
return NO;
|
||||
}
|
||||
else if (self.internalState == STKAudioPlayerInternalStatePaused)
|
||||
{
|
||||
pthread_mutex_unlock(&playerMutex);
|
||||
|
||||
@@ -1274,14 +1319,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
}
|
||||
}
|
||||
|
||||
if (disposeWasRequested)
|
||||
{
|
||||
pthread_mutex_unlock(&playerMutex);
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (currentlyPlayingEntry && currentlyPlayingEntry->parsedHeader)
|
||||
if (currentlyPlayingEntry && currentlyPlayingEntry->parsedHeader && [currentlyPlayingEntry calculatedBitRate] > 0.0)
|
||||
{
|
||||
int32_t originalSeekVersion;
|
||||
BOOL originalSeekToTimeRequested;
|
||||
@@ -1320,8 +1358,8 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
{
|
||||
playbackThreadRunLoop = [NSRunLoop currentRunLoop];
|
||||
NSThread.currentThread.threadPriority = 1;
|
||||
|
||||
[threadStartedLock lockWhenCondition:0];
|
||||
|
||||
[threadStartedLock lockWhenCondition:0];
|
||||
[threadStartedLock unlockWithCondition:1];
|
||||
|
||||
[playbackThreadRunLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
|
||||
@@ -1343,6 +1381,9 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
disposeWasRequested = NO;
|
||||
seekToTimeWasRequested = NO;
|
||||
|
||||
[currentlyReadingEntry.dataSource unregisterForEvents];
|
||||
[currentlyPlayingEntry.dataSource unregisterForEvents];
|
||||
|
||||
currentlyReadingEntry.dataSource.delegate = nil;
|
||||
currentlyPlayingEntry.dataSource.delegate = nil;
|
||||
|
||||
@@ -1355,6 +1396,8 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
|
||||
self.internalState = STKAudioPlayerInternalStateDisposed;
|
||||
|
||||
playbackThreadRunLoop = nil;
|
||||
|
||||
[threadFinishedCondLock lock];
|
||||
[threadFinishedCondLock unlockWithCondition:1];
|
||||
}
|
||||
@@ -1372,7 +1415,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
return;
|
||||
}
|
||||
|
||||
long long seekByteOffset = currentEntry->audioDataOffset + (requestedSeekTime / self.duration) * (currentlyReadingEntry.audioDataLengthInBytes);
|
||||
SInt64 seekByteOffset = currentEntry->audioDataOffset + (requestedSeekTime / self.duration) * (currentlyReadingEntry.audioDataLengthInBytes);
|
||||
|
||||
if (seekByteOffset > currentEntry.dataSource.length - (2 * currentEntry->packetBufferSize))
|
||||
{
|
||||
@@ -1410,10 +1453,11 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
AudioConverterReset(audioConverterRef);
|
||||
}
|
||||
|
||||
[currentEntry updateAudioDataSource];
|
||||
[currentEntry reset];
|
||||
[currentEntry.dataSource seekToOffset:seekByteOffset];
|
||||
|
||||
self->waitingForDataAfterSeekFrameCount = 0;
|
||||
|
||||
self.internalState = STKAudioPlayerInternalStateWaitingForDataAfterSeek;
|
||||
|
||||
if (audioGraph)
|
||||
@@ -1455,6 +1499,18 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
SInt64 position = currentlyReadingEntry.dataSource.position;
|
||||
|
||||
[currentlyReadingEntry.dataSource seekToOffset:position];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int flags = 0;
|
||||
|
||||
if (discontinuous)
|
||||
@@ -1686,17 +1742,19 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
[self invokeOnPlaybackThread:^
|
||||
{
|
||||
disposeWasRequested = YES;
|
||||
|
||||
CFRunLoopStop(CFRunLoopGetCurrent());
|
||||
}];
|
||||
|
||||
pthread_mutex_lock(&playerMutex);
|
||||
disposeWasRequested = YES;
|
||||
pthread_cond_signal(&playerThreadReadyCondition);
|
||||
pthread_mutex_unlock(&playerMutex);
|
||||
|
||||
pthread_mutex_lock(&mainThreadSyncCallMutex);
|
||||
disposeWasRequested = YES;
|
||||
pthread_cond_signal(&mainThreadSyncCallReadyCondition);
|
||||
pthread_mutex_unlock(&mainThreadSyncCallMutex);
|
||||
|
||||
CFRunLoopStop([runLoop getCFRunLoop]);
|
||||
}
|
||||
|
||||
if (wait)
|
||||
@@ -1704,6 +1762,12 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
[threadFinishedCondLock lockWhenCondition:1];
|
||||
[threadFinishedCondLock unlockWithCondition:0];
|
||||
}
|
||||
|
||||
[self destroyAudioResources];
|
||||
|
||||
runLoop = nil;
|
||||
playbackThread = nil;
|
||||
playbackThreadRunLoop = nil;
|
||||
}
|
||||
|
||||
-(BOOL) muted
|
||||
@@ -1863,6 +1927,10 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
|
||||
UInt32 flag = 1;
|
||||
|
||||
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(outputUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag)));
|
||||
|
||||
flag = 0;
|
||||
|
||||
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(outputUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag)));
|
||||
#endif
|
||||
|
||||
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &canonicalAudioStreamBasicDescription, sizeof(canonicalAudioStreamBasicDescription)));
|
||||
@@ -1879,6 +1947,7 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
|
||||
|
||||
CHECK_STATUS_AND_RETURN(AUGraphAddNode(audioGraph, &mixerDescription, &mixerNode));
|
||||
CHECK_STATUS_AND_RETURN(AUGraphNodeInfo(audioGraph, mixerNode, &mixerDescription, &mixerUnit));
|
||||
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(mixerUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maxFramesPerSlice, sizeof(maxFramesPerSlice)));
|
||||
|
||||
UInt32 busCount = 1;
|
||||
|
||||
@@ -1901,9 +1970,10 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
CHECK_STATUS_AND_RETURN(AUGraphAddNode(audioGraph, &nbandUnitDescription, &eqNode));
|
||||
CHECK_STATUS_AND_RETURN(AUGraphNodeInfo(audioGraph, eqNode, NULL, &eqUnit));
|
||||
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(eqUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maxFramesPerSlice, sizeof(maxFramesPerSlice)));
|
||||
|
||||
while (self->options.equalizerBandFrequencies[eqBandCount] != 0)
|
||||
{
|
||||
@@ -1943,34 +2013,46 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
|
||||
AudioComponentInstance convertUnit;
|
||||
|
||||
CHECK_STATUS_AND_RETURN_VALUE(AUGraphAddNode(audioGraph, &convertUnitDescription, &convertNode), 0);
|
||||
CHECK_STATUS_AND_RETURN_VALUE(status = AUGraphNodeInfo(audioGraph, convertNode, &mixerDescription, &convertUnit), 0);
|
||||
CHECK_STATUS_AND_RETURN_VALUE(AUGraphNodeInfo(audioGraph, convertNode, &mixerDescription, &convertUnit), 0);
|
||||
CHECK_STATUS_AND_RETURN_VALUE(AudioUnitSetProperty(convertUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &srcFormat, sizeof(srcFormat)), 0);
|
||||
CHECK_STATUS_AND_RETURN_VALUE(AudioUnitSetProperty(convertUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &desFormat, sizeof(desFormat)), 0);
|
||||
|
||||
CHECK_STATUS_AND_RETURN_VALUE(AudioUnitSetProperty(convertUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maxFramesPerSlice, sizeof(maxFramesPerSlice)), 0);
|
||||
|
||||
[converterNodes addObject:@(convertNode)];
|
||||
|
||||
return convertNode;
|
||||
}
|
||||
|
||||
-(void) connectNodes:(AUNode)srcNode desNode:(AUNode)desNode srcUnit:(AudioComponentInstance)srcUnit desUnit:(AudioComponentInstance)desUnit
|
||||
{
|
||||
OSStatus status;
|
||||
BOOL addConverter = NO;
|
||||
AudioStreamBasicDescription srcFormat, desFormat;
|
||||
UInt32 size = sizeof(AudioStreamBasicDescription);
|
||||
|
||||
CHECK_STATUS_AND_RETURN(AudioUnitGetProperty(srcUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &srcFormat, &size));
|
||||
CHECK_STATUS_AND_RETURN(AudioUnitGetProperty(desUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &desFormat, &size));
|
||||
|
||||
status = AudioUnitSetProperty(desUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &srcFormat, sizeof(srcFormat));
|
||||
addConverter = memcmp(&srcFormat, &desFormat, sizeof(srcFormat)) != 0;
|
||||
|
||||
if (status)
|
||||
if (addConverter)
|
||||
{
|
||||
AUNode convertNode = [self createConverterNode:srcFormat desFormat:desFormat];
|
||||
|
||||
CHECK_STATUS_AND_RETURN(AUGraphConnectNodeInput(audioGraph, convertNode, 0, mixerNode, 0));
|
||||
status = AudioUnitSetProperty(desUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &srcFormat, sizeof(srcFormat));
|
||||
|
||||
addConverter = status != 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (addConverter)
|
||||
{
|
||||
AUNode convertNode = [self createConverterNode:srcFormat desFormat:desFormat];
|
||||
|
||||
CHECK_STATUS_AND_RETURN(AUGraphConnectNodeInput(audioGraph, srcNode, 0, convertNode, 0));
|
||||
CHECK_STATUS_AND_RETURN(AUGraphConnectNodeInput(audioGraph, convertNode, 0, desNode, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_STATUS_AND_RETURN(AUGraphConnectNodeInput(audioGraph, srcNode, 0, desNode, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-(void) setOutputCallbackForFirstNode:(AUNode)firstNode firstUnit:(AudioComponentInstance)firstUnit
|
||||
@@ -1994,7 +2076,7 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
|
||||
|
||||
CHECK_STATUS_AND_RETURN(AUGraphSetNodeInputCallback(audioGraph, converterNode, 0, &callbackStruct));
|
||||
|
||||
status = AUGraphConnectNodeInput(audioGraph, converterNode, 0, firstNode, 0);
|
||||
CHECK_STATUS_AND_RETURN(AUGraphConnectNodeInput(audioGraph, converterNode, 0, firstNode, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2005,8 +2087,7 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
|
||||
-(void) createAudioGraph
|
||||
{
|
||||
OSStatus status;
|
||||
NSMutableArray* nodes = [[NSMutableArray alloc] init];
|
||||
NSMutableArray* units = [[NSMutableArray alloc] init];
|
||||
converterNodes = [[NSMutableArray alloc] init];
|
||||
|
||||
CHECK_STATUS_AND_RETURN(NewAUGraph(&audioGraph));
|
||||
CHECK_STATUS_AND_RETURN(AUGraphOpen(audioGraph));
|
||||
@@ -2015,10 +2096,45 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
|
||||
[self createMixerUnit];
|
||||
[self createOutputUnit];
|
||||
|
||||
[self connectGraph];
|
||||
|
||||
CHECK_STATUS_AND_RETURN(AUGraphInitialize(audioGraph));
|
||||
|
||||
self.volume = self->volume;
|
||||
}
|
||||
|
||||
-(void) connectGraph
|
||||
{
|
||||
OSStatus status;
|
||||
NSMutableArray* nodes = [[NSMutableArray alloc] init];
|
||||
NSMutableArray* units = [[NSMutableArray alloc] init];
|
||||
|
||||
AUGraphClearConnections(audioGraph);
|
||||
|
||||
for (NSNumber* converterNode in converterNodes)
|
||||
{
|
||||
CHECK_STATUS_AND_REPORT(AUGraphRemoveNode(audioGraph, (AUNode)converterNode.intValue));
|
||||
}
|
||||
|
||||
[converterNodes removeAllObjects];
|
||||
|
||||
if (eqNode)
|
||||
{
|
||||
[nodes addObject:@(eqNode)];
|
||||
[units addObject:[NSValue valueWithPointer:eqUnit]];
|
||||
if (self->equalizerEnabled)
|
||||
{
|
||||
[nodes addObject:@(eqNode)];
|
||||
[units addObject:[NSValue valueWithPointer:eqUnit]];
|
||||
|
||||
self->equalizerOn = YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
self->equalizerOn = NO;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self->equalizerOn = NO;
|
||||
}
|
||||
|
||||
if (mixerNode)
|
||||
@@ -2026,13 +2142,13 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
|
||||
[nodes addObject:@(mixerNode)];
|
||||
[units addObject:[NSValue valueWithPointer:mixerUnit]];
|
||||
}
|
||||
|
||||
|
||||
if (outputNode)
|
||||
{
|
||||
[nodes addObject:@(outputNode)];
|
||||
[units addObject:[NSValue valueWithPointer:outputUnit]];
|
||||
}
|
||||
|
||||
|
||||
[self setOutputCallbackForFirstNode:(AUNode)[[nodes objectAtIndex:0] intValue] firstUnit:(AudioComponentInstance)[[units objectAtIndex:0] pointerValue]];
|
||||
|
||||
for (int i = 0; i < nodes.count - 1; i++)
|
||||
@@ -2044,10 +2160,21 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
|
||||
|
||||
[self connectNodes:srcNode desNode:desNode srcUnit:srcUnit desUnit:desUnit];
|
||||
}
|
||||
}
|
||||
|
||||
-(BOOL) audioGraphIsRunning
|
||||
{
|
||||
OSStatus status;
|
||||
Boolean isRunning;
|
||||
|
||||
status = AUGraphIsRunning(audioGraph, &isRunning);
|
||||
|
||||
if (status)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
CHECK_STATUS_AND_RETURN(AUGraphInitialize(audioGraph));
|
||||
|
||||
self.volume = self->volume;
|
||||
return isRunning;
|
||||
}
|
||||
|
||||
-(BOOL) startAudioGraph
|
||||
@@ -2056,18 +2183,7 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
|
||||
|
||||
[self resetPcmBuffers];
|
||||
|
||||
Boolean isRunning;
|
||||
|
||||
status = AUGraphIsRunning(audioGraph, &isRunning);
|
||||
|
||||
if (status)
|
||||
{
|
||||
[self unexpectedError:STKAudioPlayerErrorAudioSystemError];
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (isRunning)
|
||||
if ([self audioGraphIsRunning])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
@@ -2081,6 +2197,8 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
|
||||
return NO;
|
||||
}
|
||||
|
||||
[self stopSystemBackgroundTask];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -2167,17 +2285,24 @@ OSStatus AudioConverterCallback(AudioConverterRef inAudioConverter, UInt32* ioNu
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (seekToTimeWasRequested || disposeWasRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposeWasRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (audioConverterRef == nil)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((seekToTimeWasRequested && [currentlyPlayingEntry calculatedBitRate] > 0.0))
|
||||
{
|
||||
[self wakeupPlaybackThread];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
discontinuous = NO;
|
||||
|
||||
OSStatus status;
|
||||
@@ -2234,7 +2359,6 @@ OSStatus AudioConverterCallback(AudioConverterRef inAudioConverter, UInt32* ioNu
|
||||
}
|
||||
|
||||
if (disposeWasRequested
|
||||
|| seekToTimeWasRequested
|
||||
|| self.internalState == STKAudioPlayerInternalStateStopped
|
||||
|| self.internalState == STKAudioPlayerInternalStateDisposed
|
||||
|| self.internalState == STKAudioPlayerInternalStatePendingNext)
|
||||
@@ -2243,6 +2367,15 @@ OSStatus AudioConverterCallback(AudioConverterRef inAudioConverter, UInt32* ioNu
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (seekToTimeWasRequested && [currentlyPlayingEntry calculatedBitRate] > 0.0)
|
||||
{
|
||||
pthread_mutex_unlock(&playerMutex);
|
||||
|
||||
[self wakeupPlaybackThread];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
waiting = YES;
|
||||
|
||||
@@ -2415,7 +2548,7 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
|
||||
{
|
||||
if (state == STKAudioPlayerInternalStateWaitingForData)
|
||||
{
|
||||
int64_t framesRequiredToStartPlaying = audioPlayer->framesRequiredToStartPlaying;
|
||||
SInt64 framesRequiredToStartPlaying = audioPlayer->framesRequiredToStartPlaying;
|
||||
|
||||
if (entry->lastFrameQueued >= 0)
|
||||
{
|
||||
@@ -2430,7 +2563,7 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
|
||||
}
|
||||
else if (state == STKAudioPlayerInternalStateRebuffering)
|
||||
{
|
||||
int64_t framesRequiredToStartPlaying = audioPlayer->framesRequiredToPlayAfterRebuffering;
|
||||
SInt64 framesRequiredToStartPlaying = audioPlayer->framesRequiredToPlayAfterRebuffering;
|
||||
|
||||
if (entry->lastFrameQueued >= 0)
|
||||
{
|
||||
@@ -2444,7 +2577,7 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
|
||||
}
|
||||
else if (state == STKAudioPlayerInternalStateWaitingForDataAfterSeek)
|
||||
{
|
||||
int64_t framesRequiredToStartPlaying = inNumberFrames;
|
||||
SInt64 framesRequiredToStartPlaying = 1024;
|
||||
|
||||
if (entry->lastFrameQueued >= 0)
|
||||
{
|
||||
@@ -2561,6 +2694,25 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
|
||||
return (state & STKAudioPlayerInternalStateRunning) && state != STKAudioPlayerInternalStatePaused;
|
||||
}];
|
||||
}
|
||||
else if (state == STKAudioPlayerInternalStateWaitingForDataAfterSeek)
|
||||
{
|
||||
if (totalFramesCopied == 0)
|
||||
{
|
||||
OSAtomicAdd32(inNumberFrames - totalFramesCopied, &audioPlayer->waitingForDataAfterSeekFrameCount);
|
||||
|
||||
if (audioPlayer->waitingForDataAfterSeekFrameCount > audioPlayer->framesRequiredBeforeWaitingForDataAfterSeekBecomesPlaying)
|
||||
{
|
||||
[audioPlayer setInternalState:STKAudioPlayerInternalStatePlaying ifInState:^BOOL(STKAudioPlayerInternalState state)
|
||||
{
|
||||
return (state & STKAudioPlayerInternalStateRunning) && state != STKAudioPlayerInternalStatePaused;
|
||||
}];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
audioPlayer->waitingForDataAfterSeekFrameCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (frameFilters)
|
||||
@@ -2575,6 +2727,17 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
|
||||
entry->filter(asbd.mChannelsPerFrame, asbd.mBytesPerFrame, inNumberFrames, ioData->mBuffers[0].mData);
|
||||
}
|
||||
}
|
||||
|
||||
if (audioPlayer->equalizerEnabled != audioPlayer->equalizerOn)
|
||||
{
|
||||
Boolean isUpdated;
|
||||
|
||||
[audioPlayer connectGraph];
|
||||
|
||||
AUGraphUpdate(audioPlayer->audioGraph, &isUpdated);
|
||||
|
||||
isUpdated = isUpdated;
|
||||
}
|
||||
|
||||
if (entry == nil)
|
||||
{
|
||||
@@ -2583,8 +2746,8 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
|
||||
|
||||
OSSpinLockLock(&entry->spinLock);
|
||||
|
||||
int64_t extraFramesPlayedNotAssigned = 0;
|
||||
int64_t framesPlayedForCurrent = totalFramesCopied;
|
||||
SInt64 extraFramesPlayedNotAssigned = 0;
|
||||
SInt64 framesPlayedForCurrent = totalFramesCopied;
|
||||
|
||||
if (entry->lastFrameQueued >= 0)
|
||||
{
|
||||
@@ -2612,7 +2775,7 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
|
||||
|
||||
if (newEntry != nil)
|
||||
{
|
||||
int64_t framesPlayedForCurrent = extraFramesPlayedNotAssigned;
|
||||
SInt64 framesPlayedForCurrent = extraFramesPlayedNotAssigned;
|
||||
|
||||
OSSpinLockLock(&newEntry->spinLock);
|
||||
|
||||
@@ -2657,9 +2820,16 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
|
||||
NSArray* retval;
|
||||
NSMutableArray* mutableArray = [[NSMutableArray alloc] initWithCapacity:upcomingQueue.count + bufferingQueue.count];
|
||||
|
||||
[mutableArray skipQueueWithQueue:upcomingQueue];
|
||||
[mutableArray skipQueueWithQueue:bufferingQueue];
|
||||
|
||||
for (STKQueueEntry* entry in upcomingQueue)
|
||||
{
|
||||
[mutableArray addObject:[entry queueItemId]];
|
||||
}
|
||||
|
||||
for (STKQueueEntry* entry in bufferingQueue)
|
||||
{
|
||||
[mutableArray addObject:[entry queueItemId]];
|
||||
}
|
||||
|
||||
retval = [NSArray arrayWithArray:mutableArray];
|
||||
|
||||
pthread_mutex_unlock(&playerMutex);
|
||||
@@ -2684,7 +2854,7 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
|
||||
|
||||
if (upcomingQueue.count > 0)
|
||||
{
|
||||
NSObject* retval = [upcomingQueue objectAtIndex:0];
|
||||
NSObject* retval = [[upcomingQueue objectAtIndex:0] queueItemId];
|
||||
|
||||
pthread_mutex_unlock(&playerMutex);
|
||||
|
||||
@@ -2693,7 +2863,7 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
|
||||
|
||||
if (bufferingQueue.count > 0)
|
||||
{
|
||||
NSObject* retval = [bufferingQueue objectAtIndex:0];
|
||||
NSObject* retval = [[bufferingQueue objectAtIndex:0] queueItemId];
|
||||
|
||||
pthread_mutex_unlock(&playerMutex);
|
||||
|
||||
@@ -2956,4 +3126,15 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
|
||||
return self->volume;
|
||||
}
|
||||
|
||||
-(BOOL) equalizerEnabled
|
||||
{
|
||||
return self->equalizerEnabled;
|
||||
}
|
||||
|
||||
-(void) setEqualizerEnabled:(BOOL)value
|
||||
{
|
||||
self->equalizerEnabled = value;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -36,6 +36,13 @@
|
||||
#import "STKHTTPDataSource.h"
|
||||
#import "STKDataSourceWrapper.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int watchdogPeriodSeconds;
|
||||
int inactivePeriodBeforeReconnectSeconds;
|
||||
}
|
||||
STKAutoRecoveringHTTPDataSourceOptions;
|
||||
|
||||
@interface STKAutoRecoveringHTTPDataSource : STKDataSourceWrapper
|
||||
|
||||
-(id) initWithHTTPDataSource:(STKHTTPDataSource*)innerDataSource;
|
||||
|
||||
@@ -38,18 +38,38 @@
|
||||
#import <arpa/inet.h>
|
||||
#import <ifaddrs.h>
|
||||
#import <netdb.h>
|
||||
#import "mach/mach_time.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <SystemConfiguration/SystemConfiguration.h>
|
||||
#import "STKAutoRecoveringHTTPDataSource.h"
|
||||
|
||||
#define MAX_IMMEDIATE_RECONNECT_ATTEMPTS (2)
|
||||
#define DEFAULT_WATCHDOG_PERIOD_SECONDS (8)
|
||||
#define DEFAULT_INACTIVE_PERIOD_BEFORE_RECONNECT_SECONDS (15)
|
||||
|
||||
static uint64_t GetTickCount(void)
|
||||
{
|
||||
static mach_timebase_info_data_t sTimebaseInfo;
|
||||
uint64_t machTime = mach_absolute_time();
|
||||
|
||||
if (sTimebaseInfo.denom == 0 )
|
||||
{
|
||||
(void) mach_timebase_info(&sTimebaseInfo);
|
||||
}
|
||||
|
||||
uint64_t millis = ((machTime / 1000000) * sTimebaseInfo.numer) / sTimebaseInfo.denom;
|
||||
|
||||
return millis;
|
||||
}
|
||||
|
||||
@interface STKAutoRecoveringHTTPDataSource()
|
||||
{
|
||||
int serial;
|
||||
int waitSeconds;
|
||||
NSTimer* timeoutTimer;
|
||||
BOOL waitingForNetwork;
|
||||
uint64_t ticksWhenLastDataReceived;
|
||||
SCNetworkReachabilityRef reachabilityRef;
|
||||
STKAutoRecoveringHTTPDataSourceOptions options;
|
||||
}
|
||||
|
||||
-(void) reachabilityChanged;
|
||||
@@ -66,6 +86,19 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
|
||||
}
|
||||
}
|
||||
|
||||
static void PopulateOptionsWithDefault(STKAutoRecoveringHTTPDataSourceOptions* options)
|
||||
{
|
||||
if (options->watchdogPeriodSeconds == 0)
|
||||
{
|
||||
options->watchdogPeriodSeconds = DEFAULT_WATCHDOG_PERIOD_SECONDS;
|
||||
}
|
||||
|
||||
if (options->inactivePeriodBeforeReconnectSeconds == 0)
|
||||
{
|
||||
options->inactivePeriodBeforeReconnectSeconds = DEFAULT_INACTIVE_PERIOD_BEFORE_RECONNECT_SECONDS;
|
||||
}
|
||||
}
|
||||
|
||||
@implementation STKAutoRecoveringHTTPDataSource
|
||||
|
||||
-(STKHTTPDataSource*) innerHTTPDataSource
|
||||
@@ -79,6 +112,11 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
|
||||
}
|
||||
|
||||
-(id) initWithHTTPDataSource:(STKHTTPDataSource*)innerDataSourceIn
|
||||
{
|
||||
return [self initWithHTTPDataSource:innerDataSourceIn andOptions:(STKAutoRecoveringHTTPDataSourceOptions){}];
|
||||
}
|
||||
|
||||
-(id) initWithHTTPDataSource:(STKHTTPDataSource*)innerDataSourceIn andOptions:(STKAutoRecoveringHTTPDataSourceOptions)optionsIn
|
||||
{
|
||||
if (self = [super initWithDataSource:innerDataSourceIn])
|
||||
{
|
||||
@@ -90,6 +128,10 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
|
||||
zeroAddress.sin_len = sizeof(zeroAddress);
|
||||
zeroAddress.sin_family = AF_INET;
|
||||
|
||||
PopulateOptionsWithDefault(&optionsIn);
|
||||
|
||||
self->options = optionsIn;
|
||||
|
||||
reachabilityRef = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)&zeroAddress);
|
||||
}
|
||||
|
||||
@@ -117,14 +159,70 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
|
||||
[super registerForEvents:runLoop];
|
||||
[self startNotifierOnRunLoop:runLoop];
|
||||
|
||||
if (timeoutTimer)
|
||||
{
|
||||
[timeoutTimer invalidate];
|
||||
timeoutTimer = nil;
|
||||
}
|
||||
|
||||
ticksWhenLastDataReceived = GetTickCount();
|
||||
|
||||
[self createTimeoutTimer];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
-(void) unregisterForEvents
|
||||
{
|
||||
[super unregisterForEvents];
|
||||
|
||||
[self stopNotifier];
|
||||
|
||||
[self destroyTimeoutTimer];
|
||||
}
|
||||
|
||||
-(void) timeoutTimerTick:(NSTimer*)timer
|
||||
{
|
||||
if (![self hasBytesAvailable])
|
||||
{
|
||||
if ([self hasGotNetworkConnection])
|
||||
{
|
||||
uint64_t currentTicks = GetTickCount();
|
||||
|
||||
if (((currentTicks - ticksWhenLastDataReceived) / 1000) >= options.inactivePeriodBeforeReconnectSeconds)
|
||||
{
|
||||
serial++;
|
||||
|
||||
NSLog(@"timeoutTimerTick %lld/%lld", self.position, self.length);
|
||||
|
||||
[self attemptReconnectWithSerial:@(serial)];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-(void) createTimeoutTimer
|
||||
{
|
||||
[self destroyTimeoutTimer];
|
||||
|
||||
NSRunLoop* runLoop = self.innerDataSource.eventsRunLoop;
|
||||
|
||||
if (runLoop == nil)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
timeoutTimer = [NSTimer timerWithTimeInterval:options.watchdogPeriodSeconds target:self selector:@selector(timeoutTimerTick:) userInfo:@(serial) repeats:YES];
|
||||
|
||||
[runLoop addTimer:timeoutTimer forMode:NSRunLoopCommonModes];
|
||||
}
|
||||
|
||||
-(void) destroyTimeoutTimer
|
||||
{
|
||||
if (timeoutTimer)
|
||||
{
|
||||
[timeoutTimer invalidate];
|
||||
timeoutTimer = nil;
|
||||
}
|
||||
}
|
||||
|
||||
-(void) stopNotifier
|
||||
@@ -148,6 +246,19 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
|
||||
return NO;
|
||||
}
|
||||
|
||||
-(void) seekToOffset:(int64_t)offset
|
||||
{
|
||||
ticksWhenLastDataReceived = GetTickCount();
|
||||
|
||||
[super seekToOffset:offset];
|
||||
}
|
||||
|
||||
-(void) close
|
||||
{
|
||||
[self destroyTimeoutTimer];
|
||||
[super close];
|
||||
}
|
||||
|
||||
-(void) dealloc
|
||||
{
|
||||
NSLog(@"STKAutoRecoveringHTTPDataSource dealloc");
|
||||
@@ -155,6 +266,7 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
|
||||
self.innerDataSource.delegate = nil;
|
||||
|
||||
[self stopNotifier];
|
||||
[self destroyTimeoutTimer];
|
||||
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self];
|
||||
|
||||
@@ -170,15 +282,25 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
|
||||
{
|
||||
waitingForNetwork = NO;
|
||||
|
||||
NSLog(@"reachabilityChanged %lld/%lld", self.position, self.length);
|
||||
|
||||
serial++;
|
||||
|
||||
[self attemptReconnectWithSerial:@(serial)];
|
||||
}
|
||||
}
|
||||
|
||||
-(void) dataSourceDataAvailable:(STKDataSource*)dataSource
|
||||
{
|
||||
if (![self.innerDataSource hasBytesAvailable])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
serial++;
|
||||
waitSeconds = 1;
|
||||
|
||||
ticksWhenLastDataReceived = GetTickCount();
|
||||
|
||||
[super dataSourceDataAvailable:dataSource];
|
||||
}
|
||||
|
||||
@@ -191,7 +313,10 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
|
||||
|
||||
NSLog(@"attemptReconnect %lld/%lld", self.position, self.length);
|
||||
|
||||
[self seekToOffset:self.position];
|
||||
if (self.innerDataSource.eventsRunLoop)
|
||||
{
|
||||
[self.innerDataSource reconnect];
|
||||
}
|
||||
}
|
||||
|
||||
-(void) attemptReconnectWithTimer:(NSTimer*)timer
|
||||
@@ -214,10 +339,14 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
|
||||
|
||||
if (runLoop == nil)
|
||||
{
|
||||
[self performSelector:@selector(attemptReconnectWithSerial:) withObject:@(serial) afterDelay:waitSeconds];
|
||||
// DataSource no longer used
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
serial++;
|
||||
|
||||
NSTimer* timer = [NSTimer timerWithTimeInterval:waitSeconds target:self selector:@selector(attemptReconnectWithTimer:) userInfo:@(serial) repeats:NO];
|
||||
|
||||
[runLoop addTimer:timer forMode:NSRunLoopCommonModes];
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// STKBufferChunk.h
|
||||
// StreamingKit
|
||||
//
|
||||
// Created by Thong Nguyen on 24/02/2014.
|
||||
// Copyright (c) 2014 Thong Nguyen. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface STKBufferChunk : NSObject
|
||||
{
|
||||
@public
|
||||
UInt32 index;
|
||||
UInt32 size;
|
||||
UInt32 position;
|
||||
UInt8* buffer;
|
||||
}
|
||||
|
||||
@property (readonly) UInt32 absoluteStart;
|
||||
@property (readonly) UInt32 absolutePosition;
|
||||
|
||||
-(id) initWithBufferSize:(UInt32)sizeIn;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// STKBufferChunk.m
|
||||
// StreamingKit
|
||||
//
|
||||
// Created by Thong Nguyen on 24/02/2014.
|
||||
// Copyright (c) 2014 Thong Nguyen. All rights reserved.
|
||||
//
|
||||
|
||||
#import "STKBufferChunk.h"
|
||||
|
||||
@implementation STKBufferChunk
|
||||
|
||||
-(id) initWithBufferSize:(UInt32)sizeIn
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
self->size = sizeIn;
|
||||
|
||||
self->buffer = calloc(sizeof(UInt8), sizeIn);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void) dealloc
|
||||
{
|
||||
free(self->buffer);
|
||||
}
|
||||
|
||||
-(UInt32) absoluteStart
|
||||
{
|
||||
return self->index * self->size;
|
||||
}
|
||||
|
||||
-(UInt32) absolutePosition
|
||||
{
|
||||
return self.absoluteStart + self->position;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,44 @@
|
||||
/**********************************************************************************
|
||||
STKBufferingDataSource.h
|
||||
|
||||
Created by Thong Nguyen on 16/10/2012.
|
||||
https://github.com/tumtumtum/audjustable
|
||||
|
||||
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:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. All advertising materials mentioning features or use of this software
|
||||
must display the following acknowledgement:
|
||||
This product includes software developed by Thong Nguyen (tumtumtum@gmail.com)
|
||||
4. Neither the name of Thong Nguyen nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY Thong Nguyen ''AS IS'' AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THONG NGUYEN BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**********************************************************************************/
|
||||
|
||||
#import "STKDataSource.h"
|
||||
|
||||
@interface STKBufferingDataSource : STKDataSource
|
||||
|
||||
@property (readonly) SInt64 position;
|
||||
@property (readonly) SInt64 length;
|
||||
|
||||
-(id) initWithDataSource:(STKDataSource*)dataSourceIn withMaxSize:(int)maxSizeIn;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,319 @@
|
||||
/**********************************************************************************
|
||||
STKBufferingDataSource.m
|
||||
|
||||
Created by Thong Nguyen on 16/10/2012.
|
||||
https://github.com/tumtumtum/audjustable
|
||||
|
||||
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:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. All advertising materials mentioning features or use of this software
|
||||
must display the following acknowledgement:
|
||||
This product includes software developed by Thong Nguyen (tumtumtum@gmail.com)
|
||||
4. Neither the name of Thong Nguyen nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY Thong Nguyen ''AS IS'' AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THONG NGUYEN BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**********************************************************************************/
|
||||
|
||||
#import "STKBufferingDataSource.h"
|
||||
#import "STKBufferChunk.h"
|
||||
#import <pthread.h>
|
||||
|
||||
#define STK_BUFFER_CHUNK_SIZE (128 * 1024)
|
||||
|
||||
@interface STKBufferingDataSource()
|
||||
{
|
||||
@private
|
||||
NSRunLoop* runLoop;
|
||||
SInt32 maxSize;
|
||||
UInt32 chunkSize;
|
||||
UInt32 chunkCount;
|
||||
SInt64 position;
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t condition;
|
||||
STKBufferChunk* __strong * bufferChunks;
|
||||
STKDataSource* dataSource;
|
||||
}
|
||||
@end
|
||||
|
||||
@interface STKBufferingDataSourceThread : NSThread
|
||||
{
|
||||
@private
|
||||
NSRunLoop* runLoop;
|
||||
NSConditionLock* threadStartedLock;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation STKBufferingDataSourceThread
|
||||
|
||||
-(id) init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
threadStartedLock = [[NSConditionLock alloc] initWithCondition:0];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
-(NSRunLoop*) runLoop
|
||||
{
|
||||
[threadStartedLock lockWhenCondition:1];
|
||||
[threadStartedLock unlockWithCondition:0];
|
||||
|
||||
return self->runLoop;
|
||||
}
|
||||
|
||||
-(void) main
|
||||
{
|
||||
runLoop = [NSRunLoop currentRunLoop];
|
||||
|
||||
[threadStartedLock lockWhenCondition:0];
|
||||
[threadStartedLock unlockWithCondition:1];
|
||||
|
||||
[runLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
|
||||
|
||||
while (true)
|
||||
{
|
||||
NSDate* date = [[NSDate alloc] initWithTimeIntervalSinceNow:10];
|
||||
|
||||
[runLoop runMode:NSDefaultRunLoopMode beforeDate:date];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static STKBufferingDataSourceThread* thread;
|
||||
|
||||
@implementation STKBufferingDataSource
|
||||
|
||||
+(void) initialize
|
||||
{
|
||||
thread = [[STKBufferingDataSourceThread alloc] init];
|
||||
|
||||
[thread start];
|
||||
}
|
||||
|
||||
-(id) initWithDataSource:(STKDataSource*)dataSourceIn withMaxSize:(int)maxSizeIn
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
self->maxSize = maxSizeIn;
|
||||
self->dataSource = dataSourceIn;
|
||||
self->chunkSize = STK_BUFFER_CHUNK_SIZE;
|
||||
|
||||
self->dataSource.delegate = self.delegate;
|
||||
|
||||
[self->dataSource registerForEvents:[thread runLoop]];
|
||||
|
||||
pthread_mutexattr_t attr;
|
||||
|
||||
pthread_mutexattr_init(&attr);
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
|
||||
pthread_mutex_init(&self->mutex, &attr);
|
||||
pthread_cond_init(&self->condition, NULL);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void) dealloc
|
||||
{
|
||||
self->dataSource.delegate = nil;
|
||||
|
||||
for (int i = 0; i < self->chunkCount; i++)
|
||||
{
|
||||
self->bufferChunks[i] = nil;
|
||||
}
|
||||
|
||||
free(self->bufferChunks);
|
||||
|
||||
pthread_mutex_destroy(&self->mutex);
|
||||
pthread_cond_destroy(&self->condition);
|
||||
}
|
||||
|
||||
-(void) createBuffer
|
||||
{
|
||||
if (self->bufferChunks == nil)
|
||||
{
|
||||
int length = (int)MIN(self.length == 0? 1024 * 1024 : self.length, self->maxSize);
|
||||
|
||||
self->chunkCount = (int)((length / self->chunkSize) + 1);
|
||||
self->bufferChunks = (__strong STKBufferChunk**)calloc(sizeof(STKBufferChunk*), self->chunkCount);
|
||||
}
|
||||
}
|
||||
|
||||
-(STKBufferChunk*) chunkForPosition:(SInt64)positionIn createIfNotExist:(BOOL)createIfNotExist
|
||||
{
|
||||
int chunkIndex = (int)(positionIn / chunkCount);
|
||||
|
||||
if (self->bufferChunks[chunkIndex] == nil && createIfNotExist)
|
||||
{
|
||||
self->bufferChunks[chunkIndex] = [[STKBufferChunk alloc] initWithBufferSize:STK_BUFFER_CHUNK_SIZE];
|
||||
}
|
||||
|
||||
return self->bufferChunks[chunkIndex];
|
||||
}
|
||||
|
||||
-(SInt64) length
|
||||
{
|
||||
return self->dataSource.length;
|
||||
}
|
||||
|
||||
-(void) seekToOffset:(SInt64)offset
|
||||
{
|
||||
pthread_mutex_lock(&mutex);
|
||||
|
||||
[self seekToNextGap];
|
||||
|
||||
pthread_mutex_unlock(&mutex);
|
||||
}
|
||||
|
||||
-(BOOL) hasBytesAvailable
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
-(int) readIntoBuffer:(UInt8*)bufferIn withSize:(int)size
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
-(void) invokeBlockOnEventsRunLoop:(void(^)())block
|
||||
{
|
||||
if (!runLoop)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
block = [block copy];
|
||||
|
||||
CFRunLoopPerformBlock(runLoop.getCFRunLoop, NSRunLoopCommonModes, ^
|
||||
{
|
||||
if ([self hasBytesAvailable])
|
||||
{
|
||||
block();
|
||||
}
|
||||
});
|
||||
|
||||
CFRunLoopWakeUp(runLoop.getCFRunLoop);
|
||||
}
|
||||
|
||||
-(BOOL) registerForEvents:(NSRunLoop*)runLoopIn
|
||||
{
|
||||
runLoop = runLoopIn;
|
||||
|
||||
[dataSource registerForEvents:[thread runLoop]];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
-(void) unregisterForEvents
|
||||
{
|
||||
runLoop = nil;
|
||||
|
||||
[dataSource unregisterForEvents];
|
||||
}
|
||||
|
||||
-(void) close
|
||||
{
|
||||
[dataSource unregisterForEvents];
|
||||
[dataSource close];
|
||||
}
|
||||
|
||||
-(void) seekToNextGap
|
||||
{
|
||||
int startChunkIndex = (int)(self->position / chunkCount);
|
||||
|
||||
for (int i = 0; i < self->chunkCount; i++)
|
||||
{
|
||||
int chunkIndex = (i + startChunkIndex) % self->chunkCount;
|
||||
|
||||
STKBufferChunk* chunk = self->bufferChunks[chunkIndex];
|
||||
|
||||
if (chunk == nil)
|
||||
{
|
||||
chunk = [[STKBufferChunk alloc] initWithBufferSize:STK_BUFFER_CHUNK_SIZE];
|
||||
|
||||
chunk->index = chunkIndex;
|
||||
|
||||
self->bufferChunks[chunkIndex] = chunk;
|
||||
}
|
||||
|
||||
if (chunk->position < chunk->size)
|
||||
{
|
||||
[dataSource seekToOffset:(self->chunkSize * chunk->index) + chunk->position];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-(void) dataSourceDataAvailable:(STKDataSource*)dataSourceIn
|
||||
{
|
||||
if (![dataSourceIn hasBytesAvailable])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&mutex);
|
||||
|
||||
if (self->bufferChunks == nil)
|
||||
{
|
||||
[self createBuffer];
|
||||
}
|
||||
|
||||
SInt64 sourcePosition = dataSourceIn.position;
|
||||
|
||||
STKBufferChunk* chunk = [self chunkForPosition:sourcePosition createIfNotExist:YES];
|
||||
|
||||
if (chunk->position >= chunk->size)
|
||||
{
|
||||
[self seekToNextGap];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int offset = dataSourceIn.position % self->chunkSize;
|
||||
|
||||
if (offset >= chunk->position)
|
||||
{
|
||||
[self seekToNextGap];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int bytesToRead = self->chunkSize - offset;
|
||||
int bytesRead = [dataSourceIn readIntoBuffer:(chunk->buffer + offset) withSize:bytesToRead];
|
||||
|
||||
chunk->position = offset + bytesRead;
|
||||
|
||||
pthread_mutex_unlock(&mutex);
|
||||
}
|
||||
|
||||
-(void) dataSourceErrorOccured:(STKDataSource*)dataSourceIn
|
||||
{
|
||||
[self.delegate dataSourceErrorOccured:self];
|
||||
}
|
||||
|
||||
-(void) dataSourceEof:(STKDataSource*)dataSourceIn
|
||||
{
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -54,6 +54,7 @@
|
||||
-(BOOL) reregisterForEvents;
|
||||
|
||||
-(void) open;
|
||||
-(void) openCompleted;
|
||||
-(void) dataAvailable;
|
||||
-(void) eof;
|
||||
-(void) errorOccured;
|
||||
|
||||
@@ -49,6 +49,9 @@ static void ReadStreamCallbackProc(CFReadStreamRef stream, CFStreamEventType eve
|
||||
case kCFStreamEventHasBytesAvailable:
|
||||
[datasource dataAvailable];
|
||||
break;
|
||||
case kCFStreamEventOpenCompleted:
|
||||
[datasource openCompleted];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -101,6 +104,11 @@ static void ReadStreamCallbackProc(CFReadStreamRef stream, CFStreamEventType eve
|
||||
{
|
||||
if (stream)
|
||||
{
|
||||
if (eventsRunLoop)
|
||||
{
|
||||
[self unregisterForEvents];
|
||||
}
|
||||
|
||||
CFReadStreamClose(stream);
|
||||
CFRelease(stream);
|
||||
|
||||
@@ -112,7 +120,7 @@ static void ReadStreamCallbackProc(CFReadStreamRef stream, CFStreamEventType eve
|
||||
{
|
||||
}
|
||||
|
||||
-(void) seekToOffset:(long long)offset
|
||||
-(void) seekToOffset:(SInt64)offset
|
||||
{
|
||||
}
|
||||
|
||||
@@ -127,6 +135,8 @@ static void ReadStreamCallbackProc(CFReadStreamRef stream, CFStreamEventType eve
|
||||
{
|
||||
CFReadStreamSetClient(stream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, NULL, NULL);
|
||||
CFReadStreamUnscheduleFromRunLoop(stream, [eventsRunLoop getCFRunLoop], kCFRunLoopCommonModes);
|
||||
|
||||
eventsRunLoop = nil;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,10 +158,10 @@ static void ReadStreamCallbackProc(CFReadStreamRef stream, CFStreamEventType eve
|
||||
{
|
||||
eventsRunLoop = runLoop;
|
||||
|
||||
if (!stream)
|
||||
if (!stream)
|
||||
{
|
||||
[self open];
|
||||
|
||||
// Will register when they open or seek
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -184,4 +194,8 @@ static void ReadStreamCallbackProc(CFReadStreamRef stream, CFStreamEventType eve
|
||||
return 0;
|
||||
}
|
||||
|
||||
-(void) openCompleted
|
||||
{
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -43,15 +43,10 @@
|
||||
-(void) dataSourceEof:(STKDataSource*)dataSource;
|
||||
@end
|
||||
|
||||
@protocol AudioDataSource<NSObject>
|
||||
@property (readwrite) double averageBitRate;
|
||||
@property (readwrite) long long audioDataOffset;
|
||||
@end
|
||||
|
||||
@interface STKDataSource : NSObject
|
||||
|
||||
@property (readonly) long long position;
|
||||
@property (readonly) long long length;
|
||||
@property (readonly) SInt64 position;
|
||||
@property (readonly) SInt64 length;
|
||||
@property (readonly) BOOL hasBytesAvailable;
|
||||
@property (readwrite, unsafe_unretained) id<STKDataSourceDelegate> delegate;
|
||||
|
||||
@@ -59,7 +54,7 @@
|
||||
-(void) unregisterForEvents;
|
||||
-(void) close;
|
||||
|
||||
-(void) seekToOffset:(long long)offset;
|
||||
-(void) seekToOffset:(SInt64)offset;
|
||||
-(int) readIntoBuffer:(UInt8*)buffer withSize:(int)size;
|
||||
-(AudioFileTypeID) audioFileTypeHint;
|
||||
|
||||
|
||||
@@ -37,12 +37,12 @@
|
||||
@implementation STKDataSource
|
||||
@synthesize delegate;
|
||||
|
||||
-(long long) length
|
||||
-(SInt64) length
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
-(void) seekToOffset:(long long)offset
|
||||
-(void) seekToOffset:(SInt64)offset
|
||||
{
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
return -1;
|
||||
}
|
||||
|
||||
-(long long) position
|
||||
-(SInt64) position
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -66,7 +66,7 @@
|
||||
}
|
||||
|
||||
-(void) close
|
||||
{
|
||||
{
|
||||
}
|
||||
|
||||
-(BOOL) hasBytesAvailable
|
||||
|
||||
@@ -62,12 +62,12 @@
|
||||
self.innerDataSource.delegate = nil;
|
||||
}
|
||||
|
||||
-(long long) length
|
||||
-(SInt64) length
|
||||
{
|
||||
return self.innerDataSource.length;
|
||||
}
|
||||
|
||||
-(void) seekToOffset:(long long)offset
|
||||
-(void) seekToOffset:(SInt64)offset
|
||||
{
|
||||
return [self.innerDataSource seekToOffset:offset];
|
||||
}
|
||||
@@ -77,7 +77,7 @@
|
||||
return [self.innerDataSource readIntoBuffer:buffer withSize:size];
|
||||
}
|
||||
|
||||
-(long long) position
|
||||
-(SInt64) position
|
||||
{
|
||||
return self.innerDataSource.position;
|
||||
}
|
||||
|
||||
@@ -43,12 +43,13 @@ typedef void(^STKAsyncURLProvider)(STKHTTPDataSource* dataSource, BOOL forSeek,
|
||||
@interface STKHTTPDataSource : STKCoreFoundationDataSource
|
||||
|
||||
@property (readonly, retain) NSURL* url;
|
||||
@property (readwrite) UInt32 httpStatusCode;
|
||||
@property (readonly) UInt32 httpStatusCode;
|
||||
|
||||
+(AudioFileTypeID) audioFileTypeHintFromMimeType:(NSString*)fileExtension;
|
||||
-(id) initWithURL:(NSURL*)url;
|
||||
-(id) initWithURLProvider:(STKURLProvider)urlProvider;
|
||||
-(id) initWithAsyncURLProvider:(STKAsyncURLProvider)asyncUrlProvider;
|
||||
-(NSRunLoop*) eventsRunLoop;
|
||||
-(void) reconnect;
|
||||
|
||||
@end
|
||||
|
||||
@@ -38,10 +38,13 @@
|
||||
@interface STKHTTPDataSource()
|
||||
{
|
||||
@private
|
||||
long long seekStart;
|
||||
long long relativePosition;
|
||||
long long fileLength;
|
||||
UInt32 httpStatusCode;
|
||||
SInt64 seekStart;
|
||||
SInt64 relativePosition;
|
||||
SInt64 fileLength;
|
||||
int discontinuous;
|
||||
int requestSerialNumber;
|
||||
|
||||
NSURL* currentUrl;
|
||||
STKAsyncURLProvider asyncUrlProvider;
|
||||
NSDictionary* httpHeaders;
|
||||
@@ -138,21 +141,28 @@
|
||||
|
||||
-(void) dataAvailable
|
||||
{
|
||||
if (stream == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.httpStatusCode == 0)
|
||||
{
|
||||
CFTypeRef response = CFReadStreamCopyProperty(stream, kCFStreamPropertyHTTPResponseHeader);
|
||||
|
||||
httpHeaders = (__bridge_transfer NSDictionary*)CFHTTPMessageCopyAllHeaderFields((CFHTTPMessageRef)response);
|
||||
|
||||
self.httpStatusCode = CFHTTPMessageGetResponseStatusCode((CFHTTPMessageRef)response);
|
||||
|
||||
CFRelease(response);
|
||||
if (response)
|
||||
{
|
||||
httpHeaders = (__bridge_transfer NSDictionary*)CFHTTPMessageCopyAllHeaderFields((CFHTTPMessageRef)response);
|
||||
|
||||
self->httpStatusCode = (UInt32)CFHTTPMessageGetResponseStatusCode((CFHTTPMessageRef)response);
|
||||
|
||||
CFRelease(response);
|
||||
}
|
||||
|
||||
if (self.httpStatusCode == 200)
|
||||
{
|
||||
if (seekStart == 0)
|
||||
{
|
||||
fileLength = (long long)[[httpHeaders objectForKey:@"Content-Length"] integerValue];
|
||||
fileLength = (SInt64)[[httpHeaders objectForKey:@"Content-Length"] longLongValue];
|
||||
}
|
||||
|
||||
NSString* contentType = [httpHeaders objectForKey:@"Content-Type"];
|
||||
@@ -195,28 +205,34 @@
|
||||
[super dataAvailable];
|
||||
}
|
||||
|
||||
-(long long) position
|
||||
-(SInt64) position
|
||||
{
|
||||
return seekStart + relativePosition;
|
||||
}
|
||||
|
||||
-(long long) length
|
||||
-(SInt64) length
|
||||
{
|
||||
return fileLength >= 0 ? fileLength : 0;
|
||||
}
|
||||
|
||||
-(void) seekToOffset:(long long)offset
|
||||
-(void) reconnect
|
||||
{
|
||||
if (eventsRunLoop)
|
||||
{
|
||||
[self unregisterForEvents];
|
||||
}
|
||||
NSRunLoop* savedEventsRunLoop = eventsRunLoop;
|
||||
|
||||
if (stream)
|
||||
{
|
||||
CFReadStreamClose(stream);
|
||||
CFRelease(stream);
|
||||
}
|
||||
[self close];
|
||||
|
||||
eventsRunLoop = savedEventsRunLoop;
|
||||
|
||||
[self seekToOffset:self.position];
|
||||
}
|
||||
|
||||
-(void) seekToOffset:(SInt64)offset
|
||||
{
|
||||
NSRunLoop* savedEventsRunLoop = eventsRunLoop;
|
||||
|
||||
[self close];
|
||||
|
||||
eventsRunLoop = savedEventsRunLoop;
|
||||
|
||||
NSAssert([NSRunLoop currentRunLoop] == eventsRunLoop, @"Seek called on wrong thread");
|
||||
|
||||
@@ -255,8 +271,18 @@
|
||||
|
||||
-(void) openForSeek:(BOOL)forSeek
|
||||
{
|
||||
int localRequestSerialNumber;
|
||||
|
||||
requestSerialNumber++;
|
||||
localRequestSerialNumber = requestSerialNumber;
|
||||
|
||||
asyncUrlProvider(self, forSeek, ^(NSURL* url)
|
||||
{
|
||||
if (localRequestSerialNumber != self->requestSerialNumber)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
self->currentUrl = url;
|
||||
|
||||
if (url == nil)
|
||||
@@ -317,7 +343,7 @@
|
||||
|
||||
[self reregisterForEvents];
|
||||
|
||||
self.httpStatusCode = 0;
|
||||
self->httpStatusCode = 0;
|
||||
|
||||
// Open
|
||||
|
||||
@@ -325,6 +351,8 @@
|
||||
{
|
||||
CFRelease(stream);
|
||||
CFRelease(message);
|
||||
|
||||
stream = 0;
|
||||
|
||||
[self errorOccured];
|
||||
|
||||
@@ -337,6 +365,11 @@
|
||||
});
|
||||
}
|
||||
|
||||
-(UInt32) httpStatusCode
|
||||
{
|
||||
return self->httpStatusCode;
|
||||
}
|
||||
|
||||
-(NSRunLoop*) eventsRunLoop
|
||||
{
|
||||
return self->eventsRunLoop;
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
|
||||
@interface STKLocalFileDataSource()
|
||||
{
|
||||
long long position;
|
||||
long long length;
|
||||
SInt64 position;
|
||||
SInt64 length;
|
||||
AudioFileTypeID audioFileTypeHint;
|
||||
}
|
||||
@property (readwrite, copy) NSString* filePath;
|
||||
@@ -153,12 +153,12 @@
|
||||
CFReadStreamOpen(stream);
|
||||
}
|
||||
|
||||
-(long long) position
|
||||
-(SInt64) position
|
||||
{
|
||||
return position;
|
||||
}
|
||||
|
||||
-(long long) length
|
||||
-(SInt64) length
|
||||
{
|
||||
return length;
|
||||
}
|
||||
@@ -181,7 +181,7 @@
|
||||
return retval;
|
||||
}
|
||||
|
||||
-(void) seekToOffset:(long long)offset
|
||||
-(void) seekToOffset:(SInt64)offset
|
||||
{
|
||||
CFStreamStatus status = kCFStreamStatusClosed;
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
UInt64 audioDataByteCount;
|
||||
UInt32 packetBufferSize;
|
||||
volatile Float64 seekTime;
|
||||
volatile int64_t framesQueued;
|
||||
volatile int64_t framesPlayed;
|
||||
volatile int64_t lastFrameQueued;
|
||||
volatile SInt64 framesQueued;
|
||||
volatile SInt64 framesPlayed;
|
||||
volatile SInt64 lastFrameQueued;
|
||||
volatile int processedPacketsCount;
|
||||
volatile int processedPacketsSizeTotal;
|
||||
AudioStreamBasicDescription audioStreamBasicDescription;
|
||||
@@ -40,7 +40,6 @@
|
||||
-(double) duration;
|
||||
-(Float64) progressInFrames;
|
||||
-(double) calculatedBitRate;
|
||||
-(void) updateAudioDataSource;
|
||||
-(BOOL) isDefinitelyCompatible:(AudioStreamBasicDescription*)basicDescription;
|
||||
|
||||
@end
|
||||
@@ -9,7 +9,8 @@
|
||||
#import "STKQueueEntry.h"
|
||||
#import "STKDataSource.h"
|
||||
|
||||
#define STK_BIT_RATE_ESTIMATION_MIN_PACKETS (64)
|
||||
#define STK_BIT_RATE_ESTIMATION_MIN_PACKETS_MIN (2)
|
||||
#define STK_BIT_RATE_ESTIMATION_MIN_PACKETS_PREFERRED (64)
|
||||
|
||||
@implementation STKQueueEntry
|
||||
|
||||
@@ -38,13 +39,16 @@
|
||||
{
|
||||
double retval;
|
||||
|
||||
if (packetDuration && processedPacketsCount > STK_BIT_RATE_ESTIMATION_MIN_PACKETS)
|
||||
if (packetDuration > 0)
|
||||
{
|
||||
double averagePacketByteSize = processedPacketsSizeTotal / processedPacketsCount;
|
||||
|
||||
retval = averagePacketByteSize / packetDuration * 8;
|
||||
|
||||
return retval;
|
||||
if (processedPacketsCount > STK_BIT_RATE_ESTIMATION_MIN_PACKETS_PREFERRED || (audioStreamBasicDescription.mBytesPerFrame == 0 && processedPacketsCount > STK_BIT_RATE_ESTIMATION_MIN_PACKETS_MIN))
|
||||
{
|
||||
double averagePacketByteSize = processedPacketsSizeTotal / processedPacketsCount;
|
||||
|
||||
retval = averagePacketByteSize / packetDuration * 8;
|
||||
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
retval = (audioStreamBasicDescription.mBytesPerFrame * audioStreamBasicDescription.mSampleRate) * 8;
|
||||
@@ -52,19 +56,6 @@
|
||||
return retval;
|
||||
}
|
||||
|
||||
-(void) updateAudioDataSource
|
||||
{
|
||||
if ([self.dataSource conformsToProtocol:@protocol(AudioDataSource)])
|
||||
{
|
||||
double calculatedBitrate = [self calculatedBitRate];
|
||||
|
||||
id<AudioDataSource> audioDataSource = (id<AudioDataSource>)self.dataSource;
|
||||
|
||||
audioDataSource.averageBitRate = calculatedBitrate;
|
||||
audioDataSource.audioDataOffset = audioDataOffset;
|
||||
}
|
||||
}
|
||||
|
||||
-(double) duration
|
||||
{
|
||||
if (self->sampleRate <= 0)
|
||||
|
||||
Reference in New Issue
Block a user