Added basic support for Icecast streams
This commit is contained in:
parent
b07270910b
commit
cba7db8112
|
|
@ -68,6 +68,15 @@
|
|||
[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"];
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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)",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
@ -1868,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)
|
||||
{
|
||||
|
|
@ -1900,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);
|
||||
|
||||
|
|
@ -1917,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
|
||||
|
|
|
|||
|
|
@ -38,12 +38,19 @@
|
|||
@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;
|
||||
|
|
@ -147,70 +154,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
|
||||
|
|
@ -250,6 +417,11 @@
|
|||
|
||||
self->isInErrorState = NO;
|
||||
|
||||
if (!self->supportsSeek && offset != self->relativePosition)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
[self openForSeek:YES];
|
||||
}
|
||||
|
||||
|
|
@ -260,6 +432,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)
|
||||
|
|
@ -290,7 +479,9 @@
|
|||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
self->supportsSeek = NO;
|
||||
|
||||
self->currentUrl = url;
|
||||
|
||||
if (url == nil)
|
||||
|
|
@ -300,7 +491,7 @@
|
|||
|
||||
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]);
|
||||
|
||||
|
|
@ -312,6 +503,9 @@
|
|||
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);
|
||||
|
||||
|
|
@ -323,6 +517,8 @@
|
|||
|
||||
return;
|
||||
}
|
||||
|
||||
CFReadStreamSetProperty(stream, (__bridge CFStringRef)NSStreamNetworkServiceTypeBackground, (__bridge CFStringRef)NSStreamNetworkServiceTypeBackground);
|
||||
|
||||
if (!CFReadStreamSetProperty(stream, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue))
|
||||
{
|
||||
|
|
@ -345,9 +541,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];
|
||||
|
|
@ -394,4 +587,9 @@
|
|||
return [NSString stringWithFormat:@"HTTP data source with file length: %lld and position: %lld", self.length, self.position];
|
||||
}
|
||||
|
||||
-(BOOL) supportsSeek
|
||||
{
|
||||
return self->supportsSeek;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
|||
Loading…
Reference in New Issue