On this page:
5.1 Purpose
5.2 Selecting the test mode
5.3 Queue setup
5.4 Formatting state output
5.5 State callback
5.6 EOF callback and queue advancement
5.7 Creating the player
5.8 Starting the test
5.9 Integration pattern
9.1

5 Playback Test Program🔗ℹ

Hans Dijkema <hans@dijkewijk.nl>

 (require racket-audio/play-test) package: racket-audio

The racket-audio/play-test.rkt module is a small integration test and usage example for racket-audio/audio-player. It is not the public playback API itself; normal applications should use racket-audio/audio-player directly. This module shows how a program can create an audio player, observe state updates, react to end-of-stream events, and use the EOF callback to drive a simple playback queue.

The test is intentionally close to the way an application would use the high level player API. It creates one player handle with make-audio-player, prints compact progress information from the state callback, and starts the next file from the EOF callback when queue mode is enabled.

5.1 Purpose🔗ℹ

The test exercises three parts of the player wrapper:

  • state callback handling, including cached position, duration, buffer size, volume, and logical player state;

  • EOF callback handling, including starting another file after the current stream has reached decoder end-of-stream;

  • place based playback through make-audio-player’s #:use-place argument.

The file depends on "tests.rkt" for the concrete test files, such as test-file2, test-file3, and test-file4. The test therefore documents the integration pattern rather than a portable standalone program.

5.2 Selecting the test mode🔗ℹ

The module contains a small mode variable:

(define run-queue #f)
 
(define (set-test a)
  (set! run-queue a))

When run-queue is 'queue, the EOF callback consumes files from play-queue. When it is 'once, the first EOF callback starts test-file3 once and then disables that mode. With the default #f value, the final kickoff call does not start playback.

For queue playback, select queue mode before the kickoff call:

(set-test 'queue)

In the current test file the kickoff is performed by calling the EOF callback manually at the end of the module. That is a convenient test idiom: the same callback that advances the queue after a stream has finished is also reused to start the first stream.

5.3 Queue setup🔗ℹ

The queue itself is a simple list of path values supplied by "tests.rkt":

(define play-queue (list test-file2 test-file3 test-file4))

The queue is destructive in the ordinary Racket sense: each successful EOF advance starts (car play-queue) and then updates play-queue to (cdr play-queue). When the queue is empty, the callback shuts the player down with audio-quit!.

5.4 Formatting state output🔗ℹ

The helper to-time-str turns a second count into a compact mm:ss string:

(define (to-time-str s*)
  (let* ((s (round s*))
         (minutes (quotient s 60))
         (seconds (remainder s 60)))
    (sprintf "%02d:%02d" minutes seconds)))

The state callback uses this helper to print progress lines that are easier to read than raw seconds.

5.5 State callback🔗ℹ

The state callback has the shape expected by make-audio-player:

(define (audio-player-state h st)
  ...)

The first argument is the player handle and the second argument is the state hash received from the worker side. The callback begins with an early-return guard:

(early-return
 ((? (not (audio-play? h)) => 'done))
 ...)

This avoids using a handle after it has been invalidated, for example after audio-quit!. The rest of the callback reads the current file name, position, duration, logical player state, volume, buffer size, and diagnostic message. It prints at most one line per rounded second by comparing the current second with current-sec.

The output line is deliberately compact. It contains the current file name, music id, playback time, duration, logical state, volume, buffer size, and the message stored in the state hash.

5.6 EOF callback and queue advancement🔗ℹ

The EOF callback is where the queue behaviour is implemented:

(define (audio-player-eof h)
  (dbg-sound "audio-player-eof called")
  (when (eq? run-queue 'queue)
    (if (null? play-queue)
        (audio-quit! h)
        (begin
          (audio-play! h (car play-queue))
          (set! play-queue (cdr play-queue))))))

In queue mode, an empty queue means that playback is finished and the player is closed with audio-quit!. Otherwise the next file is started with audio-play! and removed from the queue.

The same callback also contains a small 'once mode:

(when (eq? run-queue 'once)
  (set! run-queue #f)
  (audio-play! h test-file3))

That mode is useful when testing a single explicit transition from an EOF event to a new file.

5.7 Creating the player🔗ℹ

The test creates the player with the two callbacks and an explicit place-mode flag:

(define place-mode #t)
 
(define h
  (make-audio-player audio-player-state
                     audio-player-eof
                     #:use-place place-mode))

With place-mode set to #t, the player runs the playback side in a separate place. This is the normal robustness mode for audio playback, because the decoder and audio feeder run in a separate Racket VM. Setting place-mode to #f runs the same command loop in a Racket thread with ordinary asynchronous channels, which can be easier to debug from DrRacket.

5.8 Starting the test🔗ℹ

At the end of the module, logging is sent to the display and the EOF callback is called once by hand:

(sl-log-to-display)
(audio-player-eof h)

Calling audio-player-eof manually may look unusual, but it keeps the queue logic in one place. The first call starts the first queued file; later calls are made by the player wrapper when the decoder reports end-of-stream.

A typical queue test therefore looks like this in the source:

(set-test 'queue)
 
(sl-log-to-display)
(audio-player-eof h)

5.9 Integration pattern🔗ℹ

The important pattern for an application is not the global variables in the test file, but the division of responsibility:

  • create one player with make-audio-player;

  • keep display or application state in the state callback;

  • keep queue advancement in the EOF callback;

  • use audio-play! to start the next file;

  • use audio-quit! when the queue is exhausted.

An application will usually wrap the queue in its own data structure instead of using a top-level mutable list, but the control flow is the same.