Inherits from NSObject
Declared in AudioStreamer.h

Overview

This class is implemented on top of Apple’s AudioQueue framework. This framework is much too low-level for must use cases, so this class encapsulates the functionality to provide a nicer interface. The interface still requires some management, but it is far more sane than dealing with the AudioQueue structures yourself.

This class is essentially a pipeline of three components to get audio to the speakers:

CFReadStream => AudioFileStream => AudioQueue

CFReadStream

The method of reading HTTP data is using the low-level CFReadStream class because it allows configuration of proxies and scheduling/rescheduling on the event loop. All data read from the HTTP stream is piped into the AudioFileStream which then parses all of the data. This stage of the pipeline also flags that events are happening to prevent a timeout. All network activity occurs on the thread which started the audio stream.

AudioFileStream

This stage is implemented by Apple frameworks, and parses all audio data. It is composed of two callbacks which receive data. The first callback invoked in series is one which is notified whenever a new property is known about the audio stream being received. Once all properties have been read, the second callback beings to be invoked, and this callback is responsible for dealing with packets.

The second callback is invoked whenever complete “audio packets” are available to send to the audio queue. This stage is invoked on the call stack of the stream which received the data (synchronously with receiving the data).

Packets received are buffered in a static set of buffers allocated by the audio queue instance. When a buffer is full, it is committed to the audio queue, and then the next buffer is moved on to. Multiple packets can possibly fit in one buffer. When committing a buffer, if there are no more buffers available, then the http read stream is unscheduled from the run loop and all currently received data is stored aside for later processing.

AudioQueue

This final stage is also implemented by Apple, and receives all of the full buffers of data from the AudioFileStream’s parsed packets. The implementation manages its own set of threads, but callbacks are invoked on the main thread. The two callbacks that the audio stream is interested in are playback state changing and audio buffers being freed.

When a buffer is freed, then it is marked as so, and if the stream was waiting for a buffer to be freed a message to empty the queue as much as possible is sent to the main thread’s run loop. Otherwise no extra action need be performed.

The main purpose of knowing when the playback state changes is to change the state of the player accordingly.

Errors

There are a large number of places where error can happen, and the stream can bail out at any time with an error. Each error has its own code and corresponding string representation. Any error will halt the entire audio stream and cease playback. Some errors might want to be handled by the manager of the AudioStreamer class, but others normally indicate that the remote stream just won’t work. Occasionally errors might reflect a lack of local resources.

Error information can be learned from the error property.

Seeking

To seek inside an audio stream, the bit rate must be known along with some other metadata, but this is not known until after the stream has started. For this reason the seek can fail if not enough data is known yet.

If a seek succeeds, however, the actual method of doing so is as follows. First, open a stream at position 0 and collect data about the stream, when the seek is requested, cancel the stream and re-open the connection with the proper byte offset. This second stream is then used to put data through the pipelines.

Example usage

An audio stream is a one-shot thing. Once initialized, the source cannot be changed and a single audio stream cannot be re-used. To do this, multiple AudioStreamer objects need to be created/managed.

Properties

bufferCount

The number of audio buffers to have

@property (readwrite) UInt32 bufferCount

Discussion

Each audio buffer contains one or more packets of audio data. This amount is only relevant if infinite buffering is turned off. This is the amount of data which is stored in memory while playing. Once this memory is full, the remote connection will not be read and will not receive any more data until one of the buffers becomes available.

With infinite buffering turned on, this number should be at least 3 or so to make sure that there’s always data to be read. With infinite buffering turned off, this should be a number to not consume too much memory, but to also keep up with the remote data stream. The incoming data should always be able to stay ahead of these buffers being filled

Higher values will mean more data is stored so the higher you go, the more you can go without streaming more. This can help in the case of brief network slowdowns. Additionally, higher bitrates demand more buffers than lower ones as the data needed to store is much larger. The default value covers most bitrates but further tweaking may be required in certain cases.

Default: 256

Declared In

AudioStreamer.h

bufferFillCountToStart

The number of buffers to fill before starting the stream

@property (readwrite) UInt32 bufferFillCountToStart

Discussion

The higher the value, the more smooth the start will be as there is more data cached for playback. A higher value will however result in slower starts which can also impact how “in-sync” livestreams are.

This should always be lower than, or equal to, the bufferCount property but will not error if not done so. AudioStreamer will simply fallback to the bufferCount as the amount to fill instead.

Default: 32

Declared In

AudioStreamer.h

bufferInfinite

Flag if to infinitely buffer data

@property (readwrite) BOOL bufferInfinite

Discussion

If this flag is set to NO, then a statically sized buffer is used as determined by the bufferCount and bufferSize properties and the read stream will be descheduled when those fill up. This limits the bandwidth consumed to the remote source and also limits memory usage.

If, however, you wish to hold the entire stream in memory, then you can set this flag to YES. In this state, the stream will be entirely downloaded, regardless if the buffers are full or not. This way if the network stream cuts off halfway through a song, the rest of the song will be downloaded locally to finish off. The next song might still be in trouble, however… With this turned on, memory usage will be higher because the entire stream will be downloaded as fast as possible, and the bandwidth to the remote will also be consumed. Depending on the situation, this might not be that bad of a problem.

Default: NO

Declared In

AudioStreamer.h

bufferSize

The default size for each buffer allocated

@property (readwrite) UInt32 bufferSize

Discussion

Each buffer’s size is attempted to be guessed from the audio stream being received. This way each buffer is tuned for the audio stream itself. If this inferring of the buffer size fails, however, this is used as a fallback as how large each buffer should be.

If you find that this is being used, then it should be coordinated with bufferCount above to make sure that the audio stays responsive and slightly behind the HTTP stream

Default: 4096

Declared In

AudioStreamer.h

error

The error the streamer threw

@property (readonly) NSError *error

Discussion

If an error occurs on the stream, then this variable is set with the corresponding error information.

By default this is nil.

Declared In

AudioStreamer.h

errorCode

The error code the streamer threw (Deprecated: Use the -code method from the error property instead)

@property (readonly) AudioStreamerErrorCode errorCode

Discussion

If an error occurs on the stream, then this variable is set with the code corresponding to the error

By default this is AS_NO_ERROR.

Declared In

AudioStreamer.h

fileType

The file type of this audio stream

@property (readwrite) AudioFileTypeID fileType

Discussion

This is an optional parameter. If not specified, then the file type will be guessed. First, the MIME type of the response is used to guess the file type, and if that fails the extension on the url is used. If that fails as well, then the default is an MP3 stream.

If this property is set, then no inferring is done and that file type is always used.

Default: (guess)

Declared In

AudioStreamer.h

httpHeaders

Headers received from the remote source

@property (readonly) NSDictionary *httpHeaders

Discussion

Used to determine file size, but other information may be useful as well

Declared In

AudioStreamer.h

networkError

The network error the streamer threw (Deprecated: Use the -localizedFailureReason method from ‘error’ property instead

On AS_NETWORK_CONNECTION_FAILED, this will contain the error details)

@property (readonly) NSError *networkError

Discussion

Warning: AS_TIMED_OUT no longer sets this property as no other info is given

Declared In

AudioStreamer.h

playbackRate

Rate to playback audio

@property (readwrite) float playbackRate

Availability

iOS 7 and later, OS X 10.6 and later

Discussion

This property must be in the range 0.5 through 2.0.

A value of 1.0 specifies that the audio should play back at its normal rate.

Default: 1.0

Declared In

AudioStreamer.h

streamDescription

The stream’s description.

@property (readonly) AudioStreamBasicDescription streamDescription

Discussion

This property contains data like sample rate and number of audio channels.

See Apple’s AudioStreamBasicDescription documentation for more information

Declared In

AudioStreamer.h

timeoutInterval

Interval to consider timeout if no network activity is seen

@property (readwrite) int timeoutInterval

Discussion

When downloading audio data from a remote source, this is the interval in which to consider it a timeout if no data is received. If the stream is paused, then that time interval is not counted. This only counts if we are waiting for data and an amount of time larger than this elapses.

The units of this variable is seconds.

Default: 10

Declared In

AudioStreamer.h

url

The remote resource that this stream is playing

@property (readonly) NSURL *url

Discussion

This is a read-only property and cannot be changed after creation

Declared In

AudioStreamer.h

Class Methods

streamWithURL:

Allocate a new audio stream with the specified url

+ (instancetype)streamWithURL:(NSURL *)url

Parameters

url

The remote source of audio

Return Value

The stream to configure and being playback with

Discussion

The created stream has not started playback. This gives an opportunity to configure the rest of the stream as necessary. To start playback, send the stream an explicit start message.

Declared In

AudioStreamer.h

stringForErrorCode:

Converts an error code to a string (Deprecated: Use the -localizedDescription method from the ‘error’ property instead)

+ (NSString *)stringForErrorCode:(AudioStreamerErrorCode)anErrorCode

Parameters

anErrorCode

The code to convert, usually from the errorCode property

Return Value

The string description of the error code (as best as possible)

Declared In

AudioStreamer.h

Instance Methods

calculatedBitRate:

Calculates the bit rate of the stream

- (BOOL)calculatedBitRate:(double *)ret

Parameters

ret

The double to fill in with the bit rate on success

Return Value

YES if the bit rate could be calculated with a high degree of certainty, or NO if it could not be.

Discussion

Calculates the bit rate of the stream

All packets received so far contribute to the calculation of the bit rate. This is used internally to determine other factors like duration and progress.

Declared In

AudioStreamer.h

doneReason

Returns the reason that the streamer is done

- (AudioStreamerDoneReason)doneReason

Return Value

The reason for the stream being done, or that it’s not done.

Discussion

When isDone returns true, this will return the reason that the stream has been flagged as being done.

Declared In

AudioStreamer.h

duration:

Calculates the duration of the audio stream, in seconds

- (BOOL)duration:(double *)ret

Parameters

ret

The variable to fill with the duration of the stream on success

Return Value

YES if ret contains the duration of the stream, or NO if the duration could not be determined. In the NO case, the contents of ret are undefined

Discussion

Uses information about the size of the file and the calculated bit rate to determine the duration of the stream.

Declared In

AudioStreamer.h

fadeInDuration:

Fade in playback

- (void)fadeInDuration:(float)duration

Parameters

duration

a double which represents the fade-in time span.

Discussion

The AudioQueue volume is progressively increased from 0 to 1

Declared In

AudioStreamer.h

fadeOutDuration:

Fade out playback

- (void)fadeOutDuration:(float)duration

Parameters

duration

a double which represents the fade-in time span.

Discussion

The AudioQueue volume is progressively decreased from 1 to 0.

Declared In

AudioStreamer.h

isDone

Tests whether the stream is done with all operation

- (BOOL)isDone

Return Value

YES if the stream is done, or NO Otherwise

Discussion

A stream can be ‘done’ if it either hits an error or consumes all audio data from the remote source. This method also checks if the stream has been stopped.

Declared In

AudioStreamer.h

isPaused

Tests whether the stream is paused

- (BOOL)isPaused

Return Value

YES if the stream is paused, or NO Otherwise

Discussion

A stream is not paused if it is waiting for data. A stream is paused if and only if it used to be playing, but the it was paused via the pause method.

Declared In

AudioStreamer.h

isPlaying

Tests whether the stream is playing

- (BOOL)isPlaying

Return Value

YES if the stream is playing, or NO Otherwise

Declared In

AudioStreamer.h

isWaiting

Tests whether the stream is waiting

- (BOOL)isWaiting

Return Value

YES if the stream is waiting, or NO Otherwise

Discussion

This could either mean that we’re waiting on data from the network or waiting for some event with the AudioQueue instance.

Declared In

AudioStreamer.h

pause

Pause the audio stream if playing

- (BOOL)pause

Return Value

YES if the audio stream was paused, or NO if it was not in the AS_PLAYING state or an error occurred.

Declared In

AudioStreamer.h

play

Plays the audio stream if paused

- (BOOL)play

Return Value

YES if the audio stream entered into the AS_PLAYING state, or NO if any other error or bad state was encountered.

Declared In

AudioStreamer.h

progress:

Calculate the progress into the stream, in seconds

- (BOOL)progress:(double *)ret

Parameters

ret

A double which is filled in with the progress of the stream. The contents are undefined if NO is returned

Return Value

YES if the progress of the stream was determined, or NO if the progress could not be determined at this time

Discussion

The AudioQueue instance is polled to determine the current time into the stream, and this is returned.

Declared In

AudioStreamer.h

seekByDelta:

Seek to a relative time in the audio stream

- (BOOL)seekByDelta:(double)seekTimeDelta

Parameters

seekTimeDelta

The time interval from current seek time to seek to

Return Value

YES if the stream will be seeking, or NO if the stream did not have enough information available to it to seek to the specified time.

Discussion

This will calculate the current stream progress and seek relative to it by the specified delta. Useful for seeking.

Declared In

AudioStreamer.h

seekToTime:

Seek to a specified time in the audio stream

- (BOOL)seekToTime:(double)newSeekTime

Parameters

newSeekTime

The time in seconds to seek to

Return Value

YES if the stream will be seeking, or NO if the stream did not have enough information available to it to seek to the specified time.

Discussion

This can only happen once the bit rate of the stream is known because otherwise the byte offset to the stream is not known. For this reason the function can fail to actually seek.

Additionally, seeking to a new time involves re-opening the audio stream with the remote source, although this is done under the hood.

Declared In

AudioStreamer.h

setHTTPProxy:port:

Set an HTTP proxy for this stream

- (void)setHTTPProxy:(NSString *)host port:(int)port

Parameters

host

The address/hostname of the remote host

port

The port of the proxy

Declared In

AudioStreamer.h

setSOCKSProxy:port:

Set SOCKS proxy for this stream

- (void)setSOCKSProxy:(NSString *)host port:(int)port

Parameters

host

The address/hostname of the remote host

port

The port of the proxy

Declared In

AudioStreamer.h

setVolume:

Attempt to set the volume on the audio queue

- (BOOL)setVolume:(float)volume

Parameters

volume

The volume to set the stream to in the range 0.0 to 1.0 where 1.0 is the loudest and 0.0 is silent

Return Value

YES if the volume was set, or NO if the audio queue wasn’t ready to have its volume set. When the state for this audio streamer changes internally to have a stream, then setVolume: will work

Declared In

AudioStreamer.h

start

Starts playback of this audio stream.

- (BOOL)start

Return Value

YES if the stream was started, or NO if the stream was previously started and this had no effect.

Discussion

This method can only be invoked once, and other methods will not work before this method has been invoked. All properties (like proxies) must be set before this method is invoked.

Declared In

AudioStreamer.h

stop

Stop all streams, clean up resources and prevent all further events from occurring.

- (void)stop

Discussion

This method may be invoked at any time from any point of the audio stream as a signal of error happening. This method sets the state to AS_STOPPED if it isn’t already AS_STOPPED or AS_DONE.

Declared In

AudioStreamer.h