use std::path::Path;
use std::time::{Duration, Instant};
use std::thread;
+use std::sync::atomic::{AtomicU8, Ordering};
use sdl2::event::{Event, WindowEvent};
-use sdl2::keyboard::Keycode;
+use sdl2::keyboard::{Keycode, Mod};
use sdl2::mouse::MouseButton;
use sdl2::render::{Canvas, Texture, TextureCreator};
use sdl2::pixels::PixelFormatEnum;
mod osd;
use osd::*;
+#[repr(u8)]
+#[derive(Clone,Copy,Debug,PartialEq)]
+enum DecodingState {
+ Normal,
+ Waiting,
+ Flush,
+ Prefetch,
+ Error,
+ End,
+}
+
+impl Default for DecodingState {
+ fn default() -> Self { DecodingState::Normal }
+}
+
+impl From<u8> for DecodingState {
+ fn from(val: u8) -> Self {
+ match val {
+ 0 => DecodingState::Normal,
+ 1 => DecodingState::Waiting,
+ 2 => DecodingState::Flush,
+ 3 => DecodingState::Prefetch,
+ 4 => DecodingState::End,
+ _ => DecodingState::Error,
+ }
+ }
+}
+
+struct DecoderState {
+ state: AtomicU8
+}
+
+impl DecoderState {
+ const fn new() -> Self {
+ Self {
+ state: AtomicU8::new(DecodingState::Normal as u8)
+ }
+ }
+ fn set_state(&self, state: DecodingState) {
+ self.state.store(state as u8, Ordering::Release);
+ }
+ fn get_state(&self) -> DecodingState {
+ self.state.load(Ordering::Acquire).into()
+ }
+ fn is_flushing(&self) -> bool {
+ matches!(self.get_state(), DecodingState::Flush | DecodingState::Error)
+ }
+}
+
#[cfg(feature="debug")]
macro_rules! debug_log {
($log: expr; $blk: block) => {
fn new(texture_creator: &'a TextureCreator<WindowContext>, width: usize, height: usize, len: usize) -> Self {
let mut pool = Vec::with_capacity(len);
for _ in 0..len + 1 {
- let rgb_tex = texture_creator.create_texture_streaming(PixelFormatEnum::RGB24, width as u32, height as u32).unwrap();
- let yuv_tex = texture_creator.create_texture_streaming(PixelFormatEnum::IYUV, ((width + 1) & !1) as u32, ((height + 1) & !1) as u32).unwrap();
+ let rgb_tex = texture_creator.create_texture_streaming(PixelFormatEnum::RGB24, width as u32, height as u32).expect("failed to create RGB texture");
+ let yuv_tex = texture_creator.create_texture_streaming(PixelFormatEnum::IYUV, ((width + 1) & !1) as u32, ((height + 1) & !1) as u32).expect("failed to create YUV texture");
pool.push(DispFrame{ ts: 0, is_yuv: false, valid: false, rgb_tex, yuv_tex });
}
pool[len].is_yuv = false;
pool[len].rgb_tex.with_lock(None, |buffer: &mut [u8], _pitch: usize| {
for el in buffer.iter_mut() { *el = 0; }
- }).unwrap();
+ }).expect("RGB texture could not be locked");
Self { pool, first_ts: 0, last_ts: 0, start: 0, end: 0, len, width, height }
}
if osd.is_active() {
self.pool[self.len].yuv_tex.with_lock(None, |buffer: &mut [u8], pitch: usize| {
osd.draw_yuv(buffer, pitch);
- }).unwrap();
+ }).expect("YUV texture locking failure");
}
&self.pool[self.len].yuv_tex
} else {
if osd.is_active() {
self.pool[self.len].rgb_tex.with_lock(None, |buffer: &mut [u8], pitch: usize| {
osd.draw_rgb(buffer, pitch);
- }).unwrap();
+ }).expect("RGB texture locking failure");
}
&self.pool[self.len].rgb_tex
}
if osd.is_active() {
frm.yuv_tex.with_lock(None, |buffer: &mut [u8], pitch: usize| {
osd.draw_yuv(buffer, pitch);
- }).unwrap();
+ }).expect("YUV texture locking failure");
}
&frm.yuv_tex
} else {
if osd.is_active() {
frm.rgb_tex.with_lock(None, |buffer: &mut [u8], pitch: usize| {
osd.draw_rgb(buffer, pitch);
- }).unwrap();
+ }).expect("RGB texture locking failure");
}
&frm.rgb_tex
};
canvas.clear();
- canvas.copy(texture, None, None).unwrap();
+ canvas.copy(texture, None, None).expect("canvas blit failure");
canvas.present();
disp_queue.move_start();
sdl_context: sdl2::Sdl,
vsystem: sdl2::VideoSubsystem,
asystem: sdl2::AudioSubsystem,
+ xpos: Option<i32>,
+ ypos: Option<i32>,
acontrol: AudioControl,
vcontrol: VideoControl,
impl Player {
fn new() -> Self {
- let sdl_context = sdl2::init().unwrap();
- let vsystem = sdl_context.video().unwrap();
- let asystem = sdl_context.audio().unwrap();
+ let sdl_context = sdl2::init().expect("SDL2 init failure");
+ let vsystem = sdl_context.video().expect("video subsystem init failure");
+ let asystem = sdl_context.audio().expect("audio subsystem init failure");
vsystem.disable_screen_saver();
- let acontrol = AudioControl::new(None, None, &asystem);
+ let acontrol = AudioControl::new(None, None, false, &asystem);
let vcontrol = VideoControl::new(None, 0, 0, 0, 0);
Self {
sdl_context, asystem, vsystem,
+ xpos: None,
+ ypos: None,
acontrol, vcontrol,
osd: OSD::new(),
#[cfg(feature="debug")]
- logfile: File::create("debug.log").unwrap(),
+ logfile: File::create("debug.log").expect("'debug.log' should be available for writing"),
}
}
- fn seek(&mut self, off: u64, fwd: bool, dmx: &mut Demuxer, disp_queue: &mut DispQueue) {
+ fn seek(&mut self, off: u64, fwd: bool, dmx: &mut Demuxer, disp_queue: &mut DispQueue) -> Result<(), ()> {
let cur_time = self.tkeep.get_cur_time();
let seektime = if fwd { cur_time + off * 1000 } else {
cur_time.saturating_sub(off * 1000) };
let ret = dmx.seek(NATimePoint::Milliseconds(seektime));
if ret.is_err() {
println!(" seek error");
- return;
+ return Ok(()); //TODO: not ignore some of seek errors?
}
self.acontrol.flush();
disp_queue.flush();
self.tkeep.reset_ts();
- self.prefill(dmx, disp_queue);
+ self.prefill(dmx, disp_queue)?;
if !disp_queue.is_empty() {
self.tkeep.reset_all(disp_queue.first_ts);
} else {
if !self.paused {
self.acontrol.resume();
}
+ Ok(())
}
- fn prefill(&mut self, dmx: &mut Demuxer, disp_queue: &mut DispQueue) {
+ fn prefill(&mut self, dmx: &mut Demuxer, disp_queue: &mut DispQueue) -> Result<(), ()> {
debug_log!(self; {" prefilling"});
while self.vcontrol.get_queue_size() < FRAME_QUEUE_LEN {
let mut try_send = self.acontrol.get_queue_size() < FRAME_QUEUE_LEN && (!self.has_video || (!self.vcontrol.is_filled(FRAME_QUEUE_LEN) && !disp_queue.is_full()));
self.vcontrol.fill(disp_queue);
std::thread::sleep(Duration::from_millis(10));
}
- self.vcontrol.wait_for_frames();
+ self.vcontrol.wait_for_frames()?;
self.vcontrol.fill(disp_queue);
}
debug_log!(self; {format!(" prefilling done, frames {}-{} audio {}", disp_queue.start, disp_queue.end, self.acontrol.get_fill())});
+ Ok(())
}
fn toggle_pause(&mut self) {
self.paused = !self.paused;
self.acontrol.resume();
}
}
- fn handle_events(&mut self, event_pump: &mut sdl2::EventPump, canvas: &mut Canvas<Window>, dmx: &mut Demuxer, disp_queue: &mut DispQueue) -> bool {
+ fn handle_events(&mut self, event_pump: &mut sdl2::EventPump, canvas: &mut Canvas<Window>, dmx: &mut Demuxer, disp_queue: &mut DispQueue) -> Result<bool, ()> {
for event in event_pump.poll_iter() {
if let Event::Quit {..} = event {
self.end = true;
println!();
- return true;
+ return Ok(true);
}
if let Event::Window {win_event: WindowEvent::Exposed, ..} = event {
canvas.clear();
- canvas.copy(disp_queue.get_last_texture(&self.osd), None, None).unwrap();
+ canvas.copy(disp_queue.get_last_texture(&self.osd), None, None).expect("blitting failure");
canvas.present();
}
- if let Event::MouseButtonDown {mouse_btn: MouseButton::Right, ..} = event {
- self.toggle_pause();
+ if let Event::MouseButtonDown {mouse_btn, ..} = event {
+ match mouse_btn {
+ MouseButton::Right => self.toggle_pause(),
+ MouseButton::Middle => self.osd.toggle(),
+ _ => {},
+ };
}
- if let Event::KeyDown {keycode: Some(keycode), ..} = event {
+ if let Event::KeyDown {keycode: Some(keycode), keymod, ..} = event {
match keycode {
- Keycode::Escape | Keycode::Q => {
+ Keycode::Escape => {
+ self.end = true;
+ println!();
+ return Ok(true);
+ },
+ Keycode::Q if matches!(keymod, Mod::RSHIFTMOD | Mod::LSHIFTMOD) => {
self.end = true;
println!();
- return true;
+ return Ok(true);
},
- Keycode::Return => return true,
- Keycode::Right => { self.seek(10, true, dmx, disp_queue); },
- Keycode::Left => { self.seek(10, false, dmx, disp_queue); },
- Keycode::Up => { self.seek(60, true, dmx, disp_queue); },
- Keycode::Down => { self.seek(60, false, dmx, disp_queue); },
- Keycode::PageUp => { self.seek(600, true, dmx, disp_queue); },
- Keycode::PageDown => { self.seek(600, false, dmx, disp_queue); },
+ Keycode::Return => return Ok(true),
+ Keycode::Right => { self.seek(10, true, dmx, disp_queue)?; },
+ Keycode::Left => { self.seek(10, false, dmx, disp_queue)?; },
+ Keycode::Up => { self.seek(60, true, dmx, disp_queue)?; },
+ Keycode::Down => { self.seek(60, false, dmx, disp_queue)?; },
+ Keycode::PageUp => { self.seek(600, true, dmx, disp_queue)?; },
+ Keycode::PageDown => { self.seek(600, false, dmx, disp_queue)?; },
Keycode::Space => { self.toggle_pause(); },
Keycode::Plus | Keycode::KpPlus => {
self.volume = (self.volume + 10).min(MAX_VOLUME);
}
}
}
- false
+ Ok(false)
}
fn play(&mut self, name: &str, start_time: NATimePoint) {
debug_log!(self; {format!("Playing {}", name)});
// prepare data source
let path = Path::new(name);
- let mut file = File::open(path).unwrap();
+ let mut file = if let Ok(handle) = File::open(path) {
+ handle
+ } else {
+ println!("failed to open {}", name);
+ return;
+ };
let dmx_fact;
let mut fr = FileReader::new_read(&mut file);
let mut br = ByteReader::new(&mut fr);
return;
}
dmx_fact = ret.unwrap();
- br.seek(SeekFrom::Start(0)).unwrap();
+ br.seek(SeekFrom::Start(0)).expect("should be able to seek to the start");
let ret = create_demuxer(dmx_fact, &mut br);
if ret.is_err() {
println!("error creating demuxer");
let mut tb_num = 0;
let mut tb_den = 0;
let mut ainfo: Option<NAAudioInfo> = None;
+ let mut sbr_hack = false;
let mut video_dec: Option<DecoderStuff> = None;
let mut audio_dec: Option<DecoderStuff> = None;
}
self.has_video = false;
self.has_audio = false;
+ self.osd.reset();
+ self.osd.set_duration(duration);
for i in 0..dmx.get_num_streams() {
let s = dmx.get_stream(i).unwrap();
let info = s.get_info();
dsupp.pool_u8 = NAVideoBufferPool::new(reorder_depth);
dsupp.pool_u16 = NAVideoBufferPool::new(reorder_depth);
dsupp.pool_u32 = NAVideoBufferPool::new(reorder_depth);
- dec.init(&mut dsupp, info).unwrap();
+ if dec.init(&mut dsupp, info).is_err() {
+ println!("failed to initialise video decoder");
+ return;
+ }
video_dec = Some(DecoderStuff{ dsupp, dec: DecoderType::Video(dec, reord) });
self.video_str = str_id;
let (tbn, tbd) = s.get_timebase();
let mut dec = (decfunc)();
let mut dsupp = Box::new(NADecoderSupport::new());
ainfo = info.get_properties().get_audio_info();
- dec.init(&mut dsupp, info).unwrap();
+ if let (true, Some(ref ai)) = (info.get_name() == "aac", ainfo) {
+ if ai.sample_rate < 32000 {
+ sbr_hack = true;
+ }
+ }
+ if dec.init(&mut dsupp, info).is_err() {
+ println!("failed to initialise audio decoder");
+ return;
+ }
audio_dec = Some(DecoderStuff{ dsupp, dec: DecoderType::Audio(dec) });
self.audio_str = str_id;
self.has_audio = true;
let mut new_vcontrol = VideoControl::new(video_dec, width, height, tb_num, tb_den);
std::mem::swap(&mut self.vcontrol, &mut new_vcontrol);
- let mut new_acontrol = AudioControl::new(audio_dec, ainfo, &self.asystem);
+ let mut new_acontrol = AudioControl::new(audio_dec, ainfo, sbr_hack, &self.asystem);
std::mem::swap(&mut self.acontrol, &mut new_acontrol);
if self.mute {
let fname = path.file_name();
let wname = if let Some(fname) = fname {
- "NihAV player - ".to_owned() + fname.to_str().unwrap()
+ "NihAV player - ".to_owned() + fname.to_str().expect("should be able to set window title")
} else {
"NihAV player".to_owned()
};
- let window = self.vsystem.window(&wname, width as u32, height as u32)
- .position_centered().build().unwrap();
- let mut canvas = window.into_canvas().build().unwrap();
+ let mut builder = self.vsystem.window(&wname, width as u32, height as u32);
+ let window = if let (Some(xpos), Some(ypos)) = (self.xpos, self.ypos) {
+ builder.position(xpos, ypos).build().expect("should be able to set window position")
+ } else {
+ builder.position_centered().build().expect("should be able to centre window")
+ };
+ let mut canvas = window.into_canvas().build().expect("should be able to build canvas");
let texture_creator = canvas.texture_creator();
let mut disp_q = DispQueue::new(&texture_creator, width, height, if self.has_video { FRAME_QUEUE_LEN } else { 0 });
if !self.has_video {
canvas.clear();
- canvas.copy(disp_q.get_last_texture(&self.osd), None, None).unwrap();
+ canvas.copy(disp_q.get_last_texture(&self.osd), None, None).expect("blit failure");
canvas.present();
}
}
// play
- self.prefill(&mut dmx, &mut disp_q);
- self.tkeep.reset_all(0);
+ if self.prefill(&mut dmx, &mut disp_q).is_err() {
+ std::mem::swap(&mut self.vcontrol, &mut new_vcontrol);
+ new_vcontrol.finish();
+ std::mem::swap(&mut self.acontrol, &mut new_acontrol);
+ new_acontrol.finish();
+ return;
+ }
+ self.tkeep.reset_all(if !disp_q.is_empty() { disp_q.first_ts } else { 0 });
if !self.paused {
self.acontrol.resume();
}
- let mut event_pump = self.sdl_context.event_pump().unwrap();
+ let mut event_pump = self.sdl_context.event_pump().expect("should be able to create event pump");
let mut last_disp = Instant::now();
let mut has_data = true;
'main: loop {
- if self.handle_events(&mut event_pump, &mut canvas, &mut dmx, &mut disp_q) {
+ let ret = self.handle_events(&mut event_pump, &mut canvas, &mut dmx, &mut disp_q);
+ if matches!(ret, Ok(true) | Err(_)) {
println!();
break 'main;
}
self.acontrol.try_send_audio(PktSendEvent::End);
has_data = false;
},
- Err(err) => { println!("demuxer error {:?}", err); },
+ Err(err) => {
+ println!("demuxer error {:?}", err);
+ if err == DemuxerError::IOError {
+ self.vcontrol.try_send_video(PktSendEvent::End);
+ self.acontrol.try_send_audio(PktSendEvent::End);
+ has_data = false;
+ }
+ },
Ok(pkt) => {
let streamno = pkt.get_stream().get_id();
if self.has_video && streamno == self.video_str {
thread::sleep(Duration::from_millis(20));
}
}
+ let (xpos, ypos) = canvas.into_window().position();
+ self.xpos = Some(xpos);
+ self.ypos = Some(ypos);
println!();
std::mem::swap(&mut self.vcontrol, &mut new_vcontrol);
new_vcontrol.finish();