4

I'm doing a direct download of an MP3 audio stream via Rust. As this stream is indefinite, I want to be able to cancel it early to save what I have downloaded so far. Currently, I do this by pressing CTRL + C to stop the program. This results in a stream.mp3 file I can then play back and listen to, and while this works, it isn't ideal.

Given the following code, how could I programmatically stop io::copy() early and have it save the file without killing the entire program?

extern crate reqwest;

use std::io;
use std::fs::File;

// Note that this is a direct link to the stream, not a webpage with HTML and a stream
const STREAM_URL: &str = "http://path.to/stream";

fn main() {
    let mut response = reqwest::get(STREAM_URL)
        .expect("Failed to request mp3 stream");
    let mut output = File::create("stream.mp3")
        .expect("Failed to create file!");
    io::copy(&mut response, &mut output)
        .expect("Failed to copy mp3 stream to file");
}
Newbyte
  • 2,421
  • 5
  • 22
  • 45
  • 2
    You can't do that with `io::copy`. `io::copy`'s job is literally to copy the entire thing no matter what it is. You'd have to tell `reqwest` to end the downloading somehow, but given that `reqwest` is mostly a convenience library around [`hyper`](https://docs.rs/crate/hyper/0.13.0) to download things simply, I also doubt it would have an API for this. Your next move would then to use `hyper` directly, where cancelling a stream is just a matter of dropping its future. – mcarton Dec 12 '19 at 14:53
  • 2
    I also don't know much about the MP3 format, but I doubt you can just split a file at any point and get a valid MP3 file out of that. You'd probably get something that your player can play, because they are used to bad files, but it wouldn't necessarily be valid. – mcarton Dec 12 '19 at 14:56

1 Answers1

2

As the comments said, io::copy is a convenience function to read a Reader in full and write it's content to a Writer without stoping in between; it's used for when you do not care about intermediate state but just want the entire thing to be shipped from the reader to the writer.

If you just want the first few Kb from the response, you can use io::Read::take, which limits the Reader to whatever limit you specified. It will return a new Reader which you can pass to io::copy.

And yes, you can cut an MP3-file at arbitrary positions. It is a framed format and while you will most likely destroy the last frame, practically all mp3-decoders are able to handle this.


Something to the tune of

// `Read` needs to be in scope, so .take() is available on a `io::Read`
use std::io::Read;
use std::io;
use std::fs::File;

fn main() {
    let mut response = reqwest::get(STREAM_URL)
        .expect("Failed to request mp3 stream")
        .take(100*1024);  // Since `Response` is `Read`, we can `take` from it.
    let mut output = File::create("stream.mp3")
        .expect("Failed to create file!");
    // `response` is a limited reader, so it will only read 100kb before
    // returning EOF, signaling a successful completion to `copy`.
    io::copy(&mut response, &mut output)
        .expect("Failed to copy mp3 stream to file");
}
user2722968
  • 13,636
  • 2
  • 46
  • 67
  • Do you think you could add a code example of how you would implement this? Somewhat new to Rust. – Newbyte Dec 12 '19 at 18:32