video: make thread safe

This commit is contained in:
jazzfool 2024-11-03 01:17:02 +11:00
parent 68973748fd
commit 0250a2619b
2 changed files with 40 additions and 30 deletions

View file

@ -3,10 +3,10 @@ use gstreamer as gst;
use gstreamer_app as gst_app; use gstreamer_app as gst_app;
use gstreamer_app::prelude::*; use gstreamer_app::prelude::*;
use iced::widget::image as img; use iced::widget::image as img;
use std::cell::RefCell;
use std::num::NonZeroU8; use std::num::NonZeroU8;
use std::ops::{Deref, DerefMut};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
/// Position in the media. /// Position in the media.
@ -176,11 +176,11 @@ impl Internal {
/// A multimedia video loaded from a URI (e.g., a local file path or HTTP stream). /// A multimedia video loaded from a URI (e.g., a local file path or HTTP stream).
#[derive(Debug)] #[derive(Debug)]
pub struct Video(pub(crate) RefCell<Internal>); pub struct Video(pub(crate) RwLock<Internal>);
impl Drop for Video { impl Drop for Video {
fn drop(&mut self) { fn drop(&mut self) {
let inner = self.0.get_mut(); let inner = self.0.get_mut().expect("failed to lock");
inner inner
.source .source
@ -368,7 +368,7 @@ impl Video {
} }
}); });
Ok(Video(RefCell::new(Internal { Ok(Video(RwLock::new(Internal {
id, id,
bus: pipeline.bus().unwrap(), bus: pipeline.bus().unwrap(),
@ -397,14 +397,26 @@ impl Video {
}))) })))
} }
pub(crate) fn read(&self) -> impl Deref<Target = Internal> + '_ {
self.0.read().expect("lock")
}
pub(crate) fn write(&self) -> impl DerefMut<Target = Internal> + '_ {
self.0.write().expect("lock")
}
pub(crate) fn get_mut(&mut self) -> impl DerefMut<Target = Internal> + '_ {
self.0.get_mut().expect("lock")
}
/// Get the size/resolution of the video as `(width, height)`. /// Get the size/resolution of the video as `(width, height)`.
pub fn size(&self) -> (i32, i32) { pub fn size(&self) -> (i32, i32) {
(self.0.borrow().width, self.0.borrow().height) (self.read().width, self.read().height)
} }
/// Get the framerate of the video as frames per second. /// Get the framerate of the video as frames per second.
pub fn framerate(&self) -> f64 { pub fn framerate(&self) -> f64 {
self.0.borrow().framerate self.read().framerate
} }
/// Set the volume multiplier of the audio. /// Set the volume multiplier of the audio.
@ -412,74 +424,72 @@ impl Video {
/// ///
/// This uses a linear scale, for example `0.5` is perceived as half as loud. /// This uses a linear scale, for example `0.5` is perceived as half as loud.
pub fn set_volume(&mut self, volume: f64) { pub fn set_volume(&mut self, volume: f64) {
self.0.get_mut().source.set_property("volume", volume); self.get_mut().source.set_property("volume", volume);
self.set_muted(self.muted()); // for some reason gstreamer unmutes when changing volume? self.set_muted(self.muted()); // for some reason gstreamer unmutes when changing volume?
} }
/// Get the volume multiplier of the audio. /// Get the volume multiplier of the audio.
pub fn volume(&self) -> f64 { pub fn volume(&self) -> f64 {
self.0.borrow().source.property("volume") self.read().source.property("volume")
} }
/// Set if the audio is muted or not, without changing the volume. /// Set if the audio is muted or not, without changing the volume.
pub fn set_muted(&mut self, muted: bool) { pub fn set_muted(&mut self, muted: bool) {
self.0.get_mut().source.set_property("mute", muted); self.get_mut().source.set_property("mute", muted);
} }
/// Get if the audio is muted or not. /// Get if the audio is muted or not.
pub fn muted(&self) -> bool { pub fn muted(&self) -> bool {
self.0.borrow().source.property("mute") self.read().source.property("mute")
} }
/// Get if the stream ended or not. /// Get if the stream ended or not.
pub fn eos(&self) -> bool { pub fn eos(&self) -> bool {
self.0.borrow().is_eos self.read().is_eos
} }
/// Get if the media will loop or not. /// Get if the media will loop or not.
pub fn looping(&self) -> bool { pub fn looping(&self) -> bool {
self.0.borrow().looping self.read().looping
} }
/// Set if the media will loop or not. /// Set if the media will loop or not.
pub fn set_looping(&mut self, looping: bool) { pub fn set_looping(&mut self, looping: bool) {
self.0.get_mut().looping = looping; self.get_mut().looping = looping;
} }
/// Set if the media is paused or not. /// Set if the media is paused or not.
pub fn set_paused(&mut self, paused: bool) { pub fn set_paused(&mut self, paused: bool) {
let inner = self.0.get_mut(); self.get_mut().set_paused(paused)
inner.set_paused(paused);
} }
/// Get if the media is paused or not. /// Get if the media is paused or not.
pub fn paused(&self) -> bool { pub fn paused(&self) -> bool {
self.0.borrow().paused() self.read().paused()
} }
/// Jumps to a specific position in the media. /// Jumps to a specific position in the media.
/// Passing `true` to the `accurate` parameter will result in more accurate seeking, /// Passing `true` to the `accurate` parameter will result in more accurate seeking,
/// however, it is also slower. For most seeks (e.g., scrubbing) this is not needed. /// however, it is also slower. For most seeks (e.g., scrubbing) this is not needed.
pub fn seek(&mut self, position: impl Into<Position>, accurate: bool) -> Result<(), Error> { pub fn seek(&mut self, position: impl Into<Position>, accurate: bool) -> Result<(), Error> {
self.0.get_mut().seek(position, accurate) self.get_mut().seek(position, accurate)
} }
/// Set the playback speed of the media. /// Set the playback speed of the media.
/// The default speed is `1.0`. /// The default speed is `1.0`.
pub fn set_speed(&mut self, speed: f64) -> Result<(), Error> { pub fn set_speed(&mut self, speed: f64) -> Result<(), Error> {
self.0.get_mut().set_speed(speed) self.get_mut().set_speed(speed)
} }
/// Get the current playback speed. /// Get the current playback speed.
pub fn speed(&self) -> f64 { pub fn speed(&self) -> f64 {
self.0.borrow().speed self.read().speed
} }
/// Get the current playback position in time. /// Get the current playback position in time.
pub fn position(&self) -> Duration { pub fn position(&self) -> Duration {
Duration::from_nanos( Duration::from_nanos(
self.0 self.read()
.borrow()
.source .source
.query_position::<gst::ClockTime>() .query_position::<gst::ClockTime>()
.map_or(0, |pos| pos.nseconds()), .map_or(0, |pos| pos.nseconds()),
@ -488,18 +498,18 @@ impl Video {
/// Get the media duration. /// Get the media duration.
pub fn duration(&self) -> Duration { pub fn duration(&self) -> Duration {
self.0.borrow().duration self.read().duration
} }
/// Restarts a stream; seeks to the first frame and unpauses, sets the `eos` flag to false. /// Restarts a stream; seeks to the first frame and unpauses, sets the `eos` flag to false.
pub fn restart_stream(&mut self) -> Result<(), Error> { pub fn restart_stream(&mut self) -> Result<(), Error> {
self.0.get_mut().restart_stream() self.get_mut().restart_stream()
} }
/// Set the subtitle URL to display. /// Set the subtitle URL to display.
pub fn set_subtitle_url(&mut self, url: &url::Url) -> Result<(), Error> { pub fn set_subtitle_url(&mut self, url: &url::Url) -> Result<(), Error> {
let paused = self.paused(); let paused = self.paused();
let inner = self.0.get_mut(); let mut inner = self.get_mut();
inner.source.set_state(gst::State::Ready)?; inner.source.set_state(gst::State::Ready)?;
inner.source.set_property("suburi", url.as_str()); inner.source.set_property("suburi", url.as_str());
inner.set_paused(paused); inner.set_paused(paused);
@ -508,12 +518,12 @@ impl Video {
/// Get the current subtitle URL. /// Get the current subtitle URL.
pub fn subtitle_url(&self) -> Option<url::Url> { pub fn subtitle_url(&self) -> Option<url::Url> {
url::Url::parse(&self.0.borrow().source.property::<String>("suburi")).ok() url::Url::parse(&self.read().source.property::<String>("suburi")).ok()
} }
/// Get the underlying GStreamer pipeline. /// Get the underlying GStreamer pipeline.
pub fn pipeline(&self) -> gst::Pipeline { pub fn pipeline(&self) -> gst::Pipeline {
self.0.borrow().source.clone() self.read().source.clone()
} }
/// Generates a list of thumbnails based on a set of positions in the media, downscaled by a given factor. /// Generates a list of thumbnails based on a set of positions in the media, downscaled by a given factor.
@ -538,7 +548,7 @@ impl Video {
self.set_muted(true); self.set_muted(true);
let out = { let out = {
let inner = self.0.borrow(); let inner = self.read();
let width = inner.width; let width = inner.width;
let height = inner.height; let height = inner.height;
positions positions

View file

@ -156,7 +156,7 @@ where
_cursor: advanced::mouse::Cursor, _cursor: advanced::mouse::Cursor,
_viewport: &iced::Rectangle, _viewport: &iced::Rectangle,
) { ) {
let mut inner = self.video.0.borrow_mut(); let mut inner = self.video.write();
// bounds based on `Image::draw` // bounds based on `Image::draw`
let image_size = iced::Size::new(inner.width as f32, inner.height as f32); let image_size = iced::Size::new(inner.width as f32, inner.height as f32);
@ -215,7 +215,7 @@ where
shell: &mut advanced::Shell<'_, Message>, shell: &mut advanced::Shell<'_, Message>,
_viewport: &iced::Rectangle, _viewport: &iced::Rectangle,
) -> Status { ) -> Status {
let mut inner = self.video.0.borrow_mut(); let mut inner = self.video.write();
if let iced::Event::Window(iced::window::Event::RedrawRequested(_)) = event { if let iced::Event::Window(iced::window::Event::RedrawRequested(_)) = event {
if inner.restart_stream || (!inner.is_eos && !inner.paused()) { if inner.restart_stream || (!inner.is_eos && !inner.paused()) {