feat(gst): enhance GStreamer integration with new modules and improved API
This commit introduces significant enhancements to the GStreamer integration by: - Adding new modules for bins, caps, elements, pads, and plugins - Implementing a more ergonomic API with helper methods like play(), pause(), ready() - Adding support for various GStreamer plugins including app, autodetect, playback, and videoconvertscale - Improving error handling with better context attachment - Updating dependencies to latest versions including gstreamer-video 0.24.4 - Refactoring existing code to use modern Rust patterns and features
This commit is contained in:
2
gst/src/plugins/app.rs
Normal file
2
gst/src/plugins/app.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod appsink;
|
||||
pub use appsink::*;
|
||||
108
gst/src/plugins/app/appsink.rs
Normal file
108
gst/src/plugins/app/appsink.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use crate::*;
|
||||
|
||||
pub struct AppSink {
|
||||
inner: gstreamer::Element,
|
||||
}
|
||||
|
||||
impl IsElement for AppSink {
|
||||
fn as_element(&self) -> &Element {
|
||||
unsafe { core::mem::transmute(&self.inner) }
|
||||
}
|
||||
|
||||
fn into_element(self) -> Element {
|
||||
Element { inner: self.inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl Sink for AppSink {}
|
||||
|
||||
impl AppSink {
|
||||
fn appsink(&self) -> &gstreamer_app::AppSink {
|
||||
self.inner
|
||||
.downcast_ref::<gstreamer_app::AppSink>()
|
||||
.expect("Failed to downcast to AppSink")
|
||||
}
|
||||
pub fn new(name: impl AsRef<str>) -> Result<Self> {
|
||||
use gstreamer::prelude::*;
|
||||
let inner = gstreamer::ElementFactory::make("appsink")
|
||||
.name(name.as_ref())
|
||||
.build()
|
||||
.change_context(Error)
|
||||
.attach("Failed to create appsink element")?;
|
||||
Ok(AppSink { inner })
|
||||
}
|
||||
|
||||
pub fn with_caps(mut self, caps: &gstreamer::Caps) -> Self {
|
||||
use gstreamer::prelude::*;
|
||||
// self.inner.set_caps(Some(caps));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_callbacks(&self, callbacks: gstreamer_app::AppSinkCallbacks) -> Result<()> {
|
||||
self.appsink().set_callbacks(callbacks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn pull_sample(&self, timeout: impl Into<Option<core::time::Duration>>) -> Result<Sample> {
|
||||
use gstreamer::prelude::*;
|
||||
self.appsink()
|
||||
.pull_sample()
|
||||
.change_context(Error)
|
||||
.attach("Failed to pull sample from AppSink")
|
||||
.map(Sample::from)
|
||||
}
|
||||
pub fn try_pull_sample(
|
||||
&self,
|
||||
timeout: impl Into<Option<core::time::Duration>>,
|
||||
) -> Result<Option<Sample>> {
|
||||
use gstreamer::prelude::*;
|
||||
Ok(self
|
||||
.appsink()
|
||||
.try_pull_sample(duration_to_clocktime(timeout)?)
|
||||
.map(From::from))
|
||||
}
|
||||
|
||||
pub fn pull_preroll(&self, timeout: impl Into<Option<core::time::Duration>>) -> Result<Sample> {
|
||||
use gstreamer::prelude::*;
|
||||
self.appsink()
|
||||
.pull_preroll()
|
||||
.change_context(Error)
|
||||
.attach("Failed to pull preroll sample from AppSink")
|
||||
.map(Sample::from)
|
||||
}
|
||||
|
||||
pub fn try_pull_preroll(
|
||||
&self,
|
||||
timeout: impl Into<Option<core::time::Duration>>,
|
||||
) -> Result<Option<Sample>> {
|
||||
use gstreamer::prelude::*;
|
||||
Ok(self
|
||||
.appsink()
|
||||
.try_pull_preroll(duration_to_clocktime(timeout)?)
|
||||
.map(From::from))
|
||||
}
|
||||
}
|
||||
|
||||
fn duration_to_clocktime(
|
||||
timeout: impl Into<Option<core::time::Duration>>,
|
||||
) -> Result<Option<gstreamer::ClockTime>> {
|
||||
match timeout.into() {
|
||||
Some(dur) => {
|
||||
let clocktime = gstreamer::ClockTime::try_from(dur)
|
||||
.change_context(Error)
|
||||
.attach("Failed to convert duration to ClockTime")?;
|
||||
Ok(Some(clocktime))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Sample {
|
||||
inner: gstreamer::Sample,
|
||||
}
|
||||
|
||||
impl From<gstreamer::Sample> for Sample {
|
||||
fn from(inner: gstreamer::Sample) -> Self {
|
||||
Sample { inner }
|
||||
}
|
||||
}
|
||||
2
gst/src/plugins/autodetect.rs
Normal file
2
gst/src/plugins/autodetect.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod autovideosink;
|
||||
pub use autovideosink::*;
|
||||
30
gst/src/plugins/autodetect/autovideosink.rs
Normal file
30
gst/src/plugins/autodetect/autovideosink.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use crate::*;
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct AutoVideoSink {
|
||||
inner: gstreamer::Element,
|
||||
}
|
||||
|
||||
impl IsElement for AutoVideoSink {
|
||||
fn as_element(&self) -> &Element {
|
||||
unsafe { core::mem::transmute(&self.inner) }
|
||||
}
|
||||
|
||||
fn into_element(self) -> Element {
|
||||
Element { inner: self.inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl Sink for AutoVideoSink {}
|
||||
|
||||
impl AutoVideoSink {
|
||||
pub fn new(name: impl AsRef<str>) -> Result<Self> {
|
||||
use gstreamer::prelude::*;
|
||||
let element = gstreamer::ElementFactory::make("autovideosink")
|
||||
.name(name.as_ref())
|
||||
.build()
|
||||
.change_context(Error)
|
||||
.attach("Failed to create autovideosink element")?;
|
||||
Ok(AutoVideoSink { inner: element })
|
||||
}
|
||||
}
|
||||
2
gst/src/plugins/playback.rs
Normal file
2
gst/src/plugins/playback.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod playbin3;
|
||||
pub use playbin3::*;
|
||||
125
gst/src/plugins/playback/playbin3.rs
Normal file
125
gst/src/plugins/playback/playbin3.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
use crate::*;
|
||||
pub struct Playbin3 {
|
||||
inner: gstreamer::Element,
|
||||
}
|
||||
|
||||
impl Drop for Playbin3 {
|
||||
fn drop(&mut self) {
|
||||
let _ = self
|
||||
.inner
|
||||
.set_state(gstreamer::State::Null)
|
||||
.inspect_err(|e| {
|
||||
tracing::error!("Failed to set playbin3 to Null state on drop: {:?}", e)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Playbin3 {
|
||||
pub fn new(name: impl AsRef<str>) -> Result<Self> {
|
||||
use gstreamer::prelude::*;
|
||||
gstreamer::ElementFactory::make("playbin3")
|
||||
.name(name.as_ref())
|
||||
.build()
|
||||
.map(|element| Playbin3 { inner: element })
|
||||
.change_context(Error)
|
||||
}
|
||||
|
||||
pub fn with_uri(self, uri: impl AsRef<str>) -> Self {
|
||||
use gstreamer::prelude::*;
|
||||
self.inner.set_property("uri", uri.as_ref());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_video_sink(self, video_sink: &impl IsElement) -> Self {
|
||||
use gstreamer::prelude::*;
|
||||
self.inner
|
||||
.set_property("video-sink", &video_sink.as_element().inner);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_text_sink(self, text_sink: &impl IsElement) -> Self {
|
||||
use gstreamer::prelude::*;
|
||||
self.inner
|
||||
.set_property("text-sink", &text_sink.as_element().inner);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_audio_sink(self, audio_sink: &impl IsElement) -> Self {
|
||||
use gstreamer::prelude::*;
|
||||
self.inner
|
||||
.set_property("audio-sink", &audio_sink.as_element().inner);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_volume(&self, volume: f64) {
|
||||
use gstreamer::prelude::*;
|
||||
self.inner.set_property("volume", volume.clamp(1.0, 100.0))
|
||||
}
|
||||
|
||||
pub fn get_volume(&self) -> f64 {
|
||||
use gstreamer::prelude::*;
|
||||
self.inner.property::<f64>("volume")
|
||||
}
|
||||
|
||||
pub fn play(&self) -> Result<()> {
|
||||
use gstreamer::prelude::*;
|
||||
self.inner
|
||||
.set_state(gstreamer::State::Playing)
|
||||
.change_context(Error)
|
||||
.attach("Failed to set playbin3 to Playing state")?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn bus(&self) -> Result<Bus> {
|
||||
let bus = self
|
||||
.inner
|
||||
.bus()
|
||||
.ok_or(Error)
|
||||
.attach("Failed to get bus from playbin3")?;
|
||||
Ok(Bus { bus })
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_playbin3() {
|
||||
use gstreamer::prelude::*;
|
||||
use tracing_subscriber::prelude::*;
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::fmt::layer()
|
||||
.with_thread_ids(true)
|
||||
.with_file(true),
|
||||
)
|
||||
.init();
|
||||
tracing::info!("Linking videoconvert to appsink");
|
||||
gstreamer::init().unwrap();
|
||||
let playbin3 = Playbin3::new("test_playbin3").unwrap().with_uri("https://jellyfin.tsuba.darksailor.dev/Items/6010382cf25273e624d305907010d773/Download?api_key=036c140222464878862231ef66a2bc9c");
|
||||
// let mut video_sink = Bin::new("wgpu_video_sink");
|
||||
//
|
||||
// let video_convert = plugins::videoconvertscale::VideoConvert::new("wgpu_video_convert")
|
||||
// .expect("Create videoconvert");
|
||||
// let appsink = AppSink::new("test_appsink").expect("Create appsink");
|
||||
let appsink = plugins::autodetect::AutoVideoSink::new("test_autodetect_video_sink")
|
||||
.expect("Create autodetect video sink");
|
||||
// video_convert
|
||||
// .link(&appsink)
|
||||
// .expect("Link videoconvert to appsink");
|
||||
//
|
||||
// let sink_pad = video_convert.sink_pad();
|
||||
// let sink_pad = Pad::ghost(&sink_pad).expect("Create ghost pad from videoconvert src");
|
||||
// video_sink
|
||||
// .add(appsink)
|
||||
// .expect("Add appsink to video sink")
|
||||
// .add(video_convert)
|
||||
// .expect("Add videoconvert to video sink")
|
||||
// .add_pad(&sink_pad)
|
||||
// .expect("Add ghost pad to video sink");
|
||||
// sink_pad.activate(true).expect("Activate ghost pad");
|
||||
|
||||
let playbin3 = playbin3.with_video_sink(&appsink);
|
||||
playbin3.play().unwrap();
|
||||
let bus = playbin3.bus().unwrap();
|
||||
for msg in bus.iter_timed(None) {
|
||||
tracing::info!("{:#?}", &msg.view());
|
||||
}
|
||||
// std::thread::sleep(std::time::Duration::from_secs(5));
|
||||
}
|
||||
2
gst/src/plugins/videoconvertscale.rs
Normal file
2
gst/src/plugins/videoconvertscale.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod videoconvert;
|
||||
pub use videoconvert::*;
|
||||
49
gst/src/plugins/videoconvertscale/videoconvert.rs
Normal file
49
gst/src/plugins/videoconvertscale/videoconvert.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use crate::*;
|
||||
#[doc(inline)]
|
||||
pub use gstreamer_video::VideoFormat;
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct VideoConvert {
|
||||
inner: gstreamer::Element,
|
||||
}
|
||||
|
||||
impl IsElement for VideoConvert {
|
||||
fn as_element(&self) -> &Element {
|
||||
unsafe { core::mem::transmute(&self.inner) }
|
||||
}
|
||||
|
||||
fn into_element(self) -> Element {
|
||||
Element { inner: self.inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl Sink for VideoConvert {}
|
||||
impl Source for VideoConvert {}
|
||||
|
||||
impl VideoConvert {
|
||||
pub fn new(name: impl AsRef<str>) -> Result<Self> {
|
||||
use gstreamer::prelude::*;
|
||||
let element = gstreamer::ElementFactory::make("videoconvert")
|
||||
.name(name.as_ref())
|
||||
.build()
|
||||
.change_context(Error)
|
||||
.attach("Failed to create videoconvert element")?;
|
||||
Ok(VideoConvert { inner: element })
|
||||
}
|
||||
|
||||
// pub fn with_caps(mut self, caps: &gstreamer::Caps) -> Self {
|
||||
// use gstreamer::prelude::*;
|
||||
// self.inner.set_property("caps", caps);
|
||||
// self
|
||||
// }
|
||||
pub fn with_output_format(self, format: VideoFormat) -> Result<Self> {
|
||||
use gstreamer::prelude::*;
|
||||
let caps = Caps::builder(CapsType::Video)
|
||||
.field("format", format.to_str())
|
||||
.build();
|
||||
self.inner.set_property("caps", &caps.inner);
|
||||
// .change_context(Error)
|
||||
// .attach("Failed to set output format on videoconvert")?;
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user