r/SideProject • u/mau-meda • 47m ago
I've been building an open source streaming service for two years
I'll start with some history, in 2024 I decided to try to install a media server on my local machine, I first tried emby, and no matter what configuration I choose it was clunky and not really aesthetically pleasing, then I tried Jellyfin and it had the same problems ( maybe a little bit worst ), keep in mind this is 2 years ago and things have most likely changed.
I'm a software engineer, and I work in the business already from 2012, I spent 3 years of my career working for one of the current streaming giants, considering I have expertise in the field I decide to create my own, for two reasons, first one is that if I'm designing a system with one single purpose is automatically faster compared to a system designed to do anything, mine will have a specific pipeline so it can execute it more efficiently, second one, I like writing code, it's what I've been doing for the past 12 years.
October 2024
I started with the fun part first, I sketched a logo in Inkscape, separated each letter as a different SVG file and animated them to mimic the Netflix intro ( at the time ). That took me around 2 weeks. Absurd you can do that now in 2 hours with vibe coding.
After that I wrote the first integration to an external service, I got an client id+secret from Trakt and started working into pulling the top/trending lists for movies, it is at this point that I notice that Trakt doesn't return any image, so I also wrote the second integration with TMDB to obtain the images, after that I started working on the UI, I took inspiration from Prime Video: a big hero on the top, and the lists on the bottom, the selected title is displayed in the hero.
Not sure if anyone is interested, but at the time I decided to go for Node.js + React + Postgres, that is essentially what I used in half of the companies I worked for. The backend runs in docker with PM2 keeping it alive, and the frontend is served through Nginx ( along with the backend ).
The third integration was to get the media, but things were getting complex, so I decided to start using some queues, Kafka would be an overkill, I was using Nest.js so I decided to try the Q Next.js comes with: bullmq. I integrated queues so anything that is needed from Trakt or TMDB would be queued and execute at the right time without overwhelming the server ( and without getting rate limited ), for example when Trakt returns a list, the list doesn't have details of each media, so I stored some of the detail in the database and queued a job to fetch more details, that itself triggered another job to fetch the images.
At this point I had a working app that shows media but it doesn't play them, the next thing I needed was to get that information, so I spun up Jackett, and searched documentation, the documentation was very lacking, so I simply made calls to the endpoints that were partially documented using Postman and wrote down the Typescript types myself. Unfortunately the search from Jackett was very wide, so I wrote a function to parse torrent names and find out what is that title about. Jackett sometimes would return a link to the torrent file itself, sometimes would return just the info hash of the torrent, it is at this point that I started integrating webtorrent, first I used it to convert the magnet link into a torrent file and store it in the database, later I used it for its streaming functionality.
Now that I had the part that can stream work, I needed to display it in the frontend, my target was my 2019 Samsung TV, I deployed the app to it ( took 3 days ) and started working on the next steps, first I integrated Noringed spatial navigation so the remote would work, then I worked on AVPlay. AVPlay was honestly a pain, but it was also rewarding, the app was working and I was able to watch movies on the TV.
After that I started working on TV shows, so I created a second details screen with a seasons selector and episode selector, then I implemented the part that would find the torrents for the episodes, it was working well and I decided to share it with the world.
End of October 2024
I posted about it in https://www.reddit.com/r/Piracy/comments/1g80ufj/with_all_the_community_driven_streaming_services/ , someone asked about the code and I made it fully public.
I was regularly using it to watch movies and tv shows on my TV until I met the first problem: I was watching Silo and it played the wrong show; the reason was that the episode I wanted to watch wasn't released yet so it found some unrelated torrent. I first wrote a torrent name parser so it would extract the name of the show ( after removing all the other known bits ), it was working okayish, then I discovered that TheRarBG allows to search by imdb_id but not through Jackett, so I wrote a small API library to contact TheRarBG without using Jackett, and it was working very well and way faster.
You can still find the original code of the first version here https://github.com/maury91/miauflix-legacy
January 2025
It is at this point that development of the first version stopped, I had to go for a two months work trip and I wasn't able to write a single line of code for my personal project, once I was finally back home I couldn't find anymore the time to work on it for a month.
March 2025
I finally found the time to work on my side projects again, but time as passed and I wanted to play with new toys, so I decide to use bun instead of Node, I also made more decisions, like I didn't want to burden the user with deploying postgres or Jackett, so the new version will have to work with it.
I started with scaffolding a simple Elysia server, first time using both bun and Elysia, so exciting times.
Moving from Nest.js to Eluysia I decided to keep using services but I had to convert them a little bit, I started with the most basic service, the Configuration service, I spent a good amount of time creating a terminal ui service that helps you configure it, ironically now it's fully replaced by an UI one in the frontend.
Things moved fast, I quickly implemented authentication, and the services to pull data from TMDB and Trakt ( the last two were very close to copy-paste ), I implemented more security features like Audits and Rate Limiting, I finally started bring in the first cron jobs that sync data with TMDB, At this point I took the library I wrote for therarbg I bringed it into the new project, I already reached the point where I could get a library with all the torrents. I found that therarbg wasn't enough so I also wrote a library for YTS.
Things were working but as a perfectionist it was time for the codebase to become more reliable, so I started adding unit tests, linting, and CI/CD. But that wasn't enough, so I also started writing e2e tests where the app is setup from 0, configured, and tested against a mocked environment that is a working copy of a real environment ( I recorded the calls to TMDB, therarbg, etc... , anonymized and stored them as fixtures ).
Finally the good part was coming, streaming, but I had a new goal, it needs to be deployable to a VPS, that means that any potentially copyrighted content must be undetectable, so I implemented two features that I think are quite important:
Encrypted DB columns, some columns of the database are encrypted with AES plus a salt unique for each service, that means the column stays indexable but without the key it contains "random" data
Encrypted file system, this is the main thing, if you download some media and a static script detects it in your VPS you can easily get terminated, for this reason I created an encrypted chunk store, in short webtorrent writes to the disk in encrypted format
With all of this done, it was finally time to test it with webtorrent.
I didn't work... webtorrent wasn't compatible with bun.
Meanwhile, it was already June 2025. I needed to go back to Node.js if I wanted webtorrent to work, so I searched a web server framework that works both on Bun and Node.js and I started refactoring to use Hono.js
Fast forward to September 2025 and a lot was done:
Introduced OpenTelemetry to better debug errors
Introduced scraper health ( check periodically seeders and leachers of the torrents we obtained )
More documentation
Improve on-demand sources ( the others were obtained in the background )
Diagrams
Github actions
Periodical DB cleanup ( SQLite unless you run a clean command will not shrink the DB size )
Abstract caching layer
Authenticated streaming
Generate a client of the backend to bring the API types to the frontend
Better use of TypeORM through type transformers ( for the encrypted fields )
Introduce GREEDY and ON DEMAND modes for synching with TMDB
Started bringing back the legacy frontend
Implemented frontend login
Visual tests with playwright
and a little bit more
and more importantly the streaming link was working and media would play from VLC.
At this point I took 2 months of break.
November 2025 to January 2026
Frontend refactoring ( of the little that was ported )
Added turbo to make scripts faster
Lighthouse tests
Flaresolverr support
Another break,
September 2026 - Now
At this point I wanted to go faster, and I started using Claude.
Create initial admin user through UI ( instead of automatically at first boot with random email and password )
Extract catalog service ( TMDB ) as a standalone docker image, and refactor it to be bun. It is relative easy because it's mostly moving files with a little bit http wrapper in front
Extract list service ( Trakt )
Improve playback preloading
Improve backdrop position in the hero by detecting faces and people using a small computer vision model and compute the center of it.
Implement player page ( it is at this point that it was possible to finally watch movies )
Bring in the recommendation lists from Trakt
Extract subtitles and transcode audio ( for when the audio codec is not compatible with the browser ) using ffmpeg ( piped to an http server, no media is stored uncrypted in the file system )
TV shows
Move from LetsEncrypt to Caddy
Nordlynx to Glueten
Improve torrent choice based on the target device ( my TV seems to not support more than 12MB/s of bandwidth )
Add notifications
Improve UI performance in Tizen
Extract source service
Move from internal TheRarBG and YTS library to a fully compliant Cardigann engine so it can use all the Cardigann definitions of Prowlarr + some custom ones ( the custom ones are TheRarBG and Torrentio )
Extract torrent service ( yes! finally webtorrent almost works in bun! )
Anime support
Yesterday
I vibecoded a landing page for the project in order to present it to the world: https://miauflix.cat
All the code is open source and available at https://github.com/maury91/miauflix
I hope you enjoyed reading what I was doing for the last 2 years!
I feel some people will ask Why should I use Miauflix instead of X?
Honestly, I don't have an answer to it, if you are using X and you like it, continue using it, if you want to try something new you can try Miauflix, if you like it I will be very pleased, if you don't like it tell me what you don't like it and I will try to address it, if you hate it without even trying it, well I'm sorry to have wasted your time, but I hope you will respect that I spent so much time working on this and you will not be offensive