Watch Classic Television — a nostalgic, CRT-styled Flutter app that brings back the feeling of tuning in to an old television set, streaming classic cartoons and shows straight from YouTube.
Retro TV is a cross-platform streaming experience that recreates the magic of watching television on a vintage CRT set. Pick a retro TV style (1950s black & white, 1970s wood-grain cabinet, playful kids sets, and more), grab the remote, and tune in to a curated lineup of classic cartoons and shows — complete with scanlines, channel-surfing static, power-up animation, and a full-featured on-screen remote control.
The app is built for both viewers and administrators:
- Viewers get an immersive, remote-controlled TV experience on their phone, tablet, or desktop.
- Administrators (editors, admins, super admins) manage every channel, show, episode, schedule, TV style, and user through a dedicated web-style admin dashboard, all backed by Supabase (Auth + Postgres + Row Level Security).
- Authentic CRT rendering — scanline overlays, screen curvature, phosphor glow, film grain, and power-on "warm-up" animation.
- Multiple selectable TV styles — switch between themed cabinets/skins (eras, colors, aspect ratios) in one tap.
- On-screen remote control:
- Power on/off with start-up transition
- Channel up / down navigation
- Multi-digit channel entry (auto-confirms after a pause, like a real remote) with ENTER / CLEAR
- Volume up/down, mute, and a volume OSD
- LAST channel recall (jump back to the previous channel)
- RANDOM channel surfing
- GUIDE — interactive channel guide with one-tap tuning
- FULL — immersive fullscreen mode with wakelock
- Real-time programming — the server resolves the currently-airing episode per channel (
get_current_programRPC), with automatic progress to the next episode when one ends. - Smart error recovery — if a specific YouTube upload blocks app embeds, the app skips it and advances to the next playable program automatically, so viewers never get stuck on an error screen.
- Full keyboard support — on desktop/web, use real keys: arrow keys (channel/volume),
Mmute,Ffullscreen,Ppower,Gguide, and0–9to type channel numbers. - Persistent settings — volume, mute, and selected TV style are saved locally via
shared_preferences. - About screen — app info plus the developer's profile card with Instagram and email links.
Supabase Auth with four roles enforced at the database level:
| Role | Access |
|---|---|
viewer |
Watch only |
editor |
Manage Shows & Episodes |
admin |
Everything except user role changes |
super_admin |
Full access, including Users & role management |
Security note: Role checks are enforced by Postgres Row Level Security (RLS) and SQL helper functions (
is_admin(),is_content_manager()), not by client-side flags. Promotions/deactivations run through the guardedpromote_user_to_roleRPC.
A professional, sidebar-based admin console with role-aware panels:
- Dashboard — stats & quick overview
- Channels — create/tune channels (number, slug, logo, featured, kids-friendly, default episode)
- Shows — manage shows per channel
- Episodes — upload/publish episodes with YouTube video IDs, seasons & numbering; auto-wires a published episode as its channel's default program
- Schedules — time-based
channel_scheduleentries for scheduled programming - TV Styles — visually configure retro skins (colors, era, aspect ratio, glow, effects)
- Categories — organize channels into categories
- Users — activate/deactivate and promote users (super admin only)
- Settings — public site settings
- Audit Logs — full history of admin actions via
log_admin_action
lib/
├── main.dart # App entry, theming, Provider wiring
├── config.dart # Compile-time env config (Supabase URL + publishable key)
├── models/
│ ├── category.dart # Channel categories
│ ├── channel.dart # TV channels (number, logo, default episode…)
│ ├── episode.dart # Episodes (YouTube ID, season/episode numbers, status)
│ ├── profile.dart # Profiles + role enum (viewer/editor/admin/super_admin)
│ ├── schedule.dart # channel_schedule entries
│ ├── show.dart # Shows
│ └── tv_style.dart # Retro TV skins (theme config → renderer)
├── services/
│ ├── auth_service.dart # Sign in/out, profile state
│ ├── content_service.dart # Data-access layer (public + admin CRUD)
│ ├── supabase_service.dart # Supabase client singleton
│ └── tv_state.dart # Central TV state machine (power/channel/volume/playback)
├── screens/
│ ├── public/
│ │ ├── home_screen.dart # Main viewer experience
│ │ ├── fullscreen_tv_screen.dart # Immersive fullscreen mode
│ │ └── about_screen.dart # About + developer card
│ └── admin/
│ ├── admin_login_screen.dart # Secure admin sign-in
│ ├── admin_dashboard_screen.dart # Role-aware admin shell
│ └── panels/ # 10 admin management panels
├── utils/
│ └── youtube_utils.dart # YouTube ID/video helpers
└── widgets/
├── admin/admin_shared.dart # Shared admin UI components
└── tv/ # TV building blocks
├── channel_guide.dart
├── crt_overlay.dart
├── crt_tv_frame.dart
├── static_noise.dart
├── tv_controls.dart # On-screen remote
├── tv_style_selector.dart
└── youtube_screen_player.dart
supabase/
└── migrations/ # 001–006 (schema → RLS → indexes → functions → seed → storage)
└── COMBINED_MIGRATION.sql # Single-file database setup
- The app powers on and tunes to a channel.
get_current_program(a Postgres RPC) resolves the airing episode — checkingchannel_schedulefirst, then falling back to the channel'sdefault_episode_id.- The resolved episode plays automatically in the embedded YouTube player (a muted-start → auto-unmute strategy guarantees autoplay).
- When an episode ends — or embeds are blocked by the uploader —
onProgramEnded/onProgramUnavailableadvance to the next available episode or channel with zero user input.
| Layer | Technology |
|---|---|
| Frontend | Flutter (Material 3, dark theme, monospace styling) |
| State management | Provider (ChangeNotifier) |
| Backend | Supabase (Auth, Postgres, RLS, Storage) |
| Video playback | youtube_player_iframe (muted-autoplay strategy) |
| Local persistence | shared_preferences |
| Utilities | url_launcher, wakelock_plus, uuid, http |
Key packages: supabase_flutter 2.6.0, provider 6.1.5+1, youtube_player_iframe 6.0.2, shared_preferences 2.5.3, wakelock_plus 1.2.10, url_launcher 6.3.2, uuid 4.5.1
- Flutter SDK
3.9or newer - A Supabase project (free tier works fine)
1. Clone the repository
git clone https://github.com/mzainulabideendev/Retro-TV.git
cd Retro-TV2. Set up the Supabase database
Apply supabase/COMBINED_MIGRATION.sql (or run migrations 001–006 in order) in your Supabase SQL editor. This creates the full schema:
profiles,tv_styles,categories,channels,shows,episodes,channel_schedule,featured_content,site_settings,audit_logs- RLS policies for viewer/editor/admin/super_admin access
- Indexes for performance
- Helper functions & RPCs (
get_current_program,promote_user_to_role,log_admin_action, …) - Seed data and a public storage bucket for media
Then create the storage bucket:
-- 006_storage_buckets.sql
insert into storage.buckets (id, name, public) values ('media', 'media', true);3. Configure environment
Copy the example config and fill in your project's values:
cp .env.example .env# .env
SUPABASE_URL=https://your-project-ref.supabase.co
SUPABASE_PUBLISHABLE_KEY=sb_publishable_xxxxxxxxxxxxxxxxxxxxxxxxOnly the URL and publishable (anon) key belong in the client bundle — they are safe by design because the database is protected by RLS. The secret/service-role key and database URL must never be committed or shipped to the client.
You can also override the values at build time:
flutter run --dart-define=SUPABASE_URL=... --dart-define=SUPABASE_PUBLISHABLE_KEY=...4. Create your first admin
- Sign up a user in your Supabase Auth dashboard (Dashboard → Authentication → Users → Add user).
- Set their role with the guarded RPC:
select public.promote_user_to_role('USER_UUID', 'super_admin');5. Run the app
flutter pub get
flutter run# Android APK
flutter build apk --release
# Android app bundle (Play Store)
flutter build appbundle --release
# iOS
flutter build ios --release
# Web
flutter build web --release
# Linux (requires libmpv.so.2 on the target machine)
flutter build linux --releaseDesktop release channels: Prebuilt Windows packages and an MSIX are shipped via the GitHub releases; Linux is distributed as a signed
.deb+ APT repo, a signed.rpm+ DNF repo, a Flatpak bundle, and recipes for Arch/AUR and Snap. SeeLINUX_INSTALL.mdfor install instructions andLINUX_DOWNLOADS.mdfor download URLs. Signing / key-management policy is described inkeys/README.mdandSECURITY.md.
The app targets Android, iOS, Web, macOS, Windows, and Linux. The produce folder structure is already scaffolded for every platform (android/, ios/, web/, windows/, macos/, linux/).
The project ships with Flutter's standard test harness:
flutter testlib/services/tv_state.dart— the heart of the app: a complete state machine (off → starting up → on), digit buffering, channel transition animation (static → load → play), program advancement, and persisted volume/style.lib/services/content_service.dart— every read is RLS-filtered and works for anonymous viewers; every write is authorized at the database level. Episodes auto-wire to channels via_maybeSetAsChannelDefault, fixing the "channel unavailable" bug.lib/config.dart— compile-time config viaString.fromEnvironment, with a working public default so the app boots even without explicit flags.
- RLS is the boundary. Client-side role flags are convenience only; every privileged operation is enforced by Postgres policies and security-definer functions.
- Promotion writes go through
promote_user_to_role, restricted to super admins on the server. - Secrets never ship. Only the publishable/anon key is embedded; secret keys live server-side only.
Contributions, ideas, and classic-cartoon suggestions are welcome!
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Have a favorite cartoon or movie suggestion, or just want to say hi?
- Instagram: @m.zainulabideenofficial
- Email: zu4425@gmail.com
Distributed under the MIT License. See LICENSE for more information.