Constraining both width AND height caused GStreamer to stretch the video to fill the target box, distorting the aspect ratio when the box was not the same AR as the source (e.g. 720x600 vs a 16:9 source). Fix: only constrain width in the capsfilter (height=(int)[2,2160]). GStreamer then picks the height from the source's native DAR, naturally preserving aspect ratio without relying on add-borders. _fit_frame_to_viewport centres the resulting frame in the SDL viewport.
R36S DLNA Browser
A lightweight DLNA media browser for the R36S handheld, built with Python, SDL2, and GStreamer.
Author: Matteo Benedetto
Features
- Discover DLNA/UPnP media servers on local LAN via SSDP
- Browse media libraries hierarchically with D-pad navigation
- Play audio and video locally via integrated GStreamer playback inside the SDL window
- Optimised for 640×480 screen and low-power RK3326 hardware
System Prerequisites
Install these system packages before running (Ubuntu/Debian-based):
sudo apt install libsdl2-2.0-0 libsdl2-ttf-2.0-0 python3 python3-pip python3-gi gir1.2-gstreamer-1.0 gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good
On Fedora the equivalent runtime packages are:
sudo dnf install SDL2_ttf python3-gobject gstreamer1 gstreamer1-plugins-base gstreamer1-plugins-good python3 python3-pip
The current playback path is SDL-only: GStreamer decodes into frames and the app renders them inside the SDL renderer. This avoids any dependency on X11, Wayland embedding, or a window manager and is aligned with DRM/KMS-only targets.
On ArkOS-derived firmware the SDL2 libraries are typically pre-installed.
A TrueType font is bundled inside the package at src/r36s_dlna_browser/assets/
so packaged builds do not depend on a system font path. System font fallbacks are
still kept for development machines.
Quick Start
cd /path/to/R36SHack
python3 -m pip install -e ".[dev]"
python3 -m r36s_dlna_browser
Miniforge / Conda Deploy
Use Miniforge or Miniconda when you want a reproducible Linux development or packaging environment with GStreamer and SDL dependencies managed together.
cd /path/to/R36SHack
conda env create -f environment.yml
conda activate r36s-dlna-browser
python -m r36s_dlna_browser
To update an existing environment after dependency changes:
conda env update -f environment.yml --prune
conda activate r36s-dlna-browser
Or without installing:
cd /path/to/R36SHack
pip install -r requirements.txt
PYTHONPATH=src python3 -m r36s_dlna_browser
Controls
| Key / Button | Action |
|---|---|
| Up / Down | Navigate items |
| Left / Right | Page up / down |
| A (Return) | Select / Enter |
| B (Escape) | Back |
| Start | Quit |
During playback, the same controls are remapped to transport actions:
A: pause / resumeB: stop and return to browserLeft / Right: seek-10s / +10sUp / Down: volume+5 / -5H/Y: cycle HUD mode (auto / fixed / hidden)
On-Device (R36S / ArkOS)
- Copy the project to a writable location such as
/home/ark/MatHacks/R36SHack. - Ensure Wi-Fi is connected.
- Install or reuse a conda env at
/home/ark/miniconda3/envs/r36s-dlna-browser. - Run via PortMaster or a launch script:
cd /home/ark/MatHacks/R36SHack
export LD_LIBRARY_PATH=/home/ark/miniconda3/envs/r36s-dlna-browser/lib
export GST_PLUGIN_SCANNER=/home/ark/miniconda3/envs/r36s-dlna-browser/libexec/gstreamer-1.0/gst-plugin-scanner
python -m r36s_dlna_browser
An ArkOS launcher script for this layout is included at deploy/arkos/MatHacks.sh.
Git / Release Workflow
For publication on a Gitea instance with tea, initialize git locally, create a
remote repository, then push the current branch.
git init
git add .
git commit -m "Initial import"
tea repos create --name R36SHack --description "SDL2/GStreamer DLNA browser for R36S" --owner enne2
git remote add origin ssh://git@git.enne2.net:222/enne2/R36SHack.git
git push -u origin main
Published repository: https://git.enne2.net/enne2/R36SHack
Project Structure
src/r36s_dlna_browser/
├── __main__.py # Entrypoint
├── app.py # Application lifecycle
├── assets/
│ └── NotoSans-Regular.ttf
├── dlna/
│ ├── discovery.py # SSDP media-server discovery
│ ├── client.py # ContentDirectory browsing via DmsDevice
│ ├── models.py # Domain models for servers/items
│ └── browser_state.py # Navigation stack and cache
├── ui/
│ ├── sdl_app.py # SDL2 window, event loop, rendering
│ ├── screens.py # Screen definitions
│ └── theme.py # Layout constants for 640×480
├── player/
│ ├── backend.py # Player interface
│ └── gstreamer_backend.py # integrated GStreamer playback backend
└── platform/
├── controls.py # Input mapping
└── runtime.py # Runtime detection and logging
License
MIT