Stream Media with Axum

Hi, I'm trying to build a streaming service.
I can read file and send full data with axum with these code.

pub async fn routing(State(state): State<AppState>) -> Router {
    Router::new()
    .route("/stream", get(stream))
    .layer(CorsLayer::permissive())
    .with_state(state.clone())
}

async fn stream() -> impl IntoResponse {
    let file = File::open("audios/mus.mp4").await.unwrap();
    let stream = ReaderStream::new(file);
    Body::from_stream(stream)
}

But this code only creates one request from receiver and download all content then opens the video.

If i restrict the bandwith on browser it opens the video and still try to fetch all data with one request.

But if I open actual stream (some online radio station), my browser doesn't try to fetch all to play. Just starts and doesn't know when it will end and also I can't reach previous data and absolutely future data.

image

I want to make stream like this, how can I do it ? How can I stream a media file (mp3, mp4, mkv...) file that the play time is equal to everyone and prevent them to reach past, future and total media length ?

Edit:

I have struggled about this topic a lot. In case someone need help. I did it finally:

You seem to be missing the Content-Type header in the response. Can you post all the response headers as seen from the browser?

I didn't specify any header and I don't quite know how to do it. Also couldn't understand what you mean by saying "post all headers from browser".

I'm not sure but i tried this and nothing changed.

async fn stream() -> impl IntoResponse {
    let header = [
        (header::CONTENT_TYPE, "audio/mpeg")
    ];
    let file = File::open("audios/audio.mp3").await.unwrap();
    let stream = ReaderStream::new(file);
    (header,Body::from_stream(stream))
}

When you want to stream data over http/1.1, you want to make sure that the content-length header field is not set and that the transfer-encoding header is set to chunked.

You can check the browser console(especially the network tab) while doing this to see what file types and http headers(both request and response) are used.

I tried this and I think it applies as we can see in picture but nothing changes.

async fn stream() -> impl IntoResponse {
    let header = [
        (header::CONTENT_TYPE, "audio/mpeg"),
        (header::CONTENT_LENGTH, ""),
        (header::TRANSFER_ENCODING, "chunked"),
    ];
    let file = File::open("audios/audio.mp3").await.unwrap();
    let stream = ReaderStream::new(file);
    (header,Body::from_stream(stream))
}

image

Here is mine
image

and here is actual stream (Sometimes I can't see even it's header)
image

X-Firefox-Spdy: h2

Are you using HTTP/2 by any chance? axum defaults to HTTP/1.1, have you enabled the http2 feature in your Cargo.toml? Could you please check the Version field of the response and tell us what the value is? HTTP/2 ignores transfer-encoding. I'm not familiar with how HTTP/2 streaming works, all I know is that it is much more flexible than HTTP/1.1.

1 Like

Seems I'm using http2, I don't even know that.

I tried to copy actual streams headers but nothing changes.

async fn stream() -> impl IntoResponse {
    let header = [
        (header::CONTENT_TYPE, "audio/mpeg"),
        (header::CONTENT_LENGTH, ""),
        (header::TRANSFER_ENCODING, "chunked"),
        (header::CACHE_CONTROL, "no-cache, must-revalidate"),
        (header::PRAGMA, "no-cache"),

    ];
    let file = File::open("audios/audio.mp3").await.unwrap();
    let stream = ReaderStream::new(file);
    (header,Body::from_stream(stream))
}

Twitch using this kind of stream type, multiple request and octet-stream. I don't know if it's easier than one request, I'm also into that. If this solves my situation.

Could you try running your original code:

async fn stream() -> impl IntoResponse {
    let file = File::open("audios/mus.mp4").await.unwrap();
    let stream = ReaderStream::new(file);
    Body::from_stream(stream)
}

with HTTP/1.1? I.e. disabling the http2 feature from axum in your Cargo.toml.

I think it's built in feature because can't see it.

axum-server enables hyper's http2 feature unconditionally, i.e. you can't disable it through a feature of axum-server itself. Can you get rid of the dependency and use axum::serve instead?

This made lost what we had before.

The common way to do this for video is a protocol like HTTP Live Streaming or MPEG-DASH. In part, this is because video formats are complex, and chances are high that the file you have on disk requires you to seek around to find all the pieces you need to decode the first frame (e.g. .mp4 files often require you to have a directory at the end of the file).

When trying to load media it sends multiple requests (i did println! in routing)
image

but data is empty I think
image

I understand so I think if we do this for mp3, it will be a bit overkill?

Because my main purpose is stream audio files first.

and as a novice of course, I can't find any kind of resource for these kind of things. that's why I'm struggling for weeks. even for basics. Just injecting data to http body took me days for me, I don't even know I had to do this. I destroyed github, search engines, discord servers for weeks to understand what I need to do. If I had chosen JS or other populer languages on web it would be much more easy. There are 8 hours videos that people just codes and explains. I can't even understand what I need to do, maybe because I'm not enough. If somehow I can complete this project I'm going to create resource for people. Dioxus for frontend, axum for backend.It's a university project. Online radio that people can stream and others can listen and chat. Sorry for long message and if it's unnecessary. It was just brain dump for what I have been living.

And that's fine. Usually going down a few levels of abstraction means that you'll have to DIY a lot more, which is more time consuming and potentially more stressful due to the additional complexity.

Going the other way around is a valid approach. Either way you will learn about what's needed for your particular problem domain.

1 Like