Skip to content

Repository files navigation

Strozz logo

Strozz

Your streams, together on Apple TV — Twitch, YouTube, and Kick simulcasts, with chat and native emotes.

License: MIT Platform: tvOS Donate

Strozz brings live streams and chat together on the big screen. Watch Twitch and YouTube, follow supported creators across their YouTube and Kick simulcasts, or watch several channels in multi-view. It's built for the Apple TV remote and the tvOS focus engine — not a stretched phone app — with native 7TV, BTTV, and FFZ emotes. It's free and open source.

Features

Watch

  • Chat beside the video. Live streams play with the video on the left and a chat pane on the right, so you never have to choose between watching and reading along.
  • Low latency by default. A low-latency mode closes most of the gap to the live edge, so you're not minutes behind the moment.
  • Rewind live. Seek back within the live window (DVR) to catch what you missed without leaving the stream.
  • Pick your quality. Choose Auto or an explicit resolution, ordered highest-to-lowest, and Strozz remembers your choice.
  • Audio-only mode. Drop to audio with a reactive visualizer — handy for music streams, Just Chatting, or background listening.
  • Sleep timer. Set a timer or "end of stream," with a gentle "still watching?" check, a starry sleeping screen, and one press to snap back to the live edge.
  • VODs and clips. Watch past broadcasts and top clips from channel pages; VODs include synced chat replay and variable speed (0.5×–2×).
  • Multi-view. Watch several live channels at once, picked from your follows and recommendations.
  • Live captions (beta). Optional on-device captions for streams, with size, position, and styling controls.

Chat

  • Third-party emotes, built in. 7TV, BTTV, and FFZ emotes (global and channel, including animated ones) render right alongside Twitch's native, sub, and channel emotes.
  • Badges and bits. Global and channel badges plus cheermotes are shown just like they are on the web.
  • Read anonymously, or chat when signed in. Chat connects anonymously by default and auto-reconnects; sign in to send messages.
  • Make chat yours. Adjust text and emote size, font (including OpenDyslexic), spacing, width, and layout — side, overlay, or glass.
  • Live moments surfaced. Polls, predictions, hype trains, creator goals, and incoming/outgoing raids appear as calm, display-only overlays.
  • Simulcast chat merge (experimental). When a streamer you're watching is also live on YouTube or Kick, their chats can be merged into a single pane.

Discover

  • Home built around your follows. See the channels you follow that are live now, plus recommendations.
  • Recommendations you control. Optional personalized picks built from on-device watch history and your followed categories — or anonymous trending when you're signed out or have it turned off.
  • Browse and search. Explore top categories and their live streams, and search channels and categories with live results.
  • Channel pages. Top clips, past broadcasts, and similar channels for every channel.
  • Top Shelf. Your live follows and recommendations surface on the tvOS home screen above the app icon.
  • YouTube, too. Connect a YouTube account to see your subscribed streamers who are live and watch YouTube-only streams; streamers live on both platforms show up as one combined card.

Make it comfortable

  • Themes. System, Dark, OLED, and Light.
  • Night Shift. An optional warm screen wash that eases in after sunset on a solar or manual schedule.
  • Tune the grid. Adjustable stream-card sizes and a stream-language filter.

Getting started

Strozz is an early, non-commercial project and isn't on the App Store. To run it you'll build it yourself from source with Xcode and your own Twitch developer client_id. See CONTRIBUTING.md for the full setup.

You'll want:

  • An Apple TV running tvOS 18 or newer (live playback and Top Shelf need real hardware).
  • A Twitch account, if you want to sign in — browsing and anonymous chat work without one.

Reporting bugs & requesting features

Found a bug or have an idea? Please open a GitHub issue. Including your Apple TV model, tvOS version, and the stream where something went wrong helps a lot. See CONTRIBUTING.md for details.

Contributing & development

Build instructions, the Twitch auth setup, how playback is resolved, versioning, and release steps all live in CONTRIBUTING.md. Notes on the low-latency playback work are in docs/low-latency.md.

Brand assets

Strozz is the new name of this app. The Xcode project, scheme, source module, assets, and repository use Strozz. Apple bundle IDs, the shared App Group, and the watch-rewards Keychain service intentionally retain their existing Twozz identifiers: changing those would create a different app or discard access to saved sign-ins. New channel links use strozz://; existing twozz:// and twizz:// links still open. Shared build/cleanup protocol identifiers also stay unchanged for interoperability. Historical Git branches and commits are not renamed or rewritten.

Branding/strozz_logo.svg is the canonical Strozz mark. The in-app SVG and transparent splash artwork use it unchanged; the layered tvOS icons and static Top Shelf images pair it with charcoal (#1C1C1E) and a subtle purple radial glow. The background follows Plozz's smooth treatment, without grain or static.

To regenerate all catalog variants while preserving their dimensions and layers:

python3 -m pip install -r tools/requirements-brand-assets.txt
python3 tools/generate_brand_assets.py
python3 -m unittest discover -s tools/tests -p 'test_brand_assets.py'

Go Live Alerts

In-app live-channel alerts are off by default, including after updating from the old opt-out behavior. Strozz asks once on Home after Twitch sign-in: keep alerts off, enable All Channels, or Choose Channels individually. You can change this later under Settings > Go Live Alerts. Review Options reopens the introduction without resetting your selections or making the automatic prompt repeat.

All Channels includes current and future Twitch follows. Turning any channel off switches to a custom selection of your current follows, with new follows off. Turning every individual switch back on does not opt into future follows; use Enable All for that. Search-based bulk actions affect only matching channels. Turning alerts off also dismisses any pending alerts and clears the queue.

These settings stay on this Apple TV and affect only Strozz's in-app alerts. Twitch's supported Get Followed Channels API does not expose notification-bell preferences, so Strozz does not sync them or change Twitch notifications on other devices.

YouTube live-source selection

YouTube playback requires a currently live broadcast, not just a playable HLS playlist or the isLiveContent flag (which remains set on archived streams). Channel lookups use the primary player response, never arbitrary video IDs from uploads or recommendations. The native player response must confirm the selected video ID and current live status before playback. If it cannot, the YouTube source stays unavailable and Twitch remains selected; an already-selected YouTube source uses the existing bounded retry and Twitch fallback notice.

Playback diagnostics

Live playback lets AVPlayer buffer before starting instead of forcing an immediate first frame. With Prefer YouTube enabled, Strozz gives source selection up to four seconds before falling back to Twitch; it does not start Twitch and then automatically interrupt it with a YouTube switch. YouTube uses an eight-second forward-buffer preference to help absorb short delivery gaps. Because a buffer preference cannot fetch video that has not aired yet, native YouTube playback targets a six-second margin behind its available live edge. It does not automatically seek away the extra headroom gained during a buffering wait. For simulcasts, three stalls within thirty seconds trigger the existing single fresh retry, with only two extra seconds of live-edge margin; persistent trouble then falls back to Twitch instead of repeatedly reloading or adding more delay. This can add initial loading time in exchange for smoother playback; it cannot eliminate upstream or network interruptions. The loading screen clears when AVPlayer starts playing, independently of the longer startup-health check, so it does not cover video that's already audible. The in-player stream title stays with the channel across source switches and playback retries; changing channels clears it before fetching the new metadata.

Closing a live player refreshes the Home rails and the originating Following, category, or search list. Stream-card identity follows the streamer, not the broadcast ID or ranking, so tvOS can retain focus through live-status updates and reordering. Return refreshes do not force focus back to the first card.

When you return to a stream, Strozz restarts its stall-detection window rather than counting time spent in the background as a freeze. An empty buffer or expired playlist triggers a live-status check and recovery, not a "stream ended" verdict: that message requires Twitch to confirm the channel is offline. Leaving while following live also preserves that intent: returning from the background or a channel page refreshes the current source to its live position, rather than leaving playback paused at the old point. Brief trips that remain near live avoid an unnecessary reload. Deliberate pauses, rewinds, and VOD positions are preserved. "LIVE" means the source's playable live position, including its normal buffering margin, not zero broadcast/network latency.

Chat's timed read pause releases its frozen snapshot when the countdown ends; collapsing chat or changing channels also resets scrolling state. The live list follows a permanent bottom anchor as its bounded message buffer rotates. While following live, it fully lays out a viewport-sized tail rather than relying on lazy row-height estimates that can leave a blank panel after emotes resize. Pausing or scrolling still exposes the full retained history. Twitch chat checks the join handshake and sends a heartbeat every thirty seconds after joining. A missing join acknowledgement, failed send, missing heartbeat reply, or server reconnect request enters the existing backoff/rejoin loop without clearing visible messages. Quiet channels do not trigger recovery just because nobody is chatting. When playback catches up to live, queued chat is retimed to the shorter video delay and its release task wakes for the earliest pending message. Foreground return also rechecks pending deadlines, so an old pre-suspension sync delay cannot hold newer chat behind a sleeping task.

While a channel is open, its 7TV emote set is rechecked every minute, including during VOD chat replay. Newly added emotes update messages already on screen. Successful provider catalogs are cached separately; failed requests are retried without discarding known emotes or caching an outage as an empty catalog. Playback diagnostics include catalog size and pending-retry state, not emote names or chat text.

Twitch rewards and polls (experimental)

In Settings > Accounts > Twitch Rewards, connect watch rewards using the same Twitch account as your normal Strozz login. This is a separate, unofficial Twitch TV device-code connection: approve it on Twitch's activation page using your phone. Strozz never asks for your password. The rewards session is stored in a device-only Keychain item, not in preferences or the Top Shelf shared container. Disconnecting removes the saved rewards session from the TV; it does not sign out your normal account or revoke other Twitch sessions.

When connected, Strozz reports one minute only after observing a minute of advancing, visible Twitch live playback. Pauses, buffering, seeking, background time, previews, YouTube playback, and VODs do not count. In multiview, only the selected audio pane is reported; opening the full player stops reporting the underlying grid. Changing channels or player items starts a fresh measurement. It does not farm unseen channels or share announcements.

The gift button in the live Twitch player opens Polls & Rewards without leaving the video. View your channel-point balance, cast one free vote in the current poll, or redeem streamer rewards, highlighted messages, and random, chosen, or modified emote unlocks. Each redemption requires confirmation; Strozz rechecks the current price, availability, and balance before submitting. Bits purchases, paid poll votes, predictions, and sub-only-message redemptions are not supported. Rewards marked Available on Twitch cannot be redeemed from Strozz.

Collect watch bonuses is enabled with the rewards connection and can be turned off in Accounts. It claims only Twitch-provided bonuses during observed, advancing playback, using the same visibility and multiview rules above. Opening the rewards menu alone never claims a bonus or spends points. Balances and successful actions come from Twitch acknowledgements, not local estimates. If a result is unconfirmed, check Twitch before retrying.

The player controls show the watch-streak count returned by Twitch. A missing milestone is shown as awaiting Twitch, never as a locally invented streak. An accepted watch report is not proof that Twitch credited it: eligibility and streak updates remain Twitch's decision. The integration can stop working if Twitch changes its private endpoints; errors are surfaced instead of silently claiming success. Twitch TV sessions may have no scheduled expiry (expires_in: 0); Strozz still validates them on first use after launch and hourly during viewing. Expired or revoked rewards sessions require reconnecting.

Strozz keeps a bounded, local JSONL playback log in its app cache so lag reports can be examined after the fact. Logging samples playback state about every two seconds and records noteworthy state changes, stalls, access/error-log updates, seeks, and recovery actions. It is diagnostic observation only; enabling it does not change playback tuning. Samples also include chat connection/read-pause flags, message-buffer counts, time since the last IRC frame, and reconnect counts/reasons, current sync delay, and queued release/wake deadlines; they do not include chat text or chat participants. Watch-rewards diagnostics record report acknowledgements and server-returned streak counts, never access tokens or activation codes.

Pull the retained logs from the paired Apple TV and summarize the current or most recent session:

python3 tools/playback-diagnostics.py pull --device <device-id>

Select a paired Apple TV with --device or the STROZZ_DEVICE_ID environment variable. The default bundle is com.thatcube.Strozz. Every pull goes into a new UTC-stamped directory under the gitignored playback-diagnostics/ directory:

python3 tools/playback-diagnostics.py pull \
  --device <device-id> --bundle com.thatcube.Strozz
python3 tools/playback-diagnostics.py pull --device <device-id> --session <session-uuid> --json

Previously pulled data can be analyzed without Xcode or a connected device:

python3 tools/playback-diagnostics.py analyze playback-diagnostics/<timestamp>
python3 tools/playback-diagnostics.py analyze <file.jsonl> --session <session-uuid>
python3 tools/playback-diagnostics.py analyze playback-diagnostics/<timestamp> --all --json

By default, analysis uses latest-session.json, or the session containing the newest record when no manifest is available. --all reports retained sessions separately; sessions are never silently combined. A copied, incomplete final JSON line is warned about and ignored, while completed corrupt lines and unknown schema versions fail analysis. Sequence gaps, dropped telemetry, reclaimed rotation parts, bounded native access/error-log backlog skips, and sessions that are still active are marked as partial evidence.

The cache retains at most eight 4 MiB files across all sessions (about 32 MiB) and tvOS may reclaim it. It does not contain OAuth credentials, full URLs, request headers, server IP addresses, AVPlayer session IDs, SDK localized error prose, or error comments. Failures retain only structured evidence such as error domain/code, AVPlayer error-log status code, and an explicit HTTP status when AVFoundation includes one. YouTube resolution records the client/version and sanitized failure category, never visitor context or response bodies. Public channel names and viewing timestamps do appear. The tool reads locally and never uploads logs.

YouTube simulcasts use the native-HLS client shared with YouTube-only playback and captions. Startup respects AVPlayer's native buffering wait; a stall notification does not force an empty-buffer YouTube item to restart immediately. The obsolete Android VR client and silent web-manifest fallback are not used. A terminal media error (even when the item still reports ready), or 20 seconds without clock progress while playback is intended, triggers one fresh YouTube resolution. Automatic attempts are at least 10 seconds apart. If that attempt also fails, the player refreshes the Twitch source and shows a brief, non-focusable notice. It does not change the saved YouTube preference or automatically switch back during that channel visit; the source picker remains available for a deliberate retry.

Interpret summaries cautiously. Proxy timings cover playlist/master requests, not media segment transfers; AVPlayer access-log throughput is a coarse cumulative estimate, not an instantaneous network test. Low buffer, bitrate differences, dropped frames, healthy-buffer waits, decode freezes, controller interventions, and thermal state can support hypotheses but do not prove a root cause. Rates cover the retained record window; initial cumulative values in a partial tail are treated as baselines, so its counter deltas are lower bounds. recovery_completed describes the recovery task returning (load_returned, load_failed, or offline); later clock/frame progress is the health evidence. first_clock_progress confirms clock movement. Stall notifications and AVPlayer's reset-aware stall counter are reported separately from clock-classified episodes, not added together. Repeated short hiccups can fall below the four-second clock threshold, so zero classified episodes does not mean playback was uninterrupted. first_video_output_frame is currently native-Twitch-only, may be up to one watchdog interval late, and records an observed pixel buffer rather than proof that a picture was rendered on screen. A seek callback arriving after the 15-second seek_deadline_exceeded event confirms only that the target callback landed, not that a picture rendered. The proxy's last failure status, error code, and monotonic uptime remain in samples after a later success so the failure is not mistaken for the latest request.

Donate

Strozz is free and open source, and it always will be. There's no paywall, no ads, and no obligation to give anything.

If the app has been useful to you and you'd like to chip in toward its upkeep — things like the Apple Developer Program fee and time spent maintaining it — donations are welcome and genuinely appreciated. Anything is plenty, and not donating is completely fine too.

Donate via GitHub Sponsors — one-time or recurring, whatever suits you.

Credits

Strozz is an unofficial, non-commercial Twitch client. It is not affiliated with, endorsed by, or sponsored by Twitch Interactive, Inc. or Amazon. Twitch is a trademark of its owner.

Third-party emote support is provided through the public 7TV, BetterTTV, and FrankerFaceZ services, and belongs to them.

License

MIT © 2026 thatcube


More open source

Hozz        Mozz        Plozz        Strozz

Brandon Moore

About

Free, open-source Apple TV streaming viewer for Twitch, YouTube, and supported Kick simulcasts, with multi-view and native chat emotes.

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages