[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-95212":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":9,"totalLinesOfCode":9,"stars":12,"forks":13,"watchers":14,"openIssues":14,"contributorsCount":9,"subscribersCount":14,"size":14,"stars1d":14,"stars7d":14,"stars30d":14,"stars90d":14,"forks30d":14,"starsTrendScore":14,"compositeScore":15,"rankGlobal":9,"rankLanguage":9,"license":9,"archived":16,"fork":16,"defaultBranch":17,"hasWiki":16,"hasPages":16,"topics":9,"createdAt":9,"pushedAt":9,"updatedAt":18,"readmeContent":19,"aiSummary":9,"trendingCount":14,"starSnapshotCount":14,"syncStatus":20,"lastSyncTime":9,"discoverSource":21},95212,"Opaline","verback2308\u002FOpaline","verback2308","A lightweight, privacy-focused YouTube client for iOS built entirely with UIKit. No ads, no tracking, no dependencies.",null,"https:\u002F\u002Fgithub.com\u002Fverback2308\u002FOpaline","Swift",251,27,0,41.34,false,"main","2026-08-24 04:01:23","\u003Cdiv align=\"center\">\n\n# Opaline\n\n\u003Cpicture>\n  \u003Csource media=\"(prefers-color-scheme: dark)\" srcset=\"source\u002Flogo-dark.png\">\n  \u003Cimg src=\"source\u002Flogo.png\" width=\"128\" alt=\"\">\n\u003C\u002Fpicture>\n\n**A lightweight, native YouTube client for iOS 12+. No ads, no tracking, no dependencies.**\n\n[![Latest release](https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Fv\u002Frelease\u002Fverback2308\u002FOpaline?label=release&color=blue)](https:\u002F\u002Fgithub.com\u002Fverback2308\u002FOpaline\u002Freleases\u002Flatest)\n[![Downloads](https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Fdownloads\u002Fverback2308\u002FOpaline\u002Ftotal?color=brightgreen)](https:\u002F\u002Fgithub.com\u002Fverback2308\u002FOpaline\u002Freleases)\n![iOS 12+](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FiOS-12%2B-lightgrey?logo=apple)\n[![License: GPL v3](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Flicense-GPLv3-blue)](LICENSE)\n\n\u003C\u002Fdiv>\n\n> ### What happened between 14 and 17 August\n>\n> Over those days YouTube kept changing how it hands out video, and most\n> third-party clients stopped playing, this one among them. Playback cut out\n> about a minute in. A fix would land, and the next change would take something\n> else away: dubbed audio, content made for kids, watch history.\n>\n> **It plays again. Update to the latest version.**\n>\n> Getting there meant rebuilding the way the app fetches video. There are\n> several sources now, listed in Settings → Playback, where you can drag them\n> into the order you want or switch off the ones you do not:\n>\n> - two fast ones, no sign-in needed, dubbed audio works\n> - one that reaches 1080p but needs you to be signed in and takes longer to\n>   start\n> - one that serves a single 360p quality and starts instantly\n>\n> Sources are tried from the top down until one plays. Content made for kids\n> comes only from the 1080p source or the 360p one.\n>\n> Nothing to do beyond updating. Your account, settings and history are\n> untouched.\n>\n> \u003Csub>Opaline was released as YTLite until August. It was renamed to avoid\n> confusion with an unrelated tweak of the same name and is not related to it.\u003C\u002Fsub>\n\n\u003Cdiv align=\"center\">\n\n\u003Cbr>\n\n\u003Cpicture>\n  \u003Csource media=\"(prefers-color-scheme: dark)\" srcset=\"screenshots\u002Fapp\u002Fiphone\u002Fdark\u002Frecommendations.jpeg\">\n  \u003Cimg src=\"screenshots\u002Fapp\u002Fiphone\u002Flight\u002Frecommendations.jpeg\" width=\"24%\" alt=\"Home feed\">\n\u003C\u002Fpicture>\n\u003Cpicture>\n  \u003Csource media=\"(prefers-color-scheme: dark)\" srcset=\"screenshots\u002Fapp\u002Fiphone\u002Fdark\u002Fplayer.jpeg\">\n  \u003Cimg src=\"screenshots\u002Fapp\u002Fiphone\u002Flight\u002Fplayer.jpeg\" width=\"24%\" alt=\"Video player with comments\">\n\u003C\u002Fpicture>\n\u003Cpicture>\n  \u003Csource media=\"(prefers-color-scheme: dark)\" srcset=\"screenshots\u002Fapp\u002Fiphone\u002Fdark\u002Fchannel.jpeg\">\n  \u003Cimg src=\"screenshots\u002Fapp\u002Fiphone\u002Flight\u002Fchannel.jpeg\" width=\"24%\" alt=\"Channel page\">\n\u003C\u002Fpicture>\n\u003Cpicture>\n  \u003Csource media=\"(prefers-color-scheme: dark)\" srcset=\"screenshots\u002Fapp\u002Fiphone\u002Fdark\u002Fsubscriptions.jpeg\">\n  \u003Cimg src=\"screenshots\u002Fapp\u002Fiphone\u002Flight\u002Fsubscriptions.jpeg\" width=\"24%\" alt=\"Subscriptions feed\">\n\u003C\u002Fpicture>\n\n\u003Csub>Screenshots follow your GitHub theme — the app supports both light and dark mode.\u003C\u002Fsub>\n\n\u003C\u002Fdiv>\n\n## Why\n\nWhen Google dropped support for the official YouTube app on older devices, there was no way to watch videos properly. Browsers capped quality at 360p — and even that barely ran. Opaline was born to restore what was lost: high-quality playback on hardware that still works fine, just ignored by Google — a focused, lightweight client that does one thing well.\n\n## Features\n\n- **Video playback** — up to 1080p 60fps on every device; 2K\u002F4K on hardware with AV1 decode (iPhone 15 Pro and newer, M3+ iPads)\n- **Shorts** — a native full-screen viewer with vertical swiping, likes, comments and sharing; the next short is preloaded so it opens on video rather than a poster. Tapping a short anywhere carries that list into the viewer, and the whole tab can be switched off\n- **Kids content** — plays videos the standard API sources refuse, via a dedicated playback source\n- **Offline downloads** — save a video to the device and watch it with no network. Its page comes along: thumbnail, description, like counts and the comments as of the moment you saved it. Settings → Downloads picks the quality, how many comments and which subtitle languages travel with it, and a video that plays dubbed in your language is saved dubbed\n- **Pinch to zoom** — fill the screen in fullscreen with a pinch, or turn on Zoom to Fill to do it automatically\n- **Background audio** — continue listening with the screen off\n- **Media controls** — play\u002Fpause and next\u002Fprevious video from Control Center, the lock screen and headphones\n- **Picture-in-Picture** — watch while using other apps\n- **SponsorBlock** — skip sponsored segments automatically\n- **Return YouTube Dislike** — see dislike counts again\n- **Audio tracks** — switch dubbed audio on multi-language videos, or start videos dubbed in your language automatically; AI auto-dubs are marked \"(AI)\"\n- **Subtitles** — full subtitle\u002Fcaption support with VTT parsing\n- **13 languages** — localized interface, with video titles\u002Fsearch\u002Ffeeds following your language (see [Localization](#localization))\n- **Search & browse** — live suggestions, recent-search history, filters (sort, upload date, type, duration), channel pages, playlists\n- **Smart home feed** — endless recommendations with category chips read from your feed's shelves\n- **Subscriptions** — follow channels with a local subscription feed\n- **Notifications** — a bell in the top bar collects app news and new-version announcements, with the full release notes in the message; system notifications are optional and everything still collects in-app if you decline\n- **Watch history** — progress indicators, synced across devices; a video resumes where you left it the moment you reopen it, without waiting for the server to catch up\n- **Autoplay** — automatically play the next related video, with replay, previous and next offered when one ends\n- **Auto theme** — follows system dark mode on iOS 13+, scheduled hours on iOS 12; manual override available\n- **Made for old hardware** — thumbnails and channel details are fetched and decoded a few at a time rather than all at once, which is what keeps scrolling smooth on a dual-core A7\n- **Your layout** — pick the tab the app opens on, force the icon light or dark, and browse settings as a nested menu instead of one long list\n\n\u003Cdiv align=\"center\">\n\n\u003Cpicture>\n  \u003Csource media=\"(prefers-color-scheme: dark)\" srcset=\"screenshots\u002Fapp\u002Fipad\u002Fdark\u002Fplayer.jpeg\">\n  \u003Cimg src=\"screenshots\u002Fapp\u002Fipad\u002Flight\u002Fplayer.jpeg\" width=\"90%\" alt=\"iPad player with related videos side by side\">\n\u003C\u002Fpicture>\n\n\u003Csub>Native iPad layout — player and related videos side by side.\u003C\u002Fsub>\n\n\u003C\u002Fdiv>\n\n\u003Cdetails>\n\u003Csummary>\u003Cb>More screenshots\u003C\u002Fb>\u003C\u002Fsummary>\n\u003Cbr>\n\u003Cdiv align=\"center\">\n\n\u003Cpicture>\n  \u003Csource media=\"(prefers-color-scheme: dark)\" srcset=\"screenshots\u002Fapp\u002Fiphone\u002Fdark\u002Fsettings.jpeg\">\n  \u003Cimg src=\"screenshots\u002Fapp\u002Fiphone\u002Flight\u002Fsettings.jpeg\" width=\"32%\" alt=\"Settings\">\n\u003C\u002Fpicture>\n\u003Cimg src=\"screenshots\u002Fapp\u002Fiphone\u002Flight\u002Fshorts.jpeg\" width=\"32%\" alt=\"Shorts viewer\">\n\u003Cpicture>\n  \u003Csource media=\"(prefers-color-scheme: dark)\" srcset=\"screenshots\u002Fapp\u002Fipad\u002Fdark\u002Frecommendations.jpeg\">\n  \u003Cimg src=\"screenshots\u002Fapp\u002Fipad\u002Flight\u002Frecommendations.jpeg\" width=\"63%\" alt=\"iPad home feed\">\n\u003C\u002Fpicture>\n\n\u003Cpicture>\n  \u003Csource media=\"(prefers-color-scheme: dark)\" srcset=\"screenshots\u002Fapp\u002Fipad\u002Fdark\u002Fchannel.jpeg\">\n  \u003Cimg src=\"screenshots\u002Fapp\u002Fipad\u002Flight\u002Fchannel.jpeg\" width=\"48%\" alt=\"iPad channel page\">\n\u003C\u002Fpicture>\n\u003Cpicture>\n  \u003Csource media=\"(prefers-color-scheme: dark)\" srcset=\"screenshots\u002Fapp\u002Fipad\u002Fdark\u002Fsubscriptions.jpeg\">\n  \u003Cimg src=\"screenshots\u002Fapp\u002Fipad\u002Flight\u002Fsubscriptions.jpeg\" width=\"48%\" alt=\"iPad subscriptions feed\">\n\u003C\u002Fpicture>\n\n\u003C\u002Fdiv>\n\u003C\u002Fdetails>\n\n## Installation\n\nOpaline runs on devices with **iOS 12 and above**.\n\n### Non-jailbroken devices\n\n**Option 1 — Add source (recommended)**\n\nAdd the Opaline source to your sideloading app to receive automatic updates:\n\n\u003Ca href=\"https:\u002F\u002Fstikstore.app\u002Faltdirect\u002F?url=https:\u002F\u002Fverback2308.github.io\u002Frepo\u002Fapps.json\">\u003Cimg src=\"https:\u002F\u002Fgithub.com\u002FStikStore\u002Faltdirect\u002Fraw\u002Fmain\u002Fassets\u002Fpng\u002FAltSource_Blue.png\" height=\"55\" alt=\"Add Source\">\u003C\u002Fa>\n\n**Option 2 — Manual install**\n\n[Download the latest IPA](https:\u002F\u002Fgithub.com\u002Fverback2308\u002FOpaline\u002Freleases\u002Flatest) and install via **SideStore**, **AltStore**, or **LiveContainer**.\n\n**Option 3 — Build from source**\n\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002Fverback2308\u002FOpaline.git\ncd Opaline\ncp Config\u002FLocal.xcconfig.example Config\u002FLocal.xcconfig\n.\u002Fmake_ipa.sh\n```\n\n### Jailbroken devices\n\n**Option 1 — Cydia\u002FSileo repo (recommended)**\n\nAdd the repo to your package manager to install Opaline and receive automatic updates:\n\n```\nhttps:\u002F\u002Fverback2308.github.io\u002Frepo\u002F\n```\n\nRootful (`iphoneos-arm`) and rootless (`iphoneos-arm64`) packages are provided; Sileo, Zebra and Cydia are supported. Every released version stays available in the repo, so you can also install or roll back to an older one (Sileo\u002FZebra: package page → version list).\n\nPick one channel and stay on it — the repo package and the IPA share a bundle identifier, and having both installed leaves an app icon the Home Screen cannot delete. Already have the other one? Remove it first: the IPA from the Home Screen, the repo package from your package manager (or `dpkg -r com.verback.ytlite` over SSH).\n\n**Option 2 — Manual install**\n\nInstall the `.ipa` package directly:\n- **Filza** — open the `.ipa` file → Install\n- **ReProvision** — sign and install the IPA from the app\n\n## Known Issues and Limitations\n\n- Playback speeds above 2x may cause issues\n- Comments are read-only — you can browse and sort them and open replies, but not post, reply or like\n- Notification delivery is scheduled by iOS, which grants background time at its own discretion — expect news to arrive within hours of publication, not minutes, and not at all while Background App Refresh or Low Power Mode says otherwise\n- **Picture in Picture is limited by the system before iOS 15**, and no app can work around it: the window survives exactly one video, and switching to another one closes it and leaves audio playing — [the full findings are in issue #31](https:\u002F\u002Fgithub.com\u002Fverback2308\u002FOpaline\u002Fissues\u002F31#issuecomment-5148224679)\n  - Before iOS 14.2 the system only starts PiP by itself from **fullscreen**. Leaving the app while the video plays inline gives background audio instead — the PiP button works in both cases\n  - Before iOS 15 background audio requires the player to give up its video layer, and the system then refuses to open PiP for that video at all. So once a video has played in the background, automatic PiP no longer starts for it — the PiP button still does, and the next video starts clean\n  - On a **jailbroken device** PiP may not start at all — confirmed on an iPad mini 2 running iOS 12.5.8 with Chimera, and likely the same on checkra1n. The jailbreak denies it, not the app; installing [ForceInPicture](https:\u002F\u002Fgithub.com\u002FPoomSmart\u002FForceInPicture) restores it\n\n## Localization\n\nThe interface follows your system language by default and can be overridden in **Settings → Language**. The content language (video titles, search, feeds — translated server-side by YouTube, like the official app) follows the app language; the region can be set separately.\n\n> [!NOTE]\n> Wording follows the official YouTube app's own translations wherever it has an equivalent string, but mistakes are still possible. If you spot a wrong or awkward translation — or want a language that isn't here — please [open an issue](..\u002F..\u002Fissues) describing where it appears and what the correct wording should be.\n\n\u003Cdetails>\n\u003Csummary>\u003Cb>Available in 13 languages\u003C\u002Fb>\u003C\u002Fsummary>\n\u003Cbr>\n\n| | | | |\n|---|---|---|---|\n| `en` English | `ru` Русский | `uk` Українська | `de` Deutsch |\n| `es` Español | `fr` Français | `it` Italiano | `ja` 日本語 |\n| `pt` Português | `tr` Türkçe | `vi` Tiếng Việt | `zh-Hans` 简体中文 |\n| `zh-Hant` 繁體中文 | | | |\n\n\u003C\u002Fdetails>\n\n## Playback Helper Server\n\nSigned-in playback relies on a small companion service, and so does the Mobile Web source it falls back to. Preparing these streams requires evaluating JavaScript from YouTube's public player page — something iOS 12-era devices can't do on-device. The app delegates that single step to the helper server and receives the computed result back.\n\nSince 14 August this covers more than it used to. Dubbed audio and videos made for kids are prepared this way, so if the service is unreachable, dubs quietly fall back to the original audio and kids videos won't play until it's back.\n\n**What it sees:** no account data, no tokens, no cookies, no watch history — only the challenge strings taken from the public player code and the ID of the video being prepared. If you're inspecting traffic and wondering about requests to a non-YouTube host — that's this.\n\nThe server's source code will be published later so you can host your own instance and point the app at it (**Settings → Debug → Solver Server**).\n\n## Bug Reports\n\nIf you encounter a bug, you can export debug logs directly from the app:\n\n**Settings → Debug → Share Debug Log**\n\nThis generates a log file you can attach to your GitHub issue. The log includes timestamped playback, API, and caching events that help diagnose problems.\n\n\u003Cdetails>\n\u003Csummary>\u003Cb>For developers\u003C\u002Fb>\u003C\u002Fsummary>\n\n## Building\n\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002Fverback2308\u002FOpaline.git\ncd Opaline\ncp Config\u002FLocal.xcconfig.example Config\u002FLocal.xcconfig\nopen Opaline.xcodeproj\n```\n\nEdit `Config\u002FLocal.xcconfig` and set your own `PRODUCT_BUNDLE_IDENTIFIER`.\n`APP_MANIFEST_URL` in the same file points the update check at a release\nmanifest — leave the default to follow this repository, or aim it at a file\nserved from your own machine to test notifications. Release builds get the\nURL of whatever repository they are built from, written in by the workflow.\n\nSelect the **Opaline** scheme, choose your device or simulator, and build (⌘B).\n\n## Architecture\n\n```\nOpaline\u002F\n├── App\u002F              Composition root: AppDelegate, DI wiring, tab bar\n├── Core\u002F             Shared kernel (features depend on it, never on each other)\n│   ├── API\u002F          YouTube Innertube API client\n│   ├── Auth\u002F         OAuth device-code flow\n│   ├── Config\u002F       URLs, UserDefaults keys, constants\n│   ├── Transport\u002F    HTTP abstraction + decorators\n│   ├── Playback\u002F     VideoSource contracts, sources, HLS machinery\n│   ├── Services\u002F     Caching, SponsorBlock, RYD, subtitles, watchtime\n│   └── Common\u002F       Shared UI components & utilities\n└── Features\u002F         One vertical slice per feature\n    ├── Channel\u002F      Channel page with tabs\n    ├── Home\u002F         Home feed\n    ├── Library\u002F      Playlists & saved videos\n    ├── Player\u002F       Video player & watch page\n    ├── Profile\u002F      User profile\n    ├── Search\u002F       Search with suggestions\n    └── Subscriptions\u002F Subscription feed\n```\n\n### Key Design Decisions\n\n- **Zero external dependencies** — Networking via a custom `HTTPTransport` abstraction over `URLSession`, images via custom `ThumbnailImageView`, playback via `AVPlayer`\n- **All UIKit, no SwiftUI** — Programmatic layout, no storyboards\n- **iOS 12+ support** — No SF Symbols, no SwiftUI, no Combine\n- **Manual JSON parsing** — `JSONSerialization` + dictionary traversal for YouTube Innertube API responses\n- **Dependency injection** — `ServiceContainer` provides services; view controllers receive dependencies via initializers\n\n### Playback Pipeline\n\nPlayback is built on a single `VideoSource` abstraction — each way of playing a video implements the same interface and owns both stream resolution and quality selection. `PlaybackFacade` just asks a factory for the configured source, calls `loadPlayback`, and hands the prepared `AVPlayerItem` to the player shell. The sources:\n\n1. **Auto** *(default)* — Composite: tries Android VR first, transparently falls back to Mobile Web when a video fails to resolve or start.\n2. **Android VR** — Streams via YouTube's Innertube API; adaptive formats (360p–1080p AVC, up to 4K AV1 on supported hardware) are converted from DASH SIDX byte ranges into an HLS playlist for native `AVPlayer`, with progressive\u002Fnative-HLS fallbacks.\n3. **Mobile Web** — Handles videos the Android VR client refuses (e.g. kids content). Stream URLs require solving JavaScript challenges from the player page; that step is delegated to the helper server (see above), everything else stays on-device.\n4. **Progressive** — Direct 360p MP4 URL for the restricted case (e.g. server-side A\u002FB experiments).\n\nQuality selection is source-agnostic: the player UI simply renders whatever qualities the active source reports. Background audio is `AVAudioSession`-based and works across all sources.\n\n### Authentication\n\nOAuth device-code flow: the app requests a device code → user enters it at google.com\u002Fdevice → tokens are stored in Keychain. Anonymous browsing is supported.\n\n## Project Structure\n\n| Component | Purpose |\n|-----------|---------|\n| `InnertubeClient` | YouTube API: browse, search, player, comments, subscriptions |\n| `PlaybackFacade` | Selects a `VideoSource` via factory, loads it, and drives player setup |\n| `VideoPlayerView` | Custom player UI with controls, gestures, PiP |\n| `WatchViewController` | Watch page: player + metadata + comments + related |\n| `AppCache` | Dual-layer cache (memory + disk) with TTL |\n| `SponsorBlockController` | SponsorBlock API integration |\n| `ThemeManager` | App-wide theming (dark\u002Flight) |\n\n## Contributing\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feature\u002Fmy-feature`)\n3. Commit your changes (`git commit -am 'Add my feature'`)\n4. Push to the branch (`git push origin feature\u002Fmy-feature`)\n5. Open a Pull Request\n\nPlease follow the existing code style. SwiftLint is configured and runs as a build phase.\n\n\u003C\u002Fdetails>\n\n## Support\n\nIf Opaline keeps your old device alive, you can support development:\n\n\u003Ca href=\"https:\u002F\u002Fbuymeacoffee.com\u002Fverback2308\" target=\"_blank\" rel=\"noopener noreferrer\">\u003Cimg src=\"https:\u002F\u002Fcdn.buymeacoffee.com\u002Fbuttons\u002Fv2\u002Fdefault-violet.png\" alt=\"Buy me a coffee\" height=\"45\">\u003C\u002Fa>\n\n## Credits\n\n- [SponsorBlock](https:\u002F\u002Fgithub.com\u002Fajayyy\u002FSponsorBlock) — crowdsourced API for skipping sponsored segments\n- [Return YouTube Dislike](https:\u002F\u002Fgithub.com\u002FAnarios\u002Freturn-youtube-dislike) — community-maintained dislike count data\n- [yt-dlp](https:\u002F\u002Fgithub.com\u002Fyt-dlp\u002Fyt-dlp) — invaluable reference for understanding YouTube's playback infrastructure\n- [YouTubeLegacy](https:\u002F\u002Fgithub.com\u002FPoomSmart\u002FYouTubeLegacy) — inspiration for keeping YouTube alive on older devices\n\n## Legal\n\nThis project is for educational and personal use. It is not affiliated with, endorsed by, or connected to Google or YouTube. Use at your own risk.\n\n## License\n\n[GNU General Public License v3.0](LICENSE) — any derivative work must also be released under GPLv3 with its full source code.\n",2,"trending"]