1 // Copyright (C) 2019 Sebastian Dröge <sebastian@centricular.com>
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Library General Public
5 // License as published by the Free Software Foundation; either
6 // version 2 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Library General Public License for more details.
12 //
13 // You should have received a copy of the GNU Library General Public
14 // License along with this library; if not, write to the
15 // Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
16 // Boston, MA 02110-1335, USA.
17 
18 use gio::prelude::*;
19 
20 use gst::glib;
21 use gst::prelude::*;
22 
23 use gtk::prelude::*;
24 
25 use std::cell::RefCell;
26 
27 const MAIN_PIPELINE: &str = "videotestsrc is-live=true pattern=ball";
28 const FALLBACK_PIPELINE: &str = "videotestsrc is-live=true pattern=snow";
29 
30 //const MAIN_PIPELINE: &str = "videotestsrc is-live=true pattern=ball ! x264enc tune=zerolatency";
31 //const FALLBACK_PIPELINE: &str = "videotestsrc is-live=true pattern=snow ! x264enc tune=zerolatency";
32 
create_pipeline() -> (gst::Pipeline, gst::Pad, gst::Element, gtk::Widget)33 fn create_pipeline() -> (gst::Pipeline, gst::Pad, gst::Element, gtk::Widget) {
34     let pipeline = gst::Pipeline::new(None);
35 
36     let video_src = gst::parse_bin_from_description(MAIN_PIPELINE, true)
37         .unwrap()
38         .upcast();
39     let fallback_video_src = gst::parse_bin_from_description(FALLBACK_PIPELINE, true)
40         .unwrap()
41         .upcast();
42 
43     let fallbackswitch = gst::ElementFactory::make("fallbackswitch", None).unwrap();
44     fallbackswitch
45         .set_property("timeout", &gst::ClockTime::SECOND)
46         .unwrap();
47 
48     let decodebin = gst::ElementFactory::make("decodebin", None).unwrap();
49     let videoconvert = gst::ElementFactory::make("videoconvert", None).unwrap();
50 
51     let videoconvert_clone = videoconvert.clone();
52     decodebin.connect_pad_added(move |_, pad| {
53         let caps = pad.current_caps().unwrap();
54         let s = caps.structure(0).unwrap();
55 
56         let sinkpad = videoconvert_clone.static_pad("sink").unwrap();
57 
58         if s.name() == "video/x-raw" && !sinkpad.is_linked() {
59             pad.link(&sinkpad).unwrap();
60         }
61     });
62 
63     let (video_sink, video_widget) =
64         //if let Some(gtkglsink) = gst::ElementFactory::make("gtkglsink", None) {
65         //    let glsinkbin = gst::ElementFactory::make("glsinkbin", None).unwrap();
66         //    glsinkbin.set_property("sink", &gtkglsink).unwrap();
67 
68         //    let widget = gtkglsink.get_property("widget").unwrap();
69         //    (glsinkbin, widget.get::<gtk::Widget>().unwrap().unwrap())
70         //} else
71         {
72             let sink = gst::ElementFactory::make("gtksink", None).unwrap();
73             let widget = sink.property("widget").unwrap();
74             (sink, widget.get::<gtk::Widget>().unwrap())
75         };
76 
77     pipeline
78         .add_many(&[
79             &video_src,
80             &fallback_video_src,
81             &fallbackswitch,
82             &decodebin,
83             &videoconvert,
84             &video_sink,
85         ])
86         .unwrap();
87 
88     video_src
89         .link_pads(Some("src"), &fallbackswitch, Some("sink"))
90         .unwrap();
91     fallback_video_src
92         .link_pads(Some("src"), &fallbackswitch, Some("fallback_sink"))
93         .unwrap();
94     fallbackswitch
95         .link_pads(Some("src"), &decodebin, Some("sink"))
96         .unwrap();
97     videoconvert
98         .link_pads(Some("src"), &video_sink, Some("sink"))
99         .unwrap();
100 
101     (
102         pipeline,
103         video_src.static_pad("src").unwrap(),
104         video_sink,
105         video_widget,
106     )
107 }
108 
create_ui(app: &gtk::Application)109 fn create_ui(app: &gtk::Application) {
110     let (pipeline, video_src_pad, video_sink, video_widget) = create_pipeline();
111 
112     let window = gtk::Window::new(gtk::WindowType::Toplevel);
113     window.set_default_size(320, 240);
114     let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
115     vbox.pack_start(&video_widget, true, true, 0);
116 
117     let position_label = gtk::Label::new(Some("Position: 00:00:00"));
118     vbox.pack_start(&position_label, true, true, 5);
119 
120     let drop_button = gtk::ToggleButton::with_label("Drop Signal");
121     vbox.pack_start(&drop_button, true, true, 5);
122 
123     window.add(&vbox);
124     window.show_all();
125 
126     app.add_window(&window);
127 
128     let video_sink_weak = video_sink.downgrade();
129     let timeout_id = glib::timeout_add_local(std::time::Duration::from_millis(100), move || {
130         let video_sink = match video_sink_weak.upgrade() {
131             Some(video_sink) => video_sink,
132             None => return glib::Continue(true),
133         };
134 
135         let position = video_sink
136             .query_position::<gst::ClockTime>()
137             .unwrap_or(gst::ClockTime::ZERO);
138         position_label.set_text(&format!("Position: {:.1}", position));
139 
140         glib::Continue(true)
141     });
142 
143     let video_src_pad_weak = video_src_pad.downgrade();
144     let drop_id = RefCell::new(None);
145     drop_button.connect_toggled(move |drop_button| {
146         let video_src_pad = match video_src_pad_weak.upgrade() {
147             Some(video_src_pad) => video_src_pad,
148             None => return,
149         };
150 
151         let drop = drop_button.is_active();
152         if drop {
153             let mut drop_id = drop_id.borrow_mut();
154             if drop_id.is_none() {
155                 *drop_id = video_src_pad
156                     .add_probe(gst::PadProbeType::BUFFER, |_, _| gst::PadProbeReturn::Drop);
157             }
158         } else if let Some(drop_id) = drop_id.borrow_mut().take() {
159             video_src_pad.remove_probe(drop_id);
160         }
161     });
162 
163     let app_weak = app.downgrade();
164     window.connect_delete_event(move |_, _| {
165         let app = match app_weak.upgrade() {
166             Some(app) => app,
167             None => return Inhibit(false),
168         };
169 
170         app.quit();
171         Inhibit(false)
172     });
173 
174     let bus = pipeline.bus().unwrap();
175     let app_weak = app.downgrade();
176     bus.add_watch_local(move |_, msg| {
177         use gst::MessageView;
178 
179         let app = match app_weak.upgrade() {
180             Some(app) => app,
181             None => return glib::Continue(false),
182         };
183 
184         match msg.view() {
185             MessageView::Eos(..) => app.quit(),
186             MessageView::Error(err) => {
187                 println!(
188                     "Error from {:?}: {} ({:?})",
189                     msg.src().map(|s| s.path_string()),
190                     err.error(),
191                     err.debug()
192                 );
193                 app.quit();
194             }
195             _ => (),
196         };
197 
198         glib::Continue(true)
199     })
200     .expect("Failed to add bus watch");
201 
202     pipeline.set_state(gst::State::Playing).unwrap();
203 
204     // Pipeline reference is owned by the closure below, so will be
205     // destroyed once the app is destroyed
206     let timeout_id = RefCell::new(Some(timeout_id));
207     app.connect_shutdown(move |_| {
208         pipeline.set_state(gst::State::Null).unwrap();
209 
210         bus.remove_watch().unwrap();
211 
212         if let Some(timeout_id) = timeout_id.borrow_mut().take() {
213             glib::source_remove(timeout_id);
214         }
215     });
216 }
217 
main()218 fn main() {
219     gst::init().unwrap();
220     gtk::init().unwrap();
221 
222     gstfallbackswitch::plugin_register_static().expect("Failed to register fallbackswitch plugin");
223 
224     let app = gtk::Application::new(None, gio::ApplicationFlags::FLAGS_NONE);
225 
226     app.connect_activate(create_ui);
227     app.run();
228 }
229