Skip to content

Add local HTTP server for audio streaming on Linux#923

Merged
martpie merged 3 commits intomartpie:fix-linux-playbackfrom
BlueManCZ:fix-linux-playback
Feb 16, 2026
Merged

Add local HTTP server for audio streaming on Linux#923
martpie merged 3 commits intomartpie:fix-linux-playbackfrom
BlueManCZ:fix-linux-playback

Conversation

@BlueManCZ
Copy link
Contributor

@BlueManCZ BlueManCZ commented Feb 10, 2026

This commit introduces a local HTTP server for streaming audio on Linux as a workaround for WebKitGTK's asset protocol limitations with media streaming and Range requests. Includes a stream_server module and necessary changes to TS and Tauri integration.

I asked the Claude Opus AI agent to analyze this issue, and it came up with a working solution. This is its own review:

On Linux, Museeks uses WebKitGTK as its webview engine, which uses GStreamer for audio decoding. Tauri's asset:// protocol doesn't support proper media streaming on WebKitGTK (tauri-apps/tauri#3725). The previous workaround fetched entire audio files as blobs and created blob URLs, but GStreamer can't properly seek within blob URLs, causing:

  • FLAC: plays initial buffered data then stops (no seeking for more data)
  • MP3: VBR seeking is broken, causing random position jumps

Fix

Replaced the blob URL workaround with a local HTTP server (using axum) that serves audio files with proper Content-Type, Accept-Ranges, and HTTP Range request support. GStreamer's HTTP source element (souphttpsrc) handles regular HTTP streaming perfectly.

I didn't test these changes on Mac or Windows, but on Linux this is night and day type of improvement.

Fixes: #909, #912 any maybe some other related playback issues?

This commit introduces a local HTTP server for streaming audio on Linux as a workaround for WebKitGTK's asset protocol limitations with media streaming and Range requests. Includes a `stream_server` module and necessary changes to TS and Tauri integration.
@martpie
Copy link
Owner

martpie commented Feb 10, 2026

omg that's amazing, I was looking into this right now.

IIRC, I think this issue was also happening on mac with some files but I cannot reproduce it, I need to double check. If that's the case, we may want to open this via a custom setting and for all operating systems.

Let me try this branch once I get home.

@martpie
Copy link
Owner

martpie commented Feb 11, 2026

Just tested it, it works amazingly, thanks so much for doing that. I was really afraid I´d need to move the playback to the Rust side (tbh, maybe I'll still have to do it one day).

I'll review the code once I have a bit more time. You can ignore the macOS failure, it's expected as it uses my own certificate to bundle the app.

@BlueManCZ
Copy link
Contributor Author

I was really afraid I´d need to move the playback to the Rust side

If you want me to prompt this and share what Opus would came up with, we can do that. 😄

@martpie
Copy link
Owner

martpie commented Feb 11, 2026

hahahaha I have to say Claude has been working on a few PRs like #916

@BlueManCZ
Copy link
Contributor Author

I see, nice! 👍

Copy link
Owner

@martpie martpie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bunch of nits, I'm ready to merge it as-is if you don't have the bandwidth to handle those changes :)

}

fn content_type_for_extension(ext: &str) -> Option<&'static str> {
match ext {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep that in sync with

pub const SUPPORTED_TRACKS_EXTENSIONS: [&str; 9] = [
?

Basically, if an extension is added there, this function should break saying a case is not covered. This may require changing the type of the constant though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put this into the database.rs and derived SUPPORTED_AUDIO_FORMATS.

headers.insert(header::CONTENT_TYPE, content_type.parse().unwrap());
headers.insert(header::CONTENT_LENGTH, body.len().to_string().parse().unwrap());
headers.insert(header::ACCEPT_RANGES, "bytes".parse().unwrap());
headers.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*".parse().unwrap());
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure this header is required is it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, can be removed.

// On Linux, start a local HTTP server for audio streaming.
// WebKitGTK's asset protocol doesn't support media streaming properly.
#[cfg(target_os = "linux")]
let stream_server_port = plugins::stream_server::start();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any way this logic can be moved within the init of the plugin? I understand it's a sync init.

See how I did it for the DB:

tauri::async_runtime::spawn(async move {

It may create weird rust errors though, if it's too hard to change or fix the errors correctly, feel free to leave them like that, it's okeeey-ish.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to plugin

walkdir = "2.5.0"

[target.'cfg(target_os = "linux")'.dependencies]
axum = "0.7"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

genuine question, how heavy is this? are there other minimalist-yet-ergonomic http servers here? not a big deal.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on what Opus analyzed it is very light in this case. It only added 5 new crates to the lock file (axum, axum-core, matchit, serde_path_to_error, httpdate). Everything else axum depends on (hyper, http, tokio, tower, serde, bytes, futures-util, etc.) was already in the tree via Tauri. So the actual added weight is minimal.

The main alternatives would be:

  • warp - similar weight (also built on hyper/tokio), but its filter-based API is less ergonomic for this use case
  • tiny_http - truly minimal, but synchronous (doesn't use tokio), so it'd need its own thread pool for concurrent requests
  • Raw hyper - zero extra crates, but significantly more boilerplate for routing, query parsing, and response building
  • Raw tokio::net::TcpListener - zero deps, but manual HTTP parsing is error-prone

Given that axum reuses almost the entire existing dependency tree and only adds 5 small crates, it's a good trade-off for correct, maintainable code.

@martpie
Copy link
Owner

martpie commented Feb 16, 2026

Let me merge that, I think I may transform this into a Setting for people to opt-in, regardless of what OS they use, and there are a couple of nit I may do.

Thank you, hopefully I can release that this week, if not, when you can build from sources in the meantime haha (npm run tauri build).

@martpie martpie changed the base branch from master to fix-linux-playback February 16, 2026 15:46
@martpie martpie merged commit 7a1f0ca into martpie:fix-linux-playback Feb 16, 2026
5 of 6 checks passed
martpie pushed a commit that referenced this pull request Feb 17, 2026
* Add local HTTP server for audio streaming on Linux

This commit introduces a local HTTP server for streaming audio on Linux as a workaround for WebKitGTK's asset protocol limitations with media streaming and Range requests. Includes a `stream_server` module and necessary changes to TS and Tauri integration.

* Refactor stream server plugin: centralize MIME type logic and simplify initialization
martpie pushed a commit that referenced this pull request Feb 18, 2026
* Add local HTTP server for audio streaming on Linux

This commit introduces a local HTTP server for streaming audio on Linux as a workaround for WebKitGTK's asset protocol limitations with media streaming and Range requests. Includes a `stream_server` module and necessary changes to TS and Tauri integration.

* Refactor stream server plugin: centralize MIME type logic and simplify initialization
martpie pushed a commit that referenced this pull request Feb 18, 2026
* Add local HTTP server for audio streaming on Linux

This commit introduces a local HTTP server for streaming audio on Linux as a workaround for WebKitGTK's asset protocol limitations with media streaming and Range requests. Includes a `stream_server` module and necessary changes to TS and Tauri integration.

* Refactor stream server plugin: centralize MIME type logic and simplify initialization
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.

Large files playback stutters on Linux

2 participants