feat: add looping, fix some issues with minimal example, update deps

This commit is contained in:
Yusuf Bera Ertan 2021-08-25 16:39:23 +03:00
parent cb7edfea36
commit 00019a036a
No known key found for this signature in database
GPG key ID: 1D8F8FAF2294D6EA
9 changed files with 3248 additions and 84 deletions

View file

@ -3,6 +3,8 @@ use gstreamer as gst;
use gstreamer_app as gst_app;
use iced::{image as img, Command, Image, Subscription};
use num_traits::ToPrimitive;
use std::convert::identity;
use std::future;
use std::sync::{mpsc, Arc, Mutex};
use std::time::Duration;
use thiserror::Error;
@ -22,7 +24,7 @@ impl From<Position> for gst::GenericFormattedValue {
fn from(pos: Position) -> Self {
match pos {
Position::Time(t) => gst::ClockTime::from_nseconds(t.as_nanos() as _).into(),
Position::Frame(f) => gst::format::Default(Some(f)).into(),
Position::Frame(f) => gst::format::Default(f).into(),
}
}
}
@ -69,6 +71,12 @@ pub enum VideoPlayerMessage {
EndOfPlayback,
}
impl VideoPlayerMessage {
fn into_cmd(self) -> Command<Self> {
Command::perform(future::ready(self), identity)
}
}
/// Video player which handles multimedia playback.
pub struct VideoPlayer {
bus: gst::Bus,
@ -83,6 +91,9 @@ pub struct VideoPlayer {
wait: mpsc::Receiver<()>,
paused: bool,
muted: bool,
looping: bool,
is_eos: bool,
restart_stream: bool,
}
impl Drop for VideoPlayer {
@ -105,21 +116,16 @@ impl VideoPlayer {
let source = gst::parse_launch(&format!("playbin uri=\"{}\" video-sink=\"videoconvert ! videoscale ! appsink name=app_sink caps=video/x-raw,format=BGRA,pixel-aspect-ratio=1/1\"", uri.as_str()))?;
let source = source.downcast::<gst::Bin>().unwrap();
let video_sink: gst::Element = source
.get_property("video-sink")
.unwrap()
.get()
.unwrap()
.unwrap();
let pad = video_sink.get_pads().get(0).cloned().unwrap();
let video_sink: gst::Element = source.property("video-sink").unwrap().get().unwrap();
let pad = video_sink.pads().get(0).cloned().unwrap();
let pad = pad.dynamic_cast::<gst::GhostPad>().unwrap();
let bin = pad
.get_parent_element()
.parent_element()
.unwrap()
.downcast::<gst::Bin>()
.unwrap();
let app_sink = bin.get_by_name("app_sink").unwrap();
let app_sink = bin.by_name("app_sink").unwrap();
let app_sink = app_sink.downcast::<gst_app::AppSink>().unwrap();
let frame = Arc::new(Mutex::new(None));
@ -131,21 +137,15 @@ impl VideoPlayer {
gst_app::AppSinkCallbacks::builder()
.new_sample(move |sink| {
let sample = sink.pull_sample().map_err(|_| gst::FlowError::Eos)?;
let buffer = sample.get_buffer().ok_or(gst::FlowError::Error)?;
let buffer = sample.buffer().ok_or(gst::FlowError::Error)?;
let map = buffer.map_readable().map_err(|_| gst::FlowError::Error)?;
let pad = sink.get_static_pad("sink").ok_or(gst::FlowError::Error)?;
let pad = sink.static_pad("sink").ok_or(gst::FlowError::Error)?;
let caps = pad.get_current_caps().ok_or(gst::FlowError::Error)?;
let s = caps.get_structure(0).ok_or(gst::FlowError::Error)?;
let width = s
.get::<i32>("width")
.map_err(|_| gst::FlowError::Error)?
.ok_or(gst::FlowError::Error)?;
let height = s
.get::<i32>("height")
.map_err(|_| gst::FlowError::Error)?
.ok_or(gst::FlowError::Error)?;
let caps = pad.current_caps().ok_or(gst::FlowError::Error)?;
let s = caps.structure(0).ok_or(gst::FlowError::Error)?;
let width = s.get::<i32>("width").map_err(|_| gst::FlowError::Error)?;
let height = s.get::<i32>("height").map_err(|_| gst::FlowError::Error)?;
*frame_ref.lock().map_err(|_| gst::FlowError::Error)? =
Some(img::Handle::from_pixels(
@ -164,44 +164,36 @@ impl VideoPlayer {
source.set_state(gst::State::Playing)?;
// wait for up to 5 seconds until the decoder gets the source capabilities
source.get_state(gst::ClockTime::from_seconds(5)).0?;
source.state(gst::ClockTime::from_seconds(5)).0?;
// extract resolution and framerate
// TODO(jazzfool): maybe we want to extract some other information too?
let caps = pad.get_current_caps().ok_or(Error::Caps)?;
let s = caps.get_structure(0).ok_or(Error::Caps)?;
let width = s
.get::<i32>("width")
.map_err(|_| Error::Caps)?
.ok_or(Error::Caps)?;
let height = s
.get::<i32>("height")
.map_err(|_| Error::Caps)?
.ok_or(Error::Caps)?;
let caps = pad.current_caps().ok_or(Error::Caps)?;
let s = caps.structure(0).ok_or(Error::Caps)?;
let width = s.get::<i32>("width").map_err(|_| Error::Caps)?;
let height = s.get::<i32>("height").map_err(|_| Error::Caps)?;
let framerate = s
.get::<gst::Fraction>("framerate")
.map_err(|_| Error::Caps)?
.ok_or(Error::Caps)?;
.map_err(|_| Error::Caps)?;
let duration = if !live {
std::time::Duration::from_nanos(
source
.query_duration::<gst::ClockTime>()
.ok_or(Error::Duration)?
.nanoseconds()
.ok_or(Error::Duration)?,
.nseconds(),
)
} else {
std::time::Duration::from_secs(0)
};
Ok(VideoPlayer {
bus: source.get_bus().unwrap(),
bus: source.bus().unwrap(),
source,
width,
height,
framerate: num_rational::Rational::new(
framerate: num_rational::Rational32::new(
*framerate.numer() as _,
*framerate.denom() as _,
)
@ -212,15 +204,20 @@ impl VideoPlayer {
wait,
paused: false,
muted: false,
looping: false,
is_eos: false,
restart_stream: false,
})
}
/// Get the size/resolution of the video as `(width, height)`.
#[inline(always)]
pub fn size(&self) -> (i32, i32) {
(self.width, self.height)
}
/// Get the framerate of the video as frames per second.
#[inline(always)]
pub fn framerate(&self) -> f64 {
self.framerate
}
@ -240,13 +237,28 @@ impl VideoPlayer {
}
/// Get if the audio is muted or not.
#[inline(always)]
pub fn muted(&self) -> bool {
self.muted
}
#[inline(always)]
pub fn eos(&self) -> bool {
self.is_eos
}
#[inline(always)]
pub fn looping(&self) -> bool {
self.looping
}
#[inline(always)]
pub fn set_looping(&mut self, looping: bool) {
self.looping = looping;
}
/// Set if the media is paused or not.
pub fn set_paused(&mut self, paused: bool) {
self.paused = paused;
self.source
.set_state(if paused {
gst::State::Paused
@ -254,9 +266,14 @@ impl VideoPlayer {
gst::State::Playing
})
.unwrap(/* state was changed in ctor; state errors caught there */);
self.paused = paused;
if self.is_eos && !paused {
self.restart_stream = true;
}
}
/// Get if the media is paused or not.
#[inline(always)]
pub fn paused(&self) -> bool {
self.paused
}
@ -270,16 +287,17 @@ impl VideoPlayer {
}
/// Get the current playback position in time.
pub fn position(&self) -> Option<std::time::Duration> {
pub fn position(&self) -> std::time::Duration {
std::time::Duration::from_nanos(
self.source
.query_position::<gst::ClockTime>()?
.nanoseconds()?,
.query_position::<gst::ClockTime>()
.map_or(0, |pos| pos.nseconds()),
)
.into()
}
/// Get the media duration.
#[inline(always)]
pub fn duration(&self) -> std::time::Duration {
self.duration
}
@ -289,8 +307,8 @@ impl VideoPlayer {
/// 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)?;
let paused = self.paused();
let pos = self.position();
self.set_paused(false);
let out = positions
.iter()
@ -308,28 +326,45 @@ impl VideoPlayer {
pub fn update(&mut self, message: VideoPlayerMessage) -> Command<VideoPlayerMessage> {
match message {
VideoPlayerMessage::NextFrame => {
let mut cmd = Command::none();
let mut cmds = Vec::new();
let mut restart_stream = false;
if self.restart_stream {
restart_stream = true;
self.restart_stream = false;
}
let mut eos_pause = false;
for msg in self.bus.iter() {
match msg.view() {
gst::MessageView::Error(err) => panic!("{:#?}", err),
gst::MessageView::Eos(_eos) => {
cmd = Command::batch(vec![
cmd,
Command::perform(async {}, |_| VideoPlayerMessage::EndOfPlayback),
])
cmds.push(VideoPlayerMessage::EndOfPlayback.into_cmd());
if self.looping {
restart_stream = true;
} else {
eos_pause = true;
}
}
_ => {}
}
}
cmd
if restart_stream {
if let Err(err) = self.restart_stream() {
eprintln!("cannot restart stream (can't seek): {:#?}", err);
}
} else if eos_pause {
self.is_eos = true;
self.set_paused(true);
}
return Command::batch(cmds);
}
_ => Command::none(),
VideoPlayerMessage::EndOfPlayback => {}
}
Command::none()
}
pub fn subscription(&self) -> Subscription<VideoPlayerMessage> {
if !self.paused {
time::every(Duration::from_secs_f64(0.5 / self.framerate))
if self.restart_stream || (!self.is_eos && !self.paused()) {
iced::time::every(Duration::from_secs_f64(0.5 / self.framerate))
.map(|_| VideoPlayerMessage::NextFrame)
} else {
Subscription::none()
@ -349,11 +384,11 @@ impl VideoPlayer {
pub fn frame_view(&mut self) -> Image {
Image::new(self.frame_image())
}
}
// until iced 0.2 is released, which has this built-in
mod time {
pub fn every(duration: std::time::Duration) -> iced::Subscription<std::time::Instant> {
iced::time::every(duration)
pub fn restart_stream(&mut self) -> Result<(), Error> {
self.is_eos = false;
self.set_paused(false);
self.seek(0)?;
Ok(())
}
}