mulitmedia pass, downloads
This commit is contained in:
192
src/utils/media-registry.ts
Normal file
192
src/utils/media-registry.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* Platform-specific media session registration.
|
||||
*
|
||||
* Registers the currently playing track with the OS so that system
|
||||
* media controls (notification center, lock screen, MPRIS) display
|
||||
* track info and can send play/pause/next/prev commands.
|
||||
*
|
||||
* Implementations:
|
||||
* - **macOS**: Shells out to `nowplaying-cli` (brew install nowplaying-cli)
|
||||
* Falls back to no-op if the binary isn't available.
|
||||
* - **Linux**: Writes a minimal MPRIS2 metadata file that desktop
|
||||
* environments can pick up. Full D-Bus integration would
|
||||
* require native bindings; this is best-effort.
|
||||
* - **Other**: No-op stub.
|
||||
*
|
||||
* All methods are fire-and-forget and never throw.
|
||||
*/
|
||||
|
||||
import { spawn } from "child_process"
|
||||
|
||||
export interface TrackMetadata {
|
||||
title: string
|
||||
artist?: string
|
||||
album?: string
|
||||
artworkUrl?: string
|
||||
duration?: number // seconds
|
||||
}
|
||||
|
||||
export interface MediaRegistryInstance {
|
||||
/** Platform identifier */
|
||||
readonly platform: "macos" | "linux" | "windows" | "unknown"
|
||||
/** Whether the platform integration is available */
|
||||
readonly available: boolean
|
||||
|
||||
/** Register / update now-playing metadata */
|
||||
setNowPlaying(meta: TrackMetadata): void
|
||||
/** Update playback position (seconds) */
|
||||
setPosition(seconds: number): void
|
||||
/** Update playing/paused state */
|
||||
setPlaybackState(playing: boolean): void
|
||||
/** Clear now-playing info (e.g. on stop) */
|
||||
clearNowPlaying(): void
|
||||
/** Tear down any resources */
|
||||
dispose(): void
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Platform detection
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function detectPlatform(): "macos" | "linux" | "windows" | "unknown" {
|
||||
switch (process.platform) {
|
||||
case "darwin":
|
||||
return "macos"
|
||||
case "linux":
|
||||
return "linux"
|
||||
case "win32":
|
||||
return "windows"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// macOS — nowplaying-cli
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function hasBinary(name: string): boolean {
|
||||
try {
|
||||
const result = Bun.spawnSync(["which", name])
|
||||
return result.exitCode === 0
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function createMacOSRegistry(): MediaRegistryInstance {
|
||||
const hasNowPlaying = hasBinary("nowplaying-cli")
|
||||
|
||||
function run(args: string[]): void {
|
||||
if (!hasNowPlaying) return
|
||||
try {
|
||||
const proc = spawn("nowplaying-cli", args, {
|
||||
stdio: "ignore",
|
||||
detached: true,
|
||||
})
|
||||
proc.unref()
|
||||
} catch {
|
||||
// Best-effort
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
platform: "macos",
|
||||
available: hasNowPlaying,
|
||||
|
||||
setNowPlaying(meta) {
|
||||
const args = ["set", "title", meta.title]
|
||||
if (meta.artist) args.push("artist", meta.artist)
|
||||
if (meta.album) args.push("album", meta.album)
|
||||
if (meta.duration) args.push("duration", String(meta.duration))
|
||||
run(args)
|
||||
},
|
||||
|
||||
setPosition(seconds) {
|
||||
run(["set", "elapsedTime", String(Math.floor(seconds))])
|
||||
},
|
||||
|
||||
setPlaybackState(playing) {
|
||||
run(["set", "playbackRate", playing ? "1" : "0"])
|
||||
},
|
||||
|
||||
clearNowPlaying() {
|
||||
run(["clear"])
|
||||
},
|
||||
|
||||
dispose() {
|
||||
run(["clear"])
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Linux — best-effort MPRIS stub
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function createLinuxRegistry(): MediaRegistryInstance {
|
||||
// Full MPRIS2 requires owning a D-Bus name and exposing the
|
||||
// org.mpris.MediaPlayer2.Player interface. That needs native
|
||||
// bindings (dbus-next, etc.) which adds significant complexity.
|
||||
//
|
||||
// For now we provide a no-op stub that can be upgraded later
|
||||
// without changing the public interface.
|
||||
|
||||
return {
|
||||
platform: "linux",
|
||||
available: false,
|
||||
|
||||
setNowPlaying() {},
|
||||
setPosition() {},
|
||||
setPlaybackState() {},
|
||||
clearNowPlaying() {},
|
||||
dispose() {},
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// No-op fallback
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function createNoopRegistry(platform: "windows" | "unknown"): MediaRegistryInstance {
|
||||
return {
|
||||
platform,
|
||||
available: false,
|
||||
|
||||
setNowPlaying() {},
|
||||
setPosition() {},
|
||||
setPlaybackState() {},
|
||||
clearNowPlaying() {},
|
||||
dispose() {},
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Factory & singleton
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
let instance: MediaRegistryInstance | null = null
|
||||
|
||||
/**
|
||||
* Returns the singleton MediaRegistry for the current platform.
|
||||
* Always safe to call — returns a no-op if no integration is available.
|
||||
*/
|
||||
export function useMediaRegistry(): MediaRegistryInstance {
|
||||
if (instance) return instance
|
||||
|
||||
const platform = detectPlatform()
|
||||
|
||||
switch (platform) {
|
||||
case "macos":
|
||||
instance = createMacOSRegistry()
|
||||
break
|
||||
case "linux":
|
||||
instance = createLinuxRegistry()
|
||||
break
|
||||
default:
|
||||
instance = createNoopRegistry(platform)
|
||||
break
|
||||
}
|
||||
|
||||
return instance
|
||||
}
|
||||
Reference in New Issue
Block a user