Replacing videotestsrc for filesrc in gtk4paintablesink example

Hello, I have been trying to make use the sample of the gtk4paintablesink to play a local video file, this is what I have to far:

use gst::prelude::*;

use gtk::prelude::*;
use gtk::{gdk, gio, glib};

use std::cell::RefCell;

fn create_ui(app: &gtk::Application) {
    let _cwd = std::env::current_dir().unwrap();
    let cwd = _cwd.to_str().unwrap();

    let file_path = format!("{}{}", cwd, "/duck.mp4");

    println!("playing {}", file_path);
    let pipeline = gst::Pipeline::new();

    let src = gst::ElementFactory::make("filesrc").build().unwrap();
    src.set_property("location", &file_path);
    let decode = gst::ElementFactory::make("decodebin").build().unwrap();
    let convert = gst::ElementFactory::make("videoconvert").build().unwrap();

    let gtksink = gst::ElementFactory::make("gtk4paintablesink")
        .build()
        .unwrap();

    let paintable = gtksink.property::<gdk::Paintable>("paintable");

    // TODO: future plans to provide a bin-like element that works with less setup
    let sink = if paintable
        .property::<Option<gdk::GLContext>>("gl-context")
        .is_some()
    {
        // let src = gst::ElementFactory::make("gltestsrc").build().unwrap();
        println!("using glsink");
        let sink = gst::ElementFactory::make("glsinkbin")
            .property("sink", &gtksink)
            .build()
            .unwrap();
        sink
    } else {
        // let src: gst::Element = gst::ElementFactory::make("videotestsrc").build().unwrap();
        println!("using videoconvert");
        let sink = gst::Bin::default();

        sink.add(&convert).unwrap();
        sink.add(&gtksink).unwrap();
        convert.link(&gtksink).unwrap();

        sink.add_pad(&gst::GhostPad::with_target(&convert.static_pad("sink").unwrap()).unwrap())
            .unwrap();

        sink.upcast()
    };

    pipeline.add_many([&src, &decode, &sink]).unwrap();
    src.link(&decode).unwrap();

    // decode.link(&gtksink).unwrap();

    let window = gtk::ApplicationWindow::new(app);
    window.set_default_size(640, 480);

    let gst_widget = gstgtk4::RenderWidget::new(&gtksink);

    let overlay = gtk::Overlay::new();

    overlay.add_overlay(&gst_widget);

    window.set_child(Some(&overlay));
    window.present();
    // window.fullscreen();

    app.add_window(&window);

    let bus: gst::Bus = pipeline.bus().unwrap();

    pipeline
        .set_state(gst::State::Playing)
        .expect("Unable to set the pipeline to the `Playing` state");

    let app_weak = app.downgrade();
    let bus_watch = bus
        .add_watch_local(move |_, msg| {
            use gst::MessageView;

            let Some(app) = app_weak.upgrade() else {
                return glib::ControlFlow::Break;
            };

            match msg.view() {
                MessageView::Eos(..) => app.quit(),
                MessageView::Error(err) => {
                    println!(
                        "Error from {:?}: {} ({:?})",
                        err.src().map(|s| s.path_string()),
                        err.error(),
                        err.debug()
                    );
                    app.quit();
                }
                _ => (),
            };

            glib::ControlFlow::Continue
        })
        .expect("Failed to add bus watch");

    let pipeline = RefCell::new(Some(pipeline));
    let bus_watch = RefCell::new(Some(bus_watch));
    app.connect_shutdown(move |_| {
        window.close();

        drop(bus_watch.borrow_mut().take());
        if let Some(pipeline) = pipeline.borrow_mut().take() {
            pipeline
                .set_state(gst::State::Null)
                .expect("Unable to set the pipeline to the `Null` state");
        }
    });
}

fn main() -> glib::ExitCode {
    gst::init().unwrap();

    gstgtk4::plugin_register_static().expect("Failed to register gstgtk4 plugin");

    let app = gtk::Application::new(None::<&str>, gio::ApplicationFlags::FLAGS_NONE);

    app.connect_activate(create_ui);
    let res = app.run();

    unsafe {
        gst::deinit();
    }

    res
}

it runs but fails with these errors using GST_DEBUG=3

0:00:00.975147544 51628 0x7d57e80010a0 FIXME                qtdemux qtdemux_types.c:268:qtdemux_type_get: unknown QuickTime node type iods
0:00:00.979885445 51628 0x7d57e80010a0 WARN               h264parse gsth264parse.c:2201:gst_h264_parse_update_src_caps:<h264parse0> VUI framerate 30000.0 exceeds allowed maximum 64.0
0:00:01.038495649 51628 0x7d57e8001380 WARN               h264parse gsth264parse.c:2201:gst_h264_parse_update_src_caps:<h264parse0> VUI framerate 30000.0 exceeds allowed maximum 64.0
0:00:01.040271549 51628 0x7d57e8001380 WARN               h264parse gsth264parse.c:2201:gst_h264_parse_update_src_caps:<h264parse0> VUI framerate 30000.0 exceeds allowed maximum 64.0
0:00:01.178174540 51628 0x7d57e80010a0 WARN                 qtdemux qtdemux.c:7439:gst_qtdemux_loop:<qtdemux0> error: Internal data stream error.
0:00:01.178203154 51628 0x7d57e80010a0 WARN                 qtdemux qtdemux.c:7439:gst_qtdemux_loop:<qtdemux0> error: streaming stopped, reason not-linked (-1)
Error from Some("/GstPipeline:pipeline0/GstDecodeBin:decodebin0/GstQTDemux:qtdemux0"): Internal data stream error. (Some("../gst/isomp4/qtdemux.c(7439): gst_qtdemux_loop (): /GstPipeline:pipeline0/GstDecodeBin:decodebin0/GstQTDemux:qtdemux0:\nstreaming stopped, reason not-linked (-1)"))

oh I got it working but using playbin

use gst::prelude::*;

use gtk::prelude::*;
use gtk::{gdk, gio, glib};

use std::cell::RefCell;

fn create_ui(app: &gtk::Application) {
    let _cwd = std::env::current_dir().unwrap();
    let cwd = _cwd.to_str().unwrap();

    let uri = format!("file://{}/{}", cwd, "/duck.mp4");

    println!("playing {}", uri);
    let playbin = gst::ElementFactory::make("playbin")
        .property("uri", uri)
        .build()
        .unwrap();

    let gtksink = gst::ElementFactory::make("gtk4paintablesink")
        .build()
        .unwrap();

    let paintable = gtksink.property::<gdk::Paintable>("paintable");

    // TODO: future plans to provide a bin-like element that works with less setup
    let sink = if paintable
        .property::<Option<gdk::GLContext>>("gl-context")
        .is_some()
    {
        println!("using glsink");
        let sink = gst::ElementFactory::make("glsinkbin")
            .property("sink", &gtksink)
            .build()
            .unwrap();
        sink
    } else {
        println!("using videoconvert");
        let sink = gst::Bin::default();
        let convert = gst::ElementFactory::make("videoconvert").build().unwrap();

        sink.add(&convert).unwrap();
        sink.add(&gtksink).unwrap();
        convert.link(&gtksink).unwrap();

        sink.add_pad(&gst::GhostPad::with_target(&convert.static_pad("sink").unwrap()).unwrap())
            .unwrap();

        sink.upcast()
    };

    playbin.set_property("video-sink", &gtksink);

    playbin
        .add_pad(&gst::GhostPad::with_target(&sink.static_pad("sink").unwrap()).unwrap())
        .unwrap();

    // playbin.link(&sink).unwrap();

    let window = gtk::ApplicationWindow::new(app);
    window.set_default_size(640, 480);

    let gst_widget = gstgtk4::RenderWidget::new(&gtksink);

    let overlay = gtk::Overlay::new();

    overlay.add_overlay(&gst_widget);

    window.set_child(Some(&overlay));
    window.present();
    // window.fullscreen();

    app.add_window(&window);

    let bus: gst::Bus = playbin.bus().unwrap();

    playbin
        .set_state(gst::State::Playing)
        .expect("Unable to set the playbin to the `Playing` state");

    let app_weak = app.downgrade();
    let bus_watch = bus
        .add_watch_local(move |_, msg| {
            use gst::MessageView;

            let Some(app) = app_weak.upgrade() else {
                return glib::ControlFlow::Break;
            };

            match msg.view() {
                MessageView::Eos(..) => app.quit(),
                MessageView::Error(err) => {
                    println!(
                        "Error from {:?}: {} ({:?})",
                        err.src().map(|s| s.path_string()),
                        err.error(),
                        err.debug()
                    );
                    app.quit();
                }
                _ => (),
            };

            glib::ControlFlow::Continue
        })
        .expect("Failed to add bus watch");

    let playbin = RefCell::new(Some(playbin));
    let bus_watch = RefCell::new(Some(bus_watch));
    app.connect_shutdown(move |_| {
        window.close();

        drop(bus_watch.borrow_mut().take());
        if let Some(playbin) = playbin.borrow_mut().take() {
            playbin
                .set_state(gst::State::Null)
                .expect("Unable to set the playbin to the `Null` state");
        }
    });
}

fn main() -> glib::ExitCode {
    gst::init().unwrap();

    gstgtk4::plugin_register_static().expect("Failed to register gstgtk4 plugin");

    let app = gtk::Application::new(None::<&str>, gio::ApplicationFlags::FLAGS_NONE);

    app.connect_activate(create_ui);
    let res = app.run();

    unsafe {
        gst::deinit();
    }

    res
}

was missing the playbin.set_property("video-sink", &gtksink); to make the playbin use the sink, found here

The problem in your first code with decodebin is that you’re not linking decodebin to anything further downstream. See this example for how to do so.