Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da71b04aaf | |||
| 55a314b966 | |||
| 8fa821a944 | |||
| 0f69b7ea76 | |||
| 4d0fccdd70 | |||
| 5909657368 | |||
| 50bec46acc | |||
| dec8b87498 | |||
| f872de223d | |||
| 4f72249c94 | |||
| 39f0d8bdfe | |||
| 8708e48395 | |||
| 94e6cbf41b | |||
| a5b6360b1c | |||
| 09e5602464 | |||
| 4c7ce6c4ec | |||
| 5784504e38 | |||
| e73df7fd86 | |||
| 9a4b4a617d | |||
| 7d4d7b847e | |||
| 61cb8a8a7b | |||
| 8e2e451e9c | |||
| 88be5b33c6 | |||
| 50a8b610c4 | |||
| c1b3b5d8cc | |||
| 477b1f175f | |||
| 000930a295 | |||
| 269f335ee4 | |||
| 162d964372 | |||
| ef7f42c97e | |||
| ea9e40b17a | |||
| 9510d74e58 | |||
| c7e90e4d2e | |||
| cba7db8112 | |||
| 1333f7f025 | |||
| ff779b669b | |||
| b07270910b |
@@ -230,7 +230,7 @@
|
||||
A1115929188D686000641365 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0510;
|
||||
LastUpgradeCheck = 0710;
|
||||
ORGANIZATIONNAME = "Thong Nguyen";
|
||||
TargetAttributes = {
|
||||
A111594B188D686000641365 = {
|
||||
@@ -346,6 +346,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
@@ -422,6 +423,7 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 6.0;
|
||||
LLVM_LTO = YES;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "abstractpath.com.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
WRAPPER_EXTENSION = app;
|
||||
};
|
||||
@@ -438,6 +440,7 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 6.0;
|
||||
LLVM_LTO = YES;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "abstractpath.com.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
WRAPPER_EXTENSION = app;
|
||||
};
|
||||
@@ -459,6 +462,7 @@
|
||||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = "ExampleAppTests/ExampleAppTests-Info.plist";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "abstractpath.com.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
TEST_HOST = "$(BUNDLE_LOADER)";
|
||||
WRAPPER_EXTENSION = xctest;
|
||||
@@ -477,6 +481,7 @@
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "ExampleApp/ExampleApp-Prefix.pch";
|
||||
INFOPLIST_FILE = "ExampleAppTests/ExampleAppTests-Info.plist";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "abstractpath.com.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
TEST_HOST = "$(BUNDLE_LOADER)";
|
||||
WRAPPER_EXTENSION = xctest;
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration, sizeof(bufferLength), &bufferLength);
|
||||
|
||||
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
self.window.rootViewController = [[UIViewController alloc] init];
|
||||
|
||||
self.window.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
@@ -39,7 +40,6 @@
|
||||
audioPlayer.meteringEnabled = YES;
|
||||
audioPlayer.volume = 1;
|
||||
|
||||
|
||||
AudioPlayerView* audioPlayerView = [[AudioPlayerView alloc] initWithFrame:self.window.bounds andAudioPlayer:audioPlayer];
|
||||
|
||||
audioPlayerView.delegate = self;
|
||||
@@ -47,9 +47,9 @@
|
||||
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
|
||||
[self becomeFirstResponder];
|
||||
|
||||
[self.window addSubview:audioPlayerView];
|
||||
|
||||
[self.window makeKeyAndVisible];
|
||||
|
||||
[self.window.rootViewController.view addSubview:audioPlayerView];
|
||||
|
||||
return YES;
|
||||
}
|
||||
@@ -61,13 +61,22 @@
|
||||
|
||||
-(void) audioPlayerViewPlayFromHTTPSelected:(AudioPlayerView*)audioPlayerView
|
||||
{
|
||||
NSURL* url = [NSURL URLWithString:@"file:///Users/tum/Temp/airplane-cut.aac"];
|
||||
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"];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -58,42 +58,47 @@
|
||||
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.frame = CGRectMake((frame.size.width - 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((frame.size.width - size.width) / 2, frame.size.height * 0.10 + 35, 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((frame.size.width - size.width) / 2, frame.size.height * 0.10 + 70, 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((frame.size.width - size.width) / 2, frame.size.height * 0.10 + 105, 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((frame.size.width - size.width) / 2, frame.size.height * 0.10 + 140, 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((frame.size.width - 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((frame.size.width - size.width) - 30, 430, 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];
|
||||
|
||||
@@ -101,16 +106,16 @@
|
||||
|
||||
repeatSwitch = [[UISwitch alloc] initWithFrame:CGRectMake(30, frame.size.height * 0.15 + 180, size.width, size.height)];
|
||||
|
||||
enableEqSwitch = [[UISwitch alloc] initWithFrame:CGRectMake(320 - size.width - 30, frame.size.height * 0.15 + 180, size.width, size.height)];
|
||||
enableEqSwitch = [[UISwitch alloc] initWithFrame:CGRectMake(frame.size.width - size.width - 30, frame.size.height * 0.15 + 180, size.width, size.height)];
|
||||
enableEqSwitch.on = audioPlayer.equalizerEnabled;
|
||||
|
||||
[enableEqSwitch addTarget:self action:@selector(onEnableEqSwitch) forControlEvents:UIControlEventAllTouchEvents];
|
||||
|
||||
label = [[UILabel alloc] initWithFrame:CGRectMake(0, slider.frame.origin.y + slider.frame.size.height + 10, frame.size.width, 25)];
|
||||
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];
|
||||
@@ -174,6 +180,17 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (audioPlayer.currentlyPlayingQueueItemId == nil)
|
||||
{
|
||||
slider.value = 0;
|
||||
slider.minimumValue = 0;
|
||||
slider.maximumValue = 0;
|
||||
|
||||
label.text = @"";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (audioPlayer.duration != 0)
|
||||
{
|
||||
slider.minimumValue = 0;
|
||||
@@ -188,7 +205,7 @@
|
||||
slider.minimumValue = 0;
|
||||
slider.maximumValue = 0;
|
||||
|
||||
label.text = @"";
|
||||
label.text = [NSString stringWithFormat:@"Live stream %@", [self formatTimeFromSeconds:audioPlayer.progress]];
|
||||
}
|
||||
|
||||
statusLabel.text = audioPlayer.state == STKAudioPlayerStateBuffering ? @"buffering" : @"";
|
||||
@@ -203,6 +220,11 @@
|
||||
[self.delegate audioPlayerViewPlayFromHTTPSelected:self];
|
||||
}
|
||||
|
||||
-(void) playFromIcecasButtonTouched
|
||||
{
|
||||
[self.delegate audioPlayerViewPlayFromIcecastSelected:self];
|
||||
}
|
||||
|
||||
-(void) playFromLocalFileButtonTouched
|
||||
{
|
||||
[self.delegate audioPlayerViewPlayFromLocalFileSelected:self];
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>abstractpath.com.${PRODUCT_NAME:rfc1034identifier}</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
|
||||
@@ -5,16 +5,31 @@
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,31 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"idiom" : "iphone",
|
||||
"subtype" : "736h",
|
||||
"filename" : "TX6sV.png",
|
||||
"minimum-system-version" : "8.0",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"orientation" : "landscape",
|
||||
"idiom" : "iphone",
|
||||
"extent" : "full-screen",
|
||||
"minimum-system-version" : "8.0",
|
||||
"subtype" : "736h",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"idiom" : "iphone",
|
||||
"subtype" : "667h",
|
||||
"filename" : "dBEHd.png",
|
||||
"minimum-system-version" : "8.0",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"orientation" : "portrait",
|
||||
"idiom" : "iphone",
|
||||
@@ -8,11 +34,12 @@
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"orientation" : "portrait",
|
||||
"extent" : "full-screen",
|
||||
"idiom" : "iphone",
|
||||
"subtype" : "retina4",
|
||||
"extent" : "full-screen",
|
||||
"filename" : "TX6sV-2.png",
|
||||
"minimum-system-version" : "7.0",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
@@ -42,6 +69,26 @@
|
||||
"extent" : "full-screen",
|
||||
"minimum-system-version" : "7.0",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"orientation" : "portrait",
|
||||
"idiom" : "iphone",
|
||||
"extent" : "full-screen",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"orientation" : "portrait",
|
||||
"idiom" : "iphone",
|
||||
"extent" : "full-screen",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"orientation" : "portrait",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "TX6sV-1.png",
|
||||
"extent" : "full-screen",
|
||||
"subtype" : "retina4",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.3 KiB |
@@ -7,7 +7,7 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>abstractpath.com.${PRODUCT_NAME:rfc1034identifier}</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
|
||||
CGFloat meterWidth = 0;
|
||||
|
||||
if (audioPlayer.duration != 0)
|
||||
if (audioPlayer.currentlyPlayingQueueItemId != nil)
|
||||
{
|
||||
slider.minValue = 0;
|
||||
slider.maxValue = audioPlayer.duration;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
Inspired by Matt Gallagher's AudioStreamer:
|
||||
https://github.com/mattgallagher/AudioStreamer
|
||||
|
||||
Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved.
|
||||
Copyright (c) 2015 Thong Nguyen (tumtumtum@gmail.com). All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
@@ -15,12 +15,12 @@
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. All advertising materials mentioning features or use of this software
|
||||
must display the following acknowledgement:
|
||||
This product includes software developed by the <organization>.
|
||||
This product includes software developed by Thong Nguyen.
|
||||
4. Neither the name of the <organization> nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ''AS IS'' AND ANY
|
||||
THIS SOFTWARE IS PROVIDED BY THONG NGUYEN ''AS IS'' AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "StreamingKit"
|
||||
s.version = "0.1.23"
|
||||
s.version = "0.1.27"
|
||||
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>
|
||||
|
||||
@@ -7,6 +7,14 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
5B949CD21A1140E4005675A0 /* STKAudioPlayer.h in Headers */ = {isa = PBXBuildFile; fileRef = A1E7C4F1188D5E550010896F /* STKAudioPlayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5B949CD31A1140E4005675A0 /* STKAutoRecoveringHTTPDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = A1E7C4F3188D5E550010896F /* STKAutoRecoveringHTTPDataSource.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5B949CD41A1140E4005675A0 /* STKCoreFoundationDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = A1E7C4F5188D5E550010896F /* STKCoreFoundationDataSource.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5B949CD51A1140E4005675A0 /* STKDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = A1E7C4F7188D5E550010896F /* STKDataSource.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5B949CD61A1140E4005675A0 /* STKDataSourceWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = A1E7C4F9188D5E550010896F /* STKDataSourceWrapper.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5B949CD71A1140E4005675A0 /* STKHTTPDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = A1E7C4FB188D5E550010896F /* STKHTTPDataSource.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5B949CD81A1140E4005675A0 /* STKLocalFileDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = A1E7C4FD188D5E550010896F /* STKLocalFileDataSource.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5B949CD91A1140E4005675A0 /* STKQueueEntry.h in Headers */ = {isa = PBXBuildFile; fileRef = A1BF65D0189A6582004DD08C /* STKQueueEntry.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
A1A4996B189E744400E2A2E2 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A4996A189E744400E2A2E2 /* Cocoa.framework */; };
|
||||
A1A49975189E744500E2A2E2 /* StreamingKitMac.m in Sources */ = {isa = PBXBuildFile; fileRef = A1A49974189E744500E2A2E2 /* StreamingKitMac.m */; };
|
||||
A1A4997B189E744500E2A2E2 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1E7C4D9188D57F60010896F /* XCTest.framework */; };
|
||||
@@ -314,6 +322,21 @@
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
5B949CD11A1140CF005675A0 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5B949CD21A1140E4005675A0 /* STKAudioPlayer.h in Headers */,
|
||||
5B949CD31A1140E4005675A0 /* STKAutoRecoveringHTTPDataSource.h in Headers */,
|
||||
5B949CD41A1140E4005675A0 /* STKCoreFoundationDataSource.h in Headers */,
|
||||
5B949CD51A1140E4005675A0 /* STKDataSource.h in Headers */,
|
||||
5B949CD61A1140E4005675A0 /* STKDataSourceWrapper.h in Headers */,
|
||||
5B949CD71A1140E4005675A0 /* STKHTTPDataSource.h in Headers */,
|
||||
5B949CD81A1140E4005675A0 /* STKLocalFileDataSource.h in Headers */,
|
||||
5B949CD91A1140E4005675A0 /* STKQueueEntry.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
A1A49967189E744400E2A2E2 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -366,6 +389,7 @@
|
||||
A1E7C4C4188D57F50010896F /* Sources */,
|
||||
A1E7C4C5188D57F50010896F /* Frameworks */,
|
||||
A1E7C4C6188D57F50010896F /* CopyFiles */,
|
||||
5B949CD11A1140CF005675A0 /* Headers */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -401,7 +425,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
CLASSPREFIX = STK;
|
||||
LastUpgradeCheck = 0510;
|
||||
LastUpgradeCheck = 0710;
|
||||
ORGANIZATIONNAME = "Thong Nguyen";
|
||||
};
|
||||
buildConfigurationList = A1E7C4C3188D57F50010896F /* Build configuration list for PBXProject "StreamingKit" */;
|
||||
@@ -554,6 +578,7 @@
|
||||
A1A49988189E744500E2A2E2 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(DEVELOPER_FRAMEWORKS_DIR)",
|
||||
@@ -567,6 +592,7 @@
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PUBLIC_HEADERS_FOLDER_PATH = include/StreamingKit;
|
||||
SDKROOT = macosx;
|
||||
};
|
||||
name = Debug;
|
||||
@@ -574,6 +600,7 @@
|
||||
A1A49989189E744500E2A2E2 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -584,6 +611,7 @@
|
||||
GCC_PREFIX_HEADER = "StreamingKitMac/StreamingKitMac-Prefix.pch";
|
||||
MACOSX_DEPLOYMENT_TARGET = "";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PUBLIC_HEADERS_FOLDER_PATH = include/StreamingKit;
|
||||
SDKROOT = macosx;
|
||||
};
|
||||
name = Release;
|
||||
@@ -605,6 +633,7 @@
|
||||
);
|
||||
INFOPLIST_FILE = "StreamingKitMacTests/StreamingKitMacTests-Info.plist";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.abstractpath.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
WRAPPER_EXTENSION = xctest;
|
||||
@@ -625,6 +654,7 @@
|
||||
GCC_PREFIX_HEADER = "StreamingKitMac/StreamingKitMac-Prefix.pch";
|
||||
INFOPLIST_FILE = "StreamingKitMacTests/StreamingKitMacTests-Info.plist";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.abstractpath.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
WRAPPER_EXTENSION = xctest;
|
||||
@@ -648,6 +678,7 @@
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
@@ -711,6 +742,7 @@
|
||||
GCC_PREFIX_HEADER = "StreamingKit/StreamingKit-Prefix.pch";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PUBLIC_HEADERS_FOLDER_PATH = include/StreamingKit;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
@@ -727,6 +759,7 @@
|
||||
GCC_PREFIX_HEADER = "StreamingKit/StreamingKit-Prefix.pch";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PUBLIC_HEADERS_FOLDER_PATH = include/StreamingKit;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Release;
|
||||
@@ -746,6 +779,7 @@
|
||||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = "StreamingKitTests/StreamingKitTests-Info.plist";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "abstractpath.com.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
WRAPPER_EXTENSION = xctest;
|
||||
};
|
||||
@@ -762,6 +796,7 @@
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "StreamingKit/StreamingKit-Prefix.pch";
|
||||
INFOPLIST_FILE = "StreamingKitTests/StreamingKitTests-Info.plist";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "abstractpath.com.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
WRAPPER_EXTENSION = xctest;
|
||||
};
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -44,7 +44,7 @@
|
||||
#include "UIKit/UIApplication.h"
|
||||
#endif
|
||||
|
||||
typedef enum
|
||||
typedef NS_OPTIONS(NSInteger, STKAudioPlayerState)
|
||||
{
|
||||
STKAudioPlayerStateReady,
|
||||
STKAudioPlayerStateRunning = 1,
|
||||
@@ -54,10 +54,9 @@ typedef enum
|
||||
STKAudioPlayerStateStopped = (1 << 4),
|
||||
STKAudioPlayerStateError = (1 << 5),
|
||||
STKAudioPlayerStateDisposed = (1 << 6)
|
||||
}
|
||||
STKAudioPlayerState;
|
||||
};
|
||||
|
||||
typedef enum
|
||||
typedef NS_ENUM(NSInteger, STKAudioPlayerStopReason)
|
||||
{
|
||||
STKAudioPlayerStopReasonNone = 0,
|
||||
STKAudioPlayerStopReasonEof,
|
||||
@@ -65,10 +64,9 @@ typedef enum
|
||||
STKAudioPlayerStopReasonPendingNext,
|
||||
STKAudioPlayerStopReasonDisposed,
|
||||
STKAudioPlayerStopReasonError = 0xffff
|
||||
}
|
||||
STKAudioPlayerStopReason;
|
||||
};
|
||||
|
||||
typedef enum
|
||||
typedef NS_ENUM(NSInteger, STKAudioPlayerErrorCode)
|
||||
{
|
||||
STKAudioPlayerErrorNone = 0,
|
||||
STKAudioPlayerErrorDataSource,
|
||||
@@ -77,9 +75,13 @@ typedef enum
|
||||
STKAudioPlayerErrorCodecError,
|
||||
STKAudioPlayerErrorDataNotFound,
|
||||
STKAudioPlayerErrorOther = 0xffff
|
||||
}
|
||||
STKAudioPlayerErrorCode;
|
||||
};
|
||||
|
||||
///
|
||||
/// Options to initiailise the Audioplayer with.
|
||||
/// By default if you set buffer size or seconds to 0, the non-zero default will be used
|
||||
/// If you would like to disable the buffer option completely set to STK_DISABLE_BUFFER
|
||||
///
|
||||
typedef struct
|
||||
{
|
||||
/// If YES then seeking a track will cause all pending items to be flushed from the queue
|
||||
@@ -91,7 +93,7 @@ typedef struct
|
||||
/// The size of the internal I/O read buffer. This data in this buffer is transient and does not need to be larger.
|
||||
UInt32 readBufferSize;
|
||||
/// The size of the decompressed buffer (Default is 10 seconds which uses about 1.7MB of RAM)
|
||||
UInt32 bufferSizeInSeconds;
|
||||
Float32 bufferSizeInSeconds;
|
||||
/// Number of seconds of decompressed audio is required before playback first starts for each item (Default is 0.5 seconds. Must be larger than bufferSizeInSeconds)
|
||||
Float32 secondsRequiredToStartPlaying;
|
||||
/// Seconds after a seek is performed before data needs to come in (after which the state will change to playing/buffering)
|
||||
@@ -101,6 +103,8 @@ typedef struct
|
||||
}
|
||||
STKAudioPlayerOptions;
|
||||
|
||||
#define STK_DISABLE_BUFFER (0xffffffff)
|
||||
|
||||
typedef void(^STKFrameFilter)(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UInt32 frameCount, void* frames);
|
||||
|
||||
@interface STKFrameFilterEntry : NSObject
|
||||
|
||||
Regular → Executable
+297
-10
@@ -63,6 +63,9 @@
|
||||
#define STK_DEFAULT_PACKET_BUFFER_SIZE (2048)
|
||||
#define STK_DEFAULT_GRACE_PERIOD_AFTER_SEEK_SECONDS (0.5)
|
||||
|
||||
#define OSSTATUS_PRINTF_PLACEHOLDER @"%c%c%c%c"
|
||||
#define OSSTATUS_PRINTF_VALUE(status) (char)(((status) >> 24) & 0xFF), (char)(((status) >> 16) & 0xFF), (char)(((status) >> 8) & 0xFF), (char)((status) & 0xFF)
|
||||
|
||||
#define LOGINFO(x) [self logInfo:[NSString stringWithFormat:@"%s %@", sel_getName(_cmd), x]];
|
||||
|
||||
static void PopulateOptionsWithDefault(STKAudioPlayerOptions* options)
|
||||
@@ -93,6 +96,34 @@ static void PopulateOptionsWithDefault(STKAudioPlayerOptions* options)
|
||||
}
|
||||
}
|
||||
|
||||
static void NormalizeDisabledBuffers(STKAudioPlayerOptions* options)
|
||||
{
|
||||
if (options->bufferSizeInSeconds == STK_DISABLE_BUFFER)
|
||||
{
|
||||
options->bufferSizeInSeconds = 0;
|
||||
}
|
||||
|
||||
if (options->readBufferSize == STK_DISABLE_BUFFER)
|
||||
{
|
||||
options->readBufferSize = 0;
|
||||
}
|
||||
|
||||
if (options->secondsRequiredToStartPlaying == STK_DISABLE_BUFFER)
|
||||
{
|
||||
options->secondsRequiredToStartPlaying = 0;
|
||||
}
|
||||
|
||||
if (options->secondsRequiredToStartPlayingAfterBufferUnderun == STK_DISABLE_BUFFER)
|
||||
{
|
||||
options->secondsRequiredToStartPlayingAfterBufferUnderun = 0;
|
||||
}
|
||||
|
||||
if (options->gracePeriodAfterSeekInSeconds == STK_DISABLE_BUFFER)
|
||||
{
|
||||
options->gracePeriodAfterSeekInSeconds = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#define CHECK_STATUS_AND_REPORT(call) \
|
||||
if ((status = (call))) \
|
||||
{ \
|
||||
@@ -173,6 +204,7 @@ static AudioComponentDescription nbandUnitDescription;
|
||||
static AudioComponentDescription outputUnitDescription;
|
||||
static AudioComponentDescription convertUnitDescription;
|
||||
static AudioStreamBasicDescription canonicalAudioStreamBasicDescription;
|
||||
static AudioStreamBasicDescription recordAudioStreamBasicDescription;
|
||||
|
||||
@interface STKAudioPlayer()
|
||||
{
|
||||
@@ -241,7 +273,16 @@ static AudioStreamBasicDescription canonicalAudioStreamBasicDescription;
|
||||
AudioFileStreamID audioFileStream;
|
||||
NSConditionLock* threadStartedLock;
|
||||
NSConditionLock* threadFinishedCondLock;
|
||||
|
||||
|
||||
AudioFileID recordAudioFileId;
|
||||
UInt32 recordFilePacketPosition;
|
||||
AudioConverterRef recordAudioConverterRef;
|
||||
UInt32 recordOutputBufferSize;
|
||||
UInt8 *recordOutputBuffer;
|
||||
UInt32 recordPacketsPerBuffer;
|
||||
UInt32 recordPacketSize;
|
||||
AudioStreamPacketDescription *recordPacketDescriptions;
|
||||
|
||||
void(^stopBackBackgroundTaskBlock)();
|
||||
|
||||
int32_t seekVersion;
|
||||
@@ -292,9 +333,15 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
.componentSubType = kAudioUnitSubType_AUConverter,
|
||||
.componentFlags = 0,
|
||||
.componentFlagsMask = 0
|
||||
};
|
||||
};
|
||||
|
||||
#ifdef CA_CANONICAL_DEPRECATED
|
||||
const int bytesPerSample = sizeof(SInt16);
|
||||
#elif __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000
|
||||
const int bytesPerSample = sizeof(SInt16);
|
||||
#else
|
||||
const int bytesPerSample = sizeof(AudioSampleType);
|
||||
#endif
|
||||
|
||||
canonicalAudioStreamBasicDescription = (AudioStreamBasicDescription)
|
||||
{
|
||||
@@ -307,7 +354,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
.mBitsPerChannel = 8 * bytesPerSample,
|
||||
.mBytesPerPacket = (bytesPerSample * 2)
|
||||
};
|
||||
|
||||
|
||||
outputUnitDescription = (AudioComponentDescription)
|
||||
{
|
||||
.componentType = kAudioUnitType_Output,
|
||||
@@ -472,6 +519,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
self->equalizerEnabled = optionsIn.equalizerBandFrequencies[0] != 0;
|
||||
|
||||
PopulateOptionsWithDefault(&options);
|
||||
NormalizeDisabledBuffers(&options);
|
||||
|
||||
framesRequiredToStartPlaying = canonicalAudioStreamBasicDescription.mSampleRate * options.secondsRequiredToStartPlaying;
|
||||
framesRequiredToPlayAfterRebuffering = canonicalAudioStreamBasicDescription.mSampleRate * options.secondsRequiredToStartPlayingAfterBufferUnderun;
|
||||
@@ -538,6 +586,8 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
OSSpinLockUnlock(¤tEntryReferencesLock);
|
||||
}
|
||||
|
||||
[self closeRecordAudioFile];
|
||||
|
||||
[self stopAudioUnitWithReason:STKAudioPlayerStopReasonDisposed];
|
||||
|
||||
[self clearQueue];
|
||||
@@ -578,6 +628,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
pthread_cond_destroy(&mainThreadSyncCallReadyCondition);
|
||||
|
||||
free(readBuffer);
|
||||
free(pcmAudioBufferList.mBuffers[0].mData);
|
||||
}
|
||||
|
||||
-(void) startSystemBackgroundTask
|
||||
@@ -856,7 +907,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
}
|
||||
case kAudioFileStreamProperty_ReadyToProducePackets:
|
||||
{
|
||||
if (!audioConverterAudioStreamBasicDescription.mFormatID == kAudioFormatLinearPCM)
|
||||
if (audioConverterAudioStreamBasicDescription.mFormatID != kAudioFormatLinearPCM)
|
||||
{
|
||||
discontinuous = YES;
|
||||
}
|
||||
@@ -1095,6 +1146,8 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
[currentlyReadingEntry.dataSource registerForEvents:[NSRunLoop currentRunLoop]];
|
||||
[currentlyReadingEntry.dataSource seekToOffset:0];
|
||||
|
||||
[self closeRecordAudioFile];
|
||||
|
||||
if (startPlaying)
|
||||
{
|
||||
if (clearQueue)
|
||||
@@ -1397,6 +1450,8 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
currentlyReadingEntry = nil;
|
||||
OSSpinLockUnlock(¤tEntryReferencesLock);
|
||||
pthread_mutex_unlock(&playerMutex);
|
||||
|
||||
[self closeRecordAudioFile];
|
||||
|
||||
self.internalState = STKAudioPlayerInternalStateDisposed;
|
||||
|
||||
@@ -1459,6 +1514,11 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
AudioConverterReset(audioConverterRef);
|
||||
}
|
||||
|
||||
if (recordAudioConverterRef)
|
||||
{
|
||||
AudioConverterReset(recordAudioConverterRef);
|
||||
}
|
||||
|
||||
[currentEntry reset];
|
||||
[currentEntry.dataSource seekToOffset:seekByteOffset];
|
||||
|
||||
@@ -1577,7 +1637,9 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
}
|
||||
|
||||
NSObject* queueItemId = currentlyReadingEntry.queueItemId;
|
||||
|
||||
|
||||
[self closeRecordAudioFile];
|
||||
|
||||
[self dispatchSyncOnMainThread:^
|
||||
{
|
||||
[self.delegate audioPlayer:self didFinishBufferingSourceWithQueueItemId:queueItemId];
|
||||
@@ -1702,6 +1764,8 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
return;
|
||||
}
|
||||
|
||||
[self closeRecordAudioFile];
|
||||
|
||||
[self stopAudioUnitWithReason:STKAudioPlayerStopReasonUserAction];
|
||||
|
||||
[self resetPcmBuffers];
|
||||
@@ -1796,6 +1860,35 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
self.muted = NO;
|
||||
}
|
||||
|
||||
-(void) closeRecordAudioFile
|
||||
{
|
||||
if (recordAudioFileId)
|
||||
{
|
||||
AudioFileClose(recordAudioFileId);
|
||||
recordAudioFileId = NULL;
|
||||
}
|
||||
|
||||
if (recordAudioConverterRef)
|
||||
{
|
||||
AudioConverterDispose(recordAudioConverterRef);
|
||||
recordAudioConverterRef = nil;
|
||||
}
|
||||
|
||||
if (recordOutputBuffer)
|
||||
{
|
||||
free(recordOutputBuffer);
|
||||
recordOutputBuffer = NULL;
|
||||
}
|
||||
|
||||
if (recordPacketDescriptions)
|
||||
{
|
||||
free(recordPacketDescriptions);
|
||||
recordPacketDescriptions = NULL;
|
||||
}
|
||||
|
||||
recordFilePacketPosition = 0;
|
||||
}
|
||||
|
||||
-(void) dispose
|
||||
{
|
||||
[self stop];
|
||||
@@ -1868,17 +1961,43 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
|
||||
{
|
||||
OSStatus status;
|
||||
Boolean writable;
|
||||
UInt32 cookieSize;
|
||||
UInt32 cookieSize = 0;
|
||||
|
||||
if (memcmp(asbd, &audioConverterAudioStreamBasicDescription, sizeof(AudioStreamBasicDescription)) == 0)
|
||||
{
|
||||
AudioConverterReset(audioConverterRef);
|
||||
|
||||
if (recordAudioConverterRef)
|
||||
{
|
||||
AudioConverterReset(recordAudioConverterRef);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
[self destroyAudioConverter];
|
||||
|
||||
canonicalAudioStreamBasicDescription.mChannelsPerFrame = asbd->mChannelsPerFrame;
|
||||
|
||||
BOOL isRecording = currentlyReadingEntry.dataSource.recordToFileUrl != nil;
|
||||
if (isRecording)
|
||||
{
|
||||
recordAudioStreamBasicDescription = (AudioStreamBasicDescription)
|
||||
{
|
||||
.mFormatID = kAudioFormatMPEG4AAC,
|
||||
.mFormatFlags = kMPEG4Object_AAC_LC,
|
||||
.mChannelsPerFrame = canonicalAudioStreamBasicDescription.mChannelsPerFrame,
|
||||
.mSampleRate = canonicalAudioStreamBasicDescription.mSampleRate,
|
||||
};
|
||||
|
||||
UInt32 dataSize = sizeof(recordAudioStreamBasicDescription);
|
||||
AudioFormatGetProperty(kAudioFormatProperty_FormatInfo,
|
||||
0,
|
||||
NULL,
|
||||
&dataSize,
|
||||
&recordAudioStreamBasicDescription);
|
||||
}
|
||||
|
||||
AudioClassDescription classDesc;
|
||||
|
||||
if (GetHardwareCodecClassDesc(asbd->mFormatID, &classDesc))
|
||||
@@ -1897,14 +2016,29 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (isRecording && !recordAudioConverterRef)
|
||||
{
|
||||
status = AudioConverterNew(&canonicalAudioStreamBasicDescription, &recordAudioStreamBasicDescription, &recordAudioConverterRef);
|
||||
|
||||
if (status)
|
||||
{
|
||||
NSLog(@"STKAudioPlayer failed to create a recording audio converter");
|
||||
}
|
||||
}
|
||||
|
||||
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,9 +2051,87 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
|
||||
|
||||
if (status)
|
||||
{
|
||||
[self unexpectedError:STKAudioPlayerErrorAudioSystemError];
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (recordAudioConverterRef)
|
||||
{
|
||||
if (recordAudioFileId)
|
||||
{
|
||||
AudioFileClose(recordAudioFileId);
|
||||
recordAudioFileId = NULL;
|
||||
}
|
||||
|
||||
if (recordOutputBuffer)
|
||||
{
|
||||
free(recordOutputBuffer);
|
||||
recordOutputBuffer = NULL;
|
||||
}
|
||||
|
||||
if (recordPacketDescriptions)
|
||||
{
|
||||
free(recordPacketDescriptions);
|
||||
recordPacketDescriptions = NULL;
|
||||
}
|
||||
|
||||
recordOutputBufferSize = 32 * 1024;
|
||||
recordPacketSize = canonicalAudioStreamBasicDescription.mBytesPerPacket;
|
||||
|
||||
if (recordPacketSize == 0)
|
||||
{
|
||||
UInt32 size = sizeof(recordPacketSize);
|
||||
if (0 == AudioConverterGetProperty(recordAudioConverterRef, kAudioConverterPropertyMaximumOutputPacketSize, &size, &recordPacketSize))
|
||||
{
|
||||
if (recordPacketSize > recordOutputBufferSize)
|
||||
{
|
||||
recordOutputBufferSize = recordPacketSize;
|
||||
}
|
||||
|
||||
recordPacketsPerBuffer = recordOutputBufferSize / recordPacketSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
AudioConverterDispose(recordAudioConverterRef);
|
||||
recordAudioConverterRef = NULL;
|
||||
|
||||
NSLog(@"STKAudioPlayer: Can't support this output format for recording");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
recordPacketsPerBuffer = recordOutputBufferSize / recordPacketSize;
|
||||
}
|
||||
|
||||
UInt32 propertySize = sizeof(UInt32);
|
||||
UInt32 externallyFramed = 0;
|
||||
OSStatus error = AudioFormatGetProperty(kAudioFormatProperty_FormatIsExternallyFramed, sizeof(recordAudioStreamBasicDescription), &recordAudioStreamBasicDescription, &propertySize, &externallyFramed);
|
||||
|
||||
if (externallyFramed)
|
||||
{
|
||||
recordPacketDescriptions = (AudioStreamPacketDescription *)malloc(sizeof(AudioStreamPacketDescription) * recordPacketsPerBuffer);
|
||||
}
|
||||
|
||||
recordOutputBuffer = (UInt8 *)malloc(sizeof(UInt8) * recordOutputBufferSize);
|
||||
|
||||
error = AudioFileCreateWithURL(
|
||||
(__bridge CFURLRef)(currentlyReadingEntry.dataSource.recordToFileUrl),
|
||||
kAudioFileCAFType,
|
||||
&recordAudioStreamBasicDescription,
|
||||
kAudioFileFlags_EraseFile,
|
||||
&recordAudioFileId);
|
||||
|
||||
recordFilePacketPosition = 0;
|
||||
|
||||
if (error)
|
||||
{
|
||||
NSLog(@"STKAudioPlayer failed to create a recording audio file at %@", currentlyReadingEntry.dataSource.recordToFileUrl);
|
||||
|
||||
[self closeRecordAudioFile];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-(void) createOutputUnit
|
||||
@@ -2411,6 +2623,11 @@ OSStatus AudioConverterCallback(AudioConverterRef inAudioConverter, UInt32* ioNu
|
||||
status = AudioConverterFillComplexBuffer(audioConverterRef, AudioConverterCallback, (void*)&convertInfo, &framesToDecode, &localPcmBufferList, NULL);
|
||||
|
||||
framesAdded = framesToDecode;
|
||||
|
||||
if ((status == 100 || status == 0) && recordAudioFileId && recordAudioConverterRef)
|
||||
{
|
||||
[self handleRecordingOfAudioPackets:framesToDecode audioBuffer:&localPcmBufferList.mBuffers[0]];
|
||||
}
|
||||
|
||||
if (status == 100)
|
||||
{
|
||||
@@ -2454,6 +2671,11 @@ OSStatus AudioConverterCallback(AudioConverterRef inAudioConverter, UInt32* ioNu
|
||||
|
||||
framesAdded += framesToDecode;
|
||||
|
||||
if ((status == 100 || status == 0) && recordAudioFileId && recordAudioConverterRef)
|
||||
{
|
||||
[self handleRecordingOfAudioPackets:framesToDecode audioBuffer:&localPcmBufferList.mBuffers[0]];
|
||||
}
|
||||
|
||||
if (status == 100)
|
||||
{
|
||||
OSSpinLockLock(&pcmBufferSpinLock);
|
||||
@@ -2498,6 +2720,11 @@ OSStatus AudioConverterCallback(AudioConverterRef inAudioConverter, UInt32* ioNu
|
||||
|
||||
framesAdded = framesToDecode;
|
||||
|
||||
if ((status == 100 || status == 0) && recordAudioFileId && recordAudioConverterRef)
|
||||
{
|
||||
[self handleRecordingOfAudioPackets:framesToDecode audioBuffer:&localPcmBufferList.mBuffers[0]];
|
||||
}
|
||||
|
||||
if (status == 100)
|
||||
{
|
||||
OSSpinLockLock(&pcmBufferSpinLock);
|
||||
@@ -2532,6 +2759,66 @@ OSStatus AudioConverterCallback(AudioConverterRef inAudioConverter, UInt32* ioNu
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleRecordingOfAudioPackets:(UInt32)numberOfPackets audioBuffer:(AudioBuffer *)audioBuffer
|
||||
{
|
||||
if (recordAudioFileId && recordAudioConverterRef)
|
||||
{
|
||||
AudioConvertInfo recordConvertInfo;
|
||||
recordConvertInfo.done = NO;
|
||||
recordConvertInfo.numberOfPackets = numberOfPackets;
|
||||
recordConvertInfo.packetDescriptions = NULL;
|
||||
recordConvertInfo.audioBuffer = *audioBuffer;
|
||||
|
||||
AudioBufferList convertedData;
|
||||
convertedData.mNumberBuffers = 1;
|
||||
convertedData.mBuffers[0].mNumberChannels = recordAudioStreamBasicDescription.mChannelsPerFrame;
|
||||
convertedData.mBuffers[0].mDataByteSize = recordOutputBufferSize;
|
||||
convertedData.mBuffers[0].mData = recordOutputBuffer;
|
||||
|
||||
UInt32 ioOutputDataPackets;
|
||||
OSStatus status;
|
||||
|
||||
while (1)
|
||||
{
|
||||
ioOutputDataPackets = recordPacketsPerBuffer;
|
||||
|
||||
status = AudioConverterFillComplexBuffer(recordAudioConverterRef, AudioConverterCallback, (void*)&recordConvertInfo, &ioOutputDataPackets, &convertedData, recordPacketDescriptions);
|
||||
|
||||
if (status == 100 || status == 0)
|
||||
{
|
||||
if (ioOutputDataPackets > 0)
|
||||
{
|
||||
OSStatus writeError = AudioFileWritePackets(recordAudioFileId,
|
||||
NO,
|
||||
convertedData.mBuffers[0].mDataByteSize,
|
||||
recordPacketDescriptions,
|
||||
recordFilePacketPosition,
|
||||
&ioOutputDataPackets,
|
||||
convertedData.mBuffers[0].mData);
|
||||
|
||||
if (writeError)
|
||||
{
|
||||
NSLog(@"STKAudioPlayer:handleRecordingOfAudioPackets failed on AudioFileWritePackets with error \"" OSSTATUS_PRINTF_PLACEHOLDER "\"", OSSTATUS_PRINTF_VALUE(writeError));
|
||||
}
|
||||
else
|
||||
{
|
||||
recordFilePacketPosition += ioOutputDataPackets;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"STKAudioPlayer: Unexpected error during recording audio file conversion");
|
||||
}
|
||||
|
||||
if (status == 100)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData)
|
||||
{
|
||||
STKAudioPlayer* audioPlayer = (__bridge STKAudioPlayer*)inRefCon;
|
||||
|
||||
@@ -101,6 +101,8 @@ static void PopulateOptionsWithDefault(STKAutoRecoveringHTTPDataSourceOptions* o
|
||||
|
||||
@implementation STKAutoRecoveringHTTPDataSource
|
||||
|
||||
@dynamic innerDataSource;
|
||||
|
||||
-(STKHTTPDataSource*) innerHTTPDataSource
|
||||
{
|
||||
return (STKHTTPDataSource*)self.innerDataSource;
|
||||
|
||||
@@ -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;
|
||||
@@ -135,8 +137,6 @@ static void ReadStreamCallbackProc(CFReadStreamRef stream, CFStreamEventType eve
|
||||
{
|
||||
CFReadStreamSetClient(stream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, NULL, NULL);
|
||||
CFReadStreamUnscheduleFromRunLoop(stream, [eventsRunLoop getCFRunLoop], kCFRunLoopCommonModes);
|
||||
|
||||
eventsRunLoop = nil;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Regular → Executable
+3
@@ -45,10 +45,13 @@
|
||||
|
||||
@interface STKDataSource : NSObject
|
||||
|
||||
@property (readonly) BOOL supportsSeek;
|
||||
@property (readonly) SInt64 position;
|
||||
@property (readonly) SInt64 length;
|
||||
@property (readonly) BOOL hasBytesAvailable;
|
||||
@property (nonatomic, readwrite, assign) double durationHint;
|
||||
@property (readwrite, unsafe_unretained) id<STKDataSourceDelegate> delegate;
|
||||
@property (nonatomic, strong) NSURL *recordToFileUrl;
|
||||
|
||||
-(BOOL) registerForEvents:(NSRunLoop*)runLoop;
|
||||
-(void) unregisterForEvents;
|
||||
|
||||
@@ -79,4 +79,9 @@
|
||||
return 0;
|
||||
}
|
||||
|
||||
-(BOOL) supportsSeek
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Regular → Executable
+274
-63
@@ -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;
|
||||
@@ -118,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),
|
||||
@@ -125,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)
|
||||
};
|
||||
});
|
||||
|
||||
@@ -147,70 +164,233 @@
|
||||
return audioFileTypeHint;
|
||||
}
|
||||
|
||||
-(void) dataAvailable
|
||||
-(NSDictionary*) parseIceHeader:(NSData*)headerData
|
||||
{
|
||||
if (stream == NULL) {
|
||||
return;
|
||||
NSMutableDictionary* retval = [[NSMutableDictionary alloc] init];
|
||||
NSCharacterSet* characterSet = [NSCharacterSet characterSetWithCharactersInString:@"\r\n"];
|
||||
NSString* fullString = [[NSString alloc] initWithData:headerData 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* parts = [s componentsSeparatedByString:@" "];
|
||||
|
||||
if (parts.count >= 2)
|
||||
{
|
||||
self->httpStatusCode = [parts[1] intValue];
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
[retval setValue:value forKey:key];
|
||||
}
|
||||
|
||||
if (self.httpStatusCode == 0)
|
||||
{
|
||||
CFTypeRef response = CFReadStreamCopyProperty(stream, kCFStreamPropertyHTTPResponseHeader);
|
||||
return retval;
|
||||
}
|
||||
|
||||
-(BOOL) parseHttpHeader
|
||||
{
|
||||
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 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))
|
||||
{
|
||||
if (memcmp(&terminal1[0], [self->iceHeaderData bytes] + iceHeaderData.length - sizeof(terminal1), sizeof(terminal1)) == 0)
|
||||
{
|
||||
self->iceHeaderAvailable = YES;
|
||||
self->iceHeaderSearchComplete = YES;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (iceHeaderData.length >= sizeof(terminal2))
|
||||
{
|
||||
if (memcmp(&terminal2[0], [self->iceHeaderData bytes] + iceHeaderData.length - sizeof(terminal2), sizeof(terminal2)) == 0)
|
||||
{
|
||||
self->iceHeaderAvailable = YES;
|
||||
self->iceHeaderSearchComplete = YES;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (iceHeaderData.length >= 4)
|
||||
{
|
||||
if (memcmp([self->iceHeaderData bytes], "ICY ", 4) != 0 && memcmp([self->iceHeaderData bytes], "HTTP", 4) != 0)
|
||||
{
|
||||
self->iceHeaderAvailable = NO;
|
||||
self->iceHeaderSearchComplete = YES;
|
||||
prefixBytes = iceHeaderData;
|
||||
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!self->iceHeaderSearchComplete)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
httpHeaders = [self parseIceHeader:self->iceHeaderData];
|
||||
|
||||
self->iceHeaderData = nil;
|
||||
}
|
||||
|
||||
if (([httpHeaders objectForKey:@"Accept-Ranges"] ?: [httpHeaders objectForKey:@"accept-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,17 +430,43 @@
|
||||
|
||||
self->isInErrorState = NO;
|
||||
|
||||
if (!self->supportsSeek && offset != self->relativePosition)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
[self openForSeek:YES];
|
||||
}
|
||||
|
||||
-(int) readIntoBuffer:(UInt8*)buffer withSize:(int)size
|
||||
{
|
||||
return [self privateReadIntoBuffer:buffer withSize:size];
|
||||
}
|
||||
|
||||
-(int) privateReadIntoBuffer:(UInt8*)buffer withSize:(int)size
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int read = (int)CFReadStreamRead(stream, buffer, size);
|
||||
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 = [super readIntoBuffer:buffer withSize:size];
|
||||
|
||||
if (read < 0)
|
||||
{
|
||||
@@ -290,7 +496,7 @@
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
self->currentUrl = url;
|
||||
|
||||
if (url == nil)
|
||||
@@ -300,7 +506,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]);
|
||||
|
||||
@@ -310,8 +516,12 @@
|
||||
for (NSString* key in self->requestHeaders)
|
||||
{
|
||||
NSString* value = [self->requestHeaders objectForKey:key];
|
||||
|
||||
CFHTTPMessageSetHeaderFieldValue(message, (__bridge CFStringRef)key, (__bridge CFStringRef)value);
|
||||
}
|
||||
|
||||
CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Accept"), CFSTR("*/*"));
|
||||
CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Ice-MetaData"), CFSTR("0"));
|
||||
|
||||
stream = CFReadStreamCreateForHTTPRequest(NULL, message);
|
||||
|
||||
@@ -323,6 +533,8 @@
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CFReadStreamSetProperty(stream, (__bridge CFStringRef)NSStreamNetworkServiceTypeBackground, (__bridge CFStringRef)NSStreamNetworkServiceTypeBackground);
|
||||
|
||||
if (!CFReadStreamSetProperty(stream, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue))
|
||||
{
|
||||
@@ -334,20 +546,15 @@
|
||||
}
|
||||
|
||||
// Proxy support
|
||||
|
||||
CFDictionaryRef proxySettings = CFNetworkCopySystemProxySettings();
|
||||
CFReadStreamSetProperty(stream, kCFStreamPropertyHTTPProxy, proxySettings);
|
||||
CFRelease(proxySettings);
|
||||
|
||||
// SSL support
|
||||
|
||||
if ([self->currentUrl.scheme caseInsensitiveCompare:@"https"] == NSOrderedSame)
|
||||
{
|
||||
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];
|
||||
@@ -360,7 +567,6 @@
|
||||
self->httpStatusCode = 0;
|
||||
|
||||
// Open
|
||||
|
||||
if (!CFReadStreamOpen(stream))
|
||||
{
|
||||
CFRelease(stream);
|
||||
@@ -394,4 +600,9 @@
|
||||
return [NSString stringWithFormat:@"HTTP data source with file length: %lld and position: %lld", self.length, self.position];
|
||||
}
|
||||
|
||||
-(BOOL) supportsSeek
|
||||
{
|
||||
return self->supportsSeek;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Regular → Executable
+1
@@ -28,6 +28,7 @@
|
||||
volatile int processedPacketsCount;
|
||||
volatile int processedPacketsSizeTotal;
|
||||
AudioStreamBasicDescription audioStreamBasicDescription;
|
||||
double durationHint;
|
||||
}
|
||||
|
||||
@property (readonly) UInt64 audioDataLengthInBytes;
|
||||
|
||||
Regular → Executable
+4
-1
@@ -23,6 +23,7 @@
|
||||
self.dataSource = dataSourceIn;
|
||||
self.queueItemId = queueItemIdIn;
|
||||
self->lastFrameQueued = -1;
|
||||
self->durationHint = dataSourceIn.durationHint;
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -45,7 +46,7 @@
|
||||
{
|
||||
if (processedPacketsCount > STK_BIT_RATE_ESTIMATION_MIN_PACKETS_PREFERRED || (audioStreamBasicDescription.mBytesPerFrame == 0 && processedPacketsCount > STK_BIT_RATE_ESTIMATION_MIN_PACKETS_MIN))
|
||||
{
|
||||
double averagePacketByteSize = processedPacketsSizeTotal / processedPacketsCount;
|
||||
double averagePacketByteSize = (double)processedPacketsSizeTotal / (double)processedPacketsCount;
|
||||
|
||||
retval = averagePacketByteSize / packetDuration * 8;
|
||||
|
||||
@@ -60,6 +61,8 @@
|
||||
|
||||
-(double) duration
|
||||
{
|
||||
if (durationHint > 0.0) return durationHint;
|
||||
|
||||
if (self->sampleRate <= 0)
|
||||
{
|
||||
return 0;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.abstractpath.${PRODUCT_NAME:rfc1034identifier}</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>abstractpath.com.${PRODUCT_NAME:rfc1034identifier}</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
|
||||
Reference in New Issue
Block a user