2026-04-03 00:09:03 +02:00
|
|
|
use iced_video_player::gst::prelude::*;
|
|
|
|
|
use iced_video_player::{Video, gst, gst_app, gst_pbutils};
|
2025-11-08 15:25:39 -05:00
|
|
|
|
2026-04-03 00:09:03 +02:00
|
|
|
use cosmic::action;
|
|
|
|
|
use cosmic::app::Task;
|
2025-11-08 15:25:39 -05:00
|
|
|
|
2026-01-16 13:41:07 -07:00
|
|
|
#[derive(Debug, Default)]
|
|
|
|
|
pub struct VideoSettings {
|
|
|
|
|
pub mute: bool,
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-09 21:33:47 -05:00
|
|
|
pub fn new_video(
|
|
|
|
|
url: &url::Url,
|
2026-01-16 13:41:07 -07:00
|
|
|
settings: VideoSettings,
|
2026-03-20 18:06:17 -04:00
|
|
|
) -> Result<Video, cosmic::Task<cosmic::Action<super::Message>>> {
|
2025-11-08 15:25:39 -05:00
|
|
|
//TODO: this code came from iced_video_player::Video::new and has been modified to stop the pipeline on error
|
|
|
|
|
//TODO: remove unwraps and enable playback of files with only audio.
|
|
|
|
|
gst::init().unwrap();
|
2026-07-09 11:51:07 +02:00
|
|
|
disable_broken_hardware_decoders();
|
2025-11-08 15:25:39 -05:00
|
|
|
|
|
|
|
|
let pipeline = format!(
|
2026-07-09 11:51:07 +02:00
|
|
|
"playbin uri=\"{}\"{}{} video-sink=\"videoscale ! videoconvert ! videoflip method=automatic ! appsink name=iced_video drop=true caps=video/x-raw,format=NV12,pixel-aspect-ratio=1/1\"",
|
2026-01-16 13:41:07 -07:00
|
|
|
url.as_str(),
|
2026-07-09 11:51:07 +02:00
|
|
|
if settings.mute { " mute=true" } else { "" },
|
|
|
|
|
if settings.mute {
|
|
|
|
|
" audio-sink=fakesink"
|
|
|
|
|
} else {
|
|
|
|
|
""
|
|
|
|
|
}
|
2025-11-08 15:25:39 -05:00
|
|
|
);
|
|
|
|
|
let pipeline = gst::parse::launch(pipeline.as_ref())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.downcast::<gst::Pipeline>()
|
|
|
|
|
.map_err(|_| iced_video_player::Error::Cast)
|
|
|
|
|
.unwrap();
|
2026-06-01 14:22:27 +02:00
|
|
|
if let Ok(scaletempo) = gst::ElementFactory::make("scaletempo").build() {
|
|
|
|
|
pipeline.set_property("audio-filter", &scaletempo);
|
|
|
|
|
} else {
|
|
|
|
|
log::warn!("scaletempo element not available; speed changes will affect pitch");
|
|
|
|
|
}
|
2025-11-08 15:25:39 -05:00
|
|
|
pipeline.connect("element-setup", false, |vals| {
|
|
|
|
|
let Ok(elem) = vals[1].get::<gst::Element>() else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
2026-04-03 00:09:03 +02:00
|
|
|
if let Some(factory) = elem.factory()
|
|
|
|
|
&& factory.name() == "souphttpsrc"
|
|
|
|
|
{
|
|
|
|
|
elem.set_property(
|
|
|
|
|
"user-agent",
|
|
|
|
|
"Mozilla/5.0 (X11; Linux x86_64; rv:142.0) Gecko/20100101 Firefox/142.0",
|
|
|
|
|
);
|
2025-11-08 15:25:39 -05:00
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
});
|
|
|
|
|
let video_sink: gst::Element = pipeline.property("video-sink");
|
|
|
|
|
let pad = video_sink.pads().first().cloned().unwrap();
|
|
|
|
|
let pad = pad.dynamic_cast::<gst::GhostPad>().unwrap();
|
|
|
|
|
let bin = pad
|
|
|
|
|
.parent_element()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.downcast::<gst::Bin>()
|
|
|
|
|
.unwrap();
|
|
|
|
|
let video_sink = bin.by_name("iced_video").unwrap();
|
|
|
|
|
let video_sink = video_sink.downcast::<gst_app::AppSink>().unwrap();
|
|
|
|
|
|
|
|
|
|
match Video::from_gst_pipeline(pipeline.clone(), video_sink, None) {
|
|
|
|
|
Ok(ok) => Ok(ok),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
log::warn!("failed to open {}: {err}", url);
|
|
|
|
|
// Handle codecs required before the file can play
|
|
|
|
|
let mut commands = Vec::new();
|
|
|
|
|
while let Some(msg) = pipeline
|
|
|
|
|
.bus()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.pop_filtered(&[gst::MessageType::Element])
|
|
|
|
|
{
|
2026-04-03 00:09:03 +02:00
|
|
|
if let gst::MessageView::Element(element) = msg.view()
|
|
|
|
|
&& gst_pbutils::MissingPluginMessage::is(element)
|
|
|
|
|
{
|
|
|
|
|
commands.push(Task::perform(
|
|
|
|
|
async { action::app(super::Message::MissingPlugin(msg)) },
|
|
|
|
|
|x| x,
|
|
|
|
|
));
|
|
|
|
|
// Do one codec install at a time
|
|
|
|
|
break;
|
2025-11-08 15:25:39 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pipeline.set_state(gst::State::Null).unwrap();
|
2026-03-20 18:06:17 -04:00
|
|
|
Err(Task::batch(commands))
|
2025-11-08 15:25:39 -05:00
|
|
|
}
|
|
|
|
|
}
|
2025-11-09 21:33:47 -05:00
|
|
|
}
|
2026-07-09 11:51:07 +02:00
|
|
|
|
|
|
|
|
fn disable_broken_hardware_decoders() {
|
|
|
|
|
let registry = gst::Registry::get();
|
|
|
|
|
for decoder in [
|
|
|
|
|
"nvav1dec",
|
|
|
|
|
"nvh264dec",
|
|
|
|
|
"nvh265dec",
|
|
|
|
|
"nvjpegdec",
|
|
|
|
|
"nvmpeg2videodec",
|
|
|
|
|
"nvmpeg4videodec",
|
|
|
|
|
"nvmpegvideodec",
|
|
|
|
|
"nvvp8dec",
|
|
|
|
|
"nvvp9dec",
|
|
|
|
|
] {
|
|
|
|
|
if let Some(feature) = registry.lookup_feature(decoder) {
|
|
|
|
|
let rank = feature.rank();
|
|
|
|
|
feature.set_rank(gst::Rank::NONE);
|
|
|
|
|
log::debug!("disabled GStreamer decoder {decoder} with rank {rank:?}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|