Skip to content

Latest commit

 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TV4 Play

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.

Controls

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.

Installing

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.

How it works

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.

Things that will need touching up

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 in localStorage; 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.

Signing in

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 allSeasonLinks for 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.

Why it is served locally

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.

When sign-in does not take

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.

DRM

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:

  1. Plain HLS is asked for first. Anything unencrypted comes back that way and is played natively.
  2. If the title turns out to be protected, and only then, the manifest is requested again as protocol=dash&drm=playready and 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 an x-dt-auth-token header. 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.

Not implemented

  • Search. The gateway has a PanelSearch operation, 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.

Reporting Issues

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.

License

GPLv3+

About

An unofficial 10-foot webapp UI for tv4play.se

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages