Plays a piece in real time: a process that draws a frame every 1/fps
seconds and hands it on.
{:ok, player} = Ascii.Player.start_link(piece: "donut")
receive do
{:ascii_frame, _ref, frame} -> IO.write(Ascii.Render.ANSI.render(frame))
endWhere frames go
Started with start_link/1 or start/1, a player sends its frames to the
process that started it (or :to) as {:ascii_frame, ref, %Ascii.Frame{}}, and stops when that process, its owner, exits. Given a
:callback, it calls that instead, in the player's process, and has no
owner.
Any process can also subscribe/1 to a player and receive the same
messages until it unsubscribes or exits, and frame/1 returns the latest
frame. That is how one player serves many viewers:
children = [
{Ascii.Player, piece: "elixir", name: MyApp.Logo}
]
# in each LiveView
def mount(_params, _session, socket) do
if connected?(socket), do: Ascii.Player.subscribe(MyApp.Logo)
{:ok, assign(socket, frame: Ascii.Player.frame(MyApp.Logo))}
end
def handle_info({:ascii_frame, _ref, frame}, socket),
do: {:noreply, assign(socket, frame: frame)}Under a supervisor (child_spec/1) a player has no owner unless given
:to: it sends nothing to the supervisor, only to its callback and its
subscribers. A player with no one to send to holds its frame and does no
work, and play time waits with it, until someone subscribes.
Time
Play time advances as upstream's mount advances it: by the real time
since the last frame, at most 100 ms a frame, and not at all while paused.
Ticks are scheduled against the monotonic clock, so they keep their pace
rather than drift by the time each frame takes to draw.
start_link/1 checks the piece and its options, and returns their error
if there is one, without starting a process. It returns as soon as the
player has started; the player then sets the piece up (a scene takes up to
a second, more on a small board) before it does anything else, so a call
made meanwhile waits for it. A supervisor starting a scene's player is not
held up.
A piece that closes a loop (Ascii.Meta's loop), played with its
default options and frame rate, is drawn once a frame of its cycle, as
play first reaches each, and then repeated, so after one cycle it costs
nothing to play: the frames are kept, up to a megabyte. Frames come at the
cycle's places rather than at the exact play time, which is every
1/fps of a second anyway; a frame's t is still the play time.
It needs nothing from Phoenix, so it serves a terminal app (see
mix ascii.play) or a device: on Nerves, start it under your own
supervisor and draw frame.lines and frame.colors to the display in the
callback.
Options
:piece(required) - a slug or module.:to- the owner: the pid to send frames to, which the player stops with. The caller by default, or no one under a supervisor;nilfor no one.:callback- a function of one frame, instead of messages to an owner.:ref- the tag on each message (a fresh reference by default; seeref/1).:cache- keep and repeat the frames of a loop (defaulttrue).:options,:fps,:paper,:color,:ink,:gradient,:parts- as forAscii.new/2.:name- registers the player. Under a supervisor it is also the child's id, so one supervisor can hold several named players.
Summary
Functions
A child spec for a supervisor: {Ascii.Player, piece: "donut", name: MyApp.Donut}.
The latest frame drawn. Frame 0 until the player has played; while the
piece is still being set up, this waits for it, up to timeout ms.
A key or a click for the piece (see Ascii.handle_event/2): its new
options if it took the event, the new picture drawn and sent at once;
:ignore if it had no use for it.
Holds the current frame; play time stops.
The tag on this player's frame messages.
Plays on from where it paused.
Changes the frame rate, 0 to 60; 0 holds the picture.
New options for the piece, merged over its own (see
Ascii.put_options/2): for live data, a reading or the latest samples.
The new picture is drawn and sent at once.
Starts a player without a link.
Starts a player linked to the caller. See the module docs for options.
Stops the player.
Sends the caller every frame from now on, as the owner gets them, until it
calls unsubscribe/1 or exits. Subscribing twice changes nothing.
Stops sending the caller frames.
Functions
@spec child_spec(opts :: keyword()) :: Supervisor.child_spec()
A child spec for a supervisor: {Ascii.Player, piece: "donut", name: MyApp.Donut}.
The player sends frames to no one by default, rather than to the supervisor that starts it; see the module docs.
@spec frame(player :: GenServer.server(), timeout :: timeout()) :: Ascii.Frame.t()
The latest frame drawn. Frame 0 until the player has played; while the
piece is still being set up, this waits for it, up to timeout ms.
@spec handle_event(player :: GenServer.server(), event :: Ascii.event()) :: {:ok, options :: map()} | :ignore
A key or a click for the piece (see Ascii.handle_event/2): its new
options if it took the event, the new picture drawn and sent at once;
:ignore if it had no use for it.
@spec pause(player :: GenServer.server()) :: :ok
Holds the current frame; play time stops.
@spec ref(player :: GenServer.server()) :: term()
The tag on this player's frame messages.
@spec resume(player :: GenServer.server()) :: :ok
Plays on from where it paused.
@spec set_fps(player :: GenServer.server(), fps :: number()) :: :ok | {:error, {:invalid_option, :fps}}
Changes the frame rate, 0 to 60; 0 holds the picture.
@spec set_options(player :: GenServer.server(), options :: map() | keyword()) :: :ok | Ascii.error()
New options for the piece, merged over its own (see
Ascii.put_options/2): for live data, a reading or the latest samples.
The new picture is drawn and sent at once.
@spec start(opts :: keyword()) :: GenServer.on_start() | Ascii.error()
Starts a player without a link.
@spec start_link(opts :: keyword()) :: GenServer.on_start() | Ascii.error()
Starts a player linked to the caller. See the module docs for options.
@spec stop(player :: GenServer.server()) :: :ok
Stops the player.
@spec subscribe(player :: GenServer.server(), timeout :: timeout()) :: :ok
Sends the caller every frame from now on, as the owner gets them, until it
calls unsubscribe/1 or exits. Subscribing twice changes nothing.
@spec unsubscribe(player :: GenServer.server()) :: :ok
Stops sending the caller frames.