#JavaScript SDK integration

AsciiPlayer supports live WebSocket sessions and static ASCF files. This checkout adds synchronized live playback and pixel DCT negotiation. The player is asciline-player.js; live transport/timeline ownership is in live-session.js.

#Checkout versus published package

Use local source for the integration described here. npm install asciline-player or an unversioned CDN may load a previously published version. Checkout metadata 0.1.5 does not prove these changes are published. There is no Rust-specific JavaScript package: server engine selection is independent of SDK installation.

Use examples/sdk-live.html, served by the backend at /static/examples/sdk-live.html. For a custom host, copy all src/, preserving module paths, and serve over HTTP(S). A bare asciline-player import needs a bundler or import map; the example below uses a concrete module URL.

#Minimal live player

<div style="position:relative;aspect-ratio:16/9;background:#000">
  <canvas id="video"></canvas>
</div>
<button id="play">Play / pause</button>
<button id="audio">Enable audio</button>
<script type="module">
  import { AsciiPlayer } from '/static/src/asciline-player.js';
  const player = new AsciiPlayer('#video', {
    url: 'ws://localhost:8000/ws',
    selectionLayer: false, playOverlay: false, clickToPlayPause: false,
  });
  document.querySelector('#play').onclick = () => player.togglePlay();
  document.querySelector('#audio').onclick = () => player.unmute();
  player.on('init', info => console.log(info.pixelCodec, info.cols, info.rows));
  player.on('error', error => console.error(error));
  window.addEventListener('pagehide', () => player.destroy());
</script>

This assumes the page/modules are served by the local backend. For a separate frontend use your module URL and backend WebSocket address; HTTPS requires wss://. An omitted/auto URL uses page origin with /ws. The SDK preserves existing query parameters, adds adaptive capability if absent, requests sync=1, and advertises pixel_codec=dct-v1 when its decoder supports tag 4. The server still selects RAW/DCT. Audio URL is derived from the resolved backend URL.

#Options

Option Default Meaning
url null Live URL; omitted derives page origin
src, audioSrc null ASCF and paired audio URLs
container canvas parent Layout element or selector
audio true Create element; selector/element or false accepted
muted false Initially mute audio
autoplay false Attempt playback in constructor
loop false Static ASCF looping; live queues use server --loop
selectionLayer null Selection element; true creates one
playOverlay true Built-in play button
muteButton true Built-in mute toggle
clickToPlayPause true Toggle by canvas click
keyboardShortcuts true Built-in spacebar handling
bufferSize 4 Buffer-related sizing; sync preroll stays four frames
filters default values Contrast, gamma, brightness, sharpness, invert, palette

Audio playback policy still applies. Autoplay may give silent video until a gesture enables audio. Muted audio can advance; zero volume is not pausing its clock. Live playback uses a wall clock with audio:false, webcam, or unavailable audio.

#Controls and events

player.play();                // configured source
player.play('ws://localhost:8000/ws');
player.pause();
player.resume();
player.seek(10);               // live or static, seconds
player.skip(-10);
player.setPixelMode(true);     // live reinit
player.setRenderMode(6);       // live color-mode reinit
player.setFilters({ contrast: 1.2 });
player.setVolume(0.8);
player.mute();
player.unmute();
player.getMasterClock();       // seconds, selected audio/wall clock
player.getState();
player.destroy();

Live seeks clamp to known duration. Webcam does not seek/pause. A synchronized seek waits for the matching marker and new frames. Live resume re-seeks at the paused position, aligning audio range, clock and buffered frames. ASCII/pixel reinit receives a fresh INIT and rebuilds its pipeline.

destroy() stops render/report scheduling and disposes socket handlers and pending frames. Previous-timeline decode completions are discarded. play() can reconnect; a UI can instead create a fresh instance, as the supplied example does. Calling play while connecting/playing does not create a second connection.

Event Payload/use
init Live: fps, cols, rows, duration, pixelMode, renderMode, queueIdx, isWebcam, pixelCodec
fps fps, targetFps, buffered
timeupdate Seconds
statechange IDLE, CONNECTING, PLAYING, PAUSED, ENDED, ERROR
buffering Connection/startup status
seek Requested live target
ended Playback completion
error Error object/message

Register with on(name, handler) and remove with off(name, handler). Update seek UI from timeupdate when not dragging. Focusing a range input through Tab is not necessarily dragging; the sample active-element guard is a UI choice rather than a playback requirement.

#Timeline and predictor

LiveSession serializes async decoding. Seek, INIT, reinit and disposal change the epoch; old completions cannot populate the new buffer. Predictor resets create a fresh decoder. Discarding a late draw frame differs from discarding a predictive packet: packet reconstruction must remain ordered.

Sync sends four preroll frames and waits for client playback-ready. Start waits for audio playing, or falls back to wall clock. Matching SEEKED markers separate timelines. Short streams can start with fewer preroll frames on clean EOF. Old servers without sync INIT fields retain the legacy path; DCT requires capability and tag-4 support.

#Static ASCF and custom element

import { AsciiPlayer } from './src/asciline-player.js';
const player = new AsciiPlayer('#video', {
  src: 'clip.ascf', audioSrc: 'clip.mp3', loop: true,
});

Static playback uses its existing reader path, independently of LiveSession. Rust is not needed at playback time. The compiler produces ASCF and paired audio; JavaScript reconstructs the representation.

<script type="module" src="./src/ascf-element.js"></script>
<ascf-player src="clip.ascf" audio="clip.mp3" loop
  style="width:100%;aspect-ratio:16/9"></ascf-player>

Use ws="ws://localhost:8000/ws" instead of src for live. Attributes are src, audio, ws, autoplay, loop, muted. Events bubble with ascf- prefix such as ascf-timeupdate. Set source attributes before attaching: the current attribute handler does not implement runtime source replacement. Detach disposes the player.

The root showcase and packaged SDK are separate clients. The component documented here is src/ascf-element.js.

#Verify and package

npm test
npm pack --dry-run
python -m pytest -q test/test_sdk_profile.py

The npm files list includes src, covering live-session.js. Dry-run checks inclusion; it does not publish or prove a release shipped. Live tests exercise negotiation, decode order, seek isolation, pause/resume, reinit, destroy, replay, legacy and short EOF. The Python test sends actual Python/Rust DCT packets through the embedded codec and runs SDK against a real server. Rust coverage requires a built module; check skips. Node mocks/synthetic tests do not measure physical A/V skew or browser throughput; use the local example for observation.