The existing landscape of streaming services often falls short in terms of providing a seamless and lightning-fast user experience. Users face frustratingly long loading times and buffering issues, resulting in a diminished quality of content consumption. Additionally, traditional content delivery methods often rely on centralised servers, which can lead to network congestion and latency.
All streaming platforms are proprietary. We need an open-source platform streaming platform that's provider agnostic, and serves as a base layer for being fast and dynamic.
So I set out on creating the system with Next 13 (App Dir), Auth.js, TailwindCSS, Edgio and TVMaze.
Code
https://github.com/rishi-raj-jain/video-player
Demo
Key Highlights
I'll be highlighting the technical positives of the application that enables it to serve as a base layer for being fast and dynamic 👇🏻
1. A Media Player powered with Next.js 13's Intercepting Routes, Middleware, and Auth
- Next.js introduced Intercepting Routes in their app dir (i.e. Next.js 13 version and beyond). This allows to take over a client side navigation when navigating from one route to another, and by "intercepting" the navigation, one can serve different view from when the url is being accessed directly. In the app, when someone clicks on a Show's card to open it, we intercept the route and show the preview of the Show with information such as poster image, name, cast, location, etc.
Using Next Middleware, the app is able to add a personalised
ott-pathname
header that is used to populate the application's SEO dynamically for each page. Take a look at the general generateMetaDeta function here.Enabling Login Authentication with Next.js has became a child's work, thanks to Auth.js. But there was a catch, I wanted to cache the pages, and to do that I couldn't always server side render the page. Hence, I used API Routes by Next.js to retrieve session on the client side, and differentiate the client side layer from the server side for authentication. So every page is cached, but the authentication state is dynamic based on /api/session's response. Creating a custom authentication page was a matter of 30 lines
2. Lazy Loading Show Card
- Imagine a total of thousands of Shows. It'd take a user to scroll a lot, and go back and forth between the sliders to view all of them. So why load the card images before they are required? Using the onScreen hook, one is able to detect if that card is in view, and iff load it's image.
- Whenever a Show's card came into the view (detected with onScreen hook), the service worker is used to prefetch the API responses that populate Show's cast, poster image, title, credits, etc. This ensures that the navigation is heck fast.
3. Prefetching
With the use of Edgio's Service Worker, one is able to prefetch only what's cached on the origin server. This allows to stay dynamic, and load content before the user requests it. To make sure that prefetching doesn't intervene with the main thread, it's done in a service worker. In the application, Show's preview information, images, and video, and any Page's CSS, Image and JS files are prefetched. Prefetches stay in the browser for the configured time, resulting in about 1ms response times to serve the content, providing heck of a fast experience.
4. Caching
While the whole app is serverless, how can one consider speeding it up? Caching. The app ensures that it caches everything as iff possible, such as responses to the Show's information that's likely not gonna change over time, image, CSS and JS files that come with their own unique hashes if changed, Next.js Image Optimisation images that cost a lot at scale, and server side responses for intercepting routes because they are as less likely to change as Show's preview information. Even though it might sound like I just cached everything for eternity, the granular control with Edgio's caching helped me to cache separately for browser and edge, allotting time periods for how long a cache stays on the edge, after how long it shall revalidate itself on the Caching Shield and propagate, and how long shall the request remain cached in the browser. This ensures that the caching is configured according to the expectation from the platform, resulting in a fast navigation with fast responses.