mirror of
https://github.com/asciinema/asciinema.git
synced 2025-12-16 03:38:03 +01:00
180 lines
5.7 KiB
Rust
180 lines
5.7 KiB
Rust
use anyhow::Result;
|
|
use tokio::sync::mpsc;
|
|
use tokio::time::{self, Duration, Instant};
|
|
|
|
use crate::asciicast::{self, Event, EventData};
|
|
use crate::config::Key;
|
|
use crate::tty::{DevTty, Tty};
|
|
|
|
pub struct KeyBindings {
|
|
pub quit: Key,
|
|
pub pause: Key,
|
|
pub step: Key,
|
|
pub next_marker: Key,
|
|
}
|
|
|
|
impl Default for KeyBindings {
|
|
fn default() -> Self {
|
|
Self {
|
|
quit: Some(vec![0x03]),
|
|
pause: Some(vec![b' ']),
|
|
step: Some(vec![b'.']),
|
|
next_marker: Some(vec![b']']),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn play(
|
|
recording: asciicast::Asciicast<'static>,
|
|
speed: f64,
|
|
idle_time_limit_override: Option<f64>,
|
|
pause_on_markers: bool,
|
|
keys: &KeyBindings,
|
|
auto_resize: bool,
|
|
) -> Result<bool> {
|
|
let initial_cols = recording.header.term_cols;
|
|
let initial_rows = recording.header.term_rows;
|
|
let mut events = emit_session_events(recording, speed, idle_time_limit_override)?;
|
|
let mut epoch = Instant::now();
|
|
let mut pause_elapsed_time: Option<u64> = None;
|
|
let mut next_event = events.recv().await.transpose()?;
|
|
let mut input = [0u8; 1024];
|
|
let mut tty = DevTty::open().await?;
|
|
|
|
if auto_resize {
|
|
tty.resize((initial_cols as usize, initial_rows as usize).into())
|
|
.await?;
|
|
}
|
|
|
|
while let Some(Event { time, data }) = &next_event {
|
|
if let Some(pet) = pause_elapsed_time {
|
|
let n = tty.read(&mut input).await?;
|
|
let key = &input[..n];
|
|
|
|
if keys.quit.as_ref().is_some_and(|k| k == key) {
|
|
tty.write_all("\r\n".as_bytes()).await?;
|
|
return Ok(false);
|
|
}
|
|
|
|
if keys.pause.as_ref().is_some_and(|k| k == key) {
|
|
epoch = Instant::now() - Duration::from_micros(pet);
|
|
pause_elapsed_time = None;
|
|
} else if keys.step.as_ref().is_some_and(|k| k == key) {
|
|
pause_elapsed_time = Some(*time);
|
|
|
|
match data {
|
|
EventData::Output(data) => {
|
|
tty.write_all(data.as_bytes()).await?;
|
|
}
|
|
|
|
EventData::Resize(cols, rows) if auto_resize => {
|
|
tty.resize((*cols as usize, *rows as usize).into()).await?;
|
|
}
|
|
|
|
_ => {}
|
|
}
|
|
|
|
next_event = events.recv().await.transpose()?;
|
|
} else if keys.next_marker.as_ref().is_some_and(|k| k == key) {
|
|
while let Some(Event { time, data }) = next_event {
|
|
next_event = events.recv().await.transpose()?;
|
|
|
|
match data {
|
|
EventData::Output(data) => {
|
|
tty.write_all(data.as_bytes()).await?;
|
|
}
|
|
|
|
EventData::Marker(_) => {
|
|
pause_elapsed_time = Some(time);
|
|
break;
|
|
}
|
|
|
|
EventData::Resize(cols, rows) if auto_resize => {
|
|
tty.resize((cols as usize, rows as usize).into()).await?;
|
|
}
|
|
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
while let Some(Event { time, data }) = &next_event {
|
|
let delay = *time as i64 - epoch.elapsed().as_micros() as i64;
|
|
|
|
if delay > 0 {
|
|
let delay = (*time as i64 - epoch.elapsed().as_micros() as i64).max(0) as u64;
|
|
|
|
if let Ok(result) =
|
|
time::timeout(Duration::from_micros(delay), tty.read(&mut input)).await
|
|
{
|
|
let n = result?;
|
|
let key = &input[..n];
|
|
|
|
if keys.quit.as_ref().is_some_and(|k| k == key) {
|
|
tty.write_all("\r\n".as_bytes()).await?;
|
|
return Ok(false);
|
|
}
|
|
|
|
if keys.pause.as_ref().is_some_and(|k| k == key) {
|
|
pause_elapsed_time = Some(epoch.elapsed().as_micros() as u64);
|
|
break;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
match data {
|
|
EventData::Output(data) => {
|
|
tty.write_all(data.as_bytes()).await?;
|
|
}
|
|
|
|
EventData::Resize(cols, rows) if auto_resize => {
|
|
tty.resize((*cols as usize, *rows as usize).into()).await?;
|
|
}
|
|
|
|
EventData::Marker(_) => {
|
|
if pause_on_markers {
|
|
pause_elapsed_time = Some(*time);
|
|
next_event = events.recv().await.transpose()?;
|
|
break;
|
|
}
|
|
}
|
|
|
|
_ => (),
|
|
}
|
|
|
|
next_event = events.recv().await.transpose()?;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
fn emit_session_events(
|
|
recording: asciicast::Asciicast<'static>,
|
|
speed: f64,
|
|
idle_time_limit_override: Option<f64>,
|
|
) -> Result<mpsc::Receiver<Result<Event>>> {
|
|
let idle_time_limit = idle_time_limit_override
|
|
.or(recording.header.idle_time_limit)
|
|
.unwrap_or(f64::MAX);
|
|
|
|
let events = asciicast::limit_idle_time(recording.events, idle_time_limit);
|
|
let events = asciicast::accelerate(events, speed);
|
|
// TODO avoid collect, support playback from stdin
|
|
let events: Vec<_> = events.collect();
|
|
let (tx, rx) = mpsc::channel::<Result<Event>>(1024);
|
|
|
|
tokio::spawn(async move {
|
|
for event in events {
|
|
if tx.send(event).await.is_err() {
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
Ok(rx)
|
|
}
|