<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>A Twofer &#187; Audio Queue Services</title>
	<atom:link href="http://www.atwofer.com/category/audio-queue-services/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.atwofer.com</link>
	<description>A place to talk about Software Development (RoR, J2EE, J2ME, CSS, HTML, JavaScript, Objective-C, etc) and some other stuff</description>
	<lastBuildDate>Fri, 16 Jul 2010 14:56:02 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Streaming and playing fixed-length MP3s using the iPhone SDK</title>
		<link>http://www.atwofer.com/2009/06/30/streaming-and-playing-fixed-length-mp3s-using-the-iphone-sdk/</link>
		<comments>http://www.atwofer.com/2009/06/30/streaming-and-playing-fixed-length-mp3s-using-the-iphone-sdk/#comments</comments>
		<pubDate>Tue, 30 Jun 2009 22:49:34 +0000</pubDate>
		<dc:creator>Randy</dc:creator>
				<category><![CDATA[Audio Queue Services]]></category>
		<category><![CDATA[AudioStreamer]]></category>
		<category><![CDATA[Objective-C]]></category>
		<category><![CDATA[iPhone SDK]]></category>
		<category><![CDATA[MPMoviePlayerController]]></category>

		<guid isPermaLink="false">http://www.atwofer.com/?p=100</guid>
		<description><![CDATA[The Problem
Lately, I&#8217;ve been doing a fair amount of coding using Objective-C and the iPhone SDK.  Once I got used to the language I really started to like it.  The more I used the SDK the more impressed I became with how much I could do with it.  However, when a customer [...]]]></description>
			<content:encoded><![CDATA[<p><strong>The Problem</strong></p>
<p>Lately, I&#8217;ve been doing a fair amount of coding using Objective-C and the iPhone SDK.  Once I got used to the language I really started to like it.  The more I used the SDK the more impressed I became with how much I could do with it.  However, when a customer of ours at <a href="http://www.episodic.com" target="_blank">Episodic</a> needed an iPhone App that could play a list of MP3s (RSS Feed to be exact) with &#8220;iPod-like&#8221; functionality, I was surprised to find that this is not something I can do with the embedded Quicktime Player.  In fact, you can&#8217;t even just hook into the <em>MPMoviePlayerController</em>.  For example, there is no way to know when one songs ends so that I can start the next song since the <em>MPMoviePlayerPlaybackDidFinishNotification</em> notification is fired when the song ends or the user hits the done button.  So after a lot of searching I came across Matt Gallagher&#8217;s great post &#8220;<a href="http://cocoawithlove.com/2009/06/revisiting-old-post-streaming-and.html" target="_blank">Revisiting an old post: Streaming and playing an MP3 stream</a>&#8220;.</p>
<p><strong>Modifying the AudioStreamer Class to Play Fixed-Length MP3s</strong></p>
<p>So Matt&#8217;s post got me part of the way there.  It at least gave me an understanding of the tools I would need to use to accomplish my task.  The main difference between what he had and what I needed is that he was playing an MP3 stream and I wanted to play MP3 files.  The first change I made was I added a new constructor to the AudioStreamer class.  This allowed me to initialized the class with a URL to a file on the file system (I&#8217;ll talk about how to get the file on your file system later).</p>
<p><code><span style="padding-left: 20px;">- (id)initWithFileURL:(NSURL *)aURL {</span><br />
<span style="padding-left: 40px;">[self initWithURL:aURL];</span><br />
<span style="padding-left: 40px;">fixedLength = YES;</span><br />
<span style="padding-left: 40px;">return self;</span><br />
<span style="padding-left: 20px;">}</span></code></p>
<p>This constructor sets a flag to indicate that we are streaming fixed-length MP3s.  I then key off this flag in the <em>openFileStream</em> method.</p>
<p><code><span style="padding-left: 20px;">if (fixedLength) {</span><br />
<span style="padding-left: 40px;">stream = CFReadStreamCreateWithFile(</span><br />
<span style="padding-left: 80px;">kCFAllocatorDefault, (CFURLRef)url);</span><br />
<span style="padding-left: 20px;">} else {</span><br />
<span style="padding-left: 40px;">CFHTTPMessageRef message = CFHTTPMessageCreateRequest(</span><br />
<span style="padding-left: 80px;">NULL, (CFStringRef)@"GET", (CFURLRef)url, kCFHTTPVersion1_1);</span><br />
<span style="padding-left: 40px;">stream = CFReadStreamCreateForHTTPRequest(NULL, message);</span><br />
<span style="padding-left: 40px;">CFRelease(message);</span><br />
<span style="padding-left: 40px;">...</span></code></p>
<p>The above code simply creates the stream from the file system instead of a remote file.  Because the result is still a CFReadStreamRef object the rest of the class mostly remains unchanged.</p>
<p><strong>Downloading the MP3</strong></p>
<p>In my controller I just need to download the MP3 file to a location on the file system.  This can be done very easily by using a combination of <em>NSURLConnection</em> and <em>NSFileHandle</em>.</p>
<p><code><span style="padding-left: 20px;">audioFile = [[NSFileHandle</span><br />
<span style="padding-left: 60px;">fileHandleForWritingAtPath:downloadFileName] retain];</span><br />
<span style="padding-left: 20px;">NSURLRequest *downloadRequest = [NSURLRequest</span><br />
<span style="padding-left: 60px;">requestWithURL:resourceURL </span><br />
<span style="padding-left: 60px;">cachePolicy:NSURLRequestUseProtocolCachePolicy</span><br />
<span style="padding-left: 60px;">timeoutInterval:60.0]; </span><br />
<span style="padding-left: 20px;">download = [[NSURLConnection alloc]</span><br />
<span style="padding-left: 60px;">initWithRequest:downloadRequest delegate:self];</span></code></p>
<p>This code creates the file to write the data to and creates the connection to fetch the data.  I know just need to handle the data.</p>
<p><code><span style="padding-left: 20px;">- (void)connection:(NSURLConnection *)connection </span><br />
<span style="padding-left: 60px;">didReceiveData:(NSData *)data {</span><br />
<span style="padding-left: 40px;">// Append the data to the file on the file system</span><br />
<span style="padding-left: 40px;">[audioFile writeData:data];</span><br />
<span style="padding-left: 40px;">downloadedLength += [data length];</span><br />
<span style="padding-left: 40px;"> </span><br />
<span style="padding-left: 40px;">// When we get enough of the file, then just start playing it.</span><br />
<span style="padding-left: 40px;">if (!streamer &amp;&amp; downloadedLength &gt; DEFAULT_MIN_LENGTH_TO_PLAY) {</span><br />
<span style="padding-left: 60px;">NSLog(@"start playback for %@", downloadFileName);</span><br />
<span style="padding-left: 60px;">[self initAndStartStreamer];</span><br />
<span style="padding-left: 40px;">}</span><br />
<span style="padding-left: 20px;">}</span></code></p>
<p>The above code is the <em>NSURLConnection</em> delegate method for receiving the data and writing the data to the file.  Once I have &#8220;enough&#8221; of the MP3 I can start playing it by create the streamer and passing it the URL to our <em>audioFile</em>.</p>
<p><strong>NEW: Handling EOF</strong></p>
<p>One bug that I ran into since my initial post is that if I actually put this on my phone and went outside, away from my wireless network, the song would sometimes end early.  What was happening is that the AudioStreamer was playing the file faster than it was being downloaded.  The AudioStreamer would hit the EOF and then stop.  To prevent this case I added some &#8220;throttling&#8221; to the playback loop that checks if we are too close to the end of the file and changes the state to buffering until we have enough to continue.  The following code is in startInternal.</p>
<p><code><span style="padding-left: 20px;">// Flag to indicate that we have gotten too close to the EOF before the</span><br />
<span style="padding-left: 20px;">//  entire file has downloaded so we need throttle the playback.</span><br />
<span style="padding-left: 20px;">BOOL isThrottling = NO;{</span><br />
<span> </span><br />
<span style="padding-left: 20px;">//</span><br />
<span style="padding-left: 20px;">// Process the run loop until playback is finished or failed.</span><br />
<span style="padding-left: 20px;">//</span><br />
<span style="padding-left: 20px;">BOOL isRunning = YES;</span><br />
<span style="padding-left: 20px;">do</span><br />
<span style="padding-left: 20px;">{</span><br />
<span style="padding-left: 40px;">// If we are playing a fixed-length MP3 make sure we are not too close</span><br />
<span style="padding-left: 40px;">// to the end of the file. This prevents us from hitting the end of</span><br />
<span style="padding-left: 40px;">// the file. before it is fully downloaded.  Very useful when not on</span><br />
<span style="padding-left: 40px;">// 3G since the song may be played faster than it is downloaded.</span><br />
<span style="padding-left: 40px;">if (!fixedLength || self.fileDownloadComplete || </span><br />
<span style="padding-left: 80px;">self.fileDownloadCurrentSize &gt; (fileDownloadBytesRead + </span><br />
<span style="padding-left: 80px;">(kAQBufSize * kNumAQBufs))) {</span><br />
<span> </span><br />
<span style="padding-left: 60px;">isRunning = [[NSRunLoop currentRunLoop</span><br />
<span style="padding-left: 100px;">runMode:NSDefaultRunLoopMode</span><br />
<span style="padding-left: 100px;">beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];</span><br />
<span> </span><br />
<span style="padding-left: 60px;">if (isThrottling) {</span><br />
<span style="padding-left: 80px;">isThrottling = NO;</span><br />
<span style="padding-left: 80px;">AudioQueueStart(audioQueue, NULL);</span><br />
<span style="padding-left: 80px;">self.state = AS_PLAYING;</span><br />
<span style="padding-left: 60px;">}</span><br />
<span style="padding-left: 60px;"> </span><br />
<span style="padding-left: 60px;">//</span><br />
<span style="padding-left: 60px;">// If there are no queued buffers, we need to check here since the</span><br />
<span style="padding-left: 60px;">// handleBufferCompleteForQueue:buffer: should not change the state</span><br />
<span style="padding-left: 60px;">// (may not enter the synchronized section).</span><br />
<span style="padding-left: 60px;">//</span><br />
<span style="padding-left: 60px;">if (buffersUsed == 0 &amp;&amp; self.state == AS_PLAYING)</span><br />
<span style="padding-left: 60px;">{</span><br />
<span style="padding-left: 80px;">self.state = AS_BUFFERING;</span><br />
<span style="padding-left: 60px;">}</span><br />
<span style="padding-left: 40px;">} else if (!isThrottling &amp;&amp; self.state == AS_PLAYING) {</span><br />
<span style="padding-left: 60px;">NSLog(@"Start throttling because we are too close to EOF.");<br />
<span style="padding-left: 60px;"> </span><br />
<span style="padding-left: 60px;">self.state = AS_BUFFERING;</span><br />
<span style="padding-left: 60px;"> </span><br />
<span style="padding-left: 60px;">AudioQueuePause(audioQueue);</span><br />
<span style="padding-left: 60px;">isThrottling = YES;</span><br />
<span style="padding-left: 60px;"> </span><br />
<span style="padding-left: 60px;">// Sleep for a few seconds</span><br />
<span style="padding-left: 60px;">[NSThread sleepForTimeInterval:3.0];</span><br />
<span style="padding-left: 40px;">}</span><br />
<span style="padding-left: 40px;"> </span><br />
<span style="padding-left: 20px;">} while (isRunning &amp;&amp; ![self runLoopShouldExit]);</span><br />
</span></code></p>
<p><strong>Seeking?</strong></p>
<p>I was unable to figure out a way to allow for seeking. This would also provide a way for playback to pick up where it left off when the connection during the download phase.  If anyone has ideas on how to support this I would love to hear about it.</p>
<p><strong>Conclusion</strong></p>
<p>This is still far from ideal and it really seems like this is something that should be a lot easier to do within the SDK.  In the meantime, hopefully this is helpful to others.</p>
<p>You can download my source code <a title="Source Code" href="http://www.atwofer.com/wp-content/uploads/2009/06/FixedLengthPlayer-v2.zip">here</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwofer.com/2009/06/30/streaming-and-playing-fixed-length-mp3s-using-the-iphone-sdk/feed/</wfw:commentRss>
		<slash:comments>50</slash:comments>
		</item>
	</channel>
</rss>
