add thumbnail support

This commit is contained in:
jazzfool 2020-08-31 11:19:59 +10:00
parent 76de72cdca
commit 09286fbb0f
2 changed files with 50 additions and 8 deletions

View file

@ -14,6 +14,7 @@ Features:
- Audio support. - Audio support.
- Programmatic control. - Programmatic control.
- Small (around 300 lines). - Small (around 300 lines).
- Capture thumbnails from a set of timestamps.
Limitations (hopefully to be fixed): Limitations (hopefully to be fixed):
- GStreamer hardware acceleration not working? (leads to choppy playback in some scenarios). - GStreamer hardware acceleration not working? (leads to choppy playback in some scenarios).

View file

@ -1,13 +1,14 @@
use gst::prelude::*; use gst::prelude::*;
use gstreamer as gst; use gstreamer as gst;
use gstreamer_app as gst_app; use gstreamer_app as gst_app;
use iced::{image as img, Image, Subscription}; use iced::{image as img, Command, Image, Subscription};
use num_traits::ToPrimitive; use num_traits::ToPrimitive;
use std::sync::{Arc, Mutex}; use std::sync::{mpsc, Arc, Mutex};
use std::time::Duration; use std::time::Duration;
use thiserror::Error; use thiserror::Error;
/// Position in the media. /// Position in the media.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Position { pub enum Position {
/// Position based on time. /// Position based on time.
/// ///
@ -58,11 +59,14 @@ pub enum Error {
Caps, Caps,
#[error("failed to query media duration or position")] #[error("failed to query media duration or position")]
Duration, Duration,
#[error("failed to sync with playback")]
Sync,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VideoPlayerMessage { pub enum VideoPlayerMessage {
NextFrame, NextFrame,
EndOfPlayback,
} }
/// Video player which handles multimedia playback. /// Video player which handles multimedia playback.
@ -76,6 +80,7 @@ pub struct VideoPlayer {
duration: std::time::Duration, duration: std::time::Duration,
frame: Arc<Mutex<Option<img::Handle>>>, frame: Arc<Mutex<Option<img::Handle>>>,
wait: mpsc::Receiver<()>,
paused: bool, paused: bool,
muted: bool, muted: bool,
} }
@ -116,6 +121,8 @@ impl VideoPlayer {
let frame = Arc::new(Mutex::new(None)); let frame = Arc::new(Mutex::new(None));
let frame_ref = Arc::clone(&frame); let frame_ref = Arc::clone(&frame);
let (notify, wait) = mpsc::channel();
app_sink.set_callbacks( app_sink.set_callbacks(
gst_app::AppSinkCallbacks::builder() gst_app::AppSinkCallbacks::builder()
.new_sample(move |sink| { .new_sample(move |sink| {
@ -143,6 +150,8 @@ impl VideoPlayer {
map.as_slice().to_owned(), map.as_slice().to_owned(),
)); ));
notify.send(()).map_err(|_| gst::FlowError::Error)?;
Ok(gst::FlowSuccess::Ok) Ok(gst::FlowSuccess::Ok)
}) })
.build(), .build(),
@ -192,6 +201,7 @@ impl VideoPlayer {
duration, duration,
frame, frame,
wait,
paused: false, paused: false,
muted: false, muted: false,
}) })
@ -227,10 +237,10 @@ impl VideoPlayer {
} }
/// Set if the media is paused or not. /// Set if the media is paused or not.
pub fn set_paused(&mut self, pause: bool) { pub fn set_paused(&mut self, paused: bool) {
self.paused = pause; self.paused = paused;
self.source self.source
.set_state(if pause { .set_state(if paused {
gst::State::Paused gst::State::Paused
} else { } else {
gst::State::Playing gst::State::Playing
@ -266,15 +276,46 @@ impl VideoPlayer {
self.duration self.duration
} }
pub fn update(&mut self, message: VideoPlayerMessage) { /// Generates a list of thumbnails based on a set of positions in the media.
///
/// Slow; only needs to be called once for each instance.
/// It's best to call this at the very start of playback, otherwise the position may shift.
pub fn thumbnails(&mut self, positions: &[Position]) -> Result<Vec<img::Handle>, Error> {
let paused = self.paused;
let pos = self.position().ok_or(Error::Duration)?;
self.set_paused(false);
let out = positions
.iter()
.map(|&pos| {
self.seek(pos)?;
self.wait.recv().map_err(|_| Error::Sync)?;
Ok(self.frame_image())
})
.collect();
self.set_paused(paused);
self.seek(pos)?;
out
}
pub fn update(&mut self, message: VideoPlayerMessage) -> Command<VideoPlayerMessage> {
match message { match message {
VideoPlayerMessage::NextFrame => { VideoPlayerMessage::NextFrame => {
let mut cmd = Command::none();
for msg in self.bus.iter() { for msg in self.bus.iter() {
if let gst::MessageView::Error(err) = msg.view() { match msg.view() {
panic!("{:#?}", err); gst::MessageView::Error(err) => panic!("{:#?}", err),
gst::MessageView::Eos(_eos) => {
cmd = Command::batch(vec![
cmd,
Command::perform(async {}, |_| VideoPlayerMessage::EndOfPlayback),
])
}
_ => {}
} }
} }
cmd
} }
_ => Command::none(),
} }
} }