mirror of
https://github.com/steredbits/pelagia.git
synced 2026-10-17 17:01:33 +02:00
5.6 KiB
5.6 KiB
Architecture
Pelagia is a native Jellyfin client for jailbroken PS5 consoles, written in C++17. The goal is a simple, reliable player; the PS5-specific layer is kept as thin as possible and everything else is portable code that is developed and tested on Linux.
Layout
core/ portable code (C++17), no platform dependency
api/ Jellyfin REST client (libcurl + cJSON): authentication, libraries, items, stream URLs,
audio/subtitle tracks (model, default choice, labels)
player/ playback pipeline: ffmpeg demux/decode, frame queues, A/V sync, network source
ui/ screens and navigation, driven by abstract input events; track menus, text subtitles
util/ logs, config, image cache, track languages, SRT subtitles, version, app identity,
interface localization (i18n.h, catalogs in util/i18n/strings_{en,fr}.def)
platform/
platform.h abstract interface: window/rendering, audio, input, network, files, time
sdl/ rendering, audio, input with SDL2, shared by Linux and PS5
posix/ files (POSIX), shared by Linux and PS5
linux/ Linux specifics (XDG folders); development and tests
ps5/ PS5 specifics (folders, logs, fullscreen)
tests/ unit and integration tests (ctest); mocked API, JSON parsing, queues, UI navigation
tools/ fake Jellyfin server, release and maintenance scripts
assets/ third_party/ embedded fonts (Noto Sans, OFL); stb_truetype, cJSON
pkg/ ci/ cmake/ packaging resources, build scripts
docs/screenshots/ screenshots of the interface (generated headlessly)
Rule: core/ never includes a header from platform/sdl, platform/linux or platform/ps5, only
platform/platform.h. The platform layer may use util/log.
Technical choices
- C++17 without exceptions or RTTI in the core (portability to the PS5 toolchain), built with CMake for two
targets:
-DPLATFORM=linuxand-DPLATFORM=ps5(cross-compilation with the open-source ps5-payload-dev SDK and PacBrew libraries; no Sony SDK or leaked header is ever used). - Dependencies: libcurl, cJSON (vendored), ffmpeg (libavformat/libavcodec/libswscale/libswresample), SDL2.
- Video: software decoding. The server is asked for H.264 1080p + AAC stereo through the stream parameters, so only one pair of codecs needs to be handled; direct streaming is used when the server can.
- Rendering: SDL software renderer with an IYUV texture, linear scaling, letterboxing. The UI is drawn with a small canvas interface (SDL on Linux/PS5, a CPU rasteriser for screenshots and tests).
- Input: gamepad events are mapped to abstract events (
Up,Down,Ok,Back,PlayPause, seek…). - The UI never blocks (network, disk, decoding, opening a stream): work goes through worker threads and results are applied on the main thread.
On the console
The app is a websrv homebrew: eboot.elf +
sce_sys/icon0.png in /data/homebrew/Pelagia/ or in /mnt/usbN/homebrew/Pelagia/. Session, poster cache
and logs are always under /data/homebrew/Pelagia/ (platform/ps5/ps5_paths.h); pelagia.conf is only
read, from the USB stick first, then from /data. Logs go to stdout and to logs/pelagia.log.
Jellyfin API notes
- Authentication: the general API only accepts the
Authorization: MediaBrowser …, Token="…"header. The stream accepts that header too, and the client uses it alone by default (--stream-auth header) so the token appears in no URL.queryandbothremain available as fallbacks; the client never switches on its own. - Seeking is not possible inside the progressive stream (
Accept-Ranges: none): the stream is reopened withstartTimeTicksand a freshplaySessionId, afterDELETE /Videos/ActiveEncodingsfor the previous session (otherwise the server reuses the job and ignores the new position). - Direct streaming: depending on the file the server copies the video and only transcodes the audio; the first audio packet of the MPEG-TS can arrive after several MB of video, so the probe goes up to 32 MB or 10 s, and the player never stays stuck (clock locked to the video after 3 s without audio, give up after 20 s with nothing readable).
- Tracks: the server computes
DefaultAudioStreamIndex/DefaultSubtitleStreamIndexper media source for the user profile and the client follows them (local fallback:api/track_selection). The stream URL carriesaudioStreamIndexand, only for burned-in subtitles,subtitleStreamIndex+subtitleMethod=Encode. Text subtitles are downloaded separately as SRT and drawn by the client, so the stream is unchanged. The server only remembers tracks fromProgressreports. - Logs never contain the token:
util/logmasks registered secrets and theapi_key=/Token="…"patterns. Do not write to stderr/stdout around it, and let ffmpeg logs go throughplayer/av_log_bridge.
Testing
ctest --test-dir build-linux --output-on-failure: unit tests plus end-to-end tests againsttools/fake_jellyfin_server.py(a small Jellyfin imitation with a media test pattern)../ci/build-sanitize.sh: Debug build with libstdc++ assertions, ASan and UBSan, all tests (also a CI job).tools/update_screenshots.sh: regeneratesdocs/screenshotsheadlessly after a visible UI change.tools/scan-secrets.sh: gitleaks scan (CI job).- PS5 builds can only be checked by cross-compiling (
ci/build-ps5.sh); behaviour on the console can only be checked on a console, so keep PS5-specific code minimal and log what it does.