improvements to the sleep timer
🏗️ Build and Test JellyTau / Run Tests (push) Failing after 14s
Traceability Validation / Check Requirement Traces (push) Failing after 2s
🏗️ Build and Test JellyTau / Build Android APK (push) Has been skipped

This commit is contained in:
2026-02-28 20:33:22 +01:00
parent e8e37649fa
commit c5be9eb18c
19 changed files with 475 additions and 57 deletions
+80 -2
View File
@@ -44,7 +44,7 @@ pub use android::{
set_media_command_handler, set_remote_volume_handler, get_detected_codecs,
};
use log::{debug, warn};
use log::{debug, error, warn};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::sync::Mutex as TokioMutex;
@@ -86,6 +86,9 @@ pub struct PlayerController {
// End reason tracking for autoplay decision making
end_reason: Arc<Mutex<Option<EndReason>>>,
// Auto-play episode counter (session-based, resets on manual play)
autoplay_episode_count: Arc<Mutex<u32>>,
}
impl PlayerController {
@@ -107,6 +110,7 @@ impl PlayerController {
playback_reporter,
position_throttler,
end_reason: Arc::new(Mutex::new(None)),
autoplay_episode_count: Arc::new(Mutex::new(0)),
};
// Start background timer thread for sleep timer countdown
@@ -161,11 +165,38 @@ impl PlayerController {
self.end_reason.lock().unwrap().take()
}
/// Increment autoplay episode counter. Returns true if limit is reached.
fn increment_autoplay_count(&self) -> bool {
let max = self.autoplay_settings.lock().unwrap().max_episodes;
if max == 0 {
// Unlimited
return false;
}
let mut count = self.autoplay_episode_count.lock().unwrap();
*count += 1;
debug!("[PlayerController] Autoplay episode count: {}/{}", *count, max);
*count >= max
}
/// Reset autoplay episode counter (called on manual play actions)
fn reset_autoplay_count(&self) {
let mut count = self.autoplay_episode_count.lock().unwrap();
if *count > 0 {
debug!("[PlayerController] Resetting autoplay episode counter (was {})", *count);
}
*count = 0;
}
/// Load and play a single item (also sets the queue to contain only this item)
pub fn play_item(&self, item: MediaItem) -> Result<(), PlayerError> {
debug!("[PlayerController] play_item: {}", item.title);
// Reset autoplay counter on manual play
self.reset_autoplay_count();
// Update queue with this single item
{
let mut queue = self.queue.lock().unwrap();
@@ -262,6 +293,9 @@ impl PlayerController {
pub fn play_queue(&self, items: Vec<MediaItem>, start_index: usize) -> Result<(), PlayerError> {
debug!("[PlayerController] play_queue: {} items, starting at index {}", items.len(), start_index);
// Reset autoplay counter on manual queue start
self.reset_autoplay_count();
{
let mut queue = self.queue.lock().unwrap();
queue.set_queue(items, start_index);
@@ -374,6 +408,9 @@ impl PlayerController {
/// Note: load_and_play sets EndReason::NewTrackLoaded to prevent autoplay
/// from triggering when the current track's EndFile event fires
pub fn next(&self) -> Result<(), PlayerError> {
// Reset autoplay counter on manual skip
self.reset_autoplay_count();
let next_item = {
let mut queue = self.queue.lock().unwrap();
queue.next().cloned()
@@ -394,6 +431,8 @@ impl PlayerController {
/// Note: load_and_play sets EndReason::NewTrackLoaded to prevent autoplay
/// from triggering when the current track's EndFile event fires
pub fn previous(&self) -> Result<(), PlayerError> {
// Reset autoplay counter on manual skip
self.reset_autoplay_count();
// If we're more than 3 seconds in, restart current track
{
let backend = self.backend.lock().unwrap();
@@ -544,6 +583,7 @@ impl PlayerController {
fn start_timer_thread(&self) {
let sleep_timer = self.sleep_timer.clone();
let event_emitter = self.event_emitter.clone();
let backend = self.backend.clone();
std::thread::spawn(move || {
loop {
@@ -553,6 +593,27 @@ impl PlayerController {
if timer.is_active() {
timer.update_remaining_seconds();
// Time-based timer expired: stop playback
if matches!(timer.mode, SleepTimerMode::Time { .. }) && timer.remaining_seconds == 0 {
debug!("[SleepTimer] Time-based timer expired, stopping playback");
timer.cancel();
// Emit cancelled state
if let Some(emitter) = event_emitter.lock().unwrap().as_ref() {
emitter.emit(PlayerStatusEvent::SleepTimerChanged {
mode: SleepTimerMode::Off,
remaining_seconds: 0,
});
}
drop(timer);
// Stop the backend
if let Err(e) = backend.lock().unwrap().stop() {
error!("[SleepTimer] Failed to stop playback: {}", e);
}
continue;
}
// Emit update event
if let Some(emitter) = event_emitter.lock().unwrap().as_ref() {
emitter.emit(PlayerStatusEvent::SleepTimerChanged {
@@ -673,6 +734,16 @@ impl PlayerController {
};
match &timer_mode {
SleepTimerMode::Time { end_time } => {
// If time has expired, stop instead of playing next
let now = chrono::Utc::now().timestamp_millis();
if now >= *end_time {
debug!("[PlayerController] Time-based sleep timer expired at track boundary");
self.sleep_timer.lock().unwrap().cancel();
self.emit_sleep_timer_changed();
return Ok(AutoplayDecision::Stop);
}
}
SleepTimerMode::EndOfTrack => {
// Stop at end of track
self.sleep_timer.lock().unwrap().cancel();
@@ -702,11 +773,18 @@ impl PlayerController {
if current.media_type == MediaType::Video && self.is_episode_item(&current).await {
if let Some(next_ep) = self.fetch_next_episode_for_item(&current).await? {
let settings = self.autoplay_settings.lock().unwrap().clone();
// Check if auto-play episode limit is reached
let limit_reached = self.increment_autoplay_count();
if limit_reached {
debug!("[PlayerController] Auto-play episode limit reached ({} episodes)", settings.max_episodes);
}
return Ok(AutoplayDecision::ShowNextEpisodePopup {
current_episode: next_ep.0, // Repository MediaItem
next_episode: next_ep.1,
countdown_seconds: settings.countdown_seconds,
auto_advance: settings.enabled,
auto_advance: settings.enabled && !limit_reached,
});
}
// No next episode found