Over several evenings in the past week or two I’ve been trying to get a Formula1 TV app running on my ancient LG television from 2018. Todays race-day finally saw me able to play video and watch the (last part, it was a work in progress) of the race! This is how and why I built my own F1TV app.
Can’t you use F1’s own TV-app?
Hah, that’s where you’re immediately wrong, my dear reader. Because sadly Formula1 has decided to deem televisions from LG (as well as some others like Samsung’s) not deserving of an app (yet). Instead, they refer to using casting options like Airplay, which the majority of newer TVs from LG do actually support. Sadly though, my LG is already 4 years old (which really isn’t that old for a TV!), and Airplay to the TV doesn’t work properly with Formula1’s streams. Officially Airplay is even only supported for casting to Apple TV.
It left me unable to watch F1 on my nice OLED TV, which is honestly a shame. And if I’m paying for a subscription, I damn sure want to be able to watch it on the big screen without jumping through a lot of hoops. So, for that reason I decided to jump through probably the biggest hoop possible, and built an app that works just fine on my ancient TV.
What does it look like?
A picture paints a thousand words, and a video even more. So here’s a video of a portion of the app I’ve built.
Using the right tech for the job is always a must. So here is the tech I’ve used:
Ever since I started playing with Lightning more and more, it has become apparent that this framework really is the best way to rapidly get started and have a base application ready. It’s very easy to style and configure most of your UI, and support for most TVs works roughly out of the box. It’s definitely my current open source TV-app development framework of preference.
API, Stream & Image Proxy: PHP
Something I’ll explain a bit later in the ‘problems and issues’ section, is why this is even required. But in order to be able to access the data that the app needs, I’ve used some PHP scripts that act as a proxy. This means being able to send and retrieve data from various F1 API endpoints, as well as doing some manipulation in between the connections.
Funny enough LG WebOS isn’t officially supported by Shakaplayer. It however does the job more than fine, especially with the customisation options available to control how playback is started and how network requests are handled.
Problems and issues I came across
Building the frontend is honestly the least problematic in most of the apps I’ve worked on. Especially here where I can basically make any design I want. I did want to make it look like the F1 branding, so I’ve used the colours, fonts and general style that’s also used on the website and other apps from F1.
Problem one: accessing F1 APIs
Now, onto the hard parts of all of this. Let’s start with the first: accessing the F1TV data in places that are not owned by the F1. What I mean with that is fairly simple: F1 doesn’t want any third parties accessing the data that’s meant for the F1 website and apps. And it does so by leveraging most if not all of the browser-based access-management features out there.
From having CORS applied on both API and Image CDN, to using required cookies, most of the things you can imagine to make a website more secure are definitely there. And for good reason! F1 should protect their brand and of course also abide with all the cookie laws out there. But that definitely makes my job a lot harder, since the TV-app also runs in a browser-based environment.
The solution? Proxy everything! Since I can control the CORS headers on my own server, I can overcome those issues. So on a VPS that I use as sort of a playground, I have some PHP scripts running that proxy all of the requests to and from the F1 API and image-server. My application simply sends the proxy the required URLs, together with the required headers, and gets back the data from the F1 API.
Problem two: logging in require complex cookies
A problem I honestly didn’t overcome yet, is the cookies the API requires in order to login. There is a certain ‘reese84’ privacy cookie that’s required before the login can work. Now, I could definitely try and work around that by making the server/proxy a bit smarter, but honestly I really just wanted to watch the race today. So for the time being I’m required to login on the regular website, which allows me to retrieve the temporary headers you get from the API after logging in. Those headers can then be used throughout the rest of the application when requests need to be authenticated.
Problem three: streams…
As usual, streams are usually the problem. It doesn’t matter which application or device, there will always be an issue with video. And in this case that was definitely true, too.
The F1TV API gives back various different types of stream, all of which we’ll have to handle in the app:
- MPEG-Dash (Clear)
- MPEG-Dash (Widevine)
With all of these formats I came across different issues, some bigger some smaller. The biggest of the bunch was due to an issue that only happened on my TV, it worked fine in my browser implementation.
Streams from F1 have some authentication methods attached, in order to prevent them from getting watched by others. DRM being one of them, but honestly handling that was a piece of cake: proxy the request and done. The other authentication mechanism makes use of a cookie that’s being sent when the browser downloads the manifest. This cookie is subsequently used in any segment request in order to validate that the user is actually allowed to retrieve the data. It so happens that my LG from 2018 doesn’t support this functionality. The cookie simply… disappears.
My solution here is yet again having to proxy the requests, do some manipulation, and send it back to the device. Since the cookie needs to be stored somehow, and I didn’t make the server too smart, it’s now in a custom header that’s stored on the frontend and then shared with the proxy for segment requests. The proxy then sets it as an actual cookie and badabing badaboom, playback is working correctly.
And last, there were some other issues with HLS and unsupported formats, but luckily that was easily solved by transmuxing the stream on the spot. Nothing that couldn’t be solved.
I haven’t implemented many features like being able to watch different drivers, login (duh) or simply having a detail page (although I much more prefer to have immediate playback). And then there are the bugs and loading speed which I’m not happy with (proxy-ception). So there is definitely some work that needs to happen before you can call this a ‘production-ready’ app. But, overall, for now I can watch on the TV without too much hassle. Which is a major improvement over needing an external device to even watch.
In the end though, this app will likely not see the light of day in a real appstore from LG. Unless Liberty wants to work together to make that happen (I’d be happy to!), I think there are too many legal issues to deal with to get this available. There are certain apps that can be sideloaded for AndroidTV and other platforms, that have similar F1TV implementations. One being ‘RaceControl’, which is available on Github, and currently not actively being blocked by F1, despite being available for quite some time already. That might be an approach I could take.
So if you have any suggestions, or entry ways into getting into talks with Liberty, happy to hear from you! ;) Until then I’ll work silently to make my own watching experience ever so slightly better.