1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// RustPixel
// copyright zipxing@hotmail.com 2022~2026
//! Audio module provides sound playback functionality for RustPixel games.
//!
//! This module offers cross-platform audio support through different backends:
//! - **Native**: Direct audio file playback via rodio
//! - **Web**: Browser-based audio through JavaScript interop
//! audio provides playing music and sound effect, reference
//! https://docs.rs/rodio
#[cfg(audio_support)]
use rodio::{Decoder, OutputStreamBuilder, Source};
#[cfg(audio_support)]
use std::fs::File;
#[cfg(audio_support)]
use std::io::BufReader;
#[cfg(audio_support)]
use std::sync::{Arc, Mutex, OnceLock};
#[cfg(audio_support)]
use std::thread;
#[cfg(audio_support)]
type AudioStreamHandle = Box<dyn std::any::Any + Send>;
#[cfg(audio_support)]
static GLOBAL_AUDIO_HANDLE: OnceLock<Arc<Mutex<Option<AudioStreamHandle>>>> = OnceLock::new();
#[cfg(audio_support)]
fn get_or_create_audio_handle() -> Arc<Mutex<Option<AudioStreamHandle>>> {
GLOBAL_AUDIO_HANDLE
.get_or_init(|| Arc::new(Mutex::new(None)))
.clone()
}
pub struct Audio {}
impl Default for Audio {
fn default() -> Self {
Self::new()
}
}
impl Audio {
pub fn new() -> Self {
Self {}
}
#[cfg(audio_support)]
pub fn play_file(&self, fpath: &str, is_loop: bool) {
#[cfg(audio_support)]
{
// Use global GAME_CONFIG for project path
let project_path = &crate::get_game_config().project_path;
let path = format!("{}/assets/{}", project_path, fpath);
log::info!("Attempting to play audio file: {}", path);
let audio_handle = get_or_create_audio_handle();
let path_clone = path.clone();
// Spawn a thread to handle audio playback
thread::spawn(move || {
match OutputStreamBuilder::open_default_stream() {
Ok(stream_handle) => {
// Store the handle to keep it alive
{
let mut handle_guard = audio_handle.lock().unwrap();
*handle_guard = Some(Box::new(()) as AudioStreamHandle);
}
match File::open(&path_clone) {
Ok(file) => {
match Decoder::try_from(BufReader::new(file)) {
Ok(source) => {
let final_source = if is_loop {
Box::new(source.repeat_infinite())
as Box<dyn Source<Item = f32> + Send>
} else {
Box::new(source) as Box<dyn Source<Item = f32> + Send>
};
stream_handle.mixer().add(final_source);
log::info!("Audio file started playing: {}", path_clone);
// Keep the thread alive to maintain the audio stream
if is_loop {
// For looping audio, keep the thread alive indefinitely
loop {
thread::sleep(std::time::Duration::from_secs(1));
}
} else {
// For non-looping audio, estimate duration and sleep
thread::sleep(std::time::Duration::from_secs(10));
}
}
Err(e) => log::warn!(
"Failed to decode audio file '{}': {}",
path_clone,
e
),
}
}
Err(e) => {
log::warn!("Failed to open audio file '{}': {}", path_clone, e)
}
}
}
Err(e) => log::warn!("Failed to open audio stream: {}", e),
}
});
}
#[cfg(not(audio_support))]
{
log::info!("Audio playback not supported on this platform: {}", fpath);
}
}
}