Baseline: full tree snapshot after git repository loss
The previous .git directory was found gutted (only info/ remained), so history is unrecoverable locally. This commit restores version control with the tree exactly as it stands: the working app, the preview-tracks launcher on the artist hero, the scrollable-track-previews design bundle (docs + approved mockups), and the Phase 0 validation artifacts.
This commit is contained in:
commit
a667b4b28e
161 files changed
+53170
No files matched your search
@@ -0,0 +1,41 @@
|
||||
//! Local playback smoke test: play a file, seek, report positions.
|
||||
//!
|
||||
//! cargo run -p player --example play -- "C:\path\to\file.flac"
|
||||
|
||||
use player::{Player, PlayerEvent};
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")).init();
|
||||
let path = std::env::args().nth(1).expect("usage: play <file>");
|
||||
|
||||
let (player, events) = Player::spawn();
|
||||
player.play_local(&path, None);
|
||||
|
||||
let mut last_pos = 0.0;
|
||||
let start = std::time::Instant::now();
|
||||
let mut seeked = false;
|
||||
|
||||
while start.elapsed() < Duration::from_secs(12) {
|
||||
match events.recv_timeout(Duration::from_millis(500)) {
|
||||
Ok(PlayerEvent::Playing) => println!("PLAYING"),
|
||||
Ok(PlayerEvent::Position { secs, duration }) => {
|
||||
last_pos = secs;
|
||||
println!("pos={secs:.1} dur={duration:?}");
|
||||
if secs > 3.0 && !seeked {
|
||||
seeked = true;
|
||||
println!("SEEK to 60s…");
|
||||
player.seek(60.0);
|
||||
}
|
||||
}
|
||||
Ok(PlayerEvent::Error(e)) => {
|
||||
println!("ERROR: {e}");
|
||||
return;
|
||||
}
|
||||
Ok(ev) => println!("{ev:?}"),
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
println!("final pos: {last_pos:.1} (expect > 60 if seek worked)");
|
||||
player.stop();
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
//! Phase 0.3 — native transport probe (isolated diagnostic, not feature code).
|
||||
//!
|
||||
//! Answers the native column's core questions with the existing crate, no new
|
||||
//! dependencies:
|
||||
//!
|
||||
//! 1. Can a SECOND `Player` instance coexist with a "main" one — two sinks,
|
||||
//! two event channels, no shared state?
|
||||
//! 2. Does a real Deezer preview (MPEG-1 Layer III, 128k CBR, ~480 KB)
|
||||
//! decode and play through rodio/symphonia?
|
||||
//! 3. Do pause / resume / stop behave on the second instance while the
|
||||
//! first is mid-track, without cross-talk on either event channel?
|
||||
//!
|
||||
//! The preview file is downloaded OUTSIDE this probe (curl) and passed as
|
||||
//! argv[1]; argv[2] is any long local track for the "main" side. Run:
|
||||
//!
|
||||
//! cargo run -p player --example preview_probe -- preview.mp3 main.mp3
|
||||
|
||||
use std::sync::mpsc::RecvTimeoutError;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use player::{Player, PlayerEvent};
|
||||
|
||||
fn drain(
|
||||
label: &str,
|
||||
rx: &std::sync::mpsc::Receiver<PlayerEvent>,
|
||||
for_ms: u64,
|
||||
log: &mut Vec<String>,
|
||||
) {
|
||||
let end = Instant::now() + Duration::from_millis(for_ms);
|
||||
loop {
|
||||
let left = end.saturating_duration_since(Instant::now());
|
||||
if left.is_zero() {
|
||||
break;
|
||||
}
|
||||
match rx.recv_timeout(left) {
|
||||
Ok(ev) => log.push(format!("{label}: {ev:?}")),
|
||||
Err(RecvTimeoutError::Timeout) => break,
|
||||
Err(RecvTimeoutError::Disconnected) => {
|
||||
log.push(format!("{label}: channel closed"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut args = std::env::args().skip(1);
|
||||
let preview = args.next().expect("argv[1]: downloaded preview mp3");
|
||||
let main_track = args.next();
|
||||
|
||||
let mut log = Vec::new();
|
||||
|
||||
// "Main" player — only if a second file was provided; the coexistence
|
||||
// half of the probe is skipped otherwise.
|
||||
let main_pair = main_track.map(|p| {
|
||||
let (main, main_rx) = Player::spawn();
|
||||
main.play_local(&p, None);
|
||||
(main, main_rx, p)
|
||||
});
|
||||
|
||||
// Preview player: a wholly separate instance and event channel.
|
||||
let (prev, prev_rx) = Player::spawn();
|
||||
let t0 = Instant::now();
|
||||
prev.play_local(&preview, Some(30.0));
|
||||
|
||||
drain("preview", &prev_rx, 2500, &mut log);
|
||||
let started = log.iter().any(|l| l.contains("Playing"));
|
||||
println!("PROBE preview-starts: {started} ({} ms to first events)", t0.elapsed().as_millis());
|
||||
|
||||
prev.pause();
|
||||
drain("preview", &prev_rx, 600, &mut log);
|
||||
let paused = log.iter().any(|l| l.contains("Paused"));
|
||||
println!("PROBE preview-pauses: {paused}");
|
||||
|
||||
prev.resume();
|
||||
drain("preview", &prev_rx, 900, &mut log);
|
||||
|
||||
if let Some((_main, main_rx, ref path)) = main_pair {
|
||||
let mut main_log = Vec::new();
|
||||
drain("main", &main_rx, 1200, &mut main_log);
|
||||
let main_alive = main_log.iter().any(|l| l.contains("Playing") || l.contains("Position"));
|
||||
println!("PROBE main-coexists: {main_alive} ({path})");
|
||||
// cross-talk check: no preview-channel event may appear on main's
|
||||
// channel or vice versa — separate receivers make this structural,
|
||||
// so what we actually assert is that both channels stayed live.
|
||||
println!("PROBE channels-independent: true (separate mpsc receivers by construction)");
|
||||
} else {
|
||||
println!("PROBE main-coexists: skipped (no second file)");
|
||||
}
|
||||
|
||||
prev.stop();
|
||||
drain("preview", &prev_rx, 600, &mut log);
|
||||
let stopped = log.iter().any(|l| l.contains("Stopped"));
|
||||
println!("PROBE preview-stops: {stopped}");
|
||||
|
||||
println!("--- event log ---");
|
||||
for l in &log {
|
||||
println!("{l}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
//! Print an ASCII preview of a file's waveform shape.
|
||||
//! cargo run -p player --example wf -- "C:\path\to\file.flac"
|
||||
|
||||
fn main() {
|
||||
let path = std::path::PathBuf::from(std::env::args().nth(1).expect("usage: wf <file>"));
|
||||
let t = std::time::Instant::now();
|
||||
let peaks = player::compute_waveform(&path, 80);
|
||||
println!("{} buckets in {:?}", peaks.len(), t.elapsed());
|
||||
let glyphs = [' ', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
|
||||
let bars: String = peaks
|
||||
.iter()
|
||||
.map(|&v| glyphs[((v * 8.0) as usize).min(8)])
|
||||
.collect();
|
||||
println!("{bars}");
|
||||
let flat = peaks.iter().filter(|&&v| v > 0.95).count();
|
||||
println!("buckets >0.95: {flat}/{}", peaks.len());
|
||||
}
|
||||
Reference in new issue
Block a user