cog/Application/PlaybackEventController.m

348 lines
14 KiB
Objective-C

//
// PlaybackEventController.m
// Cog
//
// 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"
#import "AudioScrobbler.h"
NSString *TrackNotification = @"com.apple.iTunes.playerInfo";
NSString *TrackArtist = @"Artist";
NSString *TrackAlbum = @"Album";
NSString *TrackTitle = @"Name";
NSString *TrackGenre = @"Genre";
NSString *TrackNumber = @"Track Number";
NSString *TrackLength = @"Total Time";
NSString *TrackPath = @"Location";
NSString *TrackState = @"Player State";
typedef enum
{
TrackPlaying,
TrackPaused,
TrackStopped
} TrackStatus;
@implementation PlaybackEventController
- (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];
}
- (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 setWithObjects:playCategory, nil]];
}
}];
[center setDelegate:self];
}
queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:1];
scrobbler = [[AudioScrobbler alloc] init];
[[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self];
entry = nil;
}
return self;
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
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)){
if ([[response actionIdentifier] isEqualToString:@"skip"]) {
[playbackController next:self];
}
}
- (NSDictionary *)fillNotificationDictionary:(PlaylistEntry *)pe status:(TrackStatus)status
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
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;
}
[dict setObject:state forKey:TrackState];
return dict;
}
- (void)performPlaybackDidBeginActions:(PlaylistEntry *)pe
{
if (NO == [pe error]) {
entry = pe;
[[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"]) {
[scrobbler start:pe];
if ([AudioScrobbler isRunning]) return;
}
if (@available(macOS 10.14,*))
{
if (didGainUN) {
UNUserNotificationCenter * center = [UNUserNotificationCenter currentNotificationCenter];
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
content.title = @"Now Playing";
NSString *subtitle;
if ([pe artist] && [pe album]) {
subtitle = [NSString stringWithFormat:@"%@ - %@", [pe artist], [pe album]];
} else if ([pe artist]) {
subtitle = [pe artist];
} else if ([pe album]) {
subtitle = [pe album];
} 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]) {
NSError *error = nil;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *tmpSubFolderURL = [[NSURL fileURLWithPath:NSTemporaryDirectory()]
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];
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}];
[jpgData writeToURL:fileURL atomically:YES];
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];
}
}
}
UNNotificationRequest * request = [UNNotificationRequest requestWithIdentifier:@"PlayTrack" content:content trigger:nil];
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
NSLog(@"%@", error.localizedDescription);
}];
}
}
else
{
NSUserNotification *notif = [[NSUserNotification alloc] init];
notif.title = [pe title];
NSString *subtitle;
if ([pe artist] && [pe album]) {
subtitle = [NSString stringWithFormat:@"%@ - %@", [pe artist], [pe album]];
} else if ([pe artist]) {
subtitle = [pe artist];
} else if ([pe album]) {
subtitle = [pe album];
} else {
subtitle = @"";
}
if ([defaults boolForKey:@"notifications.itunes-style"]) {
notif.subtitle = subtitle;
[notif setValue:@YES forKey:@"_showsButtons"];
}
else {
notif.informativeText = subtitle;
}
if ([notif respondsToSelector:@selector(setContentImage:)]) {
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 {
notif.contentImage = image;
}
}
}
notif.actionButtonTitle = @"Skip";
[[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)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];
entry = nil;
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) observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
}
- (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 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(performPlaybackDidPauseActions) object:nil];
[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 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(performPlaybackDidStopActions) object:nil];
[queue addOperation:op];
}
- (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;
[NSApp activateIgnoringOtherApps:YES];
[window makeKeyAndOrderFront:self];
};
break;
default:
break;
}
}
@end