Textream is a free macOS teleprompter app for streamers, interviewers, and presenters. It highlights your script in real-time as you speak, displayed in a beautiful Dynamic Island overlay. With extensible features.
https://github.com/f/textream.git
A free macOS teleprompter with real-time word tracking, classic auto-scroll, and voice-activated scrolling.
Built for streamers, interviewers, presenters, and podcasters.
Download Β· Features Β· How It Works Β· Build
Textream is a free, open-source macOS app that guides you through your script with three modes: word tracking (highlights each word as you say it), classic (constant-speed auto-scroll), and voice-activated (scrolls while you speak, pauses when you're silent). It displays your text in a sleek Dynamic Island-style overlay at the top of your screen, a draggable floating window, or fullscreen on a Sidecar iPad β visible only to you, invisible to your audience.
Paste your script, hit play, and start speaking. When you're done, the overlay closes automatically.
Download the latest .dmg from Releases
Or install with Homebrew:
brew install f/textream/textream
Requires macOS 15 Sequoia or later. Works on Apple Silicon and Intel.
Since Textream is distributed outside the Mac App Store, macOS may block it on first open. Run this once in Terminal:
xattr -cr /Applications/Textream.app
Then right-click the app β Open. After the first launch, macOS remembers your choice.
| Mode | Description | Microphone |
|---|---|---|
| Word Tracking (default) | On-device speech recognition highlights each word as you say it. No cloud, no latency, works offline. Supports dozens of languages. | Required |
| Classic | Auto-scrolls at a constant speed. No microphone needed. | Not needed |
| Voice-Activated | Scrolls while you speak, pauses when you're silent or muted. Perfect for natural pacing. | Required |
| Mode | Description |
|---|---|
| Pinned to Notch | A Dynamic Islandβshaped overlay anchored below the MacBook notch. Sits above all apps. |
| Floating Window | A draggable window you can place anywhere on screen. Always on top. |
| Fullscreen | Fullscreen teleprompter on any display. Press Esc to stop. |
| Setting | Options |
|---|---|
| Font Family | Sans, Serif, Mono, OpenDyslexic (dyslexia-friendly) |
| Font Size | XS (14 pt), SM (16 pt), LG (20 pt), XL (24 pt) |
| Highlight Color | White, Yellow, Green, Blue, Pink, Orange |
| Mode | Description |
|---|---|
| Off | No external display output. |
| Teleprompter | Fullscreen teleprompter on the selected external display or Sidecar iPad. |
| Mirror | Flipped output for prompter mirror rigs. |
View your teleprompter on any device β phone, tablet, or another computer β via a local network browser connection.
Let someone else control your teleprompter remotely. A director can write, edit, and push scripts to your teleprompter in real time from any browser.
| Use case | How Textream helps |
|---|---|
| Streamers | Read sponsor segments, announcements, and talking points without looking away from the camera. |
| Interviewers | Keep your questions visible while maintaining natural eye contact with your guest. |
| Presenters | Deliver keynotes, demos, and talks with confidence. Never lose your place. |
| Podcasters | Follow show notes, ad reads, and topic outlines hands-free while recording. |
git clone https://github.com/f/textream.git
cd textream/Textream
open Textream.xcodeproj
Build and run with βR in Xcode.
Textream/
βββ Textream.xcodeproj
βββ Info.plist
βββ Textream/
βββ TextreamApp.swift # App entry point, deep link handling
βββ ContentView.swift # Main text editor UI + About view
βββ TextreamService.swift # Service layer, URL scheme handling
βββ SpeechRecognizer.swift # On-device speech recognition engine
βββ NotchOverlayController.swift # Dynamic Island + floating overlay
βββ ExternalDisplayController.swift # Sidecar / external display output
βββ NotchSettings.swift # User preferences and presets
βββ SettingsView.swift # Tabbed settings UI
βββ MarqueeTextView.swift # Word flow layout and highlighting
βββ BrowserServer.swift # Remote connection HTTP + WebSocket server
βββ DirectorServer.swift # Director mode HTTP + WebSocket server
βββ PresentationNotesExtractor.swift # PPTX presenter notes extraction
βββ UpdateChecker.swift # GitHub release update checker
βββ Assets.xcassets/ # App icon and colors
Textream supports the textream:// URL scheme for launching directly into the overlay:
textream://read?text=Hello%20world
It also registers as a macOS Service, so you can select text in any app and send it to Textream via the Services menu.
The Director Mode exposes an HTTP server and a WebSocket server on your local network. You can build your own director client using the protocol below.
| Service | Default Port | Configurable in |
|---|---|---|
| HTTP (serves the built-in web UI) | 7575 | Settings β Director β Advanced |
| WebSocket (bidirectional communication) | 7576 (HTTP port + 1) | Automatic |
ws://<mac-ip>:<ws-port> (e.g. ws://192.168.1.42:7576).Send JSON messages over the WebSocket:
setText β Start reading a new script{
"type": "setText",
"text": "Welcome everyone to today's live stream..."
}
Replaces the current text, starts word tracking, and opens the teleprompter overlay. This is equivalent to pressing Go in the built-in web UI.
updateText β Edit unread text while active{
"type": "updateText",
"text": "Welcome everyone to today's live stream We changed the rest of the script...",
"readCharCount": 42
}
Updates the full script text while preserving the read position. readCharCount is the number of characters already read (locked). Only text after this offset is replaced. Use this for live editing during a read.
stop β Stop the teleprompter{
"type": "stop"
}
Stops word tracking and dismisses the overlay.
The server broadcasts a JSON object on every tick (~100 ms):
{
"words": ["Welcome", "everyone", "to", "today's", "live", "stream"],
"highlightedCharCount": 24,
"totalCharCount": 120,
"isActive": true,
"isDone": false,
"isListening": true,
"fontColor": "#F5F5F7",
"lastSpokenText": "Welcome everyone to today's",
"audioLevels": [0.12, 0.34, 0.08, ...]
}
| Field | Type | Description |
|---|---|---|
words | string[] | The script split into words (same order as displayed in the overlay). |
highlightedCharCount | int | Number of characters recognized so far. Use this to determine the read boundary. |
totalCharCount | int | Total character count of the full script. |
isActive | bool | true when the teleprompter overlay is visible and a script is loaded. |
isDone | bool | true when highlightedCharCount >= totalCharCount (finished reading). |
isListening | bool | true when the microphone is actively listening. |
fontColor | string | CSS color of the text in the overlay (user preference). |
lastSpokenText | string | Last recognized speech fragment. |
audioLevels | double[] | Array of audio level samples (0.0β1.0) for waveform visualization. |
isActive: false and empty arrays.
import asyncio, json, websockets
async def director():
async with websockets.connect("ws://192.168.1.42:7576") as ws:
# Send a script
await ws.send(json.dumps({
"type": "setText",
"text": "Hello everyone, welcome to the show."
}))
# Listen for state updates
async for msg in ws:
state = json.loads(msg)
pct = 0
if state["totalCharCount"] > 0:
pct = state["highlightedCharCount"] / state["totalCharCount"] * 100
print(f"Progress: {pct:.0f}% Done: {state['isDone']}")
if state["isDone"]:
break
# Stop
await ws.send(json.dumps({"type": "stop"}))
asyncio.run(director())
MIT
Original idea by Semih KΔ±Εlar β thanks to him!
Made by Fatih Kadir Akin