desktop: Move cpal audio backend to frontend-utils
This commit is contained in:
parent
ac3f8991ea
commit
0f2b66a808
|
@ -4397,6 +4397,8 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-channel",
|
"async-channel",
|
||||||
"async-io",
|
"async-io",
|
||||||
|
"bytemuck",
|
||||||
|
"cpal",
|
||||||
"futures-lite",
|
"futures-lite",
|
||||||
"macro_rules_attribute",
|
"macro_rules_attribute",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
|
|
@ -52,6 +52,7 @@ naga = { version = "22.1.0", features = ["wgsl-out"] }
|
||||||
wgpu = "22.1.0"
|
wgpu = "22.1.0"
|
||||||
egui = { git = "https://github.com/emilk/egui.git", rev = "f4697bc007447c6c2674beb4e25f599fb7afa093" }
|
egui = { git = "https://github.com/emilk/egui.git", rev = "f4697bc007447c6c2674beb4e25f599fb7afa093" }
|
||||||
clap = { version = "4.5.17", features = ["derive"] }
|
clap = { version = "4.5.17", features = ["derive"] }
|
||||||
|
cpal = "0.15.3"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
slotmap = "1.0.7"
|
slotmap = "1.0.7"
|
||||||
async-channel = "2.3.1"
|
async-channel = "2.3.1"
|
||||||
|
|
|
@ -12,7 +12,7 @@ workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
cpal = "0.15.3"
|
cpal = { workspace = true }
|
||||||
egui = { workspace = true }
|
egui = { workspace = true }
|
||||||
egui_extras = { git = "https://github.com/emilk/egui.git", rev = "f4697bc007447c6c2674beb4e25f599fb7afa093", default-features = false, features = ["image"] }
|
egui_extras = { git = "https://github.com/emilk/egui.git", rev = "f4697bc007447c6c2674beb4e25f599fb7afa093", default-features = false, features = ["image"] }
|
||||||
egui-wgpu = { git = "https://github.com/emilk/egui.git", rev = "f4697bc007447c6c2674beb4e25f599fb7afa093", features = ["winit"] }
|
egui-wgpu = { git = "https://github.com/emilk/egui.git", rev = "f4697bc007447c6c2674beb4e25f599fb7afa093", features = ["winit"] }
|
||||||
|
@ -24,7 +24,7 @@ ruffle_render = { path = "../render", features = ["clap"] }
|
||||||
ruffle_render_wgpu = { path = "../render/wgpu", features = ["clap"] }
|
ruffle_render_wgpu = { path = "../render/wgpu", features = ["clap"] }
|
||||||
ruffle_video_software = { path = "../video/software", optional = true }
|
ruffle_video_software = { path = "../video/software", optional = true }
|
||||||
ruffle_video_external = { path = "../video/external", optional = true }
|
ruffle_video_external = { path = "../video/external", optional = true }
|
||||||
ruffle_frontend_utils = { path = "../frontend-utils" }
|
ruffle_frontend_utils = { path = "../frontend-utils", features = ["cpal"] }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tracing-subscriber = { workspace = true }
|
tracing-subscriber = { workspace = true }
|
||||||
tracing-appender = "0.2.3"
|
tracing-appender = "0.2.3"
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
mod audio;
|
|
||||||
mod external_interface;
|
mod external_interface;
|
||||||
mod fscommand;
|
mod fscommand;
|
||||||
mod navigator;
|
mod navigator;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
pub use audio::CpalAudioBackend;
|
|
||||||
pub use external_interface::DesktopExternalInterfaceProvider;
|
pub use external_interface::DesktopExternalInterfaceProvider;
|
||||||
pub use fscommand::DesktopFSCommandProvider;
|
pub use fscommand::DesktopFSCommandProvider;
|
||||||
pub use navigator::DesktopNavigatorInterface;
|
pub use navigator::DesktopNavigatorInterface;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::backends::{
|
use crate::backends::{
|
||||||
CpalAudioBackend, DesktopExternalInterfaceProvider, DesktopFSCommandProvider,
|
DesktopExternalInterfaceProvider, DesktopFSCommandProvider, DesktopNavigatorInterface,
|
||||||
DesktopNavigatorInterface, DesktopUiBackend,
|
DesktopUiBackend,
|
||||||
};
|
};
|
||||||
use crate::custom_event::RuffleEvent;
|
use crate::custom_event::RuffleEvent;
|
||||||
use crate::gui::{FilePicker, MovieView};
|
use crate::gui::{FilePicker, MovieView};
|
||||||
|
@ -11,6 +11,7 @@ use ruffle_core::backend::navigator::{OpenURLMode, SocketMode};
|
||||||
use ruffle_core::config::Letterbox;
|
use ruffle_core::config::Letterbox;
|
||||||
use ruffle_core::events::{GamepadButton, KeyCode};
|
use ruffle_core::events::{GamepadButton, KeyCode};
|
||||||
use ruffle_core::{DefaultFont, LoadBehavior, Player, PlayerBuilder, PlayerEvent};
|
use ruffle_core::{DefaultFont, LoadBehavior, Player, PlayerBuilder, PlayerEvent};
|
||||||
|
use ruffle_frontend_utils::backends::audio::CpalAudioBackend;
|
||||||
use ruffle_frontend_utils::backends::executor::{AsyncExecutor, PollRequester};
|
use ruffle_frontend_utils::backends::executor::{AsyncExecutor, PollRequester};
|
||||||
use ruffle_frontend_utils::backends::navigator::ExternalNavigatorBackend;
|
use ruffle_frontend_utils::backends::navigator::ExternalNavigatorBackend;
|
||||||
use ruffle_frontend_utils::bundle::source::BundleSourceError;
|
use ruffle_frontend_utils::bundle::source::BundleSourceError;
|
||||||
|
@ -134,7 +135,7 @@ impl ActivePlayer {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut builder = PlayerBuilder::new();
|
let mut builder = PlayerBuilder::new();
|
||||||
|
|
||||||
match CpalAudioBackend::new(&preferences) {
|
match CpalAudioBackend::new(preferences.output_device_name().as_deref()) {
|
||||||
Ok(audio) => {
|
Ok(audio) => {
|
||||||
builder = builder.with_audio(audio);
|
builder = builder.with_audio(audio);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,9 @@ version.workspace = true
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
cpal = ["dep:cpal", "dep:bytemuck"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
toml_edit = { version = "0.22.20", features = ["parse"] }
|
toml_edit = { version = "0.22.20", features = ["parse"] }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
|
@ -23,8 +26,16 @@ async-channel = { workspace = true }
|
||||||
slotmap = { workspace = true }
|
slotmap = { workspace = true }
|
||||||
async-io = "2.3.4"
|
async-io = "2.3.4"
|
||||||
futures-lite = "2.3.0"
|
futures-lite = "2.3.0"
|
||||||
reqwest = { version = "0.12.7", default-features = false, features = ["rustls-tls", "cookies", "charset", "http2", "macos-system-configuration"] }
|
reqwest = { version = "0.12.7", default-features = false, features = [
|
||||||
|
"rustls-tls",
|
||||||
|
"cookies",
|
||||||
|
"charset",
|
||||||
|
"http2",
|
||||||
|
"macos-system-configuration",
|
||||||
|
] }
|
||||||
tokio = { workspace = true, features = ["net"] }
|
tokio = { workspace = true, features = ["net"] }
|
||||||
|
cpal = { workspace = true, optional = true }
|
||||||
|
bytemuck = { workspace = true, optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#[cfg(feature = "cpal")]
|
||||||
|
pub mod audio;
|
||||||
pub mod executor;
|
pub mod executor;
|
||||||
pub mod navigator;
|
pub mod navigator;
|
||||||
pub mod storage;
|
pub mod storage;
|
||||||
|
|
|
@ -1,12 +1,29 @@
|
||||||
use crate::preferences::GlobalPreferences;
|
|
||||||
use anyhow::{anyhow, Context, Error};
|
|
||||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||||
|
use cpal::SampleFormat;
|
||||||
use ruffle_core::backend::audio::{
|
use ruffle_core::backend::audio::{
|
||||||
swf, AudioBackend, AudioMixer, DecodeError, RegisterError, SoundHandle, SoundInstanceHandle,
|
swf, AudioBackend, AudioMixer, DecodeError, RegisterError, SoundHandle, SoundInstanceHandle,
|
||||||
SoundStreamInfo, SoundTransform,
|
SoundStreamInfo, SoundTransform,
|
||||||
};
|
};
|
||||||
use ruffle_core::impl_audio_mixer_backend;
|
use ruffle_core::impl_audio_mixer_backend;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum CpalError {
|
||||||
|
#[error("No audio devices available")]
|
||||||
|
NoDevices,
|
||||||
|
|
||||||
|
#[error("Failed to get default output config")]
|
||||||
|
DefaultStream(#[from] cpal::DefaultStreamConfigError),
|
||||||
|
|
||||||
|
#[error("Unsupported sample format {0:?}")]
|
||||||
|
UnsupportedSampleFormat(SampleFormat),
|
||||||
|
|
||||||
|
#[error("Couldn't play the audio stream")]
|
||||||
|
Play(#[from] cpal::PlayStreamError),
|
||||||
|
|
||||||
|
#[error("Failed to construct audio stream")]
|
||||||
|
Build(#[from] cpal::BuildStreamError),
|
||||||
|
}
|
||||||
|
|
||||||
pub struct CpalAudioBackend {
|
pub struct CpalAudioBackend {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
device: cpal::Device,
|
device: cpal::Device,
|
||||||
|
@ -17,16 +34,16 @@ pub struct CpalAudioBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CpalAudioBackend {
|
impl CpalAudioBackend {
|
||||||
pub fn new(preferences: &GlobalPreferences) -> Result<Self, Error> {
|
pub fn new(preferred_device_name: Option<&str>) -> Result<Self, CpalError> {
|
||||||
// Create CPAL audio device.
|
// Create CPAL audio device.
|
||||||
let host = cpal::default_host();
|
let host = cpal::default_host();
|
||||||
let device = get_suitable_output_device(preferences, &host)
|
let device =
|
||||||
.ok_or_else(|| anyhow!("No audio devices available"))?;
|
get_suitable_output_device(preferred_device_name, &host).ok_or(CpalError::NoDevices)?;
|
||||||
|
|
||||||
// Create audio stream for device.
|
// Create audio stream for device.
|
||||||
let config = device
|
let config = device
|
||||||
.default_output_config()
|
.default_output_config()
|
||||||
.context("Failed to get default output config")?;
|
.map_err(CpalError::DefaultStream)?;
|
||||||
let sample_format = config.sample_format();
|
let sample_format = config.sample_format();
|
||||||
let config = cpal::StreamConfig::from(config);
|
let config = cpal::StreamConfig::from(config);
|
||||||
let mixer = AudioMixer::new(config.channels as u8, config.sample_rate.0);
|
let mixer = AudioMixer::new(config.channels as u8, config.sample_rate.0);
|
||||||
|
@ -63,11 +80,11 @@ impl CpalAudioBackend {
|
||||||
error_handler,
|
error_handler,
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
_ => anyhow::bail!("Unsupported sample format {sample_format:?}"),
|
_ => return Err(CpalError::UnsupportedSampleFormat(sample_format)),
|
||||||
}?
|
}?
|
||||||
};
|
};
|
||||||
|
|
||||||
stream.play().context("Couldn't play the audio stream")?;
|
stream.play().map_err(CpalError::Play)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
device,
|
device,
|
||||||
|
@ -91,14 +108,14 @@ impl AudioBackend for CpalAudioBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_suitable_output_device(
|
fn get_suitable_output_device(
|
||||||
preferences: &GlobalPreferences,
|
preferred_device_name: Option<&str>,
|
||||||
host: &cpal::Host,
|
host: &cpal::Host,
|
||||||
) -> Option<cpal::Device> {
|
) -> Option<cpal::Device> {
|
||||||
// First let's check for any user preference...
|
// First let's check for any user preference...
|
||||||
if let Some(preferred_device_name) = preferences.output_device_name() {
|
if let Some(preferred_device_name) = preferred_device_name {
|
||||||
if let Ok(mut devices) = host.output_devices() {
|
if let Ok(mut devices) = host.output_devices() {
|
||||||
if let Some(device) =
|
if let Some(device) =
|
||||||
devices.find(|device| device.name().ok().as_deref() == Some(&preferred_device_name))
|
devices.find(|device| device.name().ok().as_deref() == Some(preferred_device_name))
|
||||||
{
|
{
|
||||||
return Some(device);
|
return Some(device);
|
||||||
}
|
}
|
Loading…
Reference in New Issue