Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] feat: playing sound from remote source #282

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ description = "Audio playback library"
keywords = ["audio", "playback", "gamedev"]
repository = "https://github.com/RustAudio/rodio"
documentation = "http://docs.rs/rodio"
autoexamples = true
Copy link
Member

Choose a reason for hiding this comment

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

This is redundant as true is already the default

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In fact I am not even sure if setting it to true have any effect...
But at least, it remove a warning.

Since I am setting an example like:

[[example]]
name = "http_flac"
path = "examples/http_flac.rs"
required-features = ["http"]

I got a warning:

warning: An explicit [[example]] section is specified in Cargo.toml which currently
disables Cargo from automatically inferring other example targets.
This inference behavior will change in the Rust 2018 edition and the following
files will be included as a example target:

* /home/tirz/Documents/Rust/rodio/examples/basic.rs
* /home/tirz/Documents/Rust/rodio/examples/music_mp3.rs
* /home/tirz/Documents/Rust/rodio/examples/music_flac.rs
* /home/tirz/Documents/Rust/rodio/examples/reverb.rs
* /home/tirz/Documents/Rust/rodio/examples/music_wav.rs
* /home/tirz/Documents/Rust/rodio/examples/music_ogg.rs
* /home/tirz/Documents/Rust/rodio/examples/spatial.rs

This is likely to break cargo build or cargo test as these files may not be
ready to be compiled as a example target today. You can future-proof yourself
and disable this warning by adding `autoexamples = false` to your [package]
section. You may also move the files to a location where Cargo would not
automatically infer them to be a target, such as in subfolders.

For more information on this warning you can consult
https://github.com/rust-lang/cargo/issues/5330


[dependencies]
claxon = { version = "0.4.2", optional = true }
Expand All @@ -15,6 +16,7 @@ hound = { version = "3.3.1", optional = true }
lazy_static = "1.0.0"
lewton = { version = "0.10", optional = true }
minimp3 = { version = "0.3.2", optional = true }
reqwest = { version = "0.10.0", default-features = false, features = ["blocking"], optional = true }

[features]
default = ["flac", "vorbis", "wav", "mp3"]
Expand All @@ -23,3 +25,26 @@ flac = ["claxon"]
vorbis = ["lewton"]
wav = ["hound"]
mp3 = ["minimp3"]
http = ["reqwest"]
https-rustls = ["http", "reqwest/rustls-tls"]
https-native = ["http", "reqwest/native-tls"]

[[example]]
name = "http_flac"
path = "examples/http_flac.rs"
required-features = ["https-rustls"]

[[example]]
name = "http_mp3"
path = "examples/http_mp3.rs"
required-features = ["https-rustls"]

[[example]]
name = "http_ogg"
path = "examples/http_ogg.rs"
required-features = ["https-rustls"]

[[example]]
name = "http_wav"
path = "examples/http_wav.rs"
required-features = ["https-rustls"]
12 changes: 12 additions & 0 deletions examples/http_flac.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
fn main() {
let device = rodio::default_output_device().unwrap();
let sink = rodio::Sink::new(&device);

let url = "https://github.com/RustAudio/rodio/raw/master/examples/music.flac";
let request = rodio::SeekableRequest::get(url);
let buffer = rodio::SeekableBufReader::new(request);
let source = rodio::Decoder::new(buffer).unwrap();

sink.append(source);
sink.sleep_until_end();
}
12 changes: 12 additions & 0 deletions examples/http_mp3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
fn main() {
let device = rodio::default_output_device().unwrap();
let sink = rodio::Sink::new(&device);

let url = "https://github.com/RustAudio/rodio/raw/master/examples/music.mp3";
let request = rodio::SeekableRequest::get(url);
let buffer = rodio::SeekableBufReader::new(request);
let source = rodio::Decoder::new(buffer).unwrap();

sink.append(source);
sink.sleep_until_end();
}
12 changes: 12 additions & 0 deletions examples/http_ogg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
fn main() {
let device = rodio::default_output_device().unwrap();
let sink = rodio::Sink::new(&device);

let url = "https://github.com/RustAudio/rodio/raw/master/examples/music.ogg";
let request = rodio::SeekableRequest::get(url);
let buffer = rodio::SeekableBufReader::new(request);
let source = rodio::Decoder::new(buffer).unwrap();

sink.append(source);
sink.sleep_until_end();
}
12 changes: 12 additions & 0 deletions examples/http_wav.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
fn main() {
let device = rodio::default_output_device().unwrap();
let sink = rodio::Sink::new(&device);

let url = "https://github.com/RustAudio/rodio/raw/master/examples/music.wav";
let request = rodio::SeekableRequest::get(url);
let buffer = rodio::SeekableBufReader::new(request);
let source = rodio::Decoder::new(buffer).unwrap();

sink.append(source);
sink.sleep_until_end();
}
17 changes: 9 additions & 8 deletions examples/music_flac.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
extern crate rodio;

use std::io::BufReader;
use std::{fs::File, io::BufReader};

fn main() {
let device = rodio::default_output_device().unwrap();
let sink = rodio::Sink::new(&device);
let device = rodio::default_output_device().unwrap();
let sink = rodio::Sink::new(&device);

let file = std::fs::File::open("examples/music.flac").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
let path = "examples/music.flac";
let file = File::open(path).unwrap();
let buffer = BufReader::new(file);
let source = rodio::Decoder::new(buffer).unwrap();

sink.sleep_until_end();
sink.append(source);
sink.sleep_until_end();
}
17 changes: 9 additions & 8 deletions examples/music_mp3.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
extern crate rodio;

use std::io::BufReader;
use std::{fs::File, io::BufReader};

fn main() {
let device = rodio::default_output_device().unwrap();
let sink = rodio::Sink::new(&device);
let device = rodio::default_output_device().unwrap();
Copy link
Member

Choose a reason for hiding this comment

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

Rodio uses spaces for indentation.

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 planed to run cargo fmt at the end of this PR but right now, I got an error because of my version of Rust.

Warning: Unknown configuration option `fn_args_density`
Warning: Unknown configuration option `fn_brace_style`
Warning: Unknown configuration option `fn_call_style`
Warning: Unknown configuration option `fn_empty_single_line`
Warning: Unknown configuration option `generics_indent`
Warning: Unknown configuration option `impl_empty_single_line`
Warning: Unknown configuration option `reorder_imported_names`
Warning: Unknown configuration option `reorder_imports_in_group`
Warning: Unknown configuration option `where_density`
Warning: Unknown configuration option `where_style`
Warning: Unknown configuration option `wrap_match_arms`
Warning: Unknown configuration option `write_mode`
Error: Decoding config file failed:
unknown variant `Visual`, expected one of `Compressed`, `Tall`, `Vertical` for key `fn_args_layout`
Please check your config file.

I will fix it later.

let sink = rodio::Sink::new(&device);

let file = std::fs::File::open("examples/music.mp3").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
let path = "examples/music.mp3";
let file = File::open(path).unwrap();
let buffer = BufReader::new(file);
let source = rodio::Decoder::new(buffer).unwrap();

sink.sleep_until_end();
sink.append(source);
sink.sleep_until_end();
}
17 changes: 9 additions & 8 deletions examples/music_ogg.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
extern crate rodio;

use std::io::BufReader;
use std::{fs::File, io::BufReader};

fn main() {
let device = rodio::default_output_device().unwrap();
let sink = rodio::Sink::new(&device);
let device = rodio::default_output_device().unwrap();
let sink = rodio::Sink::new(&device);

let file = std::fs::File::open("examples/music.ogg").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
let path = "examples/music.ogg";
let file = File::open(path).unwrap();
let buffer = BufReader::new(file);
let source = rodio::Decoder::new(buffer).unwrap();

sink.sleep_until_end();
sink.append(source);
sink.sleep_until_end();
}
17 changes: 9 additions & 8 deletions examples/music_wav.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
extern crate rodio;

use std::io::BufReader;
use std::{fs::File, io::BufReader};

fn main() {
let device = rodio::default_output_device().unwrap();
let sink = rodio::Sink::new(&device);
let device = rodio::default_output_device().unwrap();
let sink = rodio::Sink::new(&device);

let file = std::fs::File::open("examples/music.wav").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
let path = "examples/music.wav";
let file = File::open(path).unwrap();
let buffer = BufReader::new(file);
let source = rodio::Decoder::new(buffer).unwrap();

sink.sleep_until_end();
sink.append(source);
sink.sleep_until_end();
}
8 changes: 8 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ extern crate lazy_static;
extern crate lewton;
#[cfg(feature = "mp3")]
extern crate minimp3;
#[cfg(feature = "http")]
extern crate reqwest;

pub use cpal::{
traits::DeviceTrait, Device, Devices, DevicesError, Format, InputDevices, OutputDevices
Expand All @@ -105,6 +107,11 @@ pub use engine::play_raw;
pub use sink::Sink;
pub use source::Source;
pub use spatial_sink::SpatialSink;
#[cfg(feature = "http")]
pub use utils::{
buffer::seekable_bufreader::SeekableBufReader,
source::http::SeekableRequest,
};

use cpal::traits::HostTrait;
use std::io::{Read, Seek};
Expand All @@ -117,6 +124,7 @@ mod spatial_sink;
pub mod buffer;
pub mod decoder;
pub mod dynamic_mixer;
pub mod utils;
pub mod queue;
pub mod source;
pub mod static_buffer;
Expand Down
2 changes: 2 additions & 0 deletions src/utils/buffer/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#[cfg(feature = "http")]
pub mod seekable_bufreader;
50 changes: 50 additions & 0 deletions src/utils/buffer/seekable_bufreader.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use std::io::{BufRead, Error, Read, Seek, SeekFrom};

use crate::utils::Cache;

pub struct SeekableBufReader<B> {
buffer: B,
}

impl<B: Cache> SeekableBufReader<B> {
pub fn new(buffer: B) -> Self {
SeekableBufReader {
buffer,
}
}
}

impl<B: Cache> Read for SeekableBufReader<B> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
let position = self.buffer.position();
let cache = self.buffer.slice(
position,
position + buf.len(),
);
let amt = cache.len();

buf[..amt].copy_from_slice(cache);
self.consume(amt);
Ok(amt)
}
}

impl<B: Cache> Seek for SeekableBufReader<B> {
fn seek(&mut self, pos: SeekFrom) -> Result<u64, Error> {
self.buffer.seek(pos)
}
}

impl<B: Cache> BufRead for SeekableBufReader<B> {
fn fill_buf(&mut self) -> Result<&[u8], Error> {
Ok(self.buffer.slice(
self.buffer.position(),
self.buffer.available(),
))
}

#[allow(unused_must_use)]
fn consume(&mut self, amt: usize) {
self.buffer.seek(SeekFrom::Current(amt as i64));
}
}
19 changes: 19 additions & 0 deletions src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use std::io::{Read, Seek};

pub mod buffer;
pub mod source;

#[cfg(feature = "http")]
pub trait Cache: Read + Seek {
fn available(&self) -> usize;

fn position(&self) -> usize;

fn get(&mut self, index: usize) -> Option<&u8>;

fn slice(&mut self, from: usize, to: usize) -> &[u8];

fn cache_to_index(&mut self, index: usize);

fn cache_to_end(&mut self);
}
106 changes: 106 additions & 0 deletions src/utils/source/http.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use std::io::{Error, ErrorKind, Read, Seek, SeekFrom};

use reqwest::blocking::{get, Response};

use crate::utils::Cache;

pub struct SeekableRequest {}

impl SeekableRequest {
pub fn get(url: &str) -> SeekableResponse {
SeekableResponse::from(get(url).unwrap())
}
}

pub struct SeekableResponse {
inner: Response,
position: usize,
buffer: Vec<u8>,
}

impl From<Response> for SeekableResponse {
fn from(inner: Response) -> Self {
SeekableResponse {
inner,
position: 0,
buffer: Vec::default(),
}
}
}

impl Cache for SeekableResponse {
fn available(&self) -> usize {
self.buffer.len()
}

fn position(&self) -> usize {
self.position
}

fn get(&mut self, index: usize) -> Option<&u8> {
if self.buffer.len() <= index {
self.cache_to_index(index);
}
self.buffer.get(index)
}

fn slice(&mut self, from: usize, to: usize) -> &[u8] {
if self.buffer.len() <= to {
self.cache_to_index(to);
}
if self.buffer.len() <= from {
return &[];
}
if self.buffer.len() <= to {
return &self.buffer[from..];
}
&self.buffer[from..to]
}

#[allow(unused_must_use)]
fn cache_to_index(&mut self, index: usize) {
let available = self.buffer.len();
if index >= available {
self.read(&mut vec![0u8; index - available]);
}
}

#[allow(unused_must_use)]
fn cache_to_end(&mut self) {
self.read_to_end(&mut Vec::default());
}
}

impl Read for SeekableResponse {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
Ok(self.inner.read(buf).map(|len| {
self.buffer.extend(&buf[..len]);
len
})?)
}
}

impl Seek for SeekableResponse {
fn seek(&mut self, pos: SeekFrom) -> Result<u64, Error> {
let (position, offset) = match pos {
SeekFrom::Start(position) => (0, position as i64),
SeekFrom::Current(position) => (self.position, position),
SeekFrom::End(position) => (self.buffer.len(), position),
};
let position = if offset < 0 {
position.checked_sub(offset.wrapping_neg() as usize)
} else {
position.checked_add(offset as usize)
};
match position {
Some(position) => {
self.position = position;
Ok(position as u64)
}
None => Err(Error::new(
ErrorKind::InvalidInput,
"invalid seek to a negative or overflowing position",
)),
}
}
}
2 changes: 2 additions & 0 deletions src/utils/source/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#[cfg(feature = "http")]
pub mod http;