# Transkrypcja na żywo

> Przesyłaj dźwięk z mikrofonu lub karty przez WebSocket do transcript1 i odbieraj linie napisów w trakcie mówienia, opcjonalnie z tłumaczeniem w czasie rzeczywistym.

Canonical: https://transcript1.com/pl/docs/live
Language: pl
Last updated: 2026-11-03

Transkrypcja na żywo zamienia strumień audio w linie napisów, kiedy ludzie jeszcze mówią. Twój klient wysyła surowe audio PCM przez WebSocket, a transcript1 odsyła linie, które najpierw są wstępnym rozpoznaniem, a potem stają się ostateczne. Dodaj `translate_to`, a każda linia dostanie też tłumaczenie. Po zakończeniu sesji wynik zapisuje się jako zwykła transkrypcja.

Nie ma osobnych planów dla transkrypcji na żywo: korzysta ona z tego samego salda minut co wszystko inne. Jak liczy się czas połączenia, opisuje sekcja [Rozliczenia](https://transcript1.com/pl/docs/live#billing).

## Kiedy używać transkrypcji na żywo

| Użyj transkrypcji na żywo, gdy | Użyj [zwykłej transkrypcji](https://transcript1.com/pl/docs/getting-started), gdy |
|---|---|
| Dźwięk dopiero powstaje: rozmowa, wykład, transmisja, film, który właśnie oglądasz. | Masz link albo gotowy plik. |
| Potrzebujesz napisów w ciągu sekundy lub dwóch od wypowiedzi. | Zależy ci na najwyższej dokładności i możesz poczekać na wynik. |
| Chcesz mieć tłumaczenie w czasie rzeczywistym obok każdej linii. | Chcesz przetłumaczyć gotową transkrypcję albo na kilka języków. |

Transkrypcja na żywo przyjmuje wyłącznie dźwięk, jako surowe PCM. Nie przyjmuje linków do wideo, plików przesłanych, WebM, Opus ani MP3. Nagraj dźwięk po stronie klienta i najpierw zamień go na PCM. Jak to zrobić, pokazuje sekcja [Przechwytywanie dźwięku w przeglądarce](https://transcript1.com/pl/docs/live#capture-audio-in-a-browser).

## Szybki start z SDK

Zainstaluj SDK w wersji 1.5.0 lub nowszej: `npm i @transcript1/sdk@latest`. `t1.live.start` tworzy sesję, łączy się i kończy się, gdy sesja wystartuje, więc od razu możesz wysyłać dźwięk.

```javascript
import { createReadStream } from 'node:fs';
import { setTimeout as sleep } from 'node:timers/promises';
import { Transcript1 } from '@transcript1/sdk';

const t1 = new Transcript1(); // reads TRANSCRIPT1_API_KEY

const live = await t1.live.start({ language: 'en', translateTo: 'es', sampleRate: 16000 });

live.on('line', line => {
  const mark = line.final ? 'final  ' : 'partial';
  console.log(mark, line.text, '→', line.translation?.text ?? '');
});
live.on('warning', warning => console.warn(warning.code));

// speech.pcm: 16 kHz, mono, signed 16-bit little-endian, no header
for await (const chunk of createReadStream('./speech.pcm', { highWaterMark: 3200 })) {
  live.send(chunk);
  await sleep(100); // 3,200 bytes are 100 ms of audio: send at real-time speed
}

const result = await live.end();
console.log(result.reason, result.transcriptionId, result.minutesCharged);
```

Linie aktualizują się w miejscu. Gdy przyjdzie linia z tym samym `id`, zastąp tę, którą już masz, albo odczytaj wszystkie z `live.lines`. `end()` kończy sesję, czeka na ostatnie linie i zapisuje transkrypcję. Wszystkie metody i zdarzenia opisuje [SDK dla Node.js](https://transcript1.com/pl/docs/sdk#live-transcription).

## Szybki start z czystym WebSocketem

Bez SDK są dwa kroki: utwórz sesję przez HTTPS, a potem otwórz WebSocket z jednorazowym tokenem, który dostaniesz w odpowiedzi.

### Utwórz sesję

```bash
curl -X POST https://api.transcript1.com/v1/live/sessions \
  -H "Authorization: Bearer $TRANSCRIPT1_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"language": "en", "translate_to": "es", "sample_rate": 16000}'
```

API odpowiada kodem `201`:

```json
{
  "id": "lv_8fK2Lm0PqRsTuVwXyZaBcD",
  "status": "waiting",
  "transcription_id": "tr_3NcQvXy7BtHjKd5ErFgLmA",
  "websocket_url": "wss://api.transcript1.com/v1/live/sessions/lv_8fK2Lm0PqRsTuVwXyZaBcD/stream",
  "token": "Zk3x...",
  "token_expires_at": "2026-11-02T12:01:00Z",
  "rate": 1
}
```

Gdy `save` ma wartość `false`, `transcription_id` to `null`. Token jest jednorazowy i wygasa po 60 sekundach, więc połącz się od razu. `rate` to liczba minut salda pobieranych za każdą rozpoczętą minutę tej sesji; zobacz [Rozliczenia](https://transcript1.com/pl/docs/live#billing).

Ciało żądania przyjmuje te pola. Wszystkie są opcjonalne, a wymagany zakres to `transcriptions:write`:

| Pole | Domyślnie | Zasada |
|---|---|---|
| `language` | `auto` | `auto` albo dwuliterowy kod języka obsługiwany przez transkrypcję na żywo. |
| `translate_to` | brak | Dwuliterowy kod języka. Jeden język docelowy na sesję. Zobacz [Tłumaczenie na żywo](https://transcript1.com/pl/docs/live#translate-live). |
| `diarize` | `false` | Oznacza rozmówców. Niektóre ustawienia nie pozwalają rozpoznawać rozmówców, na przykład razem z `language: "auto"`, i kończą się błędem `422`. Sprawdź [możliwości](https://transcript1.com/pl/docs/live#capabilities). |
| `audio_format` | `pcm_s16le` | Jedyna wartość. Pole istnieje, żeby można było później dodać kolejne formaty. |
| `sample_rate` | `16000` | Liczba całkowita od 8000 do 48000. Tylko mono. |
| `save` | `true` | `false` nie zachowuje niczego po zakończeniu sesji. Zobacz [Zapisane transkrypcje](https://transcript1.com/pl/docs/live#saved-transcripts). |

### Połącz się i przesyłaj dźwięk

Otwórz WebSocket z tokenem jako drugim podprotokołem. Działa to w przeglądarkach i w Node 22+ bez własnych nagłówków. Zanim wyślesz dźwięk, poczekaj na `session.started`: ramki binarne wysłane wcześniej są odrzucane i dostajesz ostrzeżenie `audio_dropped`.

```javascript
import { createReadStream } from 'node:fs';
import { setTimeout as sleep } from 'node:timers/promises';

const session = await fetch('https://api.transcript1.com/v1/live/sessions', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.TRANSCRIPT1_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ language: 'en', translate_to: 'es', sample_rate: 16000 }),
}).then(response => response.json());

const ws = new WebSocket(session.websocket_url, ['t1-live', session.token]);
const lines = new Map();

ws.addEventListener('message', async message => {
  const event = JSON.parse(message.data);

  if (event.type === 'session.started') {
    for await (const chunk of createReadStream('./speech.pcm', { highWaterMark: 3200 })) {
      ws.send(chunk);
      await sleep(100);
    }
    ws.send(JSON.stringify({ type: 'end' }));
  } else if (event.type === 'line') {
    lines.set(event.line.id, event.line); // the full current state: replace by id
    console.log(event.line.final, event.line.text, event.line.translation?.text);
  } else if (event.type === 'session.ended') {
    console.log('ended:', event.reason, event.minutes_charged);
  }
});

ws.addEventListener('close', event => console.log('closed', event.code));
```

W Node 20 nie ma wbudowanego `WebSocket`: użyj pakietu `ws`, który ma tę samą formę `new WebSocket(url, protocols)`.

Na poprawny token serwer odpowiada podprotokołem `t1-live` i niczym więcej. Zły token nigdy nie psuje handshake'u: gniazdo się otwiera, dostajesz zdarzenie `error`, a gniazdo zamyka się kodem 4xxx, więc przeglądarka pokazuje prawdziwy powód zamiast ogólnego 1006. Zobacz [Kody zamknięcia](https://transcript1.com/pl/docs/live#close-codes).

## Przechwytywanie dźwięku w przeglądarce

Dźwięk na żywo musi być monofonicznym PCM 16-bit signed little-endian z częstotliwością `sample_rate` sesji. Przeglądarki dają próbki zmiennoprzecinkowe, więc zamień je w `AudioWorklet`. Ten fragment przechwytuje mikrofon jako PCM 16 kHz w porcjach po 100 ms i wysyła go przez wejście SDK dla przeglądarek. `getAccessToken` zwraca token dostępu OAuth; nigdy nie umieszczaj klucza API w przeglądarce. Zobacz [Bezpieczeństwo](https://transcript1.com/pl/docs/live#security).

```javascript
import { createLiveClient } from '@transcript1/sdk/live';

const workletSource = `
class Pcm16Processor extends AudioWorkletProcessor {
  constructor() {
    super();
    this.buffer = new Int16Array(1600); // 100 ms at 16 kHz
    this.filled = 0;
  }

  process(inputs) {
    const channel = inputs[0][0];
    if (!channel) return true;
    for (let i = 0; i < channel.length; i++) {
      const sample = Math.max(-1, Math.min(1, channel[i]));
      this.buffer[this.filled++] = sample < 0 ? sample * 0x8000 : sample * 0x7fff;
      if (this.filled === this.buffer.length) {
        this.port.postMessage(this.buffer.slice());
        this.filled = 0;
      }
    }
    return true;
  }
}
registerProcessor('pcm16', Pcm16Processor);
`;

const client = createLiveClient({ getAccessToken: () => myApp.getOAuthAccessToken() });

const stream = await navigator.mediaDevices.getUserMedia({ audio: { channelCount: 1 } });
const context = new AudioContext({ sampleRate: 16000 });
const moduleUrl = URL.createObjectURL(new Blob([workletSource], { type: 'text/javascript' }));
await context.audioWorklet.addModule(moduleUrl);

// the context's real rate: a browser that cannot do 16 kHz picks its own
const live = await client.start({ language: 'auto', sampleRate: context.sampleRate });
live.on('line', renderLine);

const microphone = context.createMediaStreamSource(stream);
const capture = new AudioWorkletNode(context, 'pcm16');
capture.port.onmessage = event => live.send(event.data); // an Int16Array
const mute = context.createGain();
mute.gain.value = 0;
microphone.connect(capture).connect(mute).connect(context.destination);

async function stop() {
  stream.getTracks().forEach(track => track.stop());
  await context.close();
  return live.end();
}
```

Worklet jest napisany pod 16 kHz. Jeśli kontekst działa z inną częstotliwością, porcja nie ma już 100 ms, ale PCM pozostaje poprawne, bo `sampleRate` zgadza się z kontekstem. Żeby przechwycić dźwięk karty lub filmu zamiast mikrofonu, utwórz `MediaStream` przez `getDisplayMedia` albo z `captureStream()` elementu multimedialnego i podłącz go tak samo.

## Tłumaczenie na żywo

Dodaj `translate_to` do sesji, a transcript1 będzie tłumaczyć każdą linię w trakcie mówienia.

- Jeden język docelowy na sesję. Kolejne języki dodasz później do zapisanej transkrypcji [endpointem tłumaczeń](https://transcript1.com/pl/docs/live#saved-transcripts).
- Każda `line` ma oryginał (`text`, `final`) i obiekt `translation` (`language`, `text`, `final`). `translation` to `null`, gdy `translate_to` nie jest ustawione.
- Oryginał i tłumaczenie stają się ostateczne niezależnie od siebie: linia może mieć ostateczny oryginał, a jej tłumaczenie wciąż się zmienia. Pokaż linię jako ostateczną dopiero, gdy oba są ostateczne.
- Linia, która jest już w języku docelowym, używa własnego tekstu jako tłumaczenia i jest ostateczna, gdy się zamknie.
- Linia zamyka się, gdy mówca zrobi pauzę, zmieni się mówca, przerwa między słowami przekroczy 0,9 sekundy, linia osiągnie dwa wiersze napisów (84 znaki) albo 15 sekund, albo wyślesz `finalize`. Przy włączonym tłumaczeniu zamknięcie linii może potrwać o około 3 sekundy dłużej, bo czeka na tłumaczenie. Wtedy tłumaczenie może pozostać otwarte (`final: false`) i dalej rosnąć, gdy oryginał jest już ostateczny.
- Tłumaczenie jest darmowe. Nie zużywa minut.

To, jakie języki docelowe i pary językowe są dostępne, zależy od dostawcy rozpoznawania mowy na żywo. Zamiast wpisywać reguły na sztywno, czytaj [możliwości](https://transcript1.com/pl/docs/live#capabilities). Nieobsługiwany język docelowy kończy się błędem `422 unsupported_translation`. Dostawca może na przykład wymagać jawnego `language` zamiast `auto` albo dopuszczać tylko pary zawierające angielski.

## Protokół WebSocket

### Łączenie

```javascript
new WebSocket(websocket_url, ['t1-live', token]);
```

Token podróżuje jako podprotokół WebSocket. Nigdy nie trafia do URL-a ani do logów dostępu. Nie wysyłaj nagłówka `Authorization`: poświadczeniem jest token. Serwer wysyła ping co 30 sekund; przeglądarki i pakiet `ws` odpowiadają automatycznie. Gniazdo, które przez 60 sekund nie wyśle pong, zostaje rozłączone.

Poprawne połączenie z sesją, która ma już gniazdo, zastępuje stare. Zobacz [Wznawianie i przejęcie](https://transcript1.com/pl/docs/live#resume-and-takeover).

### Komunikaty klienta

| Komunikat | Znaczenie |
|---|---|
| Ramka binarna | Dźwięk: PCM s16le mono z `sample_rate` sesji. Każda ramka ma parzystą długość i najwyżej 256 KB. Dobry rozmiar to około 100 ms na ramkę. |
| `{"type":"finalize"}` | Zamknij bieżącą linię teraz, na przykład gdy film jest wstrzymany. |
| `{"type":"end"}` | Zakończ sesję i zapisz ją. Serwer odpowiada `session.ended` i zamyka gniazdo. |
| Cokolwiek innego | Niekrytyczne zdarzenie `error` z kodem `invalid_message`. |

Ramka o nieparzystej długości albo zbyt duża to naruszenie protokołu i zamyka gniazdo kodem `4008`.

Zacznij wysyłać dźwięk po `session.started`. Klient, który wysyła dźwięk szybciej niż w czasie rzeczywistym, jest rozliczany za czas dźwięku zamiast czasu połączenia (zobacz [Rozliczenia](https://transcript1.com/pl/docs/live#billing)), więc wysyłaj w tempie, w jakim dźwięk powstaje.

### Zdarzenia serwera

Każde zdarzenie to tekstowa ramka JSON z polem `type`.

| Zdarzenie | Kiedy jest wysyłane |
|---|---|
| `session.started` | Połączenie zostało przyjęte. Od teraz wysyłaj dźwięk. |
| `line` | Linia napisów powstała albo się zmieniła. |
| `usage` | Po każdym pobraniu opłaty. |
| `warning` | Coś wymaga uwagi, ale sesja trwa dalej. |
| `error` | Niekrytyczny problem albo powód natychmiastowego zamknięcia. |
| `reconnect` | Serwer zaraz się zrestartuje. Wznów sesję. |
| `session.ended` | Sesja się skończyła i została zapisana. Ostatnie zdarzenie przed normalnym zamknięciem. |

`session.started` mówi, czy to wznowienie, i ile dźwięku sesja już przetworzyła:

```json
{
  "type": "session.started",
  "session_id": "lv_8fK2Lm0PqRsTuVwXyZaBcD",
  "transcription_id": "tr_3NcQvXy7BtHjKd5ErFgLmA",
  "resumed": false,
  "offset_ms": 0
}
```

`line` zawiera pełny aktualny stan jednej linii, więc zastąp nim linię z tym samym `id`. Zobacz [Obiekt line](https://transcript1.com/pl/docs/live#the-line-object).

```json
{
  "type": "line",
  "line": {
    "id": 12,
    "start_ms": 53120,
    "end_ms": 56040,
    "speaker": null,
    "language": "en",
    "text": "Welcome back to the stream",
    "final": true,
    "translation": { "language": "es", "text": "Bienvenidos de nuevo", "final": false }
  }
}
```

`usage` jest wysyłane po każdym pobraniu opłaty, z czasem połączenia, czasem dźwięku, liczbą minut salda pobranych do tej pory i minutami, które ci zostały:

```json
{
  "type": "usage",
  "connected_ms": 61240,
  "audio_ms": 60100,
  "minutes_charged": 2,
  "minutes_remaining": 898
}
```

`warning` ma pole `code`:

| `code` | Znaczenie |
|---|---|
| `balance_low` | Następne pobranie opłaty się nie powiedzie. Gdy nadejdzie jego termin, sesja kończy się z `balance_exhausted`. Nie jest wysyłane, gdy włączone jest [automatyczne doładowanie](https://transcript1.com/pl/docs/billing-and-limits#auto-top-up). |
| `audio_dropped` | Dźwięk przyszedł, zanim sesja była gotowa, albo przyszło go za dużo, gdy łączył się dostawca rozpoznawania mowy. Najstarszy dźwięk został odrzucony. |
| `idle_timeout_soon` | Brak dźwięku od 4 minut. Sesja kończy się po 5 minutach bez dźwięku. Wyślij dźwięk, żeby ją utrzymać. |

```json
{ "type": "warning", "code": "idle_timeout_soon" }
```

`error` ma `code` i `message`. Na gnieździe live mogą pojawić się te kody:

| `code` | Krytyczny | Znaczenie |
|---|---|---|
| `invalid_message` | Nie | Ramka tekstowa, która nie jest `finalize` ani `end`. |
| `invalid_token` | Tak | Token jest nieprawidłowy, wygasł albo został już użyty. Gniazdo zamyka się kodem `4001`. |
| `not_found` | Tak | Sesja nie istnieje. Gniazdo zamyka się kodem `4004`. |
| `session_ended` | Tak | Sesja już się zakończyła. Gniazdo zamyka się kodem `4004`. |

```json
{ "type": "error", "code": "invalid_message", "message": "Expected {\"type\":\"finalize\"} or {\"type\":\"end\"}" }
```

`reconnect` przychodzi, zanim serwer zamknie gniazdo kodem `1012`, na przykład podczas wdrożenia. Pobierz nowy token i wznów sesję.

```json
{ "type": "reconnect", "reason": "server_restart" }
```

`session.ended` to ostatnie zdarzenie każdej sesji. Liczby to końcowe sumy:

```json
{
  "type": "session.ended",
  "reason": "client_ended",
  "transcription_id": "tr_3NcQvXy7BtHjKd5ErFgLmA",
  "minutes_charged": 3,
  "connected_ms": 142500,
  "audio_ms": 141900
}
```

### Obiekt line

| Pole | Znaczenie |
|---|---|
| `id` | Numer liczony od 0, rosnący o jeden z każdą linią. To indeks segmentu w zapisanej transkrypcji. |
| `start_ms`, `end_ms` | Początek i koniec w czasie dźwięku sesji. Ten czas biegnie dalej po ponownych połączeniach. |
| `speaker` | Etykieta rozmówcy, gdy `diarize` jest włączone, w przeciwnym razie `null`. Etykiety są takie same jak w zwykłych transkrypcjach. |
| `language` | Wykryty lub podany język linii albo `null`, gdy nie jest jeszcze znany. |
| `text` | Dotychczasowy tekst oryginału. |
| `final` | `true`, gdy tekst oryginału już się nie zmieni. |
| `translation` | `null` bez `translate_to`, w przeciwnym razie `language`, `text` i `final`. |

Linie z `final: false` pokazuj jaśniejszym stylem: ich tekst może się jeszcze zmienić. Tekst linii z `final: true` nigdy się już nie zmienia, a ostateczna linia nigdy nie jest usuwana.

SDK używa `camelCase` dla pól linii: `startMs`, `endMs`.

### Powody zakończenia

`session.ended` i `GET /v1/live/sessions/:id` podają `end_reason`:

| Powód | Znaczenie |
|---|---|
| `client_ended` | Wysłano `end`. |
| `balance_exhausted` | Następne pobranie opłaty się nie powiodło. Sesja kończy się na granicy minuty i jest zapisywana. |
| `idle_timeout` | 5 minut bez dźwięku. |
| `max_duration` | Sesja była połączona przez 300 minut. Rozpocznij nową. |
| `disconnected` | Klient był nieobecny dłużej niż 2 minuty i nie wznowił sesji. |
| `upstream_error` | Dostawca rozpoznawania mowy zawiódł. |
| `internal_error` | Coś poszło nie tak po naszej stronie. |
| `not_connected` | Sesja nie połączyła się w ciągu 5 minut. Nigdy nie jest rozliczana i widać ją tylko przez `GET`. |

### Kody zamknięcia

| Kod | Znaczenie |
|---|---|
| `1000` | Normalne zamknięcie. Zawsze po `session.ended`. |
| `1011` | Nieoczekiwany błąd serwera. Serwer odzyskuje sesję: wznów ją z nowym tokenem. |
| `1012` | Restart serwera. Po `reconnect`: wznów sesję z nowym tokenem. |
| `4001` | Token jest nieprawidłowy, wygasł albo został już użyty. |
| `4004` | Nie znaleziono sesji albo już się zakończyła. |
| `4008` | Naruszenie protokołu: ramka binarna o nieparzystej długości albo zbyt duża. |
| `4010` | Zastąpione nowszym połączeniem z tą samą sesją. |

Podczas restartu API nowe żądanie WebSocket może dostać odpowiedź HTTP `503`. Spróbuj ponownie za chwilę.

## Wznawianie i przejęcie

Sesja przetrwa zerwaną sieć, wdrożenie API i awarię. Po zerwaniu połączenia czeka na ciebie 2 minuty; potem kończy się jako `disconnected` i jest zapisywana.

1. Wywołaj `POST /v1/live/sessions/:id/token`. Odpowiedź zawiera `token`, `token_expires_at` i `websocket_url`, czyli te same pola co przy tworzeniu sesji. Gdy sesja już się zakończyła, kończy się to błędem `409 session_ended`.
2. Otwórz nowy WebSocket z tym tokenem.
3. Przychodzi `session.started` z `resumed: true`. `offset_ms` to ilość dźwięku, którą sesja już przetworzyła: kontynuuj od tego miejsca. Identyfikatory linii liczą się dalej.

Linie, które były już ostateczne, zostają. Niedokończony koniec z chwili zerwania przepada, więc jeśli buforujesz dźwięk, odtwórz ponownie kilka sekund od `offset_ms`. Przerwy na ponowne łączenie nie są rozliczane.

Przejęcie to to samo wywołanie. Jeśli serwer nadal uważa, że gniazdo jest otwarte, nowe poprawne połączenie je zastępuje, a stare gniazdo zamyka się kodem `4010` po zapisaniu jego linii. Limit równoczesnych sesji nigdy nie blokuje wznowienia.

SDK robi to wszystko za ciebie: po zerwaniu połączenia albo zdarzeniu `reconnect` pobiera token i łączy się ponownie z opóźnieniem rosnącym od 0,5 do 8 sekund, przez maksymalnie 2 minuty, a ostatnie 10 sekund dźwięku wysłanego w czasie przerwy odtwarza ponownie.

## Rozliczenia

Transkrypcja na żywo korzysta z tego samego salda minut co transkrypcje wsadowe.

- **Rozliczany jest czas połączenia**, od przyjęcia gniazda do jego zamknięcia. Cisza i wstrzymany film się liczą. Czas między zerwaniem a wznowieniem już nie.
- **Za każdą rozpoczętą minutę, minimum 1.** Pierwsza minuta jest pobierana, gdy pierwsze połączenie zostanie przyjęte, a każda następna, gdy się zaczyna.
- **Szybciej niż w czasie rzeczywistym rozlicza się jako czas dźwięku.** Jeśli wysłany dźwięk jest dłuższy niż czas połączenia, rozliczany jest czas dźwięku.
- **Stawkę odczytujesz z API.** Jedna rozpoczęta minuta kosztuje `rate` minut salda, zaokrąglone w górę. Stawka to zwykle 1 i może mieć części dziesiętne. Stawkę live odczytasz z `rates.live` w `GET /v1/account`, z `rate` w `GET /v1/live/capabilities` oraz z `rate` w tworzonej sesji. Stawka sesji jest stała od jej utworzenia.
- **Tłumaczenie jest darmowe.**
- **Brak połączenia oznacza brak opłaty.** Sesja, która nie połączy się w ciągu 5 minut, kończy się jako `not_connected`.
- **Błędy są zwracane.** Jeśli sesja kończy się z `upstream_error` lub `internal_error`, zanim wytworzy choć jedną linię, wszystkie opłaty wracają na konto. Pusta sesja z powodu ciszy jest rozliczana i zapisywana jako pusta.
- **Gdy saldo się skończy** i włączone jest [automatyczne doładowanie](https://transcript1.com/pl/docs/billing-and-limits#auto-top-up), transcript1 obciąża zapisaną kartę, nie przerywając strumienia, i sesja trwa dalej. Jeśli automatyczne doładowanie jest wyłączone lub wstrzymane albo obciążenie się nie powiedzie, sesja jest finalizowana, zapisywana i kończy się z `balance_exhausted` na granicy minuty. Ostrzeżenie `balance_low` pojawia się wcześniej, chyba że automatyczne doładowanie jest włączone.

Utworzenie sesji zwraca `402 insufficient_minutes`, gdy twoje dostępne saldo jest niższe niż jedna rozpoczęta minuta przy bieżącej stawce. Jeśli włączone jest automatyczne doładowanie, transcript1 najpierw próbuje doładować konto. Zobacz [Rozliczenia i limity](https://transcript1.com/pl/docs/billing-and-limits#live-transcription).

## Limity

| | Free | Monthly | Annual |
|---|---|---|---|
| Równoczesne sesje na żywo | 1 | 2 | 3 |

- Sesja liczy się, dopóki jest w stanie `waiting`, `connected` lub `disconnected` (w ramach 2-minutowego okna). Po przekroczeniu limitu `POST /v1/live/sessions` zwraca `429 too_many_live_sessions` z nagłówkiem `Retry-After: 30`.
- Sesje na żywo nie liczą się do limitu aktywnych zadań.
- Sesja trwa najwyżej 300 minut czasu połączenia. Potem kończy się z `max_duration`.
- Sesja bez dźwięku przez 5 minut kończy się z `idle_timeout`; po 4 minutach dostajesz `idle_timeout_soon`. Wstrzymany strumień nadal nalicza czas połączenia i liczy się jako bezczynny, więc przy długiej przerwie zakończ sesję.
- transcript1 ogranicza też łączną liczbę wszystkich sesji na żywo. Gdy wszystkie miejsca są zajęte, `POST /v1/live/sessions` zwraca `503 live_capacity`, co można ponowić. Poczekaj na `Retry-After` (30 sekund) i spróbuj jeszcze raz. Sprawdzamy to tylko przy tworzeniu sesji, nigdy w jej trakcie.
- Tworzenie sesji i pobieranie tokenów liczą się do limitu żądań na minutę.
- Dźwięk to wyłącznie surowe PCM: `pcm_s16le`, mono, od 8000 do 48000 Hz.

## Możliwości

`GET /v1/live/capabilities` opisuje, co transkrypcja na żywo obsługuje w tej chwili, żeby klienci nie musieli wpisywać reguł dostawcy na sztywno. Wymagany zakres to `transcriptions:read`.

```json
{
  "available": true,
  "auto_language": true,
  "languages": ["en", "es"],
  "translation": {
    "targets": ["es", "de"],
    "pairs": "any",
    "requires_language": false
  },
  "diarize": true,
  "audio_formats": ["pcm_s16le"],
  "sample_rate": { "min": 8000, "max": 48000 },
  "max_session_minutes": 300,
  "rate": 1
}
```

Listy są tu skrócone. Odczytaj je z API.

- `translation.pairs` to `"any"` (dowolny wykryty język na dowolny język docelowy) albo lista obiektów z polami `from` i `to`.
- `translation.requires_language` ma wartość `true`, gdy tłumaczenie wymaga jawnego `language` zamiast `auto`.
- Gdy transkrypcja na żywo nie jest skonfigurowana, odpowiedź to `available: false` tylko z polem `rate`, a utworzenie sesji zwraca `503 live_unavailable`.

W SDK: `await t1.live.capabilities()`.

## Odczyt sesji

`GET /v1/live/sessions/:id` zwraca stan sesji. Wymagany zakres to `transcriptions:read`.

```bash
curl https://api.transcript1.com/v1/live/sessions/lv_8fK2Lm0PqRsTuVwXyZaBcD \
  -H "Authorization: Bearer $TRANSCRIPT1_API_KEY"
```

```json
{
  "id": "lv_8fK2Lm0PqRsTuVwXyZaBcD",
  "status": "ended",
  "end_reason": "client_ended",
  "transcription_id": "tr_3NcQvXy7BtHjKd5ErFgLmA",
  "options": {
    "language": "en",
    "translate_to": "es",
    "diarize": false,
    "audio_format": "pcm_s16le",
    "sample_rate": 16000,
    "save": true
  },
  "connected_ms": 142500,
  "audio_ms": 141900,
  "minutes_charged": 3,
  "rate": 1,
  "created_at": "2026-11-02T11:58:12Z",
  "ended_at": "2026-11-02T12:00:35Z"
}
```

`status` to `waiting`, `connected`, `disconnected` albo `ended`. `end_reason` to `null`, dopóki sesja się nie zakończy. W SDK: `await t1.live.get(id)`.

## Błędy

Błędy żądań używają zwykłej [koperty błędu](https://transcript1.com/pl/docs/errors). Oto te, które zwracają `POST /v1/live/sessions` i pokrewne endpointy:

| Status HTTP | `code` | Można ponowić | Kiedy |
|---|---|---|---|
| 422 | `invalid_request` | Nie | Brakuje pola albo jest nieprawidłowe, na przykład `sample_rate` spoza zakresu 8000–48000. |
| 422 | `unsupported_language` | Nie | `language` nie jest obsługiwany w transkrypcji na żywo. |
| 422 | `unsupported_translation` | Nie | Język docelowy albo para językowa nie jest obsługiwana. |
| 402 | `insufficient_minutes` | Nie | Twoje dostępne saldo jest niższe niż jedna rozpoczęta minuta przy bieżącej stawce. |
| 403 | `email_not_verified` | Nie | Najpierw potwierdź adres e-mail. |
| 429 | `too_many_live_sessions` | Tak | Wszystkie równoczesne sesje na żywo z twojego planu są w użyciu. Poczekaj na `Retry-After`. |
| 503 | `live_capacity` | Tak | Cała pojemność transkrypcji na żywo jest zajęta. Poczekaj na `Retry-After` (30 sekund). |
| 503 | `live_unavailable` | Nie | Transkrypcja na żywo jest teraz niedostępna. |
| 409 | `session_ended` | Nie | `POST /v1/live/sessions/:id/token` dla sesji, która już się zakończyła. |

Problemy na gnieździe docierają jako zdarzenia `error` i [kody zamknięcia](https://transcript1.com/pl/docs/live#close-codes).

## Zapisane transkrypcje

Przy `save: true` (domyślnie) sesja po zakończeniu zapisuje się jako zwykła transkrypcja. Dźwięk nigdy nie jest przechowywany.

- `transcription_id` dostajesz przy tworzeniu sesji. Jego status to `live`, gdy sesja trwa, i `completed` po jej zakończeniu. Sesja, która nigdy się nie połączyła, zostawia transkrypcję ze statusem `canceled`.
- Transkrypcja ma `source: { "platform": "live", "session_id": "lv_…" }`. Nie ma linku ani pliku. Panel wyświetla ją jako sesję na żywo.
- Każda linia staje się jednym segmentem o tym samym indeksie co `id` linii. Transkrypcję odczytasz jako JSON, tekst, SRT lub VTT, tak jak każdą inną: zobacz [dokumentację API](https://transcript1.com/pl/docs/api-reference).
- Tłumaczenie na żywo zapisuje się razem z nią. Odczytasz je parametrem `translation`, jak opisano w sekcji [Tłumaczenie](https://transcript1.com/pl/docs/translation#read-the-translation).
- Kolejne języki tłumaczenia dodasz później przez `POST /v1/transcriptions/:id/translations`, gdy transkrypcja ma status `completed`. Zobacz [Tłumaczenie](https://transcript1.com/pl/docs/translation#translate-an-existing-transcript).

Przy `save: false` po zakończeniu sesji nic nie zostaje: nie powstaje transkrypcja, a `transcription_id` to `null`. Nadal dostajesz każdą linię przez gniazdo i obowiązuje zwykła opłata.

## Bezpieczeństwo

- Token, który dostajesz przy tworzeniu sesji, jest jednorazowy i wygasa po 60 sekundach. Jest przesyłany jako podprotokół WebSocket, a nie w URL-u, więc nie trafia do logów dostępu. Wyciekły token jest bezużyteczny, gdy został użyty albo wygasł.
- Klucza API używaj tylko na serwerze, który kontrolujesz. Nigdy nie umieszczaj klucza API na stronie internetowej ani w rozszerzeniu.
- W przeglądarce lub rozszerzeniu loguj użytkowników przez OAuth i przekaż token dostępu OAuth do `getAccessToken` w `createLiveClient`. Do rozpoczynania sesji token potrzebuje zakresu `transcriptions:write`. Tokeny dostępu OAuth wygasają po 1 godzinie, a przy każdym wznowieniu SDK prosi `getAccessToken` o świeży token.
- Twój serwer może też utworzyć sesję kluczem API i przekazać przeglądarce tylko `websocket_url` oraz `token`. Klient łączy się wtedy bezpośrednio. Bez nowego tokena od ciebie nie może wznowić sesji.
