# Live streaming

> How the live screen works, frame rates per plan, controlling a device with taps, swipes, text and keys, and what a viewer keeps alive.

Source: https://akaphone-docs.pages.dev/concepts/streaming/

Every running device can stream its screen to you while you watch. The dashboard uses this for its live view; you can use the same WebSocket from your own code (protocol in [Live stream API](https://akaphone-docs.pages.dev/api/stream/)).

## How it works

1. A viewer opens a WebSocket to `/v1/devices/:id/stream`.
2. When the first viewer connects, AkaPhone asks the device's host to start capturing. The host sends JPEG frames, AkaPhone relays each one to every connected viewer.
3. Taps, swipes, text and keys travel the other way and are executed on the device with `adb shell input`.
4. When the last viewer disconnects, capture stops. Streaming does not cost anything beyond the device minutes.

Frames are only produced while someone is watching. To grab a single image without a stream, call `GET /v1/devices/:id/screenshot`.

## Frame rate

| Plan | Frames per second |
|---|---|
| Free | 2 |
| Dev | 4 |
| Team | 5 |

Frames are JPEG, downscaled by the host (540 px wide by default). When the screen does not change the host still resends a frame every 10 seconds, so a new viewer is never left blank for long. A viewer that connects gets the last frame immediately if one exists.

## Input

The same four input types work over the stream socket and over `POST /v1/devices/:id/input`:

| Type | Fields | Example |
|---|---|---|
| `tap` | `x`, `y` (device pixels, 0 to 10000) | `{"type":"tap","x":360,"y":780}` |
| `swipe` | `x1`, `y1`, `x2`, `y2`, `ms` (duration, 1 to 10000, default 300) | `{"type":"swipe","x1":360,"y1":1200,"x2":360,"y2":400,"ms":250}` |
| `text` | `text` (1 to 500 characters) | `{"type":"text","text":"hello@example.com"}` |
| `key` | `key`: `HOME`, `BACK`, `RECENTS`, `ENTER`, `DEL` or `TAB` | `{"type":"key","key":"BACK"}` |

Coordinates are in the device's own pixels (for the default `pixel_8` profile, 720×1560), not in the downscaled frame. Scale clicks on a frame by `device width / frame width`.

Input on the socket is limited to **20 messages per second per connection**. Extra messages are dropped with an `error` event. Long text is best sent as one `text` message rather than one per character.

## Viewers keep a device alive

A device with at least one open stream connection is never stopped as idle. Close the socket (or the browser tab) when you are done watching, or stop the device. If your balance runs out, the device stops anyway and viewers get `device.stopped` with reason `credits`.

## Authentication

Browsers cannot set an `Authorization` header on a WebSocket, so there are three ways in:

| Who | How |
|---|---|
| The dashboard | The `ak_session` cookie, from `app.akaphone.com` only. |
| Servers and scripts | `Authorization: Bearer ak_live_…` on the upgrade request. |
| Your own web page | Get a short-lived stream token from your backend with `POST /v1/devices/:id/stream-token`, then connect with `?key=<token>`. Tokens work once, for one device, for 5 minutes. |

Never put a `ak_live_` key in front-end code. Stream tokens exist so you do not have to.
