fix(player): return from background audio onto the episode it advanced to
An episode that ends while backgrounded in audio-only mode advances in the
backend, but player_exit_background_audio returned only a position, so the
video page reloaded the episode it was mounted with -- the previous one, at
the new episode's timestamp.
The command now returns BackgroundAudioResume { itemId, positionSeconds }.
planHandoffReturn yields "other-item" when the id differs from the mounted
one, and the player page navigates to that episode with resumeAt=<seconds>,
marking the outgoing episode watched and suppressing its stale stop report.
TRACES: UR-040, UR-023 | DR-296 | UT-265, UT-266
This commit is contained in:
@@ -939,12 +939,17 @@ pub async fn player_background_action(
|
||||
Ok(action)
|
||||
}
|
||||
|
||||
/// TRACES: UR-040 | DR-052 | UT-061, IT-013
|
||||
/// Returns the item the native player is on and its absolute position. The
|
||||
/// item matters: an episode that ended while backgrounded has already advanced
|
||||
/// in the backend, so reloading the video the webview was mounted with would
|
||||
/// bring back the previous episode. (DR-296)
|
||||
///
|
||||
/// TRACES: UR-040, UR-023 | DR-052, DR-296 | UT-061, IT-013
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_exit_background_audio(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
) -> Result<f64, String> {
|
||||
) -> Result<crate::player::BackgroundAudioResume, String> {
|
||||
let controller = player.0.lock().await;
|
||||
|
||||
// Read the position BEFORE clearing either base. The position tick applies the
|
||||
@@ -954,23 +959,23 @@ pub async fn player_exit_background_audio(
|
||||
// lock discipline from CLAUDE.md: never hold work across a re-entrant call.
|
||||
// (DR-159)
|
||||
//
|
||||
// `absolute_position` rather than `position`, because a tick that has not
|
||||
// `background_audio_resume` reads `absolute_position` rather than `position`, because a tick that has not
|
||||
// landed *yet* is the same hazard from the other side: returning to the
|
||||
// foreground while the audio-only transcode is still opening read 0.0, and
|
||||
// the video reloaded at StartTimeTicks=0 — the episode restarting from the
|
||||
// beginning. Flooring at the handoff base cannot overshoot: the stream is
|
||||
// physically incapable of being behind its own starting point. (DR-178)
|
||||
let absolute = controller.absolute_position();
|
||||
let resume = controller.background_audio_resume();
|
||||
|
||||
// Now safe to tear the handoff down, native side first.
|
||||
let _ = crate::player::set_lockscreen_position_offset(0.0);
|
||||
controller.exit_background_audio();
|
||||
controller.stop().map_err(|e| e.to_string())?;
|
||||
info!(
|
||||
"player_exit_background_audio: resuming the video at {:.1}s",
|
||||
absolute
|
||||
"player_exit_background_audio: resuming {:?} at {:.1}s",
|
||||
resume.item_id, resume.position_seconds
|
||||
);
|
||||
Ok(absolute)
|
||||
Ok(resume)
|
||||
}
|
||||
|
||||
/// Play a queue of media items
|
||||
|
||||
Reference in New Issue
Block a user