Does anyone know of a package to do this - I’ve tried searching - but it’s really hard to find a useful search.
Here is my use case - I want to be able to detect (at system level) when audio is playing (through another application/browser, etc.) so that I can pause audio to make an announcement, then start it again after the announcement.
Note that I’m using the system keyboard media control to send ‘audio_play’ to pause/resume - but if NO audio is playing, this will start the audio and play over the announcement, then pause it again afterwards…
Preferably cross platform - but windows only is better than nothing…
For Windows I believe you’re going to want to use WASAPI. Here are some Windows Core Audio bindings:
But - detecting whether audio is playing might not be adequate because it could be a system sound, etc. Anyway, I got Gemini to generate a completely broken example using this lib and I then got it working:
package main
import (
"fmt"
"log"
"time"
"unsafe"
"github.com/go-ole/go-ole"
"github.com/moutend/go-wca/pkg/wca"
)
func main() {
// 1. Initialize COM
if err := ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED); err != nil {
log.Fatal("Init error: ", err)
}
defer ole.CoUninitialize()
// 2. Create the Device Enumerator using ole.CreateInstance
// This function returns an *ole.IUnknown, which matches the memory layout
// of the specific interface we need, so we can cast it.
unknown, err := ole.CreateInstance(wca.CLSID_MMDeviceEnumerator, wca.IID_IMMDeviceEnumerator)
if err != nil {
log.Fatal("Could not create instance: ", err)
}
// Cast the generic IUnknown to the specific MMDeviceEnumerator
enumerator := (*wca.IMMDeviceEnumerator)(unsafe.Pointer(unknown))
defer enumerator.Release()
// 3. Get Default Speaker
var mmDevice *wca.IMMDevice
if err := enumerator.GetDefaultAudioEndpoint(wca.ERender, wca.EMultimedia, &mmDevice); err != nil {
log.Fatal("Endpoint error: ", err)
}
defer mmDevice.Release()
// 4. Activate Audio Meter
var audioMeter *wca.IAudioMeterInformation
if err := mmDevice.Activate(wca.IID_IAudioMeterInformation, wca.CLSCTX_ALL, nil, &audioMeter); err != nil {
log.Fatal("Activate error: ", err)
}
defer audioMeter.Release()
fmt.Println("Listening on Windows Default Output...")
// 5. Loop and measure
for {
var peak float32
if err := audioMeter.GetPeakValue(&peak); err != nil {
log.Printf("Error reading peak: %v", err)
continue
}
// Visualizer
bars := int(peak * 30)
visual := ""
for i := 0; i < bars; i++ {
visual += "█"
}
fmt.Printf("\rVol: [%-30s] %.2f", visual, peak)
time.Sleep(50 * time.Millisecond)
}
}
That worked fine for me in Windows 10.
Every linux distro I’ve used has used PulseAudio. You could try using the pulse audio CLI. Something like this (completely untested so take with a grain of salt!):
package main
import (
"bytes"
"fmt"
"os"
"os/exec"
"strings"
"time"
)
func isAudioPlayingLinux() bool {
// We use pactl to list sink inputs (apps playing sound)
cmd := exec.Command("pactl", "list", "sink-inputs")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
fmt.Printf("ERROR: %v\n", err)
os.Exit(1)
}
// Check if any input is "Running" (actively playing)
// "State: RUNNING" indicates active playback.
// "State: CORKED" indicates paused.
return strings.Contains(out.String(), "State: RUNNING")
}
func main() {
for {
if isAudioPlayingLinux() {
fmt.Println("Audio is playing!")
} else {
fmt.Println("Silence.")
}
time.Sleep(1 * time.Second)
}
}
macOS is the most difficult platform for this task. Apple creates a strict separation between applications. You cannot easily query the “Master Volume Meter” without writing a Kernel Extension (deprecated) or an Audio Server Plugin (complex C/C++).
The Workaround: If you simply need to know if the user is playing music via Music.app or Spotify, you can use AppleScript via Go. If you need to detect system-wide audio (like YouTube in Chrome), there is no native Go solution. You would likely need to use CGo to bridge into CoreAudio AudioObjectGetPropertyData, which is non-trivial.
func isMusicPlayingMac() bool {
cmd := exec.Command("osascript", "-e", "tell application \"Music\" to player state as string")
out, _ := cmd.Output()
return strings.TrimSpace(string(out)) == "playing"
}
I tested this and it works, but it asks for permissions on MacOS.
Oh and I forgot to mention: if you want a cross-platform solution, normalize these functions and test them. Then use build tags to separate out implementations by OS: