diff --git a/Application/PlaybackEventController.h b/Application/PlaybackEventController.h index 6b9e58683..a177174fa 100644 --- a/Application/PlaybackEventController.h +++ b/Application/PlaybackEventController.h @@ -13,20 +13,13 @@ #import "PlaylistEntry.h" @class AudioScrobbler; -@interface PlaybackEventController : NSObject { - NSOperationQueue *queue; - - PlaylistEntry *entry; - - AudioScrobbler *scrobbler; - + +@interface PlaybackEventController + : NSObject { IBOutlet PlaybackController *playbackController; - + IBOutlet NSWindow *mainWindow; IBOutlet NSWindow *miniWindow; - - Boolean didGainUN; - } @end diff --git a/Application/PlaybackEventController.m b/Application/PlaybackEventController.m index e0c56a387..067076bba 100644 --- a/Application/PlaybackEventController.m +++ b/Application/PlaybackEventController.m @@ -4,9 +4,6 @@ // // Created by Vincent Spader on 3/5/09. // Copyright 2009 __MyCompanyName__. All rights reserved. -// -// New Notification Center code shamelessly based off this: -// https://github.com/kbhomes/radiant-player-mac/tree/master/radiant-player-mac/Notifications #import "PlaybackEventController.h" @@ -23,141 +20,158 @@ NSString *TrackLength = @"Total Time"; NSString *TrackPath = @"Location"; NSString *TrackState = @"Player State"; -typedef enum -{ - TrackPlaying, - TrackPaused, - TrackStopped -} TrackStatus; +typedef NS_ENUM(NSInteger, TrackStatus) { TrackPlaying, TrackPaused, TrackStopped }; -@implementation PlaybackEventController +@implementation PlaybackEventController { + AudioScrobbler *scrobbler; -- (void)initDefaults -{ - NSDictionary *defaultsDictionary = [NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithBool:YES], @"enableAudioScrobbler", - [NSNumber numberWithBool:NO], @"automaticallyLaunchLastFM", - [NSNumber numberWithBool:YES], @"notifications.enable", - [NSNumber numberWithBool:YES], @"notifications.itunes-style", - [NSNumber numberWithBool:YES], @"notifications.show-album-art", - nil]; - - [[NSUserDefaults standardUserDefaults] registerDefaults:defaultsDictionary]; + NSOperationQueue *queue; + + PlaylistEntry *entry; + + Boolean didGainUN API_AVAILABLE(macosx(10.14)); } -- (id)init -{ - self = [super init]; - if (self) - { - [self initDefaults]; - - didGainUN = NO; - - if (@available(macOS 10.14,*)) { - UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; - [center requestAuthorizationWithOptions:UNAuthorizationOptionAlert - completionHandler:^(BOOL granted, NSError * _Nullable error) { - self->didGainUN = granted; +- (void)initDefaults { + NSDictionary *defaultsDictionary = @{ + @"enableAudioScrobbler" : @YES, + @"automaticallyLaunchLastFM" : @NO, + @"notifications.enable" : @YES, + @"notifications.itunes-style" : @YES, + @"notifications.show-album-art" : @YES + }; + + [[NSUserDefaults standardUserDefaults] registerDefaults:defaultsDictionary]; +} + +- (id)init { + self = [super init]; + if (self) { + [self initDefaults]; + + didGainUN = NO; + + if (@available(macOS 10.14, *)) { + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center + requestAuthorizationWithOptions:UNAuthorizationOptionAlert + completionHandler:^(BOOL granted, NSError *_Nullable error) { + self->didGainUN = granted; + + if (granted) { + UNNotificationAction *skipAction = [UNNotificationAction + actionWithIdentifier:@"skip" + title:@"Skip" + options:UNNotificationActionOptionNone]; + + UNNotificationCategory *playCategory = [UNNotificationCategory + categoryWithIdentifier:@"play" + actions:@[ skipAction ] + intentIdentifiers:@[] + options:UNNotificationCategoryOptionNone]; + + [center setNotificationCategories: + [NSSet setWithObject:playCategory]]; + } + }]; - if (granted) { - UNNotificationAction * skipAction = [UNNotificationAction actionWithIdentifier:@"skip" title:@"Skip" options:UNNotificationActionOptionNone]; - - UNNotificationCategory* playCategory = [UNNotificationCategory - categoryWithIdentifier:@"play" - actions:@[skipAction] - intentIdentifiers:@[] - options:UNNotificationCategoryOptionNone]; - - [center setNotificationCategories:[NSSet setWithObjects:playCategory, nil]]; - } - }]; - [center setDelegate:self]; } - - queue = [[NSOperationQueue alloc] init]; - [queue setMaxConcurrentOperationCount:1]; - - scrobbler = [[AudioScrobbler alloc] init]; + + queue = [[NSOperationQueue alloc] init]; + [queue setMaxConcurrentOperationCount:1]; + + scrobbler = [[AudioScrobbler alloc] init]; [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self]; - + entry = nil; - } - - return self; + } + + return self; } - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification - withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler API_AVAILABLE(macos(10.14)){ - UNNotificationPresentationOptions presentationOptions = - UNNotificationPresentationOptionAlert; - + withCompletionHandler: + (void (^)(UNNotificationPresentationOptions options))completionHandler + API_AVAILABLE(macos(10.14)) { + UNNotificationPresentationOptions presentationOptions = UNNotificationPresentationOptionAlert; + completionHandler(presentationOptions); } - (void)userNotificationCenter:(UNUserNotificationCenter *)center -didReceiveNotificationResponse:(UNNotificationResponse *)response - withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(macos(10.14)){ + didReceiveNotificationResponse:(UNNotificationResponse *)response + withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(macos(10.14)) { if ([[response actionIdentifier] isEqualToString:@"skip"]) { [playbackController next:self]; } } -- (NSDictionary *)fillNotificationDictionary:(PlaylistEntry *)pe status:(TrackStatus)status -{ +- (NSDictionary *)fillNotificationDictionary:(PlaylistEntry *)pe status:(TrackStatus)status { NSMutableDictionary *dict = [NSMutableDictionary dictionary]; - if (pe == nil) - return dict; - + if (pe == nil) return dict; + [dict setObject:[[pe URL] absoluteString] forKey:TrackPath]; if ([pe title]) [dict setObject:[pe title] forKey:TrackTitle]; if ([pe artist]) [dict setObject:[pe artist] forKey:TrackArtist]; if ([pe album]) [dict setObject:[pe album] forKey:TrackAlbum]; if ([pe genre]) [dict setObject:[pe genre] forKey:TrackGenre]; - if ([pe track]) [dict setObject:[NSString stringWithFormat:@"%@",[pe track]] forKey:TrackNumber]; - if ([pe length]) [dict setObject:[NSNumber numberWithInteger:(NSInteger)([[pe length] doubleValue] * 1000.0)] forKey:TrackLength]; - - NSString * state = nil; - - switch (status) - { - case TrackPlaying: state = @"Playing"; break; - case TrackPaused: state = @"Paused"; break; - case TrackStopped: state = @"Stopped"; break; - default: break; + if ([pe track]) + [dict setObject:[NSString stringWithFormat:@"%@", [pe track]] forKey:TrackNumber]; + if ([pe length]) + [dict setObject:[NSNumber numberWithInteger:(NSInteger)([[pe length] doubleValue] * 1000.0)] + forKey:TrackLength]; + + NSString *state = nil; + + switch (status) { + case TrackPlaying: + state = @"Playing"; + break; + case TrackPaused: + state = @"Paused"; + break; + case TrackStopped: + state = @"Stopped"; + break; + default: + break; } - + [dict setObject:state forKey:TrackState]; - + return dict; } -- (void)performPlaybackDidBeginActions:(PlaylistEntry *)pe -{ +- (void)performPlaybackDidBeginActions:(PlaylistEntry *)pe { if (NO == [pe error]) { entry = pe; - - [[NSDistributedNotificationCenter defaultCenter] postNotificationName:TrackNotification object:nil userInfo:[self fillNotificationDictionary:pe status:TrackPlaying] deliverImmediately:YES]; - + + [[NSDistributedNotificationCenter defaultCenter] + postNotificationName:TrackNotification + object:nil + userInfo:[self fillNotificationDictionary:pe status:TrackPlaying] + deliverImmediately:YES]; + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; if ([defaults boolForKey:@"notifications.enable"]) { - if([defaults boolForKey:@"enableAudioScrobbler"]) { + if ([defaults boolForKey:@"enableAudioScrobbler"]) { [scrobbler start:pe]; if ([AudioScrobbler isRunning]) return; } - if (@available(macOS 10.14,*)) - { + if (@available(macOS 10.14, *)) { if (didGainUN) { - UNUserNotificationCenter * center = [UNUserNotificationCenter currentNotificationCenter]; + UNUserNotificationCenter *center = + [UNUserNotificationCenter currentNotificationCenter]; + + UNMutableNotificationContent *content = + [[UNMutableNotificationContent alloc] init]; - UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init]; - content.title = @"Now Playing"; - + NSString *subtitle; if ([pe artist] && [pe album]) { subtitle = [NSString stringWithFormat:@"%@ - %@", [pe artist], [pe album]]; @@ -168,55 +182,67 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response } else { subtitle = @""; } - + NSString *body = [NSString stringWithFormat:@"%@\n%@", [pe title], subtitle]; content.body = body; content.sound = nil; content.categoryIdentifier = @"play"; - - if ([defaults boolForKey:@"notifications.show-album-art"] && [pe albumArtInternal]) { + + if ([defaults boolForKey:@"notifications.show-album-art"] && + [pe albumArtInternal]) { NSError *error = nil; NSFileManager *fileManager = [NSFileManager defaultManager]; NSURL *tmpSubFolderURL = [[NSURL fileURLWithPath:NSTemporaryDirectory()] - URLByAppendingPathComponent:@"cog-artworks-cache" isDirectory:true]; + URLByAppendingPathComponent:@"cog-artworks-cache" + isDirectory:true]; if ([fileManager createDirectoryAtPath:[tmpSubFolderURL path] withIntermediateDirectories:true attributes:nil error:&error]) { - NSString *tmpFileName = [[NSProcessInfo.processInfo globallyUniqueString] stringByAppendingString:@".jpg"]; - NSURL *fileURL = [tmpSubFolderURL URLByAppendingPathComponent:tmpFileName]; + NSString *tmpFileName = + [[NSProcessInfo.processInfo globallyUniqueString] + stringByAppendingString:@".jpg"]; + NSURL *fileURL = + [tmpSubFolderURL URLByAppendingPathComponent:tmpFileName]; NSImage *image = [pe albumArt]; - CGImageRef cgRef = [image CGImageForProposedRect:NULL context:nil hints:nil]; - NSBitmapImageRep *newRep = [[NSBitmapImageRep alloc] initWithCGImage:cgRef]; - NSData *jpgData = [newRep representationUsingType:NSBitmapImageFileTypeJPEG - properties:@{NSImageCompressionFactor: @0.5f}]; + CGImageRef cgRef = [image CGImageForProposedRect:NULL + context:nil + hints:nil]; + NSBitmapImageRep *newRep = + [[NSBitmapImageRep alloc] initWithCGImage:cgRef]; + NSData *jpgData = [newRep + representationUsingType:NSBitmapImageFileTypeJPEG + properties:@{NSImageCompressionFactor : @0.5f}]; [jpgData writeToURL:fileURL atomically:YES]; - UNNotificationAttachment *icon = [UNNotificationAttachment attachmentWithIdentifier:@"art" - URL:fileURL - options:nil - error:&error]; + UNNotificationAttachment *icon = + [UNNotificationAttachment attachmentWithIdentifier:@"art" + URL:fileURL + options:nil + error:&error]; if (error) { // We have size limit of 10MB per image attachment. NSLog(@"%@", error.localizedDescription); } else { - content.attachments = @[icon]; + content.attachments = @[ icon ]; } } } - UNNotificationRequest * request = [UNNotificationRequest requestWithIdentifier:@"PlayTrack" content:content trigger:nil]; - - [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { - NSLog(@"%@", error.localizedDescription); - }]; + UNNotificationRequest *request = + [UNNotificationRequest requestWithIdentifier:@"PlayTrack" + content:content + trigger:nil]; + + [center addNotificationRequest:request + withCompletionHandler:^(NSError *_Nullable error) { + NSLog(@"%@", error.localizedDescription); + }]; } - } - else - { + } else { NSUserNotification *notif = [[NSUserNotification alloc] init]; notif.title = [pe title]; - + NSString *subtitle; if ([pe artist] && [pe album]) { subtitle = [NSString stringWithFormat:@"%@ - %@", [pe artist], [pe album]]; @@ -227,118 +253,133 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response } else { subtitle = @""; } - + if ([defaults boolForKey:@"notifications.itunes-style"]) { notif.subtitle = subtitle; [notif setValue:@YES forKey:@"_showsButtons"]; - } - else { + } else { notif.informativeText = subtitle; } - + if ([notif respondsToSelector:@selector(setContentImage:)]) { - if ([defaults boolForKey:@"notifications.show-album-art"] && [pe albumArtInternal]) { + if ([defaults boolForKey:@"notifications.show-album-art"] && + [pe albumArtInternal]) { NSImage *image = [pe albumArt]; - + if ([defaults boolForKey:@"notifications.itunes-style"]) { [notif setValue:image forKey:@"_identityImage"]; - } - else { + } else { notif.contentImage = image; } } } - + notif.actionButtonTitle = @"Skip"; - - [[NSUserNotificationCenter defaultUserNotificationCenter] scheduleNotification:notif]; + + [[NSUserNotificationCenter defaultUserNotificationCenter] + scheduleNotification:notif]; } } - } + } } -- (void)performPlaybackDidPauseActions -{ - [[NSDistributedNotificationCenter defaultCenter] postNotificationName:TrackNotification object:nil userInfo:[self fillNotificationDictionary:entry status:TrackPaused] deliverImmediately:YES]; - if([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) { - [scrobbler pause]; - } +- (void)performPlaybackDidPauseActions { + [[NSDistributedNotificationCenter defaultCenter] + postNotificationName:TrackNotification + object:nil + userInfo:[self fillNotificationDictionary:entry status:TrackPaused] + deliverImmediately:YES]; + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) { + [scrobbler pause]; + } } -- (void)performPlaybackDidResumeActions -{ - [[NSDistributedNotificationCenter defaultCenter] postNotificationName:TrackNotification object:nil userInfo:[self fillNotificationDictionary:entry status:TrackPlaying] deliverImmediately:YES]; - if([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) { - [scrobbler resume]; - } +- (void)performPlaybackDidResumeActions { + [[NSDistributedNotificationCenter defaultCenter] + postNotificationName:TrackNotification + object:nil + userInfo:[self fillNotificationDictionary:entry status:TrackPlaying] + deliverImmediately:YES]; + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) { + [scrobbler resume]; + } } -- (void)performPlaybackDidStopActions -{ - [[NSDistributedNotificationCenter defaultCenter] postNotificationName:TrackNotification object:nil userInfo:[self fillNotificationDictionary:entry status:TrackStopped] deliverImmediately:YES]; +- (void)performPlaybackDidStopActions { + [[NSDistributedNotificationCenter defaultCenter] + postNotificationName:TrackNotification + object:nil + userInfo:[self fillNotificationDictionary:entry status:TrackStopped] + deliverImmediately:YES]; entry = nil; - if([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) { - [scrobbler stop]; - } + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) { + [scrobbler stop]; + } } - -- (void)awakeFromNib -{ - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackDidBegin:) name:CogPlaybackDidBeginNotficiation object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackDidPause:) name:CogPlaybackDidPauseNotficiation object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackDidResume:) name:CogPlaybackDidResumeNotficiation object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackDidStop:) name:CogPlaybackDidStopNotficiation object:nil]; +- (void)awakeFromNib { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(playbackDidBegin:) + name:CogPlaybackDidBeginNotficiation + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(playbackDidPause:) + name:CogPlaybackDidPauseNotficiation + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(playbackDidResume:) + name:CogPlaybackDidResumeNotficiation + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(playbackDidStop:) + name:CogPlaybackDidStopNotficiation + object:nil]; } -- (void) observeValueForKeyPath:(NSString *)keyPath - ofObject:(id)object - change:(NSDictionary *)change - context:(void *)context -{ +- (void)playbackDidBegin:(NSNotification *)notification { + NSOperation *op = [NSBlockOperation blockOperationWithBlock:^{ + [self performPlaybackDidBeginActions:(PlaylistEntry *)[notification object]]; + }]; + [queue addOperation:op]; } -- (void)playbackDidBegin:(NSNotification *)notification -{ - NSOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(performPlaybackDidBeginActions:) object:[notification object]]; - [queue addOperation:op]; +- (void)playbackDidPause:(NSNotification *)notification { + NSOperation *op = [NSBlockOperation blockOperationWithBlock:^{ + [self performPlaybackDidPauseActions]; + }]; + [queue addOperation:op]; } -- (void)playbackDidPause:(NSNotification *)notification -{ - NSOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(performPlaybackDidPauseActions) object:nil]; - [queue addOperation:op]; +- (void)playbackDidResume:(NSNotification *)notification { + NSOperation *op = [NSBlockOperation blockOperationWithBlock:^{ + [self performPlaybackDidResumeActions]; + }]; + [queue addOperation:op]; } -- (void)playbackDidResume:(NSNotification *)notification -{ - NSOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(performPlaybackDidResumeActions) object:nil]; - [queue addOperation:op]; +- (void)playbackDidStop:(NSNotification *)notification { + NSOperation *op = [NSBlockOperation blockOperationWithBlock:^{ + [self performPlaybackDidStopActions]; + }]; + [queue addOperation:op]; } -- (void)playbackDidStop:(NSNotification *)notification -{ - NSOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(performPlaybackDidStopActions) object:nil]; - [queue addOperation:op]; -} - -- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification -{ - switch (notification.activationType) - { +- (void)userNotificationCenter:(NSUserNotificationCenter *)center + didActivateNotification:(NSUserNotification *)notification { + switch (notification.activationType) { case NSUserNotificationActivationTypeActionButtonClicked: [playbackController next:self]; break; - - case NSUserNotificationActivationTypeContentsClicked: - { - NSWindow *window = [[NSUserDefaults standardUserDefaults] boolForKey:@"miniMode"] ? miniWindow : mainWindow; - + + case NSUserNotificationActivationTypeContentsClicked: { + NSWindow *window = [[NSUserDefaults standardUserDefaults] boolForKey:@"miniMode"] + ? miniWindow + : mainWindow; + [NSApp activateIgnoringOtherApps:YES]; [window makeKeyAndOrderFront:self]; - }; - break; - + }; break; + default: break; }