Full-Screen UI Mode

The full-screen UI is the default mode for the lcyt CLI. It provides a rich, multi-panel terminal interface for live captioning sessions.


Starting Full-Screen Mode

lcyt          # opens full-screen UI automatically when no other flags are given
lcyt -f       # explicit flag
lcyt --fullscreen

On the very first run (no stream key configured), the CLI launches a short setup wizard instead of the full-screen UI. After entering your stream key, the full-screen UI starts.


Panel Layout

The screen is divided into four panels:

PanelLocationPurpose
Text PreviewTop-leftShows the next line to be sent from a loaded file
LogBottom-leftOperational messages and caption send results
Sent CaptionsRightRolling history of captions delivered to YouTube
Input FieldBottomType captions or slash-commands here

Sending Captions

Type your caption text in the Input Field and press Enter to send it immediately. The caption is delivered to YouTube and logged in the Sent Captions panel.


Loading a File

Use the /load command to load a plain-text script, then step through it line by line:

/load /path/to/script.txt

Once a file is loaded, the Text Preview panel shows the current line. Use the arrow keys to navigate and Enter to send the current line.


Commands

Type a slash-command in the input field and press Enter:

CommandDescription
/load <path>Load a caption script from a file path or URL
/batchToggle batch mode — queue captions before sending
/sendSend the batch queue immediately
/api <path>Load a Google API credentials JSON for YouTube status polling
/stream <url-or-id>Set a YouTube video ID or URL to poll for live status
/resetReset the sequence counter to 0
/quitExit the full-screen UI

Batch Mode

Batch mode lets you queue multiple captions and send them as a group:

  1. Type /batch to enable batch mode.
  2. Enter each caption and press Enter — lines are queued but not sent yet.
  3. Type /send (or press the batch-send shortcut) to deliver the whole batch at once.

Keyboard Shortcuts

KeyAction
EnterSend current line or command
↑ / ↓Navigate through loaded file lines
Page Up / Page DownScroll the Sent Captions panel
Ctrl+CExit

Options

All connection options can be set once and are saved to ~/.lcyt-config.json:

lcyt --stream-key YOUR_KEY   # Set stream key (saved for future sessions)
lcyt --base-url URL          # Override ingestion URL
lcyt --region reg1           # Set region identifier
lcyt --verbose               # Enable verbose logging

Interactive Mode

Interactive mode reads captions line by line from standard input. It is useful for scripting, piping text from another program, or captioning in a terminal without the full-screen UI.


Starting Interactive Mode

lcyt -i
lcyt --interactive

Once started, the CLI waits for you to type a line and press Enter. Each line is sent to YouTube as a separate caption.


How It Works

$ lcyt -i
> Hello, welcome to the stream!       ← you type this, then Enter
✓ Caption sent [seq 1]
> Today we will be discussing...
✓ Caption sent [seq 2]
  • Each line you enter is sent immediately after pressing Enter.
  • The sequence counter increments automatically.
  • Press Ctrl+D (EOF) or Ctrl+C to quit.

Reading from a File or Pipe

Because interactive mode reads from stdin, you can pipe any program’s output directly into it:

# Send all lines of a text file, one by one
cat script.txt | lcyt -i

# Send output of a real-time transcription tool
my-transcriber | lcyt -i

When stdin is a pipe (not a TTY), the CLI reads lines until EOF and then exits automatically.


Options

FlagDescription
-i, --interactiveEnable interactive/pipe mode
-k, --stream-key KEYYouTube stream key
-t, --timestamp ISOManual timestamp override for every caption
--heartbeatSend a single heartbeat and exit (useful to test the connection before starting)
--verbosePrint detailed HTTP request/response information
--log-stderrWrite log output to stderr (useful when stdout is consumed by a pipe)

Example: Scripted Session

#!/bin/bash
# Send a series of pre-written captions with a delay between each
captions=(
  "Welcome to the show!"
  "Today's topic: live captioning"
  "Stay tuned for more"
)

for caption in "${captions[@]}"; do
  echo "$caption" | lcyt -i
  sleep 5
done

Notes

  • Interactive mode does not display the full-screen blessed UI; it is purely text-based and safe to use in CI pipelines, cron jobs, or scripts.
  • The stream key must be configured either via --stream-key or stored in ~/.lcyt-config.json beforehand.
  • Timestamps default to the current system time unless overridden with --timestamp.

Single Caption Mode

Single caption mode sends one caption (or a batch of captions) and exits immediately. It requires no interactive input and is the simplest way to send a caption from the command line or a script.


Sending a Caption

Pass the caption text as a positional argument:

lcyt "Hello, world!"
lcyt "Welcome to the stream!"

The caption is sent once and the process exits with code 0 on success or 1 on failure.


Setting the Stream Key

If you have not configured a stream key yet, set it with --stream-key. The key is saved to ~/.lcyt-config.json for future use:

lcyt --stream-key YOUR_KEY "First caption with key"

After the key is saved you can omit it in subsequent calls:

lcyt "Second caption"

Timestamp Override

By default the current time is used. Supply an explicit ISO timestamp with -t:

lcyt -t "2024-06-01T10:00:00.000" "Caption with a custom timestamp"

Batch Mode

Queue multiple captions across separate invocations and send them all at once:

# Queue captions (stored in ~/.lcyt-config.json)
lcyt -b "Line one"
lcyt -b "Line two" -t "2024-06-01T10:00:05.000"
lcyt -b "Line three"

# Send the queue in a single request
lcyt --send

The --send flag delivers all queued captions and clears the queue.


Heartbeat / Connection Test

Send a heartbeat to verify that the connection and stream key are working without sending a real caption:

lcyt --heartbeat

A successful heartbeat prints a confirmation and exits with code 0.


All Options

FlagShortDescription
--stream-key KEY-kYouTube stream key
--base-url URL-uOverride ingestion URL
--region ID-rRegion identifier (default: reg1)
--cue IDCue identifier (default: cue1)
--timestamp ISO-tManual timestamp override
--batch-bQueue caption instead of sending immediately
--sendSend all queued batch captions
--heartbeatSend heartbeat and exit
--resetReset sequence counter to 0
--show-configPrint current configuration
--verbose-vEnable verbose logging
--log-stderrRoute logs to stderr
--config PATH-cUse a custom config file path

Exit Codes

CodeMeaning
0Caption sent successfully
1Error (network failure, invalid key, etc.)

LCYT — Live Captions for YouTube

LCYT (Live Captions for YouTube) lets you send real-time closed captions to a YouTube Live stream directly from your browser, using Google’s official HTTP POST caption ingestion API.

It is especially useful for non-English broadcasts — you can send captions in any language to make your stream accessible to a global audience.


What you can do with LCYT

FeatureDescription
Live captioningType captions manually and send them with a single keystroke
Speech-to-textUse your microphone with automatic speech recognition (browser API or Google Cloud STT)
File playbackLoad a pre-written script and step through it line by line
TranslationAutomatically translate captions to a second language
Viewer pageBroadcast captions to audience members via a public SSE-based viewer page (no YouTube account needed on the viewer’s side)
RTMP relayRe-stream your audio/video to up to 4 destinations simultaneously with embedded captions
CEA-708 captionsEmbed captions directly in the RTMP video stream (requires ffmpeg with CEA-708 support)
HLS streamingServe a live HLS video+audio stream via the backend (embeddable in any web page)
Radio HLSServe a live audio-only HLS stream via the backend
Stream previewSee a live JPEG thumbnail of your incoming RTMP stream in the web UI
DSK overlayOverlay PNG/WebP/SVG graphics on the relayed video using a downstream keyer (green screen) page
Embed widgetsDrop standalone <iframe> widgets into any site — audio, input bar, file player, full file UI, viewer, settings, or RTMP relay
Dark & light modeComfortable UI in any environment
Mobile supportFully usable on phones and tablets with a dedicated mobile bar

Dashboard

LCYT dashboard — dark mode

The main dashboard is split into two panels side by side (or stacked on mobile):

  • Left panel — Drop zone for script files, file tabs, the caption preview, and the microphone / audio meter
  • Right panel — Log of all captions sent during the current session

The status bar at the top contains five buttons:

ButtonPurpose
Connect / DisconnectToggle the backend session (green = connected, red on hover = click to disconnect)
SettingsConfigure connection credentials, theme, language, text size, and advanced options
CCConfigure speech recognition, caption targets (receivers), details, and translation
ControlsView session status and run diagnostic actions
PrivacyReview the privacy policy

Status bar

The input bar at the bottom is where you type and send captions.


Project components

ComponentDescription
lcyt-webBrowser-based web app (this guide)
lcyt-cliCommand-line tool for sending captions from a terminal
lcyt-backendExpress.js relay backend (multi-user, API keys)
lcyt (npm)Node.js library for direct YouTube caption ingestion
lcyt (PyPI)Python library with the same API
lcyt-mcpModel Context Protocol server — lets AI assistants send captions

Next steps

Getting Started

This guide walks you through the full setup from a brand-new account to your first live caption.


Prerequisites

Before you start you need:

  1. A YouTube Live stream with a 30-second delay enabled
  2. An LCYT API key (free — see below)
  3. A modern browser (Chrome, Edge, or any Chromium-based browser recommended)

Step 1 — Enable closed captions in YouTube Studio

  1. Go to YouTube StudioGo Live.
  2. In your stream settings, open the Advanced tab.
  3. Enable Closed captions and choose the HTTP POST method.
  4. Set the stream delay to at least 30 seconds (required by the caption API).
  5. Copy your Stream Key — you will need it shortly.

Step 2 — Get a free API key

Visit lcyt.fi/app and fill in the free-key form with your name and email address. Your key is emailed to you instantly and works for up to 200 captions per day for 30 days.

Tip: Paid keys with higher limits and longer validity are available — contact the LCYT team.


Step 3 — Open the web app

Navigate to app.lcyt.fi.
On the first visit the Privacy modal opens automatically. Read the policy and click Accept to continue.

Privacy modal on first visit


Step 4 — Enter your credentials

Click Settings in the top status bar to open the Settings modal.

Settings modal — Basic tab

Fill in the Basic tab:

FieldValue
API KeyThe key you received by email
Stream KeyCopied from YouTube Studio
Backend URLhttps://api.lcyt.fi (default — leave as-is)

Your credentials are saved automatically as you type. Close the modal when done.

Optionally enable Auto-connect to reconnect automatically on every visit.


Step 5 — Connect

Click the Connect button in the top status bar.

  • The button turns green when the session is established.
  • To disconnect, click the button again (it turns red on hover to confirm).

Step 6 — Send your first caption

Type a test caption in the input bar at the bottom of the page and press Enter (or click Send).

Input bar

The caption appears in the Sent log on the right and is delivered to your YouTube stream within a few seconds.


Troubleshooting

SymptomLikely causeFix
Settings opens instead of connectingNo credentials savedFill in API Key + Backend URL in Settings → Basic
”Connection failed”Wrong API key or backend URLDouble-check both fields in Settings
Captions not appearing on streamStream delay or captions not enabledEnable HTTP POST captions in YouTube Studio
Network banner (⚠)Backend unreachableCheck your internet connection; the app retries every 30 s
Clock offset warningServer and browser clocks differClick ⟳ Sync Now in the Controls panel

Sending Captions

LCYT gives you several ways to get text onto the stream. You can use them in any combination during a session.


The input bar

The input bar sits at the bottom of the app. Type your caption text here and press Enter to send it immediately.

Input bar

  • Clear — remove the text without sending
  • Send button — same as Enter

Tip: After sending, the input bar is cleared automatically so you can type the next caption right away.


Microphone (Speech-to-Text)

Click the microphone button (🎙) to start continuous speech recognition. Words are transcribed and displayed in the caption preview area in real time. The system sends each completed utterance as a caption automatically.

See Caption settings → Model tab for engine and language selection.

Desktop

The audio meter and mic toggle are embedded in the left panel.

Mobile

On phones, the mic button lives in the mobile audio bar at the bottom of the screen.

Mobile audio bar

The mobile bar contains (left to right):

ButtonAction
Audio meterShows microphone volume in real time
🎙 / ⏹Toggle speech recognition on/off
Go to previous line (script mode)
Send current line
+Go to next line (script mode)

Loading a script file

You can load a pre-written caption script (plain text, one caption per line) by:

  • Dragging and dropping the file onto the drop zone in the left panel, or
  • Clicking the drop zone to open a file picker.

Left panel with drop zone

Once loaded, the file appears as a tab above the caption view. The current line is highlighted.

ActionKeyboardMobile
Send current lineEnter► button
Next line / Page Down+ button
Previous line / Page Up− button
First lineHome
Last lineEnd
Cycle file tabsTab

Caption file format

Caption files are plain text (.txt), one caption per line. In addition to plain text, the file format supports metadata comments, stanza blocks, and empty-send markers.

Metadata comments

HTML-style comments on their own line attach metadata codes to all subsequent caption lines. Any key is accepted.

<!-- lang: fi-FI -->
<!-- section: chorus -->
<!-- speaker: Alice -->
<!-- lyrics: true -->
<!-- no-translate: true -->

To clear a code, set its value to empty:

<!-- lang: -->

Metadata comment lines are not sent as captions.

Stanza blocks

A stanza block attaches multi-line “singing aid” text to subsequent captions so viewers can see the upcoming lyrics. Open the block with <!-- stanza (no closing -->), write the stanza lines, then close with --> on its own line.

<!-- stanza
Amazing grace, how sweet the sound
That saved a wretch like me
-->
Amazing grace, how sweet the sound
That saved a wretch like me

The stanza text is pushed to the viewer when the first caption in the block is sent. To clear the stanza, add an empty block:

<!-- stanza
-->

Empty-send markers

A line containing only _ fires the current metadata codes (including the active stanza) to the viewer without sending any caption text to YouTube. This is useful for pushing a stanza to the viewer before the singing starts.

<!-- stanza
Amazing grace, how sweet the sound
-->
_
Amazing grace, how sweet the sound

You can optionally add a label after the underscore. The label is shown in red in the caption view as a visual cue for the operator — it is never sent to YouTube.

_ Show verse 1
_ ♪ Chorus
_ [pause here]

A bare _ displays a dimmed ⊘ send codes indicator. A labeled _ text displays the label text in red.


Batch mode

Batch mode lets you queue multiple captions and send them at regular intervals. This is useful for pre-written scripts where you want smooth, evenly timed delivery.

Configure the batch interval in Caption settings → Other tab.


Sent captions log

The right panel shows every caption you have sent in the current session, newest at the top. You can scroll back to review what was sent.

Sent captions log

Caption Settings (CC)

Click CC in the top status bar to open the Closed Captions modal. It has up to four tabs, with the Details tab visible only in advanced mode.


Receivers tab {#receivers}

The Receivers tab is the first tab and manages all caption delivery destinations.

CC modal — Receivers tab

Click + Add target to add a new entry. Each entry can be:

TypeDescription
YouTubeA YouTube stream key — captions are sent to that stream via YouTube’s HTTP POST caption ingestion API
ViewerA viewer key — captions are broadcast to audience members via the public SSE endpoint at /viewer/:key. Viewers open the viewer page at /view/:key or the embed widget at /embed/viewer.
GenericA custom HTTP POST endpoint with optional JSON headers

You can add multiple targets (e.g. two YouTube streams and a viewer feed). Each target has an enable toggle. Only enabled targets receive captions.

Each target can also have Disable batch sending enabled, which forces captions to be sent individually to that target regardless of the global batch setting.

Add a YouTube target here with your stream key if you want to send captions directly to a YouTube Live stream.

Viewer targets

Viewer targets broadcast captions to anyone with the viewer page URL — no YouTube account required. After adding a viewer target you’ll see a shareable link:

https://app.lcyt.fi/view/<your-viewer-key>?server=https://api.lcyt.fi

You can also use the embeddable viewer widget:

https://app.lcyt.fi/embed/viewer?key=<your-viewer-key>&server=https://api.lcyt.fi

The viewer page displays the current caption prominently with a dimmed history list. It connects to the backend via Server-Sent Events and reconnects automatically on disconnect.

You can optionally assign an icon (PNG or SVG logo) to a viewer target. The icon is shown in the viewer page header. Upload icons in Settings → Icons (see below).


Service tab {#service}

The Service tab controls which speech-to-text engine is used and how it is configured.

CC modal — Service tab

STT engine

OptionDescription
Web Speech APIBrowser built-in (Chrome / Edge). No account required. Enable prefer local to use on-device recognition (no audio sent to server).
Google Cloud STTHigher accuracy, more language models. Requires a service account JSON key.

Microphone

Select which microphone to use if your device has more than one. Click Refresh to update the list.

Recognition language

Choose the spoken language for the speech recogniser. Type to filter the list.

STT model (Google Cloud only)

ModelBest for
latest_longLong-form speech; best for broadcast
latest_shortShort commands; lower latency
telephonyPhone-quality audio

Additional Cloud STT options: Auto-punctuation, Profanity filter, Confidence threshold, Max caption length, and Google Service Account key upload.

Utterance end button (advanced mode only)

Shows a 🗣 icon on the audio meter during active speech recognition. Click it to force-end the current utterance immediately (commits the partial transcript as a final caption).

Utterance end timer (advanced mode only)

Automatically force-ends the utterance after N seconds (0 = disabled). Useful for segmenting long speeches into shorter captions.


Details tab (advanced mode only) {#details}

This tab is only visible when Show advanced options is enabled in Settings → Basic.

Batching

SettingDescription
Batch window0 = send each caption immediately. 1–20 s = collect captions over the window, then send as a single batch.

Transcription offset

Shifts the caption timestamp relative to when the transcription arrives. Use a negative value (e.g. −5 s) to compensate for transcription processing delay, so captions line up with the moment the speaker started talking in the YouTube stream.

Double-click the slider to reset to 0.

Client-side VAD

Voice Activity Detection (VAD) monitors microphone energy and forces the recogniser to finalise when silence is detected. Helps segment long unbroken speech on mobile Chrome.

SettingDescription
Enable VADTurn on silence detection (WebKit engine only)
Silence durationHow long (ms) energy must stay below threshold before the recogniser is stopped
Energy thresholdRMS amplitude below which audio is considered silent (lower = more sensitive)

Translation tab {#translation}

The Translation tab configures real-time caption translation.

CC modal — Translation tab

Click + Add translation to add a target language. Each entry specifies:

FieldDescription
EnabledToggle this translation on/off
LanguageTarget language (e.g. en-US)
Targetcaptions (YouTube stream), file (local), or backend-file
FormatYouTube or WebVTT (for file targets)

Translation vendors

VendorNotes
MyMemoryFree, no API key needed
Google Cloud TranslationHigh quality; requires an API key
DeepLPremium quality; requires an API key
LibreTranslateSelf-hosted; provide server URL and optional key

Show original

Enable Show original to include the original text alongside the translation in the YouTube caption stream (separated by a line break).

Translation

LCYT can translate your captions in real time and deliver the translation alongside the original text.

Click CC in the top status bar, then select the Translation tab.


Translation vendor

VendorNotes
MyMemoryFree, no API key required
Google Cloud TranslationHigh quality, wide language support; requires a Google Cloud API key
LibreTranslateSelf-hosted open-source option; provide your server URL and optional API key
DeepLPremium translation quality; requires a DeepL API key

Adding a translation target

Click + Add translation to add a new row. Each row defines one translation output:

FieldOptionsDescription
EnabledToggle this translation on/off without deleting it
LanguageAny supported languageTarget language for translation
Targetcaptions, file, backend-fileWhere the translated text is delivered
Formatyoutube, vttCaption format (for file targets only)

Target options

TargetBehaviour
captionsTranslation is appended below the original caption on the YouTube stream (separated by <br>)
fileTranslation is saved as a local file in the chosen format
backend-fileTranslation is saved as a file on the relay backend server

Note: Only one captions target is allowed at a time (YouTube accepts one caption block per request).


Multilingual video player

Every enabled translation language automatically becomes a selectable subtitle track in the embeddable video player at GET /video/:key — no extra configuration required. Viewers use their browser’s built-in CC button to switch languages.

See Multilingual Video Player for setup instructions and embed code.


Show original

Enable Show original to keep the original caption text in the stream alongside the translation. When disabled, only the translation is shown.


API key setup

Google Cloud

  1. Create a project in the Google Cloud Console.
  2. Enable the Cloud Translation API.
  3. Create an API key and paste it into the Google Cloud API Key field.

LibreTranslate

  1. Deploy a LibreTranslate instance (or use a public one).
  2. Enter the server URL (e.g. https://translate.example.com).
  3. Optionally enter an API key if your server requires one.

Settings

Click Settings in the top status bar to open the Settings modal.

Settings modal — Basic tab


Basic tab

The Basic tab contains all connection credentials and core preferences.

FieldDescription
Backend URLURL of the LCYT relay backend (default: https://api.lcyt.fi)
API KeyYour LCYT API key — get one at lcyt.fi/app
Stream KeyYouTube Live stream key from YouTube Studio
Auto-connectReconnect automatically the next time you open the app
ThemeAuto (system), Dark, or Light
LanguageUI display language (English / Finnish / Swedish)
Text sizeFont size in the caption preview area (10–24 px)
Show advanced optionsReveals the Stream tab here, and the Details tab in the CC modal

All fields are saved to your browser automatically as you type — no explicit Save button is needed.


Stream tab (advanced mode only)

This tab is only visible when Show advanced options is enabled on the Basic tab.

The Stream tab controls the RTMP relay and its associated broadcast settings.

Relay active toggle

Enable Active to start relaying your incoming RTMP stream to all configured destinations. When inactive, the backend accepts the stream but does not forward it. The toggle takes effect immediately — if your RTMP stream is already live when you enable the relay, fan-out starts without reconnecting.

Relay destinations

Click + Add relay to configure a new RTMP destination. Each entry supports:

TypeDescription
YouTubeEnter your YouTube RTMP stream key. The full ingest URL (rtmp://a.rtmp.youtube.com/live2/<key>) is shown as a preview.
GenericEnter a custom RTMP base URL and optional stream name / key.

Click to remove a destination. You can configure up to 4 destinations.

Per-slot advanced options (⚙ gear button)

Each relay slot has an optional advanced settings panel (click the ⚙ gear icon):

OptionDescription
Caption modehttp (default) — send captions via YouTube’s HTTP ingestion API. cea708 — embed captions directly in the RTMP video stream (requires CEA-708 capable ffmpeg on the server).
ScaleOutput video resolution, e.g. 1280x720. Enable the Use original checkbox to pass the original resolution through unchanged.
FPSOutput frame rate (integer). Enable Use original to keep the source frame rate.
Video bitratee.g. 3000k or 6M. Leave blank or check Use original to keep the source bitrate.
Audio bitratee.g. 128k. Leave blank or check Use original to keep the source audio bitrate.

Transcoding (scale / FPS / bitrate) and CEA-708 caption mode cannot be combined on the same key — CEA-708 takes priority.

RTMP ingest address

When the relay is enabled on the server, the ingest address is shown at the bottom of the Stream tab:

rtmp://<server>/stream/<your-api-key>

Configure your broadcasting software (e.g. OBS) to push to this address. The stream key is your API key.

DSK RTMP ingest address

If the server supports DSK (Downstream Keyer) RTMP ingest, an additional address is shown:

rtmp://<server>/dsk/<your-api-key>

Push a second RTMP stream (e.g. a green-screen graphics feed) to this address to composite it on top of the main stream.

Requires an active backend connection and relay_allowed permission on your API key.

Controls Panel

Click Controls in the top status bar to open the Controls floating panel.

The Controls panel combines the session status display and diagnostic actions in one convenient location.


Status section

Controls panel — status

RowDescription
ConnectionGreen ● Connected or grey ○ Disconnected
Backend URLThe relay backend the app is talking to
SequenceCaption sequence number — increments with every caption sent
Clock offsetDifference (ms) between your browser clock and the server clock. Used for accurate caption timestamps
Last connectedTime of the most recent successful connection

Stats button

Click Stats to open the Usage Stats modal. It shows per-key caption counts, daily limits, and session history. Requires an active connection.

My Files button

Click My Files to list caption and translation files saved on the backend for your API key, with download and delete options. Requires an active connection.


Actions section

ActionDescription
⟳ Sync NowRuns an NTP-style clock sync with the backend to minimise timestamp drift. Run this if captions appear noticeably early or late
♥ HeartbeatSends a blank caption to verify the connection end-to-end without showing anything on stream
↺ Reset sequenceResets the caption sequence counter to 0. Use if the YouTube stream shows duplicate or out-of-order captions
↗ Set sequenceManually set the sequence counter to a specific number
🗑 Clear saved configRemoves all locally stored settings (API key, stream key, preferences). Does not affect server-side data

Caption codes

Active metadata codes sent alongside every caption. Click a code button to toggle it:

CodeEffect
langSet the speaker language (overrides the input bar language picker)
no-translatePrevent translation for captions with this code
custom codeAdd any arbitrary key: value metadata pair

File-level metadata (<!-- lang: fi-FI -->) overrides per-line codes.

File actions

ButtonDescription
✏ Edit FileSwitch the current file to raw text editor mode. Hold 2 seconds (or click without a file) to create a new file
✕ Clear sent logRemove all entries from the sent captions log

Keyboard Shortcuts

LCYT is designed for fast, keyboard-driven operation during a live broadcast.


Global shortcuts (always active)

ShortcutAction
Ctrl+, / Cmd+,Open / close Settings
Ctrl+1Ctrl+9 / Cmd+1Cmd+9Switch to file tab 1–9

Shortcuts active when no text input is focused

These shortcuts are blocked when you are typing in any text field, to avoid accidentally sending captions or navigating while editing.

Sending

ShortcutAction
EnterSend the current caption (or the focused file line)

File navigation (when a script file is loaded)

ShortcutAction
/ Move to the previous / next line
Page Up / Page DownJump 10 lines back / forward
HomeJump to the first line
EndJump to the last line
TabCycle between open file tabs

Modals

ShortcutAction
EscapeClose the currently open modal or floating panel

Shortcuts reference in the app

All keyboard shortcuts are listed in the Settings → Credentials & Shortcuts tab for quick in-app reference.


Tips

  • Keep the input bar unfocused (click anywhere outside it) to keep navigation shortcuts active.
  • On mobile, use the navigation buttons in the mobile audio bar (−, ►, +) for the same line-navigation actions.
  • Combine file navigation with speech recognition: navigate to a line and read it aloud while the mic is active, or manually advance through your script and send with Enter.
  • Use Ctrl+1 to quickly jump to your first script file, Ctrl+2 for the second, and so on — handy when managing multiple scripts for different segments.

Embedding lcyt-web in Another Site

lcyt-web ships eight standalone embed widgets that you can drop into any page as <iframe> elements. Each widget renders only a specific part of the UI, configured entirely through URL parameters — no React knowledge required on the host site.


Overview

WidgetPathWhat it renders
Audio capture/embed/audioMicrophone / speech recognition panel
Input bar + log/embed/inputText input field and sent-captions log (owns the session)
Sent log only/embed/sentlogRead-only delivery log (subscribes to a sibling widget’s session)
Simple file drop/embed/file-dropDrop one file → send lines one by one (owns the session)
Full file UI/embed/filesComplete file manager: tabs, drop zone, caption view, input bar, sent log
Settings/embed/settingsConnection credentials, theme, and CC targets (General + CC tabs)
RTMP relay/embed/rtmpRTMP relay slot management widget
Viewer/embed/viewerRead-only live caption viewer for audience members

Looking for the video player? The embeddable multilingual video player is served directly from the backend, not from lcyt-web. Use <iframe src="https://api.example.com/video/<key>"> — see Multilingual Video Player.


Live iframe examples

Open any widget in a standalone iframe preview (opens a new window):

Audio capture example

Input + log example

Sent log example

File drop example

Files manager example

Settings example

RTMP relay example

Viewer example


URL Parameters

All embed pages share these common URL parameters:

ParamDescriptionDefault
serverBackend relay URL(empty — prompts user to connect)
apikeyLCYT API key(empty)
themedark or lightdark

When both server and apikey are present the widget connects automatically on load.

EmbedSentLogPage (/embed/sentlog) does not connect to the backend itself and therefore ignores server and apikey. It receives those from a sibling widget on the same page via BroadcastChannel.


Widget Details

/embed/audio — Audio Capture Widget

Renders the full AudioPanel (microphone button, interim text, VAD, translation pipeline). Owns the backend session when server + apikey are supplied.

<iframe
  src="https://your-lcyt-host/embed/audio?server=https://api.example.com&apikey=YOUR_KEY&theme=dark"
  allow="microphone"
  style="width:100%; height:220px; border:none;">
</iframe>

The allow="microphone" attribute is required for speech recognition to work inside an iframe.


/embed/input — Input Bar + Sent Log Widget

Renders a text caption input field at the bottom and a scrollable sent-captions log above it. Owns its own backend session.

<iframe
  src="https://your-lcyt-host/embed/input?server=https://api.example.com&apikey=YOUR_KEY&theme=dark"
  style="width:100%; height:320px; border:none;">
</iframe>

/embed/sentlog — Sent Log Widget (read-only)

Renders only the sent-captions log. Does not own a session. Instead, it receives the session JWT token and caption texts from a sibling /embed/audio or /embed/input widget on the same host page via the browser’s BroadcastChannel API, then opens its own independent EventSource connection to /events on the backend to receive real-time delivery confirmations.

<iframe
  src="https://your-lcyt-host/embed/sentlog?theme=dark"
  style="width:100%; height:320px; border:none;">
</iframe>

Requirement: At least one other embed widget (/embed/audio, /embed/input, /embed/file-drop, or /embed/files) must be present on the same host page and connected to the backend.


/embed/file-drop — Simple File Drop Widget

The minimal caption-from-file widget. Phase 1 shows a large drag-and-drop zone (click to browse is also supported). Drop one .txt file and Phase 2 immediately shows the player:

  • The current line is displayed prominently in the centre of the widget.
  • ◀ Prev, Send, ▶ Next buttons control navigation and delivery.
  • Keyboard shortcuts work without clicking buttons: / to move, Enter to send and advance.
  • A ✕ reset link in the header returns to Phase 1 to load a different file.
  • The connection status dot in the header shows whether the backend relay is connected.

Send delivers the current line to YouTube and automatically advances the pointer to the next line.

<iframe
  src="https://your-lcyt-host/embed/file-drop?server=https://api.example.com&apikey=YOUR_KEY&theme=dark"
  style="width:100%; height:280px; border:none;">
</iframe>

/embed/files — Full File Management Widget

Renders the complete file-based captioning workflow from the main app, without the status bar, settings modals, or audio panel:

  • FileTabs row at the top — switch between open files, add new files, toggle drop zone.
  • DropZone — collapsible drag-and-drop file loader (auto-hides once a file is loaded).
  • CaptionView — scrollable line list with the active pointer highlighted; supports raw text editing.
  • InputBar — text input with batch mode, translation, and all keyboard shortcuts.
  • Sent log panel — togglable delivery log (✓✓ button in the toolbar); starts visible by default.

Line double-click in CaptionView sends that line immediately via the InputBar, exactly as in the main app.

<iframe
  src="https://your-lcyt-host/embed/files?server=https://api.example.com&apikey=YOUR_KEY&theme=dark"
  style="width:100%; height:640px; border:none;">
</iframe>

To start with the sent log panel hidden, add &sentlog=0 to the URL.


/embed/settings — Settings Widget

Renders the Settings panel as a standalone widget. Provides the General tab (backend URL, API key, stream key, theme) and the CC tab (caption targets, STT language, translation settings). The widget connects automatically if credentials are present in the URL.

<iframe
  src="https://your-lcyt-host/embed/settings?server=https://api.example.com&apikey=YOUR_KEY&theme=dark"
  style="width:100%; height:480px; border:none;">
</iframe>

Useful for building custom operator dashboards where settings management and captioning are in separate panels.


/embed/rtmp — RTMP Relay Widget

Renders the RTMP relay slot management UI as a standalone widget. Shows the relay active toggle, configured slots, RTMP ingest address, and per-slot advanced options (scale, FPS, bitrate, caption mode).

<iframe
  src="https://your-lcyt-host/embed/rtmp?server=https://api.example.com&apikey=YOUR_KEY&theme=dark"
  style="width:100%; height:320px; border:none;">
</iframe>

Requires an active backend connection and relay_allowed on the API key.


/embed/viewer — Caption Viewer Widget

Renders a read-only live caption display for audience members. Connects directly to the backend’s public GET /viewer/:key SSE endpoint — no API key or JWT required.

<iframe
  src="https://your-lcyt-host/embed/viewer?key=my-event-key&server=https://api.example.com&theme=dark"
  style="width:100%; height:200px; border:none;">
</iframe>

URL parameters specific to /embed/viewer:

ParamDescriptionDefault
keyViewer key configured by the streamer(required)
serverBackend URL(required)
themedark or lightdark

This widget does not need apikey or a session — it is intended for the audience, not the operator. The server and key parameters are required; the widget shows a “waiting for stream” state until captions arrive.

The full-screen version (non-embed) is available at /view/:key?server=<backendUrl>.


Cross-Widget Communication

When widgets are spread across different parts of the same host page they coordinate using the browser’s BroadcastChannel API (channel name: 'lcyt-embed'). All iframes must be served from the same origin (the same lcyt-web deployment URL) for BroadcastChannel to work — this is a browser security requirement.

Message types

TypeDirectionPayload
lcyt:sessionaudio/input → sentlog{ token, backendUrl } — sent on connect and in response to lcyt:request_session
lcyt:captionaudio/input → sentlog{ requestId, text, timestamp } — sent for each caption dispatched to the backend
lcyt:request_sessionsentlog → audio/input(no payload) — sent on mount so a late-joining sentlog gets the token

The sentlog widget also opens its own EventSource to GET /events?token=... on the backend to receive caption_result and caption_error events independently, without going through the session-owning widget.


Splitting the UI Across a Host Page

A common integration pattern is to place each widget in a different region of the host page:

<!-- Left sidebar: microphone -->
<div id="sidebar-left">
  <iframe
    src="https://your-lcyt-host/embed/audio?server=https://api.example.com&apikey=YOUR_KEY"
    allow="microphone"
    style="width:100%; height:240px; border:none;">
  </iframe>
</div>

<!-- Main content: text input -->
<div id="main-input">
  <iframe
    src="https://your-lcyt-host/embed/input?server=https://api.example.com&apikey=YOUR_KEY"
    style="width:100%; height:300px; border:none;">
  </iframe>
</div>

<!-- Right sidebar: delivery log (read-only, no credentials needed) -->
<div id="sidebar-right">
  <iframe
    src="https://your-lcyt-host/embed/sentlog"
    style="width:100%; height:400px; border:none;">
  </iframe>
</div>

Only one widget should own the session at a time. If both /embed/audio and /embed/input are on the page with the same credentials they will each start a separate session — captions from one will not appear in the other’s log unless you use /embed/sentlog (which listens to all sibling broadcasts). For a split audio + log layout, use /embed/audio as the session owner and /embed/sentlog for the delivery log.


CORS Configuration

The backend must accept requests from the origin of the host page (not the lcyt-web origin). Configure the ALLOWED_DOMAINS environment variable on the backend to include the host site’s domain:

ALLOWED_DOMAINS=lcyt.fi,www.lcyt.fi,yoursite.com,www.yoursite.com

The embed widgets themselves are served from the lcyt-web origin and communicate with the backend using the API key — the CORS domain is the domain registered when the API key was created, which must match the domain field sent in POST /live.


Listening for postMessage Events (Optional)

All embed pages that own a session (/embed/audio, /embed/input) are ready for future postMessage integration with the host page. Currently, the BroadcastChannel mechanism handles cross-widget communication automatically. If you need the host page itself to react to caption events you can listen on the BroadcastChannel from a host-page script:

const ch = new BroadcastChannel('lcyt-embed');
ch.onmessage = (ev) => {
  if (ev.data.type === 'lcyt:caption') {
    console.log('Caption sent:', ev.data.text);
  }
  if (ev.data.type === 'lcyt:session') {
    console.log('Session token received');
  }
};

Note: BroadcastChannel only works between same-origin contexts. The host page script must be served from the same origin as the embed iframes (i.e., the lcyt-web deployment URL), or you must use the iframes’ own postMessage API relayed through the parent page.

Multilingual Video Player

When you configure a viewer target in the CC → Targets tab, the backend automatically generates an embeddable video player at:

https://api.example.com/video/<viewer-key>

This player combines your live video stream with real-time subtitle tracks in every language you have configured under CC → Translation. Viewers can select their preferred language using their browser’s built-in CC (closed captions) button — no custom controls are needed.


What you need

  1. HLS video stream — the RTMP stream you send to the server must have hls_enabled = true on your API key (set by an admin). The video and subtitle tracks share the same key.
  2. Viewer target — configure a viewer target in CC → Targets with the same key you use for the HLS stream. Every caption you send is automatically transcribed into subtitle segments.
  3. Translations (optional) — add one or more translation languages in CC → Translation. Each language appears as a separate subtitle track in the player.

Setting up

1. Configure a viewer target

Open CCTargets tab and add a target:

FieldValue
TypeViewer
Viewer keyA short, unique identifier for your event (e.g. my-event-2026)

Use the same key for the HLS video stream (the stream key configured in nginx-rtmp must match).

2. Add translation languages (optional)

Open CCTranslation tab and add any languages you want as subtitle tracks. Each enabled language will appear in the player’s CC menu as a selectable track.

The original caption text is always included as the Original track regardless of whether any translations are configured.

3. Share the player URL

The player is available at:

https://api.example.com/video/<viewer-key>

Replace api.example.com with your backend URL and <viewer-key> with the key you set in step 1.


Embedding the player

The player page is iframe-embeddable on any website:

<iframe
  src="https://api.example.com/video/my-event-2026"
  width="960" height="540"
  frameborder="0"
  allow="autoplay"
  allowfullscreen>
</iframe>

Theme

Add ?theme=light for a light background:

<iframe src="https://api.example.com/video/my-event-2026?theme=light" ...></iframe>

Subtitle language selection

The player exposes all active subtitle tracks through the browser’s native CC button in the standard video controls:

  • Chrome / Edge / Firefox — click the CC (⧉) button in the video controls to open the language menu.
  • Safari (macOS / iOS) — click the CC button or the subtitles option in AirPlay / fullscreen controls.
  • Android Chrome — tap the CC button in the controls.

Tracks appear as soon as the first captions arrive for that language — you do not need to reload the player.


How subtitles are generated

Each time a caption is sent, the backend:

  1. Writes the original text and each translation into rolling 6-second WebVTT segment files (one set per language).
  2. Updates an HLS subtitle playlist per language, using EXT-X-PROGRAM-DATE-TIME headers so the player can align subtitle cues to the video by wall clock.
  3. Serves a master HLS manifest that references both the video stream and all subtitle playlists.

Because the subtitle system uses the same viewer key as the SSE viewer endpoint (/viewer/:key), there is no extra configuration — any key that receives captions automatically gets a subtitle sidecar.


Subtitle track reference

Track label in playerSource
OriginalThe raw text typed or captured via speech recognition
Finnish / SuomiTranslation to Finnish, if configured
German / DeutschTranslation to German, if configured
(other languages)Determined by which languages are enabled in CC → Translation

Troubleshooting

SymptomLikely cause
”Stream not live” messageThe HLS video stream is not running (hls_enabled must be set for the key)
No CC button visibleNo captions have been sent yet; the button appears once the first subtitle track is active
Subtitle track names show BCP-47 codes instead of language namesThe language tag is not in the built-in name map; the tag itself is used as a fallback
Subtitles appear late or are offset from speechNormal for live transcription; the backend writes segments every 6 seconds

Technical details

For the full API reference, including manifest format, WebVTT segment format, and environment variables, see API: /video.