diff --git a/Cargo.lock b/Cargo.lock index 464e905..7d30ec2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1784,7 +1784,7 @@ dependencies = [ [[package]] name = "iced_video_player" -version = "0.5.0" +version = "0.6.0" dependencies = [ "glib", "gstreamer", diff --git a/Cargo.toml b/Cargo.toml index 6907529..bb6d6bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/jazzfool/iced_video_player" readme = "README.md" keywords = ["gui", "iced", "video"] categories = ["gui", "multimedia"] -version = "0.5.0" +version = "0.6.0" authors = ["jazzfool"] edition = "2021" resolver = "2" diff --git a/examples/minimal.rs b/examples/minimal.rs index c290e22..df24192 100644 --- a/examples/minimal.rs +++ b/examples/minimal.rs @@ -1,5 +1,5 @@ use iced::{ - widget::{Button, Column, Row, Slider, Text}, + widget::{Button, Column, Container, Row, Slider, Space, Text}, Element, }; use iced_video_player::{Video, VideoPlayer}; @@ -82,21 +82,42 @@ impl App { fn view(&self) -> Element { Column::new() .push( - VideoPlayer::new(&self.video) - .on_end_of_stream(Message::EndOfStream) - .on_new_frame(Message::NewFrame), + Container::new( + VideoPlayer::new(&self.video) + .width(iced::Length::Shrink) + .height(iced::Length::Shrink) + .content_fit(iced::ContentFit::Contain) + .on_end_of_stream(Message::EndOfStream) + .on_new_frame(Message::NewFrame), + ) + .width(iced::Length::Fill) + .height(iced::Length::Fill) + .center(iced::Length::Fill), + ) + .push( + Container::new( + Slider::new( + 0.0..=self.video.duration().as_secs_f64(), + self.position, + Message::Seek, + ) + .step(0.1) + .on_release(Message::SeekRelease), + ) + .padding(iced::Padding::new(5.0).left(10.0).right(10.0)), ) .push( Row::new() .spacing(5) .align_y(iced::alignment::Vertical::Center) - .padding(iced::Padding::new(5.0)) + .padding(iced::Padding::new(10.0).top(0.0)) .push( Button::new(Text::new(if self.video.paused() { "Play" } else { "Pause" })) + .width(80.0) .on_press(Message::TogglePause), ) .push( @@ -105,21 +126,19 @@ impl App { } else { "Enable Loop" })) + .width(120.0) .on_press(Message::ToggleLoop), ) - .push(Text::new(format!( - "{:#?}s / {:#?}s", - self.position as u64, - self.video.duration().as_secs() - ))) .push( - Slider::new( - 0.0..=self.video.duration().as_secs_f64(), - self.position, - Message::Seek, - ) - .step(0.1) - .on_release(Message::SeekRelease), + Text::new(format!( + "{}:{:02}s / {}:{:02}s", + self.position as u64 / 60, + self.position as u64 % 60, + self.video.duration().as_secs() / 60, + self.video.duration().as_secs() % 60, + )) + .width(iced::Length::Fill) + .align_x(iced::alignment::Horizontal::Right), ), ) .into() diff --git a/src/video.rs b/src/video.rs index d2eccd2..8f06d93 100644 --- a/src/video.rs +++ b/src/video.rs @@ -258,13 +258,11 @@ impl Video { } /// Get the size/resolution of the video as `(width, height)`. - #[inline(always)] pub fn size(&self) -> (i32, i32) { (self.0.borrow().width, self.0.borrow().height) } /// Get the framerate of the video as frames per second. - #[inline(always)] pub fn framerate(&self) -> f64 { self.0.borrow().framerate } @@ -285,25 +283,21 @@ impl Video { } /// Get if the audio is muted or not. - #[inline(always)] pub fn muted(&self) -> bool { self.0.borrow().muted } /// Get if the stream ended or not. - #[inline(always)] pub fn eos(&self) -> bool { self.0.borrow().is_eos } /// Get if the media will loop or not. - #[inline(always)] pub fn looping(&self) -> bool { self.0.borrow().looping } /// Set if the media will loop or not. - #[inline(always)] pub fn set_looping(&mut self, looping: bool) { self.0.get_mut().looping = looping; } @@ -315,7 +309,6 @@ impl Video { } /// Get if the media is paused or not. - #[inline(always)] pub fn paused(&self) -> bool { self.0.borrow().paused } @@ -350,7 +343,6 @@ impl Video { } /// Get the media duration. - #[inline(always)] pub fn duration(&self) -> std::time::Duration { self.0.borrow().duration } @@ -360,6 +352,11 @@ impl Video { self.0.borrow_mut().restart_stream() } + /// Get the underlying GStreamer pipeline. + pub fn pipeline(&self) -> gst::Pipeline { + self.0.borrow().source.clone() + } + /// Generates a list of thumbnails based on a set of positions in the media. /// /// Slow; only needs to be called once for each instance. diff --git a/src/video_player.rs b/src/video_player.rs index f7ee2d9..0a5bbb2 100644 --- a/src/video_player.rs +++ b/src/video_player.rs @@ -2,7 +2,7 @@ use crate::{pipeline::VideoPrimitive, video::Video}; use gstreamer as gst; use iced::{ advanced::{self, graphics::core::event::Status, layout, widget, Widget}, - Element, + Element, Size, }; use iced_wgpu::primitive::Renderer as PrimitiveRenderer; use log::error; @@ -15,6 +15,9 @@ where Renderer: PrimitiveRenderer, { video: &'a Video, + content_fit: iced::ContentFit, + width: iced::Length, + height: iced::Length, on_end_of_stream: Option, on_new_frame: Option, on_error: Option Message + 'a>>, @@ -29,6 +32,9 @@ where pub fn new(video: &'a Video) -> Self { VideoPlayer { video, + content_fit: iced::ContentFit::default(), + width: iced::Length::Shrink, + height: iced::Length::Shrink, on_end_of_stream: None, on_new_frame: None, on_error: None, @@ -36,6 +42,30 @@ where } } + /// Sets the width of the `VideoPlayer` boundaries. + pub fn width(self, width: impl Into) -> Self { + VideoPlayer { + width: width.into(), + ..self + } + } + + /// Sets the height of the `VideoPlayer` boundaries. + pub fn height(self, height: impl Into) -> Self { + VideoPlayer { + height: height.into(), + ..self + } + } + + /// Sets the `ContentFit` of the `VideoPlayer`. + pub fn content_fit(self, content_fit: iced::ContentFit) -> Self { + VideoPlayer { + content_fit: content_fit, + ..self + } + } + /// Message to send when the video reaches the end of stream (i.e., the video ends). pub fn on_end_of_stream(self, on_end_of_stream: Message) -> Self { VideoPlayer { @@ -82,22 +112,24 @@ where _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let (width, height) = self.video.size(); - let (width, height) = (width as f32, height as f32); - let size = limits.resolve( - iced::Length::Fill, - iced::Length::Fill, - iced::Size::new(width, height), - ); + let (video_width, video_height) = self.video.size(); - // fixed aspect ratio + never exceed available size - let size = if (size.width / size.height) > (width / height) { - iced::Size::new(size.height * (width / height), size.height) - } else { - iced::Size::new(size.width, size.width * (height / width)) + // based on `Image::layout` + let image_size = iced::Size::new(video_width as f32, video_height as f32); + let raw_size = limits.resolve(self.width, self.height, image_size); + let full_size = self.content_fit.fit(image_size, raw_size); + let final_size = iced::Size { + width: match self.width { + iced::Length::Shrink => f32::min(raw_size.width, full_size.width), + _ => raw_size.width, + }, + height: match self.height { + iced::Length::Shrink => f32::min(raw_size.height, full_size.height), + _ => raw_size.height, + }, }; - layout::Node::new(size) + layout::Node::new(final_size) } fn draw(