Skip to content

did lots of updates backend and frontend, i loved your design but qml frontend was the best option for performance - #21

Open
neur0map wants to merge 216 commits into
ashmitvoid:mainfrom
Ryoku-dev:main
Open

neur0map wants to merge 216 commits into
ashmitvoid:mainfrom
Ryoku-dev:main

Conversation

@neur0map

@neur0map neur0map commented Sep 8, 2026

Copy link
Copy Markdown

your design was good but it really was causing a lot of ram spikes and issues with audio.. if you want deeper description of what i did let me know.

neur0map and others added 30 commits September 2, 2026 23:09
Three interlocking causes made each track start refetch the 2.9 MB player.js
and spawn a fresh hidden WebKit process:

- the cipher and PoToken webviews were torn down 15 s after use, which any
  song outlives; keep them while media is loaded and release after 5 min idle
- the player hash was re-read from iframe_api on every resolve because
  current_hash.txt was written but never read; pin it for the cache TTL
- a WEB_REMIX HEAD 403, routine on capped videos, ran the full self-heal
  (delete player.js, drop the bridge and the PoToken session) each time;
  allow one heal per ten minutes

Measured on a Ryoku desktop: six consecutive track changes with no fetch and
no webview build, where before each one paid ~1.2 s and a WebProcess.
mpv titled the stream by its URL, so the PipeWire node carried the full
signed googlevideo link. Pass a per-file force-media-title (Artist - Title)
through loadfile's options, quoted for the command parser and
length-prefixed for the option parser so any title round-trips verbatim.
Includes a probe example that asserts the round trip.
The reveal was gated on the Ryoku token IPC and retried for ~1.85 s against a
1.5 s native failsafe, so a cold WebKit mount was routinely revealed natively
and unstyled. Race the palette against 250 ms; the native failsafe moved to
4 s in the previous commit's lib.rs change.
Tauri sets cfg(dev) whenever custom-protocol is absent, so the source
PKGBUILD's plain cargo build produced a binary that opened build.devUrl.
Default the feature on; cargo tauri dev still passes --no-default-features.
Locale::default() is hard-coded to US/en, so every InnerTube request
announces English even when the system language and the signed-in
account are French; the API's hl/gl context wins over account
preferences and the UI never switches.

Add Locale::from_env(), which reads LC_ALL/LANG, parses
lang_TERRITORY and maps the compound tags YouTube localises
(zh-CN/TW/HK, pt-BR/PT, es-419/es-US, en-GB, fr-CA), falling back
to US/en when nothing parseable is set. The session bootstrap now
uses it instead of Locale::default().
InnerTube content is now localised by the backend locale (hl/gl), but the
app-owned chrome (context menus, settings, dialogs, status text) is
hard-coded English in the Svelte UI. Add a small reactive i18n module
(t() with a fr dictionary keyed by the exact English source string,
locale detected from navigator.language) and wrap every static UI
string in it. Unknown keys fall back to English, so missing
translations degrade gracefully. Covers 36 components/routes (~490
strings), including ternary display branches and aria labels; dynamic
API-driven text is untouched.
Both seek sliders hold the dragged value locally and commit it on the
input's change event. Release the thumb outside the window (the bar sits
on the bottom edge) and WebKitGTK delivers neither change nor pointerup;
the local value then shadowed every later position tick and the timeline
sat frozen for the rest of the session while playback carried on.
Reproduced by dragging past the window edge: MPRIS kept ticking, the bar
did not. A shared guard now commits the pending drag on pointerup,
pointercancel, lostpointercapture, window blur, or the first pointer
movement with no button held, and a track change ends a drag outright.
Home toggled ryo-is-scrolling on <main> on each scroll event and again
110 ms after the last one, and the queue/lyrics wheel helper did the same
on its scroller. Rules keyed on that class matched every <img> and every
promoted .ryo-art-wash layer, so each toggle was a full style recalc plus
compositing-layer churn, twice per wheel notch. Scrolling is otherwise
paint-bound in WebKitGTK (measured the same 60% of a core with and
without the toggle), so the class and its rules go rather than get
cheaper.
WebKitGTK 2.42's feature API can flip compositing borders and repaint
counters on, which is how to see what a scroll actually repaints; the
webkit2gtk crate does not bind it yet, so the five calls are declared
here and gated on an env var that is normally unset.
The cold-start reveal failsafe grew to 4000 ms in 531c2b5 but the
invariant still asked for 1500; the private-path scan matched the .git
file of a git worktree; two files had drifted from rustfmt.
The host calls lastfm::spawn from Tauri's synchronous setup closure, on
the main thread with no ambient Tokio context; the tokio::spawn that
replaced tauri::async_runtime::spawn panicked there at startup. Take the
runtime handle explicitly (the daemon will pass its own) and format the
files the extraction left unformatted.
neur0map and others added 30 commits October 13, 2026 22:57
The queue had no context menu at all, so moving a queued track into a
playlist meant liking it first, finding it in Liked Songs and using the
menu there. The cards on Home, Search and the library had none either,
and neither did the playing track in the bar.

Every surface now answers a right-click through the one Menu the lists
already use:

- queue rows, with "Remove from queue"; the playing row keeps the menu
  but not the removal, since the queue cannot drop the track out from
  under itself
- cards, through CardGrid and Shelf. A song card gets the track menu; an
  album, artist or playlist is not a track, so it gets Play, Open and Add
  to shortcuts, run through the card's own logic
- the playing track in the player bar and in the mini window

Three things came out of doing it:

"Play next" and "Add to queue" are a no-op on the track that is already
playing, so they are left out there rather than offered and ignored.

The menu lived inside the list that opened it, so its dismiss layer only
covered that list: a click anywhere else left the menu standing. It now
lives on the window, which also means a list with clip: true can no
longer cut the panel off.

openAt read panel.height to decide whether to flip the menu above the
press, but the height follows the items the song produces and is not
known at that moment. The placement is a binding on the panel instead,
so a menu opened near the bottom of the window lands inside it. That is
what the player bar needs every single time.
"Recently played" on Home is a list of browse items like the shelves, so
it answers a right-click the same way. That made the block deciding what
an item is a third copy, so it moved into ItemMenu: the host hands it its
own play and open functions and the component picks the track menu for a
song and Play / Open / Add to shortcuts for anything else.

The wheel closing the menu did not work. WheelHandler is the one handler
Qt narrows to PointerDevice.Mouse, and the Wayland plugin stamps every
wheel event with the seat's primary pointing device, which is reported as
a touchpad on a machine with no touchpad in it. PlayerBar and HomeFeed
already name both devices for that reason; this one did not.
…efused rather than worked around (#11)

* feat: tidal joins the provider selector, with its protected streams refused rather than worked around

* feat: the tidal selector wears tidal's own mark instead of a borrowed glyph

* feat: signing in to tidal is one button, with the client pair shipped the way spotify's already is

* fix: a stored tidal pair overrides the shipped one field by field, and the account menu signs in to tidal like it does to spotify

* fix: the account menu shows one tidal line when signed out, the account line only once there is an account or a code

* fix: the sign-in message names the address tidal itself returns instead of a guessed one

* feat: the tidal account shows its profile name, and falls back to the login email only when there is none

* fix: a signed-out tidal stops the queue instead of skipping through it, a sign-out outlives the exchange it interrupts, a long album gives up all its tracks, and the heart on a tidal track stops going to youtube

---------

Co-authored-by: eximora-private <198456237+eximora-private@users.noreply.github.com>
… lossy

Settings > Downloads gains a FLAC choice next to Original, MP3 and Opus.
yt-dlp extracts with --audio-format flac and no bitrate, and ffmpeg writes
16-bit samples: the sources are YouTube and SoundCloud streams, so the bits
a 24-bit decode adds are noise FLAC cannot compress, and dropping them makes
the file about half the size. Cover art and lyrics go in through the same
lofty path as the other formats, and the completed-file detection already
knew the flac extension.
…epository

Self update only knew pacman, so on Ryoku's Void and Fedora editions
Settings > Software updates refused with "Self update needs pacman and
polkit". There Ryotunes comes from the Ryoku package repository, which
builds each release from its source. Update now checks which package owns
the running binary, asks polkit to run "xbps-install -Syu ryotunes" or
"dnf upgrade --refresh ryotunes", and reports a release the repository
has not built yet instead of claiming it installed. Arch keeps its
verified release-package path.
QuickJS pulls sinh/cosh from libm and glibc 2.44 gave both a new symbol
version, so the 1.2.1 daemon dies on Void (2.41). Define them in the
daemon so it never imports them, and fail the package build if any
binary needs newer than 2.41.
The release is still the Arch package. On Void it gets unpacked, repacked
with xbps-create into a local repo and installed through pkexec
xbps-install. Systemd units are dropped since ryotunes already starts the
daemon itself without them. A release that needs a newer glibc than the
system has is refused before anything is installed.
Void splits Qt's TLS backend into qt6-plugin-tls-openssl. Without it the
QML client can't load a single cover. The Void package now depends on it
and diagnostics checks for it.
…ce every source is lossy

# Conflicts:
#	CHANGELOG.md
TIDAL's waiting event carried the approval URL but the client only
showed the code. Return the URL so Playback opens it while preserving
the existing sign-in message.
fix: open TIDAL sign-in page in browser
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants