Skip to main content

android_media/
player.rs

1use {
2    super::{constants::*, load_class},
3    jni::{
4        JNIEnv,
5        errors::Error as JniError,
6        objects::{GlobalRef, JObject, JString},
7    },
8    std::{
9        collections::HashMap,
10        error::Error,
11        fmt::{Display, Formatter, Result as FmtResult},
12        mem::transmute,
13        sync::{
14            LazyLock, Mutex, PoisonError,
15            atomic::{AtomicI16, Ordering},
16        },
17    },
18    tokio::sync::oneshot::{Sender, channel, error::RecvError},
19    tracing::error,
20};
21
22#[derive(Debug)]
23pub enum PlayerError {
24    Jni(JniError),
25    Poison(String),
26    Recv(RecvError),
27    Write(String),
28}
29
30impl Display for PlayerError {
31    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
32        write!(f, "PlayerError: ")?;
33        match self {
34            Self::Jni(e) => Display::fmt(e, f),
35            Self::Poison(msg) => write!(f, "PoisonError: {}", msg),
36            Self::Recv(e) => Display::fmt(e, f),
37            Self::Write(msg) => write!(f, "WriteError: {}", msg),
38        }
39    }
40}
41
42impl Error for PlayerError {}
43
44impl From<JniError> for PlayerError {
45    fn from(value: JniError) -> Self {
46        Self::Jni(value)
47    }
48}
49
50impl From<RecvError> for PlayerError {
51    fn from(value: RecvError) -> Self {
52        Self::Recv(value)
53    }
54}
55
56impl<T> From<PoisonError<T>> for PlayerError {
57    fn from(value: PoisonError<T>) -> Self {
58        Self::Poison(value.to_string())
59    }
60}
61
62type PlayerResult<T> = Result<T, PlayerError>;
63
64/// Player write task callbacks
65static PLAYER_WRITE_CALLBACKS: LazyLock<Mutex<HashMap<i16, Sender<PlayerResult<()>>>>> =
66    LazyLock::new(|| Mutex::new(Default::default()));
67
68/// Rust wrapper for Android audio player
69pub struct AudioPlayer {
70    /// JNI environment
71    env: JNIEnv<'static>,
72    /// Java object instance
73    this: GlobalRef,
74    /// Sample rate (Hz)
75    sample_rate: SampleRate,
76    /// Audio channel count
77    channel_count: i32,
78    /// Audio encoding format
79    audio_format: AudioEncoding,
80    /// Playback mode
81    mode: PlayerMode,
82}
83
84//noinspection SpellCheckingInspection
85#[allow(dead_code)]
86impl AudioPlayer {
87    /// Create a new AudioPlayer instance
88    ///
89    /// # Arguments
90    /// * `env` - JNI environment
91    /// * `context` - GlobalRef to context object (used to get the correct ClassLoader)
92    /// * `sample_rate` - Sample rate (e.g., SampleRate::Rate16000)
93    /// * `channel_config` - Channel configuration (ChannelOutConfig::Mono or ChannelOutConfig::Stereo)
94    /// * `audio_format` - Audio encoding format (AudioEncoding::Pcm8bit, AudioEncoding::Pcm16bit, or AudioEncoding::PcmFloat)
95    /// * `mode` - Playback mode (PlayerMode::Static or PlayerMode::Stream)
96    ///
97    /// # Returns
98    /// Returns initialized AudioPlayer instance
99    ///
100    /// # Examples
101    ///
102    /// See the README.md for complete usage examples.
103    pub fn new(
104        mut env: JNIEnv,
105        context: &GlobalRef,
106        sample_rate: SampleRate,
107        channel_config: ChannelOutConfig,
108        audio_format: AudioEncoding,
109        mode: PlayerMode,
110    ) -> PlayerResult<Self> {
111        let sample_rate_value = sample_rate.value();
112        let channel_config_value = channel_config.value();
113        let audio_format_value = audio_format.value();
114        let mode_value = mode.value();
115
116        // Load Player class using ClassLoader
117        let player_class = load_class(&mut env, context, "rust.android.Player")?;
118
119        // Create Player Java object
120        let player_obj = env.new_object(
121            &player_class,
122            "(IIII)V",
123            &[
124                sample_rate_value.into(),
125                channel_config_value.into(),
126                audio_format_value.into(),
127                mode_value.into(),
128            ],
129        )?;
130        let this = env.new_global_ref(player_obj)?;
131
132        // Initialize audio player
133        env.call_method(
134            &this,
135            "init",
136            "(IIII)V",
137            &[
138                sample_rate_value.into(),
139                channel_config_value.into(),
140                audio_format_value.into(),
141                mode_value.into(),
142            ],
143        )?;
144
145        unsafe {
146            Ok(Self {
147                env: transmute(env),
148                this,
149                sample_rate,
150                channel_count: channel_config.channel_count(),
151                audio_format,
152                mode,
153            })
154        }
155    }
156
157    fn get_env(&self) -> JNIEnv<'_> {
158        unsafe { self.env.unsafe_clone() }
159    }
160
161    /// Start playback
162    pub fn play(&self) -> PlayerResult<()> {
163        self.get_env().call_method(&self.this, "play", "()V", &[])?;
164        Ok(())
165    }
166
167    /// Stop playback
168    pub fn stop(&self) -> PlayerResult<()> {
169        self.get_env().call_method(&self.this, "stop", "()V", &[])?;
170
171        Ok(())
172    }
173
174    /// Pause playback
175    pub fn pause(&self) -> PlayerResult<()> {
176        self.get_env()
177            .call_method(&self.this, "pause", "()V", &[])?;
178        Ok(())
179    }
180
181    /// Flush buffer
182    pub fn flush(&self) -> PlayerResult<()> {
183        self.get_env()
184            .call_method(&self.this, "flush", "()V", &[])?;
185
186        Ok(())
187    }
188
189    /// Release resources
190    pub fn release(&self) -> PlayerResult<()> {
191        self.get_env()
192            .call_method(&self.this, "release", "()V", &[])?;
193
194        Ok(())
195    }
196
197    /// Write audio data asynchronously
198    ///
199    /// # Arguments
200    /// * `audio_data` - Audio data (raw PCM data)
201    ///
202    /// # Returns
203    /// * `Result<(), PlayerError>` - Returns () on success, error on failure
204    ///
205    /// # Examples
206    ///
207    /// See the README.md for complete usage examples.
208    pub async fn write(&self, audio_data: &[u8]) -> PlayerResult<()> {
209        static ID: AtomicI16 = AtomicI16::new(0);
210        let task_id = ID.fetch_add(1, Ordering::AcqRel);
211        let (tx, rx) = channel();
212
213        {
214            let mut lock = PLAYER_WRITE_CALLBACKS.lock()?;
215            lock.insert(task_id, tx);
216        }
217
218        // Create byte array
219        {
220            let jbyte_array = self.env.byte_array_from_slice(audio_data)?;
221
222            // Call Java layer's write method
223            self.get_env()
224                .call_method(
225                    &self.this,
226                    "performWrite",
227                    "(S[B)V",
228                    &[task_id.into(), (&jbyte_array).into()],
229                )?
230                .v()?;
231        }
232
233        rx.await?
234    }
235
236    /// Get sample rate
237    pub fn get_sample_rate(&self) -> i32 {
238        self.sample_rate.value()
239    }
240
241    /// Get channel count
242    pub fn get_channel_count(&self) -> i32 {
243        self.channel_count
244    }
245
246    /// Get audio format
247    pub fn get_audio_format(&self) -> i32 {
248        self.audio_format.value()
249    }
250
251    /// Get playback mode
252    pub fn get_mode(&self) -> i32 {
253        self.mode.value()
254    }
255
256    /// Get buffer size
257    pub fn get_buffer_size(&self) -> PlayerResult<i32> {
258        Ok(self
259            .get_env()
260            .call_method(&self.this, "getBufferSize", "()I", &[])?
261            .i()?)
262    }
263}
264
265unsafe impl Send for AudioPlayer {}
266unsafe impl Sync for AudioPlayer {}
267
268impl Drop for AudioPlayer {
269    fn drop(&mut self) {
270        if let Err(e) = self.release() {
271            error!(?e, "Dropping error.");
272        }
273    }
274}
275
276//noinspection SpellCheckingInspection
277/// Player write success callback
278///
279/// This function is called by the Java layer after audio data is successfully written
280///
281/// # Arguments
282/// * `env` - JNI environment
283/// * `_this` - Java object
284/// * `task_id` - Task ID
285#[allow(non_snake_case)]
286#[unsafe(no_mangle)]
287extern "C" fn Java_rust_android_Player_onWriteSuccess(_env: JNIEnv, _this: JObject, task_id: i16) {
288    let Ok(mut lock) = PLAYER_WRITE_CALLBACKS.lock() else {
289        return;
290    };
291    let Some(tx) = lock.remove(&task_id) else {
292        return;
293    };
294    drop(lock);
295
296    let _ = tx.send(Ok(()));
297}
298
299//noinspection SpellCheckingInspection
300/// Player write failure callback
301///
302/// This function is called by the Java layer after audio data write fails
303///
304/// # Arguments
305/// * `env` - JNI environment
306/// * `_this` - Java object
307/// * `task_id` - Task ID
308/// * `error_message` - Error message (string)
309#[allow(non_snake_case)]
310#[unsafe(no_mangle)]
311extern "C" fn Java_rust_android_Player_onWriteFailure(
312    mut env: JNIEnv,
313    _this: JObject,
314    task_id: i16,
315    error_message: JString,
316) {
317    let Ok(mut lock) = PLAYER_WRITE_CALLBACKS.lock() else {
318        return;
319    };
320    let Some(tx) = lock.remove(&task_id) else {
321        return;
322    };
323
324    let error_msg = match env.get_string(&error_message) {
325        Ok(msg) => msg.to_string_lossy().to_string(),
326        Err(e) => format!("Parsing error failed: {}", e),
327    };
328
329    if let Err(e) = tx.send(Err(PlayerError::Write(format!(
330        "Player write failed: {}",
331        error_msg
332    )))) {
333        error!(?e, "Sending error failed.");
334    }
335}