OPENTV
A TV and film tracker for people leaving TV Time. It imports your whole export and rebuilds your history on the phone, and the database on the phone is the source of truth: it works offline, needs no account, and nothing about what you watched is sent anywhere unless you ask for it — by joining the optional community, or by switching on a backup.


- MY ROLEEverything — design, build, test, release, support.
- PLATFORMSiOS and Android, native, with home-screen widgets on both.
- STACKReact Native (Expo), TypeScript, SQLite on device; TheTVDB with TMDB as fallback; a Cloudflare Worker for the optional community.
- SHIPPEDFirst Play release 13 Jul 2026; both stores from late July; 1.6.3 on 21 Sep 2026.
- USERS300+ — my own count as of August 2026, not an independent figure.
- LICENCEMPL-2.0 app, AGPL-3.0 community server. Both public.
01 / THE PROBLEMYour history, held by someone else
TV Time was where a lot of people kept a decade of what they had watched — every episode, with dates. When leaving it, you could ask for your data and get a GDPR export back: a ZIP nobody else could open into anything useful. The history was yours on paper and nowhere in practice.
I wanted a tracker where that could not happen again. So the rule I set on day one was not a feature, it was a constraint: the app must be complete without a server. Import the export, rebuild the history locally, keep it in a database on the phone, and export it back out at any time in TV Time's own format so nobody is ever locked in a second time.
That constraint decided almost everything that followed.
02 / HOW IT WORKSImport, track, and keep it
You start by importing — the official TV Time export, or the community browser-extension exports — and OpenTV rebuilds your shows, episodes and watch dates into SQLite on the device. From there it is a tracker: what to watch next, every episode checked off, films added in a tap, stats over your own library. Metadata comes from TheTVDB, with TMDB filling in the few things it does not carry.
The community — comments, ratings, follows, shared lists — is a separate, optional layer behind its own server, and nothing reaches it unless you join. Your episode-by-episode history is not part of what it carries either way.
Two other things can leave the phone, and only because you switched them on: a backup, to Google Drive, to OpenTV's server or to a server you run yourself; and crash reports, which arrived in 1.6.3. The default remains a tracker that talks to nobody.

Welcome: the open-source home for your TV Time. 
A film's page, with a single control to mark it watched. 
The feed: discovery, and the community tabs for people who opt in. 
A profile: stats over your own library, and the community counts if you joined. 
Comments on a title — the part that needs an account, and says so.
03 / THE DECISIONLocal-first, and what it costs
Making the phone's database the source of truth is the decision the whole product hangs on. It is what lets the app work with no account, offline, and with no way for anybody — including me — to fetch a library off a phone. There is no route for it, no flag, no support path.
That is the point, and it has a price I chose to pay:
- Bug reports are reproduced from a description. I cannot pull someone's data to see what went wrong. Every import bug has been found by reading the report, building the same shape of library, and running it.
- Backup and sync had to be built as their own features, rather than falling out of a server. They exist now — Google Drive backup shipped in 1.4.1, cloud backup to OpenTV's server or to a server you run yourself in 1.6.3 — and each one had to keep the same rule: the copy that crosses is the one you asked for.
- The optional community keeps drifting toward being required, because a backup needs a token and the only token the server issues is a community one. The 1.6.4 work separates signing in from joining, and makes a new profile private by default. I wrote that contradiction down in the changelog rather than hoping nobody noticed.
"There is no way for anybody, including us, to fetch your library off your phone. Not a route, not a flag, not a support path. Bug reports get reproduced from a description, like every other bug in this project."— the project README
04 / THE HARD PARTTwo different ideas of what episode 12 is
The first releases built episode lists from TMDB. TV Time, and therefore every export, numbers episodes the way TheTVDB does. For most shows the two agree. For anime they do not: TMDB files Detective Conan as one 1,208-episode season and Jujutsu Kaisen as one season of 59, where TheTVDB — and your export — has 34 seasons and 3. So anime imported with collapsed seasons, and watches landed on the wrong episodes. The app also carried code that shuffled watch rows around to fit TMDB's layout, which was the one place in the whole system that could quietly drop an episode rating.
The fix in 1.2.0 (30 July 2026) was to change the database the app runs on: seasons, episode numbers, titles and air dates now come from TheTVDB, matching what people saw in TV Time, with TMDB called only for the three things TheTVDB has no equivalent for. Existing libraries were repaired automatically on update behind a progress screen — nothing to re-import. I checked the result against a real export before shipping: 388 of 389 watch rows lined up exactly. A dead API key cannot break a library either: everything already stored stays, and only new imports fall back, clearly labelled.
A later one, from 1.6.3, is worth a line because it is the same lesson from the other side. An episode you un-tick is remembered as a tombstone so that re-importing your own export cannot resurrect it. A cloud restore is not a re-import, and a tombstone can be left by another of your own devices — so one restore dropped 215 episodes from a show and reported "verdict":"ok". Restores clear tombstones first now; imports still honour them. Both fixes are in the public changelog, with the numbers.
05 / KEEPING IT HONESTHow it is tested
- 1,146 Jest tests,
tsc --noEmitkept clean, and lint, on every change. - A release checklist that is a file in the repo, not a memory.
- Six languages, including Arabic with right-to-left layout and all six CLDR plural categories — the translation key types are generated, so a missing string fails the build rather than the user.
- Long lists are
FlatList, never a mappedScrollView. A 1,207-episode library taught that one. - The React Compiler is on, which means a counter meant to force a re-read of SQLite can be memoised away silently. I verify that class of change in the compiled bundle, not the source.
06 / WHAT SHIPPEDTen weeks of shipping
| WHEN | VERSION | WHAT LANDED |
|---|---|---|
| 13 Jul 2026 | 1.1.0 | First release on Google Play |
| 30 Jul 2026 | 1.2.0 | Both stores: TheTVDB becomes the source of episodes, lists, sharing, iPad |
| 7 Aug 2026 | 1.2.1 | Six languages |
| 11 Aug 2026 | 1.3.0 | The optional community layer |
| 14–18 Aug 2026 | 1.4.x | Wrapped, filters, private accounts, Google Drive backup, widgets |
| 30 Aug – 2 Sep 2026 | 1.5.0 – 1.6.1 | Shared lists, OpenTV Plus, the light theme, Plex, the films TV Time left out |
| 21 Sep 2026 | 1.6.3 | Siri, episode ratings, cloud backup to OpenTV or your own server, Trakt and Simkl imports, crash reports |
Dates are from the project's changelog, which is reconstructed from git and the store consoles; the rows above are the ones that reached both stores, so the changelog also lists versions that were submitted, folded into a later release or never shipped. The user count is my own, as written in my CV in August 2026; I have not had it verified by anyone else.
OpenTV also has a paid tier, OpenTV Plus, for the on-device extras — advanced filters, the activity heatmap, the watch timeline, deep stats. Wrapped is free for everybody on purpose: it is the one screen built to leave the app.
07 / WHAT I'D DO NEXTThe next version, honestly
The next release separates an account from a profile: sign in for a backup without becoming a public handle, and new profiles private by default. That is the sharpest contradiction still in the product, and the person most likely to want a backup — the privacy-minded one — is the person it currently serves worst.
Two more I want: a way to answer the one person who left a one-star review saying the import failed, without breaking the privacy policy; and moving the hand-built iOS widget extension into a config plugin, so iOS can be built in the cloud like Android already is.
building things I'd want to use, and keeping them.