use std::{
fmt::{Debug, Formatter, Result as FmtResult},
io::Cursor,
sync::Arc,
};
use cpal::traits::DeviceTrait;
use log::error;
use rodio::{
default_output_device, output_devices, Decoder, Device, Devices, OutputDevices, Sink,
Source as RSource,
};
use amethyst_core::ecs::World;
use crate::{sink::AudioSink, source::Source, DecoderError};
#[derive(Clone)]
pub struct Output {
pub(crate) device: Arc<Device>,
}
impl Default for Output {
fn default() -> Self {
default_output_device()
.map(|device| Output {
device: Arc::new(device),
})
.expect("No default output device")
}
}
impl Output {
pub fn name(&self) -> String {
self.device.name().unwrap_or_else(|e| {
error!("Failed to determine output device name: {}", e);
String::from("<unnamed_output_device>")
})
}
pub fn try_play_once(&self, source: &Source, volume: f32) -> Result<(), DecoderError> {
self.try_play_n_times(source, volume, 1)
}
pub fn play_once(&self, source: &Source, volume: f32) {
self.play_n_times(source, volume, 1);
}
pub fn play_n_times(&self, source: &Source, volume: f32, n: u16) {
if let Err(err) = self.try_play_n_times(source, volume, n) {
error!("An error occurred while trying to play a sound: {:?}", err);
}
}
pub fn try_play_n_times(
&self,
source: &Source,
volume: f32,
n: u16,
) -> Result<(), DecoderError> {
let sink = Sink::new(&self.device);
for _ in 0..n {
sink.append(
Decoder::new(Cursor::new(source.clone()))
.map_err(|_| DecoderError)?
.amplify(volume),
);
}
sink.detach();
Ok(())
}
}
impl Debug for Output {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.debug_struct("Output")
.field("device", &self.name())
.finish()
}
}
#[allow(missing_debug_implementations)]
pub struct OutputIterator {
devices: OutputDevices<Devices>,
}
impl Iterator for OutputIterator {
type Item = Output;
fn next(&mut self) -> Option<Output> {
self.devices.next().map(|device| Output {
device: Arc::new(device),
})
}
}
pub fn default_output() -> Option<Output> {
default_output_device().map(|device| Output {
device: Arc::new(device),
})
}
pub fn outputs() -> OutputIterator {
let devices =
output_devices().unwrap_or_else(|e| panic!("Error retrieving output devices: `{}`", e));
OutputIterator { devices }
}
pub fn init_output(world: &mut World) {
if let Some(o) = default_output() {
world
.entry::<AudioSink>()
.or_insert_with(|| AudioSink::new(&o));
world.entry::<Output>().or_insert_with(|| o);
} else {
error!("Failed finding a default audio output to hook AudioSink to, audio will not work!")
}
}
#[cfg(test)]
mod tests {
#[cfg(target_os = "linux")]
use {
crate::{output::Output, source::Source, DecoderError},
amethyst_utils::app_root_dir::application_root_dir,
std::{fs::File, io::Read, vec::Vec},
};
#[test]
#[cfg(target_os = "linux")]
fn test_play_wav() {
test_play("tests/sound_test.wav", true)
}
#[test]
#[cfg(target_os = "linux")]
fn test_play_mp3() {
test_play("tests/sound_test.mp3", true);
}
#[test]
#[cfg(target_os = "linux")]
fn test_play_flac() {
test_play("tests/sound_test.flac", true);
}
#[test]
#[cfg(target_os = "linux")]
fn test_play_ogg() {
test_play("tests/sound_test.ogg", true);
}
#[test]
#[cfg(target_os = "linux")]
fn test_play_fake() {
test_play("tests/sound_test.fake", false);
}
#[cfg(target_os = "linux")]
fn test_play(file_name: &str, should_pass: bool) {
let app_root = application_root_dir().unwrap();
let audio_path = app_root.join(file_name);
let mut f = File::open(audio_path).unwrap();
let mut buffer = Vec::new();
f.read_to_end(&mut buffer).unwrap();
let src = Source { bytes: buffer };
let vol: f32 = 4.0;
let n: u16 = 5;
let output = Output::default();
output.play_once(&src, vol);
output.play_n_times(&src, vol, n);
let result_try_play_once = output.try_play_once(&src, vol);
check_result(result_try_play_once, should_pass);
let result_try_play_n_times = output.try_play_n_times(&src, vol, n);
check_result(result_try_play_n_times, should_pass);
}
#[cfg(target_os = "linux")]
fn check_result(result: Result<(), DecoderError>, should_pass: bool) {
match result {
Ok(_pass) => assert!(
should_pass,
"Expected `play` result to be Err(..), but was Ok(..)"
),
Err(fail) => assert!(
!should_pass,
"Expected `play` result to be `Ok(..)`, but was {:?}",
fail
),
};
}
}