This seals up a major memory leak of the playback state whenever a chain is released on stop or on manual track change. CogAudioMulti was retaining the input node due to its listeners, and InputNode was not releasing the listeners when asked to stop running. This is fixed now. Fixes #221 Signed-off-by: Christopher Snowhill <kode54@gmail.com>
207 lines
5.7 KiB
Objective-C
207 lines
5.7 KiB
Objective-C
//
|
|
// CogPluginMulti.m
|
|
// CogAudio
|
|
//
|
|
// Created by Christopher Snowhill on 10/21/13.
|
|
//
|
|
//
|
|
|
|
#import "CogPluginMulti.h"
|
|
|
|
NSArray * sortClassesByPriority(NSArray * theClasses)
|
|
{
|
|
NSMutableArray *sortedClasses = [NSMutableArray arrayWithArray:theClasses];
|
|
[sortedClasses sortUsingComparator:
|
|
^NSComparisonResult(id obj1, id obj2)
|
|
{
|
|
NSString *classString1 = (NSString *)obj1;
|
|
NSString *classString2 = (NSString *)obj2;
|
|
|
|
Class class1 = NSClassFromString(classString1);
|
|
Class class2 = NSClassFromString(classString2);
|
|
|
|
float priority1 = [class1 priority];
|
|
float priority2 = [class2 priority];
|
|
|
|
if (priority1 == priority2) return NSOrderedSame;
|
|
else if (priority1 > priority2) return NSOrderedAscending;
|
|
else return NSOrderedDescending;
|
|
}];
|
|
return sortedClasses;
|
|
}
|
|
|
|
@implementation CogDecoderMulti
|
|
|
|
+ (NSArray *)mimeTypes
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
+ (NSArray *)fileTypes
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
+ (float)priority
|
|
{
|
|
return -1.0;
|
|
}
|
|
|
|
+ (NSArray *)fileTypeAssociations {
|
|
return nil;
|
|
}
|
|
|
|
- (id)initWithDecoders:(NSArray *)decoders
|
|
{
|
|
self = [super init];
|
|
if ( self )
|
|
{
|
|
theDecoders = sortClassesByPriority(decoders);
|
|
theDecoder = nil;
|
|
cachedObservers = [[NSMutableArray alloc] init];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (NSDictionary *)properties
|
|
{
|
|
if ( theDecoder != nil ) return [theDecoder properties];
|
|
return nil;
|
|
}
|
|
|
|
- (int)readAudio:(void *)buffer frames:(UInt32)frames
|
|
{
|
|
if ( theDecoder != nil ) return [theDecoder readAudio:buffer frames:frames];
|
|
return 0;
|
|
}
|
|
|
|
- (BOOL)open:(id<CogSource>)source
|
|
{
|
|
for (NSString *classString in theDecoders)
|
|
{
|
|
Class decoder = NSClassFromString(classString);
|
|
theDecoder = [[decoder alloc] init];
|
|
for (NSDictionary *obsItem in cachedObservers) {
|
|
[theDecoder addObserver:[obsItem objectForKey:@"observer"]
|
|
forKeyPath:[obsItem objectForKey:@"keyPath"]
|
|
options:[[obsItem objectForKey:@"options"] unsignedIntegerValue]
|
|
context:(__bridge void *)([obsItem objectForKey:@"context"])];
|
|
}
|
|
if ([theDecoder open:source])
|
|
return YES;
|
|
for (NSDictionary *obsItem in cachedObservers) {
|
|
[theDecoder removeObserver:[obsItem objectForKey:@"observer"] forKeyPath:[obsItem objectForKey:@"keyPath"]];
|
|
}
|
|
if ([source seekable])
|
|
[source seek:0 whence:SEEK_SET];
|
|
}
|
|
theDecoder = nil;
|
|
return NO;
|
|
}
|
|
|
|
- (long)seek:(long)frame
|
|
{
|
|
if ( theDecoder != nil ) return [theDecoder seek:frame];
|
|
return -1;
|
|
}
|
|
|
|
- (void)close
|
|
{
|
|
if ( theDecoder != nil ) {
|
|
for (NSDictionary *obsItem in cachedObservers) {
|
|
[theDecoder removeObserver:[obsItem objectForKey:@"observer"] forKeyPath:[obsItem objectForKey:@"keyPath"]];
|
|
}
|
|
[cachedObservers removeAllObjects];
|
|
[theDecoder close];
|
|
theDecoder = nil;
|
|
}
|
|
}
|
|
|
|
- (BOOL)setTrack:(NSURL *)track
|
|
{
|
|
if ( theDecoder != nil && [theDecoder respondsToSelector: @selector(setTrack:)] ) return [theDecoder setTrack:track];
|
|
return NO;
|
|
}
|
|
|
|
/* By the current design, the core adds its observers to decoders before they are opened */
|
|
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
|
|
{
|
|
if(context != nil) {
|
|
[cachedObservers addObject:[NSDictionary dictionaryWithObjectsAndKeys:observer, @"observer", keyPath, @"keyPath", @(options), @"options", context, @"context", nil]];
|
|
} else {
|
|
[cachedObservers addObject:[NSDictionary dictionaryWithObjectsAndKeys:observer, @"observer", keyPath, @"keyPath", @(options), @"options", nil]];
|
|
}
|
|
if (theDecoder) {
|
|
[theDecoder addObserver:observer forKeyPath:keyPath options:options context:context];
|
|
}
|
|
}
|
|
|
|
/* And this is currently called after the decoder is closed */
|
|
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
|
|
{
|
|
for (NSDictionary *obsItem in cachedObservers) {
|
|
if ([obsItem objectForKey:@"observer"] == observer && [keyPath isEqualToString:[obsItem objectForKey:@"keyPath"]]) {
|
|
[cachedObservers removeObject:obsItem];
|
|
break;
|
|
}
|
|
}
|
|
if (theDecoder) {
|
|
[theDecoder removeObserver:observer forKeyPath:keyPath];
|
|
}
|
|
}
|
|
|
|
|
|
@end
|
|
|
|
@implementation CogContainerMulti
|
|
|
|
+ (NSArray *)urlsForContainerURL:(NSURL *)url containers:(NSArray *)containers
|
|
{
|
|
NSArray * sortedContainers = sortClassesByPriority(containers);
|
|
for (NSString *classString in sortedContainers)
|
|
{
|
|
Class container = NSClassFromString(classString);
|
|
NSArray * urls = [container urlsForContainerURL:url];
|
|
if ([urls count])
|
|
return urls;
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation CogMetadataReaderMulti
|
|
|
|
+ (NSDictionary *)metadataForURL:(NSURL *)url readers:(NSArray *)readers
|
|
{
|
|
NSArray * sortedReaders = sortClassesByPriority(readers);
|
|
for (NSString *classString in sortedReaders)
|
|
{
|
|
Class reader = NSClassFromString(classString);
|
|
NSDictionary * data = [reader metadataForURL:url];
|
|
if ([data count])
|
|
return data;
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation CogPropertiesReaderMulti
|
|
|
|
+ (NSDictionary *)propertiesForSource:(id<CogSource>)source readers:(NSArray *)readers
|
|
{
|
|
NSArray * sortedReaders = sortClassesByPriority(readers);
|
|
for (NSString *classString in sortedReaders)
|
|
{
|
|
Class reader = NSClassFromString(classString);
|
|
NSDictionary * data = [reader propertiesForSource:source];
|
|
if ([data count])
|
|
return data;
|
|
if ([source seekable])
|
|
[source seek:0 whence:SEEK_SET];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
@end
|