Skip to content

Commit

Permalink
add start time option to sync playback with other players
Browse files Browse the repository at this point in the history
  • Loading branch information
Peacockli committed Oct 30, 2024
1 parent 3bffd11 commit fddd388
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 8 deletions.
71 changes: 71 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ indicatif = "0.17.8"
indicatif-log-bridge = "0.2.3"
tabled = "0.16.0"
rusqlite = { version = "0.32.1", features = ["bundled"] }
chrono = "0.4.38"

[features]
default = []
Expand Down
49 changes: 43 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use std::{fs, io::stdin, path::Path, path::PathBuf, process::exit};
use tabled::{builder::Builder, settings::Style};
use webfishing_player::{PlayerSettings, WebfishingPlayer};
use xcap::Window;
use chrono::{Local, NaiveTime, Timelike};

const MIDI_DIR: &str = "./midi";
const WINDOW_NAMES: [&str; 3] = ["steam_app_3146520", "Fish! (On the WEB!)", "Godot_Engine"];
Expand Down Expand Up @@ -83,7 +84,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
};

let (should_sing, loop_midi, add_another_song, playback_speed) = get_user_options(&theme)?;
let (should_sing, loop_midi, add_another_song, playback_speed, start_time) = get_user_options(&theme)?;

let mut sing_above: u8 = 60;
if should_sing {
Expand All @@ -95,7 +96,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}

// Add the selected song to the queue
let mut settings = match PlayerSettings::new(midi_data, loop_midi, should_sing, sing_above, playback_speed) {
let mut settings = match PlayerSettings::new(midi_data, loop_midi, should_sing, sing_above, playback_speed, start_time) {
Ok(settings) => settings,
Err(e) => {
error!("Failed to parse MIDI data: {}", e);
Expand All @@ -116,7 +117,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {

// Play all songs in the queue
for (index, settings) in song_queue.into_iter().enumerate() {
let is_first_song = index == 0;
let is_first_song = index == 0 && !settings.start_time.is_some();

let mut player = match WebfishingPlayer::new(
settings,
Expand Down Expand Up @@ -148,12 +149,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}

fn get_user_options(theme: &ColorfulTheme) -> Result<(bool, bool, bool, f64), dialoguer::Error> {
fn get_user_options(theme: &ColorfulTheme) -> Result<(bool, bool, bool, f64, Option<u64>), dialoguer::Error> {
let options = vec![
"Sing along",
"Loop the song",
"Queue another song",
"Set playback speed",
"Set start time",
];

let selected_options = MultiSelect::with_theme(theme)
Expand All @@ -165,7 +167,9 @@ fn get_user_options(theme: &ColorfulTheme) -> Result<(bool, bool, bool, f64), di
let loop_midi = selected_options.contains(&1);
let add_another_song = selected_options.contains(&2);
let mut playback_speed = 1.0;
let mut start_time: Option<u64> = None;

// Playback speed
if selected_options.contains(&3) {
let speed_input = Input::with_theme(theme)
.with_prompt("Enter playback speed:")
Expand All @@ -175,6 +179,39 @@ fn get_user_options(theme: &ColorfulTheme) -> Result<(bool, bool, bool, f64), di
playback_speed = speed_input.trim().parse().unwrap_or(1.0);
}

// Start time
if selected_options.contains(&4) {
// Get the next whole minute to use as default
let now = Local::now();
let next_minute = now + chrono::Duration::seconds(60 - now.second() as i64);
let default_time = next_minute.format("%H:%M:%S").to_string();
let time_input = Input::with_theme(theme)
.with_prompt("Enter start time (HH:MM:SS):")
.default(default_time) // Set the default to the next whole minute
.interact_text()?;

if let Ok(naive_time) = NaiveTime::parse_from_str(&time_input, "%H:%M:%S") {
let current_date = Local::now().date();
let start_datetime = current_date.and_time(naive_time);
start_time = Some(start_datetime.expect("idk what to put here but otherwise it doesn't work").timestamp() as u64 * 1000);

let delay_input = Input::with_theme(theme)
.with_prompt("Enter delay in ms to account for latency (Ping to host):")
.default("0".to_string())
.interact_text()?;

if let Ok(delay) = delay_input.trim().parse::<u64>() {
if let Some(existing_start_time) = start_time {
start_time = Some(existing_start_time + delay);
}
} else {
println!("Invalid delay input. No delay will be added.");
}
} else {
println!("Invalid time format. Please use HH:MM:SS.");
}
}

// Check for conflicting options
if loop_midi && add_another_song {
let confirm = dialoguer::Confirm::with_theme(theme)
Expand All @@ -183,13 +220,13 @@ fn get_user_options(theme: &ColorfulTheme) -> Result<(bool, bool, bool, f64), di
.interact()?;

if confirm {
return Ok((should_sing, true, false, playback_speed))
return Ok((should_sing, true, false, playback_speed, start_time))
} else {
return get_user_options(theme);
}
}

Ok((should_sing, loop_midi, add_another_song, playback_speed))
Ok((should_sing, loop_midi, add_another_song, playback_speed, start_time))
}

fn get_window(name: &str) -> Option<Window> {
Expand Down
33 changes: 31 additions & 2 deletions src/webfishing_player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use std::{
Arc,
},
thread::sleep,
time::{Duration, Instant},
time::{Duration, Instant, SystemTime},
};
use xcap::Window;

Expand Down Expand Up @@ -92,10 +92,11 @@ pub struct PlayerSettings<'a> {
pub sing_above: u8,
pub tracks: Option<Vec<usize>>,
pub playback_speed: f64,
pub start_time: Option<u64>,
}

impl<'a> PlayerSettings<'a> {
pub fn new(midi_data: Vec<u8>, loop_midi: bool, should_sing: bool, sing_above: u8, playback_speed: f64) -> Result<Self, midly::Error> {
pub fn new(midi_data: Vec<u8>, loop_midi: bool, should_sing: bool, sing_above: u8, playback_speed: f64, start_time: Option<u64>) -> Result<Self, midly::Error> {
let smf = Smf::parse(&midi_data)?;
// This is safe because we keep midi_data & smf alive in the struct
let smf = unsafe { std::mem::transmute::<Smf<'_>, Smf<'a>>(smf) };
Expand All @@ -108,6 +109,7 @@ impl<'a> PlayerSettings<'a> {
sing_above,
tracks: None,
playback_speed,
start_time,
})
}
}
Expand All @@ -129,6 +131,7 @@ pub struct WebfishingPlayer<'a> {
sing_above: u8,
tracks: Vec<usize>,
playback_speed: f64,
start_time: Option<u64>,
multi: &'a MultiProgress,
paused: Arc<AtomicBool>,
song_elapsed_micros: Arc<AtomicU64>,
Expand Down Expand Up @@ -187,6 +190,7 @@ impl<'a> WebfishingPlayer<'a> {
sing_above: settings.sing_above,
tracks: settings.tracks.unwrap_or(Vec::new()),
playback_speed: settings.playback_speed,
start_time: settings.start_time,
multi,
paused: Arc::new(AtomicBool::new(false)),
song_elapsed_micros: Arc::new(AtomicU64::new(0)),
Expand Down Expand Up @@ -325,13 +329,38 @@ impl<'a> WebfishingPlayer<'a> {
if self.wait_for_user {
// Attempt to press space in-case the user's OS requires a permission pop-up for input
self.enigo.key(Key::Space, Click).unwrap();

#[cfg(feature = "silent_input")]
println!("Press backspace to start playing");
#[cfg(not(feature = "silent_input"))]
println!("Tab over to the game and press backspace to start playing");
loop {
if device_state.get_keys().contains(&Keycode::Backspace) {
break;
}
}
}
else {
// Wait to start at a certain timestamp if provided
if let Some(start_time) = self.start_time {
let current_time = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("Time went backwards")
.as_millis();

if u128::from(start_time) > current_time {
let wait_duration = Duration::from_millis((start_time as u128 - current_time) as u64);
let wait_seconds = wait_duration.as_secs();

#[cfg(feature = "silent_input")]
println!("Starting playback in {} seconds...", wait_seconds);
#[cfg(not(feature = "silent_input"))]
println!("Tab over to the game, starting playback in {} seconds...", wait_seconds);

std::thread::sleep(wait_duration);
}
}
}

// Reset the guitar to all open string
self.set_fret(6, 0);
Expand Down

0 comments on commit fddd388

Please sign in to comment.