Compare commits

..

9 Commits

Author SHA1 Message Date
Thong Nguyen c7e90e4d2e Merge branch 'master' of https://github.com/tumtumtum/StreamingKit 2014-11-08 15:45:43 +00:00
Thong Nguyen cba7db8112 Added basic support for Icecast streams 2014-11-08 15:45:40 +00:00
Thong Nguyen 1333f7f025 Merge pull request #138 from danielgindi/master
Missing mime types
2014-10-31 09:36:23 +00:00
Daniel Cohen Gindi ff779b669b Missing mime types 2014-10-31 10:54:49 +02:00
Thong Nguyen b07270910b Fixed URL for HTTP pointing to local file. Oops 2014-09-11 15:29:32 +01:00
Thong Nguyen 4d9cea0a31 Fixed HE-AAC format being stripped 2014-08-20 15:49:36 +01:00
Thong Nguyen acdf65c7cb Merge pull request #128 from kwillick/master
Support adding http headers to a STKHTTPDataSource
2014-08-19 09:56:21 +01:00
Kipp Hickman ac951bfc7a Added ability to add http headers to a STKHTTPDataSource. 2014-08-15 12:00:49 -07:00
Thong Nguyen 8c64914314 Updated test URLs to use abstractpath.com 2014-06-10 16:29:30 +01:00
15 changed files with 361 additions and 98 deletions
+12 -2
View File
@@ -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];
+2
View File
@@ -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;
+20 -9
View File
@@ -61,39 +61,44 @@
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];
@@ -106,11 +111,11 @@
[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;
@@ -121,6 +126,7 @@
[self addSubview:slider];
[self addSubview:playButton];
[self addSubview:playFromHTTPButton];
[self addSubview:playFromIcecastButton];
[self addSubview:playFromLocalFileButton];
[self addSubview:queueShortFileButton];
[self addSubview:queuePcmWaveFileFromHTTPButton];
@@ -203,6 +209,11 @@
[self.delegate audioPlayerViewPlayFromHTTPSelected:self];
}
-(void) playFromIcecasButtonTouched
{
[self.delegate audioPlayerViewPlayFromIcecastSelected:self];
}
-(void) playFromLocalFileButtonTouched
{
[self.delegate audioPlayerViewPlayFromLocalFileSelected:self];
+1 -1
View File
@@ -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
+4 -4
View File
@@ -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 -1
View File
@@ -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>
@@ -401,7 +401,7 @@
isa = PBXProject;
attributes = {
CLASSPREFIX = STK;
LastUpgradeCheck = 0510;
LastUpgradeCheck = 0610;
ORGANIZATIONNAME = "Thong Nguyen";
};
buildConfigurationList = A1E7C4C3188D57F50010896F /* Build configuration list for PBXProject "StreamingKit" */;
@@ -554,6 +554,7 @@
A1A49988189E744500E2A2E2 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
COMBINE_HIDPI_IMAGES = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(DEVELOPER_FRAMEWORKS_DIR)",
@@ -574,6 +575,7 @@
A1A49989189E744500E2A2E2 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
COMBINE_HIDPI_IMAGES = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
+19 -13
View File
@@ -294,7 +294,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
.componentFlagsMask = 0
};
const int bytesPerSample = sizeof(AudioSampleType);
const int bytesPerSample = 2;
canonicalAudioStreamBasicDescription = (AudioStreamBasicDescription)
{
@@ -805,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;
@@ -886,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;
}
}
@@ -1869,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)
{
@@ -1901,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);
@@ -1918,6 +1922,8 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
if (status)
{
[self unexpectedError:STKAudioPlayerErrorAudioSystemError];
return;
}
}
@@ -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;
+283 -61
View File
@@ -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