diff --git a/BGMApp/BGMApp.xcodeproj/project.pbxproj b/BGMApp/BGMApp.xcodeproj/project.pbxproj index ad47bad..76f43b2 100644 --- a/BGMApp/BGMApp.xcodeproj/project.pbxproj +++ b/BGMApp/BGMApp.xcodeproj/project.pbxproj @@ -85,6 +85,7 @@ 278D71E91CABB6FF00899CF9 /* BGMXPCHelperTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 279597401C996E2000A002FB /* BGMXPCHelperTests.m */; }; 278D71F61CABBC3B00899CF9 /* BGMXPCHelperTests-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 278D71F51CABBC3B00899CF9 /* BGMXPCHelperTests-Info.plist */; }; 2795973B1C982E4E00A002FB /* BGMXPCListener.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2795973A1C982E4E00A002FB /* BGMXPCListener.mm */; }; + 279F48771DD6D73A00768A85 /* BGMHermes.m in Sources */ = {isa = PBXBuildFile; fileRef = 279F48761DD6D73900768A85 /* BGMHermes.m */; }; 27C457E61CF2BC2600A6C9A6 /* BGMAutoPauseMenuItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 27C457E51CF2BC2600A6C9A6 /* BGMAutoPauseMenuItem.m */; }; 27D643BE1C9FB84C00737F6E /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 27D643BB1C9FB84C00737F6E /* Info.plist */; }; 27D643C01C9FB99200737F6E /* BGMXPCHelperService.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D643BA1C9FB84C00737F6E /* BGMXPCHelperService.m */; }; @@ -213,6 +214,9 @@ 2795973A1C982E4E00A002FB /* BGMXPCListener.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMXPCListener.mm; sourceTree = ""; }; 2795973C1C982E8C00A002FB /* BGMXPCListener.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMXPCListener.h; sourceTree = ""; }; 279597401C996E2000A002FB /* BGMXPCHelperTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BGMXPCHelperTests.m; sourceTree = ""; }; + 279F48751DD6D73900768A85 /* BGMHermes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMHermes.h; path = "Music Players/BGMHermes.h"; sourceTree = ""; }; + 279F48761DD6D73900768A85 /* BGMHermes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMHermes.m; path = "Music Players/BGMHermes.m"; sourceTree = ""; }; + 279F48781DD6D94000768A85 /* Hermes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Hermes.h; path = "Music Players/Hermes.h"; sourceTree = ""; }; 27C457E41CF2BC2600A6C9A6 /* BGMAutoPauseMenuItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMAutoPauseMenuItem.h; sourceTree = ""; }; 27C457E51CF2BC2600A6C9A6 /* BGMAutoPauseMenuItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BGMAutoPauseMenuItem.m; sourceTree = ""; }; 27D643B41C9FABBD00737F6E /* BGM_Types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGM_Types.h; path = ../SharedSource/BGM_Types.h; sourceTree = ""; }; @@ -352,6 +356,8 @@ 1C4699461BD5C0E400F78043 /* BGMiTunes.m */, 1C2336DD1BEAE10C004C1C4E /* BGMSpotify.h */, 1C2336DE1BEAE10C004C1C4E /* BGMSpotify.m */, + 279F48751DD6D73900768A85 /* BGMHermes.h */, + 279F48761DD6D73900768A85 /* BGMHermes.m */, 27379B881C7C562D0084A24C /* BGMVLC.h */, 27379B891C7C562D0084A24C /* BGMVLC.m */, 273F10DD1CC3D0B900C1C6DA /* BGMVOX.h */, @@ -463,6 +469,7 @@ 27F7D4911D2484A300821C4B /* Decibel.h */, 27379B851C7C54870084A24C /* iTunes.h */, 27379B861C7C54870084A24C /* Spotify.h */, + 279F48781DD6D94000768A85 /* Hermes.h */, 27379B871C7C552A0084A24C /* VLC.h */, 273F10DC1CC3CF9C00C1C6DA /* VOX.h */, ); @@ -733,6 +740,7 @@ 1C1962E41BC94E15008A4DF7 /* CARingBuffer.cpp in Sources */, 273F10DF1CC3D0B900C1C6DA /* BGMVOX.m in Sources */, 1CC1DF811BE5068A00FB8FE4 /* CACFArray.cpp in Sources */, + 279F48771DD6D73A00768A85 /* BGMHermes.m in Sources */, 1C0BD0A81BF1B029004F4CF5 /* BGMPreferencesMenu.mm in Sources */, 1C1962F41BCABFC5008A4DF7 /* CAHALAudioObject.cpp in Sources */, 27F7D4901D2483B100821C4B /* BGMDecibel.m in Sources */, @@ -1342,6 +1350,7 @@ 2743CA001D86CFFA0089613B /* DebugOpt */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; 278D71ED1CABB6FF00899CF9 /* Build configuration list for PBXNativeTarget "BGMXPCHelperTests" */ = { isa = XCConfigurationList; diff --git a/BGMApp/BGMApp/Music Players/BGMHermes.h b/BGMApp/BGMApp/Music Players/BGMHermes.h new file mode 100644 index 0000000..ea15eb9 --- /dev/null +++ b/BGMApp/BGMApp/Music Players/BGMHermes.h @@ -0,0 +1,30 @@ +// This file is part of Background Music. +// +// Background Music is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 2 of the +// License, or (at your option) any later version. +// +// Background Music is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Background Music. If not, see . + +// +// BGMHermes.h +// BGMApp +// +// Copyright © 2016 Kyle Neideck +// + +// Superclass/Protocol Import +#import "BGMMusicPlayer.h" + + +@interface BGMHermes : BGMMusicPlayerBase + +@end + diff --git a/BGMApp/BGMApp/Music Players/BGMHermes.m b/BGMApp/BGMApp/Music Players/BGMHermes.m new file mode 100644 index 0000000..bc7af6e --- /dev/null +++ b/BGMApp/BGMApp/Music Players/BGMHermes.m @@ -0,0 +1,102 @@ +// This file is part of Background Music. +// +// Background Music is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 2 of the +// License, or (at your option) any later version. +// +// Background Music is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Background Music. If not, see . + +// +// BGMHermes.m +// BGMApp +// +// Copyright © 2016 Kyle Neideck +// + +// Self Include +#import "BGMHermes.h" + +// Auto-generated Scripting Bridge header +#import "Hermes.h" + +// Local Includes +#import "BGMScriptingBridge.h" + +// PublicUtility Includes +#undef CoreAudio_ThreadStampMessages +#define CoreAudio_ThreadStampMessages 0 // Requires C++ +#include "CADebugMacros.h" + + +#pragma clang assume_nonnull begin + +@implementation BGMHermes { + BGMScriptingBridge* scriptingBridge; +} + +- (id) init { + // If you're copying this class, replace the ID string with a new one generated by uuidgen. (Command line tool.) + if ((self = [super initWithMusicPlayerID:[BGMMusicPlayerBase makeID:@"0CDC67B0-56D3-4D94-BC06-6E380D8F5E34"] + name:@"Hermes" + bundleID:@"com.alexcrichton.Hermes"])) { + scriptingBridge = [[BGMScriptingBridge alloc] initWithBundleID:(NSString*)self.bundleID]; + } + + return self; +} + +- (HermesApplication* __nullable) hermes { + return (HermesApplication* __nullable)scriptingBridge.application; +} + +- (BOOL) isRunning { + // Note that this will return NO if is self.hermes is nil (i.e. Hermes isn't running). + return self.hermes.running; +} + +// isPlaying and isPaused check self.running first just in case Hermes is closed but self.hermes hasn't become +// nil yet. In that case, reading self.hermes.playerState could make Scripting Bridge open Hermes. + +- (BOOL) isPlaying { + return self.running && (self.hermes.playbackState == HermesPlayerStatesPlaying); +} + +- (BOOL) isPaused { + return self.running && (self.hermes.playbackState == HermesPlayerStatesPaused); +} + +- (BOOL) pause { + // isPlaying checks isRunning, so we don't need to check it here and waste an Apple event + BOOL wasPlaying = self.playing; + + if (wasPlaying) { + DebugMsg("BGMHermes::pause: Pausing Hermes"); + [self.hermes pause]; + } + + return wasPlaying; +} + +- (BOOL) unpause { + // isPaused checks isRunning, so we don't need to check it here and waste an Apple event + BOOL wasPaused = self.paused; + + if (wasPaused) { + DebugMsg("BGMHermes::unpause: Unpausing Hermes"); + [self.hermes play]; + } + + return wasPaused; +} + +@end + +#pragma clang assume_nonnull end + diff --git a/BGMApp/BGMApp/Music Players/BGMMusicPlayers.mm b/BGMApp/BGMApp/Music Players/BGMMusicPlayers.mm index 143202c..9e8bc02 100644 --- a/BGMApp/BGMApp/Music Players/BGMMusicPlayers.mm +++ b/BGMApp/BGMApp/Music Players/BGMMusicPlayers.mm @@ -32,6 +32,7 @@ #import "BGMVLC.h" #import "BGMVOX.h" #import "BGMDecibel.h" +#import "BGMHermes.h" #pragma clang assume_nonnull begin @@ -52,7 +53,8 @@ [BGMVLC class], [BGMSpotify class], [BGMiTunes class], - [BGMDecibel class]] + [BGMDecibel class], + [BGMHermes class] ] userDefaults:defaults]; } diff --git a/BGMApp/BGMApp/Music Players/Hermes.h b/BGMApp/BGMApp/Music Players/Hermes.h new file mode 100644 index 0000000..f714f41 --- /dev/null +++ b/BGMApp/BGMApp/Music Players/Hermes.h @@ -0,0 +1,78 @@ +/* + * Hermes.h + * + * Generated with + * sdef /Applications/Hermes.app | sdp -fh --basename Hermes + */ + +#import +#import + + +@class HermesApplication, HermesSong, HermesStation; + +// Legal player states +enum HermesPlayerStates { + HermesPlayerStatesStopped = 'stop' /* Player is stopped */, + HermesPlayerStatesPlaying = 'play' /* Player is playing */, + HermesPlayerStatesPaused = 'paus' /* Player is paused */ +}; +typedef enum HermesPlayerStates HermesPlayerStates; + + + +/* + * Hermes Suite + */ + +// The Pandora player. +@interface HermesApplication : SBApplication + +- (SBElementArray *) stations; + +@property NSInteger playbackVolume; // The current playback volume (0–100). +@property HermesPlayerStates playbackState; // The current playback state. +@property (readonly) double playbackPosition; // The current song’s playback position, in seconds. +@property (readonly) double currentSongDuration; // The duration (length) of the current song, in seconds. +@property (copy) HermesStation *currentStation; // The currently selected Pandora station. +@property (copy, readonly) HermesSong *currentSong; // The currently playing (or paused) Pandora song (WARNING: This is an invalid reference in current versions of Hermes; you must access the current song’s properties individually or as a group directly instead.) + +- (void) playpause; // Play the current song if it is paused; pause the current song if it is playing. +- (void) pause; // Pause the currently playing song. +- (void) play; // Resume playing the current song. +- (void) nextSong; // Skip to the next song on the current station. +- (void) thumbsUp; // Tell Pandora you like the current song. +- (void) thumbsDown; // Tell Pandora you don’t like the current song. +- (void) tiredOfSong; // Tell Pandora you’re tired of the current song. +- (void) increaseVolume; // Increase the playback volume. +- (void) decreaseVolume; // Decrease the playback volume. +- (void) maximizeVolume; // Set the playback volume to its maximum level. +- (void) mute; // Mutes playback, saving the current volume level. +- (void) unmute; // Restores the volume to the level prior to muting. + +@end + +// A Pandora song (track). +@interface HermesSong : SBObject + +@property (copy, readonly) NSString *title; // The song’s title. +@property (copy, readonly) NSString *artist; // The song’s artist. +@property (copy, readonly) NSString *album; // The song’s album. +@property (copy, readonly) NSString *artworkURL; // An image URL for the album’s cover artwork. +@property (readonly) NSInteger rating; // The song’s numeric rating. +@property (copy, readonly) NSString *albumURL; // A Pandora URL for more information on the album. +@property (copy, readonly) NSString *artistURL; // A Pandora URL for more information on the artist. +@property (copy, readonly) NSString *trackURL; // A Pandora URL for more information on the track. + + +@end + +// A Pandora station. +@interface HermesStation : SBObject + +@property (copy, readonly) NSString *name; // The station’s name. +@property (copy, readonly) NSString *stationID; // The station’s ID. + + +@end + diff --git a/README.md b/README.md index aaca642..5be0600 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ![](Images/README/FermataIcon.png) # Background Music -##### OS X audio utility +##### macOS audio utility ![](Images/README/Screenshot.png) @@ -25,10 +25,11 @@ Background Music can pause your music player app when other audio starts playing that when I'm listening to music and pause it to watch a video or something I always forget to unpause it afterwards. So this keeps me from wearing headphones for hours listening to nothing. -So far iTunes, [Spotify](https://www.spotify.com), [Decibel](https://sbooth.org/Decibel/), -[VLC](https://www.videolan.org/vlc/) and [VOX](https://coppertino.com/vox/mac) are supported, but adding support for a -music player should only take a few minutes (see `BGMMusicPlayer.h`). If you don't know how to program, or just don't -feel like it, create an issue and I'll try to add it for you. +So far iTunes, [Spotify](https://www.spotify.com), [VLC](https://www.videolan.org/vlc/), +[VOX](https://coppertino.com/vox/mac), [Decibel](https://sbooth.org/Decibel/) and [Hermes](http://hermesapp.org/) are +supported. Adding support for a new music player should only take a few minutes[1](#f1) -- see +(BGMMusicPlayer.h)[BGMApp/BGMApp/Music Players/BGMMusicPlayer.h]. If you don't know how to program, or just don't feel +like it, create an issue and I'll try to add it for you. ## App volumes @@ -110,7 +111,7 @@ change the default device and then change it back again. Failing that, you might the Sound section in System Preferences, click the Output tab and change your default output device to something other than Background Music Device. Alternatively, you may Option+Click on the Sound icon in the menu bar to select a different output device. - This happens when OS X remembers that Background Music Device was your default audio device the last time you last + This happens when macOS remembers that Background Music Device was your default audio device the last time you last used (or didn't use) headphones. - [A recent Chrome bug](https://bugs.chromium.org/p/chromium/issues/detail?id=557620) can stop Chrome from switching to Background Music Device after you open Background Music. Chrome's audio will still play, but Background Music won't be @@ -153,3 +154,11 @@ change the default device and then change it back again. Failing that, you might ## License GPLv2 or later + +---- + +[1] However, if the music player doesn't support AppleScript, or doesn't support the events Background +Music needs (`isPlaying`, `isPaused`, `play` and `pause`), it can take significantly more effort to add. (And in some +cases would require changes to the music player itself.) [↩](#a1) + +