2 Audio Player
| (require racket-audio/audio-player) | package: racket-audio |
The racket-audio/audio-player module is the high level interface for audio playback. It hides the command protocol of racket-audio/audio-placed-player, creates the playback place or thread, receives asynchronous events, and exposes a small handle-based API for starting, pausing, seeking, stopping, and observing playback.
The player is asynchronous. Playback commands are sent to a worker side and normally return after the command has been accepted, not after all audio has finished playing. State changes and end-of-stream notifications are delivered through callbacks supplied when the player is created.
2.1 Creating a player
procedure
(make-audio-player cb-state cb-eof-stream #:use-place use-place) → audio-play? cb-state : procedure? cb-eof-stream : procedure? use-place : boolean?
The cb-state callback is called as:
(cb-state player state-hash)
where player is the player handle and state-hash is the most recent state snapshot received from the worker side. The callback is called from the event thread created by make-audio-player.
The cb-eof-stream callback is called as:
(cb-eof-stream player)
when the decoder reports that the current stream has been read. This means that the decoder has finished queueing the stream. The audio device may still have buffered samples to play, and the logical player state may move to 'stopped slightly later when the output queue has drained.
When use-place is true, make-audio-player starts placed-player with dynamic-place and communicates with it through place channels. When use-place is false, the same command loop is started in an ordinary Racket thread and communicates through async channels. The default value is (place-enabled?).
The place-based mode is the normal playback mode. A place gives the audio side a separate Racket VM, so decoding and buffer feeding are less exposed to scheduling delays caused by DrRacket, GUI event handling, debugging, logging, or other active threads in the main VM. Those delays can otherwise be heard as clicks, gaps, or stuttering playback. Thread mode is useful for debugging the protocol and callbacks, but it is not the preferred mode for robust playback.
procedure
(audio-play? v) → boolean?
v : any/c
The predicate is intentionally stricter than merely recognizing the underlying structure. After audio-quit! or after the worker has died, the handle is invalidated and audio-play? returns #f.
2.2 Basic playback
procedure
(audio-play! player audio-file) → symbol?
player : audio-play? audio-file : path-string?
Calling audio-play! while another file is still active replaces the current stream. The worker side interrupts the old decoder, clears the output queue, waits for the old worker thread, and then starts the new stream.
procedure
(audio-pause! player paused?) → symbol?
player : audio-play? paused? : boolean?
Pause is implemented as player state observed by the worker. The worker translates the state to calls to the asynchronous audio backend.
procedure
(audio-paused? player) → boolean?
player : audio-play?
procedure
(audio-stop! player) → symbol?
player : audio-play?
The player remains valid after audio-stop!, so another audio-play! call can be used to start a new file.
procedure
(audio-quit! player) → (or/c number? boolean? symbol?)
player : audio-play?
After this procedure returns, the command loop in the place or thread is expected to terminate. Most other operations require audio-play? and therefore should not be used on the handle after quitting. The implementation also registers a finalizer that sends 'quit when a still-valid handle is collected, but explicit shutdown with audio-quit! is preferred.
2.3 Position, volume, and buffering
procedure
(audio-seek! player percentage) → symbol?
player : audio-play? percentage : (and/c number? (>=/c 0) (<=/c 100))
Seeking does not change the logical playback state. A playing stream remains playing, and a paused stream remains paused.
procedure
(audio-volume! player percentage) → symbol?
player : audio-play? percentage : (and/c number? (>=/c 0))
procedure
(audio-volume player) → number?
player : audio-play?
procedure
(audio-buf-seconds! player min max) → (or/c symbol? boolean?)
player : audio-play? min : number? max : number?
The wrapper normalizes the values before sending the command. A min below 1 is raised to 1, and a min above 10 is lowered to 10. A max below min is changed to (+ min 1), and a max above 30 is lowered to 30. The worker side applies its own safe ordering and clamping before using the values. In normal use the return value is 'ok.
2.4 State snapshots
The player keeps a local cache of the most recent state snapshot received from the worker. The cache is updated by an event thread created by make-audio-player. The state accessor procedures below read this local cache; they do not synchronously ask the worker for a fresh state.
Before the first state event has arrived, most accessors return #f. The logical state accessor returns 'initialized for a valid handle whose state hash does not yet contain a 'state entry. If the worker dies or the handle is explicitly invalidated, audio-state returns 'invalid.
procedure
(audio-full-state player) → hash?
player : audio-play?
procedure
(audio-state player) → symbol?
player : audio-play?
procedure
(audio-at-second player) → (or/c number? boolean?)
player : audio-play?
procedure
(audio-duration player) → (or/c number? boolean?)
player : audio-play?
procedure
(audio-file player) → (or/c path-string? boolean?)
player : audio-play?
procedure
(audio-music-id player) → (or/c number? boolean?)
player : audio-play?
procedure
(audio-bits player) → (or/c number? boolean?)
player : audio-play?
procedure
(audio-rate player) → (or/c number? boolean?)
player : audio-play?
procedure
(audio-channels player) → (or/c number? boolean?)
player : audio-play?
procedure
(audio-decoder player) → (or/c symbol? boolean?)
player : audio-play?
2.5 Events and callbacks
The wrapper receives asynchronous events from the worker side. A state event updates the cached state hash and calls cb-state. An 'audio-done event calls cb-eof-stream. Unknown events are reported through the module’s warning mechanism.
Callbacks run in the event thread owned by the player handle. They should therefore be quick, should not block for long periods, and should avoid performing complicated UI work directly. A GUI program can use the callbacks to enqueue work onto the GUI eventspace instead.
The RPC command path is protected by a mutex in the wrapper. This allows different application threads to call playback procedures on the same handle without interleaving the command and reply parts of a single RPC.
2.6 Example
The following example creates a player, prints state changes, plays a file, and then shuts the player down explicitly.
For a larger integration example, see "play-test.rkt". The queue variant in that file, selected with (set-test 'queue), is documented separately in "play-test.scrbl".
#lang racket/base (require "audio-player.rkt") (define player (make-audio-player (lambda (p st) (printf "state: ~a at ~a seconds\n" (hash-ref st 'state #f) (hash-ref st 'at-second #f))) (lambda (p) (printf "decoder reached end of stream\n")))) (audio-play! player "track.flac") ;; Later, for example in response to user input: (audio-pause! player #t) (audio-pause! player #f) (audio-seek! player 50) (audio-volume! player 80) (audio-stop! player) ;; When the player is no longer needed: (audio-quit! player)
For debugging the worker in the same Racket VM, create the player with #:use-place #f:
(define debug-player (make-audio-player (lambda (p st) (void)) (lambda (p) (void)) #:use-place #f))
This uses the same command loop and event handling, but starts the worker side in a normal Racket thread instead of a place.