Class: Charming::Audio::Player

Inherits:
Object
  • Object
show all
Defined in:
lib/charming/audio/player.rb

Overview

Player plays a single sound file by spawning a system audio binary, and exposes ‘stop`/`playing?`/`wait` to manage the child process. It never blocks the event loop on its own — call #play for fire-and-forget playback, or drive it from a controller `run_task` (spawn + #wait, with an `ensure player.stop`) to get a completion event and reliable teardown when the app quits.

A backend binary is resolved on first use, in priority order: ‘ffplay` (from ffmpeg) on every platform, then OS-native players (`afplay` on macOS; `paplay`, `mpg123`, `aplay` on Linux). Unavailable is raised when none are installed.

Defined Under Namespace

Classes: Unavailable

Constant Summary collapse

BACKENDS =

Candidate backends in resolution order. ‘:os` is `:any`, `:macos`, or `:linux`; `:args` are inserted before the file path in the spawned command.

[
  {command: "ffplay", os: :any, args: ["-nodisp", "-autoexit", "-loglevel", "quiet"]},
  {command: "afplay", os: :macos, args: []},
  {command: "paplay", os: :linux, args: []},
  {command: "mpg123", os: :linux, args: ["-q"]},
  {command: "aplay", os: :linux, args: ["-q"]}
].freeze

Instance Method Summary collapse

Constructor Details

#initialize(system: System.new) ⇒ Player

system is the OS adapter used to probe ‘PATH` and spawn/track the player process. The default talks to the real OS; specs inject a fake.



32
33
34
35
# File 'lib/charming/audio/player.rb', line 32

def initialize(system: System.new)
  @system = system
  @pid = nil
end

Instance Method Details

#available?Boolean

True when a backend binary is installed for this platform. Lets callers degrade gracefully (e.g. skip a chime) instead of rescuing Unavailable.

Returns:

  • (Boolean)


73
74
75
# File 'lib/charming/audio/player.rb', line 73

def available?
  !backend.nil?
end

#play(path) ⇒ Object

Plays the sound file at path, stopping any sound already in progress first. Spawns the resolved backend and returns the child PID. Raises Unavailable when no backend binary is installed for this platform.



40
41
42
43
44
# File 'lib/charming/audio/player.rb', line 40

def play(path)
  backend = resolve_backend!
  stop if playing?
  @pid = @system.spawn([backend[:command], *backend[:args], path.to_s])
end

#playing?Boolean

True while a spawned sound is still playing.

Returns:

  • (Boolean)


57
58
59
# File 'lib/charming/audio/player.rb', line 57

def playing?
  !@pid.nil? && @system.alive?(@pid)
end

#stopObject

Stops the current sound (if any), terminating and reaping the child process. Safe to call when nothing is playing.



48
49
50
51
52
53
54
# File 'lib/charming/audio/player.rb', line 48

def stop
  return unless @pid

  @system.terminate(@pid)
  @system.wait(@pid)
  @pid = nil
end

#waitObject

Blocks until the current sound finishes, then clears it. Intended for use inside a background ‘run_task`. If the task thread is killed mid-wait (e.g. on app shutdown), `@pid` is left intact so an `ensure player.stop` can reap the child.



64
65
66
67
68
69
# File 'lib/charming/audio/player.rb', line 64

def wait
  return unless @pid

  @system.wait(@pid)
  @pid = nil
end