How Vorssaint-utils Implements Now Playing Media Controls on macOS

Vorssaint-utils implements Now Playing media controls through an out-of-process bridge that combines a Swift-compiled dynamic library, a Perl wrapper, and a bridge runner to circumvent macOS 15.4+ code signature restrictions on the private MediaRemote framework.

Vorssaint-utils is an open-source macOS utility that displays current media playback information through its radial menu interface. Because macOS 15.4 and later restrict direct access to the MediaRemote framework to Apple-signed processes only, the project employs a sophisticated architectural workaround to capture real-time Now Playing data. This article examines the complete implementation across the adapter library, Perl bridge, and SwiftUI service layers.

The macOS 15.4+ Signature Restriction

Apple's MediaRemote framework is a private API that provides access to system-wide media playback information. Starting with macOS 15.4, the framework enforces code signature validation, returning data only to processes signed by Apple. Since Vorssaint-utils cannot carry Apple's code signature, it cannot link against MediaRemote directly. Instead, the project uses an out-of-process bridge architecture that isolates the sensitive framework calls into a separately compiled dynamic library.

Architecture Overview

The Now Playing implementation follows a multi-stage pipeline to transport data from the private framework to the SwiftUI interface:


RadialNowPlayingService → MediaRemoteNowPlayingBridge → /usr/bin/perl → now-playing.pl → libVorssaintNowPlaying.dylib → MediaRemote framework → JSON → RadialNowPlayingSnapshot → UI

This flow circumvents the code signature restriction by executing the framework-calling code in a separate process while maintaining clean data transfer via JSON.

The Adapter Library (libVorssaintNowPlaying.dylib)

The core of the workaround resides in Sources/NowPlayingAdapter/NowPlayingAdapter.swift, which compiles into libVorssaintNowPlaying.dylib. This dynamic library directly invokes private MediaRemote C-functions and exposes a C entry point for external callers.

Loading Private Framework Symbols

The library uses dlopen to load the MediaRemote framework at runtime and dlsym to resolve function symbols. A generic helper function handles this dynamic linking:

// Lines 33-36 in NowPlayingAdapter.swift
func function<T>(_ name: String) -> T {
    // dlopen MediaRemote.framework and dlsym the symbol
}

Gathering Now Playing Data

The vorssaintNowPlayingGet function (lines 44-111) orchestrates data collection using MRMediaRemoteGetNowPlayingInfo, MRMediaRemoteGetNowPlayingApplicationPID, and related callbacks. It utilizes a dispatch group to synchronize asynchronous metadata retrieval, collecting the title, artist, album, playback rate, optional artwork (base-64-encoded), PID, display ID, and playing state.

JSON Serialization

Once collected, the metadata dictionary is serialized to a single-line JSON string printed to stdout via the emit helper (lines 38-42). This format ensures safe, parseable data transfer between the Perl wrapper and the Swift bridge.

The Perl Bridge Layer

A Perl script located at Resources/now-playing.pl serves as the intermediary between the compiled library and the main application. The script uses DynaLoader to load libVorssaintNowPlaying.dylib at runtime and exposes the C function vorssaint_now_playing_get. When invoked, it triggers the Swift code, captures the JSON output line, and forwards it to stdout for the bridge runner to consume.

The Bridge Runner (MediaRemoteNowPlayingBridge)

Within Sources/Vorssaint/Services/RadialMenu/RadialNowPlayingService.swift, the MediaRemoteNowPlayingBridge class manages the out-of-process execution. It launches /usr/bin/perl with the script path and compiled library location, enforces a strict 2-second timeout to prevent hanging, and parses the returned JSON into a RadialNowPlayingSnapshot struct (lines 95-108).

Key implementation details:

  • Process spawning: Uses Process API to execute the Perl interpreter
  • Timeout handling: Terminates the process if it exceeds 2 seconds
  • JSON parsing: Converts the single-line JSON output into a strongly-typed Swift struct

Service and UI Integration

RadialNowPlayingService Singleton

The RadialNowPlayingService acts as the primary interface for the UI layer. As a singleton (lines 12-41), it requests fresh snapshots from the bridge, caches results, and drives the RadialNowPlayingCard SwiftUI view. The service implements state management through a refresh method (lines 62-91) that handles loading states, successful playback detection, and "nothing playing" scenarios.

Opening the Source Application

When users click the Now Playing card, the service resolves the originating application via bundle ID or PID and opens it using RadialNowPlayingApplication.open(snapshot) (lines 124-132, 136-188). This creates a seamless workflow where users can jump directly to the media source from the radial menu.

Implementation Code Examples

Fetching a snapshot manually using the bridge:

let bridge = MediaRemoteNowPlayingBridge()
bridge.fetch { snapshot in
    if let s = snapshot {
        print("Now playing: \(s.title ?? "-") by \(s.artist ?? "-")")
    } else {
        print("No media playing")
    }
}

Using the high-level service as implemented in the application:

RadialNowPlayingService.shared.refresh { state in
    switch state {
    case .playing(let snapshot):
        print("Now playing:", snapshot.title ?? "-")
    case .loading:
        print("Loading now-playing info…")
    case .nothingPlaying:
        print("Nothing is playing")
    }
}

Opening the originating application from the UI card:

RadialNowPlayingApplication.open(snapshot)

Summary

  • Signature workaround: Vorssaint-utils cannot access MediaRemote directly due to macOS 15.4+ restrictions, so it uses an out-of-process bridge architecture.
  • Dynamic library: libVorssaintNowPlaying.dylib (built from NowPlayingAdapter.swift) handles raw MediaRemote framework calls and emits JSON metadata.
  • Perl wrapper: Resources/now-playing.pl loads the dynamic library via DynaLoader and forwards output to the bridge runner.
  • Bridge execution: MediaRemoteNowPlayingBridge spawns the Perl process with a 2-second timeout and parses the resulting RadialNowPlayingSnapshot.
  • UI integration: RadialNowPlayingService manages state, caching, and presentation through RadialNowPlayingCard, plus application launching via RadialNowPlayingApplication.

Frequently Asked Questions

Why doesn't Vorssaint-utils access MediaRemote directly?

macOS 15.4 and later require Apple code signatures to receive data from the private MediaRemote framework. As a third-party application, Vorssaint-utils carries a different code signature, causing the framework to return empty results. The out-of-process bridge isolates the framework calls into a separately compiled component that runs under the required constraints.

What data fields are available in the Now Playing snapshot?

The RadialNowPlayingSnapshot struct contains the track title, artist name, album name, current playback rate, optional artwork data (base-64-encoded), the originating application's PID, the display ID where playback is occurring, and a boolean indicating whether media is currently playing.

How does the bridge handle cases where no media is playing?

When MRMediaRemoteGetNowPlayingInfo returns no data, the adapter library emits a JSON structure indicating the absence of playback. The MediaRemoteNowPlayingBridge parses this as a nil or empty snapshot, which RadialNowPlayingService translates into the .nothingPlaying state for UI presentation.

Is the dynamic library signed differently than the main application?

The libVorssaintNowPlaying.dylib is built and signed independently from the main Vorssaint-utils binary. This separation is crucial because it allows the library to be loaded by the Perl interpreter (a system process with distinct entitlements) while still executing code that interfaces with the restricted MediaRemote framework.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →