Remote usage of audio-placed-player.rkt
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
racket/contract
|
||||
racket/place
|
||||
racket/async-channel
|
||||
uni-channel
|
||||
port-channel
|
||||
"../audio-placed-player.rkt"
|
||||
"../audio-player.rkt"))
|
||||
|
||||
@@ -30,9 +32,11 @@ The placed player is implemented as a command loop around a decoder, an
|
||||
asynchronous libao output handle, and a small amount of state that is reported
|
||||
back to the controlling side. In normal use it runs in a Racket place, so that
|
||||
the audio side has a separate Racket VM. The same function can also run in a
|
||||
normal Racket thread with async channels. That mode is useful for debugging,
|
||||
because the player then stays in the same process and can be inspected more
|
||||
easily.
|
||||
normal Racket thread with async channels. Both modes are mediated through
|
||||
@racketmodname[uni-channel]: each of the existing logical channels is wrapped as
|
||||
a uni-channel endpoint, so the worker no longer depends directly on
|
||||
@racket[place-channel-put], @racket[place-channel-get],
|
||||
@racket[async-channel-put], or @racket[async-channel-get].
|
||||
|
||||
It is normally run in a separate place so that audio decoding and feeding are
|
||||
isolated from scheduling delays in the main Racket VM, such as GUI activity,
|
||||
@@ -40,22 +44,58 @@ debugging, or interaction with DrRacket.
|
||||
|
||||
@section{Interface}
|
||||
|
||||
@defproc[(placed-player [ch-in (or/c place-channel? async-channel?)]) void?]{
|
||||
Runs the placed-player command loop on @racket[ch-in]. The channel may be a
|
||||
place channel or an async channel. The command loop receives list commands,
|
||||
@defproc[(placed-player
|
||||
[ch-in (or/c uni-channel? place-channel? async-channel? port-channel?)]
|
||||
[ch-out (or/c #f uni-channel? place-channel? async-channel? port-channel?) #f]
|
||||
[ch-evt (or/c #f uni-channel? place-channel? async-channel? port-channel?) #f])
|
||||
void?]{
|
||||
Runs the placed-player command loop on @racket[ch-in]. Each channel may already
|
||||
be a @racket[uni-channel?] or may be a supported raw channel that can be wrapped
|
||||
with @racket[make-uni-channel]. The command loop receives list commands,
|
||||
initializes its reply and event channels, and then processes playback commands
|
||||
until it receives @racket['quit].
|
||||
|
||||
The function is designed to be started either by @racket[dynamic-place] or by
|
||||
@racket[thread]. In place mode, all three channels are place channels. In
|
||||
thread mode, all three channels are async channels. The implementation detects
|
||||
the kind of channel and uses @racket[place-channel-put],
|
||||
@racket[place-channel-get], @racket[async-channel-put], or
|
||||
@racket[async-channel-get] as appropriate.}
|
||||
When @racket[ch-out] and @racket[ch-evt] are @racket[#f], the command loop
|
||||
expects an initial @racket['init] command containing the raw reply and event
|
||||
channels. This remains the normal path for @racket[dynamic-place], because a
|
||||
@racket[uni-channel] value contains procedures and must not be sent through a
|
||||
place channel. In that case the controlling side sends the raw place channels
|
||||
and the worker wraps them locally.
|
||||
|
||||
When all three channels are supplied, the worker starts initialized. This is
|
||||
used by the standard-port worker entry point and can also be used by custom
|
||||
launchers.}
|
||||
|
||||
@defproc[(placed-player/stdio [#:log-file log-file (or/c #f path-string?) (racket-sound-log-file 'placed-audio-player-stdio)])
|
||||
void?]{
|
||||
Runs @racket[placed-player] using the process standard streams as three logical
|
||||
channels: @racket[current-input-port] is the command channel,
|
||||
@racket[current-output-port] is the reply channel, and
|
||||
@racket[current-error-port] is the asynchronous event channel. The streams are
|
||||
wrapped through @racket[make-port-channel] and then through
|
||||
@racket[make-uni-channel].
|
||||
|
||||
Because stdout and stderr are protocol streams in this mode, ordinary display
|
||||
and log output is redirected while the worker is running. By default, output is
|
||||
appended to a log file under Racket's standard cache directory, for example
|
||||
@filepath{~/.cache/racket/racket-audio/placed-audio-player-stdio.log} on many
|
||||
Unix-like systems. Pass @racket[#f] explicitly to discard ordinary output, or
|
||||
pass a path to choose a different log file.
|
||||
|
||||
The module also has a command-line entry point. A remote or local subprocess
|
||||
worker can be started with:
|
||||
|
||||
@verbatim{
|
||||
racket -l racket-audio/audio-placed-player -- --stdio
|
||||
}
|
||||
|
||||
This is the command used by @racket[make-audio-player] when SSH remote playback
|
||||
is enabled, unless the caller supplies a custom remote command.}
|
||||
|
||||
The public wrapper in @racketmodname[racket-audio/audio-player] creates the channels,
|
||||
sends the initial @racket['init] command, starts an event thread, and exposes a
|
||||
contracted API. The placed player itself only exports @racket[placed-player].
|
||||
sends the initial @racket['init] command when needed, starts an event thread, and exposes a
|
||||
contracted API. The placed player exports @racket[placed-player] and the
|
||||
standard-port worker entry point @racket[placed-player/stdio].
|
||||
|
||||
@section{Overall state model}
|
||||
|
||||
@@ -91,7 +131,10 @@ installed.
|
||||
|
||||
The controlling side sends commands as lists on @racket[ch-in]. The result of
|
||||
an RPC-style command is sent on the reply channel installed by
|
||||
@racket['init]. Asynchronous events are sent on the event channel.
|
||||
@racket['init] or supplied directly to @racket[placed-player]. Asynchronous
|
||||
events are sent on the event channel. These are logical channels; the concrete
|
||||
transport may be place channels, async channels, or port channels wrapped as
|
||||
uni-channels.
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{@racket[(list 'init ch-out ch-evt)] installs @racket[ch-out] and
|
||||
@@ -252,7 +295,8 @@ place or thread then terminates.
|
||||
@section{Running in a place or in a thread}
|
||||
|
||||
The normal path in @racket[make-audio-player] uses @racket[dynamic-place] when
|
||||
places are enabled. This gives the audio side its own Racket VM and isolates
|
||||
places are enabled. The command, reply, and event channels are wrapped with
|
||||
@racket[make-uni-channel] on each side. This gives the audio side its own Racket VM and isolates
|
||||
it from the main controller, while the command and event protocol stays the
|
||||
same.
|
||||
|
||||
|
||||
@@ -28,7 +28,14 @@ through callbacks supplied when the player is created.
|
||||
@defproc[(make-audio-player
|
||||
[cb-state procedure?]
|
||||
[cb-eof-stream procedure?]
|
||||
[#:use-place use-place boolean?])
|
||||
[#:use-place use-place boolean?]
|
||||
[#:remote-host remote-host (or/c #f string?) #f]
|
||||
[#:remote-path-map remote-path-map any/c '()]
|
||||
[#:remote-racket remote-racket path-string? "racket"]
|
||||
[#:remote-module remote-module string? "racket-audio/audio-placed-player"]
|
||||
[#:remote-command remote-command (or/c #f (listof string?)) #f]
|
||||
[#:ssh-program ssh-program path-string? (current-racket-sound-ssh-program)]
|
||||
[#:ssh-options ssh-options (or/c #f (listof string?)) #f])
|
||||
audio-play?]{
|
||||
Creates an audio player and returns a player handle. The handle is passed to
|
||||
all other procedures in this module.
|
||||
@@ -108,8 +115,87 @@ 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.}
|
||||
protocol and callbacks, but it is not the preferred mode for robust playback.
|
||||
|
||||
When @racket[remote-host] is a string, @racket[make-audio-player] starts the
|
||||
worker over SSH instead of starting a local place or thread. The remote worker
|
||||
is expected to run @racket[placed-player/stdio], where stdin is the command
|
||||
channel, stdout is the reply channel, and stderr is the event channel. The
|
||||
client side wraps those three process ports through @racketmodname[port-channel]
|
||||
and @racketmodname[uni-channel]. The default command is equivalent to:
|
||||
|
||||
@racketblock[
|
||||
(list remote-racket "-l" remote-module "--" "--stdio")]
|
||||
|
||||
The remote launcher defaults are supplied by the remote utility layer. On Unix-like systems @racket[ssh] is used with @racket['("-T" "-q")]. On Windows the launcher first looks for PuTTY @tt{plink.exe} or @tt{plink}; if found, the default options are @racket['("-batch" "-T")]. If @tt{plink} is not found, the launcher falls back to OpenSSH @tt{ssh.exe}/@tt{ssh}. The default remote command is equivalent to:
|
||||
|
||||
@racketblock[
|
||||
(racket-sound-default-remote-command remote-racket remote-module)]
|
||||
|
||||
When @racket[ssh-options] is @racket[#f], suitable options are derived from the selected SSH program. If the remote setup needs a different launcher command, provide @racket[remote-command] as a list of command-line words.
|
||||
|
||||
The remote player must be able to open the requested audio files. When the
|
||||
local and remote file trees differ, use @racket[remote-path-map]. It may be a
|
||||
procedure from path string to path string, or a list of mappings. Each mapping
|
||||
may be a two-element list, a cons pair, or a two-element vector. The longest
|
||||
matching local prefix is replaced by the corresponding remote prefix before the
|
||||
@racket['open] command is sent. For example:
|
||||
|
||||
@racketblock[
|
||||
(make-audio-player cb-state cb-eof
|
||||
#:remote-host "nas"
|
||||
#:remote-path-map
|
||||
(list (list "/muziek" "/volume1/music")))]
|
||||
|
||||
With that mapping, @filepath{/muziek/klassiek/x.flac} is sent to the remote
|
||||
worker as @filepath{/volume1/music/klassiek/x.flac}.}
|
||||
|
||||
|
||||
@defproc[(audio-remote-path [path path-string?]
|
||||
[remote-path-map any/c])
|
||||
string?]{
|
||||
Applies the same path translation used by remote playback. This is primarily a
|
||||
small helper for testing SSH path-map configuration before starting playback.}
|
||||
|
||||
|
||||
@section[#:tag "audio-player-remote-defaults"]{Remote defaults}
|
||||
|
||||
@defproc[(racket-sound-default-ssh-program) path-string?]{
|
||||
Returns the default SSH client for remote playback. On Windows this prefers
|
||||
PuTTY @tt{plink.exe}/@tt{plink}, then OpenSSH @tt{ssh.exe}/@tt{ssh}. On other
|
||||
platforms it uses @tt{ssh}.}
|
||||
|
||||
@defproc[(racket-sound-default-ssh-options [ssh-program path-string?])
|
||||
(listof string?)]{
|
||||
Returns default command-line options for @racket[ssh-program]. For @tt{plink}
|
||||
this is @racket['("-batch" "-T")]; for OpenSSH this is @racket['("-T" "-q")].}
|
||||
|
||||
@defproc[(racket-sound-default-remote-racket) string?]{
|
||||
Returns the default remote Racket executable name, currently @racket["racket"].}
|
||||
|
||||
@defproc[(racket-sound-default-remote-module) string?]{
|
||||
Returns the default remote module, currently
|
||||
@racket["racket-audio/audio-placed-player"].}
|
||||
|
||||
@defproc[(racket-sound-default-remote-command
|
||||
[remote-racket path-string? (racket-sound-default-remote-racket)]
|
||||
[remote-module string? (racket-sound-default-remote-module)])
|
||||
(listof string?)]{
|
||||
Builds the default remote worker command:
|
||||
|
||||
@racketblock[
|
||||
(list remote-racket "-l" remote-module "--" "--stdio")]
|
||||
}
|
||||
|
||||
@defthing[current-racket-sound-ssh-program parameter?]{
|
||||
Parameter holding the default SSH program used by @racket[make-audio-player]
|
||||
when @racket[#:ssh-program] is not supplied.}
|
||||
|
||||
@defthing[current-racket-sound-remote-racket parameter?]{
|
||||
Parameter holding the default remote Racket executable name.}
|
||||
|
||||
@defthing[current-racket-sound-remote-module parameter?]{
|
||||
Parameter holding the default remote module name.}
|
||||
|
||||
@defproc[(audio-play? [v any/c]) boolean?]{
|
||||
Returns @racket[#t] when @racket[v] is a currently valid audio player handle.
|
||||
|
||||
Reference in New Issue
Block a user