On a TV there is no keyboard and no screenshot key, so Everframe does not put a reporter on the TV at all. The app runs in companion mode: it shows a pairing code, and the reporter lives on a phone in your hand or in the dashboard on your desk. The TV still captures everything — screenshot, breadcrumbs, console, network, device context — it just does not ask anyone to type.
This is how native Android TV and
Fire TV apps report. If your TV app is React Native, use
@everframe/react-native instead — it drives
this same machinery through the bridge, and calling both would be starting the
session twice.
Starting a session
Everframe.start(config) // the SDK itself, first
Everframe.startCompanion() // then the companion relay
// …later
Everframe.stopCompanion()
startCompanion() takes no arguments — it reads the SDK key and the companion
fields off the config you already passed to start().
Two behaviours worth knowing:
- It no-ops before
start()has run. There is no config to read yet, so the call is silently ignored rather than half-starting a session. Order matters. - It no-ops while a session is already live. Call
stopCompanion()first to restart.
Reading session state
Everframe.companion exposes each field as a Kotlin StateFlow:
lifecycleScope.launch {
Everframe.companion.state.collect { state ->
when (state) {
CompanionState.Unpaired -> showQr(
Everframe.companion.pairUrl.value,
Everframe.companion.code.value,
)
else -> hideQr()
}
}
}
| Flow | What it is |
|---|---|
state | What the relay is doing — see the table below. |
pairUrl | The URL to render as a QR code. null until the socket is up. |
code | Short display code, shown beside the QR so a teammate can find this device in the dashboard’s Companion list. |
attachedUserName | The dashboard user’s display name once someone attaches. |
resolvedName | The server-resolved name for this device. |
attachChallenge | A live attach-PIN request — AttachChallengeInfo(code, requestedByName, ttlMs). |
There is no running flag here — that is a JavaScript-side concept. On Android
you already know whether you called startCompanion().
Session states
CompanionState | Meaning |
|---|---|
Unpaired | No active pairing. The relay has issued a pair token — show the QR. |
Paired | A phone is bonded, awaiting a report trigger. |
ReportInProgress | A report request arrived; the payload is being captured and shipped. |
PhoneDisconnected | Backgrounded or the socket dropped. Reconnect with backoff is already scheduled. |
Attach from the dashboard
A teammate can attach from the dashboard’s Companion tab instead of scanning. When they do, the TV shows a four-digit PIN that the teammate types to confirm — so attaching to a screen always requires someone who can see it.
The SDK renders that PIN for you by default. A host running AttachPinUi.CUSTOM
collects it from Everframe.companion.attachChallenge and renders its own. If you
cannot commit to rendering it, choose the off mode rather than CUSTOM —
CUSTOM suppresses the built-in dialog, so a host that sets it and renders
nothing leaves every attach request stranded until the challenge expires.
Configuration
Three companion fields live on EverframeConfig:
| Field | Default | What it does |
|---|---|---|
companionDeviceId | null | Stable device identity override. null lets the SDK derive and hash one itself. |
companionBadgeEnabled | true | The on-screen badge naming who is attached. |
companionBadgePosition | null | "bottom-right" (default), "bottom-left", "top-right" or "top-left". |
The badge is identification, not a privacy control. It tells a person in the room who is attached; it is not a capture indicator and must not be described as one.