rust_expect/transcript/
player.rs1use std::io::Write;
4use std::time::{Duration, Instant};
5
6use super::format::{EventType, Transcript, TranscriptEvent};
7
8#[derive(Debug, Clone, Copy, PartialEq, Default)]
10pub enum PlaybackSpeed {
11 #[default]
13 Realtime,
14 Speed(f64),
16 Instant,
18}
19
20#[derive(Debug, Clone)]
22pub struct PlaybackOptions {
23 pub speed: PlaybackSpeed,
25 pub max_idle: Duration,
27 pub show_input: bool,
29 pub pause_at_markers: bool,
31}
32
33impl Default for PlaybackOptions {
34 fn default() -> Self {
35 Self {
36 speed: PlaybackSpeed::Realtime,
37 max_idle: Duration::from_secs(5),
38 show_input: false,
39 pause_at_markers: false,
40 }
41 }
42}
43
44impl PlaybackOptions {
45 #[must_use]
47 pub fn new() -> Self {
48 Self::default()
49 }
50
51 #[must_use]
53 pub const fn with_speed(mut self, speed: PlaybackSpeed) -> Self {
54 self.speed = speed;
55 self
56 }
57
58 #[must_use]
60 pub const fn with_max_idle(mut self, max: Duration) -> Self {
61 self.max_idle = max;
62 self
63 }
64
65 #[must_use]
67 pub const fn with_show_input(mut self, show: bool) -> Self {
68 self.show_input = show;
69 self
70 }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum PlayerState {
76 Stopped,
78 Playing,
80 Paused,
82 Finished,
84}
85
86pub struct Player<'a> {
88 transcript: &'a Transcript,
90 index: usize,
92 options: PlaybackOptions,
94 state: PlayerState,
96 start_time: Option<Instant>,
98 last_event_time: Duration,
100}
101
102impl<'a> Player<'a> {
103 #[must_use]
105 pub fn new(transcript: &'a Transcript) -> Self {
106 Self {
107 transcript,
108 index: 0,
109 options: PlaybackOptions::default(),
110 state: PlayerState::Stopped,
111 start_time: None,
112 last_event_time: Duration::ZERO,
113 }
114 }
115
116 #[must_use]
118 pub const fn with_options(mut self, options: PlaybackOptions) -> Self {
119 self.options = options;
120 self
121 }
122
123 #[must_use]
125 pub const fn state(&self) -> PlayerState {
126 self.state
127 }
128
129 #[must_use]
131 pub const fn position(&self) -> usize {
132 self.index
133 }
134
135 #[must_use]
137 pub const fn total_events(&self) -> usize {
138 self.transcript.events.len()
139 }
140
141 #[must_use]
143 pub fn current_time(&self) -> Duration {
144 if self.index < self.transcript.events.len() {
145 self.transcript.events[self.index].timestamp
146 } else {
147 self.transcript.duration()
148 }
149 }
150
151 pub fn play(&mut self) {
153 self.state = PlayerState::Playing;
154 self.start_time = Some(Instant::now());
155 }
156
157 pub const fn pause(&mut self) {
159 self.state = PlayerState::Paused;
160 }
161
162 pub const fn stop(&mut self) {
164 self.state = PlayerState::Stopped;
165 self.index = 0;
166 self.start_time = None;
167 self.last_event_time = Duration::ZERO;
168 }
169
170 pub fn seek(&mut self, index: usize) {
172 self.index = index.min(self.transcript.events.len());
173 if self.index < self.transcript.events.len() {
174 self.last_event_time = self.transcript.events[self.index].timestamp;
175 }
176 }
177
178 pub fn next_event(&mut self) -> Option<&TranscriptEvent> {
180 if self.state != PlayerState::Playing || self.index >= self.transcript.events.len() {
181 if self.index >= self.transcript.events.len() {
182 self.state = PlayerState::Finished;
183 }
184 return None;
185 }
186
187 let event = &self.transcript.events[self.index];
188 self.index += 1;
189 self.last_event_time = event.timestamp;
190 Some(event)
191 }
192
193 #[must_use]
195 pub fn delay_to_next(&self) -> Duration {
196 if self.index >= self.transcript.events.len() {
197 return Duration::ZERO;
198 }
199
200 let next_time = self.transcript.events[self.index].timestamp;
201 let delay = next_time.saturating_sub(self.last_event_time);
202
203 let delay = match self.options.speed {
205 PlaybackSpeed::Instant => Duration::ZERO,
206 PlaybackSpeed::Realtime => delay,
207 PlaybackSpeed::Speed(mult) => Duration::from_secs_f64(delay.as_secs_f64() / mult),
208 };
209
210 delay.min(self.options.max_idle)
212 }
213
214 pub fn play_to<W: Write>(&mut self, writer: &mut W) -> std::io::Result<()> {
216 self.play();
217
218 while let Some(event) = self.next_event() {
219 let event_type = event.event_type;
221 let event_data = event.data.clone();
222
223 let delay = self.delay_to_next();
225 if delay > Duration::ZERO {
226 std::thread::sleep(delay);
227 }
228
229 match event_type {
231 EventType::Output => {
232 writer.write_all(&event_data)?;
233 writer.flush()?;
234 }
235 EventType::Input if self.options.show_input => {
236 writer.write_all(&event_data)?;
238 writer.flush()?;
239 }
240 EventType::Marker if self.options.pause_at_markers => {
241 self.pause();
242 break;
244 }
245 _ => {}
246 }
247 }
248
249 Ok(())
250 }
251}
252
253pub fn play_to_stdout(transcript: &Transcript, options: PlaybackOptions) -> std::io::Result<()> {
255 let mut player = Player::new(transcript).with_options(options);
256 let mut stdout = std::io::stdout();
257 player.play_to(&mut stdout)
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263 use crate::transcript::format::TranscriptMetadata;
264
265 #[test]
266 fn player_basic() {
267 let mut transcript = Transcript::new(TranscriptMetadata::new(80, 24));
268 transcript.push(TranscriptEvent::output(Duration::ZERO, b"hello"));
269 transcript.push(TranscriptEvent::output(
270 Duration::from_millis(100),
271 b" world",
272 ));
273
274 let mut player = Player::new(&transcript);
275 assert_eq!(player.total_events(), 2);
276 assert_eq!(player.state(), PlayerState::Stopped);
277
278 player.play();
279 assert_eq!(player.state(), PlayerState::Playing);
280
281 let event = player.next_event().unwrap();
282 assert_eq!(event.data, b"hello");
283
284 let event = player.next_event().unwrap();
285 assert_eq!(event.data, b" world");
286
287 assert!(player.next_event().is_none());
288 assert_eq!(player.state(), PlayerState::Finished);
289 }
290
291 #[test]
292 fn player_instant_speed() {
293 let mut transcript = Transcript::new(TranscriptMetadata::new(80, 24));
294 transcript.push(TranscriptEvent::output(Duration::ZERO, b"a"));
295 transcript.push(TranscriptEvent::output(Duration::from_secs(10), b"b"));
296
297 let player = Player::new(&transcript)
298 .with_options(PlaybackOptions::new().with_speed(PlaybackSpeed::Instant));
299
300 assert_eq!(player.delay_to_next(), Duration::ZERO);
301 }
302}