]>
| Commit | Line | Data |
|---|---|---|
| 1 | extern crate libc; | |
| 2 | extern crate sdl2_sys; | |
| 3 | extern crate nihav_core; | |
| 4 | extern crate nihav_registry; | |
| 5 | ||
| 6 | use std::fs::File; | |
| 7 | use std::io::prelude::*; | |
| 8 | use std::io::BufReader; | |
| 9 | use std::sync::mpsc; | |
| 10 | use std::time::Duration; | |
| 11 | use std::thread; | |
| 12 | use sdl2_sys::{SDL_AudioSpec, Uint32}; | |
| 13 | use nihav_core::io::byteio::{ByteIO,FileReader}; | |
| 14 | use nihav_core::frame::*; | |
| 15 | use nihav_core::codecs::*; | |
| 16 | use nihav_core::demuxers::*; | |
| 17 | use nihav_core::soundcvt::*; | |
| 18 | use nihav_hlblocks::demux::*; | |
| 19 | ||
| 20 | mod allreg; | |
| 21 | mod command; | |
| 22 | use command::*; | |
| 23 | ||
| 24 | struct Player { | |
| 25 | ended: bool, | |
| 26 | full_reg: FullRegister, | |
| 27 | paused: bool, | |
| 28 | mute: bool, | |
| 29 | volume: u8, | |
| 30 | debug: bool, | |
| 31 | buf: Vec<i16>, | |
| 32 | } | |
| 33 | ||
| 34 | struct AudioDevice { | |
| 35 | device_id: sdl2_sys::SDL_AudioDeviceID | |
| 36 | } | |
| 37 | ||
| 38 | impl AudioDevice { | |
| 39 | fn open(arate: u32, channels: u8) -> Option<(Self, SDL_AudioSpec)> { | |
| 40 | let desired_spec = SDL_AudioSpec { | |
| 41 | freq: arate as i32, | |
| 42 | format: sdl2_sys::AUDIO_S16 as u16, | |
| 43 | channels, | |
| 44 | silence: 0, | |
| 45 | samples: 0, | |
| 46 | padding: 0, | |
| 47 | size: 0, | |
| 48 | callback: None, | |
| 49 | userdata: std::ptr::null_mut(), | |
| 50 | }; | |
| 51 | let mut dspec = desired_spec; | |
| 52 | let device_id = unsafe { sdl2_sys::SDL_OpenAudioDevice(std::ptr::null(), 0, &desired_spec, &mut dspec, 0) }; | |
| 53 | if device_id != 0 { | |
| 54 | Some((AudioDevice { device_id }, dspec)) | |
| 55 | } else { | |
| 56 | None | |
| 57 | } | |
| 58 | } | |
| 59 | fn pause(&self) { | |
| 60 | unsafe { sdl2_sys::SDL_PauseAudioDevice(self.device_id, 1); } | |
| 61 | } | |
| 62 | fn resume(&self) { | |
| 63 | unsafe { sdl2_sys::SDL_PauseAudioDevice(self.device_id, 0); } | |
| 64 | } | |
| 65 | fn clear(&self) { | |
| 66 | unsafe { sdl2_sys::SDL_ClearQueuedAudio(self.device_id); } | |
| 67 | } | |
| 68 | fn queue(&self, buf: &[i16]) { | |
| 69 | unsafe { | |
| 70 | let len = buf.len() * 2; | |
| 71 | let buf_ptr = buf.as_ptr(); | |
| 72 | sdl2_sys::SDL_QueueAudio(self.device_id, buf_ptr as *const core::ffi::c_void, len as Uint32); | |
| 73 | } | |
| 74 | } | |
| 75 | fn queue_bytes(&self, buf: &[u8]) { | |
| 76 | unsafe { | |
| 77 | let len = buf.len(); | |
| 78 | let buf_ptr = buf.as_ptr(); | |
| 79 | sdl2_sys::SDL_QueueAudio(self.device_id, buf_ptr as *const core::ffi::c_void, len as Uint32); | |
| 80 | } | |
| 81 | } | |
| 82 | fn size(&self) -> u32 { | |
| 83 | unsafe { sdl2_sys::SDL_GetQueuedAudioSize(self.device_id) } | |
| 84 | } | |
| 85 | } | |
| 86 | ||
| 87 | impl Drop for AudioDevice { | |
| 88 | fn drop(&mut self) { | |
| 89 | unsafe { sdl2_sys::SDL_CloseAudioDevice(self.device_id); } | |
| 90 | } | |
| 91 | } | |
| 92 | ||
| 93 | struct Decoder<'a> { | |
| 94 | demuxer: DemuxerObject<'a>, | |
| 95 | decoder: Box<dyn NADecoder>, | |
| 96 | dsupp: Box<NADecoderSupport>, | |
| 97 | buf: &'a mut Vec<i16>, | |
| 98 | stream_no: usize, | |
| 99 | dst_info: NAAudioInfo, | |
| 100 | dst_chmap: NAChannelMap, | |
| 101 | samplepos: u64, | |
| 102 | arate: u32, | |
| 103 | volume: u8, | |
| 104 | mute: bool, | |
| 105 | } | |
| 106 | ||
| 107 | fn output_vol_i16(device: &AudioDevice, tmp: &mut Vec<i16>, src: &[i16], mute: bool, volume: u8) { | |
| 108 | if !mute { | |
| 109 | tmp.clear(); | |
| 110 | tmp.reserve(src.len()); | |
| 111 | let vol = i32::from(volume); | |
| 112 | for &sample in src.iter() { | |
| 113 | let nsamp = vol * i32::from(sample) / 100; | |
| 114 | tmp.push(nsamp.clamp(-32768, 32767) as i16); | |
| 115 | } | |
| 116 | } else { | |
| 117 | tmp.clear(); | |
| 118 | tmp.resize(src.len(), 0); | |
| 119 | } | |
| 120 | device.queue(tmp); | |
| 121 | } | |
| 122 | ||
| 123 | fn output_vol_u8(device: &AudioDevice, tmp: &mut Vec<i16>, src: &[u8], mute: bool, volume: u8) { | |
| 124 | if !mute { | |
| 125 | tmp.clear(); | |
| 126 | tmp.reserve(src.len()); | |
| 127 | let vol = i32::from(volume); | |
| 128 | for sample in src.chunks_exact(2) { | |
| 129 | let sample = (u16::from(sample[0]) + u16::from(sample[1]) * 256) as i16; | |
| 130 | let nsamp = vol * i32::from(sample) / 100; | |
| 131 | tmp.push(nsamp.clamp(-32768, 32767) as i16); | |
| 132 | } | |
| 133 | } else { | |
| 134 | tmp.clear(); | |
| 135 | tmp.resize(src.len() / 2, 0); | |
| 136 | } | |
| 137 | device.queue(tmp); | |
| 138 | } | |
| 139 | ||
| 140 | impl<'a> Decoder<'a> { | |
| 141 | fn refill(&mut self, device: &AudioDevice) -> bool { | |
| 142 | loop { | |
| 143 | match self.demuxer.get_frame() { | |
| 144 | Ok(pkt) => { | |
| 145 | if pkt.get_stream().get_num() == self.stream_no { | |
| 146 | match self.decoder.decode(&mut self.dsupp, &pkt) { | |
| 147 | Ok(frm) => { | |
| 148 | let buf = frm.get_buffer(); | |
| 149 | if let NABufferType::None = buf { | |
| 150 | return false; | |
| 151 | } | |
| 152 | if let Some(pts) = frm.ts.get_pts() { | |
| 153 | self.samplepos = NATimeInfo::rescale_ts(pts, frm.ts.tb_num, frm.ts.tb_den, 1, self.arate); | |
| 154 | } | |
| 155 | if buf.get_audio_length() == 0 { | |
| 156 | return false; | |
| 157 | } | |
| 158 | let out_buf = convert_audio_frame(&buf, &self.dst_info, &self.dst_chmap).unwrap(); | |
| 159 | match out_buf { | |
| 160 | NABufferType::AudioI16(abuf) => { | |
| 161 | if !self.mute && self.volume == 100 { | |
| 162 | device.queue(abuf.get_data()); | |
| 163 | } else { | |
| 164 | output_vol_i16(device, self.buf, abuf.get_data(), self.mute, self.volume); | |
| 165 | } | |
| 166 | self.samplepos += abuf.get_length() as u64; | |
| 167 | }, | |
| 168 | NABufferType::AudioPacked(abuf) => { | |
| 169 | if !self.mute && self.volume == 100 { | |
| 170 | device.queue_bytes(abuf.get_data()); | |
| 171 | } else { | |
| 172 | output_vol_u8(device, self.buf, abuf.get_data(), self.mute, self.volume); | |
| 173 | } | |
| 174 | self.samplepos += abuf.get_length() as u64; | |
| 175 | }, | |
| 176 | _ => println!("unknown buffer type"), | |
| 177 | }; | |
| 178 | return false; | |
| 179 | }, | |
| 180 | Err(err) => { | |
| 181 | println!(" error decoding {:?}", err); | |
| 182 | return true; | |
| 183 | }, | |
| 184 | }; | |
| 185 | } | |
| 186 | }, | |
| 187 | Err(DemuxerError::EOF) => return true, | |
| 188 | Err(err) => { | |
| 189 | println!("demuxing error {:?}", err); | |
| 190 | return true; | |
| 191 | }, | |
| 192 | }; | |
| 193 | } | |
| 194 | } | |
| 195 | fn seek(&mut self, time: u64) -> bool { | |
| 196 | let ret = self.demuxer.seek(NATimePoint::Milliseconds(time)); | |
| 197 | if ret.is_err() { println!(" seek error\n"); } | |
| 198 | self.decoder.flush(); | |
| 199 | ret.is_ok() | |
| 200 | } | |
| 201 | } | |
| 202 | ||
| 203 | fn format_time(ms: u64) -> String { | |
| 204 | let s = ms / 1000; | |
| 205 | let ds = (ms % 1000) / 100; | |
| 206 | let (min, s) = (s / 60, s % 60); | |
| 207 | let (h, min) = (min / 60, min % 60); | |
| 208 | if h == 0 { | |
| 209 | if min == 0 { | |
| 210 | format!("{}.{}", s, ds) | |
| 211 | } else { | |
| 212 | format!("{}:{:02}.{}", min, s, ds) | |
| 213 | } | |
| 214 | } else { | |
| 215 | format!("{}:{:02}:{:02}.{}", h, min, s, ds) | |
| 216 | } | |
| 217 | } | |
| 218 | ||
| 219 | impl Player { | |
| 220 | fn new() -> Self { | |
| 221 | let mut full_reg = FullRegister::new(); | |
| 222 | crate::allreg::fill_register(&mut full_reg); | |
| 223 | ||
| 224 | unsafe { | |
| 225 | if sdl2_sys::SDL_Init(sdl2_sys::SDL_INIT_AUDIO) != 0 { | |
| 226 | panic!("cannot init SDL"); | |
| 227 | } | |
| 228 | } | |
| 229 | ||
| 230 | Self { | |
| 231 | ended: false, | |
| 232 | paused: false, | |
| 233 | full_reg, | |
| 234 | volume: 100, | |
| 235 | mute: false, | |
| 236 | debug: false, | |
| 237 | buf: Vec::new(), | |
| 238 | } | |
| 239 | } | |
| 240 | fn play_file(&mut self, name: &str, cmd_receiver: &mpsc::Receiver<Command>, start_time: NATimePoint) { | |
| 241 | let file = if let Ok(handle) = File::open(name) { | |
| 242 | if let Ok(meta) = handle.metadata() { | |
| 243 | if meta.is_dir() { | |
| 244 | return; | |
| 245 | } | |
| 246 | } | |
| 247 | handle | |
| 248 | } else { | |
| 249 | println!("error opening {name}"); | |
| 250 | return; | |
| 251 | }; | |
| 252 | let file = BufReader::new(file); | |
| 253 | ||
| 254 | let mut fr = FileReader::new_read(file); | |
| 255 | if fr.peek_byte().is_err() { | |
| 256 | println!("Cannot read {}", name); | |
| 257 | return; | |
| 258 | } | |
| 259 | let (is_raw, start, end) = detect_tags(&mut fr, true); | |
| 260 | ||
| 261 | let br: Box<dyn ByteIO>; | |
| 262 | if start != 0 || end.is_some() { | |
| 263 | //println!(" limiting range to {:X}-{:X}", start, end.unwrap_or(0)); | |
| 264 | let file = fr.finish(); | |
| 265 | br = Box::new(BoundedFileReader::new_read(file, start, end).unwrap()); | |
| 266 | } else { | |
| 267 | br = Box::new(fr); | |
| 268 | } | |
| 269 | let dmx = DemuxerObject::create(br, &self.full_reg, name, None, is_raw, &[], false); | |
| 270 | if dmx.is_none() { | |
| 271 | println!("Demuxer creation failed!"); | |
| 272 | return; | |
| 273 | } | |
| 274 | ||
| 275 | let mut ainfo = None; | |
| 276 | let mut dec: Option<(Box<NADecoderSupport>, Box<dyn NADecoder>)> = None; | |
| 277 | let mut stream_no = 0; | |
| 278 | let mut duration = dmx.get_duration(); | |
| 279 | for i in 0..dmx.get_num_streams() { | |
| 280 | let s = dmx.get_stream(i).unwrap(); | |
| 281 | let info = s.get_info(); | |
| 282 | if info.is_audio() { | |
| 283 | let decfunc = self.full_reg.dec_reg.find_decoder(info.get_name()); | |
| 284 | if decfunc.is_none() { | |
| 285 | println!("no decoder for {}", info.get_name()); | |
| 286 | continue; | |
| 287 | } | |
| 288 | let mut decoder = (decfunc.unwrap())(); | |
| 289 | let mut dsupp = Box::new(NADecoderSupport::new()); | |
| 290 | if decoder.init(&mut dsupp, info.clone()).is_err() { | |
| 291 | println!("cannot init decoder for stream {}", i); | |
| 292 | continue; | |
| 293 | } | |
| 294 | dec = Some((dsupp, decoder)); | |
| 295 | ainfo = Some(info); | |
| 296 | stream_no = i; | |
| 297 | if s.duration > 0 { | |
| 298 | duration = NATimeInfo::rescale_ts(s.duration, s.tb_num, s.tb_den, 1, 1000); | |
| 299 | } | |
| 300 | break; | |
| 301 | } | |
| 302 | } | |
| 303 | if dec.is_none() { | |
| 304 | println!("no audio decoder found"); | |
| 305 | return; | |
| 306 | } | |
| 307 | let (dsupp, decoder) = dec.unwrap(); | |
| 308 | ||
| 309 | let info = ainfo.unwrap(); | |
| 310 | let ainfo = info.get_properties().get_audio_info().unwrap(); | |
| 311 | let sbr_hack = info.get_name() == "aac" && ainfo.sample_rate < 32000; | |
| 312 | let arate = if ainfo.sample_rate > 0 { | |
| 313 | if !sbr_hack { | |
| 314 | ainfo.sample_rate | |
| 315 | } else { | |
| 316 | ainfo.sample_rate * 2 | |
| 317 | } | |
| 318 | } else { 44100 }; | |
| 319 | let ch = ainfo.channels; | |
| 320 | ||
| 321 | println!("Playing {} - {dmx} [{}Hz {}ch]", name, arate, ch); | |
| 322 | let ret = AudioDevice::open(arate, ch.min(2)); | |
| 323 | if ret.is_none() { | |
| 324 | println!("cannot open output"); | |
| 325 | return; | |
| 326 | } | |
| 327 | let (device, dspec) = ret.unwrap(); | |
| 328 | let block_limit = dmx.get_stream(stream_no).unwrap().tb_num * arate / dmx.get_stream(stream_no).unwrap().tb_den * u32::from(dspec.channels); | |
| 329 | ||
| 330 | let dst_info = NAAudioInfo { sample_rate: dspec.freq as u32, channels: dspec.channels, format: SND_S16_FORMAT, block_len: 1 }; | |
| 331 | let dst_chmap = if dst_info.channels == 2 { | |
| 332 | NAChannelMap::from_str("L,R").unwrap() | |
| 333 | } else { | |
| 334 | NAChannelMap::from_str("C").unwrap() | |
| 335 | }; | |
| 336 | let mut decoder = Decoder { | |
| 337 | demuxer: dmx, | |
| 338 | decoder, dsupp, | |
| 339 | stream_no, | |
| 340 | dst_info, dst_chmap, | |
| 341 | samplepos: 0, | |
| 342 | arate, | |
| 343 | volume: self.volume, | |
| 344 | mute: self.mute, | |
| 345 | buf: &mut self.buf, | |
| 346 | }; | |
| 347 | let mut refill_limit = arate * u32::from(dspec.channels); | |
| 348 | let underfill_limit = (arate * u32::from(dspec.channels) / 4).max(block_limit); | |
| 349 | ||
| 350 | if start_time != NATimePoint::None { | |
| 351 | let _ret = decoder.demuxer.seek(start_time); | |
| 352 | } | |
| 353 | ||
| 354 | let mut eof = decoder.refill(&device); | |
| 355 | while !eof && device.size() < refill_limit { | |
| 356 | eof = decoder.refill(&device); | |
| 357 | } | |
| 358 | ||
| 359 | let duration_str = if duration != 0 { format_time(duration) } else { "???".to_owned() }; | |
| 360 | if !self.paused { | |
| 361 | device.resume(); | |
| 362 | } | |
| 363 | let mut no_display = false; | |
| 364 | 'main: loop { | |
| 365 | let cur_time = decoder.samplepos.saturating_sub(u64::from(device.size() / 2 / u32::from(dst_info.channels))); | |
| 366 | let full_ms = cur_time * 1000 / u64::from(arate); | |
| 367 | let timestr = format_time(full_ms); | |
| 368 | let disp_vol = if self.mute { 0 } else { self.volume }; | |
| 369 | if !no_display { | |
| 370 | if !self.debug { | |
| 371 | print!("> {} / {} {}% \r", timestr, duration_str, disp_vol); | |
| 372 | } else { | |
| 373 | print!("> {} / {} |{}| {}% \r", timestr, duration_str, device.size(), disp_vol); | |
| 374 | } | |
| 375 | } | |
| 376 | std::io::stdout().flush().unwrap(); | |
| 377 | if device.size() < underfill_limit && !self.paused && (refill_limit < (1 << 20)) & !eof { | |
| 378 | if full_ms > 5000 { | |
| 379 | println!("underrun!"); | |
| 380 | } | |
| 381 | refill_limit += refill_limit >> 1; | |
| 382 | } | |
| 383 | if device.size() < refill_limit / 2 { | |
| 384 | while !eof && device.size() < refill_limit { | |
| 385 | eof = decoder.refill(&device); | |
| 386 | } | |
| 387 | } | |
| 388 | if eof && device.size() == 0 { | |
| 389 | break 'main; | |
| 390 | } | |
| 391 | while let Ok(cmd) = cmd_receiver.try_recv() { | |
| 392 | let cur_time = decoder.samplepos.saturating_sub(u64::from(device.size() / 2 / u32::from(dst_info.channels))); | |
| 393 | match cmd { | |
| 394 | Command::Forward(val) => { | |
| 395 | device.pause(); | |
| 396 | device.clear(); | |
| 397 | let seekoff = match val { | |
| 398 | 1 => 10, | |
| 399 | 2 => 60, | |
| 400 | _ => 10 * 60, | |
| 401 | }; | |
| 402 | let seek_time = cur_time * 1000 / u64::from(arate) + seekoff * 1000; | |
| 403 | let _ret = decoder.seek(seek_time); | |
| 404 | while !eof && device.size() < refill_limit { | |
| 405 | eof = decoder.refill(&device); | |
| 406 | } | |
| 407 | if eof { | |
| 408 | break 'main; | |
| 409 | } | |
| 410 | if !self.paused { | |
| 411 | device.resume(); | |
| 412 | } | |
| 413 | }, | |
| 414 | Command::Back(val) => { | |
| 415 | device.pause(); | |
| 416 | device.clear(); | |
| 417 | let seekoff = match val { | |
| 418 | 1 => 10, | |
| 419 | 2 => 60, | |
| 420 | _ => 10 * 60, | |
| 421 | }; | |
| 422 | let seek_time = (cur_time * 1000 / u64::from(arate)).saturating_sub(seekoff * 1000); | |
| 423 | let _ret = decoder.seek(seek_time); | |
| 424 | while !eof && device.size() < refill_limit { | |
| 425 | eof = decoder.refill(&device); | |
| 426 | } | |
| 427 | if eof { | |
| 428 | break 'main; | |
| 429 | } | |
| 430 | if !self.paused { | |
| 431 | device.resume(); | |
| 432 | } | |
| 433 | }, | |
| 434 | Command::Seek(seek_time) => { | |
| 435 | device.pause(); | |
| 436 | device.clear(); | |
| 437 | let _ret = decoder.seek(seek_time); | |
| 438 | while !eof && device.size() < refill_limit { | |
| 439 | eof = decoder.refill(&device); | |
| 440 | } | |
| 441 | if eof { | |
| 442 | break 'main; | |
| 443 | } | |
| 444 | if !self.paused { | |
| 445 | device.resume(); | |
| 446 | } | |
| 447 | }, | |
| 448 | Command::Quit => { | |
| 449 | device.pause(); | |
| 450 | self.ended = true; | |
| 451 | break 'main; | |
| 452 | }, | |
| 453 | Command::Next => { | |
| 454 | device.pause(); | |
| 455 | break 'main; | |
| 456 | }, | |
| 457 | Command::Repeat => { | |
| 458 | device.pause(); | |
| 459 | device.clear(); | |
| 460 | let _ret = decoder.seek(0); | |
| 461 | while !eof && device.size() < refill_limit { | |
| 462 | eof = decoder.refill(&device); | |
| 463 | } | |
| 464 | if eof { | |
| 465 | break 'main; | |
| 466 | } | |
| 467 | if !self.paused { | |
| 468 | device.resume(); | |
| 469 | } | |
| 470 | }, | |
| 471 | Command::Pause => { | |
| 472 | self.paused = !self.paused; | |
| 473 | if self.paused { | |
| 474 | device.pause(); | |
| 475 | } else { | |
| 476 | device.resume(); | |
| 477 | } | |
| 478 | }, | |
| 479 | Command::VolumeUp => { | |
| 480 | self.volume = (self.volume + 10).min(200); | |
| 481 | decoder.volume = self.volume; | |
| 482 | }, | |
| 483 | Command::VolumeDown => { | |
| 484 | self.volume = self.volume.saturating_sub(10); | |
| 485 | decoder.volume = self.volume; | |
| 486 | }, | |
| 487 | Command::Mute => { | |
| 488 | self.mute = !self.mute; | |
| 489 | decoder.mute = self.mute; | |
| 490 | }, | |
| 491 | Command::Debug => { | |
| 492 | self.debug = !self.debug; | |
| 493 | }, | |
| 494 | Command::PauseDisplay => { | |
| 495 | no_display = true; | |
| 496 | if !self.paused { | |
| 497 | device.pause(); | |
| 498 | } | |
| 499 | }, | |
| 500 | Command::ResumeDisplay => { | |
| 501 | no_display = false; | |
| 502 | if !self.paused { | |
| 503 | device.resume(); | |
| 504 | } | |
| 505 | }, | |
| 506 | }; | |
| 507 | if !no_display { | |
| 508 | print!("\r{:60}\r", ' '); | |
| 509 | } | |
| 510 | } | |
| 511 | thread::sleep(Duration::from_millis(200)); | |
| 512 | } | |
| 513 | ||
| 514 | println!(); | |
| 515 | } | |
| 516 | } | |
| 517 | ||
| 518 | fn main() { | |
| 519 | let args: Vec<String> = std::env::args().collect(); | |
| 520 | ||
| 521 | let cmd_state = CmdLineState::new(); | |
| 522 | ||
| 523 | let (cmd_reader_thread, cmd_receiver) = start_reader(); | |
| 524 | let mut player = Player::new(); | |
| 525 | ||
| 526 | if args.len() == 1 { | |
| 527 | println!("usage: nihav-sndplay file1 file2 ..."); | |
| 528 | return; | |
| 529 | } | |
| 530 | ||
| 531 | if args[1] == "--help" { | |
| 532 | println!("usage: nihav-sndplay file1 file2 ..."); | |
| 533 | println!("commands:"); | |
| 534 | println!(" escape / q - quit"); | |
| 535 | println!(" space - pause / resume playback"); | |
| 536 | println!(" enter / end - play next file"); | |
| 537 | println!(" home - play current track from the beginning"); | |
| 538 | println!(" left / right - seek 10 seconds forward / back"); | |
| 539 | println!(" up / down - seek 1 minute forward / back"); | |
| 540 | println!(" pgup / pgdn - seek 10 minutes forward / back"); | |
| 541 | println!(" + / - - increase / decrease volume"); | |
| 542 | println!(" m - mute output"); | |
| 543 | return; | |
| 544 | } | |
| 545 | ||
| 546 | let mut aiter = args[1..].iter(); | |
| 547 | let mut start_time = NATimePoint::None; | |
| 548 | while let Some(arg) = aiter.next() { | |
| 549 | match arg.as_str() { | |
| 550 | "-start" => { | |
| 551 | if let Some(arg) = aiter.next() { | |
| 552 | if let Ok(val) = arg.parse() { | |
| 553 | start_time = val; | |
| 554 | } else { | |
| 555 | println!("invalid time"); | |
| 556 | } | |
| 557 | } else { | |
| 558 | println!("argument is required"); | |
| 559 | } | |
| 560 | }, | |
| 561 | "-vol" => { | |
| 562 | if let Some(arg) = aiter.next() { | |
| 563 | if let Ok(vol) = arg.parse::<u8>() { | |
| 564 | player.volume = vol.min(200); | |
| 565 | } else { | |
| 566 | println!("wrong volume"); | |
| 567 | } | |
| 568 | } | |
| 569 | }, | |
| 570 | _ => { | |
| 571 | player.play_file(arg, &cmd_receiver, start_time); | |
| 572 | if player.ended { | |
| 573 | break; | |
| 574 | } | |
| 575 | start_time = NATimePoint::None; | |
| 576 | }, | |
| 577 | }; | |
| 578 | } | |
| 579 | cmd_state.restore(); | |
| 580 | ||
| 581 | drop(cmd_reader_thread); | |
| 582 | unsafe { sdl2_sys::SDL_Quit(); } | |
| 583 | } |