use {
Result,
error::QuicksilverError,
futures::{Future, future},
std::{
error::Error,
fmt,
io::Error as IOError,
path::Path
}
};
#[cfg(not(target_arch="wasm32"))]
use {
rodio::{
self,
decoder::{Decoder, DecoderError},
source::{SamplesConverter, Source,Amplify},
},
std::{
fs::File,
io::{Cursor, Read},
sync::Arc
}
};
#[cfg(target_arch="wasm32")]
use {
futures::Async,
std::io::ErrorKind,
stdweb::{
unstable::TryInto,
Value
}
};
#[derive(Clone, Debug)]
pub struct Sound {
#[cfg(not(target_arch="wasm32"))]
val: Arc<Vec<u8>>,
#[cfg(target_arch="wasm32")]
sound: Value,
volume: f32
}
#[cfg(target_arch="wasm32")]
fn wasm_sound_error(error: &str) -> QuicksilverError {
let error = IOError::new(ErrorKind::NotFound, error);
let error: SoundError = error.into();
error.into()
}
impl Sound {
pub fn load(path: impl AsRef<Path>) -> impl Future<Item = Sound, Error = QuicksilverError> {
Sound::load_impl(path.as_ref())
}
#[cfg(not(target_arch="wasm32"))]
fn load_impl(path: &Path) -> impl Future<Item = Sound, Error = QuicksilverError> {
future::result(load(path))
}
#[cfg(target_arch="wasm32")]
fn load_impl(path: &Path) -> impl Future<Item = Sound, Error = QuicksilverError> {
let sound = js! {
const audio = new Audio(@{path.to_str().expect("Path must be stringifiable")});
audio.hasError = false;
audio.onerror = (error) => audio.hasError = true;
return audio;
};
future::poll_fn(move || {
let error = js! ( return @{&sound}.hasError ).try_into();
let ready = js! ( return @{&sound}.readyState ).try_into();
match (error, ready) {
(Ok(false), Ok(4)) => Ok(Async::Ready(Sound {
sound: sound.clone(),
volume: 1f32
})),
(Ok(true), _) => Err(wasm_sound_error("Sound file not found or could not load")),
(Ok(false), Ok(_)) => Ok(Async::NotReady),
(Err(_), _) => Err(wasm_sound_error("Checking sound network state failed")),
(_, Err(_)) => Err(wasm_sound_error("Checking sound ready state failed")),
}
})
}
pub fn volume(&self) -> f32 {
self.volume
}
pub fn set_volume(&mut self, volume: f32) {
self.volume = volume;
}
#[cfg(not(target_arch="wasm32"))]
fn get_source(&self) -> Result<SamplesConverter<Amplify<Decoder<Cursor<Sound>>>, f32>> {
Ok(Decoder::new(Cursor::new(self.clone()))?.amplify(self.volume).convert_samples())
}
pub fn play(&self) -> Result<()> {
#[cfg(not(target_arch="wasm32"))] {
let device = match rodio::default_output_device() {
Some(device) => device,
None => return Err(SoundError::NoOutputAvailable.into())
};
rodio::play_raw(&device, self.get_source()?);
}
#[cfg(target_arch="wasm32")] js! {
@{&self.sound}.cloneNode().play();
}
Ok(())
}
#[cfg(not(target_arch="wasm32"))]
pub(crate) fn initialize() {
if let Some(ref device) = rodio::default_output_device() {
rodio::play_raw(device, rodio::source::Empty::new())
}
}
}
#[cfg(not(target_arch="wasm32"))]
fn load(path: &Path) -> Result<Sound> {
let mut bytes = Vec::new();
File::open(path)?.read_to_end(&mut bytes)?;
let val = Arc::new(bytes);
let sound = Sound {
val,
volume: 1f32
};
Decoder::new(Cursor::new(sound.clone()))?;
Ok(sound)
}
#[doc(hidden)]
#[cfg(not(target_arch="wasm32"))]
impl AsRef<[u8]> for Sound {
fn as_ref(&self) -> &[u8] {
self.val.as_ref().as_ref()
}
}
#[derive(Debug)]
pub enum SoundError {
UnrecognizedFormat,
NoOutputAvailable,
IOError(IOError)
}
impl fmt::Display for SoundError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.description())
}
}
impl Error for SoundError {
fn description(&self) -> &str {
match self {
SoundError::UnrecognizedFormat => "The sound file format was not recognized",
SoundError::NoOutputAvailable => "There was no output device available for playing",
SoundError::IOError(err) => err.description()
}
}
fn cause(&self) -> Option<&dyn Error> {
match self {
SoundError::UnrecognizedFormat
| SoundError::NoOutputAvailable => None,
SoundError::IOError(err) => Some(err)
}
}
}
#[doc(hidden)]
#[cfg(not(target_arch="wasm32"))]
impl From<DecoderError> for SoundError {
fn from(err: DecoderError) -> SoundError {
match err {
DecoderError::UnrecognizedFormat => SoundError::UnrecognizedFormat
}
}
}
#[doc(hidden)]
impl From<IOError> for SoundError {
fn from(err: IOError) -> SoundError {
SoundError::IOError(err)
}
}