Golang getting 'playing audio' system status - preferably cross platform (original) (raw)

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)
    }
}

ALSO - it looks like PulseAudio might be getting phased out. So you might need to look into pipewire.

OK so for Mac OS:

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.