This is an unofficial 10-foot UI for TV4 Play, a Swedish commercial broadcaster. The UI is designed specifically for jailbroken PS5 and is built along the same lines as the SVT Play UI.
The majority of the UI has been produced by claude.ai.
Note
Signing in is optional and manual — see Signing in. Without an account only content TV4 serves anonymously is listed and plays; cards that need a subscription carry a Premium badge.
Note
Streams provided by TV4 Play are generally geo-blocked for devices without a Swedish IP address.
| Button | Browsing | Playback |
|---|---|---|
| D-pad | Move; Left at the edge exits to the menu | Seek ±10 s / ±5 min |
| Cross | Open | Play / pause |
| Circle | Back | Stop |
| Square | — | Cycle subtitles |
| Triangle | Reload the current view | Cycle audio tracks |
Moving down the menu opens each view as the cursor settles on it; Right or Cross hands the cursor over to the listing.
The television's own remote works as well as the pad. The browser turns HDMI-CEC presses into key events of their own, and they are handled alongside the pad buttons:
| Key | Browsing | Playback |
|---|---|---|
| Top menu | To the menu | Stop, then to the menu |
| Guide | Open Kanaler | Leave playback for Kanaler |
| Channel Up / Down | Move a screenful; in the menu, an item | Next / previous in the list |
| Track Next / Previous | — | Next / previous in the list |
| Play / Pause | — | Play / pause |
| Stop | — | Stop |
| Rewind / Fast forward | — | Seek ±30 s |
| Subtitle | — | Cycle subtitles |
| Info (context) | — | Show / hide the OSD |
| Red | Reload the current view | Restart the stream |
| Green | Open | Play / pause |
| Yellow | — | Cycle subtitles |
| Blue | — | Cycle audio tracks |
Red and Green do what Triangle and Cross do on the pad, so nothing is lost by putting the pad down. "The list" is whatever the current item was started from — the rest of a season, or the other channels in Kanaler — with folders skipped over rather than opened.
A held key repeats only where the action is a small step: the D-pad, Rewind and Fast forward. Channel Up held down would otherwise change programme a dozen times, each one a fresh stream.
Assuming you have elfldr.elf running on your PS5, a launcher can be installed by running the install payload on your PS5. A TV4 Play icon will then appear on the Media tab on your PS5 dashboard.
The payload embeds everything in webapp/ and writes it to
/user/app/BREW10004/, alongside the launcher metadata. webAppUri in
sce_sys/param.json then points the browser at that directory, served locally
by websrv. The title id in param.json and in the Makefile must
match, and must be unique among the apps installed on the console.
Note
Once the installation is completed, the application can be launched without having to jailbreak the console after a reboot.
Serving the UI from the console rather than from a remote host is not only a convenience — see Why it is served locally.
sce_sys/icon0.png and sce_sys/pic1.png are plain generated placeholders so
that make works out of the box. Replace them with artwork of your own.
webapp/tv4.js talks to TV4's client gateway, an ordinary GraphQL endpoint
that accepts the whole query document in a POST body. That is a good deal less
brittle than SVT's persisted queries, which stop answering the moment a hash is
retired: nothing here breaks when the website ships a new build, as long as the
schema holds.
Playback goes through a separate service that trades a media id for a manifest URL. Unencrypted titles are played as HLS by WebKit natively, which is the path the console decodes in hardware; hls.js covers the case where the audio sits in renditions of its own, which WebKit loads no sound for. Protected titles take a second path — see DRM.
The query documents, the panel ids and the playback URL were all taken from the
Retrospect Kodi addon (channels/channel.se/tv4se/), which is
GPLv3 like this repository.
Two kinds of value in tv4.js are TV4's rather than the schema's, and they are
the ones that rot:
START_ROWS— the opaque ids of the three curated rows on the start page.LIVE_PAGE_IDS— candidates for the page behind tv4play.se/live, tried in order until one answers. The winner is remembered inlocalStorage; move a confirmed id to the front of the list once it is known.
When a row turns up empty, open tv4play.se with the browser's network tab
recording, find the Panel or Page request behind the row you want, and copy
the id out of the request body.
TV4 has no sign-in this UI can drive: the website's own form sits behind bot protection, which is why the Kodi addon abandoned username and password too. What is left is the credential your ordinary browser already holds once you have signed in on tv4play.se — the refresh token cookie — pasted in once under Konto.
That token is long lived and grants full access to the account. It is kept in
the console's localStorage and is sent nowhere except to TV4's own auth
service, which trades it for a short lived access token; only the latter goes
out with ordinary calls. Anyone else who uses the console can watch on your
account, so Logga ut is there for a reason.
Signing in changes two things:
- Listings get complete. The gateway trims
allSeasonLinksfor anonymous callers, and omits page panels an account cannot reach — so season lists and category pages are thinner than the website's until you sign in. - Protected titles become possible. See DRM.
For development, copy webapp/token.js.example to webapp/token.js and put
your token in it; it is used whenever nothing has been pasted in under Konto.
That filename is gitignored, and main.c does not embed it, so it only applies
to a copy of webapp/ served from somewhere else. The account screen says when
a development token is the one in use, and Logga ut is hidden then, since
there is nothing stored for it to remove — empty the file instead.
auth.tv4.a2d.tv varies on Origin and echoes Access-Control-Allow-Origin
back only for origins it recognises, localhost among them. The gateway and
playback services answer any origin, so browsing and free playback work from
anywhere — but the sign-in exchange does not. Hosted on a LAN address or a
public URL, the response arrives and the browser discards it before this code
can read it, which surfaces as a failed sign-in with a working token.
Serving the UI from the console over localhost puts it on the recognised
side of that check. It is the reason webAppUri points at a local path
instead of a hosted one, and it is not something a client can work around
otherwise: the origin is set by the browser and cannot be spoofed from script.
Konto has a Testa inloggningen action. Use it first: storing a token and being able to trade it for an access token are different things, and a failed exchange otherwise surfaces much later as a 401 from the playback service. The account screen also shows the last sign-in error, with TV4's own message.
Things that have gone wrong with a copied cookie value:
- Truncation. Devtools tables abbreviate long values. Use the context menu's Copy Value rather than selecting the text shown in the cell.
- Percent-encoding or quotes around the value. Both are undone automatically, since a JWT contains neither.
- The wrong cookie. The value must have three dot-separated parts. If the account screen cannot read an expiry date from it, it is not the token.
The console reports PlayReady through EME, and neither Widevine nor FairPlay. That is the reverse of what a desktop browser offers, and it is why there are two playback paths here:
- Plain HLS is asked for first. Anything unencrypted comes back that way and is played natively.
- If the title turns out to be protected, and only then, the manifest is
requested again as
protocol=dash&drm=playreadyand handed to Shaka Player. The console's own PlayReady module does the decryption; the licence comes from TV4's castlabs service in the ordinary way, with the challenge as the request body and the token from the playback response in anx-dt-auth-tokenheader. That is the same exchange the official player performs — nothing is worked around.
Whether TV4 will actually issue a PlayReady licence to a browser client is the one thing here that could not be established without trying it: their web player asks for Widevine because it expects Chrome. If the service refuses, protected titles fail with a Shaka 6xxx error and the free catalogue is unaffected.
- Search. The gateway has a
PanelSearchoperation, but typing a query on a DualSense is miserable even with the system keyboard. - Ads. TV4 stitches advertising into its manifests server-side. It plays as part of the stream; nothing here separates it out.
If you encounter problems with this unofficial TV4 Play UI, please file a github issue. If you plan on sending pull requests which affect more than a few lines of code, please file an issue before you start to work on your changes. This will allow us to discuss the solution properly before you commit time and effort.