From 46a4570cc226386b3c71da1bb466860c565dfe45 Mon Sep 17 00:00:00 2001 From: Juan Segnana <37079836+juansegnana@users.noreply.github.com> Date: Thu, 9 Jul 2026 02:23:49 -0300 Subject: [PATCH] BGMApp: Add auto-pause support for TIDAL. TIDAL is an Electron app with no AppleScript dictionary or local API, so it can't be controlled through Scripting Bridge like most of the other players. Instead, BGMTidal drives it through the macOS Accessibility API: it reads and presses the first item of TIDAL's localized "Playback" menu (the play/pause toggle) directly via AXUIElement, without visibly opening the menu. The toggle's title ("Play" vs "Pause", matched against a small list of known localizations) also tells us whether TIDAL is playing or paused. This requires the user to grant Background Music the Accessibility permission (System Settings > Privacy & Security > Accessibility). BGMTidal prompts for it in wasSelected, i.e. only once the user actually selects TIDAL as their music player. If the permission isn't granted, all the AX queries fail gracefully and TIDAL is reported as not playing/paused. --- BGMApp/BGMApp.xcodeproj/project.pbxproj | 10 + .../BGMApp/Music Players/BGMMusicPlayers.mm | 4 +- BGMApp/BGMApp/Music Players/BGMTidal.h | 39 +++ BGMApp/BGMApp/Music Players/BGMTidal.m | 273 ++++++++++++++++++ README.md | 7 + 5 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 BGMApp/BGMApp/Music Players/BGMTidal.h create mode 100644 BGMApp/BGMApp/Music Players/BGMTidal.m diff --git a/BGMApp/BGMApp.xcodeproj/project.pbxproj b/BGMApp/BGMApp.xcodeproj/project.pbxproj index 41406316..b8bdf448 100644 --- a/BGMApp/BGMApp.xcodeproj/project.pbxproj +++ b/BGMApp/BGMApp.xcodeproj/project.pbxproj @@ -143,6 +143,7 @@ 1CD9894C1ECFFCFC0014BBBF /* BGMHermes.m in Sources */ = {isa = PBXBuildFile; fileRef = 279F48761DD6D73900768A85 /* BGMHermes.m */; }; 1CD9894D1ECFFCFC0014BBBF /* BGMVLC.m in Sources */ = {isa = PBXBuildFile; fileRef = 27379B891C7C562D0084A24C /* BGMVLC.m */; }; 1CD9894E1ECFFCFC0014BBBF /* BGMVOX.m in Sources */ = {isa = PBXBuildFile; fileRef = 273F10DE1CC3D0B900C1C6DA /* BGMVOX.m */; }; + D7A1B2C30001000000000004 /* BGMTidal.m in Sources */ = {isa = PBXBuildFile; fileRef = D7A1B2C30001000000000002 /* BGMTidal.m */; }; 1CD9894F1ECFFCFC0014BBBF /* BGMPreferencesMenu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C0BD0A71BF1B029004F4CF5 /* BGMPreferencesMenu.mm */; }; 1CD989501ECFFCFC0014BBBF /* BGMAboutPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D1D6BA1DD7226C0049E707 /* BGMAboutPanel.m */; }; 1CD989511ECFFCFC0014BBBF /* BGMAutoPauseMusicPrefs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C0BD0A41BF1A8E6004F4CF5 /* BGMAutoPauseMusicPrefs.mm */; }; @@ -183,6 +184,7 @@ 271677BA1C6CBDFA0080B0A2 /* CACFNumber.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 271677B81C6CBDFA0080B0A2 /* CACFNumber.cpp */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-CACFNumber.cpp"; }; }; 27379B8A1C7C562D0084A24C /* BGMVLC.m in Sources */ = {isa = PBXBuildFile; fileRef = 27379B891C7C562D0084A24C /* BGMVLC.m */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMVLC.m"; }; }; 273F10DF1CC3D0B900C1C6DA /* BGMVOX.m in Sources */ = {isa = PBXBuildFile; fileRef = 273F10DE1CC3D0B900C1C6DA /* BGMVOX.m */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMVOX.m"; }; }; + D7A1B2C30001000000000003 /* BGMTidal.m in Sources */ = {isa = PBXBuildFile; fileRef = D7A1B2C30001000000000002 /* BGMTidal.m */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMTidal.m"; }; }; 2743C9EB1D852B360089613B /* BGMMusicPlayers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2743C9E81D852B350089613B /* BGMMusicPlayers.mm */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMMusicPlayers.mm"; }; }; 2743C9EC1D852B360089613B /* BGMScriptingBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 2743C9EA1D852B350089613B /* BGMScriptingBridge.m */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMScriptingBridge.m"; }; }; 2743C9F11D853FBB0089613B /* BGMUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 2743C9F01D853FBB0089613B /* BGMUserDefaults.m */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMUserDefaults.m"; }; }; @@ -194,6 +196,7 @@ 2743CA061D86D41C0089613B /* BGMSpotify.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C2336DE1BEAE10C004C1C4E /* BGMSpotify.m */; }; 2743CA071D86D41C0089613B /* BGMVLC.m in Sources */ = {isa = PBXBuildFile; fileRef = 27379B891C7C562D0084A24C /* BGMVLC.m */; }; 2743CA081D86D41C0089613B /* BGMVOX.m in Sources */ = {isa = PBXBuildFile; fileRef = 273F10DE1CC3D0B900C1C6DA /* BGMVOX.m */; }; + D7A1B2C30001000000000005 /* BGMTidal.m in Sources */ = {isa = PBXBuildFile; fileRef = D7A1B2C30001000000000002 /* BGMTidal.m */; }; 2743CA091D86D41C0089613B /* BGMUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 2743C9F01D853FBB0089613B /* BGMUserDefaults.m */; }; 2743CA0A1D86D52D0089613B /* BGMAudioDeviceManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CED616B1C316E1A002CAFCF /* BGMAudioDeviceManager.mm */; }; 2743CA0C1D86D7FA0089613B /* CACFArray.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CC1DF7D1BE5068A00FB8FE4 /* CACFArray.cpp */; }; @@ -398,6 +401,8 @@ 273F10DC1CC3CF9C00C1C6DA /* VOX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VOX.h; path = "Music Players/VOX.h"; sourceTree = ""; }; 273F10DD1CC3D0B900C1C6DA /* BGMVOX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMVOX.h; path = "Music Players/BGMVOX.h"; sourceTree = ""; }; 273F10DE1CC3D0B900C1C6DA /* BGMVOX.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMVOX.m; path = "Music Players/BGMVOX.m"; sourceTree = ""; }; + D7A1B2C30001000000000001 /* BGMTidal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMTidal.h; path = "Music Players/BGMTidal.h"; sourceTree = ""; }; + D7A1B2C30001000000000002 /* BGMTidal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMTidal.m; path = "Music Players/BGMTidal.m"; sourceTree = ""; }; 2743C9E71D852B350089613B /* BGMMusicPlayers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMMusicPlayers.h; path = "Music Players/BGMMusicPlayers.h"; sourceTree = ""; }; 2743C9E81D852B350089613B /* BGMMusicPlayers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMMusicPlayers.mm; path = "Music Players/BGMMusicPlayers.mm"; sourceTree = ""; }; 2743C9E91D852B350089613B /* BGMScriptingBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMScriptingBridge.h; path = "Music Players/BGMScriptingBridge.h"; sourceTree = ""; }; @@ -614,6 +619,8 @@ 27379B891C7C562D0084A24C /* BGMVLC.m */, 273F10DD1CC3D0B900C1C6DA /* BGMVOX.h */, 273F10DE1CC3D0B900C1C6DA /* BGMVOX.m */, + D7A1B2C30001000000000001 /* BGMTidal.h */, + D7A1B2C30001000000000002 /* BGMTidal.m */, 27379B841C7C53BE0084A24C /* Supporting Files */, ); name = "Music Players"; @@ -1122,6 +1129,7 @@ 1C1962E41BC94E15008A4DF7 /* CARingBuffer.cpp in Sources */, 1C8D830B2042DE9600A838F2 /* BGMGooglePlayMusicDesktopPlayer.m in Sources */, 273F10DF1CC3D0B900C1C6DA /* BGMVOX.m in Sources */, + D7A1B2C30001000000000003 /* BGMTidal.m in Sources */, 1CC1DF811BE5068A00FB8FE4 /* CACFArray.cpp in Sources */, 279F48771DD6D73A00768A85 /* BGMHermes.m in Sources */, 1C0BD0A81BF1B029004F4CF5 /* BGMPreferencesMenu.mm in Sources */, @@ -1210,6 +1218,7 @@ 1CD9894C1ECFFCFC0014BBBF /* BGMHermes.m in Sources */, 1CD9894D1ECFFCFC0014BBBF /* BGMVLC.m in Sources */, 1CD9894E1ECFFCFC0014BBBF /* BGMVOX.m in Sources */, + D7A1B2C30001000000000004 /* BGMTidal.m in Sources */, 1CD9894F1ECFFCFC0014BBBF /* BGMPreferencesMenu.mm in Sources */, 1CD989501ECFFCFC0014BBBF /* BGMAboutPanel.m in Sources */, 1CD989511ECFFCFC0014BBBF /* BGMAutoPauseMusicPrefs.mm in Sources */, @@ -1314,6 +1323,7 @@ 2743CA071D86D41C0089613B /* BGMVLC.m in Sources */, 1CF5423D1EAAEE4300445AD8 /* BGMAudioDevice.cpp in Sources */, 2743CA081D86D41C0089613B /* BGMVOX.m in Sources */, + D7A1B2C30001000000000005 /* BGMTidal.m in Sources */, 1C62FE5223D3EB2E00B9B68E /* Mock_CAHALAudioDevice.cpp in Sources */, 1C62FE5323D3EB2E00B9B68E /* MockAudioDevice.cpp in Sources */, 2743CA091D86D41C0089613B /* BGMUserDefaults.m in Sources */, diff --git a/BGMApp/BGMApp/Music Players/BGMMusicPlayers.mm b/BGMApp/BGMApp/Music Players/BGMMusicPlayers.mm index 39a5c9c6..f184179b 100644 --- a/BGMApp/BGMApp/Music Players/BGMMusicPlayers.mm +++ b/BGMApp/BGMApp/Music Players/BGMMusicPlayers.mm @@ -36,6 +36,7 @@ #import "BGMSwinsian.h" #import "BGMMusic.h" #import "BGMGooglePlayMusicDesktopPlayer.h" +#import "BGMTidal.h" #pragma clang assume_nonnull begin @@ -58,7 +59,8 @@ - (instancetype) initWithAudioDevices:(BGMAudioDeviceManager*)devices [BGMDecibel class], [BGMHermes class], [BGMSwinsian class], - [BGMMusic class] ]; + [BGMMusic class], + [BGMTidal class] ]; // We only support Google Play Music Desktop Player on macOS 10.10 and higher. if (@available(macOS 10.10, *)) { diff --git a/BGMApp/BGMApp/Music Players/BGMTidal.h b/BGMApp/BGMApp/Music Players/BGMTidal.h new file mode 100644 index 00000000..139c8b25 --- /dev/null +++ b/BGMApp/BGMApp/Music Players/BGMTidal.h @@ -0,0 +1,39 @@ +// 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 . + +// +// BGMTidal.h +// BGMApp +// +// Copyright © 2026 Background Music contributors +// +// Auto-pause support for the TIDAL desktop app. +// +// TIDAL is an Electron app that doesn't expose an AppleScript dictionary or a local API, so unlike +// most of the other music player classes we can't use Scripting Bridge. Instead, we control it +// through the macOS Accessibility API: we read and press the first item of TIDAL's "Playback" menu +// (the play/pause toggle) directly, without visibly opening the menu. +// +// This requires the user to grant Background Music the Accessibility permission +// (System Settings > Privacy & Security > Accessibility). See BGMTidal.m for the details. +// + +// Superclass/Protocol Import +#import "BGMMusicPlayer.h" + + +@interface BGMTidal : BGMMusicPlayerBase + +@end diff --git a/BGMApp/BGMApp/Music Players/BGMTidal.m b/BGMApp/BGMApp/Music Players/BGMTidal.m new file mode 100644 index 00000000..3d97bd88 --- /dev/null +++ b/BGMApp/BGMApp/Music Players/BGMTidal.m @@ -0,0 +1,273 @@ +// 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 . + +// +// BGMTidal.m +// BGMApp +// +// Copyright © 2026 Background Music contributors +// + +// Self Include +#import "BGMTidal.h" + +// PublicUtility Includes +#import "CADebugMacros.h" + +// System Includes +#import + + +#pragma clang assume_nonnull begin + +// TIDAL's bundle ID. It's an Electron app with no AppleScript dictionary or local API. +static NSString* const kBGMTidalBundleID = @"com.tidal.desktop"; + +// TIDAL's menus can be localized, so we look for the "Playback" menu by matching its title against a +// list of known titles. Within that menu, the first item is the play/pause toggle regardless of +// locale. +// +// These are functions (rather than file-scope constants) because Objective-C array literals aren't +// compile-time constants. +// +// TODO: These lists only cover a few locales. If you use TIDAL in another language, please add its +// titles here (or file an issue). +static NSArray* BGMTidalPlaybackMenuTitles(void) { + return @[ + @"Playback", // English + @"Reproducción", // Spanish + @"Wiedergabe", // German + @"Lecture" // French + ]; +} + +// The play/pause toggle item's title reads as one of these when TIDAL is currently playing (i.e. the +// item would pause playback if pressed). +static NSArray* BGMTidalPauseTitles(void) { + return @[ + @"Pause", // English / German / French + @"Pausa", // Spanish (Italian) + @"Pausar" // Spanish (alternative) + ]; +} + +// The play/pause toggle item's title reads as one of these when TIDAL is currently paused (i.e. the +// item would start playback if pressed). +static NSArray* BGMTidalPlayTitles(void) { + return @[ + @"Play", // English + @"Reproducir", // Spanish + @"Wiedergabe starten", // German + @"Lecture" // French + ]; +} + +// The state of TIDAL's playback, as read from the play/pause toggle menu item. +typedef NS_ENUM(NSInteger, BGMTidalPlaybackState) { + // TIDAL isn't running, we couldn't read the menu, or the title didn't match any known title. + BGMTidalPlaybackStateUnknown = 0, + BGMTidalPlaybackStatePlaying, + BGMTidalPlaybackStatePaused +}; + +// Read an Accessibility attribute and return its value bridged to an ARC-managed object (or nil if +// the attribute couldn't be read). AXUIElementCopyAttributeValue follows the Create Rule, so we +// hand ownership of the returned value to ARC via CFBridgingRelease. +static id __nullable BGMTidalCopyAXAttribute(AXUIElementRef element, CFStringRef attribute) { + CFTypeRef value = NULL; + AXError error = AXUIElementCopyAttributeValue(element, attribute, &value); + + if (error != kAXErrorSuccess || value == NULL) { + return nil; + } + + return CFBridgingRelease(value); +} + +@implementation BGMTidal + +- (instancetype) init { + // If you're copying this class, replace the ID string with a new one generated by uuidgen (the + // command line tool). + if ((self = [super initWithMusicPlayerID:[BGMMusicPlayerBase makeID:@"CA99EB53-26D3-43D6-9716-55AE8FEE1540"] + name:@"TIDAL" + bundleID:kBGMTidalBundleID])) { + } + + return self; +} + +- (void) wasSelected { + [super wasSelected]; + + // Controlling TIDAL's menus requires Background Music to have the Accessibility permission. Ask + // for it now that the user has actually selected TIDAL (rather than prompting on every launch), + // mirroring the way the Scripting Bridge players call ensurePermission in wasSelected. + // + // AXIsProcessTrustedWithOptions only shows the prompt if the permission hasn't been granted yet. + // If it isn't granted, all our AX queries fail gracefully and we report TIDAL as not + // playing/paused. + NSDictionary* options = @{ (__bridge NSString*)kAXTrustedCheckOptionPrompt: @YES }; + AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)options); +} + +// Find TIDAL's play/pause toggle menu item (the first item of its "Playback" menu) and return it as +// an ARC-managed AXUIElementRef, or nil if TIDAL isn't running or the menu couldn't be found. +- (id __nullable) copyPlayPauseMenuItem { + NSRunningApplication* __nullable app = + [NSRunningApplication runningApplicationsWithBundleIdentifier:kBGMTidalBundleID].firstObject; + + if (!app) { + return nil; + } + + // AXUIElementCreateApplication follows the Create Rule, so we own the returned element. + AXUIElementRef appElement = AXUIElementCreateApplication(app.processIdentifier); + + id __nullable menuBar = BGMTidalCopyAXAttribute(appElement, kAXMenuBarAttribute); + CFRelease(appElement); + + if (!menuBar) { + // Usually because Background Music doesn't have the Accessibility permission yet. + DebugMsg("BGMTidal::copyPlayPauseMenuItem: Couldn't read TIDAL's menu bar"); + return nil; + } + + NSArray* __nullable menuBarItems = + BGMTidalCopyAXAttribute((__bridge AXUIElementRef)menuBar, kAXChildrenAttribute); + + for (id menuBarItemObj in menuBarItems) { + AXUIElementRef menuBarItem = (__bridge AXUIElementRef)menuBarItemObj; + NSString* __nullable title = BGMTidalCopyAXAttribute(menuBarItem, kAXTitleAttribute); + + if (!title || ![BGMTidalPlaybackMenuTitles() containsObject:(NSString*)title]) { + continue; + } + + // A menu bar item has a single child: the menu it opens. + NSArray* __nullable menus = BGMTidalCopyAXAttribute(menuBarItem, kAXChildrenAttribute); + id __nullable menuObj = menus.firstObject; + + if (!menuObj) { + continue; + } + + NSArray* __nullable menuItems = + BGMTidalCopyAXAttribute((__bridge AXUIElementRef)menuObj, kAXChildrenAttribute); + + // The first item of the "Playback" menu is the play/pause toggle. + return menuItems.firstObject; + } + + DebugMsg("BGMTidal::copyPlayPauseMenuItem: Couldn't find TIDAL's playback menu"); + return nil; +} + +// Read TIDAL's current playback state from the play/pause toggle menu item's title. +- (BGMTidalPlaybackState) playbackState { + // Don't prompt for the Accessibility permission here (that's done in wasSelected). If it hasn't + // been granted, the AX queries below just fail and we report the state as unknown. + if (!AXIsProcessTrusted()) { + return BGMTidalPlaybackStateUnknown; + } + + id __nullable menuItem = [self copyPlayPauseMenuItem]; + + if (!menuItem) { + return BGMTidalPlaybackStateUnknown; + } + + NSString* __nullable title = + BGMTidalCopyAXAttribute((__bridge AXUIElementRef)menuItem, kAXTitleAttribute); + + if (!title) { + return BGMTidalPlaybackStateUnknown; + } + + NSString* titleNN = (NSString*)title; + + if ([BGMTidalPauseTitles() containsObject:titleNN]) { + // The toggle would pause playback, so TIDAL is currently playing. + return BGMTidalPlaybackStatePlaying; + } + + if ([BGMTidalPlayTitles() containsObject:titleNN]) { + // The toggle would start playback, so TIDAL is currently paused. + return BGMTidalPlaybackStatePaused; + } + + DebugMsg("BGMTidal::playbackState: Unrecognized play/pause menu item title"); + return BGMTidalPlaybackStateUnknown; +} + +// Press TIDAL's play/pause toggle menu item. Returns YES if the item was found and pressed. +- (BOOL) pressPlayPauseMenuItem { + id __nullable menuItem = [self copyPlayPauseMenuItem]; + + if (!menuItem) { + return NO; + } + + AXError error = AXUIElementPerformAction((__bridge AXUIElementRef)menuItem, kAXPressAction); + + if (error != kAXErrorSuccess) { + DebugMsg("BGMTidal::pressPlayPauseMenuItem: AXUIElementPerformAction failed (%d)", error); + return NO; + } + + return YES; +} + +- (BOOL) isRunning { + // Like the other music players, we check with NSRunningApplication rather than launching TIDAL. + NSArray* instances = + [NSRunningApplication runningApplicationsWithBundleIdentifier:kBGMTidalBundleID]; + + return instances.count > 0; +} + +- (BOOL) isPlaying { + return self.playbackState == BGMTidalPlaybackStatePlaying; +} + +- (BOOL) isPaused { + return self.playbackState == BGMTidalPlaybackStatePaused; +} + +- (BOOL) pause { + // isPlaying reads the menu state (which is nil/unknown if TIDAL isn't running), so we don't need + // to check isRunning here. + if (!self.playing) { + return NO; + } + + DebugMsg("BGMTidal::pause: Pausing TIDAL"); + return [self pressPlayPauseMenuItem]; +} + +- (BOOL) unpause { + // isPaused reads the menu state (which is nil/unknown if TIDAL isn't running), so we don't need + // to check isRunning here. + if (!self.paused) { + return NO; + } + + DebugMsg("BGMTidal::unpause: Unpausing TIDAL"); + return [self pressPlayPauseMenuItem]; +} + +@end + +#pragma clang assume_nonnull end diff --git a/README.md b/README.md index f8c24627..8a703197 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ The auto-pause feature currently supports following music players: + [Hermes](http://hermesapp.org/) + [Swinsian](https://swinsian.com/) + [GPMDP](https://www.googleplaymusicdesktopplayer.com/) ++ [TIDAL](https://tidal.com/download) Adding support for a new music player is usually straightforward.[1](#f1) If you don't know how to program, or just don't feel like it, feel free to [create an issue](https://github.com/kyleneideck/BackgroundMusic/issues/new). Otherwise, see @@ -157,6 +158,12 @@ and check the box next to it. Background Music doesn't actually listen to your m the permission because it gets your system audio from its virtual input device, which macOS counts as a microphone. (We're working on it in [#177](/../../issues/177).) +If you use auto-pause with **TIDAL**, you also need to allow **Background Music** under +`System Settings > Privacy & Security > Accessibility`. TIDAL doesn't have an AppleScript API, so +Background Music pauses and unpauses it through its "Playback" menu using the macOS Accessibility +API, which requires that permission. Background Music asks for it when you select TIDAL as your +music player. + If the volume slider for an app isn't working, try looking in `More Apps` for entries like `Some App (Helper)`. For some meeting or video chat apps, you may need to do this to change the current meeting volume.