Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added HE-AAC support and seeking.

  • Loading branch information...
commit 733e0b60ebaf12040c216b1bb9ea8034763d7fba 1 parent a587e20
Matt Gallagher authored
View
31 Classes/AudioStreamer.h
@@ -35,7 +35,7 @@
// to zero too often, this value may need to
// increase. Min 3, typical 8-24.
-#define kAQBufSize 2048 // Number of bytes in each audio queue buffer
+#define kAQDefaultBufSize 2048 // Number of bytes in each audio queue buffer
// Needs to be big enough to hold a packet of
// audio from the audio file. If number is too
// large, queuing of audio before playback starts
@@ -109,14 +109,19 @@ extern NSString * const ASStatusChangedNotification;
//
AudioQueueRef audioQueue;
AudioFileStreamID audioFileStream; // the audio file stream parser
+ AudioStreamBasicDescription asbd; // description of the audio
+ NSThread *internalThread; // the thread where the download and
+ // audio file stream parsing occurs
AudioQueueBufferRef audioQueueBuffer[kNumAQBufs]; // audio queue buffers
AudioStreamPacketDescription packetDescs[kAQMaxPacketDescs]; // packet descriptions for enqueuing audio
unsigned int fillBufferIndex; // the index of the audioQueueBuffer that is being filled
+ UInt32 packetBufferSize;
size_t bytesFilled; // how many bytes have been filled
size_t packetsFilled; // how many packets have been filled
bool inuse[kNumAQBufs]; // flags to indicate that a buffer is still in use
NSInteger buffersUsed;
+ NSDictionary *httpHeaders;
AudioStreamerState state;
AudioStreamerStopReason stopReason;
@@ -131,19 +136,31 @@ extern NSString * const ASStatusChangedNotification;
CFReadStreamRef stream;
NSNotificationCenter *notificationCenter;
- NSUInteger dataOffset;
- UInt32 bitRate;
+ UInt32 bitRate; // Bits per second in the file
+ NSInteger dataOffset; // Offset of the first audio packet in the stream
+ NSInteger fileLength; // Length of the file in bytes
+ NSInteger seekByteOffset; // Seek offset within the file in bytes
+ UInt64 audioDataByteCount; // Used when the actual number of audio bytes in
+ // the file is known (more accurate than assuming
+ // the whole file is audio)
+
+ UInt64 processedPacketsCount; // number of packets accumulated for bitrate estimation
+ UInt64 processedPacketsSizeTotal; // byte size of accumulated estimation packets
- bool seekNeeded;
double seekTime;
- double sampleRate;
- double lastProgress;
+ double sampleRate; // Sample rate of the file (used to compare with
+ // samples played by the queue for current playback
+ // time)
+ double packetDuration; // sample rate times frames per packet
+ double lastProgress; // last calculated progress point
}
@property AudioStreamerErrorCode errorCode;
@property (readonly) AudioStreamerState state;
@property (readonly) double progress;
+@property (readonly) double duration;
@property (readwrite) UInt32 bitRate;
+@property (readonly) NSDictionary *httpHeaders;
- (id)initWithURL:(NSURL *)aURL;
- (void)start;
@@ -153,6 +170,8 @@ extern NSString * const ASStatusChangedNotification;
- (BOOL)isPaused;
- (BOOL)isWaiting;
- (BOOL)isIdle;
+- (void)seekToTime:(double)newSeekTime;
+- (double)calculatedBitRate;
@end
View
676 Classes/AudioStreamer.m
@@ -17,6 +17,9 @@
#import <CFNetwork/CFNetwork.h>
#endif
+#define BitRateEstimationMaxPackets 200
+#define BitRateEstimationMinPackets 20
+
NSString * const ASStatusChangedNotification = @"ASStatusChangedNotification";
NSString * const AS_NO_ERROR_STRING = @"No error.";
@@ -40,7 +43,7 @@
NSString * const AS_GET_AUDIO_TIME_FAILED_STRING = @"Audio queue get current time failed.";
NSString * const AS_AUDIO_STREAMER_FAILED_STRING = @"Audio playback failed";
NSString * const AS_NETWORK_CONNECTION_FAILED_STRING = @"Network connection failed";
-NSString * const AS_AUDIO_BUFFER_TOO_SMALL_STRING = @"Audio packets are larger than kAQBufSize.";
+NSString * const AS_AUDIO_BUFFER_TOO_SMALL_STRING = @"Audio packets are larger than kAQDefaultBufSize.";
@interface AudioStreamer ()
@property (readwrite) AudioStreamerState state;
@@ -209,7 +212,7 @@ @implementation AudioStreamer
@synthesize errorCode;
@synthesize state;
@synthesize bitRate;
-@dynamic progress;
+@synthesize httpHeaders;
//
// initWithURL
@@ -234,7 +237,6 @@ - (id)initWithURL:(NSURL *)aURL
- (void)dealloc
{
[self stop];
- [notificationCenter release];
[url release];
[super dealloc];
}
@@ -418,6 +420,16 @@ - (void)failWithErrorCode:(AudioStreamerErrorCode)anErrorCode
}
}
+- (void)mainThreadStateNotification
+{
+ NSNotification *notification =
+ [NSNotification
+ notificationWithName:ASStatusChangedNotification
+ object:self];
+ [[NSNotificationCenter defaultCenter]
+ postNotification:notification];
+}
+
//
// setState:
//
@@ -436,15 +448,17 @@ - (void)setState:(AudioStreamerState)aStatus
{
state = aStatus;
- NSNotification *notification =
- [NSNotification
- notificationWithName:ASStatusChangedNotification
- object:self];
- [notificationCenter
- performSelector:@selector(postNotification:)
- onThread:[NSThread mainThread]
- withObject:notification
- waitUntilDone:NO];
+ if ([[NSThread currentThread] isEqual:[NSThread mainThread]])
+ {
+ [self mainThreadStateNotification];
+ }
+ else
+ {
+ [self
+ performSelectorOnMainThread:@selector(mainThreadStateNotification)
+ withObject:nil
+ waitUntilDone:NO];
+ }
}
}
}
@@ -519,73 +533,86 @@ - (BOOL)isIdle
}
//
-// openFileStream
+// hintForFileExtension:
+//
+// Generates a first guess for the file type based on the file's extension
+//
+// Parameters:
+// fileExtension - the file extension
+//
+// returns a file type hint that can be passed to the AudioFileStream
+//
++ (AudioFileTypeID)hintForFileExtension:(NSString *)fileExtension
+{
+ AudioFileTypeID fileTypeHint = kAudioFileMP3Type;
+ if ([fileExtension isEqual:@"mp3"])
+ {
+ fileTypeHint = kAudioFileMP3Type;
+ }
+ else if ([fileExtension isEqual:@"wav"])
+ {
+ fileTypeHint = kAudioFileWAVEType;
+ }
+ else if ([fileExtension isEqual:@"aifc"])
+ {
+ fileTypeHint = kAudioFileAIFCType;
+ }
+ else if ([fileExtension isEqual:@"aiff"])
+ {
+ fileTypeHint = kAudioFileAIFFType;
+ }
+ else if ([fileExtension isEqual:@"m4a"])
+ {
+ fileTypeHint = kAudioFileM4AType;
+ }
+ else if ([fileExtension isEqual:@"mp4"])
+ {
+ fileTypeHint = kAudioFileMPEG4Type;
+ }
+ else if ([fileExtension isEqual:@"caf"])
+ {
+ fileTypeHint = kAudioFileCAFType;
+ }
+ else if ([fileExtension isEqual:@"aac"])
+ {
+ fileTypeHint = kAudioFileAAC_ADTSType;
+ }
+ return fileTypeHint;
+}
+
+//
+// openReadStream
//
// Open the audioFileStream to parse data and the fileHandle as the data
// source.
//
-- (BOOL)openFileStream
+- (BOOL)openReadStream
{
@synchronized(self)
{
- NSAssert(stream == nil && audioFileStream == nil,
- @"audioFileStream already initialized");
+ NSAssert([[NSThread currentThread] isEqual:internalThread],
+ @"File stream download must be started on the internalThread");
+ NSAssert(stream == nil, @"Download stream already initialized");
//
- // Attempt to guess the file type from the URL. Reading the MIME type
- // from the CFReadStream would be a better approach since lots of
- // URL's don't have the right extension.
+ // Create the HTTP GET request
//
- // If you have a fixed file-type, you may want to hardcode this.
+ CFHTTPMessageRef message= CFHTTPMessageCreateRequest(NULL, (CFStringRef)@"GET", (CFURLRef)url, kCFHTTPVersion1_1);
+
//
- AudioFileTypeID fileTypeHint = kAudioFileMP3Type;
- NSString *fileExtension = [[url path] pathExtension];
- if ([fileExtension isEqual:@"mp3"])
- {
- fileTypeHint = kAudioFileMP3Type;
- }
- else if ([fileExtension isEqual:@"wav"])
- {
- fileTypeHint = kAudioFileWAVEType;
- }
- else if ([fileExtension isEqual:@"aifc"])
- {
- fileTypeHint = kAudioFileAIFCType;
- }
- else if ([fileExtension isEqual:@"aiff"])
- {
- fileTypeHint = kAudioFileAIFFType;
- }
- else if ([fileExtension isEqual:@"m4a"])
- {
- fileTypeHint = kAudioFileM4AType;
- }
- else if ([fileExtension isEqual:@"mp4"])
- {
- fileTypeHint = kAudioFileMPEG4Type;
- }
- else if ([fileExtension isEqual:@"caf"])
- {
- fileTypeHint = kAudioFileCAFType;
- }
- else if ([fileExtension isEqual:@"aac"])
- {
- fileTypeHint = kAudioFileAAC_ADTSType;
- }
-
- // create an audio file stream parser
- err = AudioFileStreamOpen(self, MyPropertyListenerProc, MyPacketsProc,
- fileTypeHint, &audioFileStream);
- if (err)
+ // If we are creating this request to seek to a location, set the
+ // requested byte range in the headers.
+ //
+ if (fileLength > 0 && seekByteOffset > 0)
{
- [self failWithErrorCode:AS_FILE_STREAM_OPEN_FAILED];
- return NO;
+ CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Range"),
+ (CFStringRef)[NSString stringWithFormat:@"bytes=%ld-%ld", seekByteOffset, fileLength]);
+ discontinuous = YES;
}
//
- // Create the GET request
+ // Create the read stream that will receive data from the HTTP request
//
- CFHTTPMessageRef message= CFHTTPMessageCreateRequest(NULL, (CFStringRef)@"GET", (CFURLRef)url, kCFHTTPVersion1_1);
stream = CFReadStreamCreateForHTTPRequest(NULL, message);
CFRelease(message);
@@ -647,6 +674,11 @@ - (BOOL)openFileStream
}
//
+ // We're now ready to receive data
+ //
+ self.state = AS_WAITING_FOR_DATA;
+
+ //
// Open the stream
//
if (!CFReadStreamOpen(stream))
@@ -761,13 +793,11 @@ - (void)startInternal
AudioSessionSetActive(true);
#endif
- self.state = AS_WAITING_FOR_DATA;
-
// initialize a mutex and condition so that we can block on buffers in use.
pthread_mutex_init(&queueBuffersMutex, NULL);
pthread_cond_init(&queueBufferReadyCondition, NULL);
- if (![self openFileStream])
+ if (![self openReadStream])
{
goto cleanup;
}
@@ -847,11 +877,17 @@ - (void)startInternal
AudioSessionSetActive(false);
#endif
+ [httpHeaders release];
+ httpHeaders = nil;
+
bytesFilled = 0;
packetsFilled = 0;
- seekTime = 0;
- seekNeeded = NO;
+ seekByteOffset = 0;
+ packetBufferSize = 0;
self.state = AS_INITIALIZED;
+
+ internalThread = nil;
+ [internalThread release];
}
[pool release];
@@ -877,11 +913,104 @@ - (void)start
notificationCenter =
[[NSNotificationCenter defaultCenter] retain];
self.state = AS_STARTING_FILE_THREAD;
- [NSThread
- detachNewThreadSelector:@selector(startInternal)
- toTarget:self
- withObject:nil];
+ internalThread =
+ [[NSThread alloc]
+ initWithTarget:self
+ selector:@selector(startInternal)
+ object:nil];
+ [internalThread start];
+ }
+ }
+}
+
+//
+// seekToTime:
+//
+// Attempts to seek to the new time. Will be ignored if the bitrate or fileLength
+// are unknown.
+//
+// Parameters:
+// newTime - the time to seek to
+//
+- (void)seekToTime:(double)newSeekTime
+{
+ @synchronized(self)
+ {
+ if ([self calculatedBitRate] == 0.0 || fileLength <= 0)
+ {
+ return;
+ }
+
+ //
+ // Calculate the byte offset for seeking
+ //
+ seekByteOffset = dataOffset +
+ (newSeekTime / self.duration) * (fileLength - dataOffset);
+
+ //
+ // Attempt to leave 1 useful packet at the end of the file (although in
+ // reality, this may still seek too far if the file has a long trailer).
+ //
+ if (seekByteOffset > fileLength - 2 * packetBufferSize)
+ {
+ seekByteOffset = fileLength - 2 * packetBufferSize;
+ }
+
+ //
+ // Store the old time from the audio queue and the time that we're seeking
+ // to so that we'll know the correct time progress after seeking.
+ //
+ seekTime = newSeekTime;
+
+ //
+ // Attempt to align the seek with a packet boundary
+ //
+ double calculatedBitRate = [self calculatedBitRate];
+ if (packetDuration > 0 &&
+ calculatedBitRate > 0)
+ {
+ UInt32 ioFlags = 0;
+ SInt64 packetAlignedByteOffset;
+ SInt64 seekPacket = floor(newSeekTime / packetDuration);
+ err = AudioFileStreamSeek(audioFileStream, seekPacket, &packetAlignedByteOffset, &ioFlags);
+ if (!err && !(ioFlags & kAudioFileStreamSeekFlag_OffsetIsEstimated))
+ {
+ seekTime -= (seekByteOffset - (packetAlignedByteOffset + dataOffset)) * 8.0 / calculatedBitRate;
+ seekByteOffset = packetAlignedByteOffset + dataOffset;
+ }
}
+
+ //
+ // Close the current read straem
+ //
+ if (stream)
+ {
+ CFReadStreamClose(stream);
+ CFRelease(stream);
+ stream = nil;
+ }
+
+ //
+ // Stop the audio queue
+ //
+ self.state = AS_STOPPING;
+ stopReason = AS_STOPPING_TEMPORARILY;
+ err = AudioQueueStop(audioQueue, true);
+ if (err)
+ {
+ [self failWithErrorCode:AS_AUDIO_QUEUE_STOP_FAILED];
+ return;
+ }
+
+ //
+ // Re-open the file stream. It will request a byte-range starting at
+ // seekByteOffset.
+ //
+ [self
+ performSelector:@selector(openReadStream)
+ onThread:internalThread
+ withObject:nil
+ waitUntilDone:NO];
}
}
@@ -925,6 +1054,48 @@ - (double)progress
}
//
+// calculatedBitRate
+//
+// returns the bit rate, if known. Uses packet duration times running bits per
+// packet if available, otherwise it returns the nominal bitrate. Will return
+// zero if no useful option available.
+//
+- (double)calculatedBitRate
+{
+ if (packetDuration && processedPacketsCount > BitRateEstimationMinPackets)
+ {
+ double averagePacketByteSize = processedPacketsSizeTotal / processedPacketsCount;
+ return 8.0 * averagePacketByteSize / packetDuration;
+ }
+
+ if (bitRate)
+ {
+ return (double)bitRate;
+ }
+
+ return 0;
+}
+
+//
+// duration
+//
+// Calculates the duration of available audio from the bitRate and fileLength.
+//
+// returns the calculated duration in seconds.
+//
+- (double)duration
+{
+ double calculatedBitRate = [self calculatedBitRate];
+
+ if (calculatedBitRate == 0 || fileLength == 0)
+ {
+ return 0.0;
+ }
+
+ return (fileLength - dataOffset) / (calculatedBitRate * 0.125);
+}
+
+//
// pause
//
// A togglable pause function.
@@ -957,26 +1128,6 @@ - (void)pause
}
//
-// shouldSeek
-//
-// Applies the logic to verify if seeking should occur.
-//
-// returns YES (seeking should occur) or NO (otherwise).
-//
-- (BOOL)shouldSeek
-{
- @synchronized(self)
- {
- if (bitRate != 0 && bitRate != ~0 && seekNeeded &&
- (state == AS_PLAYING || state == AS_PAUSED || state == AS_BUFFERING))
- {
- return YES;
- }
- }
- return NO;
-}
-
-//
// stop
//
// This method can be called to stop downloading/playback before it completes.
@@ -1028,6 +1179,14 @@ - (void)stop
- (void)handleReadFromStream:(CFReadStreamRef)aStream
eventType:(CFStreamEventType)eventType
{
+ if (aStream != stream)
+ {
+ //
+ // Ignore messages from old streams
+ //
+ return;
+ }
+
if (eventType == kCFStreamEventErrorOccurred)
{
[self failWithErrorCode:AS_AUDIO_DATA_NOT_FOUND];
@@ -1048,7 +1207,13 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream
//
if (bytesFilled)
{
- self.state = AS_FLUSHING_EOF;
+ if (self.state == AS_WAITING_FOR_DATA)
+ {
+ //
+ // Force audio data smaller than one whole buffer to play.
+ //
+ self.state = AS_FLUSHING_EOF;
+ }
[self enqueueBuffer];
}
@@ -1097,11 +1262,51 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream
}
else if (eventType == kCFStreamEventHasBytesAvailable)
{
- UInt8 bytes[kAQBufSize];
+ if (!httpHeaders)
+ {
+ CFTypeRef message =
+ CFReadStreamCopyProperty(stream, kCFStreamPropertyHTTPResponseHeader);
+ httpHeaders =
+ (NSDictionary *)CFHTTPMessageCopyAllHeaderFields((CFHTTPMessageRef)message);
+ CFRelease(message);
+
+ //
+ // Only read the content length if we seeked to time zero, otherwise
+ // we only have a subset of the total bytes.
+ //
+ if (seekByteOffset == 0)
+ {
+ fileLength = [[httpHeaders objectForKey:@"Content-Length"] integerValue];
+ }
+ }
+
+ if (!audioFileStream)
+ {
+ //
+ // Attempt to guess the file type from the URL. Reading the MIME type
+ // from the httpHeaders might be a better approach since lots of
+ // URL's don't have the right extension.
+ //
+ // If you have a fixed file-type, you may want to hardcode this.
+ //
+ AudioFileTypeID fileTypeHint =
+ [AudioStreamer hintForFileExtension:[[url path] pathExtension]];
+
+ // create an audio file stream parser
+ err = AudioFileStreamOpen(self, MyPropertyListenerProc, MyPacketsProc,
+ fileTypeHint, &audioFileStream);
+ if (err)
+ {
+ [self failWithErrorCode:AS_FILE_STREAM_OPEN_FAILED];
+ return;
+ }
+ }
+
+ UInt8 bytes[kAQDefaultBufSize];
CFIndex length;
@synchronized(self)
{
- if ([self isFinishing])
+ if ([self isFinishing] || !CFReadStreamHasBytesAvailable(stream))
{
return;
}
@@ -1109,7 +1314,7 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream
//
// Read the bytes from the stream
//
- length = CFReadStreamRead(stream, bytes, kAQBufSize);
+ length = CFReadStreamRead(stream, bytes, kAQDefaultBufSize);
if (length == -1)
{
@@ -1159,7 +1364,7 @@ - (void)enqueueBuffer
{
@synchronized(self)
{
- if ([self isFinishing])
+ if ([self isFinishing] || stream == 0)
{
return;
}
@@ -1239,6 +1444,89 @@ - (void)enqueueBuffer
}
//
+// createQueue
+//
+// Method to create the AudioQueue from the parameters gathered by the
+// AudioFileStream.
+//
+// Creation is deferred to the handling of the first audio packet (although
+// it could be handled any time after kAudioFileStreamProperty_ReadyToProducePackets
+// is true).
+//
+- (void)createQueue
+{
+ sampleRate = asbd.mSampleRate;
+ packetDuration = asbd.mFramesPerPacket / sampleRate;
+
+ // create the audio queue
+ err = AudioQueueNewOutput(&asbd, MyAudioQueueOutputCallback, self, NULL, NULL, 0, &audioQueue);
+ if (err)
+ {
+ [self failWithErrorCode:AS_AUDIO_QUEUE_CREATION_FAILED];
+ return;
+ }
+
+ // start the queue if it has not been started already
+ // listen to the "isRunning" property
+ err = AudioQueueAddPropertyListener(audioQueue, kAudioQueueProperty_IsRunning, MyAudioQueueIsRunningCallback, self);
+ if (err)
+ {
+ [self failWithErrorCode:AS_AUDIO_QUEUE_ADD_LISTENER_FAILED];
+ return;
+ }
+
+ // get the packet size if it is available
+ UInt32 sizeOfUInt32 = sizeof(UInt32);
+ err = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_PacketSizeUpperBound, &sizeOfUInt32, &packetBufferSize);
+ if (err || packetBufferSize == 0)
+ {
+ err = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_MaximumPacketSize, &sizeOfUInt32, &packetBufferSize);
+ if (err || packetBufferSize == 0)
+ {
+ // No packet size available, just use the default
+ packetBufferSize = kAQDefaultBufSize;
+ }
+ }
+
+ // allocate audio queue buffers
+ for (unsigned int i = 0; i < kNumAQBufs; ++i)
+ {
+ err = AudioQueueAllocateBuffer(audioQueue, packetBufferSize, &audioQueueBuffer[i]);
+ if (err)
+ {
+ [self failWithErrorCode:AS_AUDIO_QUEUE_BUFFER_ALLOCATION_FAILED];
+ return;
+ }
+ }
+
+ // get the cookie size
+ UInt32 cookieSize;
+ Boolean writable;
+ OSStatus ignorableError;
+ ignorableError = AudioFileStreamGetPropertyInfo(audioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, &writable);
+ if (ignorableError)
+ {
+ return;
+ }
+
+ // get the cookie data
+ void* cookieData = calloc(1, cookieSize);
+ ignorableError = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, cookieData);
+ if (ignorableError)
+ {
+ return;
+ }
+
+ // set the cookie on the queue.
+ ignorableError = AudioQueueSetProperty(audioQueue, kAudioQueueProperty_MagicCookie, cookieData, cookieSize);
+ free(cookieData);
+ if (ignorableError)
+ {
+ return;
+ }
+}
+
+//
// handlePropertyChangeForFileStream:fileStreamPropertyID:ioFlags:
//
// Object method which handles implementation of MyPropertyListenerProc
@@ -1262,85 +1550,92 @@ - (void)handlePropertyChangeForFileStream:(AudioFileStreamID)inAudioFileStream
if (inPropertyID == kAudioFileStreamProperty_ReadyToProducePackets)
{
discontinuous = true;
-
- AudioStreamBasicDescription asbd;
- UInt32 asbdSize = sizeof(asbd);
-
- // get the stream format.
- err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataFormat, &asbdSize, &asbd);
+ }
+ else if (inPropertyID == kAudioFileStreamProperty_DataOffset)
+ {
+ SInt64 offset;
+ UInt32 offsetSize = sizeof(offset);
+ err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataOffset, &offsetSize, &offset);
if (err)
{
[self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED];
return;
}
+ dataOffset = offset;
- sampleRate = asbd.mSampleRate;
-
- // create the audio queue
- err = AudioQueueNewOutput(&asbd, MyAudioQueueOutputCallback, self, NULL, NULL, 0, &audioQueue);
- if (err)
+ if (audioDataByteCount)
{
- [self failWithErrorCode:AS_AUDIO_QUEUE_CREATION_FAILED];
- return;
+ fileLength = dataOffset + audioDataByteCount;
}
-
- // start the queue if it has not been started already
- // listen to the "isRunning" property
- err = AudioQueueAddPropertyListener(audioQueue, kAudioQueueProperty_IsRunning, MyAudioQueueIsRunningCallback, self);
+ }
+ else if (inPropertyID == kAudioFileStreamProperty_AudioDataByteCount)
+ {
+ UInt32 byteCountSize = sizeof(UInt64);
+ err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_AudioDataByteCount, &byteCountSize, &audioDataByteCount);
if (err)
{
- [self failWithErrorCode:AS_AUDIO_QUEUE_ADD_LISTENER_FAILED];
+ [self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED];
return;
}
-
- // allocate audio queue buffers
- for (unsigned int i = 0; i < kNumAQBufs; ++i)
+ fileLength = dataOffset + audioDataByteCount;
+ }
+ else if (inPropertyID == kAudioFileStreamProperty_DataFormat)
+ {
+ if (asbd.mSampleRate == 0)
{
- err = AudioQueueAllocateBuffer(audioQueue, kAQBufSize, &audioQueueBuffer[i]);
+ UInt32 asbdSize = sizeof(asbd);
+
+ // get the stream format.
+ err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataFormat, &asbdSize, &asbd);
if (err)
{
- [self failWithErrorCode:AS_AUDIO_QUEUE_BUFFER_ALLOCATION_FAILED];
+ [self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED];
return;
}
}
-
- // get the cookie size
- UInt32 cookieSize;
- Boolean writable;
- OSStatus ignorableError;
- ignorableError = AudioFileStreamGetPropertyInfo(inAudioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, &writable);
- if (ignorableError)
+ }
+ else if (inPropertyID == kAudioFileStreamProperty_FormatList)
+ {
+ Boolean outWriteable;
+ UInt32 formatListSize;
+ err = AudioFileStreamGetPropertyInfo(inAudioFileStream, kAudioFileStreamProperty_FormatList, &formatListSize, &outWriteable);
+ if (err)
{
+ [self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED];
return;
}
-
- // get the cookie data
- void* cookieData = calloc(1, cookieSize);
- ignorableError = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, cookieData);
- if (ignorableError)
+
+ AudioFormatListItem *formatList = malloc(formatListSize);
+ err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_FormatList, &formatListSize, formatList);
+ if (err)
{
+ [self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED];
return;
}
- // set the cookie on the queue.
- ignorableError = AudioQueueSetProperty(audioQueue, kAudioQueueProperty_MagicCookie, cookieData, cookieSize);
- free(cookieData);
- if (ignorableError)
+ for (int i = 0; i * sizeof(AudioFormatListItem) < formatListSize; i += sizeof(AudioFormatListItem))
{
- return;
+ AudioStreamBasicDescription pasbd = formatList[i].mASBD;
+
+ if (pasbd.mFormatID == kAudioFormatMPEG4AAC_HE)
+ {
+ //
+ // We've found HE-AAC, remember this to tell the audio queue
+ // when we construct it.
+ //
+ asbd = pasbd;
+ break;
+ }
}
+ free(formatList);
}
- else if (inPropertyID == kAudioFileStreamProperty_DataOffset)
+ else
{
- SInt64 offset;
- UInt32 offsetSize = sizeof(offset);
- err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataOffset, &offsetSize, &offset);
- dataOffset = offset;
- if (err)
- {
- [self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED];
- return;
- }
+// NSLog(@"Property is %c%c%c%c",
+// ((char *)&inPropertyID)[3],
+// ((char *)&inPropertyID)[2],
+// ((char *)&inPropertyID)[1],
+// ((char *)&inPropertyID)[0]);
}
}
}
@@ -1370,27 +1665,26 @@ - (void)handleAudioPackets:(const void *)inInputData
if (bitRate == 0)
{
- UInt32 dataRateDataSize = sizeof(UInt32);
- err = AudioFileStreamGetProperty(
- audioFileStream,
- kAudioFileStreamProperty_BitRate,
- &dataRateDataSize,
- &bitRate);
- if (err)
- {
- //
- // m4a and a few other formats refuse to parse the bitrate so
- // we need to set an "unparseable" condition here. If you know
- // the bitrate (parsed it another way) you can set it on the
- // class if needed.
- //
- bitRate = ~0;
- }
+ //
+ // m4a and a few other formats refuse to parse the bitrate so
+ // we need to set an "unparseable" condition here. If you know
+ // the bitrate (parsed it another way) you can set it on the
+ // class if needed.
+ //
+ bitRate = ~0;
}
// we have successfully read the first packests from the audio stream, so
// clear the "discontinuous" flag
- discontinuous = false;
+ if (discontinuous)
+ {
+ discontinuous = false;
+ }
+
+ if (!audioQueue)
+ {
+ [self createQueue];
+ }
}
// the following code assumes we're streaming VBR data. for CBR data, the second branch is used.
@@ -1402,6 +1696,12 @@ - (void)handleAudioPackets:(const void *)inInputData
SInt64 packetSize = inPacketDescriptions[i].mDataByteSize;
size_t bufSpaceRemaining;
+ if (processedPacketsCount < BitRateEstimationMaxPackets)
+ {
+ processedPacketsSizeTotal += packetSize;
+ processedPacketsCount += 1;
+ }
+
@synchronized(self)
{
// If the audio was terminated before this point, then
@@ -1411,21 +1711,12 @@ - (void)handleAudioPackets:(const void *)inInputData
return;
}
- //
- // If we need to seek then unroll the stack back to the
- // appropriate point
- //
- if ([self shouldSeek])
- {
- return;
- }
-
- if (packetSize > kAQBufSize)
+ if (packetSize > packetBufferSize)
{
[self failWithErrorCode:AS_AUDIO_BUFFER_TOO_SMALL];
}
- bufSpaceRemaining = kAQBufSize - bytesFilled;
+ bufSpaceRemaining = packetBufferSize - bytesFilled;
}
// if the space remaining in the buffer is not enough for this packet, then enqueue the buffer.
@@ -1444,14 +1735,14 @@ - (void)handleAudioPackets:(const void *)inInputData
}
//
- // If we need to seek then unroll the stack back to the
- // appropriate point
+ // If there was some kind of issue with enqueueBuffer and we didn't
+ // make space for the new audio data then back out
//
- if ([self shouldSeek])
+ if (bytesFilled + packetSize >= packetBufferSize)
{
return;
}
-
+
// copy data to the audio queue buffer
AudioQueueBufferRef fillBuf = audioQueueBuffer[fillBufferIndex];
memcpy((char*)fillBuf->mAudioData + bytesFilled, (const char*)inInputData + packetOffset, packetSize);
@@ -1477,7 +1768,7 @@ - (void)handleAudioPackets:(const void *)inInputData
while (inNumberBytes)
{
// if the space remaining in the buffer is not enough for this packet, then enqueue the buffer.
- size_t bufSpaceRemaining = kAQBufSize - bytesFilled;
+ size_t bufSpaceRemaining = kAQDefaultBufSize - bytesFilled;
if (bufSpaceRemaining < inNumberBytes)
{
[self enqueueBuffer];
@@ -1492,18 +1783,7 @@ - (void)handleAudioPackets:(const void *)inInputData
return;
}
- //
- // If we need to seek then unroll the stack back to the
- // appropriate point
- //
- if ([self shouldSeek])
- {
- return;
- }
-
- // copy data to the audio queue buffer
- AudioQueueBufferRef fillBuf = audioQueueBuffer[fillBufferIndex];
- bufSpaceRemaining = kAQBufSize - bytesFilled;
+ bufSpaceRemaining = kAQDefaultBufSize - bytesFilled;
size_t copySize;
if (bufSpaceRemaining < inNumberBytes)
{
@@ -1513,6 +1793,18 @@ - (void)handleAudioPackets:(const void *)inInputData
{
copySize = inNumberBytes;
}
+
+ //
+ // If there was some kind of issue with enqueueBuffer and we didn't
+ // make space for the new audio data then back out
+ //
+ if (bytesFilled >= packetBufferSize)
+ {
+ return;
+ }
+
+ // copy data to the audio queue buffer
+ AudioQueueBufferRef fillBuf = audioQueueBuffer[fillBufferIndex];
memcpy((char*)fillBuf->mAudioData + bytesFilled, (const char*)(inInputData + offset), copySize);
View
2  Classes/MacStreamingPlayerController.h
@@ -21,6 +21,7 @@
IBOutlet NSTextField *downloadSourceField;
IBOutlet NSButton *button;
IBOutlet NSTextField *positionLabel;
+ IBOutlet NSSlider *progressSlider;
AudioStreamer *streamer;
NSTimer *progressUpdateTimer;
}
@@ -28,6 +29,7 @@
- (IBAction)buttonPressed:(id)sender;
- (void)spinButton;
- (void)updateProgress:(NSTimer *)aNotification;
+- (IBAction)sliderMoved:(NSSlider *)aSlider;
@end
View
30 Classes/MacStreamingPlayerController.m
@@ -18,6 +18,11 @@
@implementation MacStreamingPlayerController
+- (void)awakeFromNib
+{
+ [downloadSourceField setStringValue:@"http://192.168.1.2/~matt/inside.m4a"];
+}
+
//
// setButtonImage:
//
@@ -207,6 +212,23 @@ - (void)playbackStateChanged:(NSNotification *)aNotification
}
//
+// sliderMoved:
+//
+// Invoked when the user moves the slider
+//
+// Parameters:
+// aSlider - the slider (assumed to be the progress slider)
+//
+- (IBAction)sliderMoved:(NSSlider *)aSlider
+{
+ if (streamer.duration)
+ {
+ double newSeekTime = ([aSlider doubleValue] / 100.0) * streamer.duration;
+ [streamer seekToTime:newSeekTime];
+ }
+}
+
+//
// updateProgress:
//
// Invoked when the AudioStreamer
@@ -217,9 +239,13 @@ - (void)updateProgress:(NSTimer *)updatedTimer
if (streamer.bitRate != 0.0)
{
double progress = streamer.progress;
+ double duration = streamer.duration;
[positionLabel setStringValue:
- [NSString stringWithFormat:@"Time Played: %.1f seconds",
- progress]];
+ [NSString stringWithFormat:@"Time Played: %.1f/%.1f seconds",
+ progress,
+ duration]];
+ [progressSlider setEnabled:YES];
+ [progressSlider setDoubleValue:100 * progress / duration];
}
else
{
View
2  Classes/iPhoneStreamingPlayerViewController.h
@@ -22,6 +22,7 @@
IBOutlet UIButton *button;
IBOutlet UIView *volumeSlider;
IBOutlet UILabel *positionLabel;
+ IBOutlet UISlider *progressSlider;
AudioStreamer *streamer;
NSTimer *progressUpdateTimer;
}
@@ -29,6 +30,7 @@
- (IBAction)buttonPressed:(id)sender;
- (void)spinButton;
- (void)updateProgress:(NSTimer *)aNotification;
+- (IBAction)sliderMoved:(UISlider *)aSlider;
@end
View
29 Classes/iPhoneStreamingPlayerViewController.m
@@ -127,6 +127,8 @@ - (void)viewDidLoad
[volumeView sizeToFit];
[self setButtonImage:[UIImage imageNamed:@"playbutton.png"]];
+
+ [downloadSourceField setText:@"http://192.168.1.2/~matt/voodoo.m4a"];
}
//
@@ -204,6 +206,23 @@ - (IBAction)buttonPressed:(id)sender
}
//
+// sliderMoved:
+//
+// Invoked when the user moves the slider
+//
+// Parameters:
+// aSlider - the slider (assumed to be the progress slider)
+//
+- (IBAction)sliderMoved:(UISlider *)aSlider
+{
+ if (streamer.duration)
+ {
+ double newSeekTime = (aSlider.value / 100.0) * streamer.duration;
+ [streamer seekToTime:newSeekTime];
+ }
+}
+
+//
// playbackStateChanged:
//
// Invoked when the AudioStreamer
@@ -237,9 +256,13 @@ - (void)updateProgress:(NSTimer *)updatedTimer
if (streamer.bitRate != 0.0)
{
double progress = streamer.progress;
- positionLabel.text =
- [NSString stringWithFormat:@"Time Played: %.1f seconds",
- progress];
+ double duration = streamer.duration;
+ [positionLabel setText:
+ [NSString stringWithFormat:@"Time Played: %.1f/%.1f seconds",
+ progress,
+ duration]];
+ [progressSlider setEnabled:YES];
+ [progressSlider setValue:100 * progress / duration];
}
else
{
View
941 English.lproj/MainMenu.xib
@@ -1,23 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
-<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.03">
+<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
<data>
<int key="IBDocument.SystemTarget">1050</int>
- <string key="IBDocument.SystemVersion">9J61</string>
- <string key="IBDocument.InterfaceBuilderVersion">677</string>
- <string key="IBDocument.AppKitVersion">949.46</string>
- <string key="IBDocument.HIToolboxVersion">353.00</string>
+ <string key="IBDocument.SystemVersion">10C540</string>
+ <string key="IBDocument.InterfaceBuilderVersion">762</string>
+ <string key="IBDocument.AppKitVersion">1038.25</string>
+ <string key="IBDocument.HIToolboxVersion">458.00</string>
+ <object class="NSMutableDictionary" key="IBDocument.PluginVersions">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="NS.object.0">762</string>
+ </object>
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
<bool key="EncodedWithXMLCoder">YES</bool>
<integer value="372"/>
</object>
<object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
- <string>com.apple.InterfaceBuilderKit</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
</object>
<object class="NSMutableDictionary" key="IBDocument.Metadata">
<bool key="EncodedWithXMLCoder">YES</bool>
- <object class="NSArray" key="dict.sortedKeys">
+ <object class="NSArray" key="dict.sortedKeys" id="0">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<object class="NSMutableArray" key="dict.values">
@@ -79,7 +82,7 @@
</object>
<object class="NSMenuItem" id="609285721">
<reference key="NSMenu" ref="110575045"/>
- <string type="base64-UTF8" key="NSTitle">UHJlZmVyZW5jZXPigKY</string>
+ <string key="NSTitle">Preferences…</string>
<string key="NSKeyEquiv">,</string>
<int key="NSKeyEquivModMask">1048576</int>
<int key="NSMnemonicLoc">2147483647</int>
@@ -200,7 +203,7 @@
</object>
<object class="NSMenuItem" id="722745758">
<reference key="NSMenu" ref="720053764"/>
- <string type="base64-UTF8" key="NSTitle">T3BlbuKApg</string>
+ <string key="NSTitle">Open…</string>
<string key="NSKeyEquiv">o</string>
<int key="NSKeyEquivModMask">1048576</int>
<int key="NSMnemonicLoc">2147483647</int>
@@ -264,7 +267,7 @@
</object>
<object class="NSMenuItem" id="117038363">
<reference key="NSMenu" ref="720053764"/>
- <string type="base64-UTF8" key="NSTitle">U2F2ZSBBc+KApg</string>
+ <string key="NSTitle">Save As…</string>
<string key="NSKeyEquiv">S</string>
<int key="NSKeyEquivModMask">1179648</int>
<int key="NSMnemonicLoc">2147483647</int>
@@ -302,7 +305,7 @@
</object>
<object class="NSMenuItem" id="49223823">
<reference key="NSMenu" ref="720053764"/>
- <string type="base64-UTF8" key="NSTitle">UHJpbnTigKY</string>
+ <string key="NSTitle">Print…</string>
<string key="NSKeyEquiv">p</string>
<int key="NSKeyEquivModMask">1048576</int>
<int key="NSMnemonicLoc">2147483647</int>
@@ -425,7 +428,7 @@
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSMenuItem" id="447796847">
<reference key="NSMenu" ref="963351320"/>
- <string type="base64-UTF8" key="NSTitle">RmluZOKApg</string>
+ <string key="NSTitle">Find…</string>
<string key="NSKeyEquiv">f</string>
<int key="NSKeyEquivModMask">1048576</int>
<int key="NSMnemonicLoc">2147483647</int>
@@ -490,7 +493,7 @@
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSMenuItem" id="679648819">
<reference key="NSMenu" ref="769623530"/>
- <string type="base64-UTF8" key="NSTitle">U2hvdyBTcGVsbGluZ+KApg</string>
+ <string key="NSTitle">Show Spelling…</string>
<string key="NSKeyEquiv">:</string>
<int key="NSKeyEquivModMask">1048576</int>
<int key="NSMnemonicLoc">2147483647</int>
@@ -1017,7 +1020,7 @@
</object>
<object class="NSMenuItem" id="237841660">
<reference key="NSMenu" ref="466310130"/>
- <string type="base64-UTF8" key="NSTitle">Q3VzdG9taXplIFRvb2xiYXLigKY</string>
+ <string key="NSTitle">Customize Toolbar…</string>
<string key="NSKeyEquiv"/>
<int key="NSKeyEquivModMask">1048576</int>
<int key="NSMnemonicLoc">2147483647</int>
@@ -1113,12 +1116,12 @@
<object class="NSWindowTemplate" id="972006081">
<int key="NSWindowStyleMask">15</int>
<int key="NSWindowBacking">2</int>
- <string key="NSWindowRect">{{335, 646}, {470, 104}}</string>
+ <string key="NSWindowRect">{{335, 728}, {470, 144}}</string>
<int key="NSWTFlags">1946157056</int>
<string key="NSWindowTitle">Window</string>
<string key="NSWindowClass">NSWindow</string>
<nil key="NSViewClass"/>
- <string key="NSWindowContentMaxSize">{1024, 104}</string>
+ <string key="NSWindowContentMaxSize">{1.79769e+308, 1.79769e+308}</string>
<string key="NSWindowContentMinSize">{470, 104}</string>
<object class="NSView" key="NSWindowView" id="439893737">
<reference key="NSNextResponder"/>
@@ -1128,8 +1131,9 @@
<object class="NSButton" id="465513208">
<reference key="NSNextResponder" ref="439893737"/>
<int key="NSvFlags">268</int>
- <string key="NSFrame">{{20, 20}, {64, 64}}</string>
+ <string key="NSFrame">{{5, 53}, {80, 80}}</string>
<reference key="NSSuperview" ref="439893737"/>
+ <int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool>
<object class="NSButtonCell" key="NSCell" id="890323195">
<int key="NSCellFlags">67239424</int>
@@ -1137,12 +1141,12 @@
<string key="NSContents"/>
<object class="NSFont" key="NSSupport" id="571913998">
<string key="NSName">LucidaGrande</string>
- <double key="NSSize">1.300000e+01</double>
+ <double key="NSSize">13</double>
<int key="NSfFlags">1044</int>
</object>
<reference key="NSControlView" ref="465513208"/>
<int key="NSButtonFlags">-2039201537</int>
- <int key="NSButtonFlags2">70</int>
+ <int key="NSButtonFlags2">6</int>
<object class="NSCustomResource" key="NSNormalImage">
<string key="NSClassName">NSImage</string>
<string key="NSResourceName">playbutton</string>
@@ -1156,8 +1160,9 @@
<object class="NSTextField" id="997363787">
<reference key="NSNextResponder" ref="439893737"/>
<int key="NSvFlags">266</int>
- <string key="NSFrame">{{93, 37}, {357, 22}}</string>
+ <string key="NSFrame">{{93, 77}, {357, 22}}</string>
<reference key="NSSuperview" ref="439893737"/>
+ <int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="250554935">
<int key="NSCellFlags">-1804468671</int>
@@ -1189,8 +1194,9 @@
<object class="NSTextField" id="1038344492">
<reference key="NSNextResponder" ref="439893737"/>
<int key="NSvFlags">266</int>
- <string key="NSFrame">{{90, 66}, {363, 18}}</string>
+ <string key="NSFrame">{{93, 106}, {357, 18}}</string>
<reference key="NSSuperview" ref="439893737"/>
+ <int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="163232485">
<int key="NSCellFlags">68288064</int>
@@ -1198,7 +1204,7 @@
<string key="NSContents">Stream audio from the following URL:</string>
<object class="NSFont" key="NSSupport">
<string key="NSName">Helvetica</string>
- <double key="NSSize">1.400000e+01</double>
+ <double key="NSSize">14</double>
<int key="NSfFlags">16</int>
</object>
<reference key="NSControlView" ref="1038344492"/>
@@ -1218,8 +1224,9 @@
<object class="NSTextField" id="661414436">
<reference key="NSNextResponder" ref="439893737"/>
<int key="NSvFlags">266</int>
- <string key="NSFrame">{{90, 12}, {363, 17}}</string>
+ <string key="NSFrame">{{93, 52}, {357, 17}}</string>
<reference key="NSSuperview" ref="439893737"/>
+ <int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="40329830">
<int key="NSCellFlags">68288064</int>
@@ -1232,14 +1239,37 @@
<reference key="NSTextColor" ref="695969799"/>
</object>
</object>
+ <object class="NSSlider" id="856524559">
+ <reference key="NSNextResponder" ref="439893737"/>
+ <int key="NSvFlags">268</int>
+ <string key="NSFrame">{{91, 18}, {361, 26}}</string>
+ <reference key="NSSuperview" ref="439893737"/>
+ <int key="NSViewLayerContentsRedrawPolicy">2</int>
+ <bool key="NSEnabled">YES</bool>
+ <object class="NSSliderCell" key="NSCell" id="259515591">
+ <int key="NSCellFlags">-1543373312</int>
+ <int key="NSCellFlags2">0</int>
+ <string key="NSContents"/>
+ <reference key="NSControlView" ref="856524559"/>
+ <double key="NSMaxValue">100</double>
+ <double key="NSMinValue">0.0</double>
+ <double key="NSValue">50</double>
+ <double key="NSAltIncValue">0.0</double>
+ <int key="NSNumberOfTickMarks">9</int>
+ <int key="NSTickMarkPosition">1</int>
+ <bool key="NSAllowsTickMarkValuesOnly">NO</bool>
+ <bool key="NSVertical">NO</bool>
+ </object>
+ </object>
</object>
- <string key="NSFrameSize">{470, 104}</string>
+ <string key="NSFrameSize">{470, 144}</string>
<reference key="NSSuperview"/>
<bool key="NSViewIsLayerTreeHost">YES</bool>
+ <int key="NSViewLayerContentsRedrawPolicy">2</int>
</object>
<string key="NSScreenRect">{{0, 0}, {1440, 878}}</string>
<string key="NSMinSize">{470, 126}</string>
- <string key="NSMaxSize">{1024, 126}</string>
+ <string key="NSMaxSize">{1.79769e+308, 1.79769e+308}</string>
</object>
<object class="NSCustomObject" id="755631768">
<string key="NSClassName">NSFontManager</string>
@@ -1811,34 +1841,56 @@
</object>
<int key="connectionID">466</int>
</object>
+ <object class="IBConnectionRecord">
+ <object class="IBOutletConnection" key="connection">
+ <string key="label">initialFirstResponder</string>
+ <reference key="source" ref="972006081"/>
+ <reference key="destination" ref="997363787"/>
+ </object>
+ <int key="connectionID">467</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBOutletConnection" key="connection">
+ <string key="label">progressSlider</string>
+ <reference key="source" ref="149200707"/>
+ <reference key="destination" ref="856524559"/>
+ </object>
+ <int key="connectionID">470</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">sliderMoved:</string>
+ <reference key="source" ref="149200707"/>
+ <reference key="destination" ref="856524559"/>
+ </object>
+ <int key="connectionID">471</int>
+ </object>
</object>
<object class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBObjectRecord">
<int key="objectID">0</int>
- <object class="NSArray" key="object" id="1049">
- <bool key="EncodedWithXMLCoder">YES</bool>
- </object>
+ <reference key="object" ref="0"/>
<reference key="children" ref="1048"/>
<nil key="parent"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">-2</int>
<reference key="object" ref="1021"/>
- <reference key="parent" ref="1049"/>
- <string type="base64-UTF8" key="objectName">RmlsZSdzIE93bmVyA</string>
+ <reference key="parent" ref="0"/>
+ <string key="objectName">File's Owner</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-1</int>
<reference key="object" ref="1014"/>
- <reference key="parent" ref="1049"/>
+ <reference key="parent" ref="0"/>
<string key="objectName">First Responder</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-3</int>
<reference key="object" ref="1050"/>
- <reference key="parent" ref="1049"/>
+ <reference key="parent" ref="0"/>
<string key="objectName">Application</string>
</object>
<object class="IBObjectRecord">
@@ -1854,7 +1906,7 @@
<reference ref="586577488"/>
<reference ref="302598603"/>
</object>
- <reference key="parent" ref="1049"/>
+ <reference key="parent" ref="0"/>
<string key="objectName">MainMenu</string>
</object>
<object class="IBObjectRecord">
@@ -2390,17 +2442,18 @@
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="439893737"/>
</object>
- <reference key="parent" ref="1049"/>
+ <reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">372</int>
<reference key="object" ref="439893737"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
- <reference ref="1038344492"/>
<reference ref="997363787"/>
<reference ref="465513208"/>
<reference ref="661414436"/>
+ <reference ref="1038344492"/>
+ <reference ref="856524559"/>
</object>
<reference key="parent" ref="972006081"/>
</object>
@@ -2712,7 +2765,7 @@
<object class="IBObjectRecord">
<int key="objectID">420</int>
<reference key="object" ref="755631768"/>
- <reference key="parent" ref="1049"/>
+ <reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">452</int>
@@ -2759,7 +2812,7 @@
<object class="IBObjectRecord">
<int key="objectID">458</int>
<reference key="object" ref="149200707"/>
- <reference key="parent" ref="1049"/>
+ <reference key="parent" ref="0"/>
<string key="objectName">StreamController</string>
</object>
<object class="IBObjectRecord">
@@ -2776,14 +2829,26 @@
<reference key="object" ref="40329830"/>
<reference key="parent" ref="661414436"/>
</object>
+ <object class="IBObjectRecord">
+ <int key="objectID">468</int>
+ <reference key="object" ref="856524559"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="259515591"/>
+ </object>
+ <reference key="parent" ref="439893737"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">469</int>
+ <reference key="object" ref="259515591"/>
+ <reference key="parent" ref="856524559"/>
+ </object>
</object>
</object>
<object class="NSMutableDictionary" key="flattenedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
- <object class="NSMutableArray" key="dict.sortedKeys">
+ <object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
- <string>-1.IBPluginDependency</string>
- <string>-2.IBPluginDependency</string>
<string>-3.IBPluginDependency</string>
<string>103.IBPluginDependency</string>
<string>103.ImportedFromIB2</string>
@@ -2915,6 +2980,7 @@
<string>354.IBPluginDependency</string>
<string>354.ImportedFromIB2</string>
<string>371.IBEditorWindowLastContentRect</string>
+ <string>371.IBPluginDependency</string>
<string>371.IBWindowTemplateEditedContentRect</string>
<string>371.NSWindowTemplate.visibleAtLaunch</string>
<string>371.editorWindowContentRectSynchronizationRect</string>
@@ -2970,16 +3036,16 @@
<string>417.IBPluginDependency</string>
<string>418.IBPluginDependency</string>
<string>419.IBPluginDependency</string>
- <string>420.IBPluginDependency</string>
<string>452.IBPluginDependency</string>
<string>453.IBPluginDependency</string>
<string>454.IBPluginDependency</string>
<string>455.IBPluginDependency</string>
<string>456.IBPluginDependency</string>
<string>457.IBPluginDependency</string>
- <string>458.IBPluginDependency</string>
<string>462.IBPluginDependency</string>
<string>463.IBPluginDependency</string>
+ <string>468.IBPluginDependency</string>
+ <string>469.IBPluginDependency</string>
<string>5.IBPluginDependency</string>
<string>5.ImportedFromIB2</string>
<string>56.IBPluginDependency</string>
@@ -3019,117 +3085,115 @@
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string>com.apple.InterfaceBuilderKit</string>
- <string>com.apple.InterfaceBuilderKit</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <integer value="1" id="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>{{596, 852}, {216, 23}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>{{522, 812}, {146, 23}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>{{436, 809}, {64, 6}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>{{608, 612}, {275, 83}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>{{187, 434}, {243, 243}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>{{608, 612}, {167, 43}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>{{608, 612}, {241, 103}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>{{525, 802}, {197, 73}}</string>
<string>{{207, 285}, {478, 20}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>{74, 862}</string>
<string>{{6, 978}, {478, 20}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
@@ -3138,24 +3202,25 @@
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>{{608, 612}, {215, 63}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
+ <string>{{76, 490}, {470, 144}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
- <string>{{76, 530}, {470, 104}}</string>
- <string>{{76, 530}, {470, 104}}</string>
- <reference ref="9"/>
+ <string>{{76, 490}, {470, 144}}</string>
+ <integer value="1"/>
<string>{{33, 99}, {480, 360}}</string>
- <boolean value="YES" id="5"/>
- <reference ref="5"/>
+ <boolean value="NO"/>
+ <boolean value="YES"/>
<string>{1024, 104}</string>
<string>{470, 104}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
@@ -3217,47 +3282,45 @@
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>{{219, 102}, {245, 183}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>{{23, 794}, {245, 183}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>{{145, 474}, {199, 203}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <reference ref="9"/>
+ <integer value="1"/>
</object>
</object>
<object class="NSMutableDictionary" key="unlocalizedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
- <object class="NSArray" key="dict.sortedKeys">
- <bool key="EncodedWithXMLCoder">YES</bool>
- </object>
+ <reference key="dict.sortedKeys" ref="0"/>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
@@ -3265,15 +3328,13 @@
<nil key="activeLocalization"/>
<object class="NSMutableDictionary" key="localizations">
<bool key="EncodedWithXMLCoder">YES</bool>
- <object class="NSArray" key="dict.sortedKeys">
- <bool key="EncodedWithXMLCoder">YES</bool>
- </object>
+ <reference key="dict.sortedKeys" ref="0"/>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
</object>
<nil key="sourceID"/>
- <int key="maxID">466</int>
+ <int key="maxID">471</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<object class="NSMutableArray" key="referencedPartialClassDescriptions">
@@ -3282,22 +3343,33 @@
<string key="className">MacStreamingPlayerController</string>
<string key="superclassName">NSObject</string>
<object class="NSMutableDictionary" key="actions">
- <string key="NS.key.0">buttonPressed:</string>
- <string key="NS.object.0">id</string>
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>buttonPressed:</string>
+ <string>sliderMoved:</string>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>id</string>
+ <string>NSSlider</string>
+ </object>
</object>
<object class="NSMutableDictionary" key="outlets">
<bool key="EncodedWithXMLCoder">YES</bool>
- <object class="NSMutableArray" key="dict.sortedKeys">
+ <object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>button</string>
<string>downloadSourceField</string>
<string>positionLabel</string>
+ <string>progressSlider</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>NSButton</string>
<string>NSTextField</string>
<string>NSTextField</string>
+ <string>NSSlider</string>
</object>
</object>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
@@ -3306,9 +3378,616 @@
</object>
</object>
</object>
+ <object class="NSMutableArray" key="referencedPartialClassDescriptionsV3.2+">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSActionCell</string>
+ <string key="superclassName">NSCell</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSActionCell.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSApplication</string>
+ <string key="superclassName">NSResponder</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier" id="963752350">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSApplication.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSApplication</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier" id="198198207">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSApplicationScripting.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSApplication</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier" id="390396171">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSColorPanel.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSApplication</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSHelpManager.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSApplication</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSPageLayout.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSApplication</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSUserInterfaceItemSearching.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSBrowser</string>
+ <string key="superclassName">NSControl</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSBrowser.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSButton</string>
+ <string key="superclassName">NSControl</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSButton.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSButtonCell</string>
+ <string key="superclassName">NSActionCell</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSButtonCell.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSCell</string>
+ <string key="superclassName">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSCell.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSControl</string>
+ <string key="superclassName">NSView</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier" id="266226690">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSControl.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSDocument</string>
+ <string key="superclassName">NSObject</string>
+ <object class="NSMutableDictionary" key="actions">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>printDocument:</string>
+ <string>revertDocumentToSaved:</string>
+ <string>runPageLayout:</string>
+ <string>saveDocument:</string>
+ <string>saveDocumentAs:</string>
+ <string>saveDocumentTo:</string>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>id</string>
+ <string>id</string>
+ <string>id</string>
+ <string>id</string>
+ <string>id</string>
+ <string>id</string>
+ </object>
+ </object>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSDocument.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSDocument</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSDocumentScripting.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSDocumentController</string>
+ <string key="superclassName">NSObject</string>
+ <object class="NSMutableDictionary" key="actions">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>clearRecentDocuments:</string>
+ <string>newDocument:</string>
+ <string>openDocument:</string>
+ <string>saveAllDocuments:</string>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>id</string>
+ <string>id</string>
+ <string>id</string>
+ <string>id</string>
+ </object>
+ </object>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSDocumentController.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSFontManager</string>
+ <string key="superclassName">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier" id="85258297">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSFontManager.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSFormatter</string>
+ <string key="superclassName">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSFormatter.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSMatrix</string>
+ <string key="superclassName">NSControl</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSMatrix.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSMenu</string>
+ <string key="superclassName">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier" id="589357531">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSMenu.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSMenuItem</string>
+ <string key="superclassName">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier" id="287996405">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSMenuItem.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSMovieView</string>
+ <string key="superclassName">NSView</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSMovieView.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSAccessibility.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <reference key="sourceIdentifier" ref="963752350"/>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <reference key="sourceIdentifier" ref="198198207"/>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <reference key="sourceIdentifier" ref="390396171"/>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <reference key="sourceIdentifier" ref="266226690"/>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSDictionaryController.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSDragging.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <reference key="sourceIdentifier" ref="85258297"/>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSFontPanel.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSKeyValueBinding.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <reference key="sourceIdentifier" ref="589357531"/>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSNibLoading.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>