Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c7e90e4d2e | |||
| cba7db8112 | |||
| 1333f7f025 | |||
| ff779b669b | |||
| b07270910b | |||
| 4d9cea0a31 | |||
| acdf65c7cb | |||
| ac951bfc7a | |||
| 8c64914314 |
@@ -27,7 +27,6 @@
|
||||
A17FFB6318A0028300BAA7FF /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A17FFB6218A0028300BAA7FF /* AudioToolbox.framework */; };
|
||||
A17FFB6918A002E400BAA7FF /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1F5E491189EB3F20070B03F /* AVFoundation.framework */; };
|
||||
A1EBEE64188DE34500681B04 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1EBEE63188DE34500681B04 /* SystemConfiguration.framework */; };
|
||||
A1F3410A1908185900CA7755 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1F341091908185900CA7755 /* Accelerate.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -66,7 +65,6 @@
|
||||
A142571C18907861005F0129 /* airplane.aac */ = {isa = PBXFileReference; lastKnownFileType = file; path = airplane.aac; sourceTree = "<group>"; };
|
||||
A17FFB6218A0028300BAA7FF /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
|
||||
A1EBEE63188DE34500681B04 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
|
||||
A1F341091908185900CA7755 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
|
||||
A1F5E48F189EB3CB0070B03F /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; };
|
||||
A1F5E491189EB3F20070B03F /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
|
||||
/* End PBXFileReference section */
|
||||
@@ -76,7 +74,6 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A1F3410A1908185900CA7755 /* Accelerate.framework in Frameworks */,
|
||||
A17FFB6918A002E400BAA7FF /* AVFoundation.framework in Frameworks */,
|
||||
A17FFB6318A0028300BAA7FF /* AudioToolbox.framework in Frameworks */,
|
||||
A1EBEE64188DE34500681B04 /* SystemConfiguration.framework in Frameworks */,
|
||||
@@ -122,7 +119,6 @@
|
||||
A1115933188D686000641365 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A1F341091908185900CA7755 /* Accelerate.framework */,
|
||||
A17FFB6218A0028300BAA7FF /* AudioToolbox.framework */,
|
||||
A1F5E491189EB3F20070B03F /* AVFoundation.framework */,
|
||||
A1F5E48F189EB3CB0070B03F /* AudioUnit.framework */,
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
audioPlayer = [[STKAudioPlayer alloc] initWithOptions:(STKAudioPlayerOptions){ .flushQueueOnSeek = YES, .enableVolumeMixer = NO, .equalizerBandFrequencies = {50, 100, 200, 400, 800, 1600, 2600, 16000} }];
|
||||
audioPlayer.meteringEnabled = YES;
|
||||
audioPlayer.volume = 1;
|
||||
|
||||
|
||||
AudioPlayerView* audioPlayerView = [[AudioPlayerView alloc] initWithFrame:self.window.bounds andAudioPlayer:audioPlayer];
|
||||
|
||||
@@ -60,13 +61,22 @@
|
||||
|
||||
-(void) audioPlayerViewPlayFromHTTPSelected:(AudioPlayerView*)audioPlayerView
|
||||
{
|
||||
NSURL* url = [NSURL URLWithString:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"];
|
||||
NSURL* url = [NSURL URLWithString:@"http://www.abstractpath.com/files/audiosamples/sample.mp3"];
|
||||
|
||||
STKDataSource* dataSource = [STKAudioPlayer dataSourceFromURL:url];
|
||||
|
||||
[audioPlayer setDataSource:dataSource withQueueItemId:[[SampleQueueId alloc] initWithUrl:url andCount:0]];
|
||||
}
|
||||
|
||||
-(void) audioPlayerViewPlayFromIcecastSelected:(AudioPlayerView *)audioPlayerView
|
||||
{
|
||||
NSURL* url = [NSURL URLWithString:@"http://shoutmedia.abc.net.au:10326"];
|
||||
|
||||
STKDataSource* dataSource = [STKAudioPlayer dataSourceFromURL:url];
|
||||
|
||||
[audioPlayer setDataSource:dataSource withQueueItemId:[[SampleQueueId alloc] initWithUrl:url andCount:0]];
|
||||
}
|
||||
|
||||
-(void) audioPlayerViewQueueShortFileSelected:(AudioPlayerView*)audioPlayerView
|
||||
{
|
||||
NSString* path = [[NSBundle mainBundle] pathForResource:@"airplane" ofType:@"aac"];
|
||||
@@ -89,7 +99,7 @@
|
||||
|
||||
-(void) audioPlayerViewQueuePcmWaveFileSelected:(AudioPlayerView*)audioPlayerView
|
||||
{
|
||||
NSURL* url = [NSURL URLWithString:@"http://fs.bloom.fm/oss/audiosamples/perfectly.wav"];
|
||||
NSURL* url = [NSURL URLWithString:@"http://www.abstractpath.com/files/audiosamples/perfectly.wav"];
|
||||
|
||||
STKDataSource* dataSource = [STKAudioPlayer dataSourceFromURL:url];
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
|
||||
@protocol AudioPlayerViewDelegate<NSObject>
|
||||
-(void) audioPlayerViewPlayFromHTTPSelected:(AudioPlayerView*)audioPlayerView;
|
||||
-(void) audioPlayerViewPlayFromIcecastSelected:(AudioPlayerView*)audioPlayerView;
|
||||
-(void) audioPlayerViewQueueShortFileSelected:(AudioPlayerView*)audioPlayerView;
|
||||
-(void) audioPlayerViewPlayFromLocalFileSelected:(AudioPlayerView*)audioPlayerView;
|
||||
-(void) audioPlayerViewQueuePcmWaveFileSelected:(AudioPlayerView*)audioPlayerView;
|
||||
@@ -57,6 +58,7 @@
|
||||
UIButton* playButton;
|
||||
UIButton* stopButton;
|
||||
UIButton* playFromHTTPButton;
|
||||
UIButton* playFromIcecastButton;
|
||||
UIButton* queueShortFileButton;
|
||||
UIButton* queuePcmWaveFileFromHTTPButton;
|
||||
UIButton* playFromLocalFileButton;
|
||||
|
||||
@@ -40,9 +40,6 @@
|
||||
///
|
||||
|
||||
@interface AudioPlayerView()
|
||||
{
|
||||
NSMutableArray* meters;
|
||||
}
|
||||
-(void) setupTimer;
|
||||
-(void) updateControls;
|
||||
@end
|
||||
@@ -58,47 +55,50 @@
|
||||
{
|
||||
self.audioPlayer = audioPlayerIn;
|
||||
|
||||
self.audioPlayer.spectrumAnalyzerEnabled = YES;
|
||||
|
||||
CGSize size = CGSizeMake(220, 50);
|
||||
|
||||
playFromHTTPButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
|
||||
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];
|
||||
|
||||
playFromIcecastButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
|
||||
playFromIcecastButton.frame = CGRectMake((320 - size.width) / 2, frame.size.height * 0.10 + 50, size.width, size.height);
|
||||
[playFromIcecastButton addTarget:self action:@selector(playFromIcecasButtonTouched) forControlEvents:UIControlEventTouchUpInside];
|
||||
[playFromIcecastButton setTitle:@"Play from Icecast" forState:UIControlStateNormal];
|
||||
|
||||
playFromLocalFileButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
|
||||
playFromLocalFileButton.frame = CGRectMake((320 - size.width) / 2, frame.size.height * 0.10 + 50, size.width, size.height);
|
||||
playFromLocalFileButton.frame = CGRectMake((320 - size.width) / 2, frame.size.height * 0.10 + 100, 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.10 + 100, size.width, size.height);
|
||||
queueShortFileButton.frame = CGRectMake((320 - size.width) / 2, frame.size.height * 0.10 + 150, 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.frame = CGRectMake((320 - size.width) / 2, frame.size.height * 0.10 + 280, 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.frame = CGRectMake(30, 400, size.width, size.height);
|
||||
[playButton addTarget:self action:@selector(playButtonPressed) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
stopButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
|
||||
stopButton.frame = CGRectMake((320 - size.width) - 30, 380, size.width, size.height);
|
||||
stopButton.frame = CGRectMake((320 - size.width) - 30, 400, 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.frame = CGRectMake((320 - size.width) - 30, 420, 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 = [[UISlider alloc] initWithFrame:CGRectMake(20, 320, queuePcmWaveFileFromHTTPButton.frame.origin.y + queuePcmWaveFileFromHTTPButton.frame.size.height + 20, 20)];
|
||||
slider.continuous = YES;
|
||||
[slider addTarget:self action:@selector(sliderChanged) forControlEvents:UIControlEventValueChanged];
|
||||
|
||||
@@ -111,35 +111,22 @@
|
||||
|
||||
[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)];
|
||||
label = [[UILabel alloc] initWithFrame:CGRectMake(0, slider.frame.origin.y + slider.frame.size.height + 40, 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 = [[UILabel alloc] initWithFrame:CGRectMake(0, slider.frame.origin.y + slider.frame.size.height + label.frame.size.height + 50, frame.size.width, 50)];
|
||||
|
||||
statusLabel.textAlignment = NSTextAlignmentCenter;
|
||||
|
||||
meters = [[NSMutableArray alloc] init];
|
||||
|
||||
meter = [[UIView alloc] initWithFrame:CGRectMake(0, 450, 0, 20)];
|
||||
|
||||
meter.backgroundColor = [UIColor greenColor];
|
||||
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
UIView* freqMeter = [[UIView alloc] initWithFrame:CGRectMake(i, self.bounds.size.height, 1, 0)];
|
||||
|
||||
freqMeter.backgroundColor = [UIColor blueColor];
|
||||
|
||||
[self addSubview:freqMeter];
|
||||
[meters addObject:freqMeter];
|
||||
|
||||
[self sendSubviewToBack:freqMeter];
|
||||
}
|
||||
|
||||
[self addSubview:slider];
|
||||
[self addSubview:playButton];
|
||||
[self addSubview:playFromHTTPButton];
|
||||
[self addSubview:playFromIcecastButton];
|
||||
[self addSubview:playFromLocalFileButton];
|
||||
[self addSubview:queueShortFileButton];
|
||||
[self addSubview:queuePcmWaveFileFromHTTPButton];
|
||||
@@ -212,18 +199,9 @@
|
||||
|
||||
statusLabel.text = audioPlayer.state == STKAudioPlayerStateBuffering ? @"buffering" : @"";
|
||||
|
||||
CGFloat newWidth = 320 * (([audioPlayer testPowerWithIndex:100] + 96) / 96);
|
||||
CGFloat newWidth = 320 * (([audioPlayer averagePowerInDecibelsForChannel:1] + 60) / 60);
|
||||
|
||||
meter.frame = CGRectMake(0, 460, newWidth, 20);
|
||||
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
UIView* freqMeter = [meters objectAtIndex:i];
|
||||
|
||||
CGFloat height = 200 * (([audioPlayer testPowerWithIndex:i] + 96) / 96);
|
||||
|
||||
freqMeter.frame = CGRectMake(freqMeter.frame.origin.x, self.bounds.size.height - height, 1, height);
|
||||
}
|
||||
}
|
||||
|
||||
-(void) playFromHTTPButtonTouched
|
||||
@@ -231,6 +209,11 @@
|
||||
[self.delegate audioPlayerViewPlayFromHTTPSelected:self];
|
||||
}
|
||||
|
||||
-(void) playFromIcecasButtonTouched
|
||||
{
|
||||
[self.delegate audioPlayerViewPlayFromIcecastSelected:self];
|
||||
}
|
||||
|
||||
-(void) playFromLocalFileButtonTouched
|
||||
{
|
||||
[self.delegate audioPlayerViewPlayFromLocalFileSelected:self];
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
-(void) playFromHTTP
|
||||
{
|
||||
[audioPlayer play:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"];
|
||||
[audioPlayer play:@"http://www.abstractpath.com/files/audiosamples/sample.mp3"];
|
||||
}
|
||||
|
||||
-(void) tick:(NSTimer*)timer
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
StreamingKit (formally Audjustable) is an audio playback and streaming library for iOS and Mac OSX. StreamingKit uses CoreAudio to decompress and playback audio (using hardware or software codecs) whilst providing a clean and simple object-oriented API.
|
||||
|
||||
The primary motivation of this project was to decouple the input data sources from the actual player logic in order to allow advanced customizable input handling such as HTTP streaming, encryption/decryption, auto-recovery, dynamic-buffering. StreamingKit is the only streaming and playback library that supports dead-easy [gapless playback](https://github.com/tumtumtum/StreamingKit/wiki/Gapless-playback) between audio files of differing formats.
|
||||
The primary motivation of this project was to decouple the input data sources from the actual player logic in order to allow advanced customizable input handling such as HTTP progressive download based streaming, encryption/decryption, auto-recovery, dynamic-buffering. StreamingKit is the only streaming and playback library that supports dead-easy [gapless playback](https://github.com/tumtumtum/StreamingKit/wiki/Gapless-playback) between audio files of differing formats.
|
||||
|
||||
## Main Features
|
||||
|
||||
@@ -34,7 +34,7 @@ There are two main classes. The `STKDataSource` class which is the abstract bas
|
||||
```objective-c
|
||||
STKAudioPlayer* audioPlayer = [[STKAudioPlayer alloc] init];
|
||||
|
||||
[audioPlayer play:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"];
|
||||
[audioPlayer play:@"http://www.abstractpath.com/files/audiosamples/sample.mp3"];
|
||||
```
|
||||
|
||||
### Gapless playback
|
||||
@@ -42,8 +42,8 @@ STKAudioPlayer* audioPlayer = [[STKAudioPlayer alloc] init];
|
||||
```objective-c
|
||||
STKAudioPlayer* audioPlayer = [[STKAudioPlayer alloc] init];
|
||||
|
||||
[audioPlayer queue:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"];
|
||||
[audioPlayer queue:@"http://fs.bloom.fm/oss/audiosamples/airplane.aac"];
|
||||
[audioPlayer queue:@"http://www.abstractpath.com/files/audiosamples/sample.mp3"];
|
||||
[audioPlayer queue:@"http://www.abstractpath.com/files/audiosamples/airplane.aac"];
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "StreamingKit"
|
||||
s.version = "0.1.19"
|
||||
s.version = "0.1.23"
|
||||
s.summary = "A fast and extensible audio streamer for iOS and OSX with support for gapless playback and custom (non-HTTP) sources."
|
||||
s.homepage = "https://github.com/tumtumtum/StreamingKit/"
|
||||
s.license = 'MIT'
|
||||
|
||||
@@ -10,29 +10,29 @@
|
||||
<string>StreamingKit</string>
|
||||
<key>IDESourceControlProjectOriginsDictionary</key>
|
||||
<dict>
|
||||
<key>DD310C30-B3D0-4BD7-9565-9F29F09CC4F8</key>
|
||||
<key>3E9414865BAE5433092B9D136FFC1F054EA505C2</key>
|
||||
<string>https://github.com/tumtumtum/StreamingKit.git</string>
|
||||
</dict>
|
||||
<key>IDESourceControlProjectPath</key>
|
||||
<string>StreamingKit.xcworkspace</string>
|
||||
<key>IDESourceControlProjectRelativeInstallPathDictionary</key>
|
||||
<dict>
|
||||
<key>DD310C30-B3D0-4BD7-9565-9F29F09CC4F8</key>
|
||||
<key>3E9414865BAE5433092B9D136FFC1F054EA505C2</key>
|
||||
<string>..</string>
|
||||
</dict>
|
||||
<key>IDESourceControlProjectURL</key>
|
||||
<string>https://github.com/tumtumtum/StreamingKit.git</string>
|
||||
<key>IDESourceControlProjectVersion</key>
|
||||
<integer>110</integer>
|
||||
<integer>111</integer>
|
||||
<key>IDESourceControlProjectWCCIdentifier</key>
|
||||
<string>DD310C30-B3D0-4BD7-9565-9F29F09CC4F8</string>
|
||||
<string>3E9414865BAE5433092B9D136FFC1F054EA505C2</string>
|
||||
<key>IDESourceControlProjectWCConfigurations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>DD310C30-B3D0-4BD7-9565-9F29F09CC4F8</string>
|
||||
<string>3E9414865BAE5433092B9D136FFC1F054EA505C2</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>StreamingKit</string>
|
||||
</dict>
|
||||
|
||||
@@ -37,8 +37,6 @@
|
||||
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 */; };
|
||||
A1F341041908183300CA7755 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1F341031908183300CA7755 /* Accelerate.framework */; };
|
||||
A1F341081908183A00CA7755 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1F341071908183A00CA7755 /* Accelerate.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -127,8 +125,6 @@
|
||||
A1E7C4FD188D5E550010896F /* STKLocalFileDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = STKLocalFileDataSource.h; sourceTree = "<group>"; };
|
||||
A1E7C4FE188D5E550010896F /* STKLocalFileDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STKLocalFileDataSource.m; sourceTree = "<group>"; };
|
||||
A1E7C507188D62D20010896F /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
|
||||
A1F341031908183300CA7755 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
|
||||
A1F341071908183A00CA7755 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/System/Library/Frameworks/Accelerate.framework; sourceTree = DEVELOPER_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -136,7 +132,6 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A1F341081908183A00CA7755 /* Accelerate.framework in Frameworks */,
|
||||
A1A4996B189E744400E2A2E2 /* Cocoa.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -155,7 +150,6 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A1F341041908183300CA7755 /* Accelerate.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -254,8 +248,6 @@
|
||||
A1E7C4CA188D57F50010896F /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A1F341071908183A00CA7755 /* Accelerate.framework */,
|
||||
A1F341031908183300CA7755 /* Accelerate.framework */,
|
||||
A1A499F6189E79EA00E2A2E2 /* AudioToolbox.framework */,
|
||||
A1C9767618981BFE0057F881 /* AudioUnit.framework */,
|
||||
A1E7C507188D62D20010896F /* UIKit.framework */,
|
||||
@@ -409,7 +401,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
CLASSPREFIX = STK;
|
||||
LastUpgradeCheck = 0510;
|
||||
LastUpgradeCheck = 0610;
|
||||
ORGANIZATIONNAME = "Thong Nguyen";
|
||||
};
|
||||
buildConfigurationList = A1E7C4C3188D57F50010896F /* Build configuration list for PBXProject "StreamingKit" */;
|
||||
@@ -562,6 +554,7 @@
|
||||
A1A49988189E744500E2A2E2 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(DEVELOPER_FRAMEWORKS_DIR)",
|
||||
@@ -582,6 +575,7 @@
|
||||
A1A49989189E744500E2A2E2 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
||||
@@ -146,8 +146,6 @@ typedef void(^STKFrameFilter)(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UIn
|
||||
@property (readwrite) BOOL meteringEnabled;
|
||||
/// Enables or disables the EQ
|
||||
@property (readwrite) BOOL equalizerEnabled;
|
||||
/// Enables or disables the spectrum analyzer (fft)
|
||||
@property (readwrite) BOOL spectrumAnalyzerEnabled;
|
||||
/// 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)
|
||||
@@ -265,6 +263,4 @@ typedef void(^STKFrameFilter)(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UIn
|
||||
/// Sets the gain value (from -96 low to +24 high) for an equalizer band (0 based index)
|
||||
-(void) setGain:(float)gain forEqualizerBand:(int)bandIndex;
|
||||
|
||||
-(float) testPowerWithIndex:(int)index;
|
||||
|
||||
@end
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
#import "STKQueueEntry.h"
|
||||
#import "NSMutableArray+STKAudioPlayer.h"
|
||||
#import "libkern/OSAtomic.h"
|
||||
#include <Accelerate/Accelerate.h>
|
||||
#import <float.h>
|
||||
|
||||
#ifndef DBL_MAX
|
||||
@@ -190,7 +189,6 @@ static AudioStreamBasicDescription canonicalAudioStreamBasicDescription;
|
||||
BOOL meteringEnabled;
|
||||
BOOL equalizerOn;
|
||||
BOOL equalizerEnabled;
|
||||
BOOL spectrumAnalyzerEnabled;
|
||||
STKAudioPlayerOptions options;
|
||||
|
||||
NSMutableArray* converterNodes;
|
||||
@@ -255,14 +253,6 @@ static AudioStreamBasicDescription canonicalAudioStreamBasicDescription;
|
||||
pthread_mutex_t mainThreadSyncCallMutex;
|
||||
pthread_cond_t mainThreadSyncCallReadyCondition;
|
||||
|
||||
float* window;
|
||||
float* obtainedReal;
|
||||
float* originalReal;
|
||||
int fftStride;
|
||||
|
||||
FFTSetup setupReal;
|
||||
DSPSplitComplex fftInput;
|
||||
|
||||
volatile BOOL waiting;
|
||||
volatile double requestedSeekTime;
|
||||
volatile BOOL disposeWasRequested;
|
||||
@@ -304,7 +294,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
.componentFlagsMask = 0
|
||||
};
|
||||
|
||||
const int bytesPerSample = sizeof(AudioSampleType);
|
||||
const int bytesPerSample = 2;
|
||||
|
||||
canonicalAudioStreamBasicDescription = (AudioStreamBasicDescription)
|
||||
{
|
||||
@@ -480,7 +470,6 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
|
||||
self->volume = 1.0;
|
||||
self->equalizerEnabled = optionsIn.equalizerBandFrequencies[0] != 0;
|
||||
self->spectrumAnalyzerEnabled = NO;
|
||||
|
||||
PopulateOptionsWithDefault(&options);
|
||||
|
||||
@@ -589,9 +578,6 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
pthread_cond_destroy(&mainThreadSyncCallReadyCondition);
|
||||
|
||||
free(readBuffer);
|
||||
free(originalReal);
|
||||
free(obtainedReal);
|
||||
free(window);
|
||||
}
|
||||
|
||||
-(void) startSystemBackgroundTask
|
||||
@@ -819,7 +805,11 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
|
||||
pthread_mutex_lock(&playerMutex);
|
||||
|
||||
entryToUpdate->audioStreamBasicDescription = newBasicDescription;
|
||||
if (entryToUpdate->audioStreamBasicDescription.mFormatID == 0)
|
||||
{
|
||||
entryToUpdate->audioStreamBasicDescription = newBasicDescription;
|
||||
}
|
||||
|
||||
entryToUpdate->sampleRate = entryToUpdate->audioStreamBasicDescription.mSampleRate;
|
||||
entryToUpdate->packetDuration = entryToUpdate->audioStreamBasicDescription.mFramesPerPacket / entryToUpdate->sampleRate;
|
||||
|
||||
@@ -900,13 +890,8 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
|
||||
if (pasbd.mFormatID == kAudioFormatMPEG4AAC_HE || pasbd.mFormatID == kAudioFormatMPEG4AAC_HE_V2)
|
||||
{
|
||||
//
|
||||
// We've found HE-AAC, remember this to tell the audio queue
|
||||
// when we construct it.
|
||||
//
|
||||
#if !TARGET_IPHONE_SIMULATOR
|
||||
currentlyReadingEntry->audioStreamBasicDescription = pasbd;
|
||||
#endif
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1883,7 +1868,7 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
|
||||
{
|
||||
OSStatus status;
|
||||
Boolean writable;
|
||||
UInt32 cookieSize;
|
||||
UInt32 cookieSize = 0;
|
||||
|
||||
if (memcmp(asbd, &audioConverterAudioStreamBasicDescription, sizeof(AudioStreamBasicDescription)) == 0)
|
||||
{
|
||||
@@ -1915,11 +1900,16 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
|
||||
|
||||
audioConverterAudioStreamBasicDescription = *asbd;
|
||||
|
||||
status = AudioFileStreamGetPropertyInfo(audioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, &writable);
|
||||
if (self->currentlyReadingEntry.dataSource.audioFileTypeHint != kAudioFileAAC_ADTSType)
|
||||
{
|
||||
status = AudioFileStreamGetPropertyInfo(audioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, &writable);
|
||||
|
||||
if (!status)
|
||||
{
|
||||
void* cookieData = alloca(cookieSize);
|
||||
if (status)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void* cookieData = alloca(cookieSize);
|
||||
|
||||
status = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, cookieData);
|
||||
|
||||
@@ -1932,6 +1922,8 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
|
||||
|
||||
if (status)
|
||||
{
|
||||
[self unexpectedError:STKAudioPlayerErrorAudioSystemError];
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -2926,115 +2918,6 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
|
||||
return averagePowerDb[channelNumber];
|
||||
}
|
||||
|
||||
-(BOOL) spectrumAnalyzerEnabled
|
||||
{
|
||||
return self->spectrumAnalyzerEnabled;
|
||||
}
|
||||
|
||||
-(void) setSpectrumAnalyzerEnabled:(BOOL)value
|
||||
{
|
||||
if (self->spectrumAnalyzerEnabled == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
self->spectrumAnalyzerEnabled = value;
|
||||
|
||||
if (!value)
|
||||
{
|
||||
[self removeFrameFilterWithName:@"STKSpectrumAnalyzerFilter"];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!obtainedReal)
|
||||
{
|
||||
int maxSamples = 4096;
|
||||
int log2n = log2f(maxSamples);
|
||||
int n = 1 << log2n;
|
||||
|
||||
fftStride = 1;
|
||||
int nOver2 = maxSamples / 2;
|
||||
|
||||
fftInput.realp = (float*)calloc(nOver2, sizeof(float));
|
||||
fftInput.imagp =(float*)calloc(nOver2, sizeof(float));
|
||||
|
||||
obtainedReal = (float*)calloc(n, sizeof(float));
|
||||
originalReal = (float*)calloc(n, sizeof(float));
|
||||
window = (float*)calloc(maxSamples, sizeof(float));
|
||||
|
||||
vDSP_blkman_window(window, maxSamples, 0);
|
||||
|
||||
setupReal = vDSP_create_fftsetup(log2n, FFT_RADIX2);
|
||||
}
|
||||
|
||||
[self appendFrameFilterWithName:@"STKSpectrumAnalyzerFilter" block:^(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UInt32 frameCount, void* frames)
|
||||
{
|
||||
int log2n = log2f(frameCount);
|
||||
int n = 1 << log2n;
|
||||
int nOver2 = frameCount / 2;
|
||||
SInt16* samples16 = (SInt16*)frames;
|
||||
SInt32* samples32 = (SInt32*)frames;
|
||||
|
||||
if (bytesPerFrame / channelsPerFrame == 2)
|
||||
{
|
||||
for (int i = 0, j = 0; i < frameCount * channelsPerFrame; i+= channelsPerFrame, j += 2)
|
||||
{
|
||||
originalReal[j] = samples16[i] / 32768.0;
|
||||
}
|
||||
}
|
||||
else if (bytesPerFrame / channelsPerFrame == 4)
|
||||
{
|
||||
for (int i = 0, j = 0; i < frameCount * channelsPerFrame; i+= channelsPerFrame, j+= 2)
|
||||
{
|
||||
originalReal[j] = samples32[i] / 32768.0;
|
||||
}
|
||||
}
|
||||
|
||||
vDSP_hann_window(window, n, 0);
|
||||
vDSP_vmul(originalReal, 2, window, 1, originalReal, 2, n);
|
||||
|
||||
vDSP_ctoz((COMPLEX*)originalReal, 2, &fftInput, 1, nOver2);
|
||||
vDSP_fft_zrip(setupReal, &fftInput, fftStride, log2n, FFT_FORWARD);
|
||||
|
||||
float one = 1;
|
||||
|
||||
float scale = (float)1.0 / (2 * n);
|
||||
vDSP_vsmul(fftInput.realp, 1, &scale, fftInput.realp, 1, nOver2);
|
||||
vDSP_vsmul(fftInput.imagp, 1, &scale, fftInput.imagp, 1, nOver2);
|
||||
|
||||
pthread_mutex_lock(&self->playerMutex);
|
||||
|
||||
vDSP_zvmags(&fftInput, 1, obtainedReal, 1, nOver2);
|
||||
vDSP_vdbcon(obtainedReal, 1, &one, obtainedReal, 1, nOver2, 0);
|
||||
|
||||
float vmin = -96;
|
||||
float vmax = 0;
|
||||
|
||||
vDSP_vclip(obtainedReal, 1, &vmin, &vmax, obtainedReal, 1, nOver2);
|
||||
|
||||
pthread_mutex_unlock(&self->playerMutex);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
-(float) testPowerWithIndex:(int)index
|
||||
{
|
||||
pthread_mutex_lock(&self->playerMutex);
|
||||
|
||||
if (!obtainedReal)
|
||||
{
|
||||
pthread_mutex_unlock(&self->playerMutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
float retval = obtainedReal[index];
|
||||
|
||||
pthread_mutex_unlock(&self->playerMutex);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
-(BOOL) meteringEnabled
|
||||
{
|
||||
return self->meteringEnabled;
|
||||
@@ -3064,12 +2947,11 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
self->meteringEnabled = value;
|
||||
|
||||
if (!value)
|
||||
{
|
||||
[self removeFrameFilterWithName:@"STKMeteringFilter"];
|
||||
self->meteringEnabled = NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -43,9 +43,10 @@
|
||||
|
||||
@interface STKCoreFoundationDataSource : STKDataSource
|
||||
{
|
||||
@public
|
||||
CFReadStreamRef stream;
|
||||
@protected
|
||||
BOOL isInErrorState;
|
||||
CFReadStreamRef stream;
|
||||
NSRunLoop* eventsRunLoop;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,8 +41,10 @@ static void ReadStreamCallbackProc(CFReadStreamRef stream, CFStreamEventType eve
|
||||
switch (eventType)
|
||||
{
|
||||
case kCFStreamEventErrorOccurred:
|
||||
{
|
||||
[datasource errorOccured];
|
||||
break;
|
||||
}
|
||||
case kCFStreamEventEndEncountered:
|
||||
[datasource eof];
|
||||
break;
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
|
||||
@interface STKDataSource : NSObject
|
||||
|
||||
@property (readonly) BOOL supportsSeek;
|
||||
@property (readonly) SInt64 position;
|
||||
@property (readonly) SInt64 length;
|
||||
@property (readonly) BOOL hasBytesAvailable;
|
||||
|
||||
@@ -79,4 +79,9 @@
|
||||
return 0;
|
||||
}
|
||||
|
||||
-(BOOL) supportsSeek
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -47,6 +47,7 @@ typedef void(^STKAsyncURLProvider)(STKHTTPDataSource* dataSource, BOOL forSeek,
|
||||
|
||||
+(AudioFileTypeID) audioFileTypeHintFromMimeType:(NSString*)fileExtension;
|
||||
-(id) initWithURL:(NSURL*)url;
|
||||
-(id) initWithURL:(NSURL *)url httpRequestHeaders:(NSDictionary *)httpRequestHeaders;
|
||||
-(id) initWithURLProvider:(STKURLProvider)urlProvider;
|
||||
-(id) initWithAsyncURLProvider:(STKAsyncURLProvider)asyncUrlProvider;
|
||||
-(NSRunLoop*) eventsRunLoop;
|
||||
|
||||
@@ -38,17 +38,25 @@
|
||||
@interface STKHTTPDataSource()
|
||||
{
|
||||
@private
|
||||
BOOL supportsSeek;
|
||||
UInt32 httpStatusCode;
|
||||
SInt64 seekStart;
|
||||
SInt64 relativePosition;
|
||||
SInt64 fileLength;
|
||||
int discontinuous;
|
||||
int requestSerialNumber;
|
||||
int prefixBytesRead;
|
||||
NSData* prefixBytes;
|
||||
NSMutableData* iceHeaderData;
|
||||
BOOL iceHeaderSearchComplete;
|
||||
BOOL iceHeaderAvailable;
|
||||
BOOL httpHeaderNotAvailable;
|
||||
|
||||
NSURL* currentUrl;
|
||||
STKAsyncURLProvider asyncUrlProvider;
|
||||
NSDictionary* httpHeaders;
|
||||
AudioFileTypeID audioFileTypeHint;
|
||||
NSDictionary* requestHeaders;
|
||||
}
|
||||
-(void) open;
|
||||
|
||||
@@ -61,6 +69,13 @@
|
||||
return [self initWithURLProvider:^NSURL* { return urlIn; }];
|
||||
}
|
||||
|
||||
-(id) initWithURL:(NSURL *)urlIn httpRequestHeaders:(NSDictionary *)httpRequestHeaders
|
||||
{
|
||||
self = [self initWithURLProvider:^NSURL* { return urlIn; }];
|
||||
self->requestHeaders = httpRequestHeaders;
|
||||
return self;
|
||||
}
|
||||
|
||||
-(id) initWithURLProvider:(STKURLProvider)urlProviderIn
|
||||
{
|
||||
urlProviderIn = [urlProviderIn copy];
|
||||
@@ -110,6 +125,8 @@
|
||||
@"audio/mpg": @(kAudioFileMP3Type),
|
||||
@"audio/mpeg": @(kAudioFileMP3Type),
|
||||
@"audio/wav": @(kAudioFileWAVEType),
|
||||
@"audio/x-wav": @(kAudioFileWAVEType),
|
||||
@"audio/vnd.wav": @(kAudioFileWAVEType),
|
||||
@"audio/aifc": @(kAudioFileAIFCType),
|
||||
@"audio/aiff": @(kAudioFileAIFFType),
|
||||
@"audio/x-m4a": @(kAudioFileM4AType),
|
||||
@@ -117,10 +134,18 @@
|
||||
@"audio/aacp": @(kAudioFileAAC_ADTSType),
|
||||
@"audio/m4a": @(kAudioFileM4AType),
|
||||
@"audio/mp4": @(kAudioFileMPEG4Type),
|
||||
@"video/mp4": @(kAudioFileMPEG4Type),
|
||||
@"audio/caf": @(kAudioFileCAFType),
|
||||
@"audio/x-caf": @(kAudioFileCAFType),
|
||||
@"audio/aac": @(kAudioFileAAC_ADTSType),
|
||||
@"audio/aacp": @(kAudioFileAAC_ADTSType),
|
||||
@"audio/ac3": @(kAudioFileAC3Type),
|
||||
@"audio/3gp": @(kAudioFile3GPType)
|
||||
@"audio/3gp": @(kAudioFile3GPType),
|
||||
@"video/3gp": @(kAudioFile3GPType),
|
||||
@"audio/3gpp": @(kAudioFile3GPType),
|
||||
@"video/3gpp": @(kAudioFile3GPType),
|
||||
@"audio/3gp2": @(kAudioFile3GP2Type),
|
||||
@"video/3gp2": @(kAudioFile3GP2Type)
|
||||
};
|
||||
});
|
||||
|
||||
@@ -139,70 +164,230 @@
|
||||
return audioFileTypeHint;
|
||||
}
|
||||
|
||||
-(void) dataAvailable
|
||||
-(BOOL) parseHttpHeader
|
||||
{
|
||||
if (stream == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.httpStatusCode == 0)
|
||||
{
|
||||
CFTypeRef response = CFReadStreamCopyProperty(stream, kCFStreamPropertyHTTPResponseHeader);
|
||||
if (!httpHeaderNotAvailable)
|
||||
{
|
||||
CFTypeRef response = CFReadStreamCopyProperty(stream, kCFStreamPropertyHTTPResponseHeader);
|
||||
|
||||
if (response)
|
||||
{
|
||||
httpHeaders = (__bridge_transfer NSDictionary*)CFHTTPMessageCopyAllHeaderFields((CFHTTPMessageRef)response);
|
||||
|
||||
self->httpStatusCode = (UInt32)CFHTTPMessageGetResponseStatusCode((CFHTTPMessageRef)response);
|
||||
|
||||
if (httpHeaders.count == 0)
|
||||
{
|
||||
httpHeaderNotAvailable = YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
self->httpStatusCode = (UInt32)CFHTTPMessageGetResponseStatusCode((CFHTTPMessageRef)response);
|
||||
}
|
||||
|
||||
CFRelease(response);
|
||||
}
|
||||
|
||||
if (self.httpStatusCode == 200)
|
||||
{
|
||||
if (seekStart == 0)
|
||||
{
|
||||
fileLength = (SInt64)[[httpHeaders objectForKey:@"Content-Length"] longLongValue];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (httpHeaderNotAvailable)
|
||||
{
|
||||
if (self->iceHeaderSearchComplete && !self->iceHeaderAvailable)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (!self->iceHeaderSearchComplete)
|
||||
{
|
||||
UInt8 byte;
|
||||
UInt8 bytes[4];
|
||||
UInt8 terminal1[] = { '\n', '\n' };
|
||||
UInt8 terminal2[] = { '\r', '\n', '\r', '\n' };
|
||||
|
||||
if (iceHeaderData == nil)
|
||||
{
|
||||
iceHeaderData = [NSMutableData dataWithCapacity:1024];
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (![self hasBytesAvailable])
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
int read = [super readIntoBuffer:&byte withSize:1];
|
||||
|
||||
if (read <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
[iceHeaderData appendBytes:&byte length:read];
|
||||
|
||||
if (iceHeaderData.length >= sizeof(terminal1))
|
||||
{
|
||||
[iceHeaderData getBytes:&bytes[0] range:(NSRange){.location = iceHeaderData.length - sizeof(terminal1), .length = sizeof(terminal1)}];
|
||||
|
||||
if (memcmp(&terminal1[0], &bytes[0], sizeof(terminal1)) == 0)
|
||||
{
|
||||
self->iceHeaderAvailable = YES;
|
||||
self->iceHeaderSearchComplete = YES;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (iceHeaderData.length >= sizeof(terminal2))
|
||||
{
|
||||
[iceHeaderData getBytes:&bytes[0] range:(NSRange){.location = iceHeaderData.length - sizeof(terminal2), .length = sizeof(terminal2)}];
|
||||
|
||||
if (memcmp(&terminal2[0], &bytes[0], sizeof(terminal2)) == 0)
|
||||
{
|
||||
self->iceHeaderAvailable = YES;
|
||||
self->iceHeaderSearchComplete = YES;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (iceHeaderData.length >=4)
|
||||
{
|
||||
[iceHeaderData getBytes:&bytes[0] length:4];
|
||||
|
||||
if (memcmp(bytes, "ICY", 3) != 0 && memcmp(bytes, "HTTP", 4) != 0)
|
||||
{
|
||||
self->iceHeaderAvailable = NO;
|
||||
self->iceHeaderSearchComplete = YES;
|
||||
prefixBytes = iceHeaderData;
|
||||
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!self->iceHeaderSearchComplete)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
NSCharacterSet* characterSet = [NSCharacterSet characterSetWithCharactersInString:@"\r\n"];
|
||||
NSString* fullString = [[NSString alloc] initWithData:self->iceHeaderData encoding:NSUTF8StringEncoding];
|
||||
|
||||
NSArray* strings = [fullString componentsSeparatedByCharactersInSet:characterSet];
|
||||
|
||||
httpHeaders = [NSMutableDictionary dictionary];
|
||||
|
||||
for (NSString* s in strings)
|
||||
{
|
||||
if (s.length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ([s hasPrefix:@"ICY"])
|
||||
{
|
||||
NSArray* ss = [s componentsSeparatedByString:@" "];
|
||||
|
||||
if (ss.count >= 2)
|
||||
{
|
||||
self->httpStatusCode = [ss[1] intValue];
|
||||
}
|
||||
}
|
||||
|
||||
NSRange range = [s rangeOfString:@":"];
|
||||
|
||||
if (range.location == NSNotFound)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
NSString* key = [s substringWithRange: (NSRange){.location = 0, .length = range.location}];
|
||||
NSString* value = [s substringFromIndex:range.location + 1];
|
||||
|
||||
[httpHeaders setValue:value forKey:key];
|
||||
}
|
||||
}
|
||||
|
||||
if (([httpHeaders objectForKey:@"Accepts-Ranges"] ?: [httpHeaders objectForKey:@"accepts-ranges"]) != nil)
|
||||
{
|
||||
self->supportsSeek = YES;
|
||||
}
|
||||
|
||||
if (self.httpStatusCode == 200)
|
||||
{
|
||||
if (seekStart == 0)
|
||||
{
|
||||
id value = [httpHeaders objectForKey:@"Content-Length"] ?: [httpHeaders objectForKey:@"content-length"];
|
||||
|
||||
fileLength = (SInt64)[value longLongValue];
|
||||
}
|
||||
|
||||
NSString* contentType = [httpHeaders objectForKey:@"Content-Type"] ?: [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"] ?: [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 NO;
|
||||
}
|
||||
else if (self.httpStatusCode >= 300)
|
||||
{
|
||||
[self errorOccured];
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
-(void) dataAvailable
|
||||
{
|
||||
if (stream == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.httpStatusCode == 0)
|
||||
{
|
||||
if ([self parseHttpHeader])
|
||||
{
|
||||
if ([self hasBytesAvailable])
|
||||
{
|
||||
[super dataAvailable];
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[super dataAvailable];
|
||||
else
|
||||
{
|
||||
[super dataAvailable];
|
||||
}
|
||||
}
|
||||
|
||||
-(SInt64) position
|
||||
@@ -242,6 +427,11 @@
|
||||
|
||||
self->isInErrorState = NO;
|
||||
|
||||
if (!self->supportsSeek && offset != self->relativePosition)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
[self openForSeek:YES];
|
||||
}
|
||||
|
||||
@@ -252,6 +442,23 @@
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (prefixBytes != nil)
|
||||
{
|
||||
int count = MIN(size, (int)prefixBytes.length - prefixBytesRead);
|
||||
|
||||
[prefixBytes getBytes:buffer length:count];
|
||||
|
||||
prefixBytesRead += count;
|
||||
|
||||
if (prefixBytesRead >= prefixBytes.length)
|
||||
{
|
||||
prefixBytes = nil;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
int read = (int)CFReadStreamRead(stream, buffer, size);
|
||||
|
||||
if (read < 0)
|
||||
@@ -282,7 +489,9 @@
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
self->supportsSeek = NO;
|
||||
|
||||
self->currentUrl = url;
|
||||
|
||||
if (url == nil)
|
||||
@@ -292,13 +501,22 @@
|
||||
|
||||
CFHTTPMessageRef message = CFHTTPMessageCreateRequest(NULL, (CFStringRef)@"GET", (__bridge CFURLRef)self->currentUrl, kCFHTTPVersion1_1);
|
||||
|
||||
if (seekStart > 0)
|
||||
if (seekStart > 0 && supportsSeek)
|
||||
{
|
||||
CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Range"), (__bridge CFStringRef)[NSString stringWithFormat:@"bytes=%lld-", seekStart]);
|
||||
|
||||
discontinuous = YES;
|
||||
}
|
||||
|
||||
for (NSString* key in self->requestHeaders)
|
||||
{
|
||||
NSString* value = [self->requestHeaders objectForKey:key];
|
||||
CFHTTPMessageSetHeaderFieldValue(message, (__bridge CFStringRef)key, (__bridge CFStringRef)value);
|
||||
}
|
||||
|
||||
CFHTTPMessageSetHeaderFieldValue(message, (__bridge CFStringRef)@"Accept", (__bridge CFStringRef)@"*/*");
|
||||
CFHTTPMessageSetHeaderFieldValue(message, (__bridge CFStringRef)@"Ice-MetaData", (__bridge CFStringRef)@"0");
|
||||
|
||||
stream = CFReadStreamCreateForHTTPRequest(NULL, message);
|
||||
|
||||
if (stream == nil)
|
||||
@@ -309,6 +527,8 @@
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CFReadStreamSetProperty(stream, (__bridge CFStringRef)NSStreamNetworkServiceTypeBackground, (__bridge CFStringRef)NSStreamNetworkServiceTypeBackground);
|
||||
|
||||
if (!CFReadStreamSetProperty(stream, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue))
|
||||
{
|
||||
@@ -331,9 +551,6 @@
|
||||
{
|
||||
NSDictionary* sslSettings = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
(NSString*)kCFStreamSocketSecurityLevelNegotiatedSSL, kCFStreamSSLLevel,
|
||||
[NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates,
|
||||
[NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredRoots,
|
||||
[NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot,
|
||||
[NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain,
|
||||
[NSNull null], kCFStreamSSLPeerName,
|
||||
nil];
|
||||
@@ -380,4 +597,9 @@
|
||||
return [NSString stringWithFormat:@"HTTP data source with file length: %lld and position: %lld", self.length, self.position];
|
||||
}
|
||||
|
||||
-(BOOL) supportsSeek
|
||||
{
|
||||
return self->supportsSeek;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Reference in New Issue
Block a user