Let's kids watch YouTube channels on Apple TV that parents approve on their iPhone.
Setup time: ~1 hour if you’re an iOS developer with Xcode already installed. ~3–5 hours if you’re new to all of this. After setup, daily use takes about 10 seconds (Face ID → tap a channel). See the How long will this take? section for the full breakdown.
A “safe YouTube” app for kids and Apple TV. The parent’s iPhone is used to add YouTube channels to a whitelist; the Apple TV shows only those channels, with no search bar, no recommendations, no comments, and no way for the child to wander off into the rest of YouTube.
If you’re new to building Apple apps and you’ve been “vibe coding” with AI tools — this guide is written for you. Follow the steps in order, copy and paste the commands as written, and don’t worry if some sections seem long: the detail is there so you don’t get stuck.
| If you are… | Total time | Where the time goes |
|---|---|---|
| iOS developer — Xcode installed, comfortable with Apple Developer / Google Cloud / iCloud dashboards | ~1 hour | Mostly clicking through dashboards (Apple Dev container creation, Google Cloud API key, CloudKit indexes). About 5 minutes of actual sed/Terminal work. |
| Some tech background — used Terminal before, never built an iOS app, no developer accounts yet | ~2 hours | Add 30 min for Xcode install + first-time Apple Developer / Google Cloud enrollment. |
First-time vibe coder — never used Xcode, never used Terminal beyond cd, no developer accounts |
~3–5 hours | Most of the time is waiting on Xcode to download (~10 GB, can take an hour on home internet) and learning to navigate Apple’s and Google’s dashboards. Plan to do it across two sittings if needed. |
You do not write any Swift code. Setup is all configuration: creating accounts, copy-pasting commands into Terminal, clicking through dashboards. The longest single waits are the Xcode download (~1 hour for non-developers who don’t have it) and the first Apple TV pairing (~5 minutes of fiddling).
You can absolutely pause partway through. Setup is one-time. Daily use after setup is trivial.
This is not an App Store app. SaferStreamer extracts YouTube video streams using a community library called YouTubeKit. This is against YouTube’s Terms of Service in a grey way, and it will not pass App Store review. Apple won’t punish you for installing it on your own family’s devices — but don’t try to publish it or sell it.
You’ll install it onto your own iPhone and Apple TV using Xcode (Apple’s free developer tool). This process is called “sideloading.” It’s safe and supported, but it does require you to sign up for Apple’s developer program (free for testing, or $99/year if you want the install to last more than 7 days).
You also need a Google Cloud account to get a free YouTube API key. This is what lets the iPhone search YouTube. The free quota is far higher than any family will ever use.
For the full time breakdown by experience level, see How long will this take? above. The short version: ~1 hour if you’ve done iOS dev before, ~3–5 hours if this is your first time with Xcode and Apple’s dashboards.
You install these on your Mac, once. Open the Terminal app (press Cmd+Space, type “Terminal”, press Enter) and paste each block in order.
1. Install Xcode — Apple’s developer tool. Open the App Store, search for “Xcode”, click “Get” or “Install”. This is a ~10 GB download, plan accordingly. Once installed, open Xcode once, agree to the license, and let it install additional components.
2. Install Homebrew — a package manager that makes installing other tools painless. Paste this in Terminal:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Follow the prompts (it will ask for your Mac password — type it; you won’t see characters as you type, that’s normal). When it’s done, it tells you to run a couple of “Next steps” commands — do those, they add Homebrew to your shell.
3. Install XcodeGen — a tool this project uses to generate the Xcode project file. Paste this:
brew install xcodegen
You’re now ready to set up the project.
The steps below are in the order you should do them. Each one is a checkpoint — if you get stuck, the next step won’t make sense, so stop and fix the problem (or ask AI — see the Getting help section).
Open Terminal. Paste:
cd ~
git clone https://github.com/Proxy/youtube-safer-streamer.git
cd "youtube-safer-streamer/youtube safer streamer app"
The folder name has spaces — the quotes are required.
You should now be inside a folder called “youtube safer streamer app”. Confirm by running pwd — it should print a path ending in youtube safer streamer app. Leave this Terminal window open; you’ll keep using it.
xcodegen generate
You’ll see a few lines about generating plists and writing the project. The last line should say Created project at .../SaferStreamer.xcodeproj. If you see xcodegen: command not found, you skipped the Homebrew install in the previous section.
You need a 10-character string from Apple. Get it from:
developer.apple.com/account#MembershipDetailsCard — sign in with your Apple ID
The page shows “Team ID” with a 10-character value like A1B2C3D4E5. Copy it.
If you haven’t enrolled in the Apple Developer program yet, you’ll be prompted to do so here. Free enrollment is fine to start.
In Terminal, replace ABCDE12345 below with your actual Team ID, then paste:
TEAM_ID="ABCDE12345"
sed -i '' "s/YOUR_TEAM_ID/${TEAM_ID}/g" project.yml
xcodegen generate
If you forget to change ABCDE12345 to your real ID, the build will fail later with “No matching signing certificate.” Just rerun this step with the correct ID and rerun xcodegen generate.
Every iOS app has a globally-unique “bundle ID” like com.yourname.appname. The current bundle IDs in this project use com.jonzhao.saferstreamer, which belongs to the original author and won’t work in your Apple Developer account. You need to change jonzhao to your own unique string.
Use something short, all lowercase, no dots — typically your name, initials, or company. For example alicedev, johnsmith42, or your initials plus a number. It doesn’t have to be a real domain you own.
In Terminal, replace yourname with your chosen namespace, then paste:
NAMESPACE="yourname"
sed -i '' "s/com\\.jonzhao\\.saferstreamer/com.${NAMESPACE}.saferstreamer/g" \
project.yml \
Shared/CloudKit/CloudKitConfig.swift \
Shared/Utilities/Logger+Categories.swift \
iOS/SaferStreamer-iOS.entitlements \
tvOS/SaferStreamer-tvOS.entitlements
xcodegen generate
Verify it worked. Run:
grep -r "com.jonzhao" .
You should see no output. If you see any lines, the replacement missed something — re-run the sed block. Make sure you actually changed yourname to your real namespace before running.
From now on, write down your namespace — you’ll use it again in the Google and Apple consoles. Example: if you chose alicedev, your bundle IDs are now:
com.alicedev.saferstreamer (the iPhone app)com.alicedev.saferstreamer.tv (the Apple TV app)com.alicedev.saferstreamer.tests (the test bundle — ignore this one)The iCloud container is the private storage area where your iPhone and Apple TV exchange the channel list.
SaferStreamer.iCloud.com.<your-namespace>.saferstreamer — exactly that, with your namespace substituted. Example: iCloud.com.alicedev.saferstreamer.You don’t need to manually create App IDs — Xcode will do that automatically when you build in Step 12. You only needed to create the container ahead of time.
This is what lets the iPhone search YouTube. It’s free at the volume a single family uses.
saferstreamer. Leave Organization as “No organization.” Click CREATE.If Google prompts you to set up a billing account: you can skip / dismiss this. The YouTube Data API works under the free 10,000 units/day quota without a billing account attached. (Family use is around 200 units/day, so you’re not at risk of exceeding it.) If the prompt blocks you and won’t go away, you can attach a billing account but you won’t be charged for normal use — Google requires you to add a card in some flows but YouTube API itself is in the always-free tier.
YouTube Data API v3. Click the result. Click the blue ENABLE button. Wait ~10 seconds for it to enable.AIza...). Click EDIT API KEY to add restrictions (don’t just copy the key and close the box).SaferStreamer iOS or similar (optional).com.<your-namespace>.saferstreamercom.<your-namespace>.saferstreamer.tvThe API key is a secret. You’ll put it in a file that is automatically excluded from git (so it never accidentally gets pushed to GitHub).
In Terminal (still in the youtube safer streamer app folder), run:
cp Config/Secrets.xcconfig.example Config/Secrets.xcconfig
open -e Config/Secrets.xcconfig
A TextEdit window opens with the file contents. You’ll see a line like:
YOUTUBE_API_KEY = REPLACE_WITH_YOUR_YOUTUBE_DATA_API_V3_KEY
Replace REPLACE_WITH_YOUR_YOUTUBE_DATA_API_V3_KEY with the actual key you copied (starts with AIza...). Save the file (Cmd+S) and close TextEdit.
In Terminal:
open SaferStreamer.xcodeproj
Xcode launches and shows the project. The first time you open it, Xcode will spend ~1–2 minutes downloading the YouTubeKit Swift package (you’ll see a progress indicator at the top). Wait for it to finish.
Xcode needs to know your app uses the iCloud container you created in Step 6.
iCloud.com.<your-namespace>.saferstreamer. If it’s not there, click the + button under Containers and add it.If Xcode shows red error text like “Provisioning profile doesn’t include the iCloud entitlement” — click the Try Again button if it appears, or right-click your team name in the Signing section and choose “Download Manual Profiles.”
In Xcode’s top toolbar, with your iPhone selected as the destination, click the ▶︎ Play button (or press Cmd+R).
Wait ~30–60 seconds for the build to complete. Xcode will compile the app, sign it, install it on your iPhone, and launch it. The first run is the slowest; subsequent runs are fast.
If the build fails with a code signing error:
If the build succeeds but the app crashes on launch or shows a “Trust” error:
Mark Rober). Wait ~half a second — results appear.If search fails:
Now that you’ve added at least one channel, you need to do one final piece of setup in the CloudKit Console. Without this, the app will appear to “lose” your channels on the next launch.
iCloud.com.<your-namespace>.saferstreamer.| Record Type | Field | Index Type |
|---|---|---|
| Channel | recordName (Metadata) |
Queryable |
| Channel | addedAt |
Queryable + Sortable |
| AuditEntry | recordName (Metadata) |
Queryable |
| AuditEntry | timestamp |
Queryable + Sortable |
| WatchProgress | recordName (Metadata) |
Queryable |
| WatchProgress | lastWatchedAt |
Queryable + Sortable |
For “Queryable + Sortable” rows, add the index twice (once with Queryable, once with Sortable). For recordName (Metadata), the field is listed under “Metadata Index” / “System Fields” — you’ll see it in the dropdown.
Why this exists: CloudKit auto-creates the record types when your app writes them, but doesn’t auto-create the indexes needed to query them back. Without these, the app silently shows empty lists on reload even though your data is saved.
If you don’t add the indexes, you’ll see this error in Xcode’s console:
Field 'recordName' is not marked queryable
Once the indexes are added, restart the iPhone app and confirm your channel is still in the list.
The Apple TV needs to be paired with your Mac over the same Wi-Fi network. Both should be on the same Apple ID.
If the Apple TV doesn’t show up in Xcode:
If you see “Could not launch app because the device is not paired” on first launch:
This is the moment of truth — did all that setup actually work?
If you got here, the setup worked. 🎉
Add more channels from the iPhone whenever you want. They’ll appear on the TV within a few seconds (or definitely on the next TV-app launch).
Parent (iPhone):
Child (Apple TV):
Auto-updates:
Most likely: API key problem. In order, try:
Config/Secrets.xcconfig and confirm your key is there (starts with AIza..., no quotes around it).com.<your-namespace>.saferstreamer and com.<your-namespace>.saferstreamer.tv.Almost always: missing CloudKit indexes. Re-do Step 14 above. Restart the app.
project.yml, find from: "0.4.8", change to the newer version, run xcodegen generate, rebuild.That’s expected. Free developer signing expires after 7 days. Reopen Xcode, plug in the device, press Cmd+R to redeploy.
To skip this hassle, enroll in the paid Apple Developer program ($99/yr) — installs then last a year.
If you edited project.yml, always run xcodegen generate afterward — otherwise Xcode is still looking at the old project file.
If you added or moved any .swift file, run xcodegen generate too.
You’re doing this with AI tools. When something breaks, your AI assistant is your first line of help. Paste the exact error message into your AI (Claude, ChatGPT, or wherever you code) along with what step you were on, what you expected to happen, and what actually happened.
Useful prompt template:
I'm setting up the SaferStreamer iOS app following its README.
I'm on Step [N] — [step name].
I expected: [what you thought would happen].
What actually happened: [paste the error message verbatim].
My setup: macOS [version], Xcode [version], free / paid Apple Developer account.
If the error includes a long stack trace, paste the whole thing — AI assistants are good at parsing them.
For Xcode-specific errors: in Xcode, click the red exclamation point in the left sidebar (Issue Navigator) and click on each red line to see the full message.
The setup above is all most users need. The rest of this section is for people who want to read the code or modify it.
Shared/ Models, Stores, CloudKit, Services, Views, Utilities (used by both targets)
iOS/ iPhone-specific: Auth (BiometricGate), Notifications, parent UI
tvOS/ Apple TV-specific: child-facing viewer, AVPlayer playback
Tests/ 121 pure-logic unit tests across 9 files (no async CloudKit mocking)
Config/ Secrets.xcconfig (gitignored)
project.yml XcodeGen spec — source of truth
@Observable classes, @MainActor-isolated, hosted via @State and passed via .environment(...).reload() on launch and on scenePhase == .active.AVPlayer prefers an HD composition (separate video+audio DASH streams merged via AVMutableComposition, enabling 1080p / 4K) and falls back to the highest muxed stream (~720p) if composition fails. WKWebView is unavailable on tvOS, so the IFrame Player API approach was not feasible.iOS/Auth/BiometricGate.swift uses .deviceOwnerAuthenticationWithBiometrics (Face ID only — no passcode fallback).⋯ menu on iOS exposes Edit list / Sort list (Manual / Alphabetical / Date added) / Settings. The selected sort mode lives on the shared Settings CloudKit record, so the Apple TV grid stays in sync with whatever order the parent’s iPhone shows. Manual mode uses a dense sortIndex per Channel record, renumbered on every drag-to-reorder via .onMove. Inline minus-circle delete (.onDelete) and trailing swipe-to-remove both route through the same confirmation alert + 5-second undo toast.CIAreaAverage) and stores it as a hex string. The tvOS tile renders a gradient from that color instead of a flat fallback.xcodebuild -project SaferStreamer.xcodeproj -scheme SaferStreamerTests -destination "platform=iOS Simulator,name=iPhone 17 Pro" test
All 121 tests should pass in well under a second. Tests cover the pure-logic helpers: CKRecord round-trip mapping (including missing-field migration paths for sortIndex, channelSortMode, and backgroundChoice), biometric freshness/cooldown math, channel sort-order migration, channel sort-mode dispatch (sorted(_:by:)), Channel.withFallbackColor field-preservation invariants, settings clamping + three-field independence (length cap / sort mode / background), hex color round-trip, watch-progress completion thresholds.
MIT — do whatever you want with the code. Attribution appreciated but not required.
I made this for personal use and don’t have bandwidth to answer issues or review pull requests. Fork it, modify it, share it with friends — but please don’t open issues asking for help. Your AI coding assistant is a much faster path to a working setup than waiting on me.