Modernize PlaybackEventController.

This commit is contained in:
Dzmitry Neviadomski 2021-02-26 23:01:48 +03:00
parent d44d188a4f
commit 8105b9e2b2
2 changed files with 234 additions and 200 deletions

View file

@ -13,20 +13,13 @@
#import "PlaylistEntry.h" #import "PlaylistEntry.h"
@class AudioScrobbler; @class AudioScrobbler;
@interface PlaybackEventController : NSObject <NSUserNotificationCenterDelegate, UNUserNotificationCenterDelegate> {
NSOperationQueue *queue; @interface PlaybackEventController
: NSObject <NSUserNotificationCenterDelegate, UNUserNotificationCenterDelegate> {
PlaylistEntry *entry;
AudioScrobbler *scrobbler;
IBOutlet PlaybackController *playbackController; IBOutlet PlaybackController *playbackController;
IBOutlet NSWindow *mainWindow; IBOutlet NSWindow *mainWindow;
IBOutlet NSWindow *miniWindow; IBOutlet NSWindow *miniWindow;
Boolean didGainUN;
} }
@end @end

View file

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