# Sending video

> Go live from OBS, ffmpeg, a hardware encoder or a browser, and the limits every publisher has to stay within.

A stream accepts video over **RTMP** (OBS, ffmpeg, vMix, Wirecast, most hardware encoders) or **WHIP** (browsers and WebRTC encoders). Both are in the response to [creating a stream](/api-reference#create-a-stream). Use one publisher at a time: a second connection with the same key is refused while the first is live.

## Settings that matter

| Setting | Use | Why |
|---|---|---|
| Video codec | H.264 | Your top quality is copied to viewers untouched, which needs H.264. |
| Keyframe interval | **2 seconds** | Viewers' video comes in 4-second pieces that must start on a keyframe. Longer intervals make quality switching stutter; `ingest.keyframe_warning` turns true above 2.5 s. |
| Bitrate | Up to 6000 Kbps | Above 7.5 Mbps sustained (audio and overhead included), the stream ends after a 20-second warning. |
| Resolution | At most your `max_resolution` | Declare the tallest video you'll send: 480, 720 (default) or 1080. Sending more ends the stream after a 20-second warning. Portrait video is fine: 720×1280 counts as 720. |
| Audio | AAC, 48 kHz | 128–160 Kbps is plenty. |

## OBS Studio

**Settings → Stream:** Service **Custom**, Server = `rtmp_url`, Stream Key = `stream_key`.

**Settings → Output** (Output Mode: Advanced), Streaming tab:

| Field | Value |
|---|---|
| Encoder | x264, or your hardware H.264 encoder (NVENC, Apple VT, QuickSync) |
| Rate Control | CBR |
| Bitrate | 2500 Kbps for 720p, 4500–6000 for 1080p |
| Keyframe Interval | 2 s (not 0) |

**Settings → Video:** Output (Scaled) Resolution no taller than your `max_resolution`; 30 fps is enough for talks and classes, 60 for sport and games.

## ffmpeg

From a file, looping, without re-encoding if the file is already H.264 with 2-second keyframes:

```bash
ffmpeg -re -stream_loop -1 -i talk.mp4 -c copy -f flv "$RTMP_URL/$STREAM_KEY"
```

From a camera or any source, encoding with the right settings:

```bash
ffmpeg -re -i input.mp4 \
  -c:v libx264 -preset veryfast -b:v 2500k -maxrate 2500k -bufsize 5000k \
  -g 60 -keyint_min 60 -sc_threshold 0 -pix_fmt yuv420p -vf "scale=-2:720" \
  -c:a aac -b:a 128k -ar 48000 \
  -f flv "$RTMP_URL/$STREAM_KEY"
```

`-g 60` is a keyframe every 60 frames, which is 2 seconds at 30 fps; use `-g 120` at 60 fps. Quote the address: the stream key contains a `?`.

## From a browser (WHIP)

`whip_url` accepts a standard WHIP offer from any page: your own site, a web app, a studio tool. What to send:

- **One video track, H.264.** Browsers prefer VP8 unless told otherwise; set H.264 as the preferred codec in the offer. A publish with more than one video layer (simulcast) is refused and the stream ends with `simulcast_not_supported`.
- **The declared resolution from the start.** Browsers start small and raise the resolution over about 15 seconds. Set `degradationPreference: 'maintain-resolution'` on the sender to avoid it. JustOnAir encodes a browser's top quality at your declared size either way, so viewers never see the ramp.
- **Networks that block UDP.** A TURN relay over TCP runs on port 3478 of the host in `whip_url`: `turn:<host>:3478?transport=tcp`, username `ome`, credential `airen`.

Two WHIP response headers aren't readable from another origin: `Location` (so a page can't send the WHIP DELETE; closing the peer connection ends the publish just as well) and the TURN `Link` header (so pass the TURN server yourself, as above).

## When the connection drops

If the publisher disconnects, the stream stays `live` for **60 seconds** and accepts the same key again. OBS reconnects on its own. A publisher whose old connection is still half-open is refused once while the old one is cleared, then let in on the next try, usually within about 10 seconds. After 60 seconds without a publisher the stream ends with `end_reason: "disconnected"`.

## If you lose the stream key

The key is shown once. While the stream is still `pending`, [replace it](/api-reference#replace-a-lost-stream-key): the old key stops working at once. Once live, the key can't change, because it is how a reconnecting publisher proves it is the same one.

## Adaptive or passthrough

`profile: "abr_basic"` (the default) gives viewers the quality you send plus smaller ones: 480p below a 720p source, 720p and 480p below 1080p. Viewers on weak connections switch down instead of buffering.

`profile: "passthrough"` delivers exactly what you send and nothing else, for a sixth of the encoding price. Choose it when your viewers have good connections, or when you already send several qualities yourself in a different way.
