# ASCII video

Video, a dropped file or the webcam sampled into glyphs at up to 60 fps, with audio-reactive contrast and jitter.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown variants are available by appending `.md` to any URL or sending an `Accept: text/markdown` header. The asciicn agent skill, for building any ASCII effect on the engine, is at [/.well-known/agent-skills/asciicn/SKILL.md](/.well-known/agent-skills/asciicn/SKILL.md).

> **This is an example, not the only option.** It is one component built on the asciicn engine. Install it when it is what you were asked for. For any other look, don't stretch its props to fit: write your own field on `AsciiShader` ([Write your own](/docs/engine/write-your-own.md)), or draw a CPU grid with `AsciiGrid`, and read this example's source for technique. Its full source is in [https://asciicn.dev/r/ascii-video.json](https://asciicn.dev/r/ascii-video.json), under `files[].content`.

## Installation [#installation]

```bash
npx shadcn@latest add https://asciicn.dev/r/ascii-video.json
```

## Usage [#usage]

```tsx
import { AsciiVideo } from "@/components/asciicn/ascii-video";

export function Reel() {
  return (
    <AsciiVideo
      src="/media/reel.mp4"
      alt="Our product reel"
      charset="dense"
      className="aspect-video w-full"
    />
  );
}
```

Each frame is drawn into a small canvas at the size of the glyph grid and area-averaged into one tone and colour per sample. A 120 by 50 grid reads about 6,000 samples per frame, so sampling stays cheap even at 60 fps. `fps` caps how often a new frame is sampled; between samples nothing redraws.

The video plays muted and inline, so browsers allow autoplay. Pause stops sampling and holds the last frame.

`src` takes a URL or a `File` from an `<input type="file">` or a drop. Files on another origin need CORS headers, or the browser won't let the canvas read their pixels.

### Camera [#camera]

The camera button (or `webcam`) swaps the video for the visitor's camera, mirrored like a selfie view. Browsers only offer the camera in a secure context: `https://` or `localhost`. On plain `http://`, such as a dev server reached by its LAN or Tailscale address, the box says so instead of failing silently. The camera turns off when the button is pressed again or the component unmounts.

### Sound [#sound]

With `audioReactive`, a sound button appears. Turning it on unmutes the soundtrack (or opens the microphone, with the camera) and lets it move the glyphs. The bass raises contrast, the overall level lifts brightness and the treble shifts random rows sideways for a frame. `reactivity` scales all three.

## Examples [#examples]

### Your own file [#your-own-file]

Pick any video on your machine. It stays in the browser: nothing is uploaded.

```tsx title="examples/ascii-video-file.tsx"
"use client";

import { useState } from "react";

import { AsciiVideo } from "@/components/asciicn/ascii-video";

export default function AsciiVideoFile() {
  const [file, setFile] = useState<File | null>(null);

  return (
    <div className="flex w-full max-w-xl flex-col gap-3">
      <label className="flex items-center gap-3 font-mono text-xs">
        <span className="text-muted-foreground">open a video</span>
        <input
          type="file"
          accept="video/*"
          onChange={(event) => setFile(event.target.files?.[0] ?? null)}
          className="text-xs file:mr-3 file:border file:bg-background file:px-2 file:py-1 file:font-mono file:text-foreground"
        />
      </label>
      <AsciiVideo
        src={file ?? "/examples/video/liftoff.mp4"}
        alt={
          file ? `Your video, ${file.name}, in glyphs` : "Apollo 11 lifting off"
        }
        charset="blocks"
        colorMode="ansi16"
        ansi="xterm"
        cell={10}
        className="aspect-video w-full"
      />
    </div>
  );
}
```

### Camera in phosphor Braille [#camera-in-phosphor-braille]

Press the camera button. With sound on, the microphone drives the glyphs.

```tsx title="examples/ascii-video-webcam.tsx"
import { AsciiVideo } from "@/components/asciicn/ascii-video";

export default function AsciiVideoWebcam() {
  return (
    <AsciiVideo
      alt="Your camera, drawn in green phosphor Braille"
      charset="braille"
      colorMode="mono"
      phosphor="p1"
      autoLevels
      audioReactive
      cell={9}
      className="aspect-[4/3] w-full max-w-xl"
    />
  );
}
```

## Props [#props]

Every [ASCII image](/docs/images/ascii-image#props) look prop also applies.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `src` | `string \| Blob` |  | A video URL, or a File the visitor picked. |
| `alt` | `string` |  | What the video shows, read by screen readers. |
| `webcam` | `boolean` | `false` | Start with the camera instead of src. |
| `mirror` | `boolean` | `true for the camera, false for files` | Flip left to right. |
| `autoPlay` | `boolean` | `true` | Play muted on load. Off under reduced motion. |
| `controls` | `boolean` | `true` | Show the play, camera and sound buttons. |
| `audioReactive` | `boolean` | `false` | Add a sound button that lets audio drive contrast, brightness and row jitter. |
| `audio` | `"track" \| "mic"` | `"track", or "mic" with the camera` | Where the sound comes from. |
| `reactivity` | `number` | `0.6` | How strongly sound moves the glyphs, 0 to 1. |
| `charset` | `CharsetName` | `"standard"` | Which glyphs draw the frames. |
| `colorMode` | `"mono" \| "truecolor" \| "ansi16" \| "duotone" \| "palette"` | `"truecolor"` | How glyphs are coloured. |
| `fit` | `"cover" \| "contain" \| "fill"` | `"cover"` | How the video fills its box. |
| `autoLevels` | `boolean` | `false` | Stretch every frame's tones to the whole ramp. Helps dim webcams. |
| `fps` | `number` | `30` | Frames sampled per second, 12 to 60. |
| `cell` | `number` | `10` | Glyph cell height in CSS pixels. |
| `handle` | `RefObject<AsciiHandle \| null>` |  | Filled with a handle for export, including video recording from the canvas. |

## Notes [#notes]

* The glyphs are labelled as an image with `alt`. The buttons sit outside that image, so screen readers reach them, and each says whether it is on.
* Under reduced motion the video loads paused on its first frame. Play still works.
* Sampling pauses when the video is offscreen or the tab is hidden; playback itself continues.
* The sample clip is NASA footage of the Apollo 11 launch (public domain) with a synthesised rumble added for the sound demo.