@ferret/media
Audio sources and ambient zones, video surfaces (HLS/MP4 to texture) and HTML surfaces; recording, and frame-by-frame capture on simulated time to WebM or PNG.
Classes
AmbientZones class
class AmbientZones implements ServiceAmbient sound by zone. map({ Yard: 'wind', Hall: 'room-tone' }) names the sound each zone plays (sounds defined in the AudioService, on the ambient bus); when the player's zone changes the old one fades out and the new one fades in. A zone with no entry keeps what was playing; map it to null for silence.
readonly name: "ambient" | |
constructor(readonly opts: AmbientOptions = {}) | |
init(world: World) | |
map(table: Record<string, string | null>): this | |
get playing(): { zone: string; sound: string | null } | null | The zone whose ambience is playing and the sound. |
dispose() |
packages/media/src/ambient.ts:16
AudioPlayer class
class AudioPlayer extends MediaComponent<AudioPlayerParams>A sound that plays, pauses and seeks, with a queue of tracks (next, previous, repeat, shuffle): a radio, a jukebox, the
music of a place. Streams from its source, so long tracks do not sit decoded in memory. Goes through the music bus by
default, positioned at the entity when spatial is given. Controlled through the MediaPlayer API and reports through the
media:* events; nothing here is bound to a key or a button. (Short sounds that fire and are done stay AudioService.play.)
audio: HTMLAudioElement | |
onAttach() | |
onDetach() |
packages/media/src/audio-player.ts:14
AudioService class
class AudioService implements ServiceSound for a game: a master gain and four buses (music, sfx, ambient, voice) with their own volumes, buffers decoded once, sounds that sit in the world (a panner that follows a node, the listener follows the active camera). Browsers refuse sound before the first user action, so the context is made and resumed then (audio:unlocked); loops asked for earlier start at that moment, one-shots asked for earlier are dropped. sound:play events { name, entity? } play registered sounds.
readonly name: "audio" | |
ctx: AudioContext | null | |
constructor(readonly opts: AudioOptions = {}) | |
init(world: World) | |
get unlocked(): boolean | |
unlock(): void | Make the context and resume it. Call from a user action (the service does it on the first key, click or touch). |
volume(bus: Bus | 'master'): number | |
setVolume(bus: Bus | 'master', v: number): void | |
tap(): MediaStream | null | What the player hears (after the master gain) as a MediaStream with one audio track, for a recording. Null until the context is running: a suspended context delivers no data, and a recorder given such a track writes nothing at all. |
busNode(bus: Bus): AudioNode | null | The bus node for routing other sources (a video's sound) through the same volume control. |
define(name: string, def: SoundDef): this | |
has(name: string): boolean | |
load(url: string): Promise<AudioBuffer> | Decode a sound file once. |
play(nameOrUrl: string, o: PlayOptions = {}): SoundHandle | Play a defined sound (by name) or a file (by url). Returns a handle at once; the sound starts when decoded. |
update() | Called once per rendered frame. |
_bus(b: Bus): AudioNode | |
_register(v: Voice) | |
_unregister(v: Voice) | |
get activeVoices(): number | |
dispose() |
packages/media/src/audio.ts:49
HtmlScreen class
class HtmlScreen extends Component<HtmlScreenParams>A web page drawn onto a 3D plane, as a texture: the screen lies in the world like any mesh (hidden behind walls, seen from any angle, many
of them are cheap), but it is a picture: no scripts run and nothing can be clicked. For notice boards and posters. Make it again with
refresh() when the page changed. Events: html:ready { entity }, html:error { entity, message }.
The page is fetched and made self-contained (see inlinePage), then the browser draws it through an SVG <foreignObject>.
schema: { readonly url: { readonly type: "string"; }; readonly html: { readonly type: "string"; }; readonly base: { readonly type: "string"; }; readonly width: { readonly type: "number"; readonly min: 0.01; }; readonly height: { readonly type: "number"; readonly min: 0.01; }; readonly pxWidth: { readonly type: "number"; readonly min: 16; readonly max: 4096; }; readonly pxHeight: { readonly type: "number"; readonly min: 16; readonly max: 4096; }; readonly pixelsPerMetre: { readonly type: "number"; readonly min: 1; readonly max: 4096; }; readonly pixelRatio: { readonly type: "number"; readonly min: 0.25; readonly max: 4; }; readonly lit: { readonly type: "boolean"; }; } | |
screen: BABYLON.Mesh | |
texture: BABYLON.DynamicTexture | |
state: 'loading' | 'ready' | 'error' | |
warnings: string[] | |
error: string | |
onAttach() | |
async refresh(): Promise<void> | Fetch the page again and redraw it. Resolves when it is on the screen (or has failed: see state). |
onDetach() |
packages/media/src/html.ts:120
LampSounds class
class LampSoundsPositional sounds for the lamps a light budget says are near the viewer (a torch's crackle): a small pool of looping AudioPlayer entities that is moved from lamp to lamp. Only the nearest few lamps sound at a time (LightBudgetDef.sounds.budget), so a village of hundreds of torches costs a handful of audio elements. A player that is let go is paused and kept for the next lamp; its Web Audio graph is built once, not per lamp. Nothing is bound to a key; the sound goes through the AudioService (bus, master volume, panner that follows the entity) like every AudioPlayer.
dropped: number | starts that found no free player (the pool is at max) |
constructor(readonly world: World, readonly source: LampSoundSource, readonly opts: LampSoundsOptions = {}) | |
get active(): number[] | the lamps that sound now |
get players(): readonly Entity[] | the players made so far |
playerOf(i: number): AudioPlayer | undefined | the player of lamp i, if it sounds |
apply(start: readonly number[], stop: readonly number[]) | |
dispose() |
packages/media/src/lamp-sounds.ts:34
MediaComponent class
abstract class MediaComponent<P extends MediaPlayerParams = MediaPlayerParams> extends Component<P> implements MediaPlayerShared by the media components: owns an HTMLMediaElement, routes its sound through the AudioService (bus volume, master,
positional falloff), keeps a queue, and emits on world.events, always with { entity }:
media:state { state }, media:play, media:pause, media:ended (the queue ran out), media:track { index, src },
media:error { message }.
get state(): PlayerState | |
get playing(): boolean | |
get currentTime(): number | seconds; duration is NaN until known and Infinity for a live stream |
get duration(): number | |
play(): void | |
pause(): void | |
toggle(): void | |
stop(): void | pause and go back to the start |
seek(seconds: number): void | |
get volume(): number | 0..1 |
set volume(v: number) | 0..1 |
get muted(): boolean | |
set muted(m: boolean) | |
get rate(): number | playback speed, 1 = normal |
set rate(r: number) | playback speed, 1 = normal |
get tracks(): readonly string[] | |
get index(): number | index into tracks of the one that is current, -1 for none |
get track(): string | null | |
get repeat(): Repeat | |
set repeat(r: Repeat) | |
get shuffle(): boolean | |
set shuffle(s: boolean) | |
async setTracks(tracks: string[], start = 0): Promise<void> | replace the queue and make start current |
async select(index: number): Promise<void> | make this one current (it plays if autoplay or it was playing) |
async next(): Promise<boolean> | the next track; false when the queue is at its end (and repeat is not 'all') |
async previous(): Promise<boolean> | back to the one before when the current is near its start (true); otherwise back to its start (false) |
onUpdate() |
packages/media/src/player.ts:81
SceneRecorder class
class SceneRecorder implements ServiceRecords the scene: the engine's canvas through captureStream and MediaRecorder, with the sound the player hears when the
AudioService is unlocked. Results as one file (MP4 where the browser writes it, else WebM with its Duration put in).
Events on world.events: record:start { mime, audio }, record:stop { mime, bytes, durationMs, audio }, record:error { message }.
Bound to no key or button. Sound cannot join a recording that is running: a locked audio context delivers nothing, and a
recorder given such a track writes an empty file, so a recording that started before the first user action has no sound.
With software rendering and encoding (the simulators, a headless browser) the first data comes about a second after the start,
so a recording shorter than that can be empty: stop() then rejects with a message saying so.
readonly name: "recorder" | |
constructor(readonly opts: RecorderOptions = {}) | |
init(world: World) | |
get state(): RecorderState | |
get elapsedMs(): number | milliseconds recorded so far, pauses left out |
static get supported(): boolean | Whether this browser can record a canvas at all. |
start(o: { fps?: number; audio?: boolean; videoBitsPerSecond?: number } = {}): { mime: string; audio: boolean } | |
pause() | |
resume() | |
stop(): Promise<Recording> | Ends the recording and resolves with the file. Rejects when the recorder failed or delivered nothing. |
dispose() |
packages/media/src/recorder.ts:136
VideoScreen class
class VideoScreen extends MediaComponent<VideoScreenParams>A video on a flat screen in the world: a plane with the video as an emissive texture. The picture starts at once (muted:
browsers allow that); the sound begins on the first user action, through the AudioService's bus so the volume control and
the positional falloff apply. Controlled through the MediaPlayer API (play, pause, seek, volume, a queue of sources)
and reports through the media:* events; nothing here is bound to a key or a button.
video: HTMLVideoElement | |
screen: BABYLON.Mesh | |
texture: BABYLON.VideoTexture | |
onAttach() | |
onDetach() |
packages/media/src/video.ts:17
WorldText class
class WorldText extends Component<WorldTextParams>Text in the world: a plane with the text drawn on it, readable from the entity's +Z side (the entity's forward is the way the text faces, as an Unreal TextRender faces its +X). Unlit, with alpha; no collision. Draws with a canvas font (default Arial), so the letter shapes are the browser's.
schema: { readonly text: { readonly type: "string"; readonly required: true; }; readonly size: { readonly type: "number"; readonly min: 0.001; }; readonly xScale: { readonly type: "number"; readonly min: 0.01; }; readonly yScale: { readonly type: "number"; readonly min: 0.01; }; readonly color: { readonly type: "array"; }; readonly align: { readonly type: "string"; }; readonly valign: { readonly type: "string"; }; readonly font: { readonly type: "string"; }; readonly bold: { readonly type: "boolean"; }; } | |
plane?: BABYLON.Mesh | |
onAttach() | |
onDetach() |
packages/media/src/text.ts:31
Functions
attachSource function
async function attachSource(media: HTMLMediaElement, src: string, hlsUrl?: string, onError?: (m: string) => void): Promise<SourceResult>Point a media element (video or audio) at a source. HLS plays natively where the browser can (Safari, iOS, WPE WebKit,
the Android WebView) and through hls.js elsewhere; any other file is just set as src.
packages/media/src/source.ts:23
captureFrames function
async function captureFrames(engine: BABYLON.AbstractEngine, scene: BABYLON.Scene, world: World | null, o: CaptureOptions = {}): Promise<Capture>Captures the scene frame by frame on simulated time, not on the wall clock: before every frame the simulation moves exactly 1/fps seconds (the world's fixed steps), and the engine's frame time reads 1/fps, so animations, physics and per-frame code all see the same steady clock however slowly a frame is drawn. Nothing is dropped and a loop closes exactly. The render loop is held for the length of the capture and given back after; the canvas is drawn at supersample times the size and scaled down. Video is encoded in the browser (WebCodecs) into WebM; pictures come as PNG. The scene's sound is not captured.
packages/media/src/capture.ts:174
captureSupported function
function captureSupported(): { webm: boolean; png: boolean }Whether this browser can capture video files (WebCodecs); pictures need only a canvas.
packages/media/src/capture.ts:152
defaultMimeTypes function
function defaultMimeTypes(audio: boolean): string[]MP4 first (it plays and seeks everywhere), then WebM; with sound the codecs are named so the audio is not dropped.
packages/media/src/recorder.ts:35
drawPage function
async function drawPage(xhtml: string, ctx: CanvasRenderingContext2D, viewport: { width: number; height: number }, size: { width: number; height: number }): Promise<void>Draw a self-contained page into a canvas context at the given texture size. Throws when the browser refuses (it taints or fails).
packages/media/src/html.ts:104
inlinePage function
async function inlinePage(html: string, base: string, get: HtmlFetch): Promise<{ xhtml: string; warnings: string[] }>Make a page self-contained: stylesheets become <style>, url(...) in CSS and <img src> become data URIs, scripts, frames and media
are dropped. Returns the XHTML of the page and the problems (a resource that could not be fetched is left out, not fatal).
packages/media/src/html.ts:42
loadHls function
function loadHls(url = ((globalThis as any).FERRET_LIB ?? 'lib/ferret/') + 'hls/hls.min.js'): Promise<HlsLike | null>Load hls.js once from a script URL (it sets the global Hls). Resolves null if it cannot be loaded.
packages/media/src/source.ts:5
pageSvg function
function pageSvg(xhtml: string, width: number, height: number): stringThe page inside an SVG image of the viewport's size (the browser lays it out in a <foreignObject>).
packages/media/src/html.ts:99
pickMime function
function pickMime(types: string[], isSupported: (m: string) => boolean): string | undefinedThe first of types that isSupported accepts, or undefined (then the browser picks its own).
packages/media/src/recorder.ts:42
setWebmDuration function
function setWebmDuration(data: Uint8Array, durationMs: number): Uint8ArrayMediaRecorder writes WebM with no Duration, so players show a nonsense length and cannot seek. Puts the Duration element into the Segment's Info. Returns the bytes unchanged when the file is not a WebM this can read, or already has a Duration.
packages/media/src/recorder.ts:76
spatialFromUnreal function
function spatialFromUnreal(att: { attenuation_shape_extents?: number[]; falloff_distance?: number; distance_algorithm?: string; attenuate?: boolean }): { min: number; max: number; exact: boolean } | undefinedUnreal's sound attenuation (Linear distance algorithm, sphere shape) as the metres of spatial: full volume inside the inner radius (attenuation_shape_extents.x), silent after the falloff distance beyond it. Centimetres in, metres out. The panner falls off linearly, so another algorithm (Logarithmic, Inverse) is only approximated (exact: false). Undefined when the sound does not attenuate.
packages/media/src/lamp-sounds.ts:103
textOffset function
function textOffset(w: number, h: number, align: WorldTextParams['align'] = 'left', valign: WorldTextParams['valign'] = 'bottom'): [number, number]Where a block of text of the given measured size sits so that the anchor is at the entity's origin: the plane's centre in the reader's frame (x to the reader's right, y up).
packages/media/src/text.ts:21
writeWebm function
function writeWebm(frames: EncodedFrame[], t: WebmTrack): Uint8ArrayOne WebM file from encoded video frames: header, Info with the Duration, one track, the Cues (before the clusters, so a player can seek at once), and a cluster at every key frame (and at least every 30 s). The first frame must be a key frame.
packages/media/src/capture.ts:56
Interfaces
AmbientOptions interface
interface AmbientOptionstag?: string | the zones' tag for the listener: the entity whose zone picks the ambience (default "player") |
fade?: number | seconds of cross-fade between ambiences (default 1.5) |
packages/media/src/ambient.ts:4
AudioOptions interface
interface AudioOptionscreateContext?: () => AudioContext | make the context (tests pass a fake; default new AudioContext()) |
gestureTarget?: EventTarget | false | where to wait for the first user action; false = do not listen (the game calls unlock() itself) |
fetch?: (url: string) => Promise<{ ok: boolean; arrayBuffer(): Promise<ArrayBuffer> }> | replace fetch (tests, a Platform file API) |
packages/media/src/audio.ts:32
AudioPlayerParams interface
interface AudioPlayerParams extends MediaPlayerParamsaudio?: HTMLAudioElement | an audio element you made (a MediaStream, a test double) |
packages/media/src/audio-player.ts:3
Capture interface
interface Captureformat: 'webm' | 'png' | |
mime: string | e.g. video/webm;codecs=vp9, or image/png |
frames: number | |
fps: number | |
width: number | |
height: number | |
durationMs: number | simulated time covered, ms (frames / fps) |
blob?: Blob | the video (webm) |
pngs?: Blob[] | the pictures (png without onFrame) |
aborted: boolean | true when signal stopped it early |
packages/media/src/capture.ts:127
CaptureOptions interface
interface CaptureOptionsfps?: number | frames per second of the result, and the simulated time between frames (default 30) |
seconds?: number | how long to capture, simulated seconds (default 1); or frames |
frames?: number | |
width?: number | size of the result (default: the canvas's render size) |
height?: number | |
supersample?: number | render at this many times the size and scale down: smooth edges and fine detail (1 to 4, default 1) |
format?: 'webm' | 'png' | webm: one video file (WebCodecs); png: a picture per frame (default webm) |
codecs?: ('vp9' | 'vp8' | 'av1')[] | video codec, the first the browser encodes is used (default vp9, then vp8) |
bitrate?: number | bits per second (default about 0.12 bit per pixel and frame) |
keyEvery?: number | a key frame every this many frames (default 2 s of frames) |
advance?: (dt: number) => void | move the simulation by dt seconds (default: the world's advance, which runs its fixed steps) |
render?: () => void | draw one frame (default: scene.render()) |
beforeFrame?: (i: number, t: number) => void | called before frame i is advanced and drawn, at simulated time t since the start (a turntable turns the camera here) |
onFrame?: (i: number, png: Blob) => void | Promise<void> | with png: each picture as it is made (else they are returned together) |
signal?: AbortSignal | stops the capture between two frames; what was made so far is returned |
grab?: (canvas: HTMLCanvasElement, width: number, height: number) => CanvasImageSource & { convertToBlob?: (o: { type: string }) => Promise<Blob> } | replace the browser parts (tests) |
createEncoder?: (init: VideoEncoderInit) => VideoEncoder | |
isConfigSupported?: (c: VideoEncoderConfig) => Promise<{ supported?: boolean }> | |
makeFrame?: (source: CanvasImageSource, init: VideoFrameInit) => VideoFrame |
packages/media/src/capture.ts:91
EncodedFrame interface
interface EncodedFramedata: Uint8Array | |
timeMs: number | presentation time of the frame, ms |
key: boolean |
packages/media/src/capture.ts:35
HtmlScreenParams interface
interface HtmlScreenParamsurl?: string | the page to show (its stylesheets, images and fonts are fetched too: they need CORS, or fetch that gets around it) |
html?: string | or the page as text; base then resolves its relative links |
base?: string | |
width?: number | size of the screen in metres (default 4 x 2.25) |
height?: number | |
pxWidth?: number | the page's window in CSS pixels: the page lays out as in a browser of exactly this size. Give both, or one (the other follows the screen's shape), or none (see pixelsPerMetre) |
pxHeight?: number | |
pixelsPerMetre?: number | when the window size is not given: window = screen size in metres x this (default 256), the larger side capped at 2048 with the shape kept |
pixelRatio?: number | texture size = viewport x this (default 1; 2 for sharper text on a screen seen close) |
fetch?: HtmlFetch | replace fetch |
lit?: boolean | lit by the scene like a poster (default false: it glows like a screen) |
packages/media/src/html.ts:6
LampSoundsOptions interface
interface LampSoundsOptionsspatial?: { min?: number; max?: number } | the sound comes from the lamp: full volume inside min metres, silent beyond max (see spatialFromUnreal). Default 2 / 30 |
volume?: number | 0..1, default 1 (Unreal: the cue's volume multiplier x the component's) |
bus?: NonNullable<MediaPlayerParams['bus']> | default 'ambient' |
offset?: Vec3 | metres added to a lamp's position (Unreal: the audio component's offset from the actor) |
max?: number | how many players at most (default: no limit, but the source's own sound budget keeps it small); a start beyond it is dropped |
makeAudio?: () => HTMLAudioElement | undefined | the element a player plays through (a test double); default a new Audio |
packages/media/src/lamp-sounds.ts:13
LampSoundSource interface
interface LampSoundSourceWhat LampSounds needs of a light budget (LightBudget from
def: { items: readonly { position: Vec3; sound?: string; id?: string }[] } | |
onSounds?: (start: number[], stop: number[]) => void |
packages/media/src/lamp-sounds.ts:8
MediaPlayer interface
interface MediaPlayerWhat a game controls. Every media component (a video screen, an audio player) has this same surface, and the engine binds it to nothing: no key, button or gesture. A game calls it from its own code, from a stock behaviour's wiring or from its UI.
readonly state: PlayerState | |
readonly playing: boolean | |
readonly currentTime: number | seconds; duration is NaN until known and Infinity for a live stream |
readonly duration: number | |
volume: number | 0..1 |
muted: boolean | |
rate: number | playback speed, 1 = normal |
play(): void | |
pause(): void | |
toggle(): void | |
stop(): void | pause and go back to the start |
seek(seconds: number): void | |
readonly tracks: readonly string[] | |
readonly index: number | index into tracks of the one that is current, -1 for none |
readonly track: string | null | |
repeat: Repeat | |
shuffle: boolean | |
setTracks(tracks: string[], start?: number): Promise<void> | replace the queue and make start current |
select(index: number): Promise<void> | make this one current (it plays if autoplay or it was playing) |
next(): Promise<boolean> | the next track; false when the queue is at its end (and repeat is not 'all') |
previous(): Promise<boolean> | back to the one before when the current is near its start (true); otherwise back to its start (false) |
packages/media/src/player.ts:12
MediaPlayerParams interface
interface MediaPlayerParamssrc?: string | one source: a file or an HLS playlist (.m3u8) |
tracks?: string[] | or a queue of them |
start?: number | index of the track to start with |
repeat?: Repeat | |
loop?: boolean | shorthand for repeat: 'one' |
shuffle?: boolean | |
volume?: number | 0..1 (default 1) |
muted?: boolean | |
rate?: number | |
bus?: Bus | the bus its sound goes through (video 'sfx', audio 'music') |
spatial?: { min?: number; max?: number } | the sound comes from the entity: louder near it. min/max distances in metres |
hlsUrl?: string | where hls.js is, for browsers that cannot play HLS themselves (default hls/hls.min.js under the library folder) |
autoplay?: boolean | start by itself (default true; the sound waits for the first user action) |
random?: () => number | replaces Math.random for the shuffle (a seeded one for tests, replays) |
packages/media/src/player.ts:48
PlayOptions interface
interface PlayOptionsat?: BABYLON.TransformNode | BABYLON.Vector3 | where the sound comes from; a node it follows. Without it the sound is not positioned (UI, music) |
volume?: number | |
loop?: boolean | |
bus?: Bus | |
fadeIn?: number | seconds to fade in |
spatial?: { min?: number; max?: number } |
packages/media/src/audio.ts:15
RecorderOptions interface
interface RecorderOptionscanvas?: HTMLCanvasElement | the canvas to record (default: the engine's rendering canvas) |
fps?: number | frames per second asked of the capture (default 30) |
videoBitsPerSecond?: number | video bit rate (default: the browser's) |
audio?: boolean | add the sound the player hears when the audio is unlocked (default true) |
mimeTypes?: string[] | formats in order of preference; the first the browser can write is used |
timeslice?: number | ask for a data chunk this often, ms (default 1000): a long recording is not held back in the encoder |
createRecorder?: (stream: MediaStream, o: { mimeType?: string; videoBitsPerSecond?: number }) => MediaRecorder | replace the browser's MediaRecorder (tests) |
isTypeSupported?: (mime: string) => boolean |
packages/media/src/recorder.ts:6
Recording interface
interface Recordingblob: Blob | |
mime: string | the container and codecs the file was written with, e.g. video/mp4;codecs=avc1.42E01E |
durationMs: number | |
bytes: number | |
audio: boolean | false when the audio was still locked at the start (the file has no sound) or not asked for |
packages/media/src/recorder.ts:24
SoundDef interface
interface SoundDefurl: string | |
bus?: Bus | |
volume?: number | 0..1, default 1 |
loop?: boolean | |
spatial?: { min?: number; max?: number } | spatial: louder near the source; min is the distance of full volume, max where it is silent (metres) |
packages/media/src/audio.ts:5
SoundHandle interface
interface SoundHandlereadonly playing: boolean | |
setVolume(v: number, fade?: number): void | |
stop(fade?: number): void |
packages/media/src/audio.ts:26
SourceResult interface
interface SourceResultmode: 'native' | 'hls.js' | 'none' | |
destroy(): void |
packages/media/src/source.ts:17
VideoScreenParams interface
interface VideoScreenParams extends MediaPlayerParamsvideo?: HTMLVideoElement | or a video element you made (a MediaStream, a canvas capture) |
width?: number | size of the screen in metres (default 4 x 2.25) |
height?: number |
packages/media/src/video.ts:3
WebmTrack interface
interface WebmTrackcodec: 'V_VP9' | 'V_VP8' | 'V_AV1' | Matroska codec id: V_VP9, V_VP8 or V_AV1 |
width: number | |
height: number | |
fps: number | |
durationMs?: number | the length of the video, ms (default: the last frame's time plus one frame) |
packages/media/src/capture.ts:42
WorldTextParams interface
interface WorldTextParamstext: string | the text; \n starts a new line |
size?: number | height of a letter's em box in metres (Unreal's TextRender WorldSize / 100) |
xScale?: number | extra scale across and along the lines (Unreal's XScale, YScale) |
yScale?: number | |
color?: number[] | colour as [r, g, b] or [r, g, b, a], 0..1 |
align?: 'left' | 'center' | 'right' | how the text sits on the entity's origin, as a reader sees it |
valign?: 'top' | 'center' | 'bottom' | |
font?: string | |
bold?: boolean |
packages/media/src/text.ts:3
Types
Bus type
type Bus = 'music' | 'sfx' | 'ambient' | 'voice'packages/media/src/audio.ts:3
HlsLike type
type HlsLike = { isSupported(): boolean; new (cfg?: unknown): { loadSource(u: string): void; attachMedia(v: HTMLMediaElement): void; destroy(): void; on(ev: string, fn: (...a: any[]) => void): void } }packages/media/src/source.ts:1
HtmlFetch type
type HtmlFetch = (url: string) => Promise<{ ok: boolean; status: number; text(): Promise<string>; blob(): Promise<Blob> }>What HtmlScreen needs from fetch (a Platform file API or a proxy fits): the page and its pieces come through it.
packages/media/src/html.ts:4
PlayerState type
type PlayerState = 'idle' | 'loading' | 'playing' | 'paused' | 'ended' | 'error'packages/media/src/player.ts:5
RecorderState type
type RecorderState = 'idle' | 'recording' | 'paused'packages/media/src/recorder.ts:4
Repeat type
type Repeat = 'none' | 'one' | 'all'packages/media/src/player.ts:6