On this page:
2.1 Creating a player
make-audio-player
audio-play?
2.2 Basic playback
audio-play!
audio-pause!
audio-paused?
audio-stop!
audio-quit!
2.3 Position, volume, and buffering
audio-seek!
audio-volume!
audio-volume
audio-buf-seconds!
2.4 State snapshots
audio-full-state
audio-state
audio-at-second
audio-duration
audio-file
audio-music-id
audio-bits
audio-rate
audio-channels
audio-decoder
2.5 Events and callbacks
2.6 Example
9.1

2 Audio Player🔗ℹ

Hans Dijkema <hans@dijkewijk.nl>

 (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?
Creates an audio player and returns a player handle. The handle is passed to all other procedures in this module.

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
Returns #t when v is a currently valid audio player handle.

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?
Starts playback of audio-file. The file is opened by the worker side, a decoder is selected, and audio feeding begins asynchronously. In normal use the return value is 'ok.

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?
Pauses or resumes playback. Passing #t moves the logical player state to 'paused. Passing #f moves it back to 'playing. In normal use the return value is 'ok.

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?
Returns whether the worker currently reports the logical player state as 'paused. This is an RPC-style query to the worker side.

procedure

(audio-stop! player)  symbol?

  player : audio-play?
Stops the current stream, clears the audio output queue, closes the active decoder and output handle when present, and returns the logical state to 'stopped. In normal use the return value is 'ok.

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?
Stops playback, performs the same cleanup as audio-stop!, sends a 'quit command to the worker side, and invalidates the handle. In normal use the return value is 'quit.

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))
Seeks the current stream to percentage, where 0 is the start and 100 is the end. The output queue is cleared before the decoder is asked to seek. In normal use the return value is 'ok.

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))
Requests a new playback volume. The value is stored by the worker and applied to the audio output side when audio is processed. In normal use the return value is 'ok.

procedure

(audio-volume player)  number?

  player : audio-play?
Returns the current volume value known by the worker.

procedure

(audio-buf-seconds! player min max)  (or/c symbol? boolean?)

  player : audio-play?
  min : number?
  max : number?
Configures the output buffering range, in seconds. The worker tries to keep the queued audio between the requested lower and upper bounds while decoding.

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?
Returns the complete cached state hash. The hash is the payload of the most recent 'state event. It may contain keys such as 'state, 'file, 'duration, 'at-second, 'at-music-id, 'volume, 'bits, 'rate, 'channels, 'decoder, 'decoder-meta, and 'decoder-buf-info.

procedure

(audio-state player)  symbol?

  player : audio-play?
Returns the cached logical player state. Typical values are 'initialized, 'stopped, 'playing, 'paused, and 'invalid.

procedure

(audio-at-second player)  (or/c number? boolean?)

  player : audio-play?
Returns the cached playback position in seconds, or #f when no position is known yet.

procedure

(audio-duration player)  (or/c number? boolean?)

  player : audio-play?
Returns the cached stream duration in seconds, or #f when the duration is not known.

procedure

(audio-file player)  (or/c path-string? boolean?)

  player : audio-play?
Returns the cached path of the file currently associated with the active music id, or #f when no such file is known.

procedure

(audio-music-id player)  (or/c number? boolean?)

  player : audio-play?
Returns the cached music id used by the asynchronous output side, or #f when no output handle is active.

procedure

(audio-bits player)  (or/c number? boolean?)

  player : audio-play?
Returns the cached sample width in bits, or #f when the format is not known.

procedure

(audio-rate player)  (or/c number? boolean?)

  player : audio-play?
Returns the cached sample rate, or #f when the format is not known.

procedure

(audio-channels player)  (or/c number? boolean?)

  player : audio-play?
Returns the cached channel count, or #f when the format is not known.

procedure

(audio-decoder player)  (or/c symbol? boolean?)

  player : audio-play?
Returns the cached decoder kind, or #f when no decoder kind is known.

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.