Skip to content

Commit

Permalink
feat: playing sound from remote source
Browse files Browse the repository at this point in the history
  • Loading branch information
Théo Gaillard committed Mar 31, 2020
1 parent 6e2c022 commit 84e45b0
Show file tree
Hide file tree
Showing 12 changed files with 232 additions and 32 deletions.
8 changes: 8 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

[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.9.0", default-features = false, features = ["rustls-tls"], optional = true }

[features]
default = ["flac", "vorbis", "wav", "mp3"]
Expand All @@ -23,3 +25,9 @@ flac = ["claxon"]
vorbis = ["lewton"]
wav = ["hound"]
mp3 = ["minimp3"]
http = ["reqwest"]

[[example]]
name = "http_flac"
path = "examples/http_flac.rs"
required-features = ["http"]
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 data = rodio::SeekableReqwest::get(url);
let buffer = rodio::SeekableBufReader::new(data);
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();
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::SeekableReqwest,
};

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;
106 changes: 106 additions & 0 deletions src/utils/buffer/seekable_bufreader.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use std::io::{BufRead, Error, ErrorKind, Read, Seek, SeekFrom};

pub struct SeekableBufReader<R> {
inner: R,
position: usize,
cache: Vec<u8>,
}

impl<R: Read + Seek> SeekableBufReader<R> {
pub fn new(inner: R) -> SeekableBufReader<R> {
SeekableBufReader {
inner,
position: 0,
cache: Vec::default(),
}
}

pub fn capacity(&self) -> usize {
self.cache.capacity()
}

pub fn available(&self) -> usize {
self.cache.len()
}

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

pub fn at(&mut self, index: usize) -> Option<&u8> {
if self.available() <= index {
self.cache_to(index + 1);
}
self.cache.get(index)
}

pub fn get(&mut self, from: usize, to: usize) -> &[u8] {
let to = from + to;
if self.available() <= to {
self.cache_to(to + 1);
}
&self.cache[from..to]
}

fn cache_to(&mut self, _position: usize) {
// TODO: lazy_download
self.cache_to_end();
}

#[allow(unused_must_use)]
fn cache_to_end(&mut self) {
let mut buffer = Vec::default();
self.inner.read_to_end(&mut buffer);
self.cache.extend(buffer);
}
}

impl<R: Read + Seek> Seek for SeekableBufReader<R> {
fn seek(&mut self, seek: SeekFrom) -> Result<u64, Error> {
let (position, offset) = match seek {
SeekFrom::Start(position) => (0, position as i64),
SeekFrom::Current(position) => (self.position, position),
SeekFrom::End(position) => {
self.cache_to_end();
(self.available(), 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",
)),
}
}
}

impl<R: Read + Seek> Read for SeekableBufReader<R> {
fn read(&mut self, buffer: &mut [u8]) -> Result<usize, Error> {
let size = match self.at(self.position + buffer.len()) {
Some(_) => buffer.len(),
None => self.available() - self.position,
};
buffer[..size].clone_from_slice(self.get(self.position, size));
self.consume(size);
Ok(size)
}
}

impl<R: Read + Seek> BufRead for SeekableBufReader<R> {
fn fill_buf(&mut self) -> Result<&[u8], Error> {
Ok(self.get(self.position, self.available()))
}

fn consume(&mut self, amt: usize) {
self.position += amt;
}
}
2 changes: 2 additions & 0 deletions src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod buffer;
pub mod source;
56 changes: 56 additions & 0 deletions src/utils/source/http.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use std::io::{Error, ErrorKind, Read, Seek, SeekFrom};

use reqwest::{get, Response};

pub struct SeekableReqwest {}

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

pub struct SeekableResponse {
inner: Response,
position: usize,
}

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

impl Seek for SeekableResponse {
fn seek(&mut self, seek: SeekFrom) -> Result<u64, Error> {
let (position, offset) = match seek {
SeekFrom::Start(position) => (0, position as i64),
SeekFrom::Current(position) => (self.position, position),
SeekFrom::End(position) => (0, position), // TODO: real end index
};
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",
)),
}
}
}

impl Read for SeekableResponse {
fn read(&mut self, buffer: &mut [u8]) -> Result<usize, Error> {
self.inner.read(buffer)
}
}
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;

0 comments on commit 84e45b0

Please sign in to comment.