Optimize performance and reduce binary size
All checks were successful
Build and Release / build-and-release (push) Successful in 50s
All checks were successful
Build and Release / build-and-release (push) Successful in 50s
- Remove tokio async runtime dependency (~2MB reduction) - Optimize fuzzy search to avoid string allocations - Optimize incremental search to only rebuild tree when needed - Extract duplicate scrolling logic to helper function - Replace magic numbers with named constants - Fix terminal cleanup to run even on error - Fix context menu item count mismatch - Remove unused metadata fields (duration, codec, hash)
This commit is contained in:
395
src/main.rs
395
src/main.rs
@@ -16,8 +16,13 @@ use state::{AppState, PlayerState};
|
||||
use std::io;
|
||||
use tracing_subscriber;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
// UI update intervals and thresholds
|
||||
const METADATA_UPDATE_INTERVAL: u32 = 20; // Update metadata every N iterations (~2 seconds)
|
||||
const POLL_DURATION_STOPPED_MS: u64 = 200; // 5 FPS when stopped
|
||||
const POLL_DURATION_ACTIVE_MS: u64 = 100; // 10 FPS when playing/paused
|
||||
const DOUBLE_CLICK_MS: u128 = 500; // Double-click detection threshold
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// Initialize logging to file to avoid interfering with TUI
|
||||
let log_file = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
@@ -49,28 +54,41 @@ async fn main() -> Result<()> {
|
||||
|
||||
// Initialize player
|
||||
let mut player = player::Player::new()?;
|
||||
tracing::info!("Player initialized");
|
||||
|
||||
// Initialize app state
|
||||
let mut state = AppState::new(cache, config);
|
||||
tracing::info!("State initialized");
|
||||
|
||||
// Setup terminal
|
||||
enable_raw_mode()?;
|
||||
tracing::info!("Raw mode enabled");
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
tracing::info!("Terminal setup complete");
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
tracing::info!("Terminal created, entering main loop");
|
||||
|
||||
// Run app
|
||||
let result = run_app(&mut terminal, &mut state, &mut player).await;
|
||||
// Run app (ensure terminal cleanup even on error)
|
||||
let result = run_app(&mut terminal, &mut state, &mut player);
|
||||
|
||||
// Restore terminal
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
// Restore terminal (always run cleanup, even if result is Err)
|
||||
let cleanup_result = (|| -> Result<()> {
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
Ok(())
|
||||
})();
|
||||
|
||||
// Log cleanup errors but prioritize original error
|
||||
if let Err(e) = cleanup_result {
|
||||
tracing::error!("Terminal cleanup failed: {}", e);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
@@ -98,7 +116,6 @@ fn action_play_selection(state: &mut AppState, player: &mut player::Player) -> R
|
||||
player.play(path)?;
|
||||
// Explicitly resume playback in case MPV was paused
|
||||
player.resume()?;
|
||||
state.player_state = PlayerState::Playing;
|
||||
player.update_metadata();
|
||||
tracing::info!("Playing: {:?} (playlist: {} tracks)", path, state.playlist.len());
|
||||
}
|
||||
@@ -110,27 +127,26 @@ fn action_play_selection(state: &mut AppState, player: &mut player::Player) -> R
|
||||
}
|
||||
|
||||
fn action_toggle_play_pause(state: &mut AppState, player: &mut player::Player) -> Result<()> {
|
||||
match state.player_state {
|
||||
PlayerState::Playing => {
|
||||
player.pause()?;
|
||||
state.player_state = PlayerState::Paused;
|
||||
tracing::info!("Paused");
|
||||
}
|
||||
PlayerState::Paused => {
|
||||
player.resume()?;
|
||||
state.player_state = PlayerState::Playing;
|
||||
tracing::info!("Resumed");
|
||||
}
|
||||
PlayerState::Stopped => {
|
||||
// Restart playback from current playlist position
|
||||
if !state.playlist.is_empty() {
|
||||
state.current_file = Some(state.playlist[state.playlist_index].clone());
|
||||
state.player_state = PlayerState::Playing;
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play(path)?;
|
||||
player.resume()?;
|
||||
player.update_metadata();
|
||||
tracing::info!("Restarting playback: {:?}", path);
|
||||
if let Some(player_state) = player.get_player_state() {
|
||||
match player_state {
|
||||
PlayerState::Playing => {
|
||||
player.pause()?;
|
||||
tracing::info!("Paused");
|
||||
}
|
||||
PlayerState::Paused => {
|
||||
player.resume()?;
|
||||
tracing::info!("Resumed");
|
||||
}
|
||||
PlayerState::Stopped => {
|
||||
// Restart playback from current playlist position
|
||||
if !state.playlist.is_empty() {
|
||||
state.current_file = Some(state.playlist[state.playlist_index].clone());
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play(path)?;
|
||||
player.resume()?;
|
||||
player.update_metadata();
|
||||
tracing::info!("Restarting playback: {:?}", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,7 +156,6 @@ fn action_toggle_play_pause(state: &mut AppState, player: &mut player::Player) -
|
||||
|
||||
fn action_stop(state: &mut AppState, player: &mut player::Player) -> Result<()> {
|
||||
player.stop()?;
|
||||
state.player_state = PlayerState::Stopped;
|
||||
state.current_position = 0.0;
|
||||
state.current_duration = 0.0;
|
||||
tracing::info!("Stopped");
|
||||
@@ -149,13 +164,14 @@ fn action_stop(state: &mut AppState, player: &mut player::Player) -> Result<()>
|
||||
|
||||
fn action_remove_from_playlist(state: &mut AppState, player: &mut player::Player) -> Result<()> {
|
||||
let was_playing_removed = state.playlist_index == state.selected_playlist_index;
|
||||
let was_playing = player.get_player_state() == Some(PlayerState::Playing);
|
||||
state.remove_selected_playlist_item();
|
||||
|
||||
if state.playlist.is_empty() {
|
||||
state.player_state = PlayerState::Stopped;
|
||||
state.current_file = None;
|
||||
player.stop()?;
|
||||
} else if was_playing_removed && state.player_state == PlayerState::Playing {
|
||||
} else if was_playing_removed && was_playing && state.playlist_index < state.playlist.len() {
|
||||
// Validate index before accessing playlist
|
||||
state.current_file = Some(state.playlist[state.playlist_index].clone());
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play(path)?;
|
||||
@@ -167,40 +183,91 @@ fn action_remove_from_playlist(state: &mut AppState, player: &mut player::Player
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn action_navigate_track(state: &mut AppState, player: &mut player::Player, direction: i32) -> Result<()> {
|
||||
// direction: 1 for next, -1 for previous
|
||||
let new_index = if direction > 0 {
|
||||
state.playlist_index.saturating_add(1)
|
||||
} else {
|
||||
state.playlist_index.saturating_sub(1)
|
||||
};
|
||||
|
||||
// Validate bounds
|
||||
if state.playlist.is_empty() || new_index >= state.playlist.len() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
state.playlist_index = new_index;
|
||||
state.current_file = Some(state.playlist[state.playlist_index].clone());
|
||||
|
||||
let track_name = if direction > 0 { "Next" } else { "Previous" };
|
||||
|
||||
if let Some(player_state) = player.get_player_state() {
|
||||
match player_state {
|
||||
PlayerState::Playing => {
|
||||
// Keep playing
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play(path)?;
|
||||
player.resume()?;
|
||||
player.update_metadata();
|
||||
tracing::info!("{} track: {:?}", track_name, path);
|
||||
}
|
||||
}
|
||||
PlayerState::Paused => {
|
||||
// Load but stay paused
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play_paused(path)?;
|
||||
player.update_metadata();
|
||||
tracing::info!("{} track (paused): {:?}", track_name, path);
|
||||
}
|
||||
}
|
||||
PlayerState::Stopped => {
|
||||
// Just update current file, stay stopped
|
||||
tracing::info!("{} track selected (stopped): {:?}", track_name, state.current_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-scroll playlist to show current track (only if user is not browsing playlist)
|
||||
if !state.focus_playlist {
|
||||
state.update_playlist_scroll(20);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn action_play_from_playlist(state: &mut AppState, player: &mut player::Player, preserve_pause: bool) -> Result<()> {
|
||||
state.playlist_index = state.selected_playlist_index;
|
||||
state.current_file = Some(state.playlist[state.playlist_index].clone());
|
||||
|
||||
if preserve_pause {
|
||||
match state.player_state {
|
||||
PlayerState::Playing => {
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play(path)?;
|
||||
player.resume()?;
|
||||
player.update_metadata();
|
||||
tracing::info!("Jumped to track: {:?}", path);
|
||||
if let Some(player_state) = player.get_player_state() {
|
||||
match player_state {
|
||||
PlayerState::Playing => {
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play(path)?;
|
||||
player.resume()?;
|
||||
player.update_metadata();
|
||||
tracing::info!("Jumped to track: {:?}", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
PlayerState::Paused => {
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play(path)?;
|
||||
player.update_metadata();
|
||||
player.pause()?;
|
||||
tracing::info!("Jumped to track (paused): {:?}", path);
|
||||
PlayerState::Paused => {
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play_paused(path)?;
|
||||
player.update_metadata();
|
||||
tracing::info!("Jumped to track (paused): {:?}", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
PlayerState::Stopped => {
|
||||
state.player_state = PlayerState::Playing;
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play(path)?;
|
||||
player.resume()?;
|
||||
player.update_metadata();
|
||||
tracing::info!("Started playing track: {:?}", path);
|
||||
PlayerState::Stopped => {
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play(path)?;
|
||||
player.resume()?;
|
||||
player.update_metadata();
|
||||
tracing::info!("Started playing track: {:?}", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
state.player_state = PlayerState::Playing;
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play(path)?;
|
||||
// Explicitly resume playback in case MPV was paused
|
||||
@@ -249,7 +316,7 @@ fn handle_context_menu_action(menu_type: state::ContextMenuType, selected: usize
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_app<B: ratatui::backend::Backend>(
|
||||
fn run_app<B: ratatui::backend::Backend>(
|
||||
terminal: &mut Terminal<B>,
|
||||
state: &mut AppState,
|
||||
player: &mut player::Player,
|
||||
@@ -260,25 +327,84 @@ async fn run_app<B: ratatui::backend::Backend>(
|
||||
let mut title_bar_area = ratatui::layout::Rect::default();
|
||||
let mut file_panel_area = ratatui::layout::Rect::default();
|
||||
let mut playlist_area = ratatui::layout::Rect::default();
|
||||
let mut previous_player_state: Option<PlayerState> = None;
|
||||
|
||||
loop {
|
||||
let mut state_changed = false;
|
||||
|
||||
// Check if mpv process died (e.g., user closed video window)
|
||||
if !player.is_process_alive() && state.player_state != PlayerState::Stopped {
|
||||
state.player_state = PlayerState::Stopped;
|
||||
state.current_position = 0.0;
|
||||
state.current_duration = 0.0;
|
||||
if !player.is_process_alive() {
|
||||
if let Some(player_state) = player.get_player_state() {
|
||||
if player_state != PlayerState::Stopped {
|
||||
state.current_position = 0.0;
|
||||
state.current_duration = 0.0;
|
||||
state_changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Always update properties to keep state synchronized with MPV
|
||||
player.update_properties();
|
||||
|
||||
// Only proceed if we can successfully query player state
|
||||
let Some(player_state) = player.get_player_state() else {
|
||||
// Can't get state from MPV, skip this iteration
|
||||
if event::poll(std::time::Duration::from_millis(100))? {
|
||||
match event::read()? {
|
||||
Event::Key(key) => {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
handle_key_event(terminal, state, player, key)?;
|
||||
needs_redraw = true;
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse) => {
|
||||
handle_mouse_event(state, mouse, title_bar_area, file_panel_area, playlist_area, player)?;
|
||||
needs_redraw = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
};
|
||||
|
||||
// Check if track ended and play next
|
||||
// When MPV finishes playing a file, it goes to idle (Stopped state)
|
||||
// Detect Playing → Stopped transition = track ended, play next
|
||||
if previous_player_state == Some(PlayerState::Playing)
|
||||
&& player_state == PlayerState::Stopped
|
||||
{
|
||||
let should_continue = state.play_next();
|
||||
// play_next() returns true if should continue playing, false if should stop
|
||||
if should_continue {
|
||||
if let Some(ref path) = state.current_file {
|
||||
// Reset position/duration before playing new track
|
||||
state.current_position = 0.0;
|
||||
state.current_duration = 0.0;
|
||||
last_position = 0.0;
|
||||
|
||||
player.play(path)?;
|
||||
player.resume()?;
|
||||
}
|
||||
// Update metadata immediately when track changes
|
||||
player.update_metadata();
|
||||
metadata_update_counter = 0;
|
||||
// Auto-scroll playlist to show current track (only if user is not browsing playlist)
|
||||
if !state.focus_playlist {
|
||||
let playlist_visible_height = playlist_area.height.saturating_sub(2) as usize;
|
||||
state.update_playlist_scroll(playlist_visible_height);
|
||||
}
|
||||
} else {
|
||||
// Reached end of playlist in Normal mode - stop playback
|
||||
player.stop()?;
|
||||
}
|
||||
state_changed = true;
|
||||
}
|
||||
|
||||
// Only update properties when playing or paused (not when stopped)
|
||||
if state.player_state != PlayerState::Stopped {
|
||||
player.update_properties();
|
||||
|
||||
// Update metadata only every 20 iterations (~2 seconds) to reduce IPC calls
|
||||
// Only update metadata and track playback when not stopped
|
||||
if player_state != PlayerState::Stopped {
|
||||
// Update metadata periodically to reduce IPC calls
|
||||
metadata_update_counter += 1;
|
||||
if metadata_update_counter >= 20 {
|
||||
if metadata_update_counter >= METADATA_UPDATE_INTERVAL {
|
||||
player.update_metadata();
|
||||
metadata_update_counter = 0;
|
||||
state_changed = true;
|
||||
@@ -301,36 +427,11 @@ async fn run_app<B: ratatui::backend::Backend>(
|
||||
state.current_duration = new_duration;
|
||||
state_changed = true;
|
||||
}
|
||||
|
||||
// Check if track ended and play next (but only if track was actually loaded AND played)
|
||||
// Require position > 0.5 to ensure track actually started playing (not just loaded)
|
||||
// Also check !is_paused to avoid triggering during pause/unpause transitions
|
||||
if player.is_idle() && !player.is_paused() && state.player_state == PlayerState::Playing && state.current_duration > 0.0 && state.current_position > 0.5 {
|
||||
state.play_next();
|
||||
// play_next() handles the play mode and may stop if in Normal mode at end
|
||||
if state.player_state == PlayerState::Playing {
|
||||
if let Some(ref path) = state.current_file {
|
||||
// Reset position/duration before playing new track
|
||||
state.current_position = 0.0;
|
||||
state.current_duration = 0.0;
|
||||
last_position = 0.0;
|
||||
|
||||
player.play(path)?;
|
||||
player.resume()?;
|
||||
}
|
||||
// Update metadata immediately when track changes
|
||||
player.update_metadata();
|
||||
metadata_update_counter = 0;
|
||||
// Auto-scroll playlist to show current track (only if user is not browsing playlist)
|
||||
if !state.focus_playlist {
|
||||
let playlist_visible_height = playlist_area.height.saturating_sub(2) as usize;
|
||||
state.update_playlist_scroll(playlist_visible_height);
|
||||
}
|
||||
}
|
||||
state_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Save current state for next iteration
|
||||
previous_player_state = Some(player_state);
|
||||
|
||||
// Only redraw if something changed or forced
|
||||
if needs_redraw || state_changed {
|
||||
terminal.draw(|f| {
|
||||
@@ -343,17 +444,17 @@ async fn run_app<B: ratatui::backend::Backend>(
|
||||
}
|
||||
|
||||
// Poll for events - use longer timeout when stopped to reduce CPU
|
||||
let poll_duration = if state.player_state == PlayerState::Stopped {
|
||||
std::time::Duration::from_millis(200) // 5 FPS when stopped
|
||||
let poll_duration = if player_state == PlayerState::Stopped {
|
||||
std::time::Duration::from_millis(POLL_DURATION_STOPPED_MS)
|
||||
} else {
|
||||
std::time::Duration::from_millis(100) // 10 FPS when playing/paused
|
||||
std::time::Duration::from_millis(POLL_DURATION_ACTIVE_MS)
|
||||
};
|
||||
|
||||
if event::poll(poll_duration)? {
|
||||
match event::read()? {
|
||||
Event::Key(key) => {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
handle_key_event(terminal, state, player, key).await?;
|
||||
handle_key_event(terminal, state, player, key)?;
|
||||
needs_redraw = true; // Force redraw after key event
|
||||
}
|
||||
}
|
||||
@@ -373,7 +474,7 @@ async fn run_app<B: ratatui::backend::Backend>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_key_event<B: ratatui::backend::Backend>(terminal: &mut Terminal<B>, state: &mut AppState, player: &mut player::Player, key: KeyEvent) -> Result<()> {
|
||||
fn handle_key_event<B: ratatui::backend::Backend>(terminal: &mut Terminal<B>, state: &mut AppState, player: &mut player::Player, key: KeyEvent) -> Result<()> {
|
||||
// Handle confirmation popup
|
||||
if state.show_refresh_confirm {
|
||||
match key.code {
|
||||
@@ -457,7 +558,7 @@ async fn handle_key_event<B: ratatui::backend::Backend>(terminal: &mut Terminal<
|
||||
let max_items = match menu.menu_type {
|
||||
ContextMenuType::FilePanel => 2,
|
||||
ContextMenuType::Playlist => 2,
|
||||
ContextMenuType::TitleBar => 4,
|
||||
ContextMenuType::TitleBar => 3,
|
||||
};
|
||||
if menu.selected_index < max_items - 1 {
|
||||
menu.selected_index += 1;
|
||||
@@ -519,81 +620,11 @@ async fn handle_key_event<B: ratatui::backend::Backend>(terminal: &mut Terminal<
|
||||
}
|
||||
(KeyCode::Char('J'), KeyModifiers::SHIFT) => {
|
||||
// Next track
|
||||
if !state.playlist.is_empty() && state.playlist_index + 1 < state.playlist.len() {
|
||||
state.playlist_index += 1;
|
||||
// Validate index before accessing playlist
|
||||
if state.playlist_index < state.playlist.len() {
|
||||
state.current_file = Some(state.playlist[state.playlist_index].clone());
|
||||
|
||||
match state.player_state {
|
||||
PlayerState::Playing => {
|
||||
// Keep playing
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play(path)?;
|
||||
player.resume()?;
|
||||
player.update_metadata(); // Update metadata immediately
|
||||
tracing::info!("Next track: {:?}", path);
|
||||
}
|
||||
}
|
||||
PlayerState::Paused => {
|
||||
// Load but stay paused
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play(path)?;
|
||||
player.update_metadata(); // Update metadata immediately
|
||||
player.pause()?;
|
||||
tracing::info!("Next track (paused): {:?}", path);
|
||||
}
|
||||
}
|
||||
PlayerState::Stopped => {
|
||||
// Just update current file, stay stopped
|
||||
tracing::info!("Next track selected (stopped): {:?}", state.current_file);
|
||||
}
|
||||
}
|
||||
// Auto-scroll playlist to show current track (only if user is not browsing playlist)
|
||||
if !state.focus_playlist {
|
||||
state.update_playlist_scroll(20);
|
||||
}
|
||||
}
|
||||
}
|
||||
action_navigate_track(state, player, 1)?;
|
||||
}
|
||||
(KeyCode::Char('K'), KeyModifiers::SHIFT) => {
|
||||
// Previous track
|
||||
if !state.playlist.is_empty() && state.playlist_index > 0 {
|
||||
state.playlist_index -= 1;
|
||||
// Validate index before accessing playlist
|
||||
if state.playlist_index < state.playlist.len() {
|
||||
state.current_file = Some(state.playlist[state.playlist_index].clone());
|
||||
|
||||
match state.player_state {
|
||||
PlayerState::Playing => {
|
||||
// Keep playing
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play(path)?;
|
||||
player.resume()?;
|
||||
player.update_metadata(); // Update metadata immediately
|
||||
tracing::info!("Previous track: {:?}", path);
|
||||
}
|
||||
}
|
||||
PlayerState::Paused => {
|
||||
// Load but stay paused
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play(path)?;
|
||||
player.update_metadata(); // Update metadata immediately
|
||||
player.pause()?;
|
||||
tracing::info!("Previous track (paused): {:?}", path);
|
||||
}
|
||||
}
|
||||
PlayerState::Stopped => {
|
||||
// Just update current file, stay stopped
|
||||
tracing::info!("Previous track selected (stopped): {:?}", state.current_file);
|
||||
}
|
||||
}
|
||||
// Auto-scroll playlist to show current track (only if user is not browsing playlist)
|
||||
if !state.focus_playlist {
|
||||
state.update_playlist_scroll(20);
|
||||
}
|
||||
}
|
||||
}
|
||||
action_navigate_track(state, player, -1)?;
|
||||
}
|
||||
(KeyCode::Char('d'), KeyModifiers::CONTROL) => {
|
||||
if state.focus_playlist {
|
||||
@@ -683,13 +714,13 @@ async fn handle_key_event<B: ratatui::backend::Backend>(terminal: &mut Terminal<
|
||||
action_toggle_play_pause(state, player)?;
|
||||
}
|
||||
(KeyCode::Char('H'), KeyModifiers::SHIFT) => {
|
||||
if state.player_state != PlayerState::Stopped {
|
||||
if player.get_player_state() != Some(PlayerState::Stopped) {
|
||||
player.seek(-10.0)?;
|
||||
tracing::info!("Seek backward 10s");
|
||||
}
|
||||
}
|
||||
(KeyCode::Char('L'), KeyModifiers::SHIFT) => {
|
||||
if state.player_state != PlayerState::Stopped {
|
||||
if player.get_player_state() != Some(PlayerState::Stopped) {
|
||||
player.seek(10.0)?;
|
||||
tracing::info!("Seek forward 10s");
|
||||
}
|
||||
@@ -728,7 +759,7 @@ fn handle_mouse_event(state: &mut AppState, mouse: MouseEvent, title_bar_area: r
|
||||
let items = match menu.menu_type {
|
||||
ContextMenuType::FilePanel => 2,
|
||||
ContextMenuType::Playlist => 2,
|
||||
ContextMenuType::TitleBar => 4,
|
||||
ContextMenuType::TitleBar => 3,
|
||||
};
|
||||
let popup_width = 13;
|
||||
let popup_height = items as u16 + 2; // +2 for borders
|
||||
@@ -897,7 +928,7 @@ fn handle_mouse_event(state: &mut AppState, mouse: MouseEvent, title_bar_area: r
|
||||
let now = std::time::Instant::now();
|
||||
let is_double_click = if let (Some(last_time), Some(last_idx), false) =
|
||||
(state.last_click_time, state.last_click_index, state.last_click_is_playlist) {
|
||||
last_idx == clicked_index && now.duration_since(last_time).as_millis() < 500
|
||||
last_idx == clicked_index && now.duration_since(last_time).as_millis() < DOUBLE_CLICK_MS
|
||||
} else {
|
||||
false
|
||||
};
|
||||
@@ -958,7 +989,7 @@ fn handle_mouse_event(state: &mut AppState, mouse: MouseEvent, title_bar_area: r
|
||||
let now = std::time::Instant::now();
|
||||
let is_double_click = if let (Some(last_time), Some(last_idx), true) =
|
||||
(state.last_click_time, state.last_click_index, state.last_click_is_playlist) {
|
||||
last_idx == actual_track && now.duration_since(last_time).as_millis() < 500
|
||||
last_idx == actual_track && now.duration_since(last_time).as_millis() < DOUBLE_CLICK_MS
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user