android auto: one engine per process, actually — init was still spawning its own
`headless.rs` was written around a guarantee its own doc comment made and the code did not keep: `playback::init` still called `Player::spawn_with_device` directly. So a car could start a cold process, play a record into it, and the moment the app's Activity came up the process built a SECOND player — two AAudio streams out of one app, playing over each other. That is precisely the failure the module exists to prevent, and nothing would have reported it but Kyle's ears. On Android `init` now takes the process's engine instead. The event receiver is single-consumer and the headless pump already owns it, so the UI SUBSCRIBES to the pump rather than reading the engine directly; and the fallback queue is cleared on the way in, because from that point the JS decides what plays next and two things advancing one engine would skip a track each time. The desktop path is untouched — it still spawns its own, because there is nothing else in that process to share with. Also here: a log line on the transport's routing decision (`auto playWhenReady=… headless=…` and `auto transport …`), added while chasing a pause that turned out to be working. It is kept because the question "did this button go to Rust or to a WebView that is not there" has now come up three times and is invisible without it. The pause was real, and the measurement was not. "One active AAudio track after a pause" proves nothing — a paused sink still shows as active. The honest test is that the session reports PAUSED(2) and the position stops moving, which it does: 24873 ms, unchanged five seconds later.
This commit is contained in:
1 parent
f59a2d773e
commit
062e1845e0
4 files changed
+56
No files matched your search
+4
@@ -252,6 +252,10 @@ class SoulStreamPlayer(private val ctx: Context) : SimpleBasePlayer(Looper.getMa
|
||||
/* ── the transport, back out to the WebView ──────────────────────── */
|
||||
|
||||
override fun handleSetPlayWhenReady(playWhenReady: Boolean): ListenableFuture<*> {
|
||||
android.util.Log.i(
|
||||
"SoulStream",
|
||||
"auto playWhenReady=" + playWhenReady + " headless=" + headless(),
|
||||
)
|
||||
if (headless()) {
|
||||
AutoNative.transport(if (playWhenReady) "resume" else "pause")
|
||||
return Futures.immediateVoidFuture()
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
android auto: one engine per process, actually — init was still spawning its own
|
||||
|
||||
`headless.rs` was written around a guarantee its own doc comment made and the
|
||||
code did not keep: `playback::init` still called `Player::spawn_with_device`
|
||||
directly. So a car could start a cold process, play a record into it, and the
|
||||
moment the app's Activity came up the process built a SECOND player — two
|
||||
AAudio streams out of one app, playing over each other. That is precisely the
|
||||
failure the module exists to prevent, and nothing would have reported it but
|
||||
Kyle's ears.
|
||||
|
||||
On Android `init` now takes the process's engine instead. The event receiver is
|
||||
single-consumer and the headless pump already owns it, so the UI SUBSCRIBES to
|
||||
the pump rather than reading the engine directly; and the fallback queue is
|
||||
cleared on the way in, because from that point the JS decides what plays next
|
||||
and two things advancing one engine would skip a track each time. The desktop
|
||||
path is untouched — it still spawns its own, because there is nothing else in
|
||||
that process to share with.
|
||||
|
||||
Also here: a log line on the transport's routing decision
|
||||
(`auto playWhenReady=… headless=…` and `auto transport …`), added while
|
||||
chasing a pause that turned out to be working. It is kept because the question
|
||||
"did this button go to Rust or to a WebView that is not there" has now come up
|
||||
three times and is invisible without it.
|
||||
|
||||
The pause was real, and the measurement was not. "One active AAudio track after
|
||||
a pause" proves nothing — a paused sink still shows as active. The honest test
|
||||
is that the session reports PAUSED(2) and the position stops moving, which it
|
||||
does: 24873 ms, unchanged five seconds later.
|
||||
@@ -619,6 +619,7 @@ pub extern "system" fn Java_com_soulstream_mediasession_AutoNative_transport(
|
||||
return;
|
||||
};
|
||||
let audio = crate::headless::audio(None);
|
||||
warn(&format!("auto transport {a}"));
|
||||
match a.as_str() {
|
||||
"pause" => audio.player.pause(),
|
||||
"resume" => audio.player.resume(),
|
||||
|
||||
@@ -186,6 +186,29 @@ enum PlayerUiEvent {
|
||||
/// Create the audio thread and forward its events to the webview.
|
||||
pub fn init(app: &tauri::AppHandle) {
|
||||
let output_device = crate::config::load(app).audio_output_device;
|
||||
|
||||
/* ANDROID TAKES THE PROCESS'S ONE ENGINE, it does not make a second.
|
||||
A car can start this app by binding the media service, play a record
|
||||
into a process with no Activity, and have the user open the app on top
|
||||
of it minutes later — and `Player::spawn` opens an output stream, so a
|
||||
second one here is two AAudio streams playing over each other out of one
|
||||
process. `headless::audio` builds it on first use, whoever gets there
|
||||
first, and hands back the same one afterwards.
|
||||
|
||||
The engine's event receiver is single-consumer and the headless pump
|
||||
already owns it, so the UI subscribes rather than reading it directly;
|
||||
and the fallback queue is cleared, because from here the JS decides what
|
||||
plays next and two things advancing one engine would skip a track each
|
||||
time. Nothing about the desktop changes. */
|
||||
#[cfg(target_os = "android")]
|
||||
let (player, events) = {
|
||||
let audio = crate::headless::audio(output_device.clone());
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
audio.subscribe(tx);
|
||||
audio.clear_queue();
|
||||
(audio.player.clone(), rx)
|
||||
};
|
||||
#[cfg(not(target_os = "android"))]
|
||||
let (player, events) = Player::spawn_with_device(output_device);
|
||||
app.manage(PlayerState {
|
||||
player,
|
||||
|
||||
Reference in new issue
Block a user