mirror of
https://github.com/kyleneideck/BackgroundMusic
synced 2024-11-14 00:17:15 +00:00
commit
9441a31d9d
17 changed files with 40 additions and 36 deletions
|
@ -991,10 +991,9 @@
|
|||
};
|
||||
buildConfigurationList = 1CB8B3311BBA75EF000E2DD1 /* Build configuration list for PBXProject "BGMApp" */;
|
||||
compatibilityVersion = "Xcode 6.3";
|
||||
developmentRegion = English;
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
English,
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
|
@ -1400,6 +1399,7 @@
|
|||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
|
||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
|
@ -1509,6 +1509,7 @@
|
|||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
|
||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
|
@ -1593,6 +1594,7 @@
|
|||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
|
||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
|
|
|
@ -326,7 +326,7 @@ static NSString* const kMoreAppsMenuTitle = @"More Apps";
|
|||
menuItem.view.frameSize = NSMakeSize(width, appVolumeViewFullHeight);
|
||||
// Turn the button rightside up so the arrowhead points up.
|
||||
button.frameCenterRotation = 0.0;
|
||||
// Move the button down slightly, back to it's original position.
|
||||
// Move the button down slightly, back to its original position.
|
||||
[button setFrameOrigin:NSMakePoint(button.frame.origin.x, button.frame.origin.y + 1)];
|
||||
|
||||
// Set all of the UI elements in the menu item to "not hidden" for accessibility clients.
|
||||
|
|
|
@ -113,7 +113,7 @@ NSString* const kAudioSystemSettingsPlist =
|
|||
// BGMApp's stored preferred devices to fill in the rest optimistically. This doesn't help us
|
||||
// tell when to switch to a newly connected device, but it should improve our chances of
|
||||
// switching to the best device if the current output device is disconnected.
|
||||
NSArray<NSDictionary*>* preferredOutputDeviceInfos = @[];
|
||||
NSArray<NSDictionary*>* _Nonnull preferredOutputDeviceInfos = @[];
|
||||
|
||||
// If we can't read the Plist, we only know that the current systemwide default device is the
|
||||
// most-preferred device that's currently connected.
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
//
|
||||
// Copyright © 2017 Kyle Neideck
|
||||
//
|
||||
// Cleans up if BGMApp crashes because of an uncaught C++ or Objective C exception, or is sent
|
||||
// Cleans up if BGMApp crashes because of an uncaught C++ or Objective-C exception, or is sent
|
||||
// SIGINT/SIGTERM/SIGQUIT. Currently, it just changes the default output device from BGMDevice to
|
||||
// the real output device and records debug info for some types of crashes.
|
||||
//
|
||||
|
|
|
@ -83,7 +83,7 @@ void BGMTermination::SetUpTerminationCleanUp(BGMAudioDeviceManager* inAudioDevic
|
|||
StartExitSignalsThread();
|
||||
|
||||
// Wrap the default handler for std::terminate, which is called if BGMApp crashes because of an
|
||||
// uncaught C++ or Objective C exception, so we can clean up first.
|
||||
// uncaught C++ or Objective-C exception, so we can clean up first.
|
||||
sOriginalTerminateHandler = std::get_terminate();
|
||||
|
||||
std::set_terminate([] {
|
||||
|
|
|
@ -146,7 +146,7 @@
|
|||
- (void) initSelectedMusicPlayerFromBGMDevice {
|
||||
// When the selected music player setting hasn't been stored in user defaults yet, we get the music player
|
||||
// bundle ID from the driver and look for the music player with that bundle ID. This is mainly done for
|
||||
// backwards compatability.
|
||||
// backwards compatibility.
|
||||
|
||||
NSString* __nullable bundleID =
|
||||
(__bridge_transfer NSString* __nullable)[audioDevices bgmDevice].GetMusicPlayerBundleID();
|
||||
|
|
|
@ -221,7 +221,7 @@ typedef enum VLCEnum VLCEnum;
|
|||
@property NSInteger audioVolume; // The volume of the current playlist item from 0 to 4, where 4 is 400%
|
||||
@property NSInteger currentTime; // The current time of the current playlist item in seconds.
|
||||
@property (readonly) NSInteger durationOfCurrentItem; // The duration of the current playlist item in seconds.
|
||||
@property BOOL fullscreenMode; // indicates wheter fullscreen is enabled or not
|
||||
@property BOOL fullscreenMode; // indicates whether fullscreen is enabled or not
|
||||
@property (readonly) BOOL muted; // Is VLC currently muted?
|
||||
@property (copy, readonly) NSString *nameOfCurrentItem; // Name of the current playlist item.
|
||||
@property (copy, readonly) NSString *pathOfCurrentItem; // Path to the current playlist item.
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
- (void) rewindForwardFast; // Rewind current track forward fast.
|
||||
- (void) rewindBackward; // Rewind current track backward.
|
||||
- (void) rewindBackwardFast; // Rewind current track backward fast.
|
||||
- (void) increasVolume; // Increas volume.
|
||||
- (void) increasVolume; // Increase volume.
|
||||
- (void) decreaseVolume; // Decrease volume.
|
||||
- (void) showHidePlaylist; // Show/Hide playlist.
|
||||
|
||||
|
@ -62,7 +62,7 @@
|
|||
@property (copy, readonly) NSString *album; // Current track album.
|
||||
@property (copy, readonly) NSString *uniqueID; // Unique identifier for the current track.
|
||||
@property double currentTime; // The current playback position.
|
||||
@property (readonly) double totalTime; // The total time of the currenty playing track.
|
||||
@property (readonly) double totalTime; // The total time of the currently playing track.
|
||||
@property double playerVolume; // Player volume (0.0 to 1.0)
|
||||
@property NSInteger repeatState; // Player repeat state (none = 0, repeat one = 1, repeat all = 2)
|
||||
|
||||
|
|
|
@ -299,11 +299,11 @@ void* NullAudio_Create(CFAllocatorRef inAllocator, CFUUIDRef inRequestedTypeUUID
|
|||
{
|
||||
// This is the CFPlugIn factory function. Its job is to create the implementation for the given
|
||||
// type provided that the type is supported. Because this driver is simple and all its
|
||||
// initialization is handled via static iniitalization when the bundle is loaded, all that
|
||||
// initialization is handled via static initialization when the bundle is loaded, all that
|
||||
// needs to be done is to return the AudioServerPlugInDriverRef that points to the driver's
|
||||
// interface. A more complicated driver would create any base line objects it needs to satisfy
|
||||
// the IUnknown methods that are used to discover that actual interface to talk to the driver.
|
||||
// The majority of the driver's initilization should be handled in the Initialize() method of
|
||||
// The majority of the driver's initialization should be handled in the Initialize() method of
|
||||
// the driver's AudioServerPlugInDriverInterface.
|
||||
|
||||
#pragma unused(inAllocator)
|
||||
|
@ -338,7 +338,7 @@ static HRESULT NullAudio_QueryInterface(void* inDriver, REFIID inUUID, LPVOID* o
|
|||
FailWithAction(theRequestedUUID == NULL, theAnswer = kAudioHardwareIllegalOperationError, Done, "NullAudio_QueryInterface: failed to create the CFUUIDRef");
|
||||
|
||||
// AudioServerPlugIns only support two interfaces, IUnknown (which has to be supported by all
|
||||
// CFPlugIns and AudioServerPlugInDriverInterface (which is the actual interface the HAL will
|
||||
// CFPlugIns) and AudioServerPlugInDriverInterface (which is the actual interface the HAL will
|
||||
// use).
|
||||
if(CFEqual(theRequestedUUID, IUnknownUUID) || CFEqual(theRequestedUUID, kAudioServerPlugInDriverInterfaceUUID))
|
||||
{
|
||||
|
@ -415,7 +415,7 @@ static OSStatus NullAudio_Initialize(AudioServerPlugInDriverRef inDriver, AudioS
|
|||
// The job of this method is, as the name implies, to get the driver initialized. One specific
|
||||
// thing that needs to be done is to store the AudioServerPlugInHostRef so that it can be used
|
||||
// later. Note that when this call returns, the HAL will scan the various lists the driver
|
||||
// maintains (such as the device list) to get the inital set of objects the driver is
|
||||
// maintains (such as the device list) to get the initial set of objects the driver is
|
||||
// publishing. So, there is no need to notifiy the HAL about any objects created as part of the
|
||||
// execution of this method.
|
||||
|
||||
|
@ -553,7 +553,7 @@ Done:
|
|||
|
||||
static OSStatus NullAudio_PerformDeviceConfigurationChange(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt64 inChangeAction, void* inChangeInfo)
|
||||
{
|
||||
// This method is called to tell the device that it can perform the configuation change that it
|
||||
// This method is called to tell the device that it can perform the configuration change that it
|
||||
// had requested via a call to the host method, RequestDeviceConfigurationChange(). The
|
||||
// arguments, inChangeAction and inChangeInfo are the same as what was passed to
|
||||
// RequestDeviceConfigurationChange().
|
||||
|
@ -1546,7 +1546,7 @@ static OSStatus NullAudio_GetBoxPropertyData(AudioServerPlugInDriverRef inDriver
|
|||
break;
|
||||
|
||||
case kAudioObjectPropertyIdentify:
|
||||
// This is used to highling the device in the UI, but it's value has no meaning
|
||||
// This is used to highling the device in the UI, but its value has no meaning
|
||||
FailWithAction(inDataSize < sizeof(UInt32), theAnswer = kAudioHardwareBadPropertySizeError, Done, "NullAudio_GetBoxPropertyData: not enough space for the return value of kAudioObjectPropertyIdentify for the box");
|
||||
*((UInt32*)outData) = 0;
|
||||
*outDataSize = sizeof(UInt32);
|
||||
|
@ -1696,9 +1696,9 @@ static OSStatus NullAudio_SetBoxPropertyData(AudioServerPlugInDriverRef inDriver
|
|||
break;
|
||||
|
||||
case kAudioObjectPropertyIdentify:
|
||||
// since we don't have any actual hardware to flash, we will schedule a notificaiton for
|
||||
// since we don't have any actual hardware to flash, we will schedule a notification for
|
||||
// this property off into the future as a testing thing. Note that a real implementation
|
||||
// of this property should only send the notificaiton if the hardware wants the app to
|
||||
// of this property should only send the notification if the hardware wants the app to
|
||||
// flash it's UI for the device.
|
||||
{
|
||||
syslog(LOG_NOTICE, "The identify property has been set on the Box implemented by the NullAudio driver.");
|
||||
|
@ -2216,7 +2216,7 @@ static OSStatus NullAudio_GetDevicePropertyData(AudioServerPlugInDriverRef inDri
|
|||
|
||||
case kAudioDevicePropertyDeviceIsAlive:
|
||||
// This property returns whether or not the device is alive. Note that it is
|
||||
// note uncommon for a device to be dead but still momentarily availble in the
|
||||
// note uncommon for a device to be dead but still momentarily available in the
|
||||
// device list. In the case of this device, it will always be alive.
|
||||
FailWithAction(inDataSize < sizeof(UInt32), theAnswer = kAudioHardwareBadPropertySizeError, Done, "NullAudio_GetDevicePropertyData: not enough space for the return value of kAudioDevicePropertyDeviceIsAlive for the device");
|
||||
*((UInt32*)outData) = 1;
|
||||
|
@ -2763,7 +2763,7 @@ static OSStatus NullAudio_GetStreamPropertyData(AudioServerPlugInDriverRef inDri
|
|||
|
||||
case kAudioStreamPropertyStartingChannel:
|
||||
// This property returns the absolute channel number for the first channel in
|
||||
// the stream. For exmaple, if a device has two output streams with two
|
||||
// the stream. For example, if a device has two output streams with two
|
||||
// channels each, then the starting channel number for the first stream is 1
|
||||
// and ths starting channel number fo the second stream is 3.
|
||||
FailWithAction(inDataSize < sizeof(UInt32), theAnswer = kAudioHardwareBadPropertySizeError, Done, "NullAudio_GetStreamPropertyData: not enough space for the return value of kAudioStreamPropertyStartingChannel for the stream");
|
||||
|
@ -2772,7 +2772,7 @@ static OSStatus NullAudio_GetStreamPropertyData(AudioServerPlugInDriverRef inDri
|
|||
break;
|
||||
|
||||
case kAudioStreamPropertyLatency:
|
||||
// This property returns any additonal presentation latency the stream has.
|
||||
// This property returns any additional presentation latency the stream has.
|
||||
FailWithAction(inDataSize < sizeof(UInt32), theAnswer = kAudioHardwareBadPropertySizeError, Done, "NullAudio_GetStreamPropertyData: not enough space for the return value of kAudioStreamPropertyStartingChannel for the stream");
|
||||
*((UInt32*)outData) = 0;
|
||||
*outDataSize = sizeof(UInt32);
|
||||
|
|
|
@ -459,11 +459,11 @@
|
|||
};
|
||||
buildConfigurationList = 1CB8B35D1BBBB69C000E2DD1 /* Build configuration list for PBXProject "BGMDriver" */;
|
||||
compatibilityVersion = "Xcode 6.3";
|
||||
developmentRegion = English;
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
English,
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 1CB8B3591BBBB69C000E2DD1;
|
||||
productRefGroup = 1CB8B3651BBBB78D000E2DD1 /* Products */;
|
||||
|
@ -672,6 +672,7 @@
|
|||
CLANG_WARN_NULLABLE_TO_NONNULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
|
@ -739,6 +740,7 @@
|
|||
CLANG_WARN_NULLABLE_TO_NONNULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
|
|
|
@ -175,7 +175,7 @@ bool BGM_AudibleState::BufferIsAudible(UInt32 inIOBufferFrameSize, const Floa
|
|||
// Check each frame to see if any are audible. This could be much more accurate, but seems to
|
||||
// work well enough for now.
|
||||
//
|
||||
// The trade off here is between pausing the music player at the wrong time and unpausing it at
|
||||
// The trade-off here is between pausing the music player at the wrong time and unpausing it at
|
||||
// the wrong time. If a short sound (e.g. a UI alert) plays but has a long, barely-audible tail,
|
||||
// we might not detect the silence quickly enough and pause the music player. Similarly, if
|
||||
// we've paused the music player and there's a period of near-silence in the new audio, we might
|
||||
|
|
|
@ -68,8 +68,8 @@
|
|||
// not need to worry about being thread safe while Activate() is in progress.
|
||||
//
|
||||
// Subclasses of this class must also implement Deactivate(). This method is called when the object
|
||||
// is at the end of it's lifecycle. Once Deactivate() has been called, the object may no longer
|
||||
// perform active opertions, including Deactivating other objects. This is based on the notion that
|
||||
// is at the end of its lifecycle. Once Deactivate() has been called, the object may no longer
|
||||
// perform active operations, including Deactivating other objects. This is based on the notion that
|
||||
// all the objects have a definite point at which they are considered dead to the outside world.
|
||||
// For example, an AudioDevice object is dead if it's hardware is unplugged. The point of death is
|
||||
// the notification the owner of the device gets to signal that it has been unplugged. Note that it
|
||||
|
|
|
@ -153,11 +153,11 @@ void* BGM_Create(CFAllocatorRef inAllocator, CFUUIDRef inRequestedTypeUUID)
|
|||
{
|
||||
// This is the CFPlugIn factory function. Its job is to create the implementation for the given
|
||||
// type provided that the type is supported. Because this driver is simple and all its
|
||||
// initialization is handled via static initalization when the bundle is loaded, all that
|
||||
// initialization is handled via static initialization when the bundle is loaded, all that
|
||||
// needs to be done is to return the AudioServerPlugInDriverRef that points to the driver's
|
||||
// interface. A more complicated driver would create any base line objects it needs to satisfy
|
||||
// the IUnknown methods that are used to discover that actual interface to talk to the driver.
|
||||
// The majority of the driver's initilization should be handled in the Initialize() method of
|
||||
// The majority of the driver's initialization should be handled in the Initialize() method of
|
||||
// the driver's AudioServerPlugInDriverInterface.
|
||||
|
||||
#pragma unused(inAllocator)
|
||||
|
@ -196,7 +196,7 @@ static HRESULT BGM_QueryInterface(void* inDriver, REFIID inUUID, LPVOID* outInte
|
|||
ThrowIf(theRequestedUUID == NULL, CAException(kAudioHardwareIllegalOperationError), "BGM_QueryInterface: failed to create the CFUUIDRef");
|
||||
|
||||
// AudioServerPlugIns only support two interfaces, IUnknown (which has to be supported by all
|
||||
// CFPlugIns and AudioServerPlugInDriverInterface (which is the actual interface the HAL will
|
||||
// CFPlugIns) and AudioServerPlugInDriverInterface (which is the actual interface the HAL will
|
||||
// use).
|
||||
ThrowIf(!CFEqual(theRequestedUUID, IUnknownUUID) && !CFEqual(theRequestedUUID, kAudioServerPlugInDriverInterfaceUUID), CAException(E_NOINTERFACE), "BGM_QueryInterface: requested interface is unsupported");
|
||||
ThrowIf(gAudioServerPlugInDriverRefCount == UINT32_MAX, CAException(E_NOINTERFACE), "BGM_QueryInterface: the ref count is maxxed out");
|
||||
|
@ -268,7 +268,7 @@ static OSStatus BGM_Initialize(AudioServerPlugInDriverRef inDriver, AudioServerP
|
|||
// The job of this method is, as the name implies, to get the driver initialized. One specific
|
||||
// thing that needs to be done is to store the AudioServerPlugInHostRef so that it can be used
|
||||
// later. Note that when this call returns, the HAL will scan the various lists the driver
|
||||
// maintains (such as the device list) to get the inital set of objects the driver is
|
||||
// maintains (such as the device list) to get the initial set of objects the driver is
|
||||
// publishing. So, there is no need to notifiy the HAL about any objects created as part of the
|
||||
// execution of this method.
|
||||
|
||||
|
@ -397,7 +397,7 @@ static OSStatus BGM_RemoveDeviceClient(AudioServerPlugInDriverRef inDriver, Audi
|
|||
|
||||
static OSStatus BGM_PerformDeviceConfigurationChange(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt64 inChangeAction, void* inChangeInfo)
|
||||
{
|
||||
// This method is called to tell the device that it can perform the configuation change that it
|
||||
// This method is called to tell the device that it can perform the configuration change that it
|
||||
// had requested via a call to the host method, RequestDeviceConfigurationChange(). The
|
||||
// arguments, inChangeAction and inChangeInfo are the same as what was passed to
|
||||
// RequestDeviceConfigurationChange().
|
||||
|
|
|
@ -287,7 +287,7 @@ void BGM_Stream::GetPropertyData(AudioObjectID inObjectID,
|
|||
break;
|
||||
|
||||
case kAudioStreamPropertyLatency:
|
||||
// This property returns any additonal presentation latency the stream has.
|
||||
// This property returns any additional presentation latency the stream has.
|
||||
ThrowIf(inDataSize < sizeof(UInt32),
|
||||
CAException(kAudioHardwareBadPropertySizeError),
|
||||
"BGM_Stream::GetPropertyData: not enough space for the return "
|
||||
|
|
|
@ -53,7 +53,7 @@ class BGM_ClientTasks;
|
|||
// removed by the HAL we add it to a map of past clients to keep track of settings specific to that
|
||||
// client. (Currently only the client's volume.)
|
||||
//
|
||||
// Since the maps are read from during IO, this class has to to be real-time safe when accessing
|
||||
// Since the maps are read from during IO, this class has to be real-time safe when accessing
|
||||
// them. So each map has an identical "shadow" map, which we use to buffer updates.
|
||||
//
|
||||
// To update the clients we lock the shadow maps, modify them, have BGM_TaskQueue's real-time
|
||||
|
|
|
@ -132,7 +132,7 @@ private:
|
|||
// as one at a later time.
|
||||
pid_t mMusicPlayerProcessIDProperty = 0;
|
||||
|
||||
// The value of the kAudioDeviceCustomPropertyMusicPlayerBundleID property, or the empty string it it's
|
||||
// The value of the kAudioDeviceCustomPropertyMusicPlayerBundleID property, or the empty string if it's
|
||||
// unset/null. UTF8 encoding.
|
||||
//
|
||||
// As with mMusicPlayerProcessID, we keep a copy of the bundle ID the user sets for the music player
|
||||
|
|
|
@ -201,7 +201,7 @@ Issues](https://github.com/kyleneideck/BackgroundMusic/issues).
|
|||
- [llaudio](https://github.com/mountainstorm/llaudio) - "An old piece of work to reverse engineer the Mac OSX
|
||||
user/kernel audio interface. Shows how to read audio straight out of the kernel as you would on Darwin (where most the
|
||||
OSX goodness is missing)"
|
||||
- [mute.fm](http://www.mute.fm), [GitHub](https://github.com/jaredsohn/mutefm) (Windows) - Auto-pause music
|
||||
- [mute.fm](http://www.mutefm.com), [GitHub](https://github.com/jaredsohn/mutefm) (Windows) - Auto-pause music
|
||||
- [Jack OS X](http://www.jackosx.com) - "A Jack audio connection kit implementation for Mac OS X"
|
||||
- [PulseAudio OS X](https://github.com/zonque/PulseAudioOSX) - "PulseAudio for Mac OS X"
|
||||
- [Sound Pusher](https://github.com/q-p/SoundPusher) - "Virtual audio device, real-time encoder and SPDIF forwarder for
|
||||
|
@ -216,7 +216,7 @@ Issues](https://github.com/kyleneideck/BackgroundMusic/issues).
|
|||
Audio From Anywhere on Your Mac", "Get truly powerful control over all the audio on your Mac!"
|
||||
- [Sound Siphon](https://staticz.com/soundsiphon/), [Sound Control](https://staticz.com/soundcontrol/) - System/app audio recording, per-app volumes, system audio equaliser
|
||||
- [SoundBunny](https://www.prosofteng.com/soundbunny-mac-volume-control/) - "Control application volume independently."
|
||||
- [Boom 2](http://www.globaldelight.com/boom/index.php) - "The Best Volume Booster & Equalizer For Mac"
|
||||
- [Boom 2](https://www.globaldelight.com/boom/) - "The Best Volume Booster & Equalizer For Mac"
|
||||
|
||||
## License
|
||||
|
||||
|
|
Loading…
Reference in a new issue