- Display playlist total duration in units up to weeks and down to just seconds, and only pluralize units as necessary - Major change: Implemented a SQLite disk backed playlist, track data, and queue storage system, which will be synchronized from the player in real time, and will hopefully survive system or app crashes. Existing plist playlist will be imported on first run, and removed on shutdown.
389 lines
8.7 KiB
Objective-C
389 lines
8.7 KiB
Objective-C
//
|
|
// PlaylistEntry.m
|
|
// Cog
|
|
//
|
|
// Created by Vincent Spader on 3/14/05.
|
|
// Copyright 2005 Vincent Spader All rights reserved.
|
|
//
|
|
|
|
#import "PlaylistEntry.h"
|
|
#import "SecondsFormatter.h"
|
|
|
|
@implementation PlaylistEntry
|
|
|
|
@synthesize index;
|
|
@synthesize shuffleIndex;
|
|
@synthesize dbIndex;
|
|
|
|
@synthesize current;
|
|
@synthesize removed;
|
|
|
|
@synthesize stopAfter;
|
|
|
|
@synthesize queued;
|
|
@synthesize queuePosition;
|
|
|
|
@synthesize error;
|
|
@synthesize errorMessage;
|
|
|
|
@synthesize URL;
|
|
|
|
@synthesize artist;
|
|
@synthesize albumartist;
|
|
@synthesize album;
|
|
@synthesize genre;
|
|
@synthesize year;
|
|
@synthesize track;
|
|
|
|
@synthesize totalFrames;
|
|
@synthesize bitrate;
|
|
@synthesize channels;
|
|
@synthesize bitsPerSample;
|
|
@synthesize floatingPoint;
|
|
@synthesize Unsigned;
|
|
@synthesize sampleRate;
|
|
|
|
@synthesize codec;
|
|
|
|
@synthesize replayGainAlbumGain;
|
|
@synthesize replayGainAlbumPeak;
|
|
@synthesize replayGainTrackGain;
|
|
@synthesize replayGainTrackPeak;
|
|
@synthesize volume;
|
|
|
|
@synthesize currentPosition;
|
|
|
|
@synthesize endian;
|
|
|
|
@synthesize seekable;
|
|
|
|
@synthesize metadataLoaded;
|
|
|
|
// The following read-only keys depend on the values of other properties
|
|
|
|
+ (NSSet *)keyPathsForValuesAffectingDisplay
|
|
{
|
|
return [NSSet setWithObjects:@"artist",@"title",nil];
|
|
}
|
|
|
|
+ (NSSet *)keyPathsForValuesAffectingLength
|
|
{
|
|
return [NSSet setWithObject:@"totalFrames"];
|
|
}
|
|
|
|
+ (NSSet *)keyPathsForValuesAffectingPath
|
|
{
|
|
return [NSSet setWithObject:@"URL"];
|
|
}
|
|
|
|
+ (NSSet *)keyPathsForValuesAffectingFilename
|
|
{
|
|
return [NSSet setWithObject:@"URL"];
|
|
}
|
|
|
|
+ (NSSet *)keyPathsForValuesAffectingStatus
|
|
{
|
|
return [NSSet setWithObjects:@"current",@"queued", @"error", @"stopAfter", nil];
|
|
}
|
|
|
|
+ (NSSet *)keyPathsForValuesAffectingStatusMessage
|
|
{
|
|
return [NSSet setWithObjects:@"current", @"queued", @"queuePosition", @"error", @"errorMessage", @"stopAfter", nil];
|
|
}
|
|
|
|
+ (NSSet *)keyPathsForValuesAffectingSpam
|
|
{
|
|
return [NSSet setWithObjects:@"albumartist", @"artist", @"title", @"album", @"track", @"totalFrames", @"currentPosition", @"bitrate", nil];
|
|
}
|
|
|
|
+ (NSSet *)keyPathsForValuesAffectingPositionText
|
|
{
|
|
return [NSSet setWithObject:@"currentPosition"];
|
|
}
|
|
|
|
+ (NSSet *)keyPathsForValuesAffectingLengthText
|
|
{
|
|
return [NSSet setWithObject:@"length"];
|
|
}
|
|
|
|
+ (NSSet *)keyPathsForValuesAffectingAlbumArt
|
|
{
|
|
return [NSSet setWithObject:@"albumArtInternal"];
|
|
}
|
|
|
|
- (NSString *)description
|
|
{
|
|
return [NSString stringWithFormat:@"PlaylistEntry %li:(%@)", self.index, self.URL];
|
|
}
|
|
|
|
- (id)init
|
|
{
|
|
if (self = [super init]) {
|
|
self.replayGainAlbumGain = 0;
|
|
self.replayGainAlbumPeak = 0;
|
|
self.replayGainTrackGain = 0;
|
|
self.replayGainTrackPeak = 0;
|
|
self.volume = 1;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
self.errorMessage = nil;
|
|
|
|
self.URL = nil;
|
|
|
|
self.artist = nil;
|
|
self.albumartist = nil;
|
|
self.album = nil;
|
|
self.title = nil;
|
|
self.genre = nil;
|
|
self.year = nil;
|
|
self.track = nil;
|
|
self.albumArtInternal = nil;
|
|
|
|
self.endian = nil;
|
|
self.codec = nil;
|
|
}
|
|
|
|
// Get the URL if the title is blank
|
|
@synthesize title;
|
|
- (NSString *)title
|
|
{
|
|
if((title == nil || [title isEqualToString:@""]) && self.URL)
|
|
{
|
|
return [[self.URL path] lastPathComponent];
|
|
}
|
|
return title;
|
|
}
|
|
|
|
@dynamic display;
|
|
- (NSString *)display
|
|
{
|
|
if ((self.artist == NULL) || ([self.artist isEqualToString:@""]))
|
|
return self.title;
|
|
else {
|
|
return [NSString stringWithFormat:@"%@ - %@", self.artist, self.title];
|
|
}
|
|
}
|
|
|
|
@dynamic spam;
|
|
- (NSString *)spam
|
|
{
|
|
BOOL hasBitrate = (self.bitrate != 0);
|
|
BOOL hasArtist = (self.artist != nil) && (![self.artist isEqualToString:@""]);
|
|
BOOL hasAlbumArtist = (self.albumartist != nil) && (![self.albumartist isEqualToString:@""]);
|
|
BOOL hasAlbum = (self.album != nil) && (![self.album isEqualToString:@""]);
|
|
BOOL hasTrack = (self.track != 0);
|
|
BOOL hasLength = (self.totalFrames != 0);
|
|
BOOL hasCurrentPosition = (self.currentPosition != 0) && (self.current);
|
|
BOOL hasExtension = NO;
|
|
BOOL hasTitle = (title != nil) && (![title isEqualToString:@""]);
|
|
BOOL hasCodec = (self.codec != nil) && (![self.codec isEqualToString:@""]);
|
|
|
|
NSMutableString * filename = [NSMutableString stringWithString:[self filename]];
|
|
NSRange dotPosition = [filename rangeOfString:@"." options:NSBackwardsSearch];
|
|
NSString * extension = nil;
|
|
|
|
if (dotPosition.length > 0) {
|
|
dotPosition.location++;
|
|
dotPosition.length = [filename length] - dotPosition.location;
|
|
extension = [filename substringWithRange:dotPosition];
|
|
dotPosition.location--;
|
|
dotPosition.length++;
|
|
[filename deleteCharactersInRange:dotPosition];
|
|
hasExtension = YES;
|
|
}
|
|
|
|
NSMutableArray * elements = [NSMutableArray array];
|
|
|
|
if (hasExtension) {
|
|
[elements addObject:@"["];
|
|
if (hasCodec) {
|
|
[elements addObject:self.codec];
|
|
}
|
|
else {
|
|
[elements addObject:[extension uppercaseString]];
|
|
}
|
|
if (hasBitrate) {
|
|
[elements addObject:@"@"];
|
|
[elements addObject:[NSString stringWithFormat:@"%u", self.bitrate]];
|
|
[elements addObject:@"kbps"];
|
|
}
|
|
[elements addObject:@"] "];
|
|
}
|
|
|
|
if (hasArtist) {
|
|
if (hasAlbumArtist) {
|
|
[elements addObject:self.albumartist];
|
|
}
|
|
else {
|
|
[elements addObject:self.artist];
|
|
}
|
|
[elements addObject:@" - "];
|
|
}
|
|
|
|
if (hasAlbum) {
|
|
[elements addObject:@"["];
|
|
[elements addObject:self.album];
|
|
if (hasTrack) {
|
|
[elements addObject:@" #"];
|
|
[elements addObject:[NSString stringWithFormat:@"%@", self.track]];
|
|
}
|
|
[elements addObject:@"] "];
|
|
}
|
|
|
|
if (hasTitle) {
|
|
[elements addObject:title];
|
|
}
|
|
else {
|
|
[elements addObject:filename];
|
|
}
|
|
|
|
if (hasAlbumArtist && hasArtist) {
|
|
[elements addObject:@" // "];
|
|
[elements addObject:self.artist];
|
|
}
|
|
|
|
if (hasCurrentPosition || hasLength) {
|
|
SecondsFormatter *secondsFormatter = [[SecondsFormatter alloc] init];
|
|
[elements addObject:@" ("];
|
|
if (hasCurrentPosition) {
|
|
[elements addObject:[secondsFormatter stringForObjectValue:[NSNumber numberWithFloat:currentPosition]]];
|
|
}
|
|
if (hasLength) {
|
|
if (hasCurrentPosition) {
|
|
[elements addObject:@" / "];
|
|
}
|
|
[elements addObject:[secondsFormatter stringForObjectValue:[self length]]];
|
|
}
|
|
[elements addObject:@")"];
|
|
}
|
|
|
|
return [elements componentsJoinedByString:@""];
|
|
}
|
|
|
|
@dynamic positionText;
|
|
- (NSString *)positionText
|
|
{
|
|
SecondsFormatter *secondsFormatter = [[SecondsFormatter alloc] init];
|
|
NSString *time = [secondsFormatter stringForObjectValue:[NSNumber numberWithFloat:currentPosition]];
|
|
return time;
|
|
}
|
|
|
|
@dynamic lengthText;
|
|
- (NSString *)lengthText
|
|
{
|
|
SecondsFormatter *secondsFormatter = [[SecondsFormatter alloc] init];
|
|
NSString *time = [secondsFormatter stringForObjectValue:[self length]];
|
|
return time;
|
|
}
|
|
|
|
@synthesize albumArtInternal;
|
|
|
|
@dynamic albumArt;
|
|
- (NSImage *)albumArt
|
|
{
|
|
if (!albumArtInternal) return nil;
|
|
|
|
NSString *imageCacheTag = [NSString stringWithFormat:@"%@-%@-%@-%@", album, artist, genre, year];
|
|
NSImage *image = [NSImage imageNamed:imageCacheTag];
|
|
|
|
if (image == nil)
|
|
{
|
|
image = [[NSImage alloc] initWithData:albumArtInternal];
|
|
[image setName:imageCacheTag];
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
- (void)setAlbumArt:(id)data
|
|
{
|
|
if ([data isKindOfClass:[NSData class]])
|
|
{
|
|
[self setAlbumArtInternal:data];
|
|
}
|
|
}
|
|
|
|
@dynamic length;
|
|
- (NSNumber *)length
|
|
{
|
|
return [NSNumber numberWithDouble:(self.metadataLoaded)?((double)self.totalFrames / self.sampleRate):0.0];
|
|
}
|
|
|
|
@dynamic path;
|
|
- (NSString *)path
|
|
{
|
|
return [[self.URL path] stringByAbbreviatingWithTildeInPath];
|
|
}
|
|
|
|
@dynamic filename;
|
|
- (NSString *)filename
|
|
{
|
|
return [[self.URL path] lastPathComponent];
|
|
}
|
|
|
|
@dynamic status;
|
|
- (NSString *)status
|
|
{
|
|
if (self.stopAfter)
|
|
{
|
|
return @"stopAfter";
|
|
}
|
|
else if (self.current)
|
|
{
|
|
return @"playing";
|
|
}
|
|
else if (self.queued)
|
|
{
|
|
return @"queued";
|
|
}
|
|
else if (self.error)
|
|
{
|
|
return @"error";
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
@dynamic statusMessage;
|
|
- (NSString *)statusMessage
|
|
{
|
|
if (self.stopAfter)
|
|
{
|
|
return @"Stopping once finished...";
|
|
}
|
|
else if (self.current)
|
|
{
|
|
return @"Playing...";
|
|
}
|
|
else if (self.queued)
|
|
{
|
|
return [NSString stringWithFormat:@"Queued: %li", self.queuePosition + 1];
|
|
}
|
|
else if (self.error)
|
|
{
|
|
return errorMessage;
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (void)setMetadata:(NSDictionary *)metadata
|
|
{
|
|
if (metadata == nil)
|
|
{
|
|
self.error = YES;
|
|
self.errorMessage = @"Unable to retrieve metadata.";
|
|
}
|
|
else
|
|
{
|
|
[self setValuesForKeysWithDictionary:metadata];
|
|
}
|
|
|
|
metadataLoaded = YES;
|
|
}
|
|
|
|
@end
|