iPhone App Source Code
I finally got around to open sourcing the code for the Radio Javan iPhone App. I’ve only made the difficult part available, which is the actual streaming and playing the music. You will still have to write your own UI and views (easy stuff).
You can view and download the code at
http://www.github.com/hamedh/iphone_radio/tree/master
Please note that this works for Icecast only, and you’ll have to modify it if you wanted to read a Shoutcast stream. The code is released with GPL v2, so if you use the code, you will have to also open source it if it is for commercial use, or else you’ll need a separate license.
If you want a customized app, you could contact my friends at Return 7. Feel free to post any questions/comments about the code here.




pretty cool — thanks for the link!
hi,
I try to open your app with xcode but the app did not work properly?
could you help ?
thanks
should work fine if you have the iPhone SDK installed. where did u get stuck?
I have the same issue, you mentioned above that you took out your own UI, so when it runs I just see a Blank screen.
I am very new programer, however I am a quick learner when taught something. However I am having trouble adding UI to the program, (Which I assume is why it is not running)
Could you please give a me a few tips on adding UI
Hi Hamedh,
Can you plz tell how to get metadata from shoutcast stream? I am trying to find answer of this from long time but no success.
Thanks.
Hello,
I would like to test your application. I tried to download the zip but the page is not available. And I can’t open the TAR.
Can you give me an another link?
Thanks in advance,
Best regards,
Lucas
Hi Hamed :
Thanks a lot for sharing your code. It is working very well for me .
I have two questions :
First of all I have a list of radio channels, although I release
the radio object.It throws an error .
I’ve put the radiojavan url for two of my channels(for testing purpose ) whenver I go back and forth it plays the stream continuously(without any delay).
Second : How can I get it to work with ip address and port instead of url something like: http://10.10.9.0:8080
Thanks in advance,
Arash
John, you can find a good read about Shoutcast metadata at http://jicyshout.sourceforge.net/oreilly-article/java-streaming-mp3-pt2/java-streaming-mp3-pt2.html
Arash, the ip/port should not be an issue, for example you can change the code to load http://stream.radiojavan.com:80/radiojavan and it will work just fine.
Regarding the radio object, there may be some cleanup that it doesn’t do properly, I don’t destroy it for purposes of the RJ app. You may want to try calling connect to the same Radio instance with another URL (after stopping the playing).
Thanks for your hints.I will try to make it to works
Hi Hamed,
Thanks again for your instructions.I’ve faced another problem :
I have a list of radio channels in my application everything is working fine,
except when I go back and forth for several times the application goes to halt mode.
I’ve traced the code it goes to audioQueueCallBack and it prints the “OUT OF BUFFERS” .Apparently it doesn’t empty/reset the buffer!
Do you have any insight on that?
Thanks again for your help
Arash
Thanks a lot for the link Hamed. Can you please help me understand seeking here. Could not find much help on the internet.
I’m getting the following error when i build and run:
Error loading /Library/QuickTime/DivX Decoder.component/Contents/MacOS/DivX Decoder: dlopen(/Library/QuickTime/DivX Decoder.component/Contents/MacOS/DivX Decoder, 262): Symbol not found: _SCDynamicStoreCopyConsoleUser
Referenced from: /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/LangAnalysis.framework/Versions/A/LangAnalysis
Expected in: /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator2.0.sdk/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration
Any thoughts?
Hello! Please convert it for Shoutcast! Please
I was able to get the UI working fairly easily but have no idea where to even start to get this working with shoutcast. Is there a good resource for looking up the differences in the protocols relative to this app.
Hi Hamed,
Many thanks for making this code available. One thing I can’t work out is how to obtain metadata when the song changes. I can see the info coming in with a packet analyzer but cannot figure out where to grab it in your code.
Any help is much appreciate !
Thanks
Hamed-
I am a beginner at developing for iPhone, and I would love to use your code in order to have a simple streaming app. Would you mind pointing me in the direction of the best way to program the UI? Thanks.
S
I tried running the app in the simulator, and it worked , but when I sent it to the iphone, it just says “build succeeded” and nothing happens on the actual phone…any suggestions?
Ed… you need to remove DivX Decoder.component and/or other third party components from your /Library/Quicktime directory. Then it should work fine.
The rest of you getting just a black screen need to do the same.
Doug could you please let me know how u got the UI working? I am new to iPhone programming and it would be a great help.
hi friend ,
do you know the code for setting the volume with a slider control.
i have very urgent
Baiju
anybody know how the app control the volume with UISklder. i try like this but not
#import
@class Radio;
@interface iPhoneRadioAppDelegate : NSObject {
UIWindow *window;
UINavigationController *navigationController;
UISlider *slider;
Radio *radio;
}
@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
@property (nonatomic, retain) IBOutlet UISlider *slider;
@property (nonatomic, retain) Radio *radio;
-(IBAction)volumeChanged;
-(void)loadMainView;
-(void)updateTitle:(NSString*)title;
-(void)updateGain:(float)value;
-(void)updatePlay:(BOOL)play;
-(void)updateBuffering: (BOOL)value;
@end
*****************************
#import “iPhoneRadioAppDelegate.h”
#import “Radio.h”
@implementation iPhoneRadioAppDelegate
@synthesize window;
@synthesize navigationController;
@synthesize slider;
@synthesize radio;
-(void)loadMainView {
// this was used to switch from the starting/buffering view to the now playing view, see the “Radio Javan” app in the App Store for how that works
}
-(void)updateTitle:(NSString*)title {
// update view text
}
-(void)updateGain:(float)value {
// update volume slider
}
-(void)updatePlay:(BOOL)play {
// toggle play/pause button
}
-(void)updateBuffering: (BOOL)value {
// update buffer indicator
}
-(void)doPauseResume:(UIBarButtonItem *)theBarButtonItem {
static int pause = 0;
if (pause) {
// Resume
pause = 0;
theBarButtonItem.title = @”Durdur”;
[self.radio resume];
}
else {
// Pause
pause = 1;
theBarButtonItem.title = @”Oynat”;
[self.radio pause];
}
}
// added +++++++++++++++
- (void)sliderEvent:(id)sender{
UISlider *myslider = (UISlider *)sender;
float value = myslider.value;
NSLog(@”Valor: %f”,value);
self.radio.gain = myslider.value; // i think this code will modify
}
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Create UIViewController
UIViewController *vc = [[UIViewController alloc] initWithNibName:nil bundle:nil];
// Create a title
vc.navigationItem.title = [[[NSBundle mainBundle] infoDictionary] objectForKey:@”CFBundleName”];
// Create a pause buttonResume
vc.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@”Durdur” style:UIBarButtonItemStylePlain target:self action:@selector(doPauseResume:)];
// Create Navigation Controller
self.navigationController = [[UINavigationController alloc] initWithRootViewController:vc];
// Create a view
CGRect rect = [UIScreen mainScreen].applicationFrame;
// Create image view object
UIImageView *view = [[UIImageView alloc] initWithFrame:rect];
// Load an image
[view setImage:[UIImage imageNamed:@"back.jpg"]];
view.userInteractionEnabled = TRUE;
// Create a new slider
UISlider * mySlider = [[UISlider alloc] initWithFrame:CGRectMake(40.0f,320.0f, 250.0f, 20.0f)];
mySlider.minimumValue = 0.0; //I modified this line
mySlider.maximumValue = 1.0; //I modified this line
mySlider.value = slider.value; // I modified this line
[mySlider setContinuous:YES];
[mySlider addTarget:self action:@selector(sliderEvent:)
forControlEvents:UIControlEventValueChanged];
[mySlider addTarget:self action:@selector(sliderEvent:) forControlEvents:UIControlEventValueChanged];//added
// Add the slider as a subview of the logo image
[view addSubview:mySlider];
// Add view to view controller
vc.view = view;
// Add view controller to navigation subview
window = [[UIWindow alloc] initWithFrame:rect];
[window addSubview:[self.navigationController view]];
// Configure and show the window
[window makeKeyAndVisible];
radio = [[Radio alloc] init];
[radio connect:@"http://91.93.130.25:3333/stream.ogg" withDelegate:self withGain:(0.5)];
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Save data if appropriate
}
- (void)dealloc {
[window release];
[radio release];
[super dealloc];
}
@end
hey my friend;
i try to modify this line:
[radio connect:@"http://91.93.130.25:3333/stream.ogg" withDelegate:self withGain:(0.5)];
like this
[radio connect:@"http://91.93.130.25:3333/stream.ogg" withDelegate:self withGain:(slider.value;)];
and modify this line:
mySlider.value = slider.value;
like this
mySlider.value = 0,5;
and so slider dosent control but when simulate it slider value and volume value is same. its a succes for me
i think mySlider.value should bind to UISlider how it will i dont know any ideas
thanks for all
Hey Hamed,
First off. Big thanks for submitting your source code. It’s very generous of you!
I was just wondering.. My playback keeps pausing and resuming because of this code:
@synchronized (audioState.packetQueue) {
if (audioState.outOfBuffers > kMaxOutOfBuffers) {
[self pause];
[NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(resume) userInfo:nil repeats:NO];
audioState.outOfBuffers = 0;
}
Why does this happen? Do you need to pause the feed when the audioState is out of buffers?
Thanks,
Dan
Interesting I don’t get the out of buffer errors with your radio stream. If I change it to this:
http://202.6.74.107:8060/triplej.mp3
I get out of buffer errors. Do you think this is because their bitrate is too high?
Thanks
You can try increasing kNumberBuffers in Radio.m and also mBuffers and freeBuffers in Radio.h to a slightly higher number.. this increases the number of buffers to feed the audio device. The Radio Javan stream is 128k (icecast) and yours also seems to be 128k as well. Do you have problems on 3G or on WiFi? If you’re having problems on your WiFi, then there must be something different in your stream’s behavior that doesn’t go well with the streaming logic that I had coded.
Thanks Hamed, I’ve tested both streams over 3G & wifi. Yours plays perfectly whereas the other stream stops.
Streaming http://202.6.74.107:8060/triplej.mp3 my console log looks like this:
2009-07-24 09:11:09.404 iPhoneRadio[3226:5c0f] OUT OF BUFFERS
2009-07-24 09:11:09.445 iPhoneRadio[3226:207] AudioQueueEnqueueBuffer 418 bytes, 1 descriptions
2009-07-24 09:11:10.331 iPhoneRadio[3226:5c0f] AudioQueueEnqueueBuffer 7942 bytes, 19 descriptions
2009-07-24 09:11:10.461 iPhoneRadio[3226:5c0f] AudioQueueEnqueueBuffer 7941 bytes, 19 descriptions
2009-07-24 09:11:11.283 iPhoneRadio[3226:5c0f] AudioQueueEnqueueBuffer 7941 bytes, 19 descriptions
2009-07-24 09:11:11.413 iPhoneRadio[3226:5c0f] AudioQueueEnqueueBuffer 7941 bytes, 19 descriptions
2009-07-24 09:11:12.272 iPhoneRadio[3226:5c0f] AudioQueueEnqueueBuffer 7942 bytes, 19 descriptions
2009-07-24 09:11:12.286 iPhoneRadio[3226:5c0f] AudioQueueEnqueueBuffer 7941 bytes, 19 descriptions
2009-07-24 09:11:12.409 iPhoneRadio[3226:5c0f] OUT OF BUFFERS
2009-07-24 09:11:12.419 iPhoneRadio[3226:207] AudioQueueEnqueueBuffer 418 bytes, 1 descriptions
2009-07-24 09:11:13.327 iPhoneRadio[3226:5c0f] AudioQueueEnqueueBuffer 7941 bytes, 19 descriptions
2009-07-24 09:11:13.465 iPhoneRadio[3226:5c0f] AudioQueueEnqueueBuffer 7941 bytes, 19 descriptions
2009-07-24 09:11:14.292 iPhoneRadio[3226:5c0f] AudioQueueEnqueueBuffer 7941 bytes, 19 descriptions
2009-07-24 09:11:14.422 iPhoneRadio[3226:5c0f] AudioQueueEnqueueBuffer 7942 bytes, 19 descriptions
2009-07-24 09:11:15.290 iPhoneRadio[3226:5c0f] AudioQueueEnqueueBuffer 7941 bytes, 19 descriptions
2009-07-24 09:11:15.302 iPhoneRadio[3226:5c0f] AudioQueueEnqueueBuffer 7941 bytes, 19 descriptions
2009-07-24 09:11:15.425 iPhoneRadio[3226:5c0f] OUT OF BUFFERS
2009-07-24 09:11:15.435 iPhoneRadio[3226:207] AudioQueueEnqueueBuffer 418 bytes, 1 descriptions
2009-07-24 09:11:16.324 iPhoneRadio[3226:5c0f] AudioQueueEnqueueBuffer 7941 bytes, 19 descriptions
2009-07-24 09:11:16.454 iPhoneRadio[3226:5c0f] AudioQueueEnqueueBuffer 7942 bytes, 19 descriptions
2009-07-24 09:11:17.291 iPhoneRadio[3226:5c0f] AudioQueueEnqueueBuffer 7941 bytes, 19 descriptions
2009-07-24 09:11:17.423 iPhoneRadio[3226:5c0f] AudioQueueEnqueueBuffer 7941 bytes, 19 descriptions
2009-07-24 09:11:18.298 iPhoneRadio[3226:5c0f] AudioQueueEnqueueBuffer 7941 bytes, 19 descriptions
2009-07-24 09:11:18.315 iPhoneRadio[3226:5c0f] AudioQueueEnqueueBuffer 7941 bytes, 19 descriptions
2009-07-24 09:11:18.441 iPhoneRadio[3226:5c0f] OUT OF BUFFERS
2009-07-24 09:11:18.460 iPhoneRadio[3226:207] AudioQueueEnqueueBuffer 418 bytes, 1 descriptions
2009-07-24 09:11:18.472 iPhoneRadio[3226:207] pause
If I stream Radio Javan I don’t get these constant out of buffer errors. :-/
I listened to your app walking to my local supermarket and it didn’t drop out once. Very impressed.
This has helped:
static int kPacketSize = 10000;
static int kAudioBufferSize = 10000;
Do you mind giving a quick explanation on what this is?
Is the kPacketSize the amount of data it asks to get from the server?
&
this kAudioBufferSize
Is just the size of the buffer to match the packet size?
I think I should have used the same variable, but basically it is the size of each of the audio buffers that you have in memory. So if you multiple kAudioBufferSize by kNumberBuffers, that’s the total amount you are buffering, it has that much room to play with before you the audio drops.
Good work Hamed.
I’m trying to create my version of internet radio, but I don’t found any tutorial, guide can help me.
Have you some helpful documents?
Hi,
First of all i would like to thank you for releasing the source code for Icecast client on iPhone. Thank you, Great work!
The code runs smooth on iPhone Simulator and Device using Radio Javan on port 80 as streaming source but when i change the Radio source to any other Icecast Station running on a port distinct from 80, 8000 in my case, the app seens to be unable to connect.
Any idea on how to circunvent this behave?
Thank you very much!
Any chance to get my modifs published on your log ?
Hello,
I modified the Hamed’s code to allow to decode ICECast stream as well.
My modifications were posted on this blog’s comment a couple of days ago but they are not appearing.
As per GPL v2 license, I posted my code at http://kenai.com/projects/maxi80/
Seb
Sebastien, sure if you have the diff, I can apply it to the source on github. But I believe the code that I had posted already worked for Icecast.. was there some bugs?
Hamed, see below the diff I got when adding Sebastien’s contribution
The code definitely was not able to read shoutcast feeds.
Thanks again for your great contribution.
I’ll add a new African Radio to the AppStore soon.
Sebastien. Genial ce que tu as fait. J’ai tente mais tu as ete plus rapide, pour ne pas dire meilleur. Merci pour la contribution.
—————————–
diff –git a/Classes/Radio.h b/Classes/Radio.h
index 32f6b4a..5a358f7 100644
— a/Classes/Radio.h
+++ b/Classes/Radio.h
@@ -63,6 +63,9 @@ typedef struct {
UIAlertView *alert;
iPhoneRadioAppDelegate *appDelegate;
AQPlayerState audioState;
+
+ BOOL ICECast; //true when IceCast Stream, false for ShoutCast
+
}
-(id)init;
diff –git a/Classes/Radio.m b/Classes/Radio.m
index f661960..9f9b319 100644
— a/Classes/Radio.m
+++ b/Classes/Radio.m
@@ -341,12 +341,72 @@ void interruptionListenerCallback (void *inUserData, UInt32 interruptionState) {
}
}
+//
+// Added By Sébastien Stormacq
+// ShoutCast support
+//
+
+-(int)parseShoutCastHeaders:(NSData*)buffer {
+
+ streamHeaders = [[NSMutableDictionary alloc] init];
+
+ NSString* rawHeader = [[NSString alloc] initWithData:buffer encoding:NSASCIIStringEncoding];
+ NSString* DELIMITER = @”\r\n”;
+ NSString* END_OF_HEADER_DELIMITER = @”\r\n\r\n”;
+
+ NSArray* lines = [rawHeader componentsSeparatedByString:DELIMITER];
+ for(NSString* line in lines) {
+ //NSLog(@”line = %@”, line);
+
+ //did we reach the last line ?
+ if ([line isEqualToString:@""])
+ break;
+ else {
+
+ NSRange range = [line rangeOfString:@":"];
+ if (range.location != NSNotFound) {
+ NSString* key = [line substringToIndex:range.location];
+ NSString* value = [line substringFromIndex:range.location+1];
+
+ [streamHeaders setValue:value forKey:key];
+ }
+ }
+ }
+
+ int result = [rawHeader rangeOfString:END_OF_HEADER_DELIMITER].location + [END_OF_HEADER_DELIMITER length];
+
+ NSLog(@”ICECAST Headers = %@”, streamHeaders);
+ NSLog(@”ICECAST startOfData = %d”, result);
+
+ [rawHeader release];
+
+ return result;
+}
+//
+// end of addition
+//
+
+#pragma mark NSURLConnection callBacks
+
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSHTTPURLResponse *u = (NSHTTPURLResponse *)response;
NSLog(@”HTTP Response = %u”, [u statusCode]);
-
- streamHeaders = [u allHeaderFields];
- [streamHeaders retain];
+
+ //
+ // Modified By Sébastien Stormacq
+ // ShoutCast support
+ //
+ if ([[u allHeaderFields] objectForKey:@”Ice-Audio-Info”] != nil) {
+ ICECast = YES;
+ streamHeaders = [u allHeaderFields];
+ [streamHeaders retain];
+ } else {
+ //likely a shout cast stream
+ ICECast = NO;
+ }
+ //
+ // end of modifications
+ //
NSLog(@”HTTP Response Headers = %@”, streamHeaders);
}
@@ -355,14 +415,42 @@ void interruptionListenerCallback (void *inUserData, UInt32 interruptionState) {
// NSLog(@”didReceiveData %u bytes”, [data length]);
int length = [data length];
const char *bytes = (const char *)[data bytes];
+
+ int positionOfFirstData;
+
if (!icyInterval) {
- icyInterval = [[streamHeaders objectForKey:@"Icy-Metaint"] intValue];
+
+ if (ICECast) {
+ icyInterval = [[streamHeaders objectForKey:@"Icy-Metaint"] intValue];
+
+ //
+ // Added By Sébastien Stormacq
+ // ShoutCast support
+ //
+ } else {
+ //parse first bytes until empty line to read ShoutCast headers
+ positionOfFirstData = [self parseShoutCastHeaders:data];
+ icyInterval = [[streamHeaders objectForKey:@"icy-metaint"] intValue];
+
+ //for ICECast, remove the text header from first data block
+ NSRange range;
+ range.location = positionOfFirstData;
+ range.length = [data length] – positionOfFirstData;
+ //NSLog(@”range = %d, %d”, range.location, range.length);
+
+ NSData* subData = [data subdataWithRange:range];
+ length = [subData length];
+ bytes = (const char *)[subData bytes];
+ //NSLog(@”Pruned Data %u bytes\n—\n%@\n—”, length, [NSString stringWithCString:bytes length:length]);
+ }
+ //
+ // End of additions
+ //
+
NSLog(@”ICY INTERVAL = %u”, icyInterval);
- [self fillcurrentPacket:bytes withLength:length];
- }
- else {
- [self fillcurrentPacket:bytes withLength:length];
}
+
+ [self fillcurrentPacket:bytes withLength:length];
}
-(void)reconnect {
diff –git a/Classes/iPhoneRadioAppDelegate.m b/Classes/iPhoneRadioAppDelegate.m
index 2c4dd35..2eba747 100644
— a/Classes/iPhoneRadioAppDelegate.m
+++ b/Classes/iPhoneRadioAppDelegate.m
@@ -54,7 +54,9 @@
[window makeKeyAndVisible];
radio = [[Radio alloc] init];
- [radio connect:@"http://stream.radiojavan.com/radiojavan" withDelegate:self withGain:(0.5)];
+ //[radio connect:@"http://stream.radiojavan.com/radiojavan" withDelegate:self withGain:(0.5)];
+ [radio connect:@"http://stream.radiovazogasy.com:8001" withDelegate:self withGain:(0.5)];
+
}
Could you upload the final code somewhere?
Sébastien Stormacq can you upload as a compressed file
My browser don’t like downloading weird extensions
Hi,
Its good info here same thanks for sharing it We are also developing iphone application and android application for 3rd generation mobiles
Thanks
Sebastian can you upload the code?
Please convert the code to shoucast radio!!
Hey, I’m just getting a black screen when I build and run. What should I do to fix that?
HEY!!! I NEED HELP!!! HOW DO YOU CONTROL THE VOLUME USING A UISLIDER WITH THIS CODE?!?! PLEASE HELP!!! I’VE TRIED EVERYTHING!!!
THANK YOU!!!
Thank you very much!
What you have already done is great!
I wanted to ask you.
I try to change the URL from the one you have to: http://174.120.46.26:9000/
There is no error coming but I don’t listen to any sound.
What do you think is hapening?
Thank you!
Hi Hamed,
I am making a streaming application. I have a query , how can i detect the status of streaming server whterther it is UP or Down. Is there any way to tackle this.
Hi Hamed,
how did you open source your code? did you submit another end user license agreement to Apple?
A comment on your use of the GPL licensing model….does this not also require that you open source the entire Radio Javan application? If that isn’t your original intention, you might want to consider using the Lesser GPL (LGPL) instead.